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