Bug 1353194 Streamline the startup extension compatibility check r=kmag
☠☠ backed out by 5310033cd68c ☠ ☠
authorAndrew Swan <aswan@mozilla.com>
Sun, 10 Sep 2017 12:23:45 -0700
changeset 429468 246b6aae81574dc633230f434efd7a06d344d558
parent 429467 e3c293ceedf304cb13840a65b09ce87f9cf8cba4
child 429469 48a86fd5097d92b917eafc7470ed6d9e0ddb0e7a
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag
bugs1353194
milestone57.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 1353194 Streamline the startup extension compatibility check r=kmag Also extend activeAddons records with a started flag to avoid double-starting extensions that are upgraded during the startup check. MozReview-Commit-ID: FPX71Q3lSrw
toolkit/locales/en-US/chrome/mozapps/extensions/update.properties
toolkit/mozapps/extensions/content/update.css
toolkit/mozapps/extensions/content/update.html
toolkit/mozapps/extensions/content/update.js
toolkit/mozapps/extensions/content/update.xul
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/jar.mn
--- a/toolkit/locales/en-US/chrome/mozapps/extensions/update.properties
+++ b/toolkit/locales/en-US/chrome/mozapps/extensions/update.properties
@@ -1,21 +1,17 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-mismatchCheckNow=Check Now
-mismatchCheckNowAccesskey=C
-mismatchDontCheck=Don’t Check
-mismatchDontCheckAccesskey=D
-installButtonText=Install Now
-installButtonTextAccesskey=I
-nextButtonText=Next >
-nextButtonTextAccesskey=N
-cancelButtonText=Cancel
-cancelButtonTextAccesskey=C
-statusPrefix=Finished checking %S
-downloadingPrefix=Downloading: %S
-installingPrefix=Installing: %S
-closeButton=Close
-installErrors=%S was unable to install updates for the following add-ons:
-checkingErrors=%S was unable to check for updates for the following add-ons:
-installErrorItemFormat=%S (%S)
+# LOCALIZATION NOTE (addonUpdateHeader)
+# %S will be replace with the localized name of the application
+addonUpdateTitle=%S Update
+
+# LOCALIZATION NOTE (addonUpdateMessage)
+# %S will be replace with the localized name of the application
+addonUpdateMessage=%S is updating your extensions…
+
+addonUpdateCancelMessage=Still updating. Want to wait?
+
+# LOCALIZATION NOTE (addonUpdateCancelButton)
+# %S will be replace with the localized name of the application
+addonUpdateCancelButton=Stop update and launch %S
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/update.css
@@ -0,0 +1,26 @@
+body {
+  font: message-box;
+  min-width: 480px;
+}
+
+#message {
+  font-size: 14px;
+}
+
+#message, #cancel-section {
+  margin: 10px 5px;
+}
+
+#progress {
+  width: calc(100% - 10px);
+  margin: 0 5px;
+}
+
+#cancel-section {
+  display: flex;
+  justify-content: space-between;
+}
+
+#cancel-message {
+  vertical-align: middle;
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/update.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <script src="update.js"></script>
+    <link rel="stylesheet"  href="chrome://mozapps/content/extensions/update.css">
+  </head>
+  <body>
+    <div>
+      <div id="message"></div>
+
+      <progress id="progress" val="0" max="1"></progress>
+
+      <div id="cancel-section">
+        <span id="cancel-message"></span>
+        <button id="cancel-btn"></button>
+      </div>
+    </div>
+  </body>
+</html>
--- a/toolkit/mozapps/extensions/content/update.js
+++ b/toolkit/mozapps/extensions/content/update.js
@@ -1,633 +1,24 @@
-// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-// This UI is only opened from the Extension Manager when the app is upgraded.
-
 "use strict";
 
