Bug 1317470 Show permission prompts for background webextension updates draft
authorAndrew Swan <aswan@mozilla.com>
Thu, 19 Jan 2017 19:28:08 -0800
changeset 463950 3668e665a020e8c8288249b827812ab22bb82643
parent 463681 a3978751f45108ff1ae002ecebdc0fa23fc52b84
child 463973 1dac6822d01a31309d09dd4ff6e5fce345aea94a
child 464172 a782301b79ef05134633a0e155d1eeb0b5989f4d
push id42236
push useraswan@mozilla.com
push dateFri, 20 Jan 2017 03:34:19 +0000
bugs1317470
milestone53.0a1
Bug 1317470 Show permission prompts for background webextension updates MozReview-Commit-ID: I55ePPFPuuE
browser/base/content/browser-addons.js
browser/base/content/test/general/browser.ini
browser/base/content/test/general/browser_extension_update.js
browser/base/content/test/general/browser_webext_update.json
browser/base/content/test/general/browser_webext_update1.xpi
browser/base/content/test/general/browser_webext_update2.xpi
browser/modules/ExtensionsUI.jsm
toolkit/mozapps/extensions/AddonManager.jsm
--- a/browser/base/content/browser-addons.js
+++ b/browser/base/content/browser-addons.js
@@ -484,33 +484,51 @@ const gExtensionsNotifications = {
     if (!this.initialized) {
       return;
     }
     ExtensionsUI.off("change", this.boundUpdate);
   },
 
   updateAlerts() {
     let sideloaded = ExtensionsUI.sideloaded;
-    if (sideloaded.size == 0) {
+    let updates = ExtensionsUI.updates;
+    if (sideloaded.size + updates.size == 0) {
       gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_ADDONS);
     } else {
       gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_ADDONS,
                                        "addon-alert");
     }
 
     let container = document.getElementById("PanelUI-footer-addons");
 
     while (container.firstChild) {
       container.firstChild.remove();
     }
 
     // Strings below to be properly localized in bug 1316996
     const DEFAULT_EXTENSION_ICON =
       "chrome://mozapps/skin/extensions/extensionGeneric.svg";
     let items = 0;
