| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/renderer/chrome_render_observer.h" | |
| 6 | |
| 7 #include "base/command_line.h" | |
| 8 #include "base/message_loop.h" | |
| 9 #include "base/metrics/histogram.h" | |
| 10 #include "chrome/common/chrome_switches.h" | |
| 11 #include "chrome/common/render_messages.h" | |
| 12 #include "chrome/common/thumbnail_score.h" | |
| 13 #include "chrome/renderer/safe_browsing/phishing_classifier_delegate.h" | |
| 14 #include "chrome/renderer/translate_helper.h" | |
| 15 #include "content/renderer/content_renderer_client.h" | |
| 16 #include "content/renderer/render_view.h" | |
| 17 #include "skia/ext/bitmap_platform_device.h" | |
| 18 #include "skia/ext/image_operations.h" | |
| 19 #include "skia/ext/platform_canvas.h" | |
| 20 #include "third_party/WebKit/Source/WebKit/chromium/public/WebDataSource.h" | |
| 21 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" | |
| 22 #include "third_party/WebKit/Source/WebKit/chromium/public/WebRect.h" | |
| 23 #include "third_party/WebKit/Source/WebKit/chromium/public/WebSize.h" | |
| 24 #include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" | |
| 25 #include "ui/gfx/color_utils.h" | |
| 26 #include "ui/gfx/skbitmap_operations.h" | |
| 27 #include "webkit/glue/webkit_glue.h" | |
| 28 | |
| 29 using WebKit::WebDataSource; | |
| 30 using WebKit::WebFrame; | |
| 31 using WebKit::WebRect; | |
| 32 using WebKit::WebSize; | |
| 33 using WebKit::WebView; | |
| 34 | |
| 35 // Delay in milliseconds that we'll wait before capturing the page contents | |
| 36 // and thumbnail. | |
| 37 static const int kDelayForCaptureMs = 500; | |
| 38 | |
| 39 // Typically, we capture the page data once the page is loaded. | |
| 40 // Sometimes, the page never finishes to load, preventing the page capture | |
| 41 // To workaround this problem, we always perform a capture after the following | |
| 42 // delay. | |
| 43 static const int kDelayForForcedCaptureMs = 6000; | |
| 44 | |
| 45 // define to write the time necessary for thumbnail/DOM text retrieval, | |
| 46 // respectively, into the system debug log | |
| 47 // #define TIME_TEXT_RETRIEVAL | |
| 48 | |
| 49 // maximum number of characters in the document to index, any text beyond this | |
| 50 // point will be clipped | |
| 51 static const size_t kMaxIndexChars = 65535; | |
| 52 | |
| 53 // Size of the thumbnails that we'll generate | |
| 54 static const int kThumbnailWidth = 212; | |
| 55 static const int kThumbnailHeight = 132; | |
| 56 | |
| 57 static bool PaintViewIntoCanvas(WebView* view, | |
| 58 skia::PlatformCanvas& canvas) { | |
| 59 view->layout(); | |
| 60 const WebSize& size = view->size(); | |
| 61 | |
| 62 if (!canvas.initialize(size.width, size.height, true)) | |
| 63 return false; | |
| 64 | |
| 65 view->paint(webkit_glue::ToWebCanvas(&canvas), | |
| 66 WebRect(0, 0, size.width, size.height)); | |
| 67 // TODO: Add a way to snapshot the whole page, not just the currently | |
| 68 // visible part. | |
| 69 | |
| 70 return true; | |
| 71 } | |
| 72 | |
| 73 // Calculates how "boring" a thumbnail is. The boring score is the | |
| 74 // 0,1 ranged percentage of pixels that are the most common | |
| 75 // luma. Higher boring scores indicate that a higher percentage of a | |
| 76 // bitmap are all the same brightness. | |
| 77 static double CalculateBoringScore(SkBitmap* bitmap) { | |
| 78 int histogram[256] = {0}; | |
| 79 color_utils::BuildLumaHistogram(bitmap, histogram); | |
| 80 | |
| 81 int color_count = *std::max_element(histogram, histogram + 256); | |
| 82 int pixel_count = bitmap->width() * bitmap->height(); | |
| 83 return static_cast<double>(color_count) / pixel_count; | |
| 84 } | |
| 85 | |
| 86 ChromeRenderObserver::ChromeRenderObserver( | |
| 87 RenderView* render_view, | |
| 88 TranslateHelper* translate_helper, | |
| 89 safe_browsing::PhishingClassifierDelegate* phishing_classifier) | |
| 90 : RenderViewObserver(render_view), | |
| 91 translate_helper_(translate_helper), | |
| 92 phishing_classifier_(phishing_classifier), | |
| 93 last_indexed_page_id_(-1), | |
| 94 ALLOW_THIS_IN_INITIALIZER_LIST(page_info_method_factory_(this)) { | |
| 95 } | |
| 96 | |
| 97 ChromeRenderObserver::~ChromeRenderObserver() { | |
| 98 } | |
| 99 | |
| 100 bool ChromeRenderObserver::OnMessageReceived(const IPC::Message& message) { | |
| 101 bool handled = true; | |
| 102 IPC_BEGIN_MESSAGE_MAP(ChromeRenderObserver, message) | |
| 103 IPC_MESSAGE_HANDLER(ViewMsg_CaptureSnapshot, OnCaptureSnapshot) | |
| 104 IPC_MESSAGE_UNHANDLED(handled = false) | |
| 105 IPC_END_MESSAGE_MAP() | |
| 106 return handled; | |
| 107 } | |
| 108 | |
| 109 void ChromeRenderObserver::OnCaptureSnapshot() { | |
| 110 SkBitmap snapshot; | |
| 111 bool error = false; | |
| 112 | |
| 113 WebFrame* main_frame = render_view()->webview()->mainFrame(); | |
| 114 if (!main_frame) | |
| 115 error = true; | |
| 116 | |
| 117 if (!error && !CaptureSnapshot(render_view()->webview(), &snapshot)) | |
| 118 error = true; | |
| 119 | |
| 120 DCHECK(error == snapshot.empty()) << | |
| 121 "Snapshot should be empty on error, non-empty otherwise."; | |
| 122 | |
| 123 // Send the snapshot to the browser process. | |
| 124 Send(new ViewHostMsg_Snapshot(routing_id(), snapshot)); | |
| 125 } | |
| 126 | |
| 127 void ChromeRenderObserver::DidStopLoading() { | |
| 128 MessageLoop::current()->PostDelayedTask( | |
| 129 FROM_HERE, | |
| 130 page_info_method_factory_.NewRunnableMethod( | |
| 131 &ChromeRenderObserver::CapturePageInfo, render_view()->page_id(), | |
| 132 false), | |
| 133 render_view()->content_state_immediately() ? 0 : kDelayForCaptureMs); | |
| 134 } | |
| 135 | |
| 136 void ChromeRenderObserver::DidCommitProvisionalLoad(WebFrame* frame, | |
| 137 bool is_new_navigation) { | |
| 138 if (!is_new_navigation) | |
| 139 return; | |
| 140 | |
| 141 MessageLoop::current()->PostDelayedTask( | |
| 142 FROM_HERE, | |
| 143 page_info_method_factory_.NewRunnableMethod( | |
| 144 &ChromeRenderObserver::CapturePageInfo, render_view()->page_id(), | |
| 145 true), | |
| 146 kDelayForForcedCaptureMs); | |
| 147 } | |
| 148 | |
| 149 void ChromeRenderObserver::CapturePageInfo(int load_id, | |
| 150 bool preliminary_capture) { | |
| 151 if (load_id != render_view()->page_id()) | |
| 152 return; // This capture call is no longer relevant due to navigation. | |
| 153 | |
| 154 if (load_id == last_indexed_page_id_) | |
| 155 return; // we already indexed this page | |
| 156 | |
| 157 if (!render_view()->webview()) | |
| 158 return; | |
| 159 | |
| 160 WebFrame* main_frame = render_view()->webview()->mainFrame(); | |
| 161 if (!main_frame) | |
| 162 return; | |
| 163 | |
| 164 // Don't index/capture pages that are in view source mode. | |
| 165 if (main_frame->isViewSourceModeEnabled()) | |
| 166 return; | |
| 167 | |
| 168 // Don't index/capture pages that failed to load. This only checks the top | |
| 169 // level frame so the thumbnail may contain a frame that failed to load. | |
| 170 WebDataSource* ds = main_frame->dataSource(); | |
| 171 if (ds && ds->hasUnreachableURL()) | |
| 172 return; | |
| 173 | |
| 174 if (!preliminary_capture) | |
| 175 last_indexed_page_id_ = load_id; | |
| 176 | |
| 177 // Get the URL for this page. | |
| 178 GURL url(main_frame->url()); | |
| 179 if (url.is_empty()) | |
| 180 return; | |
| 181 | |
| 182 // Retrieve the frame's full text. | |
| 183 string16 contents; | |
| 184 CaptureText(main_frame, &contents); | |
| 185 if (contents.size()) { | |
| 186 if (translate_helper_) | |
| 187 translate_helper_->PageCaptured(contents); | |
| 188 // Send the text to the browser for indexing (the browser might decide not | |
| 189 // to index, if the URL is HTTPS for instance) and language discovery. | |
| 190 Send(new ViewHostMsg_PageContents(routing_id(), url, load_id, contents)); | |
| 191 } | |
| 192 | |
| 193 // Generate the thumbnail here if the in-browser thumbnailing isn't | |
| 194 // enabled. TODO(satorux): Remove this and related code once | |
| 195 // crbug.com/65936 is complete. | |
| 196 if (!CommandLine::ForCurrentProcess()->HasSwitch( | |
| 197 switches::kEnableInBrowserThumbnailing)) { | |
| 198 CaptureThumbnail(); | |
| 199 } | |
| 200 | |
| 201 // Will swap out the string. | |
| 202 if (phishing_classifier_) | |
| 203 phishing_classifier_->PageCaptured(&contents, preliminary_capture); | |
| 204 } | |
| 205 | |
| 206 void ChromeRenderObserver::CaptureText(WebFrame* frame, string16* contents) { | |
| 207 contents->clear(); | |
| 208 if (!frame) | |
| 209 return; | |
| 210 | |
| 211 #ifdef TIME_TEXT_RETRIEVAL | |
| 212 double begin = time_util::GetHighResolutionTimeNow(); | |
| 213 #endif | |
| 214 | |
| 215 // get the contents of the frame | |
| 216 *contents = frame->contentAsText(kMaxIndexChars); | |
| 217 | |
| 218 #ifdef TIME_TEXT_RETRIEVAL | |
| 219 double end = time_util::GetHighResolutionTimeNow(); | |
| 220 char buf[128]; | |
| 221 sprintf_s(buf, "%d chars retrieved for indexing in %gms\n", | |
| 222 contents.size(), (end - begin)*1000); | |
| 223 OutputDebugStringA(buf); | |
| 224 #endif | |
| 225 | |
| 226 // When the contents are clipped to the maximum, we don't want to have a | |
| 227 // partial word indexed at the end that might have been clipped. Therefore, | |
| 228 // terminate the string at the last space to ensure no words are clipped. | |
| 229 if (contents->size() == kMaxIndexChars) { | |
| 230 size_t last_space_index = contents->find_last_of(kWhitespaceUTF16); | |
| 231 if (last_space_index == std::wstring::npos) | |
| 232 return; // don't index if we got a huge block of text with no spaces | |
| 233 contents->resize(last_space_index); | |
| 234 } | |
| 235 } | |
| 236 | |
| 237 void ChromeRenderObserver::CaptureThumbnail() { | |
| 238 WebFrame* main_frame = render_view()->webview()->mainFrame(); | |
| 239 if (!main_frame) | |
| 240 return; | |
| 241 | |
| 242 // get the URL for this page | |
| 243 GURL url(main_frame->url()); | |
| 244 if (url.is_empty()) | |
| 245 return; | |
| 246 | |
| 247 if (render_view()->size().IsEmpty()) | |
| 248 return; // Don't create an empty thumbnail! | |
| 249 | |
| 250 ThumbnailScore score; | |
| 251 SkBitmap thumbnail; | |
| 252 if (!CaptureFrameThumbnail(render_view()->webview(), kThumbnailWidth, | |
| 253 kThumbnailHeight, &thumbnail, &score)) | |
| 254 return; | |
| 255 | |
| 256 // send the thumbnail message to the browser process | |
| 257 Send(new ViewHostMsg_Thumbnail(routing_id(), url, score, thumbnail)); | |
| 258 } | |
| 259 | |
| 260 bool ChromeRenderObserver::CaptureFrameThumbnail(WebView* view, | |
| 261 int w, | |
| 262 int h, | |
| 263 SkBitmap* thumbnail, | |
| 264 ThumbnailScore* score) { | |
| 265 base::TimeTicks beginning_time = base::TimeTicks::Now(); | |
| 266 | |
| 267 skia::PlatformCanvas canvas; | |
| 268 | |
| 269 // Paint |view| into |canvas|. | |
| 270 if (!PaintViewIntoCanvas(view, canvas)) | |
| 271 return false; | |
| 272 | |
| 273 skia::BitmapPlatformDevice& device = | |
| 274 static_cast<skia::BitmapPlatformDevice&>(canvas.getTopPlatformDevice()); | |
| 275 | |
| 276 const SkBitmap& src_bmp = device.accessBitmap(false); | |
| 277 | |
| 278 SkRect dest_rect = { 0, 0, SkIntToScalar(w), SkIntToScalar(h) }; | |
| 279 float dest_aspect = dest_rect.width() / dest_rect.height(); | |
| 280 | |
| 281 // Get the src rect so that we can preserve the aspect ratio while filling | |
| 282 // the destination. | |
| 283 SkIRect src_rect; | |
| 284 if (src_bmp.width() < dest_rect.width() || | |
| 285 src_bmp.height() < dest_rect.height()) { | |
| 286 // Source image is smaller: we clip the part of source image within the | |
| 287 // dest rect, and then stretch it to fill the dest rect. We don't respect | |
| 288 // the aspect ratio in this case. | |
| 289 src_rect.set(0, 0, static_cast<S16CPU>(dest_rect.width()), | |
| 290 static_cast<S16CPU>(dest_rect.height())); | |
| 291 score->good_clipping = false; | |
| 292 } else { | |
| 293 float src_aspect = static_cast<float>(src_bmp.width()) / src_bmp.height(); | |
| 294 if (src_aspect > dest_aspect) { | |
| 295 // Wider than tall, clip horizontally: we center the smaller thumbnail in | |
| 296 // the wider screen. | |
| 297 S16CPU new_width = static_cast<S16CPU>(src_bmp.height() * dest_aspect); | |
| 298 S16CPU x_offset = (src_bmp.width() - new_width) / 2; | |
| 299 src_rect.set(x_offset, 0, new_width + x_offset, src_bmp.height()); | |
| 300 score->good_clipping = false; | |
| 301 } else { | |
| 302 src_rect.set(0, 0, src_bmp.width(), | |
| 303 static_cast<S16CPU>(src_bmp.width() / dest_aspect)); | |
| 304 score->good_clipping = true; | |
| 305 } | |
| 306 } | |
| 307 | |
| 308 score->at_top = (view->mainFrame()->scrollOffset().height == 0); | |
| 309 | |
| 310 SkBitmap subset; | |
| 311 device.accessBitmap(false).extractSubset(&subset, src_rect); | |
| 312 | |
| 313 // First do a fast downsample by powers of two to get close to the final size. | |
| 314 SkBitmap downsampled_subset = | |
| 315 SkBitmapOperations::DownsampleByTwoUntilSize(subset, w, h); | |
| 316 | |
| 317 // Do a high-quality resize from the downscaled size to the final size. | |
| 318 *thumbnail = skia::ImageOperations::Resize( | |
| 319 downsampled_subset, skia::ImageOperations::RESIZE_LANCZOS3, w, h); | |
| 320 | |
| 321 score->boring_score = CalculateBoringScore(thumbnail); | |
| 322 | |
| 323 HISTOGRAM_TIMES("Renderer4.Thumbnail", | |
| 324 base::TimeTicks::Now() - beginning_time); | |
| 325 | |
| 326 return true; | |
| 327 } | |
| 328 | |
| 329 bool ChromeRenderObserver::CaptureSnapshot(WebView* view, SkBitmap* snapshot) { | |
| 330 base::TimeTicks beginning_time = base::TimeTicks::Now(); | |
| 331 | |
| 332 skia::PlatformCanvas canvas; | |
| 333 if (!PaintViewIntoCanvas(view, canvas)) | |
| 334 return false; | |
| 335 | |
| 336 skia::BitmapPlatformDevice& device = | |
| 337 static_cast<skia::BitmapPlatformDevice&>(canvas.getTopPlatformDevice()); | |
| 338 | |
| 339 const SkBitmap& bitmap = device.accessBitmap(false); | |
| 340 if (!bitmap.copyTo(snapshot, SkBitmap::kARGB_8888_Config)) | |
| 341 return false; | |
| 342 | |
| 343 HISTOGRAM_TIMES("Renderer4.Snapshot", | |
| 344 base::TimeTicks::Now() - beginning_time); | |
| 345 return true; | |
| 346 } | |
| OLD | NEW |