Skip to content

Commit

Permalink
refactor: use one getFormElements for all queries
Browse files Browse the repository at this point in the history
  • Loading branch information
dbajpeyi committed Nov 22, 2024
1 parent b49bc6c commit 50c6fed
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 147 deletions.
66 changes: 37 additions & 29 deletions dist/autofill-debug.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

66 changes: 37 additions & 29 deletions dist/autofill.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 3 additions & 26 deletions src/Form/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
shouldLog,
safeRegexTest,
getActiveElement,
findElementsInShadowTree,
getFormElements,
} from '../autofill-utils.js';

import { getInputSubtype, getInputMainType, createMatching, getInputVariant } from './matching.js';
Expand Down Expand Up @@ -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>} 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)
Expand All @@ -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));
Expand Down Expand Up @@ -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),
);
Expand Down
5 changes: 2 additions & 3 deletions src/Form/FormAnalyzer.js
Original file line number Diff line number Diff line change
@@ -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 */
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions src/Scanner.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
29 changes: 29 additions & 0 deletions src/autofill-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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>} 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,
Expand Down Expand Up @@ -620,4 +648,5 @@ export {
pierceShadowTree,
getActiveElement,
findElementsInShadowTree,
getFormElements,
};
Loading

0 comments on commit 50c6fed

Please sign in to comment.