Bug 1525762: Part 1b - Add databaseReady promise to replace xpi-database-loaded observer. r=aswan
authorKris Maglione <maglione.k@gmail.com>
Fri, 22 Mar 2019 09:26:33 -0700
changeset 466962 40936b75fd328853be677891f47dcff4ba85b2c0
parent 466961 cf6d3341f350209bc140849f073867608b55c51a
child 466963 935a2cd834c2a489a585dec44cbc90964471501c
push id35789
push userbtara@mozilla.com
push dateSun, 31 Mar 2019 09:00:52 +0000
treeherdermozilla-central@c06dfc552c64 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan
bugs1525762
milestone68.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 1525762: Part 1b - Add databaseReady promise to replace xpi-database-loaded observer. r=aswan All of the consumers of this observer really want it to behave like a promise. And, for the cases where the DB may or may not already be loaded when those callers run, getting the logic correct is difficult. This patch replaces the observer with a promise, and also delays the resolution of that promise until any built-in add-ons registered during XPIProvider startup have finished installing. This latter feature is currently unused, but will be necessary after subsequent patches for code that relies querying the default theme immediately after provider startup.
browser/components/customizableui/CustomizableUI.jsm
toolkit/components/telemetry/app/TelemetryEnvironment.jsm
toolkit/mozapps/extensions/AddonManager.jsm
toolkit/mozapps/extensions/internal/XPIDatabase.jsm
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -179,20 +179,23 @@ XPCOMUtils.defineLazyGetter(this, "log",
   };
   return new scope.ConsoleAPI(consoleOptions);
 });
 
 var CustomizableUIInternal = {
   initialize() {
     log.debug("Initializing");
 
-    Services.obs.addObserver(this, "xpi-database-loaded");
-    if (AddonManagerPrivate.isDBLoaded()) {
-      this.observe(null, "xpi-database-loaded");
-    }
+    AddonManagerPrivate.databaseReady.then(async () => {
+      AddonManager.addAddonListener(this);
+
+      let addons = await AddonManager.getAddonsByTypes(["theme"]);
+      gDefaultTheme = addons.find(addon => addon.id == kDefaultThemeID);
+      gSelectedTheme = addons.find(addon => addon.isActive) || gDefaultTheme;
+    });
 
     this.addListener(this);
     this._defineBuiltInWidgets();
     this.loadSavedState();
     this._updateForNewVersion();
     this._markObsoleteBuiltinButtonsSeen();
 
     this.registerArea(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL, {
@@ -251,26 +254,16 @@ var CustomizableUIInternal = {
         "personal-bookmarks",
       ],
       defaultCollapsed: true,
     }, true);
 
     SearchWidgetTracker.init();
   },
 
-  async observe(subject, topic, data) {
-    if (topic == "xpi-database-loaded") {
-      AddonManager.addAddonListener(this);
-
-      let addons = await AddonManager.getAddonsByTypes(["theme"]);
-      gDefaultTheme = addons.find(addon => addon.id == kDefaultThemeID);
-      gSelectedTheme = addons.find(addon => addon.isActive) || gDefaultTheme;
-    }
-  },
-
   onEnabled(addon) {
     if (addon.type == "theme") {
       gSelectedTheme = addon;
     }
   },
 
   get _builtinAreas() {
     return new Set([
--- a/toolkit/components/telemetry/app/TelemetryEnvironment.jsm
+++ b/toolkit/components/telemetry/app/TelemetryEnvironment.jsm
@@ -13,17 +13,17 @@ const myScope = this;
 const {Log} = ChromeUtils.import("resource://gre/modules/Log.jsm");
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/TelemetryUtils.jsm", this);
 const {ObjectUtils} = ChromeUtils.import("resource://gre/modules/ObjectUtils.jsm");
 const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 
 const Utils = TelemetryUtils;
 
-const { AddonManager } = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
+const {AddonManager, AddonManagerPrivate} = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
 
 ChromeUtils.defineModuleGetter(this, "AttributionCode",
                                "resource:///modules/AttributionCode.jsm");
 ChromeUtils.defineModuleGetter(this, "ctypes",
                                "resource://gre/modules/ctypes.jsm");
 ChromeUtils.defineModuleGetter(this, "ProfileAge",
                                "resource://gre/modules/ProfileAge.jsm");
 ChromeUtils.defineModuleGetter(this, "WindowsRegistry",
@@ -567,28 +567,19 @@ EnvironmentAddonBuilder.prototype = {
     }
 
     this._pendingTask = (async () => {
       try {
         // Gather initial addons details
         await this._updateAddons(true);
 
         if (!this._environment._addonsAreFull) {
-          // The addon database has not been loaded, so listen for the event
-          // triggered by the AddonManager when it is loaded so we can
-          // immediately gather full data at that time.
-          await new Promise(resolve => {
-            const ADDON_LOAD_NOTIFICATION = "xpi-database-loaded";
-            Services.obs.addObserver({
-              observe(subject, topic, data) {
-                Services.obs.removeObserver(this, ADDON_LOAD_NOTIFICATION);
-                resolve();
-              },
-            }, ADDON_LOAD_NOTIFICATION);
-          });
+          // The addon database has not been loaded, wait for it to
+          // initialize and gather full data as soon as it does.
+          await AddonManagerPrivate.databaseReady;
 
           // Now gather complete addons details.
           await this._updateAddons();
         }
       } catch (err) {
         this._environment._log.error("init - Exception in _updateAddons", err);
       } finally {
         this._pendingTask = null;
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -3012,16 +3012,21 @@ var AddonManagerPrivate = {
     return AddonManagerInternal._getProviderByName("XPIProvider")
                                .isTemporaryInstallID(extensionId);
   },
 
   isDBLoaded() {
     let provider = AddonManagerInternal._getProviderByName("XPIProvider");
     return provider ? provider.isDBLoaded : false;
   },
+
+  get databaseReady() {
+    let provider = AddonManagerInternal._getProviderByName("XPIProvider");
+    return provider ? provider.databaseReady : new Promise(() => {});
+  },
 };
 
 /**
  * This is the public API that UI and developers should be calling. All methods
  * just forward to AddonManagerInternal.
  * @class
  */
 var AddonManager = {
--- a/toolkit/mozapps/extensions/internal/XPIDatabase.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIDatabase.jsm
@@ -1443,19 +1443,17 @@ this.XPIDatabase = {
                       error);
           this._loadError = error;
         }
         this.timeRebuildDatabase("XPIDB_rebuildUnreadableDB_MS", aRebuildOnError);
       }
       return this.addonDB;
     })();
 
-    this._dbPromise.then(() => {
-      Services.obs.notifyObservers(null, "xpi-database-loaded");
-    });
+    XPIInternal.resolveDBReady(this._dbPromise);
 
     return this._dbPromise;
   },
 
   timeRebuildDatabase(timerName, rebuildOnError) {
     AddonManagerPrivate.recordTiming(timerName, () => {
       return this.rebuildDatabase(rebuildOnError);
     });
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -1949,32 +1949,43 @@ class BootstrapScope {
       await updateCallback();
     }
 
     this.addon = newAddon;
     return this._install(reason, callUpdate, startup, extraArgs);
   }
 }
 
+let resolveDBReady;
+let dbReadyPromise = new Promise(resolve => {
+  resolveDBReady = resolve;
+});
+let resolveProviderReady;
+let providerReadyPromise = new Promise(resolve => {
+  resolveProviderReady = resolve;
+});
+
 var XPIProvider = {
   get name() {
     return "XPIProvider";
   },
 
   BOOTSTRAP_REASONS: Object.freeze(BOOTSTRAP_REASONS),
 
   // A Map of active addons to their bootstrapScope by ID
   activeAddons: new Map(),
   // Per-addon telemetry information
   _telemetryDetails: {},
   // Have we started shutting down bootstrap add-ons?
   _closing: false,
 
   startupPromises: [],
 
+  databaseReady: Promise.all([dbReadyPromise, providerReadyPromise]),
+
   // Check if the XPIDatabase has been loaded (without actually
   // triggering unwanted imports or I/O)
   get isDBLoaded() {
     // Make sure we don't touch the XPIDatabase getter before it's
     // actually loaded, and force an early load.
     return (Object.getOwnPropertyDescriptor(gGlobalScope, "XPIDatabase").value &&
             XPIDatabase.initialized) || false;
   },
@@ -2222,16 +2233,18 @@ var XPIProvider = {
       Services.prefs.addObserver(PREF_ALLOW_LEGACY, this);
       Services.obs.addObserver(this, NOTIFICATION_FLUSH_PERMISSIONS);
 
 
       this.checkForChanges(aAppChanged, aOldAppVersion, aOldPlatformVersion);
 
       AddonManagerPrivate.markProviderSafe(this);
 
+      resolveProviderReady(Promise.all(this.startupPromises));
+
       if (AppConstants.MOZ_CRASHREPORTER) {
         // Annotate the crash report with relevant add-on information.
         try {
           Services.appinfo.annotateCrashReport("EMCheckCompatibility",
                                                AddonManager.checkCompatibility);
         } catch (e) { }
         this.addAddonsToCrashReporter();
       }
@@ -2862,16 +2875,17 @@ var XPIInternal = {
   XPIStates,
   XPI_PERMISSION,
   awaitPromise,
   canRunInSafeMode,
   getURIForResourceInFile,
   isXPI,
   iterDirectory,
   migrateAddonLoader,
+  resolveDBReady,
 };
 
 var addonTypes = [
   new AddonManagerPrivate.AddonType("extension", URI_EXTENSION_STRINGS,
                                     "type.extension.name",
                                     AddonManager.VIEW_TYPE_LIST, 4000,
                                     AddonManager.TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL),
   new AddonManagerPrivate.AddonType("theme", URI_EXTENSION_STRINGS,
--- a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js
@@ -178,23 +178,18 @@ add_task(async function test_after_corru
   // Shutdown and replace the database with a corrupt file (a directory
   // serves this purpose). On startup the add-ons manager won't rebuild
   // because there is a file there still.
   gExtensionsJSON.remove(true);
   gExtensionsJSON.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
 
   await promiseStartupManager();
 
-  await new Promise(resolve => {
-    Services.obs.addObserver(function listener() {
-      Services.obs.removeObserver(listener, "xpi-database-loaded");
-      resolve();
-    }, "xpi-database-loaded");
-    Services.obs.notifyObservers(null, "sessionstore-windows-restored");
-  });
+  Services.obs.notifyObservers(null, "sessionstore-windows-restored");
+  await AddonManagerPrivate.databaseReady;
 
   // Accessing the add-ons should open and recover the database
   info("Test add-on state after corruption");
   let addons = await getAddons(IDS);
   for (let [id, addon] of Object.entries(ADDONS)) {
     checkAddon(id, addons.get(id), addon.desiredState);
   }