--- a/browser/components/extensions/test/browser/browser_ext_user_events.js
+++ b/browser/components/extensions/test/browser/browser_ext_user_events.js
@@ -1,76 +1,172 @@
"use strict";
+/**
+ * Wait for the given PopupNotification to display
+ *
+ * @param {string} name
+ * The name of the notification to wait for.
+ *
+ * @returns {Promise}
+ * Resolves with the notification window.
+ */
+function promisePopupNotificationShown(name) {
+ return new Promise(resolve => {
+ function popupshown() {
+ let notification = PopupNotifications.getNotification(name);
+ if (!notification) { return; }
+
+ ok(notification, `${name} notification shown`);
+ ok(PopupNotifications.isPanelOpen, "notification panel open");
+
+ PopupNotifications.panel.removeEventListener("popupshown", popupshown);
+ resolve(PopupNotifications.panel.firstChild);
+ }
+
+ PopupNotifications.panel.addEventListener("popupshown", popupshown);
+ });
+}
+
// Test that different types of events are all considered
// "handling user input".
add_task(async function testSources() {
let extension = ExtensionTestUtils.loadExtension({
async background() {
- async function request() {
+ async function request(perm) {
try {
let result = await browser.permissions.request({
- permissions: ["cookies"],
+ permissions: [perm],
});
browser.test.sendMessage("request", {success: true, result});
} catch (err) {
browser.test.sendMessage("request", {success: false, errmsg: err.message});
}
}
let tabs = await browser.tabs.query({active: true, currentWindow: true});
await browser.pageAction.show(tabs[0].id);
- browser.pageAction.onClicked.addListener(request);
- browser.browserAction.onClicked.addListener(request);
+ browser.pageAction.onClicked.addListener(() => request("bookmarks"));
+ browser.browserAction.onClicked.addListener(() => request("tabs"));
browser.contextMenus.create({
id: "menu",
title: "test user events",
contexts: ["page"],
});
- browser.contextMenus.onClicked.addListener(request);
+ browser.contextMenus.onClicked.addListener(() => request("bookmarks"));
+
+ 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": function() {
+ 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: ["cookies"],
+ 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}`);
}
// Remove Sidebar button to prevent pushing extension button to overflow menu
CustomizableUI.removeWidgetFromArea("sidebar-button");
await extension.startup();
await extension.awaitMessage("actions-ready");
+ promisePopupNotificationShown("addon-webext-permissions").then(panel => {
+ panel.button.click();
+ });
+
clickPageAction(extension);
await check("page action click");
+ promisePopupNotificationShown("addon-webext-permissions").then(panel => {
+ panel.button.click();
+ });
+
clickBrowserAction(extension);
await check("browser action click");
- let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
- gBrowser.selectedTab = tab;
+ promisePopupNotificationShown("addon-webext-permissions").then(panel => {
+ panel.button.click();
+ });
+
+ await BrowserTestUtils.openNewForegroundTab(gBrowser);
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");
- await BrowserTestUtils.removeTab(tab);
+ extension.sendMessage("openOptionsPage");
+ promisePopupNotificationShown("addon-webext-permissions").then(panel => {
+ panel.button.click();
+ });
+ await check("options page link click");
+
+ await BrowserTestUtils.removeTab(gBrowser.selectedTab);
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,
async init() {
this.histogram = Services.telemetry.getHistogramById("EXTENSION_INSTALL_PROMPT_RESULT");
@@ -159,19 +166,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();
@@ -201,20 +206,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;
@@ -238,17 +245,17 @@ var ExtensionsUI = {
} else if (info.source == "AMO") {
histkey = "installAmo";
} else if (info.source == "local") {
histkey = "installLocal";
} else {
histkey = "installWeb";
}
- this.showPermissionsPrompt(target, strings, icon, histkey)
+ this.showPermissionsPrompt(browser, strings, icon, histkey)
.then(answer => {
if (answer) {
info.resolve();
} else {
info.reject();
}
});
} else if (topic == "webextension-update-permissions") {
@@ -316,17 +323,18 @@ 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;
},
- showPermissionsPrompt(browser, strings, icon, histkey) {
+ showPermissionsPrompt(target, strings, icon, histkey) {
+ let {browser, window} = getTabBrowser(target);
function eventCallback(topic) {
let doc = this.browser.ownerDocument;
if (topic == "showing") {
let textEl = doc.getElementById("addon-webext-perm-text");
textEl.textContent = strings.text;
textEl.hidden = !strings.text;
let listIntroEl = doc.getElementById("addon-webext-perm-intro");
@@ -352,17 +360,16 @@ var ExtensionsUI = {
let popupOptions = {
hideClose: true,
popupIconURL: icon || DEFAULT_EXTENSION_ICON,
persistent: true,
eventCallback,
name: strings.addonName,
};
- let win = browser.ownerGlobal;
return new Promise(resolve => {
let action = {
label: strings.acceptText,
accessKey: strings.acceptKey,
callback: () => {
if (histkey) {
this.histogram.add(histkey + "Accepted");
}
@@ -377,23 +384,23 @@ 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);
});
},
- 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") {
@@ -416,30 +423,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,
@@ -455,15 +462,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);