/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs", }); /** * This is a utility object to work with HTML labels in web pages, * including finding label elements and label text extraction. */ export 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. This map only // contains those labels with an id that matches a form element. // @type {Map} _mappedLabels: 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; }, /** * From a starting label element, find a nearby input or select element * by traversing the nodes in document order, but don't search past another * related element or outside the form. */ findAdjacentControl(labelElement, potentialLabels) { // First, look for an form element after the label. let foundElementAfter = this.findNextFormControl( labelElement, false, potentialLabels ); // If the control has the same parent as the label, return it. if (foundElementAfter?.parentNode == labelElement.parentNode) { return foundElementAfter; } // Otherwise, look for a form control with the same parent backwards // in the document. let foundElementBefore = this.findNextFormControl( labelElement, true, potentialLabels ); if (foundElementBefore?.parentNode == labelElement.parentNode) { return foundElementBefore; } // If there is no form control with the same parent forward or backward, // return the form control nearest forward, if any, even though it doesn't // have the same parent. return foundElementAfter; }, /** * Find the next form control in the document tree after a starting label that * could correspond to the label. If the form control is in potentialLabels, then * it has already been possibly matched to another label so should be ignored. * * @param {HTMLLabelElement} element * starting