diff options
author | erikkay@chromium.org <erikkay@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-02 20:19:20 +0000 |
---|---|---|
committer | erikkay@chromium.org <erikkay@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-02 20:19:20 +0000 |
commit | 2d8d923da42df61e9d65942945456d4b9304ef8e (patch) | |
tree | 21150546eca2423887f1cca5f9fe7a58ef08224a | |
parent | 9e0dfa8ae69f71441d62f725441f7075f791bd09 (diff) | |
download | chromium_src-2d8d923da42df61e9d65942945456d4b9304ef8e.zip chromium_src-2d8d923da42df61e9d65942945456d4b9304ef8e.tar.gz chromium_src-2d8d923da42df61e9d65942945456d4b9304ef8e.tar.bz2 |
Add simple popup support to browser actions. This will create a popup HTML window that extends below a browser action button when the browser is clicked. When it loses focus, it is automatically dismissed.
BUG=23596
TEST=none
Review URL: http://codereview.chromium.org/258011
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@27889 0039d316-1c4b-4281-b951-d872f2087c98
25 files changed, 575 insertions, 58 deletions
diff --git a/chrome/browser/extensions/extension_host.cc b/chrome/browser/extensions/extension_host.cc index 0f3f6d8..3baa908 100644 --- a/chrome/browser/extensions/extension_host.cc +++ b/chrome/browser/extensions/extension_host.cc @@ -295,21 +295,23 @@ void ExtensionHost::InsertThemeCSS() { } void ExtensionHost::DidStopLoading(RenderViewHost* render_view_host) { - if (!did_stop_loading_) { - did_stop_loading_ = true; - LOG(INFO) << "Sending EXTENSION_HOST_DID_STOP_LOADING"; - NotificationService::current()->Notify( - NotificationType::EXTENSION_HOST_DID_STOP_LOADING, - Source<Profile>(profile_), - Details<ExtensionHost>(this)); - } + bool notify = !did_stop_loading_; + did_stop_loading_ = true; if (extension_host_type_ == ViewType::EXTENSION_TOOLSTRIP || - extension_host_type_ == ViewType::EXTENSION_MOLE) { + extension_host_type_ == ViewType::EXTENSION_MOLE || + extension_host_type_ == ViewType::EXTENSION_POPUP) { #if defined(TOOLKIT_VIEWS) if (view_.get()) view_->DidStopLoading(); #endif } + if (notify) { + LOG(INFO) << "Sending EXTENSION_HOST_DID_STOP_LOADING"; + NotificationService::current()->Notify( + NotificationType::EXTENSION_HOST_DID_STOP_LOADING, + Source<Profile>(profile_), + Details<ExtensionHost>(this)); + } } void ExtensionHost::DocumentAvailableInMainFrame(RenderViewHost* rvh) { @@ -488,7 +490,9 @@ void ExtensionHost::RenderViewCreated(RenderViewHost* render_view_host) { int ExtensionHost::GetBrowserWindowID() const { int window_id = -1; - if (extension_host_type_ == ViewType::EXTENSION_TOOLSTRIP) { + if (extension_host_type_ == ViewType::EXTENSION_TOOLSTRIP || + extension_host_type_ == ViewType::EXTENSION_MOLE || + extension_host_type_ == ViewType::EXTENSION_POPUP) { window_id = ExtensionTabUtil::GetWindowId( const_cast<ExtensionHost* >(this)->GetBrowser()); } else if (extension_host_type_ == ViewType::EXTENSION_BACKGROUND_PAGE) { diff --git a/chrome/browser/extensions/extension_host.h b/chrome/browser/extensions/extension_host.h index 70690ef..9a0f23e 100644 --- a/chrome/browser/extensions/extension_host.h +++ b/chrome/browser/extensions/extension_host.h @@ -70,6 +70,7 @@ class ExtensionHost : public RenderViewHostDelegate, bool document_element_available() const { return document_element_available_; } + Profile* profile() const { return profile_; } // Sets the the ViewType of this host (e.g. mole, toolstrip). void SetRenderViewType(ViewType::Type type); diff --git a/chrome/browser/extensions/extension_process_manager.cc b/chrome/browser/extensions/extension_process_manager.cc index cec5a45..9ae47a7 100644 --- a/chrome/browser/extensions/extension_process_manager.cc +++ b/chrome/browser/extensions/extension_process_manager.cc @@ -57,30 +57,53 @@ ExtensionProcessManager::~ExtensionProcessManager() { ExtensionHost* ExtensionProcessManager::CreateView(Extension* extension, const GURL& url, - Browser* browser) { + Browser* browser, + ViewType::Type view_type) { DCHECK(extension); DCHECK(browser); ExtensionHost* host = - new ExtensionHost(extension, GetSiteInstanceForURL(url), url, - ViewType::EXTENSION_TOOLSTRIP); + new ExtensionHost(extension, GetSiteInstanceForURL(url), url, view_type); host->CreateView(browser); OnExtensionHostCreated(host, false); return host; } ExtensionHost* ExtensionProcessManager::CreateView(const GURL& url, - Browser* browser) { + Browser* browser, + ViewType::Type view_type) { DCHECK(browser); ExtensionsService* service = browsing_instance_->profile()->GetExtensionsService(); if (service) { Extension* extension = service->GetExtensionByURL(url); if (extension) - return CreateView(extension, url, browser); + return CreateView(extension, url, browser, view_type); } return NULL; } +ExtensionHost* ExtensionProcessManager::CreateToolstrip(Extension* extension, + const GURL& url, + Browser* browser) { + return CreateView(extension, url, browser, ViewType::EXTENSION_TOOLSTRIP); +} + +ExtensionHost* ExtensionProcessManager::CreateToolstrip(const GURL& url, + Browser* browser) { + return CreateView(url, browser, ViewType::EXTENSION_TOOLSTRIP); +} + +ExtensionHost* ExtensionProcessManager::CreatePopup(Extension* extension, + const GURL& url, + Browser* browser) { + return CreateView(extension, url, browser, ViewType::EXTENSION_POPUP); +} + +ExtensionHost* ExtensionProcessManager::CreatePopup(const GURL& url, + Browser* browser) { + return CreateView(url, browser, ViewType::EXTENSION_POPUP); +} + ExtensionHost* ExtensionProcessManager::CreateBackgroundHost( Extension* extension, const GURL& url) { ExtensionHost* host = diff --git a/chrome/browser/extensions/extension_process_manager.h b/chrome/browser/extensions/extension_process_manager.h index d2af74d..e154a33 100644 --- a/chrome/browser/extensions/extension_process_manager.h +++ b/chrome/browser/extensions/extension_process_manager.h @@ -11,6 +11,7 @@ #include "base/ref_counted.h" #include "chrome/common/notification_registrar.h" +#include "chrome/common/view_types.h" class Browser; class BrowsingInstance; @@ -36,8 +37,19 @@ class ExtensionProcessManager : public NotificationObserver { // profile. ExtensionHost* CreateView(Extension* extension, const GURL& url, - Browser* browser); - ExtensionHost* CreateView(const GURL& url, Browser* browser); + Browser* browser, + ViewType::Type view_type); + ExtensionHost* CreateView(const GURL& url, + Browser* browser, + ViewType::Type view_type); + ExtensionHost* CreateToolstrip(Extension* extension, + const GURL& url, + Browser* browser); + ExtensionHost* CreateToolstrip(const GURL& url, Browser* browser); + ExtensionHost* CreatePopup(Extension* extension, + const GURL& url, + Browser* browser); + ExtensionHost* CreatePopup(const GURL& url, Browser* browser); // Creates a new UI-less extension instance. Like CreateView, but not // displayed anywhere. diff --git a/chrome/browser/extensions/extension_shelf_model.cc b/chrome/browser/extensions/extension_shelf_model.cc index 10b56b2..dc584d8 100644 --- a/chrome/browser/extensions/extension_shelf_model.cc +++ b/chrome/browser/extensions/extension_shelf_model.cc @@ -198,7 +198,7 @@ void ExtensionShelfModel::AddExtension(Extension* extension) { toolstrip != extension->toolstrips().end(); ++toolstrip) { GURL url = toolstrip->toolstrip; ToolstripItem item; - item.host = manager->CreateView(extension, url, browser_); + item.host = manager->CreateToolstrip(extension, url, browser_); item.info = *toolstrip; item.data = NULL; item.height = 0; diff --git a/chrome/browser/views/browser_actions_container.cc b/chrome/browser/views/browser_actions_container.cc index bae129f..88c2b02 100644 --- a/chrome/browser/views/browser_actions_container.cc +++ b/chrome/browser/views/browser_actions_container.cc @@ -10,12 +10,13 @@ #include "chrome/browser/extensions/extension_tabs_module.h" #include "chrome/browser/image_loading_tracker.h" #include "chrome/browser/profile.h" +#include "chrome/browser/views/extensions/extension_popup.h" #include "chrome/browser/views/toolbar_view.h" #include "chrome/common/extensions/extension_action.h" #include "chrome/common/notification_source.h" #include "chrome/common/notification_type.h" #include "third_party/skia/include/core/SkBitmap.h" -#include "views/controls/button/text_button.h" +#include "views/controls/button/menu_button.h" // The size of the icon for page actions. static const int kIconSize = 30; @@ -29,7 +30,7 @@ static const int kHorizontalPadding = 4; // The BrowserActionImageView is a specialization of the TextButton class. // It acts on a ExtensionAction, in this case a BrowserAction and handles // loading the image for the button asynchronously on the file thread to -class BrowserActionImageView : public views::TextButton, +class BrowserActionImageView : public views::MenuButton, public views::ButtonListener, public ImageLoadingTracker::Observer, public NotificationObserver { @@ -50,6 +51,26 @@ class BrowserActionImageView : public views::TextButton, const NotificationSource& source, const NotificationDetails& details); + // MenuButton behavior overrides. These methods all default to TextButton + // behavior unless this button is a popup. In that case, it uses MenuButton + // behavior. MenuButton has the notion of a child popup being shown where the + // button will stay in the pushed state until the "menu" (a popup in this + // case) is dismissed. + virtual bool Activate(); + virtual bool OnMousePressed(const views::MouseEvent& e); + virtual void OnMouseReleased(const views::MouseEvent& e, bool canceled); + virtual bool OnKeyReleased(const views::KeyEvent& e); + virtual void OnMouseExited(const views::MouseEvent& event); + + // Does this button's action have a popup? + virtual bool IsPopup(); + + // Notifications when the popup is hidden or shown by the container. + virtual void PopupDidShow(); + virtual void PopupDidHide(); + + const ExtensionAction& browser_action() const { return *browser_action_; } + private: // Called to update the display to match the browser action's state. void OnStateUpdated(); @@ -80,7 +101,7 @@ class BrowserActionImageView : public views::TextButton, BrowserActionImageView::BrowserActionImageView( ExtensionAction* browser_action, Extension* extension, BrowserActionsContainer* panel) - : TextButton(this, L""), + : MenuButton(this, L"", NULL, false), browser_action_(browser_action), browser_action_state_(extension->browser_action_state()), tracker_(NULL), @@ -113,7 +134,7 @@ BrowserActionImageView::~BrowserActionImageView() { void BrowserActionImageView::ButtonPressed( views::Button* sender, const views::Event& event) { - panel_->OnBrowserActionExecuted(*browser_action_); + panel_->OnBrowserActionExecuted(this); } void BrowserActionImageView::OnImageLoaded(SkBitmap* image, size_t index) { @@ -142,12 +163,76 @@ void BrowserActionImageView::Observe(NotificationType type, } } +bool BrowserActionImageView::IsPopup() { + return (browser_action_ && !browser_action_->popup_url().is_empty()); +} + +bool BrowserActionImageView::Activate() { + if (IsPopup()) { + panel_->OnBrowserActionExecuted(this); + + // TODO(erikkay): Run a nested modal loop while the mouse is down to + // enable menu-like drag-select behavior. + + // The return value of this method is returned via OnMousePressed. + // We need to return false here since we're handing off focus to another + // widget/view, and true will grab it right back and try to send events + // to us. + return false; + } + return true; +} + +bool BrowserActionImageView::OnMousePressed(const views::MouseEvent& e) { + if (IsPopup()) + return MenuButton::OnMousePressed(e); + return TextButton::OnMousePressed(e); +} + +void BrowserActionImageView::OnMouseReleased(const views::MouseEvent& e, + bool canceled) { + if (IsPopup()) { + // TODO(erikkay) this never actually gets called (probably because of the + // loss of focus). + MenuButton::OnMouseReleased(e, canceled); + } else { + TextButton::OnMouseReleased(e, canceled); + } +} + +bool BrowserActionImageView::OnKeyReleased(const views::KeyEvent& e) { + if (IsPopup()) + return MenuButton::OnKeyReleased(e); + return TextButton::OnKeyReleased(e); +} + +void BrowserActionImageView::OnMouseExited(const views::MouseEvent& e) { + if (IsPopup()) + MenuButton::OnMouseExited(e); + else + TextButton::OnMouseExited(e); +} + +void BrowserActionImageView::PopupDidShow() { + SetState(views::CustomButton::BS_PUSHED); + menu_visible_ = true; +} + +void BrowserActionImageView::PopupDidHide() { + SetState(views::CustomButton::BS_NORMAL); + menu_visible_ = false; +} + //////////////////////////////////////////////////////////////////////////////// // BrowserActionsContainer BrowserActionsContainer::BrowserActionsContainer( Profile* profile, ToolbarView* toolbar) - : profile_(profile), toolbar_(toolbar) { + : profile_(profile), + toolbar_(toolbar), + popup_(NULL), + popup_button_(NULL), + ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)) { ExtensionsService* extension_service = profile->GetExtensionsService(); registrar_.Add(this, NotificationType::EXTENSION_LOADED, Source<ExtensionsService>(extension_service)); @@ -160,6 +245,7 @@ BrowserActionsContainer::BrowserActionsContainer( } BrowserActionsContainer::~BrowserActionsContainer() { + HidePopup(); DeleteBrowserActionViews(); } @@ -201,8 +287,51 @@ void BrowserActionsContainer::OnBrowserActionVisibilityChanged() { toolbar_->Layout(); } +void BrowserActionsContainer::HidePopup() { + if (popup_) { + popup_->Hide(); + popup_->DetachFromBrowser(); + delete popup_; + popup_ = NULL; + popup_button_->PopupDidHide(); + popup_button_ = NULL; + return; + } +} + void BrowserActionsContainer::OnBrowserActionExecuted( - const ExtensionAction& browser_action) { + BrowserActionImageView* button) { + const ExtensionAction& browser_action = button->browser_action(); + + // Popups just display. No notification to the extension. + // TODO(erikkay): should there be? + if (button->IsPopup()) { + // If we're showing the same popup, just hide it and return. + bool same_showing = popup_ && button == popup_button_; + + // Always hide the current popup, even if it's not the same. + // Only one popup should be visible at a time. + HidePopup(); + + if (same_showing) + return; + + gfx::Point origin; + View::ConvertPointToWidget(button, &origin); + gfx::Rect rect = bounds(); + rect.set_x(origin.x()); + rect.set_y(origin.y()); + popup_ = ExtensionPopup::Show(browser_action.popup_url(), + toolbar_->browser(), + rect, + browser_action.popup_height()); + popup_->set_delegate(this); + popup_button_ = button; + popup_button_->PopupDidShow(); + return; + } + + // Otherwise, we send the action to the extension. int window_id = ExtensionTabUtil::GetWindowId(toolbar_->browser()); ExtensionBrowserEventRouter::GetInstance()->BrowserActionExecuted( profile_, browser_action.extension_id(), window_id); @@ -235,3 +364,23 @@ void BrowserActionsContainer::Observe(NotificationType type, NOTREACHED() << L"Received unexpected notification"; } } + +void BrowserActionsContainer::BubbleBrowserWindowMoved(BrowserBubble* bubble) { +} + +void BrowserActionsContainer::BubbleBrowserWindowClosing( + BrowserBubble* bubble) { + HidePopup(); +} + +void BrowserActionsContainer::BubbleGotFocus(BrowserBubble* bubble) { +} + +void BrowserActionsContainer::BubbleLostFocus(BrowserBubble* bubble) { + // This is a bit annoying. If you click on the button that generated the + // current popup, then we first get this lost focus message, and then + // we get the click action. This results in the popup being immediately + // shown again. To workaround this, we put in a delay. + MessageLoop::current()->PostTask(FROM_HERE, + task_factory_.NewRunnableMethod(&BrowserActionsContainer::HidePopup)); +} diff --git a/chrome/browser/views/browser_actions_container.h b/chrome/browser/views/browser_actions_container.h index fd83437..178a3a2 100644 --- a/chrome/browser/views/browser_actions_container.h +++ b/chrome/browser/views/browser_actions_container.h @@ -7,15 +7,19 @@ #include <vector> +#include "base/task.h" +#include "chrome/browser/views/browser_bubble.h" #include "chrome/common/notification_observer.h" #include "chrome/common/notification_registrar.h" #include "views/view.h" +class BrowserActionImageView; class ExtensionAction; +class ExtensionPopup; class Profile; class ToolbarView; namespace views { -class TextButton; +class MenuButton; } //////////////////////////////////////////////////////////////////////////////// @@ -26,7 +30,8 @@ class TextButton; // //////////////////////////////////////////////////////////////////////////////// class BrowserActionsContainer : public views::View, - public NotificationObserver { + public NotificationObserver, + public BrowserBubble::Delegate { public: BrowserActionsContainer(Profile* profile, ToolbarView* toolbar); virtual ~BrowserActionsContainer(); @@ -43,7 +48,7 @@ class BrowserActionsContainer : public views::View, void OnBrowserActionVisibilityChanged(); // Called when the user clicks on the browser action icon. - void OnBrowserActionExecuted(const ExtensionAction& browser_action); + void OnBrowserActionExecuted(BrowserActionImageView* button); // Overridden from views::View: virtual gfx::Size GetPreferredSize(); @@ -54,9 +59,18 @@ class BrowserActionsContainer : public views::View, const NotificationSource& source, const NotificationDetails& details); + // BrowserBubble::Delegate methods. + virtual void BubbleBrowserWindowMoved(BrowserBubble* bubble); + virtual void BubbleBrowserWindowClosing(BrowserBubble* bubble); + virtual void BubbleGotFocus(BrowserBubble* bubble); + virtual void BubbleLostFocus(BrowserBubble* bubble); + private: + // Hide the current popup. + void HidePopup(); + // The vector of browser actions (icons/image buttons for each action). - std::vector<views::TextButton*> browser_action_views_; + std::vector<views::MenuButton*> browser_action_views_; NotificationRegistrar registrar_; @@ -65,6 +79,15 @@ class BrowserActionsContainer : public views::View, // The toolbar that owns us. ToolbarView* toolbar_; + // The current popup and the button it came from. NULL if no popup. + ExtensionPopup* popup_; + + // The button that triggered the current popup (just a reference to a button + // from browser_action_views_). + BrowserActionImageView* popup_button_; + + ScopedRunnableMethodFactory<BrowserActionsContainer> task_factory_; + DISALLOW_COPY_AND_ASSIGN(BrowserActionsContainer); }; diff --git a/chrome/browser/views/browser_bubble.h b/chrome/browser/views/browser_bubble.h index 1ffacb0..327d4fd 100644 --- a/chrome/browser/views/browser_bubble.h +++ b/chrome/browser/views/browser_bubble.h @@ -24,6 +24,12 @@ class BrowserBubble { // Called with the Browser Window that this bubble is attached to is // about to close. virtual void BubbleBrowserWindowClosing(BrowserBubble* bubble) = 0; + + // Called when the bubble became active / got focus. + virtual void BubbleGotFocus(BrowserBubble* bubble) {} + + // Called when the bubble became inactive / lost focus. + virtual void BubbleLostFocus(BrowserBubble* bubble) {} }; // Note that the bubble will size itself to the preferred size of |view|. @@ -54,8 +60,8 @@ class BrowserBubble { virtual void BrowserWindowClosing(); // Show or hide the bubble. - void Show(); - void Hide(); + virtual void Show(bool activate); + virtual void Hide(); bool visible() const { return visible_; } // The contained view. diff --git a/chrome/browser/views/browser_bubble_gtk.cc b/chrome/browser/views/browser_bubble_gtk.cc index d73f1df..43a6af3 100644 --- a/chrome/browser/views/browser_bubble_gtk.cc +++ b/chrome/browser/views/browser_bubble_gtk.cc @@ -30,9 +30,10 @@ void BrowserBubble::MovePopup(int x, int y, int w, int h) { pop->SetBounds(gfx::Rect(x, y, w, h)); } -void BrowserBubble::Show() { +void BrowserBubble::Show(bool activate) { if (visible_) return; + // TODO(port) respect activate flag. views::WidgetGtk* pop = static_cast<views::WidgetGtk*>(popup_); pop->Show(); visible_ = true; diff --git a/chrome/browser/views/browser_bubble_win.cc b/chrome/browser/views/browser_bubble_win.cc index e1a1a06..8cae2da 100644 --- a/chrome/browser/views/browser_bubble_win.cc +++ b/chrome/browser/views/browser_bubble_win.cc @@ -6,12 +6,53 @@ #include "app/l10n_util_win.h" #include "chrome/browser/views/frame/browser_view.h" +#include "views/widget/root_view.h" #include "views/widget/widget_win.h" #include "views/window/window.h" +class BubbleWidget : public views::WidgetWin +{ +public: + BubbleWidget(BrowserBubble* bubble) : bubble_(bubble), closed_(false) { + } + + void Show(bool activate) { + if (activate) + ShowWindow(SW_SHOW); + else + views::WidgetWin::Show(); + } + + void Close() { + if (closed_) + return; + closed_ = true; + views::WidgetWin::Close(); + } + + void OnActivate(UINT action, BOOL minimized, HWND window) { + BrowserBubble::Delegate* delegate = bubble_->delegate(); + if (!delegate) + return; + + if (action == WA_INACTIVE && !closed_) { + delegate->BubbleLostFocus(bubble_); + } else if (action == WA_ACTIVE) { + delegate->BubbleGotFocus(bubble_); + } + } + +private: + bool closed_; + BrowserBubble* bubble_; +}; + void BrowserBubble::InitPopup() { gfx::NativeWindow native_window = frame_->GetWindow()->GetNativeWindow(); - views::WidgetWin* pop = new views::WidgetWin(); + + // popup_ is a Widget, but we need to do some WidgetWin stuff first, then + // we'll assign it into popup_. + views::WidgetWin* pop = new BubbleWidget(this); pop->set_window_style(WS_POPUP); #if 0 @@ -38,11 +79,11 @@ void BrowserBubble::MovePopup(int x, int y, int w, int h) { pop->MoveWindow(x, y, w, h); } -void BrowserBubble::Show() { +void BrowserBubble::Show(bool activate) { if (visible_) return; - views::WidgetWin* pop = static_cast<views::WidgetWin*>(popup_); - pop->Show(); + BubbleWidget* pop = static_cast<BubbleWidget*>(popup_); + pop->Show(activate); visible_ = true; } diff --git a/chrome/browser/views/extensions/extension_popup.cc b/chrome/browser/views/extensions/extension_popup.cc new file mode 100644 index 0000000..b938b27 --- /dev/null +++ b/chrome/browser/views/extensions/extension_popup.cc @@ -0,0 +1,78 @@ +// 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/views/extensions/extension_popup.h" + +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_window.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/extensions/extension_process_manager.h" +#include "chrome/browser/views/frame/browser_view.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/notification_details.h" +#include "chrome/common/notification_source.h" +#include "chrome/common/notification_type.h" + +ExtensionPopup::ExtensionPopup(ExtensionHost* host, + views::Widget* frame, + const gfx::Rect& relative_to) + : BrowserBubble(host->view(), + frame, + gfx::Point()), + relative_to_(relative_to), + extension_host_(host) { + registrar_.Add(this, NotificationType::EXTENSION_HOST_DID_STOP_LOADING, + Source<Profile>(host->profile())); +} + +ExtensionPopup::~ExtensionPopup() { +} + +void ExtensionPopup::Show() { + ResizeToView(); + + // Anchor on the lower right corner and extend to the left. + SetBounds(relative_to_.right() - width(), relative_to_.bottom(), + width(), height()); + BrowserBubble::Show(true); +} + +void ExtensionPopup::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if (type == NotificationType::EXTENSION_HOST_DID_STOP_LOADING) { + // Once we receive did stop loading, the content will be complete and + // the width will have been computed. Now it's safe to show. + if (extension_host_.get() == Details<ExtensionHost>(details).ptr()) + Show(); + } else { + NOTREACHED() << L"Received unexpected notification"; + } +} + +// static +ExtensionPopup* ExtensionPopup::Show(const GURL& url, Browser* browser, + const gfx::Rect& relative_to, + int height) { + ExtensionProcessManager* manager = + browser->profile()->GetExtensionProcessManager(); + DCHECK(manager); + if (!manager) + return NULL; + + ExtensionHost* host = manager->CreatePopup(url, browser); + views::Widget* frame = BrowserView::GetBrowserViewForNativeWindow( + browser->window()->GetNativeHandle())->GetWidget(); + ExtensionPopup* popup = new ExtensionPopup(host, frame, relative_to); + gfx::Size sz = host->view()->GetPreferredSize(); + sz.set_height(height); + host->view()->SetPreferredSize(sz); + + // If the host had somehow finished loading, then we'd miss the notification + // and not show. This seems to happen in single-process mode. + if (host->did_stop_loading()) + popup->Show(); + + return popup; +} diff --git a/chrome/browser/views/extensions/extension_popup.h b/chrome/browser/views/extensions/extension_popup.h new file mode 100644 index 0000000..cdefc8e --- /dev/null +++ b/chrome/browser/views/extensions/extension_popup.h @@ -0,0 +1,58 @@ +// 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_VIEWS_EXTENSIONS_EXTENSION_POPUP_H_ +#define CHROME_BROWSER_VIEWS_EXTENSIONS_EXTENSION_POPUP_H_ + +#include "googleurl/src/gurl.h" +#include "chrome/browser/extensions/extension_host.h" +#include "chrome/browser/views/browser_bubble.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" + +class Browser; +class ExtensionHost; + +class ExtensionPopup : public BrowserBubble, + public NotificationObserver { + public: + virtual ~ExtensionPopup(); + + // Create and show a popup with |url| positioned below |relative_to| in + // |browser| coordinates. This is anchored to the lower right corner of the + // rect, extending to the left, just like the wrench and page menus. + // + // The actual display of the popup is delayed until the page contents + // finish loading in order to minimize UI flashing and resizing. + static ExtensionPopup* Show(const GURL& url, Browser* browser, + const gfx::Rect& relative_to, + int height); + + ExtensionHost* host() const { return extension_host_.get(); } + + // BrowserBubble overrides. + virtual void Show(); + + // NotificationObserver overrides. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + private: + ExtensionPopup(ExtensionHost* host, + views::Widget* frame, + const gfx::Rect& relative_to); + + // The area on the screen that the popup should be positioned relative to. + const gfx::Rect relative_to_; + + // The contained host for the view. + scoped_ptr<ExtensionHost> extension_host_; + + NotificationRegistrar registrar_; + + DISALLOW_COPY_AND_ASSIGN(ExtensionPopup); +}; + +#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_POPUP_H_ diff --git a/chrome/browser/views/extensions/extension_shelf.cc b/chrome/browser/views/extensions/extension_shelf.cc index eee60a1..a586da32 100644 --- a/chrome/browser/views/extensions/extension_shelf.cc +++ b/chrome/browser/views/extensions/extension_shelf.cc @@ -587,7 +587,7 @@ void ExtensionShelf::Toolstrip::ShowWindow() { LayoutWindow(); if (!window_visible()) - window_->Show(); + window_->Show(false); // |false| means show, but don't activate. } void ExtensionShelf::Toolstrip::DoHideShelfHandle() { @@ -611,7 +611,6 @@ void ExtensionShelf::Toolstrip::Expand(int height, const GURL& url) { DCHECK(!expanded_); expanded_ = true; - view()->set_is_toolstrip(!expanded_); ShowWindow(); bool navigate = (!url.is_empty() && url != host_->GetURL()); @@ -638,7 +637,6 @@ void ExtensionShelf::Toolstrip::Expand(int height, const GURL& url) { void ExtensionShelf::Toolstrip::Collapse(const GURL& url) { DCHECK(expanded_); expanded_ = false; - view()->set_is_toolstrip(!expanded_); if (window_visible()) mole_animation_->Hide(); @@ -1076,7 +1074,7 @@ gfx::Size ExtensionShelf::LayoutItems(bool compute_bounds_only) { if (clipped) pref.set_width(std::max(0, max_x - x)); if (view == toolstrip->view()) - toolstrip->view()->set_is_clipped(clipped); + toolstrip->view()->SetIsClipped(clipped); view->SetBounds(x, y, pref.width(), content_height); view->Layout(); if (toolstrip->window_visible()) diff --git a/chrome/browser/views/extensions/extension_view.cc b/chrome/browser/views/extensions/extension_view.cc index 24ce5f1..20fc7b1 100644 --- a/chrome/browser/views/extensions/extension_view.cc +++ b/chrome/browser/views/extensions/extension_view.cc @@ -15,7 +15,7 @@ ExtensionView::ExtensionView(ExtensionHost* host, Browser* browser) : host_(host), browser_(browser), initialized_(false), pending_preferred_width_(0), container_(NULL), - did_stop_loading_(false), is_clipped_(false), is_toolstrip_(true) { + is_clipped_(false) { host_->set_view(this); } @@ -35,10 +35,17 @@ RenderViewHost* ExtensionView::render_view_host() const { } void ExtensionView::DidStopLoading() { - did_stop_loading_ = true; ShowIfCompletelyLoaded(); } +void ExtensionView::SetIsClipped(bool is_clipped) { + if (is_clipped_ != is_clipped) { + is_clipped_ = is_clipped; + if (IsVisible()) + ShowIfCompletelyLoaded(); + } +} + void ExtensionView::SetVisible(bool is_visible) { if (is_visible != IsVisible()) { NativeViewHost::SetVisible(is_visible); @@ -96,12 +103,17 @@ void ExtensionView::CreateWidgetHostView() { } void ExtensionView::ShowIfCompletelyLoaded() { - // We wait to show the ExtensionView until it has loaded, our parent has - // given us a background and css has been inserted into page. These can happen - // in different orders. - if (!IsVisible() && host_->did_stop_loading() && render_view_host()->view() && - !is_clipped_ && did_stop_loading_ && - !render_view_host()->view()->background().empty()) { + if (IsVisible() || is_clipped_) + return; + + // We wait to show the ExtensionView until it has loaded, and the view has + // actually been created. These can happen in different orders. + if (host_->did_stop_loading() && initialized_) { + // For toolstrips, also wait until our parent has given us a background. + if (host_->GetRenderViewType() == ViewType::EXTENSION_TOOLSTRIP && + render_view_host()->view()->background().empty()) { + return; + } SetVisible(true); UpdatePreferredWidth(pending_preferred_width_); } diff --git a/chrome/browser/views/extensions/extension_view.h b/chrome/browser/views/extensions/extension_view.h index fc1ef90..3eceee0 100644 --- a/chrome/browser/views/extensions/extension_view.h +++ b/chrome/browser/views/extensions/extension_view.h @@ -37,9 +37,7 @@ class ExtensionView : public views::NativeViewHost { Extension* extension() const; RenderViewHost* render_view_host() const; void DidStopLoading(); - void set_is_clipped(bool is_clipped) { is_clipped_ = is_clipped; } - bool is_toolstrip() const { return is_toolstrip_; } - void set_is_toolstrip(bool is) { is_toolstrip_ = is; } + void SetIsClipped(bool is_clipped); // Notification from ExtensionHost. void UpdatePreferredWidth(int pref_width); @@ -94,15 +92,9 @@ class ExtensionView : public views::NativeViewHost { // Note: the view does not own its container. ExtensionContainer* container_; - // Whether the RenderView has finished loading. - bool did_stop_loading_; - // Whether this extension view is clipped. bool is_clipped_; - // Whether this view is currently displaying in toolstrip mode. - bool is_toolstrip_; - DISALLOW_COPY_AND_ASSIGN(ExtensionView); }; diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 7a20754..8214db3 100755 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -2086,6 +2086,8 @@ 'browser/views/event_utils.h', 'browser/views/extensions/extension_install_prompt.cc', 'browser/views/extensions/extension_pack_dialog.cc', + 'browser/views/extensions/extension_popup.cc', + 'browser/views/extensions/extension_popup.h', 'browser/views/extensions/extension_shelf.cc', 'browser/views/extensions/extension_shelf.h', 'browser/views/extensions/extension_view.cc', @@ -2597,6 +2599,8 @@ ['include', '^browser/views/dragged_tab_controller.h'], ['include', '^browser/views/event_utils.cc'], ['include', '^browser/views/event_utils.h'], + ['include', '^browser/views/extensions/extension_popup.cc'], + ['include', '^browser/views/extensions/extension_popup.h'], ['include', '^browser/views/extensions/extension_shelf.cc'], ['include', '^browser/views/extensions/extension_shelf.h'], ['include', '^browser/views/extensions/extension_view.cc'], diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc index 9760efe..2ac504e 100644 --- a/chrome/common/extensions/extension.cc +++ b/chrome/common/extensions/extension.cc @@ -372,6 +372,37 @@ ExtensionAction* Extension::LoadExtensionActionHelper( } result->set_name(name); + // Read the action's |popup| (optional). + DictionaryValue* popup = NULL; + if (page_action->HasKey(keys::kPageActionPopup) && + !page_action->GetDictionary(keys::kPageActionPopup, &popup)) { + *error = errors::kInvalidPageActionPopup; + return NULL; + } + if (popup) { + std::string url_str; + if (!popup->GetString(keys::kPageActionPopupPath, &url_str)) { + *error = ExtensionErrorUtils::FormatErrorMessage( + errors::kInvalidPageActionPopupPath, "<missing>"); + return NULL; + } + GURL url = GetResourceURL(url_str); + if (!url.is_valid()) { + *error = ExtensionErrorUtils::FormatErrorMessage( + errors::kInvalidPageActionPopupPath, url_str); + return NULL; + } + result->set_popup_url(url); + + int height; + if (!popup->GetInteger(keys::kPageActionPopupHeight, &height)) { + *error = ExtensionErrorUtils::FormatErrorMessage( + errors::kInvalidPageActionPopupHeight, "<missing>"); + return NULL; + } + result->set_popup_height(height); + } + return result.release(); } diff --git a/chrome/common/extensions/extension_action.h b/chrome/common/extensions/extension_action.h index f091487..630671c 100644 --- a/chrome/common/extensions/extension_action.h +++ b/chrome/common/extensions/extension_action.h @@ -10,6 +10,7 @@ #include <vector> #include "base/basictypes.h" +#include "googleurl/src/gurl.h" class ExtensionAction { public: @@ -42,6 +43,12 @@ class ExtensionAction { icon_paths_.push_back(icon_path); } + const GURL& popup_url() const { return popup_url_; } + void set_popup_url(const GURL& url) { popup_url_ = url; } + + const int popup_height() const { return popup_height_; } + void set_popup_height(int height) { popup_height_ = height; } + private: static int next_command_id_; @@ -65,6 +72,10 @@ class ExtensionAction { // An integer for use with the browser's command system. These should always // be in the range [IDC_BROWSER_ACTION_FIRST, IDC_BROWSER_ACTION_LAST]. int command_id_; + + // If the action has a popup, it has a URL and a height. + GURL popup_url_; + int popup_height_; }; typedef std::map<std::string, ExtensionAction*> ExtensionActionMap; diff --git a/chrome/common/extensions/extension_constants.cc b/chrome/common/extensions/extension_constants.cc index 8281d42..228197b 100644 --- a/chrome/common/extensions/extension_constants.cc +++ b/chrome/common/extensions/extension_constants.cc @@ -20,6 +20,9 @@ const wchar_t* kName = L"name"; const wchar_t* kPageActionId = L"id"; const wchar_t* kPageActions = L"page_actions"; const wchar_t* kPageActionIcons = L"icons"; +const wchar_t* kPageActionPopup = L"popup"; +const wchar_t* kPageActionPopupHeight = L"height"; +const wchar_t* kPageActionPopupPath = L"path"; const wchar_t* kPermissions = L"permissions"; const wchar_t* kPlugins = L"plugins"; const wchar_t* kPluginsPath = L"path"; @@ -99,6 +102,12 @@ const char* kInvalidPageActionIconPaths = "Required value 'page_actions[*].icons' is missing or invalid."; const char* kInvalidPageActionId = "Required value 'id' is missing or invalid."; +const char* kInvalidPageActionPopup = + "Invalid type for page action popup."; +const char* kInvalidPageActionPopupHeight = + "Invalid value for page action popup height [*]."; +const char* kInvalidPageActionPopupPath = + "Invalid value for page action popup path [*]."; const char* kInvalidPageActionTypeValue = "Invalid value for 'page_actions[*].type', expected 'tab' or 'permanent'."; const char* kInvalidPermissions = diff --git a/chrome/common/extensions/extension_constants.h b/chrome/common/extensions/extension_constants.h index 8b321b6..125a47f 100644 --- a/chrome/common/extensions/extension_constants.h +++ b/chrome/common/extensions/extension_constants.h @@ -21,6 +21,9 @@ namespace extension_manifest_keys { extern const wchar_t* kPageActionId; extern const wchar_t* kPageActions; extern const wchar_t* kPageActionIcons; + extern const wchar_t* kPageActionPopup; + extern const wchar_t* kPageActionPopupHeight; + extern const wchar_t* kPageActionPopupPath; extern const wchar_t* kPermissions; extern const wchar_t* kPlugins; extern const wchar_t* kPluginsPath; @@ -87,6 +90,9 @@ namespace extension_manifest_errors { extern const char* kInvalidPageActionIconPath; extern const char* kInvalidPageActionIconPaths; extern const char* kInvalidPageActionId; + extern const char* kInvalidPageActionPopup; + extern const char* kInvalidPageActionPopupHeight; + extern const char* kInvalidPageActionPopupPath; extern const char* kInvalidPageActionTypeValue; extern const char* kInvalidPermissions; extern const char* kInvalidPermission; diff --git a/chrome/common/view_types.h b/chrome/common/view_types.h index 7272250..c010244 100755 --- a/chrome/common/view_types.h +++ b/chrome/common/view_types.h @@ -1,4 +1,4 @@ -// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// 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. @@ -16,6 +16,7 @@ class ViewType { EXTENSION_TOOLSTRIP, EXTENSION_MOLE, EXTENSION_BACKGROUND_PAGE, + EXTENSION_POPUP, DEV_TOOLS_UI, INTERSTITIAL_PAGE, }; diff --git a/chrome/test/data/extensions/samples/set_page_color/icon.png b/chrome/test/data/extensions/samples/set_page_color/icon.png Binary files differnew file mode 100755 index 0000000..1f1c906 --- /dev/null +++ b/chrome/test/data/extensions/samples/set_page_color/icon.png diff --git a/chrome/test/data/extensions/samples/set_page_color/manifest.json b/chrome/test/data/extensions/samples/set_page_color/manifest.json new file mode 100755 index 0000000..081681a --- /dev/null +++ b/chrome/test/data/extensions/samples/set_page_color/manifest.json @@ -0,0 +1,12 @@ +{ + "name": "A browser action with a popup that changes the page color.", + "version": "1.0", + "permissions": [ + "tabs", "http://*/*" + ], + "browser_action": { + "name": "Set this page's color.", + "icons": ["icon.png"], + "popup": { "path": "popup.html", "height": 78 } + } +}
\ No newline at end of file diff --git a/chrome/test/data/extensions/samples/set_page_color/popup.html b/chrome/test/data/extensions/samples/set_page_color/popup.html new file mode 100755 index 0000000..e781b9b --- /dev/null +++ b/chrome/test/data/extensions/samples/set_page_color/popup.html @@ -0,0 +1,45 @@ +<style>
+body {
+ overflow: hidden;
+ margin: 0px;
+ padding: 0px;
+ background: #cccccc;
+ border: 1px solid black;
+}
+div {
+ cursor: pointer;
+ text-align: center;
+ padding: 1px 3px;
+ font: menu;
+ width: 100px;
+}
+div:hover {
+ background: #aaaaaa;
+}
+#red {
+ border: 1px solid red;
+ color: red;
+}
+#blue {
+ border: 1px solid blue;
+ color: blue;
+}
+#green {
+ border: 1px solid green;
+ color: green;
+}
+#yellow {
+ border: 1px solid yellow;
+ color: yellow;
+}
+</style>
+<script>
+function click(color) {
+ chrome.tabs.executeScript(null,
+ {code:"document.body.bgColor='" + color.id + "'"});
+}
+</script>
+<div onclick="click(this)" id="red">red</div>
+<div onclick="click(this)" id="blue">blue</div>
+<div onclick="click(this)" id="green">green</div>
+<div onclick="click(this)" id="yellow">yellow</div>
diff --git a/views/controls/button/menu_button.h b/views/controls/button/menu_button.h index e17f65d..1f5f8e8 100644 --- a/views/controls/button/menu_button.h +++ b/views/controls/button/menu_button.h @@ -47,7 +47,7 @@ class MenuButton : public TextButton { // These methods are overriden to implement a simple push button // behavior virtual bool OnMousePressed(const MouseEvent& e); - void OnMouseReleased(const MouseEvent& e, bool canceled); + virtual void OnMouseReleased(const MouseEvent& e, bool canceled); virtual bool OnKeyReleased(const KeyEvent& e); virtual void OnMouseExited(const MouseEvent& event); |