diff options
Diffstat (limited to 'chrome')
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> |