Bug 1317470 Show permission prompts for background webextension updates r=florian
authorAndrew Swan <aswan@mozilla.com>
Thu, 19 Jan 2017 19:28:08 -0800
changeset 375247 fc024a9abc9620b61391ad2002a4e6bed78891d5
parent 375246 861f01ee0e6e7ece0f78befb91bffed400d575ca
child 375248 d4d776c90a151cbd12f2f45462003576d0dacc65
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersflorian
bugs1317470
milestone53.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 1317470 Show permission prompts for background webextension updates r=florian 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
@@ -497,33 +497,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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..8eac9daa01745a2247eb9284b51858546b52f6a1
GIT binary patch
literal 4262
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..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);
           }));
         }