From 1c1c77a5021be0b902240a4f78009a8d8f71d1ac Mon Sep 17 00:00:00 2001
From: "mad@chromium.org"
 <mad@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>
Date: Tue, 3 Nov 2009 00:37:31 +0000
Subject: [PATCH] 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
---
 base/json/json_writer.cc                      |   6 +-
 base/json/json_writer.h                       |   4 +
 .../extension_function_dispatcher.cc          |   4 +
 chrome/browser/extensions/extension_host.cc   |  63 +++++++
 chrome/browser/extensions/extension_host.h    |  45 ++++-
 .../browser/extensions/extension_popup_api.cc | 164 ++++++++++++++++++
 .../browser/extensions/extension_popup_api.h  |  50 ++++++
 .../extensions/extension_popup_apitest.cc     |  13 ++
 chrome/browser/gtk/extension_view_gtk.h       |   2 +-
 .../views/browser_actions_container.cc        |  10 +-
 .../views/extensions/extension_popup.cc       |  23 ++-
 .../views/extensions/extension_popup.h        |  23 ++-
 .../views/extensions/extension_view.cc        |  13 +-
 .../browser/views/extensions/extension_view.h |   6 +-
 chrome/browser/views/location_bar_view.cc     |   5 +-
 chrome/chrome.gyp                             |  12 +-
 .../common/extensions/api/extension_api.json  |  72 ++++++++
 chrome/common/notification_type.h             |   4 +
 chrome/common/view_types.cc                   |  12 ++
 chrome/common/view_types.h                    |   9 +
 .../extensions/extension_process_bindings.cc  |  92 ++++++++--
 .../resources/extension_process_bindings.js   |  93 +++++++++-
 .../api_test/popup_api/manifest.json          |   8 +
 .../api_test/popup_api/toolband.html          |  58 +++++++
 .../api_test/popup_api/toolband_popup.html    |  17 ++
 25 files changed, 756 insertions(+), 52 deletions(-)
 create mode 100644 chrome/browser/extensions/extension_popup_api.cc
 create mode 100644 chrome/browser/extensions/extension_popup_api.h
 create mode 100644 chrome/browser/extensions/extension_popup_apitest.cc
 create mode 100644 chrome/common/view_types.cc
 create mode 100644 chrome/test/data/extensions/api_test/popup_api/manifest.json
 create mode 100644 chrome/test/data/extensions/api_test/popup_api/toolband.html
 create mode 100644 chrome/test/data/extensions/api_test/popup_api/toolband_popup.html

diff --git a/base/json/json_writer.cc b/base/json/json_writer.cc
index dffa3daf754e5..133b625c58b1c 100644
--- a/base/json/json_writer.cc
+++ b/base/json/json_writer.cc
@@ -18,6 +18,9 @@ static const char kPrettyPrintLineEnding[] = "\r\n";
 static const char kPrettyPrintLineEnding[] = "\n";
 #endif
 
+/* static */
+const char* JSONWriter::kEmptyArray = "[]";
+
 /* static */
 void JSONWriter::Write(const Value* const node,
                        bool pretty_print,
@@ -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 29aa69d28f342..0ebee0a0b74d1 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 d7105cb2d810e..a3f8a4ccfa9bd 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 2f94612404d09..94359132ed6a3 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 ea9660dfdf84f..bf66579053820 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 0000000000000..3059079445511
--- /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 0000000000000..96d6d53b25ccd
--- /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 0000000000000..cfd8404faf926
--- /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 16b7892d04bd4..2bb2923089a7c 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 92e541afe0a97..eb7b2e4b394a7 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 a84176eae0f0b..0be410cd3d2a3 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 6ab880d59ce3a..7db472aa47695 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 1d0cfc56aea4c..059d3915169ef 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 8227771925c97..bd63d73bf5bbd 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 edbe5f273bb9d..f159d7ea06be1 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 907d82b5a9585..eaec76b60015c 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 6cef9c33c0404..b6f00e3f74c88 100755
--- a/chrome/common/extensions/api/extension_api.json
+++ b/chrome/common/extensions/api/extension_api.json
@@ -175,6 +175,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": [
@@ -1583,6 +1600,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 643cca1b9245c..5da2c6ec29dc7 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 0000000000000..fe14fd2cf3a71
--- /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 c0102449db62c..794f9ab6400a7 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 2a69afd3ae002..fee736a304dd0 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 3d3fb8869dc0c..6a0584b206d64 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 0000000000000..01c2ceeb664f2
--- /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 0000000000000..dd87769123650
--- /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 0000000000000..fd203d265468f
--- /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
-- 
GitLab