diff options
author | twiz@google.com <twiz@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-16 23:28:26 +0000 |
---|---|---|
committer | twiz@google.com <twiz@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-16 23:28:26 +0000 |
commit | b2725756d6f40456048f052427746f347dcd8219 (patch) | |
tree | cf02ff046b428d9418291f0f7c6376ac71bbb1ea /chrome | |
parent | ba1d6b0893d6d07cf1fb924f4212446d4e6136d4 (diff) | |
download | chromium_src-b2725756d6f40456048f052427746f347dcd8219.zip chromium_src-b2725756d6f40456048f052427746f347dcd8219.tar.gz chromium_src-b2725756d6f40456048f052427746f347dcd8219.tar.bz2 |
Refactoring of the chrome.experimental.popup API implementation to allow display of pop-ups for extensions viewed through a tab-contents view.I added a new class, ExtensionPopupHost. This class implements the necessary environment for managing child popup windows from either an ExtensionHost, or an ExtensionDOMUI. Note that this class is added as a member to ExtensionHost and ExtensionDOMUI.
I decided to take this approach to prevent multiple inheritance of the NotificationObserver class: Both ExtensionPopupHost and ExtensionHost must inherit from this class, and I was uncertain of how the system would behave wrt virtual inheritance. Please comment on if I should have used the inheritance approach.
I also removed the customHandler tag (in extension_api.json) that I had added in the initial submission. The arguments in the schema are now those that users of the API see. The nodocs tags were also removed.
The api experimental.popup.getAnchorWindow() has been renamed to popup.getParentWindow, as per a suggestion from Erik K.
BUG=none
TEST=extension_popup_apitest.cc
Review URL: http://codereview.chromium.org/385061
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@32120 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
17 files changed, 757 insertions, 124 deletions
diff --git a/chrome/browser/extensions/extension_dom_ui.cc b/chrome/browser/extensions/extension_dom_ui.cc index 3661d6d..9f118aa 100644 --- a/chrome/browser/extensions/extension_dom_ui.cc +++ b/chrome/browser/extensions/extension_dom_ui.cc @@ -218,6 +218,10 @@ void ExtensionDOMUI::UnregisterChromeURLOverride(const std::string& page, } } +RenderViewHost* ExtensionDOMUI::GetRenderViewHost() { + return tab_contents() ? tab_contents()->render_view_host() : NULL; +} + // static void ExtensionDOMUI::UnregisterChromeURLOverrides( Profile* profile, const Extension::URLOverrideMap& overrides) { diff --git a/chrome/browser/extensions/extension_dom_ui.h b/chrome/browser/extensions/extension_dom_ui.h index 20549ac..0a53607 100644 --- a/chrome/browser/extensions/extension_dom_ui.h +++ b/chrome/browser/extensions/extension_dom_ui.h @@ -5,19 +5,24 @@ #ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_DOM_UI_H_ #define CHROME_BROWSER_EXTENSIONS_EXTENSION_DOM_UI_H_ +#include <string> + #include "base/scoped_ptr.h" #include "chrome/browser/dom_ui/dom_ui.h" #include "chrome/browser/extensions/extension_function_dispatcher.h" +#include "chrome/browser/extensions/extension_popup_host.h" #include "chrome/common/extensions/extension.h" class ListValue; class PrefService; +class RenderViewHost; class TabContents; // This class implements DOMUI for extensions and allows extensions to put UI in // the main tab contents area. class ExtensionDOMUI : public DOMUI, + public ExtensionPopupHost::PopupDelegate, public ExtensionFunctionDispatcher::Delegate { public: explicit ExtensionDOMUI(TabContents* tab_contents); @@ -36,6 +41,10 @@ class ExtensionDOMUI // ExtensionFunctionDispatcher::Delegate virtual Browser* GetBrowser(); + virtual ExtensionDOMUI* GetExtensionDOMUI() { return this; } + + // ExtensionPopupHost::Delegate + virtual RenderViewHost* GetRenderViewHost(); // BrowserURLHandler static bool HandleChromeURLOverride(GURL* url, Profile* profile); diff --git a/chrome/browser/extensions/extension_function_dispatcher.cc b/chrome/browser/extensions/extension_function_dispatcher.cc index 1f67979..cb77169 100644 --- a/chrome/browser/extensions/extension_function_dispatcher.cc +++ b/chrome/browser/extensions/extension_function_dispatcher.cc @@ -11,6 +11,7 @@ #include "chrome/browser/extensions/extension_bookmarks_module.h" #include "chrome/browser/extensions/extension_bookmarks_module_constants.h" #include "chrome/browser/extensions/extension_browser_actions_api.h" +#include "chrome/browser/extensions/extension_dom_ui.h" #include "chrome/browser/extensions/extension_function.h" #include "chrome/browser/extensions/extension_history_api.h" #include "chrome/browser/extensions/extension_i18n_api.h" @@ -239,10 +240,26 @@ Browser* ExtensionFunctionDispatcher::GetBrowser() { return delegate_->GetBrowser(); } +ExtensionPopupHost* ExtensionFunctionDispatcher::GetPopupHost() { + ExtensionHost* extension_host = GetExtensionHost(); + if (extension_host) { + DCHECK(!GetExtensionDOMUI()) << + "Function dispatcher registered in too many environments."; + return extension_host->popup_host(); + } else { + ExtensionDOMUI* dom_ui = GetExtensionDOMUI(); + return dom_ui->popup_host(); + } +} + ExtensionHost* ExtensionFunctionDispatcher::GetExtensionHost() { return delegate_->GetExtensionHost(); } +ExtensionDOMUI* ExtensionFunctionDispatcher::GetExtensionDOMUI() { + return delegate_->GetExtensionDOMUI(); +} + Extension* ExtensionFunctionDispatcher::GetExtension() { ExtensionsService* service = profile()->GetExtensionsService(); DCHECK(service); diff --git a/chrome/browser/extensions/extension_function_dispatcher.h b/chrome/browser/extensions/extension_function_dispatcher.h index d9c0ea5..df50ceb 100644 --- a/chrome/browser/extensions/extension_function_dispatcher.h +++ b/chrome/browser/extensions/extension_function_dispatcher.h @@ -14,8 +14,10 @@ class Browser; class Extension; +class ExtensionDOMUI; class ExtensionFunction; class ExtensionHost; +class ExtensionPopupHost; class Profile; class RenderViewHost; class RenderViewHostDelegate; @@ -33,6 +35,7 @@ class ExtensionFunctionDispatcher { public: virtual Browser* GetBrowser() = 0; virtual ExtensionHost* GetExtensionHost() { return NULL; } + virtual ExtensionDOMUI* GetExtensionDOMUI() { return NULL; } }; // The peer object allows us to notify ExtensionFunctions when we are @@ -43,10 +46,10 @@ class ExtensionFunctionDispatcher { : dispatcher_(dispatcher) {} ExtensionFunctionDispatcher* dispatcher_; - private: - friend class base::RefCounted<Peer>; + private: + friend class base::RefCounted<Peer>; - ~Peer() {} + ~Peer() {} }; // Gets a list of all known extension function names. @@ -79,10 +82,18 @@ class ExtensionFunctionDispatcher { // example, for positioning windows, or alert boxes, or creating tabs. Browser* GetBrowser(); + // Get the extension popup hosting environment for the ExtensionHost + // or ExtensionDOMUI associted with this dispatcher. + ExtensionPopupHost* GetPopupHost(); + // Gets the ExtensionHost associated with this object. In the case of // tab hosted extension pages, this will return NULL. ExtensionHost* GetExtensionHost(); + // Gets the ExtensionDOMUI associated with this object. In the case of + // non-tab-hosted extension pages, this will return NULL. + ExtensionDOMUI* GetExtensionDOMUI(); + // Gets the extension the function is being invoked by. This should not ever // return NULL. Extension* GetExtension(); diff --git a/chrome/browser/extensions/extension_host.cc b/chrome/browser/extensions/extension_host.cc index ba7d54b..c34cb96 100644 --- a/chrome/browser/extensions/extension_host.cc +++ b/chrome/browser/extensions/extension_host.cc @@ -20,7 +20,6 @@ #include "chrome/browser/extensions/extension_message_service.h" #include "chrome/browser/extensions/extension_tabs_module.h" #include "chrome/browser/jsmessage_box_handler.h" -#include "chrome/browser/extensions/extension_popup_api.h" #include "chrome/browser/profile.h" #include "chrome/browser/renderer_host/render_view_host.h" #include "chrome/browser/renderer_host/render_process_host.h" @@ -42,7 +41,6 @@ #include "webkit/glue/context_menu.h" #if defined(TOOLKIT_VIEWS) -#include "chrome/browser/views/extensions/extension_popup.h" #include "views/widget/widget.h" #endif @@ -121,25 +119,14 @@ ExtensionHost::ExtensionHost(Extension* extension, SiteInstance* site_instance, did_stop_loading_(false), document_element_available_(false), url_(url), -#if defined(TOOLKIT_VIEWS) - child_popup_(NULL), -#endif extension_host_type_(host_type) { render_view_host_ = new RenderViewHost(site_instance, this, MSG_ROUTING_NONE); render_view_host_->AllowBindings(BindingsPolicy::EXTENSION); if (enable_dom_automation_) render_view_host_->AllowBindings(BindingsPolicy::DOM_AUTOMATION); - -#if defined(TOOLKIT_VIEWS) - // Listen for view close requests, so that we can dismiss a hosted pop-up - // view, if necessary. - registrar_.Add(this, NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE, - Source<Profile>(profile_)); -#endif } ExtensionHost::~ExtensionHost() { - DismissPopup(); NotificationService::current()->Notify( NotificationType::EXTENSION_HOST_DESTROYED, Source<Profile>(profile_), @@ -229,15 +216,6 @@ void ExtensionHost::Observe(NotificationType type, NavigateToURL(url_); } else if (type == NotificationType::BROWSER_THEME_CHANGED) { InsertThemeCSS(); -#if defined(TOOLKIT_VIEWS) - } else if (type == NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE) { - // If we aren't the host of the popup, then disregard the notification. - if (!child_popup_ || - Details<ExtensionHost>(child_popup_->host()) != details) - return; - - DismissPopup(); -#endif } else { NOTREACHED(); } @@ -324,38 +302,6 @@ void ExtensionHost::InsertThemeCSS() { render_view_host()->InsertCSSInWebFrame(L"", css, "ToolstripThemeCSS"); } -void ExtensionHost::DismissPopup() { -#if defined(TOOLKIT_VIEWS) - if (child_popup_) { - child_popup_->Hide(); - child_popup_->DetachFromBrowser(); - delete child_popup_; - child_popup_ = NULL; - - PopupEventRouter::OnPopupClosed(GetBrowser()->profile(), - view()->render_view_host()->routing_id()); - } -#endif -} - -#if defined(TOOLKIT_VIEWS) -void ExtensionHost::BubbleBrowserWindowMoved(BrowserBubble* bubble) { - DismissPopup(); -} -void ExtensionHost::BubbleBrowserWindowClosing(BrowserBubble* bubble) { - DismissPopup(); -} - -void ExtensionHost::BubbleGotFocus(BrowserBubble* bubble) { -} - -void ExtensionHost::BubbleLostFocus(BrowserBubble* bubble) { - // TODO(twiz): Dismiss the pop-up upon loss of focus of the bubble, but not - // if the focus is transitioning to the host which owns the popup! - // DismissPopup(); -} -#endif // defined(TOOLKIT_VIEWS) - void ExtensionHost::DidStopLoading() { bool notify = !did_stop_loading_; did_stop_loading_ = true; diff --git a/chrome/browser/extensions/extension_host.h b/chrome/browser/extensions/extension_host.h index e9b414d..9407b85 100644 --- a/chrome/browser/extensions/extension_host.h +++ b/chrome/browser/extensions/extension_host.h @@ -10,11 +10,11 @@ #include "base/perftimer.h" #include "base/scoped_ptr.h" #include "chrome/browser/extensions/extension_function_dispatcher.h" +#include "chrome/browser/extensions/extension_popup_host.h" #include "chrome/browser/jsmessage_box_client.h" #include "chrome/browser/renderer_host/render_view_host_delegate.h" #include "chrome/browser/tab_contents/render_view_host_delegate_helper.h" #if defined(TOOLKIT_VIEWS) -#include "chrome/browser/views/browser_bubble.h" #include "chrome/browser/views/extensions/extension_view.h" #elif defined(OS_LINUX) #include "chrome/browser/gtk/extension_view_gtk.h" @@ -26,9 +26,6 @@ class Browser; class Extension; -#if defined(TOOLKIT_VIEWS) -class ExtensionPopup; -#endif class ExtensionProcessManager; class RenderProcessHost; class RenderWidgetHost; @@ -40,10 +37,7 @@ struct WebPreferences; // It handles setting up the renderer process, if needed, with special // privileges available to extensions. It may have a view to be shown in the // in the browser UI, or it may be hidden. -class ExtensionHost : // NOLINT -#if defined(TOOLKIT_VIEWS) - public BrowserBubble::Delegate, -#endif +class ExtensionHost : public ExtensionPopupHost::PopupDelegate, public RenderViewHostDelegate, public RenderViewHostDelegate::View, public ExtensionFunctionDispatcher::Delegate, @@ -84,14 +78,6 @@ class ExtensionHost : // NOLINT } Profile* profile() const { return profile_; } -#if defined(TOOLKIT_VIEWS) - ExtensionPopup* child_popup() const { return child_popup_; } - void set_child_popup(ExtensionPopup* popup) { child_popup_ = popup; } -#endif - - // Dismiss the hosted pop-up, if one is present. - void DismissPopup(); - // Sets the the ViewType of this host (e.g. mole, toolstrip). void SetRenderViewType(ViewType::Type type); @@ -109,22 +95,6 @@ class ExtensionHost : // NOLINT // Insert the theme CSS for a toolstrip/mole. void InsertThemeCSS(); -#if defined(TOOLKIT_VIEWS) - // BrowserBubble::Delegate implementation. - // Called when the Browser Window that this bubble is attached to moves. - virtual void BubbleBrowserWindowMoved(BrowserBubble* bubble); - - // Called with the Browser Window that this bubble is attached to is - // about to close. - virtual void BubbleBrowserWindowClosing(BrowserBubble* bubble); - - // 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); -#endif // defined(TOOLKIT_VIEWS) - // RenderViewHostDelegate implementation. virtual RenderViewHostDelegate::View* GetViewDelegate(); virtual const GURL& GetURL() const { return url_; } @@ -204,6 +174,9 @@ class ExtensionHost : // NOLINT virtual Browser* GetBrowser(); virtual ExtensionHost* GetExtensionHost() { return this; } + // ExtensionPopupHost::Delegate + virtual RenderViewHost* GetRenderViewHost() { return render_view_host(); } + // Returns true if we're hosting a background page. // This isn't valid until CreateRenderView is called. bool is_background_page() const { return !view(); } @@ -238,12 +211,6 @@ class ExtensionHost : // NOLINT // The URL being hosted. GURL url_; -#if defined(TOOLKIT_VIEWS) - // A popup view that is anchored to and owned by this ExtensionHost. However, - // the popup contains its own separate ExtensionHost - ExtensionPopup* child_popup_; -#endif - NotificationRegistrar registrar_; scoped_ptr<ExtensionFunctionDispatcher> extension_function_dispatcher_; diff --git a/chrome/browser/extensions/extension_popup_api.cc b/chrome/browser/extensions/extension_popup_api.cc index 26db2e1..f778e0e 100644 --- a/chrome/browser/extensions/extension_popup_api.cc +++ b/chrome/browser/extensions/extension_popup_api.cc @@ -12,10 +12,12 @@ #include "chrome/common/notification_service.h" #include "chrome/common/notification_source.h" #include "chrome/common/notification_type.h" +#include "chrome/browser/extensions/extension_dom_ui.h" #include "chrome/browser/extensions/extension_host.h" #include "chrome/browser/extensions/extension_message_service.h" #include "chrome/browser/browser.h" #include "chrome/browser/profile.h" +#include "chrome/browser/tab_contents/tab_contents.h" #if defined(TOOLKIT_VIEWS) #include "chrome/browser/views/extensions/extension_popup.h" #include "views/view.h" @@ -32,6 +34,7 @@ namespace { // Errors. const char kBadAnchorArgument[] = "Invalid anchor argument."; const char kInvalidURLError[] = "Invalid URL."; +const char kNotAnExtension[] = "Not an extension view."; // Keys. const wchar_t kUrlKey[] = L"url"; @@ -111,17 +114,62 @@ bool PopupShowFunction::RunImpl() { } #if defined(TOOLKIT_VIEWS) - views::View* extension_view = dispatcher()->GetExtensionHost()->view(); gfx::Point origin(dom_left, dom_top); - views::View::ConvertPointToScreen(extension_view, &origin); + if (!ConvertHostPointToScreen(&origin)) { + error_ = kNotAnExtension; + return false; + } gfx::Rect rect(origin.x(), origin.y(), dom_width, dom_height); + // Pop-up from extension views (ExtensionShelf, etc.), and drop-down when + // in a TabContents view. + BubbleBorder::ArrowLocation arrow_location = + (NULL != dispatcher()->GetExtensionHost()) ? BubbleBorder::BOTTOM_LEFT : + BubbleBorder::TOP_LEFT; popup_ = ExtensionPopup::Show(url, dispatcher()->GetBrowser(), rect, - BubbleBorder::BOTTOM_LEFT); + arrow_location); + + ExtensionPopupHost* popup_host = dispatcher()->GetPopupHost(); + DCHECK(popup_host); + + popup_host->set_child_popup(popup_); + popup_->set_delegate(popup_host); +#endif // defined(TOOLKIT_VIEWS) + return true; +} + +bool PopupShowFunction::ConvertHostPointToScreen(gfx::Point* point) { + DCHECK(point); + + // If the popup is being requested from an ExtensionHost, then compute + // the sreen coordinates based on the views::View object of the ExtensionHost. + if (dispatcher()->GetExtensionHost()) { + // A dispatcher cannot have both an ExtensionHost, and an ExtensionDOMUI. + DCHECK(!dispatcher()->GetExtensionDOMUI()); + +#if defined(TOOLKIT_VIEWS) + views::View* extension_view = dispatcher()->GetExtensionHost()->view(); + if (!extension_view) + return false; + + views::View::ConvertPointToScreen(extension_view, point); +#else + // TODO(port) + NOTIMPLEMENTED(); +#endif // defined(TOOLKIT_VIEWS) + } else if (dispatcher()->GetExtensionDOMUI()) { + // Otherwise, the popup is being requested from a TabContents, so determine + // the screen-space position through the TabContentsView. + ExtensionDOMUI* dom_ui = dispatcher()->GetExtensionDOMUI(); + TabContents* tab_contents = dom_ui->tab_contents(); + if (!tab_contents) + return false; + + gfx::Rect content_bounds; + tab_contents->GetContainerBounds(&content_bounds); + point->Offset(content_bounds.x(), content_bounds.y()); + } - dispatcher()->GetExtensionHost()->set_child_popup(popup_); - popup_->set_delegate(dispatcher()->GetExtensionHost()); -#endif return true; } @@ -144,7 +192,7 @@ void PopupShowFunction::Observe(NotificationType type, SendResponse(false); Release(); // Balanced in Run(). } -#endif +#endif // defined(TOOLKIT_VIEWS) } // static diff --git a/chrome/browser/extensions/extension_popup_api.h b/chrome/browser/extensions/extension_popup_api.h index 96d6d53..9132de1 100644 --- a/chrome/browser/extensions/extension_popup_api.h +++ b/chrome/browser/extensions/extension_popup_api.h @@ -8,6 +8,10 @@ #include "chrome/browser/extensions/extension_function.h" #include "chrome/common/notification_registrar.h" +namespace gfx { +class Point; +} // namespace gfx + class Profile; class ExtensionPopup; @@ -24,6 +28,10 @@ class PopupShowFunction : public AsyncExtensionFunction, DECLARE_EXTENSION_FUNCTION_NAME("experimental.popup.show") private: + // Computes the screen-space position of the frame-relative point in the + // extension view that is requesting to display a popup. + bool ConvertHostPointToScreen(gfx::Point* point); + // NotificationObserver methods. virtual void Observe(NotificationType type, const NotificationSource& source, diff --git a/chrome/browser/extensions/extension_popup_host.cc b/chrome/browser/extensions/extension_popup_host.cc new file mode 100644 index 0000000..20dd1a1 --- /dev/null +++ b/chrome/browser/extensions/extension_popup_host.cc @@ -0,0 +1,95 @@ +// 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/extensions/extension_popup_host.h" + +#if defined(TOOLKIT_VIEWS) +#include "chrome/browser/extensions/extension_popup_api.h" +#endif +#include "chrome/browser/profile.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/renderer_host/render_view_host.h" +#if defined(TOOLKIT_VIEWS) +#include "chrome/browser/views/extensions/extension_popup.h" +#endif +#include "chrome/common/notification_details.h" +#include "chrome/common/notification_source.h" +#include "chrome/common/notification_type.h" + + +ExtensionPopupHost* ExtensionPopupHost::PopupDelegate::popup_host() { + if (!popup_host_.get()) + popup_host_.reset(new ExtensionPopupHost(this)); + + return popup_host_.get(); +} + +ExtensionPopupHost::ExtensionPopupHost(PopupDelegate* delegate) + : // NO LINT +#if defined(TOOLKIT_VIEWS) + child_popup_(NULL), +#endif + delegate_(delegate) { + DCHECK(delegate_); + + // Listen for view close requests, so that we can dismiss a hosted pop-up + // view, if necessary. + registrar_.Add(this, NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE, + Source<Profile>(delegate_->GetBrowser()->profile())); +} + +ExtensionPopupHost::~ExtensionPopupHost() { + DismissPopup(); +} + +#if defined(TOOLKIT_VIEWS) +void ExtensionPopupHost::BubbleBrowserWindowMoved(BrowserBubble* bubble) { + DismissPopup(); +} + +void ExtensionPopupHost::BubbleBrowserWindowClosing(BrowserBubble* bubble) { + DismissPopup(); +} + +void ExtensionPopupHost::BubbleGotFocus(BrowserBubble* bubble) { +} + +void ExtensionPopupHost::BubbleLostFocus(BrowserBubble* bubble) { + // TODO(twiz): Dismiss the pop-up upon loss of focus of the bubble, but not + // if the focus is transitioning to the host which owns the popup! + // DismissPopup(); +} +#endif // defined(TOOLKIT_VIEWS) + +void ExtensionPopupHost::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if (type == NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE) { +#if defined(TOOLKIT_VIEWS) + // If we aren't the host of the popup, then disregard the notification. + if (!child_popup_ || + Details<ExtensionHost>(child_popup_->host()) != details) { + return; + } + DismissPopup(); +#endif + } else { + NOTREACHED(); + } +} + +void ExtensionPopupHost::DismissPopup() { +#if defined(TOOLKIT_VIEWS) + if (child_popup_) { + child_popup_->Hide(); + child_popup_->DetachFromBrowser(); + delete child_popup_; + child_popup_ = NULL; + + PopupEventRouter::OnPopupClosed( + delegate_->GetBrowser()->profile(), + delegate_->GetRenderViewHost()->routing_id()); + } +#endif // defined(TOOLKIT_VIEWS) +} diff --git a/chrome/browser/extensions/extension_popup_host.h b/chrome/browser/extensions/extension_popup_host.h new file mode 100644 index 0000000..4e38470 --- /dev/null +++ b/chrome/browser/extensions/extension_popup_host.h @@ -0,0 +1,104 @@ +// 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_EXTENSIONS_EXTENSION_POPUP_HOST_H_ +#define CHROME_BROWSER_EXTENSIONS_EXTENSION_POPUP_HOST_H_ + +#include "base/scoped_ptr.h" +#include "build/build_config.h" +#if defined(TOOLKIT_VIEWS) +#include "chrome/browser/views/browser_bubble.h" +#endif +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" + +#if defined(TOOLKIT_VIEWS) +class ExtensionPopup; +#endif + +class Browser; +class Profile; +class RenderViewHost; + +// ExtensionPopupHost objects implement the environment necessary to host +// ExtensionPopup views. This class manages the creation and life-time +// of extension pop-up views. +class ExtensionPopupHost : // NOLINT +#if defined(TOOLKIT_VIEWS) + public BrowserBubble::Delegate, +#endif + public NotificationObserver { + public: + // Classes wishing to host pop-ups should inherit from this class, and + // implement the virtual methods below. This class manages the lifetime + // of an ExtensionPopupHost instance. + class PopupDelegate { + public: + PopupDelegate() {} + virtual ~PopupDelegate() {} + virtual Browser* GetBrowser() = 0; + virtual RenderViewHost* GetRenderViewHost() = 0; + + // Constructs, or returns the existing ExtensionPopupHost instance. + ExtensionPopupHost* popup_host(); + private: + scoped_ptr<ExtensionPopupHost> popup_host_; + + DISALLOW_COPY_AND_ASSIGN(PopupDelegate); + }; + + explicit ExtensionPopupHost(PopupDelegate* delegate); + virtual ~ExtensionPopupHost(); + + // Dismiss the hosted pop-up, if one is present. + void DismissPopup(); + +#if defined(TOOLKIT_VIEWS) + ExtensionPopup* child_popup() const { return child_popup_; } + void set_child_popup(ExtensionPopup* popup) { + // An extension may only have one popup active at a given time. + DismissPopup(); + child_popup_ = popup; + } + + // BrowserBubble::Delegate implementation. + // Called when the Browser Window that this bubble is attached to moves. + virtual void BubbleBrowserWindowMoved(BrowserBubble* bubble); + + // Called with the Browser Window that this bubble is attached to is + // about to close. + virtual void BubbleBrowserWindowClosing(BrowserBubble* bubble); + + // 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); +#endif // defined(TOOLKIT_VIEWS) + + // NotificationObserver implementation. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + private: +#if defined(TOOLKIT_VIEWS) + // A popup view that is anchored to and owned by this ExtensionHost. However, + // the popup contains its own separate ExtensionHost + ExtensionPopup* child_popup_; +#endif + + NotificationRegistrar registrar_; + + // Non-owning pointer to the delegate for this host. + PopupDelegate* delegate_; + + // Boolean value used to ensure that the host only registers for event + // notifications once. + bool listeners_registered_; + + DISALLOW_COPY_AND_ASSIGN(ExtensionPopupHost); +}; + +#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_POPUP_HOST_H_ diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 3ecd885..4b3b096 100755 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -1412,6 +1412,8 @@ 'browser/extensions/extension_page_actions_module_constants.h', 'browser/extensions/extension_popup_api.cc', 'browser/extensions/extension_popup_api.h', + 'browser/extensions/extension_popup_host.cc', + 'browser/extensions/extension_popup_host.h', 'browser/extensions/extension_prefs.cc', 'browser/extensions/extension_prefs.h', 'browser/extensions/extension_process_manager.cc', diff --git a/chrome/common/extensions/api/extension_api.json b/chrome/common/extensions/api/extension_api.json index ec16e65..4c83fdd 100755 --- a/chrome/common/extensions/api/extension_api.json +++ b/chrome/common/extensions/api/extension_api.json @@ -1649,7 +1649,6 @@ "name": "show", "type": "function", "description": "Displays a pop-up window hosting an extension view.", - "customHandler": "true", "parameters": [ { "type": "string", @@ -1665,7 +1664,7 @@ "properties": {}, "additionalProperties": { "type": "any" }, "isInstanceOf": "HTMLElement", - "description": "The HTMLElement whose position which serve as the anchor position of the popup." + "description": "A HTML DOM object to which the pop-up's position will be made relative." } } }, @@ -1677,7 +1676,7 @@ ] }, { - "name": "getAnchorWindow", + "name": "getParentWindow", "type": "function", "description": "Returns a reference to the window object of the extension view that launched the popup.", "parameters": [], diff --git a/chrome/common/extensions/docs/experimental.popup.html b/chrome/common/extensions/docs/experimental.popup.html new file mode 100644 index 0000000..957a5e0 --- /dev/null +++ b/chrome/common/extensions/docs/experimental.popup.html @@ -0,0 +1,398 @@ +<!DOCTYPE html><!-- This page is a placeholder for generated extensions api doc. Note: + 1) The <head> information in this page is significant, should be uniform + across api docs and should be edited only with knowledge of the + templating mechanism. + 3) All <body>.innerHTML is genereated as an rendering step. If viewed in a + browser, it will be re-generated from the template, json schema and + authored overview content. + 4) The <body>.innerHTML is also generated by an offline step so that this + page may easily be indexed by search engines. +--><html xmlns="http://www.w3.org/1999/xhtml"><head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <link href="css/ApiRefStyles.css" rel="stylesheet" type="text/css"> + <link href="css/print.css" rel="stylesheet" type="text/css" media="print"> + <script type="text/javascript" src="../../../third_party/jstemplate/jstemplate_compiled.js"> + </script> + <script type="text/javascript" src="js/api_page_generator.js"></script> + <script type="text/javascript" src="js/bootstrap.js"></script> + <title>Popup</title></head><body> <div id="container"> + <!-- SUBTEMPLATES: DO NOT MOVE FROM THIS LOCATION --> + <!-- In particular, sub-templates that recurse, must be used by allowing + jstemplate to make a copy of the template in this section which + are not operated on by way of the jsskip="true" --> + <div style="display:none"> + + <!-- VALUE --> + <div id="valueTemplate"> + <dt> + <var>paramName</var> + <em> + + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span> + <a> Type</a> + </span> + <span> + <span> + array of <span><span></span></span> + </span> + <span>paramType</span> + </span> + </span> + ) + </div> + + </em> + </dt> + <dd class="todo"> + Undocumented. + </dd> + <dd> + Description of this parameter from the json schema. + </dd> + + <!-- OBJECT PROPERTIES --> + <dd> + <dl> + <div> + <div> + </div> + </div> + </dl> + </dd> + </div> <!-- /VALUE --> + + </div> <!-- /SUBTEMPLATES --> + + <a name="top"> </a> + <!-- API HEADER --> + <div id="pageHeader"> + <div id="searchbox"> + <form action="http://www.google.com/cse" id="cse-search-box"> + <div> + <input type="hidden" name="cx" value="002967670403910741006:61_cvzfqtno"> + <input type="hidden" name="ie" value="UTF-8"> + <input type="text" name="q" size="31"> + <input type="submit" name="sa" value="Search"> + </div> + </form> + + <script type="text/javascript" src="http://www.google.com/jsapi"></script> + <script type="text/javascript">google.load("elements", "1", {packages: "transliteration"});</script> + <script type="text/javascript" src="http://www.google.com/coop/cse/t13n?form=cse-search-box&t13n_langs=en"></script> + <script type="text/javascript" src="http://www.google.com/coop/cse/brand?form=cse-search-box&lang=en"></script> + </div> + <div id="pageTitle"> + <h1>Popup</h1> + </div> + </div> <!-- /pageHeader --> + + <div id="pageContent"> + <!-- SIDENAV --> + <div id="leftNav"> + <ul> + <li> <a href="index.html">Home</a></li> + <li> <a href="getstarted.html">Getting Started</a></li> + <li> <a href="overview.html">Overview</a></li> + <li> <a href="devguide.html"><div>Developer's Guide</div></a> + <ul> + <li> Browser UI + <ul> + <li><a href="browserAction.html">Browser Actions</a></li> + <li><a href="options.html">Options Pages</a></li> + <li><a href="override.html">Override Pages</a></li> + <li><a href="pageAction.html">Page Actions</a></li> + <li><a href="themes.html">Themes</a></li> + </ul> + </li> + <li> Browser Interaction + <ul> + <li><a href="bookmarks.html">Bookmarks</a></li> + <li><a href="events.html">Events</a></li> + <li><a href="tabs.html">Tabs</a></li> + <li><a href="windows.html">Windows</a></li> + </ul> + </li> + <li> Implementation + <ul> + <li><a href="background_pages.html">Background Pages</a></li> + <li><a href="content_scripts.html">Content Scripts</a></li> + <li><a href="xhr.html">Cross-Origin XHR</a></li> + <li><a href="messaging.html">Message Passing</a></li> + <li><a href="npapi.html">NPAPI Plugins</a></li> + </ul> + </li> + <li> Finishing + <ul> + <li><a href="autoupdate.html">Autoupdating</a></li> + <li><a href="packaging.html">Packaging</a></li> + </ul> + </li> + </ul> + </li> + <li><a href="tutorials.html"><div>Tutorials</div></a> + <ul> + <li><a href="tut_debugging.html">Debugging</a></li> + </ul> + </li> + <li>Reference + <ul> + <li> Formats + <ul> + <li><a href="manifest.html">Manifest Files</a></li> + <li><a href="match_patterns.html">Match Patterns</a></li> + <!-- <li>Packages (.crx)</li> --> + </ul> + </li> + <li> <a href="api_index.html">chrome.* APIs</a> </li> + <li> <a href="api_other.html">Other APIs</a> </li> + </ul> + </li> + <li><a href="samples.html">Samples</a></li> + </ul> + </div> + + <div id="mainColumn"> + <!-- TABLE OF CONTENTS --> + <div id="toc" style="display: none; "> + <p>Contents</p> + <ol> + <li> + <a>h2Name</a> + <ol> + <li> + <a>h3Name</a> + </li> + </ol> + </li> + <div> + <li> + <a href="#apiReference">API reference</a> + <ol> + <li> + <a href="#properties">Properties</a> + <ol> + <li> + <a href="#property-anchor">propertyName</a> + </li> + </ol> + </li> + <li> + <a href="#methods">Methods</a> + <ol> + <li> + <a href="#method-anchor">methodName</a> + </li> + </ol> + </li> + <li> + <a href="#events">Events</a> + <ol> + <li> + <a href="#event-anchor">eventName</a> + </li> + </ol> + </li> + <li> + <a href="#types">Types</a> + <ol> + <li> + <a href="#id-anchor">id</a> + </li> + </ol> + </li> + </ol> + </li> + </div> + </ol> + </div> + <!-- /TABLE OF CONTENTS --> + + <!-- STATIC CONTENT PLACEHOLDER --> + <div id="static"></div> + + <!-- API PAGE --> + <div class="apiPage" style="display: none; "> + <a name="apiReference"></a> + <h2>API reference: chrome.apiname </h2> + + <!-- PROPERTIES --> + <div class="apiGroup"> + <a name="properties"></a> + <h3 id="properties">Properties</h3> + + <div> + <a></a> + <h4>getLastError</h4> + <div class="summary"> + <!-- Note: intentionally longer 80 columns --> + <span>chrome.extension</span><span>lastError</span> + </div> + <div> + </div> + </div> + + </div> <!-- /apiGroup --> + + <!-- METHODS --> + <div class="apiGroup" id="methods"> + <a name="methods"></a> + <h3>Methods</h3> + + <!-- iterates over all functions --> + <div class="apiItem"> + <a></a> <!-- method-anchor --> + <h4>method name</h4> + + <div class="summary"><span>void</span> + <!-- Note: intentionally longer 80 columns --> + <span>chrome.module.methodName</span>(<span><span>, </span><span></span> + <var><span></span></var></span>)</div> + + <div class="description"> + <p class="todo">Undocumented.</p> + <p> + A description from the json schema def of the function goes here. + </p> + + <!-- PARAMETERS --> + <h4>Parameters</h4> + <dl> + <div> + <div> + </div> + </div> + </dl> + + <!-- RETURNS --> + <h4>Returns</h4> + <dl> + <div> + <div> + </div> + </div> + </dl> + + <!-- CALLBACK --> + <div> + <div> + <h4>Callback function</h4> + <p> + If you specify the <em>callback</em> parameter, + it should specify a function that looks like this: + </p> + + <!-- Note: intentionally longer 80 columns --> + <pre>function(<span>Type param1, Type param2</span>) <span class="subdued">{...}</span>);</pre> + <dl> + <div> + <div> + </div> + </div> + </dl> + </div> + </div> + + </div> <!-- /description --> + + </div> <!-- /apiItem --> + + </div> <!-- /apiGroup --> + + <!-- EVENTS --> + <div class="apiGroup"> + <a name="events"></a> + <h3 id="events">Events</h3> + + <!-- iterates over all events --> + <div class="apiItem"> + <a></a> + <h4>event name</h4> + + <div class="summary"> + <!-- Note: intentionally longer 80 columns --> + <span class="subdued">chrome.bookmarks</span><span>onEvent</span><span class="subdued">.addListener</span>(function(<span>Type param1, Type param2</span>) <span class="subdued">{...}</span>); + </div> + + <div class="description"> + <p class="todo">Undocumented.</p> + <p> + A description from the json schema def of the event goes here. + </p> + + <!-- PARAMETERS --> + <h4>Parameters</h4> + <dl> + <div> + <div> + </div> + </div> + </dl> + + </div> <!-- /decription --> + + </div> <!-- /apiItem --> + + </div> <!-- /apiGroup --> + + <!-- TYPES --> + <div class="apiGroup"> + <a name="types.sort(sortByName)"></a> + <h3 id="types">Types</h3> + + <!-- iterates over all types --> + <div class="apiItem"> + <a></a> + <h4>type name</h4> + + <div> + </div> + + </div> <!-- /apiItem --> + + </div> <!-- /apiGroup --> + + </div> <!-- /apiPage --> + </div> <!-- /mainColumn --> + </div> <!-- /pageContent --> + <div id="pageFooter" --=""> + <p> + Except as otherwise <a href="http://code.google.com/policies.html#restrictions">noted</a>, + the content of this page is licensed under the <a rel="license" href="http://creativecommons.org/licenses/by/3.0/">Creative Commons + Attribution 3.0 License</a>, and code samples are licensed under the + <a rel="license" href="http://code.google.com/google_bsd_license.html">BSD License</a>. + </p> + <p> + ©2009 Google + </p> + +<!-- begin analytics --> +<script src="http://www.google-analytics.com/urchin.js" type="text/javascript"></script> +<script src="http://www.google-analytics.com/ga.js" type="text/javascript"></script> + +<script type="text/javascript"> + // chrome doc tracking + try { + var engdocs = _gat._getTracker("YT-10763712-2"); + engdocs._trackPageview(); + } catch(err) {} + + // code.google.com site-wide tracking + try { + _uacct="UA-18071-1"; + _uanchor=1; + _uff=0; + urchinTracker(); + } + catch(e) {/* urchinTracker not available. */} +</script> +<!-- end analytics --> + + </div> <!-- /pageFooter --> + </div> <!-- /container --> +</body></html> + diff --git a/chrome/renderer/extensions/extension_process_bindings.cc b/chrome/renderer/extensions/extension_process_bindings.cc index ce5295a..e3cfe0a 100644 --- a/chrome/renderer/extensions/extension_process_bindings.cc +++ b/chrome/renderer/extensions/extension_process_bindings.cc @@ -231,8 +231,8 @@ class ExtensionImpl : public ExtensionBase { return v8::FunctionTemplate::New(GetL10nMessage); } else if (name->Equals(v8::String::New("GetPopupView"))) { return v8::FunctionTemplate::New(GetPopupView); - } else if (name->Equals(v8::String::New("GetPopupAnchorView"))) { - return v8::FunctionTemplate::New(GetPopupAnchorView); + } else if (name->Equals(v8::String::New("GetPopupParentWindow"))) { + return v8::FunctionTemplate::New(GetPopupParentWindow); } else if (name->Equals(v8::String::New("SetExtensionActionIcon"))) { return v8::FunctionTemplate::New(SetExtensionActionIcon); } @@ -289,7 +289,7 @@ class ExtensionImpl : public ExtensionBase { return PopupViewFinder(args, ViewType::EXTENSION_POPUP); } - static v8::Handle<v8::Value> GetPopupAnchorView(const v8::Arguments& args) { + static v8::Handle<v8::Value> GetPopupParentWindow(const v8::Arguments& args) { return PopupViewFinder(args, ViewType::EXTENSION_TOOLSTRIP); } diff --git a/chrome/renderer/resources/extension_process_bindings.js b/chrome/renderer/resources/extension_process_bindings.js index 2dbf7b4..2fdaad7 100644 --- a/chrome/renderer/resources/extension_process_bindings.js +++ b/chrome/renderer/resources/extension_process_bindings.js @@ -16,7 +16,7 @@ var chrome = chrome || {}; native function OpenChannelToTab(); native function GetRenderViewId(); native function GetL10nMessage(); - native function GetPopupAnchorView(); + native function GetPopupParentWindow(); native function GetPopupView(); native function SetExtensionActionIcon(); @@ -226,8 +226,8 @@ var chrome = chrome || {}; // in browser coordinates relative to the frame hosting the element. function getAbsoluteRect(domElement) { var rect = findAbsolutePosition(domElement); - rect.width = domElement.width || 0; - rect.height = domElement.height || 0; + rect.width = domElement.offsetWidth || 0; + rect.height = domElement.offsetHeight || 0; return rect; } @@ -319,11 +319,12 @@ var chrome = chrome || {}; chromeHidden.validate(arguments, this.definition.parameters); var retval; - if (this.handleRequest) + if (this.handleRequest) { retval = this.handleRequest.apply(this, arguments); - else + } else { retval = sendRequest(this.name, arguments, - this.definition.parameters); + this.definition.parameters); + } // Validate return value if defined - only in debug. if (chromeHidden.validateCallbacks && @@ -419,9 +420,26 @@ var chrome = chrome || {}; apiFunctions["experimental.popup.show"].handleRequest = function(url, showDetails, callback) { - var internalArgs = [url, getAbsoluteRect(showDetails.relativeTo), - callback]; - return sendRequest(this.name, internalArgs, this.definition.parameters); + // Second argument is a transform from HTMLElement to Rect. + var internalSchema = [ + this.definition.parameters[0], + { + type: "object", + name: "domAnchor", + properties: { + top: { type: "integer", minimum: 0 }, + left: { type: "integer", minimum: 0 }, + width: { type: "integer", minimum: 0 }, + height: { type: "integer", minimum: 0 } + } + }, + this.definition.parameters[2] + ]; + return sendRequest(this.name, + [url, + getAbsoluteRect(showDetails.relativeTo), + callback], + internalSchema); } apiFunctions["experimental.extension.getPopupView"].handleRequest = @@ -429,9 +447,9 @@ var chrome = chrome || {}; return GetPopupView(); } - apiFunctions["experimental.popup.getAnchorWindow"].handleRequest = + apiFunctions["experimental.popup.getParentWindow"].handleRequest = function() { - return GetPopupAnchorView(); + return GetPopupParentWindow(); } var canvas; diff --git a/chrome/test/data/extensions/api_test/popup_api/toolband.html b/chrome/test/data/extensions/api_test/popup_api/toolband.html index fba3577..0d346a2 100644 --- a/chrome/test/data/extensions/api_test/popup_api/toolband.html +++ b/chrome/test/data/extensions/api_test/popup_api/toolband.html @@ -1,5 +1,6 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> <script> - var globalValue = "I am not 42."; window.onload = function() { @@ -42,16 +43,20 @@ window.onload = function() { function closePopup() { chrome.test.listenOnce(chrome.experimental.popup.onClosed, function(){ - chrome.test.assertTrue( - chrome.experimental.extension.getPopupView() == undefined); + // TODO(twiz): getPopupView() should return undefined, but, due to + // shut-down races, it is sometimes still defined. See BUG + //chrome.test.assertTrue( + // chrome.experimental.extension.getPopupView() == undefined); }); chrome.experimental.extension.getPopupView().close(); } ]); } </script> +</head> <body> <div> <span id="anchorHere">TEST</span> </div> </body> +</html> diff --git a/chrome/test/data/extensions/api_test/popup_api/toolband_popup.html b/chrome/test/data/extensions/api_test/popup_api/toolband_popup.html index fd203d2..67d4cdf 100644 --- a/chrome/test/data/extensions/api_test/popup_api/toolband_popup.html +++ b/chrome/test/data/extensions/api_test/popup_api/toolband_popup.html @@ -1,3 +1,4 @@ +<html> <head> <script> function theAnswer() { @@ -5,7 +6,7 @@ function theAnswer() { } function manipulateHost() { - var popupHost = chrome.experimental.popup.getAnchorWindow(); + var popupHost = chrome.experimental.popup.getParentWindow(); if (popupHost && popupHost.globalValue) { popupHost.globalValue = 42; } @@ -14,4 +15,5 @@ function manipulateHost() { </head> <body> Popup-Contents -</body>
\ No newline at end of file +</body> +</html>
\ No newline at end of file |