Backed out 8 changesets (bug 1217129) for test failures in browser_ext_pageAction_context.js
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 18 Jan 2016 09:08:35 +0100
changeset 280317 8cb42e7a16b42162c9930f37b9e1f820c2eb126b
parent 280316 0a4331e7d7ba21c3a54d9f17fb6ed1f42b7c29ec
child 280328 cd9345c73aec5c536dd2fe9df3465ae780418684
push id29909
push usercbook@mozilla.com
push dateMon, 18 Jan 2016 08:08:55 +0000
treeherdermozilla-central@8cb42e7a16b4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1217129
milestone46.0a1
backs outdc5742de08235de4bc270189dc248d72db476068
106365a3847c66c2122563abb423c29685ea059c
628af985c7eba7e4665a1b62f9a60f6a6fd39822
7ad8b56958c54787313ecbe60eb1e8fbafb2f376
71f46fe62f59385d6a39d426fd571ed08e46a403
7d8dee4c335baa4dbf663a00fe020b9cfccdd9c9
3b5fb2845c3bb9a03bab0561fd9e0624ffbca424
93266f211716db6c1b5782e6e8d5803baaae1d27
first release with
nightly linux32
8cb42e7a16b4 / 46.0a1 / 20160118030338 / files
nightly linux64
8cb42e7a16b4 / 46.0a1 / 20160118030338 / files
nightly mac
8cb42e7a16b4 / 46.0a1 / 20160118030338 / files
nightly win32
8cb42e7a16b4 / 46.0a1 / 20160118030338 / files
nightly win64
8cb42e7a16b4 / 46.0a1 / 20160118030338 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out 8 changesets (bug 1217129) for test failures in browser_ext_pageAction_context.js Backed out changeset dc5742de0823 (bug 1217129) Backed out changeset 106365a3847c (bug 1217129) Backed out changeset 628af985c7eb (bug 1217129) Backed out changeset 7ad8b56958c5 (bug 1217129) Backed out changeset 71f46fe62f59 (bug 1217129) Backed out changeset 7d8dee4c335b (bug 1217129) Backed out changeset 3b5fb2845c3b (bug 1217129) Backed out changeset 93266f211716 (bug 1217129)
browser/components/customizableui/CustomizableUI.jsm
browser/components/customizableui/content/panelUI.js
browser/components/customizableui/content/panelUI.xml
browser/components/extensions/.eslintrc
browser/components/extensions/ext-browserAction.js
browser/components/extensions/ext-pageAction.js
browser/components/extensions/ext-utils.js
browser/components/extensions/test/browser/.eslintrc
browser/components/extensions/test/browser/browser_ext_browserAction_context.js
browser/components/extensions/test/browser/browser_ext_browserAction_popup.js
browser/components/extensions/test/browser/browser_ext_browserAction_simple.js
browser/components/extensions/test/browser/browser_ext_currentWindow.js
browser/components/extensions/test/browser/browser_ext_getViews.js
browser/components/extensions/test/browser/browser_ext_pageAction_context.js
browser/components/extensions/test/browser/browser_ext_pageAction_popup.js
browser/components/extensions/test/browser/browser_ext_popup_api_injection.js
browser/components/extensions/test/browser/browser_ext_tabs_executeScript_good.js
browser/components/extensions/test/browser/browser_ext_tabs_getCurrent.js
browser/components/extensions/test/browser/head.js
browser/themes/shared/customizableui/panelUIOverlay.inc.css
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -1337,19 +1337,17 @@ var CustomizableUIInternal = {
           additionalTooltipArguments.push(ShortcutUtils.prettifyShortcut(keyEl));
         } else {
           ERROR("Key element with id '" + aWidget.shortcutId + "' for widget '" + aWidget.id +
                 "' not found!");
         }
       }
 
       let tooltip = this.getLocalizedProperty(aWidget, "tooltiptext", additionalTooltipArguments);
-      if (tooltip) {
-        node.setAttribute("tooltiptext", tooltip);
-      }
+      node.setAttribute("tooltiptext", tooltip);
       node.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional");
 
       let commandHandler = this.handleWidgetCommand.bind(this, aWidget, node);
       node.addEventListener("command", commandHandler, false);
       let clickHandler = this.handleWidgetClick.bind(this, aWidget, node);
       node.addEventListener("click", clickHandler, false);
 
       // If the widget has a view, and has view showing / hiding listeners,
