Bug 273874 - Firefox migrator for new profiles r=mak
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Tue, 29 Nov 2011 02:30:40 -0800
changeset 85432 c550ffd3ba74600b3f892a75807d21e70e253a75
parent 85431 b6d90462cc6d882d15846848cbc45362f63f4f51
child 85433 f8974d08b3e51dc3ccbddc9e9ba3cdd9030144c9
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak
bugs273874
milestone12.0a1
Bug 273874 - Firefox migrator for new profiles r=mak
browser/components/migration/content/migration.js
browser/components/migration/content/migration.xul
browser/components/migration/src/BrowserProfileMigrators.manifest
browser/components/migration/src/FirefoxProfileMigrator.js
browser/components/migration/src/Makefile.in
browser/components/migration/src/nsProfileMigrator.cpp
browser/installer/package-manifest.in
browser/locales/en-US/chrome/browser/migration/migration.dtd
browser/locales/en-US/chrome/browser/migration/migration.properties
toolkit/components/places/PlacesUtils.jsm
--- a/browser/components/migration/content/migration.js
+++ b/browser/components/migration/content/migration.js
@@ -334,16 +334,19 @@ var MigrationWizard = {
         source = "sourceNameIE";
         break;
       case "safari":
         source = "sourceNameSafari";
         break;
       case "chrome":
         source = "sourceNameChrome";
         break;
+      case "firefox":
+        source = "sourceNameFirefox";
+        break;
     }
 
     // semi-wallpaper for crash when multiple profiles exist, since we haven't initialized mSourceProfile in places
     this._migrator.getMigrateData(this._selectedProfile, this._autoMigrate);
 
     var oldHomePageURL = this._migrator.sourceHomePageURL;
 
     if (oldHomePageURL && source) {
--- a/browser/components/migration/content/migration.xul
+++ b/browser/components/migration/content/migration.xul
@@ -71,16 +71,17 @@
 #ifdef XP_MACOSX
       <radio id="safari"    label="&importFromSafari.label;"    accesskey="&importFromSafari.accesskey;"/>
 #elifdef XP_WIN
 #ifndef NO_IE_MIGRATOR
       <radio id="ie"        label="&importFromIE.label;"        accesskey="&importFromIE.accesskey;"/>
 #endif
 #endif
       <radio id="chrome"    label="&importFromChrome.label;"    accesskey="&importFromChrome.accesskey;"/>
+      <radio id="firefox"   label="&importFromFirefox.label;"   accesskey="&importFromFirefox.accesskey;"/>
       <radio id="fromfile"  label="&importFromHTMLFile.label;"  accesskey="&importFromHTMLFile.accesskey;" hidden="true"/>
       <radio id="nothing"   label="&importFromNothing.label;"   accesskey="&importFromNothing.accesskey;" hidden="true"/>
     </radiogroup>
     <label id="noSources" hidden="true">&noMigrationSources.label;</label>
   </wizardpage>
 
   <wizardpage id="selectProfile" pageid="selectProfile" label="&selectProfile.title;"
               next="importItems"
--- a/browser/components/migration/src/BrowserProfileMigrators.manifest
+++ b/browser/components/migration/src/BrowserProfileMigrators.manifest
@@ -1,2 +1,4 @@
 component {4cec1de4-1671-4fc3-a53e-6c539dc77a26} ChromeProfileMigrator.js
 contract @mozilla.org/profile/migrator;1?app=browser&type=chrome {4cec1de4-1671-4fc3-a53e-6c539dc77a26}
+component {91185366-ba97-4438-acba-48deaca63386} FirefoxProfileMigrator.js
+contract @mozilla.org/profile/migrator;1?app=browser&type=firefox {91185366-ba97-4438-acba-48deaca63386}
copy from browser/components/migration/src/ChromeProfileMigrator.js
copy to browser/components/migration/src/FirefoxProfileMigrator.js
--- a/browser/components/migration/src/ChromeProfileMigrator.js
+++ b/browser/components/migration/src/FirefoxProfileMigrator.js
@@ -1,605 +1,404 @@
 /* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * vim: sw=2 ts=2 sts=2 et
- * ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License
- * Version 1.1 (the "License"); you may not use this file except in
- * compliance with the License. You may obtain a copy of the License
- * at http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS"
- * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
- * the License for the specific language governing rights and
- * limitations under the License.
- *
- * The Original Code is the Browser Profile Migrator.
- *
- * The Initial Developer of the Original Code is the Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2011
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Makoto Kato <m_kato@ga2.so-net.ne.jp> (Original Author)
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
+ * This Source Code is subject to the terms of the Mozilla Public License
+ * version 2.0 (the "License"). You can obtain a copy of the License at
+ * http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Migrates from a Firefox profile in a lossy manner in order to clean up a user's profile. Data
+ * is only migrated where the benefits outweigh the potential problems caused by importing
+ * undesired/invalid configurations from the source profile.
+ */
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 const MIGRATOR = Ci.nsIBrowserProfileMigrator;
 
 const LOCAL_FILE_CID = "@mozilla.org/file/local;1";
-const FILE_INPUT_STREAM_CID = "@mozilla.org/network/file-input-stream;1";
-
-const BUNDLE_MIGRATION = "chrome://browser/locale/migration/migration.properties";
-
-const S100NS_FROM1601TO1970 = 0x19DB1DED53E8000;
-const S100NS_PER_MS = 10;
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
 Components.utils.import("resource://gre/modules/NetUtil.jsm");
-
-XPCOMUtils.defineLazyGetter(this, "bookmarksSubfolderTitle", function () {
-  // get "import from google chrome" string for folder
-  let strbundle =
-    Services.strings.createBundle(BUNDLE_MIGRATION);
-  let sourceNameChrome = strbundle.GetStringFromName("sourceNameChrome");
-  return strbundle.formatStringFromName("importedBookmarksFolder",
-                                        [sourceNameChrome],
-                                        1);
-});
+Components.utils.import("resource://gre/modules/FileUtils.jsm");
 
-/**
- * Convert Chrome time format to Date object
- *
- * @param   aTime
- *          Chrome time 
- * @return  converted Date object
- * @note    Google Chrome uses FILETIME / 10 as time.
- *          FILETIME is based on same structure of Windows.
- */
-function chromeTimeToDate(aTime)
+function FirefoxProfileMigrator()
 {
-  return new Date((aTime * S100NS_PER_MS - S100NS_FROM1601TO1970 ) / 10000);
+  // profD is not available when the migrator is run during startup so use ProfDS.
+  this._paths.currentProfile = FileUtils.getDir("ProfDS", []);
 }
 
-/**
- * Insert bookmark items into specific folder.
- *
- * @param   aFolderId
- *          id of folder where items will be inserted
- * @param   aItems
- *          bookmark items to be inserted
- */
-function insertBookmarkItems(aFolderId, aItems)
-{
-  for (let i = 0; i < aItems.length; i++) {
-    let item = aItems[i];
-
-    try {
-      if (item.type == "url") {
-        PlacesUtils.bookmarks.insertBookmark(aFolderId,
-                                             NetUtil.newURI(item.url),
-                                             PlacesUtils.bookmarks.DEFAULT_INDEX,
-                                             item.name);
-      } else if (item.type == "folder") {
-        let newFolderId =
-          PlacesUtils.bookmarks.createFolder(aFolderId,
-                                             item.name,
-                                             PlacesUtils.bookmarks.DEFAULT_INDEX);
-
-        insertBookmarkItems(newFolderId, item.children);
-      }
-    } catch (e) {
-      Cu.reportError(e);
-    }
-  }
-}
-
-function ChromeProfileMigrator()
-{
-}
-
-ChromeProfileMigrator.prototype = {
+FirefoxProfileMigrator.prototype = {
   _paths: {
     bookmarks : null,
     cookies : null,
+    currentProfile: null,    // The currently running (destination) profile.
+    encryptionKey: null,
     history : null,
-    prefs : null,
-    userData : null,
+    passwords: null,
   },
 
   _homepageURL : null,
   _replaceBookmarks : false,
   _sourceProfile: null,
   _profilesCache: null,
 
   /**
    * Notify to observers to start migration
    *
    * @param   aType
    *          notification type such as MIGRATOR.BOOKMARKS
    */
-  _notifyStart : function Chrome_notifyStart(aType)
+  _notifyStart : function Firefox_notifyStart(aType)
   {
     Services.obs.notifyObservers(null, "Migration:ItemBeforeMigrate", aType);
     this._pendingCount++;
   },
 
   /**
    * Notify observers that a migration error occured with an item
    *
    * @param   aType
    *          notification type such as MIGRATOR.BOOKMARKS
    */
-  _notifyError : function Chrome_notifyError(aType)
+  _notifyError : function Firefox_notifyError(aType)
   {
     Services.obs.notifyObservers(null, "Migration:ItemError", aType);
   },
 
   /**
    * Notify to observers to finish migration for item
    * If all items are finished, it sends migration end notification.
    *
    * @param   aType
    *          notification type such as MIGRATOR.BOOKMARKS
    */
-  _notifyCompleted : function Chrome_notifyIfCompleted(aType)
+  _notifyCompleted : function Firefox_notifyIfCompleted(aType)
   {
     Services.obs.notifyObservers(null, "Migration:ItemAfterMigrate", aType);
     if (--this._pendingCount == 0) {
       // All items are migrated, so we have to send end notification.
       Services.obs.notifyObservers(null, "Migration:Ended", null);
     }
   },
 
   /**
+   * The directory used for bookmark backups
+   * @return  directory of the bookmark backups
+   */
+  get _bookmarks_backup_folder()
+  {
+    let bookmarksBackupRelativePath = PlacesUtils.backups.profileRelativeFolderPath;
+    let bookmarksBackupDir = this._sourceProfile.clone().QueryInterface(Ci.nsILocalFile);
+    bookmarksBackupDir.appendRelativePath(bookmarksBackupRelativePath);
+    return bookmarksBackupDir;
+  },
+
+  /**
    * Migrating bookmark items
    */
-  _migrateBookmarks : function Chrome_migrateBookmarks()
+  _migrateBookmarks : function Firefox_migrateBookmarks()
   {
     this._notifyStart(MIGRATOR.BOOKMARKS);
 
     try {
-      PlacesUtils.bookmarks.runInBatchMode({
-        _self : this,
-        runBatched : function (aUserData) {
-          let migrator = this._self;
-          let file = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile);
-          file.initWithPath(migrator._paths.bookmarks);
-
-          NetUtil.asyncFetch(file, function(aInputStream, aResultCode) {
-            if (!Components.isSuccessCode(aResultCode)) {
-              migrator._notifyCompleted(MIGRATOR.BOOKMARKS);
-              return;
-            }
-
-            // Parse Chrome bookmark file that is JSON format
-            let bookmarkJSON = NetUtil.readInputStreamToString(aInputStream,
-                                                               aInputStream.available(),
-                                                               { charset : "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 parentId = PlacesUtils.toolbarFolderId;
-              if (!migrator._replaceBookmarks) { 
-                parentId =
-                  PlacesUtils.bookmarks.createFolder(parentId,
-                                                     bookmarksSubfolderTitle,
-                                                     PlacesUtils.bookmarks.DEFAULT_INDEX);
-              }
-              insertBookmarkItems(parentId, roots.bookmark_bar.children);
-            }
-
-            // Importing bookmark menu items
-            if (roots.other.children &&
-                roots.other.children.length > 0) {
-              // Bookmark menu
-              let parentId = PlacesUtils.bookmarksMenuFolderId;
-              if (!migrator._replaceBookmarks) { 
-                parentId =
-                  PlacesUtils.bookmarks.createFolder(parentId,
-                                                     bookmarksSubfolderTitle,
-                                                     PlacesUtils.bookmarks.DEFAULT_INDEX);
-              }
-              insertBookmarkItems(parentId, roots.other.children);
-            }
-
-            migrator._notifyCompleted(MIGRATOR.BOOKMARKS);
-          });
-        }
-      }, null);
+      let srcBackupsDir = this._bookmarks_backup_folder;
+      let backupFolder = this._paths.currentProfile.clone();
+      backupFolder.append(srcBackupsDir.leafName);
+      if (!backupFolder.exists())
+        srcBackupsDir.copyTo(this._paths.currentProfile, null);
     } catch (e) {
       Cu.reportError(e);
-      this._notifyError(MIGRATOR.BOOKMARKS);
+      // Don't notify about backup migration errors since actual bookmarks are
+      // migrated with history.
+    } finally {
       this._notifyCompleted(MIGRATOR.BOOKMARKS);
     }
   },
 
   /**
    * Migrating history
    */
-  _migrateHistory : function Chrome_migrateHistory()
+  _migrateHistory : function Firefox_migrateHistory()
   {
     this._notifyStart(MIGRATOR.HISTORY);
 
     try {
-      PlacesUtils.history.runInBatchMode({
-        _self : this,
-        runBatched : function (aUserData) {
-          // access sqlite3 database of Chrome's history
-          let file = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile);
-          file.initWithPath(this._self._paths.history);
-
-          let dbConn = Services.storage.openUnsharedDatabase(file);
-          let stmt = dbConn.createAsyncStatement(
-              "SELECT url, title, last_visit_time, typed_count FROM urls WHERE hidden = 0");
-
-          stmt.executeAsync({
-            _asyncHistory : Cc["@mozilla.org/browser/history;1"]
-                            .getService(Ci.mozIAsyncHistory),
-            _db : dbConn,
-            _self : this._self,
-            handleResult : function(aResults) {
-              let places = [];
-              for (let row = aResults.getNextRow(); row; row = aResults.getNextRow()) {
-                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;
-
-                  places.push({
-                    uri: NetUtil.newURI(row.getResultByName("url")),
-                    title: row.getResultByName("title"),
-                    visits: [{
-                      transitionType: transType,
-                      visitDate: chromeTimeToDate(
-                                   row.getResultByName(
-                                     "last_visit_time")) * 1000,
-                    }],
-                  });
-                } catch (e) {
-                  Cu.reportError(e);
-                }
-              }
-
-              try {
-                this._asyncHistory.updatePlaces(places);
-              } catch (e) {
-                Cu.reportError(e);
-              }
-            },
-
-            handleError : function(aError) {
-              Cu.reportError("Async statement execution returned with '" +
-                             aError.result + "', '" + aError.message + "'");
-            },
-
-            handleCompletion : function(aReason) {
-              this._db.asyncClose();
-              this._self._notifyCompleted(MIGRATOR.HISTORY);
-            }
-          });
-          stmt.finalize();
-        }
-      }, null);
+      // access sqlite3 database of history
+      let file = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile);
+      file.initWithPath(this._paths.history);
+      file.copyTo(this._paths.currentProfile, null);
     } catch (e) {
       Cu.reportError(e);
       this._notifyError(MIGRATOR.HISTORY);
+    } finally {
       this._notifyCompleted(MIGRATOR.HISTORY);
     }
   },
 
   /**
    * Migrating cookies
+   *
+   * @note    Cookie permissions are not migrated since they may be inadvertently set leading to
+   *          website login problems.
    */
