OLD | NEW |
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/tab_contents/thumbnail_generator.h" | 5 #include "chrome/browser/tab_contents/thumbnail_generator.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <map> |
8 | 9 |
9 #include "base/histogram.h" | 10 #include "base/histogram.h" |
10 #include "base/time.h" | 11 #include "base/time.h" |
11 #include "build/build_config.h" | 12 #include "build/build_config.h" |
12 #include "chrome/browser/renderer_host/backing_store.h" | 13 #include "chrome/browser/renderer_host/backing_store.h" |
13 #include "chrome/browser/renderer_host/render_view_host.h" | 14 #include "chrome/browser/renderer_host/render_view_host.h" |
| 15 #include "chrome/browser/tab_contents/tab_contents.h" |
14 #include "chrome/common/notification_service.h" | 16 #include "chrome/common/notification_service.h" |
15 #include "chrome/common/property_bag.h" | 17 #include "chrome/common/property_bag.h" |
16 #include "gfx/rect.h" | 18 #include "gfx/rect.h" |
17 #include "gfx/skbitmap_operations.h" | 19 #include "gfx/skbitmap_operations.h" |
18 #include "skia/ext/platform_canvas.h" | 20 #include "skia/ext/platform_canvas.h" |
19 #include "third_party/skia/include/core/SkBitmap.h" | 21 #include "third_party/skia/include/core/SkBitmap.h" |
20 | 22 |
21 // Overview | 23 // Overview |
22 // -------- | 24 // -------- |
23 // This class provides current thumbnails for tabs. The simplest operation is | 25 // This class provides current thumbnails for tabs. The simplest operation is |
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
98 | 100 |
99 // Get the bitmap as a Skia object so we can resample it. This is a large | 101 // Get the bitmap as a Skia object so we can resample it. This is a large |
100 // allocation and we can tolerate failure here, so give up if the allocation | 102 // allocation and we can tolerate failure here, so give up if the allocation |
101 // fails. | 103 // fails. |
102 skia::PlatformCanvas temp_canvas; | 104 skia::PlatformCanvas temp_canvas; |
103 if (!backing_store->CopyFromBackingStore(gfx::Rect(gfx::Point(0, 0), | 105 if (!backing_store->CopyFromBackingStore(gfx::Rect(gfx::Point(0, 0), |
104 backing_store->size()), | 106 backing_store->size()), |
105 &temp_canvas)) | 107 &temp_canvas)) |
106 return result; | 108 return result; |
107 const SkBitmap& bmp = temp_canvas.getTopPlatformDevice().accessBitmap(false); | 109 const SkBitmap& bmp = temp_canvas.getTopPlatformDevice().accessBitmap(false); |
108 result = SkBitmapOperations::DownsampleByTwoUntilSize(bmp, kThumbnailWidth, | 110 |
| 111 #if defined(OS_CHROMEOS) |
| 112 // On ChromeOS, the thumbnail is always half the dimensions of the |
| 113 // original. |
| 114 result = SkBitmapOperations::DownsampleByTwo(bmp); |
| 115 #else |
| 116 result = SkBitmapOperations::DownsampleByTwoUntilSize(bmp, |
| 117 kThumbnailWidth, |
109 kThumbnailHeight); | 118 kThumbnailHeight); |
110 | 119 |
111 // This is a bit subtle. SkBitmaps are refcounted, but the magic ones in | 120 // This is a bit subtle. SkBitmaps are refcounted, but the magic |
112 // PlatformCanvas can't be ssigned to SkBitmap with proper | 121 // ones in PlatformCanvas can't be assigned to SkBitmap with proper |
113 // refcounting. If the bitmap doesn't change, then the downsampler will | 122 // refcounting. If the bitmap doesn't change, then the downsampler |
114 // return the input bitmap, which will be the reference to the weird | 123 // will return the input bitmap, which will be the reference to the |
115 // PlatformCanvas one insetad of a regular one. To get a regular refcounted | 124 // weird PlatformCanvas one insetad of a regular one. To get a |
116 // bitmap, we need to copy it. | 125 // regular refcounted bitmap, we need to copy it. |
117 if (bmp.width() == result.width() && bmp.height() == result.height()) | 126 if (bmp.width() == result.width() && |
| 127 bmp.height() == result.height()) |
118 bmp.copyTo(&result, SkBitmap::kARGB_8888_Config); | 128 bmp.copyTo(&result, SkBitmap::kARGB_8888_Config); |
| 129 #endif |
119 | 130 |
120 HISTOGRAM_TIMES(kThumbnailHistogramName, | 131 HISTOGRAM_TIMES(kThumbnailHistogramName, |
121 base::TimeTicks::Now() - begin_compute_thumbnail); | 132 base::TimeTicks::Now() - begin_compute_thumbnail); |
122 return result; | 133 return result; |
123 } | 134 } |
124 | 135 |
125 } // namespace | 136 } // namespace |
126 | 137 |
| 138 struct ThumbnailGenerator::AsyncRequestInfo { |
| 139 scoped_ptr<ThumbnailReadyCallback> callback; |
| 140 scoped_ptr<TransportDIB> thumbnail_dib; |
| 141 RenderWidgetHost* renderer; // Not owned. |
| 142 }; |
| 143 |
127 ThumbnailGenerator::ThumbnailGenerator() | 144 ThumbnailGenerator::ThumbnailGenerator() |
128 : no_timeout_(false) { | 145 : no_timeout_(false) { |
129 // The BrowserProcessImpl creates this non-lazily. If you add nontrivial | 146 // The BrowserProcessImpl creates this non-lazily. If you add nontrivial |
130 // stuff here, be sure to convert it to being lazily created. | 147 // stuff here, be sure to convert it to being lazily created. |
131 // | 148 // |
132 // We don't register for notifications here since BrowserProcessImpl creates | 149 // We don't register for notifications here since BrowserProcessImpl creates |
133 // us before the NotificationService is. | 150 // us before the NotificationService is. |
134 } | 151 } |
135 | 152 |
136 ThumbnailGenerator::~ThumbnailGenerator() { | 153 ThumbnailGenerator::~ThumbnailGenerator() { |
137 } | 154 } |
138 | 155 |
139 void ThumbnailGenerator::StartThumbnailing() { | 156 void ThumbnailGenerator::StartThumbnailing() { |
140 if (registrar_.IsEmpty()) { | 157 if (registrar_.IsEmpty()) { |
141 // Even though we deal in RenderWidgetHosts, we only care about its | 158 // Even though we deal in RenderWidgetHosts, we only care about its |
142 // subclass, RenderViewHost when it is in a tab. We don't make thumbnails | 159 // subclass, RenderViewHost when it is in a tab. We don't make thumbnails |
143 // for RenderViewHosts that aren't in tabs, or RenderWidgetHosts that | 160 // for RenderViewHosts that aren't in tabs, or RenderWidgetHosts that |
144 // aren't views like select popups. | 161 // aren't views like select popups. |
145 registrar_.Add(this, NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB, | 162 registrar_.Add(this, NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB, |
146 NotificationService::AllSources()); | 163 NotificationService::AllSources()); |
147 | |
148 registrar_.Add(this, NotificationType::RENDER_WIDGET_VISIBILITY_CHANGED, | 164 registrar_.Add(this, NotificationType::RENDER_WIDGET_VISIBILITY_CHANGED, |
149 NotificationService::AllSources()); | 165 NotificationService::AllSources()); |
150 registrar_.Add(this, NotificationType::RENDER_WIDGET_HOST_DESTROYED, | 166 registrar_.Add(this, NotificationType::RENDER_WIDGET_HOST_DESTROYED, |
151 NotificationService::AllSources()); | 167 NotificationService::AllSources()); |
| 168 registrar_.Add(this, NotificationType::TAB_CONTENTS_DISCONNECTED, |
| 169 NotificationService::AllSources()); |
152 } | 170 } |
153 } | 171 } |
154 | 172 |
| 173 void ThumbnailGenerator::AskForThumbnail(RenderWidgetHost* renderer, |
| 174 ThumbnailReadyCallback* callback, |
| 175 gfx::Size size) { |
| 176 SkBitmap first_try = GetThumbnailForRenderer(renderer); |
| 177 if (!first_try.isNull()) { |
| 178 // We were able to find a non-null thumbnail for this renderer, so |
| 179 // we'll go with it. |
| 180 callback->Run(first_try); |
| 181 delete callback; |
| 182 return; |
| 183 } |
| 184 |
| 185 // We are going to render the thumbnail asynchronously now, so keep |
| 186 // this callback for later lookup when the rendering is done. |
| 187 static int sequence_num = 0; |
| 188 TransportDIB* thumbnail_dib = TransportDIB::Create( |
| 189 size.width() * size.height() * 4, sequence_num++); |
| 190 linked_ptr<AsyncRequestInfo> request_info(new AsyncRequestInfo); |
| 191 request_info->callback.reset(callback); |
| 192 request_info->thumbnail_dib.reset(thumbnail_dib); |
| 193 request_info->renderer = renderer; |
| 194 ThumbnailCallbackMap::value_type new_value(thumbnail_dib->handle(), |
| 195 request_info); |
| 196 std::pair<ThumbnailCallbackMap::iterator, bool> result = |
| 197 callback_map_.insert(new_value); |
| 198 if (!result.second) { |
| 199 NOTREACHED() << "Callback already registered?"; |
| 200 return; |
| 201 } |
| 202 |
| 203 renderer->PaintAtSize(thumbnail_dib->handle(), size); |
| 204 } |
| 205 |
155 SkBitmap ThumbnailGenerator::GetThumbnailForRenderer( | 206 SkBitmap ThumbnailGenerator::GetThumbnailForRenderer( |
156 RenderWidgetHost* renderer) const { | 207 RenderWidgetHost* renderer) const { |
157 WidgetThumbnail* wt = GetDataForHost(renderer); | 208 WidgetThumbnail* wt = GetDataForHost(renderer); |
158 | 209 |
159 BackingStore* backing_store = renderer->GetBackingStore(false); | 210 BackingStore* backing_store = renderer->GetBackingStore(false); |
160 if (!backing_store) { | 211 if (!backing_store) { |
161 // When we have no backing store, there's no choice in what to use. We | 212 // When we have no backing store, there's no choice in what to use. We |
162 // have to return either the existing thumbnail or the empty one if there | 213 // have to return either the existing thumbnail or the empty one if there |
163 // isn't a saved one. | 214 // isn't a saved one. |
164 return wt->thumbnail; | 215 return wt->thumbnail; |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
214 base::TimeTicks::Now() - | 265 base::TimeTicks::Now() - |
215 base::TimeDelta::FromMilliseconds(kVisibilitySlopMS) < wt->last_shown) | 266 base::TimeDelta::FromMilliseconds(kVisibilitySlopMS) < wt->last_shown) |
216 return; // TODO(brettw) schedule thumbnail generation for this renderer in | 267 return; // TODO(brettw) schedule thumbnail generation for this renderer in |
217 // case we don't get a paint for it after the time slop, but it's | 268 // case we don't get a paint for it after the time slop, but it's |
218 // still visible. | 269 // still visible. |
219 | 270 |
220 // Clear the thumbnail, since it's now out of date. | 271 // Clear the thumbnail, since it's now out of date. |
221 wt->thumbnail = SkBitmap(); | 272 wt->thumbnail = SkBitmap(); |
222 } | 273 } |
223 | 274 |
| 275 void ThumbnailGenerator::WidgetDidReceivePaintAtSizeAck( |
| 276 RenderWidgetHost* widget, |
| 277 const TransportDIB::Handle& dib_handle, |
| 278 const gfx::Size& size) { |
| 279 // Lookup the callback, run it, and erase it. |
| 280 ThumbnailCallbackMap::iterator item = callback_map_.find(dib_handle); |
| 281 if (item != callback_map_.end()) { |
| 282 TransportDIB* dib = item->second->thumbnail_dib.get(); |
| 283 DCHECK(dib); |
| 284 if (!dib) { |
| 285 return; |
| 286 } |
| 287 |
| 288 // Create an SkBitmap from the DIB. |
| 289 SkBitmap non_owned_bitmap; |
| 290 SkBitmap result; |
| 291 |
| 292 // Fill out the non_owned_bitmap with the right config. Note that |
| 293 // this code assumes that the transport dib is a 32-bit ARGB |
| 294 // image. |
| 295 non_owned_bitmap.setConfig(SkBitmap::kARGB_8888_Config, |
| 296 size.width(), size.height()); |
| 297 non_owned_bitmap.setPixels(dib->memory()); |
| 298 |
| 299 // Now alloc/copy the memory so we own it and can pass it around, |
| 300 // and the memory won't go away when the DIB goes away. |
| 301 // TODO: Figure out a way to avoid this copy? |
| 302 non_owned_bitmap.copyTo(&result, SkBitmap::kARGB_8888_Config); |
| 303 |
| 304 item->second->callback->Run(result); |
| 305 |
| 306 // We're done with the callback, and with the DIB, so delete both. |
| 307 callback_map_.erase(item); |
| 308 } |
| 309 } |
| 310 |
224 void ThumbnailGenerator::Observe(NotificationType type, | 311 void ThumbnailGenerator::Observe(NotificationType type, |
225 const NotificationSource& source, | 312 const NotificationSource& source, |
226 const NotificationDetails& details) { | 313 const NotificationDetails& details) { |
227 switch (type.value) { | 314 switch (type.value) { |
228 case NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB: { | 315 case NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB: { |
229 // Install our observer for all new RVHs. | 316 // Install our observer for all new RVHs. |
230 RenderViewHost* renderer = Details<RenderViewHost>(details).ptr(); | 317 RenderViewHost* renderer = Details<RenderViewHost>(details).ptr(); |
231 renderer->set_painting_observer(this); | 318 renderer->set_painting_observer(this); |
232 break; | 319 break; |
233 } | 320 } |
234 | 321 |
235 case NotificationType::RENDER_WIDGET_VISIBILITY_CHANGED: | 322 case NotificationType::RENDER_WIDGET_VISIBILITY_CHANGED: |
236 if (*Details<bool>(details).ptr()) | 323 if (*Details<bool>(details).ptr()) |
237 WidgetShown(Source<RenderWidgetHost>(source).ptr()); | 324 WidgetShown(Source<RenderWidgetHost>(source).ptr()); |
238 else | 325 else |
239 WidgetHidden(Source<RenderWidgetHost>(source).ptr()); | 326 WidgetHidden(Source<RenderWidgetHost>(source).ptr()); |
240 break; | 327 break; |
241 | 328 |
242 case NotificationType::RENDER_WIDGET_HOST_DESTROYED: | 329 case NotificationType::RENDER_WIDGET_HOST_DESTROYED: |
243 WidgetDestroyed(Source<RenderWidgetHost>(source).ptr()); | 330 WidgetDestroyed(Source<RenderWidgetHost>(source).ptr()); |
244 break; | 331 break; |
245 | 332 |
| 333 case NotificationType::TAB_CONTENTS_DISCONNECTED: |
| 334 TabContentsDisconnected(Source<TabContents>(source).ptr()); |
| 335 break; |
| 336 |
246 default: | 337 default: |
247 NOTREACHED(); | 338 NOTREACHED(); |
248 } | 339 } |
249 } | 340 } |
250 | 341 |
251 void ThumbnailGenerator::WidgetShown(RenderWidgetHost* widget) { | 342 void ThumbnailGenerator::WidgetShown(RenderWidgetHost* widget) { |
252 WidgetThumbnail* wt = GetDataForHost(widget); | 343 WidgetThumbnail* wt = GetDataForHost(widget); |
253 wt->last_shown = base::TimeTicks::Now(); | 344 wt->last_shown = base::TimeTicks::Now(); |
254 | 345 |
255 // If there is no thumbnail (like we're displaying a background tab for the | 346 // If there is no thumbnail (like we're displaying a background tab for the |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
287 // make a new one. | 378 // make a new one. |
288 if (!wt->thumbnail.isNull()) | 379 if (!wt->thumbnail.isNull()) |
289 return; | 380 return; |
290 wt->thumbnail = GetThumbnailForRenderer(widget); | 381 wt->thumbnail = GetThumbnailForRenderer(widget); |
291 } | 382 } |
292 | 383 |
293 void ThumbnailGenerator::WidgetDestroyed(RenderWidgetHost* widget) { | 384 void ThumbnailGenerator::WidgetDestroyed(RenderWidgetHost* widget) { |
294 EraseHostFromShownList(widget); | 385 EraseHostFromShownList(widget); |
295 } | 386 } |
296 | 387 |
| 388 void ThumbnailGenerator::TabContentsDisconnected(TabContents* contents) { |
| 389 // Go through the existing callbacks, and find any that have the |
| 390 // same renderer as this TabContents and remove them so they don't |
| 391 // hang around. |
| 392 ThumbnailCallbackMap::iterator iterator = callback_map_.begin(); |
| 393 RenderWidgetHost* renderer = contents->render_view_host(); |
| 394 while (iterator != callback_map_.end()) { |
| 395 if (iterator->second->renderer == renderer) { |
| 396 callback_map_.erase(iterator); |
| 397 } |
| 398 ++iterator; |
| 399 } |
| 400 } |
| 401 |
297 void ThumbnailGenerator::ShownDelayHandler() { | 402 void ThumbnailGenerator::ShownDelayHandler() { |
298 base::TimeTicks threshold = base::TimeTicks::Now() - | 403 base::TimeTicks threshold = base::TimeTicks::Now() - |
299 base::TimeDelta::FromMilliseconds(kVisibilitySlopMS); | 404 base::TimeDelta::FromMilliseconds(kVisibilitySlopMS); |
300 | 405 |
301 // Check the list of all pending RWHs (normally only one) to see if any of | 406 // Check the list of all pending RWHs (normally only one) to see if any of |
302 // their times have expired. | 407 // their times have expired. |
303 for (size_t i = 0; i < shown_hosts_.size(); i++) { | 408 for (size_t i = 0; i < shown_hosts_.size(); i++) { |
304 WidgetThumbnail* wt = GetDataForHost(shown_hosts_[i]); | 409 WidgetThumbnail* wt = GetDataForHost(shown_hosts_[i]); |
305 if (no_timeout_ || wt->last_shown <= threshold) { | 410 if (no_timeout_ || wt->last_shown <= threshold) { |
306 // This thumbnail has expired, delete it. | 411 // This thumbnail has expired, delete it. |
(...skipping 12 matching lines...) Expand all Loading... |
319 &ThumbnailGenerator::ShownDelayHandler); | 424 &ThumbnailGenerator::ShownDelayHandler); |
320 } | 425 } |
321 } | 426 } |
322 | 427 |
323 void ThumbnailGenerator::EraseHostFromShownList(RenderWidgetHost* widget) { | 428 void ThumbnailGenerator::EraseHostFromShownList(RenderWidgetHost* widget) { |
324 std::vector<RenderWidgetHost*>::iterator found = | 429 std::vector<RenderWidgetHost*>::iterator found = |
325 std::find(shown_hosts_.begin(), shown_hosts_.end(), widget); | 430 std::find(shown_hosts_.begin(), shown_hosts_.end(), widget); |
326 if (found != shown_hosts_.end()) | 431 if (found != shown_hosts_.end()) |
327 shown_hosts_.erase(found); | 432 shown_hosts_.erase(found); |
328 } | 433 } |
OLD | NEW |