Bug 1333682 - Split FormAutofillContent.js into a few JSMs shared for the process. r=seanlee draft
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Mon, 13 Feb 2017 22:21:51 +0800
changeset 482823 65ff342d67b4395b717d2124f9c73e93bec190e8
parent 482461 c8f8a1b5dfeb8076165fb936312aa02a7bbafae2
child 545515 5f99300d8f91d8d44dda1edb85e09cec6e2885cc
push id45173
push usermozilla@noorenberghe.ca
push dateMon, 13 Feb 2017 14:25:58 +0000
reviewersseanlee
bugs1333682
milestone54.0a1
Bug 1333682 - Split FormAutofillContent.js into a few JSMs shared for the process. r=seanlee MozReview-Commit-ID: IafBzXSDXCg
browser/extensions/formautofill/FormAutofillContent.jsm
browser/extensions/formautofill/FormAutofillHandler.jsm
browser/extensions/formautofill/FormAutofillHeuristics.jsm
browser/extensions/formautofill/FormAutofillParent.jsm
browser/extensions/formautofill/bootstrap.js
browser/extensions/formautofill/content/FormAutofillContent.js
browser/extensions/formautofill/content/FormAutofillFrameScript.js
browser/extensions/formautofill/test/unit/head.js
browser/extensions/formautofill/test/unit/test_autofillFormFields.js
browser/extensions/formautofill/test/unit/test_collectFormFields.js
browser/extensions/formautofill/test/unit/test_getFormInputDetails.js
browser/extensions/formautofill/test/unit/test_markAsAutofillField.js
tools/lint/eslint/modules.json
rename from browser/extensions/formautofill/content/FormAutofillContent.js
rename to browser/extensions/formautofill/FormAutofillContent.jsm
--- a/browser/extensions/formautofill/content/FormAutofillContent.js
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -1,181 +1,39 @@
 /* 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/. */
 
-/* eslint-disable no-use-before-define */
+/*
+ * Form Autofill content process module.
+ */
 
-/*
- * Form Autofill frame script.
- */
+/* eslint-disable no-use-before-define */
 
 "use strict";
 
+this.EXPORTED_SYMBOLS = ["FormAutofillContent"];
+
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr, manager: Cm} = Components;
 
+Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ProfileAutoCompleteResult",
                                   "resource://formautofill/ProfileAutoCompleteResult.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillHandler",
+                                  "resource://formautofill/FormAutofillHandler.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FormLikeFactory",
                                   "resource://gre/modules/FormLikeFactory.jsm");
 
 const formFillController = Cc["@mozilla.org/satchel/form-fill-controller;1"]
                              .getService(Ci.nsIFormFillController);
 
 const AUTOFILL_FIELDS_THRESHOLD = 3;
 
