summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormad@chromium.org <mad@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-11-03 00:37:31 +0000
committermad@chromium.org <mad@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-11-03 00:37:31 +0000
commit1c1c77a5021be0b902240a4f78009a8d8f71d1ac (patch)
treec1afb46b67de923afaac68340ddb0113b7306193
parent23b3f6c67270a6aff5020c9a7279f4ed84192a02 (diff)
downloadchromium_src-1c1c77a5021be0b902240a4f78009a8d8f71d1ac.zip
chromium_src-1c1c77a5021be0b902240a4f78009a8d8f71d1ac.tar.gz
chromium_src-1c1c77a5021be0b902240a4f78009a8d8f71d1ac.tar.bz2
Submitting change from http://codereview.chromium.org/276029/show
BUG=none TEST=none git-svn-id: svn://svn.chromium.org/chrome/trunk/src@30778 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--base/json/json_writer.cc6
-rw-r--r--base/json/json_writer.h4
-rw-r--r--chrome/browser/extensions/extension_function_dispatcher.cc4
-rw-r--r--chrome/browser/extensions/extension_host.cc63
-rw-r--r--chrome/browser/extensions/extension_host.h45
-rw-r--r--chrome/browser/extensions/extension_popup_api.cc164
-rw-r--r--chrome/browser/extensions/extension_popup_api.h50
-rw-r--r--chrome/browser/extensions/extension_popup_apitest.cc13
-rw-r--r--chrome/browser/gtk/extension_view_gtk.h2
-rw-r--r--chrome/browser/views/browser_actions_container.cc10
-rw-r--r--chrome/browser/views/extensions/extension_popup.cc23
-rw-r--r--chrome/browser/views/extensions/extension_popup.h23
-rw-r--r--chrome/browser/views/extensions/extension_view.cc13
-rw-r--r--chrome/browser/views/extensions/extension_view.h6
-rw-r--r--chrome/browser/views/location_bar_view.cc5
-rwxr-xr-xchrome/chrome.gyp12
-rwxr-xr-xchrome/common/extensions/api/extension_api.json72
-rw-r--r--chrome/common/notification_type.h4
-rw-r--r--chrome/common/view_types.cc12
-rw-r--r--chrome/common/view_types.h9
-rw-r--r--chrome/renderer/extensions/extension_process_bindings.cc92
-rw-r--r--chrome/renderer/resources/extension_process_bindings.js93
-rw-r--r--chrome/test/data/extensions/api_test/popup_api/manifest.json8
-rw-r--r--chrome/test/data/extensions/api_test/popup_api/toolband.html58
-rw-r--r--chrome/test/data/extensions/api_test/popup_api/toolband_popup.html17
25 files changed, 756 insertions, 52 deletions
diff --git a/base/json/json_writer.cc b/base/json/json_writer.cc
index dffa3da..133b625 100644
--- a/base/json/json_writer.cc
+++ b/base/json/json_writer.cc
@@ -19,6 +19,9 @@ static const char kPrettyPrintLineEnding[] = "\n";
#endif
/* static */
+const char* JSONWriter::kEmptyArray = "[]";
+
+/* static */
void JSONWriter::Write(const Value* const node,
bool pretty_print,
std::string* json) {
@@ -48,7 +51,7 @@ JSONWriter::JSONWriter(bool pretty_print, std::string* json)
void JSONWriter::BuildJSONString(const Value* const node,
int depth,
bool escape) {
- switch(node->GetType()) {
+ switch (node->GetType()) {
case Value::TYPE_NULL:
json_string_->append("null");
break;
@@ -147,7 +150,6 @@ void JSONWriter::BuildJSONString(const Value* const node,
for (DictionaryValue::key_iterator key_itr = dict->begin_keys();
key_itr != dict->end_keys();
++key_itr) {
-
if (key_itr != dict->begin_keys()) {
json_string_->append(",");
if (pretty_print_)
diff --git a/base/json/json_writer.h b/base/json/json_writer.h
index 29aa69d..0ebee0a 100644
--- a/base/json/json_writer.h
+++ b/base/json/json_writer.h
@@ -33,6 +33,10 @@ class JSONWriter {
bool escape,
std::string* json);
+ // A static, constant JSON string representing an empty array. Useful
+ // for empty JSON argument passing.
+ static const char* kEmptyArray;
+
private:
JSONWriter(bool pretty_print, std::string* json);
diff --git a/chrome/browser/extensions/extension_function_dispatcher.cc b/chrome/browser/extensions/extension_function_dispatcher.cc
index d7105cb..a3f8a4c 100644
--- a/chrome/browser/extensions/extension_function_dispatcher.cc
+++ b/chrome/browser/extensions/extension_function_dispatcher.cc
@@ -17,6 +17,7 @@
#include "chrome/browser/extensions/extension_message_service.h"
#include "chrome/browser/extensions/extension_page_actions_module.h"
#include "chrome/browser/extensions/extension_page_actions_module_constants.h"
+#include "chrome/browser/extensions/extension_popup_api.h"
#include "chrome/browser/extensions/extension_process_manager.h"
#include "chrome/browser/extensions/extension_tabs_module.h"
#include "chrome/browser/extensions/extension_tabs_module_constants.h"
@@ -139,6 +140,9 @@ void FactoryRegistry::ResetFunctions() {
// I18N.
RegisterFunction<GetAcceptLanguagesFunction>();
+ // Popup API.
+ RegisterFunction<PopupShowFunction>();
+
// Test.
RegisterFunction<ExtensionTestPassFunction>();
RegisterFunction<ExtensionTestFailFunction>();
diff --git a/chrome/browser/extensions/extension_host.cc b/chrome/browser/extensions/extension_host.cc
index 2f94612..9435913 100644
--- a/chrome/browser/extensions/extension_host.cc
+++ b/chrome/browser/extensions/extension_host.cc
@@ -18,12 +18,16 @@
#include "chrome/browser/dom_ui/dom_ui_factory.h"
#include "chrome/browser/extensions/extension_message_service.h"
#include "chrome/browser/extensions/extension_tabs_module.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"
#include "chrome/browser/renderer_host/render_widget_host.h"
#include "chrome/browser/renderer_host/render_widget_host_view.h"
#include "chrome/browser/renderer_host/site_instance.h"
+#if defined(TOOLKIT_VIEWS)
+#include "chrome/browser/views/extensions/extension_popup.h"
+#endif
#include "chrome/common/bindings_policy.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/notification_service.h"
@@ -111,14 +115,25 @@ 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_),
@@ -208,6 +223,15 @@ 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();
}
@@ -294,6 +318,38 @@ 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;
@@ -325,6 +381,13 @@ void ExtensionHost::DocumentAvailableInMainFrame(RenderViewHost* rvh) {
registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED,
NotificationService::AllSources());
}
+
+ if (ViewType::EXTENSION_POPUP == GetRenderViewType()) {
+ NotificationService::current()->Notify(
+ NotificationType::EXTENSION_POPUP_VIEW_READY,
+ Source<Profile>(profile_),
+ Details<ExtensionHost>(this));
+ }
}
void ExtensionHost::RunJavaScriptMessage(const std::wstring& message,
diff --git a/chrome/browser/extensions/extension_host.h b/chrome/browser/extensions/extension_host.h
index ea9660d..bf66579 100644
--- a/chrome/browser/extensions/extension_host.h
+++ b/chrome/browser/extensions/extension_host.h
@@ -12,6 +12,7 @@
#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"
@@ -20,8 +21,12 @@
#endif
#include "chrome/common/notification_registrar.h"
+
class Browser;
class Extension;
+#if defined(TOOLKIT_VIEWS)
+class ExtensionPopup;
+#endif
class ExtensionProcessManager;
class RenderProcessHost;
class RenderWidgetHost;
@@ -33,7 +38,11 @@ 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 : public RenderViewHostDelegate,
+class ExtensionHost : // NOLINT
+#if defined(TOOLKIT_VIEWS)
+ public BrowserBubble::Delegate,
+#endif
+ public RenderViewHostDelegate,
public RenderViewHostDelegate::View,
public ExtensionFunctionDispatcher::Delegate,
public NotificationObserver {
@@ -72,6 +81,14 @@ class ExtensionHost : public RenderViewHostDelegate,
}
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);
@@ -89,6 +106,22 @@ class ExtensionHost : public RenderViewHostDelegate,
// 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_; }
@@ -192,12 +225,18 @@ class ExtensionHost : public RenderViewHostDelegate,
// 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_;
- // Only EXTENSION_TOOLSTRIP and EXTENSION_BACKGROUND_PAGE are used here,
- // others are not hostd by ExtensionHost.
+ // Only EXTENSION_TOOLSTRIP, EXTENSION_POPUP, and EXTENSION_BACKGROUND_PAGE
+ // are used here, others are not hosted by ExtensionHost.
ViewType::Type extension_host_type_;
DISALLOW_COPY_AND_ASSIGN(ExtensionHost);
diff --git a/chrome/browser/extensions/extension_popup_api.cc b/chrome/browser/extensions/extension_popup_api.cc
new file mode 100644
index 0000000..3059079
--- /dev/null
+++ b/chrome/browser/extensions/extension_popup_api.cc
@@ -0,0 +1,164 @@
+// 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_api.h"
+
+#include "base/gfx/point.h"
+#include "base/json/json_writer.h"
+#include "base/string_util.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/browser/extensions/extension_host.h"
+#include "chrome/browser/extensions/extension_message_service.h"
+#include "chrome/browser/browser.h"
+#include "chrome/browser/profile.h"
+#if defined(TOOLKIT_VIEWS)
+#include "chrome/browser/views/extensions/extension_popup.h"
+#include "views/view.h"
+#endif
+
+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.";
+
+// Keys.
+const wchar_t kUrlKey[] = L"url";
+const wchar_t kWidthKey[] = L"width";
+const wchar_t kHeightKey[] = L"height";
+const wchar_t kTopKey[] = L"top";
+const wchar_t kLeftKey[] = L"left";
+
+}; // namespace
+
+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() {
+ EXTENSION_FUNCTION_VALIDATE(args_->IsType(Value::TYPE_LIST));
+ const ListValue* args = static_cast<const ListValue*>(args_);
+
+ DictionaryValue* popup_info = NULL;
+ EXTENSION_FUNCTION_VALIDATE(args->GetDictionary(0, &popup_info));
+
+ std::string url_string;
+ EXTENSION_FUNCTION_VALIDATE(popup_info->GetString(kUrlKey,
+ &url_string));
+
+ DictionaryValue* dom_anchor = NULL;
+ EXTENSION_FUNCTION_VALIDATE(args->GetDictionary(1, &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);
+
+ 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 != dispatcher()->GetExtension()->id() ||
+ !url.SchemeIs("chrome-extension")) {
+ error_ = kInvalidURLError;
+ return false;
+ }
+
+#if defined(TOOLKIT_VIEWS)
+ views::View* extension_view = dispatcher()->GetExtensionHost()->view();
+ gfx::Point origin(dom_left, dom_top);
+ views::View::ConvertPointToScreen(extension_view, &origin);
+ gfx::Rect rect(origin.x(), origin.y(), dom_width, dom_height);
+
+ popup_ = ExtensionPopup::Show(url, dispatcher()->GetBrowser(), rect,
+ BubbleBorder::BOTTOM_LEFT);
+
+ dispatcher()->GetExtensionHost()->set_child_popup(popup_);
+ popup_->set_delegate(dispatcher()->GetExtensionHost());
+#endif
+ 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);
+
+ 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
+}
+
+// static
+void PopupEventRouter::OnPopupClosed(Profile* profile,
+ int routing_id) {
+ std::string full_event_name = StringPrintf(
+ extension_popup_module_events::kOnPopupClosed,
+ routing_id);
+
+ profile->GetExtensionMessageService()->DispatchEventToRenderers(
+ full_event_name,
+ base::JSONWriter::kEmptyArray);
+}
diff --git a/chrome/browser/extensions/extension_popup_api.h b/chrome/browser/extensions/extension_popup_api.h
new file mode 100644
index 0000000..96d6d53
--- /dev/null
+++ b/chrome/browser/extensions/extension_popup_api.h
@@ -0,0 +1,50 @@
+// 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_API_H_
+#define CHROME_BROWSER_EXTENSIONS_EXTENSION_POPUP_API_H_
+
+#include "chrome/browser/extensions/extension_function.h"
+#include "chrome/common/notification_registrar.h"
+
+class Profile;
+class ExtensionPopup;
+
+// This extension function shows a pop-up extension view. It is asynchronous
+// because the callback must be invoked only after the associated render
+// process/view has been created and fully initialized.
+class PopupShowFunction : public AsyncExtensionFunction,
+ public NotificationObserver {
+ public:
+ PopupShowFunction();
+
+ virtual void Run();
+ virtual bool RunImpl();
+ DECLARE_EXTENSION_FUNCTION_NAME("experimental.popup.show")
+
+ private:
+ // NotificationObserver methods.
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
+ NotificationRegistrar registrar_;
+
+#if defined(TOOLKIT_VIEWS)
+ // The pop-up view created by this function, saved for access during
+ // event notification. The pop-up is not owned by the PopupShowFunction
+ // instance.
+ ExtensionPopup* popup_;
+#endif
+};
+
+// Event router class for events related to the chrome.popup.* set of APIs.
+class PopupEventRouter {
+ public:
+ static void OnPopupClosed(Profile* profile,
+ int routing_id);
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PopupEventRouter);
+};
+
+#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_POPUP_API_H_
diff --git a/chrome/browser/extensions/extension_popup_apitest.cc b/chrome/browser/extensions/extension_popup_apitest.cc
new file mode 100644
index 0000000..cfd8404
--- /dev/null
+++ b/chrome/browser/extensions/extension_popup_apitest.cc
@@ -0,0 +1,13 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/common/chrome_switches.h"
+
+IN_PROC_BROWSER_TEST_F(ExtensionApiTest, Popup) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableExperimentalExtensionApis);
+ ASSERT_TRUE(RunExtensionTest("popup_api")) << message_;
+}
diff --git a/chrome/browser/gtk/extension_view_gtk.h b/chrome/browser/gtk/extension_view_gtk.h
index 16b7892..2bb2923 100644
--- a/chrome/browser/gtk/extension_view_gtk.h
+++ b/chrome/browser/gtk/extension_view_gtk.h
@@ -38,9 +38,9 @@ class ExtensionViewGtk {
// connection.
void RenderViewCreated();
- private:
RenderViewHost* render_view_host() const;
+ private:
void CreateWidgetHostView();
// True if the contents are being displayed inside the extension shelf.
diff --git a/chrome/browser/views/browser_actions_container.cc b/chrome/browser/views/browser_actions_container.cc
index 92e541a..eb7b2e4 100644
--- a/chrome/browser/views/browser_actions_container.cc
+++ b/chrome/browser/views/browser_actions_container.cc
@@ -275,7 +275,7 @@ void BrowserActionsContainer::AddBrowserAction(Extension* extension) {
BrowserActionView* view = new BrowserActionView(extension, this);
browser_action_views_.push_back(view);
AddChildView(view);
- if (GetParent())
+ if (GetParent())
GetParent()->SchedulePaint();
}
@@ -289,7 +289,7 @@ void BrowserActionsContainer::RemoveBrowserAction(Extension* extension) {
if ((*iter)->button()->extension() == extension) {
RemoveChildView(*iter);
browser_action_views_.erase(iter);
- if (GetParent())
+ if (GetParent())
GetParent()->SchedulePaint();
return;
}
@@ -362,7 +362,8 @@ void BrowserActionsContainer::OnBrowserActionExecuted(
rect.set_y(origin.y());
popup_ = ExtensionPopup::Show(browser_action->popup_url(),
toolbar_->browser(),
- rect);
+ rect,
+ BubbleBorder::TOP_RIGHT);
popup_->set_delegate(this);
popup_button_ = button;
popup_button_->PopupDidShow();
@@ -414,7 +415,8 @@ void BrowserActionsContainer::Observe(NotificationType type,
break;
case NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE:
- if (Details<ExtensionHost>(popup_->host()) != details)
+ // If we aren't the host of the popup, then disregard the notification.
+ if (!popup_ || Details<ExtensionHost>(popup_->host()) != details)
return;
HidePopup();
diff --git a/chrome/browser/views/extensions/extension_popup.cc b/chrome/browser/views/extensions/extension_popup.cc
index a84176e..0be410c 100644
--- a/chrome/browser/views/extensions/extension_popup.cc
+++ b/chrome/browser/views/extensions/extension_popup.cc
@@ -28,12 +28,16 @@ const int ExtensionPopup::kMaxHeight = 600;
ExtensionPopup::ExtensionPopup(ExtensionHost* host,
Widget* frame,
- const gfx::Rect& relative_to)
- : BrowserBubble(host->view(), frame, gfx::Point()),
+ const gfx::Rect& relative_to,
+ BubbleBorder::ArrowLocation arrow_location)
+ : BrowserBubble(host->view(),
+ frame,
+ gfx::Point()),
relative_to_(relative_to),
extension_host_(host) {
host->view()->SetContainer(this);
- registrar_.Add(this, NotificationType::EXTENSION_HOST_DID_STOP_LOADING,
+ registrar_.Add(this,
+ NotificationType::EXTENSION_HOST_DID_STOP_LOADING,
Source<Profile>(host->profile()));
// TODO(erikkay) Some of this border code is derived from InfoBubble.
@@ -44,8 +48,10 @@ ExtensionPopup::ExtensionPopup(ExtensionHost* host,
Widget::DeleteOnDestroy);
gfx::NativeView native_window = frame->GetNativeView();
border_widget_->Init(native_window, bounds());
+
border_ = new BubbleBorder;
- border_->set_arrow_location(BubbleBorder::TOP_RIGHT);
+ border_->set_arrow_location(arrow_location);
+
border_view_ = new views::View;
border_view_->set_background(new BubbleBackground(border_));
border_view_->set_border(border_);
@@ -129,8 +135,10 @@ void ExtensionPopup::OnExtensionPreferredSizeChanged(ExtensionView* view) {
}
// static
-ExtensionPopup* ExtensionPopup::Show(const GURL& url, Browser* browser,
- const gfx::Rect& relative_to) {
+ExtensionPopup* ExtensionPopup::Show(
+ const GURL& url, Browser* browser,
+ const gfx::Rect& relative_to,
+ BubbleBorder::ArrowLocation arrow_location) {
ExtensionProcessManager* manager =
browser->profile()->GetExtensionProcessManager();
DCHECK(manager);
@@ -140,7 +148,8 @@ ExtensionPopup* ExtensionPopup::Show(const GURL& url, Browser* browser,
ExtensionHost* host = manager->CreatePopup(url, browser);
views::Widget* frame = BrowserView::GetBrowserViewForNativeWindow(
browser->window()->GetNativeHandle())->GetWidget();
- ExtensionPopup* popup = new ExtensionPopup(host, frame, relative_to);
+ ExtensionPopup* popup = new ExtensionPopup(host, frame, relative_to,
+ arrow_location);
// If the host had somehow finished loading, then we'd miss the notification
// and not show. This seems to happen in single-process mode.
diff --git a/chrome/browser/views/extensions/extension_popup.h b/chrome/browser/views/extensions/extension_popup.h
index 6ab880d..7db472a 100644
--- a/chrome/browser/views/extensions/extension_popup.h
+++ b/chrome/browser/views/extensions/extension_popup.h
@@ -7,6 +7,7 @@
#include "chrome/browser/extensions/extension_host.h"
#include "chrome/browser/views/browser_bubble.h"
+#include "chrome/browser/views/extensions/extension_view.h"
#include "chrome/browser/views/bubble_border.h"
#include "chrome/common/notification_observer.h"
#include "chrome/common/notification_registrar.h"
@@ -21,14 +22,19 @@ class ExtensionPopup : public BrowserBubble,
public:
virtual ~ExtensionPopup();
- // Create and show a popup with |url| positioned below |relative_to| in
- // screen coordinates. This is anchored to the lower middle of the rect,
- // extending to the left, just like the wrench and page menus.
+ // Create and show a popup with |url| positioned adjacent to |relative_to| in
+ // screen coordinates.
+ // The positioning of the pop-up is determined by |arrow_location| according
+ // to the following logic: The popup is anchored so that the corner indicated
+ // by value of |arrow_location| remains fixed during popup resizes.
+ // If |arrow_location| is BOTTOM_*, then the popup 'pops up', otherwise
+ // the popup 'drops down'.
//
// 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);
+ const gfx::Rect& relative_to,
+ BubbleBorder::ArrowLocation arrow_location);
ExtensionHost* host() const { return extension_host_.get(); }
@@ -43,8 +49,8 @@ class ExtensionPopup : public BrowserBubble,
const NotificationDetails& details);
// ExtensionView::Container overrides.
- virtual void OnExtensionMouseEvent(ExtensionView* view) { };
- virtual void OnExtensionMouseLeave(ExtensionView* view) { };
+ virtual void OnExtensionMouseEvent(ExtensionView* view) { }
+ virtual void OnExtensionMouseLeave(ExtensionView* view) { }
virtual void OnExtensionPreferredSizeChanged(ExtensionView* view);
// The min/max height of popups.
@@ -56,7 +62,8 @@ class ExtensionPopup : public BrowserBubble,
private:
ExtensionPopup(ExtensionHost* host,
views::Widget* frame,
- const gfx::Rect& relative_to);
+ const gfx::Rect& relative_to,
+ BubbleBorder::ArrowLocation);
// The area on the screen that the popup should be positioned relative to.
gfx::Rect relative_to_;
@@ -77,4 +84,4 @@ class ExtensionPopup : public BrowserBubble,
DISALLOW_COPY_AND_ASSIGN(ExtensionPopup);
};
-#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_POPUP_H_
+#endif // CHROME_BROWSER_VIEWS_EXTENSIONS_EXTENSION_POPUP_H_
diff --git a/chrome/browser/views/extensions/extension_view.cc b/chrome/browser/views/extensions/extension_view.cc
index 1d0cfc5..059d391 100644
--- a/chrome/browser/views/extensions/extension_view.cc
+++ b/chrome/browser/views/extensions/extension_view.cc
@@ -155,6 +155,13 @@ void ExtensionView::ViewHierarchyChanged(bool is_add,
CreateWidgetHostView();
}
+void ExtensionView::PreferredSizeChanged() {
+ View::PreferredSizeChanged();
+ if (container_) {
+ container_->OnExtensionPreferredSizeChanged(this);
+ }
+}
+
void ExtensionView::HandleMouseEvent() {
if (container_)
container_->OnExtensionMouseEvent(this);
@@ -171,9 +178,3 @@ void ExtensionView::RenderViewCreated() {
pending_background_.reset();
}
}
-
-void ExtensionView::SetPreferredSize(const gfx::Size& size) {
- views::NativeViewHost::SetPreferredSize(size);
- if (container_)
- container_->OnExtensionPreferredSizeChanged(this);
-}
diff --git a/chrome/browser/views/extensions/extension_view.h b/chrome/browser/views/extensions/extension_view.h
index 8227771..bd63d73 100644
--- a/chrome/browser/views/extensions/extension_view.h
+++ b/chrome/browser/views/extensions/extension_view.h
@@ -27,6 +27,7 @@ class ExtensionView : public views::NativeViewHost {
// (bottom shelf, side bar, etc.)
class Container {
public:
+ virtual ~Container() {}
// Mouse event notifications from the view. (useful for hover UI).
virtual void OnExtensionMouseEvent(ExtensionView* view) = 0;
virtual void OnExtensionMouseLeave(ExtensionView* view) = 0;
@@ -61,7 +62,10 @@ class ExtensionView : public views::NativeViewHost {
const gfx::Rect& current);
virtual void ViewHierarchyChanged(bool is_add,
views::View *parent, views::View *child);
- virtual void SetPreferredSize(const gfx::Size& size);
+
+ protected:
+ // Overridden from views::View.
+ virtual void PreferredSizeChanged();
private:
friend class ExtensionHost;
diff --git a/chrome/browser/views/location_bar_view.cc b/chrome/browser/views/location_bar_view.cc
index edbe5f2..f159d7e 100644
--- a/chrome/browser/views/location_bar_view.cc
+++ b/chrome/browser/views/location_bar_view.cc
@@ -208,7 +208,7 @@ void LocationBarView::Init() {
#else
location_entry_->widget()
#endif
- );
+ ); // NOLINT
AddChildView(&selected_keyword_view_);
selected_keyword_view_.SetFont(font_);
@@ -1304,7 +1304,8 @@ void LocationBarView::PageActionImageView::ExecuteAction(int button) {
Browser* browser = BrowserList::GetLastActiveWithProfile(profile_);
if (!browser)
browser = BrowserList::FindBrowserWithProfile(profile_);
- ExtensionPopup::Show(page_action_->popup_url(), browser, rect);
+ ExtensionPopup::Show(page_action_->popup_url(), browser, rect,
+ BubbleBorder::TOP_RIGHT);
} else {
ExtensionBrowserEventRouter::GetInstance()->PageActionExecuted(
profile_, page_action_->extension_id(), page_action_->id(),
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp
index 907d82b..eaec76b 100755
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -90,6 +90,7 @@
'browser/extensions/extension_storage_apitest.cc',
'browser/extensions/extension_tabs_apitest.cc',
'browser/extensions/extension_i18n_apitest.cc',
+ 'browser/extensions/extension_popup_apitest.cc',
'browser/views/browser_views_accessibility_browsertest.cc',
'browser/views/find_bar_host_browsertest.cc',
# TODO(jam): http://crbug.com/15101 These tests fail on Linux and Mac.
@@ -694,6 +695,7 @@
'common/transport_dib_win.cc',
'common/url_constants.cc',
'common/url_constants.h',
+ 'common/view_types.cc',
'common/view_types.h',
'common/visitedlink_common.cc',
'common/visitedlink_common.h',
@@ -1337,6 +1339,8 @@
'browser/extensions/extension_event_names.h',
'browser/extensions/execute_code_in_tab_function.cc',
'browser/extensions/execute_code_in_tab_function.h',
+ 'browser/extensions/extension_browser_event_router.cc',
+ 'browser/extensions/extension_browser_event_router.h',
'browser/extensions/extension_file_util.cc',
'browser/extensions/extension_file_util.h',
'browser/extensions/extension_function.cc',
@@ -1349,18 +1353,18 @@
'browser/extensions/extension_history_api_constants.h',
'browser/extensions/extension_host.cc',
'browser/extensions/extension_host.h',
+ 'browser/extensions/extension_i18n_api.cc',
+ 'browser/extensions/extension_i18n_api.h',
'browser/extensions/extension_install_ui.cc',
'browser/extensions/extension_install_ui.h',
'browser/extensions/extension_message_service.cc',
'browser/extensions/extension_message_service.h',
- 'browser/extensions/extension_browser_event_router.cc',
- 'browser/extensions/extension_browser_event_router.h',
- 'browser/extensions/extension_i18n_api.cc',
- 'browser/extensions/extension_i18n_api.h',
'browser/extensions/extension_page_actions_module.cc',
'browser/extensions/extension_page_actions_module.h',
'browser/extensions/extension_page_actions_module_constants.cc',
'browser/extensions/extension_page_actions_module_constants.h',
+ 'browser/extensions/extension_popup_api.cc',
+ 'browser/extensions/extension_popup_api.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 6cef9c3..b6f00e3 100755
--- a/chrome/common/extensions/api/extension_api.json
+++ b/chrome/common/extensions/api/extension_api.json
@@ -176,6 +176,23 @@
]
},
{
+ "namespace": "experimental.extension",
+ "types": [],
+ "functions": [
+ {
+ "name": "getPopupView",
+ "type": "function",
+ "description": "Returns a reference to the window object of the popup view.",
+ "nodocs": "true",
+ "parameters": [],
+ "returns": {
+ "type": "object"
+ }
+ }
+ ],
+ "events": []
+ },
+ {
"namespace": "windows",
"types": [
{
@@ -1584,6 +1601,61 @@
"events": []
},
{
+ "namespace": "experimental.popup",
+ "types": [],
+ "functions": [
+ {
+ "name": "show",
+ "type": "function",
+ "description": "Displays a pop-up window hosting an extension view.",
+ "nodocs": "true",
+ "customHandler": "true",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "popupInfo",
+ "properties": {
+ "url": { "type": "string", "description": "The URL of the contents to which the pop-up will be navigated." }
+ }
+ },
+ {
+ "type": "object",
+ "name": "domAnchor",
+ "properties": {
+ "top": { "type": "integer", "minimum": 0, "description": "Top pixel position of the dom-anchor." },
+ "left": { "type": "integer", "minimum": 0, "description": "Left pixel position of the dom-anchor." },
+ "width": { "type": "integer", "minimum": 0, "description": "Pixel width of the dom-anchor." },
+ "height": { "type": "integer", "minimum": 0, "description": "Pixel height of the dom-anchor." }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "getAnchorWindow",
+ "type": "function",
+ "description": "Returns a reference to the window object of the extension view that launched the popup.",
+ "nodocs": "true",
+ "parameters": [],
+ "returns": {
+ "type": "object"
+ }
+ }
+ ],
+ "events": [
+ {
+ "name": "onClosed",
+ "type": "function",
+ "description": "Fired when the popup view is closed.",
+ "parameters": []
+ }
+ ]
+ },
+ {
"namespace": "devtools",
"types": [
],
diff --git a/chrome/common/notification_type.h b/chrome/common/notification_type.h
index 643cca1..5da2c6e 100644
--- a/chrome/common/notification_type.h
+++ b/chrome/common/notification_type.h
@@ -703,6 +703,10 @@ class NotificationType {
// Sent when a background page is ready so other components can load.
EXTENSION_BACKGROUND_PAGE_READY,
+ // Sent when a pop-up extension view is ready, so that notification may
+ // be sent to pending callbacks.
+ EXTENSION_POPUP_VIEW_READY,
+
// Sent when a browser action's state has changed. The source is the
// ExtensionAction* that changed. The details are an ExtensionActionState*.
EXTENSION_BROWSER_ACTION_UPDATED,
diff --git a/chrome/common/view_types.cc b/chrome/common/view_types.cc
new file mode 100644
index 0000000..fe14fd2
--- /dev/null
+++ b/chrome/common/view_types.cc
@@ -0,0 +1,12 @@
+// 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/common/view_types.h"
+
+const char* ViewType::kTabContents = "TAB";
+const char* ViewType::kToolstrip = "TOOLSTRIP";
+const char* ViewType::kMole = "MOLE";
+const char* ViewType::kBackgroundPage = "BACKGROUND";
+const char* ViewType::kPopup = "POPUP";
+const char* ViewType::kAll = "ALL";
diff --git a/chrome/common/view_types.h b/chrome/common/view_types.h
index c010244..794f9ab 100644
--- a/chrome/common/view_types.h
+++ b/chrome/common/view_types.h
@@ -21,6 +21,15 @@ class ViewType {
INTERSTITIAL_PAGE,
};
+ // Constant strings corresponding to the Type enumeration values. Used
+ // when converting JS arguments.
+ static const char* kTabContents;
+ static const char* kToolstrip;
+ static const char* kMole;
+ static const char* kBackgroundPage;
+ static const char* kPopup;
+ static const char* kAll;
+
private:
// This class is for scoping only, so you shouldn't create an instance of it.
ViewType() {}
diff --git a/chrome/renderer/extensions/extension_process_bindings.cc b/chrome/renderer/extensions/extension_process_bindings.cc
index 2a69afd..fee736a 100644
--- a/chrome/renderer/extensions/extension_process_bindings.cc
+++ b/chrome/renderer/extensions/extension_process_bindings.cc
@@ -102,6 +102,9 @@ static L10nMessagesMap* GetL10nMessagesMap(const std::string extension_id) {
}
}
+// A RenderViewVisitor class that iterates through the set of available
+// views, looking for a view of the given type, in the given browser window
+// and within the given extension.
// Used to accumulate the list of views associated with an extension.
class ExtensionViewAccumulator : public RenderViewVisitor {
public:
@@ -115,6 +118,8 @@ class ExtensionViewAccumulator : public RenderViewVisitor {
index_(0) {
}
+ v8::Local<v8::Array> views() { return views_; }
+
virtual bool Visit(RenderView* render_view) {
if (!ViewTypeMatches(render_view->view_type(), view_type_))
return true;
@@ -135,17 +140,26 @@ class ExtensionViewAccumulator : public RenderViewVisitor {
if (!context.IsEmpty()) {
v8::Local<v8::Value> window = context->Global();
DCHECK(!window.IsEmpty());
- views_->Set(v8::Integer::New(index_), window);
- index_++;
- if (view_type_ == ViewType::EXTENSION_BACKGROUND_PAGE)
- return false; // There can be only one...
+
+ if (!OnMatchedView(window))
+ return false;
}
return true;
}
- v8::Local<v8::Array> views() { return views_; }
-
private:
+ // Called on each view found matching the search criteria. Returns false
+ // to terminate the iteration.
+ bool OnMatchedView(const v8::Local<v8::Value>& view_window) {
+ views_->Set(v8::Integer::New(index_), view_window);
+ index_++;
+
+ if (view_type_ == ViewType::EXTENSION_BACKGROUND_PAGE)
+ return false; // There can be only one...
+
+ return true;
+ }
+
// Returns true is |type| "isa" |match|.
static bool ViewTypeMatches(ViewType::Type type, ViewType::Type match) {
if (type == match)
@@ -215,6 +229,10 @@ class ExtensionImpl : public ExtensionBase {
return v8::FunctionTemplate::New(GetRenderViewId);
} else if (name->Equals(v8::String::New("GetL10nMessage"))) {
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("SetExtensionActionIcon"))) {
return v8::FunctionTemplate::New(SetExtensionActionIcon);
}
@@ -228,6 +246,53 @@ class ExtensionImpl : public ExtensionBase {
return v8::String::New(GetStringResource<IDR_EXTENSION_API_JSON>());
}
+ static v8::Handle<v8::Value> PopupViewFinder(
+ const v8::Arguments& args,
+ ViewType::Type viewtype_to_find) {
+ // TODO(twiz) Correct the logic that ties the ownership of the pop-up view
+ // to the hosting view. At the moment we assume that there may only be
+ // a single pop-up view for a given extension. By doing so, we can find
+ // the pop-up view by simply searching for the only pop-up view present.
+ // We also assume that if the current view is a pop-up, we can find the
+ // hosting view by searching for a TOOLSTRIP view.
+ if (args.Length() != 0)
+ return v8::Undefined();
+
+ if (viewtype_to_find != ViewType::EXTENSION_POPUP &&
+ viewtype_to_find != ViewType::EXTENSION_TOOLSTRIP) {
+ NOTREACHED() << L"Requesting invalid view type.";
+ }
+
+ // Disallow searching for a host view if we are a popup view, and likewise
+ // if we are a toolstrip view.
+ RenderView* render_view = bindings_utils::GetRenderViewForCurrentContext();
+ if (!render_view ||
+ render_view->view_type() == viewtype_to_find) {
+ return v8::Undefined();
+ }
+
+ int browser_window_id = render_view->browser_window_id();
+ ExtensionViewAccumulator popup_matcher(ExtensionIdForCurrentContext(),
+ browser_window_id,
+ viewtype_to_find);
+ RenderView::ForEach(&popup_matcher);
+
+ if (0 == popup_matcher.views()->Length())
+ return v8::Undefined();
+ DCHECK(popup_matcher.views()->Has(0));
+
+ // Return the first view found.
+ return popup_matcher.views()->Get(v8::Integer::New(0));
+ }
+
+ static v8::Handle<v8::Value> GetPopupView(const v8::Arguments& args) {
+ return PopupViewFinder(args, ViewType::EXTENSION_POPUP);
+ }
+
+ static v8::Handle<v8::Value> GetPopupAnchorView(const v8::Arguments& args) {
+ return PopupViewFinder(args, ViewType::EXTENSION_TOOLSTRIP);
+ }
+
static v8::Handle<v8::Value> GetExtensionViews(const v8::Arguments& args) {
if (args.Length() != 2)
return v8::Undefined();
@@ -242,15 +307,17 @@ class ExtensionImpl : public ExtensionBase {
std::string view_type_string = *v8::String::Utf8Value(args[1]->ToString());
// |view_type| == ViewType::INVALID means getting any type of views.
ViewType::Type view_type = ViewType::INVALID;
- if (view_type_string == "TOOLSTRIP") {
+ if (view_type_string == ViewType::kToolstrip) {
view_type = ViewType::EXTENSION_TOOLSTRIP;
- } else if (view_type_string == "MOLE") {
+ } else if (view_type_string == ViewType::kMole) {
view_type = ViewType::EXTENSION_MOLE;
- } else if (view_type_string == "BACKGROUND") {
+ } else if (view_type_string == ViewType::kBackgroundPage) {
view_type = ViewType::EXTENSION_BACKGROUND_PAGE;
- } else if (view_type_string == "TAB") {
+ } else if (view_type_string == ViewType::kTabContents) {
view_type = ViewType::TAB_CONTENTS;
- } else if (view_type_string != "ALL") {
+ } else if (view_type_string == ViewType::kPopup) {
+ view_type = ViewType::EXTENSION_POPUP;
+ } else if (view_type_string != ViewType::kAll) {
return v8::Undefined();
}
@@ -414,7 +481,8 @@ class ExtensionImpl : public ExtensionBase {
// A special request for setting the extension action icon. This function
// accepts a canvas ImageData object, so it needs to do extra processing
// before sending the request to the browser.
- static v8::Handle<v8::Value> SetExtensionActionIcon(const v8::Arguments& args) {
+ static v8::Handle<v8::Value> SetExtensionActionIcon(
+ const v8::Arguments& args) {
v8::Local<v8::Object> details = args[1]->ToObject();
v8::Local<v8::Object> image_data =
details->Get(v8::String::New("imageData"))->ToObject();
diff --git a/chrome/renderer/resources/extension_process_bindings.js b/chrome/renderer/resources/extension_process_bindings.js
index 3d3fb88..6a0584b 100644
--- a/chrome/renderer/resources/extension_process_bindings.js
+++ b/chrome/renderer/resources/extension_process_bindings.js
@@ -16,6 +16,8 @@ var chrome = chrome || {};
native function OpenChannelToTab();
native function GetRenderViewId();
native function GetL10nMessage();
+ native function GetPopupAnchorView();
+ native function GetPopupView();
native function SetExtensionActionIcon();
if (!chrome)
@@ -187,6 +189,48 @@ var chrome = chrome || {};
};
}
+ // Helper function for positioning pop-up windows relative to DOM objects.
+ // Returns the absolute position of the given element relative to the hosting
+ // browser frame.
+ function findAbsolutePosition(domElement) {
+ var curleft = curtop = 0;
+ var parentNode = domElement.parentNode
+
+ // Ascend through the parent hierarchy, taking into account object nesting
+ // and scoll positions.
+ if (domElement.offsetParent) {
+ do {
+ if (domElement.offsetLeft) curleft += domElement.offsetLeft;
+ if (domElement.offsetTop) curtop += domElement.offsetTop;
+
+ if (domElement.scrollLeft) curleft -= domElement.scrollLeft;
+ if (domElement.scrollTop) curtop -= domElement.scrollTop;
+
+ if (parentNode != domElement.offsetParent) {
+ while(parentNode != null && parentNode != domElement.offsetParent) {
+ if (parentNode.scrollLeft) curleft -= parentNode.scrollLeft;
+ if (parentNode.scrollTop) curtop -= parentNode.scrollTop;
+ parentNode = parentNode.parentNode;
+ }
+ }
+ } while ((domElement = domElement.offsetParent) != null);
+ }
+
+ return {
+ top: curtop,
+ left: curleft
+ };
+ }
+
+ // Returns the coordiates of the rectangle encompassing the domElement,
+ // 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;
+ return rect;
+ }
+
// --- Setup additional api's not currently handled in common/extensions/api
// Page action events send (pageActionId, {tabId, tabUrl}).
@@ -221,6 +265,12 @@ var chrome = chrome || {};
new chrome.Event("toolstrip.onCollapsed." + renderViewId);
}
+ function setupPopupEvents(renderViewId) {
+ chrome.experimental.popup = chrome.experimental.popup || {};
+ chrome.experimental.popup.onClosed =
+ new chrome.Event("experimental.popup.onClosed." + renderViewId);
+ }
+
chromeHidden.onLoad.addListener(function (extensionId) {
chrome.initExtension(extensionId);
@@ -266,13 +316,23 @@ var chrome = chrome || {};
apiFunctions[apiFunction.name] = apiFunction;
module[functionDef.name] = bind(apiFunction, function() {
- chromeHidden.validate(arguments, this.definition.parameters);
+ // If the function is marked with a custom handler, then bypass
+ // validation of the arguments. This flag is required for
+ // extensions taking DOM objects as arguments. DOM objects
+ // cannot be described using the type system in extension_api.json,
+ // and so cannot be validated.
+ // A good practice is to extract the primitive data needed to
+ // service the request, and then invoke validate against that data.
+ // See popup.show(...) for an example.
+ if (!functionDef.customHandler) {
+ chromeHidden.validate(arguments, this.definition.parameters);
+ }
if (this.handleRequest)
return this.handleRequest.apply(this, arguments);
else
return sendRequest(this.name, arguments,
- this.definition.parameters);
+ this.definition.parameters);
});
});
}
@@ -358,6 +418,34 @@ var chrome = chrome || {};
return GetL10nMessage(message_name, placeholders);
}
+ apiFunctions["experimental.popup.show"].handleRequest =
+ function(url, showDetails, callback) {
+ if (!showDetails || !showDetails.relativeTo) {
+ throw new Error("showDetails.relativeTo argument missing.");
+ }
+
+ var position = getAbsoluteRect(showDetails.relativeTo);
+ var popUpInfo = {
+ "url": url
+ };
+ var domAnchor = position;
+ var modifiedArgs = [popUpInfo, domAnchor, callback];
+ chromeHidden.validate(modifiedArgs, this.definition.parameters);
+
+ return sendRequest(this.name, modifiedArgs,
+ this.definition.parameters);
+ }
+
+ apiFunctions["experimental.extension.getPopupView"].handleRequest =
+ function() {
+ return GetPopupView();
+ }
+
+ apiFunctions["experimental.popup.getAnchorWindow"].handleRequest =
+ function() {
+ return GetPopupAnchorView();
+ }
+
var canvas;
function setIconCommon(details, name, parameters) {
var EXTENSION_ACTION_ICON_SIZE = 19;
@@ -425,6 +513,7 @@ var chrome = chrome || {};
setupBrowserActionEvent(extensionId);
setupPageActionEvents(extensionId);
setupToolstripEvents(GetRenderViewId());
+ setupPopupEvents(GetRenderViewId());
});
if (!chrome.experimental)
diff --git a/chrome/test/data/extensions/api_test/popup_api/manifest.json b/chrome/test/data/extensions/api_test/popup_api/manifest.json
new file mode 100644
index 0000000..01c2cee
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/popup_api/manifest.json
@@ -0,0 +1,8 @@
+{
+ "name": "Popup tester",
+ "version": "0.1",
+ "description": "apitest for the popup api",
+ "toolstrips": [
+ "toolband.html"
+ ]
+}
diff --git a/chrome/test/data/extensions/api_test/popup_api/toolband.html b/chrome/test/data/extensions/api_test/popup_api/toolband.html
new file mode 100644
index 0000000..dd87769
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/popup_api/toolband.html
@@ -0,0 +1,58 @@
+<script>
+
+var globalValue = "I am not 42.";
+
+window.onload = function() {
+ chrome.test.runTests([
+ function show() {
+ var showDetails = {
+ "relativeTo": document.getElementById("anchorHere")
+ };
+ chrome.experimental.popup.show("toolband_popup.html",
+ showDetails,
+ chrome.test.callbackPass(function() {
+ chrome.test.assertTrue(
+ chrome.experimental.extension.getPopupView() != undefined);
+ }));
+ },
+
+ function accessPopup() {
+ var popupView = chrome.experimental.extension.getPopupView();
+ chrome.test.assertTrue(popupView != undefined,
+ "Unable to access popup view.");
+
+ chrome.test.assertTrue(popupView.theAnswer != undefined,
+ "Unable to access popup contents.");
+
+ chrome.test.assertEq(42, popupView.theAnswer());
+ chrome.test.succeed();
+ },
+
+ function accessHost() {
+ var popupView = chrome.experimental.extension.getPopupView();
+ chrome.test.assertTrue(popupView != undefined,
+ "Unable to access popup view.");
+
+ chrome.test.assertTrue(popupView.manipulateHost != undefined,
+ "Unable to access popup contents.");
+
+ popupView.manipulateHost();
+ chrome.test.assertEq(42, globalValue);
+ chrome.test.succeed();
+ },
+
+ function closePopup() {
+ chrome.test.listenOnce(chrome.experimental.popup.onClosed, function(){
+ chrome.test.assertTrue(
+ chrome.experimental.extension.getPopupView() == undefined);
+ });
+ chrome.experimental.extension.getPopupView().close();
+ }
+ ]);
+}
+</script>
+<body>
+<div>
+<span id="anchorHere">TEST</span>
+</div>
+</body>
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
new file mode 100644
index 0000000..fd203d2
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/popup_api/toolband_popup.html
@@ -0,0 +1,17 @@
+<head>
+<script>
+function theAnswer() {
+ return 42;
+}
+
+function manipulateHost() {
+ var popupHost = chrome.experimental.popup.getAnchorWindow();
+ if (popupHost && popupHost.globalValue) {
+ popupHost.globalValue = 42;
+ }
+}
+</script>
+</head>
+<body>
+Popup-Contents
+</body> \ No newline at end of file