Bug 1382953: Fix permission prompts in about:addons options browsers. r?aswan draft
authorKris Maglione <maglione.k@gmail.com>
Sat, 07 Apr 2018 20:38:21 -0700
changeset 778961 2bce19447ae0bd4b6cd8f51b16f1e7fabe59d6c8
parent 778959 a51eff8c27bfe75861ffe0d9c296367114eb9b4e
push id105627
push usermaglione.k@gmail.com
push dateSun, 08 Apr 2018 03:53:17 +0000
reviewersaswan
bugs1382953
milestone61.0a1
Bug 1382953: Fix permission prompts in about:addons options browsers. r?aswan MozReview-Commit-ID: At5F5cqGSWu
browser/components/extensions/test/browser/browser_ext_user_events.js
browser/modules/ExtensionsUI.jsm
--- 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);