From a7c09638b3bdc1f3080322d9a6caa633db8193ea Mon Sep 17 00:00:00 2001 From: Sean Lee Date: Wed, 22 Nov 2017 15:57:33 +0800 Subject: [PATCH] Bug 1416664 - Identify the sections for the fields without the section part of autocomplete attr. r=lchang,ralin MozReview-Commit-ID: 7La8Bn0TF1y --HG-- extra : rebase_source : 4864178cdfbb644912bcfb9dc1a45806f1cae46d --- .../formautofill/FormAutofillHeuristics.jsm | 91 ++++++++++++++----- .../test/fixtures/multiple_section.html | 75 +++++++++++---- .../unit/heuristics/test_multiple_section.js | 31 +++++++ 3 files changed, 153 insertions(+), 44 deletions(-) diff --git a/browser/extensions/formautofill/FormAutofillHeuristics.jsm b/browser/extensions/formautofill/FormAutofillHeuristics.jsm index ed93d618764c..b5d4fa76b908 100644 --- a/browser/extensions/formautofill/FormAutofillHeuristics.jsm +++ b/browser/extensions/formautofill/FormAutofillHeuristics.jsm @@ -21,6 +21,7 @@ FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]); const PREF_HEURISTICS_ENABLED = "extensions.formautofill.heuristics.enabled"; const PREF_SECTION_ENABLED = "extensions.formautofill.section.enabled"; +const DEFAULT_SECTION_NAME = "-moz-section-default"; /** * A scanner for traversing all elements in a form and retrieving the field @@ -35,11 +36,13 @@ class FieldScanner { * @param {Array.DOMElement} elements * The elements from a form for each parser. */ - constructor(elements) { + constructor(elements, {allowDuplicates = false, sectionEnabled = true}) { this._elementsWeakRef = Cu.getWeakReference(elements); this.fieldDetails = []; this._parsingIndex = 0; this._sections = []; + this._allowDuplicates = allowDuplicates; + this._sectionEnabled = sectionEnabled; } get _elements() { @@ -112,22 +115,63 @@ class FieldScanner { }); } - getSectionFieldDetails(allowDuplicates) { - // TODO: [Bug 1416664] If there is only one section which is not defined by - // `autocomplete` attribute, the sections should be classified by the - // heuristics. - return this._sections.map(section => { - if (allowDuplicates) { - return section.fieldDetails; + _classifySections() { + let fieldDetails = this._sections[0].fieldDetails; + this._sections = []; + let seenTypes = new Set(); + let previousType; + let sectionCount = 0; + + for (let fieldDetail of fieldDetails) { + if (!fieldDetail.fieldName) { + continue; } - return this._trimFieldDetails(section.fieldDetails); - }); + if (seenTypes.has(fieldDetail.fieldName) && + previousType != fieldDetail.fieldName) { + seenTypes.clear(); + sectionCount++; + } + previousType = fieldDetail.fieldName; + seenTypes.add(fieldDetail.fieldName); + delete fieldDetail._duplicated; + this._pushToSection(DEFAULT_SECTION_NAME + "-" + sectionCount, fieldDetail); + } + } + + /** + * The result is an array contains the sections with its belonging field + * details. If `this._sections` contains one section only with the default + * section name (DEFAULT_SECTION_NAME), `this._classifySections` should be + * able to identify all sections in the heuristic way. + * + * @returns {Array} + * The array with the sections, and the belonging fieldDetails are in + * each section. + */ + getSectionFieldDetails() { + // When the section feature is disabled, `getSectionFieldDetails` should + // provide a single section result. + if (!this._sectionEnabled) { + return [this._getFinalDetails(this.fieldDetails)]; + } + if (this._sections.length == 0) { + return []; + } + if (this._sections.length == 1 && this._sections[0].name == DEFAULT_SECTION_NAME) { + this._classifySections(); + } + + return this._sections.map(section => + this._getFinalDetails(section.fieldDetails) + ); } /** * 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 field will be pushed into `this._sections` based on the section name + * in `autocomplete` attribute. * * Any element without the related detail will be used for adding the detail * to the end of field details. @@ -173,7 +217,7 @@ class FieldScanner { if (info.addressType) { names.push(info.addressType); } - return names.length ? names.join(" ") : "-moz-section-default"; + return names.length ? names.join(" ") : DEFAULT_SECTION_NAME; } /** @@ -205,7 +249,10 @@ class FieldScanner { } /** - * Provide the field details without invalid field name and duplicated fields. + * Provide the final field details without invalid field name, and the + * duplicated fields will be removed as well. For the debugging purpose, + * the final `fieldDetails` will include the duplicated fields if + * `_allowDuplicates` is true. * * @param {Array} fieldDetails * The field details for trimming. @@ -213,14 +260,13 @@ class FieldScanner { * The array with the field details without invalid field name and * duplicated fields. */ - _trimFieldDetails(fieldDetails) { + _getFinalDetails(fieldDetails) { + if (this._allowDuplicates) { + return fieldDetails.filter(f => f.fieldName); + } return fieldDetails.filter(f => f.fieldName && !f._duplicated); } - getFieldDetails(allowDuplicates) { - return allowDuplicates ? this.fieldDetails : this._trimFieldDetails(this.fieldDetails); - } - elementExisting(index) { return index < this._elements.length; } @@ -651,7 +697,8 @@ this.FormAutofillHeuristics = { return []; } - let fieldScanner = new FieldScanner(eligibleFields); + let fieldScanner = new FieldScanner(eligibleFields, + {allowDuplicates, sectionEnabled: this._sectionEnabled}); while (!fieldScanner.parsingFinished) { let parsedPhoneFields = this._parsePhoneFields(fieldScanner); let parsedAddressFields = this._parseAddressFields(fieldScanner); @@ -666,13 +713,7 @@ this.FormAutofillHeuristics = { LabelUtils.clearLabelMap(); - if (!this._sectionEnabled) { - // When the section feature is disabled, `getFormInfo` should provide a - // single section result. - return [fieldScanner.getFieldDetails(allowDuplicates)]; - } - - return fieldScanner.getSectionFieldDetails(allowDuplicates); + return fieldScanner.getSectionFieldDetails(); }, _regExpTableHashValue(...signBits) { diff --git a/browser/extensions/formautofill/test/fixtures/multiple_section.html b/browser/extensions/formautofill/test/fixtures/multiple_section.html index fb2ca0378174..c145fa2a77a4 100644 --- a/browser/extensions/formautofill/test/fixtures/multiple_section.html +++ b/browser/extensions/formautofill/test/fixtures/multiple_section.html @@ -11,32 +11,69 @@

-
-
-
-
-
+
+
+
+
+

-
-
-
-
-
+
+
+
+
+

-
-
-
-
-
+
+
+
+
+

-
-
+
+

-
-
+
+
+

+ + +

+ + +
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+

diff --git a/browser/extensions/formautofill/test/unit/heuristics/test_multiple_section.js b/browser/extensions/formautofill/test/unit/heuristics/test_multiple_section.js index a314ff56442d..39c9c3502153 100644 --- a/browser/extensions/formautofill/test/unit/heuristics/test_multiple_section.js +++ b/browser/extensions/formautofill/test/unit/heuristics/test_multiple_section.js @@ -41,6 +41,37 @@ runHeuristicsTest([ {"section": "section-my", "addressType": "", "contactType": "", "fieldName": "country"}, ], ], + [ + [ + {"section": "", "addressType": "", "contactType": "", "fieldName": "name"}, + {"section": "", "addressType": "", "contactType": "", "fieldName": "organization"}, + {"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": "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": "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": "work", "fieldName": "tel"}, + {"section": "", "addressType": "", "contactType": "work", "fieldName": "email"}, + ], + [ + {"section": "", "addressType": "", "contactType": "home", "fieldName": "tel"}, + {"section": "", "addressType": "", "contactType": "home", "fieldName": "email"}, + ], + ], ], }, ], "../../fixtures/");