Backed out changeset ca4caf0d721c (bug 853549) for valgrind testfailures
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 28 May 2014 15:53:13 +0200
changeset 185375 d0e851fb79a6a8c8176aa8d1735b64b3477a3aa4
parent 185374 c086224d0d64921c7f1c74a3aeb5d173e477665b
child 185376 6940c3eb040ee476b0203d267c4cac6037962b41
push id26850
push userryanvm@gmail.com
push dateWed, 28 May 2014 20:21:08 +0000
treeherdermozilla-central@1ff71bef9c99 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs853549
milestone32.0a1
backs outca4caf0d721cf52da4544a0737db7b0f62df2b36
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out changeset ca4caf0d721c (bug 853549) for valgrind testfailures
b2g/installer/package-manifest.in
browser/base/content/browser.js
browser/installer/package-manifest.in
services/sync/modules/engines/passwords.js
services/sync/modules/util.js
toolkit/components/passwordmgr/LoginHelper.jsm
toolkit/components/passwordmgr/LoginImport.jsm
toolkit/components/passwordmgr/LoginStore.jsm
toolkit/components/passwordmgr/moz.build
toolkit/components/passwordmgr/nsLoginManager.js
toolkit/components/passwordmgr/passwordmgr.manifest
toolkit/components/passwordmgr/storage-json.js
toolkit/components/passwordmgr/storage-mozStorage.js
toolkit/components/passwordmgr/test/unit/head.js
toolkit/components/passwordmgr/test/unit/test_module_LoginImport.js
toolkit/components/passwordmgr/test/unit/test_module_LoginStore.js
toolkit/components/passwordmgr/test/unit/xpcshell.ini
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -383,17 +383,17 @@
 @BINPATH@/components/NetworkGeolocationProvider.manifest
 @BINPATH@/components/NetworkGeolocationProvider.js
 #ifdef MOZ_WEBRTC
 @BINPATH@/components/PeerConnection.js
 @BINPATH@/components/PeerConnection.manifest
 #endif
 @BINPATH@/components/SiteSpecificUserAgent.js
 @BINPATH@/components/SiteSpecificUserAgent.manifest
-@BINPATH@/components/storage-json.js
+@BINPATH@/components/storage-mozStorage.js
 @BINPATH@/components/crypto-SDR.js
 @BINPATH@/components/jsconsole-clhandler.manifest
 @BINPATH@/components/jsconsole-clhandler.js
 @BINPATH@/components/nsDownloadManagerUI.manifest
 @BINPATH@/components/nsDownloadManagerUI.js
 @BINPATH@/components/Downloads.manifest
 @BINPATH@/components/DownloadLegacy.js
 @BINPATH@/components/nsSidebar.manifest
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1124,28 +1124,16 @@ var gBrowserInit = {
           .DownloadsCommon.initializeAllDataLinks();
         Cu.import("resource:///modules/DownloadsTaskbar.jsm", {})
           .DownloadsTaskbar.registerIndicator(window);
       } catch (ex) {
         Cu.reportError(ex);
       }
     }, 10000);
 
-    // Load the Login Manager data from disk off the main thread, some time
-    // after startup.  If the data is required before the timeout, for example
-    // because a restored page contains a password field, it will be loaded on
-    // the main thread, and this initialization request will be ignored.
-    setTimeout(function() {
-      try {
-        Services.logins;
-      } catch (ex) {
-        Cu.reportError(ex);
-      }
-    }, 3000);
-
     // The object handling the downloads indicator is also initialized here in the
     // delayed startup function, but the actual indicator element is not loaded
     // unless there are downloads to be displayed.
     DownloadsButton.initializeIndicator();
 
 #ifndef XP_MACOSX
     updateEditUIVisibility();
     let placesContext = document.getElementById("placesContext");
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -385,17 +385,17 @@
 @BINPATH@/components/SiteSpecificUserAgent.manifest
 @BINPATH@/components/toolkitsearch.manifest
 @BINPATH@/components/nsSearchService.js
 @BINPATH@/components/nsSearchSuggestions.js
 @BINPATH@/components/passwordmgr.manifest
 @BINPATH@/components/nsLoginInfo.js
 @BINPATH@/components/nsLoginManager.js
 @BINPATH@/components/nsLoginManagerPrompter.js
-@BINPATH@/components/storage-json.js
+@BINPATH@/components/storage-mozStorage.js
 @BINPATH@/components/crypto-SDR.js
 @BINPATH@/components/jsconsole-clhandler.manifest
 @BINPATH@/components/jsconsole-clhandler.js
 @BINPATH@/components/webvtt.xpt
 @BINPATH@/components/WebVTT.manifest
 @BINPATH@/components/WebVTTParserWrapper.js
 #ifdef MOZ_GTK
 @BINPATH@/components/nsFilePicker.manifest
