diff --git a/dist/autofill-debug.js b/dist/autofill-debug.js index 4faa9cc2a..42ec43fe5 100644 --- a/dist/autofill-debug.js +++ b/dist/autofill-debug.js @@ -11002,6 +11002,17 @@ class FormAnalyzer { } }); } + + /** + * Checks if the element is present in the cusotm elements registry and ends with a '-link' suffix + * @param {any} el + * @returns + */ + isCustomWebElementLink(el) { + const tagName = el.nodeName.toLowerCase(); + const isCustomElement = customElements != null && customElements.get(tagName) != null; + return isCustomElement && /-link$/.test(tagName) && (0, _autofillUtils.findEnclosedShadowElements)(el, 'a').length > 0; + } evaluateElement(el) { const string = (0, _autofillUtils.getTextShallow)(el); if (el.matches(this.matching.cssSelector('password'))) { @@ -11042,7 +11053,7 @@ class FormAnalyzer { return; } // if an external link matches one of the regexes, we assume the match is not pertinent to the current form - if (el instanceof HTMLAnchorElement && el.href && el.getAttribute('href') !== '#' || (el.getAttribute('role') || '').toUpperCase() === 'LINK' || el.matches('button[class*=secondary]')) { + if (el instanceof HTMLAnchorElement && el.href && el.getAttribute('href') !== '#' || (el.getAttribute('role') || '').toUpperCase() === 'LINK' || el.matches('button[class*=secondary]') || this.isCustomWebElementLink(el)) { let shouldFlip = true; let strength = 1; // Don't flip forgotten password links @@ -14542,7 +14553,7 @@ class DefaultScanner { return this; } inputs.forEach(input => this.addInput(input)); - if (context instanceof HTMLFormElement && this.forms.get(context)?.hasShadowTree && inputs.length === 0) { + if (context instanceof HTMLFormElement && this.forms.get(context)?.hasShadowTree) { (0, _autofillUtils.findEnclosedShadowElements)(context, formInputsSelectorWithoutSelect).forEach(input => { if (input instanceof HTMLInputElement) { this.addInput(input, context); @@ -14618,7 +14629,7 @@ class DefaultScanner { let traversalLayerCount = 0; let element = input; // traverse the DOM to search for related inputs - while (traversalLayerCount <= 5 && element.parentElement !== document.documentElement) { + while (traversalLayerCount <= 10 && element.parentElement !== document.documentElement) { // Avoid overlapping containers or forms const siblingForm = element.parentElement?.querySelector('form'); if (siblingForm && siblingForm !== element) { @@ -14806,12 +14817,10 @@ class DefaultScanner { // find the enclosing parent form, and scan it. if (realTarget instanceof HTMLInputElement && !realTarget.hasAttribute(ATTR_INPUT_TYPE)) { const parentForm = this.getParentForm(realTarget); - if (parentForm && parentForm instanceof HTMLFormElement) { - 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); - this.findEligibleInputs(parentForm); - } + 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); + this.findEligibleInputs(parentForm); } window.performance?.mark?.('scan_shadow:init:end'); (0, _autofillUtils.logPerformance)('scan_shadow'); @@ -17562,16 +17571,16 @@ function getActiveElement() { } /** - * Takes a root element and tries to find visible elements first, and if it fails, it tries to find shadow elements + * Takes a root element and tries to find elements in shadow DOMs that match the selector * @param {HTMLElement|HTMLFormElement} root * @param {string} selector * @returns {Element[]} */ function findEnclosedShadowElements(root, selector) { - // Check if there are any shadow elements that match the selector const shadowElements = []; const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT); - let node = walker.nextNode(); + /** @type {Node|null} */ + let node = walker.currentNode; while (node) { if (node instanceof HTMLElement && node.shadowRoot) { shadowElements.push(...node.shadowRoot.querySelectorAll(selector)); diff --git a/dist/autofill.js b/dist/autofill.js index 16db0cf8c..d4b4d787c 100644 --- a/dist/autofill.js +++ b/dist/autofill.js @@ -6836,6 +6836,17 @@ class FormAnalyzer { } }); } + + /** + * Checks if the element is present in the cusotm elements registry and ends with a '-link' suffix + * @param {any} el + * @returns + */ + isCustomWebElementLink(el) { + const tagName = el.nodeName.toLowerCase(); + const isCustomElement = customElements != null && customElements.get(tagName) != null; + return isCustomElement && /-link$/.test(tagName) && (0, _autofillUtils.findEnclosedShadowElements)(el, 'a').length > 0; + } evaluateElement(el) { const string = (0, _autofillUtils.getTextShallow)(el); if (el.matches(this.matching.cssSelector('password'))) { @@ -6876,7 +6887,7 @@ class FormAnalyzer { return; } // if an external link matches one of the regexes, we assume the match is not pertinent to the current form - if (el instanceof HTMLAnchorElement && el.href && el.getAttribute('href') !== '#' || (el.getAttribute('role') || '').toUpperCase() === 'LINK' || el.matches('button[class*=secondary]')) { + if (el instanceof HTMLAnchorElement && el.href && el.getAttribute('href') !== '#' || (el.getAttribute('role') || '').toUpperCase() === 'LINK' || el.matches('button[class*=secondary]') || this.isCustomWebElementLink(el)) { let shouldFlip = true; let strength = 1; // Don't flip forgotten password links @@ -10376,7 +10387,7 @@ class DefaultScanner { return this; } inputs.forEach(input => this.addInput(input)); - if (context instanceof HTMLFormElement && this.forms.get(context)?.hasShadowTree && inputs.length === 0) { + if (context instanceof HTMLFormElement && this.forms.get(context)?.hasShadowTree) { (0, _autofillUtils.findEnclosedShadowElements)(context, formInputsSelectorWithoutSelect).forEach(input => { if (input instanceof HTMLInputElement) { this.addInput(input, context); @@ -10452,7 +10463,7 @@ class DefaultScanner { let traversalLayerCount = 0; let element = input; // traverse the DOM to search for related inputs - while (traversalLayerCount <= 5 && element.parentElement !== document.documentElement) { + while (traversalLayerCount <= 10 && element.parentElement !== document.documentElement) { // Avoid overlapping containers or forms const siblingForm = element.parentElement?.querySelector('form'); if (siblingForm && siblingForm !== element) { @@ -10640,12 +10651,10 @@ class DefaultScanner { // find the enclosing parent form, and scan it. if (realTarget instanceof HTMLInputElement && !realTarget.hasAttribute(ATTR_INPUT_TYPE)) { const parentForm = this.getParentForm(realTarget); - if (parentForm && parentForm instanceof HTMLFormElement) { - 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); - this.findEligibleInputs(parentForm); - } + 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); + this.findEligibleInputs(parentForm); } window.performance?.mark?.('scan_shadow:init:end'); (0, _autofillUtils.logPerformance)('scan_shadow'); @@ -13396,16 +13405,16 @@ function getActiveElement() { } /** - * Takes a root element and tries to find visible elements first, and if it fails, it tries to find shadow elements + * Takes a root element and tries to find elements in shadow DOMs that match the selector * @param {HTMLElement|HTMLFormElement} root * @param {string} selector * @returns {Element[]} */ function findEnclosedShadowElements(root, selector) { - // Check if there are any shadow elements that match the selector const shadowElements = []; const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT); - let node = walker.nextNode(); + /** @type {Node|null} */ + let node = walker.currentNode; while (node) { if (node instanceof HTMLElement && node.shadowRoot) { shadowElements.push(...node.shadowRoot.querySelectorAll(selector)); diff --git a/src/Form/FormAnalyzer.js b/src/Form/FormAnalyzer.js index 1cbcdfa09..1ebd33b8a 100644 --- a/src/Form/FormAnalyzer.js +++ b/src/Form/FormAnalyzer.js @@ -223,6 +223,17 @@ class FormAnalyzer { }); } + /** + * Checks if the element is present in the cusotm elements registry and ends with a '-link' suffix + * @param {any} el + * @returns + */ + isCustomWebElementLink(el) { + const tagName = el.nodeName.toLowerCase(); + const isCustomElement = customElements != null && customElements.get(tagName) != null; + return isCustomElement && /-link$/.test(tagName) && findEnclosedShadowElements(el, 'a').length > 0; + } + evaluateElement(el) { const string = getTextShallow(el); @@ -262,7 +273,8 @@ class FormAnalyzer { if ( (el instanceof HTMLAnchorElement && el.href && el.getAttribute('href') !== '#') || (el.getAttribute('role') || '').toUpperCase() === 'LINK' || - el.matches('button[class*=secondary]') + el.matches('button[class*=secondary]') || + this.isCustomWebElementLink(el) ) { let shouldFlip = true; let strength = 1; diff --git a/src/Scanner.js b/src/Scanner.js index a33a2fe01..c9425841e 100644 --- a/src/Scanner.js +++ b/src/Scanner.js @@ -160,7 +160,7 @@ class DefaultScanner { return this; } inputs.forEach((input) => this.addInput(input)); - if (context instanceof HTMLFormElement && this.forms.get(context)?.hasShadowTree && inputs.length === 0) { + if (context instanceof HTMLFormElement && this.forms.get(context)?.hasShadowTree) { findEnclosedShadowElements(context, formInputsSelectorWithoutSelect).forEach((input) => { if (input instanceof HTMLInputElement) { this.addInput(input, context); @@ -239,7 +239,7 @@ class DefaultScanner { let traversalLayerCount = 0; let element = input; // traverse the DOM to search for related inputs - while (traversalLayerCount <= 5 && element.parentElement !== document.documentElement) { + while (traversalLayerCount <= 10 && element.parentElement !== document.documentElement) { // Avoid overlapping containers or forms const siblingForm = element.parentElement?.querySelector('form'); if (siblingForm && siblingForm !== element) { @@ -436,12 +436,10 @@ class DefaultScanner { // find the enclosing parent form, and scan it. if (realTarget instanceof HTMLInputElement && !realTarget.hasAttribute(ATTR_INPUT_TYPE)) { const parentForm = this.getParentForm(realTarget); - if (parentForm && parentForm instanceof HTMLFormElement) { - const hasShadowTree = event.target?.shadowRoot != null; - const form = new Form(parentForm, realTarget, this.device, this.matching, this.shouldAutoprompt, hasShadowTree); - this.forms.set(parentForm, form); - this.findEligibleInputs(parentForm); - } + const hasShadowTree = event.target?.shadowRoot != null; + const form = new Form(parentForm, realTarget, this.device, this.matching, this.shouldAutoprompt, hasShadowTree); + this.forms.set(parentForm, form); + this.findEligibleInputs(parentForm); } window.performance?.mark?.('scan_shadow:init:end'); diff --git a/src/autofill-utils.js b/src/autofill-utils.js index 7fdd9e0f9..8de27b60d 100644 --- a/src/autofill-utils.js +++ b/src/autofill-utils.js @@ -567,17 +567,16 @@ function getActiveElement(root = document) { } /** - * Takes a root element and tries to find visible elements first, and if it fails, it tries to find shadow elements + * Takes a root element and tries to find elements in shadow DOMs that match the selector * @param {HTMLElement|HTMLFormElement} root * @param {string} selector * @returns {Element[]} */ function findEnclosedShadowElements(root, selector) { - // Check if there are any shadow elements that match the selector const shadowElements = []; const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT); - - let node = walker.nextNode(); + /** @type {Node|null} */ + let node = walker.currentNode; while (node) { if (node instanceof HTMLElement && node.shadowRoot) { shadowElements.push(...node.shadowRoot.querySelectorAll(selector)); diff --git a/swift-package/Resources/assets/autofill-debug.js b/swift-package/Resources/assets/autofill-debug.js index 4faa9cc2a..42ec43fe5 100644 --- a/swift-package/Resources/assets/autofill-debug.js +++ b/swift-package/Resources/assets/autofill-debug.js @@ -11002,6 +11002,17 @@ class FormAnalyzer { } }); } + + /** + * Checks if the element is present in the cusotm elements registry and ends with a '-link' suffix + * @param {any} el + * @returns + */ + isCustomWebElementLink(el) { + const tagName = el.nodeName.toLowerCase(); + const isCustomElement = customElements != null && customElements.get(tagName) != null; + return isCustomElement && /-link$/.test(tagName) && (0, _autofillUtils.findEnclosedShadowElements)(el, 'a').length > 0; + } evaluateElement(el) { const string = (0, _autofillUtils.getTextShallow)(el); if (el.matches(this.matching.cssSelector('password'))) { @@ -11042,7 +11053,7 @@ class FormAnalyzer { return; } // if an external link matches one of the regexes, we assume the match is not pertinent to the current form - if (el instanceof HTMLAnchorElement && el.href && el.getAttribute('href') !== '#' || (el.getAttribute('role') || '').toUpperCase() === 'LINK' || el.matches('button[class*=secondary]')) { + if (el instanceof HTMLAnchorElement && el.href && el.getAttribute('href') !== '#' || (el.getAttribute('role') || '').toUpperCase() === 'LINK' || el.matches('button[class*=secondary]') || this.isCustomWebElementLink(el)) { let shouldFlip = true; let strength = 1; // Don't flip forgotten password links @@ -14542,7 +14553,7 @@ class DefaultScanner { return this; } inputs.forEach(input => this.addInput(input)); - if (context instanceof HTMLFormElement && this.forms.get(context)?.hasShadowTree && inputs.length === 0) { + if (context instanceof HTMLFormElement && this.forms.get(context)?.hasShadowTree) { (0, _autofillUtils.findEnclosedShadowElements)(context, formInputsSelectorWithoutSelect).forEach(input => { if (input instanceof HTMLInputElement) { this.addInput(input, context); @@ -14618,7 +14629,7 @@ class DefaultScanner { let traversalLayerCount = 0; let element = input; // traverse the DOM to search for related inputs - while (traversalLayerCount <= 5 && element.parentElement !== document.documentElement) { + while (traversalLayerCount <= 10 && element.parentElement !== document.documentElement) { // Avoid overlapping containers or forms const siblingForm = element.parentElement?.querySelector('form'); if (siblingForm && siblingForm !== element) { @@ -14806,12 +14817,10 @@ class DefaultScanner { // find the enclosing parent form, and scan it. if (realTarget instanceof HTMLInputElement && !realTarget.hasAttribute(ATTR_INPUT_TYPE)) { const parentForm = this.getParentForm(realTarget); - if (parentForm && parentForm instanceof HTMLFormElement) { - 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); - this.findEligibleInputs(parentForm); - } + 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); + this.findEligibleInputs(parentForm); } window.performance?.mark?.('scan_shadow:init:end'); (0, _autofillUtils.logPerformance)('scan_shadow'); @@ -17562,16 +17571,16 @@ function getActiveElement() { } /** - * Takes a root element and tries to find visible elements first, and if it fails, it tries to find shadow elements + * Takes a root element and tries to find elements in shadow DOMs that match the selector * @param {HTMLElement|HTMLFormElement} root * @param {string} selector * @returns {Element[]} */ function findEnclosedShadowElements(root, selector) { - // Check if there are any shadow elements that match the selector const shadowElements = []; const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT); - let node = walker.nextNode(); + /** @type {Node|null} */ + let node = walker.currentNode; while (node) { if (node instanceof HTMLElement && node.shadowRoot) { shadowElements.push(...node.shadowRoot.querySelectorAll(selector)); diff --git a/swift-package/Resources/assets/autofill.js b/swift-package/Resources/assets/autofill.js index 16db0cf8c..d4b4d787c 100644 --- a/swift-package/Resources/assets/autofill.js +++ b/swift-package/Resources/assets/autofill.js @@ -6836,6 +6836,17 @@ class FormAnalyzer { } }); } + + /** + * Checks if the element is present in the cusotm elements registry and ends with a '-link' suffix + * @param {any} el + * @returns + */ + isCustomWebElementLink(el) { + const tagName = el.nodeName.toLowerCase(); + const isCustomElement = customElements != null && customElements.get(tagName) != null; + return isCustomElement && /-link$/.test(tagName) && (0, _autofillUtils.findEnclosedShadowElements)(el, 'a').length > 0; + } evaluateElement(el) { const string = (0, _autofillUtils.getTextShallow)(el); if (el.matches(this.matching.cssSelector('password'))) { @@ -6876,7 +6887,7 @@ class FormAnalyzer { return; } // if an external link matches one of the regexes, we assume the match is not pertinent to the current form - if (el instanceof HTMLAnchorElement && el.href && el.getAttribute('href') !== '#' || (el.getAttribute('role') || '').toUpperCase() === 'LINK' || el.matches('button[class*=secondary]')) { + if (el instanceof HTMLAnchorElement && el.href && el.getAttribute('href') !== '#' || (el.getAttribute('role') || '').toUpperCase() === 'LINK' || el.matches('button[class*=secondary]') || this.isCustomWebElementLink(el)) { let shouldFlip = true; let strength = 1; // Don't flip forgotten password links @@ -10376,7 +10387,7 @@ class DefaultScanner { return this; } inputs.forEach(input => this.addInput(input)); - if (context instanceof HTMLFormElement && this.forms.get(context)?.hasShadowTree && inputs.length === 0) { + if (context instanceof HTMLFormElement && this.forms.get(context)?.hasShadowTree) { (0, _autofillUtils.findEnclosedShadowElements)(context, formInputsSelectorWithoutSelect).forEach(input => { if (input instanceof HTMLInputElement) { this.addInput(input, context); @@ -10452,7 +10463,7 @@ class DefaultScanner { let traversalLayerCount = 0; let element = input; // traverse the DOM to search for related inputs - while (traversalLayerCount <= 5 && element.parentElement !== document.documentElement) { + while (traversalLayerCount <= 10 && element.parentElement !== document.documentElement) { // Avoid overlapping containers or forms const siblingForm = element.parentElement?.querySelector('form'); if (siblingForm && siblingForm !== element) { @@ -10640,12 +10651,10 @@ class DefaultScanner { // find the enclosing parent form, and scan it. if (realTarget instanceof HTMLInputElement && !realTarget.hasAttribute(ATTR_INPUT_TYPE)) { const parentForm = this.getParentForm(realTarget); - if (parentForm && parentForm instanceof HTMLFormElement) { - 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); - this.findEligibleInputs(parentForm); - } + 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); + this.findEligibleInputs(parentForm); } window.performance?.mark?.('scan_shadow:init:end'); (0, _autofillUtils.logPerformance)('scan_shadow'); @@ -13396,16 +13405,16 @@ function getActiveElement() { } /** - * Takes a root element and tries to find visible elements first, and if it fails, it tries to find shadow elements + * Takes a root element and tries to find elements in shadow DOMs that match the selector * @param {HTMLElement|HTMLFormElement} root * @param {string} selector * @returns {Element[]} */ function findEnclosedShadowElements(root, selector) { - // Check if there are any shadow elements that match the selector const shadowElements = []; const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT); - let node = walker.nextNode(); + /** @type {Node|null} */ + let node = walker.currentNode; while (node) { if (node instanceof HTMLElement && node.shadowRoot) { shadowElements.push(...node.shadowRoot.querySelectorAll(selector));