[PM-6352] Autofill functionality not working with re-hydrated DOM elements (#8033)
* [PM-6352] Autofill functionality not working with re-hydtrated DOM elements * [PM-6352] Autofill functionality not working with re-hydtrated DOM elements * [PM-6352] Fixing issue found with chrome.dom.openOrClosedShadowRoot call * [PM-6352] Implementing jest tests and adding utils methods where appropriate * [PM-6352] Implementing jest tests and adding utils methods where appropriate
This commit is contained in:
parent
3e6ba798ca
commit
b2c3ecda0f
|
@ -10,7 +10,12 @@ import AutofillField from "../models/autofill-field";
|
||||||
import AutofillOverlayButtonIframe from "../overlay/iframe-content/autofill-overlay-button-iframe";
|
import AutofillOverlayButtonIframe from "../overlay/iframe-content/autofill-overlay-button-iframe";
|
||||||
import AutofillOverlayListIframe from "../overlay/iframe-content/autofill-overlay-list-iframe";
|
import AutofillOverlayListIframe from "../overlay/iframe-content/autofill-overlay-list-iframe";
|
||||||
import { ElementWithOpId, FillableFormFieldElement, FormFieldElement } from "../types";
|
import { ElementWithOpId, FillableFormFieldElement, FormFieldElement } from "../types";
|
||||||
import { generateRandomCustomElementName, sendExtensionMessage, setElementStyles } from "../utils";
|
import {
|
||||||
|
elementIsFillableFormField,
|
||||||
|
generateRandomCustomElementName,
|
||||||
|
sendExtensionMessage,
|
||||||
|
setElementStyles,
|
||||||
|
} from "../utils";
|
||||||
import {
|
import {
|
||||||
AutofillOverlayElement,
|
AutofillOverlayElement,
|
||||||
RedirectFocusDirection,
|
RedirectFocusDirection,
|
||||||
|
@ -408,7 +413,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||||
* @param formFieldElement - The form field element that triggered the input event.
|
* @param formFieldElement - The form field element that triggered the input event.
|
||||||
*/
|
*/
|
||||||
private triggerFormFieldInput(formFieldElement: ElementWithOpId<FormFieldElement>) {
|
private triggerFormFieldInput(formFieldElement: ElementWithOpId<FormFieldElement>) {
|
||||||
if (formFieldElement instanceof HTMLSpanElement) {
|
if (!elementIsFillableFormField(formFieldElement)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,19 @@ import {
|
||||||
FormFieldElement,
|
FormFieldElement,
|
||||||
FormElementWithAttribute,
|
FormElementWithAttribute,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
|
import {
|
||||||
|
elementIsDescriptionDetailsElement,
|
||||||
|
elementIsDescriptionTermElement,
|
||||||
|
elementIsFillableFormField,
|
||||||
|
elementIsFormElement,
|
||||||
|
elementIsLabelElement,
|
||||||
|
elementIsSelectElement,
|
||||||
|
elementIsSpanElement,
|
||||||
|
nodeIsFormElement,
|
||||||
|
nodeIsElement,
|
||||||
|
elementIsInputElement,
|
||||||
|
elementIsTextAreaElement,
|
||||||
|
} from "../utils";
|
||||||
|
|
||||||
import { AutofillOverlayContentService } from "./abstractions/autofill-overlay-content.service";
|
import { AutofillOverlayContentService } from "./abstractions/autofill-overlay-content.service";
|
||||||
import {
|
import {
|
||||||
|
@ -347,7 +360,7 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
|
||||||
tagName: this.getAttributeLowerCase(element, "tagName"),
|
tagName: this.getAttributeLowerCase(element, "tagName"),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (element.tagName.toLowerCase() === "span") {
|
if (elementIsSpanElement(element)) {
|
||||||
this.cacheAutofillFieldElement(index, element, autofillFieldBase);
|
this.cacheAutofillFieldElement(index, element, autofillFieldBase);
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
@ -383,8 +396,7 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
|
||||||
autoCompleteType: this.getAutoCompleteAttribute(element),
|
autoCompleteType: this.getAutoCompleteAttribute(element),
|
||||||
disabled: this.getAttributeBoolean(element, "disabled"),
|
disabled: this.getAttributeBoolean(element, "disabled"),
|
||||||
readonly: this.getAttributeBoolean(element, "readonly"),
|
readonly: this.getAttributeBoolean(element, "readonly"),
|
||||||
selectInfo:
|
selectInfo: elementIsSelectElement(element)
|
||||||
element.tagName.toLowerCase() === "select"
|
|
||||||
? this.getSelectElementOptions(element as HTMLSelectElement)
|
? this.getSelectElementOptions(element as HTMLSelectElement)
|
||||||
: null,
|
: null,
|
||||||
form: fieldFormElement ? this.getPropertyOrAttribute(fieldFormElement, "opid") : null,
|
form: fieldFormElement ? this.getPropertyOrAttribute(fieldFormElement, "opid") : null,
|
||||||
|
@ -502,7 +514,7 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
|
||||||
|
|
||||||
let currentElement: HTMLElement | null = element;
|
let currentElement: HTMLElement | null = element;
|
||||||
while (currentElement && currentElement !== document.documentElement) {
|
while (currentElement && currentElement !== document.documentElement) {
|
||||||
if (currentElement.tagName.toLowerCase() === "label") {
|
if (elementIsLabelElement(currentElement)) {
|
||||||
labelElementsSet.add(currentElement);
|
labelElementsSet.add(currentElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -511,10 +523,10 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!labelElementsSet.size &&
|
!labelElementsSet.size &&
|
||||||
element.parentElement?.tagName.toLowerCase() === "dd" &&
|
elementIsDescriptionDetailsElement(element.parentElement) &&
|
||||||
element.parentElement.previousElementSibling?.tagName.toLowerCase() === "dt"
|
elementIsDescriptionTermElement(element.parentElement.previousElementSibling)
|
||||||
) {
|
) {
|
||||||
labelElementsSet.add(element.parentElement.previousElementSibling as HTMLElement);
|
labelElementsSet.add(element.parentElement.previousElementSibling);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.createLabelElementsTag(labelElementsSet);
|
return this.createLabelElementsTag(labelElementsSet);
|
||||||
|
@ -577,13 +589,10 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private getAutofillFieldMaxLength(element: FormFieldElement): number | null {
|
private getAutofillFieldMaxLength(element: FormFieldElement): number | null {
|
||||||
const elementTagName = element.tagName.toLowerCase();
|
const elementHasMaxLengthProperty =
|
||||||
const elementHasMaxLengthProperty = elementTagName === "input" || elementTagName === "textarea";
|
elementIsInputElement(element) || elementIsTextAreaElement(element);
|
||||||
const elementMaxLength =
|
const elementMaxLength =
|
||||||
elementHasMaxLengthProperty &&
|
elementHasMaxLengthProperty && element.maxLength > -1 ? element.maxLength : 999;
|
||||||
(element as HTMLInputElement | HTMLTextAreaElement).maxLength > -1
|
|
||||||
? (element as HTMLInputElement | HTMLTextAreaElement).maxLength
|
|
||||||
: 999;
|
|
||||||
|
|
||||||
return elementHasMaxLengthProperty ? Math.min(elementMaxLength, 999) : null;
|
return elementHasMaxLengthProperty ? Math.min(elementMaxLength, 999) : null;
|
||||||
}
|
}
|
||||||
|
@ -747,8 +756,7 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
|
||||||
// Prioritize capturing text content from elements rather than nodes.
|
// Prioritize capturing text content from elements rather than nodes.
|
||||||
currentElement = currentElement.parentElement || currentElement.parentNode;
|
currentElement = currentElement.parentElement || currentElement.parentNode;
|
||||||
|
|
||||||
let siblingElement =
|
let siblingElement = nodeIsElement(currentElement)
|
||||||
currentElement instanceof HTMLElement
|
|
||||||
? currentElement.previousElementSibling
|
? currentElement.previousElementSibling
|
||||||
: currentElement.previousSibling;
|
: currentElement.previousSibling;
|
||||||
while (siblingElement?.lastChild && !this.isNewSectionElement(siblingElement)) {
|
while (siblingElement?.lastChild && !this.isNewSectionElement(siblingElement)) {
|
||||||
|
@ -793,13 +801,13 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private getElementValue(element: FormFieldElement): string {
|
private getElementValue(element: FormFieldElement): string {
|
||||||
if (element.tagName.toLowerCase() === "span") {
|
if (!elementIsFillableFormField(element)) {
|
||||||
const spanTextContent = element.textContent || element.innerText;
|
const spanTextContent = element.textContent || element.innerText;
|
||||||
return spanTextContent || "";
|
return spanTextContent || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const elementValue = (element as FillableFormFieldElement).value || "";
|
const elementValue = element.value || "";
|
||||||
const elementType = String((element as FillableFormFieldElement).type).toLowerCase();
|
const elementType = String(element.type).toLowerCase();
|
||||||
if ("checked" in element && elementType === "checkbox") {
|
if ("checked" in element && elementType === "checkbox") {
|
||||||
return element.checked ? "✓" : "";
|
return element.checked ? "✓" : "";
|
||||||
}
|
}
|
||||||
|
@ -850,7 +858,7 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
|
||||||
const formElements: Node[] = [];
|
const formElements: Node[] = [];
|
||||||
const formFieldElements: Node[] = [];
|
const formFieldElements: Node[] = [];
|
||||||
this.queryAllTreeWalkerNodes(document.documentElement, (node: Node) => {
|
this.queryAllTreeWalkerNodes(document.documentElement, (node: Node) => {
|
||||||
if ((node as HTMLFormElement).tagName?.toLowerCase() === "form") {
|
if (nodeIsFormElement(node)) {
|
||||||
formElements.push(node);
|
formElements.push(node);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -873,7 +881,7 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private isNodeFormFieldElement(node: Node): boolean {
|
private isNodeFormFieldElement(node: Node): boolean {
|
||||||
if (!(node instanceof HTMLElement)) {
|
if (!nodeIsElement(node)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -904,15 +912,20 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
|
||||||
* @param {Node} node
|
* @param {Node} node
|
||||||
*/
|
*/
|
||||||
private getShadowRoot(node: Node): ShadowRoot | null {
|
private getShadowRoot(node: Node): ShadowRoot | null {
|
||||||
if (!(node instanceof HTMLElement) || node.childNodes.length !== 0) {
|
if (!nodeIsElement(node) || node.childNodes.length !== 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.shadowRoot) {
|
if (node.shadowRoot) {
|
||||||
return node.shadowRoot;
|
return node.shadowRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((chrome as any).dom?.openOrClosedShadowRoot) {
|
if ((chrome as any).dom?.openOrClosedShadowRoot) {
|
||||||
|
try {
|
||||||
return (chrome as any).dom.openOrClosedShadowRoot(node);
|
return (chrome as any).dom.openOrClosedShadowRoot(node);
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (node as any).openOrClosedShadowRoot;
|
return (node as any).openOrClosedShadowRoot;
|
||||||
|
@ -1052,15 +1065,14 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
|
||||||
const mutatedElements: Node[] = [];
|
const mutatedElements: Node[] = [];
|
||||||
for (let index = 0; index < nodes.length; index++) {
|
for (let index = 0; index < nodes.length; index++) {
|
||||||
const node = nodes[index];
|
const node = nodes[index];
|
||||||
if (!(node instanceof HTMLElement)) {
|
if (!nodeIsElement(node)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const autofillElementNodes = this.queryAllTreeWalkerNodes(
|
const autofillElementNodes = this.queryAllTreeWalkerNodes(
|
||||||
node,
|
node,
|
||||||
(walkerNode: Node) =>
|
(walkerNode: Node) =>
|
||||||
(walkerNode as HTMLElement).tagName?.toLowerCase() === "form" ||
|
nodeIsFormElement(walkerNode) || this.isNodeFormFieldElement(walkerNode),
|
||||||
this.isNodeFormFieldElement(walkerNode),
|
|
||||||
) as HTMLElement[];
|
) as HTMLElement[];
|
||||||
|
|
||||||
if (autofillElementNodes.length) {
|
if (autofillElementNodes.length) {
|
||||||
|
@ -1115,11 +1127,8 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
|
||||||
private deleteCachedAutofillElement(
|
private deleteCachedAutofillElement(
|
||||||
element: ElementWithOpId<HTMLFormElement> | ElementWithOpId<FormFieldElement>,
|
element: ElementWithOpId<HTMLFormElement> | ElementWithOpId<FormFieldElement>,
|
||||||
) {
|
) {
|
||||||
if (
|
if (elementIsFormElement(element) && this.autofillFormElements.has(element)) {
|
||||||
element.tagName.toLowerCase() === "form" &&
|
this.autofillFormElements.delete(element);
|
||||||
this.autofillFormElements.has(element as ElementWithOpId<HTMLFormElement>)
|
|
||||||
) {
|
|
||||||
this.autofillFormElements.delete(element as ElementWithOpId<HTMLFormElement>);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1151,7 +1160,7 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
|
||||||
*/
|
*/
|
||||||
private handleAutofillElementAttributeMutation(mutation: MutationRecord) {
|
private handleAutofillElementAttributeMutation(mutation: MutationRecord) {
|
||||||
const targetElement = mutation.target;
|
const targetElement = mutation.target;
|
||||||
if (!(targetElement instanceof HTMLElement)) {
|
if (!nodeIsElement(targetElement)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
import { EVENTS, TYPE_CHECK } from "../constants";
|
import { EVENTS, TYPE_CHECK } from "../constants";
|
||||||
import AutofillScript, { AutofillInsertActions, FillScript } from "../models/autofill-script";
|
import AutofillScript, { AutofillInsertActions, FillScript } from "../models/autofill-script";
|
||||||
import { FormFieldElement } from "../types";
|
import { FormFieldElement } from "../types";
|
||||||
|
import {
|
||||||
|
elementIsFillableFormField,
|
||||||
|
elementIsInputElement,
|
||||||
|
elementIsSelectElement,
|
||||||
|
elementIsTextAreaElement,
|
||||||
|
nodeIsInputElement,
|
||||||
|
} from "../utils";
|
||||||
|
|
||||||
import { InsertAutofillContentService as InsertAutofillContentServiceInterface } from "./abstractions/insert-autofill-content.service";
|
import { InsertAutofillContentService as InsertAutofillContentServiceInterface } from "./abstractions/insert-autofill-content.service";
|
||||||
import CollectAutofillContentService from "./collect-autofill-content.service";
|
import CollectAutofillContentService from "./collect-autofill-content.service";
|
||||||
|
@ -96,7 +103,7 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf
|
||||||
return Boolean(
|
return Boolean(
|
||||||
this.collectAutofillContentService.queryAllTreeWalkerNodes(
|
this.collectAutofillContentService.queryAllTreeWalkerNodes(
|
||||||
document.documentElement,
|
document.documentElement,
|
||||||
(node: Node) => node instanceof HTMLInputElement && node.type === "password",
|
(node: Node) => nodeIsInputElement(node) && node.type === "password",
|
||||||
false,
|
false,
|
||||||
)?.length,
|
)?.length,
|
||||||
);
|
);
|
||||||
|
@ -195,8 +202,8 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf
|
||||||
*/
|
*/
|
||||||
private insertValueIntoField(element: FormFieldElement | null, value: string) {
|
private insertValueIntoField(element: FormFieldElement | null, value: string) {
|
||||||
const elementCanBeReadonly =
|
const elementCanBeReadonly =
|
||||||
element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement;
|
elementIsInputElement(element) || elementIsTextAreaElement(element);
|
||||||
const elementCanBeFilled = elementCanBeReadonly || element instanceof HTMLSelectElement;
|
const elementCanBeFilled = elementCanBeReadonly || elementIsSelectElement(element);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!element ||
|
!element ||
|
||||||
|
@ -207,13 +214,13 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element instanceof HTMLSpanElement) {
|
if (!elementIsFillableFormField(element)) {
|
||||||
this.handleInsertValueAndTriggerSimulatedEvents(element, () => (element.innerText = value));
|
this.handleInsertValueAndTriggerSimulatedEvents(element, () => (element.innerText = value));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isFillableCheckboxOrRadioElement =
|
const isFillableCheckboxOrRadioElement =
|
||||||
element instanceof HTMLInputElement &&
|
elementIsInputElement(element) &&
|
||||||
new Set(["checkbox", "radio"]).has(element.type) &&
|
new Set(["checkbox", "radio"]).has(element.type) &&
|
||||||
new Set(["true", "y", "1", "yes", "✓"]).has(String(value).toLowerCase());
|
new Set(["true", "y", "1", "yes", "✓"]).has(String(value).toLowerCase());
|
||||||
if (isFillableCheckboxOrRadioElement) {
|
if (isFillableCheckboxOrRadioElement) {
|
||||||
|
@ -285,7 +292,7 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf
|
||||||
*/
|
*/
|
||||||
private triggerFillAnimationOnElement(element: FormFieldElement): void {
|
private triggerFillAnimationOnElement(element: FormFieldElement): void {
|
||||||
const skipAnimatingElement =
|
const skipAnimatingElement =
|
||||||
!(element instanceof HTMLSpanElement) &&
|
elementIsFillableFormField(element) &&
|
||||||
!new Set(["email", "text", "password", "number", "tel", "url"]).has(element?.type);
|
!new Set(["email", "text", "password", "number", "tel", "url"]).has(element?.type);
|
||||||
|
|
||||||
if (this.domElementVisibilityService.isElementHiddenByCss(element) || skipAnimatingElement) {
|
if (this.domElementVisibilityService.isElementHiddenByCss(element) || skipAnimatingElement) {
|
||||||
|
@ -371,6 +378,10 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf
|
||||||
element.dispatchEvent(new Event(simulatedInputEvents[index], { bubbles: true }));
|
element.dispatchEvent(new Event(simulatedInputEvents[index], { bubbles: true }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private nodeIsElement(node: Node): node is HTMLElement {
|
||||||
|
return node.nodeType === Node.ELEMENT_NODE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default InsertAutofillContentService;
|
export default InsertAutofillContentService;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { AutofillPort } from "../enums/autofill-port.enums";
|
import { AutofillPort } from "../enums/autofill-port.enums";
|
||||||
|
import { FillableFormFieldElement, FormFieldElement } from "../types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a random string of characters that formatted as a custom element name.
|
* Generates a random string of characters that formatted as a custom element name.
|
||||||
|
@ -151,6 +152,127 @@ function setupAutofillInitDisconnectAction(windowContext: Window) {
|
||||||
setupExtensionDisconnectAction(onDisconnectCallback);
|
setupExtensionDisconnectAction(onDisconnectCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies whether an element is a fillable form field.
|
||||||
|
* This is determined by whether the element is a form field and not a span.
|
||||||
|
*
|
||||||
|
* @param formFieldElement - The form field element to check.
|
||||||
|
*/
|
||||||
|
function elementIsFillableFormField(
|
||||||
|
formFieldElement: FormFieldElement,
|
||||||
|
): formFieldElement is FillableFormFieldElement {
|
||||||
|
return formFieldElement?.tagName.toLowerCase() !== "span";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies whether an element is an instance of a specific tag name.
|
||||||
|
*
|
||||||
|
* @param element - The element to check.
|
||||||
|
* @param tagName - The tag name to check against.
|
||||||
|
*/
|
||||||
|
function elementIsInstanceOf<T extends Element>(element: Element, tagName: string): element is T {
|
||||||
|
return element?.tagName.toLowerCase() === tagName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies whether an element is a span element.
|
||||||
|
*
|
||||||
|
* @param element - The element to check.
|
||||||
|
*/
|
||||||
|
function elementIsSpanElement(element: Element): element is HTMLSpanElement {
|
||||||
|
return elementIsInstanceOf<HTMLSpanElement>(element, "span");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies whether an element is an input field.
|
||||||
|
*
|
||||||
|
* @param element - The element to check.
|
||||||
|
*/
|
||||||
|
function elementIsInputElement(element: Element): element is HTMLInputElement {
|
||||||
|
return elementIsInstanceOf<HTMLInputElement>(element, "input");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies whether an element is a select field.
|
||||||
|
*
|
||||||
|
* @param element - The element to check.
|
||||||
|
*/
|
||||||
|
function elementIsSelectElement(element: Element): element is HTMLSelectElement {
|
||||||
|
return elementIsInstanceOf<HTMLSelectElement>(element, "select");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies whether an element is a textarea field.
|
||||||
|
*
|
||||||
|
* @param element - The element to check.
|
||||||
|
*/
|
||||||
|
function elementIsTextAreaElement(element: Element): element is HTMLTextAreaElement {
|
||||||
|
return elementIsInstanceOf<HTMLTextAreaElement>(element, "textarea");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies whether an element is a form element.
|
||||||
|
*
|
||||||
|
* @param element - The element to check.
|
||||||
|
*/
|
||||||
|
function elementIsFormElement(element: Element): element is HTMLFormElement {
|
||||||
|
return elementIsInstanceOf<HTMLFormElement>(element, "form");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies whether an element is a label element.
|
||||||
|
*
|
||||||
|
* @param element - The element to check.
|
||||||
|
*/
|
||||||
|
function elementIsLabelElement(element: Element): element is HTMLLabelElement {
|
||||||
|
return elementIsInstanceOf<HTMLLabelElement>(element, "label");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies whether an element is a description details `dd` element.
|
||||||
|
*
|
||||||
|
* @param element - The element to check.
|
||||||
|
*/
|
||||||
|
function elementIsDescriptionDetailsElement(element: Element): element is HTMLElement {
|
||||||
|
return elementIsInstanceOf<HTMLElement>(element, "dd");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies whether an element is a description term `dt` element.
|
||||||
|
*
|
||||||
|
* @param element - The element to check.
|
||||||
|
*/
|
||||||
|
function elementIsDescriptionTermElement(element: Element): element is HTMLElement {
|
||||||
|
return elementIsInstanceOf<HTMLElement>(element, "dt");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies whether a node is an HTML element.
|
||||||
|
*
|
||||||
|
* @param node - The node to check.
|
||||||
|
*/
|
||||||
|
function nodeIsElement(node: Node): node is Element {
|
||||||
|
return node?.nodeType === Node.ELEMENT_NODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies whether a node is an input element.
|
||||||
|
*
|
||||||
|
* @param node - The node to check.
|
||||||
|
*/
|
||||||
|
function nodeIsInputElement(node: Node): node is HTMLInputElement {
|
||||||
|
return nodeIsElement(node) && elementIsInputElement(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies whether a node is a form element.
|
||||||
|
*
|
||||||
|
* @param node - The node to check.
|
||||||
|
*/
|
||||||
|
function nodeIsFormElement(node: Node): node is HTMLFormElement {
|
||||||
|
return nodeIsElement(node) && elementIsFormElement(node);
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
generateRandomCustomElementName,
|
generateRandomCustomElementName,
|
||||||
buildSvgDomElement,
|
buildSvgDomElement,
|
||||||
|
@ -159,4 +281,17 @@ export {
|
||||||
getFromLocalStorage,
|
getFromLocalStorage,
|
||||||
setupExtensionDisconnectAction,
|
setupExtensionDisconnectAction,
|
||||||
setupAutofillInitDisconnectAction,
|
setupAutofillInitDisconnectAction,
|
||||||
|
elementIsFillableFormField,
|
||||||
|
elementIsInstanceOf,
|
||||||
|
elementIsSpanElement,
|
||||||
|
elementIsInputElement,
|
||||||
|
elementIsSelectElement,
|
||||||
|
elementIsTextAreaElement,
|
||||||
|
elementIsFormElement,
|
||||||
|
elementIsLabelElement,
|
||||||
|
elementIsDescriptionDetailsElement,
|
||||||
|
elementIsDescriptionTermElement,
|
||||||
|
nodeIsElement,
|
||||||
|
nodeIsInputElement,
|
||||||
|
nodeIsFormElement,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue