Bug 1363925: Part 5 - Move startup update check logic to XPIInstall. r?aswan draft
authorKris Maglione <maglione.k@gmail.com>
Sat, 21 Apr 2018 19:02:44 -0700
changeset 786315 df0246cd64bfc4a9e5a53fa47d83830186c37a45
parent 786314 2d3d718089254e720497ef2a6392b7848ebcd4df
child 786316 d0bde577daca1cc4091f02e6c678ef72a42bfb68
push id107433
push usermaglione.k@gmail.com
push dateSun, 22 Apr 2018 22:24:27 +0000
reviewersaswan
bugs1363925
milestone61.0a1
Bug 1363925: Part 5 - Move startup update check logic to XPIInstall. r?aswan MozReview-Commit-ID: EErgjvkAQyY
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
@@ -39,16 +39,19 @@ ChromeUtils.defineModuleGetter(this, "OS
                                "resource://gre/modules/osfile.jsm");
 ChromeUtils.defineModuleGetter(this, "ProductAddonChecker",
                                "resource://gre/modules/addons/ProductAddonChecker.jsm");
 ChromeUtils.defineModuleGetter(this, "UpdateUtils",
                                "resource://gre/modules/UpdateUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "ZipUtils",
                                "resource://gre/modules/ZipUtils.jsm");
 
+ChromeUtils.defineModuleGetter(this, "clearTimeout", "resource://gre/modules/Timer.jsm");
+ChromeUtils.defineModuleGetter(this, "setTimeout", "resource://gre/modules/Timer.jsm");
+
 const {nsIBlocklistService} = Ci;
 
 const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile",
                                        "initWithPath");
 
 const BinaryOutputStream = Components.Constructor("@mozilla.org/binaryoutputstream;1",
                                                   "nsIBinaryOutputStream", "setOutputStream");
 const CryptoHash = Components.Constructor("@mozilla.org/security/hash;1",
@@ -4226,11 +4229,161 @@ var XPIInstall = {
       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);
   },
 
