Bug 862127 - Make migration interfaces more async r?Gijs draft
authorDoug Thayer <dothayer@mozilla.com>
Fri, 12 Jan 2018 09:06:21 -0800
changeset 723847 78f760f09356b9409cf63fadfe5708d22ecdbae8
parent 723788 c5461973d6ee7845b3f560c05e1502429fd63184
child 723848 883d79bef8f643006d53bfe44a71dca191380a9b
push id96559
push userbmo:dothayer@mozilla.com
push dateTue, 23 Jan 2018 23:30:16 +0000
reviewersGijs
bugs862127
milestone60.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/IEProfileMigrator.js
browser/components/migration/MigrationUtils.jsm
browser/components/migration/SafariProfileMigrator.js
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,21 +173,20 @@ 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() {
+ChromeProfileMigrator.prototype.getSourceHomePageURL =
+  async function Chrome_getSourceHomePageURL() {
     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].
                     createInstance(Ci.nsIFileInputStream);
       fstream.init(prefsFile, -1, 0, 0);
       try {
@@ -195,18 +194,17 @@ Object.defineProperty(ChromeProfileMigra
           NetUtil.readInputStreamToString(fstream, fstream.available(),
                                           { charset: "UTF-8" })
             ).homepage;
       } catch (e) {
         Cu.reportError("Error parsing Chrome's preferences file: " + e);
       }
     }
     return "";
-  },
-});
+  };
 
 Object.defineProperty(ChromeProfileMigrator.prototype, "sourceLocked", {
   get: function Chrome_sourceLocked() {
     // There is an exclusive lock on some SQLite databases. Assume they are locked for now.
     return true;
   },
 });
 
--- 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 => {
@@ -403,20 +404,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/IEProfileMigrator.js
+++ b/browser/components/migration/IEProfileMigrator.js
@@ -373,40 +373,38 @@ IEProfileMigrator.prototype.getLastUsedD
     // dates is an array of PRTimes, which are in microseconds - convert to milliseconds
     resolve(Math.max.apply(Math, dates) / 1000);
   }));
   return Promise.all(datePromises).then(dates => {
     return new Date(Math.max.apply(Math, dates));
   });
 };
 
-Object.defineProperty(IEProfileMigrator.prototype, "sourceHomePageURL", {
-  get: function IE_get_sourceHomePageURL() {
-    let defaultStartPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
-                                                      kMainKey, "Default_Page_URL");
-    let startPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
-                                               kMainKey, "Start Page");
-    // If the user didn't customize the Start Page, he is still on the default
-    // page, that may be considered the equivalent of our about:home.  There's
-    // no reason to retain it, since it is heavily targeted to IE.
-    let homepage = startPage != defaultStartPage ? startPage : "";
+IEProfileMigrator.prototype.getSourceHomePageURL = function IE_getSourceHomePageURL() {
+  let defaultStartPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
+                                                    kMainKey, "Default_Page_URL");
+  let startPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+                                             kMainKey, "Start Page");
+  // If the user didn't customize the Start Page, he is still on the default
+  // page, that may be considered the equivalent of our about:home.  There's
+  // no reason to retain it, since it is heavily targeted to IE.
+  let homepage = startPage != defaultStartPage ? startPage : "";
 
-    // IE7+ supports secondary home pages located in a REG_MULTI_SZ key.  These
-    // are in addition to the Start Page, and no empty entries are possible,
-    // thus a Start Page is always defined if any of these exists, though it
-    // may be the default one.
-    let secondaryPages = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
-                                                    kMainKey, "Secondary Start Pages");
-    if (secondaryPages) {
-      if (homepage)
-        secondaryPages.unshift(homepage);
-      homepage = secondaryPages.join("|");
-    }
+  // IE7+ supports secondary home pages located in a REG_MULTI_SZ key.  These
+  // are in addition to the Start Page, and no empty entries are possible,
+  // thus a Start Page is always defined if any of these exists, though it
+  // may be the default one.
+  let secondaryPages = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+                                                  kMainKey, "Secondary Start Pages");
+  if (secondaryPages) {
+    if (homepage)
+      secondaryPages.unshift(homepage);
+    homepage = secondaryPages.join("|");
+  }
 
