Bug 1310049 - Refactor FormLikeFactory to its own module for use by Form Autofill. r=steveck draft
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Tue, 18 Oct 2016 13:31:09 -0700
changeset 426589 b2a28803def0dce3de4a01db5bdbc3217c5d0f83
parent 426483 01ab78dd98805e150b0311cce2351d5b408f3001
child 534222 56d5591447b4f615e7beac9d4d66e62d3f9924c9
push id32753
push usermozilla@noorenberghe.ca
push dateTue, 18 Oct 2016 20:31:40 +0000
reviewerssteveck
bugs1310049
milestone52.0a1
Bug 1310049 - Refactor FormLikeFactory to its own module for use by Form Autofill. r=steveck This introduces LoginFormFactory which wraps FormLikeFactory for use with login-specific contexts. MozReview-Commit-ID: 6rPz5JOy3Yp
browser/base/content/content.js
toolkit/components/passwordmgr/InsecurePasswordUtils.jsm
toolkit/components/passwordmgr/LoginManagerContent.jsm
toolkit/components/passwordmgr/test/mochitest/test_formless_submit.html
toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation.html
toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation_negative.html
toolkit/components/passwordmgr/test/unit/test_getFormFields.js
toolkit/components/passwordmgr/test/unit/test_getPasswordFields.js
toolkit/modules/FormLikeFactory.jsm
toolkit/modules/moz.build
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -19,17 +19,17 @@ Cu.import("resource://gre/modules/Task.j
 XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
   "resource:///modules/E10SUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
   "resource://gre/modules/BrowserUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ContentLinkHandler",
   "resource:///modules/ContentLinkHandler.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
   "resource://gre/modules/LoginManagerContent.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "FormLikeFactory",
+XPCOMUtils.defineLazyModuleGetter(this, "LoginFormFactory",
   "resource://gre/modules/LoginManagerContent.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
   "resource://gre/modules/InsecurePasswordUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PluginContent",
   "resource:///modules/PluginContent.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FormSubmitObserver",
@@ -60,22 +60,22 @@ addMessageListener("ContextMenu:DoCustom
     () => PageMenuChild.executeMenu(message.data.generatedItemId));
 });
 
 addMessageListener("RemoteLogins:fillForm", function(message) {
   LoginManagerContent.receiveMessage(message, content);
 });
 addEventListener("DOMFormHasPassword", function(event) {
   LoginManagerContent.onDOMFormHasPassword(event, content);
-  let formLike = FormLikeFactory.createFromForm(event.target);
+  let formLike = LoginFormFactory.createFromForm(event.target);
   InsecurePasswordUtils.checkForInsecurePasswords(formLike);
 });
 addEventListener("DOMInputPasswordAdded", function(event) {
   LoginManagerContent.onDOMInputPasswordAdded(event, content);
-  let formLike = FormLikeFactory.createFromField(event.target);
+  let formLike = LoginFormFactory.createFromField(event.target);
   InsecurePasswordUtils.checkForInsecurePasswords(formLike);
 });
 addEventListener("pageshow", function(event) {
   LoginManagerContent.onPageShow(event, content);
 });
 addEventListener("DOMAutoComplete", function(event) {
   LoginManagerContent.onUsernameInput(event);
 });
