Bug 1382953: Fix permission prompts in about:addons options browsers. r=aswan
authorKris Maglione <maglione.k@gmail.com>
Sat, 07 Apr 2018 20:38:21 -0700
changeset 412787 791f0155deb4500a2acbea31711ed1485df2b79f
parent 412786 9cba61d601f190e32996c481c08461daa8833d1a
child 412788 bc2f1762365b6d43b1aeb2056ebe5384bb0522cf
push id33818
push userapavel@mozilla.com
push dateWed, 11 Apr 2018 14:36:40 +0000
treeherdermozilla-central@cfe6399e142c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan
bugs1382953
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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,23 @@ 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);
+  extension.sendMessage("openOptionsPage");
+  promisePopupNotificationShown("addon-webext-permissions").then(panel => {
+    panel.button.click();
+  });
+  await check("options page link click");
+
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+  await BrowserTestUtils.removeTab(tab);
 
   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;
@@ -240,17 +247,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") {
@@ -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);