Bug 1340586 Part 2 Move common code to head.js draft
authorAndrew Swan <aswan@mozilla.com>
Mon, 27 Feb 2017 15:57:09 -0800
changeset 490267 80ef1d1bfbd78921eb01bd4e41925eaf92a47811
parent 490266 d2c3310041e784b80b3975e695c14b99c703d613
child 490268 30187c04e172c7090fe2c05f37427f6f2c9b46e4
push id47045
push useraswan@mozilla.com
push dateMon, 27 Feb 2017 23:59:12 +0000
bugs1340586
milestone54.0a1
Bug 1340586 Part 2 Move common code to head.js MozReview-Commit-ID: GhHXjx5d9q3
browser/base/content/test/webextensions/browser.ini
browser/base/content/test/webextensions/browser_extension_permissions.js
browser/base/content/test/webextensions/browser_extension_sideloading.js
browser/base/content/test/webextensions/browser_extension_update_background.js
browser/base/content/test/webextensions/browser_extension_update_interactive.js
browser/base/content/test/webextensions/head.js
--- a/browser/base/content/test/webextensions/browser.ini
+++ b/browser/base/content/test/webextensions/browser.ini
@@ -1,10 +1,11 @@
 [DEFAULT]
 support-files =
+  head.js
   file_install_extensions.html
   browser_legacy.xpi
   browser_legacy_webext.xpi
   browser_webext_permissions.xpi
   browser_webext_nopermissions.xpi
   browser_webext_update1.xpi
   browser_webext_update2.xpi
   browser_webext_update_icon1.xpi
--- a/browser/base/content/test/webextensions/browser_extension_permissions.js
+++ b/browser/base/content/test/webextensions/browser_extension_permissions.js
@@ -1,58 +1,30 @@
 "use strict";
 
 // See but 1340586 for proposal to reorganize permissions tests to
 // get rid of this...
 requestLongerTimeout(2);
 
-const BASE = getRootDirectory(gTestPath)
-  .replace("chrome://mochitests/content/", "https://example.com/");
-
 const INSTALL_PAGE = `${BASE}/file_install_extensions.html`;
 const PERMS_XPI = "browser_webext_permissions.xpi";
 const NO_PERMS_XPI = "browser_webext_nopermissions.xpi";
 const ID = "permissions@test.mozilla.org";
 
 Services.perms.add(makeURI("https://example.com/"), "install",
                    Services.perms.ALLOW_ACTION);
 
-registerCleanupFunction(async function() {
-  let addon = await AddonManager.getAddonByID(ID);
-  if (addon) {
-    ok(false, `Addon ${ID} was still installed at the end of the test`);
-    addon.uninstall();
-  }
-});
-
 function isDefaultIcon(icon) {
   // These are basically the same icon, but code within webextensions
   // generates references to the former and generic add-ons manager code
   // generates referces to the latter.
   return (icon == "chrome://browser/content/extension.svg" ||
           icon == "chrome://mozapps/skin/extensions/extensionGeneric.svg");
 }
 
-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);
-  });
-}
-
 function checkNotification(panel, filename) {
   let icon = panel.getAttribute("icon");
 
   let ul = document.getElementById("addon-webext-perm-list");
   let header = document.getElementById("addon-webext-perm-intro");
 
   if (filename == PERMS_XPI) {
     // The icon should come from the extension, don't bother with the precise
--- a/browser/base/content/test/webextensions/browser_extension_sideloading.js
+++ b/browser/base/content/test/webextensions/browser_extension_sideloading.js
@@ -67,41 +67,24 @@ class MockProvider {
     let addons = [];
     if (!types || types.includes("extension")) {
       addons = [...this.addons];
     }
     callback(addons);
   }
 }
 
-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);
-  });
-}
-
 function promiseSetDisabled(addon) {
   return new Promise(resolve => {
     setCallbacks.set(addon, resolve);
   });
 }
 
