Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(15)

Unified Diff: chrome/browser/tab_contents/thumbnail_generator.cc

Issue 118420: Adds kind-of-live thumbnail generation for a potential tab switcher. This... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 11 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/tab_contents/thumbnail_generator.cc
===================================================================
--- chrome/browser/tab_contents/thumbnail_generator.cc (revision 0)
+++ chrome/browser/tab_contents/thumbnail_generator.cc (revision 0)
@@ -0,0 +1,318 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/tab_contents/thumbnail_generator.h"
+
+#include <algorithm>
+
+#include "base/histogram.h"
+#include "base/time.h"
+#include "chrome/browser/renderer_host/backing_store.h"
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/common/property_bag.h"
+#include "skia/ext/image_operations.h"
+#include "skia/ext/platform_canvas.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+// Overview
+// --------
+// This class provides current thumbnails for tabs. The simplest operation is
+// when a request for a thumbnail comes in, to grab the backing store and make
+// a smaller version of that.
+//
+// A complication happens because we don't always have nice backing stores for
+// all tabs (there is a cache of several tabs we'll keep backing stores for).
+// To get thumbnails for tabs with expired backing stores, we listen for
+// backing stores that are being thrown out, and generate thumbnails before
+// that happens. We attach them to the RenderWidgetHost via the property bag
+// so we can retrieve them later. When a tab has a live backing store again,
+// we throw away the thumbnail since it's now out-of-date.
+//
+// Another complication is performance. If the user brings up a tab switcher, we
+// don't want to get all 5 cached backing stores since it is a very large amount
+// of data. As a result, we generate thumbnails for tabs that are hidden even
+// if the backing store is still valid. This means we'll have to do a maximum
+// of generating thumbnails for the visible tabs at any point.
+//
+// The last performance consideration is when the user switches tabs quickly.
+// This can happen by doing Control-PageUp/Down or juct clicking quickly on
+// many different tabs (like when you're looking for one). We don't want to
+// slow this down by making thumbnails for each tab as it's hidden. Therefore,
+// we have a timer so that we don't invalidate thumbnails for tabs that are
+// only shown briefly (which would cause the thumbnail to be regenerated when
+// the tab is hidden).
+
+namespace {
+
+static const int kThumbnailWidth = 294;
+static const int kThumbnailHeight = 204;
+
+// Indicates the time that the RWH must be visible for us to update the
+// thumbnail on it. If the user holds down control enter, there will be a lot
+// of backing stores created and destroyed. WE don't want to interfere with
+// that.
+//
+// Any operation that happens within this time of being shown is ignored.
+// This means we won't throw the thumbnail away when the backing store is
+// painted in this time.
+static const int kVisibilitySlopMS = 3000;
+
+struct WidgetThumbnail {
+ SkBitmap thumbnail;
+
+ // Indicates the last time the RWH was shown and hidden.
+ base::TimeTicks last_shown;
+ base::TimeTicks last_hidden;
+};
+
+PropertyAccessor<WidgetThumbnail>* GetThumbnailAccessor() {
+ static PropertyAccessor<WidgetThumbnail> accessor;
+ return &accessor;
+}
+
+// Returns the existing WidgetThumbnail for a RVH, or creates a new one and
+// returns that if none exists.
+WidgetThumbnail* GetDataForHost(RenderWidgetHost* host) {
+ WidgetThumbnail* wt = GetThumbnailAccessor()->GetProperty(
+ host->property_bag());
+ if (wt)
+ return wt;
+
+ GetThumbnailAccessor()->SetProperty(host->property_bag(),
+ WidgetThumbnail());
+ return GetThumbnailAccessor()->GetProperty(host->property_bag());
+}
+
+// Creates a downsampled thumbnail for the given backing store. The returned
+// bitmap will be isNull if there was an error creating it.
+SkBitmap GetThumbnailForBackingStore(BackingStore* backing_store) {
+ SkBitmap result;
+
+#if defined(OS_WIN)
+ // Get the bitmap as a Skia object so we can resample it. This is a large
+ // allocation and we can tolerate failure here, so give up if the allocation
+ // fails.
+ base::TimeTicks begin_compute_thumbnail = base::TimeTicks::Now();
+
+ skia::PlatformCanvas temp_canvas;
+ if (!temp_canvas.initialize(backing_store->size().width(),
+ backing_store->size().height(), true))
+ return SkBitmap();
+ HDC temp_dc = temp_canvas.beginPlatformPaint();
+ BitBlt(temp_dc,
+ 0, 0, backing_store->size().width(), backing_store->size().height(),
+ backing_store->hdc(), 0, 0, SRCCOPY);
+ temp_canvas.endPlatformPaint();
+
+ // Get the bitmap out of the canvas and resample it.
+ const SkBitmap& bmp = temp_canvas.getTopPlatformDevice().accessBitmap(false);
+ result = skia::ImageOperations::DownsampleByTwoUntilSize(
+ bmp,
+ kThumbnailWidth, kThumbnailHeight);
+ if (bmp.width() == result.width() && bmp.height() == result.height()) {
+ // This is a bit subtle. SkBitmaps are refcounted, but the magic ones in
+ // PlatformCanvas can't be ssigned to SkBitmap with proper refcounting.
+ // If the bitmap doesn't change, then the downsampler will return the input
+ // bitmap, which will be the reference to the weird PlatformCanvas one
+ // insetad of a regular one. To get a regular refcounted bitmap, we need to
+ // copy it.
+ bmp.copyTo(&result, SkBitmap::kARGB_8888_Config);
+ }
+
+ HISTOGRAM_TIMES("Thumbnail.ComputeOnDestroyMS",
+ base::TimeTicks::Now() - begin_compute_thumbnail);
+#endif
+
+ return result;
+}
+
+} // namespace
+
+ThumbnailGenerator::ThumbnailGenerator()
+ : no_timeout_(false) {
+ // Even though we deal in RenderWidgetHosts, we only care about its subclass,
+ // RenderViewHost when it is in a tab. We don't make thumbnails for
+ // RenderViewHosts that aren't in tabs, or RenderWidgetHosts that aren't
+ // views like select popups.
+ registrar_.Add(this, NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB,
+ NotificationService::AllSources());
+
+ registrar_.Add(this, NotificationType::RENDER_WIDGET_VISIBILITY_CHANGED,
+ NotificationService::AllSources());
+ registrar_.Add(this, NotificationType::RENDER_WIDGET_HOST_DESTROYED,
+ NotificationService::AllSources());
+}
+
+ThumbnailGenerator::~ThumbnailGenerator() {
+}
+
+SkBitmap ThumbnailGenerator::GetThumbnailForRenderer(
+ RenderWidgetHost* renderer) const {
+ // Return a cached one if we have it and it's still valid. This will only be
+ // valid when there used to be a backing store, but there isn't now.
+ WidgetThumbnail* wt = GetThumbnailAccessor()->GetProperty(
+ renderer->property_bag());
+ if (wt && !wt->thumbnail.isNull() &&
+ (no_timeout_ ||
+ base::TimeTicks::Now() -
+ base::TimeDelta::FromMilliseconds(kVisibilitySlopMS) < wt->last_shown))
+ return wt->thumbnail;
+
+ BackingStore* backing_store = renderer->GetBackingStore(false);
+ if (!backing_store)
+ return SkBitmap();
+
+ // Save this thumbnail in case we need to use it again soon. It will be
+ // invalidated on the next paint.
+ wt->thumbnail = GetThumbnailForBackingStore(backing_store);
+ return wt->thumbnail;
+}
+
+void ThumbnailGenerator::WidgetWillDestroyBackingStore(
+ RenderWidgetHost* widget,
+ BackingStore* backing_store) {
+ // Since the backing store is going away, we need to save it as a thumbnail.
+ WidgetThumbnail* wt = GetDataForHost(widget);
+
+ // If there is already a thumbnail on the RWH that's visible, it means that
+ // not enough time has elapsed since being shown, and we can ignore generating
+ // a new one.
+ if (!wt->thumbnail.isNull())
+ return;
+
+ // Save a scaled-down image of the page in case we're asked for the thumbnail
+ // when there is no RenderViewHost. If this fails, we don't want to overwrite
+ // an existing thumbnail.
+ SkBitmap new_thumbnail = GetThumbnailForBackingStore(backing_store);
+ if (!new_thumbnail.isNull())
+ wt->thumbnail = new_thumbnail;
+}
+
+void ThumbnailGenerator::WidgetDidUpdateBackingStore(
+ RenderWidgetHost* widget) {
+ // Clear the current thumbnail since it's no longer valid.
+ WidgetThumbnail* wt = GetThumbnailAccessor()->GetProperty(
+ widget->property_bag());
+ if (!wt)
+ return; // Nothing to do.
+
+ // If this operation is within the time slop after being shown, keep the
+ // existing thumbnail.
+ if (no_timeout_ ||
+ base::TimeTicks::Now() -
+ base::TimeDelta::FromMilliseconds(kVisibilitySlopMS) < wt->last_shown)
+ return; // TODO(brettw) schedule thumbnail generation for this renderer in
+ // case we don't get a paint for it after the time slop, but it's
+ // still visible.
+
+ // Clear the thumbnail, since it's now out of date.
+ wt->thumbnail = SkBitmap();
+}
+
+void ThumbnailGenerator::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ switch (type.value) {
+ case NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB: {
+ // Install our observer for all new RVHs.
+ RenderViewHost* renderer = Details<RenderViewHost>(details).ptr();
+ renderer->set_painting_observer(this);
+ break;
+ }
+
+ case NotificationType::RENDER_WIDGET_VISIBILITY_CHANGED:
+ if (*Details<bool>(details).ptr())
+ WidgetShown(Source<RenderWidgetHost>(source).ptr());
+ else
+ WidgetHidden(Source<RenderWidgetHost>(source).ptr());
+ break;
+
+ case NotificationType::RENDER_WIDGET_HOST_DESTROYED:
+ WidgetDestroyed(Source<RenderWidgetHost>(source).ptr());
+ break;
+
+ default:
+ NOTREACHED();
+ }
+}
+
+void ThumbnailGenerator::WidgetShown(RenderWidgetHost* widget) {
+ WidgetThumbnail* wt = GetDataForHost(widget);
+ wt->last_shown = base::TimeTicks::Now();
+
+ // If there is no thumbnail (like we're displaying a background tab for the
+ // first time), then we don't have do to invalidate the existing one.
+ if (wt->thumbnail.isNull())
+ return;
+
+ std::vector<RenderWidgetHost*>::iterator found =
+ std::find(shown_hosts_.begin(), shown_hosts_.end(), widget);
+ if (found != shown_hosts_.end()) {
+ NOTREACHED() << "Showing a RWH we already think is shown";
+ shown_hosts_.erase(found);
+ }
+ shown_hosts_.push_back(widget);
+
+ // Keep the old thumbnail for a small amount of time after the tab has been
+ // shown. This is so in case it's hidden quickly again, we don't waste any
+ // work regenerating it.
+ if (timer_.IsRunning())
+ return;
+ timer_.Start(base::TimeDelta::FromMilliseconds(
+ no_timeout_ ? 0 : kVisibilitySlopMS),
+ this, &ThumbnailGenerator::ShownDelayHandler);
+}
+
+void ThumbnailGenerator::WidgetHidden(RenderWidgetHost* widget) {
+ WidgetThumbnail* wt = GetDataForHost(widget);
+ wt->last_hidden = base::TimeTicks::Now();
+
+ // If the tab is on the list of ones to invalidate the thumbnail, we need to
+ // remove it.
+ EraseHostFromShownList(widget);
+
+ // There may still be a valid cached thumbnail on the RWH, so we don't need to
+ // make a new one.
+ if (!wt->thumbnail.isNull())
+ return;
+ wt->thumbnail = GetThumbnailForRenderer(widget);
+}
+
+void ThumbnailGenerator::WidgetDestroyed(RenderWidgetHost* widget) {
+ EraseHostFromShownList(widget);
+}
+
+void ThumbnailGenerator::ShownDelayHandler() {
+ base::TimeTicks threshold = base::TimeTicks::Now() -
+ base::TimeDelta::FromMilliseconds(kVisibilitySlopMS);
+
+ // Check the list of all pending RWHs (normally only one) to see if any of
+ // their times have expired.
+ for (size_t i = 0; i < shown_hosts_.size(); i++) {
+ WidgetThumbnail* wt = GetDataForHost(shown_hosts_[i]);
+ if (no_timeout_ || wt->last_shown <= threshold) {
+ // This thumbnail has expired, delete it.
+ wt->thumbnail = SkBitmap();
+ shown_hosts_.erase(shown_hosts_.begin() + i);
+ i--;
+ }
+ }
+
+ // We need to schedule another run if there are still items in the list to
+ // process. We use half the timeout for these re-runs to catch the items
+ // that were added since the timer was run the first time.
+ if (!shown_hosts_.empty()) {
+ DCHECK(!no_timeout_);
+ timer_.Start(base::TimeDelta::FromMilliseconds(kVisibilitySlopMS) / 2, this,
+ &ThumbnailGenerator::ShownDelayHandler);
+ }
+}
+
+void ThumbnailGenerator::EraseHostFromShownList(RenderWidgetHost* widget) {
+ std::vector<RenderWidgetHost*>::iterator found =
+ std::find(shown_hosts_.begin(), shown_hosts_.end(), widget);
+ if (found != shown_hosts_.end())
+ shown_hosts_.erase(found);
+}
Property changes on: chrome\browser\tab_contents\thumbnail_generator.cc
___________________________________________________________________
Added: svn:eol-style
+ LF
« no previous file with comments | « chrome/browser/tab_contents/thumbnail_generator.h ('k') | chrome/browser/tab_contents/thumbnail_generator_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698