+  /**
+   * If the application has been upgraded and there are add-ons outside the
+   * application directory then we may need to synchronize compatibility
+   * information but only if the mismatch UI isn't disabled.
+   *
+   * @returns null if no update check is needed, otherwise an array of add-on
+   *          IDs to check for updates.
+   */
+  shouldForceUpdateCheck(aAppChanged) {
+    AddonManagerPrivate.recordSimpleMeasure("XPIDB_metadata_age", AddonRepository.metadataAge());
+
+    let startupChanges = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_DISABLED);
+    logger.debug("shouldForceUpdateCheck startupChanges: " + startupChanges.toSource());
+    AddonManagerPrivate.recordSimpleMeasure("XPIDB_startup_disabled", startupChanges.length);
+
+    let forceUpdate = [];
+    if (startupChanges.length > 0) {
+    let addons = XPIDatabase.getAddons();
+      for (let addon of addons) {
+        if ((startupChanges.includes(addon.id)) &&
+            (addon.permissions() & AddonManager.PERM_CAN_UPGRADE) &&
+            (!addon.isCompatible || XPIProvider.isDisabledLegacy(addon))) {
+          logger.debug("shouldForceUpdateCheck: can upgrade disabled add-on " + addon.id);
+          forceUpdate.push(addon.id);
+        }
+      }
+    }
+
+    if (forceUpdate.length > 0) {
+      return forceUpdate;
+    }
+
+    return null;
+  },
+
+  /**
+   * Perform startup check for updates of legacy extensions.
+   * This runs during startup when an app update has made some add-ons
+   * incompatible and legacy add-on support is diasabled.
+   * In this case, we just do a quiet update check.
+   *
+   * @param {Array<string>} ids The ids of the addons to check for updates.
+   *
+   * @returns {Set<string>} The ids of any addons that were updated.  These
+   *                        addons will have been started by the update
+   *                        process so they should not be started by the
+   *                        regular bootstrap startup code.
+   */
+  noLegacyStartupCheck(ids) {
+    let started = new Set();
+    const DIALOG = "chrome://mozapps/content/extensions/update.html";
+    const SHOW_DIALOG_DELAY = 1000;
+    const SHOW_CANCEL_DELAY = 30000;
+
+    // Keep track of a value between 0 and 1 indicating the progress
+    // for each addon.  Just combine these linearly into a single
+    // value for the progress bar in the update dialog.
+    let updateProgress = val => {};
+    let progressByID = new Map();
+    function setProgress(id, val) {
+      progressByID.set(id, val);
+      updateProgress(Array.from(progressByID.values()).reduce((a, b) => a + b) / progressByID.size);
+    }
+
+    // Do an update check for one addon and try to apply the update if
+    // there is one.  Progress for the check is arbitrarily defined as
+    // 10% done when the update check is done, between 10-90% during the
+    // download, then 100% when the update has been installed.
+    let checkOne = async (id) => {
+      logger.debug(`Checking for updates to disabled addon ${id}\n`);
+
+      setProgress(id, 0);
+
+      let addon = await AddonManager.getAddonByID(id);
+      let install = await new Promise(resolve => addon.findUpdates({
+        onUpdateFinished() { resolve(null); },
+        onUpdateAvailable(addon, install) { resolve(install); },
+      }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED));
+
+      if (!install) {
+        setProgress(id, 1);
+        return;
+      }
+
+      setProgress(id, 0.1);
+
+      let installPromise = new Promise(resolve => {
+        let finish = () => {
+          setProgress(id, 1);
+          resolve();
+        };
+        install.addListener({
+          onDownloadProgress() {
+            if (install.maxProgress != 0) {
+              setProgress(id, 0.1 + 0.8 * install.progress / install.maxProgress);
+            }
+          },
+          onDownloadEnded() {
+            setProgress(id, 0.9);
+          },
+          onDownloadFailed: finish,
+          onInstallFailed: finish,
+          onInstallEnded() {
+            started.add(id);
+            AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED, id);
+            finish();
+          },
+        });
+      });
+      install.install();
+      await installPromise;
+    };
+
+    let finished = false;
+    Promise.all(ids.map(checkOne)).then(() => { finished = true; });
+
+    let window;
+    let timer = setTimeout(() => {
+      const FEATURES = "chrome,dialog,centerscreen,scrollbars=no";
+      window = Services.ww.openWindow(null, DIALOG, "", FEATURES, null);
+
+      let cancelDiv;
+      window.addEventListener("DOMContentLoaded", e => {
+        let progress = window.document.getElementById("progress");
+        updateProgress = val => { progress.value = val; };
+
+        cancelDiv = window.document.getElementById("cancel-section");
+        cancelDiv.setAttribute("style", "display: none;");
+
+        let cancelBtn = window.document.getElementById("cancel-btn");
+        cancelBtn.addEventListener("click", e => { finished = true; });
+      });
+
+      timer = setTimeout(() => {
+        cancelDiv.removeAttribute("style");
+        window.sizeToContent();
+      }, SHOW_CANCEL_DELAY - SHOW_DIALOG_DELAY);
+    }, SHOW_DIALOG_DELAY);
+
+    Services.tm.spinEventLoopUntil(() => finished);
+
+    clearTimeout(timer);
+    if (window) {
+      window.close();
+    }
+
+    return started;
+  },
+
+
   MutableDirectoryInstallLocation,
   SystemAddonInstallLocation,
 };
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -20,18 +20,16 @@ XPCOMUtils.defineLazyModuleGetters(this,
   Langpack: "resource://gre/modules/Extension.jsm",
   LightweightThemeManager: "resource://gre/modules/LightweightThemeManager.jsm",
   FileUtils: "resource://gre/modules/FileUtils.jsm",
   PermissionsUtils: "resource://gre/modules/PermissionsUtils.jsm",
   OS: "resource://gre/modules/osfile.jsm",
   ConsoleAPI: "resource://gre/modules/Console.jsm",
   JSONFile: "resource://gre/modules/JSONFile.jsm",
   LegacyExtensionsUtils: "resource://gre/modules/LegacyExtensionsUtils.jsm",
-  setTimeout: "resource://gre/modules/Timer.jsm",
-  clearTimeout: "resource://gre/modules/Timer.jsm",
 
   UpdateChecker: "resource://gre/modules/addons/XPIInstall.jsm",
   XPIInstall: "resource://gre/modules/addons/XPIInstall.jsm",
   verifyBundleSignedState: "resource://gre/modules/addons/XPIInstall.jsm",
 });
 
 const {nsIBlocklistService} = Ci;
 
