Bug 1370429 - Part 2: Implement address-lines parser and refactor getInfo function. r=MattN

MozReview-Commit-ID: 5gseB36n1M0

--HG--
extra : rebase_source : b3c4a5af031d9f79c3b30cda68a392afc017f99e
This commit is contained in:
Sean Lee 2017-07-19 10:15:34 +08:00
Родитель d86f56a31c
Коммит f6560013df
3 изменённых файлов: 126 добавлений и 49 удалений

Просмотреть файл

@ -23,7 +23,8 @@ const PREF_HEURISTICS_ENABLED = "extensions.formautofill.heuristics.enabled";
/**
* A scanner for traversing all elements in a form and retrieving the field
* detail with FormAutofillHeuristics.getInfo function.
* detail with FormAutofillHeuristics.getInfo function. It also provides a
* cursor (parsingIndex) to indicate which element is waiting for parsing.
*/
class FieldScanner {
/**
@ -36,12 +37,66 @@ class FieldScanner {
constructor(elements) {
this._elementsWeakRef = Cu.getWeakReference(elements);
this.fieldDetails = [];
this._parsingIndex = 0;
}
get _elements() {
return this._elementsWeakRef.get();
}
/**
* This cursor means the index of the element which is waiting for parsing.
*
* @returns {number}
* The index of the element which is waiting for parsing.
*/
get parsingIndex() {
return this._parsingIndex;
}
/**
* Move the parsingIndex to the next elements. Any elements behind this index
* means the parsing tasks are finished.
*
* @param {number} index
* The latest index of elements waiting for parsing.
*/
set parsingIndex(index) {
if (index > this.fieldDetails.length) {
throw new Error("The parsing index is out of range.");
}
this._parsingIndex = index;
}
/**
* Retrieve the field detail by the index. If the field detail is not ready,
* the elements will be traversed until matching the index.
*
* @param {number} index
* The index of the element that you want to retrieve.
* @returns {Object}
* The field detail at the specific index.
*/
getFieldDetailByIndex(index) {
if (index >= this._elements.length) {
throw new Error(`The index ${index} is out of range.(${this._elements.length})`);
}
if (index < this.fieldDetails.length) {
return this.fieldDetails[index];
}
for (let i = this.fieldDetails.length; i < (index + 1); i++) {
this.pushDetail();
}
return this.fieldDetails[index];
}
get parsingFinished() {
return this.parsingIndex >= this._elements.length;
}
/**
* This function will prepare an autocomplete info object with getInfo
* function and push the detail to fieldDetails property. Any duplicated
@ -78,6 +133,28 @@ class FieldScanner {
this.fieldDetails.push(fieldInfo);
}
/**
* When a field detail should be changed its fieldName after parsing, use
* this function to update the fieldName which is at a specific index.
*
* @param {number} index
* The index indicates a field detail to be updated.
* @param {string} fieldName
* The new fieldName
*/
updateFieldName(index, fieldName) {
if (index >= this.fieldDetails.length) {
throw new Error("Try to update the non-existing field detail.");
}
this.fieldDetails[index].fieldName = fieldName;
delete this.fieldDetails[index]._duplicated;
let indexSame = this.findSameField(this.fieldDetails[index]);
if (indexSame != index && indexSame != -1) {
this.fieldDetails[index]._duplicated = true;
}
}
findSameField(info) {
return this.fieldDetails.findIndex(f => f.section == info.section &&
f.addressType == info.addressType &&
@ -125,33 +202,62 @@ this.FormAutofillHeuristics = {
RULES: null,
/**
* Try to find the correct address-line[1-3] sequence and correct their field
* names.
*
* @param {FieldScanner} fieldScanner
* The current parsing status for all elements
* @returns {boolean}
* Return true if there is any field can be recognized in the parser,
* otherwise false.
*/
_parseAddressFields(fieldScanner) {
let parsedFields = false;
let addressLines = ["address-line1", "address-line2", "address-line3"];
for (let i = 0; !fieldScanner.parsingFinished && i < addressLines.length; i++) {
let detail = fieldScanner.getFieldDetailByIndex(fieldScanner.parsingIndex);
if (!detail || !addressLines.includes(detail.fieldName)) {
// When the field is not related to any address-line[1-3] fields, it
// means the parsing process can be terminated.
break;
}
fieldScanner.updateFieldName(fieldScanner.parsingIndex, addressLines[i]);
fieldScanner.parsingIndex++;
parsedFields = true;
}
return parsedFields;
},
getFormInfo(form) {
if (form.autocomplete == "off") {
if (form.autocomplete == "off" || form.elements.length <= 0) {
return [];
}
let fieldScanner = new FieldScanner(form.elements);
for (let i = 0; i < fieldScanner.elements.length; i++) {
let element = fieldScanner.elements[i];
let info = this.getInfo(element, fieldScanner.fieldDetails);
fieldScanner.pushDetail(i, info);
while (!fieldScanner.parsingFinished) {
let parsedAddressFields = this._parseAddressFields(fieldScanner);
// If there is no any field parsed, the parsing cursor can be moved
// forward to the next one.
if (!parsedAddressFields) {
fieldScanner.parsingIndex++;
}
}
return fieldScanner.trimmedFieldDetail;
},
/**
* 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).
* (this.RULES) matching to a feature string.
*
* @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) {
_matchStringToFieldName(string) {
let result = {
fieldName: "",
section: "",
@ -168,14 +274,6 @@ this.FormAutofillHeuristics = {
}
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;
}
@ -189,7 +287,7 @@ this.FormAutofillHeuristics = {
return null;
},
getInfo(element, fieldDetails) {
getInfo(element) {
if (!FormAutofillUtils.isFieldEligibleForAutofill(element)) {
return null;
}
@ -218,11 +316,8 @@ this.FormAutofillHeuristics = {
};
}
let existingFieldNames = fieldDetails ? fieldDetails.map(i => i.fieldName) : "";
for (let elementString of [element.id, element.name]) {
let fieldNameResult = this._matchStringToFieldName(elementString,
existingFieldNames);
let fieldNameResult = this._matchStringToFieldName(elementString);
if (fieldNameResult) {
return fieldNameResult;
}
@ -235,8 +330,7 @@ this.FormAutofillHeuristics = {
for (let label of labels) {
let strings = FormAutofillUtils.extractLabelStrings(label);
for (let string of strings) {
let fieldNameResult = this._matchStringToFieldName(string,
existingFieldNames);
let fieldNameResult = this._matchStringToFieldName(string);
if (fieldNameResult) {
return fieldNameResult;
}

Просмотреть файл

@ -93,8 +93,8 @@ const TESTCASES = [
"guid": "123",
"street-address": "2 Harrison St line2 line3",
"-moz-street-address-one-line": "2 Harrison St line2 line3",
"address-line1": "2 Harrison St line2",
"address-line2": "line2",
"address-line1": "2 Harrison St",
"address-line2": "line2 line3",
"address-line3": "line3",
}],
},

Просмотреть файл

@ -96,29 +96,12 @@ const TESTCASES = [
},
},
{
description: "2 address line inputs",
description: "address line input",
document: `<label for="targetElement">street</label>
<input id="targetElement" type="text">`,
elementId: "targetElement",
addressFieldDetails: [{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",
addressFieldDetails: [
{fieldName: "address-line1"},
{fieldName: "address-line2"},
],
expectedReturnValue: {
fieldName: "address-line3",
fieldName: "address-line1",
section: "",
addressType: "",
contactType: "",
@ -218,7 +201,7 @@ TESTCASES.forEach(testcase => {
"http://localhost:8080/test/", testcase.document);
let element = doc.getElementById(testcase.elementId);
let value = FormAutofillHeuristics.getInfo(element, testcase.addressFieldDetails);
let value = FormAutofillHeuristics.getInfo(element);
Assert.deepEqual(value, testcase.expectedReturnValue);
});