зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
d86f56a31c
Коммит
f6560013df
|
@ -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);
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче