Bug 1375568 - [Form Autofill] Merge FormAutofillHeuristics into FormAutofillHandler.jsm. r=MattN draft
authorLuke Chang <lchang@mozilla.com>
Fri, 21 Jul 2017 14:55:51 +0800
changeset 614131 6e1a06e8640f2507b2124dfaeb93b13169ad3af1
parent 614130 f09b5a0819d2ba7e09e99a08528e5cd64226c9e1
child 614132 02f4e14591fc5e0f6f40368e0f46ce182d25c388
push id69932
push userbmo:lchang@mozilla.com
push dateMon, 24 Jul 2017 07:36:51 +0000
reviewersMattN
bugs1375568
milestone56.0a1
Bug 1375568 - [Form Autofill] Merge FormAutofillHeuristics into FormAutofillHandler.jsm. r=MattN MozReview-Commit-ID: LlIFFW3vBkI
browser/extensions/formautofill/FormAutofillContent.jsm
browser/extensions/formautofill/FormAutofillHandler.jsm
browser/extensions/formautofill/FormAutofillHeuristics.jsm
browser/extensions/formautofill/FormAutofillUtils.jsm
browser/extensions/formautofill/test/unit/head.js
browser/extensions/formautofill/test/unit/test_extractLabelStrings.js
browser/extensions/formautofill/test/unit/test_findLabelElements.js
browser/extensions/formautofill/test/unit/test_getCategoriesFromFieldNames.js
browser/extensions/formautofill/test/unit/test_getInfo.js
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -100,17 +100,17 @@ AutofillProfileAutoCompleteSearch.protot
         onSearchResult: (search, result) => {
           listener.onSearchResult(this, result);
           ProfileAutocomplete.setProfileAutoCompleteResult(result);
         },
       });
       return;
     }
 
