From 793d482366c1a88ba5fafe214f7edcabc650e32b Mon Sep 17 00:00:00 2001 From: kevin9327 <5299031+kevin9327@users.noreply.github.com> Date: Sun, 20 Sep 2026 23:57:14 +0900 Subject: [PATCH 1/5] fix(extension): fill contenteditable descendants DOM.describeNode only reports the target's own attributes. A paragraph inside [contenteditable] has none, so fill rejected it before the live isContentEditable check. Agents snapshot the inner block of rich editors and then cannot type into it. --- .../src/tools/__tests__/fill.test.ts | 13 +++++++++ .../src/tools/__tests__/interaction.test.ts | 2 +- apps/extension/src/tools/interaction.ts | 29 +++++++++++++++++-- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/apps/extension/src/tools/__tests__/fill.test.ts b/apps/extension/src/tools/__tests__/fill.test.ts index 19f955030..4cb9c5917 100644 --- a/apps/extension/src/tools/__tests__/fill.test.ts +++ b/apps/extension/src/tools/__tests__/fill.test.ts @@ -396,6 +396,19 @@ describe("fill result verification", () => { expect(await h.fill(requested)).toMatchObject({ value_length: normalized.length }); }); + it("fills a contenteditable descendant that has no contenteditable attribute", async () => { + const h = await setup('
'); + let innerText = ""; + Object.defineProperty(h.element, "isContentEditable", { get: () => true }); + Object.defineProperty(h.element, "innerText", { get: () => innerText }); + h.insert.mockImplementation(async () => { + h.element.textContent = "hello"; + innerText = "hello"; + }); + expect(await h.fill("hello")).toMatchObject({ value_length: 5 }); + expect(h.insert).toHaveBeenCalledOnce(); + }); + it("does not count an editable padding break as an extra typed character", async () => { const h = await setup(''); // These two layout/editability properties are missing in Happy DOM. diff --git a/apps/extension/src/tools/__tests__/interaction.test.ts b/apps/extension/src/tools/__tests__/interaction.test.ts index 5d04d2023..351339754 100644 --- a/apps/extension/src/tools/__tests__/interaction.test.ts +++ b/apps/extension/src/tools/__tests__/interaction.test.ts @@ -1322,7 +1322,7 @@ describe("handleFill", () => { ctx.refStore.set("e1", 100, { tabId: 4 }); const fake = makeFakeCdp({ "DOM.describeNode": () => ({ - node: { backendNodeId: 100, nodeName: "DIV", attributes: [] }, + node: { backendNodeId: 100, nodeName: "BUTTON", attributes: [] }, }), }); const res = await handleFill( diff --git a/apps/extension/src/tools/interaction.ts b/apps/extension/src/tools/interaction.ts index 0a1af3298..3e663a148 100644 --- a/apps/extension/src/tools/interaction.ts +++ b/apps/extension/src/tools/interaction.ts @@ -900,9 +900,34 @@ interface DescribedNode { attributes?: string[]; } +const NEVER_FILLABLE_TAGS = new Set([ + "BUTTON", + "SELECT", + "OPTION", + "OPTGROUP", + "SCRIPT", + "STYLE", + "LINK", + "META", + "IMG", + "VIDEO", + "AUDIO", + "CANVAS", + "IFRAME", + "OBJECT", + "EMBED", + "SVG", + "BR", + "HR", +]); + /** * Decide whether a node can receive `tool.fill`: native `` / - * `