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