-    return homepage;
-  },
-});
+  return homepage;
+};
 
 IEProfileMigrator.prototype.classDescription = "IE Profile Migrator";
 IEProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=ie";
 IEProfileMigrator.prototype.classID = Components.ID("{3d2532e3-4932-4774-b7ba-968f5899d3a4}");
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([IEProfileMigrator]);
--- a/browser/components/migration/MigrationUtils.jsm
+++ b/browser/components/migration/MigrationUtils.jsm
@@ -78,17 +78,17 @@ function getMigrationBundle() {
  * 2. Create the prototype for the migrator, extending MigratorPrototype.
  *    Namely: MosaicMigrator.prototype = Object.create(MigratorPrototype);
  * 3. Set classDescription, contractID and classID for your migrator, and set
  *    NSGetFactory appropriately.
  * 4. If the migrator supports multiple profiles, override the sourceProfiles
  *    Here we default for single-profile migrator.
  * 5. Implement getResources(aProfile) (see below).
  * 6. If the migrator supports reading the home page of the source browser,
- *    override |sourceHomePageURL| getter.
+ *    override |getSourceHomePageURL| getter.
  * 7. For startup-only migrators, override |startupOnlyMigrator|.
  */
 this.MigratorPrototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserProfileMigrator]),
 
   /**
    * OVERRIDE IF AND ONLY IF the source supports multiple profiles.
    *
@@ -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 isSourceAvailable() {
     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.isSourceAvailable()) ? 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),
@@ -871,17 +893,18 @@ this.MigrationUtils = Object.freeze({
                            "_blank",
                            features,
                            params);
   },
 
   /**
    * Show the migration wizard for startup-migration.  This should only be
    * called by ProfileMigrator (see ProfileMigrator.js), which implements
-   * nsIProfileMigrator.
+   * nsIProfileMigrator. This runs asynchronously if we are running an
+   * automigration.
    *
    * @param aProfileStartup
    *        the nsIProfileStartup instance provided to ProfileMigrator.migrate.
    * @param [optional] aMigratorKey
    *        If set, the migration wizard will import from the corresponding
    *        migrator, bypassing the source-selection page.  Otherwise, the
    *        source-selection page will be displayed, either with the default
    *        browser selected, if it could be detected and if there is a
@@ -890,60 +913,74 @@ this.MigrationUtils = Object.freeze({
    *         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) {
+    if (Services.prefs.getBoolPref("browser.migrate.automigrate.enabled", false)) {
+      this.asyncStartupMigration(aProfileStartup,
+                                 aMigratorKey,
+                                 aProfileToMigrate);
+    } else {
+      this.spinResolve(this.asyncStartupMigration(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/SafariProfileMigrator.js
+++ b/browser/components/migration/SafariProfileMigrator.js
@@ -283,34 +283,16 @@ MainPreferencesPropertyList.prototype = 
           } catch (ex) {
             Cu.reportError(ex);
           }
         }
         this._callbacks.splice(0);
       });
     }
   },
-
-  // Workaround for nsIBrowserProfileMigrator.sourceHomePageURL until
-  // it's replaced with an async method.
-  _readSync: function MPPL__readSync() {
-    if ("_dict" in this)
-      return this._dict;
-
-    let inputStream = Cc["@mozilla.org/network/file-input-stream;1"].
-                      createInstance(Ci.nsIFileInputStream);
-    inputStream.init(this._file, -1, -1, 0);
-    let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].
-                       createInstance(Ci.nsIBinaryInputStream);
-    binaryStream.setInputStream(inputStream);
-    let bytes = binaryStream.readByteArray(inputStream.available());
-    this._dict = PropertyListUtils._readFromArrayBufferSync(
-      new Uint8Array(bytes).buffer);
-    return this._dict;
-  },
 };
 
 function SearchStrings(aMainPreferencesPropertyListInstance) {
   this._mainPreferencesPropertyList = aMainPreferencesPropertyListInstance;
 }
 SearchStrings.prototype = {
   type: MigrationUtils.resourceTypes.OTHERDATA,
 
@@ -397,24 +379,22 @@ Object.defineProperty(SafariProfileMigra
       }
       this._mainPreferencesPropertyList = null;
       return this._mainPreferencesPropertyList;
     }
     return this._mainPreferencesPropertyList;
   },
 });
 
-Object.defineProperty(SafariProfileMigrator.prototype, "sourceHomePageURL", {
-  get: function get_sourceHomePageURL() {
-    if (this.mainPreferencesPropertyList) {
-      let dict = this.mainPreferencesPropertyList._readSync();
-      if (dict.has("HomePage"))
-        return dict.get("HomePage");
-    }
-    return "";
-  },
-});
+SafariProfileMigrator.prototype.getSourceHomePageURL = async function SM_getSourceHomePageURL() {
+  if (this.mainPreferencesPropertyList) {
+    let dict = await new Promise(resolve => this.mainPreferencesPropertyList.read(resolve));
+    if (dict.has("HomePage"))
+      return dict.get("HomePage");
+  }
+  return "";
+};
 
 SafariProfileMigrator.prototype.classDescription = "Safari Profile Migrator";
 SafariProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=safari";
 SafariProfileMigrator.prototype.classID = Components.ID("{4b609ecf-60b2-4655-9df4-dc149e474da1}");
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SafariProfileMigrator]);
--- a/browser/components/migration/nsIBrowserProfileMigrator.idl
+++ b/browser/components/migration/nsIBrowserProfileMigrator.idl
@@ -33,45 +33,48 @@ 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
+   * Get 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)
+   * @return a promise that resolves with a boolean.
    */
-  readonly attribute boolean          sourceExists;
+  jsval isSourceAvailable();
 
 
   /**
    * An enumeration of available profiles. If the import source does
    * not support profiles, this attribute is null.
+   * @return a promise that resolves with an array of profiles or null.
    */
-  readonly attribute jsval            sourceProfiles;
+  jsval getSourceProfiles();
 
   /**
    * The import source homepage.  Returns null if not present/available
+   * @return a promise that resolves with a string or null.
    */
-  readonly attribute AUTF8String      sourceHomePageURL;
+  jsval getSourceHomePageURL();
 
 
   /**
    * Whether the source browser data is locked/in-use meaning migration likely
    * won't succeed and the user should be warned.
    */
   readonly attribute boolean          sourceLocked;
 };