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