@@ -1719,19 +1717,19 @@ var XPIProvider = {
       let flushCaches = this.checkForChanges(aAppChanged, aOldAppVersion,
                                              aOldPlatformVersion);
 
       AddonManagerPrivate.markProviderSafe(this);
 
       if (aAppChanged && !this.allAppGlobal &&
           Services.prefs.getBoolPref(PREF_EM_SHOW_MISMATCH_UI, true) &&
           AddonManager.updateEnabled) {
-        let addonsToUpdate = this.shouldForceUpdateCheck(aAppChanged);
+        let addonsToUpdate = XPIInstall.shouldForceUpdateCheck(aAppChanged);
         if (addonsToUpdate) {
-          this.noLegacyStartupCheck(addonsToUpdate);
+          XPIInstall.noLegacyStartupCheck(addonsToUpdate);
           flushCaches = true;
         }
       }
 
       if (flushCaches) {
         Services.obs.notifyObservers(null, "startupcache-invalidate");
         // UI displayed early in startup (like the compatibility UI) may have
         // caused us to cache parts of the skin or locale in memory. These must
@@ -1954,165 +1952,16 @@ var XPIProvider = {
           let method = callUpdate ? "update" : "install";
           this.callBootstrapMethod(newAddon, file, method, reason, data);
         }
       }
     }
   },
 
   /**
-   * If the application has been upgraded and there are add-ons outside the
-   * application directory then we may need to synchronize compatibility
-   * information but only if the mismatch UI isn't disabled.
-   *
-   * @returns null if no update check is needed, otherwise an array of add-on
-   *          IDs to check for updates.
-   */
-  shouldForceUpdateCheck(aAppChanged) {
-    AddonManagerPrivate.recordSimpleMeasure("XPIDB_metadata_age", AddonRepository.metadataAge());
-
-    let startupChanges = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_DISABLED);
-    logger.debug("shouldForceUpdateCheck startupChanges: " + startupChanges.toSource());
-    AddonManagerPrivate.recordSimpleMeasure("XPIDB_startup_disabled", startupChanges.length);
-
-    let forceUpdate = [];
-    if (startupChanges.length > 0) {
-    let addons = XPIDatabase.getAddons();
-      for (let addon of addons) {
-        if ((startupChanges.includes(addon.id)) &&
-            (addon.permissions() & AddonManager.PERM_CAN_UPGRADE) &&
-            (!addon.isCompatible || isDisabledLegacy(addon))) {
-          logger.debug("shouldForceUpdateCheck: can upgrade disabled add-on " + addon.id);
-          forceUpdate.push(addon.id);
-        }
-      }
-    }
-
-    if (forceUpdate.length > 0) {
-      return forceUpdate;
-    }
-
-    return null;
-  },
-
-  /**
-   * Perform startup check for updates of legacy extensions.
-   * This runs during startup when an app update has made some add-ons
-   * incompatible and legacy add-on support is diasabled.
-   * In this case, we just do a quiet update check.
-   *
-   * @param {Array<string>} ids The ids of the addons to check for updates.
-   *
-   * @returns {Set<string>} The ids of any addons that were updated.  These
-   *                        addons will have been started by the update
-   *                        process so they should not be started by the
-   *                        regular bootstrap startup code.
-   */
-  noLegacyStartupCheck(ids) {
-    let started = new Set();
-    const DIALOG = "chrome://mozapps/content/extensions/update.html";
-    const SHOW_DIALOG_DELAY = 1000;
-    const SHOW_CANCEL_DELAY = 30000;
-
-    // Keep track of a value between 0 and 1 indicating the progress
-    // for each addon.  Just combine these linearly into a single
-    // value for the progress bar in the update dialog.
-    let updateProgress = val => {};
-    let progressByID = new Map();
-    function setProgress(id, val) {
-      progressByID.set(id, val);
-      updateProgress(Array.from(progressByID.values()).reduce((a, b) => a + b) / progressByID.size);
-    }
-
-    // Do an update check for one addon and try to apply the update if
-    // there is one.  Progress for the check is arbitrarily defined as
-    // 10% done when the update check is done, between 10-90% during the
-    // download, then 100% when the update has been installed.
-    let checkOne = async (id) => {
-      logger.debug(`Checking for updates to disabled addon ${id}\n`);
-
-      setProgress(id, 0);
-
-      let addon = await AddonManager.getAddonByID(id);
-      let install = await new Promise(resolve => addon.findUpdates({
-        onUpdateFinished() { resolve(null); },
-        onUpdateAvailable(addon, install) { resolve(install); },
-      }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED));
-
-      if (!install) {
-        setProgress(id, 1);
-        return;
-      }
-
-      setProgress(id, 0.1);
-
-      let installPromise = new Promise(resolve => {
-        let finish = () => {
-          setProgress(id, 1);
-          resolve();
-        };
-        install.addListener({
-          onDownloadProgress() {
-            if (install.maxProgress != 0) {
-              setProgress(id, 0.1 + 0.8 * install.progress / install.maxProgress);
-            }
-          },
-          onDownloadEnded() {
-            setProgress(id, 0.9);
-          },
-          onDownloadFailed: finish,
-          onInstallFailed: finish,
-          onInstallEnded() {
-            started.add(id);
-            AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED, id);
-            finish();
-          },
-        });
-      });
-      install.install();
-      await installPromise;
-    };
-
-    let finished = false;
-    Promise.all(ids.map(checkOne)).then(() => { finished = true; });
-
-    let window;
-    let timer = setTimeout(() => {
-      const FEATURES = "chrome,dialog,centerscreen,scrollbars=no";
-      window = Services.ww.openWindow(null, DIALOG, "", FEATURES, null);
-
-      let cancelDiv;
-      window.addEventListener("DOMContentLoaded", e => {
-        let progress = window.document.getElementById("progress");
-        updateProgress = val => { progress.value = val; };
-
-        cancelDiv = window.document.getElementById("cancel-section");
-        cancelDiv.setAttribute("style", "display: none;");
-
-        let cancelBtn = window.document.getElementById("cancel-btn");
-        cancelBtn.addEventListener("click", e => { finished = true; });
-      });
-
-      timer = setTimeout(() => {
-        cancelDiv.removeAttribute("style");
-        window.sizeToContent();
-      }, SHOW_CANCEL_DELAY - SHOW_DIALOG_DELAY);
-    }, SHOW_DIALOG_DELAY);
-
-    Services.tm.spinEventLoopUntil(() => finished);
-
-    clearTimeout(timer);
-    if (window) {
-      window.close();
-    }
-
-    return started;
-  },
-
-  /**
    * Verifies that all installed add-ons are still correctly signed.
    */
   async verifySignatures() {
     try {
       let addons = await XPIDatabase.getAddonList(a => true);
 
       let changes = {
         enabled: [],
@@ -4756,16 +4605,17 @@ var XPIInternal = {
   SystemAddonInstallLocation,
   TemporaryInstallLocation,
   TEMPORARY_ADDON_SUFFIX,
   TOOLKIT_ID,
   XPI_PERMISSION,
   XPIStates,
   awaitPromise,
   getExternalType,
+  isDisabledLegacy,
   isTheme,
   isUsableAddon,
   isWebExtension,
   mustSign,
   recordAddonTelemetry,
 
   get XPIDatabase() { return gGlobalScope.XPIDatabase; },
 };