Bug 862127 - Remove all sync IO from Chrome migration r?Gijs draft
authorDoug Thayer <dothayer@mozilla.com>
Thu, 11 Jan 2018 09:52:56 -0800
changeset 723849 909545b0096eb6b5092ab279bd1d4967b559f90f
parent 723848 883d79bef8f643006d53bfe44a71dca191380a9b
child 723850 b90c554cb6b15609d989e4409830e397338c6f49
push id96559
push userbmo:dothayer@mozilla.com
push dateTue, 23 Jan 2018 23:30:16 +0000
reviewersGijs
bugs862127
milestone60.0a1
Bug 862127 - Remove all sync IO from Chrome migration r?Gijs Now that the interfaces are all async, we can simply replace all the sync IO in the Chrome migrator with the equivalent async IO. Other browsers will be addressed in separate patches. MozReview-Commit-ID: FyGRRKY57Gm
browser/components/migration/ChromeMigrationUtils.jsm
browser/components/migration/ChromeProfileMigrator.js
--- a/browser/components/migration/ChromeMigrationUtils.jsm
+++ b/browser/components/migration/ChromeMigrationUtils.jsm
@@ -1,20 +1,18 @@
 /* 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/. */
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["ChromeMigrationUtils"];
 
 const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
-const FILE_INPUT_STREAM_CID = "@mozilla.org/network/file-input-stream;1";
 
 Cu.import("resource://gre/modules/AppConstants.jsm");
-Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 this.ChromeMigrationUtils = {
   _chromeUserDataPath: null,
 
@@ -32,17 +30,20 @@ this.ChromeMigrationUtils = {
   // }
   _extensionLocaleStrings: {},
 
   /**
    * Get all extensions installed in a specific profile.
    * @param {String} profileId - A Chrome user profile ID. For example, "Profile 1".
    * @returns {Array} All installed Chrome extensions information.
    */
-  async getExtensionList(profileId = this.getLastUsedProfileId()) {
+  async getExtensionList(profileId) {
+    if (profileId === undefined) {
+      profileId = await this.getLastUsedProfileId();
+    }
     let path = this.getExtensionPath(profileId);
     let iterator = new OS.File.DirectoryIterator(path);
     let extensionList = [];
     await iterator.forEach(async entry => {
       if (entry.isDir) {
         let extensionInformation = await this.getExtensionInformation(entry.name, profileId);
         if (extensionInformation) {
           extensionList.push(extensionInformation);
@@ -53,17 +54,20 @@ this.ChromeMigrationUtils = {
   },
 
   /**
    * Get information of a specific Chrome extension.
    * @param {String} extensionId - The extension ID.
    * @param {String} profileId - The user profile's ID.
    * @retruns {Object} The Chrome extension information.
    */
-  async getExtensionInformation(extensionId, profileId = this.getLastUsedProfileId()) {
+  async getExtensionInformation(extensionId, profileId) {
+    if (profileId === undefined) {
+      profileId = await this.getLastUsedProfileId();
+    }
     let extensionInformation = null;
     try {
       let manifestPath = this.getExtensionPath(profileId);
       manifestPath = OS.Path.join(manifestPath, extensionId);
       // If there are multiple sub-directories in the extension directory,
       // read the files in the latest directory.
       let directories = await this._getSortedByVersionSubDirectoryNames(manifestPath);
       if (!directories[0]) {
@@ -146,50 +150,44 @@ this.ChromeMigrationUtils = {
   },
 
   /**
    * Check that a specific extension is installed or not.
    * @param {String} extensionId - The extension ID.
    * @param {String} profileId - The user profile's ID.
    * @returns {Boolean} Return true if the extension is installed otherwise return false.
    */
-  async isExtensionInstalled(extensionId, profileId = this.getLastUsedProfileId()) {
+  async isExtensionInstalled(extensionId, profileId) {
+    if (profileId === undefined) {
+      profileId = await this.getLastUsedProfileId();
+    }
     let extensionPath = this.getExtensionPath(profileId);
     let isInstalled = await OS.File.exists(OS.Path.join(extensionPath, extensionId));
     return isInstalled;
   },
 
   /**
    * Get the last used user profile's ID.
    * @returns {String} The last used user profile's ID.
    */
-  getLastUsedProfileId() {
-    let localState = this.getLocalState();
+  async getLastUsedProfileId() {
+    let localState = await this.getLocalState();
     return localState ? localState.profile.last_used : "Default";
   },
 
   /**
    * Get the local state file content.
    * @returns {Object} The JSON-based content.
    */
-  getLocalState() {
-    let localStateFile = new FileUtils.File(this.getChromeUserDataPath());
-    localStateFile.append("Local State");
-    if (!localStateFile.exists())
-      throw new Error("Chrome's 'Local State' file does not exist.");
-    if (!localStateFile.isReadable())
-      throw new Error("Chrome's 'Local State' file could not be read.");
-
+  async getLocalState() {
     let localState = null;
     try {
-      let fstream = Cc[FILE_INPUT_STREAM_CID].createInstance(Ci.nsIFileInputStream);
-      fstream.init(localStateFile, -1, 0, 0);
-      let inputStream = NetUtil.readInputStreamToString(fstream, fstream.available(),
-                                                        { charset: "UTF-8" });
-      localState = JSON.parse(inputStream);
+      let localStatePath = OS.Path.join(this.getChromeUserDataPath(), "Local State");
+      let localStateJson = await OS.File.read(localStatePath, { encoding: "utf-8" });
+      localState = JSON.parse(localStateJson);
     } catch (ex) {
       Cu.reportError(ex);
       throw ex;
     }
     return localState;
   },
 
   /**
--- a/browser/components/migration/ChromeProfileMigrator.js
+++ b/browser/components/migration/ChromeProfileMigrator.js
@@ -3,29 +3,26 @@
 /* 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/. */
 
 "use strict";
 
 const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
 
-const FILE_INPUT_STREAM_CID = "@mozilla.org/network/file-input-stream;1";
-
 const S100NS_FROM1601TO1970 = 0x19DB1DED53E8000;
 const S100NS_PER_MS = 10;
 
 const AUTH_TYPE = {
   SCHEME_HTML: 0,
   SCHEME_BASIC: 1,
   SCHEME_DIGEST: 2,
 };
 
 Cu.import("resource://gre/modules/AppConstants.jsm");
-Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/ChromeMigrationUtils.jsm");
 Cu.import("resource:///modules/MigrationUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
@@ -87,147 +84,159 @@ function convertBookmarks(items, errorAc
       Cu.reportError(ex);
       errorAccumulator(ex);
     }
   }
   return itemsToInsert;
 }
 
 function ChromeProfileMigrator() {
-  let path = ChromeMigrationUtils.getDataPath("Chrome");
-  let chromeUserDataFolder = new FileUtils.File(path);
-  this._chromeUserDataFolder = chromeUserDataFolder.exists() ?
-    chromeUserDataFolder : null;
+  this._chromeUserDataPathSuffix = "Chrome";
 }
 
 ChromeProfileMigrator.prototype = Object.create(MigratorPrototype);
 
+ChromeProfileMigrator.prototype._getChromeUserDataPathIfExists = async function() {
+  if (this._chromeUserDataPath) {
+    return this._chromeUserDataPath;
+  }
+  let path = ChromeMigrationUtils.getDataPath(this._chromeUserDataPathSuffix);
+  let exists = await OS.File.exists(path);
+  if (exists) {
+    this._chromeUserDataPath = path;
+  } else {
+    this._chromeUserDataPath = null;
+  }
+  return this._chromeUserDataPath;
+};
+
 ChromeProfileMigrator.prototype.getResources =
-  function Chrome_getResources(aProfile) {
-    if (this._chromeUserDataFolder) {
-      let profileFolder = this._chromeUserDataFolder.clone();
-      profileFolder.append(aProfile.id);
-      if (profileFolder.exists()) {
-        let possibleResources = [
+  async function Chrome_getResources(aProfile) {
+    let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
+    if (chromeUserDataPath) {
+      let profileFolder = OS.Path.join(chromeUserDataPath, aProfile.id);
+      if (await OS.File.exists(profileFolder)) {
+        let possibleResourcePromises = [
           GetBookmarksResource(profileFolder),
           GetHistoryResource(profileFolder),
           GetCookiesResource(profileFolder),
         ];
         if (AppConstants.platform == "win") {
-          possibleResources.push(GetWindowsPasswordsResource(profileFolder));
+          possibleResourcePromises.push(GetWindowsPasswordsResource(profileFolder));
         }
+        let possibleResources = await Promise.all(possibleResourcePromises);
         return possibleResources.filter(r => r != null);
       }
     }
     return [];
   };
 
 ChromeProfileMigrator.prototype.getLastUsedDate =
-  function Chrome_getLastUsedDate() {
-    let datePromises = this.sourceProfiles.map(profile => {
-      let basePath = OS.Path.join(this._chromeUserDataFolder.path, profile.id);
-      let fileDatePromises = ["Bookmarks", "History", "Cookies"].map(leafName => {
+  async function Chrome_getLastUsedDate() {
+    let sourceProfiles = await this.getSourceProfiles();
+    let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
+    if (!chromeUserDataPath) {
+      return new Date(0);
+    }
+    let datePromises = sourceProfiles.map(async profile => {
+      let basePath = OS.Path.join(chromeUserDataPath, profile.id);
+      let fileDatePromises = ["Bookmarks", "History", "Cookies"].map(async leafName => {
         let path = OS.Path.join(basePath, leafName);
-        return OS.File.stat(path).catch(() => null).then(info => {
-          return info ? info.lastModificationDate : 0;
-        });
+        let info = await OS.File.stat(path).catch(() => null);
+        return info ? info.lastModificationDate : 0;
       });
-      return Promise.all(fileDatePromises).then(dates => {
-        return Math.max.apply(Math, dates);
-      });
+      let dates = await Promise.all(fileDatePromises);
+      return Math.max(...dates);
     });
-    return Promise.all(datePromises).then(dates => {
-      dates.push(0);
-      return new Date(Math.max.apply(Math, dates));
-    });
+    let datesOuter = await Promise.all(datePromises);
+    datesOuter.push(0);
+    return new Date(Math.max(...datesOuter));
   };
 
 ChromeProfileMigrator.prototype.getSourceProfiles =
   async function Chrome_getSourceProfiles() {
     if ("__sourceProfiles" in this)
       return this.__sourceProfiles;
 
-    if (!this._chromeUserDataFolder)
+    let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
+    if (!chromeUserDataPath)
       return [];
 
     let profiles = [];
     try {
-      let info_cache = ChromeMigrationUtils.getLocalState().profile.info_cache;
+      let localState = await ChromeMigrationUtils.getLocalState();
+      let info_cache = localState.profile.info_cache;
       for (let profileFolderName in info_cache) {
-        let profileFolder = this._chromeUserDataFolder.clone();
-        profileFolder.append(profileFolderName);
         profiles.push({
           id: profileFolderName,
           name: info_cache[profileFolderName].name || profileFolderName,
         });
       }
     } catch (e) {
       Cu.reportError("Error detecting Chrome profiles: " + e);
       // If we weren't able to detect any profiles above, fallback to the Default profile.
-      let defaultProfileFolder = this._chromeUserDataFolder.clone();
-      defaultProfileFolder.append("Default");
-      if (defaultProfileFolder.exists()) {
+      let defaultProfilePath = OS.Path.join(chromeUserDataPath, "Default");
+      if (await OS.File.exists(defaultProfilePath)) {
         profiles = [{
           id: "Default",
           name: "Default",
         }];
       }
     }
 
+    let profileResources = await Promise.all(profiles.map(async profile => ({
+      profile,
+      resources: await this.getResources(profile),
+    })));
+
     // Only list profiles from which any data can be imported
-    this.__sourceProfiles = profiles.filter(function(profile) {
-      let resources = this.getResources(profile);
+    this.__sourceProfiles = profileResources.filter(({resources}) => {
       return resources && resources.length > 0;
-    }, this);
+    }, this).map(({profile}) => profile);
     return this.__sourceProfiles;
   };
 
 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);
+    let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
+    if (!chromeUserDataPath)
+      return "";
+    let prefsPath = OS.Path.join(chromeUserDataPath, "Preferences");
+    if (await OS.File.exists(prefsPath)) {
       try {
-        return JSON.parse(
-          NetUtil.readInputStreamToString(fstream, fstream.available(),
-                                          { charset: "UTF-8" })
-            ).homepage;
+        let json = await OS.File.read(prefsPath, {encoding: "UTF-8"});
+        return JSON.parse(json).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;
   },
 });
 
-function GetBookmarksResource(aProfileFolder) {
-  let bookmarksFile = aProfileFolder.clone();
-  bookmarksFile.append("Bookmarks");
-  if (!bookmarksFile.exists())
+async function GetBookmarksResource(aProfileFolder) {
+  let bookmarksPath = OS.Path.join(aProfileFolder, "Bookmarks");
+  if (!(await OS.File.exists(bookmarksPath)))
     return null;
 
   return {
     type: MigrationUtils.resourceTypes.BOOKMARKS,
 
     migrate(aCallback) {
       return (async function() {
         let gotErrors = false;
         let errorGatherer = function() { gotErrors = true; };
         // Parse Chrome bookmark file that is JSON format
-        let bookmarkJSON = await OS.File.read(bookmarksFile.path, {encoding: "UTF-8"});
+        let bookmarkJSON = await OS.File.read(bookmarksPath, {encoding: "UTF-8"});
         let roots = JSON.parse(bookmarkJSON).roots;
 
         // Importing bookmark bar items
         if (roots.bookmark_bar.children &&
             roots.bookmark_bar.children.length > 0) {
           // Toolbar
           let parentGuid = PlacesUtils.bookmarks.toolbarGuid;
           let bookmarks = convertBookmarks(roots.bookmark_bar.children, errorGatherer);
@@ -254,20 +263,19 @@ function GetBookmarksResource(aProfileFo
           throw new Error("The migration included errors.");
         }
       })().then(() => aCallback(true),
               () => aCallback(false));
     },
   };
 }
 
-function GetHistoryResource(aProfileFolder) {
-  let historyFile = aProfileFolder.clone();
-  historyFile.append("History");
-  if (!historyFile.exists())
+async function GetHistoryResource(aProfileFolder) {
+  let historyPath = OS.Path.join(aProfileFolder, "History");
+  if (!(await OS.File.exists(historyPath)))
     return null;
 
   return {
     type: MigrationUtils.resourceTypes.HISTORY,
 
     migrate(aCallback) {
       (async function() {
         const MAX_AGE_IN_DAYS = Services.prefs.getIntPref("browser.migrate.chrome.history.maxAgeInDays");
@@ -278,17 +286,17 @@ function GetHistoryResource(aProfileFold
           let maxAge = dateToChromeTime(Date.now() - MAX_AGE_IN_DAYS * 24 * 60 * 60 * 1000);
           query += " AND last_visit_time > " + maxAge;
         }
         if (LIMIT) {
           query += " ORDER BY last_visit_time DESC LIMIT " + LIMIT;
         }
 
         let rows =
-          await MigrationUtils.getRowsFromDBWithoutLocks(historyFile.path, "Chrome history", query);
+          await MigrationUtils.getRowsFromDBWithoutLocks(historyPath, "Chrome history", query);
         let places = [];
         for (let row of rows) {
           try {
             // if having typed_count, we changes transition type to typed.
             let transType = PlacesUtils.history.TRANSITION_LINK;
             if (row.getResultByName("typed_count") > 0)
               transType = PlacesUtils.history.TRANSITION_TYPED;
 
@@ -326,28 +334,27 @@ function GetHistoryResource(aProfileFold
               ex => {
                 Cu.reportError(ex);
                 aCallback(false);
               });
     },
   };
 }
 
-function GetCookiesResource(aProfileFolder) {
-  let cookiesFile = aProfileFolder.clone();
-  cookiesFile.append("Cookies");
-  if (!cookiesFile.exists())
+async function GetCookiesResource(aProfileFolder) {
+  let cookiesPath = OS.Path.join(aProfileFolder, "Cookies");
+  if (!(await OS.File.exists(cookiesPath)))
     return null;
 
   return {
     type: MigrationUtils.resourceTypes.COOKIES,
 
     async migrate(aCallback) {
       // We don't support decrypting cookies yet so only import plaintext ones.
-      let rows = await MigrationUtils.getRowsFromDBWithoutLocks(cookiesFile.path, "Chrome cookies",
+      let rows = await MigrationUtils.getRowsFromDBWithoutLocks(cookiesPath, "Chrome cookies",
        `SELECT host_key, name, value, path, expires_utc, secure, httponly, encrypted_value
         FROM cookies
         WHERE length(encrypted_value) = 0`).catch(ex => {
           Cu.reportError(ex);
           aCallback(false);
         });
       // If the promise was rejected we will have already called aCallback,
       // so we can just return here.
@@ -378,27 +385,26 @@ function GetCookiesResource(aProfileFold
           Cu.reportError(e);
         }
       }
       aCallback(true);
     },
   };
 }
 
-function GetWindowsPasswordsResource(aProfileFolder) {
-  let loginFile = aProfileFolder.clone();
-  loginFile.append("Login Data");
-  if (!loginFile.exists())
+async function GetWindowsPasswordsResource(aProfileFolder) {
+  let loginPath = OS.Path.join(aProfileFolder, "Login Data");
+  if (!(await OS.File.exists(loginPath)))
     return null;
 
   return {
     type: MigrationUtils.resourceTypes.PASSWORDS,
 
     async migrate(aCallback) {
-      let rows = await MigrationUtils.getRowsFromDBWithoutLocks(loginFile.path, "Chrome passwords",
+      let rows = await MigrationUtils.getRowsFromDBWithoutLocks(loginPath, "Chrome passwords",
        `SELECT origin_url, action_url, username_element, username_value,
         password_element, password_value, signon_realm, scheme, date_created,
         times_used FROM logins WHERE blacklisted_by_user = 0`).catch(ex => {
           Cu.reportError(ex);
           aCallback(false);
         });
       // If the promise was rejected we will have already called aCallback,
       // so we can just return here.
@@ -463,36 +469,32 @@ ChromeProfileMigrator.prototype.classDes
 ChromeProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=chrome";
 ChromeProfileMigrator.prototype.classID = Components.ID("{4cec1de4-1671-4fc3-a53e-6c539dc77a26}");
 
 
 /**
  *  Chromium migration
  **/
 function ChromiumProfileMigrator() {
-  let path = ChromeMigrationUtils.getDataPath("Chromium");
-  let chromiumUserDataFolder = new FileUtils.File(path);
-  this._chromeUserDataFolder = chromiumUserDataFolder.exists() ? chromiumUserDataFolder : null;
+  this._chromeUserDataPathSuffix = "Chromium";
 }
 
 ChromiumProfileMigrator.prototype = Object.create(ChromeProfileMigrator.prototype);
 ChromiumProfileMigrator.prototype.classDescription = "Chromium Profile Migrator";
 ChromiumProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=chromium";
 ChromiumProfileMigrator.prototype.classID = Components.ID("{8cece922-9720-42de-b7db-7cef88cb07ca}");
 
 var componentsArray = [ChromeProfileMigrator, ChromiumProfileMigrator];
 
 /**
  * Chrome Canary
  * Not available on Linux
  **/
 function CanaryProfileMigrator() {
-  let path = ChromeMigrationUtils.getDataPath("Canary");
-  let chromeUserDataFolder = new FileUtils.File(path);
-  this._chromeUserDataFolder = chromeUserDataFolder.exists() ? chromeUserDataFolder : null;
+  this._chromeUserDataPathSuffix = "Canary";
 }
 CanaryProfileMigrator.prototype = Object.create(ChromeProfileMigrator.prototype);
 CanaryProfileMigrator.prototype.classDescription = "Chrome Canary Profile Migrator";
 CanaryProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=canary";
 CanaryProfileMigrator.prototype.classID = Components.ID("{4bf85aa5-4e21-46ca-825f-f9c51a5e8c76}");
 
 if (AppConstants.platform == "win" || AppConstants.platform == "macosx") {
   componentsArray.push(CanaryProfileMigrator);