-/**
- * Returns the autocomplete information of fields according to heuristics.
- */
-let FormAutofillHeuristics = {
-  VALID_FIELDS: [
-    "organization",
-    "street-address",
-    "address-level2",
-    "address-level1",
-    "postal-code",
-    "country",
-    "tel",
-    "email",
-  ],
-
-  getInfo(element) {
-    if (!(element instanceof Ci.nsIDOMHTMLInputElement)) {
-      return null;
-    }
-
-    let info = element.getAutocompleteInfo();
-    if (!info || !info.fieldName ||
-        !this.VALID_FIELDS.includes(info.fieldName)) {
-      return null;
-    }
-
-    return info;
-  },
-};
-
-/**
- * Handles profile autofill for a DOM Form element.
- * @param {HTMLFormElement} form Form that need to be auto filled
- */
-function FormAutofillHandler(form) {
-  this.form = form;
-  this.fieldDetails = [];
-}
-
-FormAutofillHandler.prototype = {
-  /**
-   * DOM Form element to which this object is attached.
-   */
-  form: null,
-
-  /**
-   * Array of collected data about relevant form fields.  Each item is an object
-   * storing the identifying details of the field and a reference to the
-   * originally associated element from the form.
-   *
-   * The "section", "addressType", "contactType", and "fieldName" values are
-   * used to identify the exact field when the serializable data is received
-   * from the backend.  There cannot be multiple fields which have
-   * the same exact combination of these values.
-   *
-   * A direct reference to the associated element cannot be sent to the user
-   * interface because processing may be done in the parent process.
-   */
-  fieldDetails: null,
-
-  /**
-   * Returns information from the form about fields that can be autofilled, and
-   * populates the fieldDetails array on this object accordingly.
-   *
-   * @returns {Array<Object>} Serializable data structure that can be sent to the user
-   *          interface, or null if the operation failed because the constraints
-   *          on the allowed fields were not honored.
-   */
-  collectFormFields() {
-    let autofillData = [];
-
-    for (let element of this.form.elements) {
-      // Exclude elements to which no autocomplete field has been assigned.
-      let info = FormAutofillHeuristics.getInfo(element);
-      if (!info) {
-        continue;
-      }
-
-      // Store the association between the field metadata and the element.
-      if (this.fieldDetails.some(f => f.section == info.section &&
-                                      f.addressType == info.addressType &&
-                                      f.contactType == info.contactType &&
-                                      f.fieldName == info.fieldName)) {
-        // A field with the same identifier already exists.
-        return null;
-      }
-
-      let inputFormat = {
-        section: info.section,
-        addressType: info.addressType,
-        contactType: info.contactType,
-        fieldName: info.fieldName,
-      };
-      // Clone the inputFormat for caching the fields and elements together
-      let formatWithElement = Object.assign({}, inputFormat);
-
-      inputFormat.index = autofillData.length;
-      autofillData.push(inputFormat);
-
-      formatWithElement.element = element;
-      this.fieldDetails.push(formatWithElement);
-    }
-
-    return autofillData;
-  },
-
-  /**
-   * Processes form fields that can be autofilled, and populates them with the
-   * data provided by backend.
-   *
-   * @param {Array<Object>} autofillResult
-   *        Data returned by the user interface.
-   *        [{
-   *          section: Value originally provided to the user interface.
-   *          addressType: Value originally provided to the user interface.
-   *          contactType: Value originally provided to the user interface.
-   *          fieldName: Value originally provided to the user interface.
-   *          value: String with which the field should be updated.
-   *          index: Index to match the input in fieldDetails
-   *        }],
-   *        }
-   */
-  autofillFormFields(autofillResult) {
-    for (let field of autofillResult) {
-      // Get the field details, if it was processed by the user interface.
-      let fieldDetail = this.fieldDetails[field.index];
-
-      // Avoid the invalid value set
-      if (!fieldDetail || !field.value) {
-        continue;
-      }
-
-      let info = FormAutofillHeuristics.getInfo(fieldDetail.element);
-      if (!info ||
-          field.section != info.section ||
-          field.addressType != info.addressType ||
-          field.contactType != info.contactType ||
-          field.fieldName != info.fieldName) {
-        Cu.reportError("Autocomplete tokens mismatched");
-        continue;
-      }
-
-      fieldDetail.element.setUserInput(field.value);
-    }
-  },
-};
-
 // Register/unregister a constructor as a factory.
 function AutocompleteFactory() {}
 AutocompleteFactory.prototype = {
   register(targetConstructor) {
     let proto = targetConstructor.prototype;
     this._classID = proto.classID;
 
     let factory = XPCOMUtils._getFactory(targetConstructor);
@@ -262,22 +120,22 @@ AutofillProfileAutoCompleteSearch.protot
    *         The typed string for filtering out the matched profile.
    * @param  {string} data.info
    *         The input autocomplete property's information.
    * @returns {Promise}
    *          Promise that resolves when profiles returned from parent process.
    */
   getProfiles(data) {
     return new Promise((resolve) => {
-      addMessageListener("FormAutofill:Profiles", function getResult(result) {
-        removeMessageListener("FormAutofill:Profiles", getResult);
+      Services.cpmm.addMessageListener("FormAutofill:Profiles", function getResult(result) {
+        Services.cpmm.removeMessageListener("FormAutofill:Profiles", getResult);
         resolve(result.data);
       });
 
-      sendAsyncMessage("FormAutofill:GetProfiles", data);
+      Services.cpmm.sendAsyncMessage("FormAutofill:GetProfiles", data);
     });
   },
 
 
   /**
    * Get the input's information from FormAutofillContent's cache.
    *
    * @returns {Object}
@@ -323,48 +181,32 @@ let ProfileAutocomplete = {
 
     this._factory.unregister();
     this._factory = null;
     this._registered = false;
   },
 };
 
 /**
- * Handles content's interactions.
+ * Handles content's interactions for the process.
  *
  * NOTE: Declares it by "var" to make it accessible in unit tests.
  */
 var FormAutofillContent = {
   init() {
-    addEventListener("DOMContentLoaded", this);
-
-    addMessageListener("FormAutofill:enabledStatus", (result) => {
+    Services.cpmm.addMessageListener("FormAutofill:enabledStatus", (result) => {
       if (result.data) {
         ProfileAutocomplete.ensureRegistered();
       } else {
         ProfileAutocomplete.ensureUnregistered();
       }
     });
-    sendAsyncMessage("FormAutofill:getEnabledStatus");
-  },
-
-  handleEvent(evt) {
-    if (!evt.isTrusted) {
-      return;
-    }
-
-    switch (evt.type) {
-      case "DOMContentLoaded":
-        let doc = evt.target;
-        if (!(doc instanceof Ci.nsIDOMHTMLDocument)) {
-          return;
-        }
-        this._identifyAutofillFields(doc);
-        break;
-    }
+    Services.cpmm.sendAsyncMessage("FormAutofill:getEnabledStatus");
+    // TODO: use initialProcessData:
+    // Services.cpmm.initialProcessData.autofillEnabled
   },
 
   /**
    * Get the input's information from cache which is created after page identified.
    *
    * @param {HTMLInputElement} element Focused input which triggered profile searching
    * @returns {Object|null}
    *          Return target input's information that cloned from content cache
copy from browser/extensions/formautofill/content/FormAutofillContent.js
copy to browser/extensions/formautofill/FormAutofillHandler.jsm
--- a/browser/extensions/formautofill/content/FormAutofillContent.js
+++ b/browser/extensions/formautofill/FormAutofillHandler.jsm
@@ -1,67 +1,30 @@
 /* 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/. */
 
-/* eslint-disable no-use-before-define */
-
 /*
- * Form Autofill frame script.
+ * Defines a handler object to represent forms that autofill can handle.
  */
 
 "use strict";
 
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr, manager: Cm} = Components;
+this.EXPORTED_SYMBOLS = ["FormAutofillHandler"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "ProfileAutoCompleteResult",
-                                  "resource://formautofill/ProfileAutoCompleteResult.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "FormLikeFactory",
-                                  "resource://gre/modules/FormLikeFactory.jsm");
-
-const formFillController = Cc["@mozilla.org/satchel/form-fill-controller;1"]
-                             .getService(Ci.nsIFormFillController);
-
-const AUTOFILL_FIELDS_THRESHOLD = 3;
-
-/**
- * Returns the autocomplete information of fields according to heuristics.
- */
-let FormAutofillHeuristics = {
-  VALID_FIELDS: [
-    "organization",
-    "street-address",
-    "address-level2",
-    "address-level1",
-    "postal-code",
-    "country",
-    "tel",
-    "email",
-  ],
-
-  getInfo(element) {
-    if (!(element instanceof Ci.nsIDOMHTMLInputElement)) {
-      return null;
-    }
-
-    let info = element.getAutocompleteInfo();
-    if (!info || !info.fieldName ||
-        !this.VALID_FIELDS.includes(info.fieldName)) {
-      return null;
-    }
-
-    return info;
-  },
-};
+XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillHeuristics",
+                                  "resource://formautofill/FormAutofillHeuristics.jsm");
 
 /**
  * Handles profile autofill for a DOM Form element.
- * @param {HTMLFormElement} form Form that need to be auto filled
+ * @param {FormLike} form Form that need to be auto filled
  */
 function FormAutofillHandler(form) {
   this.form = form;
   this.fieldDetails = [];
 }
 
 FormAutofillHandler.prototype = {
   /**
@@ -165,291 +128,8 @@ FormAutofillHandler.prototype = {
         Cu.reportError("Autocomplete tokens mismatched");
         continue;
       }
 
       fieldDetail.element.setUserInput(field.value);
     }
   },
 };
-
-// Register/unregister a constructor as a factory.
-function AutocompleteFactory() {}
-AutocompleteFactory.prototype = {
-  register(targetConstructor) {
-    let proto = targetConstructor.prototype;
-    this._classID = proto.classID;
-
-    let factory = XPCOMUtils._getFactory(targetConstructor);
-    this._factory = factory;
-
-    let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
-    registrar.registerFactory(proto.classID, proto.classDescription,
-                              proto.contractID, factory);
-
-    if (proto.classID2) {
-      this._classID2 = proto.classID2;
-      registrar.registerFactory(proto.classID2, proto.classDescription,
-                                proto.contractID2, factory);
-    }
-  },
-
-  unregister() {
-    let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
-    registrar.unregisterFactory(this._classID, this._factory);
-    if (this._classID2) {
-      registrar.unregisterFactory(this._classID2, this._factory);
-    }
-    this._factory = null;
-  },
-};
-
-
-/**
- * @constructor
- *
- * @implements {nsIAutoCompleteSearch}
- */
-function AutofillProfileAutoCompleteSearch() {
-
-}
-AutofillProfileAutoCompleteSearch.prototype = {
-  classID: Components.ID("4f9f1e4c-7f2c-439e-9c9e-566b68bc187d"),
-  contractID: "@mozilla.org/autocomplete/search;1?name=autofill-profiles",
-  classDescription: "AutofillProfileAutoCompleteSearch",
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteSearch]),
-
-  // Begin nsIAutoCompleteSearch implementation
-
-  /**
-   * Searches for a given string and notifies a listener (either synchronously
-   * or asynchronously) of the result
-   *
-   * @param {string} searchString the string to search for
-   * @param {string} searchParam
-   * @param {Object} previousResult a previous result to use for faster searchinig
-   * @param {Object} listener the listener to notify when the search is complete
-   */
-  startSearch(searchString, searchParam, previousResult, listener) {
-    this.forceStop = false;
-    let info = this.getInputDetails();
-
-    this.getProfiles({info, searchString}).then((profiles) => {
-      if (this.forceStop) {
-        return;
-      }
-
-      // TODO: Set formInfo for ProfileAutoCompleteResult
-      // let formInfo = this.getFormDetails();
-      let result = new ProfileAutoCompleteResult(searchString, info, profiles, {});
-
-      listener.onSearchResult(this, result);
-    });
-  },
-
-  /**
-   * Stops an asynchronous search that is in progress
-   */
-  stopSearch() {
-    this.forceStop = true;
-  },
-
-  /**
-   * Get the profile data from parent process for AutoComplete result.
-   *
-   * @private
-   * @param  {Object} data
-   *         Parameters for querying the corresponding result.
-   * @param  {string} data.searchString
-   *         The typed string for filtering out the matched profile.
-   * @param  {string} data.info
-   *         The input autocomplete property's information.
-   * @returns {Promise}
-   *          Promise that resolves when profiles returned from parent process.
-   */
-  getProfiles(data) {
-    return new Promise((resolve) => {
-      addMessageListener("FormAutofill:Profiles", function getResult(result) {
-        removeMessageListener("FormAutofill:Profiles", getResult);
-        resolve(result.data);
-      });
-
-      sendAsyncMessage("FormAutofill:GetProfiles", data);
-    });
-  },
-
-
-  /**
-   * Get the input's information from FormAutofillContent's cache.
-   *
-   * @returns {Object}
-   *          Target input's information that cached in FormAutofillContent.
-   */
-  getInputDetails() {
-    // TODO: Maybe we'll need to wait for cache ready if detail is empty.
-    return FormAutofillContent.getInputDetails(formFillController.focusedInput);
-  },
-
-  /**
-   * Get the form's information from FormAutofillContent's cache.
-   *
-   * @returns {Array<Object>}
-   *          Array of the inputs' information for the target form.
-   */
-  getFormDetails() {
-    // TODO: Maybe we'll need to wait for cache ready if details is empty.
-    return FormAutofillContent.getFormDetails(formFillController.focusedInput);
-  },
-};
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AutofillProfileAutoCompleteSearch]);
-
-let ProfileAutocomplete = {
-  _registered: false,
-  _factory: null,
-
-  ensureRegistered() {
-    if (this._registered) {
-      return;
-    }
-
-    this._factory = new AutocompleteFactory();
-    this._factory.register(AutofillProfileAutoCompleteSearch);
-    this._registered = true;
-  },
-
-  ensureUnregistered() {
-    if (!this._registered) {
-      return;
-    }
-
-    this._factory.unregister();
-    this._factory = null;
-    this._registered = false;
-  },
-};
-
-/**
- * Handles content's interactions.
- *
- * NOTE: Declares it by "var" to make it accessible in unit tests.
- */
-var FormAutofillContent = {
-  init() {
-    addEventListener("DOMContentLoaded", this);
-
-    addMessageListener("FormAutofill:enabledStatus", (result) => {
-      if (result.data) {
-        ProfileAutocomplete.ensureRegistered();
-      } else {
-        ProfileAutocomplete.ensureUnregistered();
-      }
-    });
-    sendAsyncMessage("FormAutofill:getEnabledStatus");
-  },
-
-  handleEvent(evt) {
-    if (!evt.isTrusted) {
-      return;
-    }
-
-    switch (evt.type) {
-      case "DOMContentLoaded":
-        let doc = evt.target;
-        if (!(doc instanceof Ci.nsIDOMHTMLDocument)) {
-          return;
-        }
-        this._identifyAutofillFields(doc);
-        break;
-    }
-  },
-
-  /**
-   * Get the input's information from cache which is created after page identified.
-   *
-   * @param {HTMLInputElement} element Focused input which triggered profile searching
-   * @returns {Object|null}
-   *          Return target input's information that cloned from content cache
-   *          (or return null if the information is not found in the cache).
-   */
-  getInputDetails(element) {
-    for (let formDetails of this._formsDetails) {
-      for (let detail of formDetails) {
-        if (element == detail.element) {
-          return this._serializeInfo(detail);
-        }
-      }
-    }
-    return null;
-  },
-
-  /**
-   * Get the form's information from cache which is created after page identified.
-   *
-   * @param {HTMLInputElement} element Focused input which triggered profile searching
-   * @returns {Array<Object>|null}
-   *          Return target form's information that cloned from content cache
-   *          (or return null if the information is not found in the cache).
-   *
-   */
-  getFormDetails(element) {
-    for (let formDetails of this._formsDetails) {
-      if (formDetails.some((detail) => detail.element == element)) {
-        return formDetails.map((detail) => this._serializeInfo(detail));
-      }
-    }
-    return null;
-  },
-
-  /**
-   * Create a clone the information object without element reference.
-   *
-   * @param {Object} detail Profile autofill information for specific input.
-   * @returns {Object}
-   *          Return a copy of cached information object without element reference
-   *          since it's not needed for creating result.
-   */
-  _serializeInfo(detail) {
-    let info = Object.assign({}, detail);
-    delete info.element;
-    return info;
-  },
-
-  _identifyAutofillFields(doc) {
-    let forms = [];
-    this._formsDetails = [];
-
-    // Collects root forms from inputs.
-    for (let field of doc.getElementsByTagName("input")) {
-      // We only consider text-like fields for now until we support radio and
-      // checkbox buttons in the future.
-      if (!field.mozIsTextField(true)) {
-        continue;
-      }
-
-      let formLike = FormLikeFactory.createFromField(field);
-      if (!forms.some(form => form.rootElement === formLike.rootElement)) {
-        forms.push(formLike);
-      }
-    }
-
-    // Collects the fields that can be autofilled from each form and marks them
-    // as autofill fields if the amount is above the threshold.
-    forms.forEach(form => {
-      let formHandler = new FormAutofillHandler(form);
-      formHandler.collectFormFields();
-      if (formHandler.fieldDetails.length < AUTOFILL_FIELDS_THRESHOLD) {
-        return;
-      }
-
-      this._formsDetails.push(formHandler.fieldDetails);
-      formHandler.fieldDetails.forEach(
-        detail => this._markAsAutofillField(detail.element));
-    });
-  },
-
-  _markAsAutofillField(field) {
-    formFillController.markAsAutofillField(field);
-  },
-};
-
-
-FormAutofillContent.init();
copy from browser/extensions/formautofill/content/FormAutofillContent.js
copy to browser/extensions/formautofill/FormAutofillHeuristics.jsm
--- a/browser/extensions/formautofill/content/FormAutofillContent.js
+++ b/browser/extensions/formautofill/FormAutofillHeuristics.jsm
@@ -1,38 +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/. */
 
-/* eslint-disable no-use-before-define */
-
 /*
- * Form Autofill frame script.
+ * Form Autofill field heuristics.
  */
 
 "use strict";
 
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr, manager: Cm} = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+this.EXPORTED_SYMBOLS = ["FormAutofillHeuristics"];
 
-XPCOMUtils.defineLazyModuleGetter(this, "ProfileAutoCompleteResult",
-                                  "resource://formautofill/ProfileAutoCompleteResult.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "FormLikeFactory",
-                                  "resource://gre/modules/FormLikeFactory.jsm");
-
-const formFillController = Cc["@mozilla.org/satchel/form-fill-controller;1"]
-                             .getService(Ci.nsIFormFillController);
-
-const AUTOFILL_FIELDS_THRESHOLD = 3;
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 /**
  * Returns the autocomplete information of fields according to heuristics.
  */
-let FormAutofillHeuristics = {
+this.FormAutofillHeuristics = {
   VALID_FIELDS: [
     "organization",
     "street-address",
     "address-level2",
     "address-level1",
     "postal-code",
     "country",
     "tel",
@@ -48,408 +36,8 @@ let FormAutofillHeuristics = {
     if (!info || !info.fieldName ||
         !this.VALID_FIELDS.includes(info.fieldName)) {
       return null;
     }
 
     return info;
   },
 };
-
-/**
- * Handles profile autofill for a DOM Form element.
- * @param {HTMLFormElement} form Form that need to be auto filled
- */
-function FormAutofillHandler(form) {
-  this.form = form;
-  this.fieldDetails = [];
-}
-
-FormAutofillHandler.prototype = {
-  /**
-   * DOM Form element to which this object is attached.
-   */
-  form: null,
-
-  /**
-   * Array of collected data about relevant form fields.  Each item is an object
-   * storing the identifying details of the field and a reference to the
-   * originally associated element from the form.
-   *
-   * The "section", "addressType", "contactType", and "fieldName" values are
-   * used to identify the exact field when the serializable data is received
-   * from the backend.  There cannot be multiple fields which have
-   * the same exact combination of these values.
-   *
-   * A direct reference to the associated element cannot be sent to the user
-   * interface because processing may be done in the parent process.
-   */
-  fieldDetails: null,
-
-  /**
-   * Returns information from the form about fields that can be autofilled, and
-   * populates the fieldDetails array on this object accordingly.
-   *
-   * @returns {Array<Object>} Serializable data structure that can be sent to the user
-   *          interface, or null if the operation failed because the constraints
-   *          on the allowed fields were not honored.
-   */
-  collectFormFields() {
-    let autofillData = [];
-
-    for (let element of this.form.elements) {
-      // Exclude elements to which no autocomplete field has been assigned.
-      let info = FormAutofillHeuristics.getInfo(element);
-      if (!info) {
-        continue;
-      }
-
-      // Store the association between the field metadata and the element.
-      if (this.fieldDetails.some(f => f.section == info.section &&
-                                      f.addressType == info.addressType &&
-                                      f.contactType == info.contactType &&
-                                      f.fieldName == info.fieldName)) {
-        // A field with the same identifier already exists.
-        return null;
-      }
-
-      let inputFormat = {
-        section: info.section,
-        addressType: info.addressType,
-        contactType: info.contactType,
-        fieldName: info.fieldName,
-      };
-      // Clone the inputFormat for caching the fields and elements together
-      let formatWithElement = Object.assign({}, inputFormat);
-
-      inputFormat.index = autofillData.length;
-      autofillData.push(inputFormat);
-
-      formatWithElement.element = element;
-      this.fieldDetails.push(formatWithElement);
-    }
-
-    return autofillData;
-  },
-
-  /**
-   * Processes form fields that can be autofilled, and populates them with the
-   * data provided by backend.
-   *
-   * @param {Array<Object>} autofillResult
-   *        Data returned by the user interface.
-   *        [{
-   *          section: Value originally provided to the user interface.
-   *          addressType: Value originally provided to the user interface.
-   *          contactType: Value originally provided to the user interface.
-   *          fieldName: Value originally provided to the user interface.
-   *          value: String with which the field should be updated.
-   *          index: Index to match the input in fieldDetails
-   *        }],
-   *        }
-   */
-  autofillFormFields(autofillResult) {
-    for (let field of autofillResult) {
-      // Get the field details, if it was processed by the user interface.
-      let fieldDetail = this.fieldDetails[field.index];
-
-      // Avoid the invalid value set
-      if (!fieldDetail || !field.value) {
-        continue;
-      }
-
-      let info = FormAutofillHeuristics.getInfo(fieldDetail.element);
-      if (!info ||
-          field.section != info.section ||
-          field.addressType != info.addressType ||
-          field.contactType != info.contactType ||
-          field.fieldName != info.fieldName) {
-        Cu.reportError("Autocomplete tokens mismatched");
-        continue;
-      }
-
-      fieldDetail.element.setUserInput(field.value);
-    }
-  },
-};
-
-// Register/unregister a constructor as a factory.
-function AutocompleteFactory() {}
-AutocompleteFactory.prototype = {
-  register(targetConstructor) {
-    let proto = targetConstructor.prototype;
-    this._classID = proto.classID;
-
-    let factory = XPCOMUtils._getFactory(targetConstructor);
-    this._factory = factory;
-
-    let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
-    registrar.registerFactory(proto.classID, proto.classDescription,
-                              proto.contractID, factory);
-
-    if (proto.classID2) {
-      this._classID2 = proto.classID2;
-      registrar.registerFactory(proto.classID2, proto.classDescription,
-                                proto.contractID2, factory);
-    }
-  },
-
-  unregister() {
-    let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
-    registrar.unregisterFactory(this._classID, this._factory);
-    if (this._classID2) {
-      registrar.unregisterFactory(this._classID2, this._factory);
-    }
-    this._factory = null;
-  },
-};
-
-
-/**
- * @constructor
- *
- * @implements {nsIAutoCompleteSearch}
- */
-function AutofillProfileAutoCompleteSearch() {
-
-}
-AutofillProfileAutoCompleteSearch.prototype = {
-  classID: Components.ID("4f9f1e4c-7f2c-439e-9c9e-566b68bc187d"),
-  contractID: "@mozilla.org/autocomplete/search;1?name=autofill-profiles",
-  classDescription: "AutofillProfileAutoCompleteSearch",
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteSearch]),
-
-  // Begin nsIAutoCompleteSearch implementation
-
-  /**
-   * Searches for a given string and notifies a listener (either synchronously
-   * or asynchronously) of the result
-   *
-   * @param {string} searchString the string to search for
-   * @param {string} searchParam
-   * @param {Object} previousResult a previous result to use for faster searchinig
-   * @param {Object} listener the listener to notify when the search is complete
-   */
-  startSearch(searchString, searchParam, previousResult, listener) {
-    this.forceStop = false;
-    let info = this.getInputDetails();
-
-    this.getProfiles({info, searchString}).then((profiles) => {
-      if (this.forceStop) {
-        return;
-      }
-
-      // TODO: Set formInfo for ProfileAutoCompleteResult
-      // let formInfo = this.getFormDetails();
-      let result = new ProfileAutoCompleteResult(searchString, info, profiles, {});
-
-      listener.onSearchResult(this, result);
-    });
-  },
-
-  /**
-   * Stops an asynchronous search that is in progress
-   */
-  stopSearch() {
-    this.forceStop = true;
-  },
-
-  /**
-   * Get the profile data from parent process for AutoComplete result.
-   *
-   * @private
-   * @param  {Object} data
-   *         Parameters for querying the corresponding result.
-   * @param  {string} data.searchString
-   *         The typed string for filtering out the matched profile.
-   * @param  {string} data.info
-   *         The input autocomplete property's information.
-   * @returns {Promise}
-   *          Promise that resolves when profiles returned from parent process.
-   */
-  getProfiles(data) {
-    return new Promise((resolve) => {
-      addMessageListener("FormAutofill:Profiles", function getResult(result) {
-        removeMessageListener("FormAutofill:Profiles", getResult);
-        resolve(result.data);
-      });
-
-      sendAsyncMessage("FormAutofill:GetProfiles", data);
-    });
-  },
-
-
-  /**
-   * Get the input's information from FormAutofillContent's cache.
-   *
-   * @returns {Object}
-   *          Target input's information that cached in FormAutofillContent.
-   */
-  getInputDetails() {
-    // TODO: Maybe we'll need to wait for cache ready if detail is empty.
-    return FormAutofillContent.getInputDetails(formFillController.focusedInput);
-  },
-
-  /**
-   * Get the form's information from FormAutofillContent's cache.
-   *
-   * @returns {Array<Object>}
-   *          Array of the inputs' information for the target form.
-   */
-  getFormDetails() {
-    // TODO: Maybe we'll need to wait for cache ready if details is empty.
-    return FormAutofillContent.getFormDetails(formFillController.focusedInput);
-  },
-};
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AutofillProfileAutoCompleteSearch]);
-
-let ProfileAutocomplete = {
-  _registered: false,
-  _factory: null,
-
-  ensureRegistered() {
-    if (this._registered) {
-      return;
-    }
-
-    this._factory = new AutocompleteFactory();
-    this._factory.register(AutofillProfileAutoCompleteSearch);
-    this._registered = true;
-  },
-
-  ensureUnregistered() {
-    if (!this._registered) {
-      return;
-    }
-
-    this._factory.unregister();
-    this._factory = null;
-    this._registered = false;
-  },
-};
-
-/**
- * Handles content's interactions.
- *
- * NOTE: Declares it by "var" to make it accessible in unit tests.
- */
-var FormAutofillContent = {
-  init() {
-    addEventListener("DOMContentLoaded", this);
-
-    addMessageListener("FormAutofill:enabledStatus", (result) => {
-      if (result.data) {
-        ProfileAutocomplete.ensureRegistered();
-      } else {
-        ProfileAutocomplete.ensureUnregistered();
-      }
-    });
-    sendAsyncMessage("FormAutofill:getEnabledStatus");
-  },
-
-  handleEvent(evt) {
-    if (!evt.isTrusted) {
-      return;
-    }
-
-    switch (evt.type) {
-      case "DOMContentLoaded":
-        let doc = evt.target;
-        if (!(doc instanceof Ci.nsIDOMHTMLDocument)) {
-          return;
-        }
-        this._identifyAutofillFields(doc);
-        break;
-    }
-  },
-
-  /**
-   * Get the input's information from cache which is created after page identified.
-   *
-   * @param {HTMLInputElement} element Focused input which triggered profile searching
-   * @returns {Object|null}
-   *          Return target input's information that cloned from content cache
-   *          (or return null if the information is not found in the cache).
-   */
-  getInputDetails(element) {
-    for (let formDetails of this._formsDetails) {
-      for (let detail of formDetails) {
-        if (element == detail.element) {
-          return this._serializeInfo(detail);
-        }
-      }
-    }
-    return null;
-  },
-
-  /**
-   * Get the form's information from cache which is created after page identified.
-   *
-   * @param {HTMLInputElement} element Focused input which triggered profile searching
-   * @returns {Array<Object>|null}
-   *          Return target form's information that cloned from content cache
-   *          (or return null if the information is not found in the cache).
-   *
-   */
-  getFormDetails(element) {
-    for (let formDetails of this._formsDetails) {
-      if (formDetails.some((detail) => detail.element == element)) {
-        return formDetails.map((detail) => this._serializeInfo(detail));
-      }
-    }
-    return null;
-  },
-
-  /**
-   * Create a clone the information object without element reference.
-   *
-   * @param {Object} detail Profile autofill information for specific input.
-   * @returns {Object}
-   *          Return a copy of cached information object without element reference
-   *          since it's not needed for creating result.
-   */
-  _serializeInfo(detail) {
-    let info = Object.assign({}, detail);
-    delete info.element;
-    return info;
-  },
-
-  _identifyAutofillFields(doc) {
-    let forms = [];
-    this._formsDetails = [];
-
-    // Collects root forms from inputs.
-    for (let field of doc.getElementsByTagName("input")) {
-      // We only consider text-like fields for now until we support radio and
-      // checkbox buttons in the future.
-      if (!field.mozIsTextField(true)) {
-        continue;
-      }
-
-      let formLike = FormLikeFactory.createFromField(field);
-      if (!forms.some(form => form.rootElement === formLike.rootElement)) {
-        forms.push(formLike);
-      }
-    }
-
-    // Collects the fields that can be autofilled from each form and marks them
-    // as autofill fields if the amount is above the threshold.
-    forms.forEach(form => {
-      let formHandler = new FormAutofillHandler(form);
-      formHandler.collectFormFields();
-      if (formHandler.fieldDetails.length < AUTOFILL_FIELDS_THRESHOLD) {
-        return;
-      }
-
-      this._formsDetails.push(formHandler.fieldDetails);
-      formHandler.fieldDetails.forEach(
-        detail => this._markAsAutofillField(detail.element));
-    });
-  },
-
-  _markAsAutofillField(field) {
-    formFillController.markAsAutofillField(field);
-  },
-};
-
-
-FormAutofillContent.init();
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -70,17 +70,17 @@ FormAutofillParent.prototype = {
     Services.obs.addObserver(this, "advanced-pane-loaded", false);
 
     // Observing the pref (and storage) changes
     Services.prefs.addObserver(ENABLED_PREF, this, false);
     this._enabled = this._getStatus();
     // Force to trigger the onStatusChanged function for setting listeners properly
     // while initizlization
     this._onStatusChanged();
-    Services.mm.addMessageListener("FormAutofill:getEnabledStatus", this);
+    Services.ppmm.addMessageListener("FormAutofill:getEnabledStatus", this);
   },
 
   observe(subject, topic, data) {
     switch (topic) {
       case "advanced-pane-loaded": {
         let formAutofillPreferences = new FormAutofillPreferences();
         let document = subject.document;
         let prefGroup = formAutofillPreferences.init(document);
@@ -107,21 +107,21 @@ FormAutofillParent.prototype = {
   },
 
   /**
    * Add/remove message listener and broadcast the status to frames while the
    * form autofill status changed.
    */
   _onStatusChanged() {
     if (this._enabled) {
-      Services.mm.addMessageListener("FormAutofill:PopulateFieldValues", this);
-      Services.mm.addMessageListener("FormAutofill:GetProfiles", this);
+      Services.ppmm.addMessageListener("FormAutofill:PopulateFieldValues", this);
+      Services.ppmm.addMessageListener("FormAutofill:GetProfiles", this);
     } else {
-      Services.mm.removeMessageListener("FormAutofill:PopulateFieldValues", this);
-      Services.mm.removeMessageListener("FormAutofill:GetProfiles", this);
+      Services.ppmm.removeMessageListener("FormAutofill:PopulateFieldValues", this);
+      Services.ppmm.removeMessageListener("FormAutofill:GetProfiles", this);
     }
 
     Services.mm.broadcastAsyncMessage("FormAutofill:enabledStatus", this._enabled);
   },
 
   /**
    * Query pref (and storage) status to determine the overall status for
    * form autofill feature.
@@ -143,18 +143,18 @@ FormAutofillParent.prototype = {
     switch (name) {
       case "FormAutofill:PopulateFieldValues":
         this._populateFieldValues(data, target);
         break;
       case "FormAutofill:GetProfiles":
         this._getProfiles(data, target);
         break;
       case "FormAutofill:getEnabledStatus":
-        target.messageManager.sendAsyncMessage("FormAutofill:enabledStatus",
-                                               this._enabled);
+        Services.ppmm.broadcastAsyncMessage("FormAutofill:enabledStatus",
+                                            this._enabled);
         break;
     }
   },
 
   /**
    * Returns the instance of ProfileStorage. To avoid syncing issues, anyone
    * who needs to access the profile should request the instance by this instead
    * of creating a new one.
@@ -171,18 +171,18 @@ FormAutofillParent.prototype = {
    * @private
    */
   _uninit() {
     if (this._profileStore) {
       this._profileStore._saveImmediately();
       this._profileStore = null;
     }
 
-    Services.mm.removeMessageListener("FormAutofill:PopulateFieldValues", this);
-    Services.mm.removeMessageListener("FormAutofill:GetProfiles", this);
+    Services.ppmm.removeMessageListener("FormAutofill:PopulateFieldValues", this);
+    Services.ppmm.removeMessageListener("FormAutofill:GetProfiles", this);
     Services.obs.removeObserver(this, "advanced-pane-loaded");
     Services.prefs.removeObserver(ENABLED_PREF, this);
   },
 
   /**
    * Populates the field values and notifies content to fill in. Exception will
    * be thrown if there's no matching profile.
    *
@@ -215,17 +215,17 @@ FormAutofillParent.prototype = {
     let profiles = [];
 
     if (info && info.fieldName) {
       profiles = this._profileStore.getByFilter({searchString, info});
     } else {
       profiles = this._profileStore.getAll();
     }
 
-    target.messageManager.sendAsyncMessage("FormAutofill:Profiles", profiles);
+    target.sendAsyncMessage("FormAutofill:Profiles", profiles);
   },
 
   /**
    * Get the corresponding value from the specified profile according to a valid
    * @autocomplete field name.
    *
    * Note that the field name doesn't need to match the property name defined in
    * Profile object. This method can transform the raw data to fulfill it. (e.g.
--- a/browser/extensions/formautofill/bootstrap.js
+++ b/browser/extensions/formautofill/bootstrap.js
@@ -18,14 +18,14 @@ function startup() {
   // Besides this pref, we'll need dom.forms.autocomplete.experimental enabled
   // as well to make sure form autocomplete works correctly.
   if (!Services.prefs.getBoolPref("browser.formautofill.experimental")) {
     return;
   }
 
   let parent = new FormAutofillParent();
   parent.init();
-  Services.mm.loadFrameScript("chrome://formautofill/content/FormAutofillContent.js", true);
+  Services.mm.loadFrameScript("chrome://formautofill/content/FormAutofillFrameScript.js", true);
 }
 
 function shutdown() {}
 function install() {}
 function uninstall() {}
copy from browser/extensions/formautofill/content/FormAutofillContent.js
copy to browser/extensions/formautofill/content/FormAutofillFrameScript.js
--- a/browser/extensions/formautofill/content/FormAutofillContent.js
+++ b/browser/extensions/formautofill/content/FormAutofillFrameScript.js
@@ -1,455 +1,51 @@
 /* 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/. */
 
-/* eslint-disable no-use-before-define */
-
 /*
  * Form Autofill frame script.
  */
 
 "use strict";
 
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr, manager: Cm} = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "ProfileAutoCompleteResult",
-                                  "resource://formautofill/ProfileAutoCompleteResult.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "FormLikeFactory",
-                                  "resource://gre/modules/FormLikeFactory.jsm");
-
-const formFillController = Cc["@mozilla.org/satchel/form-fill-controller;1"]
-                             .getService(Ci.nsIFormFillController);
-
-const AUTOFILL_FIELDS_THRESHOLD = 3;
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
-/**
- * Returns the autocomplete information of fields according to heuristics.
- */
-let FormAutofillHeuristics = {
-  VALID_FIELDS: [
-    "organization",
-    "street-address",
-    "address-level2",
-    "address-level1",
-    "postal-code",
-    "country",
-    "tel",
-    "email",
-  ],
-
-  getInfo(element) {
-    if (!(element instanceof Ci.nsIDOMHTMLInputElement)) {
-      return null;
-    }
-
-    let info = element.getAutocompleteInfo();
-    if (!info || !info.fieldName ||
-        !this.VALID_FIELDS.includes(info.fieldName)) {
-      return null;
-    }
-
-    return info;
-  },
-};
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 /**
- * Handles profile autofill for a DOM Form element.
- * @param {HTMLFormElement} form Form that need to be auto filled
- */
-function FormAutofillHandler(form) {
-  this.form = form;
-  this.fieldDetails = [];
-}
-
-FormAutofillHandler.prototype = {
-  /**
-   * DOM Form element to which this object is attached.
-   */
-  form: null,
-
-  /**
-   * Array of collected data about relevant form fields.  Each item is an object
-   * storing the identifying details of the field and a reference to the
-   * originally associated element from the form.
-   *
-   * The "section", "addressType", "contactType", and "fieldName" values are
-   * used to identify the exact field when the serializable data is received
-   * from the backend.  There cannot be multiple fields which have
-   * the same exact combination of these values.
-   *
-   * A direct reference to the associated element cannot be sent to the user
-   * interface because processing may be done in the parent process.
-   */
-  fieldDetails: null,
-
-  /**
-   * Returns information from the form about fields that can be autofilled, and
-   * populates the fieldDetails array on this object accordingly.
-   *
-   * @returns {Array<Object>} Serializable data structure that can be sent to the user
-   *          interface, or null if the operation failed because the constraints
-   *          on the allowed fields were not honored.
-   */
-  collectFormFields() {
-    let autofillData = [];
-
-    for (let element of this.form.elements) {
-      // Exclude elements to which no autocomplete field has been assigned.
-      let info = FormAutofillHeuristics.getInfo(element);
-      if (!info) {
-        continue;
-      }
-
-      // Store the association between the field metadata and the element.
-      if (this.fieldDetails.some(f => f.section == info.section &&
-                                      f.addressType == info.addressType &&
-                                      f.contactType == info.contactType &&
-                                      f.fieldName == info.fieldName)) {
-        // A field with the same identifier already exists.
-        return null;
-      }
-
-      let inputFormat = {
-        section: info.section,
-        addressType: info.addressType,
-        contactType: info.contactType,
-        fieldName: info.fieldName,
-      };
-      // Clone the inputFormat for caching the fields and elements together
-      let formatWithElement = Object.assign({}, inputFormat);
-
-      inputFormat.index = autofillData.length;
-      autofillData.push(inputFormat);
-
-      formatWithElement.element = element;
-      this.fieldDetails.push(formatWithElement);
-    }
-
-    return autofillData;
-  },
-
-  /**
-   * Processes form fields that can be autofilled, and populates them with the
-   * data provided by backend.
-   *
-   * @param {Array<Object>} autofillResult
-   *        Data returned by the user interface.
-   *        [{
-   *          section: Value originally provided to the user interface.
-   *          addressType: Value originally provided to the user interface.
-   *          contactType: Value originally provided to the user interface.
-   *          fieldName: Value originally provided to the user interface.
-   *          value: String with which the field should be updated.
-   *          index: Index to match the input in fieldDetails
-   *        }],
-   *        }
-   */
-  autofillFormFields(autofillResult) {
-    for (let field of autofillResult) {
-      // Get the field details, if it was processed by the user interface.
-      let fieldDetail = this.fieldDetails[field.index];
-
-      // Avoid the invalid value set
-      if (!fieldDetail || !field.value) {
-        continue;
-      }
-
-      let info = FormAutofillHeuristics.getInfo(fieldDetail.element);
-      if (!info ||
-          field.section != info.section ||
-          field.addressType != info.addressType ||
-          field.contactType != info.contactType ||
-          field.fieldName != info.fieldName) {
-        Cu.reportError("Autocomplete tokens mismatched");
-        continue;
-      }
-
-      fieldDetail.element.setUserInput(field.value);
-    }
-  },
-};
-
-// Register/unregister a constructor as a factory.
-function AutocompleteFactory() {}
-AutocompleteFactory.prototype = {
-  register(targetConstructor) {
-    let proto = targetConstructor.prototype;
-    this._classID = proto.classID;
-
-    let factory = XPCOMUtils._getFactory(targetConstructor);
-    this._factory = factory;
-
-    let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
-    registrar.registerFactory(proto.classID, proto.classDescription,
-                              proto.contractID, factory);
-
-    if (proto.classID2) {
-      this._classID2 = proto.classID2;
-      registrar.registerFactory(proto.classID2, proto.classDescription,
-                                proto.contractID2, factory);
-    }
-  },
-
-  unregister() {
-    let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
-    registrar.unregisterFactory(this._classID, this._factory);
-    if (this._classID2) {
-      registrar.unregisterFactory(this._classID2, this._factory);
-    }
-    this._factory = null;
-  },
-};
-
-
-/**
- * @constructor
- *
- * @implements {nsIAutoCompleteSearch}
- */
-function AutofillProfileAutoCompleteSearch() {
-
-}
-AutofillProfileAutoCompleteSearch.prototype = {
-  classID: Components.ID("4f9f1e4c-7f2c-439e-9c9e-566b68bc187d"),
-  contractID: "@mozilla.org/autocomplete/search;1?name=autofill-profiles",
-  classDescription: "AutofillProfileAutoCompleteSearch",
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteSearch]),
-
-  // Begin nsIAutoCompleteSearch implementation
-
-  /**
-   * Searches for a given string and notifies a listener (either synchronously
-   * or asynchronously) of the result
-   *
-   * @param {string} searchString the string to search for
-   * @param {string} searchParam
-   * @param {Object} previousResult a previous result to use for faster searchinig
-   * @param {Object} listener the listener to notify when the search is complete
-   */
-  startSearch(searchString, searchParam, previousResult, listener) {
-    this.forceStop = false;
-    let info = this.getInputDetails();
-
-    this.getProfiles({info, searchString}).then((profiles) => {
-      if (this.forceStop) {
-        return;
-      }
-
-      // TODO: Set formInfo for ProfileAutoCompleteResult
-      // let formInfo = this.getFormDetails();
-      let result = new ProfileAutoCompleteResult(searchString, info, profiles, {});
-
-      listener.onSearchResult(this, result);
-    });
-  },
-
-  /**
-   * Stops an asynchronous search that is in progress
-   */
-  stopSearch() {
-    this.forceStop = true;
-  },
-
-  /**
-   * Get the profile data from parent process for AutoComplete result.
-   *
-   * @private
-   * @param  {Object} data
-   *         Parameters for querying the corresponding result.
-   * @param  {string} data.searchString
-   *         The typed string for filtering out the matched profile.
-   * @param  {string} data.info
-   *         The input autocomplete property's information.
-   * @returns {Promise}
-   *          Promise that resolves when profiles returned from parent process.
-   */
-  getProfiles(data) {
-    return new Promise((resolve) => {
-      addMessageListener("FormAutofill:Profiles", function getResult(result) {
-        removeMessageListener("FormAutofill:Profiles", getResult);
-        resolve(result.data);
-      });
-
-      sendAsyncMessage("FormAutofill:GetProfiles", data);
-    });
-  },
-
-
-  /**
-   * Get the input's information from FormAutofillContent's cache.
-   *
-   * @returns {Object}
-   *          Target input's information that cached in FormAutofillContent.
-   */
-  getInputDetails() {
-    // TODO: Maybe we'll need to wait for cache ready if detail is empty.
-    return FormAutofillContent.getInputDetails(formFillController.focusedInput);
-  },
-
-  /**
-   * Get the form's information from FormAutofillContent's cache.
-   *
-   * @returns {Array<Object>}
-   *          Array of the inputs' information for the target form.
-   */
-  getFormDetails() {
-    // TODO: Maybe we'll need to wait for cache ready if details is empty.
-    return FormAutofillContent.getFormDetails(formFillController.focusedInput);
-  },
-};
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AutofillProfileAutoCompleteSearch]);
-
-let ProfileAutocomplete = {
-  _registered: false,
-  _factory: null,
-
-  ensureRegistered() {
-    if (this._registered) {
-      return;
-    }
-
-    this._factory = new AutocompleteFactory();
-    this._factory.register(AutofillProfileAutoCompleteSearch);
-    this._registered = true;
-  },
-
-  ensureUnregistered() {
-    if (!this._registered) {
-      return;
-    }
-
-    this._factory.unregister();
-    this._factory = null;
-    this._registered = false;
-  },
-};
-
-/**
- * Handles content's interactions.
+ * Handles content's interactions for the frame.
  *
  * NOTE: Declares it by "var" to make it accessible in unit tests.
  */
-var FormAutofillContent = {
+var FormAutofillFrameScript = {
   init() {
     addEventListener("DOMContentLoaded", this);
-
-    addMessageListener("FormAutofill:enabledStatus", (result) => {
-      if (result.data) {
-        ProfileAutocomplete.ensureRegistered();
-      } else {
-        ProfileAutocomplete.ensureUnregistered();
-      }
-    });
-    sendAsyncMessage("FormAutofill:getEnabledStatus");
   },
 
   handleEvent(evt) {
     if (!evt.isTrusted) {
       return;
     }
 
+    if (!Services.prefs.getBoolPref("browser.formautofill.enabled")) {
+      return;
+    }
+
     switch (evt.type) {
-      case "DOMContentLoaded":
+      case "DOMContentLoaded": {
         let doc = evt.target;
         if (!(doc instanceof Ci.nsIDOMHTMLDocument)) {
           return;
         }
-        this._identifyAutofillFields(doc);
+        this.FormAutofillContent._identifyAutofillFields(doc);
         break;
-    }
-  },
-
-  /**
-   * Get the input's information from cache which is created after page identified.
-   *
-   * @param {HTMLInputElement} element Focused input which triggered profile searching
-   * @returns {Object|null}
-   *          Return target input's information that cloned from content cache
-   *          (or return null if the information is not found in the cache).
-   */
-  getInputDetails(element) {
-    for (let formDetails of this._formsDetails) {
-      for (let detail of formDetails) {
-        if (element == detail.element) {
-          return this._serializeInfo(detail);
-        }
-      }
-    }
-    return null;
-  },
-
-  /**
-   * Get the form's information from cache which is created after page identified.
-   *
-   * @param {HTMLInputElement} element Focused input which triggered profile searching
-   * @returns {Array<Object>|null}
-   *          Return target form's information that cloned from content cache
-   *          (or return null if the information is not found in the cache).
-   *
-   */
-  getFormDetails(element) {
-    for (let formDetails of this._formsDetails) {
-      if (formDetails.some((detail) => detail.element == element)) {
-        return formDetails.map((detail) => this._serializeInfo(detail));
       }
     }
-    return null;
-  },
-
-  /**
-   * Create a clone the information object without element reference.
-   *
-   * @param {Object} detail Profile autofill information for specific input.
-   * @returns {Object}
-   *          Return a copy of cached information object without element reference
-   *          since it's not needed for creating result.
-   */
-  _serializeInfo(detail) {
-    let info = Object.assign({}, detail);
-    delete info.element;
-    return info;
-  },
-
-  _identifyAutofillFields(doc) {
-    let forms = [];
-    this._formsDetails = [];
-
-    // Collects root forms from inputs.
-    for (let field of doc.getElementsByTagName("input")) {
-      // We only consider text-like fields for now until we support radio and
-      // checkbox buttons in the future.
-      if (!field.mozIsTextField(true)) {
-        continue;
-      }
-
-      let formLike = FormLikeFactory.createFromField(field);
-      if (!forms.some(form => form.rootElement === formLike.rootElement)) {
-        forms.push(formLike);
-      }
-    }
-
-    // Collects the fields that can be autofilled from each form and marks them
-    // as autofill fields if the amount is above the threshold.
-    forms.forEach(form => {
-      let formHandler = new FormAutofillHandler(form);
-      formHandler.collectFormFields();
-      if (formHandler.fieldDetails.length < AUTOFILL_FIELDS_THRESHOLD) {
-        return;
-      }
-
-      this._formsDetails.push(formHandler.fieldDetails);
-      formHandler.fieldDetails.forEach(
-        detail => this._markAsAutofillField(detail.element));
-    });
-  },
-
-  _markAsAutofillField(field) {
-    formFillController.markAsAutofillField(field);
   },
 };
 
+XPCOMUtils.defineLazyModuleGetter(FormAutofillFrameScript, "FormAutofillContent",
+                                  "resource://formautofill/FormAutofillContent.jsm");
 
-FormAutofillContent.init();
+FormAutofillFrameScript.init();
--- a/browser/extensions/formautofill/test/unit/head.js
+++ b/browser/extensions/formautofill/test/unit/head.js
@@ -40,31 +40,16 @@ if (!extensionDir.exists()) {
 Components.manager.addBootstrappedManifestLocation(extensionDir);
 
 // 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);
 
-function loadFormAutofillContent() {
-  let facGlobal = {
-    addEventListener() {},
-    addMessageListener() {},
-    sendAsyncMessage() {},
-  };
-  let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
-               .getService(Ci.mozIJSSubScriptLoader);
-  loader.loadSubScriptWithOptions("chrome://formautofill/content/FormAutofillContent.js", {
-    target: facGlobal,
-  });
-
-  return facGlobal;
-}
-
 /**
  * Returns a reference to a temporary file, that is guaranteed not to exist, and
  * to have never been created before.
  *
  * @param {string} leafName
  *        Suggested leaf name for the file to be created.
  *
  * @returns {nsIFile} pointing to a non-existent file in a temporary directory.
--- a/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
+++ b/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
@@ -1,15 +1,15 @@
 /*
  * Test for form auto fill content helper fill all inputs function.
  */
 
 "use strict";
 
-let {FormAutofillHandler} = loadFormAutofillContent();
+Cu.import("resource://formautofill/FormAutofillHandler.jsm");
 
 const TESTCASES = [
   {
     description: "Form without autocomplete property",
     document: `<form><input id="given-name"><input id="family-name">
                <input id="street-addr"><input id="city"><input id="country">
                <input id='email'><input id="tel"></form>`,
     fieldDetails: [],
--- a/browser/extensions/formautofill/test/unit/test_collectFormFields.js
+++ b/browser/extensions/formautofill/test/unit/test_collectFormFields.js
@@ -1,15 +1,15 @@
 /*
  * Test for form auto fill content helper collectFormFields functions.
  */
 
 "use strict";
 
-let {FormAutofillHandler} = loadFormAutofillContent();
+Cu.import("resource://formautofill/FormAutofillHandler.jsm");
 
 const TESTCASES = [
   {
     description: "Form without autocomplete property",
     document: `<form><input id="given-name"><input id="family-name">
                <input id="street-addr"><input id="city"><input id="country">
                <input id='email'><input id="tel"></form>`,
     returnedFormat: [],
--- a/browser/extensions/formautofill/test/unit/test_getFormInputDetails.js
+++ b/browser/extensions/formautofill/test/unit/test_getFormInputDetails.js
@@ -1,11 +1,11 @@
 "use strict";
 
-let {FormAutofillContent} = loadFormAutofillContent();
+Cu.import("resource://formautofill/FormAutofillContent.jsm");
 
 const TESTCASES = [
   {
     description: "Form containing 5 fields with autocomplete attribute.",
     document: `<form>
                  <input id="street-addr" autocomplete="street-address">
                  <input id="city" autocomplete="address-level2">
                  <input id="country" autocomplete="country">
--- a/browser/extensions/formautofill/test/unit/test_markAsAutofillField.js
+++ b/browser/extensions/formautofill/test/unit/test_markAsAutofillField.js
@@ -1,11 +1,11 @@
 "use strict";
 
-let {FormAutofillContent} = loadFormAutofillContent();
+Cu.import("resource://formautofill/FormAutofillContent.jsm");
 
 const TESTCASES = [
   {
     description: "Form containing 5 fields with autocomplete attribute.",
     document: `<form>
                  <input id="street-addr" autocomplete="street-address">
                  <input id="city" autocomplete="address-level2">
                  <input id="country" autocomplete="country">
--- a/tools/lint/eslint/modules.json
+++ b/tools/lint/eslint/modules.json
@@ -74,17 +74,16 @@
   "extension-storage.js": ["ExtensionStorageEngine", "EncryptionRemoteTransformer", "KeyRingEncryptionRemoteTransformer"],
   "ExtensionXPCShellUtils.jsm": ["ExtensionTestUtils"],
   "fakeservices.js": ["FakeCryptoService", "FakeFilesystemService", "FakeGUIDService", "fakeSHA256HMAC"],
   "file_expandosharing.jsm": ["checkFromJSM"],
   "file_stringencoding.jsm": ["checkFromJSM"],
   "file_url.jsm": ["checkFromJSM"],
   "file_worker_url.jsm": ["checkFromJSM"],
   "Finder.jsm": ["Finder", "GetClipboardSearchString"],
-  "FormAutofillContent.jsm": ["FormAutofillHandler"],
   "forms.js": ["FormEngine", "FormRec", "FormValidator"],
   "forms.jsm": ["FormData"],
   "frame.js": ["Collector", "Runner", "events", "runTestFile", "log", "timers", "persisted", "shutdownApplication"],
   "FrameScriptManager.jsm": ["getNewLoaderID"],
   "fxa_utils.js": ["initializeIdentityWithTokenServerResponse"],
   "fxaccounts.jsm": ["Authentication"],
   "FxAccounts.jsm": ["fxAccounts", "FxAccounts"],
   "FxAccountsCommon.js": ["log", "logPII", "FXACCOUNTS_PERMISSION", "DATA_FORMAT_VERSION", "DEFAULT_STORAGE_FILENAME", "ASSERTION_LIFETIME", "ASSERTION_USE_PERIOD", "CERT_LIFETIME", "KEY_LIFETIME", "POLL_SESSION", "ONLOGIN_NOTIFICATION", "ONVERIFIED_NOTIFICATION", "ONLOGOUT_NOTIFICATION", "ON_FXA_UPDATE_NOTIFICATION", "ON_DEVICE_CONNECTED_NOTIFICATION", "ON_DEVICE_DISCONNECTED_NOTIFICATION", "ON_PASSWORD_CHANGED_NOTIFICATION", "ON_PASSWORD_RESET_NOTIFICATION", "ON_COLLECTION_CHANGED_NOTIFICATION", "FXA_PUSH_SCOPE_ACCOUNT_UPDATE", "ON_PROFILE_CHANGE_NOTIFICATION", "ON_ACCOUNT_STATE_CHANGE_NOTIFICATION", "UI_REQUEST_SIGN_IN_FLOW", "UI_REQUEST_REFRESH_AUTH", "FX_OAUTH_CLIENT_ID", "WEBCHANNEL_ID", "ERRNO_ACCOUNT_ALREADY_EXISTS", "ERRNO_ACCOUNT_DOES_NOT_EXIST", "ERRNO_INCORRECT_PASSWORD", "ERRNO_UNVERIFIED_ACCOUNT", "ERRNO_INVALID_VERIFICATION_CODE", "ERRNO_NOT_VALID_JSON_BODY", "ERRNO_INVALID_BODY_PARAMETERS", "ERRNO_MISSING_BODY_PARAMETERS", "ERRNO_INVALID_REQUEST_SIGNATURE", "ERRNO_INVALID_AUTH_TOKEN", "ERRNO_INVALID_AUTH_TIMESTAMP", "ERRNO_MISSING_CONTENT_LENGTH", "ERRNO_REQUEST_BODY_TOO_LARGE", "ERRNO_TOO_MANY_CLIENT_REQUESTS", "ERRNO_INVALID_AUTH_NONCE", "ERRNO_ENDPOINT_NO_LONGER_SUPPORTED", "ERRNO_INCORRECT_LOGIN_METHOD", "ERRNO_INCORRECT_KEY_RETRIEVAL_METHOD", "ERRNO_INCORRECT_API_VERSION", "ERRNO_INCORRECT_EMAIL_CASE", "ERRNO_ACCOUNT_LOCKED", "ERRNO_ACCOUNT_UNLOCKED", "ERRNO_UNKNOWN_DEVICE", "ERRNO_DEVICE_SESSION_CONFLICT", "ERRNO_SERVICE_TEMP_UNAVAILABLE", "ERRNO_PARSE", "ERRNO_NETWORK", "ERRNO_UNKNOWN_ERROR", "OAUTH_SERVER_ERRNO_OFFSET", "ERRNO_UNKNOWN_CLIENT_ID", "ERRNO_INCORRECT_CLIENT_SECRET", "ERRNO_INCORRECT_REDIRECT_URI", "ERRNO_INVALID_FXA_ASSERTION", "ERRNO_UNKNOWN_CODE", "ERRNO_INCORRECT_CODE", "ERRNO_EXPIRED_CODE", "ERRNO_OAUTH_INVALID_TOKEN", "ERRNO_INVALID_REQUEST_PARAM", "ERRNO_INVALID_RESPONSE_TYPE", "ERRNO_UNAUTHORIZED", "ERRNO_FORBIDDEN", "ERRNO_INVALID_CONTENT_TYPE", "ERROR_ACCOUNT_ALREADY_EXISTS", "ERROR_ACCOUNT_DOES_NOT_EXIST", "ERROR_ACCOUNT_LOCKED", "ERROR_ACCOUNT_UNLOCKED", "ERROR_ALREADY_SIGNED_IN_USER", "ERROR_DEVICE_SESSION_CONFLICT", "ERROR_ENDPOINT_NO_LONGER_SUPPORTED", "ERROR_INCORRECT_API_VERSION", "ERROR_INCORRECT_EMAIL_CASE", "ERROR_INCORRECT_KEY_RETRIEVAL_METHOD", "ERROR_INCORRECT_LOGIN_METHOD", "ERROR_INVALID_EMAIL", "ERROR_INVALID_AUDIENCE", "ERROR_INVALID_AUTH_TOKEN", "ERROR_INVALID_AUTH_TIMESTAMP", "ERROR_INVALID_AUTH_NONCE", "ERROR_INVALID_BODY_PARAMETERS", "ERROR_INVALID_PASSWORD", "ERROR_INVALID_VERIFICATION_CODE", "ERROR_INVALID_REFRESH_AUTH_VALUE", "ERROR_INVALID_REQUEST_SIGNATURE", "ERROR_INTERNAL_INVALID_USER", "ERROR_MISSING_BODY_PARAMETERS", "ERROR_MISSING_CONTENT_LENGTH", "ERROR_NO_TOKEN_SESSION", "ERROR_NO_SILENT_REFRESH_AUTH", "ERROR_NOT_VALID_JSON_BODY", "ERROR_OFFLINE", "ERROR_PERMISSION_DENIED", "ERROR_REQUEST_BODY_TOO_LARGE", "ERROR_SERVER_ERROR", "ERROR_SYNC_DISABLED", "ERROR_TOO_MANY_CLIENT_REQUESTS", "ERROR_SERVICE_TEMP_UNAVAILABLE", "ERROR_UI_ERROR", "ERROR_UI_REQUEST", "ERROR_PARSE", "ERROR_NETWORK", "ERROR_UNKNOWN", "ERROR_UNKNOWN_DEVICE", "ERROR_UNVERIFIED_ACCOUNT", "ERROR_UNKNOWN_CLIENT_ID", "ERROR_INCORRECT_CLIENT_SECRET", "ERROR_INCORRECT_REDIRECT_URI", "ERROR_INVALID_FXA_ASSERTION", "ERROR_UNKNOWN_CODE", "ERROR_INCORRECT_CODE", "ERROR_EXPIRED_CODE", "ERROR_OAUTH_INVALID_TOKEN", "ERROR_INVALID_REQUEST_PARAM", "ERROR_INVALID_RESPONSE_TYPE", "ERROR_UNAUTHORIZED", "ERROR_FORBIDDEN", "ERROR_INVALID_CONTENT_TYPE", "ERROR_NO_ACCOUNT", "ERROR_AUTH_ERROR", "ERROR_INVALID_PARAMETER", "ERROR_CODE_METHOD_NOT_ALLOWED", "ERROR_MSG_METHOD_NOT_ALLOWED", "FXA_PWDMGR_PLAINTEXT_FIELDS", "FXA_PWDMGR_SECURE_FIELDS", "FXA_PWDMGR_MEMORY_FIELDS", "FXA_PWDMGR_REAUTH_WHITELIST", "FXA_PWDMGR_HOST", "FXA_PWDMGR_REALM", "SERVER_ERRNO_TO_ERROR", "ERROR_TO_GENERAL_ERROR_CLASS"],