Bug 1334096 Show permissions prompts when a sideloaded extension is enabled r=mossop
authorAndrew Swan <aswan@mozilla.com>
Wed, 15 Feb 2017 15:40:56 -0800
changeset 344548 fae64acfaddc96696388fbbddae07dda56d34035
parent 344547 ddc507c5fa844619f0f8cb3aea6a3fe0c7b4d5bc
child 344549 0a0ad749ef253283e2288b5bcac8ea7a06b16eed
push id31413
push usercbook@mozilla.com
push dateFri, 24 Feb 2017 10:18:46 +0000
treeherdermozilla-central@c7935d540027 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmossop
bugs1334096
milestone54.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 1334096 Show permissions prompts when a sideloaded extension is enabled r=mossop MozReview-Commit-ID: 1yXgkBg6W1p
browser/base/content/test/general/browser_extension_sideloading.js
browser/modules/ExtensionsUI.jsm
toolkit/mozapps/extensions/content/extensions.js
toolkit/mozapps/extensions/content/extensions.xml
--- a/browser/base/content/test/general/browser_extension_sideloading.js
+++ b/browser/base/content/test/general/browser_extension_sideloading.js
@@ -26,16 +26,17 @@ class MockAddon {
   }
 
   get userDisabled() {
     return this._userDisabled;
   }
 
   set userDisabled(val) {
     this._userDisabled = val;
+    AddonManagerPrivate.callAddonListeners(val ? "onDisabled" : "onEnabled", this);
     let fn = setCallbacks.get(this);
     if (fn) {
       setCallbacks.delete(this);
       fn(val);
     }
     return val;
   }
 
@@ -128,17 +129,43 @@ add_task(function* () {
     userDisabled: true,
     seen: false,
     userPermissions: {
       permissions: [],
       hosts: [],
     },
   });
 
