-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Form] Always scan shadow elements when categorizing the form inputs #703
Changes from 1 commit
446b64e
9764283
f4eab41
7ae6722
265d2c2
7ba8e49
886809e
05a38a7
b49bc6c
50c6fed
9aa8f12
16a5f5c
c5f77f0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,7 +14,8 @@ import { | |
shouldLog, | ||
safeRegexTest, | ||
getActiveElement, | ||
getFormElements, | ||
queryFormElements, | ||
findElementsInShadowTree, | ||
} from '../autofill-utils.js'; | ||
|
||
import { getInputSubtype, getInputMainType, createMatching, getInputVariant } from './matching.js'; | ||
|
@@ -386,16 +387,36 @@ class Form { | |
} | ||
} | ||
|
||
/** | ||
* The function looks for form's control elements, and returns them if they're iterable. | ||
* @param {*} selector | ||
*/ | ||
getFormControlElements(selector) { | ||
// Some sites seem to be overriding `form.elements`, so we need to check if it's still iterable. | ||
if (this.form instanceof HTMLFormElement && this.form.elements != null && Symbol.iterator in Object(this.form.elements)) { | ||
// For form elements we use .elements to catch fields outside the form itself using the form attribute. | ||
// It also catches all elements when the markup is broken. | ||
// We use .filter to avoid specific types of elements. | ||
const formControls = [...this.form.elements].filter((el) => el.matches(selector)); | ||
return [...formControls]; | ||
} else { | ||
return null; | ||
} | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The function now has a clear separate purpose, that's different from querying the elements which is what we rather do in most other cases. |
||
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) | ||
if (this.form.matches(selector)) { | ||
this.addInput(this.form); | ||
} else { | ||
/** @type {Element[] | NodeList} */ | ||
|
||
// Also scan the form for shadow elements | ||
const foundInputs = getFormElements(this.form, selector, true, true); | ||
// Attempt to get form's control elements first as it can catch elements when markup is broke, or if the fields are outside the form. | ||
// Other wise use queryFormElements, that can scan for shadow tree. | ||
const formControlElements = this.getFormControlElements(selector); | ||
const foundInputs = | ||
formControlElements != null | ||
? [...formControlElements, ...findElementsInShadowTree(this.form, selector)] | ||
: queryFormElements(this.form, selector, true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's clear here now that we need to try to get elements first using |
||
|
||
if (foundInputs.length < MAX_INPUTS_PER_FORM) { | ||
foundInputs.forEach((input) => this.addInput(input)); | ||
|
@@ -465,7 +486,7 @@ class Form { | |
|
||
get submitButtons() { | ||
const selector = this.matching.cssSelector('submitButtonSelector'); | ||
const allButtons = /** @type {HTMLElement[]} */ (getFormElements(this.form, selector)); | ||
const allButtons = /** @type {HTMLElement[]} */ (queryFormElements(this.form, selector)); | ||
return allButtons.filter( | ||
(btn) => isPotentiallyViewable(btn) && isLikelyASubmitButton(btn, this.matching) && buttonMatchesFormType(btn, this), | ||
); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -589,30 +589,20 @@ function findElementsInShadowTree(root, selector) { | |
|
||
/** | ||
* 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) { | ||
function queryFormElements(form, selector, forceScanShadowTree = false) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please note this needs to be in the utils as it's also used outside of Form class. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At this point this is function no longer has anything to do with forms, right? It's just a wrapper around There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah for sure! |
||
// 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); | ||
} | ||
const formElements = form.querySelectorAll(selector); | ||
|
||
if (forceScanShadowTree || formElements.length === 0) { | ||
return [...formElements, ...findElementsInShadowTree(form, selector)]; | ||
} else { | ||
return [...formElements]; | ||
} | ||
return [...formElements]; | ||
} | ||
|
||
export { | ||
|
@@ -648,5 +638,5 @@ export { | |
pierceShadowTree, | ||
getActiveElement, | ||
findElementsInShadowTree, | ||
getFormElements, | ||
queryFormElements, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's put this in the utils as well. This class is already gigantic.