Bug 862127 - Make migration interfaces more async r?Gijs draft
authorDoug Thayer <dothayer@mozilla.com>
Fri, 12 Jan 2018 09:06:21 -0800
changeset 719886 0d030e5f0816d245abaec81395c3467d4f4e1bae
parent 719698 2a78b2a40796d5b45659ecfa7628c69ee56a8bc0
child 719887 9772c63da96c84e831f05d634e104078e345c3bc
push id95383
push userbmo:dothayer@mozilla.com
push dateFri, 12 Jan 2018 21:16:52 +0000
reviewersGijs
bugs862127
milestone59.0a1
Bug 862127 - Make migration interfaces more async r?Gijs In order to clean up sync IO within our profile migrators, we need to have async interfaces for those parts which are currently doing sync IO. This converts the sync interfaces and adjusts most of the call sites (migration.js call site changes are addressed in a separate patch to break it out a bit). MozReview-Commit-ID: 2Kcrxco4iYr
browser/components/migration/360seProfileMigrator.js
browser/components/migration/AutoMigrate.jsm
browser/components/migration/ChromeProfileMigrator.js
browser/components/migration/EdgeProfileMigrator.js
browser/components/migration/FirefoxProfileMigrator.js
browser/components/migration/MigrationUtils.jsm
browser/components/migration/nsIBrowserProfileMigrator.idl
--- a/browser/components/migration/360seProfileMigrator.js
+++ b/browser/components/migration/360seProfileMigrator.js
@@ -207,93 +207,91 @@ function Qihoo360seProfileMigrator() {
       this._defaultUserPath = path.defaultUser;
       break;
     }
   }
 }
 
 Qihoo360seProfileMigrator.prototype = Object.create(MigratorPrototype);
 
