Bug 1349490 - Part 2: Use a set of regexp to recognize the input autofill type.; r=MattN
authorSean Lee <selee@mozilla.com>
Fri, 28 Apr 2017 17:14:15 -0700
changeset 409535 3c62252be2db131d09cc38838642b7d10788b946
parent 409534 862c6eb8b0e3bf1600e63dc7cefd442eb80486a4
child 409536 7e3416b4e5fbe92c7afeadf70df91e28ac068c66
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN
bugs1349490
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1349490 - Part 2: Use a set of regexp to recognize the input autofill type.; r=MattN MozReview-Commit-ID: B6ypdjBxlIV
browser/extensions/formautofill/FormAutofillHeuristics.jsm
browser/extensions/formautofill/content/heuristicsRegexp.js
browser/extensions/formautofill/test/fixtures/autocomplete_basic.html
browser/extensions/formautofill/test/unit/head.js
browser/extensions/formautofill/test/unit/heuristics/test_basic.js
browser/extensions/formautofill/test/unit/heuristics/third_party/test_CostCo.js
browser/extensions/formautofill/test/unit/heuristics/third_party/test_HomeDepot.js
browser/extensions/formautofill/test/unit/heuristics/third_party/test_Walmart.js
browser/extensions/formautofill/test/unit/test_collectFormFields.js
browser/extensions/formautofill/test/unit/test_getInfo.js
browser/extensions/formautofill/test/unit/xpcshell.ini
toolkit/content/license.html
--- a/browser/extensions/formautofill/FormAutofillHeuristics.jsm
+++ b/browser/extensions/formautofill/FormAutofillHeuristics.jsm
@@ -7,48 +7,58 @@
  */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["FormAutofillHeuristics"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
 this.log = null;
 FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
 
 /**
  * Returns the autocomplete information of fields according to heuristics.
  */
 this.FormAutofillHeuristics = {
-  VALID_FIELDS: [
-    "name",
-    "given-name",
-    "additional-name",
-    "family-name",
-    "organization",
-    "street-address",
-    "address-line1",
-    "address-line2",
-    "address-line3",
-    "address-level2",
-    "address-level1",
-    "postal-code",
-    "country",
-    "tel",
-    "email",
-  ],
+  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);
+      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 &&
@@ -67,22 +77,124 @@ this.FormAutofillHeuristics = {
       };
 
       fieldDetails.push(formatWithElement);
     }
 
     return fieldDetails;
   },
 
