Bug 1448221: Part 1 - Remove support for operations requiring restart. r?aswan draft
authorKris Maglione <maglione.k@gmail.com>
Thu, 22 Mar 2018 14:46:55 -0700
changeset 771496 1a98ffcd6be0728e97b355522f358c75ca606bc4
parent 771494 5966d50d1d4116f0db9bcd1f684226a7ec378fc3
child 771497 30473a35195dd846b379e60451a539ad11a9c6ed
push id103691
push usermaglione.k@gmail.com
push dateFri, 23 Mar 2018 04:50:51 +0000
reviewersaswan
bugs1448221
milestone61.0a1
Bug 1448221: Part 1 - Remove support for operations requiring restart. r?aswan MozReview-Commit-ID: EJDAAXCpViY
toolkit/mozapps/extensions/internal/XPIInstall.jsm
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/internal/XPIProviderUtils.js
--- a/toolkit/mozapps/extensions/internal/XPIInstall.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIInstall.jsm
@@ -1706,143 +1706,135 @@ class AddonInstall {
           aInstall.addon.id == this.addon.id) {
         logger.debug("Cancelling previous pending install of " + aInstall.addon.id);
         aInstall.cancel();
       }
     }
 
     let isUpgrade = this.existingAddon &&
                     this.existingAddon._installLocation == this.installLocation;