--- a/services/sync/modules/engines/passwords.js
+++ b/services/sync/modules/engines/passwords.js
@@ -108,16 +108,21 @@ PasswordEngine.prototype = {
         return local.guid;
   }
 };
 
 function PasswordStore(name, engine) {
   Store.call(this, name, engine);
   this._nsLoginInfo = new Components.Constructor(
     "@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init");
+
+  XPCOMUtils.defineLazyGetter(this, "DBConnection", function() {
+    return Services.logins.QueryInterface(Ci.nsIInterfaceRequestor)
+                   .getInterface(Ci.mozIStorageConnection);
+  });
 }
 PasswordStore.prototype = {
   __proto__: Store.prototype,
 
   _nsLoginInfoFromRecord: function PasswordStore__nsLoginInfoRec(record) {
     if (record.formSubmitURL &&
         record.httpRealm) {
       this._log.warn("Record " + record.id +
@@ -152,16 +157,31 @@ PasswordStore.prototype = {
       this._log.trace(logins.length + " items matching " + id + " found.");
       return logins[0];
     } else {
       this._log.trace("No items matching " + id + " found. Ignoring");
     }
     return null;
   },
 
+  applyIncomingBatch: function applyIncomingBatch(records) {
+    if (!this.DBConnection) {
+      return Store.prototype.applyIncomingBatch.call(this, records);
+    }
+
+    return Utils.runInTransaction(this.DBConnection, function() {
+      return Store.prototype.applyIncomingBatch.call(this, records);
+    }, this);
+  },
+
+  applyIncoming: function applyIncoming(record) {
+    Store.prototype.applyIncoming.call(this, record);
+    this._sleep(0); // Yield back to main thread after synchronous operation.
+  },
+
   getAllIDs: function PasswordStore__getAllIDs() {
     let items = {};
     let logins = Services.logins.getAllLogins({});
 
     for (let i = 0; i < logins.length; i++) {
       // Skip over Weave password/passphrase entries
       let metaInfo = logins[i].QueryInterface(Ci.nsILoginMetaInfo);
       if (Utils.getSyncCredentialsHosts().has(metaInfo.hostname)) {
--- a/services/sync/modules/util.js
+++ b/services/sync/modules/util.js
@@ -145,16 +145,32 @@ this.Utils = {
         catch(ex) {
           notify("error", ex);
           throw ex;
         }
       };
     };
   },
 
+  runInTransaction: function(db, callback, thisObj) {
+    let hasTransaction = false;
+    try {
+      db.beginTransaction();
+      hasTransaction = true;
+    } catch(e) { /* om nom nom exceptions */ }
+
+    try {
+      return callback.call(thisObj);
+    } finally {
+      if (hasTransaction) {
+        db.commitTransaction();
+      }
+    }
+  },
+
   /**
    * GUIDs are 9 random bytes encoded with base64url (RFC 4648).
    * That makes them 12 characters long with 72 bits of entropy.
    */
   makeGUID: function makeGUID() {
     return CommonUtils.encodeBase64URL(Utils.generateRandomBytes(9));
   },
 
deleted file mode 100644
--- a/toolkit/components/passwordmgr/LoginHelper.jsm
+++ /dev/null
@@ -1,233 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
-/* 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/. */
-
-/**
- * Contains functions shared by different Login Manager components.
- *
- * This JavaScript module exists in order to share code between the different
- * XPCOM components that constitute the Login Manager, including implementations
- * of nsILoginManager and nsILoginManagerStorage.
- */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = [
-  "LoginHelper",
-];
-
-////////////////////////////////////////////////////////////////////////////////
-//// Globals
-
-const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-////////////////////////////////////////////////////////////////////////////////
-//// LoginHelper
-
-/**
- * Contains functions shared by different Login Manager components.
- */
-this.LoginHelper = {
-  /**
-   * Due to the way the signons2.txt file is formatted, we need to make
-   * sure certain field values or characters do not cause the file to
-   * be parsed incorrectly.  Reject hostnames that we can't store correctly.
-   *
-   * @throws String with English message in case validation failed.
-   */
-  checkHostnameValue: function (aHostname)
-  {
-    // Nulls are invalid, as they don't round-trip well.  Newlines are also
-    // invalid for any field stored as plaintext, and a hostname made of a
-    // single dot cannot be stored in the legacy format.
-    if (aHostname == "." ||
-        aHostname.indexOf("\r") != -1 ||
-        aHostname.indexOf("\n") != -1 ||
-        aHostname.indexOf("\0") != -1) {
-      throw "Invalid hostname";
-    }
-  },
-
-  /**
-   * Due to the way the signons2.txt file is formatted, we need to make
-   * sure certain field values or characters do not cause the file to
-   * be parsed incorrectly.  Reject logins that we can't store correctly.
-   *
-   * @throws String with English message in case validation failed.
-   */
-  checkLoginValues: function (aLogin)
-  {
-    function badCharacterPresent(l, c)
-    {
-      return ((l.formSubmitURL && l.formSubmitURL.indexOf(c) != -1) ||
-              (l.httpRealm     && l.httpRealm.indexOf(c)     != -1) ||
-                                  l.hostname.indexOf(c)      != -1  ||
-                                  l.usernameField.indexOf(c) != -1  ||
-                                  l.passwordField.indexOf(c) != -1);
-    }
-
-    // Nulls are invalid, as they don't round-trip well.
-    // Mostly not a formatting problem, although ".\0" can be quirky.
-    if (badCharacterPresent(aLogin, "\0")) {
-      throw "login values can't contain nulls";
-    }
-
-    // In theory these nulls should just be rolled up into the encrypted
-    // values, but nsISecretDecoderRing doesn't use nsStrings, so the
-    // nulls cause truncation. Check for them here just to avoid
-    // unexpected round-trip surprises.
-    if (aLogin.username.indexOf("\0") != -1 ||
-        aLogin.password.indexOf("\0") != -1) {
-      throw "login values can't contain nulls";
-    }
-
-    // Newlines are invalid for any field stored as plaintext.
-    if (badCharacterPresent(aLogin, "\r") ||
-        badCharacterPresent(aLogin, "\n")) {
-      throw "login values can't contain newlines";
-    }
-
-    // A line with just a "." can have special meaning.
-    if (aLogin.usernameField == "." ||
-        aLogin.formSubmitURL == ".") {
-      throw "login values can't be periods";
-    }
-
-    // A hostname with "\ \(" won't roundtrip.
-    // eg host="foo (", realm="bar" --> "foo ( (bar)"
-    // vs host="foo", realm=" (bar" --> "foo ( (bar)"
-    if (aLogin.hostname.indexOf(" (") != -1) {
-      throw "bad parens in hostname";
-    }
-  },
-
-  /**
-   * Creates a new login object that results by modifying the given object with
-   * the provided data.
-   *
-   * @param aOldStoredLogin
-   *        Existing nsILoginInfo object to modify.
-   * @param aNewLoginData
-   *        The new login values, either as nsILoginInfo or nsIProperyBag.
-   *
-   * @return The newly created nsILoginInfo object.
-   *
-   * @throws String with English message in case validation failed.
-   */
-  buildModifiedLogin: function (aOldStoredLogin, aNewLoginData)
-  {
-    function bagHasProperty(aPropName)
-    {
-      try {
-        aNewLoginData.getProperty(aPropName);
-        return true;
-      } catch (ex) { }
-      return false;
-    }
-
-    aOldStoredLogin.QueryInterface(Ci.nsILoginMetaInfo);
-
-    let newLogin;
-    if (aNewLoginData instanceof Ci.nsILoginInfo) {
-      // Clone the existing login to get its nsILoginMetaInfo, then init it
-      // with the replacement nsILoginInfo data from the new login.
-      newLogin = aOldStoredLogin.clone();
-      newLogin.init(aNewLoginData.hostname,
-                    aNewLoginData.formSubmitURL, aNewLoginData.httpRealm,
-                    aNewLoginData.username, aNewLoginData.password,
-                    aNewLoginData.usernameField, aNewLoginData.passwordField);
-      newLogin.QueryInterface(Ci.nsILoginMetaInfo);
-
-      // Automatically update metainfo when password is changed.
-      if (newLogin.password != aOldStoredLogin.password) {
-        newLogin.timePasswordChanged = Date.now();
-      }
-    } else if (aNewLoginData instanceof Ci.nsIPropertyBag) {
-      // Clone the existing login, along with all its properties.
-      newLogin = aOldStoredLogin.clone();
-      newLogin.QueryInterface(Ci.nsILoginMetaInfo);
-
-      // Automatically update metainfo when password is changed.
-      // (Done before the main property updates, lest the caller be
-      // explicitly updating both .password and .timePasswordChanged)
-      if (bagHasProperty("password")) {
-        let newPassword = aNewLoginData.getProperty("password");
-        if (newPassword != aOldStoredLogin.password) {
-          newLogin.timePasswordChanged = Date.now();
-        }
-      }
-
-      let propEnum = aNewLoginData.enumerator;
-      while (propEnum.hasMoreElements()) {
-        let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty);
-        switch (prop.name) {
-          // nsILoginInfo
-          case "hostname":
-          case "httpRealm":
-          case "formSubmitURL":
-          case "username":
-          case "password":
-          case "usernameField":
-          case "passwordField":
-          // nsILoginMetaInfo
-          case "guid":
-          case "timeCreated":
-          case "timeLastUsed":
-          case "timePasswordChanged":
-          case "timesUsed":
-            newLogin[prop.name] = prop.value;
-            break;
-
-          // Fake property, allows easy incrementing.
-          case "timesUsedIncrement":
-            newLogin.timesUsed += prop.value;
-            break;
-
-          // Fail if caller requests setting an unknown property.
-          default:
-            throw "Unexpected propertybag item: " + prop.name;
-        }
-      }
-    } else {
-      throw "newLoginData needs an expected interface!";
-    }
-
-    // Sanity check the login
-    if (newLogin.hostname == null || newLogin.hostname.length == 0) {
-      throw "Can't add a login with a null or empty hostname.";
-    }
-
-    // For logins w/o a username, set to "", not null.
-    if (newLogin.username == null) {
-      throw "Can't add a login with a null username.";
-    }
-
-    if (newLogin.password == null || newLogin.password.length == 0) {
-      throw "Can't add a login with a null or empty password.";
-    }
-
-    if (newLogin.formSubmitURL || newLogin.formSubmitURL == "") {
-      // We have a form submit URL. Can't have a HTTP realm.
-      if (newLogin.httpRealm != null) {
-        throw "Can't add a login with both a httpRealm and formSubmitURL.";
-      }
-    } else if (newLogin.httpRealm) {
-      // We have a HTTP realm. Can't have a form submit URL.
-      if (newLogin.formSubmitURL != null) {
-        throw "Can't add a login with both a httpRealm and formSubmitURL.";
-      }
-    } else {
-      // Need one or the other!
-      throw "Can't add a login without a httpRealm or formSubmitURL.";
-    }
-
-    // Throws if there are bogus values.
-    this.checkLoginValues(newLogin);
-
-    return newLogin;
-  },
-};
deleted file mode 100644
--- a/toolkit/components/passwordmgr/LoginImport.jsm
+++ /dev/null
@@ -1,180 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
-/* 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/. */
-
-/**
- * Provides an object that has a method to import login-related data from the
- * previous SQLite storage format.
- */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = [
-  "LoginImport",
-];
-
-////////////////////////////////////////////////////////////////////////////////
-//// Globals
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-const Cr = Components.results;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "OS",
-                                  "resource://gre/modules/osfile.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
-                                  "resource://gre/modules/Sqlite.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
-                                  "resource://gre/modules/NetUtil.jsm");
-
-////////////////////////////////////////////////////////////////////////////////
-//// LoginImport
-
-/**
- * Provides an object that has a method to import login-related data from the
- * previous SQLite storage format.
- *
- * @param aStore
- *        LoginStore object where imported data will be added.
- * @param aPath
- *        String containing the file path of the SQLite login database.
- */
-this.LoginImport = function (aStore, aPath)
-{
-  this.store = aStore;
-  this.path = aPath;
-}
-
-this.LoginImport.prototype = {
-  /**
-   * LoginStore object where imported data will be added.
-   */
-  store: null,
-
-  /**
-   * String containing the file path of the SQLite login database.
-   */
-  path: null,
-
-  /**
-   * Imports login-related data from the previous SQLite storage format.
-   */
-  import: Task.async(function* () {
-    // We currently migrate data directly from the database to the JSON store at
-    // first run, then we set a preference to prevent repeating the import.
-    // Thus, merging with existing data is not a use case we support.  This
-    // restriction might be removed to support re-importing passwords set by an
-    // old version by flipping the import preference and restarting.
-    if (this.store.data.logins.length > 0 ||
-        this.store.data.disabledHosts.length > 0) {
-      throw new Error("Unable to import saved passwords because some data " +
-                      "has already been imported or saved.");
-    }
-
-    // When a timestamp is not specified, we will use the same reference time.
-    let referenceTimeMs = Date.now();
-
-    let connection = yield Sqlite.openConnection({ path: this.path });
-    try {
-      let schemaVersion = yield connection.getSchemaVersion();
-
-      // We support importing database schema versions from 3 onwards.
-      // Version 3 was implemented in bug 316084 (Firefox 3.6, March 2009).
-      // Version 4 was implemented in bug 465636 (Firefox 4, March 2010).
-      // Version 5 was implemented in bug 718817 (Firefox 13, February 2012).
-      if (schemaVersion < 3) {
-        throw new Error("Unable to import saved passwords because " +
-                        "the existing profile is too old.");
-      }
-
-      let rows = yield connection.execute("SELECT * FROM moz_logins");
-      for (let row of rows) {
-        try {
-          let hostname = row.getResultByName("hostname");
-          let httpRealm = row.getResultByName("httpRealm");
-          let formSubmitURL = row.getResultByName("formSubmitURL");
-          let usernameField = row.getResultByName("usernameField");
-          let passwordField = row.getResultByName("passwordField");
-          let encryptedUsername = row.getResultByName("encryptedUsername");
-          let encryptedPassword = row.getResultByName("encryptedPassword");
-
-          // The "guid" field was introduced in schema version 2, and the
-          // "enctype" field was introduced in schema version 3.  We don't
-          // support upgrading from older versions of the database.
-          let guid = row.getResultByName("guid");
-          let encType = row.getResultByName("encType");
-
-          // The time and count fields were introduced in schema version 4.
-          let timeCreated = null;
-          let timeLastUsed = null;
-          let timePasswordChanged = null;
-          let timesUsed = null;
-          try {
-            timeCreated = row.getResultByName("timeCreated");
-            timeLastUsed = row.getResultByName("timeLastUsed");
-            timePasswordChanged = row.getResultByName("timePasswordChanged");
-            timesUsed = row.getResultByName("timesUsed");
-          } catch (ex) { }
-
-          // These columns may be null either because they were not present in
-          // the database or because the record was created on a new schema
-          // version by an old application version.
-          if (!timeCreated) {
-            timeCreated = referenceTimeMs;
-          }
-          if (!timeLastUsed) {
-            timeLastUsed = referenceTimeMs;
-          }
-          if (!timePasswordChanged) {
-            timePasswordChanged = referenceTimeMs;
-          }
-          if (!timesUsed) {
-            timesUsed = 1;
-          }
-
-          this.store.data.logins.push({
-            id: this.store.data.nextId++,
-            hostname: hostname,
-            httpRealm: httpRealm,
-            formSubmitURL: formSubmitURL,
-            usernameField: usernameField,
-            passwordField: passwordField,
-            encryptedUsername: encryptedUsername,
-            encryptedPassword: encryptedPassword,
-            guid: guid,
-            encType: encType,
-            timeCreated: timeCreated,
-            timeLastUsed: timeLastUsed,
-            timePasswordChanged: timePasswordChanged,
-            timesUsed: timesUsed,
-          });
-        } catch (ex) {
-          Cu.reportError("Error importing login: " + ex);
-        }
-      }
-
-      rows = yield connection.execute("SELECT * FROM moz_disabledHosts");
-      for (let row of rows) {
-        try {
-          let id = row.getResultByName("id");
-          let hostname = row.getResultByName("hostname");
-
-          this.store.data.disabledHosts.push({
-            id: this.store.data.nextId++,
-            hostname: hostname,
-          });
-        } catch (ex) {
-          Cu.reportError("Error importing disabled host: " + ex);
-        }
-      }
-    } finally {
-      yield connection.close();
-    }
-  }),
-};
deleted file mode 100644
--- a/toolkit/components/passwordmgr/LoginStore.jsm
+++ /dev/null
@@ -1,301 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
-/* 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/. */
-
-/**
- * Handles serialization of login-related data and persistence into a file.
- *
- * This modules handles the raw data stored in JavaScript serializable objects,
- * and contains no special validation or query logic, that is handled entirely
- * by "storage.js" instead.
- *
- * The data can be manipulated only after it has been loaded from disk.  The
- * load process can happen asynchronously, through the "load" method, or
- * synchronously, through "ensureDataReady".  After any modification, the
- * "saveSoon" method must be called to flush the data to disk asynchronously.
- *
- * The raw data should be manipulated synchronously, without waiting for the
- * event loop or for promise resolution, so that the saved file is always
- * consistent.  This synchronous approach also simplifies the query and update
- * logic.  For example, it is possible to find an object and modify it
- * immediately without caring whether other code modifies it in the meantime.
- *
- * An asynchronous shutdown observer makes sure that data is always saved before
- * the browser is closed.  The data cannot be modified during shutdown.
- *
- * The file is stored in JSON format, without indentation, using UTF-8 encoding.
- * With indentation applied, the file would look like this:
- *
- * {
- *   "logins": [
- *     {
- *       "id": 2,
- *       "hostname": "http://www.example.com",
- *       "httpRealm": null,
- *       "formSubmitURL": "http://www.example.com/submit-url",
- *       "usernameField": "username_field",
- *       "passwordField": "password_field",
- *       "encryptedUsername": "...",
- *       "encryptedPassword": "...",
- *       "guid": "...",
- *       "encType": 1,
- *       "timeCreated": 1262304000000,
- *       "timeLastUsed": 1262304000000,
- *       "timePasswordChanged": 1262476800000,
- *       "timesUsed": 1
- *     },
- *     {
- *       "id": 4,
- *       (...)
- *     }
- *   ],
- *   "disabledHosts": [
- *     "http://www.example.org",
- *     "http://www.example.net"
- *   ],
- *   "nextId": 10,
- *   "version": 1
- * }
- */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = [
-  "LoginStore",
-];
-
-////////////////////////////////////////////////////////////////////////////////
-//// Globals
-
-const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
-                                  "resource://gre/modules/AsyncShutdown.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
-                                  "resource://gre/modules/DeferredTask.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
-                                  "resource://gre/modules/FileUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "OS",
-                                  "resource://gre/modules/osfile.jsm")
-
-XPCOMUtils.defineLazyGetter(this, "gTextDecoder", function () {
-  return new TextDecoder();
-});
-
-XPCOMUtils.defineLazyGetter(this, "gTextEncoder", function () {
-  return new TextEncoder();
-});
-
-const FileInputStream =
-      Components.Constructor("@mozilla.org/network/file-input-stream;1",
-                             "nsIFileInputStream", "init");
-
-/**
- * Delay between a change to the login data and the related save operation.
- */
-const kSaveDelayMs = 1500;
-
-/**
- * Current data version assigned by the code that last touched the data.
- *
- * This number should be updated only when it is important to understand whether
- * an old version of the code has touched the data, for example to execute an
- * update logic.  In most cases, this number should not be changed, in
- * particular when no special one-time update logic is needed.
- *
- * For example, this number should NOT be changed when a new optional field is
- * added to a login entry.
- */
-const kDataVersion = 1;
-
-////////////////////////////////////////////////////////////////////////////////
-//// LoginStore
-
-/**
- * Handles serialization of login-related data and persistence into a file.
- *
- * @param aPath
- *        String containing the file path where data should be saved.
- */
-function LoginStore(aPath)
-{
-  this.path = aPath;
-
-  this._saver = new DeferredTask(() => this.save(), kSaveDelayMs);
-  AsyncShutdown.profileBeforeChange.addBlocker("Login store: writing data",
-                                               () => this._saver.finalize());
-}
-
-LoginStore.prototype = {
-  /**
-   * String containing the file path where data should be saved.
-   */
-  path: "",
-
-  /**
-   * Serializable object containing the login-related data.  This is populated
-   * directly with the data loaded from the file, and is saved without
-   * modifications.
-   *
-   * This contains one property for each list.
-   */
-  data: null,
-
-  /**
-   * True when data has been loaded.
-   */
-  dataReady: false,
-
-  /**
-   * Loads persistent data from the file to memory.
-   *
-   * @return {Promise}
-   * @resolves When the operation finished successfully.
-   * @rejects JavaScript exception.
-   */
-  load: function ()
-  {
-    return Task.spawn(function () {
-      try {
-        let bytes = yield OS.File.read(this.path);
-
-        // If synchronous loading happened in the meantime, exit now.
-        if (this.dataReady) {
-          return;
-        }
-
-        this.data = JSON.parse(gTextDecoder.decode(bytes));
-      } catch (ex) {
-        // If an exception occurred because the file did not exist, we should
-        // just start with new data.  Other errors may indicate that the file is
-        // corrupt, thus we move it to a backup location before allowing it to
-        // be overwritten by an empty file.
-        if (!(ex instanceof OS.File.Error && ex.becauseNoSuchFile)) {
-          Cu.reportError(ex);
-
-          // Move the original file to a backup location, ignoring errors.
-          try {
-            let openInfo = yield OS.File.openUnique(this.path + ".corrupt",
-                                                    { humanReadable: true });
-            yield openInfo.file.close();
-            yield OS.File.move(this.path, openInfo.path);
-          } catch (e2) {
-            Cu.reportError(e2);
-          }
-        }
-
-        // In any case, initialize a new object to host the data.
-        this.data = {
-          nextId: 1,
-        };
-      }
-
-      this._processLoadedData();
-    }.bind(this));
-  },
-
-  /**
-   * Loads persistent data from the file to memory, synchronously.
-   */
-  ensureDataReady: function ()
-  {
-    if (this.dataReady) {
-      return;
-    }
-
-    try {
-      // This reads the file and automatically detects the UTF-8 encoding.
-      let inputStream = new FileInputStream(new FileUtils.File(this.path),
-                                            FileUtils.MODE_RDONLY,
-                                            FileUtils.PERMS_FILE, 0)
-      try {
-        let json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
-        this.data = json.decodeFromStream(inputStream,
-                                          inputStream.available());
-      } finally {
-        inputStream.close();
-      }
-    } catch (ex) {
-      // If an exception occurred because the file did not exist, we should just
-      // start with new data.  Other errors may indicate that the file is
-      // corrupt, thus we move it to a backup location before allowing it to be
-      // overwritten by an empty file.
-      if (!(ex instanceof Components.Exception &&
-            ex.result == Cr.NS_ERROR_FILE_NOT_FOUND)) {
-        Cu.reportError(ex);
-        // Move the original file to a backup location, ignoring errors.
-        try {
-          let originalFile = new FileUtils.File(this.path);
-          let backupFile = originalFile.clone();
-          backupFile.leafName += ".corrupt";
-          backupFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE,
-                                  FileUtils.PERMS_FILE);
-          backupFile.remove(false);
-          originalFile.moveTo(backupFile.parent, backupFile.leafName);
-        } catch (e2) {
-          Cu.reportError(e2);
-        }
-      }
-
-      // In any case, initialize a new object to host the data.
-      this.data = {
-        nextId: 1,
-      };
-    }
-
-    this._processLoadedData();
-  },
-
-  /**
-   * Synchronously work on the data just loaded into memory.
-   */
-  _processLoadedData: function ()
-  {
-    // Create any arrays that are not present in the saved file.
-    if (!this.data.logins) {
-      this.data.logins = [];
-    }
-    if (!this.data.disabledHosts) {
-      this.data.disabledHosts = [];
-    }
-
-    // Indicate that the current version of the code has touched the file.
-    this.data.version = kDataVersion;
-
-    this.dataReady = true;
-  },
-
-  /**
-   * Called when the data changed, this triggers asynchronous serialization.
-   */
-  saveSoon: function () this._saver.arm(),
-
-  /**
-   * DeferredTask that handles the save operation.
-   */
-  _saver: null,
-
-  /**
-   * Saves persistent data from memory to the file.
-   *
-   * If an error occurs, the previous file is not deleted.
-   *
-   * @return {Promise}
-   * @resolves When the operation finished successfully.
-   * @rejects JavaScript exception.
-   */
-  save: function ()
-  {
-    return Task.spawn(function () {
-      // Create or overwrite the file.
-      let bytes = gTextEncoder.encode(JSON.stringify(this.data));
-      yield OS.File.writeAtomic(this.path, bytes,
-                                { tmpPath: this.path + ".tmp" });
-    }.bind(this));
-  },
-};
--- a/toolkit/components/passwordmgr/moz.build
+++ b/toolkit/components/passwordmgr/moz.build
@@ -18,36 +18,23 @@ XPIDL_SOURCES += [
     'nsILoginMetaInfo.idl',
 ]
 
 XPIDL_MODULE = 'loginmgr'
 
 EXTRA_COMPONENTS += [
     'crypto-SDR.js',
     'nsLoginInfo.js',
+    'nsLoginManager.js',
     'nsLoginManagerPrompter.js',
+    'passwordmgr.manifest',
 ]
 
 EXTRA_PP_COMPONENTS += [
-    'nsLoginManager.js',
-    'passwordmgr.manifest',
+    'storage-mozStorage.js',
 ]
 
 EXTRA_JS_MODULES += [
     'InsecurePasswordUtils.jsm',
-    'LoginHelper.jsm',
     'LoginManagerContent.jsm',
 ]
 