-  _migrateCookies : function Chrome_migrateCookies()
+  _migrateCookies : function Firefox_migrateCookies()
   {
     this._notifyStart(MIGRATOR.COOKIES);
 
     try {
-      // Access sqlite3 database of Chrome's cookie
+      // Access sqlite3 database of cookies
       let file = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile);
       file.initWithPath(this._paths.cookies);
-
-      let dbConn = Services.storage.openUnsharedDatabase(file);
-      let stmt = dbConn.createAsyncStatement(
-          "SELECT host_key, path, name, value, secure, httponly, expires_utc FROM cookies");
-
-      stmt.executeAsync({
-        _db : dbConn,
-        _self : this,
-        handleResult : function(aResults) {
-          for (let row = aResults.getNextRow(); row; row = aResults.getNextRow()) {
-            let host_key = row.getResultByName("host_key");
-            if (host_key.match(/^\./)) {
-              // 1st character of host_key may be ".", so we have to remove it
-              host_key = host_key.substr(1);
-            }
-
-            try {
-              let expiresUtc =
-                chromeTimeToDate(row.getResultByName("expires_utc")) / 1000;
-              Services.cookies.add(host_key,
-                                   row.getResultByName("path"),
-                                   row.getResultByName("name"),
-                                   row.getResultByName("value"),
-                                   row.getResultByName("secure"),
-                                   row.getResultByName("httponly"),
-                                   false,
-                                   parseInt(expiresUtc));
-            } catch (e) {
-              Cu.reportError(e);
-            }
-          }
-        },
-
-        handleError : function(aError) {
-          Cu.reportError("Async statement execution returned with '" +
-                         aError.result + "', '" + aError.message + "'");
-        },
-
-        handleCompletion : function(aReason) {
-          this._db.asyncClose();
-          this._self._notifyCompleted(MIGRATOR.COOKIES);
-        },
-      });
-      stmt.finalize();
+      file.copyTo(this._paths.currentProfile, null);
     } catch (e) {
       Cu.reportError(e);
       this._notifyError(MIGRATOR.COOKIES);
+    } finally {
       this._notifyCompleted(MIGRATOR.COOKIES);
     }
   },
 
   /**
+   * Migrating passwords
+   */
+  _migratePasswords : function Firefox_migratePasswords()
+  {
+    this._notifyStart(MIGRATOR.PASSWORDS);
+
+    try {
+      // Access sqlite3 database of passwords
+      let file = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile);
+      file.initWithPath(this._paths.passwords);
+      file.copyTo(this._paths.currentProfile, null);
+
+      let encryptionKey = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile);
+      encryptionKey.initWithPath(this._paths.encryptionKey);
+      encryptionKey.copyTo(this._paths.currentProfile, null);
+    } catch (e) {
+      Cu.reportError(e);
+      this._notifyError(MIGRATOR.PASSWORDS);
+    } finally {
+      this._notifyCompleted(MIGRATOR.PASSWORDS);
+    }
+  },
+
+
+  /**
    * nsIBrowserProfileMigrator interface implementation
    */
 
   /**
    * Let's migrate all items
    *
    * @param   aItems
    *          list of data items to migrate.
    * @param   aStartup
    *          non-null if called during startup.
    * @param   aProfile
-   *          profile directory name to migrate
+   *          profile directory path to migrate from
    */