-  let provider = new MockProvider(mock1, mock2);
+  const ID3 = "addon3@tests.mozilla.org";
+  let mock3 = new MockAddon({
+    id: ID3,
+    name: "Test 3",
+    isWebExtension: true,
+    userDisabled: true,
+    seen: false,
+    userPermissions: {
+      permissions: [],
+      hosts: ["<all_urls>"],
+    }
+  });
+
+  const ID4 = "addon4@tests.mozilla.org";
+  let mock4 = new MockAddon({
+    id: ID4,
+    name: "Test 4",
+    isWebExtension: true,
+    userDisabled: true,
+    seen: false,
+    userPermissions: {
+      permissions: [],
+      hosts: ["<all_urls>"],
+    }
+  });
+
+  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*() {
@@ -173,17 +200,17 @@ add_task(function* () {
   // Check for the addons badge on the hamburger menu
   let menuButton = document.getElementById("PanelUI-menu-button");
   is(menuButton.getAttribute("badge-status"), "addon-alert", "Should have addon alert badge");
 
   // Find the menu entries for sideloaded extensions
   yield PanelUI.show();
 
   let addons = document.getElementById("PanelUI-footer-addons");
-  is(addons.children.length, 2, "Have 2 menu entries for sideloaded extensions");
+  is(addons.children.length, 4, "Have 4 menu entries for sideloaded extensions");
 
   // Click the first sideloaded extension
   let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
   addons.children[0].click();
 
   // When we get the permissions prompt, we should be at the extensions
   // list in about:addons
   let panel = yield popupPromise;
@@ -199,50 +226,119 @@ add_task(function* () {
   is(icon, ICON_URL, "Permissions notification has the addon icon");
 
   let disablePromise = promiseSetDisabled(mock1);
   panel.secondaryButton.click();
 
   let value = yield disablePromise;
   is(value, true, "Addon should remain disabled");
 
-  let [addon1, addon2] = yield AddonManager.getAddonsByIDs([ID1, ID2]);
+  let [addon1, addon2, addon3, addon4] = yield AddonManager.getAddonsByIDs([ID1, ID2, ID3, ID4]);
   ok(addon1.seen, "Addon should be marked as seen");
   is(addon1.userDisabled, true, "Addon 1 should still be disabled");
   is(addon2.userDisabled, true, "Addon 2 should still be disabled");
+  is(addon3.userDisabled, true, "Addon 3 should still be disabled");
+  is(addon4.userDisabled, true, "Addon 4 should still be disabled");
 
   yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
 
-  // Should still have 1 entry in the hamburger menu
+  // Should still have 3 entries in the hamburger menu
   yield PanelUI.show();
 
   addons = document.getElementById("PanelUI-footer-addons");
-  is(addons.children.length, 1, "Have 1 menu entry for sideloaded extensions");
+  is(addons.children.length, 3, "Have 3 menu entries for sideloaded extensions");
 
   // Click the second sideloaded extension and wait for the notification
   popupPromise = promisePopupNotificationShown("addon-webext-permissions");
   addons.children[0].click();
   panel = yield popupPromise;
 
-  isnot(menuButton.getAttribute("badge-status"), "addon-alert", "Should no longer have addon alert badge");
-
   // Again we should be at the extentions list in about:addons
   is(gBrowser.currentURI.spec, "about:addons", "Foreground tab is at about:addons");
 
   win = gBrowser.selectedBrowser.contentWindow;
   ok(!win.gViewController.isLoading, "about:addons view is fully loaded");
   is(win.gViewController.currentViewId, VIEW, "about:addons is at extensions list");
 
   // Check the notification contents, this time accept the install
   icon = panel.getAttribute("icon");
   is(icon, DEFAULT_ICON_URL, "Permissions notification has the default icon");
   disablePromise = promiseSetDisabled(mock2);
   panel.button.click();
 
   value = yield disablePromise;
   is(value, false, "Addon should be set to enabled");
 
-  [addon1, addon2] = yield AddonManager.getAddonsByIDs([ID1, ID2]);
+  [addon1, addon2, addon3, addon4] = yield AddonManager.getAddonsByIDs([ID1, ID2, ID3, ID4]);
   is(addon1.userDisabled, true, "Addon 1 should still be disabled");
   is(addon2.userDisabled, false, "Addon 2 should now be enabled");
+  is(addon3.userDisabled, true, "Addon 3 should still be disabled");
+  is(addon4.userDisabled, true, "Addon 4 should still be disabled");
+
+  // Should still have 2 entries in the hamburger menu
+  yield PanelUI.show();
+
+  addons = document.getElementById("PanelUI-footer-addons");
+  is(addons.children.length, 2, "Have 2 menu entries for sideloaded extensions");
+
+  // Close the hamburger menu and go directly to the addons manager
+  yield PanelUI.hide();
+
+  win = yield BrowserOpenAddonsMgr(VIEW);
+
+  let list = win.document.getElementById("addon-list");
+
+  // Make sure XBL bindings are applied
+  list.clientHeight;
+
+  let item = list.children.find(_item => _item.value == ID3);
+  ok(item, "Found entry for sideloaded extension in about:addons");
+  item.scrollIntoView({behavior: "instant"});
+
+  ok(is_visible(item._enableBtn), "Enable button is visible for sideloaded extension");
+  ok(is_hidden(item._disableBtn), "Disable button is not visible for sideloaded extension");
+
+  // When clicking enable we should see the permissions notification
+  popupPromise = promisePopupNotificationShown("addon-webext-permissions");
+  BrowserTestUtils.synthesizeMouseAtCenter(item._enableBtn, {},
+                                           gBrowser.selectedBrowser);
+  panel = yield popupPromise;
+
+  // Accept the permissions
+  disablePromise = promiseSetDisabled(mock3);
+  panel.button.click();
+  value = yield disablePromise;
+  is(value, false, "userDisabled should be set on addon 3");
+
+  addon3 = yield AddonManager.getAddonByID(ID3);
+  is(addon3.userDisabled, false, "Addon 3 should be enabled");
+
+  // Should still have 1 entry in the hamburger menu
+  yield PanelUI.show();
+
+  addons = document.getElementById("PanelUI-footer-addons");
+  is(addons.children.length, 1, "Have 1 menu entry for sideloaded extensions");
+
+  // Close the hamburger menu and go to the detail page for this addon
+  yield PanelUI.hide();
+
+  win = yield BrowserOpenAddonsMgr(`addons://detail/${encodeURIComponent(ID4)}`);
+  let button = win.document.getElementById("detail-enable-btn");
+
+  // When clicking enable we should see the permissions notification
+  popupPromise = promisePopupNotificationShown("addon-webext-permissions");
+  BrowserTestUtils.synthesizeMouseAtCenter(button, {},
+                                           gBrowser.selectedBrowser);
+  panel = yield popupPromise;
+
+  // Accept the permissions
+  disablePromise = promiseSetDisabled(mock4);
+  panel.button.click();
+  value = yield disablePromise;
+  is(value, false, "userDisabled should be set on addon 4");
+
+  addon4 = yield AddonManager.getAddonByID(ID4);
+  is(addon4.userDisabled, false, "Addon 4 should be enabled");
+
+  isnot(menuButton.getAttribute("badge-status"), "addon-alert", "Should no longer have addon alert badge");
 
   yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -27,16 +27,17 @@ const DEFAULT_EXTENSION_ICON = "chrome:/
 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";
 
 this.ExtensionsUI = {
   sideloaded: new Set(),
   updates: new Set(),
+  sideloadListener: null,
 
   init() {
     Services.obs.addObserver(this, "webextension-permission-prompt", false);
     Services.obs.addObserver(this, "webextension-update-permissions", false);
     Services.obs.addObserver(this, "webextension-install-notify", false);
 
     this._checkForSideloaded();
   },
@@ -48,16 +49,35 @@ this.ExtensionsUI = {
       let sideloaded = addons.filter(
         addon => addon.seen === false && (addon.permissions & AddonManager.PERM_CAN_ENABLE));
 
       if (!sideloaded.length) {
         return;
       }
 
       if (WEBEXT_PERMISSION_PROMPTS) {
+        if (!this.sideloadListener) {
+          this.sideloadListener = {
+            onEnabled: addon => {
+              if (!this.sideloaded.has(addon)) {
+                return;
+              }
+
+              this.sideloaded.delete(addon);
+              this.emit("change");
+
+              if (this.sideloaded.size == 0) {
+                AddonManager.removeAddonListener(this.sideloadListener);
+                this.sideloadListener = null;
+              }
+            },
+          };
+          AddonManager.addAddonListener(this.sideloadListener);
+        }
+
         for (let addon of sideloaded) {
           this.sideloaded.add(addon);
         }
         this.emit("change");
       } else {
         // This and all the accompanying about:newaddon code can eventually
         // be removed.  See bug 1331521.
         let win = RecentWindow.getMostRecentBrowserWindow();
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -1273,16 +1273,36 @@ var gViewController = {
       isEnabled(aAddon) {
         if (!aAddon)
           return false;
         let addonType = AddonManager.addonTypes[aAddon.type];
         return (!(addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
                 hasPermission(aAddon, "enable"));
       },
       doCommand(aAddon) {
+        if (aAddon.isWebExtension && !aAddon.seen && WEBEXT_PERMISSION_PROMPTS) {
+          let perms = aAddon.userPermissions;
+          if (perms.hosts.length > 0 || perms.permissions.length > 0) {
+            let subject = {
+              wrappedJSObject: {
+                target: getBrowserElement(),
+                info: {
+                  type: "sideload",
+                  addon: aAddon,
+                  icon: aAddon.iconURL,
+                  permissions: perms,
+                  resolve() { aAddon.userDisabled = false },
+                  reject() {},
+                },
+              },
+            };
+            Services.obs.notifyObservers(subject, "webextension-permission-prompt", null);
+            return;
+          }
+        }
         aAddon.userDisabled = false;
       },
       getTooltip(aAddon) {
         if (!aAddon)
           return "";
         if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_ENABLE)
           return gStrings.ext.GetStringFromName("enableAddonRestartRequiredTooltip");
         return gStrings.ext.GetStringFromName("enableAddonTooltip");
--- a/toolkit/mozapps/extensions/content/extensions.xml
+++ b/toolkit/mozapps/extensions/content/extensions.xml
@@ -1103,17 +1103,23 @@
                                                 "relnotes");
       </field>
 
       <property name="userDisabled">
         <getter><![CDATA[
           return this.mAddon.userDisabled;
         ]]></getter>
         <setter><![CDATA[
-          this.mAddon.userDisabled = val;
+          if (val === true) {
+            gViewController.commands["cmd_disableItem"].doCommand(this.mAddon);
+          } else if (val === false) {
+            gViewController.commands["cmd_enableItem"].doCommand(this.mAddon);
+          } else {
+            this.mAddon.userDisabled = val;
+          }
         ]]></setter>
       </property>
 
       <property name="includeUpdate">
         <getter><![CDATA[
           return this._includeUpdate.checked && !!this.mManualUpdate;
         ]]></getter>
         <setter><![CDATA[