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/browser/tab_contents/thumbnail_generator.h" 6 7 #include <algorithm> 8 #include <map> 9 10 #include "base/memory/scoped_ptr.h" 11 #include "base/metrics/histogram.h" 12 #include "base/time.h" 13 #include "build/build_config.h" 14 #include "chrome/browser/browser_process.h" 15 #include "chrome/browser/history/top_sites.h" 16 #include "chrome/browser/profiles/profile.h" 17 #include "chrome/common/thumbnail_score.h" 18 #include "content/browser/renderer_host/backing_store.h" 19 #include "content/browser/renderer_host/render_process_host.h" 20 #include "content/browser/renderer_host/render_view_host.h" 21 #include "content/browser/tab_contents/tab_contents.h" 22 #include "content/common/notification_service.h" 23 #include "content/common/property_bag.h" 24 #include "googleurl/src/gurl.h" 25 #include "skia/ext/bitmap_platform_device.h" 26 #include "skia/ext/image_operations.h" 27 #include "skia/ext/platform_canvas.h" 28 #include "third_party/skia/include/core/SkBitmap.h" 29 #include "ui/gfx/color_utils.h" 30 #include "ui/gfx/rect.h" 31 #include "ui/gfx/skbitmap_operations.h" 32 33 #if defined(OS_WIN) 34 #include "content/common/section_util_win.h" 35 #endif 36 37 // Overview 38 // -------- 39 // This class provides current thumbnails for tabs. The simplest operation is 40 // when a request for a thumbnail comes in, to grab the backing store and make 41 // a smaller version of that. Clients of the class can send such a request by 42 // GetThumbnailForRenderer() and AskForSnapshot(). 43 // 44 // The class also provides a service for updating thumbnails to be used in 45 // "Most visited" section of the new tab page. The service can be started 46 // by StartThumbnailing(). The current algorithm of the service is as 47 // simple as follows: 48 // 49 // When a renderer is about to be hidden (this usually occurs when the 50 // current tab is closed or another tab is clicked), update the 51 // thumbnail for the tab rendered by the renderer, if needed. The 52 // heuristics to judge whether or not to update the thumbnail is 53 // implemented in ShouldUpdateThumbnail(). 54 // 55 // We'll likely revise the algorithm to improve quality of thumbnails this 56 // service generates. 57 58 namespace { 59 60 static const int kThumbnailWidth = 212; 61 static const int kThumbnailHeight = 132; 62 63 static const char kThumbnailHistogramName[] = "Thumbnail.ComputeMS"; 64 65 // Returns a property accessor used for attaching a TabContents to a 66 // RenderWidgetHost. We maintain the RenderWidgetHost to TabContents 67 // mapping so that we can retrieve a TabContents from a RenderWidgetHost 68 PropertyAccessor<TabContents*>* GetTabContentsAccessor() { 69 static PropertyAccessor<TabContents*> accessor; 70 return &accessor; 71 } 72 73 // Creates a downsampled thumbnail for the given backing store. The returned 74 // bitmap will be isNull if there was an error creating it. 75 SkBitmap GetBitmapForBackingStore( 76 BackingStore* backing_store, 77 int desired_width, 78 int desired_height, 79 int options, 80 ThumbnailGenerator::ClipResult* clip_result) { 81 base::TimeTicks begin_compute_thumbnail = base::TimeTicks::Now(); 82 83 SkBitmap result; 84 85 // Get the bitmap as a Skia object so we can resample it. This is a large 86 // allocation and we can tolerate failure here, so give up if the allocation 87 // fails. 88 skia::PlatformCanvas temp_canvas; 89 if (!backing_store->CopyFromBackingStore(gfx::Rect(backing_store->size()), 90 &temp_canvas)) 91 return result; 92 const SkBitmap& bmp = temp_canvas.getTopPlatformDevice().accessBitmap(false); 93 94 // Check if a clipped thumbnail is requested. 95 if (options & ThumbnailGenerator::kClippedThumbnail) { 96 SkBitmap clipped_bitmap = ThumbnailGenerator::GetClippedBitmap( 97 bmp, desired_width, desired_height, clip_result); 98 99 // Need to resize it to the size we want, so downsample until it's 100 // close, and let the caller make it the exact size if desired. 101 result = SkBitmapOperations::DownsampleByTwoUntilSize( 102 clipped_bitmap, desired_width, desired_height); 103 } else { 104 // Need to resize it to the size we want, so downsample until it's 105 // close, and let the caller make it the exact size if desired. 106 result = SkBitmapOperations::DownsampleByTwoUntilSize( 107 bmp, desired_width, desired_height); 108 109 // This is a bit subtle. SkBitmaps are refcounted, but the magic 110 // ones in PlatformCanvas can't be assigned to SkBitmap with proper 111 // refcounting. If the bitmap doesn't change, then the downsampler 112 // will return the input bitmap, which will be the reference to the 113 // weird PlatformCanvas one insetad of a regular one. To get a 114 // regular refcounted bitmap, we need to copy it. 115 if (bmp.width() == result.width() && 116 bmp.height() == result.height()) 117 bmp.copyTo(&result, SkBitmap::kARGB_8888_Config); 118 } 119 120 HISTOGRAM_TIMES(kThumbnailHistogramName, 121 base::TimeTicks::Now() - begin_compute_thumbnail); 122 return result; 123 } 124 125 } // namespace 126 127 struct ThumbnailGenerator::AsyncRequestInfo { 128 scoped_ptr<ThumbnailReadyCallback> callback; 129 scoped_ptr<TransportDIB> thumbnail_dib; 130 RenderWidgetHost* renderer; // Not owned. 131 }; 132 133 ThumbnailGenerator::ThumbnailGenerator() { 134 // The BrowserProcessImpl creates this non-lazily. If you add nontrivial 135 // stuff here, be sure to convert it to being lazily created. 136 // 137 // We don't register for notifications here since BrowserProcessImpl creates 138 // us before the NotificationService is. 139 } 140 141 ThumbnailGenerator::~ThumbnailGenerator() { 142 } 143 144 void ThumbnailGenerator::StartThumbnailing() { 145 if (registrar_.IsEmpty()) { 146 // Even though we deal in RenderWidgetHosts, we only care about its 147 // subclass, RenderViewHost when it is in a tab. We don't make thumbnails 148 // for RenderViewHosts that aren't in tabs, or RenderWidgetHosts that 149 // aren't views like select popups. 150 registrar_.Add(this, NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB, 151 NotificationService::AllSources()); 152 registrar_.Add(this, NotificationType::RENDER_WIDGET_VISIBILITY_CHANGED, 153 NotificationService::AllSources()); 154 registrar_.Add(this, NotificationType::TAB_CONTENTS_DISCONNECTED, 155 NotificationService::AllSources()); 156 } 157 } 158 159 void ThumbnailGenerator::MonitorRenderer(RenderWidgetHost* renderer, 160 bool monitor) { 161 Source<RenderWidgetHost> renderer_source = Source<RenderWidgetHost>(renderer); 162 bool currently_monitored = 163 registrar_.IsRegistered( 164 this, 165 NotificationType::RENDER_WIDGET_HOST_DID_RECEIVE_PAINT_AT_SIZE_ACK, 166 renderer_source); 167 if (monitor != currently_monitored) { 168 if (monitor) { 169 registrar_.Add( 170 this, 171 NotificationType::RENDER_WIDGET_HOST_DID_RECEIVE_PAINT_AT_SIZE_ACK, 172 renderer_source); 173 } else { 174 registrar_.Remove( 175 this, 176 NotificationType::RENDER_WIDGET_HOST_DID_RECEIVE_PAINT_AT_SIZE_ACK, 177 renderer_source); 178 } 179 } 180 } 181 182 void ThumbnailGenerator::AskForSnapshot(RenderWidgetHost* renderer, 183 bool prefer_backing_store, 184 ThumbnailReadyCallback* callback, 185 gfx::Size page_size, 186 gfx::Size desired_size) { 187 if (prefer_backing_store) { 188 BackingStore* backing_store = renderer->GetBackingStore(false); 189 if (backing_store) { 190 // We were able to find a non-null backing store for this renderer, so 191 // we'll go with it. 192 SkBitmap first_try = GetBitmapForBackingStore(backing_store, 193 desired_size.width(), 194 desired_size.height(), 195 kNoOptions, 196 NULL); 197 callback->Run(first_try); 198 199 delete callback; 200 return; 201 } 202 // Now, if the backing store didn't exist, we will still try and 203 // render asynchronously. 204 } 205 206 // We are going to render the thumbnail asynchronously now, so keep 207 // this callback for later lookup when the rendering is done. 208 static int sequence_num = 0; 209 sequence_num++; 210 scoped_ptr<TransportDIB> thumbnail_dib(TransportDIB::Create( 211 desired_size.width() * desired_size.height() * 4, sequence_num)); 212 213 #if defined(OS_WIN) 214 // Duplicate the handle to the DIB here because the renderer process does not 215 // have permission. The duplicated handle is owned by the renderer process, 216 // which is responsible for closing it. 217 TransportDIB::Handle renderer_dib_handle = chrome::GetSectionForProcess( 218 thumbnail_dib->handle(), 219 renderer->process()->GetHandle(), 220 false); 221 if (!renderer_dib_handle) { 222 LOG(WARNING) << "Could not duplicate dib handle for renderer"; 223 delete callback; 224 return; 225 } 226 #else 227 TransportDIB::Handle renderer_dib_handle = thumbnail_dib->handle(); 228 #endif 229 230 linked_ptr<AsyncRequestInfo> request_info(new AsyncRequestInfo); 231 request_info->callback.reset(callback); 232 request_info->thumbnail_dib.reset(thumbnail_dib.release()); 233 request_info->renderer = renderer; 234 ThumbnailCallbackMap::value_type new_value(sequence_num, request_info); 235 std::pair<ThumbnailCallbackMap::iterator, bool> result = 236 callback_map_.insert(new_value); 237 if (!result.second) { 238 NOTREACHED() << "Callback already registered?"; 239 return; 240 } 241 242 renderer->PaintAtSize( 243 renderer_dib_handle, sequence_num, page_size, desired_size); 244 } 245 246 SkBitmap ThumbnailGenerator::GetThumbnailForRenderer( 247 RenderWidgetHost* renderer) const { 248 return GetThumbnailForRendererWithOptions(renderer, kNoOptions, NULL); 249 } 250 251 SkBitmap ThumbnailGenerator::GetThumbnailForRendererWithOptions( 252 RenderWidgetHost* renderer, 253 int options, 254 ClipResult* clip_result) const { 255 BackingStore* backing_store = renderer->GetBackingStore(false); 256 if (!backing_store) { 257 // When we have no backing store, there's no choice in what to use. We 258 // have to return the empty thumbnail. 259 return SkBitmap(); 260 } 261 262 return GetBitmapForBackingStore(backing_store, 263 kThumbnailWidth, 264 kThumbnailHeight, 265 options, 266 clip_result); 267 } 268 269 void ThumbnailGenerator::WidgetDidReceivePaintAtSizeAck( 270 RenderWidgetHost* widget, 271 int sequence_num, 272 const gfx::Size& size) { 273 // Lookup the callback, run it, and erase it. 274 ThumbnailCallbackMap::iterator item = callback_map_.find(sequence_num); 275 if (item != callback_map_.end()) { 276 TransportDIB* dib = item->second->thumbnail_dib.get(); 277 DCHECK(dib); 278 if (!dib || !dib->Map()) { 279 return; 280 } 281 282 // Create an SkBitmap from the DIB. 283 SkBitmap non_owned_bitmap; 284 SkBitmap result; 285 286 // Fill out the non_owned_bitmap with the right config. Note that 287 // this code assumes that the transport dib is a 32-bit ARGB 288 // image. 289 non_owned_bitmap.setConfig(SkBitmap::kARGB_8888_Config, 290 size.width(), size.height()); 291 non_owned_bitmap.setPixels(dib->memory()); 292 293 // Now alloc/copy the memory so we own it and can pass it around, 294 // and the memory won't go away when the DIB goes away. 295 // TODO: Figure out a way to avoid this copy? 296 non_owned_bitmap.copyTo(&result, SkBitmap::kARGB_8888_Config); 297 298 item->second->callback->Run(result); 299 300 // We're done with the callback, and with the DIB, so delete both. 301 callback_map_.erase(item); 302 } 303 } 304 305 void ThumbnailGenerator::Observe(NotificationType type, 306 const NotificationSource& source, 307 const NotificationDetails& details) { 308 switch (type.value) { 309 case NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB: { 310 // Install our observer for all new RVHs. 311 RenderViewHost* renderer = Details<RenderViewHost>(details).ptr(); 312 TabContents* contents = Source<TabContents>(source).ptr(); 313 MonitorRenderer(renderer, true); 314 // Attach the tab contents to the renderer. 315 // TODO(satorux): Rework this code. This relies on some internals of 316 // how TabContents and RVH work. We should make this class 317 // per-tab. See also crbug.com/78990. 318 GetTabContentsAccessor()->SetProperty( 319 renderer->property_bag(), contents); 320 VLOG(1) << "renderer " << renderer << "is created for tab " << contents; 321 break; 322 } 323 324 case NotificationType::RENDER_WIDGET_VISIBILITY_CHANGED: 325 if (!*Details<bool>(details).ptr()) 326 WidgetHidden(Source<RenderWidgetHost>(source).ptr()); 327 break; 328 329 case NotificationType::RENDER_WIDGET_HOST_DID_RECEIVE_PAINT_AT_SIZE_ACK: { 330 RenderWidgetHost::PaintAtSizeAckDetails* size_ack_details = 331 Details<RenderWidgetHost::PaintAtSizeAckDetails>(details).ptr(); 332 WidgetDidReceivePaintAtSizeAck( 333 Source<RenderWidgetHost>(source).ptr(), 334 size_ack_details->tag, 335 size_ack_details->size); 336 break; 337 } 338 339 case NotificationType::TAB_CONTENTS_DISCONNECTED: 340 TabContentsDisconnected(Source<TabContents>(source).ptr()); 341 break; 342 343 default: 344 NOTREACHED() << "Unexpected notification type: " << type.value; 345 } 346 } 347 348 void ThumbnailGenerator::WidgetHidden(RenderWidgetHost* widget) { 349 // Retrieve the tab contents rendered by the widget. 350 TabContents** property = GetTabContentsAccessor()->GetProperty( 351 widget->property_bag()); 352 if (!property) { 353 LOG(ERROR) << "This widget is not associated with tab contents: " 354 << widget; 355 return; 356 } 357 TabContents* contents = *property; 358 UpdateThumbnailIfNecessary(contents); 359 } 360 361 void ThumbnailGenerator::TabContentsDisconnected(TabContents* contents) { 362 // Go through the existing callbacks, and find any that have the 363 // same renderer as this TabContents and remove them so they don't 364 // hang around. 365 ThumbnailCallbackMap::iterator iterator = callback_map_.begin(); 366 RenderWidgetHost* renderer = contents->render_view_host(); 367 while (iterator != callback_map_.end()) { 368 if (iterator->second->renderer == renderer) { 369 ThumbnailCallbackMap::iterator nuked = iterator; 370 ++iterator; 371 callback_map_.erase(nuked); 372 continue; 373 } 374 ++iterator; 375 } 376 } 377 378 double ThumbnailGenerator::CalculateBoringScore(SkBitmap* bitmap) { 379 if (bitmap->isNull() || bitmap->empty()) 380 return 1.0; 381 int histogram[256] = {0}; 382 color_utils::BuildLumaHistogram(bitmap, histogram); 383 384 int color_count = *std::max_element(histogram, histogram + 256); 385 int pixel_count = bitmap->width() * bitmap->height(); 386 return static_cast<double>(color_count) / pixel_count; 387 } 388 389 SkBitmap ThumbnailGenerator::GetClippedBitmap(const SkBitmap& bitmap, 390 int desired_width, 391 int desired_height, 392 ClipResult* clip_result) { 393 const SkRect dest_rect = { 0, 0, 394 SkIntToScalar(desired_width), 395 SkIntToScalar(desired_height) }; 396 const float dest_aspect = dest_rect.width() / dest_rect.height(); 397 398 // Get the src rect so that we can preserve the aspect ratio while filling 399 // the destination. 400 SkIRect src_rect; 401 if (bitmap.width() < dest_rect.width() || 402 bitmap.height() < dest_rect.height()) { 403 // Source image is smaller: we clip the part of source image within the 404 // dest rect, and then stretch it to fill the dest rect. We don't respect 405 // the aspect ratio in this case. 406 src_rect.set(0, 0, static_cast<S16CPU>(dest_rect.width()), 407 static_cast<S16CPU>(dest_rect.height())); 408 if (clip_result) 409 *clip_result = ThumbnailGenerator::kSourceIsSmaller; 410 } else { 411 const float src_aspect = 412 static_cast<float>(bitmap.width()) / bitmap.height(); 413 if (src_aspect > dest_aspect) { 414 // Wider than tall, clip horizontally: we center the smaller 415 // thumbnail in the wider screen. 416 S16CPU new_width = static_cast<S16CPU>(bitmap.height() * dest_aspect); 417 S16CPU x_offset = (bitmap.width() - new_width) / 2; 418 src_rect.set(x_offset, 0, new_width + x_offset, bitmap.height()); 419 if (clip_result) 420 *clip_result = ThumbnailGenerator::kWiderThanTall; 421 } else if (src_aspect < dest_aspect) { 422 src_rect.set(0, 0, bitmap.width(), 423 static_cast<S16CPU>(bitmap.width() / dest_aspect)); 424 if (clip_result) 425 *clip_result = ThumbnailGenerator::kTallerThanWide; 426 } else { 427 src_rect.set(0, 0, bitmap.width(), bitmap.height()); 428 if (clip_result) 429 *clip_result = ThumbnailGenerator::kNotClipped; 430 } 431 } 432 433 SkBitmap clipped_bitmap; 434 bitmap.extractSubset(&clipped_bitmap, src_rect); 435 return clipped_bitmap; 436 } 437 438 void ThumbnailGenerator::UpdateThumbnailIfNecessary( 439 TabContents* tab_contents) { 440 const GURL& url = tab_contents->GetURL(); 441 history::TopSites* top_sites = tab_contents->profile()->GetTopSites(); 442 // Skip if we don't need to update the thumbnail. 443 if (!ShouldUpdateThumbnail(tab_contents->profile(), top_sites, url)) 444 return; 445 446 const int options = ThumbnailGenerator::kClippedThumbnail; 447 ThumbnailGenerator::ClipResult clip_result = ThumbnailGenerator::kNotClipped; 448 SkBitmap thumbnail = GetThumbnailForRendererWithOptions( 449 tab_contents->render_view_host(), options, &clip_result); 450 // Failed to generate a thumbnail. Maybe the tab is in the background? 451 if (thumbnail.isNull()) 452 return; 453 454 // Compute the thumbnail score. 455 ThumbnailScore score; 456 score.at_top = 457 (tab_contents->render_view_host()->last_scroll_offset().y() == 0); 458 score.boring_score = ThumbnailGenerator::CalculateBoringScore(&thumbnail); 459 score.good_clipping = 460 (clip_result == ThumbnailGenerator::kTallerThanWide || 461 clip_result == ThumbnailGenerator::kNotClipped); 462 463 top_sites->SetPageThumbnail(url, thumbnail, score); 464 VLOG(1) << "Thumbnail taken for " << url << ": " << score.ToString(); 465 } 466 467 bool ThumbnailGenerator::ShouldUpdateThumbnail(Profile* profile, 468 history::TopSites* top_sites, 469 const GURL& url) { 470 if (!profile || !top_sites) 471 return false; 472 // Skip if it's in the incognito mode. 473 if (profile->IsOffTheRecord()) 474 return false; 475 // Skip if the given URL is not appropriate for history. 476 if (!HistoryService::CanAddURL(url)) 477 return false; 478 // Skip if the top sites list is full, and the URL is not known. 479 if (top_sites->IsFull() && !top_sites->IsKnownURL(url)) 480 return false; 481 // Skip if we don't have to udpate the existing thumbnail. 482 ThumbnailScore current_score; 483 if (top_sites->GetPageThumbnailScore(url, ¤t_score) && 484 !current_score.ShouldConsiderUpdating()) 485 return false; 486 // Skip if we don't have to udpate the temporary thumbnail (i.e. the one 487 // not yet saved). 488 ThumbnailScore temporary_score; 489 if (top_sites->GetTemporaryPageThumbnailScore(url, &temporary_score) && 490 !temporary_score.ShouldConsiderUpdating()) 491 return false; 492 493 return true; 494 } 495