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
28DEFINE_WEB_CONTENTS_USER_DATA_KEY(ThumbnailTabHelper);
29
30class 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
45using content::RenderViewHost;
46using content::RenderWidgetHost;
47using content::WebContents;
48
49using thumbnails::ClipResult;
50using thumbnails::ThumbnailingContext;
51using thumbnails::ThumbnailingAlgorithm;
52
53namespace {
54
55// Feed the constructed thumbnail to the thumbnail service.
56void 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
64void 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
78void 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
116ThumbnailTabHelper::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
129ThumbnailTabHelper::~ThumbnailTabHelper() {
130}
131
132void 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
150void 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
164void ThumbnailTabHelper::DidStartLoading(
165    content::RenderViewHost* render_view_host) {
166  load_interrupted_ = false;
167}
168
169void ThumbnailTabHelper::NavigationStopped() {
170  // This function gets called when the page loading is interrupted by the
171  // stop button.
172  load_interrupted_ = true;
173}
174
175void 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
207void 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
224void ThumbnailTabHelper::WidgetHidden(RenderWidgetHost* widget) {
225  if (!enabled_)
226    return;
227  UpdateThumbnailIfNecessary(web_contents());
228}
229