Bug 1409245 Part 2 Implement bootstrap update() method draft
authorAndrew Swan <aswan@mozilla.com>
Wed, 25 Oct 2017 15:34:45 -0700
changeset 689494 aa76ce45d5c249685cb65345c4c4d99277b0e3f9
parent 689493 6b95e1d859c4533ce353c2ac13220bc4fdcd0a31
child 689495 a35091602cf6d93e9bea9b8dd6aaabb9e7d04a3f
push id87036
push useraswan@mozilla.com
push dateTue, 31 Oct 2017 16:41:42 +0000
bugs1409245
milestone58.0a1
Bug 1409245 Part 2 Implement bootstrap update() method When we are updating versions of an extension, instead of calling uninstall() on the old version followed by calling install() on the new version, we now call update() once with details about the old and new versions. This logic is only applied for webextensions, any other bootstrapped extensions retain the old behavior. MozReview-Commit-ID: EwdvGATG0rK
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
@@ -1794,33 +1794,38 @@ class AddonInstall {
                                                  this.listeners, this.wrapper,
                                                  this.addon.wrapper);
       } else {
         // The install is completed so it should be removed from the active list
         XPIProvider.removeActiveInstall(this);
 
         // Deactivate and remove the old add-on as necessary
         let reason = BOOTSTRAP_REASONS.ADDON_INSTALL;
+        let callUpdate = false;
         if (this.existingAddon) {
           if (Services.vc.compare(this.existingAddon.version, this.addon.version) < 0)
             reason = BOOTSTRAP_REASONS.ADDON_UPGRADE;
           else
             reason = BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
 
+          callUpdate = isWebExtension(this.addon.type) && isWebExtension(this.existingAddon.type);
+
           if (this.existingAddon.bootstrap) {
             let file = this.existingAddon._sourceBundle;
             if (this.existingAddon.active) {
               XPIProvider.callBootstrapMethod(this.existingAddon, file,
                                               "shutdown", reason,
                                               { newVersion: this.addon.version });
             }
 
-            XPIProvider.callBootstrapMethod(this.existingAddon, file,
-                                            "uninstall", reason,
-                                            { newVersion: this.addon.version });
+            if (!callUpdate) {
+              XPIProvider.callBootstrapMethod(this.existingAddon, file,
+                                              "uninstall", reason,
+                                              { newVersion: this.addon.version });
+            }
             XPIProvider.unloadBootstrapScope(this.existingAddon.id);
             flushChromeCaches();
           }
 
           if (!isUpgrade && this.existingAddon.active) {
             XPIDatabase.updateAddonActive(this.existingAddon, false);
           }
         }
@@ -1856,17 +1861,18 @@ class AddonInstall {
         XPIStates.save();
 
         let extraParams = {};
         if (this.existingAddon) {
           extraParams.oldVersion = this.existingAddon.version;
         }
 
         if (this.addon.bootstrap) {
-          XPIProvider.callBootstrapMethod(this.addon, file, "install",
+          let method = callUpdate ? "update" : "install";
+          XPIProvider.callBootstrapMethod(this.addon, file, method,
                                           reason, extraParams);
         }
 
         AddonManagerPrivate.callAddonListeners("onInstalled",
                                                this.addon.wrapper);
 
         logger.debug("Install of " + this.sourceURI.spec + " completed.");
         this.state = AddonManager.STATE_INSTALLED;
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -2387,33 +2387,38 @@ this.XPIProvider = {
     let tempLocation = XPIStates.getLocation(TemporaryInstallLocation.name);
     if (tempLocation) {
       for (let [id, addon] of tempLocation.entries()) {
         tempLocation.delete(id);
 
         let reason = BOOTSTRAP_REASONS.ADDON_UNINSTALL;
 
         let existing = XPIStates.findAddon(id, loc => loc != tempLocation);
+        let callUpdate = false;
         if (existing) {
           reason = newVersionReason(addon.version, existing.version);
+          callUpdate = (isWebExtension(addon.type) && isWebExtension(existing.type));
         }
 
         this.callBootstrapMethod(addon, addon.file, "shutdown", reason);
-        this.callBootstrapMethod(addon, addon.file, "uninstall", reason);
+        if (!callUpdate) {
+          this.callBootstrapMethod(addon, addon.file, "uninstall", reason);
+        }
         this.unloadBootstrapScope(id);
         TemporaryInstallLocation.uninstallAddon(id);
         XPIStates.removeAddon(TemporaryInstallLocation.name, id);
 
         if (existing) {
           let newAddon = XPIDatabase.makeAddonLocationVisible(id, existing.location.name);
 
           let file = new nsIFile(newAddon.path);
 
           let data = {oldVersion: addon.version};
-          this.callBootstrapMethod(newAddon, file, "install", reason, data);
+          let method = callUpdate ? "update" : "install";
+          this.callBootstrapMethod(newAddon, file, method, reason, data);
         }
       }
     }
   },
 
   /**
    * Applies any pending theme change to the preferences.
    */
@@ -3548,16 +3553,17 @@ this.XPIProvider = {
 
     if (!addon.bootstrap) {
       throw new Error("Only restartless (bootstrap) add-ons"
                     + " can be installed from sources:", addon.id);
     }
     let installReason = BOOTSTRAP_REASONS.ADDON_INSTALL;
     let oldAddon = await new Promise(
                    resolve => XPIDatabase.getVisibleAddonForID(addon.id, resolve));
+    let callUpdate = false;
 
     let extraParams = {};
     extraParams.temporarilyInstalled = aInstallLocation === TemporaryInstallLocation;
     if (oldAddon) {
       if (!oldAddon.bootstrap) {
         logger.warn("Non-restartless Add-on is already installed", addon.id);
         throw new Error("Non-restartless add-on with ID "
                         + oldAddon.id + " is already installed");
@@ -3576,34 +3582,40 @@ this.XPIProvider = {
         let oldVersion = oldAddon.version;
 
         installReason = newVersionReason(oldVersion, newVersion);
         let uninstallReason = installReason;
 
         extraParams.newVersion = newVersion;
         extraParams.oldVersion = oldVersion;
 
+        callUpdate = isWebExtension(oldAddon.type) && isWebExtension(addon.type);
+
         if (oldAddon.active) {
           XPIProvider.callBootstrapMethod(oldAddon, existingAddon,
                                           "shutdown", uninstallReason,
                                           extraParams);
         }
-        this.callBootstrapMethod(oldAddon, existingAddon,
-                                 "uninstall", uninstallReason, extraParams);
+
+        if (!callUpdate) {
+          this.callBootstrapMethod(oldAddon, existingAddon,
+                                   "uninstall", uninstallReason, extraParams);
+        }
         this.unloadBootstrapScope(existingAddonID);
         flushChromeCaches();
       }
     } else {
       addon.installDate = Date.now();
     }
 
     let file = addon._sourceBundle;
 
     XPIProvider._addURIMapping(addon.id, file);
-    XPIProvider.callBootstrapMethod(addon, file, "install", installReason, extraParams);
+    let method = callUpdate ? "update" : "install";
+    XPIProvider.callBootstrapMethod(addon, file, method, installReason, extraParams);
     addon.state = AddonManager.STATE_INSTALLED;
     logger.debug("Install of temporary addon in " + aFile.path + " completed.");
     addon.visible = true;
     addon.enabled = true;
     addon.active = true;
     // WebExtension themes are installed as disabled, fix that here.
     addon.userDisabled = false;
 
@@ -4648,31 +4660,35 @@ this.XPIProvider = {
       // strange as in some cases this operation can complete without a restart
       // so really this is now saying that the uninstall isn't going to happen
       // immediately but will happen later.
       AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper,
                                              makePending);
     }
 
     let reason = BOOTSTRAP_REASONS.ADDON_UNINSTALL;
+    let callUpdate = false;
     let existingAddon = XPIStates.findAddon(aAddon.id, loc =>
       loc.name != aAddon._installLocation.name);
     if (existingAddon) {
       reason = newVersionReason(aAddon.version, existingAddon.version);
+      callUpdate = isWebExtension(aAddon.type) && isWebExtension(existingAddon.type);
     }
 
     if (!makePending) {
       if (aAddon.bootstrap) {
         if (aAddon.active) {
           this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown",
                                    reason);
         }
 
-        this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "uninstall",
-                                 reason);
+        if (!callUpdate) {
+          this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "uninstall",
+                                   reason);
+        }
         XPIStates.disableAddon(aAddon.id);
         this.unloadBootstrapScope(aAddon.id);
         flushChromeCaches();
       }
       aAddon._installLocation.uninstallAddon(aAddon.id);
       XPIDatabase.removeAddonMetadata(aAddon);
       XPIStates.removeAddon(aAddon.location, aAddon.id);
       AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);
@@ -4684,18 +4700,19 @@ this.XPIProvider = {
           let wrappedAddon = existing.wrapper;
           AddonManagerPrivate.callAddonListeners("onInstalling", wrappedAddon, false);
 
           if (!existing.disabled && !XPIProvider.enableRequiresRestart(existing)) {
             XPIDatabase.updateAddonActive(existing, true);
           }
 
           if (aAddon.bootstrap) {
+            let method = callUpdate ? "update" : "install";
             XPIProvider.callBootstrapMethod(existing, existing._sourceBundle,
-                                            "install", reason);
+                                            method, reason);
 
             if (existing.active) {
               XPIProvider.callBootstrapMethod(existing, existing._sourceBundle,
                                               "startup", reason);
             } else {
               XPIProvider.unloadBootstrapScope(existing.id);
             }
           }