Bug 1363925: Part 6 - Move staged add-on install logic to XPIInstall. r=aswan
authorKris Maglione <maglione.k@gmail.com>
Sat, 21 Apr 2018 19:47:16 -0700
changeset 468744 afd6f3e6e6af154925a2c13bd363d38187cecf2a
parent 468743 801a6d33fa88b3030a3989d602a34237fb467e5c
child 468745 ee1bdb001079770b20899df675e6cd0640a2d5fe
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan
bugs1363925
milestone61.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 1363925: Part 6 - Move staged add-on install logic to XPIInstall. r=aswan MozReview-Commit-ID: IDXsbKvl5U3
toolkit/mozapps/extensions/internal/XPIInstall.jsm
toolkit/mozapps/extensions/internal/XPIProvider.jsm
--- a/toolkit/mozapps/extensions/internal/XPIInstall.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIInstall.jsm
@@ -3647,16 +3647,89 @@ var XPIInstall = {
     XPIStates.addAddon(addon);
     logger.debug("Installed distribution add-on " + id);
 
     Services.prefs.setBoolPref(PREF_BRANCH_INSTALLED_ADDON + id, true);
 
     return addon;
   },
 
+  /**
+   * Completes the install of an add-on which was staged during the last
+   * session.
+   *
+   * @param {string} id
+   *        The expected ID of the add-on.
+   * @param {object} metadata
+   *        The parsed metadata for the staged install.
+   * @param {InstallLocation} location
+   *        The install location to install the add-on to.
+   * @returns {AddonInternal}
+   *        The installed Addon object, upon success.
+   */
+  async installStagedAddon(id, metadata, location) {
+    let source = getFile(`${id}.xpi`, location.getStagingDir());
+
+    // Check that the directory's name is a valid ID.
+    if (!gIDTest.test(id) || !source.exists() || !source.isFile()) {
+      throw new Error(`Ignoring invalid staging directory entry: ${id}`);
+    }
+
+    let addon = await loadManifestFromFile(source, location);
+
+    if (mustSign(addon.type) &&
+        addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) {
+      throw new Error(`Refusing to install staged add-on ${id} with signed state ${addon.signedState}`);
+    }
+
+    addon.importMetadata(metadata);
+
+    var oldBootstrap = null;
+    logger.debug(`Processing install of ${id} in ${location.name}`);
+    let existingAddon = XPIStates.findAddon(id);
+    if (existingAddon && existingAddon.bootstrapped) {
+      try {
+        var file = existingAddon.file;
+        if (file.exists()) {
+          oldBootstrap = existingAddon;
+
+          // We'll be replacing a currently active bootstrapped add-on so
+          // call its uninstall method
+          let newVersion = addon.version;
+          let oldVersion = existingAddon;
+          let uninstallReason = newVersionReason(oldVersion, newVersion);
+
+          XPIProvider.callBootstrapMethod(existingAddon,
+                                          file, "uninstall", uninstallReason,
+                                          { newVersion });
+          XPIProvider.unloadBootstrapScope(id);
+          flushChromeCaches();
+        }
+      } catch (e) {
+        Cu.reportError(e);
+      }
+    }
+
+    try {
+      addon._sourceBundle = location.installAddon({
+        id, source, existingAddonID: id,
+      });
+      XPIStates.addAddon(addon);
+    } catch (e) {
+      if (oldBootstrap) {
+        // Re-install the old add-on
+        XPIProvider.callBootstrapMethod(oldBootstrap, existingAddon, "install",
+                                        BOOTSTRAP_REASONS.ADDON_INSTALL);
+      }
+      throw e;
+    }
+
+    return addon;
+  },
+
   async updateSystemAddons() {
     let systemAddonLocation = XPIProvider.installLocationsByName[KEY_APP_SYSTEM_ADDONS];
     if (!systemAddonLocation)
       return;
 
     // Don't do anything in safe mode
     if (Services.appinfo.inSafeMode)
       return;
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -2189,93 +2189,38 @@ var XPIProvider = {
       // We can't install or uninstall anything in locked locations
       if (location.locked) {
         continue;
       }
 
       let state = XPIStates.getLocation(location.name);
 
       let cleanNames = [];
+      let promises = [];
       for (let [id, metadata] of state.getStagedAddons()) {
         state.unstageAddon(id);
 
-        let source = getFile(`${id}.xpi`, location.getStagingDir());
-
-        // Check that the directory's name is a valid ID.
-        if (!gIDTest.test(id) || !source.exists() || !source.isFile()) {
-          logger.warn("Ignoring invalid staging directory entry: ${id}", {id});
-          cleanNames.push(source.leafName);
-          continue;
-        }
-
+        aManifests[location.name][id] = null;
+        promises.push(
+          XPIInstall.installStagedAddon(id, metadata, location).then(
+            addon => {
+              aManifests[location.name][id] = addon;
+            },
+            error => {
+              delete aManifests[location.name][id];
+              cleanNames.push(`${id}.xpi`);
+
+              logger.error(`Failed to install staged add-on ${id} in ${location.name}`,
+                           error);
+            }));
+      }
+
+      if (promises.length) {
         changed = true;
-        aManifests[location.name][id] = null;
-
-        let addon;
-        try {
-          addon = XPIInstall.syncLoadManifestFromFile(source, location);
-        } catch (e) {
-          logger.error(`Unable to read add-on manifest from ${source.path}`, e);
-          cleanNames.push(source.leafName);
-          continue;
-        }
-
-        if (mustSign(addon.type) &&
-            addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) {
-          logger.warn(`Refusing to install staged add-on ${id} with signed state ${addon.signedState}`);
-          cleanNames.push(source.leafName);
-          continue;
-        }
-
-        addon.importMetadata(metadata);
-        aManifests[location.name][id] = addon;
-
-        var oldBootstrap = null;
-        logger.debug(`Processing install of ${id} in ${location.name}`);
-        let existingAddon = XPIStates.findAddon(id);
-        if (existingAddon && existingAddon.bootstrapped) {
-          try {
-            var file = existingAddon.file;
-            if (file.exists()) {
-              oldBootstrap = existingAddon;
-
-              // We'll be replacing a currently active bootstrapped add-on so
-              // call its uninstall method
-              let newVersion = addon.version;
-              let oldVersion = existingAddon;
-              let uninstallReason = XPIInstall.newVersionReason(oldVersion, newVersion);
-
-              this.callBootstrapMethod(existingAddon,
-                                       file, "uninstall", uninstallReason,
-                                       { newVersion });
-              this.unloadBootstrapScope(id);
-              XPIInstall.flushChromeCaches();
-            }
-          } catch (e) {
-            Cu.reportError(e);
-          }
-        }
-
-        try {
-          addon._sourceBundle = location.installAddon({
-            id, source, existingAddonID: id,
-          });
-          XPIStates.addAddon(addon);
-        } catch (e) {
-          logger.error("Failed to install staged add-on " + id + " in " + location.name,
-                e);
-
-          delete aManifests[location.name][id];
-
-          if (oldBootstrap) {
-            // Re-install the old add-on
-            this.callBootstrapMethod(oldBootstrap, existingAddon, "install",
-                                     BOOTSTRAP_REASONS.ADDON_INSTALL);
-          }
-        }
+        awaitPromise(Promise.all(promises));
       }
 
       try {
         if (cleanNames.length) {
           location.cleanStagingDir(cleanNames);
         }
       } catch (e) {
         // Non-critical, just saves some perf on startup if we clean this up.