-if CONFIG['OS_TARGET'] == 'Android':
-    EXTRA_COMPONENTS += [
-        'storage-mozStorage.js',
-    ]
-else:
-    EXTRA_COMPONENTS += [
-        'storage-json.js',
-    ]
-    EXTRA_JS_MODULES += [
-        'LoginImport.jsm',
-        'LoginStore.jsm',
-    ]
-
 JAR_MANIFESTS += ['jar.mn']
--- a/toolkit/components/passwordmgr/nsLoginManager.js
+++ b/toolkit/components/passwordmgr/nsLoginManager.js
@@ -120,21 +120,17 @@ LoginManager.prototype = {
                                  false);
 
         // Initialize storage so that asynchronous data loading can start.
         this._initStorage();
     },
 
 
     _initStorage : function () {
-#ifdef ANDROID
         var contractID = "@mozilla.org/login-manager/storage/mozStorage;1";
-#else
-        var contractID = "@mozilla.org/login-manager/storage/json;1";
-#endif
         try {
             var catMan = Cc["@mozilla.org/categorymanager;1"].
                          getService(Ci.nsICategoryManager);
             contractID = catMan.getCategoryEntry("login-manager-storage",
                                                  "nsILoginManagerStorage");
             log("Found alternate nsILoginManagerStorage with contract ID:", contractID);
         } catch (e) {
             log("No alternate nsILoginManagerStorage registered");
--- a/toolkit/components/passwordmgr/passwordmgr.manifest
+++ b/toolkit/components/passwordmgr/passwordmgr.manifest
@@ -1,17 +1,12 @@
 component {cb9e0de8-3598-4ed7-857b-827f011ad5d8} nsLoginManager.js
 contract @mozilla.org/login-manager;1 {cb9e0de8-3598-4ed7-857b-827f011ad5d8}
 component {749e62f4-60ae-4569-a8a2-de78b649660e} nsLoginManagerPrompter.js
 contract @mozilla.org/passwordmanager/authpromptfactory;1 {749e62f4-60ae-4569-a8a2-de78b649660e}
 component {8aa66d77-1bbb-45a6-991e-b8f47751c291} nsLoginManagerPrompter.js
 contract @mozilla.org/login-manager/prompter;1 {8aa66d77-1bbb-45a6-991e-b8f47751c291}
 component {0f2f347c-1e4f-40cc-8efd-792dea70a85e} nsLoginInfo.js
 contract @mozilla.org/login-manager/loginInfo;1 {0f2f347c-1e4f-40cc-8efd-792dea70a85e}
-#ifdef ANDROID
 component {8c2023b9-175c-477e-9761-44ae7b549756} storage-mozStorage.js
 contract @mozilla.org/login-manager/storage/mozStorage;1 {8c2023b9-175c-477e-9761-44ae7b549756}
-#else
-component {c00c432d-a0c9-46d7-bef6-9c45b4d07341} storage-json.js
-contract @mozilla.org/login-manager/storage/json;1 {c00c432d-a0c9-46d7-bef6-9c45b4d07341}
-#endif
 component {dc6c2976-0f73-4f1f-b9ff-3d72b4e28309} crypto-SDR.js
 contract @mozilla.org/login-manager/crypto/SDR;1 {dc6c2976-0f73-4f1f-b9ff-3d72b4e28309}
deleted file mode 100644
--- a/toolkit/components/passwordmgr/storage-json.js
+++ /dev/null
@@ -1,654 +0,0 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/* vim: set sw=4 ts=4 et lcs=trail\:.,tab\:>~ : */
-/* 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/. */
-
-/**
- * nsILoginManagerStorage implementation for the JSON back-end.
- */
-
-////////////////////////////////////////////////////////////////////////////////
-//// Globals
-
-const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
-                                  "resource://gre/modules/LoginHelper.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "LoginImport",
-                                  "resource://gre/modules/LoginImport.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "LoginStore",
-                                  "resource://gre/modules/LoginStore.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "OS",
-                                  "resource://gre/modules/osfile.jsm");
-
-XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
-                                   "@mozilla.org/uuid-generator;1",
-                                   "nsIUUIDGenerator");
-
-////////////////////////////////////////////////////////////////////////////////
-//// LoginManagerStorage_json
-
-this.LoginManagerStorage_json = function () {}
-
-this.LoginManagerStorage_json.prototype = {
-    classID : Components.ID("{c00c432d-a0c9-46d7-bef6-9c45b4d07341}"),
-    QueryInterface : XPCOMUtils.generateQI([Ci.nsILoginManagerStorage]),
-
-    __crypto : null,  // nsILoginManagerCrypto service
-    get _crypto() {
-        if (!this.__crypto)
-            this.__crypto = Cc["@mozilla.org/login-manager/crypto/SDR;1"].
-                            getService(Ci.nsILoginManagerCrypto);
-        return this.__crypto;
-    },
-
-    /*
-     * log
-     *
-     * Internal function for logging debug messages to the Error Console.
-     */
-    log : function (message) {
-        if (!this._debug)
-            return;
-        dump("PwMgr json: " + message + "\n");
-        Services.console.logStringMessage("PwMgr json: " + message);
-    },
-    _debug : false,
-
-
-    /*
-     * initialize
-     *
-     */
-    initialize : function () {
-        this._debug = Services.prefs.getBoolPref("signon.debug");
-
-        try {
-            // Force initialization of the crypto module.
-            // See bug 717490 comment 17.
-            this._crypto;
-
-            // Set the reference to LoginStore synchronously.
-            let jsonPath = OS.Path.join(OS.Constants.Path.profileDir,
-                                        "logins.json");
-            this._store = new LoginStore(jsonPath);
-
-            return Task.spawn(function () {
-                // Load the data asynchronously.
-                this.log("Opening database at " + this._store.path);
-                yield this._store.load();
-
-                // The import from previous versions operates the first time
-                // that this built-in storage back-end is used.  This may be
-                // later than expected, in case add-ons have registered an
-                // alternate storage that disabled the default one.
-                try {
-                    if (Services.prefs.getBoolPref("signon.importedFromSqlite")) {
-                        return;
-                    }
-                } catch (ex) {
-                    // If the preference does not exist, we need to import.
-                }
-
-                // Import only happens asynchronously.
-                let sqlitePath = OS.Path.join(OS.Constants.Path.profileDir,
-                                              "signons.sqlite");
-                if (yield OS.File.exists(sqlitePath)) {
-                    let loginImport = new LoginImport(this._store, sqlitePath);
-                    // Failures during import, for example due to a corrupt
-                    // file or a schema version that is too old, will not
-                    // prevent us from marking the operation as completed.
-                    // At the next startup, we will not try the import again.
-                    yield loginImport.import().catch(Cu.reportError);
-                    this._store.saveSoon();
-                }
-
-                // We won't attempt import again on next startup.
-                Services.prefs.setBoolPref("signon.importedFromSqlite", true);
-            }.bind(this)).catch(Cu.reportError);
-        } catch (e) {
-            this.log("Initialization failed: " + e);
-            throw "Initialization failed";
-        }
-    },
-
-
-    /*
-     * terminate
-     *
-     * Internal method used by regression tests only.  It is called before
-     * replacing this storage module with a new instance.
-     */
-    terminate : function () {
-        this._store._saver.disarm();
-        return this._store.save();
-    },
-
-
-    /*
-     * addLogin
-     *
-     */
-    addLogin : function (login) {
-        this._store.ensureDataReady();
-
-        let encUsername, encPassword;
-
-        // Throws if there are bogus values.
-        LoginHelper.checkLoginValues(login);
-
-        [encUsername, encPassword, encType] = this._encryptLogin(login);
-
-        // Clone the login, so we don't modify the caller's object.
-        let loginClone = login.clone();
-
-        // Initialize the nsILoginMetaInfo fields, unless the caller gave us values
-        loginClone.QueryInterface(Ci.nsILoginMetaInfo);
-        if (loginClone.guid) {
-            if (!this._isGuidUnique(loginClone.guid))
-                throw "specified GUID already exists";
-        } else {
-            loginClone.guid = gUUIDGenerator.generateUUID().toString();
-        }
-
-        // Set timestamps
-        let currentTime = Date.now();
-        if (!loginClone.timeCreated)
-            loginClone.timeCreated = currentTime;
-        if (!loginClone.timeLastUsed)
-            loginClone.timeLastUsed = currentTime;
-        if (!loginClone.timePasswordChanged)
-            loginClone.timePasswordChanged = currentTime;
-        if (!loginClone.timesUsed)
-            loginClone.timesUsed = 1;
-
-        this._store.data.logins.push({
-            id:                  this._store.data.nextId++,
-            hostname:            loginClone.hostname,
-            httpRealm:           loginClone.httpRealm,
-            formSubmitURL:       loginClone.formSubmitURL,
-            usernameField:       loginClone.usernameField,
-            passwordField:       loginClone.passwordField,
-            encryptedUsername:   encUsername,
-            encryptedPassword:   encPassword,
-            guid:                loginClone.guid,
-            encType:             encType,
-            timeCreated:         loginClone.timeCreated,
-            timeLastUsed:        loginClone.timeLastUsed,
-            timePasswordChanged: loginClone.timePasswordChanged,
-            timesUsed:           loginClone.timesUsed
-        });
-        this._store.saveSoon();
-
-        // Send a notification that a login was added.
-        this._sendNotification("addLogin", loginClone);
-    },
-
-
-    /*
-     * removeLogin
-     *
-     */
-    removeLogin : function (login) {
-        this._store.ensureDataReady();
-
-        let [idToDelete, storedLogin] = this._getIdForLogin(login);
-        if (!idToDelete)
-            throw "No matching logins";
-
-        let foundIndex = this._store.data.logins.findIndex(l => l.id == idToDelete);
-        if (foundIndex != -1) {
-            this._store.data.logins.splice(foundIndex, 1);
-            this._store.saveSoon();
-        }
-
-        this._sendNotification("removeLogin", storedLogin);
-    },
-
-
-    /*
-     * modifyLogin
-     *
-     */
-    modifyLogin : function (oldLogin, newLoginData) {
-        this._store.ensureDataReady();
-
-        let [idToModify, oldStoredLogin] = this._getIdForLogin(oldLogin);
-        if (!idToModify)
-            throw "No matching logins";
-
-        let newLogin = LoginHelper.buildModifiedLogin(oldStoredLogin, newLoginData);
-
-        // Check if the new GUID is duplicate.
-        if (newLogin.guid != oldStoredLogin.guid &&
-            !this._isGuidUnique(newLogin.guid)) 
-        {
-            throw "specified GUID already exists";
-        }
-
-        // Look for an existing entry in case key properties changed.
-        if (!newLogin.matches(oldLogin, true)) {
-            let logins = this.findLogins({}, newLogin.hostname,
-                                         newLogin.formSubmitURL,
-                                         newLogin.httpRealm);
-
-            if (logins.some(login => newLogin.matches(login, true)))
-                throw "This login already exists.";
-        }
-
-        // Get the encrypted value of the username and password.
-        let [encUsername, encPassword, encType] = this._encryptLogin(newLogin);
-
-        for (let loginItem of this._store.data.logins) {
-            if (loginItem.id == idToModify) {
-                loginItem.hostname = newLogin.hostname;
-                loginItem.httpRealm = newLogin.httpRealm;
-                loginItem.formSubmitURL = newLogin.formSubmitURL;
-                loginItem.usernameField = newLogin.usernameField;
-                loginItem.passwordField = newLogin.passwordField;
-                loginItem.encryptedUsername = encUsername;
-                loginItem.encryptedPassword = encPassword;
-                loginItem.guid = newLogin.guid;
-                loginItem.encType = encType;
-                loginItem.timeCreated = newLogin.timeCreated;
-                loginItem.timeLastUsed = newLogin.timeLastUsed;
-                loginItem.timePasswordChanged = newLogin.timePasswordChanged;
-                loginItem.timesUsed = newLogin.timesUsed;
-                this._store.saveSoon();
-                break;
-            }
-        }
-
-        this._sendNotification("modifyLogin", [oldStoredLogin, newLogin]);
-    },
-
-
-    /*
-     * getAllLogins
-     *
-     * Returns an array of nsILoginInfo.
-     */
-    getAllLogins : function (count) {
-        let [logins, ids] = this._searchLogins({});
-
-        // decrypt entries for caller.
-        logins = this._decryptLogins(logins);
-
-        this.log("_getAllLogins: returning " + logins.length + " logins.");
-        if (count)
-            count.value = logins.length; // needed for XPCOM
-        return logins;
-    },
-
-
-    /*
-     * searchLogins
-     *
-     * Public wrapper around _searchLogins to convert the nsIPropertyBag to a
-     * JavaScript object and decrypt the results.
-     *
-     * Returns an array of decrypted nsILoginInfo.
-     */
-    searchLogins : function(count, matchData) {
-        let realMatchData = {};
-        // Convert nsIPropertyBag to normal JS object
-        let propEnum = matchData.enumerator;
-        while (propEnum.hasMoreElements()) {
-            let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty);
-            realMatchData[prop.name] = prop.value;
-        }
-
-        let [logins, ids] = this._searchLogins(realMatchData);
-
-        // Decrypt entries found for the caller.
-        logins = this._decryptLogins(logins);
-
-        count.value = logins.length; // needed for XPCOM
-        return logins;
-    },
-
-
-    /*
-     * _searchLogins
-     *
-     * Private method to perform arbitrary searches on any field. Decryption is
-     * left to the caller.
-     *
-     * Returns [logins, ids] for logins that match the arguments, where logins
-     * is an array of encrypted nsLoginInfo and ids is an array of associated
-     * ids in the database.
-     */
-    _searchLogins : function (matchData) {
-        this._store.ensureDataReady();
-
-        let conditions = [];
-
-        function match(aLogin) {
-            for (let field in matchData) {
-                let value = matchData[field];
-                switch (field) {
-                    // Historical compatibility requires this special case
-                    case "formSubmitURL":
-                        if (value != null) {
-                            if (aLogin.formSubmitURL != "" && aLogin.formSubmitURL != value) {
-                                return false;
-                            }
-                            break;
-                        }
-                    // Normal cases.
-                    case "hostname":
-                    case "httpRealm":
-                    case "id":
-                    case "usernameField":
-                    case "passwordField":
-                    case "encryptedUsername":
-                    case "encryptedPassword":
-                    case "guid":
-                    case "encType":
-                    case "timeCreated":
-                    case "timeLastUsed":
-                    case "timePasswordChanged":
-                    case "timesUsed":
-                        if (value == null && aLogin[field]) {
-                            return false;
-                        } else if (aLogin[field] != value) {
-                            return false;
-                        }
-                        break;
-                    // Fail if caller requests an unknown property.
-                    default:
-                        throw "Unexpected field: " + field;
-                }
-            }
-            return true;
-        }
-
-        let foundLogins = [], foundIds = [];
-        for (let loginItem of this._store.data.logins) {
-            if (match(loginItem)) {
-                // Create the new nsLoginInfo object, push to array
-                let login = Cc["@mozilla.org/login-manager/loginInfo;1"].
-                            createInstance(Ci.nsILoginInfo);
-                login.init(loginItem.hostname, loginItem.formSubmitURL,
-                           loginItem.httpRealm, loginItem.encryptedUsername,
-                           loginItem.encryptedPassword, loginItem.usernameField,
-                           loginItem.passwordField);
-                // set nsILoginMetaInfo values
-                login.QueryInterface(Ci.nsILoginMetaInfo);
-                login.guid = loginItem.guid;
-                login.timeCreated = loginItem.timeCreated;
-                login.timeLastUsed = loginItem.timeLastUsed;
-                login.timePasswordChanged = loginItem.timePasswordChanged;
-                login.timesUsed = loginItem.timesUsed;
-                foundLogins.push(login);
-                foundIds.push(loginItem.id);
-            }
-        }
-
-        this.log("_searchLogins: returning " + foundLogins.length + " logins");
-        return [foundLogins, foundIds];
-    },
-
-    /*
-     * removeAllLogins
-     *
-     * Removes all logins from storage.
-     *
-     * Disabled hosts are kept, as one presumably doesn't want to erase those.
-     */
-    removeAllLogins : function () {
-        this._store.ensureDataReady();
-
-        this.log("Removing all logins");
-        this._store.data.logins = [];
-        this._store.saveSoon();
-
-        this._sendNotification("removeAllLogins", null);
-    },
-
-
-    /*
-     * getAllDisabledHosts
-     *
-     */
-    getAllDisabledHosts : function (count) {
-        this._store.ensureDataReady();
-
-        let disabledHosts = this._store.data.disabledHosts.slice(0);
-
-        this.log("_getAllDisabledHosts: returning " + disabledHosts.length + " disabled hosts.");
-        if (count)
-            count.value = disabledHosts.length; // needed for XPCOM
-        return disabledHosts;
-    },
-
-
-    /*
-     * getLoginSavingEnabled
-     *
-     */
-    getLoginSavingEnabled : function (hostname) {
-        this._store.ensureDataReady();
-
-        this.log("Getting login saving is enabled for " + hostname);
-        return this._store.data.disabledHosts.indexOf(hostname) == -1;
-    },
-
-
-    /*
-     * setLoginSavingEnabled
-     *
-     */
-    setLoginSavingEnabled : function (hostname, enabled) {
-        this._store.ensureDataReady();
-
-        // Throws if there are bogus values.
-        LoginHelper.checkHostnameValue(hostname);
-
-        this.log("Setting login saving enabled for " + hostname + " to " + enabled);
-        let foundIndex = this._store.data.disabledHosts.indexOf(hostname);
-        if (enabled) {
-            if (foundIndex != -1) {
-                this._store.data.disabledHosts.splice(foundIndex, 1);
-                this._store.saveSoon();
-            }
-        } else {
-            if (foundIndex == -1) {
-                this._store.data.disabledHosts.push(hostname);
-                this._store.saveSoon();
-            }
-        }
-
-        this._sendNotification(enabled ? "hostSavingEnabled" : "hostSavingDisabled", hostname);
-    },
-
-
-    /*
-     * findLogins
-     *
-     */
-    findLogins : function (count, hostname, formSubmitURL, httpRealm) {
-        let loginData = {
-            hostname: hostname,
-            formSubmitURL: formSubmitURL,
-            httpRealm: httpRealm
-        };
-        let matchData = { };
-        for each (let field in ["hostname", "formSubmitURL", "httpRealm"])
-            if (loginData[field] != '')
-                matchData[field] = loginData[field];
-        let [logins, ids] = this._searchLogins(matchData);
-
-        // Decrypt entries found for the caller.
-        logins = this._decryptLogins(logins);
-
-        this.log("_findLogins: returning " + logins.length + " logins");
-        count.value = logins.length; // needed for XPCOM
-        return logins;
-    },
-
-
-    /*
-     * countLogins
-     *
-     */
-    countLogins : function (hostname, formSubmitURL, httpRealm) {
-        let count = {};
-        let loginData = {
-            hostname: hostname,
-            formSubmitURL: formSubmitURL,
-            httpRealm: httpRealm
-        };
-        let matchData = { };
-        for each (let field in ["hostname", "formSubmitURL", "httpRealm"])
-            if (loginData[field] != '')
-                matchData[field] = loginData[field];
-        let [logins, ids] = this._searchLogins(matchData);
-
-        this.log("_countLogins: counted logins: " + logins.length);
-        return logins.length;
-    },
-
-
-    /*
-     * uiBusy
-     */
-    get uiBusy() {
-        return this._crypto.uiBusy;
-    },
-
-
-    /*
-     * isLoggedIn
-     */
-    get isLoggedIn() {
-        return this._crypto.isLoggedIn;
-    },
-
-
-    /*
-     * _sendNotification
-     *
-     * Send a notification when stored data is changed.
-     */
-    _sendNotification : function (changeType, data) {
-        let dataObject = data;
-        // Can't pass a raw JS string or array though notifyObservers(). :-(
-        if (data instanceof Array) {
-            dataObject = Cc["@mozilla.org/array;1"].
-                         createInstance(Ci.nsIMutableArray);
-            for (let i = 0; i < data.length; i++)
-                dataObject.appendElement(data[i], false);
-        } else if (typeof(data) == "string") {
-            dataObject = Cc["@mozilla.org/supports-string;1"].
-                         createInstance(Ci.nsISupportsString);
-            dataObject.data = data;
-        }
-        Services.obs.notifyObservers(dataObject, "passwordmgr-storage-changed", changeType);
-    },
-
-
-    /*
-     * _getIdForLogin
-     *
-     * Returns an array with two items: [id, login]. If the login was not
-     * found, both items will be null. The returned login contains the actual
-     * stored login (useful for looking at the actual nsILoginMetaInfo values).
-     */
-    _getIdForLogin : function (login) {
-        let matchData = { };
-        for each (let field in ["hostname", "formSubmitURL", "httpRealm"])
-            if (login[field] != '')
-                matchData[field] = login[field];
-        let [logins, ids] = this._searchLogins(matchData);
-
-        let id = null;
-        let foundLogin = null;
-
-        // The specified login isn't encrypted, so we need to ensure
-        // the logins we're comparing with are decrypted. We decrypt one entry
-        // at a time, lest _decryptLogins return fewer entries and screw up
-        // indices between the two.
-        for (let i = 0; i < logins.length; i++) {
-            let [decryptedLogin] = this._decryptLogins([logins[i]]);
-
-            if (!decryptedLogin || !decryptedLogin.equals(login))
-                continue;
-
-            // We've found a match, set id and break
-            foundLogin = decryptedLogin;
-            id = ids[i];
-            break;
-        }
-
-        return [id, foundLogin];
-    },
-
-
-    /*
-     * _isGuidUnique
-     *
-     * Checks to see if the specified GUID already exists.
-     */
-    _isGuidUnique : function (guid) {
-        this._store.ensureDataReady();
-
-        return this._store.data.logins.every(l => l.guid != guid);
-    },
-
-
-    /*
-     * _encryptLogin
-     *
-     * Returns the encrypted username, password, and encrypton type for the specified
-     * login. Can throw if the user cancels a master password entry.
-     */
-    _encryptLogin : function (login) {
-        let encUsername = this._crypto.encrypt(login.username);
-        let encPassword = this._crypto.encrypt(login.password);
-        let encType     = this._crypto.defaultEncType;
-
-        return [encUsername, encPassword, encType];
-    },
-
-
-    /*
-     * _decryptLogins
-     *
-     * Decrypts username and password fields in the provided array of
-     * logins.
-     *
-     * The entries specified by the array will be decrypted, if possible.
-     * An array of successfully decrypted logins will be returned. The return
-     * value should be given to external callers (since still-encrypted
-     * entries are useless), whereas internal callers generally don't want
-     * to lose unencrypted entries (eg, because the user clicked Cancel
-     * instead of entering their master password)
-     */
-    _decryptLogins : function (logins) {
-        let result = [];
-
-        for each (let login in logins) {
-            try {
-                login.username = this._crypto.decrypt(login.username);
-                login.password = this._crypto.decrypt(login.password);
-            } catch (e) {
-                // If decryption failed (corrupt entry?), just skip it.
-                // Rethrow other errors (like canceling entry of a master pw)
-                if (e.result == Cr.NS_ERROR_FAILURE)
-                    continue;
-                throw e;
-            }
-            result.push(login);
-        }
-
-        return result;
-    },
-};
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([LoginManagerStorage_json]);
--- a/toolkit/components/passwordmgr/storage-mozStorage.js
+++ b/toolkit/components/passwordmgr/storage-mozStorage.js
@@ -8,20 +8,19 @@
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 
 const DB_VERSION = 5; // The database schema version
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
-Components.utils.import("resource://gre/modules/Promise.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
-                                  "resource://gre/modules/LoginHelper.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+                                  "resource://gre/modules/Promise.jsm");
 
 /**
  * Object that manages a database transaction properly so consumers don't have
  * to worry about it throwing.
  *
  * @param aDatabase
  *        The mozIStorageConnection to start a transaction on.
  */
@@ -239,17 +238,17 @@ LoginManagerStorage_mozStorage.prototype
     /*
      * addLogin
      *
      */
     addLogin : function (login) {
         let encUsername, encPassword;
 
         // Throws if there are bogus values.
-        LoginHelper.checkLoginValues(login);
+        this._checkLoginValues(login);
 
         [encUsername, encPassword, encType] = this._encryptLogin(login);
 
         // Clone the login, so we don't modify the caller's object.
         let loginClone = login.clone();
 
         // Initialize the nsILoginMetaInfo fields, unless the caller gave us values
         loginClone.QueryInterface(Ci.nsILoginMetaInfo);
@@ -351,26 +350,119 @@ LoginManagerStorage_mozStorage.prototype
     /*
      * modifyLogin
      *
      */
     modifyLogin : function (oldLogin, newLoginData) {
         let [idToModify, oldStoredLogin] = this._getIdForLogin(oldLogin);
         if (!idToModify)
             throw "No matching logins";
+        oldStoredLogin.QueryInterface(Ci.nsILoginMetaInfo);
 
-        let newLogin = LoginHelper.buildModifiedLogin(oldStoredLogin, newLoginData);
+        let newLogin;
+        if (newLoginData instanceof Ci.nsILoginInfo) {
+            // Clone the existing login to get its nsILoginMetaInfo, then init it
+            // with the replacement nsILoginInfo data from the new login.
+            newLogin = oldStoredLogin.clone();
+            newLogin.init(newLoginData.hostname,
+                          newLoginData.formSubmitURL, newLoginData.httpRealm,
+                          newLoginData.username, newLoginData.password,
+                          newLoginData.usernameField, newLoginData.passwordField);
+            newLogin.QueryInterface(Ci.nsILoginMetaInfo);
+
+            // Automatically update metainfo when password is changed.
+            if (newLogin.password != oldLogin.password)
+                newLogin.timePasswordChanged = Date.now();
+        } else if (newLoginData instanceof Ci.nsIPropertyBag) {
+            function _bagHasProperty(aPropName) {
+                try {
+                    newLoginData.getProperty(aPropName);
+                    return true;
+                } catch (e) {
+                    return false;
+                }
+            }
+
+            // Clone the existing login, along with all its properties.
+            newLogin = oldStoredLogin.clone();
+            newLogin.QueryInterface(Ci.nsILoginMetaInfo);
+
+            // Automatically update metainfo when password is changed.
+            // (Done before the main property updates, lest the caller be
+            // explicitly updating both .password and .timePasswordChanged)
+            if (_bagHasProperty("password")) {
+                let newPassword = newLoginData.getProperty("password");
+                if (newPassword != oldLogin.password)
+                    newLogin.timePasswordChanged = Date.now();
+            }
 
-        // Check if the new GUID is duplicate.
-        if (newLogin.guid != oldStoredLogin.guid &&
-            !this._isGuidUnique(newLogin.guid)) 
-        {
-            throw "specified GUID already exists";
+            let propEnum = newLoginData.enumerator;
+            while (propEnum.hasMoreElements()) {
+                let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty);
+                switch (prop.name) {
+                    // nsILoginInfo properties...
+                    case "hostname":
+                    case "httpRealm":
+                    case "formSubmitURL":
+                    case "username":
+                    case "password":
+                    case "usernameField":
+                    case "passwordField":
+                    // nsILoginMetaInfo properties...
+                    case "guid":
+                    case "timeCreated":
+                    case "timeLastUsed":
+                    case "timePasswordChanged":
+                    case "timesUsed":
+                        newLogin[prop.name] = prop.value;
+                        if (prop.name == "guid" && !this._isGuidUnique(newLogin.guid))
+                            throw "specified GUID already exists";
+                        break;
+
+                    // Fake property, allows easy incrementing.
+                    case "timesUsedIncrement":
+                        newLogin.timesUsed += prop.value;
+                        break;
+
+                    // Fail if caller requests setting an unknown property.
+                    default:
+                        throw "Unexpected propertybag item: " + prop.name;
+                }
+            }
+        } else {
+            throw "newLoginData needs an expected interface!";
         }
 
+        // Sanity check the login
+        if (newLogin.hostname == null || newLogin.hostname.length == 0)
+            throw "Can't add a login with a null or empty hostname.";
+
+        // For logins w/o a username, set to "", not null.
+        if (newLogin.username == null)
+            throw "Can't add a login with a null username.";
+
+        if (newLogin.password == null || newLogin.password.length == 0)
+            throw "Can't add a login with a null or empty password.";
+
+        if (newLogin.formSubmitURL || newLogin.formSubmitURL == "") {
+            // We have a form submit URL. Can't have a HTTP realm.
+            if (newLogin.httpRealm != null)
+                throw "Can't add a login with both a httpRealm and formSubmitURL.";
+        } else if (newLogin.httpRealm) {
+            // We have a HTTP realm. Can't have a form submit URL.
+            if (newLogin.formSubmitURL != null)
+                throw "Can't add a login with both a httpRealm and formSubmitURL.";
+        } else {
+            // Need one or the other!
+            throw "Can't add a login without a httpRealm or formSubmitURL.";
+        }
+
+        // Throws if there are bogus values.
+        this._checkLoginValues(newLogin);
+
         // Look for an existing entry in case key properties changed.
         if (!newLogin.matches(oldLogin, true)) {
             let logins = this.findLogins({}, newLogin.hostname,
                                          newLogin.formSubmitURL,
                                          newLogin.httpRealm);
 
             if (logins.some(login => newLogin.matches(login, true)))
                 throw "This login already exists.";
@@ -568,30 +660,32 @@ LoginManagerStorage_mozStorage.prototype
     },
 
     /* storeDeletedLogin
      *
      * Moves a login to the deleted logins table
      *
      */
      storeDeletedLogin : function(aLogin) {
+#ifdef ANDROID
           let stmt = null; 
           try {
               this.log("Storing " + aLogin.guid + " in deleted passwords\n");
               let query = "INSERT INTO moz_deleted_logins (guid, timeDeleted) VALUES (:guid, :timeDeleted)";
               let params = { guid: aLogin.guid,
                              timeDeleted: Date.now() };
               let stmt = this._dbCreateStatement(query, params);
               stmt.execute();
           } catch(ex) {
               throw ex;
           } finally {
               if (stmt)
                   stmt.reset();
           }		
+#endif
      },
 
 
     /*
      * removeAllLogins
      *
      * Removes all logins from storage.
      */
@@ -615,17 +709,17 @@ LoginManagerStorage_mozStorage.prototype
             throw "Couldn't write to database";
         } finally {
             if (stmt) {
                 stmt.reset();
             }
         }
 
         this._sendNotification("removeAllLogins", null);
-    },
+   },
 
 
     /*
      * getAllDisabledHosts
      *
      */
     getAllDisabledHosts : function (count) {
         let disabledHosts = this._queryDisabledHosts(null);
@@ -648,17 +742,17 @@ LoginManagerStorage_mozStorage.prototype
 
 
     /*
      * setLoginSavingEnabled
      *
      */
     setLoginSavingEnabled : function (hostname, enabled) {
         // Throws if there are bogus values.
-        LoginHelper.checkHostnameValue(hostname);
+        this._checkHostnameValue(hostname);
 
         this.log("Setting login saving enabled for " + hostname + " to " + enabled);
         let query;
         if (enabled)
             query = "DELETE FROM moz_disabledHosts " +
                     "WHERE hostname = :hostname";
         else
             query = "INSERT INTO moz_disabledHosts " +
@@ -689,18 +783,18 @@ LoginManagerStorage_mozStorage.prototype
     findLogins : function (count, hostname, formSubmitURL, httpRealm) {
         let loginData = {
             hostname: hostname,
             formSubmitURL: formSubmitURL,
             httpRealm: httpRealm
         };
         let matchData = { };
         for each (let field in ["hostname", "formSubmitURL", "httpRealm"])
-            if (loginData[field] != '')
-                matchData[field] = loginData[field];
+          if (loginData[field] != '')
+              matchData[field] = loginData[field];
         let [logins, ids] = this._searchLogins(matchData);
 
         // Decrypt entries found for the caller.
         logins = this._decryptLogins(logins);
 
         this.log("_findLogins: returning " + logins.length + " logins");
         count.value = logins.length; // needed for XPCOM
         return logins;
@@ -880,16 +974,80 @@ LoginManagerStorage_mozStorage.prototype
             params["httpRealm"] = httpRealm;
         }
 
         return [conditions, params];
     },
 
 
     /*
+     * _checkLoginValues
+     *
+     * Due to the way the signons2.txt file is formatted, we need to make
+     * sure certain field values or characters do not cause the file to
+     * be parse incorrectly. Reject logins that we can't store correctly.
+     */
+    _checkLoginValues : function (aLogin) {
+        function badCharacterPresent(l, c) {
+            return ((l.formSubmitURL && l.formSubmitURL.indexOf(c) != -1) ||
+                    (l.httpRealm     && l.httpRealm.indexOf(c)     != -1) ||
+                                        l.hostname.indexOf(c)      != -1  ||
+                                        l.usernameField.indexOf(c) != -1  ||
+                                        l.passwordField.indexOf(c) != -1);
+        }
+
+        // Nulls are invalid, as they don't round-trip well.
+        // Mostly not a formatting problem, although ".\0" can be quirky.
+        if (badCharacterPresent(aLogin, "\0"))
+            throw "login values can't contain nulls";
+
+        // In theory these nulls should just be rolled up into the encrypted
+        // values, but nsISecretDecoderRing doesn't use nsStrings, so the
+        // nulls cause truncation. Check for them here just to avoid
+        // unexpected round-trip surprises.
+        if (aLogin.username.indexOf("\0") != -1 ||
+            aLogin.password.indexOf("\0") != -1)
+            throw "login values can't contain nulls";
+
+        // Newlines are invalid for any field stored as plaintext.
+        if (badCharacterPresent(aLogin, "\r") ||
+            badCharacterPresent(aLogin, "\n"))
+            throw "login values can't contain newlines";
+
+        // A line with just a "." can have special meaning.
+        if (aLogin.usernameField == "." ||
+            aLogin.formSubmitURL == ".")
+            throw "login values can't be periods";
+
+        // A hostname with "\ \(" won't roundtrip.
+        // eg host="foo (", realm="bar" --> "foo ( (bar)"
+        // vs host="foo", realm=" (bar" --> "foo ( (bar)"
+        if (aLogin.hostname.indexOf(" (") != -1)
+            throw "bad parens in hostname";
+    },
+
+
+    /*
+     * _checkHostnameValue
+     *
+     * Legacy storage prohibited newlines and nulls in hostnames, so we'll keep
+     * that standard here. Throws on illegal format.
+     */
+    _checkHostnameValue : function (hostname) {
+        // File format prohibits certain values. Also, nulls
+        // won't round-trip with getAllDisabledHosts().
+        if (hostname == "." ||
+            hostname.indexOf("\r") != -1 ||
+            hostname.indexOf("\n") != -1 ||
+            hostname.indexOf("\0") != -1)
+            throw "Invalid hostname";
+    },
+
+
+    /*
      * _isGuidUnique
      *
      * Checks to see if the specified GUID already exists.
      */
     _isGuidUnique : function (guid) {
         let query = "SELECT COUNT(1) AS numLogins FROM moz_logins WHERE guid = :guid";
         let params = { guid: guid };
 
--- a/toolkit/components/passwordmgr/test/unit/head.js
+++ b/toolkit/components/passwordmgr/test/unit/head.js
@@ -12,24 +12,22 @@
 ////////////////////////////////////////////////////////////////////////////////
 //// Globals
 
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
-                                  "resource://gre/modules/DownloadPaths.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
-                                  "resource://gre/modules/Promise.jsm");
+                                  "resource://gre/modules/commonjs/sdk/core/promise.js");
 
 const LoginInfo =
       Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
                              "nsILoginInfo", "init");
 
 /**
  * All the tests are implemented with add_task, this starts them automatically.
  */