-/* exported gAdminDisabledPage, gFinishedPage, gFoundPage, gInstallErrorsPage,
- *          gNoUpdatesPage, gOfflinePage, gUpdatePage */
-
-const PREF_UPDATE_EXTENSIONS_ENABLED            = "extensions.update.enabled";
-const PREF_XPINSTALL_ENABLED                    = "xpinstall.enabled";
-
-// timeout (in milliseconds) to wait for response to the metadata ping
-const METADATA_TIMEOUT    = 30000;
-
-Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate", "resource://gre/modules/AddonManager.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", "resource://gre/modules/addons/AddonRepository.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Log", "resource://gre/modules/Log.jsm");
-var logger = null;
-
-var gUpdateWizard = {
-  // When synchronizing app compatibility info this contains all installed
-  // add-ons. When checking for compatible versions this contains only
-  // incompatible add-ons.
-  addons: [],
-  // Contains a Set of IDs for add-on that were disabled by the application update.
-  affectedAddonIDs: null,
-  // The add-ons that we found updates available for
-  addonsToUpdate: [],
-  shouldSuggestAutoChecking: false,
-  shouldAutoCheck: false,
-  xpinstallEnabled: true,
-  xpinstallLocked: false,
-  // cached AddonInstall entries for add-ons we might want to update,
-  // keyed by add-on ID
-  addonInstalls: new Map(),
-  shuttingDown: false,
-  // Count the add-ons disabled by this update, enabled/disabled by
-  // metadata checks, and upgraded.
-  disabled: 0,
-  metadataEnabled: 0,
-  metadataDisabled: 0,
-  upgraded: 0,
-  upgradeFailed: 0,
-  upgradeDeclined: 0,
-
-  init() {
-    logger = Log.repository.getLogger("addons.update-dialog");
-    // XXX could we pass the addons themselves rather than the IDs?
-    this.affectedAddonIDs = new Set(window.arguments[0]);
-
-    try {
-      this.shouldSuggestAutoChecking =
-        !Services.prefs.getBoolPref(PREF_UPDATE_EXTENSIONS_ENABLED);
-    } catch (e) {
-    }
-
-    try {
-      this.xpinstallEnabled = Services.prefs.getBoolPref(PREF_XPINSTALL_ENABLED);
-      this.xpinstallLocked = Services.prefs.prefIsLocked(PREF_XPINSTALL_ENABLED);
-    } catch (e) {
-    }
-
-    if (Services.io.offline)
-      document.documentElement.currentPage = document.getElementById("offline");
-    else
-      document.documentElement.currentPage = document.getElementById("versioninfo");
-  },
-
-  onWizardFinish: function gUpdateWizard_onWizardFinish() {
-    if (this.shouldSuggestAutoChecking)
-      Services.prefs.setBoolPref(PREF_UPDATE_EXTENSIONS_ENABLED, this.shouldAutoCheck);
-  },
-
-  _setUpButton(aButtonID, aButtonKey, aDisabled) {
-    var strings = document.getElementById("updateStrings");
-    var button = document.documentElement.getButton(aButtonID);
-    if (aButtonKey) {
-      button.label = strings.getString(aButtonKey);
-      try {
-        button.setAttribute("accesskey", strings.getString(aButtonKey + "Accesskey"));
-      } catch (e) {
-      }
-    }
-    button.disabled = aDisabled;
-  },
-
-  setButtonLabels(aBackButton, aBackButtonIsDisabled,
-                             aNextButton, aNextButtonIsDisabled,
-                             aCancelButton, aCancelButtonIsDisabled) {
-    this._setUpButton("back", aBackButton, aBackButtonIsDisabled);
-    this._setUpButton("next", aNextButton, aNextButtonIsDisabled);
-    this._setUpButton("cancel", aCancelButton, aCancelButtonIsDisabled);
-  },
-
-  // Update Errors
-  errorItems: [],
-
-  checkForErrors(aElementIDToShow) {
-    if (this.errorItems.length > 0)
-      document.getElementById(aElementIDToShow).hidden = false;
-  },
-
-  onWizardClose(aEvent) {
-    return this.onWizardCancel();
-  },
-
-  onWizardCancel() {
-    gUpdateWizard.shuttingDown = true;
-    // Allow add-ons to continue downloading and installing
-    // in the background, though some may require a later restart
-    // Pages that are waiting for user input go into the background
-    // on cancel
-    if (gMismatchPage.waiting) {
-      logger.info("Dialog closed in mismatch page");
-      if (gUpdateWizard.addonInstalls.size > 0) {
-        gInstallingPage.startInstalls(
-          Array.from(gUpdateWizard.addonInstalls.values()));
-      }
-      return true;
-    }
-
-    // Pages that do asynchronous things will just keep running and check
-    // gUpdateWizard.shuttingDown to trigger background behaviour
-    if (!gInstallingPage.installing) {
-      logger.info("Dialog closed while waiting for updated compatibility information");
-    } else {
-      logger.info("Dialog closed while downloading and installing updates");
-    }
-    return true;
-  }
-};
-
-var gOfflinePage = {
-  onPageAdvanced() {
-    Services.io.offline = false;
-    return true;
-  },
-
-  toggleOffline() {
-    var nextbtn = document.documentElement.getButton("next");
-    nextbtn.disabled = !nextbtn.disabled;
-  }
-}
-
-// Addon listener to count addons enabled/disabled by metadata checks
-var listener = {
-  onDisabled(aAddon) {
-    gUpdateWizard.affectedAddonIDs.add(aAddon.id);
-    gUpdateWizard.metadataDisabled++;
-  },
-  onEnabled(aAddon) {
-    gUpdateWizard.affectedAddonIDs.delete(aAddon.id);
-    gUpdateWizard.metadataEnabled++;
-  }
-};
+Components.utils.import("resource://gre/modules/Services.jsm");
 
