Home | History | Annotate | Download | only in thumbnails
      1 // Copyright (c) 2012 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/thumbnails/thumbnail_tab_helper.h"
      6 
      7 #include "chrome/browser/browser_process.h"
      8 #include "chrome/browser/profiles/profile.h"
      9 #include "chrome/browser/thumbnails/render_widget_snapshot_taker.h"
     10 #include "chrome/browser/thumbnails/thumbnail_service.h"
     11 #include "chrome/browser/thumbnails/thumbnail_service_factory.h"
     12 #include "chrome/browser/thumbnails/thumbnailing_algorithm.h"
     13 #include "chrome/browser/thumbnails/thumbnailing_context.h"
     14 #include "content/public/browser/notification_details.h"
     15 #include "content/public/browser/notification_source.h"
     16 #include "content/public/browser/notification_types.h"
     17 #include "content/public/browser/render_view_host.h"
     18 #include "content/public/browser/render_widget_host_view.h"
     19 #include "ui/gfx/color_utils.h"
     20 #include "ui/gfx/size_conversions.h"
     21 #include "ui/gfx/screen.h"
     22 #include "ui/gfx/scrollbar_size.h"
     23 #include "ui/gfx/skbitmap_operations.h"
     24 
     25 #if defined(OS_WIN)
     26 #include "base/win/windows_version.h"
     27 #endif
     28 
     29 DEFINE_WEB_CONTENTS_USER_DATA_KEY(ThumbnailTabHelper);
     30 
     31 class SkBitmap;
     32 
     33 // Overview
     34 // --------
     35 // This class provides a service for updating thumbnails to be used in
     36 // "Most visited" section of the new tab page. The service can be started
     37 // by StartThumbnailing(). The current algorithm of the service is as
     38 // simple as follows:
     39 //
     40 //    When a renderer is about to be hidden (this usually occurs when the
     41 //    current tab is closed or another tab is clicked), update the
     42 //    thumbnail for the tab rendered by the renderer, if needed. The
     43 //    heuristics to judge whether or not to update the thumbnail is
     44 //    implemented in ShouldUpdateThumbnail().
     45 
     46 using content::RenderViewHost;
     47 using content::RenderWidgetHost;
     48 using content::WebContents;
     49 
     50 using thumbnails::ClipResult;
     51 using thumbnails::ThumbnailingContext;
     52 using thumbnails::ThumbnailingAlgorithm;
     53 
     54 namespace {
     55 
     56 // Feed the constructed thumbnail to the thumbnail service.
     57 void UpdateThumbnail(const ThumbnailingContext& context,
     58                      const SkBitmap& thumbnail) {
     59   gfx::Image image = gfx::Image::CreateFrom1xBitmap(thumbnail);
     60   context.service->SetPageThumbnail(context, image);
     61   VLOG(1) << "Thumbnail taken for " << context.url << ": "
     62           << context.score.ToString();
     63 }
     64 
     65 void ProcessCapturedBitmap(scoped_refptr<ThumbnailingContext> context,
     66                            scoped_refptr<ThumbnailingAlgorithm> algorithm,
     67                            bool succeeded,
     68                            const SkBitmap& bitmap) {
     69   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
     70   if (!succeeded)
     71     return;
     72 
     73   algorithm->ProcessBitmap(context, base::Bind(&UpdateThumbnail), bitmap);
     74 }
     75 
     76 void GotSnapshotFromRenderer(base::Callback<void(const SkBitmap&)> callback,
     77                              bool success,
     78                              const SkBitmap& bitmap) {
     79   if (success)
     80     callback.Run(bitmap);
     81 }
     82 
     83 void AsyncProcessThumbnail(content::WebContents* web_contents,
     84                            scoped_refptr<ThumbnailingContext> context,
     85                            scoped_refptr<ThumbnailingAlgorithm> algorithm) {
     86   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
     87   RenderWidgetHost* render_widget_host = web_contents->GetRenderViewHost();
     88   content::RenderWidgetHostView* view = render_widget_host->GetView();
     89   if (!view)
     90     return;
     91   if (!view->IsSurfaceAvailableForCopy()) {
     92     // On Windows XP and possibly due to driver issues, neither the backing
     93     // store nor the compositing surface is available in the browser when
     94     // accelerated compositing is active, so ask the renderer to send a snapshot
     95     // for creating the thumbnail.
     96     render_widget_host->GetSnapshotFromRenderer(
     97       gfx::Rect(),
     98       base::Bind(GotSnapshotFromRenderer, base::Bind(
     99           &ThumbnailingAlgorithm::ProcessBitmap,
    100           algorithm, context, base::Bind(&UpdateThumbnail))));
    101     return;
    102   }
    103 
    104   gfx::Rect copy_rect = gfx::Rect(view->GetViewBounds().size());
    105   // Clip the pixels that will commonly hold a scrollbar, which looks bad in
    106   // thumbnails.
    107   int scrollbar_size = gfx::scrollbar_size();
    108   gfx::Size copy_size;
    109   copy_rect.Inset(0, 0, scrollbar_size, scrollbar_size);
    110 
    111   if (copy_rect.IsEmpty())
    112     return;
    113 
    114   context->clip_result = algorithm->GetCanvasCopyInfo(
    115       copy_rect.size(),
    116       ui::GetScaleFactorForNativeView(view->GetNativeView()),
    117       &copy_rect,
    118       &context->requested_copy_size);
    119 
    120   render_widget_host->CopyFromBackingStore(
    121       copy_rect,
    122       context->requested_copy_size,
    123       base::Bind(&ProcessCapturedBitmap, context, algorithm));
    124 }
    125 
    126 }  // namespace
    127 
    128 ThumbnailTabHelper::ThumbnailTabHelper(content::WebContents* contents)
    129     : content::WebContentsObserver(contents),
    130       enabled_(true),
    131       load_interrupted_(false) {
    132   // Even though we deal in RenderWidgetHosts, we only care about its
    133   // subclass, RenderViewHost when it is in a tab. We don't make thumbnails
    134   // for RenderViewHosts that aren't in tabs, or RenderWidgetHosts that
    135   // aren't views like select popups.
    136   registrar_.Add(this,
    137                  content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED,
    138                  content::Source<WebContents>(contents));
    139 }
    140 
    141 ThumbnailTabHelper::~ThumbnailTabHelper() {
    142 }
    143 
    144 void ThumbnailTabHelper::Observe(int type,
    145                                  const content::NotificationSource& source,
    146                                  const content::NotificationDetails& details) {
    147   switch (type) {
    148     case content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED:
    149       RenderViewHostCreated(content::Details<RenderViewHost>(details).ptr());
    150       break;
    151 
    152     case content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED:
    153       if (!*content::Details<bool>(details).ptr())
    154         WidgetHidden(content::Source<RenderWidgetHost>(source).ptr());
    155       break;
    156 
    157     default:
    158       NOTREACHED() << "Unexpected notification type: " << type;
    159   }
    160 }
    161 
    162 void ThumbnailTabHelper::RenderViewDeleted(
    163     content::RenderViewHost* render_view_host) {
    164   g_browser_process->GetRenderWidgetSnapshotTaker()->CancelSnapshot(
    165       render_view_host);
    166 
    167   bool registered = registrar_.IsRegistered(
    168       this,
    169       content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED,
    170       content::Source<RenderWidgetHost>(render_view_host));
    171   if (registered) {
    172     registrar_.Remove(
    173         this,
    174         content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED,
    175         content::Source<RenderWidgetHost>(render_view_host));
    176   }
    177 }
    178 
    179 void ThumbnailTabHelper::DidStartLoading(
    180     content::RenderViewHost* render_view_host) {
    181   load_interrupted_ = false;
    182 }
    183 
    184 void ThumbnailTabHelper::StopNavigation() {
    185   // This function gets called when the page loading is interrupted by the
    186   // stop button.
    187   load_interrupted_ = true;
    188 }
    189 
    190 void ThumbnailTabHelper::UpdateThumbnailIfNecessary(
    191     WebContents* web_contents) {
    192   // Destroying a WebContents may trigger it to be hidden, prompting a snapshot
    193   // which would be unwise to attempt <http://crbug.com/130097>. If the
    194   // WebContents is in the middle of destruction, do not risk it.
    195   if (!web_contents || web_contents->IsBeingDestroyed())
    196     return;
    197   // Skip if a pending entry exists. WidgetHidden can be called while navigating
    198   // pages and this is not a time when thumbnails should be generated.
    199   if (web_contents->GetController().GetPendingEntry())
    200     return;
    201   const GURL& url = web_contents->GetURL();
    202   Profile* profile =
    203       Profile::FromBrowserContext(web_contents->GetBrowserContext());
    204 
    205   scoped_refptr<thumbnails::ThumbnailService> thumbnail_service =
    206       ThumbnailServiceFactory::GetForProfile(profile);
    207 
    208   // Skip if we don't need to update the thumbnail.
    209   if (thumbnail_service.get() == NULL ||
    210       !thumbnail_service->ShouldAcquirePageThumbnail(url)) {
    211     return;
    212   }
    213 
    214   scoped_refptr<thumbnails::ThumbnailingAlgorithm> algorithm(
    215       thumbnail_service->GetThumbnailingAlgorithm());
    216 
    217   scoped_refptr<ThumbnailingContext> context(new ThumbnailingContext(
    218       web_contents, thumbnail_service.get(), load_interrupted_));
    219   AsyncProcessThumbnail(web_contents, context, algorithm);
    220 }
    221 
    222 void ThumbnailTabHelper::RenderViewHostCreated(
    223     content::RenderViewHost* renderer) {
    224   // NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED is really a new
    225   // RenderView, not RenderViewHost, and there is no good way to get
    226   // notifications of RenderViewHosts. So just be tolerant of re-registrations.
    227   bool registered = registrar_.IsRegistered(
    228       this,
    229       content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED,
    230       content::Source<RenderWidgetHost>(renderer));
    231   if (!registered) {
    232     registrar_.Add(
    233         this,
    234         content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED,
    235         content::Source<RenderWidgetHost>(renderer));
    236   }
    237 }
    238 
    239 void ThumbnailTabHelper::WidgetHidden(RenderWidgetHost* widget) {
    240   if (!enabled_)
    241     return;
    242   UpdateThumbnailIfNecessary(web_contents());
    243 }
    244