-  getInfo(element) {
-    if (!(element instanceof Ci.nsIDOMHTMLInputElement)) {
+  /**
+   * 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) {
+    if (!(element instanceof Ci.nsIDOMHTMLInputElement) ||
+        !["text", "email", "tel", "number"].includes(element.type) ||
+        element.autocomplete == "off") {
       return null;
     }
 
     let info = element.getAutocompleteInfo();
-    if (!info || !info.fieldName ||
-        !this.VALID_FIELDS.includes(info.fieldName)) {
+    // An input[autocomplete="on"] will not be early return here since it stll
+    // needs to find the field name.
+    if (info && info.fieldName && info.fieldName != "on") {
+      return info;
+    }
+
+    // "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 (element.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 = FormAutofillUtils.findLabelElements(element);
+    if (!labels || labels.length == 0) {
+      log.debug("No label found for", element);
       return null;
     }
+    for (let label of labels) {
+      let strings = FormAutofillUtils.extractLabelStrings(label);
+      for (let string of strings) {
+        let fieldNameResult = this._matchStringToFieldName(string,
+                                                           existingFieldNames);
+        if (fieldNameResult) {
+          return fieldNameResult;
+        }
+      }
+    }
 
-    return info;
+    return null;
   },
 };
+
+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;
+});
+
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/content/heuristicsRegexp.js
@@ -0,0 +1,201 @@
+/* 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 RegExp.
+ */
+
+/* exported HeuristicsRegExp */
+
+"use strict";
+
+var HeuristicsRegExp = {
+  // These regular expressions are from Chromium source codes [1]. Most of them
+  // converted to JS format have the same meaning with the original ones except
+  // the first line of "address-level1".
+  // [1] https://cs.chromium.org/chromium/src/components/autofill/core/browser/autofill_regex_constants.cc
+  RULES: {
+    // ==== Email ====
+    "email": new RegExp(
+      "e.?mail" +
+      "|courriel" +                                 // fr
+      "|メールアドレス" +                           // ja-JP
+      "|Электронной.?Почты" +                       // ru
+      "|邮件|邮箱" +                                // zh-CN
+      "|電郵地址" +                                 // zh-TW
+      "|(?:이메일|전자.?우편|[Ee]-?mail)(.?주소)?", // ko-KR
+      "iu"
+    ),
+
+    // ==== Telephone ====
+    "tel": new RegExp(
+      "phone|mobile|contact.?number" +
+      "|telefonnummer" +                             // de-DE
+      "|telefono|teléfono" +                         // es
+      "|telfixe" +                                   // fr-FR
+      "|電話" +                                      // ja-JP
+      "|telefone|telemovel" +                        // pt-BR, pt-PT
+      "|телефон" +                                   // ru
+      "|电话" +                                      // zh-CN
+      "|(?:전화|핸드폰|휴대폰|휴대전화)(?:.?번호)?", // ko-KR
+      "iu"
+    ),
+
+    // ==== Address Fields ====
+    "organization": new RegExp(
+      "company|business|organization|organisation" +
+      "|firma|firmenname" +   // de-DE
+      "|empresa" +            // es
+      "|societe|société" +    // fr-FR
+      "|ragione.?sociale" +   // it-IT
+      "|会社" +               // ja-JP
+      "|название.?компании" + // ru
+      "|单位|公司" +          // zh-CN
+      "|회사|직장",           // ko-KR
+      "iu"
+    ),
+    "street-address": new RegExp(
+      "streetaddress|street-address",
+      "iu"
+    ),
+    "address-line1": new RegExp(
+      "^address$|address[_-]?line(one)?|address1|addr1|street" +
+      "|(?:shipping|billing)address$" +
+      "|strasse|straße|hausnummer|housenumber" + // de-DE
+      "|house.?name" + // en-GB
+      "|direccion|dirección" + // es
+      "|adresse" + // fr-FR
+      "|indirizzo" + // it-IT
+      "|^住所$|住所1" + // ja-JP
+      "|morada|endereço" +  // pt-BR, pt-PT
+      "|Адрес" + // ru
+      "|地址" +  // zh-CN
+      "|^주소.?$|주소.?1",  // ko-KR
+      "iu"
+    ),
+    "address-line2": new RegExp(
+      "address[_-]?line(2|two)|address2|addr2|street|suite|unit" +
+      "|adresszusatz|ergänzende.?angaben" + // de-DE
+      "|direccion2|colonia|adicional" + // es
+      "|addresssuppl|complementnom|appartement" + // fr-FR
+      "|indirizzo2" + // it-IT
+      "|住所2" + // ja-JP
+      "|complemento|addrcomplement" + // pt-BR, pt-PT
+      "|Улица" + // ru
+      "|地址2" + // zh-CN
+      "|주소.?2",  // ko-KR
+      "iu"
+    ),
+    "address-line3": new RegExp(
+      "address[_-]?line(3|three)|address3|addr3|street|suite|unit" +
+      "|adresszusatz|ergänzende.?angaben" + // de-DE
+      "|direccion3|colonia|adicional" + // es
+      "|addresssuppl|complementnom|appartement" + // fr-FR
+      "|indirizzo3" + // it-IT
+      "|住所3" + // ja-JP
+      "|complemento|addrcomplement" + // pt-BR, pt-PT
+      "|Улица" + // ru
+      "|地址3" + // zh-CN
+      "|주소.?3",  // ko-KR
+      "iu"
+    ),
+    "address-level2": new RegExp(
+      "city|town" +
+      "|\\bort\\b|stadt" + // de-DE
+      "|suburb" + // en-AU
+      "|ciudad|provincia|localidad|poblacion" + // es
+      "|ville|commune" + // fr-FR
+      "|localita" +  // it-IT
+      "|市区町村" +  // ja-JP
+      "|cidade" + // pt-BR, pt-PT
+      "|Город" + // ru
+      "|市" + // zh-CN
+      "|分區" + // zh-TW
+      "|^시[^도·・]|시[·・]?군[·・]?구",  // ko-KR
+      "iu"
+    ),
+    "address-level1": new RegExp(
+      // TODO: [Bug 1358960] JS does not support backward matching, and we
+      // should apply this pattern in JS rather than regexp.
+      // "(?<!united )state|county|region|province"
+      "state|county|region|province" +
+      "|land" + // de-DE
+      "|county|principality" + // en-UK
+      "|都道府県" + // ja-JP
+      "|estado|provincia" + // pt-BR, pt-PT
+      "|область" + // ru
+      "|省" + // zh-CN
+      "|地區" + // zh-TW
+      "|^시[·・]?도",  // ko-KR
+      "iu"
+    ),
+    "postal-code": new RegExp(
+      "zip|postal|post.*code|pcode" +
+      "|pin.?code" +               // en-IN
+      "|postleitzahl" +            // de-DE
+      "|\\bcp\\b" +                // es
+      "|\\bcdp\\b" +               // fr-FR
+      "|\\bcap\\b" +               // it-IT
+      "|郵便番号" +                // ja-JP
+      "|codigo|codpos|\\bcep\\b" + // pt-BR, pt-PT
+      "|Почтовый.?Индекс" +        // ru
+      "|邮政编码|邮编" +           // zh-CN
+      "|郵遞區號" +                // zh-TW
+      "|우편.?번호",               // ko-KR
+      "iu"
+    ),
+    "country": new RegExp(
+      "country|countries" +
+      "|país|pais" + // es
+      "|国" +        // ja-JP
+      "|国家" +      // zh-CN
+      "|국가|나라",  // ko-KR
+      "iu"
+    ),
+
+    // ==== Name Fields ====
+    "given-name": new RegExp(
+      "first.*name|initials|fname|first$|given.*name" +
+      "|vorname" +                // de-DE
+      "|nombre" +                 // es
+      "|forename|prénom|prenom" + // fr-FR
+      "|名" +                     // ja-JP
+      "|nome" +                   // pt-BR, pt-PT
+      "|Имя" +                    // ru
+      "|이름",                    // ko-KR
+      "iu"
+    ),
+    "additional-name": new RegExp(
+      "middle.*name|mname|middle$" +
+      "|apellido.?materno|lastlastname" + // es
+
+      // This rule is for middle initial.
+      "middle.*initial|m\\.i\\.|mi$|\\bmi\\b",
+      "iu"
+    ),
+    "family-name": new RegExp(
+      "last.*name|lname|surname|last$|secondname|family.*name" +
+      "|nachname" +                           // de-DE
+      "|apellido" +                           // es
+      "|famille|^nom" +                       // fr-FR
+      "|cognome" +                            // it-IT
+      "|姓" +                                 // ja-JP
+      "|morada|apelidos|surename|sobrenome" + // pt-BR, pt-PT
+      "|Фамилия" +                            // ru
+      "|\\b성(?:[^명]|\\b)",                  // ko-KR
+      "iu"
+    ),
+    "name": new RegExp(
+      "^name|full.?name|your.?name|customer.?name|bill.?name|ship.?name" +
+      "|name.*first.*last|firstandlastname" +
+      "|nombre.*y.*apellidos" + // es
+      "|^nom" +                 // fr-FR
+      "|お名前|氏名" +          // ja-JP
+      "|^nome" +                // pt-BR, pt-PT
+      "|姓名" +                 // zh-CN
+      "|성명",                  // ko-KR
+      "iu"
+    ),
+  },
+};
--- a/browser/extensions/formautofill/test/fixtures/autocomplete_basic.html
+++ b/browser/extensions/formautofill/test/fixtures/autocomplete_basic.html
@@ -13,11 +13,27 @@
     <p><label>addressLevel1: <input type="text" id="address-level1" name="address-level1" autocomplete="address-level1" /></label></p>
     <p><label>postalCode: <input type="text" id="postal-code" name="postal-code" autocomplete="postal-code" /></label></p>
     <p><label>country: <input type="text" id="country" name="country" autocomplete="country" /></label></p>
     <p><label>tel: <input type="text" id="tel" name="tel" autocomplete="tel" /></label></p>
     <p><label>email: <input type="text" id="email" name="email" autocomplete="email" /></label></p>
     <p><input type="submit" /></p>
     <p><button type="reset">Reset</button></p>
   </form>
+
+  <form id="formB">
+    <p><label>Organization: <input type="text" /></label></p>
+    <p><label><input type="text" id="B_address-line1" /></label></p>
+    <p><label><input type="text" name="address-line2" /></label></p>
+    <p><label><input type="text" id="B_address-line3" name="address-line3" /></label></p>
+    <p><label>City: <input type="text" name="address-level2" /></label></p>
+    <p><label>State: <input type="text" id="B_address-level1" /></label></p>
+    <p><input type="text" id="B_postal-code" name="postal-code" /></p>
+    <p><label>Country: <input type="text" id="B_country" name="country" /></label></p>
+    <p><label>Telephone: <input type="text" id="B_tel" name="tel" /></label></p>
+    <p><label>Email: <input type="text" id="B_email" name="email" /></label></p>
+    <p><input type="submit" /></p>
+    <p><button type="reset">Reset</button></p>
+  </form>
+
 </body>
 </html>
 
--- a/browser/extensions/formautofill/test/unit/head.js
+++ b/browser/extensions/formautofill/test/unit/head.js
@@ -101,18 +101,18 @@ function runHeuristicsTest(patterns, fix
           }
         }
       }
 
       Assert.equal(forms.length, testPattern.expectedResult.length, "Expected form count.");
 
       forms.forEach((form, formIndex) => {
         let formInfo = FormAutofillHeuristics.getFormInfo(form);
-        // TODO: This line should be uncommented to make sure every field are verified.
-        // Assert.equal(formInfo.length, testPattern.expectedResult[formIndex].length, "Expected field count.");
+        do_print("FieldName Prediction Results: " + formInfo.map(i => i.fieldName));
+        Assert.equal(formInfo.length, testPattern.expectedResult[formIndex].length, "Expected field count.");
         formInfo.forEach((field, fieldIndex) => {
           let expectedField = testPattern.expectedResult[formIndex][fieldIndex];
           expectedField.elementWeakRef = field.elementWeakRef;
           Assert.deepEqual(field, expectedField);
         });
       });
     });
   });