-    let collectionName = FormAutofillUtils.isAddressField(info.fieldName) ?
+    let collectionName = FormAutofillHandler.prototype.isAddressField(info.fieldName) ?
       "addresses" : "creditCards";
 
     this._getRecords({collectionName, info, searchString}).then((records) => {
       if (this.forceStop) {
         return;
       }
       // Sort addresses by timeLastUsed for showing the lastest used address at top.
       records.sort((a, b) => b.timeLastUsed - a.timeLastUsed);
@@ -455,21 +455,16 @@ var FormAutofillContent = {
   identifyAutofillFields(element) {
     this.log.debug("identifyAutofillFields:", "" + element.ownerDocument.location);
 
     if (!this.savedFieldNames) {
       this.log.debug("identifyAutofillFields: savedFieldNames are not known yet");
       Services.cpmm.sendAsyncMessage("FormAutofill:InitStorage");
     }
 
-    if (!FormAutofillUtils.isFieldEligibleForAutofill(element)) {
-      this.log.debug("Not an eligible field.");
-      return;
-    }
-
     let formHandler = this.getFormHandler(element);
     if (!formHandler) {
       let formLike = FormLikeFactory.createFromField(element);
       formHandler = new FormAutofillHandler(formLike);
     } else if (!formHandler.isFormChangedSinceLastCollection) {
       this.log.debug("No control is removed or inserted since last collection.");
       return;
     }
--- a/browser/extensions/formautofill/FormAutofillHandler.jsm
+++ b/browser/extensions/formautofill/FormAutofillHandler.jsm
@@ -3,26 +3,29 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*
  * Defines a handler object to represent forms that autofill can handle.
  */
 
 "use strict";
 
-this.EXPORTED_SYMBOLS = ["FormAutofillHandler"];
+this.EXPORTED_SYMBOLS = ["FormAutofillHandler", "FormAutofillHeuristics"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
+Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
 Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillHeuristics",
-                                  "resource://formautofill/FormAutofillHeuristics.jsm");
+const PREF_HEURISTICS_ENABLED = "extensions.formautofill.heuristics.enabled";
+
+const ALLOWED_TYPES = Array.from(FormAutofillUtils.ALLOWED_TYPES);
+const FIELD_NAME_INFO = Object.assign({}, FormAutofillUtils.FIELD_NAME_INFO);
+const AUTOFILL_FIELDS_THRESHOLD = FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD;
 
 this.log = null;
 FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
 
 /**
  * Handles profile autofill for a DOM Form element.
  * @param {FormLike} form Form that need to be auto filled
  */
@@ -64,17 +67,17 @@ FormAutofillHandler.prototype = {
 
   /**
    * Similiar to `fieldDetails`, and `creditCardFieldDetails` contains the
    * Credit Card records only.
    */
   creditCardFieldDetails: null,
 
   get isValidAddressForm() {
-    return this.addressFieldDetails.length > FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD;
+    return this.addressFieldDetails.length > AUTOFILL_FIELDS_THRESHOLD;
   },
 
   get isValidCreditCardForm() {
     return this.creditCardFieldDetails.some(i => i.fieldName == "cc-number");
   },
 
   /**
    * String of the filled profile's guid.
@@ -112,20 +115,20 @@ FormAutofillHandler.prototype = {
   collectFormFields() {
     this._cacheValue.allFieldNames = null;
     this._formFieldCount = this.form.elements.length;
     let fieldDetails = FormAutofillHeuristics.getFormInfo(this.form);
     this.fieldDetails = fieldDetails ? fieldDetails : [];
     log.debug("Collected details on", this.fieldDetails.length, "fields");
 
     this.addressFieldDetails = this.fieldDetails.filter(
-      detail => FormAutofillUtils.isAddressField(detail.fieldName)
+      detail => this.isAddressField(detail.fieldName)
     );
     this.creditCardFieldDetails = this.fieldDetails.filter(
-      detail => FormAutofillUtils.isCreditCardField(detail.fieldName)
+      detail => this.isCreditCardField(detail.fieldName)
     );
   },
 
   getFieldDetailByName(fieldName) {
     return this.fieldDetails.find(detail => detail.fieldName == fieldName);
   },
 
   _cacheValue: {
@@ -395,9 +398,292 @@ FormAutofillHandler.prototype = {
         return;
       }
 
       profile[detail.fieldName] = value;
     });
 
     return profile;
   },
+
+  isAddressField(fieldName) {
+    return !!FIELD_NAME_INFO[fieldName] && !this.isCreditCardField(fieldName);
+  },
+
+  isCreditCardField(fieldName) {
+    return FIELD_NAME_INFO[fieldName] == "creditCard";
+  },
 };
+
+/**
+ * Returns the autocomplete information of fields according to heuristics.
+ */
+this.FormAutofillHeuristics = {
+  FIELD_GROUPS: {
+    NAME: [
+      "name",
+      "given-name",
+      "additional-name",
+      "family-name",
+    ],
+    ADDRESS: [
+      "organization",
+      "street-address",
+      "address-line1",
+      "address-line2",
+      "address-line3",
+      "address-level2",
+      "address-level1",
+      "postal-code",
+      "country",
+    ],
+    TEL: ["tel"],
+    EMAIL: ["email"],
+  },
+
+  RULES: null,
+
+  getFormInfo(form) {
+    let fieldDetails = [];
+    if (form.autocomplete == "off") {
+      return [];
+    }
+    for (let element of form.elements) {
+      // Exclude elements to which no autocomplete field has been assigned.
+      let info = this.getInfo(element, fieldDetails);
+      if (!info) {
+        continue;
+      }
+
+      // Store the association between the field metadata and the element.
+      if (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.
+        log.debug("Not collecting a field matching another with the same info:", info);
+        continue;
+      }
+
+      let formatWithElement = {
+        section: info.section,
+        addressType: info.addressType,
+        contactType: info.contactType,
+        fieldName: info.fieldName,
+        elementWeakRef: Cu.getWeakReference(element),
+      };
+
+      fieldDetails.push(formatWithElement);
+    }
+
+    return fieldDetails;
+  },
+
+  /**
+   * Get the autocomplete info (e.g. fieldName) determined by the regexp
+   * (this.RULES) matching to a feature string. The result is based on the
+   * existing field names to prevent duplicating predictions
+   * (e.g. address-line[1-3).
+   *
+   * @param {string} string a feature string to be determined.
+   * @param {Array<string>} existingFieldNames the array of exising field names
+   *                        in a form.
+   * @returns {Object}
+   *          Provide the predicting result including the field name.
+   *
+   */
+  _matchStringToFieldName(string, existingFieldNames) {
+    let result = {
+      fieldName: "",
+      section: "",
+      addressType: "",
+      contactType: "",
+    };
+    if (this.RULES.email.test(string)) {
+      result.fieldName = "email";
+      return result;
+    }
+    if (this.RULES.tel.test(string)) {
+      result.fieldName = "tel";
+      return result;
+    }
+    for (let fieldName of this.FIELD_GROUPS.ADDRESS) {
+      if (this.RULES[fieldName].test(string)) {
+        // If "address-line1" or "address-line2" exist already, the string
+        // could be satisfied with "address-line2" or "address-line3".
+        if ((fieldName == "address-line1" &&
+            existingFieldNames.includes("address-line1")) ||
+            (fieldName == "address-line2" &&
+            existingFieldNames.includes("address-line2"))) {
+          continue;
+        }
+        result.fieldName = fieldName;
+        return result;
+      }
+    }
+    for (let fieldName of this.FIELD_GROUPS.NAME) {
+      if (this.RULES[fieldName].test(string)) {
+        result.fieldName = fieldName;
+        return result;
+      }
+    }
+    return null;
+  },
+
+  getInfo(element, fieldDetails) {
+    let autocomplete = element.autocomplete;
+    let tagName = element.tagName;
+    let type = element.type;
+
+    if (autocomplete == "off") {
+      return null;
+    } else if (tagName == "INPUT") {
+      if (!ALLOWED_TYPES.includes(type)) {
+        return null;
+      }
+    } else if (tagName != "SELECT") {
+      return null;
+    }
+
+    // An input[autocomplete="on"] will not be early return here since it stll
+    // needs to find the field name.
+    if (autocomplete != "on") {
+      let info = element.getAutocompleteInfo();
+      if (info && info.fieldName) {
+        return info;
+      }
+    }
+
+    if (!this._prefEnabled) {
+      return null;
+    }
+
+    // "email" type of input is accurate for heuristics to determine its Email
+    // field or not. However, "tel" type is used for ZIP code for some web site
+    // (e.g. HomeDepot, BestBuy), so "tel" type should be not used for "tel"
+    // prediction.
+    if (type == "email") {
+      return {
+        fieldName: "email",
+        section: "",
+        addressType: "",
+        contactType: "",
+      };
+    }
+
+    let existingFieldNames = fieldDetails ? fieldDetails.map(i => i.fieldName) : [];
+
+    for (let elementString of [element.id, element.name]) {
+      let fieldNameResult = this._matchStringToFieldName(elementString,
+                                                         existingFieldNames);
+      if (fieldNameResult) {
+        return fieldNameResult;
+      }
+    }
+    let labels = this.findLabelElements(element);
+    if (!labels || labels.length == 0) {
+      log.debug("No label found for", element);
+      return null;
+    }
+    for (let label of labels) {
+      let strings = this.extractLabelStrings(label);
+      for (let string of strings) {
+        let fieldNameResult = this._matchStringToFieldName(string,
+                                                           existingFieldNames);
+        if (fieldNameResult) {
+          return fieldNameResult;
+        }
+      }
+    }
+
+    return null;
+  },
+
+  // The tag name list is from Chromium except for "STYLE":
+  // eslint-disable-next-line max-len
+  // https://cs.chromium.org/chromium/src/components/autofill/content/renderer/form_autofill_util.cc?l=216&rcl=d33a171b7c308a64dc3372fac3da2179c63b419e
+  EXCLUDED_TAGS: ["SCRIPT", "NOSCRIPT", "OPTION", "STYLE"],
+  /**
+   * Extract all strings of an element's children to an array.
+   * "element.textContent" is a string which is merged of all children nodes,
+   * and this function provides an array of the strings contains in an element.
+   *
+   * @param  {Object} element
+   *         A DOM element to be extracted.
+   * @returns {Array}
+   *          All strings in an element.
+   */
+  extractLabelStrings(element) {
+    let strings = [];
+    let _extractLabelStrings = (el) => {
+      if (this.EXCLUDED_TAGS.includes(el.tagName)) {
+        return;
+      }
+
+      if (el.nodeType == Ci.nsIDOMNode.TEXT_NODE ||
+          el.childNodes.length == 0) {
+        let trimmedText = el.textContent.trim();
+        if (trimmedText) {
+          strings.push(trimmedText);
+        }
+        return;
+      }
+
+      for (let node of el.childNodes) {
+        if (node.nodeType != Ci.nsIDOMNode.ELEMENT_NODE &&
+            node.nodeType != Ci.nsIDOMNode.TEXT_NODE) {
+          continue;
+        }
+        _extractLabelStrings(node);
+      }
+    };
+    _extractLabelStrings(element);
+    return strings;
+  },
+
+  findLabelElements(element) {
+    let document = element.ownerDocument;
+    let id = element.id;
+    let labels = [];
+    // TODO: querySelectorAll is inefficient here. However, bug 1339726 is for
+    // a more efficient implementation from DOM API perspective. This function
+    // should be refined after input.labels API landed.
+    for (let label of document.querySelectorAll("label[for]")) {
+      if (id == label.htmlFor) {
+        labels.push(label);
+      }
+    }
+
+    if (labels.length > 0) {
+      log.debug("Label found by ID", id);
+      return labels;
+    }
+
+    let parent = element.parentNode;
+    if (!parent) {
+      return [];
+    }
+    do {
+      if (parent.tagName == "LABEL" &&
+          parent.control == element &&
+          !parent.hasAttribute("for")) {
+        log.debug("Label found in input's parent or ancestor.");
+        return [parent];
+      }
+      parent = parent.parentNode;
+    } while (parent);
+
+    return [];
+  },
+};
+
+XPCOMUtils.defineLazyGetter(this.FormAutofillHeuristics, "RULES", () => {
+  let sandbox = {};
+  let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
+                       .getService(Ci.mozIJSSubScriptLoader);
+  const HEURISTICS_REGEXP = "chrome://formautofill/content/heuristicsRegexp.js";
+  scriptLoader.loadSubScript(HEURISTICS_REGEXP, sandbox, "utf-8");
+  return sandbox.HeuristicsRegExp.RULES;
+});
+
+XPCOMUtils.defineLazyGetter(this.FormAutofillHeuristics, "_prefEnabled", () => {
+  return Services.prefs.getBoolPref(PREF_HEURISTICS_ENABLED);
+});
deleted file mode 100644
--- a/browser/extensions/formautofill/FormAutofillHeuristics.jsm
+++ /dev/null
@@ -1,301 +0,0 @@
-/* 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/. */
-
-/*
- * Form Autofill field heuristics.
- */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["FormAutofillHeuristics"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://formautofill/FormAutofillUtils.jsm");
-
-this.log = null;
-FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
-
-const PREF_HEURISTICS_ENABLED = "extensions.formautofill.heuristics.enabled";
-
-const ALLOWED_TYPES = Array.from(FormAutofillUtils.ALLOWED_TYPES);
-
-/**
- * Returns the autocomplete information of fields according to heuristics.
- */
-this.FormAutofillHeuristics = {
-  FIELD_GROUPS: {
-    NAME: [
-      "name",
-      "given-name",
-      "additional-name",
-      "family-name",
-    ],
-    ADDRESS: [
-      "organization",
-      "street-address",
-      "address-line1",
-      "address-line2",
-      "address-line3",
-      "address-level2",
-      "address-level1",
-      "postal-code",
-      "country",
-    ],
-    TEL: ["tel"],
-    EMAIL: ["email"],
-  },
-
-  RULES: null,
-
-  getFormInfo(form) {
-    let fieldDetails = [];
-    if (form.autocomplete == "off") {
-      return [];
-    }
-    for (let element of form.elements) {
-      // Exclude elements to which no autocomplete field has been assigned.
-      let info = this.getInfo(element, fieldDetails);
-      if (!info) {
-        continue;
-      }
-
-      // Store the association between the field metadata and the element.
-      if (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.
-        log.debug("Not collecting a field matching another with the same info:", info);
-        continue;
-      }
-
-      let formatWithElement = {
-        section: info.section,
-        addressType: info.addressType,
-        contactType: info.contactType,
-        fieldName: info.fieldName,
-        elementWeakRef: Cu.getWeakReference(element),
-      };
-
-      fieldDetails.push(formatWithElement);
-    }
-
-    return fieldDetails;
-  },
-
-  /**
-   * Get the autocomplete info (e.g. fieldName) determined by the regexp
-   * (this.RULES) matching to a feature string. The result is based on the
-   * existing field names to prevent duplicating predictions
-   * (e.g. address-line[1-3).
-   *
-   * @param {string} string a feature string to be determined.
-   * @param {Array<string>} existingFieldNames the array of exising field names
-   *                        in a form.
-   * @returns {Object}
-   *          Provide the predicting result including the field name.
-   *
-   */
-  _matchStringToFieldName(string, existingFieldNames) {
-    let result = {
-      fieldName: "",
-      section: "",
-      addressType: "",
-      contactType: "",
-    };
-    if (this.RULES.email.test(string)) {
-      result.fieldName = "email";
-      return result;
-    }
-    if (this.RULES.tel.test(string)) {
-      result.fieldName = "tel";
-      return result;
-    }
-    for (let fieldName of this.FIELD_GROUPS.ADDRESS) {
-      if (this.RULES[fieldName].test(string)) {
-        // If "address-line1" or "address-line2" exist already, the string
-        // could be satisfied with "address-line2" or "address-line3".
-        if ((fieldName == "address-line1" &&
-            existingFieldNames.includes("address-line1")) ||
-            (fieldName == "address-line2" &&
-            existingFieldNames.includes("address-line2"))) {
-          continue;
-        }
-        result.fieldName = fieldName;
-        return result;
-      }
-    }
-    for (let fieldName of this.FIELD_GROUPS.NAME) {
-      if (this.RULES[fieldName].test(string)) {
-        result.fieldName = fieldName;
-        return result;
-      }
-    }
-    return null;
-  },
-
-  getInfo(element, fieldDetails) {
-    let autocomplete = element.autocomplete;
-    let tagName = element.tagName;
-    let type = element.type;
-
-    if (autocomplete == "off") {
-      return null;
-    } else if (tagName == "INPUT") {
-      if (!ALLOWED_TYPES.includes(type)) {
-        return null;
-      }
-    } else if (tagName != "SELECT") {
-      return null;
-    }
-
-    // An input[autocomplete="on"] will not be early return here since it stll
-    // needs to find the field name.
-    let info = autocomplete && autocomplete != "on" ? element.getAutocompleteInfo() : null;
-    if (info && info.fieldName) {
-      return info;
-    }
-
-    if (!this._prefEnabled) {
-      return null;
-    }
-
-    // "email" type of input is accurate for heuristics to determine its Email
-    // field or not. However, "tel" type is used for ZIP code for some web site
-    // (e.g. HomeDepot, BestBuy), so "tel" type should be not used for "tel"
-    // prediction.
-    if (type == "email") {
-      return {
-        fieldName: "email",
-        section: "",
-        addressType: "",
-        contactType: "",
-      };
-    }
-
-    let existingFieldNames = fieldDetails ? fieldDetails.map(i => i.fieldName) : [];
-
-    for (let elementString of [element.id, element.name]) {
-      let fieldNameResult = this._matchStringToFieldName(elementString,
-                                                         existingFieldNames);
-      if (fieldNameResult) {
-        return fieldNameResult;
-      }
-    }
-    let labels = this.findLabelElements(element);
-    if (!labels || labels.length == 0) {
-      log.debug("No label found for", element);
-      return null;
-    }
-    for (let label of labels) {
-      let strings = this.extractLabelStrings(label);
-      for (let string of strings) {
-        let fieldNameResult = this._matchStringToFieldName(string,
-                                                           existingFieldNames);
-        if (fieldNameResult) {
-          return fieldNameResult;
-        }
-      }
-    }
-
-    return null;
-  },
-
-  // The tag name list is from Chromium except for "STYLE":
-  // eslint-disable-next-line max-len
-  // https://cs.chromium.org/chromium/src/components/autofill/content/renderer/form_autofill_util.cc?l=216&rcl=d33a171b7c308a64dc3372fac3da2179c63b419e
-  EXCLUDED_TAGS: ["SCRIPT", "NOSCRIPT", "OPTION", "STYLE"],
-  /**
-   * Extract all strings of an element's children to an array.
-   * "element.textContent" is a string which is merged of all children nodes,
-   * and this function provides an array of the strings contains in an element.
-   *
-   * @param  {Object} element
-   *         A DOM element to be extracted.
-   * @returns {Array}
-   *          All strings in an element.
-   */
-  extractLabelStrings(element) {
-    let strings = [];
-    let _extractLabelStrings = (el) => {
-      if (this.EXCLUDED_TAGS.includes(el.tagName)) {
-        return;
-      }
-
-      if (el.nodeType == Ci.nsIDOMNode.TEXT_NODE ||
-          el.childNodes.length == 0) {
-        let trimmedText = el.textContent.trim();
-        if (trimmedText) {
-          strings.push(trimmedText);
-        }
-        return;
-      }
-
-      for (let node of el.childNodes) {
-        if (node.nodeType != Ci.nsIDOMNode.ELEMENT_NODE &&
-            node.nodeType != Ci.nsIDOMNode.TEXT_NODE) {
-          continue;
-        }
-        _extractLabelStrings(node);
-      }
-    };
-    _extractLabelStrings(element);
-    return strings;
-  },
-
-  findLabelElements(element) {
-    let document = element.ownerDocument;
-    let id = element.id;
-    let labels = [];
-    // TODO: querySelectorAll is inefficient here. However, bug 1339726 is for
-    // a more efficient implementation from DOM API perspective. This function
-    // should be refined after input.labels API landed.
-    for (let label of document.querySelectorAll("label[for]")) {
-      if (id == label.htmlFor) {
-        labels.push(label);
-      }
-    }
-
-    if (labels.length > 0) {
-      log.debug("Label found by ID", id);
-      return labels;
-    }
-
-    let parent = element.parentNode;
-    if (!parent) {
-      return [];
-    }
-    do {
-      if (parent.tagName == "LABEL" &&
-          parent.control == element &&
-          !parent.hasAttribute("for")) {
-        log.debug("Label found in input's parent or ancestor.");
-        return [parent];
-      }
-      parent = parent.parentNode;
-    } while (parent);
-
-    return [];
-  },
-};
-
-XPCOMUtils.defineLazyGetter(this.FormAutofillHeuristics, "RULES", () => {
-  let sandbox = {};
-  let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
-                       .getService(Ci.mozIJSSubScriptLoader);
-  const HEURISTICS_REGEXP = "chrome://formautofill/content/heuristicsRegexp.js";
-  scriptLoader.loadSubScript(HEURISTICS_REGEXP, sandbox, "utf-8");
-  return sandbox.HeuristicsRegExp.RULES;
-});
-
-XPCOMUtils.defineLazyGetter(this.FormAutofillHeuristics, "_prefEnabled", () => {
-  return Services.prefs.getBoolPref(PREF_HEURISTICS_ENABLED);
-});
-
-Services.prefs.addObserver(PREF_HEURISTICS_ENABLED, () => {
-  this.FormAutofillHeuristics._prefEnabled = Services.prefs.getBoolPref(PREF_HEURISTICS_ENABLED);
-});
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -10,17 +10,17 @@ const {classes: Cc, interfaces: Ci, util
 
 const ADDRESS_REFERENCES = "chrome://formautofill/content/addressReferences.js";
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 this.FormAutofillUtils = {
   get AUTOFILL_FIELDS_THRESHOLD() { return 3; },
 
-  _fieldNameInfo: {
+  FIELD_NAME_INFO: {
     "name": "name",
     "given-name": "name",
     "additional-name": "name",
     "family-name": "name",
     "organization": "organization",
     "street-address": "address",
     "address-line1": "address",
     "address-line2": "address",
@@ -40,26 +40,18 @@ this.FormAutofillUtils = {
     "email": "email",
     "cc-name": "creditCard",
     "cc-number": "creditCard",
     "cc-exp-month": "creditCard",
     "cc-exp-year": "creditCard",
   },
   _addressDataLoaded: false,
 
-  isAddressField(fieldName) {
-    return !!this._fieldNameInfo[fieldName] && !this.isCreditCardField(fieldName);
-  },
-
-  isCreditCardField(fieldName) {
-    return this._fieldNameInfo[fieldName] == "creditCard";
-  },
-
   getCategoryFromFieldName(fieldName) {
-    return this._fieldNameInfo[fieldName];
+    return this.FIELD_NAME_INFO[fieldName];
   },
 
   getCategoriesFromFieldNames(fieldNames) {
     let categories = new Set();
     for (let fieldName of fieldNames) {
       let info = this.getCategoryFromFieldName(fieldName);
       if (info) {
         categories.add(info);
--- a/browser/extensions/formautofill/test/unit/head.js
+++ b/browser/extensions/formautofill/test/unit/head.js
@@ -1,12 +1,13 @@
 /**
  * Provides infrastructure for automated formautofill components tests.
  */
 
+/* global FormAutofillHeuristics */
 /* exported getTempFile, loadFormAutofillContent, runHeuristicsTest, sinon,
  *          initProfileStorage, getSyncChangeCounter, objectMatches
  */
 
 "use strict";
 
 var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
@@ -113,17 +114,17 @@ async function initProfileStorage(fileNa
     do_check_true(profileStorage.addresses.add(record));
     await onChanged;
   }
   await profileStorage._saveImmediately();
   return profileStorage;
 }
 
 function runHeuristicsTest(patterns, fixturePathPrefix) {
-  Cu.import("resource://formautofill/FormAutofillHeuristics.jsm");
+  Cu.import("resource://formautofill/FormAutofillHandler.jsm");
   Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
   patterns.forEach(testPattern => {
     add_task(async function() {
       do_print("Starting test fixture: " + testPattern.fixturePath);
       let file = do_get_file(fixturePathPrefix + testPattern.fixturePath);
       let doc = MockDocument.createTestDocumentFromFile("http://localhost:8080/test/", file);
 
--- a/browser/extensions/formautofill/test/unit/test_extractLabelStrings.js
+++ b/browser/extensions/formautofill/test/unit/test_extractLabelStrings.js
@@ -1,11 +1,11 @@
 "use strict";
 
-Cu.import("resource://formautofill/FormAutofillHeuristics.jsm");
+Cu.import("resource://formautofill/FormAutofillHandler.jsm");
 
 const TESTCASES = [
   {
     description: "A label element contains one input element.",
     document: `<label id="typeA"> label type A
                  <!-- This comment should not be extracted. -->
                  <input type="text">
                  <script>FOO</script>
--- a/browser/extensions/formautofill/test/unit/test_findLabelElements.js
+++ b/browser/extensions/formautofill/test/unit/test_findLabelElements.js
@@ -1,11 +1,11 @@
 "use strict";
 
-Cu.import("resource://formautofill/FormAutofillHeuristics.jsm");
+Cu.import("resource://formautofill/FormAutofillHandler.jsm");
 
 const TESTCASES = [
   {
     description: "Input contains in a label element.",
     document: `<form>
                  <label id="labelA"> label type A
                    <input id="typeA" type="text">
                  </label>
--- a/browser/extensions/formautofill/test/unit/test_getCategoriesFromFieldNames.js
+++ b/browser/extensions/formautofill/test/unit/test_getCategoriesFromFieldNames.js
@@ -1,12 +1,13 @@
 
 "use strict";
 
 Cu.import("resource://formautofill/FormAutofillUtils.jsm");
+Cu.import("resource://formautofill/FormAutofillHandler.jsm");
 
 add_task(async function test_isAddressField_isCreditCardField() {
   const TEST_CASES = {
     "given-name": {
       isAddressField: true,
       isCreditCardField: false,
     },
     "organization": {
@@ -37,20 +38,20 @@ add_task(async function test_isAddressFi
       isAddressField: false,
       isCreditCardField: false,
     },
   };
 
   for (let fieldName of Object.keys(TEST_CASES)) {
     do_print("Starting testcase: " + fieldName);
     let info = TEST_CASES[fieldName];
-    Assert.equal(FormAutofillUtils.isAddressField(fieldName),
+    Assert.equal(FormAutofillHandler.prototype.isAddressField(fieldName),
                  info.isAddressField,
                  "isAddressField");
-    Assert.equal(FormAutofillUtils.isCreditCardField(fieldName),
+    Assert.equal(FormAutofillHandler.prototype.isCreditCardField(fieldName),
                  info.isCreditCardField,
                  "isCreditCardField");
   }
 });
 
 add_task(async function test_getCategoriesFromFieldNames() {
   const TEST_CASES = [
     {
--- a/browser/extensions/formautofill/test/unit/test_getInfo.js
+++ b/browser/extensions/formautofill/test/unit/test_getInfo.js
@@ -1,11 +1,11 @@
 "use strict";
 
-Cu.import("resource://formautofill/FormAutofillHeuristics.jsm");
+Cu.import("resource://formautofill/FormAutofillHandler.jsm");
 
 const TESTCASES = [
   {
     description: "Input element in a label element",
     document: `<form>
                  <label> E-Mail
                    <input id="targetElement" type="text">
                  </label>