From 7e173375a9f86fe4a3dcb0087fb4233fbc5b7efd Mon Sep 17 00:00:00 2001 From: ch-z-hc <45302000+ch-z-hc@users.noreply.github.com> Date: Thu, 15 Oct 2026 01:03:40 +0800 Subject: [PATCH] fix: stop selecting controls disabled during focus --- .../src/tools/__tests__/interaction.test.ts | 58 +++++++ .../tools/__tests__/select.browser.test.ts | 142 ++++++++++++++++++ apps/extension/src/tools/interaction.ts | 8 +- 3 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 apps/extension/src/tools/__tests__/select.browser.test.ts diff --git a/apps/extension/src/tools/__tests__/interaction.test.ts b/apps/extension/src/tools/__tests__/interaction.test.ts index 8184b6dc..e66d95ef 100644 --- a/apps/extension/src/tools/__tests__/interaction.test.ts +++ b/apps/extension/src/tools/__tests__/interaction.test.ts @@ -1923,6 +1923,64 @@ describe("handleSelect", () => { "Runtime.callFunctionOn": () => ({ result: { value: mutation } }), }); + it.each([ + { name: "disabled during focus", markup: "", disabled: true }, + { name: "enabled", markup: "", disabled: false }, + ])("respects the disabled state of a $name select", async ({ markup, disabled }) => { + const host = document.createElement("div"); + host.innerHTML = markup; + const select = host.querySelector("select")!; + select.innerHTML = + ''; + const events: string[] = []; + select.addEventListener("input", () => events.push("input")); + select.addEventListener("change", () => events.push("change")); + const sm = new SessionManager({ agentWindow: fakeAgentWindow([100]) }); + const ctx = await sm.start("aa11"); + ctx.refStore.set("e1", 555, { tabId: 4 }); + const fake = makeFakeCdp({ + ...selectHandlers({ ok: true }), + "DOM.focus": () => { + select.disabled = disabled; + return {}; + }, + "Runtime.callFunctionOn": (params) => { + const script = params as { + functionDeclaration: string; + arguments: Array<{ value: unknown }>; + }; + const fn = new Function(`return (${script.functionDeclaration})`)(); + return { + result: { + value: fn.apply( + select, + script.arguments.map((arg) => arg.value), + ), + }, + }; + }, + }); + + const res = await handleSelect( + sm, + { session_id: "aa11", ref: "e1", values: ["ca"] }, + { cdp: fake.cdp, tabsApi: fake.tabsApi }, + ); + + if (disabled) { + expect(res).toMatchObject({ + code: "invalid_params", + message: "select requires an enabled ", disabled: true }, + { + name: "disabled fieldset", + markup: "
", + disabled: true, + }, + { + name: "first legend exception", + markup: "
", + disabled: false, + }, + { + name: "second legend", + markup: + "
First
", + disabled: true, + }, + { + name: "disabled by focus handler", + markup: '', + disabled: true, + focusDisables: true, + }, + { + name: "fieldset disabled by focus handler", + markup: '
', + disabled: true, + focusDisables: true, + }, + { name: "enabled", markup: "", disabled: false }, + ])("respects $name", async ({ markup, disabled, focusDisables }) => { + const { targetId } = await send<{ targetId: string }>("Target.createTarget", { + url: "about:blank", + }); + try { + const { sessionId } = await send<{ sessionId: string }>("Target.attachToTarget", { + targetId, + flatten: true, + }); + const evaluate = async (expression: string) => { + const reply = await send<{ result: { value: unknown }; exceptionDetails?: unknown }>( + "Runtime.evaluate", + { expression, returnByValue: true }, + sessionId, + ); + expect(reply.exceptionDetails).toBeUndefined(); + return reply.result.value; + }; + await evaluate(`document.body.innerHTML = ${JSON.stringify(markup)}; + const select = document.querySelector('select'); + select.innerHTML = ''; + window.events = []; + for (const type of ['input', 'change']) select.addEventListener(type, () => window.events.push(type));`); + const manager = new SessionManager({ + agentWindow: { + create: async () => ({ windowId: 100, initialTabIds: [] }), + remove: async () => {}, + ensureActiveTab: async () => 4, + }, + }); + await manager.start("aa11"); + const cdp: CdpRunner = { + send: async (_tabId, method, params) => send(method, params, sessionId) as never, + }; + const result = await handleSelect( + manager, + { + session_id: "aa11", + selector: "select", + values: ["ca"], + }, + { + cdp, + tabsApi: { + get: async (id) => ({ id, windowId: 100, active: true }) as chrome.tabs.Tab, + query: async () => [{ id: 4, windowId: 100, active: true } as chrome.tabs.Tab], + }, + }, + ); + expect(result).toMatchObject( + disabled + ? focusDisables + ? { code: "invalid_params", message: "select requires an enabled " }; + } if (mutation?.reason === "option_not_found") { return rpcError( "invalid_params",