--- a/browser/components/extensions/test/browser/browser_ext_user_events.js
+++ b/browser/components/extensions/test/browser/browser_ext_user_events.js
@@ -50,25 +50,78 @@ add_task(async function testSources() {
browser.contextMenus.create({
id: "menu",
title: "test user events",
contexts: ["page"],
});
browser.contextMenus.onClicked.addListener(() => request("webNavigation"));
+ browser.test.onMessage.addListener(msg => {
+ if (msg === "openOptionsPage") {
+ browser.runtime.openOptionsPage();
+ }
+ });
+
browser.test.sendMessage("actions-ready");
},
+ files: {
+ "options.html": `<!DOCTYPE html>
+ <html lang="en">
+ <head>
+ <meta charset="UTF-8">
+ <script src="options.js"></script>
+ <script src="https://example.com/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body>
+ <a id="link" href="#">Link</a>
+ </body>
+ </html>`,
+
+ "options.js"() {
+ addEventListener("load", async () => {
+ let link = document.getElementById("link");
+ link.onclick = async event => {
+ event.preventDefault();
+
+ try {
+ let result = await browser.permissions.request({
+ permissions: ["webRequest"],
+ });
+ browser.test.sendMessage("request", {success: true, result});
+ } catch (err) {
+ browser.test.sendMessage("request", {success: false, errmsg: err.message});
+ }
+ };
+
+ // Make a few trips through the event loop to make sure the
+ // options browser is fully visible. This is a bit dodgy, but
+ // we don't really have a reliable way to detect this from the
+ // options page side, and synthetic click events won't work
+ // until it is.
+ for (let i = 0; i < 10; i++) {
+ await new Promise(resolve => setTimeout(resolve, 0));
+ }
+
+ synthesizeMouseAtCenter(link, {});
+ }, {once: true});
+ },
+ },
+
manifest: {
browser_action: {default_title: "test"},
page_action: {default_title: "test"},
permissions: ["contextMenus"],
- optional_permissions: ["bookmarks", "tabs", "webNavigation"],
+ optional_permissions: ["bookmarks", "tabs", "webNavigation", "webRequest"],
+ options_ui: {page: "options.html"},
+ content_security_policy: "script-src 'self' https://example.com; object-src 'none';",
},
+
+ useAddonManager: "temporary",
});
async function check(what) {
let result = await extension.awaitMessage("request");
ok(result.success, `request() did not throw when called from ${what}`);
is(result.result, true, `request() succeeded when called from ${what}`);
}
@@ -100,15 +153,21 @@ add_task(async function testSources() {
gBrowser.selectedTab = tab;
let menu = await openContextMenu("body");
let items = menu.getElementsByAttribute("label", "test user events");
is(items.length, 1, "Found context menu item");
EventUtils.synthesizeMouseAtCenter(items[0], {});
await check("context menu click");
- BrowserTestUtils.removeTab(tab);
+ await BrowserTestUtils.removeTab(tab);
+
+ extension.sendMessage("openOptionsPage");
+ promisePopupNotificationShown("addon-webext-permissions").then(panel => {
+ panel.button.click();
+ });
+ await check("options page link click");
await extension.unload();
registerCleanupFunction(() => CustomizableUI.reset());
});
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -26,16 +26,23 @@ XPCOMUtils.defineLazyPreferenceGetter(th
const DEFAULT_EXTENSION_ICON = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
const BROWSER_PROPERTIES = "chrome://browser/locale/browser.properties";
const BRAND_PROPERTIES = "chrome://branding/locale/brand.properties";
const HTML_NS = "http://www.w3.org/1999/xhtml";
+function getTabBrowser(browser) {
+ while (browser.ownerDocument.docShell.itemType !== Ci.nsIDocShell.typeChrome) {
+ browser = browser.ownerDocument.docShell.chromeEventHandler;
+ }
+ return {browser, window: browser.ownerGlobal};
+}
+
var ExtensionsUI = {
sideloaded: new Set(),
updates: new Set(),
sideloadListener: null,
histogram: null,
pendingNotifications: new WeakMap(),
@@ -161,19 +168,17 @@ var ExtensionsUI = {
AppMenuNotifications.showBadgeOnlyNotification("addon-alert");
}
this.emit("change");
},
showAddonsManager(browser, strings, icon, histkey) {
let global = browser.selectedBrowser.ownerGlobal;
return global.BrowserOpenAddonsMgr("addons://list/extension").then(aomWin => {
- let aomBrowser = aomWin.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDocShell)
- .chromeEventHandler;
+ let aomBrowser = aomWin.document.docShell.chromeEventHandler;
return this.showPermissionsPrompt(aomBrowser, strings, icon, histkey);
});
},
showSideloaded(browser, addon) {
addon.markAsSeen();
this.sideloaded.delete(addon);
this._updateNotifications();
@@ -203,20 +208,22 @@ var ExtensionsUI = {
this._updateNotifications();
});
},
observe(subject, topic, data) {
if (topic == "webextension-permission-prompt") {
let {target, info} = subject.wrappedJSObject;
+ let {browser, window} = getTabBrowser(target);
+
// Dismiss the progress notification. Note that this is bad if
// there are multiple simultaneous installs happening, see
// bug 1329884 for a longer explanation.
- let progressNotification = target.ownerGlobal.PopupNotifications.getNotification("addon-progress", target);
+ let progressNotification = window.PopupNotifications.getNotification("addon-progress", browser);
if (progressNotification) {
progressNotification.remove();
}
info.unsigned = info.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING;
if (info.unsigned && Cu.isInAutomation &&
Services.prefs.getBoolPref("extensions.ui.ignoreUnsigned", false)) {
info.unsigned = false;
@@ -318,23 +325,23 @@ var ExtensionsUI = {
let appName = brandBundle.GetStringFromName("brandShortName");
let info2 = Object.assign({appName}, info);
let strings = ExtensionData.formatPermissionStrings(info2, bundle);
strings.addonName = info.addon.name;
return strings;
},
- async showPermissionsPrompt(browser, strings, icon, histkey) {
- let win = browser.ownerGlobal;
+ async showPermissionsPrompt(target, strings, icon, histkey) {
+ let {browser, window} = getTabBrowser(target);
// Wait for any pending prompts in this window to complete before
// showing the next one.
let pending;
- while ((pending = this.pendingNotifications.get(win))) {
+ while ((pending = this.pendingNotifications.get(window))) {
await pending;
}
let promise = new Promise(resolve => {
function eventCallback(topic) {
let doc = this.browser.ownerDocument;
if (topic == "showing") {
let textEl = doc.getElementById("addon-webext-perm-text");
@@ -392,27 +399,27 @@ var ExtensionsUI = {
if (histkey) {
this.histogram.add(histkey + "Rejected");
}
resolve(false);
},
},
];
- win.PopupNotifications.show(browser, "addon-webext-permissions", strings.header,
- "addons-notification-icon", action,
- secondaryActions, popupOptions);
+ window.PopupNotifications.show(browser, "addon-webext-permissions", strings.header,
+ "addons-notification-icon", action,
+ secondaryActions, popupOptions);
});
- this.pendingNotifications.set(win, promise);
- promise.finally(() => this.pendingNotifications.delete(win));
+ this.pendingNotifications.set(window, promise);
+ promise.finally(() => this.pendingNotifications.delete(window));
return promise;
},
- showDefaultSearchPrompt(browser, strings, icon) {
+ showDefaultSearchPrompt(target, strings, icon) {
return new Promise(resolve => {
let popupOptions = {
hideClose: true,
popupIconURL: icon || DEFAULT_EXTENSION_ICON,
persistent: false,
removeOnDismissal: true,
eventCallback(topic) {
if (topic == "removed") {
@@ -435,30 +442,30 @@ var ExtensionsUI = {
label: strings.cancelText,
accessKey: strings.cancelKey,
callback: () => {
resolve(false);
},
},
];
- let win = browser.ownerGlobal;
- win.PopupNotifications.show(browser, "addon-webext-defaultsearch", strings.text,
- "addons-notification-icon", action,
- secondaryActions, popupOptions);
+ let {browser, window} = getTabBrowser(target);
+ window.PopupNotifications.show(browser, "addon-webext-defaultsearch", strings.text,
+ "addons-notification-icon", action,
+ secondaryActions, popupOptions);
});
},
showInstallNotification(target, addon) {
- let win = target.ownerGlobal;
- let popups = win.PopupNotifications;
+ let {browser, window} = getTabBrowser(target);
+ let popups = window.PopupNotifications;
- let brandBundle = win.document.getElementById("bundle_brand");
+ let brandBundle = window.document.getElementById("bundle_brand");
let appName = brandBundle.getString("brandShortName");
- let bundle = win.gNavigatorBundle;
+ let bundle = window.gNavigatorBundle;
let message = bundle.getFormattedString("addonPostInstall.message1",
["<>", appName]);
return new Promise(resolve => {
let action = {
label: bundle.getString("addonPostInstall.okay.label"),
accessKey: bundle.getString("addonPostInstall.okay.key"),
callback: resolve,
@@ -474,15 +481,15 @@ var ExtensionsUI = {
eventCallback(topic) {
if (topic == "dismissed") {
resolve();
}
},
name: addon.name,
};
- popups.show(target, "addon-installed", message, "addons-notification-icon",
+ popups.show(browser, "addon-installed", message, "addons-notification-icon",
action, null, options);
});
},
};
EventEmitter.decorate(ExtensionsUI);