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 ©_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