@@ -40,56 +38,16 @@ function run_test()
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Global helpers
 
 // Some of these functions are already implemented in other parts of the source
 // tree, see bug 946708 about sharing more code.
 
-// While the previous test file should have deleted all the temporary files it
-// used, on Windows these might still be pending deletion on the physical file
-// system.  Thus, start from a new base number every time, to make a collision
-// with a file that is still pending deletion highly unlikely.
-let gFileCounter = Math.floor(Math.random() * 1000000);
-
-/**
- * Returns a reference to a temporary file, that is guaranteed not to exist, and
- * to have never been created before.
- *
- * @param aLeafName
- *        Suggested leaf name for the file to be created.
- *
- * @return nsIFile pointing to a non-existent file in a temporary directory.
- *
- * @note It is not enough to delete the file if it exists, or to delete the file
- *       after calling nsIFile.createUnique, because on Windows the delete
- *       operation in the file system may still be pending, preventing a new
- *       file with the same name to be created.
- */
-function getTempFile(aLeafName)
-{
-  // Prepend a serial number to the extension in the suggested leaf name.
-  let [base, ext] = DownloadPaths.splitBaseNameAndExtension(aLeafName);
-  let leafName = base + "-" + gFileCounter + ext;
-  gFileCounter++;
-
-  // Get a file reference under the temporary directory for this test file.
-  let file = FileUtils.getFile("TmpD", [leafName]);
-  do_check_false(file.exists());
-
-  do_register_cleanup(function () {
-    if (file.exists()) {
-      file.remove(false);
-    }
-  });
-
-  return file;
-}
-
 /**
  * Allows waiting for an observer notification once.
  *
  * @param aTopic
  *        Notification topic to observe.
  *
  * @return {Promise}
  * @resolves The array [aSubject, aData] from the observed notification.
deleted file mode 100644
--- a/toolkit/components/passwordmgr/test/unit/test_module_LoginImport.js
+++ /dev/null
@@ -1,248 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Tests the LoginImport object.
- */
-
-"use strict";
-
-////////////////////////////////////////////////////////////////////////////////
-//// Globals
-
-Cu.import("resource://gre/modules/Task.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
-                                  "resource://gre/modules/LoginHelper.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "LoginImport",
-                                  "resource://gre/modules/LoginImport.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "LoginStore",
-                                  "resource://gre/modules/LoginStore.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
-                                  "resource://gre/modules/Sqlite.jsm");
-
-XPCOMUtils.defineLazyServiceGetter(this, "gLoginManagerCrypto",
-                                   "@mozilla.org/login-manager/crypto/SDR;1",
-                                   "nsILoginManagerCrypto");
-XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
-                                   "@mozilla.org/uuid-generator;1",
-                                   "nsIUUIDGenerator");
-
-/**
- * Creates empty login data tables in the given SQLite connection, resembling
- * the most recent schema version (excluding indices).
- */
-function promiseCreateDatabaseSchema(aConnection)
-{
-  return Task.spawn(function () {
-    yield aConnection.setSchemaVersion(5);
-    yield aConnection.execute("CREATE TABLE moz_logins (" +
-                              "id                  INTEGER PRIMARY KEY," +
-                              "hostname            TEXT NOT NULL,"       +
-                              "httpRealm           TEXT,"                +
-                              "formSubmitURL       TEXT,"                +
-                              "usernameField       TEXT NOT NULL,"       +
-                              "passwordField       TEXT NOT NULL,"       +
-                              "encryptedUsername   TEXT NOT NULL,"       +
-                              "encryptedPassword   TEXT NOT NULL,"       +
-                              "guid                TEXT,"                +
-                              "encType             INTEGER,"             +
-                              "timeCreated         INTEGER,"             +
-                              "timeLastUsed        INTEGER,"             +
-                              "timePasswordChanged INTEGER,"             +
-                              "timesUsed           INTEGER)");
-    yield aConnection.execute("CREATE TABLE moz_disabledHosts ("         +
-                              "id                  INTEGER PRIMARY KEY," +
-                              "hostname            TEXT UNIQUE)");
-    yield aConnection.execute("CREATE TABLE moz_deleted_logins ("        +
-                              "id                  INTEGER PRIMARY KEY," +
-                              "guid                TEXT,"                +
-                              "timeDeleted         INTEGER)");
-  });
-}
-
-/**
- * Inserts a new entry in the database resembling the given nsILoginInfo object.
- */
-function promiseInsertLoginInfo(aConnection, aLoginInfo)
-{
-  aLoginInfo.QueryInterface(Ci.nsILoginMetaInfo);
-
-  // We can't use the aLoginInfo object directly in the execute statement
-  // because the bind code in Sqlite.jsm doesn't allow objects with extra
-  // properties beyond those being binded. So we might as well use an array as
-  // it is simpler.
-  let values = [
-    aLoginInfo.hostname,
-    aLoginInfo.httpRealm,
-    aLoginInfo.formSubmitURL,
-    aLoginInfo.usernameField,
-    aLoginInfo.passwordField,
-    gLoginManagerCrypto.encrypt(aLoginInfo.username),
-    gLoginManagerCrypto.encrypt(aLoginInfo.password),
-    aLoginInfo.guid,
-    aLoginInfo.encType,
-    aLoginInfo.timeCreated,
-    aLoginInfo.timeLastUsed,
-    aLoginInfo.timePasswordChanged,
-    aLoginInfo.timesUsed,
-  ];
-
-  return aConnection.execute("INSERT INTO moz_logins (hostname, " +
-                             "httpRealm, formSubmitURL, usernameField, " +
-                             "passwordField, encryptedUsername, " +
-                             "encryptedPassword, guid, encType, timeCreated, " +
-                             "timeLastUsed, timePasswordChanged, timesUsed) " +
-                             "VALUES (?" + ",?".repeat(12) + ")", values);
-}
-
-/**
- * Inserts a new disabled host entry in the database.
- */
-function promiseInsertDisabledHost(aConnection, aHostname)
-{
-  return aConnection.execute("INSERT INTO moz_disabledHosts (hostname) " +
-                             "VALUES (?)", [aHostname]);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-//// Tests
-
-/**
- * Imports login data from a SQLite file constructed using the test data.
- */
-add_task(function test_import()
-{
-  let store = new LoginStore(getTempFile("test-import.json").path);
-  let loginsSqlite = getTempFile("test-logins.sqlite").path;
-
-  // Prepare the logins to be imported, including the nsILoginMetaInfo data.
-  let loginList = TestData.loginList();
-  for (let loginInfo of loginList) {
-    loginInfo.QueryInterface(Ci.nsILoginMetaInfo);
-    loginInfo.guid = gUUIDGenerator.generateUUID().toString();
-    loginInfo.timeCreated = Date.now();
-    loginInfo.timeLastUsed = Date.now();
-    loginInfo.timePasswordChanged = Date.now();
-    loginInfo.timesUsed = 1;
-  }
-
-  // Create and populate the SQLite database first.
-  let connection = yield Sqlite.openConnection({ path: loginsSqlite });
-  try {
-    yield promiseCreateDatabaseSchema(connection);
-    for (let loginInfo of loginList) {
-      yield promiseInsertLoginInfo(connection, loginInfo);
-    }
-    yield promiseInsertDisabledHost(connection, "http://www.example.com");
-    yield promiseInsertDisabledHost(connection, "https://www.example.org");
-  } finally {
-    yield connection.close();
-  }
-
-  // The "load" method must be called before importing data.
-  yield store.load();
-  yield new LoginImport(store, loginsSqlite).import();
-
-  // Verify that every login in the test data has a matching imported row.
-  do_check_eq(loginList.length, store.data.logins.length);
-  do_check_true(loginList.every(function (loginInfo) {
-    return store.data.logins.some(function (loginDataItem) {
-      let username = gLoginManagerCrypto.decrypt(loginDataItem.encryptedUsername);
-      let password = gLoginManagerCrypto.decrypt(loginDataItem.encryptedPassword);
-      return loginDataItem.hostname == loginInfo.hostname &&
-             loginDataItem.httpRealm == loginInfo.httpRealm &&
-             loginDataItem.formSubmitURL == loginInfo.formSubmitURL &&
-             loginDataItem.usernameField == loginInfo.usernameField &&
-             loginDataItem.passwordField == loginInfo.passwordField &&
-             username == loginInfo.username &&
-             password == loginInfo.password &&
-             loginDataItem.guid == loginInfo.guid &&
-             loginDataItem.encType == loginInfo.encType &&
-             loginDataItem.timeCreated == loginInfo.timeCreated &&
-             loginDataItem.timeLastUsed == loginInfo.timeLastUsed &&
-             loginDataItem.timePasswordChanged == loginInfo.timePasswordChanged &&
-             loginDataItem.timesUsed == loginInfo.timesUsed;
-    });
-  }));
-
-  // Verify that disabled hosts have been imported.
-  do_check_eq(store.data.disabledHosts.length, 2);
-  do_check_true(store.data.disabledHosts.some(
-                dataItem => dataItem.hostname == "http://www.example.com"));
-  do_check_true(store.data.disabledHosts.some(
-                dataItem => dataItem.hostname == "https://www.example.org"));
-});
-
-/**
- * Tests imports of NULL values due to a downgraded database.
- */
-add_task(function test_import_downgraded()
-{
-  let store = new LoginStore(getTempFile("test-import-downgraded.json").path);
-  let loginsSqlite = getTempFile("test-logins-downgraded.sqlite").path;
-  let loginList = TestData.loginList();
-
-  // Create and populate the SQLite database first.
-  let connection = yield Sqlite.openConnection({ path: loginsSqlite });
-  try {
-    yield promiseCreateDatabaseSchema(connection);
-    yield connection.setSchemaVersion(3);
-    yield promiseInsertLoginInfo(connection, TestData.formLogin({
-      guid: gUUIDGenerator.generateUUID().toString(),
-      timeCreated: null,
-      timeLastUsed: null,
-      timePasswordChanged: null,
-      timesUsed: 0,
-    }));
-  } finally {
-    yield connection.close();
-  }
-
-  // The "load" method must be called before importing data.
-  yield store.load();
-  yield new LoginImport(store, loginsSqlite).import();
-
-  // Verify that the missing metadata was generated correctly.
-  let loginItem = store.data.logins[0];
-  let creationTime = loginItem.timeCreated;
-  LoginTest.assertTimeIsAboutNow(creationTime);
-  do_check_eq(loginItem.timeLastUsed, creationTime);
-  do_check_eq(loginItem.timePasswordChanged, creationTime);
-  do_check_eq(loginItem.timesUsed, 1);
-});
-
-/**
- * Verifies that importing from a SQLite file with database version 2 fails.
- */
-add_task(function test_import_v2()
-{
-  let store = new LoginStore(getTempFile("test-import-v2.json").path);
-  let loginsSqlite = do_get_file("data/signons-v2.sqlite").path;
-
-  // The "load" method must be called before importing data.
-  yield store.load();
-  try {
-    yield new LoginImport(store, loginsSqlite).import();
-    do_throw("The operation should have failed.");
-  } catch (ex) { }
-});
-
-/**
- * Imports login data from a SQLite file, with database version 3.
- */
-add_task(function test_import_v3()
-{
-  let store = new LoginStore(getTempFile("test-import-v3.json").path);
-  let loginsSqlite = do_get_file("data/signons-v3.sqlite").path;
-
-  // The "load" method must be called before importing data.
-  yield store.load();
-  yield new LoginImport(store, loginsSqlite).import();
-
-  // We only execute basic integrity checks.
-  do_check_eq(store.data.logins[0].usernameField, "u1");
-  do_check_eq(store.data.disabledHosts.length, 0);
-});
deleted file mode 100644
--- a/toolkit/components/passwordmgr/test/unit/test_module_LoginStore.js
+++ /dev/null
@@ -1,208 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Tests the LoginStore object.
- */
-
-"use strict";
-
-////////////////////////////////////////////////////////////////////////////////
-//// Globals
-
-XPCOMUtils.defineLazyModuleGetter(this, "LoginStore",
-                                  "resource://gre/modules/LoginStore.jsm");
-
-const TEST_STORE_FILE_NAME = "test-logins.json";
-
-////////////////////////////////////////////////////////////////////////////////
-//// Tests
-
-/**
- * Saves login data to a file, then reloads it.
- */
-add_task(function test_save_reload()
-{
-  let storeForSave = new LoginStore(getTempFile(TEST_STORE_FILE_NAME).path);
-
-  // The "load" method must be called before preparing the data to be saved.
-  yield storeForSave.load();
-
-  let rawLoginData = {
-    id:                  storeForSave.data.nextId++,
-    hostname:            "http://www.example.com",
-    httpRealm:           null,
-    formSubmitURL:       "http://www.example.com/submit-url",
-    usernameField:       "field_" + String.fromCharCode(533, 537, 7570, 345),
-    passwordField:       "field_" + String.fromCharCode(421, 259, 349, 537),
-    encryptedUsername:   "(test)",
-    encryptedPassword:   "(test)",
-    guid:                "(test)",
-    encType:             Ci.nsILoginManagerCrypto.ENCTYPE_SDR,
-    timeCreated:         Date.now(),
-    timeLastUsed:        Date.now(),
-    timePasswordChanged: Date.now(),
-    timesUsed:           1,
-  };
-  storeForSave.data.logins.push(rawLoginData);
-
-  storeForSave.data.disabledHosts.push("http://www.example.org");
-
-  yield storeForSave.save();
-
-  // Test the asynchronous initialization path.
-  let storeForLoad = new LoginStore(storeForSave.path);
-  yield storeForLoad.load();
-
-  do_check_eq(storeForLoad.data.logins.length, 1);
-  do_check_matches(storeForLoad.data.logins[0], rawLoginData);
-  do_check_eq(storeForLoad.data.disabledHosts.length, 1);
-  do_check_eq(storeForLoad.data.disabledHosts[0], "http://www.example.org");
-
-  // Test the synchronous initialization path.
-  storeForLoad = new LoginStore(storeForSave.path);
-  storeForLoad.ensureDataReady();
-
-  do_check_eq(storeForLoad.data.logins.length, 1);
-  do_check_matches(storeForLoad.data.logins[0], rawLoginData);
-  do_check_eq(storeForLoad.data.disabledHosts.length, 1);
-  do_check_eq(storeForLoad.data.disabledHosts[0], "http://www.example.org");
-});
-
-/**
- * Checks that loading from a missing file results in empty arrays.
- */
-add_task(function test_load_empty()
-{
-  let store = new LoginStore(getTempFile(TEST_STORE_FILE_NAME).path);
-
-  do_check_false(yield OS.File.exists(store.path));
-
-  yield store.load();
-
-  do_check_false(yield OS.File.exists(store.path));
-
-  do_check_eq(store.data.logins.length, 0);
-  do_check_eq(store.data.disabledHosts.length, 0);
-});
-
-/**
- * Checks that saving empty data still overwrites any existing file.
- */
-add_task(function test_save_empty()
-{
-  let store = new LoginStore(getTempFile(TEST_STORE_FILE_NAME).path);
-
-  yield store.load();
-
-  let createdFile = yield OS.File.open(store.path, { create: true });
-  yield createdFile.close();
-
-  yield store.save();
-
-  do_check_true(yield OS.File.exists(store.path));
-});
-
-/**
- * Loads data from a string in a predefined format.  The purpose of this test is
- * to verify that the JSON format used in previous versions can be loaded.
- */
-add_task(function test_load_string_predefined()
-{
-  let store = new LoginStore(getTempFile(TEST_STORE_FILE_NAME).path);
-
-  let string = "{\"logins\":[{" +
-                "\"id\":1," +
-                "\"hostname\":\"http://www.example.com\"," +
-                "\"httpRealm\":null," +
-                "\"formSubmitURL\":\"http://www.example.com/submit-url\"," +
-                "\"usernameField\":\"usernameField\"," +
-                "\"passwordField\":\"passwordField\"," +
-                "\"encryptedUsername\":\"(test)\"," +
-                "\"encryptedPassword\":\"(test)\"," +
-                "\"guid\":\"(test)\"," +
-                "\"encType\":1," +
-                "\"timeCreated\":1262304000000," +
-                "\"timeLastUsed\":1262390400000," +
-                "\"timePasswordChanged\":1262476800000," +
-                "\"timesUsed\":1}],\"disabledHosts\":[" +
-                "\"http://www.example.org\"]}";
-
-  yield OS.File.writeAtomic(store.path,
-                            new TextEncoder().encode(string),
-                            { tmpPath: store.path + ".tmp" });
-
-  yield store.load();
-
-  do_check_eq(store.data.logins.length, 1);
-  do_check_matches(store.data.logins[0], {
-    id:                  1,
-    hostname:            "http://www.example.com",
-    httpRealm:           null,
-    formSubmitURL:       "http://www.example.com/submit-url",
-    usernameField:       "usernameField",
-    passwordField:       "passwordField",
-    encryptedUsername:   "(test)",
-    encryptedPassword:   "(test)",
-    guid:                "(test)",
-    encType:             Ci.nsILoginManagerCrypto.ENCTYPE_SDR,
-    timeCreated:         1262304000000,
-    timeLastUsed:        1262390400000,
-    timePasswordChanged: 1262476800000,
-    timesUsed:           1,
-  });
-
-  do_check_eq(store.data.disabledHosts.length, 1);
-  do_check_eq(store.data.disabledHosts[0], "http://www.example.org");
-});
-
-/**
- * Loads login data from a malformed JSON string.
- */
-add_task(function test_load_string_malformed()
-{
-  let store = new LoginStore(getTempFile(TEST_STORE_FILE_NAME).path);
-
-  let string = "{\"logins\":[{\"hostname\":\"http://www.example.com\"," +
-                "\"id\":1,";
-
-  yield OS.File.writeAtomic(store.path, new TextEncoder().encode(string),
-                            { tmpPath: store.path + ".tmp" });
-
-  yield store.load();
-
-  // A backup file should have been created.
-  do_check_true(yield OS.File.exists(store.path + ".corrupt"));
-  yield OS.File.remove(store.path + ".corrupt");
-
-  // The store should be ready to accept new data.
-  do_check_eq(store.data.logins.length, 0);
-  do_check_eq(store.data.disabledHosts.length, 0);
-});
-
-/**
- * Loads login data from a malformed JSON string, using the synchronous
- * initialization path.
- */
-add_task(function test_load_string_malformed_sync()
-{
-  let store = new LoginStore(getTempFile(TEST_STORE_FILE_NAME).path);
-
-  let string = "{\"logins\":[{\"hostname\":\"http://www.example.com\"," +
-                "\"id\":1,";
-
-  yield OS.File.writeAtomic(store.path, new TextEncoder().encode(string),
-                            { tmpPath: store.path + ".tmp" });
-
-  store.ensureDataReady();
-
-  // A backup file should have been created.
-  do_check_true(yield OS.File.exists(store.path + ".corrupt"));
-  yield OS.File.remove(store.path + ".corrupt");
-
-  // The store should be ready to accept new data.
-  do_check_eq(store.data.logins.length, 0);
-  do_check_eq(store.data.disabledHosts.length, 0);
-});
--- a/toolkit/components/passwordmgr/test/unit/xpcshell.ini
+++ b/toolkit/components/passwordmgr/test/unit/xpcshell.ini
@@ -1,25 +1,15 @@
 [DEFAULT]
 head = head.js
 tail =
 support-files = data/**
 
-# Test JSON file access and import from SQLite, not applicable to Android.
-[test_module_LoginImport.js]
-skip-if = os == "android"
-[test_module_LoginStore.js]
-skip-if = os == "android"
-
-# Test SQLite database backup and migration, applicable to Android only.
-[test_storage_mozStorage.js]
-skip-if = os != "android"
-
-# The following tests apply to any storage back-end.
 [test_disabled_hosts.js]
 [test_legacy_empty_formSubmitURL.js]
 [test_legacy_validation.js]
 [test_logins_change.js]
 [test_logins_decrypt_failure.js]
 [test_logins_metainfo.js]
 [test_logins_search.js]
 [test_notifications.js]
 [test_storage.js]
+[test_storage_mozStorage.js]