-var gVersionInfoPage = {
-  _completeCount: 0,
-  _totalCount: 0,
-  _versionInfoDone: false,
-  async onPageShow() {
-    gUpdateWizard.setButtonLabels(null, true,
-                                  "nextButtonText", true,
-                                  "cancelButtonText", false);
-
-    gUpdateWizard.disabled = gUpdateWizard.affectedAddonIDs.size;
-
-    // Ensure compatibility overrides are up to date before checking for
-    // individual addon updates.
-    AddonManager.addAddonListener(listener);
-    if (AddonRepository.isMetadataStale()) {
-      // Do the metadata ping, listening for any newly enabled/disabled add-ons.
-      await AddonRepository.repopulateCache(METADATA_TIMEOUT);
-      if (gUpdateWizard.shuttingDown) {
-        logger.debug("repopulateCache completed after dialog closed");
-      }
-    }
-    // Fetch the add-ons that are still affected by this update,
-    // excluding the hotfix add-on.
-    let idlist = Array.from(gUpdateWizard.affectedAddonIDs).filter(
-      a => a.id != AddonManager.hotfixID);
-    if (idlist.length < 1) {
-      gVersionInfoPage.onAllUpdatesFinished();
-      return;
-    }
-
-    logger.debug("Fetching affected addons " + idlist.toSource());
-    let fetchedAddons = await AddonManager.getAddonsByIDs(idlist);
-    // We shouldn't get nulls here, but let's be paranoid...
-    gUpdateWizard.addons = fetchedAddons.filter(a => a);
-    if (gUpdateWizard.addons.length < 1) {
-      gVersionInfoPage.onAllUpdatesFinished();
-      return;
-    }
-
-    gVersionInfoPage._totalCount = gUpdateWizard.addons.length;
-
-    for (let addon of gUpdateWizard.addons) {
-      logger.debug("VersionInfo Finding updates for ${id}", addon);
-      addon.findUpdates(gVersionInfoPage, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED);
-    }
-  },
-
-  onAllUpdatesFinished() {
-    AddonManager.removeAddonListener(listener);
-    AddonManagerPrivate.recordSimpleMeasure("appUpdate_disabled",
-        gUpdateWizard.disabled);
-    AddonManagerPrivate.recordSimpleMeasure("appUpdate_metadata_enabled",
-        gUpdateWizard.metadataEnabled);
-    AddonManagerPrivate.recordSimpleMeasure("appUpdate_metadata_disabled",
-        gUpdateWizard.metadataDisabled);
-    // Record 0 for these here in case we exit early; values will be replaced
-    // later if we actually upgrade any.
-    AddonManagerPrivate.recordSimpleMeasure("appUpdate_upgraded", 0);
-    AddonManagerPrivate.recordSimpleMeasure("appUpdate_upgradeFailed", 0);
-    AddonManagerPrivate.recordSimpleMeasure("appUpdate_upgradeDeclined", 0);
-    // Filter out any add-ons that are now enabled.
-    let addonList = gUpdateWizard.addons.map(a => a.id + ":" + a.appDisabled);
-    logger.debug("VersionInfo updates finished: found " + addonList.toSource());
-    let filteredAddons = [];
-    for (let a of gUpdateWizard.addons) {
-      if (a.appDisabled) {
-        logger.debug("Continuing with add-on " + a.id);
-        filteredAddons.push(a);
-      } else if (gUpdateWizard.addonInstalls.has(a.id)) {
-        gUpdateWizard.addonInstalls.get(a.id).cancel();
-        gUpdateWizard.addonInstalls.delete(a.id);
-      }
-    }
-    gUpdateWizard.addons = filteredAddons;
+let BRAND_PROPS = "chrome://branding/locale/brand.properties";
+let UPDATE_PROPS = "chrome://mozapps/locale/extensions/update.properties";
 
-    if (gUpdateWizard.shuttingDown) {
-      // jump directly to updating auto-update add-ons in the background
-      if (gUpdateWizard.addonInstalls.size > 0) {
-        let installs = Array.from(gUpdateWizard.addonInstalls.values());
-        gInstallingPage.startInstalls(installs);
-      }
-      return;
-    }
-
-    if (filteredAddons.length > 0) {
-      if (!gUpdateWizard.xpinstallEnabled && gUpdateWizard.xpinstallLocked) {
-        document.documentElement.currentPage = document.getElementById("adminDisabled");
-        return;
-      }
-      document.documentElement.currentPage = document.getElementById("mismatch");
-    } else {
-      logger.info("VersionInfo: No updates require further action");
-      // VersionInfo compatibility updates resolved all compatibility problems,
-      // close this window and continue starting the application...
-      // XXX Bug 314754 - We need to use setTimeout to close the window due to
-      // the EM using xmlHttpRequest when checking for updates.
-      setTimeout(close, 0);
-    }
-  },
-
-  // UpdateListener
-  onUpdateFinished(aAddon, status) {
-    ++this._completeCount;
-
-    if (status != AddonManager.UPDATE_STATUS_NO_ERROR) {
-      logger.debug("VersionInfo update " + this._completeCount + " of " + this._totalCount +
-           " failed for " + aAddon.id + ": " + status);
-      gUpdateWizard.errorItems.push(aAddon);
-    } else {
-      logger.debug("VersionInfo update " + this._completeCount + " of " + this._totalCount +
-           " finished for " + aAddon.id);
-    }
-
-    // If we're not in the background, just make a list of add-ons that have
-    // updates available
-    if (!gUpdateWizard.shuttingDown) {
-      // If we're still in the update check window and the add-on is now active
-      // then it won't have been disabled by startup
-      if (aAddon.active) {
-        AddonManagerPrivate.removeStartupChange(AddonManager.STARTUP_CHANGE_DISABLED, aAddon.id);
-        gUpdateWizard.metadataEnabled++;
-      }
-
-      // Update the status text and progress bar
-      var updateStrings = document.getElementById("updateStrings");
-      var statusElt = document.getElementById("versioninfo.status");
-      var statusString = updateStrings.getFormattedString("statusPrefix", [aAddon.name]);
-      statusElt.setAttribute("value", statusString);
-
-      // Update the status text and progress bar
-      var progress = document.getElementById("versioninfo.progress");
-      progress.mode = "normal";
-      progress.value = Math.ceil((this._completeCount / this._totalCount) * 100);
-    }
-
-    if (this._completeCount == this._totalCount)
-      this.onAllUpdatesFinished();
-  },
-
-  onUpdateAvailable(aAddon, aInstall) {
-    logger.debug("VersionInfo got an install for " + aAddon.id + ": " + aAddon.version);
-    gUpdateWizard.addonInstalls.set(aAddon.id, aInstall);
-  },
-};
-
-var gMismatchPage = {
-  waiting: false,
-
-  onPageShow() {
-    gMismatchPage.waiting = true;
-    gUpdateWizard.setButtonLabels(null, true,
-                                  "mismatchCheckNow", false,
-                                  "mismatchDontCheck", false);
-    document.documentElement.getButton("next").focus();
+let appName = Services.strings.createBundle(BRAND_PROPS)
+                      .GetStringFromName("brandShortName");
+let bundle = Services.strings.createBundle(UPDATE_PROPS);
 
-    var incompatible = document.getElementById("mismatch.incompatible");
-    for (let addon of gUpdateWizard.addons) {
-      var listitem = document.createElement("listitem");
-      listitem.setAttribute("label", addon.name + " " + addon.version);
-      incompatible.appendChild(listitem);
-    }
-  }
-};
-
-var gUpdatePage = {
-  _totalCount: 0,
-  _completeCount: 0,
-  onPageShow() {
-    gMismatchPage.waiting = false;
-    gUpdateWizard.setButtonLabels(null, true,
-                                  "nextButtonText", true,
-                                  "cancelButtonText", false);
-    document.documentElement.getButton("next").focus();
-
-    gUpdateWizard.errorItems = [];
-
-    this._totalCount = gUpdateWizard.addons.length;
-    for (let addon of gUpdateWizard.addons) {
-      logger.debug("UpdatePage requesting update for " + addon.id);
-      // Redundant call to find updates again here when we already got them
-      // in the VersionInfo page: https://bugzilla.mozilla.org/show_bug.cgi?id=960597
-      addon.findUpdates(this, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED);
-    }
-  },
-
-  onAllUpdatesFinished() {
-    if (gUpdateWizard.shuttingDown)
-      return;
-
-    var nextPage = document.getElementById("noupdates");
-    if (gUpdateWizard.addonsToUpdate.length > 0)
-      nextPage = document.getElementById("found");
-    document.documentElement.currentPage = nextPage;
-  },
-
-  // UpdateListener
-  onUpdateAvailable(aAddon, aInstall) {
-    logger.debug("UpdatePage got an update for " + aAddon.id + ": " + aAddon.version);
-    gUpdateWizard.addonsToUpdate.push(aInstall);
-  },
-
-  onUpdateFinished(aAddon, status) {
-    if (status != AddonManager.UPDATE_STATUS_NO_ERROR)
-      gUpdateWizard.errorItems.push(aAddon);
-
-    ++this._completeCount;
-
-    if (!gUpdateWizard.shuttingDown) {
-      // Update the status text and progress bar
-      var updateStrings = document.getElementById("updateStrings");
-      var statusElt = document.getElementById("checking.status");
-      var statusString = updateStrings.getFormattedString("statusPrefix", [aAddon.name]);
-      statusElt.setAttribute("value", statusString);
-
-      var progress = document.getElementById("checking.progress");
-      progress.value = Math.ceil((this._completeCount / this._totalCount) * 100);
-    }
-
-    if (this._completeCount == this._totalCount)
-      this.onAllUpdatesFinished()
-  },
-};
-
-var gFoundPage = {
-  onPageShow() {
-    gUpdateWizard.setButtonLabels(null, true,
-                                  "installButtonText", false,
-                                  null, false);
+let titleText = bundle.formatStringFromName("addonUpdateTitle", [appName], 1);
+let messageText = bundle.formatStringFromName("addonUpdateMessage", [appName], 1);
+let cancelText = bundle.GetStringFromName("addonUpdateCancelMessage");
+let cancelButtonText = bundle.formatStringFromName("addonUpdateCancelButton", [appName], 1);
 
-    var foundUpdates = document.getElementById("found.updates");
-    for (let install of gUpdateWizard.addonsToUpdate) {
-      let listItem = foundUpdates.appendItem(install.name + " " + install.version);
-      listItem.setAttribute("type", "checkbox");
-      listItem.setAttribute("checked", "true");
-      listItem.install = install;
-    }
-
-    if (!gUpdateWizard.xpinstallEnabled) {
-      document.getElementById("xpinstallDisabledAlert").hidden = false;
-      document.getElementById("enableXPInstall").focus();
-      document.documentElement.getButton("next").disabled = true;
-    } else {
-      document.documentElement.getButton("next").focus();
-      document.documentElement.getButton("next").disabled = false;
-    }
-  },
-
-  toggleXPInstallEnable(aEvent) {
-    var enabled = aEvent.target.checked;
-    gUpdateWizard.xpinstallEnabled = enabled;
-    var pref = Components.classes["@mozilla.org/preferences-service;1"]
-                         .getService(Components.interfaces.nsIPrefBranch);
-    pref.setBoolPref(PREF_XPINSTALL_ENABLED, enabled);
-    this.updateNextButton();
-  },
-
-  updateNextButton() {
-    if (!gUpdateWizard.xpinstallEnabled) {
-      document.documentElement.getButton("next").disabled = true;
-      return;
-    }
-
-    var oneChecked = false;
-    var foundUpdates = document.getElementById("found.updates");
-    var updates = foundUpdates.getElementsByTagName("listitem");
-    for (let update of updates) {
-      if (!update.checked)
-        continue;
-      oneChecked = true;
-      break;
-    }
-
-    gUpdateWizard.setButtonLabels(null, true,
-                                  "installButtonText", true,
-                                  null, false);
-    document.getElementById("found").setAttribute("next", "installing");
-    document.documentElement.getButton("next").disabled = !oneChecked;
-  }
-};
-
-var gInstallingPage = {
-  _installs: [],
-  _errors: [],
-  _strings: null,
-  _currentInstall: -1,
-  _installing: false,
-
-  // Initialize fields we need for installing and tracking progress,
-  // and start iterating through the installations
-  startInstalls(aInstallList) {
-    if (!gUpdateWizard.xpinstallEnabled) {
-      return;
-    }
-
-    let installs = Array.from(aInstallList).map(a => a.existingAddon.id);
-    logger.debug("Start installs for " + installs.toSource());
-    this._errors = [];
-    this._installs = aInstallList;
-    this._installing = true;
-    this.startNextInstall();
-  },
-
-  onPageShow() {
-    gUpdateWizard.setButtonLabels(null, true,
-                                  "nextButtonText", true,
-                                  null, true);
+document.title = titleText;
 
-    var foundUpdates = document.getElementById("found.updates");
-    var updates = foundUpdates.getElementsByTagName("listitem");
-    let toInstall = [];
-    for (let update of updates) {
-      if (!update.checked) {
-        logger.info("User chose to cancel update of " + update.label);
-        gUpdateWizard.upgradeDeclined++;
-        update.install.cancel();
-        continue;
-      }
-      toInstall.push(update.install);
-    }
-    this._strings = document.getElementById("updateStrings");
-
-    this.startInstalls(toInstall);
-  },
-
-  startNextInstall() {
-    if (this._currentInstall >= 0) {
-      this._installs[this._currentInstall].removeListener(this);
-    }
-
-    this._currentInstall++;
-
-    if (this._installs.length == this._currentInstall) {
-      Services.obs.notifyObservers(null, "TEST:all-updates-done");
-      AddonManagerPrivate.recordSimpleMeasure("appUpdate_upgraded",
-          gUpdateWizard.upgraded);
-      AddonManagerPrivate.recordSimpleMeasure("appUpdate_upgradeFailed",
-          gUpdateWizard.upgradeFailed);
-      AddonManagerPrivate.recordSimpleMeasure("appUpdate_upgradeDeclined",
-          gUpdateWizard.upgradeDeclined);
-      this._installing = false;
-      if (gUpdateWizard.shuttingDown) {
-        return;
-      }
-      var nextPage = this._errors.length > 0 ? "installerrors" : "finished";
-      document.getElementById("installing").setAttribute("next", nextPage);
-      document.documentElement.advance();
-      return;
-    }
-
-    let install = this._installs[this._currentInstall];
-
-    if (gUpdateWizard.shuttingDown && !AddonManager.shouldAutoUpdate(install.existingAddon)) {
-      logger.debug("Don't update " + install.existingAddon.id + " in background");
-      gUpdateWizard.upgradeDeclined++;
-      install.cancel();
-      this.startNextInstall();
-      return;
-    }
-    install.addListener(this);
-    install.install();
-  },
-
-  // InstallListener
-  onDownloadStarted(aInstall) {
-    if (gUpdateWizard.shuttingDown) {
-      return;
-    }
-    var strings = document.getElementById("updateStrings");
-    var label = strings.getFormattedString("downloadingPrefix", [aInstall.name]);
-    var actionItem = document.getElementById("actionItem");
-    actionItem.value = label;
-  },
-
-  onDownloadProgress(aInstall) {
-    if (gUpdateWizard.shuttingDown) {
-      return;
-    }
-    var downloadProgress = document.getElementById("downloadProgress");
-    downloadProgress.value = Math.ceil(100 * aInstall.progress / aInstall.maxProgress);
-  },
-
-  onDownloadEnded(aInstall) {
-  },
-
-  onDownloadFailed(aInstall) {
-    this._errors.push(aInstall);
-
-    gUpdateWizard.upgradeFailed++;
-    this.startNextInstall();
-  },
-
-  onInstallStarted(aInstall) {
-    if (gUpdateWizard.shuttingDown) {
-      return;
-    }
-    var strings = document.getElementById("updateStrings");
-    var label = strings.getFormattedString("installingPrefix", [aInstall.name]);
-    var actionItem = document.getElementById("actionItem");
-    actionItem.value = label;
-  },
-
-  onInstallEnded(aInstall, aAddon) {
-    if (!gUpdateWizard.shuttingDown) {
-      // Remember that this add-on was updated during startup
-      AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
-                                           aAddon.id);
-    }
-
-    gUpdateWizard.upgraded++;
-    this.startNextInstall();
-  },
-
-  onInstallFailed(aInstall) {
-    this._errors.push(aInstall);
-
-    gUpdateWizard.upgradeFailed++;
-    this.startNextInstall();
-  }
-};
-
-var gInstallErrorsPage = {
-  onPageShow() {
-    gUpdateWizard.setButtonLabels(null, true, null, true, null, true);
-    document.documentElement.getButton("finish").focus();
-  },
-};
-
-// Displayed when there are incompatible add-ons and the xpinstall.enabled
-// pref is false and locked.
-var gAdminDisabledPage = {
-  onPageShow() {
-    gUpdateWizard.setButtonLabels(null, true, null, true,
-                                  "cancelButtonText", true);
-    document.documentElement.getButton("finish").focus();
-  }
-};
-
-// Displayed when selected add-on updates have been installed without error.
-// There can still be add-ons that are not compatible and don't have an update.
-var gFinishedPage = {
-  onPageShow() {
-    gUpdateWizard.setButtonLabels(null, true, null, true, null, true);
-    document.documentElement.getButton("finish").focus();
-
-    if (gUpdateWizard.shouldSuggestAutoChecking) {
-      document.getElementById("finishedCheckDisabled").hidden = false;
-      gUpdateWizard.shouldAutoCheck = true;
-    } else
-      document.getElementById("finishedCheckEnabled").hidden = false;
-
-    document.documentElement.getButton("finish").focus();
-  }
-};
-
-// Displayed when there are incompatible add-ons and there are no available
-// updates.
-var gNoUpdatesPage = {
-  onPageShow(aEvent) {
-    gUpdateWizard.setButtonLabels(null, true, null, true, null, true);
-    if (gUpdateWizard.shouldSuggestAutoChecking) {
-      document.getElementById("noupdatesCheckDisabled").hidden = false;
-      gUpdateWizard.shouldAutoCheck = true;
-    } else
-      document.getElementById("noupdatesCheckEnabled").hidden = false;
-
-    gUpdateWizard.checkForErrors("updateCheckErrorNotFound");
-    document.documentElement.getButton("finish").focus();
-  }
-};
+window.addEventListener("load", e => {
+  document.getElementById("message").textContent = messageText;
+  document.getElementById("cancel-message").textContent = cancelText;
+  document.getElementById("cancel-btn").textContent = cancelButtonText;
+  window.sizeToContent();
+});
deleted file mode 100644
--- a/toolkit/mozapps/extensions/content/update.xul
+++ /dev/null
@@ -1,194 +0,0 @@
-<?xml version="1.0"?>
-
-# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> 
-<?xml-stylesheet href="chrome://mozapps/skin/extensions/update.css" type="text/css"?> 
-
-<!DOCTYPE wizard [
-<!ENTITY % updateDTD SYSTEM "chrome://mozapps/locale/extensions/update.dtd">
-<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
-%updateDTD;
-%brandDTD;
-]>
-
-<wizard id="updateWizard"
-        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        title="&updateWizard.title;"
-        windowtype="Addons:Compatibility"
-        branded="true"
-        onload="gUpdateWizard.init();"
-        onwizardfinish="gUpdateWizard.onWizardFinish();"
-        onwizardcancel="return gUpdateWizard.onWizardCancel();"
-        onclose="return gUpdateWizard.onWizardClose(event);"
-        buttons="accept,cancel">
-
-  <script type="application/javascript" src="chrome://mozapps/content/extensions/update.js"/>
-  
-  <stringbundleset id="updateSet">
-    <stringbundle id="brandStrings" src="chrome://branding/locale/brand.properties"/>
-    <stringbundle id="updateStrings" src="chrome://mozapps/locale/extensions/update.properties"/>
-  </stringbundleset>
-  
-  <wizardpage id="dummy" pageid="dummy"/>
-  
-  <wizardpage id="offline" pageid="offline" next="versioninfo"
-              label="&offline.title;"
-              onpageadvanced="return gOfflinePage.onPageAdvanced();">
-    <description>&offline.description;</description>
-    <checkbox id="toggleOffline"
-              checked="true"
-              label="&offline.toggleOffline.label;"
-              accesskey="&offline.toggleOffline.accesskey;"
-              oncommand="gOfflinePage.toggleOffline();"/>
-  </wizardpage>
-  
-  <wizardpage id="versioninfo" pageid="versioninfo" next="mismatch"
-              label="&versioninfo.wizard.title;"
-              onpageshow="gVersionInfoPage.onPageShow();">
-    <label>&versioninfo.top.label;</label>
-    <separator class="thin"/>
-    <progressmeter id="versioninfo.progress" mode="undetermined"/>
-    <hbox align="center">
-      <image id="versioninfo.throbber" class="throbber"/>
-      <label flex="1" id="versioninfo.status" crop="right">&versioninfo.waiting;</label>
-    </hbox>
-    <separator/>
-  </wizardpage>
-
-  <wizardpage id="mismatch" pageid="mismatch" next="checking"
-              label="&mismatch.win.title;"
-              onpageshow="gMismatchPage.onPageShow();">
-    <label>&mismatch.top.label;</label>
-    <separator class="thin"/>
-    <listbox id="mismatch.incompatible" flex="1"/>
-    <separator class="thin"/>
-    <label>&mismatch.bottom.label;</label>
-  </wizardpage>
-  
-  <wizardpage id="checking" pageid="checking" next="noupdates"
-              label="&checking.wizard.title;"
-              onpageshow="gUpdatePage.onPageShow();">
-    <label>&checking.top.label;</label>
-    <separator class="thin"/>
-    <progressmeter id="checking.progress"/>
-    <hbox align="center">
-      <image id="checking.throbber" class="throbber"/>
-      <label id="checking.status" flex="1" crop="right">&checking.status;</label>
-    </hbox>
-  </wizardpage>
-    
-  <wizardpage id="noupdates" pageid="noupdates"
-              label="&noupdates.wizard.title;"
-              onpageshow="gNoUpdatesPage.onPageShow();">
-    <description>&noupdates.intro.desc;</description>
-    <separator class="thin"/>
-    <hbox id="updateCheckErrorNotFound" class="alertBox" hidden="true" align="top">
-      <description flex="1">&noupdates.error.desc;</description>
-    </hbox>
-    <separator class="thin"/>
-    <description id="noupdatesCheckEnabled" hidden="true">
-      &noupdates.checkEnabled.desc;
-    </description>
-    <vbox id="noupdatesCheckDisabled" hidden="true">
-      <description>&finished.checkDisabled.desc;</description>
-      <checkbox label="&enableChecking.label;" checked="true"
-                oncommand="gUpdateWizard.shouldAutoCheck = this.checked;"/>
-    </vbox>
-    <separator flex="1"/>
-#ifndef XP_MACOSX
-    <label>&clickFinish.label;</label>
-#else
-    <label>&clickFinish.labelMac;</label>
-#endif
-    <separator class="thin"/>
-  </wizardpage>
-
-  <wizardpage id="found" pageid="found" next="installing"
-              label="&found.wizard.title;"
-              onpageshow="gFoundPage.onPageShow();">
-    <label>&found.top.label;</label>
-    <separator class="thin"/>
-    <listbox id="found.updates" flex="1" seltype="multiple"
-             onclick="gFoundPage.updateNextButton();"/>
-    <separator class="thin"/>
-    <vbox align="left" id="xpinstallDisabledAlert" hidden="true">
-      <description>&found.disabledXPinstall.label;</description>
-      <checkbox label="&found.enableXPInstall.label;"
-                id="enableXPInstall"
-                accesskey="&found.enableXPInstall.accesskey;"
-                oncommand="gFoundPage.toggleXPInstallEnable(event);"/>
-    </vbox>
-  </wizardpage>
-
-  <wizardpage id="installing" pageid="installing" next="finished"
-              label="&installing.wizard.title;"
-              onpageshow="gInstallingPage.onPageShow();">
-    <label>&installing.top.label;</label>
-    <progressmeter id="downloadProgress"/>
-    <hbox align="center">
-      <image id="installing.throbber" class="throbber"/>
-      <label id="actionItem" flex="1" crop="right"/>
-    </hbox>
-    <separator/>
-  </wizardpage>
-  
-  <wizardpage id="installerrors" pageid="installerrors"
-              label="&installerrors.wizard.title;"
-              onpageshow="gInstallErrorsPage.onPageShow();">
-    <hbox align="top" class="alertBox">
-      <description flex="1">&installerrors.intro.label;</description>
-    </hbox>
-    <separator flex="1"/>
-#ifndef XP_MACOSX
-    <label>&clickFinish.label;</label>
-#else
-    <label>&clickFinish.labelMac;</label>
-#endif
-    <separator class="thin"/>
-  </wizardpage>
-  
-  <wizardpage id="adminDisabled" pageid="adminDisabled"
-              label="&adminDisabled.wizard.title;"
-              onpageshow="gAdminDisabledPage.onPageShow();">
-    <separator/>
-    <hbox class="alertBox" align="top">
-      <description flex="1">&adminDisabled.warning.label;</description>
-    </hbox>
-    <separator flex="1"/>
-#ifndef XP_MACOSX
-    <label>&clickFinish.label;</label>
-#else
-    <label>&clickFinish.labelMac;</label>
-#endif
-    <separator class="thin"/>
-  </wizardpage>
-
-  <wizardpage id="finished" pageid="finished"
-              label="&finished.wizard.title;"
-              onpageshow="gFinishedPage.onPageShow();">
-
-    <label>&finished.top.label;</label>
-    <separator/>
-    <description id="finishedCheckEnabled" hidden="true">
-      &finished.checkEnabled.desc;
-    </description>
-    <vbox id="finishedCheckDisabled" hidden="true">
-      <description>&finished.checkDisabled.desc;</description>
-      <checkbox label="&enableChecking.label;" checked="true"
-                oncommand="gUpdateWizard.shouldAutoCheck = this.checked;"/>
-    </vbox>
-    <separator flex="1"/>
-#ifndef XP_MACOSX
-    <label>&clickFinish.label;</label>
-#else
-    <label>&clickFinish.labelMac;</label>
-#endif
-    <separator class="thin"/>
-  </wizardpage>
-  
-</wizard>
-
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -31,16 +31,18 @@ XPCOMUtils.defineLazyModuleGetters(this,
   PermissionsUtils: "resource://gre/modules/PermissionsUtils.jsm",
   OS: "resource://gre/modules/osfile.jsm",
   ConsoleAPI: "resource://gre/modules/Console.jsm",
   ProductAddonChecker: "resource://gre/modules/addons/ProductAddonChecker.jsm",
   UpdateUtils: "resource://gre/modules/UpdateUtils.jsm",
   isAddonPartOfE10SRollout: "resource://gre/modules/addons/E10SAddonsRollout.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",
 
   DownloadAddonInstall: "resource://gre/modules/addons/XPIInstall.jsm",
   LocalAddonInstall: "resource://gre/modules/addons/XPIInstall.jsm",
   StagedAddonInstall: "resource://gre/modules/addons/XPIInstall.jsm",
   UpdateChecker: "resource://gre/modules/addons/XPIInstall.jsm",
   loadManifestFromFile: "resource://gre/modules/addons/XPIInstall.jsm",
   verifyBundleSignedState: "resource://gre/modules/addons/XPIInstall.jsm",
 });