-    let requiresRestart = XPIProvider.installRequiresRestart(this.addon);
 
     logger.debug("Starting install of " + this.addon.id + " from " + this.sourceURI.spec);
     AddonManagerPrivate.callAddonListeners("onInstalling",
                                            this.addon.wrapper,
-                                           requiresRestart);
+                                           false);
 
     let stagedAddon = this.installLocation.getStagingDir();
 
     (async () => {
       await this.installLocation.requestStagingDir();
 
       // remove any previously staged files
       await this.unstageInstall(stagedAddon);
 
       stagedAddon.append(`${this.addon.id}.xpi`);
 
-      await this.stageInstall(requiresRestart, stagedAddon, isUpgrade);
+      await this.stageInstall(false, stagedAddon, isUpgrade);
 
-      if (requiresRestart) {
-        this.state = AddonManager.STATE_INSTALLED;
-        AddonManagerPrivate.callInstallListeners("onInstallEnded",
-                                                 this.listeners, this.wrapper,
-                                                 this.addon.wrapper);
-      } else {
-        // The install is completed so it should be removed from the active list
-        XPIProvider.removeActiveInstall(this);
+      // 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);
+      // 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;
 
-          if (this.existingAddon.bootstrap) {
-            let file = this.existingAddon._sourceBundle;
-            if (this.existingAddon.active) {
-              XPIProvider.callBootstrapMethod(this.existingAddon, file,
-                                              "shutdown", reason,
-                                              { newVersion: this.addon.version });
-            }
+        callUpdate = isWebExtension(this.addon.type) && isWebExtension(this.existingAddon.type);
 
-            if (!callUpdate) {
-              XPIProvider.callBootstrapMethod(this.existingAddon, file,
-                                              "uninstall", reason,
-                                              { newVersion: this.addon.version });
-            }
-            XPIProvider.unloadBootstrapScope(this.existingAddon.id);
-            flushChromeCaches();
+        if (this.existingAddon.bootstrap) {
+          let file = this.existingAddon._sourceBundle;
+          if (this.existingAddon.active) {
+            XPIProvider.callBootstrapMethod(this.existingAddon, file,
+                                            "shutdown", reason,
+                                            { newVersion: this.addon.version });
           }
 
-          if (!isUpgrade && this.existingAddon.active) {
-            XPIDatabase.updateAddonActive(this.existingAddon, false);
+          if (!callUpdate) {
+            XPIProvider.callBootstrapMethod(this.existingAddon, file,
+                                            "uninstall", reason,
+                                            { newVersion: this.addon.version });
           }
+          XPIProvider.unloadBootstrapScope(this.existingAddon.id);
+          flushChromeCaches();
         }
 
-        // Install the new add-on into its final location
-        let existingAddonID = this.existingAddon ? this.existingAddon.id : null;
-        let file = this.installLocation.installAddon({
-          id: this.addon.id,
-          source: stagedAddon,
-          existingAddonID
-        });
+        if (!isUpgrade && this.existingAddon.active) {
+          XPIDatabase.updateAddonActive(this.existingAddon, false);
+        }
+      }
 
-        // Update the metadata in the database
-        this.addon._sourceBundle = file;
-        this.addon.visible = true;
+      // Install the new add-on into its final location
+      let existingAddonID = this.existingAddon ? this.existingAddon.id : null;
+      let file = this.installLocation.installAddon({
+        id: this.addon.id,
+        source: stagedAddon,
+        existingAddonID
+      });
+
+      // Update the metadata in the database
+      this.addon._sourceBundle = file;
+      this.addon.visible = true;
 
-        if (isUpgrade) {
-          this.addon =  XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon,
-                                                        file.path);
-          let state = XPIStates.getAddon(this.installLocation.name, this.addon.id);
-          if (state) {
-            state.syncWithDB(this.addon, true);
-          } else {
-            logger.warn("Unexpected missing XPI state for add-on ${id}", this.addon);
-          }
+      if (isUpgrade) {
+        this.addon =  XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon,
+                                                      file.path);
+        let state = XPIStates.getAddon(this.installLocation.name, this.addon.id);
+        if (state) {
+          state.syncWithDB(this.addon, true);
         } else {
-          this.addon.active = (this.addon.visible && !this.addon.disabled);
-          this.addon = XPIDatabase.addAddonMetadata(this.addon, file.path);
-          XPIStates.addAddon(this.addon);
-          this.addon.installDate = this.addon.updateDate;
-          XPIDatabase.saveChanges();
+          logger.warn("Unexpected missing XPI state for add-on ${id}", this.addon);
         }
-        XPIStates.save();
+      } else {
+        this.addon.active = (this.addon.visible && !this.addon.disabled);
+        this.addon = XPIDatabase.addAddonMetadata(this.addon, file.path);
+        XPIStates.addAddon(this.addon);
+        this.addon.installDate = this.addon.updateDate;
+        XPIDatabase.saveChanges();
+      }
+      XPIStates.save();
 
-        let extraParams = {};
-        if (this.existingAddon) {
-          extraParams.oldVersion = this.existingAddon.version;
-        }
+      let extraParams = {};
+      if (this.existingAddon) {
+        extraParams.oldVersion = this.existingAddon.version;
+      }
 
-        if (this.addon.bootstrap) {
-          let method = callUpdate ? "update" : "install";
-          XPIProvider.callBootstrapMethod(this.addon, file, method,
-                                          reason, extraParams);
-        }
+      if (this.addon.bootstrap) {
+        let method = callUpdate ? "update" : "install";
+        XPIProvider.callBootstrapMethod(this.addon, file, method,
+                                        reason, extraParams);
+      }
 
-        AddonManagerPrivate.callAddonListeners("onInstalled",
+      AddonManagerPrivate.callAddonListeners("onInstalled",
+                                             this.addon.wrapper);
+
+      logger.debug("Install of " + this.sourceURI.spec + " completed.");
+      this.state = AddonManager.STATE_INSTALLED;
+      AddonManagerPrivate.callInstallListeners("onInstallEnded",
+                                               this.listeners, this.wrapper,
                                                this.addon.wrapper);
 
-        logger.debug("Install of " + this.sourceURI.spec + " completed.");
-        this.state = AddonManager.STATE_INSTALLED;
-        AddonManagerPrivate.callInstallListeners("onInstallEnded",
-                                                 this.listeners, this.wrapper,
-                                                 this.addon.wrapper);
+      if (this.addon.bootstrap) {
+        if (this.addon.active) {
+          XPIProvider.callBootstrapMethod(this.addon, file, "startup",
+                                          reason, extraParams);
+        } else {
+          // XXX this makes it dangerous to do some things in onInstallEnded
+          // listeners because important cleanup hasn't been done yet
+          XPIProvider.unloadBootstrapScope(this.addon.id);
+        }
+      }
+      recordAddonTelemetry(this.addon);
 
-        if (this.addon.bootstrap) {
-          if (this.addon.active) {
-            XPIProvider.callBootstrapMethod(this.addon, file, "startup",
-                                            reason, extraParams);
-          } else {
-            // XXX this makes it dangerous to do some things in onInstallEnded
-            // listeners because important cleanup hasn't been done yet
-            XPIProvider.unloadBootstrapScope(this.addon.id);
-          }
-        }
-        recordAddonTelemetry(this.addon);
-
-        // Notify providers that a new theme has been enabled.
-        if (isTheme(this.addon.type) && this.addon.active)
-          AddonManagerPrivate.notifyAddonChanged(this.addon.id, this.addon.type, requiresRestart);
-      }
+      // Notify providers that a new theme has been enabled.
+      if (isTheme(this.addon.type) && this.addon.active)
+        AddonManagerPrivate.notifyAddonChanged(this.addon.id, this.addon.type);
     })().catch((e) => {
       logger.warn(`Failed to install ${this.file.path} from ${this.sourceURI.spec} to ${stagedAddon.path}`, e);
 
       if (stagedAddon.exists())
         recursiveRemove(stagedAddon);
       this.state = AddonManager.STATE_INSTALL_FAILED;
       this.error = AddonManager.ERROR_FILE_ACCESS;
       XPIProvider.removeActiveInstall(this);
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -3751,21 +3751,18 @@ var XPIProvider = {
   /**
    * Called when a new add-on has been enabled when only one add-on of that type
    * can be enabled.
    *
    * @param  aId
    *         The ID of the newly enabled add-on
    * @param  aType
    *         The type of the newly enabled add-on
-   * @param  aPendingRestart
-   *         true if the newly enabled add-on will only become enabled after a
-   *         restart
    */
-  addonChanged(aId, aType, aPendingRestart) {
+  addonChanged(aId, aType) {
     // We only care about themes in this provider
     if (!isTheme(aType))
       return;
 
     if (!aId) {
       // Fallback to the default theme when no theme was enabled
       this.enableDefaultTheme();
       return;
@@ -3777,48 +3774,38 @@ var XPIProvider = {
     let newSkin = this.defaultSkin;
     let addons = XPIDatabase.getAddonsByType("theme", "webextension-theme");
     for (let theme of addons) {
       if (!theme.visible)
         return;
       let isChangedAddon = (theme.id == aId);
       if (isWebExtension(theme.type)) {
         if (!isChangedAddon)
-          this.updateAddonDisabledState(theme, true, undefined, aPendingRestart);
+          this.updateAddonDisabledState(theme, true, undefined);
       } else if (isChangedAddon) {
         newSkin = theme.internalName;
       } else if (!theme.userDisabled && !theme.pendingUninstall) {
         previousTheme = theme;
       }
     }
 
-    if (aPendingRestart) {
-      Services.prefs.setBoolPref(PREF_SKIN_SWITCHPENDING, true);
-      Services.prefs.setCharPref(PREF_SKIN_TO_SELECT, newSkin);
-    } else if (newSkin == this.currentSkin) {
-      try {
-        Services.prefs.clearUserPref(PREF_SKIN_SWITCHPENDING);
-      } catch (e) { }
-      try {
-        Services.prefs.clearUserPref(PREF_SKIN_TO_SELECT);
-      } catch (e) { }
-    } else {
+    if (newSkin != this.currentSkin) {
       Services.prefs.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN, newSkin);
       this.currentSkin = newSkin;
     }
     this.selectedSkin = newSkin;
 
     // Flush the preferences to disk so they don't get out of sync with the
     // database
     Services.prefs.savePrefFile(null);
 
     // Mark the previous theme as disabled. This won't cause recursion since
     // only enabled calls notifyAddonChanged.
     if (previousTheme)
-      this.updateAddonDisabledState(previousTheme, true, undefined, aPendingRestart);
+      this.updateAddonDisabledState(previousTheme, true, undefined);
   },
 
   /**
    * Update the appDisabled property for all add-ons.
    */
   updateAddonAppDisabledStates() {
     let addons = XPIDatabase.getAddons();
     for (let addon of addons) {
@@ -3930,166 +3917,16 @@ var XPIProvider = {
       case PREF_ALLOW_LEGACY:
         this.updateAddonAppDisabledStates();
         break;
       }
     }
   },
 
   /**
-   * Tests whether enabling an add-on will require a restart.
-   *
-   * @param  aAddon
-   *         The add-on to test
-   * @return true if the operation requires a restart
-   */
-  enableRequiresRestart(aAddon) {
-    // If the platform couldn't have activated extensions then we can make
-    // changes without any restart.
-    if (!this.extensionsActive)
-      return false;
-
-    // If the application is in safe mode then any change can be made without
-    // restarting
-    if (Services.appinfo.inSafeMode)
-      return false;
-
-    // Anything that is active is already enabled
-    if (aAddon.active)
-      return false;
-
-    if (isTheme(aAddon.type)) {
-      if (isWebExtension(aAddon.type)) {
-        // Enabling a WebExtension type theme requires a restart ONLY when the
-        // theme-to-be-disabled requires a restart.
-        let theme = XPIDatabase.getVisibleAddonForInternalName(this.currentSkin);
-        return !theme || this.disableRequiresRestart(theme);
-      }
-
-      // If the theme is already the theme in use then no restart is necessary.
-      // This covers the case where the default theme is in use but a
-      // lightweight theme is considered active.
-      return aAddon.internalName != this.currentSkin;
-    }
-
-    return !aAddon.bootstrap;
-  },
-
-  /**
-   * Tests whether disabling an add-on will require a restart.
-   *
-   * @param  aAddon
-   *         The add-on to test
-   * @return true if the operation requires a restart
-   */
-  disableRequiresRestart(aAddon) {
-    // If the platform couldn't have activated up extensions then we can make
-    // changes without any restart.
-    if (!this.extensionsActive)
-      return false;
-
-    // If the application is in safe mode then any change can be made without
-    // restarting
-    if (Services.appinfo.inSafeMode)
-      return false;
-
-    // Anything that isn't active is already disabled
-    if (!aAddon.active)
-      return false;
-
-    if (aAddon.type == "theme") {
-      // Non-default themes always require a restart to disable since it will
-      // be switching from one theme to another or to the default theme and a
-      // lightweight theme.
-      if (aAddon.internalName != this.defaultSkin)
-        return true;
-
-      // The default theme requires a restart to disable if we are in the
-      // process of switching to a different theme. Note that this makes the
-      // disabled flag of operationsRequiringRestart incorrect for the default
-      // theme (it will be false most of the time). Bug 520124 would be required
-      // to fix it. For the UI this isn't a problem since we never try to
-      // disable or uninstall the default theme.
-      return this.selectedSkin != this.currentSkin;
-    }
-
-    return !aAddon.bootstrap;
-  },
-
-  /**
-   * Tests whether installing an add-on will require a restart.
-   *
-   * @param  aAddon
-   *         The add-on to test
-   * @return true if the operation requires a restart
-   */
-  installRequiresRestart(aAddon) {
-    // If the platform couldn't have activated up extensions then we can make
-    // changes without any restart.
-    if (!this.extensionsActive)
-      return false;
-
-    // If the application is in safe mode then any change can be made without
-    // restarting
-    if (Services.appinfo.inSafeMode)
-      return false;
-
-    // Add-ons that are already installed don't require a restart to install.
-    // This wouldn't normally be called for an already installed add-on (except
-    // for forming the operationsRequiringRestart flags) so is really here as
-    // a safety measure.
-    if (aAddon.inDatabase)
-      return false;
-
-    // If we have an AddonInstall for this add-on then we can see if there is
-    // an existing installed add-on with the same ID
-    if ("_install" in aAddon && aAddon._install) {
-      // If there is an existing installed add-on and uninstalling it would
-      // require a restart then installing the update will also require a
-      // restart
-      let existingAddon = aAddon._install.existingAddon;
-      if (existingAddon && this.uninstallRequiresRestart(existingAddon))
-        return true;
-    }
-
-    // If the add-on is not going to be active after installation then it
-    // doesn't require a restart to install.
-    if (aAddon.disabled)
-      return false;
-
-    // Themes will require a restart (even if dynamic switching is enabled due
-    // to some caching issues) and non-bootstrapped add-ons will require a
-    // restart
-    return aAddon.type == "theme" || !aAddon.bootstrap;
-  },
-
-  /**
-   * Tests whether uninstalling an add-on will require a restart.
-   *
-   * @param  aAddon
-   *         The add-on to test
-   * @return true if the operation requires a restart
-   */
-  uninstallRequiresRestart(aAddon) {
-    // If the platform couldn't have activated up extensions then we can make
-    // changes without any restart.
-    if (!this.extensionsActive)
-      return false;
-
-    // If the application is in safe mode then any change can be made without
-    // restarting
-    if (Services.appinfo.inSafeMode)
-      return false;
-
-    // If the add-on can be disabled without a restart then it can also be
-    // uninstalled without a restart
-    return this.disableRequiresRestart(aAddon);
-  },
-
-  /**
    * Loads a bootstrapped add-on's bootstrap.js into a sandbox and the reason
    * values as constants in the scope. This will also add information about the
    * add-on to the bootstrappedAddons dictionary and notify the crash reporter
    * that new add-ons have been loaded.
    *
    * @param  aId
    *         The add-on's ID
    * @param  aFile
@@ -4341,26 +4178,23 @@ var XPIProvider = {
    * @param  aAddon
    *         The DBAddonInternal to update
    * @param  aUserDisabled
    *         Value for the userDisabled property. If undefined the value will
    *         not change
    * @param  aSoftDisabled
    *         Value for the softDisabled property. If undefined the value will
    *         not change. If true this will force userDisabled to be true
-   * @param  aPendingRestart
-   *         If the addon is updated whilst the disabled state of another non-
-   *         restartless addon is also set, we need to carry that forward.
    * @return a tri-state indicating the action taken for the add-on:
    *           - undefined: The add-on did not change state
    *           - true: The add-on because disabled
    *           - false: The add-on became enabled
    * @throws if addon is not a DBAddonInternal
    */
-  updateAddonDisabledState(aAddon, aUserDisabled, aSoftDisabled, aPendingRestart = false) {
+  updateAddonDisabledState(aAddon, aUserDisabled, aSoftDisabled) {
     if (!(aAddon.inDatabase))
       throw new Error("Can only update addon states for installed addons.");
     if (aUserDisabled !== undefined && aSoftDisabled !== undefined) {
       throw new Error("Cannot change userDisabled and softDisabled at the " +
                       "same time");
     }
 
     if (aUserDisabled === undefined) {
@@ -4422,48 +4256,42 @@ var XPIProvider = {
       logger.warn("No XPIState for ${id} in ${location}", aAddon);
     }
 
     // Have we just gone back to the current state?
     if (isDisabled != aAddon.active) {
       AddonManagerPrivate.callAddonListeners("onOperationCancelled", wrapper);
     } else {
       if (isDisabled) {
-        var needsRestart = aPendingRestart || this.disableRequiresRestart(aAddon);
-        AddonManagerPrivate.callAddonListeners("onDisabling", wrapper,
-                                               needsRestart);
+        AddonManagerPrivate.callAddonListeners("onDisabling", wrapper, false);
       } else {
-        needsRestart = this.enableRequiresRestart(aAddon);
-        AddonManagerPrivate.callAddonListeners("onEnabling", wrapper,
-                                               needsRestart);
+        AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, false);
       }
 
-      if (!needsRestart) {
-        XPIDatabase.updateAddonActive(aAddon, !isDisabled);
-
-        if (isDisabled) {
-          if (aAddon.bootstrap && this.activeAddons.has(aAddon.id)) {
-            this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown",
-                                     BOOTSTRAP_REASONS.ADDON_DISABLE);
-            this.unloadBootstrapScope(aAddon.id);
-          }
-          AddonManagerPrivate.callAddonListeners("onDisabled", wrapper);
-        } else {
-          if (aAddon.bootstrap) {
-            this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "startup",
-                                     BOOTSTRAP_REASONS.ADDON_ENABLE);
-          }
-          AddonManagerPrivate.callAddonListeners("onEnabled", wrapper);
+      XPIDatabase.updateAddonActive(aAddon, !isDisabled);
+
+      if (isDisabled) {
+        if (aAddon.bootstrap && this.activeAddons.has(aAddon.id)) {
+          this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown",
+                                   BOOTSTRAP_REASONS.ADDON_DISABLE);
+          this.unloadBootstrapScope(aAddon.id);
         }
+        AddonManagerPrivate.callAddonListeners("onDisabled", wrapper);
+      } else {
+        if (aAddon.bootstrap) {
+          this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "startup",
+                                   BOOTSTRAP_REASONS.ADDON_ENABLE);
+        }
+        AddonManagerPrivate.callAddonListeners("onEnabled", wrapper);
       }
     }
 
     // Notify any other providers that a new theme has been enabled
     if (isTheme(aAddon.type) && !isDisabled) {
-      AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type, needsRestart);
+      AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type);
 
       if (xpiState) {
         xpiState.syncWithDB(aAddon);
         XPIStates.save();
       }
     }
 
     return isDisabled;
@@ -4471,36 +4299,33 @@ var XPIProvider = {
 
   /**
    * Uninstalls an add-on, immediately if possible or marks it as pending
    * uninstall if not.
    *
    * @param  aAddon
    *         The DBAddonInternal to uninstall
    * @param  aForcePending
-   *         Force this addon into the pending uninstall state, even if
-   *         it isn't marked as requiring a restart (used e.g. while the
-   *         add-on manager is open and offering an "undo" button)
+   *         Force this addon into the pending uninstall state (used
+   *         e.g. while the add-on manager is open and offering an
+   *         "undo" button)
    * @throws if the addon cannot be uninstalled because it is in an install
    *         location that does not allow it
    */
   uninstallAddon(aAddon, aForcePending) {
     if (!(aAddon.inDatabase))
       throw new Error("Cannot uninstall addon " + aAddon.id + " because it is not installed");
 
     if (aAddon._installLocation.locked)
       throw new Error("Cannot uninstall addon " + aAddon.id
           + " from locked install location " + aAddon._installLocation.name);
 
-    // Inactive add-ons don't require a restart to uninstall
-    let requiresRestart = this.uninstallRequiresRestart(aAddon);
-
     // if makePending is true, we don't actually apply the uninstall,
     // we just mark the addon as having a pending uninstall
-    let makePending = aForcePending || requiresRestart;
+    let makePending = aForcePending;
 
     if (makePending && aAddon.pendingUninstall)
       throw new Error("Add-on is already marked to be uninstalled");
 
     aAddon._hasResourceCache.clear();
 
     if (aAddon._updateCheck) {
       logger.debug("Cancel in-progress update check for " + aAddon.id);
@@ -4536,22 +4361,18 @@ var XPIProvider = {
     // If the add-on is not visible then there is no need to notify listeners.
     if (!aAddon.visible)
       return;
 
     let wrapper = aAddon.wrapper;
 
     // If the add-on wasn't already pending uninstall then notify listeners.
     if (!wasPending) {
-      // Passing makePending as the requiresRestart parameter is a little
-      // 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);
+                                             !!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);
@@ -4580,48 +4401,46 @@ var XPIProvider = {
 
       if (existingAddon) {
         XPIDatabase.getAddonInLocation(aAddon.id, existingAddon.location.name, existing => {
           XPIDatabase.makeAddonVisible(existing);
 
           let wrappedAddon = existing.wrapper;
           AddonManagerPrivate.callAddonListeners("onInstalling", wrappedAddon, false);
 
-          if (!existing.disabled && !XPIProvider.enableRequiresRestart(existing)) {
+          if (!existing.disabled) {
             XPIDatabase.updateAddonActive(existing, true);
           }
 
           if (aAddon.bootstrap) {
             let method = callUpdate ? "update" : "install";
             XPIProvider.callBootstrapMethod(existing, existing._sourceBundle,
                                             method, reason);
 
             if (existing.active) {
               XPIProvider.callBootstrapMethod(existing, existing._sourceBundle,
                                               "startup", reason);
             } else {
               XPIProvider.unloadBootstrapScope(existing.id);
             }
           }
 
-          // We always send onInstalled even if a restart is required to enable
-          // the revealed add-on
           AddonManagerPrivate.callAddonListeners("onInstalled", wrappedAddon);
         });
       }
-    } else if (aAddon.bootstrap && aAddon.active && !this.disableRequiresRestart(aAddon)) {
+    } else if (aAddon.bootstrap && aAddon.active) {
       this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown", reason);
       XPIStates.disableAddon(aAddon.id);
       this.unloadBootstrapScope(aAddon.id);
       XPIDatabase.updateAddonActive(aAddon, false);
     }
 
     // Notify any other providers that a new theme has been enabled
     if (isTheme(aAddon.type) && aAddon.active)
-      AddonManagerPrivate.notifyAddonChanged(null, aAddon.type, requiresRestart);
+      AddonManagerPrivate.notifyAddonChanged(null, aAddon.type);
   },
 
   /**
    * Cancels the pending uninstall of an add-on.
    *
    * @param  aAddon
    *         The DBAddonInternal to cancel uninstall for
    */
@@ -4645,17 +4464,17 @@ var XPIProvider = {
     XPIStates.save();
 
     Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
 
     // TODO hide hidden add-ons (bug 557710)
     let wrapper = aAddon.wrapper;
     AddonManagerPrivate.callAddonListeners("onOperationCancelled", wrapper);
 
-    if (aAddon.bootstrap && !aAddon.disabled && !this.enableRequiresRestart(aAddon)) {
+    if (aAddon.bootstrap && !aAddon.disabled) {
       this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "startup",
                                BOOTSTRAP_REASONS.ADDON_INSTALL);
       XPIDatabase.updateAddonActive(aAddon, true);
     }
 
     // Notify any other providers that this theme is now enabled again.
     if (isTheme(aAddon.type) && aAddon.active)
       AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type, false);
@@ -5356,28 +5175,17 @@ AddonWrapper.prototype = {
 
     if (addon.pendingUpgrade)
       pending |= AddonManager.PENDING_UPGRADE;
 
     return pending;
   },
 
   get operationsRequiringRestart() {
-    let addon = addonFor(this);
-    let ops = 0;
-    if (XPIProvider.installRequiresRestart(addon))
-      ops |= AddonManager.OP_NEEDS_RESTART_INSTALL;
-    if (XPIProvider.uninstallRequiresRestart(addon))
-      ops |= AddonManager.OP_NEEDS_RESTART_UNINSTALL;
-    if (XPIProvider.enableRequiresRestart(addon))
-      ops |= AddonManager.OP_NEEDS_RESTART_ENABLE;
-    if (XPIProvider.disableRequiresRestart(addon))
-      ops |= AddonManager.OP_NEEDS_RESTART_DISABLE;
-
-    return ops;
+    return 0;
   },
 
   get isDebuggable() {
     return this.isActive && addonFor(this).bootstrap;
   },
 
   get permissions() {
     return addonFor(this).permissions();
@@ -6415,21 +6223,16 @@ class SystemAddonInstallLocation extends
   }
 
   isValidAddon(aAddon) {
     if (aAddon.appDisabled) {
       logger.warn(`System add-on ${aAddon.id} isn't compatible with the application.`);
       return false;
     }
 
-    if (!aAddon.bootstrap) {
-      logger.warn(`System add-on ${aAddon.id} isn't restartless.`);
-      return false;
-    }
-
     return true;
   }
 
   /**
    * Tests whether the loaded add-on information matches what is expected.
    */
   isValid(aAddons) {
     for (let id of Object.keys(this._addonSet.addons)) {
--- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
+++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
@@ -178,29 +178,16 @@ function DBAddonInternal(aLoaded) {
 
   this._key = this.location + ":" + this.id;
 
   if (!aLoaded._sourceBundle) {
     throw new Error("Expected passed argument to contain a path");
   }
 
   this._sourceBundle = aLoaded._sourceBundle;
-
-  XPCOMUtils.defineLazyGetter(this, "pendingUpgrade", function() {
-      for (let install of XPIProvider.installs) {
-        if (install.state == AddonManager.STATE_INSTALLED &&
-            !(install.addon.inDatabase) &&
-            install.addon.id == this.id &&
-            install.installLocation == this._installLocation) {
-          delete this.pendingUpgrade;
-          return this.pendingUpgrade = install.addon;
-        }
-      }
-      return null;
-    });
 }
 
 DBAddonInternal.prototype = Object.create(AddonInternal.prototype);
 Object.assign(DBAddonInternal.prototype, {
   applyCompatibilityUpdate(aUpdate, aSyncCompatibility) {
     let wasCompatible = this.isCompatible;
 
     this.targetApplications.forEach(function(aTargetApp) {
@@ -826,20 +813,17 @@ this.XPIDatabase = {
    * @param  aTypes
    *         The types of add-ons to retrieve or null to get all types
    * @param  aCallback
    *         A callback to pass the array of DBAddonInternal to
    */
   getVisibleAddonsWithPendingOperations(aTypes, aCallback) {
     this.getAddonList(
         aAddon => (aAddon.visible &&
-                   (aAddon.pendingUninstall ||
-                    // Logic here is tricky. If we're active but disabled,
-                    // we're pending disable; !active && !disabled, we're pending enable
-                    (aAddon.active == aAddon.disabled)) &&
+                   aAddon.pendingUninstall &&
                    (!aTypes || (aTypes.length == 0) || (aTypes.indexOf(aAddon.type) > -1))),
         aCallback);
   },
 
   /**
    * Asynchronously get an add-on by its Sync GUID.
    *
    * @param  aGUID
@@ -1165,17 +1149,17 @@ this.XPIDatabaseReconcile = {
       if (aNewAddon.id != aId) {
         throw new Error("Invalid addon ID: expected addon ID " + aId +
                         ", found " + aNewAddon.id + " in manifest");
       }
     } catch (e) {
       logger.warn("addMetadata: Add-on " + aId + " is invalid", e);
 
       // Remove the invalid add-on from the install location if the install
-      // location isn't locked, no restart will be necessary
+      // location isn't locked
       if (aInstallLocation.isLinkedAddon(aId))
         logger.warn("Not uninstalling invalid item because it is a proxy file");
       else if (aInstallLocation.locked)
         logger.warn("Could not uninstall invalid item from locked install location");
       else
         aInstallLocation.uninstallAddon(aId);
       return null;
     }
@@ -1223,19 +1207,18 @@ this.XPIDatabaseReconcile = {
    */
   removeMetadata(aOldAddon) {
     // This add-on has disappeared
     logger.debug("Add-on " + aOldAddon.id + " removed from " + aOldAddon.location);
     XPIDatabase.removeAddonMetadata(aOldAddon);
   },
 
   /**
-   * Updates an add-on's metadata and determines if a restart of the
-   * application is necessary. This is called when either the add-on's
-   * install directory path or last modified time has changed.
+   * Updates an add-on's metadata and determines. This is called when either the
+   * add-on's install directory path or last modified time has changed.
    *
    * @param  aInstallLocation
    *         The install location containing the add-on
    * @param  aOldAddon
    *         The AddonInternal as it appeared the last time the application
    *         ran
    * @param  aAddonState
    *         The new state of the add-on
@@ -1248,22 +1231,16 @@ this.XPIDatabaseReconcile = {
     logger.debug("Add-on " + aOldAddon.id + " modified in " + aInstallLocation.name);
 
     try {
       // If there isn't an updated install manifest for this add-on then load it.
       if (!aNewAddon) {
         let file = new nsIFile(aAddonState.path);
         aNewAddon = syncLoadManifestFromFile(file, aInstallLocation);
 
-        // Carry over any pendingUninstall state to add-ons modified directly
-        // in the profile. This is important when the attempt to remove the
-        // add-on in processPendingFileChanges failed and caused an mtime
-        // change to the add-ons files.
-        aNewAddon.pendingUninstall = aOldAddon.pendingUninstall;
-
         aNewAddon.updateBlocklistState({oldAddon: aOldAddon});
       }
 
       // The ID in the manifest that was loaded must match the ID of the old
       // add-on.
       if (aNewAddon.id != aOldAddon.id)
         throw new Error("Incorrect id in install manifest for existing add-on " + aOldAddon.id);
     } catch (e) {