///
import type { Page } from "playwright";
// Playwright's listener subscription enables Chromium interception asynchronously.
// Keep it enabled for the Page lifetime and await the browser acknowledgement
// before any real gesture; otherwise keyboard.press can overtake interception.
export async function prepareNativePicker(page: Page): Promise {
page.on("filechooser", () => {});
const session = await page.context().newCDPSession(page);
await session.send("Page.setInterceptFileChooserDialog", { enabled: true });
}
// Observe native controls without substituting them or synthesizing activation.
export async function observePicker(page: Page): Promise {
await page.addInitScript(() => {
const records: unknown[] = [];
Object.assign(window, { __pickerDiagnostics: records });
const record = (kind: string, element: Element | null, extra = {}) =>
records.push({
kind,
time: performance.now(),
active: navigator.userActivation.isActive,
hasBeenActive: navigator.userActivation.hasBeenActive,
connected: element?.isConnected,
tag: element?.tagName,
className: element?.className,
editor: document.querySelector(".cm-content")?.textContent,
focused: document.activeElement?.className,
...extra,
});
document.addEventListener("keydown", (event) => {
if (["Enter", "Tab", "Escape"].includes(event.key)) {
record("keydown", event.target as Element, { key: event.key, trusted: event.isTrusted });
queueMicrotask(() => record("after-keydown", event.target as Element, { key: event.key }));
}
}, true);
const nativeClick = HTMLInputElement.prototype.click;
HTMLInputElement.prototype.click = function () {
if (this.type === "file") record("input.click", this, { disabled: this.disabled });
nativeClick.call(this);
if (this.type === "file") record("after-input.click", this);
};
document.addEventListener("click", (event) => {
if ((event.target as HTMLInputElement)?.type === "file") {
record("input-click-event", event.target as Element, { trusted: event.isTrusted });
}
}, true);
const observer = new MutationObserver((changes) => {
for (const change of changes) {
for (
const [kind, nodes] of [
["added", change.addedNodes],
["removed", change.removedNodes],
] as const
) {
for (const node of Array.from(nodes)) {
if (
node instanceof Element && (node.matches("input[type=file], .cm-editor") ||
node.querySelector("input[type=file], .cm-editor"))
) record(kind, node);
}
}
}
});
observer.observe(document, { childList: true, subtree: true });
});
}
export async function pickerDiagnostics(page: Page): Promise {
return await page.evaluate(() =>
(window as unknown as { __pickerDiagnostics: unknown }).__pickerDiagnostics
);
}