From d87ca6798b647107a3c7bbf79902397e72e90269 Mon Sep 17 00:00:00 2001 From: chzhc <45302000+ch-z-hc@users.noreply.github.com> Date: Thu, 15 Oct 2026 03:27:39 +0800 Subject: [PATCH] fix(extract): respect content-visibility hidden subtrees --- apps/extension/src/tools/extract/collector.ts | 6 ++++ .../src/tools/extract/extract.browser.test.ts | 33 +++++++++++++++++++ .../src/tools/extract/extract.test.ts | 7 ++++ 3 files changed, 46 insertions(+) diff --git a/apps/extension/src/tools/extract/collector.ts b/apps/extension/src/tools/extract/collector.ts index 27a0c3a9..a65bdf1b 100644 --- a/apps/extension/src/tools/extract/collector.ts +++ b/apps/extension/src/tools/extract/collector.ts @@ -78,6 +78,8 @@ export function collectExtractDom(this: Element, options: CollectorOptions): Raw } chain.push(current); const style = styleOf(current); + // Chromium ignores content-visibility on ordinary inline, boxless and + // non-cell table boxes. Keep their rendered contents extractable. if ( current.hasAttribute("hidden") || current.hasAttribute("data-bsk-overlay") || @@ -85,6 +87,10 @@ export function collectExtractDom(this: Element, options: CollectorOptions): Raw current.tagName.toLowerCase() === "browser-skill-overlay" || closedDetailsContent(current) || style.display === "none" || + (style.contentVisibility === "hidden" && + !/^(inline( list-item)?|contents|(?:inline-)?table|table-(caption|row|row-group|header-group|footer-group|column|column-group)|ruby(-.*)?)$/.test( + style.display, + )) || (style.visibility === "collapse" && /^table-(row|row-group|header-group|footer-group|column|column-group)$/.test( style.display, diff --git a/apps/extension/src/tools/extract/extract.browser.test.ts b/apps/extension/src/tools/extract/extract.browser.test.ts index dec0d768..460b2389 100644 --- a/apps/extension/src/tools/extract/extract.browser.test.ts +++ b/apps/extension/src/tools/extract/extract.browser.test.ts @@ -53,6 +53,10 @@ const values = (outcome: Outcome) => outcome.result!.rows.map((row) => outcome.result!.columns.map((column) => row[column.key])); const fixtures: Record = { + contentVisibility: `
Visible
Hidden text
Offscreen text
+
Hidden table
+ `, + ignoredContentVisibility: `
Caption
Inline text
Contents text
Inline itemHidden cell
`, rows1000: plainTable(1000, 3), wide: plainTable(600, 100), template: cells(` @@ -269,6 +273,35 @@ describe.skipIf(!process.env.BSK_CLICK_CHROME)("real browser structured extracti ).toEqual(["Shipped", "42", "edited", "x"]); }); + it("omits content-visibility hidden text without dropping auto offscreen content", async () => { + expect(values(await extract("contentVisibility", { action: "table", selector: "#t" }))).toEqual( + [["Visible", "Offscreen text"]], + ); + const list = await extract("contentVisibility", { action: "list", selector: "#l" }); + expect(list.result!.rows).toEqual([{ text: "Visible item", url: null }]); + }); + + it("does not discover or extract containers inside content-visibility hidden subtrees", async () => { + const discovery = await extract("contentVisibility", { action: "discover" }); + expect(discovery.result!.targets!.map((target) => target.name)).toEqual(["t", "l"]); + for (const [action, selector] of [ + ["table", "#hiddenTable"], + ["list", "#hiddenList"], + ] as const) { + expect((await extract("contentVisibility", { action, selector })).error).toMatch( + /^extract_target_hidden:/, + ); + } + }); + + it("keeps text where content-visibility does not apply to the display type", async () => { + expect( + values(await extract("ignoredContentVisibility", { action: "table", selector: "#t" })), + ).toEqual([["Inline textContents text", "Inline item", null]]); + const discovery = await extract("ignoredContentVisibility", { action: "discover" }); + expect(discovery.result!.targets!.map((target) => target.name)).toEqual(["Caption"]); + }); + it("charges the byte budget for emitted text, not template indentation", async () => { const { result } = await extract("indented", { action: "table", diff --git a/apps/extension/src/tools/extract/extract.test.ts b/apps/extension/src/tools/extract/extract.test.ts index 3ef6f765..b1d9f00c 100644 --- a/apps/extension/src/tools/extract/extract.test.ts +++ b/apps/extension/src/tools/extract/extract.test.ts @@ -44,6 +44,13 @@ beforeEach(() => { document.body.innerHTML = ""; }); describe("structured extraction facts", () => { + it.each(["hidden", "auto", "visible"])("respects content-visibility: %s", (visibility) => { + document.body.innerHTML = `
Visible
Extra
`; + expect(extract("#t").rows).toEqual([ + { c1: visibility === "hidden" ? "Visible" : "Visible\nExtra" }, + ]); + }); + it("preserves identifiers, currency, multiline text, empty cells and row provenance", () => { document.body.innerHTML = orders; const result = extract("#orders");