+let cleanup;
+
 add_task(function* () {
   // XXX remove this when prompts are enabled by default
   yield SpecialPowers.pushPrefEnv({set: [
     ["extensions.webextPermissionPrompts", true],
   ]});
 
   // ICON_URL wouldn't ever appear as an actual webextension icon, but
   // we're just mocking out the addon here, so all we care about is that
@@ -163,24 +146,25 @@ add_task(function* () {
   let provider = new MockProvider(mock1, mock2, mock3, mock4);
   AddonManagerPrivate.registerProvider(provider, [{
     id: "extension",
     name: "Extensions",
     uiPriority: 4000,
     flags: AddonManager.TYPE_UI_VIEW_LIST |
            AddonManager.TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL,
   }]);
-  registerCleanupFunction(function*() {
+
+  testCleanup = async function() {
     AddonManagerPrivate.unregisterProvider(provider);
 
     // clear out ExtensionsUI state about sideloaded extensions so
     // subsequent tests don't get confused.
     ExtensionsUI.sideloaded.clear();
     ExtensionsUI.emit("change");
-  });
+  };
 
   // Navigate away from the starting page to force about:addons to load
   // in a new tab during the tests below.
   gBrowser.selectedBrowser.loadURI("about:robots");
   yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
 
   registerCleanupFunction(function*() {
     // Return to about:blank when we're done
--- a/browser/base/content/test/webextensions/browser_extension_update_background.js
+++ b/browser/base/content/test/webextensions/browser_extension_update_background.js
@@ -1,41 +1,15 @@
 const {AddonManagerPrivate} = Cu.import("resource://gre/modules/AddonManager.jsm", {});
 
-const URL_BASE = "https://example.com/browser/browser/base/content/test/general";
 const ID = "update2@tests.mozilla.org";
 const ID_ICON = "update_icon2@tests.mozilla.org";
 const ID_PERMS = "update_perms@tests.mozilla.org";
 const ID_LEGACY = "legacy_update@tests.mozilla.org";
 
-registerCleanupFunction(async function() {
-  for (let id of [ID, ID_ICON, ID_PERMS, ID_LEGACY]) {
-    let addon = await AddonManager.getAddonByID(id);
-    if (addon) {
-      ok(false, `Addon ${id} was still installed at the end of the test`);
-      addon.uninstall();
-    }
-  }
-});
-
-function promiseInstallAddon(url) {
-  return AddonManager.getInstallForURL(url, null, "application/x-xpinstall")
-                     .then(install => {
-                       ok(install, "Created install");
-                       return new Promise(resolve => {
-                         install.addListener({
-                           onInstallEnded(_install, addon) {
-                             resolve(addon);
-                           },
-                         });
-                         install.install();
-                       });
-                     });
-}
-
 function promiseViewLoaded(tab, viewid) {
   let win = tab.linkedBrowser.contentWindow;
   if (win.gViewController && !win.gViewController.isLoading &&
       win.gViewController.currentViewId == viewid) {
      return Promise.resolve();
   }
 
   return new Promise(resolve => {
@@ -45,51 +19,21 @@ function promiseViewLoaded(tab, viewid) 
       }
       win.document.removeEventListener("ViewChanged", listener);
       resolve();
     }
     win.document.addEventListener("ViewChanged", listener);
   });
 }
 
-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);
-  });
-}
-
 function getBadgeStatus() {
   let menuButton = document.getElementById("PanelUI-menu-button");
   return menuButton.getAttribute("badge-status");
 }
 
