Bug 1370429 - Part 1: Implement FieldScanner and refactor getFormInfo function. r=MattN
MozReview-Commit-ID: GvFlOLuKSDU
--- a/browser/extensions/formautofill/FormAutofillHeuristics.jsm
+++ b/browser/extensions/formautofill/FormAutofillHeuristics.jsm
@@ -17,16 +17,93 @@ Cu.import("resource://gre/modules/XPCOMU
Cu.import("resource://formautofill/FormAutofillUtils.jsm");
this.log = null;
FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
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.
+ */
+class FieldScanner {
+ /**
+ * Create a FieldScanner based on form elements with the existing
+ * fieldDetails.
+ *
+ * @param {Array.DOMElement} elements
+ * The elements from a form for each parser.
+ */
+ constructor(elements) {
+ this._elementsWeakRef = Cu.getWeakReference(elements);
+ this.fieldDetails = [];
+ }
+
+ get _elements() {
+ return this._elementsWeakRef.get();
+ }
+
+ /**
+ * This function will prepare an autocomplete info object with getInfo
+ * function and push the detail to fieldDetails property. Any duplicated
+ * detail will be marked as _duplicated = true for the parser.
+ *
+ * Any element without the related detail will be used for adding the detail
+ * to the end of field details.
+ */
+ pushDetail() {
+ let elementIndex = this.fieldDetails.length;
+ if (elementIndex >= this._elements.length) {
+ log.warn("try to push the non-exising element info.");
+ return;
+ }
+ let element = this._elements[elementIndex];
+ let info = FormAutofillHeuristics.getInfo(element);
+ if (!info) {
+ info = {};
+ }
+ let fieldInfo = {
+ section: info.section,
+ addressType: info.addressType,
+ contactType: info.contactType,
+ fieldName: info.fieldName,
+ elementWeakRef: Cu.getWeakReference(element),
+ };
+
+ // Store the association between the field metadata and the element.
+ if (this.findSameField(info) != -1) {
+ // A field with the same identifier already exists.
+ log.debug("Not collecting a field matching another with the same info:", info);
+ fieldInfo._duplicated = true;
+ }
+
+ this.fieldDetails.push(fieldInfo);
+ }
+
+ findSameField(info) {
+ return this.fieldDetails.findIndex(f => f.section == info.section &&
+ f.addressType == info.addressType &&
+ f.contactType == info.contactType &&
+ f.fieldName == info.fieldName);
+ }
+
+ /**
+ * Provide the field details without invalid field name and duplicated fields.
+ *
+ * @returns {Array<Object>}
+ * The array with the field details without invalid field name and
+ * duplicated fields.
+ */
+ get trimmedFieldDetail() {
+ return this.fieldDetails.filter(f => f.fieldName && !f._duplicated);
+ }
+}
+
+/**
* Returns the autocomplete information of fields according to heuristics.
*/
this.FormAutofillHeuristics = {
FIELD_GROUPS: {
NAME: [
"name",
"given-name",
"additional-name",
@@ -45,49 +122,26 @@ this.FormAutofillHeuristics = {
],
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);
+ 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);
}
-
- return fieldDetails;
+ 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).
*