From 50c6feddd800ebef479b1e55e3d08ac366ea981e Mon Sep 17 00:00:00 2001 From: dbajpeyi Date: Fri, 22 Nov 2024 12:24:31 +0100 Subject: [PATCH] refactor: use one getFormElements for all queries --- dist/autofill-debug.js | 66 +++++++++++-------- dist/autofill.js | 66 +++++++++++-------- src/Form/Form.js | 29 +------- src/Form/FormAnalyzer.js | 5 +- src/Scanner.js | 4 +- src/autofill-utils.js | 29 ++++++++ .../Resources/assets/autofill-debug.js | 66 +++++++++++-------- swift-package/Resources/assets/autofill.js | 66 +++++++++++-------- 8 files changed, 184 insertions(+), 147 deletions(-) diff --git a/dist/autofill-debug.js b/dist/autofill-debug.js index 7e2bc6091..b45069264 100644 --- a/dist/autofill-debug.js +++ b/dist/autofill-debug.js @@ -10211,27 +10211,6 @@ class Form { }); } } - - /** - * Function attempts to find elements using .elements to catch field outside the form itself using the form attribute. - * It also catches all elements when the markup is broken. - * We use .filter to avoid fieldset, button, textarea etc. - * If .elements doesn't work, it falls back to querySelectorAll. - * Doesn't look for shadow elements. - * @param {string} selector - * @returns {Element[]} - */ - getFormElements(selector) { - // Some sites seem to be overriding `form.elements`, so we need to check if it's still iterable. - /** @type {Element[]|NodeListOf} element */ - let formElements = []; - if (this.form instanceof HTMLFormElement && this.form.elements != null && Symbol.iterator in Object(this.form.elements)) { - formElements = [...this.form.elements].filter(el => el.matches(selector)); - } else { - formElements = this.form.querySelectorAll(selector); - } - return [...formElements, ...(0, _autofillUtils.findElementsInShadowTree)(this.form, selector)]; - } categorizeInputs() { const selector = this.matching.cssSelector('formInputsSelector'); // If there's no form container and it's just a lonely input field (this.form is an input field) @@ -10241,7 +10220,7 @@ class Form { /** @type {Element[] | NodeList} */ // Also scan the form for shadow elements - const foundInputs = this.getFormElements(selector); + const foundInputs = (0, _autofillUtils.getFormElements)(this.form, selector, true, true); if (foundInputs.length < MAX_INPUTS_PER_FORM) { foundInputs.forEach(input => this.addInput(input)); } else { @@ -10302,8 +10281,7 @@ class Form { } get submitButtons() { const selector = this.matching.cssSelector('submitButtonSelector'); - const buttons = this.form.querySelectorAll(selector); - const allButtons = /** @type {HTMLElement[]} */buttons.length > 0 ? [...buttons] : (0, _autofillUtils.findElementsInShadowTree)(this.form, selector); + const allButtons = /** @type {HTMLElement[]} */(0, _autofillUtils.getFormElements)(this.form, selector); return allButtons.filter(btn => (0, _autofillUtils.isPotentiallyViewable)(btn) && (0, _autofillUtils.isLikelyASubmitButton)(btn, this.matching) && (0, _autofillUtils.buttonMatchesFormType)(btn, this)); } attemptSubmissionIfNeeded() { @@ -11100,8 +11078,7 @@ class FormAnalyzer { // Check form contents (noisy elements are skipped with the safeUniversalSelector) const selector = this.matching.cssSelector('safeUniversalSelector'); - const elements = this.form.querySelectorAll(selector); - const formElements = elements.length > 0 ? [...elements] : (0, _autofillUtils.findElementsInShadowTree)(this.form, selector); + const formElements = (0, _autofillUtils.getFormElements)(this.form, selector); for (let i = 0; i < formElements.length; i++) { // Safety cutoff to avoid huge DOMs freezing the browser if (i >= 200) break; @@ -14829,8 +14806,8 @@ class DefaultScanner { if (realTarget instanceof HTMLInputElement && !realTarget.hasAttribute(ATTR_INPUT_TYPE)) { const parentForm = this.getParentForm(realTarget); - // If the parent form is an input element or the same as the target, we don't want to scan it further. - if (parentForm instanceof HTMLInputElement || parentForm.isEqualNode(realTarget)) return; + // If the parent form is an input element we bail. + if (parentForm instanceof HTMLInputElement) return; const hasShadowTree = event.target?.shadowRoot != null; const form = new _Form.Form(parentForm, realTarget, this.device, this.matching, this.shouldAutoprompt, hasShadowTree); this.forms.set(parentForm, form); @@ -16974,7 +16951,9 @@ exports.escapeXML = escapeXML; exports.findElementsInShadowTree = findElementsInShadowTree; exports.formatDuckAddress = void 0; exports.getActiveElement = getActiveElement; -exports.isEventWithinDax = exports.isAutofillEnabledFromProcessedConfig = exports.getTextShallow = exports.getDaxBoundingBox = void 0; +exports.getDaxBoundingBox = void 0; +exports.getFormElements = getFormElements; +exports.isEventWithinDax = exports.isAutofillEnabledFromProcessedConfig = exports.getTextShallow = void 0; exports.isFormLikelyToBeUsedAsPageWrapper = isFormLikelyToBeUsedAsPageWrapper; exports.isLikelyASubmitButton = exports.isIncontextSignupEnabledFromProcessedConfig = void 0; exports.isLocalNetwork = isLocalNetwork; @@ -17604,6 +17583,35 @@ function findElementsInShadowTree(root, selector) { return shadowElements; } +/** + * Default operation: finds elements using querySelectorAll. + * Optionally, if forced an form.elements is iterable, attempts to find elements using .elements instead, + * to catch field outside the form itsel using the form attribute. It also catches all elements + * when the markup is broken. We use .filter along with a selector to avoid any unwanted elements. + * Optionally, can be forced to scan the shadow tree. + * @param {string} selector + * @param {boolean} forceScanShadowTree + * @param {boolean} shouldCheckFormControls + * @returns {Element[]} + */ +function getFormElements(form, selector) { + let forceScanShadowTree = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + let shouldCheckFormControls = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; + // Some sites seem to be overriding `form.elements`, so we need to check if it's still iterable. + /** @type {Element[]|NodeListOf} element */ + let formElements = []; + if (shouldCheckFormControls && form instanceof HTMLFormElement && form.elements != null && Symbol.iterator in Object(form.elements)) { + formElements = [...form.elements].filter(el => el.matches(selector)); + } else { + formElements = form.querySelectorAll(selector); + } + if (forceScanShadowTree || formElements.length === 0) { + return [...formElements, ...findElementsInShadowTree(form, selector)]; + } else { + return [...formElements]; + } +} + },{"./Form/matching.js":44,"./constants.js":67,"@duckduckgo/content-scope-scripts/src/apple-utils":1}],65:[function(require,module,exports){ "use strict"; diff --git a/dist/autofill.js b/dist/autofill.js index a5a8edb31..a911e38d5 100644 --- a/dist/autofill.js +++ b/dist/autofill.js @@ -6045,27 +6045,6 @@ class Form { }); } } - - /** - * Function attempts to find elements using .elements to catch field outside the form itself using the form attribute. - * It also catches all elements when the markup is broken. - * We use .filter to avoid fieldset, button, textarea etc. - * If .elements doesn't work, it falls back to querySelectorAll. - * Doesn't look for shadow elements. - * @param {string} selector - * @returns {Element[]} - */ - getFormElements(selector) { - // Some sites seem to be overriding `form.elements`, so we need to check if it's still iterable. - /** @type {Element[]|NodeListOf} element */ - let formElements = []; - if (this.form instanceof HTMLFormElement && this.form.elements != null && Symbol.iterator in Object(this.form.elements)) { - formElements = [...this.form.elements].filter(el => el.matches(selector)); - } else { - formElements = this.form.querySelectorAll(selector); - } - return [...formElements, ...(0, _autofillUtils.findElementsInShadowTree)(this.form, selector)]; - } categorizeInputs() { const selector = this.matching.cssSelector('formInputsSelector'); // If there's no form container and it's just a lonely input field (this.form is an input field) @@ -6075,7 +6054,7 @@ class Form { /** @type {Element[] | NodeList} */ // Also scan the form for shadow elements - const foundInputs = this.getFormElements(selector); + const foundInputs = (0, _autofillUtils.getFormElements)(this.form, selector, true, true); if (foundInputs.length < MAX_INPUTS_PER_FORM) { foundInputs.forEach(input => this.addInput(input)); } else { @@ -6136,8 +6115,7 @@ class Form { } get submitButtons() { const selector = this.matching.cssSelector('submitButtonSelector'); - const buttons = this.form.querySelectorAll(selector); - const allButtons = /** @type {HTMLElement[]} */buttons.length > 0 ? [...buttons] : (0, _autofillUtils.findElementsInShadowTree)(this.form, selector); + const allButtons = /** @type {HTMLElement[]} */(0, _autofillUtils.getFormElements)(this.form, selector); return allButtons.filter(btn => (0, _autofillUtils.isPotentiallyViewable)(btn) && (0, _autofillUtils.isLikelyASubmitButton)(btn, this.matching) && (0, _autofillUtils.buttonMatchesFormType)(btn, this)); } attemptSubmissionIfNeeded() { @@ -6934,8 +6912,7 @@ class FormAnalyzer { // Check form contents (noisy elements are skipped with the safeUniversalSelector) const selector = this.matching.cssSelector('safeUniversalSelector'); - const elements = this.form.querySelectorAll(selector); - const formElements = elements.length > 0 ? [...elements] : (0, _autofillUtils.findElementsInShadowTree)(this.form, selector); + const formElements = (0, _autofillUtils.getFormElements)(this.form, selector); for (let i = 0; i < formElements.length; i++) { // Safety cutoff to avoid huge DOMs freezing the browser if (i >= 200) break; @@ -10663,8 +10640,8 @@ class DefaultScanner { if (realTarget instanceof HTMLInputElement && !realTarget.hasAttribute(ATTR_INPUT_TYPE)) { const parentForm = this.getParentForm(realTarget); - // If the parent form is an input element or the same as the target, we don't want to scan it further. - if (parentForm instanceof HTMLInputElement || parentForm.isEqualNode(realTarget)) return; + // If the parent form is an input element we bail. + if (parentForm instanceof HTMLInputElement) return; const hasShadowTree = event.target?.shadowRoot != null; const form = new _Form.Form(parentForm, realTarget, this.device, this.matching, this.shouldAutoprompt, hasShadowTree); this.forms.set(parentForm, form); @@ -12808,7 +12785,9 @@ exports.escapeXML = escapeXML; exports.findElementsInShadowTree = findElementsInShadowTree; exports.formatDuckAddress = void 0; exports.getActiveElement = getActiveElement; -exports.isEventWithinDax = exports.isAutofillEnabledFromProcessedConfig = exports.getTextShallow = exports.getDaxBoundingBox = void 0; +exports.getDaxBoundingBox = void 0; +exports.getFormElements = getFormElements; +exports.isEventWithinDax = exports.isAutofillEnabledFromProcessedConfig = exports.getTextShallow = void 0; exports.isFormLikelyToBeUsedAsPageWrapper = isFormLikelyToBeUsedAsPageWrapper; exports.isLikelyASubmitButton = exports.isIncontextSignupEnabledFromProcessedConfig = void 0; exports.isLocalNetwork = isLocalNetwork; @@ -13438,6 +13417,35 @@ function findElementsInShadowTree(root, selector) { return shadowElements; } +/** + * Default operation: finds elements using querySelectorAll. + * Optionally, if forced an form.elements is iterable, attempts to find elements using .elements instead, + * to catch field outside the form itsel using the form attribute. It also catches all elements + * when the markup is broken. We use .filter along with a selector to avoid any unwanted elements. + * Optionally, can be forced to scan the shadow tree. + * @param {string} selector + * @param {boolean} forceScanShadowTree + * @param {boolean} shouldCheckFormControls + * @returns {Element[]} + */ +function getFormElements(form, selector) { + let forceScanShadowTree = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + let shouldCheckFormControls = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; + // Some sites seem to be overriding `form.elements`, so we need to check if it's still iterable. + /** @type {Element[]|NodeListOf} element */ + let formElements = []; + if (shouldCheckFormControls && form instanceof HTMLFormElement && form.elements != null && Symbol.iterator in Object(form.elements)) { + formElements = [...form.elements].filter(el => el.matches(selector)); + } else { + formElements = form.querySelectorAll(selector); + } + if (forceScanShadowTree || formElements.length === 0) { + return [...formElements, ...findElementsInShadowTree(form, selector)]; + } else { + return [...formElements]; + } +} + },{"./Form/matching.js":34,"./constants.js":57,"@duckduckgo/content-scope-scripts/src/apple-utils":1}],55:[function(require,module,exports){ "use strict"; diff --git a/src/Form/Form.js b/src/Form/Form.js index 2709c76ef..b759c4dbd 100644 --- a/src/Form/Form.js +++ b/src/Form/Form.js @@ -14,7 +14,7 @@ import { shouldLog, safeRegexTest, getActiveElement, - findElementsInShadowTree, + getFormElements, } from '../autofill-utils.js'; import { getInputSubtype, getInputMainType, createMatching, getInputVariant } from './matching.js'; @@ -386,27 +386,6 @@ class Form { } } - /** - * Function attempts to find elements using .elements to catch field outside the form itself using the form attribute. - * It also catches all elements when the markup is broken. - * We use .filter to avoid fieldset, button, textarea etc. - * If .elements doesn't work, it falls back to querySelectorAll. - * Doesn't look for shadow elements. - * @param {string} selector - * @returns {Element[]} - */ - getFormElements(selector) { - // Some sites seem to be overriding `form.elements`, so we need to check if it's still iterable. - /** @type {Element[]|NodeListOf} element */ - let formElements = []; - if (this.form instanceof HTMLFormElement && this.form.elements != null && Symbol.iterator in Object(this.form.elements)) { - formElements = [...this.form.elements].filter((el) => el.matches(selector)); - } else { - formElements = this.form.querySelectorAll(selector); - } - return [...formElements, ...findElementsInShadowTree(this.form, selector)]; - } - categorizeInputs() { const selector = this.matching.cssSelector('formInputsSelector'); // If there's no form container and it's just a lonely input field (this.form is an input field) @@ -416,7 +395,7 @@ class Form { /** @type {Element[] | NodeList} */ // Also scan the form for shadow elements - const foundInputs = this.getFormElements(selector); + const foundInputs = getFormElements(this.form, selector, true, true); if (foundInputs.length < MAX_INPUTS_PER_FORM) { foundInputs.forEach((input) => this.addInput(input)); @@ -486,9 +465,7 @@ class Form { get submitButtons() { const selector = this.matching.cssSelector('submitButtonSelector'); - const buttons = this.form.querySelectorAll(selector); - const allButtons = /** @type {HTMLElement[]} */ (buttons.length > 0 ? [...buttons] : findElementsInShadowTree(this.form, selector)); - + const allButtons = /** @type {HTMLElement[]} */ (getFormElements(this.form, selector)); return allButtons.filter( (btn) => isPotentiallyViewable(btn) && isLikelyASubmitButton(btn, this.matching) && buttonMatchesFormType(btn, this), ); diff --git a/src/Form/FormAnalyzer.js b/src/Form/FormAnalyzer.js index d55bb2aef..b8e873333 100644 --- a/src/Form/FormAnalyzer.js +++ b/src/Form/FormAnalyzer.js @@ -1,7 +1,7 @@ import { removeExcessWhitespace, Matching } from './matching.js'; import { constants } from '../constants.js'; import { matchingConfiguration } from './matching-config/__generated__/compiled-matching-config.js'; -import { findElementsInShadowTree, getTextShallow, isLikelyASubmitButton, safeRegexTest } from '../autofill-utils.js'; +import { findElementsInShadowTree, getFormElements, getTextShallow, isLikelyASubmitButton, safeRegexTest } from '../autofill-utils.js'; class FormAnalyzer { /** @type HTMLElement */ @@ -311,8 +311,7 @@ class FormAnalyzer { // Check form contents (noisy elements are skipped with the safeUniversalSelector) const selector = this.matching.cssSelector('safeUniversalSelector'); - const elements = this.form.querySelectorAll(selector); - const formElements = elements.length > 0 ? [...elements] : findElementsInShadowTree(this.form, selector); + const formElements = getFormElements(this.form, selector); for (let i = 0; i < formElements.length; i++) { // Safety cutoff to avoid huge DOMs freezing the browser if (i >= 200) break; diff --git a/src/Scanner.js b/src/Scanner.js index 00ffe6762..8514dc01f 100644 --- a/src/Scanner.js +++ b/src/Scanner.js @@ -439,8 +439,8 @@ class DefaultScanner { if (realTarget instanceof HTMLInputElement && !realTarget.hasAttribute(ATTR_INPUT_TYPE)) { const parentForm = this.getParentForm(realTarget); - // If the parent form is an input element or the same as the target, we don't want to scan it further. - if (parentForm instanceof HTMLInputElement || parentForm.isEqualNode(realTarget)) return; + // If the parent form is an input element we bail. + if (parentForm instanceof HTMLInputElement) return; const hasShadowTree = event.target?.shadowRoot != null; const form = new Form(parentForm, realTarget, this.device, this.matching, this.shouldAutoprompt, hasShadowTree); diff --git a/src/autofill-utils.js b/src/autofill-utils.js index 1e412edeb..8319e5248 100644 --- a/src/autofill-utils.js +++ b/src/autofill-utils.js @@ -587,6 +587,34 @@ function findElementsInShadowTree(root, selector) { return shadowElements; } +/** + * Default operation: finds elements using querySelectorAll. + * Optionally, if forced an form.elements is iterable, attempts to find elements using .elements instead, + * to catch field outside the form itsel using the form attribute. It also catches all elements + * when the markup is broken. We use .filter along with a selector to avoid any unwanted elements. + * Optionally, can be forced to scan the shadow tree. + * @param {string} selector + * @param {boolean} forceScanShadowTree + * @param {boolean} shouldCheckFormControls + * @returns {Element[]} + */ +function getFormElements(form, selector, forceScanShadowTree = false, shouldCheckFormControls = false) { + // Some sites seem to be overriding `form.elements`, so we need to check if it's still iterable. + /** @type {Element[]|NodeListOf} element */ + let formElements = []; + if (shouldCheckFormControls && form instanceof HTMLFormElement && form.elements != null && Symbol.iterator in Object(form.elements)) { + formElements = [...form.elements].filter((el) => el.matches(selector)); + } else { + formElements = form.querySelectorAll(selector); + } + + if (forceScanShadowTree || formElements.length === 0) { + return [...formElements, ...findElementsInShadowTree(form, selector)]; + } else { + return [...formElements]; + } +} + export { notifyWebApp, sendAndWaitForAnswer, @@ -620,4 +648,5 @@ export { pierceShadowTree, getActiveElement, findElementsInShadowTree, + getFormElements, }; diff --git a/swift-package/Resources/assets/autofill-debug.js b/swift-package/Resources/assets/autofill-debug.js index 7e2bc6091..b45069264 100644 --- a/swift-package/Resources/assets/autofill-debug.js +++ b/swift-package/Resources/assets/autofill-debug.js @@ -10211,27 +10211,6 @@ class Form { }); } } - - /** - * Function attempts to find elements using .elements to catch field outside the form itself using the form attribute. - * It also catches all elements when the markup is broken. - * We use .filter to avoid fieldset, button, textarea etc. - * If .elements doesn't work, it falls back to querySelectorAll. - * Doesn't look for shadow elements. - * @param {string} selector - * @returns {Element[]} - */ - getFormElements(selector) { - // Some sites seem to be overriding `form.elements`, so we need to check if it's still iterable. - /** @type {Element[]|NodeListOf} element */ - let formElements = []; - if (this.form instanceof HTMLFormElement && this.form.elements != null && Symbol.iterator in Object(this.form.elements)) { - formElements = [...this.form.elements].filter(el => el.matches(selector)); - } else { - formElements = this.form.querySelectorAll(selector); - } - return [...formElements, ...(0, _autofillUtils.findElementsInShadowTree)(this.form, selector)]; - } categorizeInputs() { const selector = this.matching.cssSelector('formInputsSelector'); // If there's no form container and it's just a lonely input field (this.form is an input field) @@ -10241,7 +10220,7 @@ class Form { /** @type {Element[] | NodeList} */ // Also scan the form for shadow elements - const foundInputs = this.getFormElements(selector); + const foundInputs = (0, _autofillUtils.getFormElements)(this.form, selector, true, true); if (foundInputs.length < MAX_INPUTS_PER_FORM) { foundInputs.forEach(input => this.addInput(input)); } else { @@ -10302,8 +10281,7 @@ class Form { } get submitButtons() { const selector = this.matching.cssSelector('submitButtonSelector'); - const buttons = this.form.querySelectorAll(selector); - const allButtons = /** @type {HTMLElement[]} */buttons.length > 0 ? [...buttons] : (0, _autofillUtils.findElementsInShadowTree)(this.form, selector); + const allButtons = /** @type {HTMLElement[]} */(0, _autofillUtils.getFormElements)(this.form, selector); return allButtons.filter(btn => (0, _autofillUtils.isPotentiallyViewable)(btn) && (0, _autofillUtils.isLikelyASubmitButton)(btn, this.matching) && (0, _autofillUtils.buttonMatchesFormType)(btn, this)); } attemptSubmissionIfNeeded() { @@ -11100,8 +11078,7 @@ class FormAnalyzer { // Check form contents (noisy elements are skipped with the safeUniversalSelector) const selector = this.matching.cssSelector('safeUniversalSelector'); - const elements = this.form.querySelectorAll(selector); - const formElements = elements.length > 0 ? [...elements] : (0, _autofillUtils.findElementsInShadowTree)(this.form, selector); + const formElements = (0, _autofillUtils.getFormElements)(this.form, selector); for (let i = 0; i < formElements.length; i++) { // Safety cutoff to avoid huge DOMs freezing the browser if (i >= 200) break; @@ -14829,8 +14806,8 @@ class DefaultScanner { if (realTarget instanceof HTMLInputElement && !realTarget.hasAttribute(ATTR_INPUT_TYPE)) { const parentForm = this.getParentForm(realTarget); - // If the parent form is an input element or the same as the target, we don't want to scan it further. - if (parentForm instanceof HTMLInputElement || parentForm.isEqualNode(realTarget)) return; + // If the parent form is an input element we bail. + if (parentForm instanceof HTMLInputElement) return; const hasShadowTree = event.target?.shadowRoot != null; const form = new _Form.Form(parentForm, realTarget, this.device, this.matching, this.shouldAutoprompt, hasShadowTree); this.forms.set(parentForm, form); @@ -16974,7 +16951,9 @@ exports.escapeXML = escapeXML; exports.findElementsInShadowTree = findElementsInShadowTree; exports.formatDuckAddress = void 0; exports.getActiveElement = getActiveElement; -exports.isEventWithinDax = exports.isAutofillEnabledFromProcessedConfig = exports.getTextShallow = exports.getDaxBoundingBox = void 0; +exports.getDaxBoundingBox = void 0; +exports.getFormElements = getFormElements; +exports.isEventWithinDax = exports.isAutofillEnabledFromProcessedConfig = exports.getTextShallow = void 0; exports.isFormLikelyToBeUsedAsPageWrapper = isFormLikelyToBeUsedAsPageWrapper; exports.isLikelyASubmitButton = exports.isIncontextSignupEnabledFromProcessedConfig = void 0; exports.isLocalNetwork = isLocalNetwork; @@ -17604,6 +17583,35 @@ function findElementsInShadowTree(root, selector) { return shadowElements; } +/** + * Default operation: finds elements using querySelectorAll. + * Optionally, if forced an form.elements is iterable, attempts to find elements using .elements instead, + * to catch field outside the form itsel using the form attribute. It also catches all elements + * when the markup is broken. We use .filter along with a selector to avoid any unwanted elements. + * Optionally, can be forced to scan the shadow tree. + * @param {string} selector + * @param {boolean} forceScanShadowTree + * @param {boolean} shouldCheckFormControls + * @returns {Element[]} + */ +function getFormElements(form, selector) { + let forceScanShadowTree = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + let shouldCheckFormControls = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; + // Some sites seem to be overriding `form.elements`, so we need to check if it's still iterable. + /** @type {Element[]|NodeListOf} element */ + let formElements = []; + if (shouldCheckFormControls && form instanceof HTMLFormElement && form.elements != null && Symbol.iterator in Object(form.elements)) { + formElements = [...form.elements].filter(el => el.matches(selector)); + } else { + formElements = form.querySelectorAll(selector); + } + if (forceScanShadowTree || formElements.length === 0) { + return [...formElements, ...findElementsInShadowTree(form, selector)]; + } else { + return [...formElements]; + } +} + },{"./Form/matching.js":44,"./constants.js":67,"@duckduckgo/content-scope-scripts/src/apple-utils":1}],65:[function(require,module,exports){ "use strict"; diff --git a/swift-package/Resources/assets/autofill.js b/swift-package/Resources/assets/autofill.js index a5a8edb31..a911e38d5 100644 --- a/swift-package/Resources/assets/autofill.js +++ b/swift-package/Resources/assets/autofill.js @@ -6045,27 +6045,6 @@ class Form { }); } } - - /** - * Function attempts to find elements using .elements to catch field outside the form itself using the form attribute. - * It also catches all elements when the markup is broken. - * We use .filter to avoid fieldset, button, textarea etc. - * If .elements doesn't work, it falls back to querySelectorAll. - * Doesn't look for shadow elements. - * @param {string} selector - * @returns {Element[]} - */ - getFormElements(selector) { - // Some sites seem to be overriding `form.elements`, so we need to check if it's still iterable. - /** @type {Element[]|NodeListOf} element */ - let formElements = []; - if (this.form instanceof HTMLFormElement && this.form.elements != null && Symbol.iterator in Object(this.form.elements)) { - formElements = [...this.form.elements].filter(el => el.matches(selector)); - } else { - formElements = this.form.querySelectorAll(selector); - } - return [...formElements, ...(0, _autofillUtils.findElementsInShadowTree)(this.form, selector)]; - } categorizeInputs() { const selector = this.matching.cssSelector('formInputsSelector'); // If there's no form container and it's just a lonely input field (this.form is an input field) @@ -6075,7 +6054,7 @@ class Form { /** @type {Element[] | NodeList} */ // Also scan the form for shadow elements - const foundInputs = this.getFormElements(selector); + const foundInputs = (0, _autofillUtils.getFormElements)(this.form, selector, true, true); if (foundInputs.length < MAX_INPUTS_PER_FORM) { foundInputs.forEach(input => this.addInput(input)); } else { @@ -6136,8 +6115,7 @@ class Form { } get submitButtons() { const selector = this.matching.cssSelector('submitButtonSelector'); - const buttons = this.form.querySelectorAll(selector); - const allButtons = /** @type {HTMLElement[]} */buttons.length > 0 ? [...buttons] : (0, _autofillUtils.findElementsInShadowTree)(this.form, selector); + const allButtons = /** @type {HTMLElement[]} */(0, _autofillUtils.getFormElements)(this.form, selector); return allButtons.filter(btn => (0, _autofillUtils.isPotentiallyViewable)(btn) && (0, _autofillUtils.isLikelyASubmitButton)(btn, this.matching) && (0, _autofillUtils.buttonMatchesFormType)(btn, this)); } attemptSubmissionIfNeeded() { @@ -6934,8 +6912,7 @@ class FormAnalyzer { // Check form contents (noisy elements are skipped with the safeUniversalSelector) const selector = this.matching.cssSelector('safeUniversalSelector'); - const elements = this.form.querySelectorAll(selector); - const formElements = elements.length > 0 ? [...elements] : (0, _autofillUtils.findElementsInShadowTree)(this.form, selector); + const formElements = (0, _autofillUtils.getFormElements)(this.form, selector); for (let i = 0; i < formElements.length; i++) { // Safety cutoff to avoid huge DOMs freezing the browser if (i >= 200) break; @@ -10663,8 +10640,8 @@ class DefaultScanner { if (realTarget instanceof HTMLInputElement && !realTarget.hasAttribute(ATTR_INPUT_TYPE)) { const parentForm = this.getParentForm(realTarget); - // If the parent form is an input element or the same as the target, we don't want to scan it further. - if (parentForm instanceof HTMLInputElement || parentForm.isEqualNode(realTarget)) return; + // If the parent form is an input element we bail. + if (parentForm instanceof HTMLInputElement) return; const hasShadowTree = event.target?.shadowRoot != null; const form = new _Form.Form(parentForm, realTarget, this.device, this.matching, this.shouldAutoprompt, hasShadowTree); this.forms.set(parentForm, form); @@ -12808,7 +12785,9 @@ exports.escapeXML = escapeXML; exports.findElementsInShadowTree = findElementsInShadowTree; exports.formatDuckAddress = void 0; exports.getActiveElement = getActiveElement; -exports.isEventWithinDax = exports.isAutofillEnabledFromProcessedConfig = exports.getTextShallow = exports.getDaxBoundingBox = void 0; +exports.getDaxBoundingBox = void 0; +exports.getFormElements = getFormElements; +exports.isEventWithinDax = exports.isAutofillEnabledFromProcessedConfig = exports.getTextShallow = void 0; exports.isFormLikelyToBeUsedAsPageWrapper = isFormLikelyToBeUsedAsPageWrapper; exports.isLikelyASubmitButton = exports.isIncontextSignupEnabledFromProcessedConfig = void 0; exports.isLocalNetwork = isLocalNetwork; @@ -13438,6 +13417,35 @@ function findElementsInShadowTree(root, selector) { return shadowElements; } +/** + * Default operation: finds elements using querySelectorAll. + * Optionally, if forced an form.elements is iterable, attempts to find elements using .elements instead, + * to catch field outside the form itsel using the form attribute. It also catches all elements + * when the markup is broken. We use .filter along with a selector to avoid any unwanted elements. + * Optionally, can be forced to scan the shadow tree. + * @param {string} selector + * @param {boolean} forceScanShadowTree + * @param {boolean} shouldCheckFormControls + * @returns {Element[]} + */ +function getFormElements(form, selector) { + let forceScanShadowTree = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + let shouldCheckFormControls = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; + // Some sites seem to be overriding `form.elements`, so we need to check if it's still iterable. + /** @type {Element[]|NodeListOf} element */ + let formElements = []; + if (shouldCheckFormControls && form instanceof HTMLFormElement && form.elements != null && Symbol.iterator in Object(form.elements)) { + formElements = [...form.elements].filter(el => el.matches(selector)); + } else { + formElements = form.querySelectorAll(selector); + } + if (forceScanShadowTree || formElements.length === 0) { + return [...formElements, ...findElementsInShadowTree(form, selector)]; + } else { + return [...formElements]; + } +} + },{"./Form/matching.js":34,"./constants.js":57,"@duckduckgo/content-scope-scripts/src/apple-utils":1}],55:[function(require,module,exports){ "use strict";