-  migrate : function Chrome_migrate(aItems, aStartup, aProfile)
+  migrate : function Firefox_migrate(aItems, aStartup, aProfile)
   {
     if (aStartup) {
       aStartup.doStartup();
       this._replaceBookmarks = true;
     }
 
-    this._sourceProfile = aProfile;
-
     Services.obs.notifyObservers(null, "Migration:Started", null);
 
-    // Reset panding count.  If this count becomes 0, "Migration:Ended"
+    // Reset pending count.  If this count becomes 0, "Migration:Ended"
     // notification is sent
     this._pendingCount = 1;
 
     if (aItems & MIGRATOR.HISTORY)
       this._migrateHistory();
 
     if (aItems & MIGRATOR.COOKIES)
       this._migrateCookies();
 
     if (aItems & MIGRATOR.BOOKMARKS)
       this._migrateBookmarks();
 
+    if (aItems & MIGRATOR.PASSWORDS)
+      this._migratePasswords();
+
     if (--this._pendingCount == 0) {
-      // When async imports are immeditelly completed unfortunately,
+      // When async imports are immediately completed unfortunately,
       // this will be called.
       // Usually, this notification is sent by _notifyCompleted()
       Services.obs.notifyObservers(null, "Migration:Ended", null);
     }
   },
 
   /**
    * return supported migration types
    *
    * @param   aProfile
-   *          directory name of the profile
+   *          the profile path to migrate from
    * @param   aDoingStartup
    *          non-null if called during startup.
    * @return  supported migration types
+   *
+   * @todo    Bug 715315 - make sure source databases are not in-use
    */
