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.
UpdateThumbnail(const ThumbnailingContext & context,const SkBitmap & thumbnail)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
ProcessCapturedBitmap(scoped_refptr<ThumbnailingContext> context,scoped_refptr<ThumbnailingAlgorithm> algorithm,bool succeeded,const SkBitmap & bitmap)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
AsyncProcessThumbnail(content::WebContents * web_contents,scoped_refptr<ThumbnailingContext> context,scoped_refptr<ThumbnailingAlgorithm> algorithm)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 ©_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
ThumbnailTabHelper(content::WebContents * contents)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
~ThumbnailTabHelper()129 ThumbnailTabHelper::~ThumbnailTabHelper() {
130 }
131
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)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
RenderViewDeleted(content::RenderViewHost * render_view_host)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
DidStartLoading(content::RenderViewHost * render_view_host)164 void ThumbnailTabHelper::DidStartLoading(
165 content::RenderViewHost* render_view_host) {
166 load_interrupted_ = false;
167 }
168
NavigationStopped()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
UpdateThumbnailIfNecessary(WebContents * web_contents)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
RenderViewHostCreated(content::RenderViewHost * renderer)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
WidgetHidden(RenderWidgetHost * widget)224 void ThumbnailTabHelper::WidgetHidden(RenderWidgetHost* widget) {
225 if (!enabled_)
226 return;
227 UpdateThumbnailIfNecessary(web_contents());
228 }
229