Bug 1441271 Show permissions notifications for distribution addons draft
authorAndrew Swan <aswan@mozilla.com>
Wed, 28 Feb 2018 18:36:36 -0800
changeset 764086 a4176081c526205b481de798ed56956345032324
parent 763109 51200c0fdaddb2749549a82596da5323a4cbd499
push id101660
push useraswan@mozilla.com
push dateWed, 07 Mar 2018 05:13:11 +0000
bugs1441271
milestone60.0a1
Bug 1441271 Show permissions notifications for distribution addons As described in the bug, this is intended as a temporary solution to enable some experiments. If this becomes a real feature, UX will put some thought into a better startup experience. MozReview-Commit-ID: 4DGMHj29M3e
browser/modules/ExtensionsUI.jsm
toolkit/mozapps/extensions/AddonManager.jsm
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/test/xpcshell/test_shutdown.js
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -44,16 +44,17 @@ var ExtensionsUI = {
     Services.obs.addObserver(this, "webextension-update-permissions");
     Services.obs.addObserver(this, "webextension-install-notify");
     Services.obs.addObserver(this, "webextension-optional-permission-prompt");
     Services.obs.addObserver(this, "webextension-defaultsearch-prompt");
 
     await Services.wm.getMostRecentWindow("navigator:browser").delayedStartupPromise;
 
     this._checkForSideloaded();
+    this._checkNewDistroAddons();
   },
 
   async _checkForSideloaded() {
     let sideloaded = await AddonManagerPrivate.getNewSideloads();
 
     if (!sideloaded.length) {
       // No new side-loads. We're done.
       return;
@@ -92,16 +93,70 @@ var ExtensionsUI = {
       // be removed.  See bug 1331521.
       let win = RecentWindow.getMostRecentBrowserWindow();
       for (let addon of sideloaded) {
         win.openUILinkIn(`about:newaddon?id=${addon.id}`, "tab");
       }
     }
   },
 
+  async _checkNewDistroAddons() {
+    let newDistroAddons = AddonManagerPrivate.getNewDistroAddons();
+    if (!newDistroAddons) {
+      return;
+    }
+
+    for (let id of newDistroAddons) {
+      let addon = await AddonManager.getAddonByID(id);
+
+      let win = Services.wm.getMostRecentWindow("navigator:browser");
+      if (!win) {
+        return;
+      }
+
+      let {gBrowser} = win;
+      let browser = gBrowser.selectedBrowser;
+
+      // The common case here is that we enter this code right after startup
+      // in a brand new profile so we haven't yet loaded a page.  That state is
+      // surprisingly difficult to detect but wait until we've actually loaded
+      // a page.
+      if (browser.currentURI.spec == "about:blank" ||
+          browser.webProgress.isLoadingDocument) {
+        await new Promise(resolve => {
+          let listener = {
+            onLocationChange(browser_, webProgress, ...ignored) {
+              if (webProgress.isTopLevel && browser_ == browser) {
+                gBrowser.removeTabsProgressListener(listener);
+                resolve();
+              }
+            },
+          };
+          gBrowser.addTabsProgressListener(listener);
+        });
+      }
+
+      // If we're at about:newtab and the url bar gets focus, that will
+      // prevent a doorhanger from displaying.
+      // Our elegant solution is to ... take focus away from the url bar.
+      win.gURLBar.blur();
+
+      let strings = this._buildStrings({
+        addon,
+        permissions: addon.userPermissions,
+      });
+      let accepted = await this.showPermissionsPrompt(browser, strings,
+                                                      addon.iconURL);
+      if (accepted) {
+        addon.userDisabled = false;
+      }
+    }
+  },
+
+
   _updateNotifications() {
     if (this.sideloaded.size + this.updates.size == 0) {
       AppMenuNotifications.removeNotification("addon-alert");
     } else {
       AppMenuNotifications.showBadgeOnlyNotification("addon-alert");
     }
     this.emit("change");
   },
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -2978,16 +2978,27 @@ var AddonManagerPrivate = {
    *
    * @returns {Promise<Array<Addon>>}
    */
   getNewSideloads() {
     return AddonManagerInternal._getProviderByName("XPIProvider")
                                .getNewSideloads();
   },
 
+  /**
+   * Gets a set of (ids of) distribution addons which were installed into the
+   * current profile at browser startup, or null if none were installed.
+   *
+   * @return {Set<String> | null}
+   */
+  getNewDistroAddons() {
+    return AddonManagerInternal._getProviderByName("XPIProvider")
+                               .getNewDistroAddons();
+  },
+
   get browserUpdated() {
     return gBrowserUpdated;
   },
 
   registerProvider(aProvider, aTypes) {
     AddonManagerInternal.registerProvider(aProvider, aTypes);
   },
 
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -79,16 +79,17 @@ const PREF_XPI_DIRECT_WHITELISTED     = 
 const PREF_XPI_FILE_WHITELISTED       = "xpinstall.whitelist.fileRequest";
 // xpinstall.signatures.required only supported in dev builds
 const PREF_XPI_SIGNATURES_REQUIRED    = "xpinstall.signatures.required";
 const PREF_XPI_SIGNATURES_DEV_ROOT    = "xpinstall.signatures.dev-root";
 const PREF_XPI_PERMISSIONS_BRANCH     = "xpinstall.";
 const PREF_INSTALL_REQUIRESECUREORIGIN = "extensions.install.requireSecureOrigin";
 const PREF_INSTALL_DISTRO_ADDONS      = "extensions.installDistroAddons";
 const PREF_BRANCH_INSTALLED_ADDON     = "extensions.installedDistroAddon.";
+const PREF_DISTRO_ADDONS_PERMS        = "extensions.distroAddons.promptForPermissions";
 const PREF_INTERPOSITION_ENABLED      = "extensions.interposition.enabled";
 const PREF_SYSTEM_ADDON_SET           = "extensions.systemAddonSet";
 const PREF_SYSTEM_ADDON_UPDATE_URL    = "extensions.systemAddon.update.url";
 const PREF_ALLOW_LEGACY               = "extensions.legacy.enabled";
 const PREF_ALLOW_NON_MPC              = "extensions.allow-non-mpc-extensions";
 
 const PREF_EM_MIN_COMPAT_APP_VERSION      = "extensions.minCompatibleAppVersion";
 const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion";
@@ -1809,16 +1810,18 @@ var XPIProvider = {
   minCompatiblePlatformVersion: null,
   // A Map of active addons to their bootstrapScope by ID
   activeAddons: new Map(),
   // True if the platform could have activated extensions
   extensionsActive: false,
   // True if all of the add-ons found during startup were installed in the
   // application install location
   allAppGlobal: true,
+  // New distribution addons awaiting permissions approval
+  newDistroAddons: null,
   // Keep track of startup phases for telemetry
   runPhase: XPI_STARTING,
   // Per-addon telemetry information
   _telemetryDetails: {},
   // A Map from an add-on install to its ID
   _addonFileMap: new Map(),
   // Have we started shutting down bootstrap add-ons?
   _closing: false,
@@ -3100,16 +3103,24 @@ var XPIProvider = {
         }
       } else if (Services.prefs.getBoolPref(PREF_BRANCH_INSTALLED_ADDON + id, false)) {
         continue;
       }
 
       // Install the add-on
       try {
         addon._sourceBundle = profileLocation.installAddon({ id, source: entry, action: "copy" });
+        if (Services.prefs.getBoolPref(PREF_DISTRO_ADDONS_PERMS, false)) {
+          addon.userDisabled = true;
+          if (!this.newDistroAddons) {
+            this.newDistroAddons = new Set();
+          }
+          this.newDistroAddons.add(id);
+        }
+
         XPIStates.addAddon(addon);
         logger.debug("Installed distribution add-on " + id);
 
         Services.prefs.setBoolPref(PREF_BRANCH_INSTALLED_ADDON + id, true);
 
         // aManifests may contain a copy of a newly installed add-on's manifest
         // and we'll have overwritten that so instead cache our install manifest
         // which will later be put into the database in processFileChanges
@@ -3122,16 +3133,22 @@ var XPIProvider = {
       }
     }
 
     entries.close();
 
     return changed;
   },
 
+  getNewDistroAddons() {
+    let addons = this.newDistroAddons;
+    this.newDistroAddons = null;
+    return addons;
+  },
+
   /**
    * Imports the xpinstall permissions from preferences into the permissions
    * manager for the user to change later.
    */
   importPermissions() {
     PermissionsUtils.importFromPrefs(PREF_XPI_PERMISSIONS_BRANCH,
                                      XPI_PERMISSION);
   },
--- a/toolkit/mozapps/extensions/test/xpcshell/test_shutdown.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_shutdown.js
@@ -13,17 +13,17 @@ const IGNORE = ["getPreferredIconURL", "
                 "mapURIToAddonID", "shutdown", "init",
                 "stateToString", "errorToString", "getUpgradeListener",
                 "addUpgradeListener", "removeUpgradeListener"];
 
 const IGNORE_PRIVATE = ["AddonAuthor", "AddonCompatibilityOverride",
                         "AddonScreenshot", "AddonType", "startup", "shutdown",
                         "addonIsActive", "registerProvider", "unregisterProvider",
                         "addStartupChange", "removeStartupChange",
-                        "getNewSideloads",
+                        "getNewSideloads", "getNewDistroAddons",
                         "recordTimestamp", "recordSimpleMeasure",
                         "recordException", "getSimpleMeasures", "simpleTimer",
                         "setTelemetryDetails", "getTelemetryDetails",
                         "callNoUpdateListeners", "backgroundUpdateTimerHandler",
                         "hasUpgradeListener", "getUpgradeListener",
                         "isDBLoaded", "BOOTSTRAP_REASONS"];
 
 async function test_functions() {