--- a/browser/components/extensions/.eslintrc
+++ b/browser/components/extensions/.eslintrc
@@ -1,16 +1,17 @@
{
"extends": "../../../toolkit/components/extensions/.eslintrc",
"globals": {
+ "AllWindowEvents": true,
"currentWindow": true,
"EventEmitter": true,
"IconDetails": true,
- "openPanel": true,
"makeWidgetId": true,
+ "PanelPopup": true,
"TabContext": true,
- "AllWindowEvents": true,
+ "ViewPopup": true,
"WindowEventManager": true,
"WindowListManager": true,
"WindowManager": true,
},
}
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -8,28 +8,31 @@ 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;
- this.id = makeWidgetId(extension.id) + "-browser-action";
+ this.id = `${makeWidgetId(extension.id)}-browser-action`;
+ this.viewId = `PanelUI-webext-${makeWidgetId(extension.id)}-browser-action-view`;
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);
@@ -50,54 +53,79 @@ function BrowserAction(options, extensio
EventEmitter.decorate(this);
}
BrowserAction.prototype = {
build() {
let widget = CustomizableUI.createWidget({
id: this.id,
- type: "custom",
+ viewId: this.viewId,
+ type: "view",
removable: true,
+ label: this.defaults.title || this.extension.name,
+ tooltiptext: this.defaults.title || "",
defaultArea: CustomizableUI.AREA_NAVBAR,
- onBuild: document => {
- let node = document.createElement("toolbarbutton");
- node.id = this.id;
- node.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional badged-button");
+
+ 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");
node.setAttribute("constrain-size", "true");
this.updateButton(node, this.defaults);
+ },
+ onViewShowing: event => {
+ let document = event.target.ownerDocument;
let tabbrowser = document.defaultView.gBrowser;
- 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");
+ let tab = tabbrowser.selectedTab;
+ let popupURL = this.getProperty(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) {
+ try {
+ new ViewPopup(this.extension, event.target, popupURL);
+ } catch (e) {
+ Cu.reportError(e);
+ event.preventDefault();
}
- });
-
- return node;
+ } else {
+ // This isn't not a hack, but it seems to provide the correct behavior
+ // with the fewest complications.
+ event.preventDefault();
+ this.emit("click");
+ }
},
});
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.badgeText) {
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -133,22 +133,26 @@ 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 popup = this.tabContext.get(tab).popup;
+ let popupURL = this.tabContext.get(tab).popup;
this.tabManager.addActiveTabPermission(tab);
- if (popup) {
- openPanel(this.getButton(window), popup, this.extension);
+ // 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);
} else {
this.emit("click", tab);
}
},
handleLocationChange(eventType, tab, fromBrowse) {
if (fromBrowse) {
this.tabContext.clear(tab);
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -1,18 +1,22 @@
/* -*- 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|
@@ -121,113 +125,203 @@ global.IconDetails = {
};
global.makeWidgetId = id => {
id = id.toLowerCase();
// FIXME: This allows for collisions.
return id.replace(/[^a-z0-9_-]/g, "_");
};
-// 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;
+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;
- let popupURI = Services.io.newURI(popupURL, null, extension.baseURI);
+ this.contentReady = new Promise(resolve => {
+ this._resolveContentReady = resolve;
+ });
+
+ this.viewNode.addEventListener(this.DESTROY_EVENT, this);
- Services.scriptSecurityManager.checkLoadURIWithPrincipal(
- extension.principal, popupURI,
- Services.scriptSecurityManager.DISALLOW_SCRIPT);
+ this.browser = null;
+ this.browserReady = this.createBrowser(viewNode, popupURI);
+ }
+
+ 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);
- 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");
+ this.context.unload();
+ this.browser.remove();
+ });
+ }
+
+ handleEvent(event) {
+ switch (event.type) {
+ case this.DESTROY_EVENT:
+ this.destroy();
+ 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;
+ 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;
+ }
}
- 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);
+ 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.
- let titleChangedListener = () => {
- panel.setAttribute("aria-label", browser.contentTitle);
- };
+ // 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 context;
- let popuphidden = () => {
- panel.removeEventListener("popuphidden", popuphidden);
- browser.removeEventListener("DOMTitleChanged", titleChangedListener, true);
- context.unload();
- panel.remove();
- };
- panel.addEventListener("popuphidden", popuphidden);
+ 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 loadListener = () => {
- panel.removeEventListener("load", loadListener);
+ this.browser.addEventListener("load", this, true);
+ this.browser.addEventListener("DOMTitleChanged", this, true);
+ this.browser.addEventListener("DOMWindowClose", this, true);
+ });
+ }
- 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);
+ // 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;
- let contentLoadListener = event => {
- if (event.target != browser.contentDocument) {
- return;
- }
- browser.removeEventListener("load", contentLoadListener, true);
+ // 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 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];
- }
+ this.browser.style.width = `${width}px`;
+ this.browser.style.height = `${height}px`;
+
+ this._resolveContentReady();
+ }
+}
+
+global.PanelPopup = class PanelPopup extends BasePopup {
+ constructor(extension, button, popupURL) {
+ let document = button.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 window = document.defaultView;
- width /= window.devicePixelRatio;
- height /= window.devicePixelRatio;
- width = Math.min(width, 800);
- height = Math.min(height, 800);
+ document.getElementById("mainPopupSet").appendChild(panel);
+
+ super(extension, panel, popupURL);
- browser.setAttribute("width", width);
- browser.setAttribute("height", height);
+ this.contentReady.then(() => {
+ panel.openPopup(button, "bottomcenter topright", 0, 0, false, false);
+ });
+ }
+
+ get DESTROY_EVENT() {
+ return "popuphidden";
+ }
- panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
- };
- browser.addEventListener("load", contentLoadListener, true);
+ destroy() {
+ super.destroy();
+ this.viewNode.remove();
+ }
- browser.addEventListener("DOMTitleChanged", titleChangedListener, true);
- };
- panel.addEventListener("load", loadListener);
+ closePopup() {
+ this.viewNode.hidePopup();
+ }
+};
- return panel;
+global.ViewPopup = class ViewPopup extends BasePopup {
+ get DESTROY_EVENT() {
+ return "ViewHiding";
+ }
+
+ closePopup() {
+ CustomizableUI.hidePanelForNode(this.viewNode);
+ }
};
// 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/browser_ext_browserAction_popup.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_popup.js
@@ -1,15 +1,15 @@
/* -*- 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) {
+ if (popup.state == "open") {
resolve();
} else {
let onPopupShown = event => {
popup.removeEventListener("popupshown", onPopupShown);
resolve();
};
popup.addEventListener("popupshown", onPopupShown);
}
@@ -111,35 +111,35 @@ add_task(function* testPageActionPopup()
}
});
browser.test.sendMessage("next-test");
},
},
});
- let panelId = makeWidgetId(extension.id) + "-panel";
+ let viewId = `PanelUI-webext-${makeWidgetId(extension.id)}-browser-action-view`;
extension.onMessage("send-click", () => {
clickBrowserAction(extension);
});
extension.onMessage("next-test", Task.async(function* () {
- let panel = document.getElementById(panelId);
+ let panel = getBrowserActionPopup(extension);
if (panel) {
yield promisePopupShown(panel);
panel.hidePopup();
- panel = document.getElementById(panelId);
+ panel = getBrowserActionPopup(extension);
is(panel, null, "panel successfully removed from document after hiding");
}
extension.sendMessage("next-test");
}));
yield Promise.all([extension.startup(), extension.awaitFinish("browseraction-tests-done")]);
yield extension.unload();
- let panel = document.getElementById(panelId);
- is(panel, null, "browserAction panel removed from document");
+ let view = document.getElementById(viewId);
+ is(view, null, "browserAction view removed from document");
});
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_simple.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_simple.js
@@ -28,29 +28,19 @@ 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++) {
- let evt = new CustomEvent("command", {
- bubbles: true,
- cancelable: true,
- });
- node.dispatchEvent(evt);
+ clickBrowserAction(extension);
yield extension.awaitMessage("popup");
- let panel = node.querySelector("panel");
- if (panel) {
- panel.hidePopup();
- }
+ closeBrowserAction(extension);
}
yield extension.unload();
});
--- a/browser/components/extensions/test/browser/browser_ext_currentWindow.js
+++ b/browser/components/extensions/test/browser/browser_ext_currentWindow.js
@@ -105,30 +105,23 @@ add_task(function* () {
}
yield focusWindow(win1);
yield checkWindow("background", winId1, "win1");
yield focusWindow(win2);
yield checkWindow("background", winId2, "win2");
function* triggerPopup(win, callback) {
- 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 clickBrowserAction(extension, win);
yield extension.awaitMessage("popup-ready");
yield callback();
- let panel = node.querySelector("panel");
+ let panel = getBrowserActionPopup(extension, win);
if (panel) {
panel.hidePopup();
}
}
// Set focus to some other window.
yield focusWindow(window);
--- a/browser/components/extensions/test/browser/browser_ext_getViews.js
+++ b/browser/components/extensions/test/browser/browser_ext_getViews.js
@@ -111,35 +111,34 @@ 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) {
- 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 clickBrowserAction(extension, win);
yield extension.awaitMessage("popup-ready");
yield callback();
- let panel = node.querySelector("panel");
+ let panel = getBrowserActionPopup(extension, win);
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_popup_api_injection.js
+++ b/browser/components/extensions/test/browser/browser_ext_popup_api_injection.js
@@ -37,29 +37,16 @@ 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);
}
});
});
@@ -67,40 +54,45 @@ 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();
- openPopup(browserActionId);
+ yield clickBrowserAction(extension);
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();
- openPopup(pageActionId);
+ yield clickPageAction(extension);
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");
- // Check that unprivileged documents don't get the API.
- openPopup(browserActionId);
+ yield clickBrowserAction(extension);
yield extension.awaitMessage("from-popup-a");
+ yield closeBrowserAction(extension);
- openPopup(pageActionId);
+ yield clickPageAction(extension);
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,16 +43,21 @@ 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({
@@ -77,16 +82,17 @@ 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": {},
},
@@ -94,25 +100,27 @@ 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" },
},
@@ -120,16 +128,17 @@ 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,11 +58,12 @@ 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,13 +1,17 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
-/* exported CustomizableUI makeWidgetId focusWindow clickBrowserAction clickPageAction */
+/* exported CustomizableUI makeWidgetId focusWindow
+ * clickBrowserAction clickPageAction
+ * getBrowserActionPopup getPageActionPopup
+ * closeBrowserAction closePageAction
+ */
var {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm");
function makeWidgetId(id) {
id = id.toLowerCase();
return id.replace(/[^a-z0-9_-]/g, "_");
}
@@ -23,32 +27,60 @@ var focusWindow = Task.async(function* f
resolve();
}, true);
});
win.focus();
yield promise;
});
+function getBrowserActionPopup(extension, win = window) {
+ return win.document.getElementById("customizationui-widget-panel");
+}
+
function clickBrowserAction(extension, win = window) {
let browserActionId = makeWidgetId(extension.id) + "-browser-action";
let elem = win.document.getElementById(browserActionId);
EventUtils.synthesizeMouseAtCenter(elem, {}, win);
return new Promise(SimpleTest.executeSoon);
}
+function closeBrowserAction(extension, win = window) {
+ let node = getBrowserActionPopup(extension, win);
+ if (node) {
+ node.hidePopup();
+ }
+
+ return Promise.resolve();
+}
+
+function getPageActionPopup(extension, win = window) {
+ let panelId = makeWidgetId(extension.id) + "-panel";
+ return win.document.getElementById(panelId);
+}
+
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
// tests.
/* globals SetPageProxyState */
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,16 +245,21 @@ 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;