| Index: chrome/browser/tab_contents/thumbnail_generator.cc
 | 
| diff --git a/chrome/browser/tab_contents/thumbnail_generator.cc b/chrome/browser/tab_contents/thumbnail_generator.cc
 | 
| new file mode 100755
 | 
| index 0000000000000000000000000000000000000000..4654b14a994450c3adb4088e89e765f6d1fc0e13
 | 
| --- /dev/null
 | 
| +++ b/chrome/browser/tab_contents/thumbnail_generator.cc
 | 
| @@ -0,0 +1,321 @@
 | 
| +// 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;
 | 
| +
 | 
| +  // TODO(brettw) write this for other platforms. If you enable this, be sure
 | 
| +  // to also enable the unit tests for the same platform in
 | 
| +  // thumbnail_generator_unittest.cc
 | 
| +#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);
 | 
| +}
 | 
| 
 |