--- a/toolkit/components/passwordmgr/InsecurePasswordUtils.jsm
+++ b/toolkit/components/passwordmgr/InsecurePasswordUtils.jsm
@@ -38,17 +38,17 @@ this.InsecurePasswordUtils = {
   },
 
   /**
    * Checks if there are insecure password fields present on the form's document
    * i.e. passwords inside forms with http action, inside iframes with http src,
    * or on insecure web pages. If insecure password fields are present,
    * a log message is sent to the web console to warn developers.
    *
-   * @param {FormLike} aForm A form-like object. @See {FormLikeFactory}
+   * @param {FormLike} aForm A form-like object. @See {LoginFormFactory}
    */
   checkForInsecurePasswords(aForm) {
     if (this._formRootsWarned.has(aForm.rootElement) ||
         this._formRootsWarned.get(aForm.rootElement)) {
       return;
     }
 
     let domDoc = aForm.ownerDocument;
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -1,27 +1,29 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = [ "LoginManagerContent",
-                          "FormLikeFactory",
+                          "LoginFormFactory",
                           "UserAutoCompleteResult" ];
 
 const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
 const PASSWORD_INPUT_ADDED_COALESCING_THRESHOLD_MS = 1;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FormLikeFactory",
+                                  "resource://gre/modules/FormLikeFactory.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "LoginRecipesContent",
                                   "resource://gre/modules/LoginRecipes.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
                                   "resource://gre/modules/LoginHelper.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gNetUtil",
                                    "@mozilla.org/network/util;1",
@@ -44,17 +46,17 @@ var observer = {
   // nsIFormSubmitObserver
   notify(formElement, aWindow, actionURI) {
     log("observer notified for form submission.");
 
     // We're invoked before the content's |onsubmit| handlers, so we
     // can grab form data before it might be modified (see bug 257781).
 
     try {
-      let formLike = FormLikeFactory.createFromForm(formElement);
+      let formLike = LoginFormFactory.createFromForm(formElement);
       LoginManagerContent._onFormSubmit(formLike);
     } catch (e) {
       log("Caught error in onFormSubmit(", e.lineNumber, "):", e.message);
       Cu.reportError(e);
     }
 
     return true; // Always return true, or form submit will be canceled.
   },
@@ -139,17 +141,17 @@ var LoginManagerContent = {
 
   _messages: [ "RemoteLogins:loginsFound",
                "RemoteLogins:loginsAutoCompleted" ],
 
   /**
    * WeakMap of the root element of a FormLike to the FormLike representing its fields.
    *
    * This is used to be able to lookup an existing FormLike for a given root element since multiple
-   * calls to FormLikeFactory won't give the exact same object. When batching fills we don't always
+   * calls to LoginFormFactory won't give the exact same object. When batching fills we don't always
    * want to use the most recent list of elements for a FormLike since we may end up doing multiple
    * fills for the same set of elements when a field gets added between arming and running the
    * DeferredTask.
    *
    * @type {WeakMap}
    */
   _formLikeByRootElement: new WeakMap(),
 
@@ -279,17 +281,17 @@ var LoginManagerContent = {
     return this._sendRequest(messageManager, requestData,
                              "RemoteLogins:findLogins",
                              messageData);
   },
 
   _autoCompleteSearchAsync(aSearchString, aPreviousResult,
                                      aElement, aRect) {
     let doc = aElement.ownerDocument;
-    let form = FormLikeFactory.createFromField(aElement);
+    let form = LoginFormFactory.createFromField(aElement);
     let win = doc.defaultView;
 
     let formOrigin = LoginUtils._getPasswordOrigin(doc.documentURI);
     let actionOrigin = LoginUtils._getActionOrigin(form);
 
     let messageManager = messageManagerFromWindow(win);
 
     let remote = (Services.appinfo.processType ===
@@ -333,17 +335,17 @@ var LoginManagerContent = {
   },
 
   onDOMFormHasPassword(event, window) {
     if (!event.isTrusted) {
       return;
     }
 
     let form = event.target;
-    let formLike = FormLikeFactory.createFromForm(form);
+    let formLike = LoginFormFactory.createFromForm(form);
     log("onDOMFormHasPassword:", form, formLike);
     this._fetchLoginsFromParentAndFillForm(formLike, window);
   },
 
   onDOMInputPasswordAdded(event, window) {
     if (!event.isTrusted) {
       return;
     }
@@ -353,17 +355,17 @@ var LoginManagerContent = {
       // Fill is handled by onDOMFormHasPassword which is already throttled.
       return;
     }
 
     // Only setup the listener for formless inputs.
     // Capture within a <form> but without a submit event is bug 1287202.
     this.setupProgressListener(window);
 
-    let formLike = FormLikeFactory.createFromField(pwField);
+    let formLike = LoginFormFactory.createFromField(pwField);
     log("onDOMInputPasswordAdded:", pwField, formLike);
 
     let deferredTask = this._deferredPasswordAddedTasksByRootElement.get(formLike.rootElement);
     if (!deferredTask) {
       log("Creating a DeferredTask to call _fetchLoginsFromParentAndFillForm soon");
       this._formLikeByRootElement.set(formLike.rootElement, formLike);
 
       deferredTask = new DeferredTask(function* deferredInputProcessing() {
@@ -503,17 +505,17 @@ var LoginManagerContent = {
       }
     }
 
     let clobberUsername = true;
     let options = {
       inputElement,
     };
 
-    let form = FormLikeFactory.createFromField(inputElement);
+    let form = LoginFormFactory.createFromField(inputElement);
     if (inputElement.type == "password") {
       clobberUsername = false;
     }
     this._fillForm(form, true, clobberUsername, true, true, loginsFound, recipes, options);
   },
 
   loginsFound({ form, loginsFound, recipes }) {
     let doc = form.ownerDocument;
@@ -538,17 +540,17 @@ var LoginManagerContent = {
 
     // This is probably a bit over-conservatative.
     if (!(acInputField.ownerDocument instanceof Ci.nsIDOMHTMLDocument))
       return;
 
     if (!LoginHelper.isUsernameFieldType(acInputField))
       return;
 
-    var acForm = FormLikeFactory.createFromField(acInputField);
+    var acForm = LoginFormFactory.createFromField(acInputField);
     if (!acForm)
       return;
 
     // If the username is blank, bail out now -- we don't want
     // fillForm() to try filling in a login without a username
     // to filter on (bug 471906).
     if (!acInputField.value)
       return;
@@ -644,17 +646,17 @@ var LoginManagerContent = {
     var fieldOverrideRecipe = LoginRecipesContent.getFieldOverrides(recipes, form);
     if (fieldOverrideRecipe) {
       var pwOverrideField = LoginRecipesContent.queryLoginField(
         form,
         fieldOverrideRecipe.passwordSelector
       );
       if (pwOverrideField) {
         // The field from the password override may be in a different FormLike.
-        let formLike = FormLikeFactory.createFromField(pwOverrideField);
+        let formLike = LoginFormFactory.createFromField(pwOverrideField);
         pwFields = [{
           index   : [...formLike.elements].indexOf(pwOverrideField),
           element : pwOverrideField,
         }];
       }
 
       var usernameOverrideField = LoginRecipesContent.queryLoginField(
         form,
@@ -1129,17 +1131,17 @@ var LoginManagerContent = {
    */
   getFieldContext(aField) {
     // If the element is not a proper form field, return null.
     if (!(aField instanceof Ci.nsIDOMHTMLInputElement) ||
         (aField.type != "password" && !LoginHelper.isUsernameFieldType(aField)) ||
         !aField.ownerDocument) {
       return null;
     }
-    let form = FormLikeFactory.createFromField(aField);
+    let form = LoginFormFactory.createFromField(aField);
 
     let doc = aField.ownerDocument;
     let messageManager = messageManagerFromWindow(doc.defaultView);
     let recipes = messageManager.sendSyncMessage("RemoteLogins:findRecipes", {
       formOrigin: LoginUtils._getPasswordOrigin(doc.documentURI),
     })[0];
 
     let [usernameField, newPasswordField] =
@@ -1294,154 +1296,69 @@ UserAutoCompleteResult.prototype = {
     }
   }
 };
 
 /**
  * A factory to generate FormLike objects that represent a set of login fields
  * which aren't necessarily marked up with a <form> element.
  */
-var FormLikeFactory = {
-  _propsFromForm: [
-    "autocomplete",
-    "ownerDocument",
-  ],
-
+var LoginFormFactory = {
   /**
-   * Create a FormLike object from a <form>.
+   * Create a LoginForm object from a <form>.
    *
    * @param {HTMLFormElement} aForm
-   * @return {FormLike}
+   * @return {LoginForm}
    * @throws Error if aForm isn't an HTMLFormElement
    */
   createFromForm(aForm) {
-    if (!(aForm instanceof Ci.nsIDOMHTMLFormElement)) {
-      throw new Error("createFromForm: aForm must be a nsIDOMHTMLFormElement");
-    }
-
-    let formLike = {
-      action: LoginUtils._getActionOrigin(aForm),
-      elements: [...aForm.elements],
-      rootElement: aForm,
-    };
-
-    for (let prop of this._propsFromForm) {
-      formLike[prop] = aForm[prop];
-    }
-
-    this._addToJSONProperty(formLike);
+    let formLike = FormLikeFactory.createFromForm(aForm);
+    formLike.action = LoginUtils._getActionOrigin(aForm);
 
     let state = LoginManagerContent.stateForDocument(formLike.ownerDocument);
     state.loginFormRootElements.add(formLike.rootElement);
     log("adding", formLike.rootElement, "to loginFormRootElements for", formLike.ownerDocument);
 
     LoginManagerContent._formLikeByRootElement.set(formLike.rootElement, formLike);
     return formLike;
   },
 
   /**
-   * Create a FormLike object from a password or username field.
+   * Create a LoginForm object from a password or username field.
    *
-   * If the field is in a <form>, construct the FormLike from the form.
-   * Otherwise, create a FormLike with a rootElement (wrapper) according to
-   * heuristics. Currently all <input> not in a <form> are one FormLike but this
+   * If the field is in a <form>, construct the LoginForm from the form.
+   * Otherwise, create a LoginForm with a rootElement (wrapper) according to
+   * heuristics. Currently all <input> not in a <form> are one LoginForm but this
    * shouldn't be relied upon as the heuristics may change to detect multiple
    * "forms" (e.g. registration and login) on one page with a <form>.
    *
-   * Note that two FormLikes created from the same field won't return the same FormLike object.
-   * Use the `rootElement` property on the FormLike as a key instead.
+   * Note that two LoginForms created from the same field won't return the same LoginForm object.
+   * Use the `rootElement` property on the LoginForm as a key instead.
    *
    * @param {HTMLInputElement} aField - a password or username field in a document
-   * @return {FormLike}
+   * @return {LoginForm}
    * @throws Error if aField isn't a password or username field in a document
    */
   createFromField(aField) {
     if (!(aField instanceof Ci.nsIDOMHTMLInputElement) ||
         (aField.type != "password" && !LoginHelper.isUsernameFieldType(aField)) ||
         !aField.ownerDocument) {
       throw new Error("createFromField requires a password or username field in a document");
     }
 
     if (aField.form) {
       return this.createFromForm(aField.form);
     }
 
-    let doc = aField.ownerDocument;
-    log("Created non-form FormLike for rootElement:", doc.documentElement);
-    let elements = [];
-    for (let el of doc.documentElement.querySelectorAll("input")) {
-      if (!el.form) {
-        elements.push(el);
-      }
-    }
-    let formLike = {
-      action: LoginUtils._getPasswordOrigin(doc.baseURI),
-      autocomplete: "on",
-      // Exclude elements inside the rootElement that are already in a <form> as
-      // they will be handled by their own FormLike.
-      elements,
-      ownerDocument: doc,
-      rootElement: doc.documentElement,
-    };
+    let formLike = FormLikeFactory.createFromField(aField);
+    formLike.action = LoginUtils._getPasswordOrigin(aField.ownerDocument.baseURI);
+    log("Created non-form FormLike for rootElement:", aField.ownerDocument.documentElement);
 
     let state = LoginManagerContent.stateForDocument(formLike.ownerDocument);
     state.loginFormRootElements.add(formLike.rootElement);
     log("adding", formLike.rootElement, "to loginFormRootElements for", formLike.ownerDocument);
 
 
     LoginManagerContent._formLikeByRootElement.set(formLike.rootElement, formLike);
 
-    this._addToJSONProperty(formLike);
     return formLike;
   },
-
-  /**
-   * Add a `toJSON` property to a FormLike so logging which ends up going
-   * through dump doesn't include usless garbage from DOM objects.
-   */
-  _addToJSONProperty(aFormLike) {
-    function prettyElementOutput(aElement) {
-      let idText = aElement.id ? "#" + aElement.id : "";
-      let classText = "";
-      for (let className of aElement.classList) {
-        classText += "." + className;
-      }
-      return `<${aElement.nodeName + idText + classText}>`;
-    }
-
-    Object.defineProperty(aFormLike, "toJSON", {
-      value: () => {
-        let cleansed = {};
-        for (let key of Object.keys(aFormLike)) {
-          let value = aFormLike[key];
-          let cleansedValue = value;
-
-          switch (key) {
-            case "elements": {
-              cleansedValue = [];
-              for (let element of value) {
-                cleansedValue.push(prettyElementOutput(element));
-              }
-              break;
-            }
-
-            case "ownerDocument": {
-              cleansedValue = {
-                location: {
-                  href: value.location.href,
-                },
-              };
-              break;
-            }
-
-            case "rootElement": {
-              cleansedValue = prettyElementOutput(value);
-              break;
-            }
-          }
-
-          cleansed[key] = cleansedValue;
-        }
-        return cleansed;
-      }
-    });
-  },
 };
--- a/toolkit/components/passwordmgr/test/mochitest/test_formless_submit.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_formless_submit.html
@@ -6,17 +6,17 @@
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
   <script src="/tests/SimpleTest/SpawnTask.js"></script>
   <script src="pwmgr_common.js"></script>
   <link rel="stylesheet" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <script type="application/javascript;version=1.8">
 const LMCBackstagePass = SpecialPowers.Cu.import("resource://gre/modules/LoginManagerContent.jsm");
-const { LoginManagerContent, FormLikeFactory } = LMCBackstagePass;
+const { LoginManagerContent, LoginFormFactory } = LMCBackstagePass;
 
 let chromeScript = runInParent(SimpleTest.getTestFileURL("pwmgr_common.js"));
 
 let loadPromise = new Promise(resolve => {
   document.addEventListener("DOMContentLoaded", () => {
     document.getElementById("loginFrame").addEventListener("load", (evt) => {
       resolve();
     });
@@ -138,17 +138,17 @@ add_task(function* test() {
   let loginFrame = document.getElementById("loginFrame");
   let frameDoc = loginFrame.contentWindow.document;
 
   for (let tc of TESTCASES) {
     info("Starting testcase: " + JSON.stringify(tc));
     frameDoc.documentElement.innerHTML = tc.document;
     let inputForFormLike = frameDoc.querySelectorAll("input")[tc.inputIndexForFormLike];
 
-    let formLike = FormLikeFactory.createFromField(inputForFormLike);
+    let formLike = LoginFormFactory.createFromField(inputForFormLike);
 
     info("Calling _onFormSubmit with FormLike");
     let processedPromise = getSubmitMessage();
     LoginManagerContent._onFormSubmit(formLike);
 
     let submittedResult = yield processedPromise;
 
     // Check data sent via RemoteLogins:onFormSubmit
--- a/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation.html
@@ -6,17 +6,17 @@
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
   <script src="/tests/SimpleTest/SpawnTask.js"></script>
   <script src="pwmgr_common.js"></script>
   <link rel="stylesheet" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <script type="application/javascript;version=1.8">
 const LMCBackstagePass = SpecialPowers.Cu.import("resource://gre/modules/LoginManagerContent.jsm");
-const { LoginManagerContent, FormLikeFactory } = LMCBackstagePass;
+const { LoginManagerContent, LoginFormFactory } = LMCBackstagePass;
 
 let chromeScript = runInParent(SimpleTest.getTestFileURL("pwmgr_common.js"));
 
 let loadPromise = new Promise(resolve => {
   document.addEventListener("DOMContentLoaded", () => {
     document.getElementById("loginFrame").addEventListener("load", (evt) => {
       resolve();
     });
--- a/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation_negative.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation_negative.html
@@ -6,17 +6,17 @@
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
   <script src="/tests/SimpleTest/SpawnTask.js"></script>
   <script src="pwmgr_common.js"></script>
   <link rel="stylesheet" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <script type="application/javascript;version=1.8">
 const LMCBackstagePass = SpecialPowers.Cu.import("resource://gre/modules/LoginManagerContent.jsm");
-const { LoginManagerContent, FormLikeFactory } = LMCBackstagePass;
+const { LoginManagerContent, LoginFormFactory } = LMCBackstagePass;
 
 SimpleTest.requestFlakyTimeout("Testing that a message doesn't arrive");
 
 let chromeScript = runInParent(SimpleTest.getTestFileURL("pwmgr_common.js"));
 
 let loadPromise = new Promise(resolve => {
   document.addEventListener("DOMContentLoaded", () => {
     document.getElementById("loginFrame").addEventListener("load", (evt) => {
--- a/toolkit/components/passwordmgr/test/unit/test_getFormFields.js
+++ b/toolkit/components/passwordmgr/test/unit/test_getFormFields.js
@@ -4,17 +4,17 @@
 
 "use strict";
 
 //Services.prefs.setBoolPref("signon.debug", true);
 
 Cu.importGlobalProperties(["URL"]);
 
 const LMCBackstagePass = Cu.import("resource://gre/modules/LoginManagerContent.jsm");
-const { LoginManagerContent, FormLikeFactory } = LMCBackstagePass;
+const { LoginManagerContent, LoginFormFactory } = LMCBackstagePass;
 const TESTCASES = [
   {
     description: "1 password field outside of a <form>",
     document: `<input id="pw1" type=password>`,
     returnedFieldIDs: [null, "pw1", null],
     skipEmptyFields: undefined,
   },
   {
@@ -118,17 +118,17 @@ for (let tc of TESTCASES) {
     add_task(function*() {
       do_print("Starting testcase: " + testcase.description);
       let document = MockDocument.createTestDocument("http://localhost:8080/test/",
                                                       testcase.document);
 
       let input = document.querySelector("input");
       MockDocument.mockOwnerDocumentProperty(input, document, "http://localhost:8080/test/");
 
-      let formLike = FormLikeFactory.createFromField(input);
+      let formLike = LoginFormFactory.createFromField(input);
 
       let actual = LoginManagerContent._getFormFields(formLike,
                                                       testcase.skipEmptyFields,
                                                       new Set());
 
       Assert.strictEqual(testcase.returnedFieldIDs.length, 3,
                          "_getFormFields returns 3 elements");
 
--- a/toolkit/components/passwordmgr/test/unit/test_getPasswordFields.js
+++ b/toolkit/components/passwordmgr/test/unit/test_getPasswordFields.js
@@ -1,16 +1,16 @@
 /*
- * Test for LoginManagerContent._getPasswordFields using FormLikeFactory.
+ * Test for LoginManagerContent._getPasswordFields using LoginFormFactory.
  */
 
 "use strict";
 
 const LMCBackstagePass = Cu.import("resource://gre/modules/LoginManagerContent.jsm");
-const { LoginManagerContent, FormLikeFactory } = LMCBackstagePass;
+const { LoginManagerContent, LoginFormFactory } = LMCBackstagePass;
 const TESTCASES = [
   {
     description: "Empty document",
     document: ``,
     returnedFieldIDsByFormLike: [],
     skipEmptyFields: undefined,
   },
   {
@@ -92,17 +92,17 @@ for (let tc of TESTCASES) {
     let testcase = tc;
     add_task(function*() {
       do_print("Starting testcase: " + testcase.description);
       let document = MockDocument.createTestDocument("http://localhost:8080/test/",
                                                      testcase.document);
 
       let mapRootElementToFormLike = new Map();
       for (let input of document.querySelectorAll("input")) {
-        let formLike = FormLikeFactory.createFromField(input);
+        let formLike = LoginFormFactory.createFromField(input);
         let existingFormLike = mapRootElementToFormLike.get(formLike.rootElement);
         if (!existingFormLike) {
           mapRootElementToFormLike.set(formLike.rootElement, formLike);
           continue;
         }
 
         // If the formLike is already present, ensure that the properties are the same.
         do_print("Checking if the new FormLike for the same root has the same properties");
@@ -114,17 +114,17 @@ for (let tc of TESTCASES) {
 
       let formLikeIndex = -1;
       for (let formLikeFromInput of mapRootElementToFormLike.values()) {
         formLikeIndex++;
         let pwFields = LoginManagerContent._getPasswordFields(formLikeFromInput,
                                                               testcase.skipEmptyFields);
 
         if (formLikeFromInput.rootElement instanceof Ci.nsIDOMHTMLFormElement) {
-          let formLikeFromForm = FormLikeFactory.createFromForm(formLikeFromInput.rootElement);
+          let formLikeFromForm = LoginFormFactory.createFromForm(formLikeFromInput.rootElement);
           do_print("Checking that the FormLike created for the <form> matches" +
                    " the one from a password field");
           formLikeEqual(formLikeFromInput, formLikeFromForm);
         }
 
 
         if (testcase.returnedFieldIDsByFormLike[formLikeIndex].length === 0) {
           Assert.strictEqual(pwFields, null,
copy from toolkit/components/passwordmgr/LoginManagerContent.jsm
copy to toolkit/modules/FormLikeFactory.jsm
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/modules/FormLikeFactory.jsm
@@ -1,1311 +1,26 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-this.EXPORTED_SYMBOLS = [ "LoginManagerContent",
-                          "FormLikeFactory",
-                          "UserAutoCompleteResult" ];
+this.EXPORTED_SYMBOLS = ["FormLikeFactory"];
 
 const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
-const PASSWORD_INPUT_ADDED_COALESCING_THRESHOLD_MS = 1;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "LoginRecipesContent",
-                                  "resource://gre/modules/LoginRecipes.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
-                                  "resource://gre/modules/LoginHelper.jsm");
-
-XPCOMUtils.defineLazyServiceGetter(this, "gNetUtil",
-                                   "@mozilla.org/network/util;1",
-                                   "nsINetUtil");
-
-XPCOMUtils.defineLazyGetter(this, "log", () => {
-  let logger = LoginHelper.createLogger("LoginManagerContent");
-  return logger.log.bind(logger);
-});
-
-// These mirror signon.* prefs.
-var gEnabled, gAutofillForms, gStoreWhenAutocompleteOff;
-
-var observer = {
-  QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
-                                          Ci.nsIFormSubmitObserver,
-                                          Ci.nsIWebProgressListener,
-                                          Ci.nsISupportsWeakReference]),
-
-  // nsIFormSubmitObserver
-  notify(formElement, aWindow, actionURI) {
-    log("observer notified for form submission.");
-
-    // We're invoked before the content's |onsubmit| handlers, so we
-    // can grab form data before it might be modified (see bug 257781).
-
-    try {
-      let formLike = FormLikeFactory.createFromForm(formElement);
-      LoginManagerContent._onFormSubmit(formLike);
-    } catch (e) {
-      log("Caught error in onFormSubmit(", e.lineNumber, "):", e.message);
-      Cu.reportError(e);
-    }
-
-    return true; // Always return true, or form submit will be canceled.
-  },
-
-  onPrefChange() {
-    gEnabled = Services.prefs.getBoolPref("signon.rememberSignons");
-    gAutofillForms = Services.prefs.getBoolPref("signon.autofillForms");
-    gStoreWhenAutocompleteOff = Services.prefs.getBoolPref("signon.storeWhenAutocompleteOff");
-  },
-
-  // nsIWebProgressListener
-  onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
-    // Only handle pushState/replaceState here.
-    if (!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) ||
-        !(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_PUSHSTATE)) {
-      return;
-    }
-
-    log("onLocationChange handled:", aLocation.spec, aWebProgress.DOMWindow.document);
-
-    LoginManagerContent._onNavigation(aWebProgress.DOMWindow.document);
-  },
-
-  onStateChange(aWebProgress, aRequest, aState, aStatus) {
-    if (!(aState & Ci.nsIWebProgressListener.STATE_START)) {
-      return;
-    }
-
-    // We only care about when a page triggered a load, not the user. For example:
-    // clicking refresh/back/forward, typing a URL and hitting enter, and loading a bookmark aren't
-    // likely to be when a user wants to save a login.
-    let channel = aRequest.QueryInterface(Ci.nsIChannel);
-    let triggeringPrincipal = channel.loadInfo.triggeringPrincipal;
-    if (triggeringPrincipal.isNullPrincipal ||
-        triggeringPrincipal.equals(Services.scriptSecurityManager.getSystemPrincipal())) {
-      return;
-    }
-
-    // Don't handle history navigation, reload, or pushState not triggered via chrome UI.
-    // e.g. history.go(-1), location.reload(), history.replaceState()
-    if (!(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_NORMAL)) {
-      log("onStateChange: loadType isn't LOAD_CMD_NORMAL:", aWebProgress.loadType);
-      return;
-    }
-
-    log("onStateChange handled:", channel);
-    LoginManagerContent._onNavigation(aWebProgress.DOMWindow.document);
-  },
-};
-
-Services.obs.addObserver(observer, "earlyformsubmit", false);
-var prefBranch = Services.prefs.getBranch("signon.");
-prefBranch.addObserver("", observer.onPrefChange, false);
-
-observer.onPrefChange(); // read initial values
-
-
-function messageManagerFromWindow(win) {
-  return win.QueryInterface(Ci.nsIInterfaceRequestor)
-            .getInterface(Ci.nsIWebNavigation)
-            .QueryInterface(Ci.nsIDocShell)
-            .QueryInterface(Ci.nsIInterfaceRequestor)
-            .getInterface(Ci.nsIContentFrameMessageManager);
-}
-
-// This object maps to the "child" process (even in the single-process case).
-var LoginManagerContent = {
-
-  __formFillService : null, // FormFillController, for username autocompleting
-  get _formFillService() {
-    if (!this.__formFillService)
-      this.__formFillService =
-                      Cc["@mozilla.org/satchel/form-fill-controller;1"].
-                      getService(Ci.nsIFormFillController);
-    return this.__formFillService;
-  },
-
-  _getRandomId() {
-    return Cc["@mozilla.org/uuid-generator;1"]
-             .getService(Ci.nsIUUIDGenerator).generateUUID().toString();
-  },
-
-  _messages: [ "RemoteLogins:loginsFound",
-               "RemoteLogins:loginsAutoCompleted" ],
-
-  /**
-   * WeakMap of the root element of a FormLike to the FormLike representing its fields.
-   *
-   * This is used to be able to lookup an existing FormLike for a given root element since multiple
-   * calls to FormLikeFactory won't give the exact same object. When batching fills we don't always
-   * want to use the most recent list of elements for a FormLike since we may end up doing multiple
-   * fills for the same set of elements when a field gets added between arming and running the
-   * DeferredTask.
-   *
-   * @type {WeakMap}
-   */
-  _formLikeByRootElement: new WeakMap(),
-
-  /**
-   * WeakMap of the root element of a WeakMap to the DeferredTask to fill its fields.
-   *
-   * This is used to be able to throttle fills for a FormLike since onDOMInputPasswordAdded gets
-   * dispatched for each password field added to a document but we only want to fill once per
-   * FormLike when multiple fields are added at once.
-   *
-   * @type {WeakMap}
-   */
-  _deferredPasswordAddedTasksByRootElement: new WeakMap(),
-
-  // Map from form login requests to information about that request.
-  _requests: new Map(),
-
-  // Number of outstanding requests to each manager.
-  _managers: new Map(),
-
-  _takeRequest(msg) {
-    let data = msg.data;
-    let request = this._requests.get(data.requestId);
-
-    this._requests.delete(data.requestId);
-
-    let count = this._managers.get(msg.target);
-    if (--count === 0) {
-      this._managers.delete(msg.target);
-
-      for (let message of this._messages)
-        msg.target.removeMessageListener(message, this);
-    } else {
-      this._managers.set(msg.target, count);
-    }
-
-    return request;
-  },
-
-  _sendRequest(messageManager, requestData,
-                         name, messageData) {
-    let count;
-    if (!(count = this._managers.get(messageManager))) {
-      this._managers.set(messageManager, 1);
-
-      for (let message of this._messages)
-        messageManager.addMessageListener(message, this);
-    } else {
-      this._managers.set(messageManager, ++count);
-    }
-
-    let requestId = this._getRandomId();
-    messageData.requestId = requestId;
-
-    messageManager.sendAsyncMessage(name, messageData);
-
-    let deferred = Promise.defer();
-    requestData.promise = deferred;
-    this._requests.set(requestId, requestData);
-    return deferred.promise;
-  },
-
-  receiveMessage(msg, window) {
-    if (msg.name == "RemoteLogins:fillForm") {
-      this.fillForm({
-        topDocument: window.document,
-        loginFormOrigin: msg.data.loginFormOrigin,
-        loginsFound: LoginHelper.vanillaObjectsToLogins(msg.data.logins),
-        recipes: msg.data.recipes,
-        inputElement: msg.objects.inputElement,
-      });
-      return;
-    }
-
-    let request = this._takeRequest(msg);
-    switch (msg.name) {
-      case "RemoteLogins:loginsFound": {
-        let loginsFound = LoginHelper.vanillaObjectsToLogins(msg.data.logins);
-        request.promise.resolve({
-          form: request.form,
-          loginsFound: loginsFound,
-          recipes: msg.data.recipes,
-        });
-        break;
-      }
-
-      case "RemoteLogins:loginsAutoCompleted": {
-        let loginsFound =
-          LoginHelper.vanillaObjectsToLogins(msg.data.logins);
-        // If we're in the parent process, don't pass a message manager so our
-        // autocomplete result objects know they can remove the login from the
-        // login manager directly.
-        let messageManager =
-          (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) ?
-            msg.target : undefined;
-        request.promise.resolve({ logins: loginsFound, messageManager });
-        break;
-      }
-    }
-  },
-
-  /**
-   * Get relevant logins and recipes from the parent
-   *
-   * @param {HTMLFormElement} form - form to get login data for
-   * @param {Object} options
-   * @param {boolean} options.showMasterPassword - whether to show a master password prompt
-   */
-  _getLoginDataFromParent(form, options) {
-    let doc = form.ownerDocument;
-    let win = doc.defaultView;
-
-    let formOrigin = LoginUtils._getPasswordOrigin(doc.documentURI);
-    if (!formOrigin) {
-      return Promise.reject("_getLoginDataFromParent: A form origin is required");
-    }
-    let actionOrigin = LoginUtils._getActionOrigin(form);
-
-    let messageManager = messageManagerFromWindow(win);
-
-    // XXX Weak??
-    let requestData = { form: form };
-    let messageData = { formOrigin: formOrigin,
-                        actionOrigin: actionOrigin,
-                        options: options };
-
-    return this._sendRequest(messageManager, requestData,
-                             "RemoteLogins:findLogins",
-                             messageData);
-  },
-
-  _autoCompleteSearchAsync(aSearchString, aPreviousResult,
-                                     aElement, aRect) {
-    let doc = aElement.ownerDocument;
-    let form = FormLikeFactory.createFromField(aElement);
-    let win = doc.defaultView;
-
-    let formOrigin = LoginUtils._getPasswordOrigin(doc.documentURI);
-    let actionOrigin = LoginUtils._getActionOrigin(form);
-
-    let messageManager = messageManagerFromWindow(win);
-
-    let remote = (Services.appinfo.processType ===
-                  Services.appinfo.PROCESS_TYPE_CONTENT);
-
-    let previousResult = aPreviousResult ?
-                           { searchString: aPreviousResult.searchString,
-                             logins: LoginHelper.loginsToVanillaObjects(aPreviousResult.logins) } :
-                           null;
-
-    let requestData = {};
-    let messageData = { formOrigin: formOrigin,
-                        actionOrigin: actionOrigin,
-                        searchString: aSearchString,
-                        previousResult: previousResult,
-                        rect: aRect,
-                        remote: remote };
-
-    return this._sendRequest(messageManager, requestData,
-                             "RemoteLogins:autoCompleteLogins",
-                             messageData);
-  },
-
-  setupProgressListener(window) {
-    if (!LoginHelper.formlessCaptureEnabled) {
-      return;
-    }
-
-    try {
-      let webProgress = window.QueryInterface(Ci.nsIInterfaceRequestor).
-                        getInterface(Ci.nsIWebNavigation).
-                        QueryInterface(Ci.nsIDocShell).
-                        QueryInterface(Ci.nsIInterfaceRequestor).
-                        getInterface(Ci.nsIWebProgress);
-      webProgress.addProgressListener(observer,
-                                      Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT |
-                                      Ci.nsIWebProgress.NOTIFY_LOCATION);
-    } catch (ex) {
-      // Ignore NS_ERROR_FAILURE if the progress listener was already added
-    }
-  },
-
-  onDOMFormHasPassword(event, window) {
-    if (!event.isTrusted) {
-      return;
-    }
-
-    let form = event.target;
-    let formLike = FormLikeFactory.createFromForm(form);
-    log("onDOMFormHasPassword:", form, formLike);
-    this._fetchLoginsFromParentAndFillForm(formLike, window);
-  },
-
-  onDOMInputPasswordAdded(event, window) {
-    if (!event.isTrusted) {
-      return;
-    }
-
-    let pwField = event.target;
-    if (pwField.form) {
-      // Fill is handled by onDOMFormHasPassword which is already throttled.
-      return;
-    }
-
-    // Only setup the listener for formless inputs.
-    // Capture within a <form> but without a submit event is bug 1287202.
-    this.setupProgressListener(window);
-
-    let formLike = FormLikeFactory.createFromField(pwField);
-    log("onDOMInputPasswordAdded:", pwField, formLike);
-
-    let deferredTask = this._deferredPasswordAddedTasksByRootElement.get(formLike.rootElement);
-    if (!deferredTask) {
-      log("Creating a DeferredTask to call _fetchLoginsFromParentAndFillForm soon");
-      this._formLikeByRootElement.set(formLike.rootElement, formLike);
-
-      deferredTask = new DeferredTask(function* deferredInputProcessing() {
-        // Get the updated formLike instead of the one at the time of creating the DeferredTask via
-        // a closure since it could be stale since FormLike.elements isn't live.
-        let formLike2 = this._formLikeByRootElement.get(formLike.rootElement);
-        log("Running deferred processing of onDOMInputPasswordAdded", formLike2);
-        this._deferredPasswordAddedTasksByRootElement.delete(formLike2.rootElement);
-        this._fetchLoginsFromParentAndFillForm(formLike2, window);
-      }.bind(this), PASSWORD_INPUT_ADDED_COALESCING_THRESHOLD_MS);
-
-      this._deferredPasswordAddedTasksByRootElement.set(formLike.rootElement, deferredTask);
-    }
-
-    if (deferredTask.isArmed) {
-      log("DeferredTask is already armed so just updating the FormLike");
-      // We update the FormLike so it (most important .elements) is fresh when the task eventually
-      // runs since changes to the elements could affect our field heuristics.
-      this._formLikeByRootElement.set(formLike.rootElement, formLike);
-    } else if (window.document.readyState == "complete") {
-      log("Arming the DeferredTask we just created since document.readyState == 'complete'");
-      deferredTask.arm();
-    } else {
-      window.addEventListener("DOMContentLoaded", function armPasswordAddedTask() {
-        window.removeEventListener("DOMContentLoaded", armPasswordAddedTask);
-        log("Arming the onDOMInputPasswordAdded DeferredTask due to DOMContentLoaded");
-        deferredTask.arm();
-      });
-    }
-  },
-
-  /**
-   * Fetch logins from the parent for a given form and then attempt to fill it.
-   *
-   * @param {FormLike} form to fetch the logins for then try autofill.
-   * @param {Window} window
-   */
-  _fetchLoginsFromParentAndFillForm(form, window) {
-    this._detectInsecureFormLikes(window);
-
-    let messageManager = messageManagerFromWindow(window);
-    messageManager.sendAsyncMessage("LoginStats:LoginEncountered");
-
-    if (!gEnabled) {
-      return;
-    }
-
-    this._getLoginDataFromParent(form, { showMasterPassword: true })
-        .then(this.loginsFound.bind(this))
-        .then(null, Cu.reportError);
-  },
-
-  onPageShow(event, window) {
-    this._detectInsecureFormLikes(window);
-  },
-
-  /**
-   * Maps all DOM content documents in this content process, including those in
-   * frames, to the current state used by the Login Manager.
-   */
-  loginFormStateByDocument: new WeakMap(),
-
-  /**
-   * Retrieves a reference to the state object associated with the given
-   * document. This is initialized to an object with default values.
-   */
-  stateForDocument(document) {
-    let loginFormState = this.loginFormStateByDocument.get(document);
-    if (!loginFormState) {
-      loginFormState = {
-        loginFormRootElements: new Set(),
-      };
-      this.loginFormStateByDocument.set(document, loginFormState);
-    }
-    return loginFormState;
-  },
-
-  /**
-   * Compute whether there is an insecure login form on any frame of the current page, and
-   * notify the parent process. This is used to control whether insecure password UI appears.
-   */
-  _detectInsecureFormLikes(topWindow) {
-    log("_detectInsecureFormLikes", topWindow.location.href);
-
-    // Returns true if this window or any subframes have insecure login forms.
-    let hasInsecureLoginForms = (thisWindow) => {
-      let doc = thisWindow.document;
-      let hasLoginForm = this.stateForDocument(doc).loginFormRootElements.size > 0;
-      return (hasLoginForm && !thisWindow.isSecureContext) ||
-             Array.some(thisWindow.frames,
-                        frame => hasInsecureLoginForms(frame));
-    };
-
-    let messageManager = messageManagerFromWindow(topWindow);
-    messageManager.sendAsyncMessage("RemoteLogins:insecureLoginFormPresent", {
-      hasInsecureLoginForms: hasInsecureLoginForms(topWindow),
-    });
-  },
-
-  /**
-   * Perform a password fill upon user request coming from the parent process.
-   * The fill will be in the form previously identified during page navigation.
-   *
-   * @param An object with the following properties:
-   *        {
-   *          topDocument:
-   *            DOM document currently associated to the the top-level window
-   *            for which the fill is requested. This may be different from the
-   *            document that originally caused the login UI to be displayed.
-   *          loginFormOrigin:
-   *            String with the origin for which the login UI was displayed.
-   *            This must match the origin of the form used for the fill.
-   *          loginsFound:
-   *            Array containing the login to fill. While other messages may
-   *            have more logins, for this use case this is expected to have
-   *            exactly one element. The origin of the login may be different
-   *            from the origin of the form used for the fill.
-   *          recipes:
-   *            Fill recipes transmitted together with the original message.
-   *          inputElement:
-   *            Username or password input element from the form we want to fill.
-   *        }
-   */
-  fillForm({ topDocument, loginFormOrigin, loginsFound, recipes, inputElement }) {
-    if (!inputElement) {
-      log("fillForm: No input element specified");
-      return;
-    }
-    if (LoginUtils._getPasswordOrigin(topDocument.documentURI) != loginFormOrigin) {
-      if (!inputElement ||
-          LoginUtils._getPasswordOrigin(inputElement.ownerDocument.documentURI) != loginFormOrigin) {
-        log("fillForm: The requested origin doesn't match the one form the",
-            "document. This may mean we navigated to a document from a different",
-            "site before we had a chance to indicate this change in the user",
-            "interface.");
-        return;
-      }
-    }
-
-    let clobberUsername = true;
-    let options = {
-      inputElement,
-    };
-
-    let form = FormLikeFactory.createFromField(inputElement);
-    if (inputElement.type == "password") {
-      clobberUsername = false;
-    }
-    this._fillForm(form, true, clobberUsername, true, true, loginsFound, recipes, options);
-  },
-
-  loginsFound({ form, loginsFound, recipes }) {
-    let doc = form.ownerDocument;
-    let autofillForm = gAutofillForms && !PrivateBrowsingUtils.isContentWindowPrivate(doc.defaultView);
-
-    this._fillForm(form, autofillForm, false, false, false, loginsFound, recipes);
-  },
-
-  /*
-   * onUsernameInput
-   *
-   * Listens for DOMAutoComplete and blur events on an input field.
-   */
-  onUsernameInput(event) {
-    if (!event.isTrusted)
-      return;
-
-    if (!gEnabled)
-      return;
-
-    var acInputField = event.target;
-
-    // This is probably a bit over-conservatative.
-    if (!(acInputField.ownerDocument instanceof Ci.nsIDOMHTMLDocument))
-      return;
-
-    if (!LoginHelper.isUsernameFieldType(acInputField))
-      return;
-
-    var acForm = FormLikeFactory.createFromField(acInputField);
-    if (!acForm)
-      return;
-
-    // If the username is blank, bail out now -- we don't want
-    // fillForm() to try filling in a login without a username
-    // to filter on (bug 471906).
-    if (!acInputField.value)
-      return;
-
-    log("onUsernameInput from", event.type);
-
-    let doc = acForm.ownerDocument;
-    let messageManager = messageManagerFromWindow(doc.defaultView);
-    let recipes = messageManager.sendSyncMessage("RemoteLogins:findRecipes", {
-      formOrigin: LoginUtils._getPasswordOrigin(doc.documentURI),
-    })[0];
-
-    // Make sure the username field fillForm will use is the
-    // same field as the autocomplete was activated on.
-    var [usernameField, passwordField, ignored] =
-        this._getFormFields(acForm, false, recipes);
-    if (usernameField == acInputField && passwordField) {
-      this._getLoginDataFromParent(acForm, { showMasterPassword: false })
-          .then(({ form, loginsFound, recipes }) => {
-            this._fillForm(form, true, false, true, true, loginsFound, recipes);
-          })
-          .then(null, Cu.reportError);
-    } else {
-      // Ignore the event, it's for some input we don't care about.
-    }
-  },
-
-  /**
-   * @param {FormLike} form - the FormLike to look for password fields in.
-   * @param {bool} [skipEmptyFields=false] - Whether to ignore password fields with no value.
-   *                                         Used at capture time since saving empty values isn't
-   *                                         useful.
-   * @return {Array|null} Array of password field elements for the specified form.
-   *                      If no pw fields are found, or if more than 3 are found, then null
-   *                      is returned.
-   */
-  _getPasswordFields(form, skipEmptyFields = false) {
-    // Locate the password fields in the form.
-    let pwFields = [];
-    for (let i = 0; i < form.elements.length; i++) {
-      let element = form.elements[i];
-      if (!(element instanceof Ci.nsIDOMHTMLInputElement) ||
-          element.type != "password") {
-        continue;
-      }
-
-      if (skipEmptyFields && !element.value) {
-        continue;
-      }
-
-      pwFields[pwFields.length] = {
-                                    index   : i,
-                                    element : element
-                                  };
-    }
-
-    // If too few or too many fields, bail out.
-    if (pwFields.length == 0) {
-      log("(form ignored -- no password fields.)");
-      return null;
-    } else if (pwFields.length > 3) {
-      log("(form ignored -- too many password fields. [ got ", pwFields.length, "])");
-      return null;
-    }
-
-    return pwFields;
-  },
-
-  /**
-   * Returns the username and password fields found in the form.
-   * Can handle complex forms by trying to figure out what the
-   * relevant fields are.
-   *
-   * @param {FormLike} form
-   * @param {bool} isSubmission
-   * @param {Set} recipes
-   * @return {Array} [usernameField, newPasswordField, oldPasswordField]
-   *
-   * usernameField may be null.
-   * newPasswordField will always be non-null.
-   * oldPasswordField may be null. If null, newPasswordField is just
-   * "theLoginField". If not null, the form is apparently a
-   * change-password field, with oldPasswordField containing the password
-   * that is being changed.
-   *
-   * Note that even though we can create a FormLike from a text field,
-   * this method will only return a non-null usernameField if the
-   * FormLike has a password field.
-   */
-  _getFormFields(form, isSubmission, recipes) {
-    var usernameField = null;
-    var pwFields = null;
-    var fieldOverrideRecipe = LoginRecipesContent.getFieldOverrides(recipes, form);
-    if (fieldOverrideRecipe) {
-      var pwOverrideField = LoginRecipesContent.queryLoginField(
-        form,
-        fieldOverrideRecipe.passwordSelector
-      );
-      if (pwOverrideField) {
-        // The field from the password override may be in a different FormLike.
-        let formLike = FormLikeFactory.createFromField(pwOverrideField);
-        pwFields = [{
-          index   : [...formLike.elements].indexOf(pwOverrideField),
-          element : pwOverrideField,
-        }];
-      }
-
-      var usernameOverrideField = LoginRecipesContent.queryLoginField(
-        form,
-        fieldOverrideRecipe.usernameSelector
-      );
-      if (usernameOverrideField) {
-        usernameField = usernameOverrideField;
-      }
-    }
-
-    if (!pwFields) {
-      // Locate the password field(s) in the form. Up to 3 supported.
-      // If there's no password field, there's nothing for us to do.
-      pwFields = this._getPasswordFields(form, isSubmission);
-    }
-
-    if (!pwFields) {
-      return [null, null, null];
-    }
-
-    if (!usernameField) {
-      // Locate the username field in the form by searching backwards
-      // from the first password field, assume the first text field is the
-      // username. We might not find a username field if the user is
-      // already logged in to the site.
-      for (var i = pwFields[0].index - 1; i >= 0; i--) {
-        var element = form.elements[i];
-        if (!LoginHelper.isUsernameFieldType(element)) {
-          continue;
-        }
-
-        if (fieldOverrideRecipe && fieldOverrideRecipe.notUsernameSelector &&
-            element.matches(fieldOverrideRecipe.notUsernameSelector)) {
-          continue;
-        }
-
-        usernameField = element;
-        break;
-      }
-    }
-
-    if (!usernameField)
-      log("(form -- no username field found)");
-    else
-      log("Username field ", usernameField, "has name/value:",
-          usernameField.name, "/", usernameField.value);
-
-    // If we're not submitting a form (it's a page load), there are no
-    // password field values for us to use for identifying fields. So,
-    // just assume the first password field is the one to be filled in.
-    if (!isSubmission || pwFields.length == 1) {
-      var passwordField = pwFields[0].element;
-      log("Password field", passwordField, "has name: ", passwordField.name);
-      return [usernameField, passwordField, null];
-    }
-
-
-    // Try to figure out WTF is in the form based on the password values.
-    var oldPasswordField, newPasswordField;
-    var pw1 = pwFields[0].element.value;
-    var pw2 = pwFields[1].element.value;
-    var pw3 = (pwFields[2] ? pwFields[2].element.value : null);
-
-    if (pwFields.length == 3) {
-      // Look for two identical passwords, that's the new password
-
-      if (pw1 == pw2 && pw2 == pw3) {
-        // All 3 passwords the same? Weird! Treat as if 1 pw field.
-        newPasswordField = pwFields[0].element;
-        oldPasswordField = null;
-      } else if (pw1 == pw2) {
-        newPasswordField = pwFields[0].element;
-        oldPasswordField = pwFields[2].element;
-      } else if (pw2 == pw3) {
-        oldPasswordField = pwFields[0].element;
-        newPasswordField = pwFields[2].element;
-      } else  if (pw1 == pw3) {
-        // A bit odd, but could make sense with the right page layout.
-        newPasswordField = pwFields[0].element;
-        oldPasswordField = pwFields[1].element;
-      } else {
-        // We can't tell which of the 3 passwords should be saved.
-        log("(form ignored -- all 3 pw fields differ)");
-        return [null, null, null];
-      }
-    } else if (pw1 == pw2) {
-      // pwFields.length == 2
-      // Treat as if 1 pw field
-      newPasswordField = pwFields[0].element;
-      oldPasswordField = null;
-    } else {
-      // Just assume that the 2nd password is the new password
-      oldPasswordField = pwFields[0].element;
-      newPasswordField = pwFields[1].element;
-    }
-
-    log("Password field (new) id/name is: ", newPasswordField.id, " / ", newPasswordField.name);
-    if (oldPasswordField) {
-      log("Password field (old) id/name is: ", oldPasswordField.id, " / ", oldPasswordField.name);
-    } else {
-      log("Password field (old):", oldPasswordField);
-    }
-    return [usernameField, newPasswordField, oldPasswordField];
-  },
-
-
-  /**
-   * @return true if the page requests autocomplete be disabled for the
-   *              specified element.
-   */
-  _isAutocompleteDisabled(element) {
-    return element && element.autocomplete == "off";
-  },
-
-  /**
-   * Trigger capture on any relevant FormLikes due to a navigation alone (not
-   * necessarily due to an actual form submission). This method is used to
-   * capture logins for cases where form submit events are not used.
-   *
-   * To avoid multiple notifications for the same FormLike, this currently
-   * avoids capturing when dealing with a real <form> which are ideally already
-   * using a submit event.
-   *
-   * @param {Document} document being navigated
-   */
-  _onNavigation(aDocument) {
-    let state = this.stateForDocument(aDocument);
-    let loginFormRootElements = state.loginFormRootElements;
-    log("_onNavigation: state:", state, "loginFormRootElements size:", loginFormRootElements.size,
-        "document:", aDocument);
-
-    for (let formRoot of state.loginFormRootElements) {
-      if (formRoot instanceof Ci.nsIDOMHTMLFormElement) {
-        // For now only perform capture upon navigation for FormLike's without
-        // a <form> to avoid capture from both an earlyformsubmit and
-        // navigation for the same "form".
-        log("Ignoring navigation for the form root to avoid multiple prompts " +
-            "since it was for a real <form>");
-        continue;
-      }
-      let formLike = this._formLikeByRootElement.get(formRoot);
-      this._onFormSubmit(formLike);
-    }
-  },
-
-  /**
-   * Called by our observer when notified of a form submission.
-   * [Note that this happens before any DOM onsubmit handlers are invoked.]
-   * Looks for a password change in the submitted form, so we can update
-   * our stored password.
-   *
-   * @param {FormLike} form
-   */
-  _onFormSubmit(form) {
-    log("_onFormSubmit", form);
-    var doc = form.ownerDocument;
-    var win = doc.defaultView;
-
-    if (PrivateBrowsingUtils.isContentWindowPrivate(win)) {
-      // We won't do anything in private browsing mode anyway,
-      // so there's no need to perform further checks.
-      log("(form submission ignored in private browsing mode)");
-      return;
-    }
-
-    // If password saving is disabled (globally or for host), bail out now.
-    if (!gEnabled)
-      return;
-
-    var hostname = LoginUtils._getPasswordOrigin(doc.documentURI);
-    if (!hostname) {
-      log("(form submission ignored -- invalid hostname)");
-      return;
-    }
-
-    let formSubmitURL = LoginUtils._getActionOrigin(form);
-    let messageManager = messageManagerFromWindow(win);
-
-    let recipes = messageManager.sendSyncMessage("RemoteLogins:findRecipes", {
-      formOrigin: hostname,
-    })[0];
-
-    // Get the appropriate fields from the form.
-    var [usernameField, newPasswordField, oldPasswordField] =
-          this._getFormFields(form, true, recipes);
-
-    // Need at least 1 valid password field to do anything.
-    if (newPasswordField == null)
-      return;
-
-    // Check for autocomplete=off attribute. We don't use it to prevent
-    // autofilling (for existing logins), but won't save logins when it's
-    // present and the storeWhenAutocompleteOff pref is false.
-    // XXX spin out a bug that we don't update timeLastUsed in this case?
-    if ((this._isAutocompleteDisabled(form) ||
-         this._isAutocompleteDisabled(usernameField) ||
-         this._isAutocompleteDisabled(newPasswordField) ||
-         this._isAutocompleteDisabled(oldPasswordField)) &&
-        !gStoreWhenAutocompleteOff) {
-      log("(form submission ignored -- autocomplete=off found)");
-      return;
-    }
-
-    // Don't try to send DOM nodes over IPC.
-    let mockUsername = usernameField ?
-                         { name: usernameField.name,
-                           value: usernameField.value } :
-                         null;
-    let mockPassword = { name: newPasswordField.name,
-                         value: newPasswordField.value };
-    let mockOldPassword = oldPasswordField ?
-                            { name: oldPasswordField.name,
-                              value: oldPasswordField.value } :
-                            null;
-
-    // Make sure to pass the opener's top in case it was in a frame.
-    let openerTopWindow = win.opener ? win.opener.top : null;
-
-    messageManager.sendAsyncMessage("RemoteLogins:onFormSubmit",
-                                    { hostname: hostname,
-                                      formSubmitURL: formSubmitURL,
-                                      usernameField: mockUsername,
-                                      newPasswordField: mockPassword,
-                                      oldPasswordField: mockOldPassword },
-                                    { openerTopWindow });
-  },
-
-  /**
-   * Attempt to find the username and password fields in a form, and fill them
-   * in using the provided logins and recipes.
-   *
-   * @param {HTMLFormElement} form
-   * @param {bool} autofillForm denotes if we should fill the form in automatically
-   * @param {bool} clobberUsername controls if an existing username can be overwritten.
-   *                               If this is false and an inputElement of type password
-   *                               is also passed, the username field will be ignored.
-   *                               If this is false and no inputElement is passed, if the username
-   *                               field value is not found in foundLogins, it will not fill the password.
-   * @param {bool} clobberPassword controls if an existing password value can be
-   *                               overwritten
-   * @param {bool} userTriggered is an indication of whether this filling was triggered by
-   *                             the user
-   * @param {nsILoginInfo[]} foundLogins is an array of nsILoginInfo that could be used for the form
-   * @param {Set} recipes that could be used to affect how the form is filled
-   * @param {Object} [options = {}] is a list of options for this method.
-            - [inputElement] is an optional target input element we want to fill
-   */
-  _fillForm(form, autofillForm, clobberUsername, clobberPassword,
-                        userTriggered, foundLogins, recipes, {inputElement} = {}) {
-    log("_fillForm", form.elements);
-    let ignoreAutocomplete = true;
-    const AUTOFILL_RESULT = {
-      FILLED: 0,
-      NO_PASSWORD_FIELD: 1,
-      PASSWORD_DISABLED_READONLY: 2,
-      NO_LOGINS_FIT: 3,
-      NO_SAVED_LOGINS: 4,
-      EXISTING_PASSWORD: 5,
-      EXISTING_USERNAME: 6,
-      MULTIPLE_LOGINS: 7,
-      NO_AUTOFILL_FORMS: 8,
-      AUTOCOMPLETE_OFF: 9,
-    };
-
-    function recordAutofillResult(result) {
-      if (userTriggered) {
-        // Ignore fills as a result of user action.
-        return;
-      }
-      const autofillResultHist = Services.telemetry.getHistogramById("PWMGR_FORM_AUTOFILL_RESULT");
-      autofillResultHist.add(result);
-    }
-
-    try {
-      // Nothing to do if we have no matching logins available.
-      if (foundLogins.length == 0) {
-        // We don't log() here since this is a very common case.
-        recordAutofillResult(AUTOFILL_RESULT.NO_SAVED_LOGINS);
-        return;
-      }
-
-      // Heuristically determine what the user/pass fields are
-      // We do this before checking to see if logins are stored,
-      // so that the user isn't prompted for a master password
-      // without need.
-      var [usernameField, passwordField, ignored] =
-            this._getFormFields(form, false, recipes);
-
-      // If we have a password inputElement parameter and it's not
-      // the same as the one heuristically found, use the parameter
-      // one instead.
-      if (inputElement) {
-        if (inputElement.type == "password") {
-          passwordField = inputElement;
-          if (!clobberUsername) {
-            usernameField = null;
-          }
-        } else if (LoginHelper.isUsernameFieldType(inputElement)) {
-          usernameField = inputElement;
-        } else {
-          throw new Error("Unexpected input element type.");
-        }
-      }
-
-      // Need a valid password field to do anything.
-      if (passwordField == null) {
-        log("not filling form, no password field found");
-        recordAutofillResult(AUTOFILL_RESULT.NO_PASSWORD_FIELD);
-        return;
-      }
-
-      // If the password field is disabled or read-only, there's nothing to do.
-      if (passwordField.disabled || passwordField.readOnly) {
-        log("not filling form, password field disabled or read-only");
-        recordAutofillResult(AUTOFILL_RESULT.PASSWORD_DISABLED_READONLY);
-        return;
-      }
-
-      var isAutocompleteOff = false;
-      if (this._isAutocompleteDisabled(form) ||
-          this._isAutocompleteDisabled(usernameField) ||
-          this._isAutocompleteDisabled(passwordField)) {
-        isAutocompleteOff = true;
-      }
-
-      // Discard logins which have username/password values that don't
-      // fit into the fields (as specified by the maxlength attribute).
-      // The user couldn't enter these values anyway, and it helps
-      // with sites that have an extra PIN to be entered (bug 391514)
-      var maxUsernameLen = Number.MAX_VALUE;
-      var maxPasswordLen = Number.MAX_VALUE;
-
-      // If attribute wasn't set, default is -1.
-      if (usernameField && usernameField.maxLength >= 0)
-        maxUsernameLen = usernameField.maxLength;
-      if (passwordField.maxLength >= 0)
-        maxPasswordLen = passwordField.maxLength;
-
-      var logins = foundLogins.filter(function (l) {
-        var fit = (l.username.length <= maxUsernameLen &&
-                   l.password.length <= maxPasswordLen);
-        if (!fit)
-          log("Ignored", l.username, "login: won't fit");
-
-        return fit;
-      }, this);
-
-      if (logins.length == 0) {
-        log("form not filled, none of the logins fit in the field");
-        recordAutofillResult(AUTOFILL_RESULT.NO_LOGINS_FIT);
-        return;
-      }
-
-      // Attach autocomplete stuff to the username field, if we have
-      // one. This is normally used to select from multiple accounts,
-      // but even with one account we should refill if the user edits.
-      if (usernameField)
-        this._formFillService.markAsLoginManagerField(usernameField);
-
-      // Don't clobber an existing password.
-      if (passwordField.value && !clobberPassword) {
-        log("form not filled, the password field was already filled");
-        recordAutofillResult(AUTOFILL_RESULT.EXISTING_PASSWORD);
-        return;
-      }
-
-      // Select a login to use for filling in the form.
-      var selectedLogin;
-      if (!clobberUsername && usernameField && (usernameField.value ||
-                                                usernameField.disabled ||
-                                                usernameField.readOnly)) {
-        // If username was specified in the field, it's disabled or it's readOnly, only fill in the
-        // password if we find a matching login.
-        var username = usernameField.value.toLowerCase();
-
-        let matchingLogins = logins.filter(l =>
-                                           l.username.toLowerCase() == username);
-        if (matchingLogins.length == 0) {
-          log("Password not filled. None of the stored logins match the username already present.");
-          recordAutofillResult(AUTOFILL_RESULT.EXISTING_USERNAME);
-          return;
-        }
-
-        // If there are multiple, and one matches case, use it
-        for (let l of matchingLogins) {
-          if (l.username == usernameField.value) {
-            selectedLogin = l;
-          }
-        }
-        // Otherwise just use the first
-        if (!selectedLogin) {
-          selectedLogin = matchingLogins[0];
-        }
-      } else if (logins.length == 1) {
-        selectedLogin = logins[0];
-      } else {
-        // We have multiple logins. Handle a special case here, for sites
-        // which have a normal user+pass login *and* a password-only login
-        // (eg, a PIN). Prefer the login that matches the type of the form
-        // (user+pass or pass-only) when there's exactly one that matches.
-        let matchingLogins;
-        if (usernameField)
-          matchingLogins = logins.filter(l => l.username);
-        else
-          matchingLogins = logins.filter(l => !l.username);
-
-        if (matchingLogins.length != 1) {
-          log("Multiple logins for form, so not filling any.");
-          recordAutofillResult(AUTOFILL_RESULT.MULTIPLE_LOGINS);
-          return;
-        }
-
-        selectedLogin = matchingLogins[0];
-      }
-
-      // We will always have a selectedLogin at this point.
-
-      if (!autofillForm) {
-        log("autofillForms=false but form can be filled");
-        recordAutofillResult(AUTOFILL_RESULT.NO_AUTOFILL_FORMS);
-        return;
-      }
-
-      if (isAutocompleteOff && !ignoreAutocomplete) {
-        log("Not filling the login because we're respecting autocomplete=off");
-        recordAutofillResult(AUTOFILL_RESULT.AUTOCOMPLETE_OFF);
-        return;
-      }
-
-      // Fill the form
-
-      if (usernameField) {
-      // Don't modify the username field if it's disabled or readOnly so we preserve its case.
-        let disabledOrReadOnly = usernameField.disabled || usernameField.readOnly;
-
-        let userNameDiffers = selectedLogin.username != usernameField.value;
-        // Don't replace the username if it differs only in case, and the user triggered
-        // this autocomplete. We assume that if it was user-triggered the entered text
-        // is desired.
-        let userEnteredDifferentCase = userTriggered && userNameDiffers &&
-               usernameField.value.toLowerCase() == selectedLogin.username.toLowerCase();
-
-        if (!disabledOrReadOnly && !userEnteredDifferentCase && userNameDiffers) {
-          usernameField.setUserInput(selectedLogin.username);
-        }
-      }
-      if (passwordField.value != selectedLogin.password) {
-        passwordField.setUserInput(selectedLogin.password);
-      }
-
-      log("_fillForm succeeded");
-      recordAutofillResult(AUTOFILL_RESULT.FILLED);
-      let doc = form.ownerDocument;
-      let win = doc.defaultView;
-      let messageManager = messageManagerFromWindow(win);
-      messageManager.sendAsyncMessage("LoginStats:LoginFillSuccessful");
-    } finally {
-      Services.obs.notifyObservers(form.rootElement, "passwordmgr-processed-form", null);
-    }
-  },
-
-  /**
-   * Verify if a field is a valid login form field and
-   * returns some information about it's FormLike.
-   *
-   * @param {Element} aField
-   *                  A form field we want to verify.
-   *
-   * @returns {Object} an object with information about the
-   *                   FormLike username and password field
-   *                   or null if the passed field is invalid.
-   */
-  getFieldContext(aField) {
-    // If the element is not a proper form field, return null.
-    if (!(aField instanceof Ci.nsIDOMHTMLInputElement) ||
-        (aField.type != "password" && !LoginHelper.isUsernameFieldType(aField)) ||
-        !aField.ownerDocument) {
-      return null;
-    }
-    let form = FormLikeFactory.createFromField(aField);
-
-    let doc = aField.ownerDocument;
-    let messageManager = messageManagerFromWindow(doc.defaultView);
-    let recipes = messageManager.sendSyncMessage("RemoteLogins:findRecipes", {
-      formOrigin: LoginUtils._getPasswordOrigin(doc.documentURI),
-    })[0];
-
-    let [usernameField, newPasswordField] =
-          this._getFormFields(form, false, recipes);
-
-    // If we are not verifying a password field, we want
-    // to use aField as the username field.
-    if (aField.type != "password") {
-      usernameField = aField;
-    }
-
-    return {
-      usernameField: {
-        found: !!usernameField,
-        disabled: usernameField && (usernameField.disabled || usernameField.readOnly),
-      },
-      passwordField: {
-        found: !!newPasswordField,
-        disabled: newPasswordField && (newPasswordField.disabled || newPasswordField.readOnly),
-      },
-    };
-  },
-};
-
-var LoginUtils = {
-  /**
-   * Get the parts of the URL we want for identification.
-   * Strip out things like the userPass portion
-   */
-  _getPasswordOrigin(uriString, allowJS) {
-    var realm = "";
-    try {
-      var uri = Services.io.newURI(uriString, null, null);
-
-      if (allowJS && uri.scheme == "javascript")
-        return "javascript:";
-
-      // Build this manually instead of using prePath to avoid including the userPass portion.
-      realm = uri.scheme + "://" + uri.hostPort;
-    } catch (e) {
-      // bug 159484 - disallow url types that don't support a hostPort.
-      // (although we handle "javascript:..." as a special case above.)
-      log("Couldn't parse origin for", uriString, e);
-      realm = null;
-    }
-
-    return realm;
-  },
-
-  _getActionOrigin(form) {
-    var uriString = form.action;
-
-    // A blank or missing action submits to where it came from.
-    if (uriString == "")
-      uriString = form.baseURI; // ala bug 297761
-
-    return this._getPasswordOrigin(uriString, true);
-  },
-};
-
-// nsIAutoCompleteResult implementation
-function UserAutoCompleteResult (aSearchString, matchingLogins, messageManager) {
-  function loginSort(a, b) {
-    var userA = a.username.toLowerCase();
-    var userB = b.username.toLowerCase();
-
-    if (userA < userB)
-      return -1;
-
-    if (userA > userB)
-      return  1;
-
-    return 0;
-  }
-
-  this.searchString = aSearchString;
-  this.logins = matchingLogins.sort(loginSort);
-  this.matchCount = matchingLogins.length;
-  this._messageManager = messageManager;
-
-  if (this.matchCount > 0) {
-    this.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
-    this.defaultIndex = 0;
-  }
-}
-
-UserAutoCompleteResult.prototype = {
-  QueryInterface : XPCOMUtils.generateQI([Ci.nsIAutoCompleteResult,
-                                          Ci.nsISupportsWeakReference]),
-
-  // private
-  logins : null,
-
-  // Allow autoCompleteSearch to get at the JS object so it can
-  // modify some readonly properties for internal use.
-  get wrappedJSObject() {
-    return this;
-  },
-
-  // Interfaces from idl...
-  searchString : null,
-  searchResult : Ci.nsIAutoCompleteResult.RESULT_NOMATCH,
-  defaultIndex : -1,
-  errorDescription : "",
-  matchCount : 0,
-
-  getValueAt(index) {
-    if (index < 0 || index >= this.logins.length)
-      throw new Error("Index out of range.");
-
-    return this.logins[index].username;
-  },
-
-  getLabelAt(index) {
-    return this.getValueAt(index);
-  },
-
-  getCommentAt(index) {
-    return "";
-  },
-
-  getStyleAt(index) {
-    return "";
-  },
-
-  getImageAt(index) {
-    return "";
-  },
-
-  getFinalCompleteValueAt(index) {
-    return this.getValueAt(index);
-  },
-
-  removeValueAt(index, removeFromDB) {
-    if (index < 0 || index >= this.logins.length)
-        throw new Error("Index out of range.");
-
-    var [removedLogin] = this.logins.splice(index, 1);
-
-    this.matchCount--;
-    if (this.defaultIndex > this.logins.length)
-      this.defaultIndex--;
-
-    if (removeFromDB) {
-      if (this._messageManager) {
-        let vanilla = LoginHelper.loginToVanillaObject(removedLogin);
-        this._messageManager.sendAsyncMessage("RemoteLogins:removeLogin",
-                                              { login: vanilla });
-      } else {
-        Services.logins.removeLogin(removedLogin);
-      }
-    }
-  }
-};
 
 /**
- * A factory to generate FormLike objects that represent a set of login fields
- * which aren't necessarily marked up with a <form> element.
+ * A factory to generate FormLike objects that represent a set of related fields
+ * which aren't necessarily marked up with a <form> element. FormLike's emulate
+ * the properties of an HTMLFormElement which are relevant to form tasks.
  */
-var FormLikeFactory = {
+let FormLikeFactory = {
   _propsFromForm: [
+    "action",
     "autocomplete",
     "ownerDocument",
   ],
 
   /**
    * Create a FormLike object from a <form>.
    *
    * @param {HTMLFormElement} aForm
@@ -1313,87 +28,72 @@ var FormLikeFactory = {
    * @throws Error if aForm isn't an HTMLFormElement
    */
   createFromForm(aForm) {
     if (!(aForm instanceof Ci.nsIDOMHTMLFormElement)) {
       throw new Error("createFromForm: aForm must be a nsIDOMHTMLFormElement");
     }
 
     let formLike = {
-      action: LoginUtils._getActionOrigin(aForm),
       elements: [...aForm.elements],
       rootElement: aForm,
     };
 
     for (let prop of this._propsFromForm) {
       formLike[prop] = aForm[prop];
     }
 
     this._addToJSONProperty(formLike);
 
-    let state = LoginManagerContent.stateForDocument(formLike.ownerDocument);
-    state.loginFormRootElements.add(formLike.rootElement);
-    log("adding", formLike.rootElement, "to loginFormRootElements for", formLike.ownerDocument);
-
-    LoginManagerContent._formLikeByRootElement.set(formLike.rootElement, formLike);
     return formLike;
   },
 
   /**
-   * Create a FormLike object from a password or username field.
+   * Create a FormLike object from an <input> in a document.
    *
    * If the field is in a <form>, construct the FormLike from the form.
    * Otherwise, create a FormLike with a rootElement (wrapper) according to
    * heuristics. Currently all <input> not in a <form> are one FormLike but this
    * shouldn't be relied upon as the heuristics may change to detect multiple
    * "forms" (e.g. registration and login) on one page with a <form>.
    *
    * Note that two FormLikes created from the same field won't return the same FormLike object.
    * Use the `rootElement` property on the FormLike as a key instead.
    *
-   * @param {HTMLInputElement} aField - a password or username field in a document
+   * @param {HTMLInputElement} aField - a field in a document
    * @return {FormLike}
    * @throws Error if aField isn't a password or username field in a document
    */
   createFromField(aField) {
     if (!(aField instanceof Ci.nsIDOMHTMLInputElement) ||
-        (aField.type != "password" && !LoginHelper.isUsernameFieldType(aField)) ||
         !aField.ownerDocument) {
-      throw new Error("createFromField requires a password or username field in a document");
+      throw new Error("createFromField requires a field in a document");
     }
 
     if (aField.form) {
       return this.createFromForm(aField.form);
     }
 
     let doc = aField.ownerDocument;
-    log("Created non-form FormLike for rootElement:", doc.documentElement);
     let elements = [];
     for (let el of doc.documentElement.querySelectorAll("input")) {
       if (!el.form) {
         elements.push(el);
       }
     }
     let formLike = {
-      action: LoginUtils._getPasswordOrigin(doc.baseURI),
+      action: doc.baseURI,
       autocomplete: "on",
       // Exclude elements inside the rootElement that are already in a <form> as
       // they will be handled by their own FormLike.
       elements,
       ownerDocument: doc,
       rootElement: doc.documentElement,
     };
 
-    let state = LoginManagerContent.stateForDocument(formLike.ownerDocument);
-    state.loginFormRootElements.add(formLike.rootElement);
-    log("adding", formLike.rootElement, "to loginFormRootElements for", formLike.ownerDocument);
-
-
-    LoginManagerContent._formLikeByRootElement.set(formLike.rootElement, formLike);
-
     this._addToJSONProperty(formLike);
     return formLike;
   },
 
   /**
    * Add a `toJSON` property to a FormLike so logging which ends up going
    * through dump doesn't include usless garbage from DOM objects.
    */
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -38,16 +38,17 @@ EXTRA_JS_MODULES += [
     'DateTimePickerHelper.jsm',
     'debug.js',
     'DeferredTask.jsm',
     'Deprecated.jsm',
     'FileUtils.jsm',
     'Finder.jsm',
     'FinderHighlighter.jsm',
     'FinderIterator.jsm',
+    'FormLikeFactory.jsm',
     'Geometry.jsm',
     'GMPInstallManager.jsm',
     'GMPUtils.jsm',
     'Http.jsm',
     'InlineSpellChecker.jsm',
     'InlineSpellCheckerContent.jsm',
     'Integration.jsm',
     'JSONFile.jsm',