+    for (let update of updates) {
+      if (++items > 4) {
+        break;
+      }
+      let button = document.createElement("toolbarbutton");
+      button.setAttribute("label", `"${update.addon.name}" requires new permissions`);
+
+      let icon = update.addon.iconURL || DEFAULT_EXTENSION_ICON;
+      button.setAttribute("image", icon);
+
+      button.addEventListener("click", evt => {
+        ExtensionsUI.showUpdate(gBrowser, update);
+      });
+
+      container.appendChild(button);
+    }
+
     for (let addon of sideloaded) {
       if (++items > 4) {
         break;
       }
       let button = document.createElement("toolbarbutton");
       button.setAttribute("label", `"${addon.name}" added to Firefox`);
 
       let icon = addon.iconURL || DEFAULT_EXTENSION_ICON;
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -115,16 +115,19 @@ support-files =
   test_mcb_redirect.sjs
   file_bug1045809_1.html
   file_bug1045809_2.html
   file_csp_block_all_mixedcontent.html
   file_csp_block_all_mixedcontent.js
   file_install_extensions.html
   browser_webext_permissions.xpi
   browser_webext_nopermissions.xpi
+  browser_webext_update1.xpi
+  browser_webext_update2.xpi
+  browser_webext_update.json
   !/image/test/mochitest/blue.png
   !/toolkit/components/passwordmgr/test/browser/form_basic.html
   !/toolkit/components/passwordmgr/test/browser/insecure_test.html
   !/toolkit/components/passwordmgr/test/browser/insecure_test_subframe.html
   !/toolkit/content/tests/browser/common/mockTransfer.js
   !/toolkit/modules/tests/browser/metadata_*.html
   !/toolkit/mozapps/extensions/test/xpinstall/amosigned.xpi
   !/toolkit/mozapps/extensions/test/xpinstall/corrupt.xpi
@@ -301,16 +304,17 @@ skip-if = os == "mac" # decoder doctor i
 [browser_discovery.js]
 [browser_double_close_tab.js]
 [browser_documentnavigation.js]
 [browser_duplicateIDs.js]
 [browser_drag.js]
 skip-if = true # browser_drag.js is disabled, as it needs to be updated for the new behavior from bug 320638.
 [browser_extension_permissions.js]
 [browser_extension_sideloading.js]
+[browser_extension_update.js]
 [browser_favicon_change.js]
 [browser_favicon_change_not_in_document.js]
 [browser_findbarClose.js]
 [browser_focusonkeydown.js]
 [browser_fullscreen-window-open.js]
 tags = fullscreen
 skip-if = os == "linux" # Linux: Intermittent failures - bug 941575.
 [browser_fxaccounts.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_extension_update.js
@@ -0,0 +1,192 @@
+const {AddonManagerPrivate} = Cu.import("resource://gre/modules/AddonManager.jsm", {});
+
+const URL_BASE = "https://example.com/browser/browser/base/content/test/general";
+const ID = "update@tests.mozilla.org";
+
+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 getBadgeStatus() {
+  let menuButton = document.getElementById("PanelUI-menu-button");
+  return menuButton.getAttribute("badge-status");
+}
+
+function promiseUpdateDownloaded(addon) {
+  return new Promise(resolve => {
+    let listener = {
+      onDownloadEnded(install) {
+        if (install.addon.id == addon.id) {
+          AddonManager.removeInstallListener(listener);
+          resolve();
+        }
+      },
+    };
+    AddonManager.addInstallListener(listener);
+  });
+}
+
+function promiseUpgrade(addon) {
+  return new Promise(resolve => {
+    let listener = {
+      onInstallEnded(install, newAddon) {
+        if (newAddon.id == addon.id) {
+          AddonManager.removeInstallListener(listener);
+          resolve(newAddon);
+        }
+      },
+    };
+    AddonManager.addInstallListener(listener);
+  });
+}
+
+add_task(function* () {
+  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`],
+
+    // 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
+    ["extensions.webextPermissionPrompts", true],
+  ]});
+
+  // Install version 1.0 of the test extension
+  let url1 = `${URL_BASE}/browser_webext_update1.xpi`;
+  let install = yield AddonManager.getInstallForURL(url1, null, "application/x-xpinstall");
+  ok(install, "Created install");
+
+  let addon = yield new Promise(resolve => {
+    install.addListener({
+      onInstallEnded(_install, _addon) {
+        resolve(_addon);
+      },
+    });
+    install.install();
+  });
+
+  ok(addon, "Addon was installed");
+  is(getBadgeStatus(), "", "Should not start out with an addon alert badge");
+
+  // Trigger an update check and wait for the update for this addon
+  // to be downloaded.
+  let updatePromise = promiseUpdateDownloaded(addon);
+  AddonManagerPrivate.backgroundUpdateCheck();
+  yield updatePromise;
+
+  is(getBadgeStatus(), "addon-alert", "Should have addon alert badge");
+
+  // Find the menu entry for the update
+  yield PanelUI.show();
+
+  let addons = document.getElementById("PanelUI-footer-addons");
+  is(addons.children.length, 1, "Have a menu entry for the update");
+
+  // Click the menu item
+  let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
+  let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
+  addons.children[0].click();
+
+  // about:addons should load and go to the list of extensions
+  let tab = yield tabPromise;
+  is(tab.linkedBrowser.currentURI.spec, "about:addons");
+
+  const VIEW = "addons://list/extension";
+  yield promiseViewLoaded(tab, VIEW);
+  let win = tab.linkedBrowser.contentWindow;
+  ok(!win.gViewController.isLoading, "about:addons view is fully loaded");
+  is(win.gViewController.currentViewId, VIEW, "about:addons is at extensions list");
+
+  // Wait for the permission prompt and cancel it
+  let panel = yield popupPromise;
+  panel.secondaryButton.click();
+
+  addon = yield AddonManager.getAddonByID(ID);
+  is(addon.version, "1.0", "Should still be running the old version");
+
+  yield BrowserTestUtils.removeTab(tab);
+
+  // Alert badge and hamburger menu items should be gone
+  is(getBadgeStatus(), "", "Addon alert badge should be gone");
+
+  yield PanelUI.show();
+  addons = document.getElementById("PanelUI-footer-addons");
+  is(addons.children.length, 0, "Update menu entries should be gone");
+  yield PanelUI.hide();
+
+  // Re-check for an update
+  updatePromise = promiseUpdateDownloaded(addon);
+  yield AddonManagerPrivate.backgroundUpdateCheck();
+  yield updatePromise;
+
+  is(getBadgeStatus(), "addon-alert", "Should have addon alert badge");
+
+  // Find the menu entry for the update
+  yield PanelUI.show();
+
+  addons = document.getElementById("PanelUI-footer-addons");
+  is(addons.children.length, 1, "Have a menu entry for the update");
+
+  // Click the menu item
+  tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
+  popupPromise = promisePopupNotificationShown("addon-webext-permissions");
+  addons.children[0].click();
+
+  // Wait for about:addons to load
+  tab = yield tabPromise;
+  is(tab.linkedBrowser.currentURI.spec, "about:addons");
+
+  yield promiseViewLoaded(tab, VIEW);
+  win = tab.linkedBrowser.contentWindow;
+  ok(!win.gViewController.isLoading, "about:addons view is fully loaded");
+  is(win.gViewController.currentViewId, VIEW, "about:addons is at extensions list");
+
+  // Wait for the permission prompt and accept it this time
+  updatePromise = promiseUpgrade(addon);
+  panel = yield popupPromise;
+  panel.button.click();
+
+  addon = yield updatePromise;
+  is(addon.version, "2.0", "Should have upgraded to the new version");
+
+  yield BrowserTestUtils.removeTab(tab);
+
+  is(getBadgeStatus(), "", "Addon alert badge should be gone");
+});
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_webext_update.json
@@ -0,0 +1,18 @@
+{
+  "addons": {
+    "update@tests.mozilla.org": {
+      "updates": [
+        {
+          "version": "2.0",
+          "update_link": "https://example.com/browser/browser/base/content/test/general/browser_webext_update2.xpi",
+          "applications": {
+            "gecko": {
+              "strict_min_version": "1",
+              "advisory_max_version": "55.0"
+            }
+          }
+        }
+      ]
+    }
+  }
+}
new file mode 100644
index 0000000000000000000000000000000000000000..8eac9daa01745a2247eb9284b51858546b52f6a1
GIT binary patch
literal 4262
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
new file mode 100644
index 0000000000000000000000000000000000000000..81c7407ba7f17e486ce553318369dc7aaf1b017b
GIT binary patch
literal 4287
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
Bo$det
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -21,19 +21,21 @@ XPCOMUtils.defineLazyPreferenceGetter(th
                                       "extensions.webextPermissionPrompts", false);
 
 const DEFAULT_EXENSION_ICON = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
 this.ExtensionsUI = {
   sideloaded: new Set(),
+  updates: new Set(),
 
   init() {
     Services.obs.addObserver(this, "webextension-permission-prompt", false);
+    Services.obs.addObserver(this, "webextension-update-permissions", false);
 
     this._checkForSideloaded();
   },
 
   _checkForSideloaded() {
     AddonManager.getAllAddons(addons => {
       // Check for any side-loaded addons that the user is allowed
       // to enable.
@@ -55,66 +57,98 @@ this.ExtensionsUI = {
         let win = RecentWindow.getMostRecentBrowserWindow();
         for (let addon of sideloaded) {
           win.openUILinkIn(`about:newaddon?id=${addon.id}`, "tab");
         }
       }
     });
   },
 
-  showSideloaded(browser, addon) {
-    addon.markAsSeen();
-    this.sideloaded.delete(addon);
-    this.emit("change");
-
+  showAddonsManager(browser, info) {
     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 = browser.addTab("about:addons");
     browser.selectedTab = tab;
-    loadPromise.then(win => {
+
+    return loadPromise.then(win => {
       win.loadView("addons://list/extension");
-      let info = {
-        addon,
-        icon: addon.iconURL,
-        type: "sideload",
-      };
-      this.showPermissionsPrompt(browser.selectedBrowser, info).then(answer => {
-        addon.userDisabled = !answer;
-      });
+      return this.showPermissionsPrompt(browser.selectedBrowser, info);
+    });
+  },
+
+  showSideloaded(browser, addon) {
+    addon.markAsSeen();
+    this.sideloaded.delete(addon);
+    this.emit("change");
+
+    let info = {
+      addon,
+      permissions: addon.userPermissions,
+      icon: addon.iconURL,
+      type: "sideload",
+    };
+    this.showAddonsManager(browser, info).then(answer => {
+      addon.userDisabled = !answer;
+    });
+  },
+
+  showUpdate(browser, info) {
+    info.type = "update";
+    this.showAddonsManager(browser, info).then(answer => {
+      if (answer) {
+        info.resolve();
+      } else {
+        info.reject();
+      }
+      // At the moment, this prompt will re-appear next time we do an update
+      // check.  See bug 1332360 for proposal to avoid this.
+      this.updates.delete(info);
+      this.emit("change");
     });
   },
 
   observe(subject, topic, data) {
     if (topic == "webextension-permission-prompt") {
       let {target, info} = subject.wrappedJSObject;
 
       // 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);
       if (progressNotification) {
         progressNotification.remove();
       }
 
-      this.showPermissionsPrompt(target, info).then(answer => {
+      let reply = answer => {
         Services.obs.notifyObservers(subject, "webextension-permission-response",
                                      JSON.stringify(answer));
-      });
+      };
+
+      let perms = info.addon.userPermissions;
+      if (!perms) {
+        reply(true);
+      } else {
+        info.permissions = perms;
+        this.showPermissionsPrompt(target, info).then(reply);
+      }
+    } else if (topic == "webextension-update-permissions") {
+      this.updates.add(subject.wrappedJSObject);
+      this.emit("change");
     }
   },
 
   showPermissionsPrompt(target, info) {
-    let perms = info.addon.userPermissions;
+    let perms = info.permissions;
     if (!perms) {
       return Promise.resolve();
     }
 
     let win = target.ownerGlobal;
 
     let name = info.addon.name;
     if (name.length > 50) {
@@ -142,16 +176,21 @@ this.ExtensionsUI = {
 
     if (info.type == "sideload") {
       header = `${name} added`;
       text = "Another program on your computer installed an add-on that may affect your browser.  Please review this add-on's permission requests and choose to Enable or Disable";
       acceptText = "Enable";
       acceptKey = "E";
       cancelText = "Disable";
       cancelKey = "D";
+    } else if (info.type == "update") {
+      header = "";
+      text = `${name} has been updated.  You must approve new permissions before the updated version will install.`;
+      acceptText = "Update";
+      acceptKey = "U";
     }
 
     let formatPermission = perm => {
       try {
         // return bundle.getString(`webextPerms.description.${perm}`);
         return `localized description of permission ${perm}`;
       } catch (err) {
         // return bundle.getFormattedString("webextPerms.description.unknown",
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -1371,16 +1371,45 @@ var AddonManagerInternal = {
         return aMatch;
       }
     });
 
     // escape() does not properly encode + symbols in any embedded FVF strings.
     return uri.replace(/\+/g, "%2B");
   },
 
+  _updatePromptHandler(info) {
+    let oldPerms = info.existingAddon.userPermissions || {hosts: [], permissions: []};
+    let newPerms = info.addon.userPermissions;
+
+    // See bug 1331769: should we do something more complicated to
+    // compare host permissions?
+    // e.g., if we go from <all_urls> to a specific host or from
+    // a *.domain.com to specific-host.domain.com that's actually a
+    // drop in permissions but the simple test below will cause a prompt.
+    let difference = {
+      hosts: newPerms.hosts.filter(perm => !oldPerms.hosts.includes(perm)),
+      permissions: newPerms.permissions.filter(perm => !oldPerms.permissions.includes(perm)),
+    };
+
+    // If there are no new permissions, just go ahead with the update
+    if (difference.hosts.length == 0 && difference.permissions.length == 0) {
+      return Promise.resolve();
+    }
+
+    return new Promise((resolve, reject) => {
+      let subject = {wrappedJSObject: {
+        addon: info.addon,
+        permissions: difference,
+        resolve, reject
+      }};
+      Services.obs.notifyObservers(subject, "webextension-update-permissions", null);
+    });
+  },
+
   /**
    * Performs a background update check by starting an update for all add-ons
    * that can be updated.
    * @return Promise{null} Resolves when the background update check is complete
    *                       (the resulting addon installations may still be in progress).
    */
   backgroundUpdateCheck() {
     if (!gStarted)
@@ -1425,16 +1454,19 @@ var AddonManagerInternal = {
                 // Start installing updates when the add-on can be updated and
                 // background updates should be applied.
                 logger.debug("Found update for add-on ${id}", aAddon);
                 if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE &&
                     AddonManager.shouldAutoUpdate(aAddon)) {
                   // XXX we really should resolve when this install is done,
                   // not when update-available check completes, no?
                   logger.debug(`Starting upgrade install of ${aAddon.id}`);
+                  if (WEBEXT_PERMISSION_PROMPTS) {
+                    aInstall.promptHandler = (...args) => AddonManagerInternal._updatePromptHandler(...args);
+                  }
                   aInstall.install();
                 }
               },
 
               onUpdateFinished: aAddon => { logger.debug("onUpdateFinished for ${id}", aAddon); resolve(); }
             }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
           }));
         }