# HG changeset patch # User Kris Maglione # Date 1523158701 25200 # Sat Apr 07 20:38:21 2018 -0700 # Node ID f172519157c2c1ffa10693f2b0128ddc6cceebb8 # Parent 45c0a10df388111e8c0c1a33f8a87167d3bc7244 Bug 1382953: Fix permission prompts in about:addons options browsers. r=aswan MozReview-Commit-ID: At5F5cqGSWu diff --git a/browser/components/extensions/test/browser/browser-common.ini b/browser/components/extensions/test/browser/browser-common.ini --- a/browser/components/extensions/test/browser/browser-common.ini +++ b/browser/components/extensions/test/browser/browser-common.ini @@ -190,16 +190,17 @@ skip-if = os == 'mac' # Save as PDF not [browser_ext_tabs_cookieStoreId.js] [browser_ext_tabs_update.js] [browser_ext_tabs_zoom.js] [browser_ext_tabs_update_url.js] [browser_ext_themes_icons.js] [browser_ext_themes_validation.js] [browser_ext_url_overrides_newtab.js] [browser_ext_user_events.js] +skip-if = debug [browser_ext_webRequest.js] [browser_ext_webNavigation_frameId0.js] [browser_ext_webNavigation_getFrames.js] [browser_ext_webNavigation_onCreatedNavigationTarget.js] [browser_ext_webNavigation_onCreatedNavigationTarget_contextmenu.js] [browser_ext_webNavigation_onCreatedNavigationTarget_named_window.js] [browser_ext_webNavigation_onCreatedNavigationTarget_subframe_window_open.js] [browser_ext_webNavigation_onCreatedNavigationTarget_window_open.js] diff --git a/browser/components/extensions/test/browser/browser_ext_user_events.js b/browser/components/extensions/test/browser/browser_ext_user_events.js --- 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": ` + + + + + + + + Link + + `, + + "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"); + promisePopupNotificationShown("addon-webext-permissions").then(panel => { + panel.button.click(); + }); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); - 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"); - 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()); }); diff --git a/browser/modules/ExtensionsUI.jsm b/browser/modules/ExtensionsUI.jsm --- 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);