-Object.defineProperty(Qihoo360seProfileMigrator.prototype, "sourceProfiles", {
-  get() {
-    if ("__sourceProfiles" in this)
-      return this.__sourceProfiles;
+Qihoo360seProfileMigrator.prototype.getSourceProfiles = function() {
+  if ("__sourceProfiles" in this)
+    return this.__sourceProfiles;
+
+  if (!this._usersDir) {
+    this.__sourceProfiles = [];
+    return this.__sourceProfiles;
+  }
 
-    if (!this._usersDir) {
-      this.__sourceProfiles = [];
-      return this.__sourceProfiles;
+  let profiles = [];
+  let noLoggedInUser = true;
+  try {
+    let loginIni = this._usersDir.clone();
+    loginIni.append("login.ini");
+    if (!loginIni.exists()) {
+      throw new Error("360 Secure Browser's 'login.ini' does not exist.");
+    }
+    if (!loginIni.isReadable()) {
+      throw new Error("360 Secure Browser's 'login.ini' file could not be read.");
     }
 
-    let profiles = [];
-    let noLoggedInUser = true;
+    let loginIniInUtf8 = copyToTempUTF8File(loginIni, "GBK");
+    let loginIniObj = parseINIStrings(loginIniInUtf8);
     try {
-      let loginIni = this._usersDir.clone();
-      loginIni.append("login.ini");
-      if (!loginIni.exists()) {
-        throw new Error("360 Secure Browser's 'login.ini' does not exist.");
-      }
-      if (!loginIni.isReadable()) {
-        throw new Error("360 Secure Browser's 'login.ini' file could not be read.");
+      loginIniInUtf8.remove(false);
+    } catch (ex) {}
+
+    let nowLoginEmail = loginIniObj.NowLogin && loginIniObj.NowLogin.email;
+
+    /*
+     * NowLogin section may:
+     * 1. be missing or without email, before any user logs in.
+     * 2. represents the current logged in user
+     * 3. represents the most recent logged in user
+     *
+     * In the second case, user represented by NowLogin should be the first
+     * profile; otherwise the default user should be selected by default.
+     */
+    if (nowLoginEmail) {
+      if (loginIniObj.NowLogin.IsLogined === "1") {
+        noLoggedInUser = false;
       }
 
-      let loginIniInUtf8 = copyToTempUTF8File(loginIni, "GBK");
-      let loginIniObj = parseINIStrings(loginIniInUtf8);
-      try {
-        loginIniInUtf8.remove(false);
-      } catch (ex) {}
-
-      let nowLoginEmail = loginIniObj.NowLogin && loginIniObj.NowLogin.email;
-
-      /*
-       * NowLogin section may:
-       * 1. be missing or without email, before any user logs in.
-       * 2. represents the current logged in user
-       * 3. represents the most recent logged in user
-       *
-       * In the second case, user represented by NowLogin should be the first
-       * profile; otherwise the default user should be selected by default.
-       */
-      if (nowLoginEmail) {
-        if (loginIniObj.NowLogin.IsLogined === "1") {
-          noLoggedInUser = false;
-        }
-
-        profiles.push({
-          id: this._getIdFromConfig(loginIniObj.NowLogin),
-          name: nowLoginEmail,
-        });
-      }
-
-      for (let section in loginIniObj) {
-        if (!loginIniObj[section].email ||
-            (nowLoginEmail && loginIniObj[section].email == nowLoginEmail)) {
-          continue;
-        }
-
-        profiles.push({
-          id: this._getIdFromConfig(loginIniObj[section]),
-          name: loginIniObj[section].email,
-        });
-      }
-    } catch (e) {
-      Cu.reportError("Error detecting 360 Secure Browser profiles: " + e);
-    } finally {
-      profiles[noLoggedInUser ? "unshift" : "push"]({
-        id: this._defaultUserPath,
-        name: "Default",
+      profiles.push({
+        id: this._getIdFromConfig(loginIniObj.NowLogin),
+        name: nowLoginEmail,
       });
     }
 
-    this.__sourceProfiles = profiles.filter(profile => {
-      let resources = this.getResources(profile);
-      return resources && resources.length > 0;
+    for (let section in loginIniObj) {
+      if (!loginIniObj[section].email ||
+          (nowLoginEmail && loginIniObj[section].email == nowLoginEmail)) {
+        continue;
+      }
+
+      profiles.push({
+        id: this._getIdFromConfig(loginIniObj[section]),
+        name: loginIniObj[section].email,
+      });
+    }
+  } catch (e) {
+    Cu.reportError("Error detecting 360 Secure Browser profiles: " + e);
+  } finally {
+    profiles[noLoggedInUser ? "unshift" : "push"]({
+      id: this._defaultUserPath,
+      name: "Default",
     });
-    return this.__sourceProfiles;
   }
-});
+
+  this.__sourceProfiles = profiles.filter(profile => {
+    let resources = this.getResources(profile);
+    return resources && resources.length > 0;
+  });
+  return this.__sourceProfiles;
+};
 
 Qihoo360seProfileMigrator.prototype._getIdFromConfig = function(aConfig) {
   return aConfig.UserMd5 || getHash(aConfig.email);
 };
 
 Qihoo360seProfileMigrator.prototype.getResources = function(aProfile) {
   let profileFolder = this._usersDir.clone();
   profileFolder.append(aProfile.id);
@@ -303,22 +301,23 @@ Qihoo360seProfileMigrator.prototype.getR
   }
 
   let resources = [
     new Bookmarks(profileFolder)
   ];
   return resources.filter(r => r.exists);
 };
 
-Qihoo360seProfileMigrator.prototype.getLastUsedDate = function() {
-  let bookmarksPaths = this.sourceProfiles.map(({id}) => {
+Qihoo360seProfileMigrator.prototype.getLastUsedDate = async function() {
+  let sourceProfiles = await this.getSourceProfiles();
+  let bookmarksPaths = sourceProfiles.map(({id}) => {
     return OS.Path.join(this._usersDir.path, id, kBookmarksFileName);
   });
   if (!bookmarksPaths.length) {
-    return Promise.resolve(new Date(0));
+    return new Date(0);
   }
   let datePromises = bookmarksPaths.map(path => {
     return OS.File.stat(path).catch(() => null).then(info => {
       return info ? info.lastModificationDate : 0;
     });
   });
   return Promise.all(datePromises).then(dates => {
     return new Date(Math.max.apply(Math, dates));
--- a/browser/components/migration/AutoMigrate.jsm
+++ b/browser/components/migration/AutoMigrate.jsm
@@ -85,27 +85,27 @@ const AutoMigrate = {
 
   /**
    * Automatically pick a migrator and resources to migrate,
    * then migrate those and start up.
    *
    * @throws if automatically deciding on migrators/data
    *         failed for some reason.
    */
-  migrate(profileStartup, migratorKey, profileToMigrate) {
+  async migrate(profileStartup, migratorKey, profileToMigrate) {
     let histogram = Services.telemetry.getHistogramById(
       "FX_STARTUP_MIGRATION_AUTOMATED_IMPORT_PROCESS_SUCCESS");
     histogram.add(0);
-    let {migrator, pickedKey} = this.pickMigrator(migratorKey);
+    let {migrator, pickedKey} = await this.pickMigrator(migratorKey);
     histogram.add(5);
 
-    profileToMigrate = this.pickProfile(migrator, profileToMigrate);
+    profileToMigrate = await this.pickProfile(migrator, profileToMigrate);
     histogram.add(10);
 
-    let resourceTypes = migrator.getMigrateData(profileToMigrate, profileStartup);
+    let resourceTypes = await migrator.getMigrateData(profileToMigrate, profileStartup);
     if (!(resourceTypes & this.resourceTypesToUse)) {
       throw new Error("No usable resources were found for the selected browser!");
     }
     histogram.add(15);
 
     let sawErrors = false;
     let migrationObserver = (subject, topic) => {
       if (topic == "Migration:ItemError") {
@@ -124,56 +124,56 @@ const AutoMigrate = {
             return {state: this._saveUndoStateTrackerForShutdown};
           });
       }
     };
 
     MigrationUtils.initializeUndoData();
     Services.obs.addObserver(migrationObserver, "Migration:Ended");
     Services.obs.addObserver(migrationObserver, "Migration:ItemError");
-    migrator.migrate(this.resourceTypesToUse, profileStartup, profileToMigrate);
+    await migrator.migrate(this.resourceTypesToUse, profileStartup, profileToMigrate);
     histogram.add(20);
   },
 
   /**
    * Pick and return a migrator to use for automatically migrating.
    *
    * @param {String} migratorKey   optional, a migrator key to prefer/pick.
    * @returns {Object}             an object with the migrator to use for migrating, as
    *                               well as the key we eventually ended up using to obtain it.
    */
-  pickMigrator(migratorKey) {
+  async pickMigrator(migratorKey) {
     if (!migratorKey) {
       let defaultKey = MigrationUtils.getMigratorKeyForDefaultBrowser();
       if (!defaultKey) {
         throw new Error("Could not determine default browser key to migrate from");
       }
       migratorKey = defaultKey;
     }
     if (migratorKey == "firefox") {
       throw new Error("Can't automatically migrate from Firefox.");
     }
 
-    let migrator = MigrationUtils.getMigrator(migratorKey);
+    let migrator = await MigrationUtils.getMigrator(migratorKey);
     if (!migrator) {
       throw new Error("Migrator specified or a default was found, but the migrator object is not available (or has no data).");
     }
     return {migrator, pickedKey: migratorKey};
   },
 
   /**
    * Pick a source profile (from the original browser) to use.
    *
    * @param {Migrator} migrator     the migrator object to use
    * @param {String}   suggestedId  the id of the profile to migrate, if pre-specified, or null
    * @returns                       the profile to migrate, or null if migrating
    *                                from the default profile.
    */
-  pickProfile(migrator, suggestedId) {
-    let profiles = migrator.sourceProfiles;
+  async pickProfile(migrator, suggestedId) {
+    let profiles = await migrator.getSourceProfiles();
     if (profiles && !profiles.length) {
       throw new Error("No profile data found to migrate.");
     }
     if (suggestedId) {
       if (!profiles) {
         throw new Error("Profile specified but only a default profile found.");
       }
       let suggestedProfile = profiles.find(profile => profile.id == suggestedId);
--- a/browser/components/migration/ChromeProfileMigrator.js
+++ b/browser/components/migration/ChromeProfileMigrator.js
@@ -135,18 +135,18 @@ ChromeProfileMigrator.prototype.getLastU
       });
     });
     return Promise.all(datePromises).then(dates => {
       dates.push(0);
       return new Date(Math.max.apply(Math, dates));
     });
   };
 
-Object.defineProperty(ChromeProfileMigrator.prototype, "sourceProfiles", {
-  get: function Chrome_sourceProfiles() {
+ChromeProfileMigrator.prototype.getSourceProfiles =
+  async function Chrome_getSourceProfiles() {
     if ("__sourceProfiles" in this)
       return this.__sourceProfiles;
 
     if (!this._chromeUserDataFolder)
       return [];
 
     let profiles = [];
     try {
@@ -173,18 +173,17 @@ Object.defineProperty(ChromeProfileMigra
     }
 
     // Only list profiles from which any data can be imported
     this.__sourceProfiles = profiles.filter(function(profile) {
       let resources = this.getResources(profile);
       return resources && resources.length > 0;
     }, this);
     return this.__sourceProfiles;
-  }
-});
+  };
 
 Object.defineProperty(ChromeProfileMigrator.prototype, "sourceHomePageURL", {
   get: function Chrome_sourceHomePageURL() {
     let prefsFile = this._chromeUserDataFolder.clone();
     prefsFile.append("Preferences");
     if (prefsFile.exists()) {
       // XXX reading and parsing JSON is synchronous.
       let fstream = Cc[FILE_INPUT_STREAM_CID].
--- a/browser/components/migration/EdgeProfileMigrator.js
+++ b/browser/components/migration/EdgeProfileMigrator.js
@@ -369,20 +369,21 @@ EdgeProfileMigrator.prototype.getResourc
   ];
   let windowsVaultFormPasswordsMigrator =
     MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
   windowsVaultFormPasswordsMigrator.name = "EdgeVaultFormPasswords";
   resources.push(windowsVaultFormPasswordsMigrator);
   return resources.filter(r => r.exists);
 };
 
-EdgeProfileMigrator.prototype.getLastUsedDate = function() {
+EdgeProfileMigrator.prototype.getLastUsedDate = async function() {
   // Don't do this if we don't have a single profile (see the comment for
   // sourceProfiles) or if we can't find the database file:
-  if (this.sourceProfiles !== null || !gEdgeDatabase) {
+  let sourceProfiles = await this.getSourceProfiles();
+  if (sourceProfiles !== null || !gEdgeDatabase) {
     return Promise.resolve(new Date(0));
   }
   let logFilePath = OS.Path.join(gEdgeDatabase.parent.path, "LogFiles", "edb.log");
   let dbPath = gEdgeDatabase.path;
   let cookieMigrator = MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE);
   let cookiePaths = cookieMigrator._cookiesFolders.map(f => f.path);
   let datePromises = [logFilePath, dbPath, ...cookiePaths].map(path => {
     return OS.File.stat(path).catch(() => null).then(info => {
@@ -402,20 +403,20 @@ EdgeProfileMigrator.prototype.getLastUse
   });
 };
 
 /* Somewhat counterintuitively, this returns:
  * - |null| to indicate "There is only 1 (default) profile" (on win10+)
  * - |[]| to indicate "There are no profiles" (on <=win8.1) which will avoid using this migrator.
  * See MigrationUtils.jsm for slightly more info on how sourceProfiles is used.
  */
-EdgeProfileMigrator.prototype.__defineGetter__("sourceProfiles", function() {
+EdgeProfileMigrator.prototype.getSourceProfiles = function() {
   let isWin10OrHigher = AppConstants.isPlatformAndVersionAtLeast("win", "10");
   return isWin10OrHigher ? null : [];
-});
+};
 
 EdgeProfileMigrator.prototype.__defineGetter__("sourceLocked", function() {
   // There is an exclusive lock on some databases. Assume they are locked for now.
   return true;
 });
 
 
 EdgeProfileMigrator.prototype.classDescription = "Edge Profile Migrator";
--- a/browser/components/migration/FirefoxProfileMigrator.js
+++ b/browser/components/migration/FirefoxProfileMigrator.js
@@ -56,21 +56,19 @@ FirefoxProfileMigrator.prototype._getAll
   }
   return allProfiles;
 };
 
 function sorter(a, b) {
   return a.id.toLocaleLowerCase().localeCompare(b.id.toLocaleLowerCase());
 }
 
-Object.defineProperty(FirefoxProfileMigrator.prototype, "sourceProfiles", {
-  get() {
-    return [...this._getAllProfiles().keys()].map(x => ({id: x, name: x})).sort(sorter);
-  }
-});
+FirefoxProfileMigrator.prototype.getSourceProfiles = function() {
+  return [...this._getAllProfiles().keys()].map(x => ({id: x, name: x})).sort(sorter);
+};
 
 FirefoxProfileMigrator.prototype._getFileObject = function(dir, fileName) {
   let file = dir.clone();
   file.append(fileName);
 
   // File resources are monolithic.  We don't make partial copies since
   // they are not expected to work alone. Return null to avoid trying to
   // copy non-existing files.
--- a/browser/components/migration/MigrationUtils.jsm
+++ b/browser/components/migration/MigrationUtils.jsm
@@ -98,17 +98,17 @@ this.MigratorPrototype = {
    *   name - a pretty name to display to the user in the UI
    *
    * Only profiles from which data can be imported should be listed.  Otherwise
    * the behavior of the migration wizard isn't well-defined.
    *
    * For a single-profile source (e.g. safari, ie), this returns null,
    * and not an empty array.  That is the default implementation.
    */
-  get sourceProfiles() {
+  getSourceProfiles() {
     return null;
   },
 
   /**
    * MUST BE OVERRIDDEN.
    *
    * Returns an array of "migration resources" objects for the given profile,
    * or for the "default" profile, if the migrator does not support multiple
@@ -201,18 +201,18 @@ this.MigratorPrototype = {
   },
 
   /**
    * DO NOT OVERRIDE - After deCOMing migration, the UI will just call
    * getResources.
    *
    * @see nsIBrowserProfileMigrator
    */
-  getMigrateData: function MP_getMigrateData(aProfile) {
-    let resources = this._getMaybeCachedResources(aProfile);
+  getMigrateData: async function MP_getMigrateData(aProfile) {
+    let resources = await this._getMaybeCachedResources(aProfile);
     if (!resources) {
       return [];
     }
     let types = resources.map(r => r.type);
     return types.reduce((a, b) => { a |= b; return a; }, 0);
   },
 
   getBrowserKey: function MP_getBrowserKey() {
@@ -220,18 +220,18 @@ this.MigratorPrototype = {
   },
 
   /**
    * DO NOT OVERRIDE - After deCOMing migration, the UI will just call
    * migrate for each resource.
    *
    * @see nsIBrowserProfileMigrator
    */
-  migrate: function MP_migrate(aItems, aStartup, aProfile) {
-    let resources = this._getMaybeCachedResources(aProfile);
+  migrate: async function MP_migrate(aItems, aStartup, aProfile) {
+    let resources = await this._getMaybeCachedResources(aProfile);
     if (resources.length == 0)
       throw new Error("migrate called for a non-existent source");
 
     if (aItems != Ci.nsIBrowserProfileMigrator.ALL)
       resources = resources.filter(r => aItems & r.type);
 
     // Used to periodically give back control to the main-thread loop.
     let unblockMainThread = function() {
@@ -406,49 +406,49 @@ this.MigratorPrototype = {
   },
 
   /**
    * DO NOT OVERRIDE - After deCOMing migration, this code
    * won't be part of the migrator itself.
    *
    * @see nsIBrowserProfileMigrator
    */
-  get sourceExists() {
+  async getSourceExists() {
     if (this.startupOnlyMigrator && !MigrationUtils.isStartupMigration)
       return false;
 
     // For a single-profile source, check if any data is available.
     // For multiple-profiles source, make sure that at least one
     // profile is available.
     let exists = false;
     try {
-      let profiles = this.sourceProfiles;
+      let profiles = await this.getSourceProfiles();
       if (!profiles) {
-        let resources = this._getMaybeCachedResources("");
+        let resources = await this._getMaybeCachedResources("");
         if (resources && resources.length > 0)
           exists = true;
       } else {
         exists = profiles.length > 0;
       }
     } catch (ex) {
       Cu.reportError(ex);
     }
     return exists;
   },
 
   /** * PRIVATE STUFF - DO NOT OVERRIDE ***/
-  _getMaybeCachedResources: function PMB__getMaybeCachedResources(aProfile) {
+  _getMaybeCachedResources: async function PMB__getMaybeCachedResources(aProfile) {
     let profileKey = aProfile ? aProfile.id : "";
     if (this._resourcesByProfile) {
       if (profileKey in this._resourcesByProfile)
         return this._resourcesByProfile[profileKey];
     } else {
       this._resourcesByProfile = { };
     }
-    this._resourcesByProfile[profileKey] = this.getResources(aProfile);
+    this._resourcesByProfile[profileKey] = await this.getResources(aProfile);
     return this._resourcesByProfile[profileKey];
   }
 };
 
 this.MigrationUtils = Object.freeze({
   resourceTypes: {
     SETTINGS:   Ci.nsIBrowserProfileMigrator.SETTINGS,
     COOKIES:    Ci.nsIBrowserProfileMigrator.COOKIES,
@@ -658,16 +658,38 @@ this.MigrationUtils = Object.freeze({
 
   get _migrators() {
     if (!gMigrators) {
       gMigrators = new Map();
     }
     return gMigrators;
   },
 
+  spinResolve: function MU_spinResolve(promise) {
+    if (!(promise instanceof Promise)) {
+      return promise;
+    }
+    let done = false;
+    let result = null;
+    let error = null;
+    promise.catch(e => {
+      error = e;
+    }).then(r => {
+      result = r;
+      done = true;
+    });
+
+    Services.tm.spinEventLoopUntil(() => done);
+    if (error) {
+      throw error;
+    } else {
+      return result;
+    }
+  },
+
   /*
    * Returns the migrator for the given source, if any data is available
    * for this source, or null otherwise.
    *
    * @param aKey internal name of the migration source.
    *             Supported values: ie (windows),
    *                               edge (windows),
    *                               safari (mac),
@@ -680,30 +702,30 @@ this.MigrationUtils = Object.freeze({
    * If null is returned,  either no data can be imported
    * for the given migrator, or aMigratorKey is invalid  (e.g. ie on mac,
    * or mosaic everywhere).  This method should be used rather than direct
    * getService for future compatibility (see bug 718280).
    *
    * @return profile migrator implementing nsIBrowserProfileMigrator, if it can
    *         import any data, null otherwise.
    */
-  getMigrator: function MU_getMigrator(aKey) {
+  getMigrator: async function MU_getMigrator(aKey) {
     let migrator = null;
     if (this._migrators.has(aKey)) {
       migrator = this._migrators.get(aKey);
     } else {
       try {
         migrator = Cc["@mozilla.org/profile/migrator;1?app=browser&type=" +
                       aKey].createInstance(Ci.nsIBrowserProfileMigrator);
       } catch (ex) { Cu.reportError(ex); }
       this._migrators.set(aKey, migrator);
     }
 
     try {
-      return migrator && migrator.sourceExists ? migrator : null;
+      return migrator && (await migrator.getSourceExists()) ? migrator : null;
     } catch (ex) { Cu.reportError(ex); return null; }
   },
 
   /**
    * Figure out what is the default browser, and if there is a migrator
    * for it, return that migrator's internal name.
    * For the time being, the "internal name" of a migrator is its contract-id
    * trailer (e.g. ie for @mozilla.org/profile/migrator;1?app=browser&type=ie),
@@ -868,16 +890,29 @@ this.MigrationUtils = Object.freeze({
 
     Services.ww.openWindow(aOpener,
                            "chrome://browser/content/migration/migration.xul",
                            "_blank",
                            features,
                            params);
   },
 
+  startupMigration:
+  function MU_startupMigrator(aProfileStartup, aMigratorKey, aProfileToMigrate) {
+    if (Services.prefs.getBoolPref("browser.migrate.automigrate.enabled", false)) {
+      this.asyncStartupMigration(aProfileStartup,
+                                 aMigratorKey,
+                                 aProfileToMigrate);
+    } else {
+      this.spinResolve(this.asyncStartupMigration(aProfileStartup,
+                                                  aMigratorKey,
+                                                  aProfileToMigrate));
+    }
+  },
+
   /**
    * Show the migration wizard for startup-migration.  This should only be
    * called by ProfileMigrator (see ProfileMigrator.js), which implements
    * nsIProfileMigrator.
    *
    * @param aProfileStartup
    *        the nsIProfileStartup instance provided to ProfileMigrator.migrate.
    * @param [optional] aMigratorKey
@@ -888,62 +923,63 @@ this.MigrationUtils = Object.freeze({
    *        migrator for it, or with the first option selected as a fallback
    *        (The first option is hardcoded to be the most common browser for
    *         the OS we run on.  See migration.xul).
    * @param [optional] aProfileToMigrate
    *        If set, the migration wizard will import from the profile indicated.
    * @throws if aMigratorKey is invalid or if it points to a non-existent
    *         source.
    */
-  startupMigration:
-  function MU_startupMigrator(aProfileStartup, aMigratorKey, aProfileToMigrate) {
+  asyncStartupMigration:
+  async function MU_asyncStartupMigrator(aProfileStartup, aMigratorKey, aProfileToMigrate) {
     if (!aProfileStartup) {
       throw new Error("an profile-startup instance is required for startup-migration");
     }
     gProfileStartup = aProfileStartup;
 
     let skipSourcePage = false, migrator = null, migratorKey = "";
     if (aMigratorKey) {
-      migrator = this.getMigrator(aMigratorKey);
+      migrator = await this.getMigrator(aMigratorKey);
       if (!migrator) {
         // aMigratorKey must point to a valid source, so, if it doesn't
         // cleanup and throw.
         this.finishMigration();
         throw new Error("startMigration was asked to open auto-migrate from " +
                         "a non-existent source: " + aMigratorKey);
       }
       migratorKey = aMigratorKey;
       skipSourcePage = true;
     } else {
       let defaultBrowserKey = this.getMigratorKeyForDefaultBrowser();
       if (defaultBrowserKey) {
-        migrator = this.getMigrator(defaultBrowserKey);
+        migrator = await this.getMigrator(defaultBrowserKey);
         if (migrator)
           migratorKey = defaultBrowserKey;
       }
     }
 
     if (!migrator) {
+      let migrators = await Promise.all(gAvailableMigratorKeys.map(key => this.getMigrator(key)));
       // If there's no migrator set so far, ensure that there is at least one
       // migrator available before opening the wizard.
       // Note that we don't need to check the default browser first, because
       // if that one existed we would have used it in the block above this one.
-      if (!gAvailableMigratorKeys.some(key => !!this.getMigrator(key))) {
+      if (!migrators.some(m => m)) {
         // None of the keys produced a usable migrator, so finish up here:
         this.finishMigration();
         return;
       }
     }
 
     let isRefresh = migrator && skipSourcePage &&
                     migratorKey == AppConstants.MOZ_APP_NAME;
 
     if (!isRefresh && AutoMigrate.enabled) {
       try {
-        AutoMigrate.migrate(aProfileStartup, migratorKey, aProfileToMigrate);
+        await AutoMigrate.migrate(aProfileStartup, migratorKey, aProfileToMigrate);
         return;
       } catch (ex) {
         // If automigration failed, continue and show the dialog.
         Cu.reportError(ex);
       }
     }
 
     let migrationEntryPoint = this.MIGRATION_ENTRYPOINT_FIRSTRUN;
--- a/browser/components/migration/nsIBrowserProfileMigrator.idl
+++ b/browser/components/migration/nsIBrowserProfileMigrator.idl
@@ -33,40 +33,40 @@ interface nsIBrowserProfileMigrator : ns
   void migrate(in unsigned short aItems, in nsIProfileStartup aStartup, in jsval aProfile);
 
   /**
    * A bit field containing profile items that this migrator
    * offers for import. 
    * @param   aProfile the profile that we are looking for available data
    *          to import
    * @param   aDoingStartup "true" if the profile is not currently being used.
-   * @return  bit field containing profile items (see above)
+   * @return  Promise containing a bit field containing profile items (see above)
    * @note    a return value of 0 represents no items rather than ALL.
    */
-  unsigned short getMigrateData(in jsval aProfile, in boolean aDoingStartup);
+  jsval getMigrateData(in jsval aProfile, in boolean aDoingStartup);
 
   /**
    * Get the last time data from this browser was modified
    * @return a promise that resolves to a JS Date object
    */
   jsval getLastUsedDate();
 
   /**
    * Whether or not there is any data that can be imported from this
    * browser (i.e. whether or not it is installed, and there exists
    * a user profile)
    */
-  readonly attribute boolean          sourceExists;
+  jsval getSourceExists();
 
 
   /**
    * An enumeration of available profiles. If the import source does
    * not support profiles, this attribute is null.
    */
-  readonly attribute jsval            sourceProfiles;
+  jsval getSourceProfiles();
 
   /**
    * The import source homepage.  Returns null if not present/available
    */
   readonly attribute AUTF8String      sourceHomePageURL;
 
 
   /**