Bug 1382953 - Fix permission prompts in about:addons options browsers. r=aswan, a=jcristau
authorKris Maglione <maglione.k@gmail.com>
Tue, 22 May 2018 18:42:02 -0400
changeset 449326 62bc8ca444f93c09e9e87c4910a7910596f8010e
parent 449325 f2165acbf563b92e790843ef3005cfbddad00cfc
child 449327 4df35eb4d62e1711090ec0e9538c25a413798108
push id41
push userryanvm@gmail.com
push dateTue, 22 May 2018 22:53:43 +0000
treeherdermozilla-esr60@5962d597aa94 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan, jcristau
bugs1382953
milestone60.0.2
Bug 1382953 - Fix permission prompts in about:addons options browsers. r=aswan, a=jcristau MozReview-Commit-ID: At5F5cqGSWu
browser/components/extensions/test/browser/browser-common.ini
browser/components/extensions/test/browser/browser_ext_user_events.js
browser/modules/ExtensionsUI.jsm
--- 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]
--- 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);