diff options
Diffstat (limited to 'chrome/browser')
18 files changed, 1129 insertions, 90 deletions
diff --git a/chrome/browser/browser_init.cc b/chrome/browser/browser_init.cc index a5e1706..8da58f6 100644 --- a/chrome/browser/browser_init.cc +++ b/chrome/browser/browser_init.cc @@ -21,6 +21,7 @@ #include "chrome/browser/extensions/extensions_service.h" #include "chrome/browser/first_run.h" #include "chrome/browser/net/dns_global.h" +#include "chrome/browser/net/url_fixer_upper.h" #include "chrome/browser/notifications/desktop_notification_service.h" #include "chrome/browser/pref_service.h" #include "chrome/browser/profile.h" @@ -30,13 +31,12 @@ #include "chrome/browser/sessions/session_restore.h" #include "chrome/browser/sessions/session_service.h" #include "chrome/browser/shell_integration.h" -#include "chrome/browser/tabs/pinned_tab_codec.h" +#include "chrome/browser/status_icons/status_tray_manager.h" #include "chrome/browser/tab_contents/infobar_delegate.h" #include "chrome/browser/tab_contents/navigation_controller.h" #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/browser/tab_contents/tab_contents_view.h" -#include "chrome/browser/net/url_fixer_upper.h" -#include "chrome/browser/status_icons/status_tray_manager.h" +#include "chrome/browser/tabs/pinned_tab_codec.h" #include "chrome/browser/user_data_manager.h" #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_paths.h" @@ -68,7 +68,6 @@ #if defined(OS_CHROMEOS) #include "chrome/browser/chromeos/browser_notification_observers.h" -#include "chrome/browser/dom_ui/mediaplayer_ui.h" #include "chrome/browser/chromeos/cros/cros_library.h" #include "chrome/browser/chromeos/cros/mount_library.h" #include "chrome/browser/chromeos/cros/power_library.h" @@ -76,6 +75,8 @@ #include "chrome/browser/chromeos/low_battery_observer.h" #include "chrome/browser/chromeos/usb_mount_observer.h" #include "chrome/browser/chromeos/wm_message_listener.h" +#include "chrome/browser/chromeos/wm_overview_controller.h" +#include "chrome/browser/dom_ui/mediaplayer_ui.h" #endif namespace { @@ -398,6 +399,9 @@ bool BrowserInit::LaunchBrowser( // of what window has focus. chromeos::WmMessageListener::instance(); + // Create the WmOverviewController so it can register with the listener. + chromeos::WmOverviewController::instance(); + // Install the GView request interceptor that will redirect requests // of compatible documents (PDF, etc) to the GView document viewer. const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess(); diff --git a/chrome/browser/chromeos/frame/browser_view.cc b/chrome/browser/chromeos/frame/browser_view.cc index d6aff2f..b3925c1 100644 --- a/chrome/browser/chromeos/frame/browser_view.cc +++ b/chrome/browser/chromeos/frame/browser_view.cc @@ -6,6 +6,7 @@ #include <algorithm> #include <string> +#include <vector> #include "app/menus/simple_menu_model.h" #include "app/theme_provider.h" @@ -392,16 +393,29 @@ void BrowserView::Init() { otr_avatar_icon_->SetImage(GetOTRAvatarIcon()); otr_avatar_icon_->SetID(VIEW_ID_OTR_AVATAR); AddChildView(otr_avatar_icon_); + + // Make sure the window is set to the right type. + std::vector<int> params; + params.push_back(browser()->tab_count()); + params.push_back(browser()->selected_index()); + WmIpc::instance()->SetWindowType( + GTK_WIDGET(frame()->GetWindow()->GetNativeWindow()), + WmIpc::WINDOW_TYPE_CHROME_TOPLEVEL, + ¶ms); } void BrowserView::Show() { bool was_visible = frame()->GetWindow()->IsVisible(); ::BrowserView::Show(); if (!was_visible) { + // Have to update the tab count and selected index to reflect reality. + std::vector<int> params; + params.push_back(browser()->tab_count()); + params.push_back(browser()->selected_index()); WmIpc::instance()->SetWindowType( GTK_WIDGET(frame()->GetWindow()->GetNativeWindow()), WmIpc::WINDOW_TYPE_CHROME_TOPLEVEL, - NULL); + ¶ms); } } diff --git a/chrome/browser/chromeos/panels/panel_browsertest.cc b/chrome/browser/chromeos/panels/panel_browsertest.cc index 1ec09b2..4b01924 100644 --- a/chrome/browser/chromeos/panels/panel_browsertest.cc +++ b/chrome/browser/chromeos/panels/panel_browsertest.cc @@ -64,7 +64,7 @@ IN_PROC_BROWSER_TEST_F(PanelTest, PanelOpenSmall) { EXPECT_EQ( WmIpc::WINDOW_TYPE_CHROME_PANEL_CONTENT, WmIpc::instance()->GetWindowType( - GTK_WIDGET(new_browser->window()->GetNativeHandle()))); + GTK_WIDGET(new_browser->window()->GetNativeHandle()), NULL)); } // Large popups should open as new tab. diff --git a/chrome/browser/chromeos/wm_ipc.cc b/chrome/browser/chromeos/wm_ipc.cc index da1ed06..f3e298b 100644 --- a/chrome/browser/chromeos/wm_ipc.cc +++ b/chrome/browser/chromeos/wm_ipc.cc @@ -84,12 +84,18 @@ bool WmIpc::SetWindowType(GtkWidget* widget, type_to_atom_[ATOM_CHROME_WINDOW_TYPE], values); } -WmIpc::WindowType WmIpc::GetWindowType(GtkWidget* widget) { - int type; - if (x11_util::GetIntProperty( +WmIpc::WindowType WmIpc::GetWindowType(GtkWidget* widget, + std::vector<int>* params) { + std::vector<int> properties; + if (x11_util::GetIntArrayProperty( x11_util::GetX11WindowFromGtkWidget(widget), atom_to_string_[type_to_atom_[ATOM_CHROME_WINDOW_TYPE]], - &type)) { + &properties)) { + int type = properties.front(); + if (params) { + params->clear(); + params->insert(params->begin(), properties.begin() + 1, properties.end()); + } return static_cast<WindowType>(type); } else { return WINDOW_TYPE_UNKNOWN; diff --git a/chrome/browser/chromeos/wm_ipc.h b/chrome/browser/chromeos/wm_ipc.h index 2d35c0b..7df76a29 100644 --- a/chrome/browser/chromeos/wm_ipc.h +++ b/chrome/browser/chromeos/wm_ipc.h @@ -41,20 +41,14 @@ class WmIpc { WINDOW_TYPE_UNKNOWN = 0, // A top-level Chrome window. + // param[0]: The number of tabs currently in this Chrome window. + // param[1]: The index of the currently selected tab in this + // Chrome window. WINDOW_TYPE_CHROME_TOPLEVEL, - // A window showing scaled-down views of all of the tabs within a - // Chrome window. - WINDOW_TYPE_CHROME_TAB_SUMMARY, - - // A tab that's been detached from a Chrome window and is currently - // being dragged. - // param[0]: Cursor's initial X position at the start of the drag - // param[1]: Cursor's initial Y position - // param[2]: X component of cursor's offset from upper-left corner of - // tab at start of drag - // param[3]: Y component of cursor's offset - WINDOW_TYPE_CHROME_FLOATING_TAB, + // Vestiges of the old windows-across-the-bottom overview mode. + DEPRECATED_WINDOW_TYPE_CHROME_TAB_SUMMARY, + DEPRECATED_WINDOW_TYPE_CHROME_FLOATING_TAB, // The contents of a popup window. // param[0]: X ID of associated titlebar, which must be mapped before @@ -66,8 +60,8 @@ class WmIpc { // drawn above the panel when it's expanded. WINDOW_TYPE_CHROME_PANEL_TITLEBAR, - // A small window that when clicked creates a new browser window. - WINDOW_TYPE_CREATE_BROWSER_WINDOW, + // Vestiges of an earlier UI design. + DEPRECATED_WINDOW_TYPE_CREATE_BROWSER_WINDOW, // A Chrome info bubble (e.g. the bookmark bubble). These are // transient RGBA windows; we skip the usual transient behavior of @@ -76,8 +70,7 @@ class WmIpc { // A window showing a view of a tab within a Chrome window. // param[0]: X ID of toplevel window that owns it. - // param[1]: index of this tab in the tab order (range is 0 to - // sum of all tabs in all browsers). + // param[1]: index of this tab in the toplevel window that owns it. WINDOW_TYPE_CHROME_TAB_SNAPSHOT, // The following types are used for the windows that represent a user that @@ -118,27 +111,10 @@ class WmIpc { enum Type { UNKNOWN = 0, - // Notify Chrome when a floating tab has entered or left a tab - // summary window. Sent to the summary window. - // param[0]: X ID of the floating tab window - // param[1]: state (0 means left, 1 means entered or currently in) - // param[2]: X coordinate relative to summary window - // param[3]: Y coordinate - CHROME_NOTIFY_FLOATING_TAB_OVER_TAB_SUMMARY, - - // Notify Chrome when a floating tab has entered or left a top-level - // window. Sent to the window being entered/left. - // param[0]: X ID of the floating tab window - // param[1]: state (0 means left, 1 means entered) - CHROME_NOTIFY_FLOATING_TAB_OVER_TOPLEVEL, - - // Instruct a top-level Chrome window to change the visibility of its - // tab summary window. - // param[0]: desired visibility (0 means hide, 1 means show) - // param[1]: X position (relative to the left edge of the root - // window) of the center of the top-level window. Only - // relevant for "show" messages - CHROME_SET_TAB_SUMMARY_VISIBILITY, + // Vestiges of the old windows-across-the-bottom overview mode. + DEPRECATED_CHROME_NOTIFY_FLOATING_TAB_OVER_TAB_SUMMARY, + DEPRECATED_CHROME_NOTIFY_FLOATING_TAB_OVER_TOPLEVEL, + DEPRECATED_CHROME_SET_TAB_SUMMARY_VISIBILITY, // Tell the WM to collapse or expand a panel. // param[0]: X ID of the panel window @@ -148,16 +124,12 @@ class WmIpc { // Notify Chrome that the panel state has changed. Sent to the panel // window. // param[0]: new state (0 means collapsed, 1 means expanded) + // TODO: Deprecate this; Chrome can just watch for changes to the + // _CHROME_STATE property to get the same information. CHROME_NOTIFY_PANEL_STATE, - // Instruct the WM to move a floating tab. The passed-in position is - // that of the cursor; the tab's composited window is displaced based - // on the cursor's offset from the upper-left corner of the tab at - // the start of the drag. - // param[0]: X ID of the floating tab window - // param[1]: X coordinate to which the tab should be moved - // param[2]: Y coordinate - WM_MOVE_FLOATING_TAB, + // From the old windows-across-the-bottom overview mode. + DEPRECATED_WM_MOVE_FLOATING_TAB, // Notify the WM that a panel has been dragged. // param[0]: X ID of the panel's content window @@ -180,18 +152,21 @@ class WmIpc { // param[0]: X ID of the panel's content window WM_NOTIFY_PANEL_DRAG_COMPLETE, - // Deprecated. Send a _NET_ACTIVE_WINDOW client message to focus a window - // instead (e.g. using gtk_window_present()). + // Deprecated. Send a _NET_ACTIVE_WINDOW client message to focus a + // window instead (e.g. using gtk_window_present()). DEPRECATED_WM_FOCUS_WINDOW, // Notify Chrome that the layout mode (for example, overview or - // focused) has changed. - // param[0]: new mode (0 means focused, 1 means overview) + // active) has changed. Since overview mode can be "cancelled" + // (user hits escape to revert), we have an extra parameter to + // indicate this. + // param[0]: new mode (0 means active mode, 1 means overview mode) + // param[1]: was mode cancelled? (0 = no, 1 = yes) CHROME_NOTIFY_LAYOUT_MODE, - // Instruct the WM to enter overview mode. + // Deprecated. Instruct the WM to enter overview mode. // param[0]: X ID of the window to show the tab overview for. - WM_SWITCH_TO_OVERVIEW_MODE, + DEPRECATED_WM_SWITCH_TO_OVERVIEW_MODE, // Let the WM know which version of this file Chrome is using. It's // difficult to make changes synchronously to Chrome and the WM (our @@ -218,11 +193,11 @@ class WmIpc { // param[0]: version of this protocol currently supported WM_NOTIFY_IPC_VERSION, - // Notify Chrome when a tab snapshot has been 'magnified' in the - // overview. Sent to the top level window. - // param[0]: X ID of the tab snapshot window - // param[1]: state (0 means end magnify, 1 means begin magnify) - CHROME_NOTIFY_TAB_SNAPSHOT_MAGNIFY, + // Notify Chrome when a tab has been selected in the overview. + // Sent to the toplevel window associated with the magnified + // tab. + // param[0]: tab index of newly selected tab. + CHROME_NOTIFY_TAB_SELECT, // Forces the window manager to hide the login windows. WM_HIDE_LOGIN, @@ -295,9 +270,11 @@ class WmIpc { WindowType type, const std::vector<int>* params); - // Gets the type of the window. The caller is responsible for trapping - // errors from the X server. - WmIpc::WindowType GetWindowType(GtkWidget* widget); + // Gets the type of the window, and any associated parameters. The + // caller is responsible for trapping errors from the X server. If + // the parameters are not interesting to the caller, NULL may be + // passed for |params|. + WmIpc::WindowType GetWindowType(GtkWidget* widget, std::vector<int>* params); // Sends a message to the WM. void SendMessage(const Message& msg); diff --git a/chrome/browser/chromeos/wm_overview_controller.cc b/chrome/browser/chromeos/wm_overview_controller.cc new file mode 100644 index 0000000..01a0888 --- /dev/null +++ b/chrome/browser/chromeos/wm_overview_controller.cc @@ -0,0 +1,517 @@ +// Copyright (c) 2010 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/chromeos/wm_overview_controller.h" + +#include <algorithm> +#include <vector> + +#include "base/linked_ptr.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/chromeos/wm_ipc.h" +#include "chrome/browser/chromeos/wm_overview_snapshot.h" +#include "chrome/browser/renderer_host/render_view_host.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/tab_contents/tab_contents_view.h" +#include "chrome/browser/tab_contents/thumbnail_generator.h" +#include "chrome/browser/views/frame/browser_extender.h" +#include "chrome/browser/views/frame/browser_view.h" +#include "chrome/common/notification_service.h" +#include "views/widget/root_view.h" +#include "views/widget/widget_gtk.h" +#include "views/window/window.h" + +using std::vector; + +#if !defined(OS_CHROMEOS) +#error This file is only meant to be compiled for ChromeOS +#endif + +namespace chromeos { + +class BrowserListener : public TabStripModelObserver { + public: + BrowserListener(Browser* browser, WmOverviewController* parent); + ~BrowserListener(); + + // Begin TabStripModelObserver methods + virtual void TabInsertedAt(TabContents* contents, + int index, + bool foreground); + virtual void TabClosingAt(TabContents* contents, int index) {} + virtual void TabDetachedAt(TabContents* contents, int index); + virtual void TabMoved(TabContents* contents, + int from_index, + int to_index); + virtual void TabChangedAt(TabContents* contents, int index, + TabStripModelObserver::TabChangeType change_type); + virtual void TabStripEmpty(); + virtual void TabDeselectedAt(TabContents* contents, int index) {} + virtual void TabSelectedAt(TabContents* old_contents, + TabContents* new_contents, + int index, + bool user_gesture); + // End TabStripModelObserver methods + + // Returns the number of tabs in this child. + int count() const { return browser_->tabstrip_model()->count(); } + + // Returns the browser that this child gets its data from. + Browser* browser() const { return browser_; } + + // Removes all the snapshots and re-populates them from the browser. + void RecreateSnapshots(); + + // Updates the selected index and tab count on the toplevel window. + void UpdateSelectedIndex(int index); + + // Finds the first cell with no snapshot and invokes ConfigureCell + // for it. Returns false if there are no more cells to configure on + // this listener. + bool ConfigureNextUnconfiguredSnapshot(); + + // Saves the currently selected tab. + void SaveCurrentTab() { original_selected_tab_ = browser_->selected_index(); } + + // Reverts the selected browser tab to the tab that was selected + // when This BrowserListener was created, or the last time + // SaveCurrentTab was called. + void RestoreOriginalSelectedTab(); + + // Selects the tab at the given index. + void SelectTab(int index); + + // Shows any snapshots that are not visible, and updates their + // bitmaps. + void ShowSnapshots(); + + private: + // Returns the tab contents from the tab model for this child at index. + TabContents* GetTabContentsAt(int index) const { + return browser_->tabstrip_model()->GetTabContentsAt(index); + } + + // Configures a cell from the tab contents. + void ConfigureCell(WmOverviewSnapshot* cell, TabContents* contents); + + // Configures a cell from the model. + void ConfigureCell(WmOverviewSnapshot* cell, int index) { + ConfigureCell(cell, GetTabContentsAt(index)); + } + + // Inserts a new snapshot, initialized from the model, at the given + // index, and renumbers any following snapshots. + void InsertSnapshot(int index); + + // Removes the snapshot at index. + void ClearSnapshot(int index); + + // Renumbers the index atom in the snapshots starting at the given + // index. + void RenumberSnapshots(int start_index); + + Browser* browser_; // Not owned + WmOverviewController* controller_; // Not owned + + // Widgets containing snapshot images for this browser. + typedef std::vector<WmOverviewSnapshot* > SnapshotVector; + SnapshotVector snapshots_; + + // True if the snapshots are showing. + bool snapshots_showing_; + + // The tab selected the last time SaveCurrentTab is called. + int original_selected_tab_; + + DISALLOW_COPY_AND_ASSIGN(BrowserListener); +}; + +BrowserListener::BrowserListener(Browser* browser, + WmOverviewController* controller) + : browser_(browser), + controller_(controller), + snapshots_showing_(false), + original_selected_tab_(-1) { + CHECK(browser_); + CHECK(controller_); + browser_->tabstrip_model()->AddObserver(this); +} + +BrowserListener::~BrowserListener() { + browser_->tabstrip_model()->RemoveObserver(this); +} + +void BrowserListener::TabInsertedAt(TabContents* contents, + int index, + bool foreground) { + InsertSnapshot(index); + RenumberSnapshots(index); + UpdateSelectedIndex(browser_->selected_index()); +} + +void BrowserListener::TabDetachedAt(TabContents* contents, int index) { + ClearSnapshot(index); + UpdateSelectedIndex(browser_->selected_index()); + RenumberSnapshots(index); +} + +void BrowserListener::TabMoved(TabContents* contents, + int from_index, + int to_index) { + // Need to reorder tab in the snapshots list, and reset the window + // type atom on the affected snapshots (the one moved, and all the + // ones after it), so that their indices are correct. + WmOverviewSnapshot* snapshot = snapshots_[from_index]; + snapshots_.erase(snapshots_.begin() + from_index); + snapshots_.insert(snapshots_.begin() + to_index, snapshot); + + RenumberSnapshots(std::min(to_index, from_index)); +} + +void BrowserListener::TabChangedAt( + TabContents* contents, + int index, + TabStripModelObserver::TabChangeType change_type) { + snapshots_[index]->reload_snapshot(); + controller_->StartDelayTimer(); +} + +void BrowserListener::TabStripEmpty() { + snapshots_.clear(); +} + +void BrowserListener::TabSelectedAt(TabContents* old_contents, + TabContents* new_contents, + int index, + bool user_gesture) { + UpdateSelectedIndex(index); +} + +void BrowserListener::RecreateSnapshots() { + snapshots_.clear(); + + for (int i = 0; i < count(); ++i) { + InsertSnapshot(i); + } + + RenumberSnapshots(0); +} + +void BrowserListener::UpdateSelectedIndex(int index) { + // Get the window params and check to make sure that they are + // different from what we know before we set them, to avoid extra + // notifications. + std::vector<int> params; + WmIpc::WindowType type = WmIpc::instance()->GetWindowType( + GTK_WIDGET(browser_->window()->GetNativeHandle()), + ¶ms); + DCHECK(type == WmIpc::WINDOW_TYPE_CHROME_TOPLEVEL); + if (params.size() > 1) { + if (params[0] == browser_->tab_count() && + params[0] == index) + return; + } + + params.clear(); + params.push_back(browser_->tab_count()); + params.push_back(index); + WmIpc::instance()->SetWindowType( + GTK_WIDGET(browser_->window()->GetNativeHandle()), + WmIpc::WINDOW_TYPE_CHROME_TOPLEVEL, + ¶ms); +} + +bool BrowserListener::ConfigureNextUnconfiguredSnapshot() { + for (SnapshotVector::size_type i = 0; i < snapshots_.size(); ++i) { + WmOverviewSnapshot* cell = snapshots_[i]; + if (!cell->configured_snapshot()) { + ConfigureCell(cell, GetTabContentsAt(i)); + return true; + } + } + return false; +} + +void BrowserListener::RestoreOriginalSelectedTab() { + if (original_selected_tab_ >= 0) { + browser_->SelectTabContentsAt(original_selected_tab_, false); + } +} + +void BrowserListener::ShowSnapshots() { + for (SnapshotVector::size_type i = 0; i < snapshots_.size(); ++i) { + WmOverviewSnapshot* snapshot = snapshots_[i]; + snapshot->reload_snapshot(); + if (!snapshot->IsVisible()) { + snapshot->Show(); + } + } +} + +void BrowserListener::SelectTab(int index) { + browser_->SelectTabContentsAt(index, true); +} + +void BrowserListener::ConfigureCell(WmOverviewSnapshot* cell, + TabContents* contents) { + if (contents) { + if (controller_->allow_show_snapshots()) { + ThumbnailGenerator* generator = + g_browser_process->GetThumbnailGenerator(); + // TODO: Make sure that if the cell gets deleted before the + // callback is called that it sticks around until it gets + // called. (some kind of "in flight" list that uses linked_ptr + // to make sure they don't actually get deleted?) Also, make + // sure that any request for a thumbnail eventually returns + // (even if it has bogus data), so we don't leak orphaned cells. + ThumbnailGenerator::ThumbnailReadyCallback* callback = + NewCallback(cell, &WmOverviewSnapshot::SetImage); + generator->AskForThumbnail(contents->render_view_host(), + callback, cell->size()); + } + } else { + // This happens because the contents haven't been loaded yet. + + // Make sure we set the snapshot image to something, otherwise + // configured_snapshot remains false and ConfigureNextUnconfiguredSnapshot + // would get stuck. + if (controller_->allow_show_snapshots()) { + cell->SetImage(SkBitmap()); + } + } +} + +void BrowserListener::InsertSnapshot(int index) { + WmOverviewSnapshot* snapshot = new WmOverviewSnapshot; + gfx::Rect bounds = + static_cast<BrowserView*>(browser_->window())->GetClientAreaBounds(); + gfx::Size size(bounds.width()/2, bounds.height()/2); + snapshot->Init(size, browser_, index); + snapshots_.insert(snapshots_.begin() + index, snapshot); + snapshot->reload_snapshot(); + controller_->StartDelayTimer(); +} + +// Removes the snapshot at index. +void BrowserListener::ClearSnapshot(int index) { + snapshots_[index]->CloseNow(); + snapshots_.erase(snapshots_.begin() + index); +} + +void BrowserListener::RenumberSnapshots(int start_index) { + int changes = 0; + for (SnapshotVector::size_type i = start_index; i < snapshots_.size(); ++i) { + if (snapshots_[i]->index() != static_cast<int>(i)) { + snapshots_[i]->UpdateIndex(browser_, i); + changes++; + } + } +} + +/////////////////////////////////// +// WmOverviewController methods + +// static +WmOverviewController* WmOverviewController::instance() { + static WmOverviewController* instance = NULL; + if (!instance) { + instance = Singleton<WmOverviewController>::get(); + } + return instance; +} + +WmOverviewController::WmOverviewController() + : allow_show_snapshots_(false) { + AddAllBrowsers(); + BrowserList::AddObserver(this); + WmMessageListener::instance()->AddObserver(this); +} + +WmOverviewController::~WmOverviewController() { + WmMessageListener::instance()->RemoveObserver(this); + BrowserList::RemoveObserver(this); + listeners_.clear(); +} + +void WmOverviewController::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + // Now that the browser window is ready, we create the snapshots. + if (type == NotificationType::BROWSER_WINDOW_READY) { + const Browser* browser = Source<const Browser>(source).ptr(); + BrowserListenerVector::value_type new_listener( + new BrowserListener(const_cast<Browser*>(browser), this)); + listeners_.push_back(new_listener); + new_listener->RecreateSnapshots(); + + // This makes sure that the new listener is in the right order (to + // match the order in the browser list). + AddAllBrowsers(); + } +} + +// Called immediately before a browser is removed from the list. +void WmOverviewController::OnBrowserRemoving(const Browser* browser) { + for (BrowserListenerVector::iterator i = listeners_.begin(); + i != listeners_.end(); ++i) { + if ((*i)->browser() == browser) { + listeners_.erase(i); + return; + } + } +} + +BrowserView* GetBrowserViewForGdkWindow(GdkWindow* gdk_window) { + gpointer data = NULL; + gdk_window_get_user_data(gdk_window, &data); + GtkWidget* widget = reinterpret_cast<GtkWidget*>(data); + if (widget) { + GtkWindow* gtk_window = GTK_WINDOW(widget); + return BrowserView::GetBrowserViewForNativeWindow(gtk_window); + } else { + return NULL; + } +} + +void WmOverviewController::ProcessWmMessage(const WmIpc::Message& message, + GdkWindow* window) { + switch (message.type()) { + case WmIpc::Message::CHROME_NOTIFY_LAYOUT_MODE: { + if (message.param(0) == 0 || BrowserList::size() == 0) { + Hide(message.param(1) != 0); + } else { + Show(); + } + break; + } + case WmIpc::Message::CHROME_NOTIFY_TAB_SELECT: { + BrowserView* browser_window = GetBrowserViewForGdkWindow(window); + // Find out which listener this goes to, and send it there. + for (BrowserListenerVector::iterator i = listeners_.begin(); + i != listeners_.end(); ++i) { + if ((*i)->browser()->window() == browser_window) { + (*i)->SelectTab(message.param(0)); + break; + } + } + break; + } + default: + break; + } +} + +void WmOverviewController::StartDelayTimer() { + // We leave the delay timer running if it already is -- this means + // we're rate limiting the number of times we can reconfigure the + // snapshots (at most once every 350ms). If we were to restart the + // delay timer, it could result in a very long delay until they get + // configured if tabs keep changing. + if (!delay_timer_.IsRunning()) { + configure_timer_.Stop(); + // Note that this pause is kind of a hack: it's just long enough + // that the overview-mode transitions will have finished happening + // before we start refreshing snapshots, so we don't bog the CPU + // while it's trying to animate the overview transitions. + delay_timer_.Start( + base::TimeDelta::FromMilliseconds(350), this, + &WmOverviewController::StartConfiguring); + } +} + +void WmOverviewController::RestoreTabSelections() { + for (BrowserListenerVector::iterator i = listeners_.begin(); + i != listeners_.end(); ++i) { + (*i)->RestoreOriginalSelectedTab(); + } +} + +void WmOverviewController::SaveTabSelections() { + for (BrowserListenerVector::iterator i = listeners_.begin(); + i != listeners_.end(); ++i) { + (*i)->SaveCurrentTab(); + } +} + +void WmOverviewController::Show() { + SaveTabSelections(); + for (BrowserListenerVector::iterator i = listeners_.begin(); + i != listeners_.end(); ++i) { + (*i)->ShowSnapshots(); + } + allow_show_snapshots_ = false; + StartDelayTimer(); +} + +void WmOverviewController::Hide(bool cancelled) { + configure_timer_.Stop(); + delay_timer_.Stop(); + if (cancelled) { + RestoreTabSelections(); + } +} + +void WmOverviewController::StartConfiguring() { + allow_show_snapshots_ = true; + configure_timer_.Stop(); + configure_timer_.Start( + base::TimeDelta::FromMilliseconds(10), this, + &WmOverviewController::ConfigureNextUnconfiguredSnapshot); +} + +// Just configure one unconfigured cell. If there aren't any left, +// then stop the timer. +void WmOverviewController::ConfigureNextUnconfiguredSnapshot() { + for (BrowserListenerVector::size_type i = 0; i < listeners_.size(); ++i) { + BrowserListener* listener = listeners_[i].get(); + if (listener->ConfigureNextUnconfiguredSnapshot()) + return; + } + configure_timer_.Stop(); +} + +void WmOverviewController::AddAllBrowsers() { + // Make a copy so the old ones aren't deleted yet. + BrowserListenerVector old_listeners; + + listeners_.swap(old_listeners); + + // Iterator through the browser list, adding all the browsers in the + // new order. If they were in the old list of listeners, just copy + // that linked pointer, instead of making a new listener, so that we + // can avoid lots of spurious destroy/create messages. + BrowserList::const_iterator iterator = BrowserList::begin(); + while (iterator != BrowserList::end()) { + BrowserListenerVector::value_type item( + BrowserListenerVector::value_type(NULL)); + for (BrowserListenerVector::iterator old_iter = old_listeners.begin(); + old_iter != old_listeners.end(); ++old_iter) { + if ((*old_iter)->browser() == *iterator) { + item = *old_iter; + break; + } + } + + // This browser isn't owned by any listener, so create it. + if (item.get() == NULL) { + item = BrowserListenerVector::value_type( + new BrowserListener(*iterator, this)); + + // This browser didn't already exist, and so we haven't been + // watching it for tab insertions, so we need to create the + // snapshots associated with it. + item->RecreateSnapshots(); + } + listeners_.push_back(item); + ++iterator; + } + + // Make sure we get notifications for when browser windows are ready. + if (registrar_.IsEmpty()) + registrar_.Add(this, NotificationType::BROWSER_WINDOW_READY, + NotificationService::AllSources()); +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/wm_overview_controller.h b/chrome/browser/chromeos/wm_overview_controller.h new file mode 100644 index 0000000..b37f965 --- /dev/null +++ b/chrome/browser/chromeos/wm_overview_controller.h @@ -0,0 +1,147 @@ +// Copyright (c) 2010 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_CHROMEOS_WM_OVERVIEW_CONTROLLER_H_ +#define CHROME_BROWSER_CHROMEOS_WM_OVERVIEW_CONTROLLER_H_ + +#include <vector> + +#include "base/linked_ptr.h" +#include "base/singleton.h" +#include "base/timer.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/chromeos/wm_message_listener.h" +#include "chrome/common/notification_registrar.h" +#include "gfx/rect.h" + +namespace views { +class Widget; +} + +class Animation; +class Browser; + +namespace chromeos { + +class BrowserListener; +class WmOverviewSnapshot; + +// WmOverviewController is responsible for managing a list of objects +// that listen to the browsers (BrowserListeners, defined in the +// source file for this class) for changes, and keep a list of +// snapshot images in sync with the browser tab contents. +// +// As tabs are added/removed from the browsers, the number of snapshot +// windows changes to match. +// +// As obtaining and setting snapshots is expensive we delay setting +// the snapshot. The delay is controlled by delay_timer_. Once the +// timer fires another timer is started (configure_timer_). This timer +// invokes ConfigureNextUnconfiguredCell on the BrowserListener, which +// obtains and sets the snapshot of the next uncofigured +// cell. ConfigureNextUnconfiguredCell only configures one cell at a +// time until they are all configured. + +class WmOverviewController : public BrowserList::Observer, + public WmMessageListener::Observer, + public NotificationObserver { + public: + // This class is a singleton. + static WmOverviewController* instance(); + + // BrowserList::Observer methods + // This is called immediately after a browser is added to the list. + void OnBrowserAdded(const Browser* browser) {} + + // This is called immediately before a browser is removed from the list. + void OnBrowserRemoving(const Browser* browser); + // End BrowserList::Observer methods + + // WmMessageListener::Observer methods + // This is called immediately after a browser is added to the list. + void ProcessWmMessage(const WmIpc::Message& message, + GdkWindow* window); + // End WmMessageListener::Observer methods + + // NotificationObserver methods + void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + // End NotificationObserver methods + + // Used by the BrowserListeners to configure their snapshots. + const gfx::Rect& monitor_bounds() const { return monitor_bounds_; } + + // Tells the listeners whether or not they're allowed to show + // snapshots yet. + bool allow_show_snapshots() const { return allow_show_snapshots_; } + + // Starts the delay timer, and once the delay is over, configures + // any unconfigured snapshots one at a time until none are left to + // be configured. + void StartDelayTimer(); + + private: + friend struct DefaultSingletonTraits<WmOverviewController>; + + // This class is a singleton. + WmOverviewController(); + ~WmOverviewController(); + + // Restores tab selections on all browsers to what they were when + // Show was last called. Used when cancelling overview mode. + void RestoreTabSelections(); + + // Saves the currently selected tabs in the snapshots so that they + // can be restored later with RestoreTabSelections. + void SaveTabSelections(); + + // Show the snapshot windows, saving current tab selections. + void Show(); + + // Hide the snapshot windows. When |cancelled| is true, then the + // tab selections that were saved when the snapshot windows were + // shown are restored. + void Hide(bool cancelled); + + // Invoked by delay_timer_. Sets allow_show_snapshots_ to true and starts + // configure_timer_. + void StartConfiguring(); + + // Configure the next unconfigured snapshot window owned by any of + // the listeners. + void ConfigureNextUnconfiguredSnapshot(); + + // Add browser listeners for all existing browsers, reusing any that + // were already there. + void AddAllBrowsers(); + + // This is so we can register for notifications. + NotificationRegistrar registrar_; + + // This is a vector of listeners that listen to all the browsers. + typedef std::vector<linked_ptr<BrowserListener> > BrowserListenerVector; + BrowserListenerVector listeners_; + + // This is the bounds of the monitor we're being displayed on. This + // is used to adjust the size of snapshots so they'll fit. + gfx::Rect monitor_bounds_; + + // This indicates whether we should actually set the snapshots so we + // don't do it when we don't need to. It is initially false, then + // set to true by StartConfiguring. + bool allow_show_snapshots_; + + // See description above class for details. + base::OneShotTimer<WmOverviewController> delay_timer_; + + // See description above class for details. + base::RepeatingTimer<WmOverviewController> configure_timer_; + + DISALLOW_COPY_AND_ASSIGN(WmOverviewController); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_WM_OVERVIEW_CONTROLLER_H_ diff --git a/chrome/browser/chromeos/wm_overview_snapshot.cc b/chrome/browser/chromeos/wm_overview_snapshot.cc new file mode 100644 index 0000000..97c2a8e --- /dev/null +++ b/chrome/browser/chromeos/wm_overview_snapshot.cc @@ -0,0 +1,80 @@ +// Copyright (c) 2010 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/chromeos/wm_overview_snapshot.h" + +#include <vector> + +#include "app/x11_util.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_window.h" +#include "chrome/browser/chromeos/wm_ipc.h" +#include "views/border.h" +#include "views/controls/image_view.h" +#include "views/controls/label.h" +#include "views/grid_layout.h" + +using views::ColumnSet; +using views::GridLayout; +using std::vector; + +#if !defined(OS_CHROMEOS) +#error This file is only meant to be compiled for ChromeOS +#endif + +namespace chromeos { + +WmOverviewSnapshot::WmOverviewSnapshot() + : WidgetGtk(TYPE_WINDOW), + snapshot_view_(NULL), + index_(-1), + configured_snapshot_(false) { +} + +void WmOverviewSnapshot::Init(const gfx::Size& size, + Browser* browser, + int index) { + snapshot_view_ = new views::ImageView(); + MakeTransparent(); + + snapshot_view_->set_background( + views::Background::CreateSolidBackground(SK_ColorWHITE)); + snapshot_view_->set_border( + views::Border::CreateSolidBorder(1, SkColorSetRGB(176, 176, 176))); + + WidgetGtk::Init(NULL, gfx::Rect(gfx::Point(0,0), size)); + + SetContentsView(snapshot_view_); + + UpdateIndex(browser, index); +} + + +void WmOverviewSnapshot::UpdateIndex(Browser* browser, int index) { + vector<int> params; + params.push_back(x11_util::GetX11WindowFromGtkWidget( + GTK_WIDGET(browser->window()->GetNativeHandle()))); + params.push_back(index); + WmIpc::instance()->SetWindowType( + GetNativeView(), + WmIpc::WINDOW_TYPE_CHROME_TAB_SNAPSHOT, + ¶ms); + index_ = index; +} + +void WmOverviewSnapshot::SetImage(const SkBitmap& image) { + CHECK(snapshot_view_) << "Init not called before setting image."; + snapshot_view_->SetImage(image); + + // Reset the bounds to the size of the image. + gfx::Rect bounds; + GetBounds(&bounds, false); + bounds.set_width(image.width()); + bounds.set_height(image.height()); + SetBounds(bounds); + + configured_snapshot_ = true; +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/wm_overview_snapshot.h b/chrome/browser/chromeos/wm_overview_snapshot.h new file mode 100644 index 0000000..7bc3699 --- /dev/null +++ b/chrome/browser/chromeos/wm_overview_snapshot.h @@ -0,0 +1,59 @@ +// Copyright (c) 2010 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_CHROMEOS_WM_OVERVIEW_SNAPSHOT_H_ +#define CHROME_BROWSER_CHROMEOS_WM_OVERVIEW_SNAPSHOT_H_ + +#include "third_party/skia/include/core/SkBitmap.h" +#include "views/view.h" +#include "views/widget/widget_gtk.h" +#include "views/controls/image_view.h" + +class Browser; + +namespace chromeos { + +// A single snapshot displayed by WmOverviewController. +// WmOverviewSnapshot contains a label, favicon and snapshot. +class WmOverviewSnapshot : public views::WidgetGtk { + public: + WmOverviewSnapshot(); + void Init(const gfx::Size& size, Browser* browser, int index); + + void SetImage(const SkBitmap& image); + + void UpdateIndex(Browser* browser, int index); + int index() const { return index_; } + + // Returns the size of the snapshot widget. + gfx::Size size() const { + gfx::Rect rect; + GetBounds(&rect, false); + return rect.size(); + } + + // Has the snapshot been configured? This is true after SetSnapshot + // is invoked. + bool configured_snapshot() const { return configured_snapshot_; } + + // This resets the configured_snapshot flag for this snapshot so it will + // get reloaded the next time we check. + void reload_snapshot() { configured_snapshot_ = false; } + + private: + // This control is the contents view for this widget. + views::ImageView* snapshot_view_; + + // This is the tab index of the snapshot in the associated browser. + int index_; + + // This indicates whether or not the snapshot has been configured. + bool configured_snapshot_; + + DISALLOW_COPY_AND_ASSIGN(WmOverviewSnapshot); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_WM_OVERVIEW_SNAPSHOT_H_ diff --git a/chrome/browser/renderer_host/backing_store_x.cc b/chrome/browser/renderer_host/backing_store_x.cc index d88dca7..f10e589 100644 --- a/chrome/browser/renderer_host/backing_store_x.cc +++ b/chrome/browser/renderer_host/backing_store_x.cc @@ -326,6 +326,7 @@ bool BackingStoreX::CopyFromBackingStore(const gfx::Rect& rect, // TODO(jhawkins): Need to convert the image data if the image bits per pixel // is not 32. + // Note that this also initializes the output bitmap as opaque. if (!output->initialize(width, height, true) || image->bits_per_pixel != 32) { if (shared_memory_support_ != x11_util::SHARED_MEMORY_NONE) @@ -335,15 +336,19 @@ bool BackingStoreX::CopyFromBackingStore(const gfx::Rect& rect, return false; } - // The X image might have a different row stride, so iterate through it and - // copy each row out, only up to the pixels we're actually using. - // This code assumes a visual mode where a pixel is represented using - // a byte for each component. + // The X image might have a different row stride, so iterate through + // it and copy each row out, only up to the pixels we're actually + // using. This code assumes a visual mode where a pixel is + // represented using a 32-bit unsigned int, with a byte per component. SkBitmap bitmap = output->getTopPlatformDevice().accessBitmap(true); for (int y = 0; y < height; y++) { + const uint32* src_row = reinterpret_cast<uint32*>( + &image->data[image->bytes_per_line * y]); uint32* dest_row = bitmap.getAddr32(0, y); - const char* src_row = &image->data[image->bytes_per_line * y]; - memcpy(dest_row, src_row, width * 4); + for (int x = 0; x < width; ++x, ++dest_row) { + // Force alpha to be 0xff, because otherwise it causes rendering problems. + *dest_row = src_row[x] | 0xff000000; + } } if (shared_memory_support_ != x11_util::SHARED_MEMORY_NONE) diff --git a/chrome/browser/renderer_host/render_widget_helper.h b/chrome/browser/renderer_host/render_widget_helper.h index 53b336c..33ca744 100644 --- a/chrome/browser/renderer_host/render_widget_helper.h +++ b/chrome/browser/renderer_host/render_widget_helper.h @@ -188,7 +188,7 @@ class RenderWidgetHelper #endif // A map of live paint messages. Must hold pending_paints_lock_ to access. - // The PaintMsgProxy objects are not owned by this map. (See PaintMsgProxy + // The UpdateMsgProxy objects are not owned by this map. (See UpdateMsgProxy // for details about how the lifetime of instances are managed.) UpdateMsgProxyMap pending_paints_; Lock pending_paints_lock_; diff --git a/chrome/browser/renderer_host/render_widget_host.cc b/chrome/browser/renderer_host/render_widget_host.cc index 6b20935..59b2231 100644 --- a/chrome/browser/renderer_host/render_widget_host.cc +++ b/chrome/browser/renderer_host/render_widget_host.cc @@ -127,6 +127,7 @@ void RenderWidgetHost::OnMessageReceived(const IPC::Message &msg) { IPC_MESSAGE_HANDLER(ViewHostMsg_RenderViewGone, OnMsgRenderViewGone) IPC_MESSAGE_HANDLER(ViewHostMsg_Close, OnMsgClose) IPC_MESSAGE_HANDLER(ViewHostMsg_RequestMove, OnMsgRequestMove) + IPC_MESSAGE_HANDLER(ViewHostMsg_PaintAtSize_ACK, OnMsgPaintAtSizeAck) IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateRect, OnMsgUpdateRect) IPC_MESSAGE_HANDLER(ViewHostMsg_CreateVideo, OnMsgCreateVideo) IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateVideo, OnMsgUpdateVideo) @@ -280,6 +281,14 @@ void RenderWidgetHost::SetIsLoading(bool is_loading) { view_->SetIsLoading(is_loading); } +void RenderWidgetHost::PaintAtSize(TransportDIB::Handle dib_handle, + const gfx::Size& size) { + // Ask the renderer to create a bitmap regardless of whether it's + // hidden, being resized, redrawn, etc., and to scale it by the + // scale factor given. + Send(new ViewMsg_PaintAtSize(routing_id_, dib_handle, size)); +} + 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. If we're not @@ -676,6 +685,13 @@ void RenderWidgetHost::OnMsgRequestMove(const gfx::Rect& pos) { } } +void RenderWidgetHost::OnMsgPaintAtSizeAck( + const TransportDIB::Handle& dib_handle, const gfx::Size& size) { + if (painting_observer_) { + painting_observer_->WidgetDidReceivePaintAtSizeAck(this, dib_handle, size); + } +} + void RenderWidgetHost::OnMsgUpdateRect( const ViewHostMsg_UpdateRect_Params& params) { TimeTicks paint_start = TimeTicks::Now(); diff --git a/chrome/browser/renderer_host/render_widget_host.h b/chrome/browser/renderer_host/render_widget_host.h index d57d8bc..7b87d13 100644 --- a/chrome/browser/renderer_host/render_widget_host.h +++ b/chrome/browser/renderer_host/render_widget_host.h @@ -205,6 +205,16 @@ class RenderWidgetHost : public IPC::Channel::Listener, // Indicates if the page has finished loading. void SetIsLoading(bool is_loading); + // This tells the renderer to paint into a bitmap and return it, + // regardless of whether the tab is hidden or not. It returns the + // bitmap scaled so it matches the requested size, so that the + // scaling happens on the rendering thread. When the bitmap is + // ready, the renderer sends a PaintAtSizeACK to this host, and the + // painting observer is notified. Note that this bypasses most of + // the update logic that is normally invoked, and doesn't put the + // results into the backing store. + void PaintAtSize(TransportDIB::Handle dib_handle, const gfx::Size& size); + // 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. If you pass |force_create| as true, then the backing @@ -413,6 +423,7 @@ class RenderWidgetHost : public IPC::Channel::Listener, FRIEND_TEST(RenderWidgetHostTest, Resize); FRIEND_TEST(RenderWidgetHostTest, ResizeThenCrash); FRIEND_TEST(RenderWidgetHostTest, HiddenPaint); + FRIEND_TEST(RenderWidgetHostTest, PaintAtSize); // Tell this object to destroy itself. void Destroy(); @@ -421,7 +432,7 @@ class RenderWidgetHost : public IPC::Channel::Listener, // if it is. void CheckRendererIsUnresponsive(); - // Called if we know the renderer is responsive. When we currently thing the + // Called if we know the renderer is responsive. When we currently think the // renderer is unresponsive, this will clear that state and call // NotifyRendererResponsive. void RendererIsResponsive(); @@ -431,6 +442,8 @@ class RenderWidgetHost : public IPC::Channel::Listener, void OnMsgRenderViewGone(); void OnMsgClose(); void OnMsgRequestMove(const gfx::Rect& pos); + void OnMsgPaintAtSizeAck(const TransportDIB::Handle& dib_handle, + const gfx::Size& size); void OnMsgUpdateRect(const ViewHostMsg_UpdateRect_Params& params); void OnMsgCreateVideo(const gfx::Size& size); void OnMsgUpdateVideo(TransportDIB::Id bitmap, const gfx::Rect& bitmap_rect); diff --git a/chrome/browser/renderer_host/render_widget_host_painting_observer.h b/chrome/browser/renderer_host/render_widget_host_painting_observer.h index d24b61a..332e446 100644 --- a/chrome/browser/renderer_host/render_widget_host_painting_observer.h +++ b/chrome/browser/renderer_host/render_widget_host_painting_observer.h @@ -5,8 +5,15 @@ #ifndef CHROME_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_PAINTING_OBSERVER_H_ #define CHROME_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_PAINTING_OBSERVER_H_ +#include "app/surface/transport_dib.h" + class BackingStore; class RenderWidgetHost; +class SkBitmap; + +namespace gfx { +class Size; +} // This class can be used to observe painting events for a RenderWidgetHost. // Its primary goal in Chrome is to allow thumbnails to be generated. @@ -19,6 +26,13 @@ class RenderWidgetHostPaintingObserver { // Indicates that the RenderWidgetHost just updated the backing store. virtual void WidgetDidUpdateBackingStore(RenderWidgetHost* widget) = 0; + + // This notifies the painting observer that a PaintAtSizeACK was + // received. + virtual void WidgetDidReceivePaintAtSizeAck( + RenderWidgetHost* widget, + const TransportDIB::Handle& dib_handle, + const gfx::Size& size) = 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 e0ffe1a..28be917 100644 --- a/chrome/browser/renderer_host/render_widget_host_unittest.cc +++ b/chrome/browser/renderer_host/render_widget_host_unittest.cc @@ -9,6 +9,7 @@ #include "base/timer.h" #include "build/build_config.h" #include "chrome/browser/renderer_host/backing_store.h" +#include "chrome/browser/renderer_host/render_widget_host_painting_observer.h" #include "chrome/browser/renderer_host/test/test_render_view_host.h" #include "chrome/common/render_messages.h" #include "gfx/canvas.h" @@ -19,6 +20,10 @@ using base::TimeDelta; using WebKit::WebInputEvent; using WebKit::WebMouseWheelEvent; +namespace gfx { +class Size; +} + // RenderWidgetHostProcess ----------------------------------------------------- class RenderWidgetHostProcess : public MockRenderProcessHost { @@ -191,6 +196,32 @@ class MockRenderWidgetHost : public RenderWidgetHost { bool unresponsive_timer_fired_; }; +// MockPaintingObserver -------------------------------------------------------- + +class MockPaintingObserver : public RenderWidgetHostPaintingObserver { + public: + void WidgetWillDestroyBackingStore(RenderWidgetHost* widget, + BackingStore* backing_store) {} + void WidgetDidUpdateBackingStore(RenderWidgetHost* widget) {} + void WidgetDidReceivePaintAtSizeAck(RenderWidgetHost* host, + const TransportDIB::Handle& dib_handle, + const gfx::Size& size) { + host_ = reinterpret_cast<MockRenderWidgetHost*>(host); + dib_handle_ = dib_handle; + size_ = size; + } + + MockRenderWidgetHost* host() const { return host_; } + TransportDIB::Handle dib_handle() const { return dib_handle_; } + gfx::Size size() const { return size_; } + + private: + MockRenderWidgetHost* host_; + TransportDIB::Handle dib_handle_; + gfx::Size size_; +}; + + // RenderWidgetHostTest -------------------------------------------------------- class RenderWidgetHostTest : public testing::Test { @@ -504,6 +535,24 @@ TEST_F(RenderWidgetHostTest, HiddenPaint) { EXPECT_TRUE(needs_repaint.a); } +TEST_F(RenderWidgetHostTest, PaintAtSize) { + host_->PaintAtSize(TransportDIB::GetFakeHandleForTest(), gfx::Size(20, 30)); + EXPECT_TRUE( + process_->sink().GetUniqueMessageMatching(ViewMsg_PaintAtSize::ID)); + + MockPaintingObserver observer; + host_->set_painting_observer(&observer); + + // Need to generate a fake handle value on all platforms. + TransportDIB::Handle handle = TransportDIB::GetFakeHandleForTest(); + host_->OnMsgPaintAtSizeAck(handle, gfx::Size(20, 30)); + EXPECT_TRUE(host_ == observer.host()); + EXPECT_TRUE(handle == observer.dib_handle()); + EXPECT_EQ(20, observer.size().width()); + EXPECT_EQ(30, observer.size().height()); + host_->set_painting_observer(NULL); +} + TEST_F(RenderWidgetHostTest, HandleKeyEventsWeSent) { // Simulate a keyboard event. SimulateKeyboardEvent(WebInputEvent::RawKeyDown); @@ -649,4 +698,3 @@ TEST_F(RenderWidgetHostTest, StopAndStartHangMonitorTimeout) { MessageLoop::current()->Run(); EXPECT_TRUE(host_->unresponsive_timer_fired()); } - diff --git a/chrome/browser/tab_contents/thumbnail_generator.cc b/chrome/browser/tab_contents/thumbnail_generator.cc index 212045e..a3c476c 100644 --- a/chrome/browser/tab_contents/thumbnail_generator.cc +++ b/chrome/browser/tab_contents/thumbnail_generator.cc @@ -5,12 +5,14 @@ #include "chrome/browser/tab_contents/thumbnail_generator.h" #include <algorithm> +#include <map> #include "base/histogram.h" #include "base/time.h" #include "build/build_config.h" #include "chrome/browser/renderer_host/backing_store.h" #include "chrome/browser/renderer_host/render_view_host.h" +#include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/common/notification_service.h" #include "chrome/common/property_bag.h" #include "gfx/rect.h" @@ -105,17 +107,26 @@ SkBitmap GetThumbnailForBackingStore(BackingStore* backing_store) { &temp_canvas)) return result; const SkBitmap& bmp = temp_canvas.getTopPlatformDevice().accessBitmap(false); - result = SkBitmapOperations::DownsampleByTwoUntilSize(bmp, kThumbnailWidth, + +#if defined(OS_CHROMEOS) + // On ChromeOS, the thumbnail is always half the dimensions of the + // original. + result = SkBitmapOperations::DownsampleByTwo(bmp); +#else + result = SkBitmapOperations::DownsampleByTwoUntilSize(bmp, + kThumbnailWidth, kThumbnailHeight); - // 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. - 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 assigned 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. + if (bmp.width() == result.width() && + bmp.height() == result.height()) bmp.copyTo(&result, SkBitmap::kARGB_8888_Config); +#endif HISTOGRAM_TIMES(kThumbnailHistogramName, base::TimeTicks::Now() - begin_compute_thumbnail); @@ -124,6 +135,12 @@ SkBitmap GetThumbnailForBackingStore(BackingStore* backing_store) { } // namespace +struct ThumbnailGenerator::AsyncRequestInfo { + scoped_ptr<ThumbnailReadyCallback> callback; + scoped_ptr<TransportDIB> thumbnail_dib; + RenderWidgetHost* renderer; // Not owned. +}; + ThumbnailGenerator::ThumbnailGenerator() : no_timeout_(false) { // The BrowserProcessImpl creates this non-lazily. If you add nontrivial @@ -144,14 +161,48 @@ void ThumbnailGenerator::StartThumbnailing() { // 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()); + registrar_.Add(this, NotificationType::TAB_CONTENTS_DISCONNECTED, + NotificationService::AllSources()); } } +void ThumbnailGenerator::AskForThumbnail(RenderWidgetHost* renderer, + ThumbnailReadyCallback* callback, + gfx::Size size) { + SkBitmap first_try = GetThumbnailForRenderer(renderer); + if (!first_try.isNull()) { + // We were able to find a non-null thumbnail for this renderer, so + // we'll go with it. + callback->Run(first_try); + delete callback; + return; + } + + // We are going to render the thumbnail asynchronously now, so keep + // this callback for later lookup when the rendering is done. + static int sequence_num = 0; + TransportDIB* thumbnail_dib = TransportDIB::Create( + size.width() * size.height() * 4, sequence_num++); + linked_ptr<AsyncRequestInfo> request_info(new AsyncRequestInfo); + request_info->callback.reset(callback); + request_info->thumbnail_dib.reset(thumbnail_dib); + request_info->renderer = renderer; + ThumbnailCallbackMap::value_type new_value(thumbnail_dib->handle(), + request_info); + std::pair<ThumbnailCallbackMap::iterator, bool> result = + callback_map_.insert(new_value); + if (!result.second) { + NOTREACHED() << "Callback already registered?"; + return; + } + + renderer->PaintAtSize(thumbnail_dib->handle(), size); +} + SkBitmap ThumbnailGenerator::GetThumbnailForRenderer( RenderWidgetHost* renderer) const { WidgetThumbnail* wt = GetDataForHost(renderer); @@ -221,6 +272,42 @@ void ThumbnailGenerator::WidgetDidUpdateBackingStore( wt->thumbnail = SkBitmap(); } +void ThumbnailGenerator::WidgetDidReceivePaintAtSizeAck( + RenderWidgetHost* widget, + const TransportDIB::Handle& dib_handle, + const gfx::Size& size) { + // Lookup the callback, run it, and erase it. + ThumbnailCallbackMap::iterator item = callback_map_.find(dib_handle); + if (item != callback_map_.end()) { + TransportDIB* dib = item->second->thumbnail_dib.get(); + DCHECK(dib); + if (!dib) { + return; + } + + // Create an SkBitmap from the DIB. + SkBitmap non_owned_bitmap; + SkBitmap result; + + // Fill out the non_owned_bitmap with the right config. Note that + // this code assumes that the transport dib is a 32-bit ARGB + // image. + non_owned_bitmap.setConfig(SkBitmap::kARGB_8888_Config, + size.width(), size.height()); + non_owned_bitmap.setPixels(dib->memory()); + + // Now alloc/copy the memory so we own it and can pass it around, + // and the memory won't go away when the DIB goes away. + // TODO: Figure out a way to avoid this copy? + non_owned_bitmap.copyTo(&result, SkBitmap::kARGB_8888_Config); + + item->second->callback->Run(result); + + // We're done with the callback, and with the DIB, so delete both. + callback_map_.erase(item); + } +} + void ThumbnailGenerator::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { @@ -243,6 +330,10 @@ void ThumbnailGenerator::Observe(NotificationType type, WidgetDestroyed(Source<RenderWidgetHost>(source).ptr()); break; + case NotificationType::TAB_CONTENTS_DISCONNECTED: + TabContentsDisconnected(Source<TabContents>(source).ptr()); + break; + default: NOTREACHED(); } @@ -294,6 +385,20 @@ void ThumbnailGenerator::WidgetDestroyed(RenderWidgetHost* widget) { EraseHostFromShownList(widget); } +void ThumbnailGenerator::TabContentsDisconnected(TabContents* contents) { + // Go through the existing callbacks, and find any that have the + // same renderer as this TabContents and remove them so they don't + // hang around. + ThumbnailCallbackMap::iterator iterator = callback_map_.begin(); + RenderWidgetHost* renderer = contents->render_view_host(); + while (iterator != callback_map_.end()) { + if (iterator->second->renderer == renderer) { + callback_map_.erase(iterator); + } + ++iterator; + } +} + void ThumbnailGenerator::ShownDelayHandler() { base::TimeTicks threshold = base::TimeTicks::Now() - base::TimeDelta::FromMilliseconds(kVisibilitySlopMS); diff --git a/chrome/browser/tab_contents/thumbnail_generator.h b/chrome/browser/tab_contents/thumbnail_generator.h index 499c2d6..6398e17 100644 --- a/chrome/browser/tab_contents/thumbnail_generator.h +++ b/chrome/browser/tab_contents/thumbnail_generator.h @@ -5,22 +5,30 @@ #ifndef CHROME_BROWSER_TAB_CONTENTS_THUMBNAIL_GENERATOR_H_ #define CHROME_BROWSER_TAB_CONTENTS_THUMBNAIL_GENERATOR_H_ +#include <map> +#include <utility> #include <vector> #include "base/basictypes.h" +#include "base/callback.h" +#include "base/linked_ptr.h" +#include "base/lock.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 RenderViewHost; class RenderWidgetHost; class SkBitmap; +class TabContents; // 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: + typedef Callback1<const SkBitmap&>::Type ThumbnailReadyCallback; // This class will do nothing until you call StartThumbnailing. ThumbnailGenerator(); ~ThumbnailGenerator(); @@ -29,6 +37,17 @@ class ThumbnailGenerator : public RenderWidgetHostPaintingObserver, // be called repeatedly and with wild abandon to no ill effect. void StartThumbnailing(); + // This registers a callback that can receive the resulting SkBitmap + // from the renderer when it is done rendering it. This differs + // from GetThumbnailForRenderer in that it is asynchronous, and + // because it will also fetch the bitmap even if the tab is hidden. + // In addition, if the renderer has to be invoked, the scaling of + // the thumbnail happens on the rendering thread. Takes ownership + // of the callback object. + void AskForThumbnail(RenderWidgetHost* renderer, + ThumbnailReadyCallback* callback, + gfx::Size size); + SkBitmap GetThumbnailForRenderer(RenderWidgetHost* renderer) const; #ifdef UNIT_TEST @@ -45,6 +64,11 @@ class ThumbnailGenerator : public RenderWidgetHostPaintingObserver, BackingStore* backing_store); virtual void WidgetDidUpdateBackingStore(RenderWidgetHost* widget); + virtual void WidgetDidReceivePaintAtSizeAck( + RenderWidgetHost* widget, + const TransportDIB::Handle& dib_handle, + const gfx::Size& size); + // NotificationObserver interface. virtual void Observe(NotificationType type, const NotificationSource& source, @@ -57,6 +81,10 @@ class ThumbnailGenerator : public RenderWidgetHostPaintingObserver, // Called when the given widget is destroyed. void WidgetDestroyed(RenderWidgetHost* widget); + // Called when the given tab contents are disconnected (either + // through being closed, or because the renderer is no longer there). + void TabContentsDisconnected(TabContents* contents); + // 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(); @@ -76,6 +104,12 @@ class ThumbnailGenerator : public RenderWidgetHostPaintingObserver, // See the setter above. bool no_timeout_; + // Map of callback objects by TransportDIB::Handle. + struct AsyncRequestInfo; + typedef std::map<TransportDIB::Handle, + linked_ptr<AsyncRequestInfo> > ThumbnailCallbackMap; + ThumbnailCallbackMap callback_map_; + DISALLOW_COPY_AND_ASSIGN(ThumbnailGenerator); }; diff --git a/chrome/browser/tab_contents/thumbnail_generator_unittest.cc b/chrome/browser/tab_contents/thumbnail_generator_unittest.cc index 38a40f0..787873c 100644 --- a/chrome/browser/tab_contents/thumbnail_generator_unittest.cc +++ b/chrome/browser/tab_contents/thumbnail_generator_unittest.cc @@ -11,8 +11,8 @@ #include "chrome/common/notification_service.h" #include "chrome/common/render_messages.h" #include "chrome/test/testing_profile.h" -#include "testing/gtest/include/gtest/gtest.h" #include "skia/ext/platform_canvas.h" +#include "testing/gtest/include/gtest/gtest.h" #include "third_party/skia/include/core/SkColorPriv.h" static const int kBitmapWidth = 100; @@ -142,7 +142,7 @@ TEST_F(ThumbnailGeneratorTest, DiscardBackingStore) { SendPaint(TRANSPORT_BLACK); widget_.WasHidden(); ASSERT_TRUE(BackingStoreManager::ExpireBackingStoreForTest(&widget_)); - ASSERT_FALSE(widget_.GetBackingStore(false)); + ASSERT_FALSE(widget_.GetBackingStore(false, false)); // The thumbnail generator should have stashed a thumbnail of the page. SkBitmap result = generator_.GetThumbnailForRenderer(&widget_); @@ -156,7 +156,7 @@ TEST_F(ThumbnailGeneratorTest, QuickShow) { SendPaint(TRANSPORT_BLACK); widget_.WasHidden(); ASSERT_TRUE(BackingStoreManager::ExpireBackingStoreForTest(&widget_)); - ASSERT_FALSE(widget_.GetBackingStore(false)); + ASSERT_FALSE(widget_.GetBackingStore(false, false)); // Now show the widget and paint white. widget_.WasRestored(); |