Index: chrome/browser/extensions/extension_popup_api.cc |
=================================================================== |
--- chrome/browser/extensions/extension_popup_api.cc (revision 74596) |
+++ chrome/browser/extensions/extension_popup_api.cc (working copy) |
@@ -1,555 +0,0 @@ |
-// Copyright (c) 2011 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_api.h" |
- |
-#include <string> |
- |
-#include "base/json/json_writer.h" |
-#include "base/string_util.h" |
-#include "base/stringprintf.h" |
-#include "base/values.h" |
-#include "chrome/browser/extensions/extension_event_router.h" |
-#include "chrome/browser/extensions/extension_host.h" |
-#include "chrome/browser/extensions/extension_web_ui.h" |
-#include "chrome/browser/profiles/profile.h" |
-#include "chrome/browser/renderer_host/render_view_host.h" |
-#include "chrome/browser/renderer_host/render_view_host_delegate.h" |
-#include "chrome/browser/renderer_host/render_widget_host_view.h" |
-#include "chrome/browser/ui/browser.h" |
-#include "chrome/browser/ui/browser_window.h" |
-#include "chrome/browser/ui/window_sizer.h" |
-#include "chrome/common/extensions/extension.h" |
-#include "chrome/common/notification_details.h" |
-#include "chrome/common/notification_service.h" |
-#include "chrome/common/notification_source.h" |
-#include "chrome/common/notification_type.h" |
-#include "chrome/common/url_constants.h" |
-#include "ui/gfx/point.h" |
- |
-#if defined(TOOLKIT_VIEWS) |
-#include "chrome/browser/ui/views/bubble_border.h" |
-#include "chrome/browser/ui/views/extensions/extension_popup.h" |
-#include "views/view.h" |
-#include "views/focus/focus_manager.h" |
-#endif // TOOLKIT_VIEWS |
- |
-namespace extension_popup_module_events { |
- |
-const char kOnPopupClosed[] = "experimental.popup.onClosed.%d"; |
- |
-} // namespace extension_popup_module_events |
- |
-namespace { |
- |
-// Errors. |
-const char kBadAnchorArgument[] = "Invalid anchor argument."; |
-const char kInvalidURLError[] = "Invalid URL."; |
-const char kNotAnExtension[] = "Not an extension view."; |
-const char kPopupsDisallowed[] = |
- "Popups are only supported from tab-contents views."; |
- |
-// Keys. |
-const char kWidthKey[] = "width"; |
-const char kHeightKey[] = "height"; |
-const char kTopKey[] = "top"; |
-const char kLeftKey[] = "left"; |
-const char kGiveFocusKey[] = "giveFocus"; |
-const char kDomAnchorKey[] = "domAnchor"; |
-const char kBorderStyleKey[] = "borderStyle"; |
-const char kMaxSizeKey[] = "maxSize"; |
- |
-// chrome enumeration values |
-const char kRectangleChrome[] = "rectangle"; |
- |
-#if defined(TOOLKIT_VIEWS) |
-// Returns an updated arrow location, conditioned on the type of intersection |
-// between the popup window, and the screen. |location| is the current position |
-// of the arrow on the popup. |intersection| is the rect representing the |
-// intersection between the popup view and its working screen. |popup_rect| |
-// is the rect of the popup window in screen space coordinates. |
-// The returned location will be horizontally or vertically inverted based on |
-// if the popup has been clipped horizontally or vertically. |
-BubbleBorder::ArrowLocation ToggleArrowLocation( |
- BubbleBorder::ArrowLocation location, const gfx::Rect& intersection, |
- const gfx::Rect& popup_rect) { |
- // If the popup has been clipped horizontally, flip the right-left position |
- // of the arrow. |
- if (intersection.right() != popup_rect.right() || |
- intersection.x() != popup_rect.x()) { |
- location = BubbleBorder::horizontal_mirror(location); |
- } |
- |
- // If the popup has been clipped vertically, flip the bottom-top position |
- // of the arrow. |
- if (intersection.y() != popup_rect.y() || |
- intersection.bottom() != popup_rect.bottom()) { |
- location = BubbleBorder::vertical_mirror(location); |
- } |
- |
- return location; |
-} |
-#endif // TOOLKIT_VIEWS |
- |
-}; // namespace |
- |
-#if defined(TOOLKIT_VIEWS) |
-// ExtensionPopupHost objects implement the environment necessary to host |
-// an ExtensionPopup views for the popup api. Its main job is to handle |
-// its lifetime and to fire the popup-closed event when the popup is closed. |
-// Because the close-on-focus-lost behavior is different from page action |
-// and browser action, it also manages its own focus change listening. The |
-// difference in close-on-focus-lost is that in the page action and browser |
-// action cases, the popup closes when the focus leaves the popup or any of its |
-// children. In this case, the popup closes when the focus leaves the popups |
-// containing view or any of *its* children. |
-class ExtensionPopupHost : public ExtensionPopup::Observer, |
- public views::WidgetFocusChangeListener, |
- public base::RefCounted<ExtensionPopupHost>, |
- public NotificationObserver { |
- public: |
- // Pass |max_popup_size| to specify the maximal size to which the popup |
- // will expand. A width or height of 0 will result in the popup making use |
- // of the default max width or height, respectively: ExtensionPopup:kMaxWidth, |
- // and ExtensionPopup::kMaxHeight. |
- explicit ExtensionPopupHost(ExtensionFunctionDispatcher* dispatcher, |
- const gfx::Size& max_popup_size) |
- : dispatcher_(dispatcher), popup_(NULL), max_popup_size_(max_popup_size) { |
- AddRef(); // Balanced in DispatchPopupClosedEvent(). |
- views::FocusManager::GetWidgetFocusManager()->AddFocusChangeListener(this); |
- } |
- |
- ~ExtensionPopupHost() { |
- views::FocusManager::GetWidgetFocusManager()-> |
- RemoveFocusChangeListener(this); |
- } |
- |
- void set_popup(ExtensionPopup* popup) { |
- popup_ = popup; |
- |
- // Now that a popup has been assigned, listen for subsequent popups being |
- // created in the same extension - we want to disallow more than one |
- // concurrently displayed popup windows. |
- registrar_.Add( |
- this, |
- NotificationType::EXTENSION_HOST_CREATED, |
- Source<ExtensionProcessManager>( |
- dispatcher_->profile()->GetExtensionProcessManager())); |
- |
- registrar_.Add( |
- this, |
- NotificationType::RENDER_VIEW_HOST_WILL_CLOSE_RENDER_VIEW, |
- Source<RenderViewHost>(dispatcher_->render_view_host())); |
- |
- registrar_.Add( |
- this, |
- NotificationType::EXTENSION_FUNCTION_DISPATCHER_DESTROYED, |
- Source<Profile>(dispatcher_->profile())); |
- } |
- |
- // Overridden from ExtensionPopup::Observer |
- virtual void ExtensionPopupIsClosing(ExtensionPopup* popup) { |
- // Unregister the automation resource routing registered upon host |
- // creation. |
- AutomationResourceRoutingDelegate* router = |
- GetRoutingFromDispatcher(dispatcher_); |
- if (router) |
- router->UnregisterRenderViewHost(popup_->host()->render_view_host()); |
- } |
- |
- virtual void ExtensionPopupClosed(void* popup_token) { |
- if (popup_ == popup_token) { |
- popup_ = NULL; |
- DispatchPopupClosedEvent(); |
- } |
- } |
- |
- virtual void ExtensionHostCreated(ExtensionHost* host) { |
- // Pop-up views should share the same automation routing configuration as |
- // their hosting views, so register the RenderViewHost of the pop-up with |
- // the AutomationResourceRoutingDelegate interface of the dispatcher. |
- AutomationResourceRoutingDelegate* router = |
- GetRoutingFromDispatcher(dispatcher_); |
- if (router) |
- router->RegisterRenderViewHost(host->render_view_host()); |
- |
- // Extension hosts created for popup contents exist in the same tab |
- // contents as the ExtensionFunctionDispatcher that requested the popup. |
- // For example, '_blank' link navigation should be routed through the tab |
- // contents that requested the popup. |
- if (dispatcher_ && dispatcher_->delegate()) { |
- host->set_associated_tab_contents( |
- dispatcher_->delegate()->associated_tab_contents()); |
- } |
- } |
- |
- virtual void ExtensionPopupCreated(ExtensionPopup* popup) { |
- // The popup has been created, but not yet displayed, so install the max |
- // size overrides before the first positioning. |
- if (max_popup_size_.width()) |
- popup->set_max_width(max_popup_size_.width()); |
- |
- if (max_popup_size_.height()) |
- popup->set_max_height(max_popup_size_.height()); |
- } |
- |
- virtual void ExtensionPopupResized(ExtensionPopup* popup) { |
- // Reposition the location of the arrow on the popup so that the popup |
- // better fits on the working monitor. |
- gfx::Rect popup_rect = popup->GetOuterBounds(); |
- if (popup_rect.IsEmpty()) |
- return; |
- |
- scoped_ptr<WindowSizer::MonitorInfoProvider> monitor_provider( |
- WindowSizer::CreateDefaultMonitorInfoProvider()); |
- gfx::Rect monitor_bounds( |
- monitor_provider->GetMonitorWorkAreaMatching(popup_rect)); |
- gfx::Rect intersection = monitor_bounds.Intersect(popup_rect); |
- |
- // If the popup is totally out of the bounds of the monitor, then toggling |
- // the arrow location will not result in an un-clipped window. |
- if (intersection.IsEmpty()) |
- return; |
- |
- if (!intersection.Equals(popup_rect)) { |
- // The popup was clipped by the monitor. Toggle the arrow position |
- // to see if that improves visibility. Note: The assignment and |
- // re-assignment of the arrow-position will not trigger an intermittent |
- // display. |
- BubbleBorder::ArrowLocation previous_location = popup->arrow_position(); |
- BubbleBorder::ArrowLocation flipped_location = ToggleArrowLocation( |
- previous_location, intersection, popup_rect); |
- popup->SetArrowPosition(flipped_location); |
- |
- // Double check that toggling the position actually improved the |
- // situation - the popup will be contained entirely in its working monitor |
- // bounds. |
- gfx::Rect flipped_bounds = popup->GetOuterBounds(); |
- gfx::Rect updated_monitor_bounds = |
- monitor_provider->GetMonitorWorkAreaMatching(flipped_bounds); |
- if (!updated_monitor_bounds.Contains(flipped_bounds)) |
- popup->SetArrowPosition(previous_location); |
- } |
- } |
- |
- // Overridden from views::WidgetFocusChangeListener |
- virtual void NativeFocusWillChange(gfx::NativeView focused_before, |
- gfx::NativeView focused_now) { |
- // If the popup doesn't exist, then do nothing. |
- if (!popup_) |
- return; |
- |
- // If no view is to be focused, then Chrome was deactivated, so hide the |
- // popup. |
- if (focused_now) { |
- // On XP, the focus change handler may be invoked when the delegate has |
- // already been revoked. |
- // TODO(twiz@chromium.org): Resolve the trigger of this behaviour. |
- if (!dispatcher_ || !dispatcher_->delegate()) |
- return; |
- |
- gfx::NativeView host_view = |
- dispatcher_->delegate()->GetNativeViewOfHost(); |
- |
- // If the widget hosting the popup contains the newly focused view, then |
- // don't dismiss the pop-up. |
- ExtensionView* view = popup_->host()->view(); |
- if (view) { |
- views::Widget* popup_root_widget = view->GetWidget(); |
- if (popup_root_widget && |
- popup_root_widget->ContainsNativeView(focused_now)) |
- return; |
- } |
- |
- // If the widget or RenderWidgetHostView hosting the extension that |
- // launched the pop-up is receiving focus, then don't dismiss the popup. |
- views::Widget* host_widget = |
- views::Widget::GetWidgetFromNativeView(host_view); |
- if (host_widget && host_widget->ContainsNativeView(focused_now)) |
- return; |
- |
- RenderWidgetHostView* render_host_view = |
- RenderWidgetHostView::GetRenderWidgetHostViewFromNativeView( |
- host_view); |
- if (render_host_view && |
- render_host_view->ContainsNativeView(focused_now)) |
- return; |
- } |
- |
- // We are careful here to let the current event loop unwind before |
- // causing the popup to be closed. |
- MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(popup_, |
- &ExtensionPopup::Close)); |
- } |
- |
- // Overridden from NotificationObserver |
- virtual void Observe(NotificationType type, |
- const NotificationSource& source, |
- const NotificationDetails& details) { |
- if (NotificationType::EXTENSION_HOST_CREATED == type) { |
- Details<ExtensionHost> details_host(details); |
- // Disallow multiple pop-ups from the same extension, by closing |
- // the presently opened popup during construction of any new popups. |
- if (ViewType::EXTENSION_POPUP == details_host->GetRenderViewType() && |
- popup_->host()->extension() == details_host->extension() && |
- Details<ExtensionHost>(popup_->host()) != details) { |
- popup_->Close(); |
- } |
- } else if (NotificationType::RENDER_VIEW_HOST_WILL_CLOSE_RENDER_VIEW == |
- type) { |
- if (Source<RenderViewHost>(dispatcher_->render_view_host()) == source) { |
- // If the parent render view is about to be closed, signal closure |
- // of the popup. |
- popup_->Close(); |
- } |
- } else if (NotificationType::EXTENSION_FUNCTION_DISPATCHER_DESTROYED == |
- type) { |
- // Popups should not outlive the dispatchers that launched them. |
- // Normally, long-lived popups will be dismissed in response to the |
- // RENDER_VIEW_WILL_CLOSE_BY_RENDER_VIEW_HOST message. Unfortunately, |
- // if the hosting view invokes window.close(), there is no communication |
- // back to the browser until the entire view has been torn down, at which |
- // time the dispatcher will be invoked. |
- // Note: The onClosed event will not be fired, but because the hosting |
- // view has already been torn down, it is already too late to process it. |
- // TODO(twiz): Add a communication path between the renderer and browser |
- // for RenderView closure notifications initiatied within the renderer. |
- if (Details<ExtensionFunctionDispatcher>(dispatcher_) == details) { |
- dispatcher_ = NULL; |
- popup_->Close(); |
- } |
- } |
- } |
- |
- private: |
- // Returns the AutomationResourceRoutingDelegate interface for |dispatcher|. |
- static AutomationResourceRoutingDelegate* |
- GetRoutingFromDispatcher(ExtensionFunctionDispatcher* dispatcher) { |
- if (!dispatcher) |
- return NULL; |
- |
- RenderViewHost* render_view_host = dispatcher->render_view_host(); |
- RenderViewHostDelegate* delegate = |
- render_view_host ? render_view_host->delegate() : NULL; |
- |
- return delegate ? delegate->GetAutomationResourceRoutingDelegate() : NULL; |
- } |
- |
- void DispatchPopupClosedEvent() { |
- if (dispatcher_) { |
- PopupEventRouter::OnPopupClosed( |
- dispatcher_->profile(), |
- dispatcher_->render_view_host()->routing_id()); |
- dispatcher_ = NULL; |
- } |
- Release(); // Balanced in ctor. |
- } |
- |
- // A pointer to the dispatcher that handled the request that opened this |
- // popup view. |
- ExtensionFunctionDispatcher* dispatcher_; |
- |
- // A pointer to the popup. |
- ExtensionPopup* popup_; |
- |
- // The maximal size to which the popup is permitted to expand. |
- gfx::Size max_popup_size_; |
- |
- NotificationRegistrar registrar_; |
- |
- DISALLOW_COPY_AND_ASSIGN(ExtensionPopupHost); |
-}; |
-#endif // TOOLKIT_VIEWS |
- |
-PopupShowFunction::PopupShowFunction() |
-#if defined (TOOLKIT_VIEWS) |
- : popup_(NULL) |
-#endif |
-{} |
- |
-void PopupShowFunction::Run() { |
-#if defined(TOOLKIT_VIEWS) |
- if (!RunImpl()) { |
- SendResponse(false); |
- } else { |
- // If the contents of the popup are already available, then immediately |
- // send the response. Otherwise wait for the EXTENSION_POPUP_VIEW_READY |
- // notification. |
- if (popup_->host() && popup_->host()->document_element_available()) { |
- SendResponse(true); |
- } else { |
- AddRef(); |
- registrar_.Add(this, NotificationType::EXTENSION_POPUP_VIEW_READY, |
- NotificationService::AllSources()); |
- registrar_.Add(this, NotificationType::EXTENSION_HOST_DESTROYED, |
- NotificationService::AllSources()); |
- } |
- } |
-#else |
- SendResponse(false); |
-#endif |
-} |
- |
-bool PopupShowFunction::RunImpl() { |
- // Popups may only be displayed from TAB_CONTENTS and EXTENSION_INFOBAR. |
- ViewType::Type view_type = |
- dispatcher()->render_view_host()->delegate()->GetRenderViewType(); |
- if (ViewType::TAB_CONTENTS != view_type && |
- ViewType::EXTENSION_INFOBAR != view_type) { |
- error_ = kPopupsDisallowed; |
- return false; |
- } |
- |
- std::string url_string; |
- EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &url_string)); |
- |
- DictionaryValue* show_details = NULL; |
- EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &show_details)); |
- |
- DictionaryValue* dom_anchor = NULL; |
- EXTENSION_FUNCTION_VALIDATE(show_details->GetDictionary(kDomAnchorKey, |
- &dom_anchor)); |
- |
- int dom_top, dom_left; |
- EXTENSION_FUNCTION_VALIDATE(dom_anchor->GetInteger(kTopKey, |
- &dom_top)); |
- EXTENSION_FUNCTION_VALIDATE(dom_anchor->GetInteger(kLeftKey, |
- &dom_left)); |
- |
- int dom_width, dom_height; |
- EXTENSION_FUNCTION_VALIDATE(dom_anchor->GetInteger(kWidthKey, |
- &dom_width)); |
- EXTENSION_FUNCTION_VALIDATE(dom_anchor->GetInteger(kHeightKey, |
- &dom_height)); |
- EXTENSION_FUNCTION_VALIDATE(dom_top >= 0 && dom_left >= 0 && |
- dom_width >= 0 && dom_height >= 0); |
- |
- // The default behaviour is to give the focus to the pop-up window. |
- bool give_focus = true; |
- if (show_details->HasKey(kGiveFocusKey)) { |
- EXTENSION_FUNCTION_VALIDATE(show_details->GetBoolean(kGiveFocusKey, |
- &give_focus)); |
- } |
- |
- int max_width = 0; |
- int max_height = 0; |
- if (show_details->HasKey(kMaxSizeKey)) { |
- DictionaryValue* max_size = NULL; |
- EXTENSION_FUNCTION_VALIDATE(show_details->GetDictionary(kMaxSizeKey, |
- &max_size)); |
- |
- if (max_size->HasKey(kWidthKey)) |
- EXTENSION_FUNCTION_VALIDATE(max_size->GetInteger(kWidthKey, &max_width)); |
- |
- if (max_size->HasKey(kHeightKey)) |
- EXTENSION_FUNCTION_VALIDATE(max_size->GetInteger(kHeightKey, |
- &max_height)); |
- } |
- |
-#if defined(TOOLKIT_VIEWS) |
- // The default behaviour is to provide the bubble-chrome to the popup. |
- ExtensionPopup::PopupChrome chrome = ExtensionPopup::BUBBLE_CHROME; |
- if (show_details->HasKey(kBorderStyleKey)) { |
- std::string chrome_string; |
- EXTENSION_FUNCTION_VALIDATE(show_details->GetString(kBorderStyleKey, |
- &chrome_string)); |
- if (chrome_string == kRectangleChrome) |
- chrome = ExtensionPopup::RECTANGLE_CHROME; |
- } |
-#endif |
- |
- GURL url = dispatcher()->url().Resolve(url_string); |
- if (!url.is_valid()) { |
- error_ = kInvalidURLError; |
- return false; |
- } |
- |
- // Disallow non-extension requests, or requests outside of the requesting |
- // extension view's extension. |
- const std::string& extension_id = url.host(); |
- if (extension_id != GetExtension()->id() || |
- !url.SchemeIs(chrome::kExtensionScheme)) { |
- error_ = kInvalidURLError; |
- return false; |
- } |
- |
- gfx::Point origin(dom_left, dom_top); |
- if (!dispatcher()->render_view_host()->view()) { |
- error_ = kNotAnExtension; |
- return false; |
- } |
- |
- gfx::Rect content_bounds = |
- dispatcher()->render_view_host()->view()->GetViewBounds(); |
- origin.Offset(content_bounds.x(), content_bounds.y()); |
- gfx::Rect rect(origin.x(), origin.y(), dom_width, dom_height); |
- |
- // Get the correct native window to pass to ExtensionPopup. |
- // ExtensionFunctionDispatcher::Delegate may provide a custom implementation |
- // of this. |
- gfx::NativeWindow window = |
- dispatcher()->delegate()->GetCustomFrameNativeWindow(); |
- if (!window) |
- window = GetCurrentBrowser()->window()->GetNativeHandle(); |
- |
-#if defined(TOOLKIT_VIEWS) |
- BubbleBorder::ArrowLocation arrow_location = BubbleBorder::TOP_LEFT; |
- |
- // ExtensionPopupHost manages it's own lifetime. |
- ExtensionPopupHost* popup_host = |
- new ExtensionPopupHost(dispatcher(), gfx::Size(max_width, max_height)); |
- popup_ = ExtensionPopup::Show(url, |
- GetCurrentBrowser(), |
- dispatcher()->profile(), |
- window, |
- rect, |
- arrow_location, |
- give_focus, |
- false, // inspect_with_devtools |
- chrome, |
- popup_host); // ExtensionPopup::Observer |
- |
- // popup_host will handle focus change listening and close the popup when |
- // focus leaves the containing views hierarchy. |
- popup_->set_close_on_lost_focus(false); |
- popup_host->set_popup(popup_); |
-#endif // defined(TOOLKIT_VIEWS) |
- |
- return true; |
-} |
- |
-void PopupShowFunction::Observe(NotificationType type, |
- const NotificationSource& source, |
- const NotificationDetails& details) { |
-#if defined(TOOLKIT_VIEWS) |
- DCHECK(type == NotificationType::EXTENSION_POPUP_VIEW_READY || |
- type == NotificationType::EXTENSION_HOST_DESTROYED); |
- DCHECK(popup_ != NULL); |
- |
- // Wait for notification that the popup view is ready (and onload has been |
- // called), before completing the API call. |
- if (popup_ && type == NotificationType::EXTENSION_POPUP_VIEW_READY && |
- Details<ExtensionHost>(popup_->host()) == details) { |
- SendResponse(true); |
- Release(); // Balanced in Run(). |
- } else if (popup_ && type == NotificationType::EXTENSION_HOST_DESTROYED && |
- Details<ExtensionHost>(popup_->host()) == details) { |
- // If the host was destroyed, then report failure, and release the remaining |
- // reference. |
- SendResponse(false); |
- Release(); // Balanced in Run(). |
- } |
-#endif // defined(TOOLKIT_VIEWS) |
-} |
- |
-// static |
-void PopupEventRouter::OnPopupClosed(Profile* profile, |
- int routing_id) { |
- std::string full_event_name = base::StringPrintf( |
- extension_popup_module_events::kOnPopupClosed, |
- routing_id); |
- |
- profile->GetExtensionEventRouter()->DispatchEventToRenderers( |
- full_event_name, base::JSONWriter::kEmptyArray, profile, GURL()); |
-} |