-  getMigrateData: function Chrome_getMigrateData(aProfile, aDoingStartup)
+  getMigrateData: function Firefox_getMigrateData(aProfile, aDoingStartup)
   {
-    this._sourceProfile = aProfile;
-    let chromeProfileDir = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile);
-    chromeProfileDir.initWithPath(this._paths.userData + aProfile);
+    this._sourceProfile = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile);
+    this._sourceProfile.initWithPath(aProfile);
 
     let result = 0;
-    if (!chromeProfileDir.exists() || !chromeProfileDir.isReadable())
+    if (!this._sourceProfile.exists() || !this._sourceProfile.isReadable()) {
+      Cu.reportError("source profile directory doesn't exist or is not readable");
+      return result;
+    }
+
+    // Migration initiated from the UI is not supported.
+    if (!aDoingStartup)
       return result;
 
-    // bookmark and preference are JSON format
-
+    // Bookmark backups in JSON format
     try {
-      let file = chromeProfileDir.clone();
-      file.append("Bookmarks");
+      let file = this._bookmarks_backup_folder;
       if (file.exists()) {
         this._paths.bookmarks = file.path;
         result += MIGRATOR.BOOKMARKS;
       }
     } catch (e) {
       Cu.reportError(e);
     }
 
-    if (!this._paths.prefs) {
-      let file = chromeProfileDir.clone();
-      file.append("Preferences");
-      this._paths.prefs = file.path;
-    }
-
-    // history and cookies are SQLite database
-
+    // Bookmarks, history and favicons
     try {
-      let file = chromeProfileDir.clone();
-      file.append("History");
+      let file = this._sourceProfile.clone();
+      file.append("places.sqlite");
       if (file.exists()) {
         this._paths.history = file.path;
         result += MIGRATOR.HISTORY;
+        result |= MIGRATOR.BOOKMARKS;
       }
     } catch (e) {
       Cu.reportError(e);
     }
 