@@ -1382,48 +1380,44 @@ var CustomizableUIInternal = {
       }
     }
 
     aWidget.instances.set(aDocument, node);
     return node;
   },
 
   getLocalizedProperty: function(aWidget, aProp, aFormatArgs, aDef) {
-    const kReqStringProps = ["label"];
-
     if (typeof aWidget == "string") {
       aWidget = gPalette.get(aWidget);
     }
     if (!aWidget) {
       throw new Error("getLocalizedProperty was passed a non-widget to work with.");
     }
     let def, name;
     // Let widgets pass their own string identifiers or strings, so that
     // we can use strings which aren't the default (in case string ids change)
     // and so that non-builtin-widgets can also provide labels, tooltips, etc.
-    if (aWidget[aProp] != null) {
+    if (aWidget[aProp]) {
       name = aWidget[aProp];
       // By using this as the default, if a widget provides a full string rather
       // than a string ID for localization, we will fall back to that string
       // and return that.
       def = aDef || name;
     } else {
       name = aWidget.id + "." + aProp;
       def = aDef || "";
     }
     try {
       if (Array.isArray(aFormatArgs) && aFormatArgs.length) {
         return gWidgetsBundle.formatStringFromName(name, aFormatArgs,
           aFormatArgs.length) || def;
       }
       return gWidgetsBundle.GetStringFromName(name) || def;
     } catch(ex) {
-      // If an empty string was explicitly passed, treat it as an actual
-      // value rather than a missing property.
-      if (!def && (name != "" || kReqStringProps.includes(aProp))) {
+      if (!def) {
         ERROR("Could not localize property '" + name + "'.");
       }
     }
     return def;
   },
 
   addShortcut: function(aShortcutNode, aTargetNode) {
     if (!aTargetNode)
@@ -2337,17 +2331,16 @@ var CustomizableUIInternal = {
 
     if (aSource == CustomizableUI.SOURCE_BUILTIN) {
       widget._introducedInVersion = aData.introducedInVersion || 0;
     }
 
     this.wrapWidgetEventHandler("onBeforeCreated", widget);
     this.wrapWidgetEventHandler("onClick", widget);
     this.wrapWidgetEventHandler("onCreated", widget);
-    this.wrapWidgetEventHandler("onDestroyed", widget);
 
     if (widget.type == "button") {
       widget.onCommand = typeof aData.onCommand == "function" ?
                            aData.onCommand :
                            null;
     } else if (widget.type == "view") {
       if (typeof aData.viewId != "string") {
         ERROR("Expected a string for widget " + widget.id + " viewId, but got "
@@ -2441,19 +2434,16 @@ var CustomizableUIInternal = {
           for (let eventName of kSubviewEvents) {
             let handler = "on" + eventName;
             if (typeof widget[handler] == "function") {
               viewNode.removeEventListener(eventName, widget[handler], false);
             }
           }
         }
       }
-      if (widgetNode && widget.onDestroyed) {
-        widget.onDestroyed(window.document);
-      }
     }
 
     gPalette.delete(aWidgetId);
     gGroupWrapperCache.delete(aWidgetId);
 
     this.notifyListeners("onWidgetDestroyed", aWidgetId);
   },
 
@@ -3177,21 +3167,16 @@ this.CustomizableUI = {
    *                  that will be invoked before the widget gets a DOM node
    *                  constructed, passing the document in which that will happen.
    *                  This is useful especially for 'view' type widgets that need
    *                  to construct their views on the fly (e.g. from bootstrapped
    *                  add-ons)
    * - onCreated(aNode): Attached to all widgets; a function that will be invoked
    *                  whenever the widget has a DOM node constructed, passing the
    *                  constructed node as an argument.
-   * - onDestroyed(aDoc): Attached to all non-custom widgets; a function that
-   *                  will be invoked after the widget has a DOM node destroyed,
-   *                  passing the document from which it was removed. This is
-   *                  useful especially for 'view' type widgets that need to
-   *                  cleanup after views that were constructed on the fly.
    * - onCommand(aEvt): Only useful for button widgets; a function that will be
    *                    invoked when the user activates the button.
    * - onClick(aEvt): Attached to all widgets; a function that will be invoked
    *                  when the user clicks the widget.
    * - onViewShowing(aEvt): Only useful for views; a function that will be
    *                  invoked when a user shows your view.
    * - onViewHiding(aEvt): Only useful for views; a function that will be
    *                  invoked when a user hides your view.
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -324,17 +324,16 @@ const PanelUI = {
     } else if (!aAnchor.open) {
       aAnchor.open = true;
       // Emit the ViewShowing event so that the widget definition has a chance
       // to lazily populate the subview with things.
       let evt = document.createEvent("CustomEvent");
       evt.initCustomEvent("ViewShowing", true, true, viewNode);
       viewNode.dispatchEvent(evt);
       if (evt.defaultPrevented) {
-        aAnchor.open = false;
         return;
       }
 
       let tempPanel = document.createElement("panel");
       tempPanel.setAttribute("type", "arrow");
       tempPanel.setAttribute("id", "customizationui-widget-panel");
       tempPanel.setAttribute("class", "cui-widget-panel");
       tempPanel.setAttribute("viewId", aViewId);
--- a/browser/components/customizableui/content/panelUI.xml
+++ b/browser/components/customizableui/content/panelUI.xml
@@ -321,22 +321,16 @@
               this._mainView.style.removeProperty("height");
               this.showMainView();
               this._mainViewObserver.disconnect();
               break;
           }
         ]]></body>
       </method>
 
-      <method name="_shouldSetPosition">
-        <body><![CDATA[
-          return this.getAttribute("nosubviews") == "true";
-        ]]></body>
-      </method>
-
       <method name="_shouldSetHeight">
         <body><![CDATA[
           return this.getAttribute("nosubviews") != "true";
         ]]></body>
       </method>
 
       <method name="_setMaxHeight">
         <body><![CDATA[
@@ -346,55 +340,44 @@
           // Ignore the mutation that'll fire when we set the height of
           // the main view.
           this.ignoreMutations = true;
           this._mainView.style.height =
             this.getBoundingClientRect().height + "px";
           this.ignoreMutations = false;
         ]]></body>
       </method>
-      <method name="_adjustContainerHeight">
-        <body><![CDATA[
-          if (!this.ignoreMutations && !this.showingSubView && !this._transitioning) {
-            let height;
-            if (this.showingSubViewAsMainView) {
-              height = this._heightOfSubview(this._mainView);
-            } else {
-              height = this._mainView.scrollHeight;
-            }
-            this._viewContainer.style.height = height + "px";
-          }
-        ]]></body>
-      </method>
       <method name="_syncContainerWithSubView">
         <body><![CDATA[
           // Check that this panel is still alive:
           if (!this._panel || !this._panel.parentNode) {
             return;
           }
 
           if (!this.ignoreMutations && this.showingSubView) {
             let newHeight = this._heightOfSubview(this._currentSubView, this._subViews);
             this._viewContainer.style.height = newHeight + "px";
           }
         ]]></body>
       </method>
       <method name="_syncContainerWithMainView">
         <body><![CDATA[
           // Check that this panel is still alive:
-          if (!this._panel || !this._panel.parentNode) {
+          if (!this._panel || !this._panel.parentNode || !this._shouldSetHeight()) {
             return;
           }
 
-          if (this._shouldSetPosition()) {
-            this._panel.adjustArrowPosition();
-          }
-
-          if (this._shouldSetHeight()) {
-            this._adjustContainerHeight();
+          if (!this.ignoreMutations && !this.showingSubView && !this._transitioning) {
+            let height;
+            if (this.showingSubViewAsMainView) {
+              height = this._heightOfSubview(this._mainView);
+            } else {
+              height = this._mainView.scrollHeight;
+            }
+            this._viewContainer.style.height = height + "px";
           }
         ]]></body>
       </method>
 
       <method name="_heightOfSubview">
         <parameter name="aSubview"/>
         <parameter name="aContainerToCheck"/>
         <body><![CDATA[
--- a/browser/components/extensions/.eslintrc
+++ b/browser/components/extensions/.eslintrc
@@ -1,17 +1,16 @@
 {
   "extends": "../../../toolkit/components/extensions/.eslintrc",
 
   "globals": {
-    "AllWindowEvents": true,
     "currentWindow": true,
     "EventEmitter": true,
     "IconDetails": true,
+    "openPanel": true,
     "makeWidgetId": true,
-    "PanelPopup": true,
     "TabContext": true,
-    "ViewPopup": true,
+    "AllWindowEvents": true,
     "WindowEventManager": true,
     "WindowListManager": true,
     "WindowManager": true,
   },
 }
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -8,46 +8,41 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 Cu.import("resource://devtools/shared/event-emitter.js");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   EventManager,
   runSafe,
 } = ExtensionUtils;
 
-const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-
 // WeakMap[Extension -> BrowserAction]
 var browserActionMap = new WeakMap();
 
 function browserActionOf(extension) {
   return browserActionMap.get(extension);
 }
 
 // Responsible for the browser_action section of the manifest as well
 // as the associated popup.
 function BrowserAction(options, extension) {
   this.extension = extension;
-
-  let widgetId = makeWidgetId(extension.id);
-  this.id = `${widgetId}-browser-action`;
-  this.viewId = `PanelUI-webext-${widgetId}-browser-action-view`;
+  this.id = makeWidgetId(extension.id) + "-browser-action";
   this.widget = null;
 
   this.tabManager = TabManager.for(extension);
 
   let title = extension.localize(options.default_title || "");
   let popup = extension.localize(options.default_popup || "");
   if (popup) {
     popup = extension.baseURI.resolve(popup);
   }
 
   this.defaults = {
     enabled: true,
-    title: title || extension.name,
+    title: title,
     badgeText: "",
     badgeBackgroundColor: null,
     icon: IconDetails.normalize({ path: options.default_icon }, extension,
                                 null, true),
     popup: popup,
   };
 
   this.tabContext = new TabContext(tab => Object.create(this.defaults),
@@ -55,85 +50,66 @@ function BrowserAction(options, extensio
 
   EventEmitter.decorate(this);
 }
 
 BrowserAction.prototype = {
   build() {
     let widget = CustomizableUI.createWidget({
       id: this.id,
-      viewId: this.viewId,
-      type: "view",
+      type: "custom",
       removable: true,
-      label: this.defaults.title || this.extension.name,
-      tooltiptext: this.defaults.title || "",
       defaultArea: CustomizableUI.AREA_NAVBAR,
-
-      onBeforeCreated: document => {
-        let view = document.createElementNS(XUL_NS, "panelview");
-        view.id = this.viewId;
-        view.setAttribute("flex", "1");
-
-        document.getElementById("PanelUI-multiView").appendChild(view);
-      },
-
-      onDestroyed: document => {
-        let view = document.getElementById(this.viewId);
-        if (view) {
-          view.remove();
-        }
-      },
-
-      onCreated: node => {
-        node.classList.add("badged-button");
+      onBuild: document => {
+        let node = document.createElement("toolbarbutton");
+        node.id = this.id;
+        node.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional badged-button");
         node.setAttribute("constrain-size", "true");
 
         this.updateButton(node, this.defaults);
-      },
 
-      onViewShowing: event => {
-        let document = event.target.ownerDocument;
         let tabbrowser = document.defaultView.gBrowser;
 
-        let tab = tabbrowser.selectedTab;
-        let popupURL = this.getProperty(tab, "popup");
-        this.tabManager.addActiveTabPermission(tab);
+        node.addEventListener("command", event => { // eslint-disable-line mozilla/balanced-listeners
+          let tab = tabbrowser.selectedTab;
+          let popup = this.getProperty(tab, "popup");
+          this.tabManager.addActiveTabPermission(tab);
+          if (popup) {
+            this.togglePopup(node, popup);
+          } else {
+            this.emit("click");
+          }
+        });
 
-        // If the widget has a popup URL defined, we open a popup, but do not
-        // dispatch a click event to the extension.
-        // If it has no popup URL defined, we dispatch a click event, but do not
-        // open a popup.
-        if (popupURL) {
-          try {
-            new ViewPopup(this.extension, event.target, popupURL);
-          } catch (e) {
-            Cu.reportError(e);
-            event.preventDefault();
-          }
-        } else {
-          // This isn't not a hack, but it seems to provide the correct behavior
-          // with the fewest complications.
-          event.preventDefault();
-          this.emit("click");
-        }
+        return node;
       },
     });
 
     this.tabContext.on("tab-select", // eslint-disable-line mozilla/balanced-listeners
                        (evt, tab) => { this.updateWindow(tab.ownerDocument.defaultView); });
 
     this.widget = widget;
   },
 
+  togglePopup(node, popupResource) {
+    openPanel(node, popupResource, this.extension);
+  },
+
   // Update the toolbar button |node| with the tab context data
   // in |tabData|.
   updateButton(node, tabData) {
-    let title = tabData.title || this.extension.name;
-    node.setAttribute("tooltiptext", title);
-    node.setAttribute("label", title);
+    if (tabData.title) {
+      node.setAttribute("tooltiptext", tabData.title);
+      node.setAttribute("label", tabData.title);
+      node.setAttribute("aria-label", tabData.title);
+    } else {
+      node.removeAttribute("tooltiptext");
+      node.removeAttribute("label");
+      node.removeAttribute("aria-label");
+    }
 
     if (tabData.badgeText) {
       node.setAttribute("badge", tabData.badgeText);
     } else {
       node.removeAttribute("badge");
     }
 
     if (tabData.enabled) {
@@ -181,20 +157,18 @@ BrowserAction.prototype = {
     }
   },
 
   // tab is allowed to be null.
   // prop should be one of "icon", "title", "badgeText", "popup", or "badgeBackgroundColor".
   setProperty(tab, prop, value) {
     if (tab == null) {
       this.defaults[prop] = value;
-    } else if (value != null) {
+    } else {
       this.tabContext.get(tab)[prop] = value;
-    } else {
-      delete this.tabContext.get(tab)[prop];
     }
 
     this.updateOnChange(tab);
   },
 
   // tab is allowed to be null.
   // prop should be one of "title", "badgeText", "popup", or "badgeBackgroundColor".
   getProperty(tab, prop) {
@@ -247,23 +221,17 @@ extensions.registerSchemaAPI("browserAct
 
       disable: function(tabId) {
         let tab = tabId !== null ? TabManager.getTab(tabId) : null;
         browserActionOf(extension).setProperty(tab, "enabled", false);
       },
 
       setTitle: function(details) {
         let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
-
-        let title = details.title;
-        // Clear the tab-specific title when given a null string.
-        if (tab && title == "") {
-          title = null;
-        }
-        browserActionOf(extension).setProperty(tab, "title", title);
+        browserActionOf(extension).setProperty(tab, "title", details.title);
       },
 
       getTitle: function(details, callback) {
         let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
         let title = browserActionOf(extension).getProperty(tab, "title");
         runSafe(context, callback, title);
       },
 
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -23,17 +23,17 @@ function PageAction(options, extension) 
   let title = extension.localize(options.default_title || "");
   let popup = extension.localize(options.default_popup || "");
   if (popup) {
     popup = extension.baseURI.resolve(popup);
   }
 
   this.defaults = {
     show: false,
-    title: title || extension.name,
+    title: title,
     icon: IconDetails.normalize({ path: options.default_icon }, extension,
                                 null, true),
     popup: popup && extension.baseURI.resolve(popup),
   };
 
   this.tabContext = new TabContext(tab => Object.create(this.defaults),
                                    extension);
 
@@ -53,22 +53,17 @@ PageAction.prototype = {
   },
 
   // Sets the value of the property |prop| for the given tab to the
   // given value, symmetrically to |getProperty|.
   //
   // If |tab| is currently selected, updates the page action button to
   // reflect the new value.
   setProperty(tab, prop, value) {
-    if (value != null) {
-      this.tabContext.get(tab)[prop] = value;
-    } else {
-      delete this.tabContext.get(tab)[prop];
-    }
-
+    this.tabContext.get(tab)[prop] = value;
     if (tab.selected) {
       this.updateButton(tab.ownerDocument.defaultView);
     }
   },
 
   // Updates the page action button in the given window to reflect the
   // properties of the currently selected tab:
   //
@@ -84,19 +79,23 @@ PageAction.prototype = {
       return;
     }
 
     let button = this.getButton(window);
 
     if (tabData.show) {
       // Update the title and icon only if the button is visible.
 
-      let title = tabData.title || this.extension.name;
-      button.setAttribute("tooltiptext", title);
-      button.setAttribute("aria-label", title);
+      if (tabData.title) {
+        button.setAttribute("tooltiptext", tabData.title);
+        button.setAttribute("aria-label", tabData.title);
+      } else {
+        button.removeAttribute("tooltiptext");
+        button.removeAttribute("aria-label");
+      }
 
       let icon = IconDetails.getURL(tabData.icon, window, this.extension);
       button.setAttribute("src", icon);
     }
 
     button.hidden = !tabData.show;
   },
 
@@ -133,26 +132,22 @@ PageAction.prototype = {
 
   // Handles a click event on the page action button for the given
   // window.
   // If the page action has a |popup| property, a panel is opened to
   // that URL. Otherwise, a "click" event is emitted, and dispatched to
   // the any click listeners in the add-on.
   handleClick(window) {
     let tab = window.gBrowser.selectedTab;
-    let popupURL = this.tabContext.get(tab).popup;
+    let popup = this.tabContext.get(tab).popup;
 
     this.tabManager.addActiveTabPermission(tab);
 
-    // If the widget has a popup URL defined, we open a popup, but do not
-    // dispatch a click event to the extension.
-    // If it has no popup URL defined, we dispatch a click event, but do not
-    // open a popup.
-    if (popupURL) {
-      new PanelPopup(this.extension, this.getButton(window), popupURL);
+    if (popup) {
+      openPanel(this.getButton(window), popup, this.extension);
     } else {
       this.emit("click", tab);
     }
   },
 
   handleLocationChange(eventType, tab, fromBrowse) {
     if (fromBrowse) {
       this.tabContext.clear(tab);
@@ -213,19 +208,17 @@ extensions.registerSchemaAPI("pageAction
 
       hide(tabId) {
         let tab = TabManager.getTab(tabId);
         PageAction.for(extension).setProperty(tab, "show", false);
       },
 
       setTitle(details) {
         let tab = TabManager.getTab(details.tabId);
-
-        // Clear the tab-specific title when given a null string.
-        PageAction.for(extension).setProperty(tab, "title", details.title || null);
+        PageAction.for(extension).setProperty(tab, "title", details.title);
       },
 
       getTitle(details, callback) {
         let tab = TabManager.getTab(details.tabId);
         let title = PageAction.for(extension).getProperty(tab, "title");
         runSafe(context, callback, title);
       },
 
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -1,22 +1,18 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
-                                  "resource:///modules/CustomizableUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 Cu.import("resource://gre/modules/AddonManager.jsm");
 
-const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-
 const INTEGER = /^[1-9]\d*$/;
 
 var {
   EventManager,
   instanceOf,
 } = ExtensionUtils;
 
 // This file provides some useful code for the |tabs| and |windows|
@@ -125,213 +121,113 @@ global.IconDetails = {
 };
 
 global.makeWidgetId = id => {
   id = id.toLowerCase();
   // FIXME: This allows for collisions.
   return id.replace(/[^a-z0-9_-]/g, "_");
 };
 
-class BasePopup {
-  constructor(extension, viewNode, popupURL) {
-    let popupURI = Services.io.newURI(popupURL, null, extension.baseURI);
-
-    Services.scriptSecurityManager.checkLoadURIWithPrincipal(
-      extension.principal, popupURI,
-      Services.scriptSecurityManager.DISALLOW_SCRIPT);
-
-    this.extension = extension;
-    this.popupURI = popupURI;
-    this.viewNode = viewNode;
-    this.window = viewNode.ownerDocument.defaultView;
-
-    this.contentReady = new Promise(resolve => {
-      this._resolveContentReady = resolve;
-    });
+// Open a panel anchored to the given node, containing a browser opened
+// to the given URL, owned by the given extension. If |popupURL| is not
+// an absolute URL, it is resolved relative to the given extension's
+// base URL.
+global.openPanel = (node, popupURL, extension) => {
+  let document = node.ownerDocument;
 
-    this.viewNode.addEventListener(this.DESTROY_EVENT, this);
-
-    this.browser = null;
-    this.browserReady = this.createBrowser(viewNode, popupURI);
-  }
+  let popupURI = Services.io.newURI(popupURL, null, extension.baseURI);
 
-  destroy() {
-    this.browserReady.then(() => {
-      this.browser.removeEventListener("load", this, true);
-      this.browser.removeEventListener("DOMTitleChanged", this, true);
-      this.browser.removeEventListener("DOMWindowClose", this, true);
-
-      this.viewNode.removeEventListener(this.DESTROY_EVENT, this);
-
-      this.context.unload();
-      this.browser.remove();
+  Services.scriptSecurityManager.checkLoadURIWithPrincipal(
+    extension.principal, popupURI,
+    Services.scriptSecurityManager.DISALLOW_SCRIPT);
 
-      this.browser = null;
-      this.viewNode = null;
-      this.context = null;
-    });
-  }
-
-  // Returns the name of the event fired on `viewNode` when the popup is being
-  // destroyed. This must be implemented by every subclass.
-  get DESTROY_EVENT() {
-    throw new Error("Not implemented");
-  }
-
-  handleEvent(event) {
-    switch (event.type) {
-      case this.DESTROY_EVENT:
-        this.destroy();
-        break;
+  let panel = document.createElement("panel");
+  panel.setAttribute("id", makeWidgetId(extension.id) + "-panel");
+  panel.setAttribute("class", "browser-extension-panel");
+  panel.setAttribute("type", "arrow");
+  panel.setAttribute("role", "group");
 
-      case "DOMWindowClose":
-        if (event.target === this.browser.contentWindow) {
-          event.preventDefault();
-          this.closePopup();
-        }
-        break;
-
-      case "DOMTitleChanged":
-        this.viewNode.setAttribute("aria-label", this.browser.contentTitle);
-        break;
-
-      case "load":
-        // We use a capturing listener, so we get this event earlier than any
-        // load listeners in the content page. Resizing after a timeout ensures
-        // that we calculate the size after the entire event cycle has completed
-        // (unless someone spins the event loop, anyway), and hopefully after
-        // the content has made any modifications.
-        //
-        // In the future, to match Chrome's behavior, we'll need to update this
-        // dynamically, probably in response to MozScrolledAreaChanged events.
-        this.window.setTimeout(() => this.resizeBrowser(), 0);
-        break;
-    }
+  let anchor;
+  if (node.localName == "toolbarbutton") {
+    // Toolbar buttons are a special case. The panel becomes a child of
+    // the button, and is anchored to the button's icon.
+    node.appendChild(panel);
+    anchor = document.getAnonymousElementByAttribute(node, "class", "toolbarbutton-icon");
+  } else {
+    // In all other cases, the panel is anchored to the target node
+    // itself, and is a child of a popupset node.
+    document.getElementById("mainPopupSet").appendChild(panel);
+    anchor = node;
   }
 
-  createBrowser(viewNode, popupURI) {
-    let document = viewNode.ownerDocument;
-
-    this.browser = document.createElementNS(XUL_NS, "browser");
-    this.browser.setAttribute("type", "content");
-    this.browser.setAttribute("disableglobalhistory", "true");
-
-    // Note: When using noautohide panels, the popup manager will add width and
-    // height attributes to the panel, breaking our resize code, if the browser
-    // starts out smaller than 30px by 10px. This isn't an issue now, but it
-    // will be if and when we popup debugging.
+  const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+  let browser = document.createElementNS(XUL_NS, "browser");
+  browser.setAttribute("type", "content");
+  browser.setAttribute("disableglobalhistory", "true");
+  panel.appendChild(browser);
 
-    // This overrides the content's preferred size when displayed in a
-    // fixed-size, slide-in panel.
-    this.browser.setAttribute("flex", "1");
-
-    viewNode.appendChild(this.browser);
-
-    return new Promise(resolve => {
-      // The first load event is for about:blank.
-      // We can't finish setting up the browser until the binding has fully
-      // initialized. Waiting for the first load event guarantees that it has.
-      let loadListener = event => {
-        this.browser.removeEventListener("load", loadListener, true);
-        resolve();
-      };
-      this.browser.addEventListener("load", loadListener, true);
-    }).then(() => {
-      let { contentWindow } = this.browser;
+  let titleChangedListener = () => {
+    panel.setAttribute("aria-label", browser.contentTitle);
+  };
 
-      contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                   .getInterface(Ci.nsIDOMWindowUtils)
-                   .allowScriptsToClose();
-
-      this.context = new ExtensionPage(this.extension, {
-        type: "popup",
-        contentWindow,
-        uri: popupURI,
-        docShell: this.browser.docShell,
-      });
-
-      GlobalManager.injectInDocShell(this.browser.docShell, this.extension, this.context);
-      this.browser.setAttribute("src", this.context.uri.spec);
+  let context;
+  let popuphidden = () => {
+    panel.removeEventListener("popuphidden", popuphidden);
+    browser.removeEventListener("DOMTitleChanged", titleChangedListener, true);
+    context.unload();
+    panel.remove();
+  };
+  panel.addEventListener("popuphidden", popuphidden);
 
-      this.browser.addEventListener("load", this, true);
-      this.browser.addEventListener("DOMTitleChanged", this, true);
-      this.browser.addEventListener("DOMWindowClose", this, true);
-    });
-  }
+  let loadListener = () => {
+    panel.removeEventListener("load", loadListener);
 
-  // Resizes the browser to match the preferred size of the content.
-  resizeBrowser() {
-    let width, height;
-    try {
-      let w = {}, h = {};
-      this.browser.docShell.contentViewer.getContentSize(w, h);
-
-      width = w.value / this.window.devicePixelRatio;
-      height = h.value / this.window.devicePixelRatio;
+    context = new ExtensionPage(extension, {
+      type: "popup",
+      contentWindow: browser.contentWindow,
+      uri: popupURI,
+      docShell: browser.docShell,
+    });
+    GlobalManager.injectInDocShell(browser.docShell, extension, context);
+    browser.setAttribute("src", context.uri.spec);
 
-      // The width calculation is imperfect, and is often a fraction of a pixel
-      // too narrow, even after taking the ceiling, which causes lines of text
-      // to wrap.
-      width += 1;
-    } catch (e) {
-      // getContentSize can throw
-      [width, height] = [400, 400];
-    }
-
-    width = Math.ceil(Math.min(width, 800));
-    height = Math.ceil(Math.min(height, 600));
+    let contentLoadListener = event => {
+      if (event.target != browser.contentDocument) {
+        return;
+      }
+      browser.removeEventListener("load", contentLoadListener, true);
 
-    this.browser.style.width = `${width}px`;
-    this.browser.style.height = `${height}px`;
-
-    this._resolveContentReady();
-  }
-}
-
-global.PanelPopup = class PanelPopup extends BasePopup {
-  constructor(extension, imageNode, popupURL) {
-    let document = imageNode.ownerDocument;
-
-    let panel = document.createElement("panel");
-    panel.setAttribute("id", makeWidgetId(extension.id) + "-panel");
-    panel.setAttribute("class", "browser-extension-panel");
-    panel.setAttribute("type", "arrow");
-    panel.setAttribute("role", "group");
+      let contentViewer = browser.docShell.contentViewer;
+      let width = {}, height = {};
+      try {
+        contentViewer.getContentSize(width, height);
+        [width, height] = [width.value, height.value];
+      } catch (e) {
+        // getContentSize can throw
+        [width, height] = [400, 400];
+      }
 
-    document.getElementById("mainPopupSet").appendChild(panel);
-
-    super(extension, panel, popupURL);
+      let window = document.defaultView;
+      width /= window.devicePixelRatio;
+      height /= window.devicePixelRatio;
+      width = Math.min(width, 800);
+      height = Math.min(height, 800);
 
-    this.contentReady.then(() => {
-      panel.openPopup(imageNode, "bottomcenter topright", 0, 0, false, false);
-    });
-  }
-
-  get DESTROY_EVENT() {
-    return "popuphidden";
-  }
+      browser.setAttribute("width", width);
+      browser.setAttribute("height", height);
 
-  destroy() {
-    super.destroy();
-    this.viewNode.remove();
-  }
+      panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
+    };
+    browser.addEventListener("load", contentLoadListener, true);
 
-  closePopup() {
-    this.viewNode.hidePopup();
-  }
-};
+    browser.addEventListener("DOMTitleChanged", titleChangedListener, true);
+  };
+  panel.addEventListener("load", loadListener);
 
-global.ViewPopup = class ViewPopup extends BasePopup {
-  get DESTROY_EVENT() {
-    return "ViewHiding";
-  }
-
-  closePopup() {
-    CustomizableUI.hidePanelForNode(this.viewNode);
-  }
+  return panel;
 };
 
 // Manages tab-specific context data, and dispatching tab select events
 // across all windows.
 global.TabContext = function TabContext(getDefaults, extension) {
   this.extension = extension;
   this.getDefaults = getDefaults;
 
--- a/browser/components/extensions/test/browser/.eslintrc
+++ b/browser/components/extensions/test/browser/.eslintrc
@@ -5,19 +5,16 @@
     "webextensions": true,
   },
 
   "globals": {
     "NetUtil": true,
     "XPCOMUtils": true,
     "Task": true,
 
-    // Browser window globals.
-    "PanelUI": false,
-
     // Test harness globals
     "ExtensionTestUtils": false,
 
     "clickBrowserAction": true,
     "clickPageAction": true,
     "CustomizableUI": true,
     "focusWindow": true,
     "makeWidgetId": true,
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
@@ -1,157 +1,24 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-function* runTests(options) {
-  function background(getTests) {
-    // Gets the current details of the browser action, and returns a
-    // promise that resolves to an object containing them.
-    function getDetails(tabId) {
-      return Promise.all([
-        new Promise(resolve => browser.browserAction.getTitle({tabId}, resolve)),
-        new Promise(resolve => browser.browserAction.getPopup({tabId}, resolve)),
-        new Promise(resolve => browser.browserAction.getBadgeText({tabId}, resolve)),
-        new Promise(resolve => browser.browserAction.getBadgeBackgroundColor({tabId}, resolve))]
-      ).then(details => {
-        return Promise.resolve({ title: details[0],
-                                 popup: details[1],
-                                 badge: details[2],
-                                 badgeBackgroundColor: details[3] });
-      });
-    }
-
-    function checkDetails(expecting, tabId) {
-      return getDetails(tabId).then(details => {
-        browser.test.assertEq(expecting.title, details.title,
-                              "expected value from getTitle");
-
-        browser.test.assertEq(expecting.popup, details.popup,
-                              "expected value from getPopup");
-
-        browser.test.assertEq(expecting.badge, details.badge,
-                              "expected value from getBadge");
-
-        browser.test.assertEq(String(expecting.badgeBackgroundColor),
-                              String(details.badgeBackgroundColor),
-                              "expected value from getBadgeBackgroundColor");
-      });
-    }
-
-    let expectDefaults = expecting => {
-      return checkDetails(expecting);
-    };
-
-    let tabs = [];
-    let tests = getTests(tabs, expectDefaults);
-
-    // Runs the next test in the `tests` array, checks the results,
-    // and passes control back to the outer test scope.
-    function nextTest() {
-      let test = tests.shift();
-
-      test(expecting => {
-        // Check that the API returns the expected values, and then
-        // run the next test.
-        new Promise(resolve => {
-          return browser.tabs.query({ active: true, currentWindow: true }, resolve);
-        }).then(tabs => {
-          return checkDetails(expecting, tabs[0].id);
-        }).then(() => {
-          // Check that the actual icon has the expected values, then
-          // run the next test.
-          browser.test.sendMessage("nextTest", expecting, tests.length);
-        });
-      });
-    }
-
-    browser.test.onMessage.addListener((msg) => {
-      if (msg != "runNextTest") {
-        browser.test.fail("Expecting 'runNextTest' message");
-      }
-
-      nextTest();
-    });
-
-    browser.tabs.query({ active: true, currentWindow: true }, resultTabs => {
-      tabs[0] = resultTabs[0].id;
-
-      nextTest();
-    });
-  }
-
+add_task(function* testTabSwitchContext() {
   let extension = ExtensionTestUtils.loadExtension({
-    manifest: options.manifest,
-
-    background: `(${background})(${options.getTests})`,
-  });
-
-
-  let browserActionId = makeWidgetId(extension.id) + "-browser-action";
-
-  function checkDetails(details) {
-    let button = document.getElementById(browserActionId);
-
-    ok(button, "button exists");
-
-    let title = details.title || options.manifest.name;
-
-    is(button.getAttribute("image"), details.icon, "icon URL is correct");
-    is(button.getAttribute("tooltiptext"), title, "image title is correct");
-    is(button.getAttribute("label"), title, "image label is correct");
-    is(button.getAttribute("badge"), details.badge, "badge text is correct");
-    is(button.getAttribute("disabled") == "true", Boolean(details.disabled), "disabled state is correct");
-
-    if (details.badge && details.badgeBackgroundColor) {
-      let badge = button.ownerDocument.getAnonymousElementByAttribute(
-        button, "class", "toolbarbutton-badge");
-
-      let badgeColor = window.getComputedStyle(badge).backgroundColor;
-      let color = details.badgeBackgroundColor;
-      let expectedColor = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
-
-      is(badgeColor, expectedColor, "badge color is correct");
-    }
-
-
-    // TODO: Popup URL.
-  }
-
-  let awaitFinish = new Promise(resolve => {
-    extension.onMessage("nextTest", (expecting, testsRemaining) => {
-      checkDetails(expecting);
-
-      if (testsRemaining) {
-        extension.sendMessage("runNextTest");
-      } else {
-        resolve();
-      }
-    });
-  });
-
-  yield extension.startup();
-
-  yield awaitFinish;
-
-  yield extension.unload();
-}
-
-add_task(function* testTabSwitchContext() {
-  yield runTests({
     manifest: {
       "browser_action": {
         "default_icon": "default.png",
         "default_popup": "default.html",
         "default_title": "Default Title",
       },
       "permissions": ["tabs"],
     },
 
-    getTests(tabs, expectDefaults) {
+    background: function() {
       let details = [
         { "icon": browser.runtime.getURL("default.png"),
           "popup": browser.runtime.getURL("default.html"),
           "title": "Default Title",
           "badge": "",
           "badgeBackgroundColor": null },
         { "icon": browser.runtime.getURL("1.png"),
           "popup": browser.runtime.getURL("default.html"),
@@ -178,17 +45,20 @@ add_task(function* testTabSwitchContext(
           "disabled": false },
         { "icon": browser.runtime.getURL("default-2.png"),
           "popup": browser.runtime.getURL("default-2.html"),
           "title": "Default Title 2",
           "badge": "d2",
           "badgeBackgroundColor": [0, 0xff, 0, 0xff] },
       ];
 
-      return [
+      let tabs = [];
+
+      let expectDefaults;
+      let tests = [
         expect => {
           browser.test.log("Initial state, expect default properties.");
           expectDefaults(details[0]).then(() => {
             expect(details[0]);
           });
         },
         expect => {
           browser.test.log("Change the icon in the current tab. Expect default properties excluding the icon.");
@@ -282,87 +152,129 @@ add_task(function* testTabSwitchContext(
         },
         expect => {
           browser.test.log("Delete tab.");
           browser.tabs.remove(tabs[2], () => {
             expect(details[4]);
           });
         },
       ];
+
+      // Gets the current details of the browser action, and returns a
+      // promise that resolves to an object containing them.
+      function getDetails(tabId) {
+        return Promise.all([
+          new Promise(resolve => browser.browserAction.getTitle({tabId}, resolve)),
+          new Promise(resolve => browser.browserAction.getPopup({tabId}, resolve)),
+          new Promise(resolve => browser.browserAction.getBadgeText({tabId}, resolve)),
+          new Promise(resolve => browser.browserAction.getBadgeBackgroundColor({tabId}, resolve))]
+        ).then(details => {
+          return Promise.resolve({ title: details[0],
+                                   popup: details[1],
+                                   badge: details[2],
+                                   badgeBackgroundColor: details[3] });
+        });
+      }
+
+      function checkDetails(expecting, tabId) {
+        return getDetails(tabId).then(details => {
+          browser.test.assertEq(expecting.title, details.title,
+                                "expected value from getTitle");
+
+          browser.test.assertEq(expecting.popup, details.popup,
+                                "expected value from getPopup");
+
+          browser.test.assertEq(expecting.badge, details.badge,
+                                "expected value from getBadge");
+
+          browser.test.assertEq(String(expecting.badgeBackgroundColor),
+                                String(details.badgeBackgroundColor),
+                                "expected value from getBadgeBackgroundColor");
+        });
+      }
+
+      expectDefaults = expecting => {
+        return checkDetails(expecting);
+      };
+
+      // Runs the next test in the `tests` array, checks the results,
+      // and passes control back to the outer test scope.
+      function nextTest() {
+        let test = tests.shift();
+
+        test(expecting => {
+          // Check that the API returns the expected values, and then
+          // run the next test.
+          new Promise(resolve => {
+            return browser.tabs.query({ active: true, currentWindow: true }, resolve);
+          }).then(tabs => {
+            return checkDetails(expecting, tabs[0].id);
+          }).then(() => {
+            // Check that the actual icon has the expected values, then
+            // run the next test.
+            browser.test.sendMessage("nextTest", expecting, tests.length);
+          });
+        });
+      }
+
+      browser.test.onMessage.addListener((msg) => {
+        if (msg != "runNextTest") {
+          browser.test.fail("Expecting 'runNextTest' message");
+        }
+
+        nextTest();
+      });
+
+      browser.tabs.query({ active: true, currentWindow: true }, resultTabs => {
+        tabs[0] = resultTabs[0].id;
+
+        nextTest();
+      });
     },
   });
-});
 
-add_task(function* testDefaultTitle() {
-  yield runTests({
-    manifest: {
-      "name": "Foo Extension",
+  let browserActionId = makeWidgetId(extension.id) + "-browser-action";
 
-      "browser_action": {
-        "default_icon": "icon.png",
-      },
+  function checkDetails(details) {
+    let button = document.getElementById(browserActionId);
 
-      "permissions": ["tabs"],
-    },
+    ok(button, "button exists");
 
-    getTests(tabs, expectDefaults) {
-      let details = [
-        { "title": "Foo Extension",
-          "popup": "",
-          "badge": "",
-          "badgeBackgroundColor": null,
-          "icon": browser.runtime.getURL("icon.png") },
-        { "title": "Foo Title",
-          "popup": "",
-          "badge": "",
-          "badgeBackgroundColor": null,
-          "icon": browser.runtime.getURL("icon.png") },
-        { "title": "Bar Title",
-          "popup": "",
-          "badge": "",
-          "badgeBackgroundColor": null,
-          "icon": browser.runtime.getURL("icon.png") },
-        { "title": "",
-          "popup": "",
-          "badge": "",
-          "badgeBackgroundColor": null,
-          "icon": browser.runtime.getURL("icon.png") },
-      ];
+    is(button.getAttribute("image"), details.icon, "icon URL is correct");
+    is(button.getAttribute("tooltiptext"), details.title, "image title is correct");
+    is(button.getAttribute("label"), details.title, "image label is correct");
+    is(button.getAttribute("aria-label"), details.title, "image aria-label is correct");
+    is(button.getAttribute("badge"), details.badge, "badge text is correct");
+    is(button.getAttribute("disabled") == "true", Boolean(details.disabled), "disabled state is correct");
+
+    if (details.badge && details.badgeBackgroundColor) {
+      let badge = button.ownerDocument.getAnonymousElementByAttribute(
+        button, "class", "toolbarbutton-badge");
+
+      let badgeColor = window.getComputedStyle(badge).backgroundColor;
+      let color = details.badgeBackgroundColor;
+      let expectedColor = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
 
-      return [
-        expect => {
-          browser.test.log("Initial state. Expect extension title as default title.");
-          expectDefaults(details[0]).then(() => {
-            expect(details[0]);
-          });
-        },
-        expect => {
-          browser.test.log("Change the title. Expect new title.");
-          browser.browserAction.setTitle({ tabId: tabs[0], title: "Foo Title" });
-          expectDefaults(details[0]).then(() => {
-            expect(details[1]);
-          });
-        },
-        expect => {
-          browser.test.log("Change the default. Expect same properties.");
-          browser.browserAction.setTitle({ title: "Bar Title" });
-          expectDefaults(details[2]).then(() => {
-            expect(details[1]);
-          });
-        },
-        expect => {
-          browser.test.log("Clear the title. Expect new default title.");
-          browser.browserAction.setTitle({ tabId: tabs[0], title: "" });
-          expectDefaults(details[2]).then(() => {
-            expect(details[2]);
-          });
-        },
-        expect => {
-          browser.test.log("Set default title to null string. Expect null string from API, extension title in UI.");
-          browser.browserAction.setTitle({ title: "" });
-          expectDefaults(details[3]).then(() => {
-            expect(details[3]);
-          });
-        },
-      ];
-    },
+      is(badgeColor, expectedColor, "badge color is correct");
+    }
+
+
+    // TODO: Popup URL.
+  }
+
+  let awaitFinish = new Promise(resolve => {
+    extension.onMessage("nextTest", (expecting, testsRemaining) => {
+      checkDetails(expecting);
+
+      if (testsRemaining) {
+        extension.sendMessage("runNextTest");
+      } else {
+        resolve();
+      }
+    });
   });
+
+  yield extension.startup();
+
+  yield awaitFinish;
+
+  yield extension.unload();
 });
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_popup.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_popup.js
@@ -1,13 +1,27 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-function* testInArea(area) {
+function promisePopupShown(popup) {
+  return new Promise(resolve => {
+    if (popup.popupOpen) {
+      resolve();
+    } else {
+      let onPopupShown = event => {
+        popup.removeEventListener("popupshown", onPopupShown);
+        resolve();
+      };
+      popup.addEventListener("popupshown", onPopupShown);
+    }
+  });
+}
+
+add_task(function* testPageActionPopup() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "background": {
         "page": "data/background.html",
       },
       "browser_action": {
         "default_popup": "popup-a.html",
       },
@@ -97,39 +111,35 @@ function* testInArea(area) {
           }
         });
 
         browser.test.sendMessage("next-test");
       },
     },
   });
 
+  let panelId = makeWidgetId(extension.id) + "-panel";
+
   extension.onMessage("send-click", () => {
     clickBrowserAction(extension);
   });
 
-  let widget;
   extension.onMessage("next-test", Task.async(function* () {
-    if (!widget) {
-      widget = getBrowserActionWidget(extension);
-      CustomizableUI.addWidgetToArea(widget.id, area);
+    let panel = document.getElementById(panelId);
+    if (panel) {
+      yield promisePopupShown(panel);
+      panel.hidePopup();
+
+      panel = document.getElementById(panelId);
+      is(panel, null, "panel successfully removed from document after hiding");
     }
 
-    yield closeBrowserAction(extension);
-
     extension.sendMessage("next-test");
   }));
 
+
   yield Promise.all([extension.startup(), extension.awaitFinish("browseraction-tests-done")]);
 
   yield extension.unload();
 
-  let view = document.getElementById(widget.viewId);
-  is(view, null, "browserAction view removed from document");
-}
-
-add_task(function* testBrowserActionInToolbar() {
-  yield testInArea(CustomizableUI.AREA_NAVBAR);
+  let panel = document.getElementById(panelId);
+  is(panel, null, "browserAction panel removed from document");
 });
-
-add_task(function* testBrowserActionInPanel() {
-  yield testInArea(CustomizableUI.AREA_PANEL);
-});
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_simple.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_simple.js
@@ -28,19 +28,29 @@ add_task(function* () {
         browser.test.assertEq(msg, "from-popup", "correct message received");
         browser.test.sendMessage("popup");
       });
     },
   });
 
   yield extension.startup();
 
+  let widgetId = makeWidgetId(extension.id) + "-browser-action";
+  let node = CustomizableUI.getWidget(widgetId).forWindow(window).node;
+
   // Do this a few times to make sure the pop-up is reloaded each time.
   for (let i = 0; i < 3; i++) {
-    clickBrowserAction(extension);
+    let evt = new CustomEvent("command", {
+      bubbles: true,
+      cancelable: true,
+    });
+    node.dispatchEvent(evt);
 
     yield extension.awaitMessage("popup");
 
-    closeBrowserAction(extension);
+    let panel = node.querySelector("panel");
+    if (panel) {
+      panel.hidePopup();
+    }
   }
 
   yield extension.unload();
 });
--- a/browser/components/extensions/test/browser/browser_ext_currentWindow.js
+++ b/browser/components/extensions/test/browser/browser_ext_currentWindow.js
@@ -105,23 +105,33 @@ add_task(function* () {
   }
 
   yield focusWindow(win1);
   yield checkWindow("background", winId1, "win1");
   yield focusWindow(win2);
   yield checkWindow("background", winId2, "win2");
 
   function* triggerPopup(win, callback) {
-    yield clickBrowserAction(extension, win);
+    let widgetId = makeWidgetId(extension.id) + "-browser-action";
+    let node = CustomizableUI.getWidget(widgetId).forWindow(win).node;
+
+    let evt = new CustomEvent("command", {
+      bubbles: true,
+      cancelable: true,
+    });
+    node.dispatchEvent(evt);
 
     yield extension.awaitMessage("popup-ready");
 
     yield callback();
 
-    closeBrowserAction(extension, win);
+    let panel = node.querySelector("panel");
+    if (panel) {
+      panel.hidePopup();
+    }
   }
 
   // Set focus to some other window.
   yield focusWindow(window);
 
   yield triggerPopup(win1, function*() {
     yield checkWindow("popup", winId1, "win1");
   });
--- a/browser/components/extensions/test/browser/browser_ext_getViews.js
+++ b/browser/components/extensions/test/browser/browser_ext_getViews.js
@@ -111,31 +111,35 @@ add_task(function* () {
   yield checkViews("background", 1, 0);
   yield checkViews("tab", 1, 0);
 
   yield openTab(winId2);
 
   yield checkViews("background", 2, 0);
 
   function* triggerPopup(win, callback) {
-    yield clickBrowserAction(extension, win);
+    let widgetId = makeWidgetId(extension.id) + "-browser-action";
+    let node = CustomizableUI.getWidget(widgetId).forWindow(win).node;
+
+    let evt = new CustomEvent("command", {
+      bubbles: true,
+      cancelable: true,
+    });
+    node.dispatchEvent(evt);
 
     yield extension.awaitMessage("popup-ready");
 
     yield callback();
 
-    closeBrowserAction(extension, win);
+    let panel = node.querySelector("panel");
+    if (panel) {
+      panel.hidePopup();
+    }
   }
 
-  // The popup occasionally closes prematurely if we open it immediately here.
-  // I'm not sure what causes it to close (it's something internal, and seems to
-  // be focus-related, but it's not caused by JS calling hidePopup), but even a
-  // short timeout seems to consistently fix it.
-  yield new Promise(resolve => win1.setTimeout(resolve, 10));
-
   yield triggerPopup(win1, function*() {
     yield checkViews("background", 2, 1);
     yield checkViews("popup", 2, 1);
   });
 
   yield triggerPopup(win2, function*() {
     yield checkViews("background", 2, 1);
     yield checkViews("popup", 2, 1);
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_context.js
@@ -1,187 +1,39 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-function* runTests(options) {
-  function background(getTests) {
-    let tabs;
-    let tests;
-
-    // Gets the current details of the page action, and returns a
-    // promise that resolves to an object containing them.
-    function getDetails() {
-      return new Promise(resolve => {
-        return browser.tabs.query({ active: true, currentWindow: true }, resolve);
-      }).then(tabs => {
-        let tabId = tabs[0].id;
-        return Promise.all([
-          new Promise(resolve => browser.pageAction.getTitle({tabId}, resolve)),
-          new Promise(resolve => browser.pageAction.getPopup({tabId}, resolve))]);
-      }).then(details => {
-        return Promise.resolve({ title: details[0],
-                                 popup: details[1] });
-      });
-    }
-
-
-    // Runs the next test in the `tests` array, checks the results,
-    // and passes control back to the outer test scope.
-    function nextTest() {
-      let test = tests.shift();
-
-      test(expecting => {
-        function finish() {
-          // Check that the actual icon has the expected values, then
-          // run the next test.
-          browser.test.sendMessage("nextTest", expecting, tests.length);
-        }
-
-        if (expecting) {
-          // Check that the API returns the expected values, and then
-          // run the next test.
-          getDetails().then(details => {
-            browser.test.assertEq(expecting.title, details.title,
-                                  "expected value from getTitle");
-
-            browser.test.assertEq(expecting.popup, details.popup,
-                                  "expected value from getPopup");
-
-            finish();
-          });
-        } else {
-          finish();
-        }
-      });
-    }
-
-    function runTests() {
-      tabs = [];
-      tests = getTests(tabs);
-
-      browser.tabs.query({ active: true, currentWindow: true }, resultTabs => {
-        tabs[0] = resultTabs[0].id;
-
-        nextTest();
-      });
-    }
-
-    browser.test.onMessage.addListener((msg) => {
-      if (msg == "runTests") {
-        runTests();
-      } else if (msg == "runNextTest") {
-        nextTest();
-      } else {
-        browser.test.fail(`Unexpected message: ${msg}`);
-      }
-    });
-
-    runTests();
-  }
-
+add_task(function* testTabSwitchContext() {
   let extension = ExtensionTestUtils.loadExtension({
-    manifest: options.manifest,
-
-    background: `(${background})(${options.getTests})`,
-  });
-
-  let pageActionId = makeWidgetId(extension.id) + "-page-action";
-  let currentWindow = window;
-  let windows = [];
-
-  function checkDetails(details) {
-    let image = currentWindow.document.getElementById(pageActionId);
-    if (details == null) {
-      ok(image == null || image.hidden, "image is hidden");
-    } else {
-      ok(image, "image exists");
-
-      is(image.src, details.icon, "icon URL is correct");
-
-      let title = details.title || options.manifest.name;
-      is(image.getAttribute("tooltiptext"), title, "image title is correct");
-      is(image.getAttribute("aria-label"), title, "image aria-label is correct");
-      // TODO: Popup URL.
-    }
-  }
-
-  let testNewWindows = 1;
-
-  let awaitFinish = new Promise(resolve => {
-    extension.onMessage("nextTest", (expecting, testsRemaining) => {
-      checkDetails(expecting);
-
-      if (testsRemaining) {
-        extension.sendMessage("runNextTest");
-      } else if (testNewWindows) {
-        testNewWindows--;
-
-        BrowserTestUtils.openNewBrowserWindow().then(window => {
-          windows.push(window);
-          currentWindow = window;
-          return focusWindow(window);
-        }).then(() => {
-          extension.sendMessage("runTests");
-        });
-      } else {
-        resolve();
-      }
-    });
-  });
-
-  yield extension.startup();
-
-  yield awaitFinish;
-
-  yield extension.unload();
-
-  let node = document.getElementById(pageActionId);
-  is(node, null, "pageAction image removed from document");
-
-  currentWindow = null;
-  for (let win of windows.splice(0)) {
-    node = win.document.getElementById(pageActionId);
-    is(node, null, "pageAction image removed from second document");
-
-    yield BrowserTestUtils.closeWindow(win);
-  }
-}
-
-add_task(function* testTabSwitchContext() {
-  yield runTests({
     manifest: {
-      "name": "Foo Extension",
-
       "page_action": {
         "default_icon": "default.png",
         "default_popup": "default.html",
         "default_title": "Default Title \u263a",
       },
-
       "permissions": ["tabs"],
     },
 
-    getTests(tabs) {
+    background: function() {
       let details = [
         { "icon": browser.runtime.getURL("default.png"),
           "popup": browser.runtime.getURL("default.html"),
           "title": "Default Title \u263a" },
         { "icon": browser.runtime.getURL("1.png"),
           "popup": browser.runtime.getURL("default.html"),
           "title": "Default Title \u263a" },
         { "icon": browser.runtime.getURL("2.png"),
           "popup": browser.runtime.getURL("2.html"),
           "title": "Title 2" },
-        { "icon": browser.runtime.getURL("2.png"),
-          "popup": browser.runtime.getURL("2.html"),
-          "title": "Default Title \u263a" },
       ];
 
-      return [
+      let tabs;
+      let tests;
+      let allTests = [
         expect => {
           browser.test.log("Initial state. No icon visible.");
           expect(null);
         },
         expect => {
           browser.test.log("Show the icon on the first tab, expect default properties.");
           browser.pageAction.show(tabs[0]);
           expect(details[0]);
@@ -204,22 +56,16 @@ add_task(function* testTabSwitchContext(
           browser.pageAction.show(tabId);
           browser.pageAction.setIcon({ tabId, path: "2.png" });
           browser.pageAction.setPopup({ tabId, popup: "2.html" });
           browser.pageAction.setTitle({ tabId, title: "Title 2" });
 
           expect(details[2]);
         },
         expect => {
-          browser.test.log("Clear the title. Expect default title.");
-          browser.pageAction.setTitle({ tabId: tabs[1], title: "" });
-
-          expect(details[3]);
-        },
-        expect => {
           browser.test.log("Navigate to a new page. Expect icon hidden.");
 
           // TODO: This listener should not be necessary, but the |tabs.update|
           // callback currently fires too early in e10s windows.
           browser.tabs.onUpdated.addListener(function listener(tabId, changed) {
             if (tabId == tabs[1] && changed.url) {
               browser.tabs.onUpdated.removeListener(listener);
               expect(null);
@@ -253,58 +99,140 @@ add_task(function* testTabSwitchContext(
           });
         },
         expect => {
           browser.test.log("Hide the icon. Expect hidden.");
           browser.pageAction.hide(tabs[0]);
           expect(null);
         },
       ];
+
+      // Gets the current details of the page action, and returns a
+      // promise that resolves to an object containing them.
+      function getDetails() {
+        return new Promise(resolve => {
+          return browser.tabs.query({ active: true, currentWindow: true }, resolve);
+        }).then(tabs => {
+          let tabId = tabs[0].id;
+          return Promise.all([
+            new Promise(resolve => browser.pageAction.getTitle({tabId}, resolve)),
+            new Promise(resolve => browser.pageAction.getPopup({tabId}, resolve))]);
+        }).then(details => {
+          return Promise.resolve({ title: details[0],
+                                   popup: details[1] });
+        });
+      }
+
+
+      // Runs the next test in the `tests` array, checks the results,
+      // and passes control back to the outer test scope.
+      function nextTest() {
+        let test = tests.shift();
+
+        test(expecting => {
+          function finish() {
+            // Check that the actual icon has the expected values, then
+            // run the next test.
+            browser.test.sendMessage("nextTest", expecting, tests.length);
+          }
+
+          if (expecting) {
+            // Check that the API returns the expected values, and then
+            // run the next test.
+            getDetails().then(details => {
+              browser.test.assertEq(expecting.title, details.title,
+                                    "expected value from getTitle");
+
+              browser.test.assertEq(expecting.popup, details.popup,
+                                    "expected value from getPopup");
+
+              finish();
+            });
+          } else {
+            finish();
+          }
+        });
+      }
+
+      function runTests() {
+        tabs = [];
+        tests = allTests.slice();
+
+        browser.tabs.query({ active: true, currentWindow: true }, resultTabs => {
+          tabs[0] = resultTabs[0].id;
+
+          nextTest();
+        });
+      }
+
+      browser.test.onMessage.addListener((msg) => {
+        if (msg == "runTests") {
+          runTests();
+        } else if (msg == "runNextTest") {
+          nextTest();
+        } else {
+          browser.test.fail(`Unexpected message: ${msg}`);
+        }
+      });
+
+      runTests();
     },
   });
-});
+
+  let pageActionId = makeWidgetId(extension.id) + "-page-action";
+  let currentWindow = window;
+  let windows = [];
 
-add_task(function* testDefaultTitle() {
-  yield runTests({
-    manifest: {
-      "name": "Foo Extension",
-
-      "page_action": {
-        "default_icon": "icon.png",
-      },
+  function checkDetails(details) {
+    let image = currentWindow.document.getElementById(pageActionId);
+    if (details == null) {
+      ok(image == null || image.hidden, "image is hidden");
+    } else {
+      ok(image, "image exists");
 
-      "permissions": ["tabs"],
-    },
+      is(image.src, details.icon, "icon URL is correct");
+      is(image.getAttribute("tooltiptext"), details.title, "image title is correct");
+      is(image.getAttribute("aria-label"), details.title, "image aria-label is correct");
+      // TODO: Popup URL.
+    }
+  }
+
+  let testNewWindows = 1;
 
-    getTests(tabs) {
-      let details = [
-        { "title": "Foo Extension",
-          "popup": "",
-          "icon": browser.runtime.getURL("icon.png") },
-        { "title": "Foo Title",
-          "popup": "",
-          "icon": browser.runtime.getURL("icon.png") },
-      ];
+  let awaitFinish = new Promise(resolve => {
+    extension.onMessage("nextTest", (expecting, testsRemaining) => {
+      checkDetails(expecting);
+
+      if (testsRemaining) {
+        extension.sendMessage("runNextTest");
+      } else if (testNewWindows) {
+        testNewWindows--;
 
-      return [
-        expect => {
-          browser.test.log("Initial state. No icon visible.");
-          expect(null);
-        },
-        expect => {
-          browser.test.log("Show the icon on the first tab, expect extension title as default title.");
-          browser.pageAction.show(tabs[0]);
-          expect(details[0]);
-        },
-        expect => {
-          browser.test.log("Change the title. Expect new title.");
-          browser.pageAction.setTitle({ tabId: tabs[0], title: "Foo Title" });
-          expect(details[1]);
-        },
-        expect => {
-          browser.test.log("Clear the title. Expect extension title.");
-          browser.pageAction.setTitle({ tabId: tabs[0], title: "" });
-          expect(details[0]);
-        },
-      ];
-    },
+        BrowserTestUtils.openNewBrowserWindow().then(window => {
+          windows.push(window);
+          currentWindow = window;
+          return focusWindow(window);
+        }).then(() => {
+          extension.sendMessage("runTests");
+        });
+      } else {
+        resolve();
+      }
+    });
   });
+
+  yield extension.startup();
+
+  yield awaitFinish;
+
+  yield extension.unload();
+
+  let node = document.getElementById(pageActionId);
+  is(node, null, "pageAction image removed from document");
+
+  currentWindow = null;
+  for (let win of windows.splice(0)) {
+    node = win.document.getElementById(pageActionId);
+    is(node, null, "pageAction image removed from second document");
+
+    yield BrowserTestUtils.closeWindow(win);
+  }
 });
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_popup.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_popup.js
@@ -1,37 +1,49 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+function promisePopupShown(popup) {
+  return new Promise(resolve => {
+    if (popup.popupOpen) {
+      resolve();
+    } else {
+      let onPopupShown = event => {
+        popup.removeEventListener("popupshown", onPopupShown);
+        resolve();
+      };
+      popup.addEventListener("popupshown", onPopupShown);
+    }
+  });
+}
+
 add_task(function* testPageActionPopup() {
-  let scriptPage = url => `<html><head><meta charset="utf-8"><script src="${url}"></script></head></html>`;
-
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "background": {
         "page": "data/background.html",
       },
       "page_action": {
         "default_popup": "popup-a.html",
       },
     },
 
     files: {
-      "popup-a.html": scriptPage("popup-a.js"),
+      "popup-a.html": `<script src="popup-a.js"></script>`,
       "popup-a.js": function() {
         browser.runtime.sendMessage("from-popup-a");
       },
 
-      "data/popup-b.html": scriptPage("popup-b.js"),
+      "data/popup-b.html": `<script src="popup-b.js"></script>`,
       "data/popup-b.js": function() {
         browser.runtime.sendMessage("from-popup-b");
       },
 
-      "data/background.html": scriptPage("background.js"),
+      "data/background.html": `<script src="background.js"></script>`,
 
       "data/background.js": function() {
         let tabId;
 
         let sendClick;
         let tests = [
           () => {
             sendClick({ expectEvent: false, expectPopup: "a" });
--- a/browser/components/extensions/test/browser/browser_ext_popup_api_injection.js
+++ b/browser/components/extensions/test/browser/browser_ext_popup_api_injection.js
@@ -37,16 +37,29 @@ add_task(function* testPageActionPopup()
         browser.browserAction.setPopup({ popup: "/popup-a.html" });
         browser.pageAction.setPopup({ tabId, popup: "popup-b.html" });
 
         browser.test.sendMessage("ok");
       });
     },
   });
 
+  let browserActionId = makeWidgetId(extension.id) + "-browser-action";
+  let pageActionId = makeWidgetId(extension.id) + "-page-action";
+
+  function openPopup(buttonId) {
+    let button = document.getElementById(buttonId);
+    if (buttonId == pageActionId) {
+      // TODO: I don't know why a proper synthesized event doesn't work here.
+      button.dispatchEvent(new MouseEvent("click", {}));
+    } else {
+      EventUtils.synthesizeMouseAtCenter(button, {}, window);
+    }
+  }
+
   let promiseConsoleMessage = pattern => new Promise(resolve => {
     Services.console.registerListener(function listener(msg) {
       if (pattern.test(msg.message)) {
         resolve(msg.message);
         Services.console.unregisterListener(listener);
       }
     });
   });
@@ -54,45 +67,40 @@ add_task(function* testPageActionPopup()
   yield extension.startup();
   yield extension.awaitMessage("ready");
 
 
   // Check that unprivileged documents don't get the API.
   // BrowserAction:
   let awaitMessage = promiseConsoleMessage(/WebExt Privilege Escalation: BrowserAction/);
   SimpleTest.expectUncaughtException();
-  yield clickBrowserAction(extension);
+  openPopup(browserActionId);
 
   let message = yield awaitMessage;
   ok(message.includes("WebExt Privilege Escalation: BrowserAction: typeof(browser) = undefined"),
      `No BrowserAction API injection`);
 
-  yield closeBrowserAction(extension);
-
   // PageAction
   awaitMessage = promiseConsoleMessage(/WebExt Privilege Escalation: PageAction/);
   SimpleTest.expectUncaughtException();
-  yield clickPageAction(extension);
+  openPopup(pageActionId);
 
   message = yield awaitMessage;
   ok(message.includes("WebExt Privilege Escalation: PageAction: typeof(browser) = undefined"),
      `No PageAction API injection: ${message}`);
 
-  yield closePageAction(extension);
-
   SimpleTest.expectUncaughtException(false);
 
 
   // Check that privileged documents *do* get the API.
   extension.sendMessage("next");
   yield extension.awaitMessage("ok");
 
 
-  yield clickBrowserAction(extension);
+  // Check that unprivileged documents don't get the API.
+  openPopup(browserActionId);
   yield extension.awaitMessage("from-popup-a");
-  yield closeBrowserAction(extension);
 
-  yield clickPageAction(extension);
+  openPopup(pageActionId);
   yield extension.awaitMessage("from-popup-b");
-  yield closePageAction(extension);
 
   yield extension.unload();
 });
--- a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_good.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_good.js
@@ -43,21 +43,16 @@ function* testHasPermission(params) {
 
   if (params.setup) {
     yield params.setup(extension);
   }
 
   extension.sendMessage("execute-script");
 
   yield extension.awaitFinish("executeScript");
-
-  if (params.tearDown) {
-    yield params.tearDown(extension);
-  }
-
   yield extension.unload();
 }
 
 add_task(function* testGoodPermissions() {
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/", true);
 
   info("Test explicit host permission");
   yield testHasPermission({
@@ -82,17 +77,16 @@ add_task(function* testGoodPermissions()
     },
     contentSetup() {
       browser.browserAction.onClicked.addListener(() => {
         browser.test.log("Clicked.");
       });
       return Promise.resolve();
     },
     setup: clickBrowserAction,
-    tearDown: closeBrowserAction,
   });
 
   info("Test activeTab permission with a page action click");
   yield testHasPermission({
     manifest: {
       "permissions": ["activeTab"],
       "page_action": {},
     },
@@ -100,27 +94,25 @@ add_task(function* testGoodPermissions()
       return new Promise(resolve => {
         browser.tabs.query({ active: true, currentWindow: true }, tabs => {
           browser.pageAction.show(tabs[0].id);
           resolve();
         });
       });
     },
     setup: clickPageAction,
-    tearDown: closePageAction,
   });
 
   info("Test activeTab permission with a browser action w/popup click");
   yield testHasPermission({
     manifest: {
       "permissions": ["activeTab"],
       "browser_action": { "default_popup": "_blank.html" },
     },
     setup: clickBrowserAction,
-    tearDown: closeBrowserAction,
   });
 
   info("Test activeTab permission with a page action w/popup click");
   yield testHasPermission({
     manifest: {
       "permissions": ["activeTab"],
       "page_action": { "default_popup": "_blank.html" },
     },
@@ -128,17 +120,16 @@ add_task(function* testGoodPermissions()
       return new Promise(resolve => {
         browser.tabs.query({ active: true, currentWindow: true }, tabs => {
           browser.pageAction.show(tabs[0].id);
           resolve();
         });
       });
     },
     setup: clickPageAction,
-    tearDown: closePageAction,
   });
 
   info("Test activeTab permission with a context menu click");
   yield testHasPermission({
     manifest: {
       "permissions": ["activeTab", "contextMenus"],
     },
     contentSetup() {
--- a/browser/components/extensions/test/browser/browser_ext_tabs_getCurrent.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_getCurrent.js
@@ -58,12 +58,11 @@ add_task(function* () {
 
   yield extension.startup();
 
   yield extension.awaitMessage("background-finished");
   yield extension.awaitMessage("tab-finished");
 
   clickBrowserAction(extension);
   yield extension.awaitMessage("popup-finished");
-  yield closeBrowserAction(extension);
 
   yield extension.unload();
 });
--- a/browser/components/extensions/test/browser/head.js
+++ b/browser/components/extensions/test/browser/head.js
@@ -1,19 +1,13 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-/* exported CustomizableUI makeWidgetId focusWindow forceGC
- *          getBrowserActionWidget
- *          clickBrowserAction clickPageAction
- *          getBrowserActionPopup getPageActionPopup
- *          closeBrowserAction closePageAction
- *          promisePopupShown
- */
+/* exported AppConstants CustomizableUI forceGC makeWidgetId focusWindow clickBrowserAction clickPageAction */
 
 var {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm");
 var {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm");
 
 // Bug 1239884: Our tests occasionally hit a long GC pause at unpredictable
 // times in debug builds, which results in intermittent timeouts. Until we have
 // a better solution, we force a GC after certain strategic tests, which tend to
 // accumulate a high number of unreaped windows.
@@ -40,68 +34,22 @@ var focusWindow = Task.async(function* f
       resolve();
     }, true);
   });
 
   win.focus();
   yield promise;
 });
 
-function promisePopupShown(popup) {
-  return new Promise(resolve => {
-    if (popup.state == "open") {
-      resolve();
-    } else {
-      let onPopupShown = event => {
-        popup.removeEventListener("popupshown", onPopupShown);
-        resolve();
-      };
-      popup.addEventListener("popupshown", onPopupShown);
-    }
-  });
-}
-
-function getBrowserActionWidget(extension) {
-  return CustomizableUI.getWidget(makeWidgetId(extension.id) + "-browser-action");
-}
-
-function getBrowserActionPopup(extension, win = window) {
-  let group = getBrowserActionWidget(extension);
+function clickBrowserAction(extension, win = window) {
+  let browserActionId = makeWidgetId(extension.id) + "-browser-action";
+  let elem = win.document.getElementById(browserActionId);
 
-  if (group.areaType == CustomizableUI.TYPE_TOOLBAR) {
-    return win.document.getElementById("customizationui-widget-panel");
-  }
-  return null;
-}
-
-var clickBrowserAction = Task.async(function* (extension, win = window) {
-  let group = getBrowserActionWidget(extension);
-  let widget = group.forWindow(win);
-
-  if (group.areaType == CustomizableUI.TYPE_TOOLBAR) {
-    ok(!widget.overflowed, "Expect widget not to be overflowed");
-  } else if (group.areaType == CustomizableUI.TYPE_MENU_PANEL) {
-    yield win.PanelUI.show();
-  }
-
-  EventUtils.synthesizeMouseAtCenter(widget.node, {}, win);
-});
-
-function closeBrowserAction(extension, win = window) {
-  let group = getBrowserActionWidget(extension);
-
-  let node = win.document.getElementById(group.viewId);
-  CustomizableUI.hidePanelForNode(node);
-
-  return Promise.resolve();
-}
-
-function getPageActionPopup(extension, win = window) {
-  let panelId = makeWidgetId(extension.id) + "-panel";
-  return win.document.getElementById(panelId);
+  EventUtils.synthesizeMouseAtCenter(elem, {}, win);
+  return new Promise(SimpleTest.executeSoon);
 }
 
 function clickPageAction(extension, win = window) {
   // This would normally be set automatically on navigation, and cleared
   // when the user types a value into the URL bar, to show and hide page
   // identity info and icons such as page action buttons.
   //
   // Unfortunately, that doesn't happen automatically in browser chrome
@@ -110,18 +58,8 @@ function clickPageAction(extension, win 
   SetPageProxyState("valid");
 
   let pageActionId = makeWidgetId(extension.id) + "-page-action";
   let elem = win.document.getElementById(pageActionId);
 
   EventUtils.synthesizeMouseAtCenter(elem, {}, win);
   return new Promise(SimpleTest.executeSoon);
 }
-
-function closePageAction(extension, win = window) {
-  let node = getPageActionPopup(extension, win);
-  if (node) {
-    node.hidePopup();
-  }
-
-  return Promise.resolve();
-}
-
--- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css
+++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css
@@ -245,21 +245,16 @@ panelmultiview[nosubviews=true] > .panel
   max-width: @menuPanelWidth@;
 }
 
 #BMB_bookmarksPopup,
 .panel-mainview:not([panelid="PanelUI-popup"]) {
   max-width: @standaloneSubviewWidth@;
 }
 
-/* Give WebExtension stand-alone panels extra width for Chrome compatibility */
-.cui-widget-panel[viewId^=PanelUI-webext-] .panel-mainview {
-  max-width: 800px;
-}
-
 panelview:not([mainview]) .toolbarbutton-text,
 .cui-widget-panel toolbarbutton > .toolbarbutton-text {
   text-align: start;
   display: -moz-box;
 }
 
 .cui-widget-panel > .panel-arrowcontainer > .panel-arrowcontent {
   padding: 4px 0;