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