+    // Cookies
     try {
-      let file = chromeProfileDir.clone();
-      file.append("Cookies");
+      let file = this._sourceProfile.clone();
+      file.append("cookies.sqlite");
       if (file.exists()) {
         this._paths.cookies = file.path;
         result += MIGRATOR.COOKIES;
       }
     } catch (e) {
       Cu.reportError(e);
     }
 
+    // Passwords & encryption key
+    try {
+      let passwords = this._sourceProfile.clone();
+      passwords.append("signons.sqlite");
+      let encryptionKey = this._sourceProfile.clone();
+      encryptionKey.append("key3.db");
+      if (passwords.exists() && encryptionKey.exists()) {
+        this._paths.passwords = passwords.path;
+        this._paths.encryptionKey = encryptionKey.path;
+        result += MIGRATOR.PASSWORDS;
+      }
+    } catch (e) {
+      Cu.reportError(e);
+    }
+
     return result;
   },
 
   /**
-   * Whether we support migration of Chrome
+   * Whether we support migration of Firefox
    *
    * @return true if supported
    */
   get sourceExists()
   {
-#ifdef XP_WIN
-    this._paths.userData = Services.dirsvc.get("LocalAppData", Ci.nsIFile).path +
-                            "\\Google\\Chrome\\User Data\\";
-#elifdef XP_MACOSX
-    this._paths.userData = Services.dirsvc.get("Home", Ci.nsIFile).path +
-                            "/Library/Application Support/Google/Chrome/";
-#else
-    this._paths.userData = Services.dirsvc.get("Home", Ci.nsIFile).path +
-                            "/.config/google-chrome/";
-#endif
+    let userData = Services.dirsvc.get("DefProfRt", Ci.nsIFile).path;
     let result = 0;
     try {
       let userDataDir = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile);
-      userDataDir.initWithPath(this._paths.userData);
+      userDataDir.initWithPath(userData);
       if (!userDataDir.exists() || !userDataDir.isReadable())
         return false;
 
       let profiles = this.sourceProfiles;
       if (profiles.length < 1)
         return false;
-
-      // check that we can actually get data from the first profile
-      result = this.getMigrateData(profiles.queryElementAt(0, Ci.nsISupportsString), false);
+      // Check that we can get data from at least one profile since profile selection has not
+      // happened yet.
+      for (let i = 0; i < profiles.length; i++) {
+        result = this.getMigrateData(profiles.queryElementAt(i, Ci.nsISupportsString), true);
+        if (result)
+          break;
+      }
     } catch (e) {
       Cu.reportError(e);
     }
     return result > 0;
   },
 
   get sourceHasMultipleProfiles()
   {
     return this.sourceProfiles.length > 1;
   },
 
   get sourceProfiles()
   {
-    let profiles = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
-    try {
-      if (!this._profilesCache) {
-        let localState = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile);
-        // Local State is a JSON file that contains profile info.
-        localState.initWithPath(this._paths.userData + "Local State");
-        if (!localState.exists())
-          throw new Components.Exception("Chrome's 'Local State' file does not exist.",
-                                         Cr.NS_ERROR_FILE_NOT_FOUND);
-        if (!localState.isReadable())
-          throw new Components.Exception("Chrome's 'Local State' file could not be read.",
-                                         Cr.NS_ERROR_FILE_ACCESS_DENIED);
-        let fstream = Cc[FILE_INPUT_STREAM_CID].createInstance(Ci.nsIFileInputStream);
-        fstream.init(localState, -1, 0, 0);
-        let inputStream = NetUtil.readInputStreamToString(fstream, fstream.available(),
-                                                          { charset: "UTF-8" });
-        this._profilesCache = JSON.parse(inputStream).profile.info_cache;
-      }
+    try
+    {
+      if (!this._profilesCache)
+      {
+        this._profilesCache = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+        let profileService = Cc["@mozilla.org/toolkit/profile-service;1"]
+                               .getService(Ci.nsIToolkitProfileService);
+        // Only allow migrating from the default (selected) profile since this will be the only one
+        // returned by the toolkit profile service after bug 214675 has landed.
+        var profile = profileService.selectedProfile;
+        if (profile.rootDir.path === this._paths.currentProfile.path)
+          return null;
 
-      for (let index in this._profilesCache) {
         let str = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
-        str.data = index;
-        profiles.appendElement(str, false);
+        str.data = profile.rootDir.path;
+        this._profilesCache.appendElement(str, false);
       }
     } catch (e) {
-      Cu.reportError("Error detecting Chrome profiles: " + e);
-      // if we weren't able to detect any profiles above, fallback to the Default profile.
-      if (profiles.length < 1) {
-        let str = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
-        // the default profile name is "Default"
-        str.data = "Default";
-        profiles.appendElement(str, false);
-      }
+      Cu.reportError("Error detecting Firefox profiles: " + e);
     }
-    return profiles;
+    return this._profilesCache;
   },
 
   /**
    * Return home page URL
    *
    * @return  home page URL
+   *
+   * @todo    Bug 715348 will migrate preferences such as the homepage
    */
   get sourceHomePageURL()
   {
     try  {
       if (this._homepageURL)
         return this._homepageURL;
-
-      if (!this._paths.prefs)
-        this.getMigrateData(this._sourceProfile, false);
-
-      // XXX reading and parsing JSON is synchronous.
-      let file = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile);
-      file.initWithPath(this._paths.prefs);
-      let fstream = Cc[FILE_INPUT_STREAM_CID].
-                    createInstance(Ci.nsIFileInputStream);
-      fstream.init(file, -1, 0, 0); 
-      this._homepageURL = JSON.parse(
-        NetUtil.readInputStreamToString(fstream, fstream.available(),
-                                        { charset: "UTF-8" })).homepage;
-      return this._homepageURL;
     } catch (e) {
       Cu.reportError(e);
     }
     return "";
   },
 
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsIBrowserProfileMigrator
   ]),
 
