--- 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>