@@ -102,17 +104,16 @@ const PREF_EM_LAST_APP_BUILD_ID       = 
 
 const OBSOLETE_PREFERENCES = [
   "extensions.bootstrappedAddons",
   "extensions.enabledAddons",
   "extensions.xpiState",
   "extensions.installCache",
 ];
 
-const URI_EXTENSION_UPDATE_DIALOG     = "chrome://mozapps/content/extensions/update.xul";
 const URI_EXTENSION_STRINGS           = "chrome://mozapps/locale/extensions/extensions.properties";
 
 const DIR_EXTENSIONS                  = "extensions";
 const DIR_SYSTEM_ADDONS               = "features";
 const DIR_STAGE                       = "staged";
 const DIR_TRASH                       = "trash";
 
 const FILE_XPI_STATES                 = "addonStartup.json.lz4";
@@ -768,16 +769,30 @@ function canRunInSafeMode(aAddon) {
   // in safe mode. assuming for now that they are.
   if (aAddon._installLocation.name == KEY_APP_TEMPORARY)
     return true;
 
   return aAddon._installLocation.isSystem;
 }
 
 /**
+ * Determine if this addon should be disabled due to being legacy
+ *
+ * @param {Addon} addon The addon to check
+ *
+ * @returns {boolean} Whether the addon should be disabled for being legacy
+ */
+function isDisabledLegacy(addon) {
+  return (!AddonSettings.ALLOW_LEGACY_EXTENSIONS &&
+          LEGACY_TYPES.has(addon.type) &&
+          !addon._installLocation.isSystem &&
+          addon.signedState !== AddonManager.SIGNEDSTATE_PRIVILEGED);
+}
+
+/**
  * Calculates whether an add-on should be appDisabled or not.
  *
  * @param  aAddon
  *         The add-on to check
  * @return true if the add-on should not be appDisabled
  */
 function isUsableAddon(aAddon) {
   // Hack to ensure the default theme is always usable
@@ -823,19 +838,17 @@ function isUsableAddon(aAddon) {
       let active = XPIProvider.activeAddons.get(id);
       return active && !active.disable;
     };
 
     if (aAddon.dependencies.some(id => !isActive(id)))
       return false;
   }
 