-function promiseInstallEvent(addon, event) {
-  return new Promise(resolve => {
-    let listener = {};
-    listener[event] = (install, ...args) => {
-      if (install.addon.id == addon.id) {
-        AddonManager.removeInstallListener(listener);
-        resolve(...args);
-      }
-    };
-    AddonManager.addInstallListener(listener);
-  });
-}
-
 // Set some prefs that apply to all the tests in this file
 add_task(function* setup() {
   yield SpecialPowers.pushPrefEnv({set: [
     // We don't have pre-pinned certificates for the local mochitest server
     ["extensions.install.requireBuiltInCerts", false],
     ["extensions.update.requireBuiltInCerts", false],
 
     // XXX remove this when prompts are enabled by default
@@ -110,17 +54,17 @@ add_task(function* setup() {
 
 // Helper function to test background updates.
 function* backgroundUpdateTest(url, id, checkIconFn) {
   yield SpecialPowers.pushPrefEnv({set: [
     // Turn on background updates
     ["extensions.update.enabled", true],
 
     // Point updates to the local mochitest server
-    ["extensions.update.background.url", `${URL_BASE}/browser_webext_update.json`],
+    ["extensions.update.background.url", `${BASE}/browser_webext_update.json`],
   ]});
 
   // Install version 1.0 of the test extension
   let addon = yield promiseInstallAddon(url);
 
   ok(addon, "Addon was installed");
   is(getBadgeStatus(), "", "Should not start out with an addon alert badge");
 
@@ -223,38 +167,38 @@ function* backgroundUpdateTest(url, id, 
   yield SpecialPowers.popPrefEnv();
 }
 
 function checkDefaultIcon(icon) {
   is(icon, "chrome://mozapps/skin/extensions/extensionGeneric.svg",
      "Popup has the default extension icon");
 }
 
-add_task(() => backgroundUpdateTest(`${URL_BASE}/browser_webext_update1.xpi`,
+add_task(() => backgroundUpdateTest(`${BASE}/browser_webext_update1.xpi`,
                                     ID, checkDefaultIcon));
 
 function checkNonDefaultIcon(icon) {
   // The icon should come from the extension, don't bother with the precise
   // path, just make sure we've got a jar url pointing to the right path
   // inside the jar.
   ok(icon.startsWith("jar:file://"), "Icon is a jar url");
   ok(icon.endsWith("/icon.png"), "Icon is icon.png inside a jar");
 }
 
-add_task(() => backgroundUpdateTest(`${URL_BASE}/browser_webext_update_icon1.xpi`,
+add_task(() => backgroundUpdateTest(`${BASE}/browser_webext_update_icon1.xpi`,
                                     ID_ICON, checkNonDefaultIcon));
 
 // Helper function to test an upgrade that should not show a prompt
 async function testNoPrompt(origUrl, id) {
   await SpecialPowers.pushPrefEnv({set: [
     // Turn on background updates
     ["extensions.update.enabled", true],
 
     // Point updates to the local mochitest server
-    ["extensions.update.background.url", `${URL_BASE}/browser_webext_update.json`],
+    ["extensions.update.background.url", `${BASE}/browser_webext_update.json`],
   ]});
 
   // Install version 1.0 of the test extension
   let addon = await promiseInstallAddon(origUrl);
 
   ok(addon, "Addon was installed");
 
   let sawPopup = false;
@@ -281,16 +225,16 @@ async function testNoPrompt(origUrl, id)
   is(addon.version, "2.0", "Update should have applied");
 
   addon.uninstall();
   await SpecialPowers.popPrefEnv();
 }
 
 // Test that an update that adds new non-promptable permissions is just
 // applied without showing a notification dialog.
-add_task(() => testNoPrompt(`${URL_BASE}/browser_webext_update_perms1.xpi`,
+add_task(() => testNoPrompt(`${BASE}/browser_webext_update_perms1.xpi`,
                             ID_PERMS));
 
 // Test that an update from a legacy extension to a webextension
 // doesn't show a prompt even when the webextension uses
 // promptable required permissions.
-add_task(() => testNoPrompt(`${URL_BASE}/browser_legacy.xpi`, ID_LEGACY));
+add_task(() => testNoPrompt(`${BASE}/browser_legacy.xpi`, ID_LEGACY));
 
--- a/browser/base/content/test/webextensions/browser_extension_update_interactive.js
+++ b/browser/base/content/test/webextensions/browser_extension_update_interactive.js
@@ -1,88 +1,13 @@
 const {AddonManagerPrivate} = Cu.import("resource://gre/modules/AddonManager.jsm", {});
 
-const URL_BASE = "https://example.com/browser/browser/base/content/test/webextensions";
 const ID = "update2@tests.mozilla.org";
 const ID_LEGACY = "legacy_update@tests.mozilla.org";
 
-registerCleanupFunction(async function() {
-  for (let id of [ID, ID_LEGACY]) {
-    let addon = await AddonManager.getAddonByID(id);
-    if (addon) {
-      ok(false, `Addon ${id} was still installed at the end of the test`);
-      addon.uninstall();
-    }
-  }
-});
-
-function promiseInstallAddon(url) {
-  return AddonManager.getInstallForURL(url, null, "application/x-xpinstall")
-                     .then(install => {
-                       ok(install, "Created install");
-                       return new Promise(resolve => {
-                         install.addListener({
-                           onInstallEnded(_install, addon) {
-                             resolve(addon);
-                           },
-                         });
-                         install.install();
-                       });
-                     });
-}
-
-function promiseViewLoaded(tab, viewid) {
-  let win = tab.linkedBrowser.contentWindow;
-  if (win.gViewController && !win.gViewController.isLoading &&
-      win.gViewController.currentViewId == viewid) {
-     return Promise.resolve();
-  }
-
-  return new Promise(resolve => {
-    function listener() {
-      if (win.gViewController.currentViewId != viewid) {
-        return;
-      }
-      win.document.removeEventListener("ViewChanged", listener);
-      resolve();
-    }
-    win.document.addEventListener("ViewChanged", listener);
-  });
-}
-
-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);
-  });
-}
-
-function promiseInstallEvent(addon, event) {
-  return new Promise(resolve => {
-    let listener = {};
-    listener[event] = (install, ...args) => {
-      if (install.addon.id == addon.id) {
-        AddonManager.removeInstallListener(listener);
-        resolve(...args);
-      }
-    };
-    AddonManager.addInstallListener(listener);
-  });
-}
-
 // Set some prefs that apply to all the tests in this file
 add_task(function* setup() {
   yield SpecialPowers.pushPrefEnv({set: [
     // We don't have pre-pinned certificates for the local mochitest server
     ["extensions.install.requireBuiltInCerts", false],
     ["extensions.update.requireBuiltInCerts", false],
 
     // XXX remove this when prompts are enabled by default
@@ -94,17 +19,17 @@ add_task(function* setup() {
 // `checkFn` is a callable that triggers a check for updates.
 // `autoUpdate` specifies whether the test should be run with
 // updates applied automatically or not.
 function* interactiveUpdateTest(autoUpdate, checkFn) {
   yield SpecialPowers.pushPrefEnv({set: [
     ["extensions.update.autoUpdateDefault", autoUpdate],
 
     // Point updates to the local mochitest server
-    ["extensions.update.url", `${URL_BASE}/browser_webext_update.json`],
+    ["extensions.update.url", `${BASE}/browser_webext_update.json`],
   ]});
 
   // Trigger an update check, manually applying the update if we're testing
   // without auto-update.
   function* triggerUpdate(win, addon) {
     let manualUpdatePromise;
     if (!autoUpdate) {
       manualUpdatePromise = new Promise(resolve => {
@@ -128,39 +53,27 @@ function* interactiveUpdateTest(autoUpda
       // Make sure we have XBL bindings
       list.clientHeight;
 
       let item = list.children.find(_item => _item.value == ID);
       EventUtils.synthesizeMouseAtCenter(item._updateBtn, {}, win);
     }
   }
 
+  // Navigate away from the starting page to force about:addons to load
+  // in a new tab during the tests below.
+  gBrowser.selectedBrowser.loadURI("about:robots");
+  yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
   // Install version 1.0 of the test extension
-  let addon = yield promiseInstallAddon(`${URL_BASE}/browser_webext_update1.xpi`);
+  let addon = yield promiseInstallAddon(`${BASE}/browser_webext_update1.xpi`);
   ok(addon, "Addon was installed");
   is(addon.version, "1.0", "Version 1 of the addon is installed");
 
-  // Open add-ons manager and navigate to extensions list
-  let loadPromise = new Promise(resolve => {
-    let listener = (subject, topic) => {
-      if (subject.location.href == "about:addons") {
-        Services.obs.removeObserver(listener, topic);
-        resolve(subject);
-      }
-    };
-    Services.obs.addObserver(listener, "EM-loaded", false);
-  });
-  let tab = gBrowser.addTab("about:addons");
-  gBrowser.selectedTab = tab;
-  let win = yield loadPromise;
-
-  const VIEW = "addons://list/extension";
-  let viewPromise = promiseViewLoaded(tab, VIEW);
-  win.loadView(VIEW);
-  yield viewPromise;
+  let win = yield BrowserOpenAddonsMgr("addons://list/extension");
 
   // Trigger an update check
   let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
   yield triggerUpdate(win, addon);
   let panel = yield popupPromise;
 
   // Click the cancel button, wait to see the cancel event
   let cancelPromise = promiseInstallEvent(addon, "onInstallCancelled");
@@ -177,17 +90,17 @@ function* interactiveUpdateTest(autoUpda
   // This time, accept the upgrade
   let updatePromise = promiseInstallEvent(addon, "onInstallEnded");
   panel = yield popupPromise;
   panel.button.click();
 
   addon = yield updatePromise;
   is(addon.version, "2.0", "Should have upgraded");
 
-  yield BrowserTestUtils.removeTab(tab);
+  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
   addon.uninstall();
   yield SpecialPowers.popPrefEnv();
 }
 
 // Invoke the "Check for Updates" menu item
 function checkAll(win) {
   win.gViewController.doCommand("cmd_findAllUpdates");
 }
@@ -206,25 +119,25 @@ function checkOne(win, addon) {
 add_task(() => interactiveUpdateTest(true, checkOne));
 add_task(() => interactiveUpdateTest(false, checkOne));
 
 // Check that an update from a legacy extension to a webextensino
 // does not display a prompt
 add_task(async function() {
   await SpecialPowers.pushPrefEnv({set: [
     // Point updates to the local mochitest server
-    ["extensions.update.url", `${URL_BASE}/browser_webext_update.json`],
+    ["extensions.update.url", `${BASE}/browser_webext_update.json`],
   ]});
 
   // Navigate away to ensure that BrowserOpenAddonMgr() opens a new tab
   gBrowser.selectedBrowser.loadURI("about:robots");
   await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
 
   // Install initial version of the test extension
-  let addon = await promiseInstallAddon(`${URL_BASE}/browser_legacy.xpi`);
+  let addon = await promiseInstallAddon(`${BASE}/browser_legacy.xpi`);
   ok(addon, "Addon was installed");
   is(addon.version, "1.1", "Version 1 of the addon is installed");
 
   // Go to Extensions in about:addons
   let win = await BrowserOpenAddonsMgr("addons://list/extension");
 
   let sawPopup = false;
   PopupNotifications.panel.addEventListener("popupshown",
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/webextensions/head.js
@@ -0,0 +1,282 @@
+
+const BASE = getRootDirectory(gTestPath)
+  .replace("chrome://mochitests/content/", "https://example.com/");
+
+/**
+ * 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);
+  });
+}
+
+/**
+ * Wait for a specific install event to fire for a given addon
+ *
+ * @param {AddonWrapper} addon
+ *        The addon to watch for an event on
+ * @param {string}
+ *        The name of the event to watch for (e.g., onInstallEnded)
+ *
+ * @returns {Promise}
+ *          Resolves when the event triggers with the first argument
+ *          to the event handler as the resolution value.
+ */
+function promiseInstallEvent(addon, event) {
+  return new Promise(resolve => {
+    let listener = {};
+    listener[event] = (install, arg) => {
+      if (install.addon.id == addon.id) {
+        AddonManager.removeInstallListener(listener);
+        resolve(arg);
+      }
+    };
+    AddonManager.addInstallListener(listener);
+  });
+}
+
+/**
+ * Install an (xpi packaged) extension
+ *
+ * @param {string} url
+ *        URL of the .xpi file to install
+ *
+ * @returns {Promise}
+ *          Resolves when the extension has been installed with the Addon
+ *          object as the resolution value.
+ */
+function promiseInstallAddon(url) {
+  return AddonManager.getInstallForURL(url, null, "application/x-xpinstall")
+                     .then(install => {
+                       ok(install, "Created install");
+                       return new Promise(resolve => {
+                         install.addListener({
+                           onInstallEnded(_install, addon) {
+                             resolve(addon);
+                           },
+                         });
+                         install.install();
+                       });
+                     });
+}
+
+function isDefaultIcon(icon) {
+  // These are basically the same icon, but code within webextensions
+  // generates references to the former and generic add-ons manager code
+  // generates referces to the latter.
+  return (icon == "chrome://browser/content/extension.svg" ||
+          icon == "chrome://mozapps/skin/extensions/extensionGeneric.svg");
+}
+
+function is_hidden(element) {
+  var style = element.ownerGlobal.getComputedStyle(element);
+  if (style.display == "none")
+    return true;
+  if (style.visibility != "visible")
+    return true;
+  if (style.display == "-moz-popup")
+    return ["hiding", "closed"].indexOf(element.state) != -1;
+
+  // Hiding a parent element will hide all its children
+  if (element.parentNode != element.ownerDocument)
+    return is_hidden(element.parentNode);
+
+  return false;
+}
+
+function is_visible(element) {
+  var style = element.ownerGlobal.getComputedStyle(element);
+  if (style.display == "none")
+    return false;
+  if (style.visibility != "visible")
+    return false;
+  if (style.display == "-moz-popup" && element.state != "open")
+    return false;
+
+  // Hiding a parent element will hide all its children
+  if (element.parentNode != element.ownerDocument)
+    return is_visible(element.parentNode);
+
+  return true;
+}
+
+/**
+ * Test that install-time permission prompts work for a given
+ * installation method.
+ *
+ * @param {Function} installFn
+ *        Callable that takes the name of an xpi file to install and
+ *        starts to install it.  Should return a Promise that resolves
+ *        when the install is finished or rejects if the install is canceled.
+ *
+ * @returns {Promise}
+ */
+async function testInstallMethod(installFn) {
+  const PERMS_XPI = "browser_webext_permissions.xpi";
+  const NO_PERMS_XPI = "browser_webext_nopermissions.xpi";
+  const ID = "permissions@test.mozilla.org";
+
+  await SpecialPowers.pushPrefEnv({set: [
+    ["extensions.webapi.testing", true],
+    ["extensions.install.requireBuiltInCerts", false],
+
+    // XXX remove this when prompts are enabled by default
+    ["extensions.webextPermissionPrompts", true],
+  ]});
+
+  let testURI = makeURI("https://example.com/");
+  Services.perms.add(testURI, "install", Services.perms.ALLOW_ACTION);
+  registerCleanupFunction(() => Services.perms.remove(testURI, "install"));
+
+  async function runOnce(filename, cancel) {
+    let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+    let installPromise = new Promise(resolve => {
+      let listener = {
+        onDownloadCancelled() {
+          AddonManager.removeInstallListener(listener);
+          resolve(false);
+        },
+
+        onDownloadFailed() {
+          AddonManager.removeInstallListener(listener);
+          resolve(false);
+        },
+
+        onInstallCancelled() {
+          AddonManager.removeInstallListener(listener);
+          resolve(false);
+        },
+
+        onInstallEnded() {
+          AddonManager.removeInstallListener(listener);
+          resolve(true);
+        },
+
+        onInstallFailed() {
+          AddonManager.removeInstallListener(listener);
+          resolve(false);
+        },
+      };
+      AddonManager.addInstallListener(listener);
+    });
+
+    let installMethodPromise = installFn(filename);
+
+    let panel = await promisePopupNotificationShown("addon-webext-permissions");
+    let icon = panel.getAttribute("icon");
+
+    let ul = document.getElementById("addon-webext-perm-list");
+    let header = document.getElementById("addon-webext-perm-intro");
+
+    if (filename == PERMS_XPI) {
+      // The icon should come from the extension, don't bother with the precise
+      // path, just make sure we've got a jar url pointing to the right path
+      // inside the jar.
+      ok(icon.startsWith("jar:file://"), "Icon is a jar url");
+      ok(icon.endsWith("/icon.png"), "Icon is icon.png inside a jar");
+
+      is(header.getAttribute("hidden"), "", "Permission list header is visible");
+      is(ul.childElementCount, 5, "Permissions list has 5 entries");
+      // Real checking of the contents here is deferred until bug 1316996 lands
+    } else if (filename == NO_PERMS_XPI) {
+      // This extension has no icon, it should have the default
+      ok(isDefaultIcon(icon), "Icon is the default extension icon");
+
+      is(header.getAttribute("hidden"), "true", "Permission list header is hidden");
+      is(ul.childElementCount, 0, "Permissions list has 0 entries");
+    }
+
+    if (cancel) {
+      panel.secondaryButton.click();
+      try {
+        await installMethodPromise;
+      } catch (err) {}
+    } else {
+      // Look for post-install notification
+      let postInstallPromise = promisePopupNotificationShown("addon-installed");
+      panel.button.click();
+
+      // Press OK on the post-install notification
+      panel = await postInstallPromise;
+      panel.button.click();
+
+      await installMethodPromise;
+    }
+
+    let result = await installPromise;
+    let addon = await AddonManager.getAddonByID(ID);
+    if (cancel) {
+      ok(!result, "Installation was cancelled");
+      is(addon, null, "Extension is not installed");
+    } else {
+      ok(result, "Installation completed");
+      isnot(addon, null, "Extension is installed");
+      addon.uninstall();
+    }
+
+    await BrowserTestUtils.removeTab(tab);
+  }
+
+  // A few different tests for each installation method:
+  // 1. Start installation of an extension that requests no permissions,
+  //    verify the notification contents, then cancel the install
+  await runOnce(NO_PERMS_XPI, true);
+
+  // 2. Same as #1 but with an extension that requests some permissions.
+  await runOnce(PERMS_XPI, true);
+
+  // 3. Repeat with the same extension from step 2 but this time,
+  //    accept the permissions to install the extension.  (Then uninstall
+  //    the extension to clean up.)
+  await runOnce(PERMS_XPI, false);
+
+  await SpecialPowers.popPrefEnv();
+}
+
+// The tests in this directory install a bunch of extensions but they
+// need to uninstall them before exiting, as a stray leftover extension
+// after one test can foul up subsequent tests.
+// So, add a task to run before any tests that grabs a list of all the
+// add-ons that are pre-installed in the test environment and then checks
+// the list of installed add-ons at the end of the test to make sure no
+// new add-ons have been added.
+// Individual tests can store a cleanup function in the testCleanup global
+// to ensure it gets called before the final check is performed.
+let testCleanup;
+add_task(async function() {
+  let addons = await AddonManager.getAllAddons();
+  let existingAddons = new Set(addons.map(a => a.id));
+
+  registerCleanupFunction(async function() {
+    if (testCleanup) {
+      await testCleanup();
+      testCleanup = null;
+    }
+
+    for (let addon of await AddonManager.getAllAddons()) {
+      if (!existingAddons.has(addon.id)) {
+        ok(false, `Addon ${addon.id} was left installed at the end of the test`);
+        addon.uninstall();
+      }
+    }
+  });
+});