From 52703e294a82456605ba3be5a02e1ac5005790e0 Mon Sep 17 00:00:00 2001 From: Erik Rose Date: Tue, 23 Mar 2021 18:31:07 +0000 Subject: [PATCH] Bug 1681985 - Extract LabelUtils to FormAutofillUtils.jsm. r=zbraniecki We need it from both FormAutofillHeuristics and CreditCardRuleset, and it would make a circular import otherwise: FormAutofillHeuristics -> CreditCardRuleset -> FormAutofillHeuristics. Differential Revision: https://phabricator.services.mozilla.com/D100140 --- .../formautofill/FormAutofillHeuristics.jsm | 114 +----------------- .../formautofill/FormAutofillUtils.jsm | 112 ++++++++++++++++- .../extensions/formautofill/test/unit/head.js | 4 +- .../test/unit/test_extractLabelStrings.js | 2 +- .../test/unit/test_findLabelElements.js | 2 +- .../formautofill/test/unit/test_getInfo.js | 5 +- tools/lint/eslint/modules.json | 4 +- 7 files changed, 124 insertions(+), 119 deletions(-) diff --git a/browser/extensions/formautofill/FormAutofillHeuristics.jsm b/browser/extensions/formautofill/FormAutofillHeuristics.jsm index 158785ace401..bf4917711430 100644 --- a/browser/extensions/formautofill/FormAutofillHeuristics.jsm +++ b/browser/extensions/formautofill/FormAutofillHeuristics.jsm @@ -8,7 +8,7 @@ "use strict"; -var EXPORTED_SYMBOLS = ["FormAutofillHeuristics", "LabelUtils"]; +var EXPORTED_SYMBOLS = ["FormAutofillHeuristics"]; const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { XPCOMUtils } = ChromeUtils.import( @@ -25,6 +25,8 @@ ChromeUtils.defineModuleGetter( XPCOMUtils.defineLazyModuleGetters(this, { CreditCard: "resource://gre/modules/CreditCard.jsm", + creditCardRuleset: "resource://formautofill/CreditCardRuleset.jsm", + LabelUtils: "resource://formautofill/FormAutofillUtils.jsm", // TODO: Is there a better way of importing multiple symbols from a single module? We already import from this one above. }); this.log = null; @@ -332,116 +334,6 @@ class FieldScanner { } } -var LabelUtils = { - // The tag name list is from Chromium except for "STYLE": - // eslint-disable-next-line max-len - // https://cs.chromium.org/chromium/src/components/autofill/content/renderer/form_autofill_util.cc?l=216&rcl=d33a171b7c308a64dc3372fac3da2179c63b419e - EXCLUDED_TAGS: ["SCRIPT", "NOSCRIPT", "OPTION", "STYLE"], - - // A map object, whose keys are the id's of form fields and each value is an - // array consisting of label elements correponding to the id. - // @type {Map} - _mappedLabels: null, - - // An array consisting of label elements whose correponding form field doesn't - // have an id attribute. - // @type {Array} - _unmappedLabels: null, - - // A weak map consisting of label element and extracted strings pairs. - // @type {WeakMap} - _labelStrings: null, - - /** - * Extract all strings of an element's children to an array. - * "element.textContent" is a string which is merged of all children nodes, - * and this function provides an array of the strings contains in an element. - * - * @param {Object} element - * A DOM element to be extracted. - * @returns {Array} - * All strings in an element. - */ - extractLabelStrings(element) { - if (this._labelStrings.has(element)) { - return this._labelStrings.get(element); - } - let strings = []; - let _extractLabelStrings = el => { - if (this.EXCLUDED_TAGS.includes(el.tagName)) { - return; - } - - if (el.nodeType == el.TEXT_NODE || !el.childNodes.length) { - let trimmedText = el.textContent.trim(); - if (trimmedText) { - strings.push(trimmedText); - } - return; - } - - for (let node of el.childNodes) { - let nodeType = node.nodeType; - if (nodeType != node.ELEMENT_NODE && nodeType != node.TEXT_NODE) { - continue; - } - _extractLabelStrings(node); - } - }; - _extractLabelStrings(element); - this._labelStrings.set(element, strings); - return strings; - }, - - generateLabelMap(doc) { - let mappedLabels = new Map(); - let unmappedLabels = []; - - for (let label of doc.querySelectorAll("label")) { - let id = label.htmlFor; - if (!id) { - let control = label.control; - if (!control) { - continue; - } - id = control.id; - } - if (id) { - let labels = mappedLabels.get(id); - if (labels) { - labels.push(label); - } else { - mappedLabels.set(id, [label]); - } - } else { - unmappedLabels.push(label); - } - } - - this._mappedLabels = mappedLabels; - this._unmappedLabels = unmappedLabels; - this._labelStrings = new WeakMap(); - }, - - clearLabelMap() { - this._mappedLabels = null; - this._unmappedLabels = null; - this._labelStrings = null; - }, - - findLabelElements(element) { - if (!this._mappedLabels) { - this.generateLabelMap(element.ownerDocument); - } - - let id = element.id; - if (!id) { - return this._unmappedLabels.filter(label => label.control == element); - } - return this._mappedLabels.get(id) || []; - }, -}; - /** * Returns the autocomplete information of fields according to heuristics. */ diff --git a/browser/extensions/formautofill/FormAutofillUtils.jsm b/browser/extensions/formautofill/FormAutofillUtils.jsm index d765c3a2ebcb..199855fef111 100644 --- a/browser/extensions/formautofill/FormAutofillUtils.jsm +++ b/browser/extensions/formautofill/FormAutofillUtils.jsm @@ -4,7 +4,7 @@ "use strict"; -var EXPORTED_SYMBOLS = ["FormAutofillUtils", "AddressDataLoader"]; +var EXPORTED_SYMBOLS = ["FormAutofillUtils", "AddressDataLoader", "LabelUtils"]; const ADDRESS_METADATA_PATH = "resource://formautofill/addressmetadata/"; const ADDRESS_REFERENCES = "addressReferences.js"; @@ -1102,6 +1102,116 @@ this.FormAutofillUtils = { }, }; +const LabelUtils = { + // The tag name list is from Chromium except for "STYLE": + // eslint-disable-next-line max-len + // https://cs.chromium.org/chromium/src/components/autofill/content/renderer/form_autofill_util.cc?l=216&rcl=d33a171b7c308a64dc3372fac3da2179c63b419e + EXCLUDED_TAGS: ["SCRIPT", "NOSCRIPT", "OPTION", "STYLE"], + + // A map object, whose keys are the id's of form fields and each value is an + // array consisting of label elements correponding to the id. + // @type {Map} + _mappedLabels: null, + + // An array consisting of label elements whose correponding form field doesn't + // have an id attribute. + // @type {Array} + _unmappedLabels: null, + + // A weak map consisting of label element and extracted strings pairs. + // @type {WeakMap} + _labelStrings: null, + + /** + * Extract all strings of an element's children to an array. + * "element.textContent" is a string which is merged of all children nodes, + * and this function provides an array of the strings contains in an element. + * + * @param {Object} element + * A DOM element to be extracted. + * @returns {Array} + * All strings in an element. + */ + extractLabelStrings(element) { + if (this._labelStrings.has(element)) { + return this._labelStrings.get(element); + } + let strings = []; + let _extractLabelStrings = el => { + if (this.EXCLUDED_TAGS.includes(el.tagName)) { + return; + } + + if (el.nodeType == el.TEXT_NODE || !el.childNodes.length) { + let trimmedText = el.textContent.trim(); + if (trimmedText) { + strings.push(trimmedText); + } + return; + } + + for (let node of el.childNodes) { + let nodeType = node.nodeType; + if (nodeType != node.ELEMENT_NODE && nodeType != node.TEXT_NODE) { + continue; + } + _extractLabelStrings(node); + } + }; + _extractLabelStrings(element); + this._labelStrings.set(element, strings); + return strings; + }, + + generateLabelMap(doc) { + let mappedLabels = new Map(); + let unmappedLabels = []; + + for (let label of doc.querySelectorAll("label")) { + let id = label.htmlFor; + if (!id) { + let control = label.control; + if (!control) { + continue; + } + id = control.id; + } + if (id) { + let labels = mappedLabels.get(id); + if (labels) { + labels.push(label); + } else { + mappedLabels.set(id, [label]); + } + } else { + unmappedLabels.push(label); + } + } + + this._mappedLabels = mappedLabels; + this._unmappedLabels = unmappedLabels; + this._labelStrings = new WeakMap(); + }, + + clearLabelMap() { + this._mappedLabels = null; + this._unmappedLabels = null; + this._labelStrings = null; + }, + + findLabelElements(element) { + if (!this._mappedLabels) { + this.generateLabelMap(element.ownerDocument); + } + + let id = element.id; + if (!id) { + return this._unmappedLabels.filter(label => label.control == element); + } + return this._mappedLabels.get(id) || []; + }, +}; + this.log = null; FormAutofill.defineLazyLogGetter(this, EXPORTED_SYMBOLS[0]); diff --git a/browser/extensions/formautofill/test/unit/head.js b/browser/extensions/formautofill/test/unit/head.js index 7cd5080f05f3..0af818e2d296 100644 --- a/browser/extensions/formautofill/test/unit/head.js +++ b/browser/extensions/formautofill/test/unit/head.js @@ -208,10 +208,10 @@ var AddressDataLoader, FormAutofillUtils; async function runHeuristicsTest(patterns, fixturePathPrefix) { add_task(async function setup() { - ({ FormAutofillHeuristics, LabelUtils } = ChromeUtils.import( + ({ FormAutofillHeuristics } = ChromeUtils.import( "resource://formautofill/FormAutofillHeuristics.jsm" )); - ({ AddressDataLoader, FormAutofillUtils } = ChromeUtils.import( + ({ AddressDataLoader, FormAutofillUtils, LabelUtils } = ChromeUtils.import( "resource://formautofill/FormAutofillUtils.jsm" )); }); diff --git a/browser/extensions/formautofill/test/unit/test_extractLabelStrings.js b/browser/extensions/formautofill/test/unit/test_extractLabelStrings.js index 6264b5081f67..920d715c89c5 100644 --- a/browser/extensions/formautofill/test/unit/test_extractLabelStrings.js +++ b/browser/extensions/formautofill/test/unit/test_extractLabelStrings.js @@ -3,7 +3,7 @@ var LabelUtils; add_task(async function() { ({ LabelUtils } = ChromeUtils.import( - "resource://formautofill/FormAutofillHeuristics.jsm" + "resource://formautofill/FormAutofillUtils.jsm" )); }); diff --git a/browser/extensions/formautofill/test/unit/test_findLabelElements.js b/browser/extensions/formautofill/test/unit/test_findLabelElements.js index 97c305f16081..1639180268ea 100644 --- a/browser/extensions/formautofill/test/unit/test_findLabelElements.js +++ b/browser/extensions/formautofill/test/unit/test_findLabelElements.js @@ -3,7 +3,7 @@ var LabelUtils; add_task(async function() { ({ LabelUtils } = ChromeUtils.import( - "resource://formautofill/FormAutofillHeuristics.jsm" + "resource://formautofill/FormAutofillUtils.jsm" )); }); diff --git a/browser/extensions/formautofill/test/unit/test_getInfo.js b/browser/extensions/formautofill/test/unit/test_getInfo.js index 2a033642b26a..2f6e297c2519 100644 --- a/browser/extensions/formautofill/test/unit/test_getInfo.js +++ b/browser/extensions/formautofill/test/unit/test_getInfo.js @@ -2,9 +2,12 @@ var FormAutofillHeuristics, LabelUtils; add_task(async function() { - ({ FormAutofillHeuristics, LabelUtils } = ChromeUtils.import( + ({ FormAutofillHeuristics } = ChromeUtils.import( "resource://formautofill/FormAutofillHeuristics.jsm" )); + ({ LabelUtils } = ChromeUtils.import( + "resource://formautofill/FormAutofillUtils.jsm" + )); }); const TESTCASES = [ diff --git a/tools/lint/eslint/modules.json b/tools/lint/eslint/modules.json index 865079d73fe2..d0f7195a6fdb 100644 --- a/tools/lint/eslint/modules.json +++ b/tools/lint/eslint/modules.json @@ -69,10 +69,10 @@ "Finder.jsm": ["Finder", "GetClipboardSearchString", "SetClipboardSearchString"], "format.js": ["pprint", "truncate"], "formautofill.jsm": ["Address", "CreditCard", "DumpAddresses", "DumpCreditCards"], - "FormAutofillHeuristics.jsm": ["FormAutofillHeuristics", "LabelUtils"], + "FormAutofillHeuristics.jsm": ["FormAutofillHeuristics"], "FormAutofillStorage.jsm": ["formAutofillStorage"], "FormAutofillSync.jsm": ["AddressesEngine", "CreditCardsEngine"], - "FormAutofillUtils.jsm": ["FormAutofillUtils", "AddressDataLoader"], + "FormAutofillUtils.jsm": ["FormAutofillUtils", "AddressDataLoader", "LabelUtils"], "forms.js": ["FormEngine", "FormRec", "FormValidator"], "forms.jsm": ["FormData"], "FrameScriptManager.jsm": ["getNewLoaderID"],