--- a/browser/extensions/formautofill/test/unit/heuristics/test_basic.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/test_basic.js
@@ -11,12 +11,24 @@ runHeuristicsTest([
         {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
       ],
+      [
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "organization"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line3"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
+      ],
     ],
   },
 ], "../../fixtures/");
 
--- a/browser/extensions/formautofill/test/unit/heuristics/third_party/test_CostCo.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/third_party/test_CostCo.js
@@ -10,31 +10,31 @@ runHeuristicsTest([
       [],
       [
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "additional-name"}, // middle-name initial
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "organization"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "country"}, // TODO: select,country
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"}, // TODO: fix the regexp
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"}, // city
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // TODO: select,state
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
       ],
       [
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "additional-name"}, // middle-name initial
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "organization"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "country"}, // TODO: select,country
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"}, // TODO: fix the regexp
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"}, // city
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // TODO: select,state
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
       ],
       [],
       [
@@ -60,31 +60,31 @@ runHeuristicsTest([
       [],
       [
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "additional-name"}, // middle-name initial
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "organization"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "country"}, // TODO: select, country
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"}, // TODO: fix the regexp
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"}, // city
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // TODO: select, state
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
       ],
       [
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "additional-name"}, // middle-name initial
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "organization"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "country"}, // TODO: select, country
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"}, // TODO: fix the regexp
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // TODO: select, state
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
       ],
       [],
       [
--- a/browser/extensions/formautofill/test/unit/heuristics/third_party/test_HomeDepot.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/third_party/test_HomeDepot.js
@@ -8,18 +8,18 @@ runHeuristicsTest([
     expectedResult: [
       [
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
       ],
     ],
   }, {
     fixturePath: "SignIn.html",
     expectedResult: [
       [
--- a/browser/extensions/formautofill/test/unit/heuristics/third_party/test_Walmart.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/third_party/test_Walmart.js
@@ -4,18 +4,17 @@
 
 runHeuristicsTest([
   {
     fixturePath: "Checkout.html",
     expectedResult: [
       [
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
       ],
-      [
-      ],
+      [],
       [
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "password"}, // ac-off
       ],
       [
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "email"}, // ac-off
@@ -26,17 +25,17 @@ runHeuristicsTest([
   }, {
     fixturePath: "Payment.html",
     expectedResult: [
       [
       ],
       [
         {"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "family-name"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
+        {"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "cc-number"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
         {"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "tel"},
       ],
     ],
   }, {
     fixturePath: "Shipping.html",
@@ -46,17 +45,17 @@ runHeuristicsTest([
       ],
       [
       ],
       [
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"}, // TODO: fix the regexp
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"}, // city
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // TODO: select, state
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
       ],
     ],
   },
 ], "../../../fixtures/third_party/Walmart/");
 
--- a/browser/extensions/formautofill/test/unit/test_collectFormFields.js
+++ b/browser/extensions/formautofill/test/unit/test_collectFormFields.js
@@ -7,18 +7,26 @@
 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: [],
-    fieldDetails: [],
+    fieldDetails: [
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
+    ],
+    ids: ["given-name", "family-name", "street-addr", "city", "country", "email", "tel"],
   },
   {
     description: "Form with autocomplete properties and 1 token",
     document: `<form><input id="given-name" autocomplete="given-name">
                <input id="family-name" autocomplete="family-name">
                <input id="street-addr" autocomplete="street-address">
                <input id="city" autocomplete="address-level2">
                <input id="country" autocomplete="country">
@@ -79,19 +87,24 @@ for (let tc of TESTCASES) {
     let testcase = tc;
     add_task(function* () {
       do_print("Starting testcase: " + testcase.description);
 
       let doc = MockDocument.createTestDocument("http://localhost:8080/test/",
                                                 testcase.document);
       let form = doc.querySelector("form");
 
-      testcase.fieldDetails.forEach((detail) => {
-        detail.elementWeakRef = Cu.getWeakReference(doc.querySelector(
-          "input[autocomplete*='" + detail.fieldName + "']"));
+      testcase.fieldDetails.forEach((detail, index) => {
+        let elementRef;
+        if (testcase.ids && testcase.ids[index]) {
+          elementRef = doc.getElementById(testcase.ids[index]);
+        } else {
+          elementRef = doc.querySelector("input[autocomplete*='" + detail.fieldName + "']");
+        }
+        detail.elementWeakRef = Cu.getWeakReference(elementRef);
       });
       let handler = new FormAutofillHandler(form);
 
       handler.collectFormFields();
 
       handler.fieldDetails.forEach((detail, index) => {
         Assert.equal(detail.section, testcase.fieldDetails[index].section);
         Assert.equal(detail.addressType, testcase.fieldDetails[index].addressType);
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/test_getInfo.js
@@ -0,0 +1,198 @@
+"use strict";
+
+Cu.import("resource://formautofill/FormAutofillHeuristics.jsm");
+
+const TESTCASES = [
+  {
+    description: "Input element in a label element",
+    document: `<form>
+                 <label> E-Mail
+                   <input id="targetElement" type="text">
+                 </label>
+               </form>`,
+    elementId: "targetElement",
+    expectedReturnValue: {
+      fieldName: "email",
+      section: "",
+      addressType: "",
+      contactType: "",
+    },
+  },
+  {
+    description: "A label element is out of the form contains the related input",
+    document: `<label for="targetElement"> E-Mail</label>
+               <form>
+                 <input id="targetElement" type="text">
+               </form>`,
+    elementId: "targetElement",
+    expectedReturnValue: {
+      fieldName: "email",
+      section: "",
+      addressType: "",
+      contactType: "",
+    },
+  },
+  {
+    description: "A label element contains span element",
+    document: `<label for="targetElement">FOO<span>E-Mail</span>BAR</label>
+               <form>
+                 <input id="targetElement" type="text">
+               </form>`,
+    elementId: "targetElement",
+    expectedReturnValue: {
+      fieldName: "email",
+      section: "",
+      addressType: "",
+      contactType: "",
+    },
+  },
+  {
+    description: "The signature in 'name' attr of an input",
+    document: `<input id="targetElement" name="email" type="text">`,
+    elementId: "targetElement",
+    expectedReturnValue: {
+      fieldName: "email",
+      section: "",
+      addressType: "",
+      contactType: "",
+    },
+  },
+  {
+    description: "The signature in 'id' attr of an input",
+    document: `<input id="targetElement_email" name="tel" type="text">`,
+    elementId: "targetElement_email",
+    expectedReturnValue: {
+      fieldName: "email",
+      section: "",
+      addressType: "",
+      contactType: "",
+    },
+  },
+  {
+    description: "2 address line inputs",
+    document: `<label for="targetElement">street</label>
+               <input id="targetElement" type="text">`,
+    elementId: "targetElement",
+    fieldDetails: [{fieldName: "address-line1"}],
+    expectedReturnValue: {
+      fieldName: "address-line2",
+      section: "",
+      addressType: "",
+      contactType: "",
+    },
+  },
+  {
+    description: "3 address line inputs",
+    document: `<label for="targetElement">street</label>
+               <input id="targetElement" type="text">`,
+    elementId: "targetElement",
+    fieldDetails: [
+      {fieldName: "address-line1"},
+      {fieldName: "address-line2"},
+    ],
+    expectedReturnValue: {
+      fieldName: "address-line3",
+      section: "",
+      addressType: "",
+      contactType: "",
+    },
+  },
+  {
+    description: "CJK character - Traditional Chinese",
+    document: `<label> 郵遞區號
+                 <input id="targetElement" />
+               </label>`,
+    elementId: "targetElement",
+    expectedReturnValue: {
+      fieldName: "postal-code",
+      section: "",
+      addressType: "",
+      contactType: "",
+    },
+  },
+  {
+    description: "CJK character - Japanese",
+    document: `<label> 郵便番号
+                 <input id="targetElement" />
+               </label>`,
+    elementId: "targetElement",
+    expectedReturnValue: {
+      fieldName: "postal-code",
+      section: "",
+      addressType: "",
+      contactType: "",
+    },
+  },
+  {
+    description: "CJK character - Korean",
+    document: `<label> 우편 번호
+                 <input id="targetElement" />
+               </label>`,
+    elementId: "targetElement",
+    expectedReturnValue: {
+      fieldName: "postal-code",
+      section: "",
+      addressType: "",
+      contactType: "",
+    },
+  },
+  {
+    description: "",
+    document: `<input id="targetElement" name="fullname">`,
+    elementId: "targetElement",
+    expectedReturnValue: {
+      fieldName: "name",
+      section: "",
+      addressType: "",
+      contactType: "",
+    },
+  },
+  {
+    description: "non-input element",
+    document: `<label id="targetElement">street</label>`,
+    elementId: "targetElement",
+    expectedReturnValue: null,
+  },
+  {
+    description: "input element with \"submit\" type",
+    document: `<input id="targetElement" type="submit" />`,
+    elementId: "targetElement",
+    expectedReturnValue: null,
+  },
+  {
+    description: "The signature in 'name' attr of an email input",
+    document: `<input id="targetElement" name="email" type="number">`,
+    elementId: "targetElement",
+    expectedReturnValue: {
+      fieldName: "email",
+      section: "",
+      addressType: "",
+      contactType: "",
+    },
+  },
+  {
+    description: "input element with \"email\" type",
+    document: `<input id="targetElement" type="email" />`,
+    elementId: "targetElement",
+    expectedReturnValue: {
+      fieldName: "email",
+      section: "",
+      addressType: "",
+      contactType: "",
+    },
+  },
+];
+
+TESTCASES.forEach(testcase => {
+  add_task(function* () {
+    do_print("Starting testcase: " + testcase.description);
+
+    let doc = MockDocument.createTestDocument(
+      "http://localhost:8080/test/", testcase.document);
+
+    let element = doc.getElementById(testcase.elementId);
+    let value = FormAutofillHeuristics.getInfo(element, testcase.fieldDetails);
+
+    Assert.deepEqual(value, testcase.expectedReturnValue);
+  });
+});
--- a/browser/extensions/formautofill/test/unit/xpcshell.ini
+++ b/browser/extensions/formautofill/test/unit/xpcshell.ini
@@ -20,15 +20,16 @@ support-files =
 [test_addressRecords.js]
 [test_autofillFormFields.js]
 [test_collectFormFields.js]
 [test_creditCardRecords.js]
 [test_extractLabelStrings.js]
 [test_findLabelElements.js]
 [test_getCategoriesFromFieldNames.js]
 [test_getFormInputDetails.js]
+[test_getInfo.js]
 [test_isCJKName.js]
 [test_markAsAutofillField.js]
 [test_nameUtils.js]
 [test_onFormSubmitted.js]
 [test_profileAutocompleteResult.js]
 [test_savedFieldNames.js]
 [test_transformFields.js]
--- a/toolkit/content/license.html
+++ b/toolkit/content/license.html
@@ -2712,18 +2712,21 @@ WITH THE USE OR PERFORMANCE OF THIS SOFT
 
 
     <hr>
 
     <h1><a id="chromium"></a>Chromium License</h1>
 
     <p>This license applies to parts of the code in:</p>
     <ul>
+#ifndef RELEASE_OR_BETA
+        <li><code>browser/extensions/formautofill/content/heuristicsRegexp.js</code></li>
         <li><code>browser/extensions/formautofill/content/nameReferences.js</code></li>
         <li><code>browser/extensions/formautofill/FormAutofillNameUtils.jsm</code></li>
+#endif
         <li><code>browser/extensions/mortar/host/common/opengles2-utils.jsm</code></li>
         <li><code>editor/libeditor/EditorEventListener.cpp</code></li>
         <li><code>security/sandbox/</code></li>
         <li><code>widget/cocoa/GfxInfo.mm</code></li>
     </ul>
     <p>and also some files in these directories:</p>
     <ul>
         <li><code>browser/extensions/mortar/ppapi/</code></li>