Bug 1363925: Part 6 - Move staged add-on install logic to XPIInstall. r?zombie draft
authorKris Maglione <maglione.k@gmail.com>
Sat, 21 Apr 2018 19:47:16 -0700
changeset 786316 d0bde577daca1cc4091f02e6c678ef72a42bfb68
parent 786315 df0246cd64bfc4a9e5a53fa47d83830186c37a45
child 786317 e637881fe80460f95c6c72f26213c68350f323f0
push id107433
push usermaglione.k@gmail.com
push dateSun, 22 Apr 2018 22:24:27 +0000
reviewerszombie
bugs1363925
milestone61.0a1
Bug 1363925: Part 6 - Move staged add-on install logic to XPIInstall. r?zombie 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
@@ -3650,16 +3650,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
@@ -2038,93 +2038,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.