-  if (!AddonSettings.ALLOW_LEGACY_EXTENSIONS && LEGACY_TYPES.has(aAddon.type) &&
-      !aAddon._installLocation.isSystem &&
-      aAddon.signedState !== AddonManager.SIGNEDSTATE_PRIVILEGED) {
+  if (isDisabledLegacy(aAddon)) {
     logger.warn(`disabling legacy extension ${aAddon.id}`);
     return false;
   }
 
   if (!ALLOW_NON_MPC && aAddon.type == "extension" &&
       aAddon.multiprocessCompatible !== true) {
     logger.warn(`disabling ${aAddon.id} since it is not multiprocess compatible`);
     return false;
@@ -2155,20 +2168,21 @@ this.XPIProvider = {
                                              aOldPlatformVersion);
 
       // Changes to installed extensions may have changed which theme is selected
       this.applyThemeChange();
 
       AddonManagerPrivate.markProviderSafe(this);
 
       if (aAppChanged && !this.allAppGlobal &&
-          Services.prefs.getBoolPref(PREF_EM_SHOW_MISMATCH_UI, true)) {
+          Services.prefs.getBoolPref(PREF_EM_SHOW_MISMATCH_UI, true) &&
+          AddonManager.updateEnabled) {
         let addonsToUpdate = this.shouldForceUpdateCheck(aAppChanged);
         if (addonsToUpdate) {
-          this.showUpgradeUI(addonsToUpdate);
+          this.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
@@ -2190,16 +2204,22 @@ this.XPIProvider = {
         } catch (e) { }
         this.addAddonsToCrashReporter();
       }
 
       try {
         AddonManagerPrivate.recordTimestamp("XPI_bootstrap_addons_begin");
 
         for (let addon of this.sortBootstrappedAddons()) {
+          // The startup update check above may have already started some
+          // extensions, make sure not to try to start them twice.
+          let activeAddon = this.activeAddons.get(addon.id);
+          if (activeAddon && activeAddon.started) {
+            continue;
+          }
           try {
             let reason = BOOTSTRAP_REASONS.APP_STARTUP;
             // Eventually set INSTALLED reason when a bootstrap addon
             // is dropped in profile folder and automatically installed
             if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
                             .indexOf(addon.id) !== -1)
               reason = BOOTSTRAP_REASONS.ADDON_INSTALL;
             this.callBootstrapMethod(createAddonDetails(addon.id, addon),
@@ -2410,76 +2430,158 @@ this.XPIProvider = {
     Services.prefs.clearUserPref(PREF_SKIN_SWITCHPENDING);
   },
 
   /**
    * 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 False if no update check is needed, otherwise an array of add-on
-   *          IDs to check for updates. Array may be empty if no add-ons can be/need
-   *           to be updated, but the metadata check needs to be performed.
+   * @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.indexOf(addon.id) != -1) &&
             (addon.permissions() & AddonManager.PERM_CAN_UPGRADE) &&
-            !addon.isCompatible) {
+            (!addon.isCompatible || isDisabledLegacy(addon))) {
           logger.debug("shouldForceUpdateCheck: can upgrade disabled add-on " + addon.id);
           forceUpdate.push(addon.id);
         }
       }
     }
 
-    if (AddonRepository.isMetadataStale()) {
-      logger.debug("shouldForceUpdateCheck: metadata is stale");
-      return forceUpdate;
-    }
     if (forceUpdate.length > 0) {
       return forceUpdate;
     }
 
-    return false;
+    return null;
   },
 
   /**
-   * Shows the "Compatibility Updates" UI.
+   * 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  aAddonIDs
-   *         Array opf addon IDs that were disabled by the application update, and
-   *         should therefore be checked for updates.
+   * @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.
    */
-  showUpgradeUI(aAddonIDs) {
-    logger.debug("XPI_showUpgradeUI: " + aAddonIDs.toSource());
-    Services.telemetry.getHistogramById("ADDON_MANAGER_UPGRADE_UI_SHOWN").add(1);
-
-    // Flip a flag to indicate that we interrupted startup with an interactive prompt
-    Services.startup.interrupted = true;
-
-    var variant = Cc["@mozilla.org/variant;1"].
-                  createInstance(Ci.nsIWritableVariant);
-    variant.setFromVariant(aAddonIDs);
-
-    // This *must* be modal as it has to block startup.
-    var features = "chrome,centerscreen,dialog,titlebar,modal";
-    var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
-             getService(Ci.nsIWindowWatcher);
-    ww.openWindow(null, URI_EXTENSION_UPDATE_DIALOG, "", features, variant);
-
-    Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, false);
+  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;
   },
 
   async updateSystemAddons() {
     let systemAddonLocation = XPIProvider.installLocationsByName[KEY_APP_SYSTEM_ADDONS];
     if (!systemAddonLocation)
       return;
 
     // Don't do anything in safe mode
@@ -4176,16 +4278,17 @@ this.XPIProvider = {
    */
   loadBootstrapScope(aId, aFile, aVersion, aType,
                                aMultiprocessCompatible, aRunInSafeMode,
                                aDependencies, hasEmbeddedWebExtension) {
     this.activeAddons.set(aId, {
       bootstrapScope: null,
       // a Symbol passed to this add-on, which it can use to identify itself
       instanceID: Symbol(aId),
+      started: false,
     });
 
     // Mark the add-on as active for the crash reporter before loading
     this.addAddonsToCrashReporter();
 
     let activeAddon = this.activeAddons.get(aId);
 
     // Locales only contain chrome and can't have bootstrap scripts
@@ -4332,22 +4435,28 @@ this.XPIProvider = {
       let scope = activeAddon.bootstrapScope;
       try {
         method = scope[aMethod] || Cu.evalInSandbox(`${aMethod};`, scope);
       } catch (e) {
         // An exception will be caught if the expected method is not defined.
         // That will be logged below.
       }
 
-      // Extensions are automatically deinitialized in the correct order at shutdown.
-      if (aMethod == "shutdown" && aReason != BOOTSTRAP_REASONS.APP_SHUTDOWN) {
-        activeAddon.disable = true;
-        for (let addon of this.getDependentAddons(aAddon)) {
-          if (addon.active)
-            this.updateAddonDisabledState(addon);
+      if (aMethod == "startup") {
+        activeAddon.started = true;
+      } else if (aMethod == "shutdown") {
+        activeAddon.started = false;
+
+        // Extensions are automatically deinitialized in the correct order at shutdown.
+        if (aReason != BOOTSTRAP_REASONS.APP_SHUTDOWN) {
+          activeAddon.disable = true;
+          for (let addon of this.getDependentAddons(aAddon)) {
+            if (addon.active)
+              this.updateAddonDisabledState(addon);
+          }
         }
       }
 
       let params = {
         id: aAddon.id,
         version: aAddon.version,
         installPath: aFile.clone(),
         resourceURI: getURIForResourceInFile(aFile, "")
--- a/toolkit/mozapps/extensions/jar.mn
+++ b/toolkit/mozapps/extensions/jar.mn
@@ -11,18 +11,19 @@ toolkit.jar:
 * content/mozapps/extensions/extensions.xml                     (content/extensions.xml)
   content/mozapps/extensions/updateinfo.xsl                     (content/updateinfo.xsl)
   content/mozapps/extensions/about.xul                          (content/about.xul)
   content/mozapps/extensions/about.js                           (content/about.js)
   content/mozapps/extensions/blocklist.xul                      (content/blocklist.xul)
   content/mozapps/extensions/blocklist.js                       (content/blocklist.js)
   content/mozapps/extensions/blocklist.css                      (content/blocklist.css)
   content/mozapps/extensions/blocklist.xml                      (content/blocklist.xml)
-* content/mozapps/extensions/update.xul                         (content/update.xul)
+  content/mozapps/extensions/update.html                        (content/update.html)
   content/mozapps/extensions/update.js                          (content/update.js)
+  content/mozapps/extensions/update.css                         (content/update.css)
   content/mozapps/extensions/eula.xul                           (content/eula.xul)
   content/mozapps/extensions/eula.js                            (content/eula.js)
   content/mozapps/extensions/newaddon.xul                       (content/newaddon.xul)
   content/mozapps/extensions/newaddon.js                        (content/newaddon.js)
   content/mozapps/extensions/pluginPrefs.xul                    (content/pluginPrefs.xul)
   content/mozapps/extensions/gmpPrefs.xul                       (content/gmpPrefs.xul)
   content/mozapps/extensions/OpenH264-license.txt               (content/OpenH264-license.txt)
 #endif