diff options
author | brettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-16 21:41:02 +0000 |
---|---|---|
committer | brettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-16 21:41:02 +0000 |
commit | 67a46b7f6da205b7a1e772b7383bb20bab8ca611 (patch) | |
tree | 6345ab0ffb453906e3e4f8f87514b1e21649e6b6 | |
parent | 3cc848c2747c8bc2ecede8e8df4eeb343d3d8988 (diff) | |
download | chromium_src-67a46b7f6da205b7a1e772b7383bb20bab8ca611.zip chromium_src-67a46b7f6da205b7a1e772b7383bb20bab8ca611.tar.gz chromium_src-67a46b7f6da205b7a1e772b7383bb20bab8ca611.tar.bz2 |
Adds kind-of-live thumbnail generation for a potential tab switcher.
This listens to tab events and tries to keep thumbnails ready to go. See
thumbnail_generator.cc for a more detailed design.
This adds a painting observer to the RenderWidgetHost to enable this new
behavior, as well as a notification to allow the thumbnail generator to hook
its observer in. There is also a new notification that a backing store has been
disabled, which required making the backing stores know about their owning
widget hosts.
This component is currently disabled. We just need to uncomment the member in
Profile and it will start to work.
Original review: http://codereview.chromium.org/118420
Review URL: http://codereview.chromium.org/126101
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@18540 0039d316-1c4b-4281-b951-d872f2087c98
34 files changed, 872 insertions, 138 deletions
diff --git a/chrome/DEPS b/chrome/DEPS index 3c413c6..bb61606 100644..100755 --- a/chrome/DEPS +++ b/chrome/DEPS @@ -24,8 +24,8 @@ include_rules = [ # Allow usage of Google Toolbox for Mac. "+third_party/GTM", - # Brett's test. Contact him for questions. - "+frame_window", + # Our Skia extensions. + "+skia/ext", # On Linux, we include some breakpad headers "+breakpad/linux", diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS index 90a473f..a18a7a56 100644..100755 --- a/chrome/browser/DEPS +++ b/chrome/browser/DEPS @@ -8,7 +8,6 @@ include_rules = [ "+grit", # For generated headers "+sandbox/src", "+skia/include", - "+skia/ext", "+webkit/default_plugin", "+webkit/glue", # Defines some types that are marshalled over IPC. diff --git a/chrome/browser/browser_process_impl.h b/chrome/browser/browser_process_impl.h index f056476..27ce62acd 100644 --- a/chrome/browser/browser_process_impl.h +++ b/chrome/browser/browser_process_impl.h @@ -18,6 +18,7 @@ #include "base/scoped_ptr.h" #include "chrome/browser/automation/automation_provider_list.h" #include "chrome/browser/browser_process.h" +#include "chrome/browser/tab_contents/thumbnail_generator.h" #if defined(OS_WIN) #include "sandbox/src/sandbox.h" @@ -270,6 +271,16 @@ class BrowserProcessImpl : public BrowserProcess, public NonThreadSafe { bool checked_for_new_frames_; bool using_new_frames_; +#if defined(LINUX2) + // TODO(brettw) enable this for all builds when we have a need for it. This + // component has some overhead, so we don't want to have it running without + // any consumers. Since it integrates by listening to notifications, it's + // sufficient to just not instatiate it to make it disabled. + + // This service just sits around and makes thumanails for tabs. + ThumbnailGenerator thumbnail_generator_; +#endif + // An event that notifies when we are shutting-down. scoped_ptr<base::WaitableEvent> shutdown_event_; diff --git a/chrome/browser/profile.cc b/chrome/browser/profile.cc index b19fb2d7..3e8b196 100644 --- a/chrome/browser/profile.cc +++ b/chrome/browser/profile.cc @@ -13,6 +13,7 @@ #include "chrome/browser/bookmarks/bookmark_model.h" #include "chrome/browser/browser_list.h" #include "chrome/browser/browser_process.h" +#include "chrome/browser/browser_theme_provider.h" #include "chrome/browser/download/download_manager.h" #include "chrome/browser/extensions/extension_process_manager.h" #include "chrome/browser/extensions/extensions_service.h" @@ -29,7 +30,6 @@ #include "chrome/browser/spellchecker.h" #include "chrome/browser/ssl/ssl_host_state.h" #include "chrome/browser/thumbnail_store.h" -#include "chrome/browser/browser_theme_provider.h" #include "chrome/browser/visitedlink_master.h" #include "chrome/browser/webdata/web_data_service.h" #include "chrome/common/chrome_constants.h" diff --git a/chrome/browser/renderer_host/backing_store.h b/chrome/browser/renderer_host/backing_store.h index 5d817d0..0fb47ab 100644 --- a/chrome/browser/renderer_host/backing_store.h +++ b/chrome/browser/renderer_host/backing_store.h @@ -21,6 +21,7 @@ #endif class RenderWidgetHost; +class SkBitmap; class TransportDIB; // BackingStore ---------------------------------------------------------------- @@ -29,31 +30,31 @@ class TransportDIB; class BackingStore { public: #if defined(OS_WIN) || defined(OS_MACOSX) - explicit BackingStore(const gfx::Size& size); + BackingStore(RenderWidgetHost* widget, const gfx::Size& size); #elif defined(OS_LINUX) - // Create a backing store on the X server. - // size: the size of the server-side pixmap - // x_connection: the display to target - // depth: the depth of the X window which will be drawn into - // visual: An Xlib Visual describing the format of the target window - // root_window: The X id of the root window - // use_render: if true, the X server supports Xrender - // use_shared_memory: if true, the X server is local - BackingStore(const gfx::Size& size, Display* x_connection, int depth, - void* visual, XID root_window, bool use_render, - bool use_shared_memory); + // Create a backing store on the X server. The visual is an Xlib Visual + // describing the format of the target window and the depth is the color + // depth of the X window which will be drawn into. + BackingStore(RenderWidgetHost* widget, + const gfx::Size& size, + void* visual, + int depth); + // This is for unittesting only. An object constructed using this constructor // will silently ignore all paints - explicit BackingStore(const gfx::Size& size); + BackingStore(RenderWidgetHost* widget, const gfx::Size& size); #endif ~BackingStore(); + RenderWidgetHost* render_widget_host() const { return render_widget_host_; } const gfx::Size& size() { return size_; } #if defined(OS_WIN) HDC hdc() { return hdc_; } + #elif defined(OS_MACOSX) skia::PlatformCanvas* canvas() { return &canvas_; } + #elif defined(OS_LINUX) // Copy from the server-side backing store to the target window // display: the display of the backing store and target window @@ -77,6 +78,9 @@ class BackingStore { const gfx::Size& view_size); private: + // The owner of this backing store. + RenderWidgetHost* render_widget_host_; + // The size of the backing store. gfx::Size size_; diff --git a/chrome/browser/renderer_host/backing_store_mac.cc b/chrome/browser/renderer_host/backing_store_mac.cc index 569e3f6..2395a6c 100644 --- a/chrome/browser/renderer_host/backing_store_mac.cc +++ b/chrome/browser/renderer_host/backing_store_mac.cc @@ -10,8 +10,9 @@ #include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkCanvas.h" -BackingStore::BackingStore(const gfx::Size& size) - : size_(size) { +BackingStore::BackingStore(RenderWidgetHost* widget, const gfx::Size& size) + : render_widget_host_(widget), + size_(size) { if (!canvas_.initialize(size.width(), size.height(), true)) SK_CRASH(); } diff --git a/chrome/browser/renderer_host/backing_store_manager.cc b/chrome/browser/renderer_host/backing_store_manager.cc index 3ff2e76..7b16a43 100644 --- a/chrome/browser/renderer_host/backing_store_manager.cc +++ b/chrome/browser/renderer_host/backing_store_manager.cc @@ -7,6 +7,7 @@ #include "base/sys_info.h" #include "chrome/browser/renderer_host/backing_store.h" #include "chrome/browser/renderer_host/render_widget_host.h" +#include "chrome/browser/renderer_host/render_widget_host_painting_observer.h" #include "chrome/common/chrome_constants.h" namespace { @@ -15,35 +16,59 @@ typedef OwningMRUCache<RenderWidgetHost*, BackingStore*> BackingStoreCache; static BackingStoreCache* cache = NULL; // Returns the size of the backing store cache. -static int GetBackingStoreCacheSize() { +static size_t GetBackingStoreCacheSize() { // This uses a similar approach to GetMaxRendererProcessCount. The goal // is to reduce memory pressure and swapping on low-resource machines. - static const int kMaxDibCountByRamTier[] = { + static const size_t kMaxDibCountByRamTier[] = { 2, // less than 256MB 3, // 256MB 4, // 512MB 5 // 768MB and above }; - static int max_size = kMaxDibCountByRamTier[ + static size_t max_size = kMaxDibCountByRamTier[ std::min(base::SysInfo::AmountOfPhysicalMemoryMB() / 256, static_cast<int>(arraysize(kMaxDibCountByRamTier)) - 1)]; return max_size; } +// Expires the given backing store from the cache. +void ExpireBackingStoreAt(BackingStoreCache::iterator backing_store) { + RenderWidgetHost* rwh = backing_store->second->render_widget_host(); + if (rwh->painting_observer()) { + rwh->painting_observer()->WidgetWillDestroyBackingStore( + backing_store->first, + backing_store->second); + } + cache->Erase(backing_store); +} + // Creates the backing store for the host based on the dimensions passed in. // Removes the existing backing store if there is one. BackingStore* CreateBackingStore(RenderWidgetHost* host, const gfx::Size& backing_store_size) { + // Remove any existing backing store in case we're replacing it. BackingStoreManager::RemoveBackingStore(host); + size_t max_cache_size = GetBackingStoreCacheSize(); + if (max_cache_size > 0 && !cache) + cache = new BackingStoreCache(BackingStoreCache::NO_AUTO_EVICT); + + if (cache->size() >= max_cache_size) { + // Need to remove an old backing store to make room for the new one. We + // don't want to do this when the backing store is being replace by a new + // one for the same tab, but this case won't get called then: we'll have + // removed the onld one in the RemoveBackingStore above, and the cache + // won't be over-sized. + // + // Crazy C++ alert: rbegin.base() is a forward iterator pointing to end(), + // so we need to do -- to move one back to the actual last item. + ExpireBackingStoreAt(--cache->rbegin().base()); + } + BackingStore* backing_store = host->AllocBackingStore(backing_store_size); - int backing_store_cache_size = GetBackingStoreCacheSize(); - if (backing_store_cache_size > 0) { - if (!cache) - cache = new BackingStoreCache(backing_store_cache_size); + if (max_cache_size > 0) cache->Put(host, backing_store); - } return backing_store; } @@ -110,7 +135,6 @@ void BackingStoreManager::RemoveBackingStore(RenderWidgetHost* host) { BackingStoreCache::iterator it = cache->Peek(host); if (it == cache->end()) return; - cache->Erase(it); if (cache->empty()) { @@ -118,3 +142,12 @@ void BackingStoreManager::RemoveBackingStore(RenderWidgetHost* host) { cache = NULL; } } + +// static +bool BackingStoreManager::ExpireBackingStoreForTest(RenderWidgetHost* host) { + BackingStoreCache::iterator it = cache->Peek(host); + if (it == cache->end()) + return false; + ExpireBackingStoreAt(it); + return true; +} diff --git a/chrome/browser/renderer_host/backing_store_manager.h b/chrome/browser/renderer_host/backing_store_manager.h index 5b929f6..3078a93 100644 --- a/chrome/browser/renderer_host/backing_store_manager.h +++ b/chrome/browser/renderer_host/backing_store_manager.h @@ -58,6 +58,11 @@ class BackingStoreManager { // Removes the backing store for the host. static void RemoveBackingStore(RenderWidgetHost* host); + // Expires the given backing store. This emulates something getting evicted + // from the cache for the purpose of testing. Returns true if the host was + // removed, false if it wasn't found. + static bool ExpireBackingStoreForTest(RenderWidgetHost* host); + private: // Not intended for instantiation. BackingStoreManager() {} diff --git a/chrome/browser/renderer_host/backing_store_win.cc b/chrome/browser/renderer_host/backing_store_win.cc index 1241e42..b3a1839 100644 --- a/chrome/browser/renderer_host/backing_store_win.cc +++ b/chrome/browser/renderer_host/backing_store_win.cc @@ -25,8 +25,9 @@ HANDLE CreateDIB(HDC dc, int width, int height, int color_depth) { // BackingStore (Windows) ------------------------------------------------------ -BackingStore::BackingStore(const gfx::Size& size) - : size_(size), +BackingStore::BackingStore(RenderWidgetHost* widget, const gfx::Size& size) + : render_widget_host_(widget), + size_(size), backing_store_dib_(NULL), original_bitmap_(NULL) { HDC screen_dc = ::GetDC(NULL); diff --git a/chrome/browser/renderer_host/backing_store_x.cc b/chrome/browser/renderer_host/backing_store_x.cc index 6cb7c91..52fd7dc 100644 --- a/chrome/browser/renderer_host/backing_store_x.cc +++ b/chrome/browser/renderer_host/backing_store_x.cc @@ -24,41 +24,39 @@ // shared memory or over the wire, and XRENDER is used to convert them to the // correct format for the backing store. -BackingStore::BackingStore(const gfx::Size& size, - Display* display, - int depth, +BackingStore::BackingStore(RenderWidgetHost* widget, + const gfx::Size& size, void* visual, - Drawable root_window, - bool use_render, - bool use_shared_memory) - : size_(size), - display_(display), - use_shared_memory_(use_shared_memory), - use_render_(use_render), + int depth) + : render_widget_host_(widget), + size_(size), + display_(x11_util::GetXDisplay()), + use_shared_memory_(x11_util::QuerySharedMemorySupport(display_)), + use_render_(x11_util::QueryRenderSupport(display_)), visual_depth_(depth), - root_window_(root_window) { - const int width = size.width(); - const int height = size.height(); - + root_window_(x11_util::GetX11RootWindow()) { COMPILE_ASSERT(__BYTE_ORDER == __LITTLE_ENDIAN, assumes_little_endian); - pixmap_ = XCreatePixmap(display_, root_window, width, height, depth); + pixmap_ = XCreatePixmap(display_, root_window_, + size.width(), size.height(), depth); if (use_render_) { picture_ = XRenderCreatePicture( display_, pixmap_, - x11_util::GetRenderVisualFormat(display_, static_cast<Visual*>(visual)), - 0, NULL); + x11_util::GetRenderVisualFormat(display_, + static_cast<Visual*>(visual)), + 0, NULL); } else { picture_ = 0; - pixmap_bpp_ = x11_util::BitsPerPixelForPixmapDepth(display, depth); + pixmap_bpp_ = x11_util::BitsPerPixelForPixmapDepth(display_, depth); } pixmap_gc_ = XCreateGC(display_, pixmap_, 0, NULL); } -BackingStore::BackingStore(const gfx::Size& size) - : size_(size), +BackingStore::BackingStore(RenderWidgetHost* widget, const gfx::Size& size) + : render_widget_host_(widget), + size_(size), display_(NULL), use_shared_memory_(false), use_render_(false), diff --git a/chrome/browser/renderer_host/render_widget_host.cc b/chrome/browser/renderer_host/render_widget_host.cc index fccd45d..51d2b3b 100644 --- a/chrome/browser/renderer_host/render_widget_host.cc +++ b/chrome/browser/renderer_host/render_widget_host.cc @@ -11,6 +11,7 @@ #include "chrome/browser/renderer_host/backing_store_manager.h" #include "chrome/browser/renderer_host/render_process_host.h" #include "chrome/browser/renderer_host/render_widget_helper.h" +#include "chrome/browser/renderer_host/render_widget_host_painting_observer.h" #include "chrome/browser/renderer_host/render_widget_host_view.h" #include "chrome/common/notification_service.h" #include "chrome/common/render_messages.h" @@ -49,6 +50,7 @@ RenderWidgetHost::RenderWidgetHost(RenderProcessHost* process, : renderer_initialized_(false), view_(NULL), process_(process), + painting_observer_(NULL), routing_id_(routing_id), is_loading_(false), is_hidden_(false), @@ -142,6 +144,12 @@ void RenderWidgetHost::WasHidden() { // Tell the RenderProcessHost we were hidden. process_->WidgetHidden(); + + bool is_visible = false; + NotificationService::current()->Notify( + NotificationType::RENDER_WIDGET_VISIBILITY_CHANGED, + Source<RenderWidgetHost>(this), + Details<bool>(&is_visible)); } void RenderWidgetHost::WasRestored() { @@ -163,6 +171,12 @@ void RenderWidgetHost::WasRestored() { Send(new ViewMsg_WasRestored(routing_id_, needs_repainting)); process_->WidgetRestored(); + + bool is_visible = true; + NotificationService::current()->Notify( + NotificationType::RENDER_WIDGET_VISIBILITY_CHANGED, + Source<RenderWidgetHost>(this), + Details<bool>(&is_visible)); } void RenderWidgetHost::WasResized() { @@ -217,10 +231,13 @@ void RenderWidgetHost::SetIsLoading(bool is_loading) { view_->SetIsLoading(is_loading); } -BackingStore* RenderWidgetHost::GetBackingStore() { +BackingStore* RenderWidgetHost::GetBackingStore(bool force_create) { // We should not be asked to paint while we are hidden. If we are hidden, - // then it means that our consumer failed to call WasRestored. - DCHECK(!is_hidden_) << "GetBackingStore called while hidden!"; + // then it means that our consumer failed to call WasRestored. If we're not + // force creating the backing store, it's OK since we can feel free to give + // out our cached one if we have it. + DCHECK(!is_hidden_ || !force_create) << + "GetBackingStore called while hidden!"; // We should never be called recursively; this can theoretically lead to // infinite recursion and almost certainly leads to lower performance. @@ -230,6 +247,11 @@ BackingStore* RenderWidgetHost::GetBackingStore() { // We might have a cached backing store that we can reuse! BackingStore* backing_store = BackingStoreManager::GetBackingStore(this, current_size_); + if (!force_create) { + in_get_backing_store_ = false; + return backing_store; + } + // If we fail to find a backing store in the cache, send out a request // to the renderer to paint the view if required. if (!backing_store && !repaint_ack_pending_ && !resize_ack_pending_ && @@ -259,7 +281,6 @@ BackingStore* RenderWidgetHost::GetBackingStore() { BackingStore* RenderWidgetHost::AllocBackingStore(const gfx::Size& size) { if (!view_) return NULL; - return view_->AllocBackingStore(size); } @@ -549,6 +570,9 @@ void RenderWidgetHost::OnMsgPaintRect( } } + if (painting_observer_) + painting_observer_->WidgetDidUpdateBackingStore(this); + // Log the time delta for processing a paint message. TimeDelta delta = TimeTicks::Now() - paint_start; UMA_HISTOGRAM_TIMES("MPArch.RWH_OnMsgPaintRect", delta); @@ -595,6 +619,9 @@ void RenderWidgetHost::OnMsgScrollRect( view_being_painted_ = false; } + if (painting_observer_) + painting_observer_->WidgetDidUpdateBackingStore(this); + // Log the time delta for processing a scroll message. TimeDelta delta = TimeTicks::Now() - scroll_start; UMA_HISTOGRAM_TIMES("MPArch.RWH_OnMsgScrollRect", delta); diff --git a/chrome/browser/renderer_host/render_widget_host.h b/chrome/browser/renderer_host/render_widget_host.h index 0645496..c5913ef 100644 --- a/chrome/browser/renderer_host/render_widget_host.h +++ b/chrome/browser/renderer_host/render_widget_host.h @@ -14,6 +14,7 @@ #include "base/timer.h" #include "chrome/common/ipc_channel.h" #include "chrome/common/native_web_keyboard_event.h" +#include "chrome/common/property_bag.h" #include "testing/gtest/include/gtest/gtest_prod.h" #include "webkit/glue/webtextdirection.h" @@ -31,6 +32,7 @@ class BackingStore; class PaintObserver; class RenderProcessHost; class RenderWidgetHostView; +class RenderWidgetHostPaintingObserver; class TransportDIB; class WebCursor; struct ViewHostMsg_PaintRect_Params; @@ -140,6 +142,22 @@ class RenderWidgetHost : public IPC::Channel::Listener { paint_observer_.reset(paint_observer); } + // Returns the property bag for this widget, where callers can add extra data + // they may wish to associate with it. Returns a pointer rather than a + // reference since the PropertyAccessors expect this. + const PropertyBag* property_bag() const { return &property_bag_; } + PropertyBag* property_bag() { return &property_bag_; } + + // The painting observer that will be called for paint events. This + // pointer's ownership will remain with the caller and must remain valid + // until this class is destroyed or the observer is replaced. + RenderWidgetHostPaintingObserver* painting_observer() const { + return painting_observer_; + } + void set_painting_observer(RenderWidgetHostPaintingObserver* observer) { + painting_observer_ = observer; + } + // Called when a renderer object already been created for this host, and we // just need to be attached to it. Used for window.open, <select> dropdown // menus, and other times when the renderer initiates creating an object. @@ -182,9 +200,11 @@ class RenderWidgetHost : public IPC::Channel::Listener { // Get access to the widget's backing store. If a resize is in progress, // then the current size of the backing store may be less than the size of - // the widget's view. This method returns NULL if the backing store could - // not be created. - BackingStore* GetBackingStore(); + // the widget's view. If you pass |force_create| as true, then the backing + // store will be created if it doesn't exist. Otherwise, NULL will be returned + // if the backing store doesn't already exist. It will also return NULL if the + // backing store could not be created. + BackingStore* GetBackingStore(bool force_create); // Allocate a new backing store of the given size. Returns NULL on failure // (for example, if we don't currently have a RenderWidgetHostView.) @@ -358,6 +378,13 @@ class RenderWidgetHost : public IPC::Channel::Listener { // renderer crashed, so you must always check that. RenderProcessHost* process_; + // Stores random bits of data for others to associate with this object. + PropertyBag property_bag_; + + // Observer that will be called for paint events. This may be NULL. The + // pointer is not owned by this class. + RenderWidgetHostPaintingObserver* painting_observer_; + // The ID of the corresponding object in the Renderer Instance. int routing_id_; diff --git a/chrome/browser/renderer_host/render_widget_host_painting_observer.h b/chrome/browser/renderer_host/render_widget_host_painting_observer.h new file mode 100755 index 0000000..d24b61a --- /dev/null +++ b/chrome/browser/renderer_host/render_widget_host_painting_observer.h @@ -0,0 +1,24 @@ +// 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. + +#ifndef CHROME_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_PAINTING_OBSERVER_H_ +#define CHROME_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_PAINTING_OBSERVER_H_ + +class BackingStore; +class RenderWidgetHost; + +// This class can be used to observe painting events for a RenderWidgetHost. +// Its primary goal in Chrome is to allow thumbnails to be generated. +class RenderWidgetHostPaintingObserver { + public: + // Indicates the RenderWidgetHost is about to destroy the backing store. The + // backing store will still be valid when this call is made. + virtual void WidgetWillDestroyBackingStore(RenderWidgetHost* widget, + BackingStore* backing_store) = 0; + + // Indicates that the RenderWidgetHost just updated the backing store. + virtual void WidgetDidUpdateBackingStore(RenderWidgetHost* widget) = 0; +}; + +#endif // CHROME_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_PAINTING_OBSERVER_H_ diff --git a/chrome/browser/renderer_host/render_widget_host_unittest.cc b/chrome/browser/renderer_host/render_widget_host_unittest.cc index 083434d..f41aa29 100644 --- a/chrome/browser/renderer_host/render_widget_host_unittest.cc +++ b/chrome/browser/renderer_host/render_widget_host_unittest.cc @@ -100,7 +100,7 @@ bool RenderWidgetHostProcess::WaitForPaintMsg(int render_widget_id, // This test view allows us to specify the size. class TestView : public TestRenderWidgetHostView { public: - TestView() {} + TestView(RenderWidgetHost* rwh) : TestRenderWidgetHostView(rwh) {} // Sets the bounds returned by GetViewBounds. void set_bounds(const gfx::Rect& bounds) { @@ -112,10 +112,6 @@ class TestView : public TestRenderWidgetHostView { return bounds_; } - BackingStore* AllocBackingStore(const gfx::Size& size) { - return new BackingStore(size); - } - protected: gfx::Rect bounds_; DISALLOW_COPY_AND_ASSIGN(TestView); @@ -160,7 +156,7 @@ class RenderWidgetHostTest : public testing::Test { profile_.reset(new TestingProfile()); process_ = new RenderWidgetHostProcess(profile_.get()); host_.reset(new MockRenderWidgetHost(process_, 1)); - view_.reset(new TestView); + view_.reset(new TestView(host_.get())); host_->set_view(view_.get()); host_->Init(); } @@ -303,7 +299,7 @@ TEST_F(RenderWidgetHostTest, GetBackingStore_NoRepaintAck) { // We don't currently have a backing store, and if the renderer doesn't send // one in time, we should get nothing. process_->set_paint_msg_should_reply(false); - BackingStore* backing = host_->GetBackingStore(); + BackingStore* backing = host_->GetBackingStore(true); EXPECT_FALSE(backing); // The widget host should have sent a request for a repaint, and there should // be no paint ACK. @@ -315,7 +311,7 @@ TEST_F(RenderWidgetHostTest, GetBackingStore_NoRepaintAck) { process_->sink().ClearMessages(); process_->set_paint_msg_should_reply(true); process_->set_paint_msg_reply_flags(0); - backing = host_->GetBackingStore(); + backing = host_->GetBackingStore(true); EXPECT_TRUE(backing); // The widget host should NOT have sent a request for a repaint, since there // was an ACK already pending. @@ -331,7 +327,7 @@ TEST_F(RenderWidgetHostTest, GetBackingStore_RepaintAck) { process_->set_paint_msg_should_reply(true); process_->set_paint_msg_reply_flags( ViewHostMsg_PaintRect_Flags::IS_REPAINT_ACK); - BackingStore* backing = host_->GetBackingStore(); + BackingStore* backing = host_->GetBackingStore(true); EXPECT_TRUE(backing); // We still should not have sent out a repaint request since the last flags // didn't have the repaint ack set, and the pending flag will still be set. @@ -342,7 +338,7 @@ TEST_F(RenderWidgetHostTest, GetBackingStore_RepaintAck) { // Asking again for the backing store should just re-use the existing one // and not send any messagse. process_->sink().ClearMessages(); - backing = host_->GetBackingStore(); + backing = host_->GetBackingStore(true); EXPECT_TRUE(backing); EXPECT_FALSE(process_->sink().GetUniqueMessageMatching(ViewMsg_Repaint::ID)); EXPECT_FALSE(process_->sink().GetUniqueMessageMatching( diff --git a/chrome/browser/renderer_host/render_widget_host_view_gtk.cc b/chrome/browser/renderer_host/render_widget_host_view_gtk.cc index 4570d40..192ab0b 100644 --- a/chrome/browser/renderer_host/render_widget_host_view_gtk.cc +++ b/chrome/browser/renderer_host/render_widget_host_view_gtk.cc @@ -446,19 +446,6 @@ void RenderWidgetHostViewGtk::SelectionChanged(const std::string& text) { gtk_clipboard_set_text(x_clipboard, text.c_str(), text.length()); } -BackingStore* RenderWidgetHostViewGtk::AllocBackingStore( - const gfx::Size& size) { - Display* display = x11_util::GetXDisplay(); - void* visual = x11_util::GetVisualFromGtkWidget(view_.get()); - XID root_window = x11_util::GetX11RootWindow(); - bool use_render = x11_util::QueryRenderSupport(display); - bool use_shared_memory = x11_util::QuerySharedMemorySupport(display); - int depth = gtk_widget_get_visual(view_.get())->depth; - - return new BackingStore(size, display, depth, visual, root_window, - use_render, use_shared_memory); -} - void RenderWidgetHostViewGtk::PasteFromSelectionClipboard() { GtkClipboard* x_clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY); gtk_clipboard_request_text(x_clipboard, ReceivedSelectionText, this); @@ -472,12 +459,19 @@ void RenderWidgetHostViewGtk::ShowingContextMenu(bool showing) { GetRenderWidgetHost()->Blur(); } +BackingStore* RenderWidgetHostViewGtk::AllocBackingStore( + const gfx::Size& size) { + return new BackingStore(host_, size, + x11_util::GetVisualFromGtkWidget(view_.get()), + gtk_widget_get_visual(view_.get())->depth); +} + void RenderWidgetHostViewGtk::Paint(const gfx::Rect& damage_rect) { DCHECK(!about_to_validate_and_paint_); invalid_rect_ = damage_rect; about_to_validate_and_paint_ = true; - BackingStore* backing_store = host_->GetBackingStore(); + BackingStore* backing_store = host_->GetBackingStore(true); // Calling GetBackingStore maybe have changed |invalid_rect_|... about_to_validate_and_paint_ = false; diff --git a/chrome/browser/renderer_host/render_widget_host_view_gtk.h b/chrome/browser/renderer_host/render_widget_host_view_gtk.h index 0bed0c5..af0ac47 100644 --- a/chrome/browser/renderer_host/render_widget_host_view_gtk.h +++ b/chrome/browser/renderer_host/render_widget_host_view_gtk.h @@ -30,38 +30,34 @@ class RenderWidgetHostViewGtk : public RenderWidgetHostView { // Initialize this object for use as a drawing area. void InitAsChild(); - // --------------------------------------------------------------------------- - // Implementation of RenderWidgetHostView... - - void InitAsPopup(RenderWidgetHostView* parent_host_view, - const gfx::Rect& pos); - RenderWidgetHost* GetRenderWidgetHost() const { return host_; } - void DidBecomeSelected(); - void WasHidden(); - void SetSize(const gfx::Size& size); - gfx::NativeView GetNativeView(); - void MovePluginWindows( + // RenderWidgetHostView implementation. + virtual void InitAsPopup(RenderWidgetHostView* parent_host_view, + const gfx::Rect& pos); + virtual RenderWidgetHost* GetRenderWidgetHost() const { return host_; } + virtual void DidBecomeSelected(); + virtual void WasHidden(); + virtual void SetSize(const gfx::Size& size); + virtual gfx::NativeView GetNativeView(); + virtual void MovePluginWindows( const std::vector<WebPluginGeometry>& plugin_window_moves); - void Focus(); - void Blur(); - bool HasFocus(); - void Show(); - void Hide(); - gfx::Rect GetViewBounds() const; - void UpdateCursor(const WebCursor& cursor); - void SetIsLoading(bool is_loading); - void IMEUpdateStatus(int control, const gfx::Rect& caret_rect); - void DidPaintRect(const gfx::Rect& rect); - void DidScrollRect( - const gfx::Rect& rect, int dx, int dy); - void RenderViewGone(); - void Destroy(); - void SetTooltipText(const std::wstring& tooltip_text); - void SelectionChanged(const std::string& text); - void PasteFromSelectionClipboard(); - void ShowingContextMenu(bool showing); - BackingStore* AllocBackingStore(const gfx::Size& size); - // --------------------------------------------------------------------------- + virtual void Focus(); + virtual void Blur(); + virtual bool HasFocus(); + virtual void Show(); + virtual void Hide(); + virtual gfx::Rect GetViewBounds() const; + virtual void UpdateCursor(const WebCursor& cursor); + virtual void SetIsLoading(bool is_loading); + virtual void IMEUpdateStatus(int control, const gfx::Rect& caret_rect); + virtual void DidPaintRect(const gfx::Rect& rect); + virtual void DidScrollRect(const gfx::Rect& rect, int dx, int dy); + virtual void RenderViewGone(); + virtual void Destroy(); + virtual void SetTooltipText(const std::wstring& tooltip_text); + virtual void SelectionChanged(const std::string& text); + virtual void PasteFromSelectionClipboard(); + virtual void ShowingContextMenu(bool showing); + virtual BackingStore* AllocBackingStore(const gfx::Size& size); gfx::NativeView native_view() const { return view_.get(); } @@ -80,7 +76,8 @@ class RenderWidgetHostViewGtk : public RenderWidgetHostView { gpointer userdata); // The model object. - RenderWidgetHost *const host_; + RenderWidgetHost* const host_; + // The native UI widget. OwnedWidgetGtk view_; @@ -88,6 +85,7 @@ class RenderWidgetHostViewGtk : public RenderWidgetHostView { // paint requests by expanding the invalid rect rather than actually // painting. bool about_to_validate_and_paint_; + // This is the rectangle which we'll paint. gfx::Rect invalid_rect_; diff --git a/chrome/browser/renderer_host/render_widget_host_view_mac.mm b/chrome/browser/renderer_host/render_widget_host_view_mac.mm index 4580c2f..a18dd08 100644 --- a/chrome/browser/renderer_host/render_widget_host_view_mac.mm +++ b/chrome/browser/renderer_host/render_widget_host_view_mac.mm @@ -297,7 +297,7 @@ void RenderWidgetHostViewMac::SetTooltipText(const std::wstring& tooltip_text) { BackingStore* RenderWidgetHostViewMac::AllocBackingStore( const gfx::Size& size) { - return new BackingStore(size); + return new BackingStore(render_widget_host_, size); } // Display a popup menu for WebKit using Cocoa widgets. @@ -415,7 +415,7 @@ void RenderWidgetHostViewMac::ShutdownHost() { renderWidgetHostView_->invalid_rect_ = dirtyRect; renderWidgetHostView_->about_to_validate_and_paint_ = true; BackingStore* backing_store = - renderWidgetHostView_->render_widget_host_->GetBackingStore(); + renderWidgetHostView_->render_widget_host_->GetBackingStore(true); skia::PlatformCanvas* canvas = backing_store->canvas(); renderWidgetHostView_->about_to_validate_and_paint_ = false; dirtyRect = renderWidgetHostView_->invalid_rect_; diff --git a/chrome/browser/renderer_host/render_widget_host_view_win.cc b/chrome/browser/renderer_host/render_widget_host_view_win.cc index 9c33865..15bee47 100644 --- a/chrome/browser/renderer_host/render_widget_host_view_win.cc +++ b/chrome/browser/renderer_host/render_widget_host_view_win.cc @@ -650,7 +650,7 @@ void RenderWidgetHostViewWin::SetTooltipText(const std::wstring& tooltip_text) { BackingStore* RenderWidgetHostViewWin::AllocBackingStore( const gfx::Size& size) { - return new BackingStore(size); + return new BackingStore(render_widget_host_, size); } void RenderWidgetHostViewWin::SetBackground(const SkBitmap& background) { @@ -692,7 +692,7 @@ void RenderWidgetHostViewWin::OnPaint(HDC dc) { DCHECK(render_widget_host_->process()->channel()); about_to_validate_and_paint_ = true; - BackingStore* backing_store = render_widget_host_->GetBackingStore(); + BackingStore* backing_store = render_widget_host_->GetBackingStore(true); // We initialize |paint_dc| (and thus call BeginPaint()) after calling // GetBackingStore(), so that if it updates the invalid rect we'll catch the diff --git a/chrome/browser/renderer_host/test_render_view_host.cc b/chrome/browser/renderer_host/test_render_view_host.cc index 65dd323..982beb6 100644 --- a/chrome/browser/renderer_host/test_render_view_host.cc +++ b/chrome/browser/renderer_host/test_render_view_host.cc @@ -17,7 +17,7 @@ TestRenderViewHost::TestRenderViewHost(SiteInstance* instance, : RenderViewHost(instance, delegate, routing_id, modal_dialog_event), render_view_created_(false), delete_counter_(NULL) { - set_view(new TestRenderWidgetHostView()); + set_view(new TestRenderWidgetHostView(this)); } TestRenderViewHost::~TestRenderViewHost() { @@ -66,9 +66,14 @@ void TestRenderViewHost::SendNavigate(int page_id, const GURL& url) { OnMsgNavigate(msg); } +TestRenderWidgetHostView::TestRenderWidgetHostView(RenderWidgetHost* rwh) + : rwh_(rwh), + is_showing_(false) { +} + BackingStore* TestRenderWidgetHostView::AllocBackingStore( const gfx::Size& size) { - return new BackingStore(size); + return new BackingStore(rwh_, size); } void RenderViewHostTestHarness::NavigateAndCommit(const GURL& url) { diff --git a/chrome/browser/renderer_host/test_render_view_host.h b/chrome/browser/renderer_host/test_render_view_host.h index ac80096..37b100a 100644 --- a/chrome/browser/renderer_host/test_render_view_host.h +++ b/chrome/browser/renderer_host/test_render_view_host.h @@ -38,7 +38,7 @@ class TestTabContents; // without having side-effects. class TestRenderWidgetHostView : public RenderWidgetHostView { public: - TestRenderWidgetHostView() : is_showing_(false) {} + explicit TestRenderWidgetHostView(RenderWidgetHost* rwh); virtual void InitAsPopup(RenderWidgetHostView* parent_host_view, const gfx::Rect& pos) {} @@ -82,6 +82,7 @@ class TestRenderWidgetHostView : public RenderWidgetHostView { bool is_showing() const { return is_showing_; } private: + RenderWidgetHost* rwh_; bool is_showing_; }; diff --git a/chrome/browser/tab_contents/render_view_host_manager.cc b/chrome/browser/tab_contents/render_view_host_manager.cc index 710723f..68421a7 100644 --- a/chrome/browser/tab_contents/render_view_host_manager.cc +++ b/chrome/browser/tab_contents/render_view_host_manager.cc @@ -58,6 +58,10 @@ void RenderViewHostManager::Init(Profile* profile, site_instance = SiteInstance::CreateSiteInstance(profile); render_view_host_ = RenderViewHostFactory::Create( site_instance, render_view_delegate_, routing_id, modal_dialog_event); + NotificationService::current()->Notify( + NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB, + Source<RenderViewHostManager>(this), + Details<RenderViewHost>(render_view_host_)); } RenderViewHost* RenderViewHostManager::Navigate(const NavigationEntry& entry) { @@ -407,6 +411,10 @@ bool RenderViewHostManager::CreatePendingRenderView(SiteInstance* instance) { pending_render_view_host_ = RenderViewHostFactory::Create( instance, render_view_delegate_, MSG_ROUTING_NONE, NULL); + NotificationService::current()->Notify( + NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB, + Source<RenderViewHostManager>(this), + Details<RenderViewHost>(pending_render_view_host_)); bool success = delegate_->CreateRenderViewForRenderManager( pending_render_view_host_); diff --git a/chrome/browser/tab_contents/thumbnail_generator.cc b/chrome/browser/tab_contents/thumbnail_generator.cc new file mode 100755 index 0000000..4654b14 --- /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); +} diff --git a/chrome/browser/tab_contents/thumbnail_generator.h b/chrome/browser/tab_contents/thumbnail_generator.h new file mode 100755 index 0000000..76afb3c --- /dev/null +++ b/chrome/browser/tab_contents/thumbnail_generator.h @@ -0,0 +1,75 @@ +// 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. + +#ifndef CHROME_BROWSER_TAB_CONTENTS_THUMBNAIL_GENERATOR_H_ +#define CHROME_BROWSER_TAB_CONTENTS_THUMBNAIL_GENERATOR_H_ + +#include "base/basictypes.h" +#include "base/timer.h" +#include "chrome/browser/renderer_host/render_widget_host_painting_observer.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" + +class RenderWidgetHost; +class SkBitmap; + +// This class MUST be destroyed after the RenderWidgetHosts, since it installs +// a painting observer that is not removed. +class ThumbnailGenerator : public RenderWidgetHostPaintingObserver, + public NotificationObserver { + public: + ThumbnailGenerator(); + ~ThumbnailGenerator(); + + SkBitmap GetThumbnailForRenderer(RenderWidgetHost* renderer) const; + +#ifdef UNIT_TEST + // When true, the class will not use a timeout to do the expiration. This + // will cause expiration to happen on the next run of the message loop. + // Unit tests case use this to test expiration by choosing when the message + // loop runs. + void set_no_timeout(bool no_timeout) { no_timeout_ = no_timeout; } +#endif + + private: + // RenderWidgetHostPaintingObserver implementation. + virtual void WidgetWillDestroyBackingStore(RenderWidgetHost* widget, + BackingStore* backing_store); + virtual void WidgetDidUpdateBackingStore(RenderWidgetHost* widget); + + // NotificationObserver interface. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // Indicates that the given widget has changed is visibility. + void WidgetShown(RenderWidgetHost* widget); + void WidgetHidden(RenderWidgetHost* widget); + + // Called when the given widget is destroyed. + void WidgetDestroyed(RenderWidgetHost* widget); + + // Timer function called on a delay after a tab has been shown. It will + // invalidate the thumbnail for hosts with expired thumbnails in shown_hosts_. + void ShownDelayHandler(); + + // Removes the given host from the shown_hosts_ list, if it is there. + void EraseHostFromShownList(RenderWidgetHost* host); + + NotificationRegistrar registrar_; + + base::OneShotTimer<ThumbnailGenerator> timer_; + + // A list of all RWHs that have been shown and need to have their thumbnail + // expired at some time in the future with the "slop" time has elapsed. This + // list will normally have 0 or 1 items in it. + std::vector<RenderWidgetHost*> shown_hosts_; + + // See the setter above. + bool no_timeout_; + + DISALLOW_COPY_AND_ASSIGN(ThumbnailGenerator); +}; + +#endif // CHROME_BROWSER_TAB_CONTENTS_THUMBNAIL_GENERATOR_H_ diff --git a/chrome/browser/tab_contents/thumbnail_generator_unittest.cc b/chrome/browser/tab_contents/thumbnail_generator_unittest.cc new file mode 100755 index 0000000..53a17fc --- /dev/null +++ b/chrome/browser/tab_contents/thumbnail_generator_unittest.cc @@ -0,0 +1,179 @@ +// 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 "base/basictypes.h" +#include "chrome/browser/renderer_host/backing_store_manager.h" +#include "chrome/browser/renderer_host/mock_render_process_host.h" +#include "chrome/browser/renderer_host/test_render_view_host.h" +#include "chrome/browser/tab_contents/thumbnail_generator.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/render_messages.h" +#include "chrome/common/transport_dib.h" +#include "chrome/test/testing_profile.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "skia/ext/platform_canvas.h" +#include "third_party/skia/include/core/SkColorPriv.h" + +static const int kBitmapWidth = 100; +static const int kBitmapHeight = 100; + +// TODO(brettw) enable this when GetThumbnailForBackingStore is implemented +// for other platforms in thumbnail_generator.cc +//#if defined(OS_WIN) +// TODO(brettw) enable this on Windows after we clobber a build to see if the +// failures of this on the buildbot can be resolved. +#if 0 + +class ThumbnailGeneratorTest : public testing::Test { + public: + ThumbnailGeneratorTest() + : profile_(), + process_(new MockRenderProcessHost(&profile_)), + widget_(process_, 1), + view_(&widget_) { + // Paiting will be skipped if there's no view. + widget_.set_view(&view_); + + // Need to send out a create notification for the RWH to get hooked. This is + // a little scary in that we don't have a RenderView, but the only listener + // will want a RenderWidget, so it works out OK. + NotificationService::current()->Notify( + NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB, + Source<RenderViewHostManager>(NULL), + Details<RenderViewHost>(reinterpret_cast<RenderViewHost*>(&widget_))); + + transport_dib_.reset(TransportDIB::Create(kBitmapWidth * kBitmapHeight * 4, + 1)); + + // We don't want to be sensitive to timing. + generator_.set_no_timeout(true); + } + + protected: + // Indicates what bitmap should be sent with the paint message. _OTHER will + // only be retrned by CheckFirstPixel if the pixel is none of the others. + enum TransportType { TRANSPORT_BLACK, TRANSPORT_WHITE, TRANSPORT_OTHER }; + + void SendPaint(TransportType type) { + ViewHostMsg_PaintRect_Params params; + params.bitmap_rect = gfx::Rect(0, 0, kBitmapWidth, kBitmapHeight); + params.view_size = params.bitmap_rect.size(); + params.flags = 0; + + scoped_ptr<skia::PlatformCanvas> canvas( + transport_dib_->GetPlatformCanvas(kBitmapWidth, kBitmapHeight)); + switch (type) { + case TRANSPORT_BLACK: + canvas->getTopPlatformDevice().accessBitmap(true).eraseARGB( + 0xFF, 0, 0, 0); + break; + case TRANSPORT_WHITE: + canvas->getTopPlatformDevice().accessBitmap(true).eraseARGB( + 0xFF, 0xFF, 0xFF, 0xFF); + break; + case TRANSPORT_OTHER: + default: + NOTREACHED(); + break; + } + + params.bitmap = transport_dib_->id(); + + ViewHostMsg_PaintRect msg(1, params); + widget_.OnMessageReceived(msg); + } + + TransportType ClassifyFirstPixel(const SkBitmap& bitmap) { + // Returns the color of the first pixel of the bitmap. The bitmap must be + // non-empty. + SkAutoLockPixels lock(bitmap); + uint32 pixel = *bitmap.getAddr32(0, 0); + + if (SkGetPackedA32(pixel) != 0xFF) + return TRANSPORT_OTHER; // All values expect an opqaue alpha channel + + if (SkGetPackedR32(pixel) == 0 && + SkGetPackedG32(pixel) == 0 && + SkGetPackedB32(pixel) == 0) + return TRANSPORT_BLACK; + + if (SkGetPackedR32(pixel) == 0xFF && + SkGetPackedG32(pixel) == 0xFF && + SkGetPackedB32(pixel) == 0xFF) + return TRANSPORT_WHITE; + + EXPECT_TRUE(false) << "Got weird color: " << pixel; + return TRANSPORT_OTHER; + } + + MessageLoopForUI message_loop_; + + TestingProfile profile_; + + // This will get deleted when the last RHWH associated with it is destroyed. + MockRenderProcessHost* process_; + + RenderWidgetHost widget_; + TestRenderWidgetHostView view_; + ThumbnailGenerator generator_; + + scoped_ptr<TransportDIB> transport_dib_; + + private: + // testing::Test implementation. + void SetUp() { + } + void TearDown() { + } +}; + +TEST_F(ThumbnailGeneratorTest, NoThumbnail) { + // This is the case where there is no thumbnail available on the tab and + // there is no backing store. There should be no image returned. + SkBitmap result = generator_.GetThumbnailForRenderer(&widget_); + EXPECT_TRUE(result.isNull()); +} + +// Tests basic thumbnail generation when a backing store is discarded. +TEST_F(ThumbnailGeneratorTest, DiscardBackingStore) { + // First set up a backing store and then discard it. + SendPaint(TRANSPORT_BLACK); + widget_.WasHidden(); + ASSERT_TRUE(BackingStoreManager::ExpireBackingStoreForTest(&widget_)); + ASSERT_FALSE(widget_.GetBackingStore(false)); + + // The thumbnail generator should have stashed a thumbnail of the page. + SkBitmap result = generator_.GetThumbnailForRenderer(&widget_); + ASSERT_FALSE(result.isNull()); + EXPECT_EQ(TRANSPORT_BLACK, ClassifyFirstPixel(result)); +} + +TEST_F(ThumbnailGeneratorTest, QuickShow) { + // Set up a hidden widget with a black cached thumbnail and an expired + // backing store. + SendPaint(TRANSPORT_BLACK); + widget_.WasHidden(); + ASSERT_TRUE(BackingStoreManager::ExpireBackingStoreForTest(&widget_)); + ASSERT_FALSE(widget_.GetBackingStore(false)); + + // Now show the widget and paint white. + widget_.WasRestored(); + SendPaint(TRANSPORT_WHITE); + + // The black thumbnail should still be cached because it hasn't processed the + // timer message yet. + SkBitmap result = generator_.GetThumbnailForRenderer(&widget_); + ASSERT_FALSE(result.isNull()); + EXPECT_EQ(TRANSPORT_BLACK, ClassifyFirstPixel(result)); + + // Running the message loop will process the timer, which should expire the + // cached thumbnail. Asking again should give us a new one computed from the + // backing store. + message_loop_.RunAllPending(); + result = generator_.GetThumbnailForRenderer(&widget_); + ASSERT_FALSE(result.isNull()); + EXPECT_EQ(TRANSPORT_WHITE, ClassifyFirstPixel(result)); +} + +#endif diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 39e3de6..c5184bb 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -1396,6 +1396,8 @@ 'browser/tab_contents/tab_contents_view_mac.mm', 'browser/tab_contents/tab_util.cc', 'browser/tab_contents/tab_util.h', + 'browser/tab_contents/thumbnail_generator.cc', + 'browser/tab_contents/thumbnail_generator.h', 'browser/tab_contents/web_drag_source.cc', 'browser/tab_contents/web_drag_source.h', 'browser/tab_contents/web_drop_target.cc', @@ -3416,6 +3418,7 @@ 'browser/tab_contents/navigation_entry_unittest.cc', 'browser/tab_contents/render_view_host_manager_unittest.cc', 'browser/tab_contents/site_instance_unittest.cc', + 'browser/tab_contents/thumbnail_generator_unittest.cc', 'browser/tab_contents/web_contents_unittest.cc', 'browser/tabs/tab_strip_model_unittest.cc', 'browser/task_manager_unittest.cc', diff --git a/chrome/common/notification_type.h b/chrome/common/notification_type.h index 3c8b26a..678c942 100644 --- a/chrome/common/notification_type.h +++ b/chrome/common/notification_type.h @@ -286,6 +286,12 @@ class NotificationType { // Source<TabContents>. TAB_CONTENTS_DESTROYED, + // A RenderViewHost was created for a TabContents. The source is the + // RenderViewHostManager who owns it, and the details is the RenderViewHost + // pointer. Note that the source will be NULL in some cases for testing, + // when there is no RVHManager. + RENDER_VIEW_HOST_CREATED_FOR_TAB, + // Stuff inside the tabs --------------------------------------------------- // This message is sent after a constrained window has been closed. The @@ -326,6 +332,11 @@ class NotificationType { // the RenderWidgetHost, the details are not used. RENDER_WIDGET_HOST_DESTROYED, + // Indicates a RenderWidgetHost has been hidden or restored. The source is + // the RWH whose visibility changed, the details is a bool set to true if + // the new state is "visible." + RENDER_WIDGET_VISIBILITY_CHANGED, + // Notification from TabContents that we have received a response from the // renderer after using the dom inspector. DOM_INSPECT_ELEMENT_RESPONSE, diff --git a/chrome/common/property_bag.h b/chrome/common/property_bag.h index d2f3559..afc7bd3 100644 --- a/chrome/common/property_bag.h +++ b/chrome/common/property_bag.h @@ -131,7 +131,7 @@ class PropertyAccessor : public PropertyAccessorBase { PropertyAccessor() : PropertyAccessorBase() {} virtual ~PropertyAccessor() {} - // Takes ownership of the |prop| pointer. + // Makes a copy of the |prop| object for storage. void SetProperty(PropertyBag* bag, const T& prop) { SetPropertyInternal(bag, new Container(prop)); } diff --git a/chrome/common/transport_dib.h b/chrome/common/transport_dib.h index 33c0649..5712827 100644..100755 --- a/chrome/common/transport_dib.h +++ b/chrome/common/transport_dib.h @@ -20,6 +20,9 @@ namespace gfx { class Size; } +namespace skia { +class PlatformCanvas; +} // ----------------------------------------------------------------------------- // A TransportDIB is a block of memory that is used to transport pixels @@ -84,6 +87,11 @@ class TransportDIB { // Map the referenced transport DIB. Returns NULL on failure. static TransportDIB* Map(Handle transport_dib); + // Returns a canvas using the memory of this TransportDIB. The returned + // pointer will be owned by the caller. The bitmap will be of the given size, + // which should fit inside this memory. + skia::PlatformCanvas* GetPlatformCanvas(int w, int h); + // Return a pointer to the shared memory void* memory() const; diff --git a/chrome/common/transport_dib_linux.cc b/chrome/common/transport_dib_linux.cc index be673d9..1037341 100644..100755 --- a/chrome/common/transport_dib_linux.cc +++ b/chrome/common/transport_dib_linux.cc @@ -11,6 +11,7 @@ #include "base/logging.h" #include "chrome/common/transport_dib.h" #include "chrome/common/x11_util.h" +#include "skia/ext/platform_canvas.h" // The shmat system call uses this as it's invalid return address static void *const kInvalidAddress = (void*) -1; @@ -80,6 +81,11 @@ TransportDIB* TransportDIB::Map(Handle shmkey) { return dib; } +skia::PlatformCanvas* TransportDIB::GetPlatformCanvas(int w, int h) { + return new skia::PlatformCanvas(w, h, true, + reinterpret_cast<uint8_t*>(memory())); +} + void* TransportDIB::memory() const { DCHECK_NE(address_, kInvalidAddress); return address_; diff --git a/chrome/common/transport_dib_mac.cc b/chrome/common/transport_dib_mac.cc index b4c7a2a7..638866c 100644..100755 --- a/chrome/common/transport_dib_mac.cc +++ b/chrome/common/transport_dib_mac.cc @@ -9,6 +9,7 @@ #include "base/eintr_wrapper.h" #include "base/shared_memory.h" +#include "skia/ext/platform_canvas.h" TransportDIB::TransportDIB() : size_(0) { @@ -52,6 +53,11 @@ TransportDIB* TransportDIB::Map(TransportDIB::Handle handle) { return dib; } +skia::PlatformCanvas* TransportDIB::GetPlatformCanvas(int w, int h) { + return new skia::PlatformCanvas(w, h, true, + reinterpret_cast<uint8_t*>(dib->memory())); +} + void* TransportDIB::memory() const { return shared_memory_.memory(); } diff --git a/chrome/common/transport_dib_win.cc b/chrome/common/transport_dib_win.cc index 49cb755..41fe925 100644..100755 --- a/chrome/common/transport_dib_win.cc +++ b/chrome/common/transport_dib_win.cc @@ -8,6 +8,7 @@ #include "base/logging.h" #include "base/sys_info.h" #include "chrome/common/transport_dib.h" +#include "skia/ext/platform_canvas.h" TransportDIB::TransportDIB() { } @@ -59,6 +60,10 @@ TransportDIB* TransportDIB::Map(TransportDIB::Handle handle) { return dib; } +skia::PlatformCanvas* TransportDIB::GetPlatformCanvas(int w, int h) { + return new skia::PlatformCanvas(w, h, true, handle()); +} + void* TransportDIB::memory() const { return shared_memory_.memory(); } diff --git a/chrome/renderer/DEPS b/chrome/renderer/DEPS index cb1f386..d5673d2 100644..100755 --- a/chrome/renderer/DEPS +++ b/chrome/renderer/DEPS @@ -6,7 +6,6 @@ include_rules = [ "+media/base", "+media/filters", "+sandbox/src", - "+skia/ext", "+skia/include", "+webkit/default_plugin", "+webkit/extensions", diff --git a/chrome/renderer/render_process.cc b/chrome/renderer/render_process.cc index 6cfb56d..027b209 100644 --- a/chrome/renderer/render_process.cc +++ b/chrome/renderer/render_process.cc @@ -136,21 +136,6 @@ bool RenderProcess::InProcessPlugins() { // ----------------------------------------------------------------------------- // Platform specific code for dealing with bitmap transport... -// ----------------------------------------------------------------------------- -// Create a platform canvas object which renders into the given transport -// memory. -// ----------------------------------------------------------------------------- -static skia::PlatformCanvas* CanvasFromTransportDIB( - TransportDIB* dib, const gfx::Rect& rect) { -#if defined(OS_WIN) - return new skia::PlatformCanvas(rect.width(), rect.height(), true, - dib->handle()); -#elif defined(OS_LINUX) || defined(OS_MACOSX) - return new skia::PlatformCanvas(rect.width(), rect.height(), true, - reinterpret_cast<uint8_t*>(dib->memory())); -#endif -} - TransportDIB* RenderProcess::CreateTransportDIB(size_t size) { #if defined(OS_WIN) || defined(OS_LINUX) // Windows and Linux create transport DIBs inside the renderer @@ -196,7 +181,7 @@ skia::PlatformCanvas* RenderProcess::GetDrawingCanvas( return false; } - return CanvasFromTransportDIB(*memory, rect); + return (*memory)->GetPlatformCanvas(rect.width(), rect.height()); } void RenderProcess::ReleaseTransportDIB(TransportDIB* mem) { diff --git a/chrome/test/unit/unittests.vcproj b/chrome/test/unit/unittests.vcproj index 2e35b33..3a9cda1 100644 --- a/chrome/test/unit/unittests.vcproj +++ b/chrome/test/unit/unittests.vcproj @@ -810,6 +810,10 @@ > </File> <File + RelativePath="..\..\browser\tab_contents\thumbnail_generator_unittest.cc" + > + </File> + <File RelativePath="..\..\browser\thumbnail_store_unittest.cc" > </File> |