Home | History | Annotate | Download | only in tab_contents
      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, &current_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