-  classDescription: "Chrome Profile Migrator",
-  contractID: "@mozilla.org/profile/migrator;1?app=browser&type=chrome",
-  classID: Components.ID("{4cec1de4-1671-4fc3-a53e-6c539dc77a26}")
+  classDescription: "Firefox Profile Migrator",
+  contractID: "@mozilla.org/profile/migrator;1?app=browser&type=firefox",
+  classID: Components.ID("{91185366-ba97-4438-acba-48deaca63386}")
 };
 
-const NSGetFactory = XPCOMUtils.generateNSGetFactory([ChromeProfileMigrator]);
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([FirefoxProfileMigrator]);
--- a/browser/components/migration/src/Makefile.in
+++ b/browser/components/migration/src/Makefile.in
@@ -60,16 +60,17 @@ endif
 
 ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
 CPPSRCS += nsSafariProfileMigrator.cpp \
            $(NULL)
 endif            
 
 EXTRA_PP_COMPONENTS = \
   ChromeProfileMigrator.js \
+  FirefoxProfileMigrator.js \
   $(NULL)
 
 EXTRA_COMPONENTS = \
 	BrowserProfileMigrators.manifest \
 	$(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
--- a/browser/components/migration/src/nsProfileMigrator.cpp
+++ b/browser/components/migration/src/nsProfileMigrator.cpp
@@ -135,16 +135,17 @@ nsProfileMigrator::Migrate(nsIProfileSta
 
 NS_IMPL_ISUPPORTS1(nsProfileMigrator, nsIProfileMigrator)
 
 #ifdef XP_WIN
 
 #define INTERNAL_NAME_IEXPLORE        "iexplore"
 #define INTERNAL_NAME_MOZILLA_SUITE   "apprunner"
 #define INTERNAL_NAME_CHROME          "chrome"
+#define INTERNAL_NAME_FIREFOX         "firefox"
 #endif
 
 nsresult
 nsProfileMigrator::GetDefaultBrowserMigratorKey(nsACString& aKey,
                                                 nsCOMPtr<nsIBrowserProfileMigrator>& bpm)
 {
 #if XP_WIN
 
@@ -218,29 +219,34 @@ nsProfileMigrator::GetDefaultBrowserMigr
   if (internalName.LowerCaseEqualsLiteral(INTERNAL_NAME_IEXPLORE)) {
     aKey = "ie";
     return NS_OK;
   }
   else if (internalName.LowerCaseEqualsLiteral(INTERNAL_NAME_CHROME)) {
     aKey = "chrome";
     return NS_OK;
   }
+  else if (internalName.LowerCaseEqualsLiteral(INTERNAL_NAME_FIREFOX)) {
+    aKey = "firefox";
+    return NS_OK;
+  }
 
 #else
   bool exists = false;
 #define CHECK_MIGRATOR(browser) do {\
   bpm = do_CreateInstance(NS_BROWSERPROFILEMIGRATOR_CONTRACTID_PREFIX browser);\
   if (bpm)\
     bpm->GetSourceExists(&exists);\
   if (exists) {\
     aKey = browser;\
     return NS_OK;\
   }} while(0)
 
 #if defined(XP_MACOSX)
   CHECK_MIGRATOR("safari");
 #endif
   CHECK_MIGRATOR("chrome");
+  CHECK_MIGRATOR("firefox");
 
 #undef CHECK_MIGRATOR
 #endif
   return NS_ERROR_FAILURE;
 }
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -367,16 +367,17 @@
 @BINPATH@/components/nsTelephonyWorker.js
 @BINPATH@/components/Telephony.manifest
 @BINPATH@/components/Telephony.js
 @BINPATH@/components/nsWifiWorker.js
 @BINPATH@/components/nsWifiWorker.manifest
 #endif
 @BINPATH@/components/BrowserProfileMigrators.manifest
 @BINPATH@/components/ChromeProfileMigrator.js
+@BINPATH@/components/FirefoxProfileMigrator.js
 #ifdef XP_MACOSX
 @BINPATH@/components/libalerts_s.dylib
 #endif
 #ifdef MOZ_ENABLE_DBUS
 @BINPATH@/components/@DLL_PREFIX@dbusservice@DLL_SUFFIX@
 #endif
 @BINPATH@/components/nsINIProcessor.manifest
 @BINPATH@/components/nsINIProcessor.js
--- a/browser/locales/en-US/chrome/browser/migration/migration.dtd
+++ b/browser/locales/en-US/chrome/browser/migration/migration.dtd
@@ -8,16 +8,18 @@
 <!ENTITY importFromIE.label             "Microsoft Internet Explorer">
 <!ENTITY importFromIE.accesskey         "M">
 <!ENTITY importFromNothing.label        "Don't import anything">
 <!ENTITY importFromNothing.accesskey    "D">
 <!ENTITY importFromSafari.label         "Safari">
 <!ENTITY importFromSafari.accesskey     "S">
 <!ENTITY importFromChrome.label         "Chrome">
 <!ENTITY importFromChrome.accesskey     "C">
+<!ENTITY importFromFirefox.label        "Firefox">
+<!ENTITY importFromFirefox.accesskey    "X">
 <!ENTITY importFromHTMLFile.label       "From an HTML File">
 <!ENTITY importFromHTMLFile.accesskey   "F">
 
 <!ENTITY noMigrationSources.label       "No programs that contain bookmarks, history or password data could be found.">
 
 <!ENTITY importSource.title             "Import Settings and Data">
 <!ENTITY importItems.title              "Items to Import">
 <!ENTITY importItems.label              "Select which items to import:">
--- a/browser/locales/en-US/chrome/browser/migration/migration.properties
+++ b/browser/locales/en-US/chrome/browser/migration/migration.properties
@@ -1,43 +1,48 @@
 profileName_format=%S %S
 
 # Browser Specific
 sourceNameIE=Internet Explorer
 sourceNameSafari=Safari
 sourceNameChrome=Google Chrome
+sourceNameFirefox=Mozilla Firefox
 
 importedBookmarksFolder=From %S
 importedSearchURLsFolder=Keyword Searches (From %S)
 importedSearchURLsTitle=Search on %S
 importedSearchUrlDesc=Type "%S <search query>" in the Location Bar to perform a search on %S.
 
 importedSafariBookmarks=From Safari
 
 # Import Sources
 1_ie=Internet Options
 1_safari=Preferences
 1_chrome=Preferences
 
 2_ie=Cookies
 2_safari=Cookies
 2_chrome=Cookies
+2_firefox=Cookies
 
 4_ie=Browsing History
 4_safari=Browsing History
 4_chrome=Browsing History
+4_firefox=Browsing History
 
 8_ie=Saved Form History
 8_safari=Saved Form History
 8_chrome=Saved Form History
 
 16_ie=Saved Passwords
 16_safari=Saved Passwords
 16_chrome=Saved Passwords
+16_firefox=Saved Passwords
 
 32_ie=Favorites
 32_safari=Bookmarks
 32_chrome=Bookmarks
+32_firefox=Bookmarks
 
 64_ie=Other Data
 64_safari=Other Data
 64_chrome=Other Data
 
--- a/toolkit/components/places/PlacesUtils.jsm
+++ b/toolkit/components/places/PlacesUtils.jsm
@@ -1813,26 +1813,28 @@ var PlacesUtils = {
         localizedFilename.substr(0, localizedFilename.indexOf("-"));
       delete this._filenamesRegex;
       return this._filenamesRegex =
         new RegExp("^(bookmarks|" + localizedFilenamePrefix + ")-([0-9-]+)\.(json|html)");
     },
 
     get folder() {
       let bookmarksBackupDir = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
-      bookmarksBackupDir.append("bookmarkbackups");
+      bookmarksBackupDir.append(this.profileRelativeFolderPath);
       if (!bookmarksBackupDir.exists()) {
         bookmarksBackupDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0700);
         if (!bookmarksBackupDir.exists())
           throw("Unable to create bookmarks backup folder");
       }
       delete this.folder;
       return this.folder = bookmarksBackupDir;
     },
 
+    get profileRelativeFolderPath() "bookmarkbackups",
+
     /**
      * Cache current backups in a sorted (by date DESC) array.
      */
     get entries() {
       delete this.entries;
       this.entries = [];
       let files = this.folder.directoryEntries;
       while (files.hasMoreElements()) {