// ======= type guards ======= // @note instanceof fails for elements inside iframes export function isHTMLElement(el: unknown): el is HTMLElement { // @todo either specify to HTMLElement or allow Element here. return !!el && (el as Node).nodeType === 1 } export function isInputElement(el: Element): el is HTMLInputElement { return el?.nodeType === 1 && el.tagName === 'INPUT' } export function isTextAreaElement(el: Element): el is HTMLTextAreaElement { return el?.nodeType === 1 && el.tagName === 'TEXTAREA' } export function isSelectElement(el: Element): el is HTMLSelectElement { return el?.nodeType === 1 && el.tagName === 'SELECT' } export function isAnchorElement(el: Element): el is HTMLAnchorElement { return el?.nodeType === 1 && el.tagName === 'A' } // ======= iframe helpers ======= /** Iframe offset for translating element coordinates to top-frame viewport. */ export function getIframeOffset(element: HTMLElement): { x: number; y: number } { const frame = element.ownerDocument.defaultView?.frameElement as HTMLElement | null if (!frame) return { x: 0, y: 0 } const rect = frame.getBoundingClientRect() return { x: rect.left, y: rect.top } } /** * Get native value setter from the element's own prototype (iframe-safe). * @note for React */ export function getNativeValueSetter(element: HTMLInputElement | HTMLTextAreaElement) { // eslint-disable-next-line @typescript-eslint/unbound-method return Object.getOwnPropertyDescriptor(Object.getPrototypeOf(element) as object, 'value')! .set as (v: string) => void } // ======= general utils ======= export async function waitFor(seconds: number): Promise { await new Promise((resolve) => setTimeout(resolve, seconds * 1000)) } // ======= mask events ======= /** * Move the visual pointer to a position within an element. * @param x - x coordinate in the element's document viewport * @param y - y coordinate in the element's document viewport */ export async function movePointerToElement(element: HTMLElement, x: number, y: number) { const offset = getIframeOffset(element) window.dispatchEvent( new CustomEvent('PageAgent::MovePointerTo', { detail: { x: x + offset.x, y: y + offset.y }, }) ) await waitFor(0.3) } export async function clickPointer() { window.dispatchEvent(new CustomEvent('PageAgent::ClickPointer')) } export async function enablePassThrough() { window.dispatchEvent(new CustomEvent('PageAgent::EnablePassThrough')) } export async function disablePassThrough() { window.dispatchEvent(new CustomEvent('PageAgent::DisablePassThrough')) }