From 769c889f71d0e43dd38dcfc5b007aea5d79c114c Mon Sep 17 00:00:00 2001 From: shivam <91240327+shivamhwp@users.noreply.github.com> Date: Fri, 11 Sep 2026 22:58:20 +0000 Subject: [PATCH 1/3] fix(desktop): include Chrome app text in macOS SnapShots --- .../snapShot/SnapShotAccessibility.test.ts | 50 +++++++++++ .../src/snapShot/SnapShotAccessibility.ts | 11 ++- apps/desktop/src/snapShot/snapShot.test.ts | 83 +++++++++++++++++++ apps/desktop/src/snapShot/snapShot.ts | 34 +++++++- 4 files changed, 172 insertions(+), 6 deletions(-) create mode 100644 apps/desktop/src/snapShot/SnapShotAccessibility.test.ts diff --git a/apps/desktop/src/snapShot/SnapShotAccessibility.test.ts b/apps/desktop/src/snapShot/SnapShotAccessibility.test.ts new file mode 100644 index 0000000000000..651eeb2698686 --- /dev/null +++ b/apps/desktop/src/snapShot/SnapShotAccessibility.test.ts @@ -0,0 +1,50 @@ +import { expect, it } from "vite-plus/test"; + +import { readAccessibleWindowContextWithApp } from "./SnapShotAccessibility.ts"; + +const bounds = { x: 0, y: 33, width: 1470, height: 857 }; +const window = { + name: "Issues · pingdotgg/t3code - Google Chrome – Profile", + role: "window", + bounds, + children: async () => [], + tree: async () => ({ + role: "window", + name: "Issues · pingdotgg/t3code", + children: [{ role: "link", name: "Issues" }], + }), +}; +const App = { + byPid: async (pid: number) => ({ children: async () => (pid === 42 ? [window] : []) }), +} as unknown as Parameters[0]; + +const request = { + active: { + title: "Issues · pingdotgg/t3code", + bounds, + owner: { processId: 42, bundleId: "com.google.Chrome" }, + }, + platform: "darwin" as const, + sourceTitle: "Issues · pingdotgg/t3code", + imageSize: { width: 1470, height: 857 }, +}; + +it("includes Chrome accessibility context when macOS adds a profile to its title", async () => { + const context = await readAccessibleWindowContextWithApp(App, request); + expect(context?.accessibleText).toContain("Issues"); + expect(context?.accessibility).toBeDefined(); +}); + +it("does not borrow browser accessibility from another process or application", async () => { + for (const owner of [ + { processId: 43, bundleId: "com.google.Chrome" }, + { processId: 42, bundleId: "com.example.Editor" }, + ]) { + expect( + await readAccessibleWindowContextWithApp(App, { + ...request, + active: { ...request.active, owner }, + }), + ).toBeUndefined(); + } +}); diff --git a/apps/desktop/src/snapShot/SnapShotAccessibility.ts b/apps/desktop/src/snapShot/SnapShotAccessibility.ts index bb6cb95952b3c..902945ef652ed 100644 --- a/apps/desktop/src/snapShot/SnapShotAccessibility.ts +++ b/apps/desktop/src/snapShot/SnapShotAccessibility.ts @@ -20,7 +20,7 @@ export type AccessibleWindowIdentity = { readonly title: string; readonly bounds: Electron.Rectangle; readonly clientBounds?: Electron.Rectangle; - readonly owner: { readonly processId: number }; + readonly owner: { readonly processId: number; readonly bundleId?: string }; readonly accessibilityBoundsReliable?: boolean; }; @@ -114,7 +114,14 @@ async function readCapturedWindowAccessibility( const matchMode = isWaylandSession(platform, process.env) ? "wayland" : "screen-bounds"; const window = findAccessibleWindow( windows, - { title: active.title, sourceTitle, bounds: active.bounds, clientBounds: active.clientBounds }, + { + title: active.title, + sourceTitle, + bounds: active.bounds, + clientBounds: active.clientBounds, + platform, + bundleId: active.owner.bundleId, + }, matchMode, ); if (!window) { diff --git a/apps/desktop/src/snapShot/snapShot.test.ts b/apps/desktop/src/snapShot/snapShot.test.ts index 6960718759aaa..62e14ac5cfdd2 100644 --- a/apps/desktop/src/snapShot/snapShot.test.ts +++ b/apps/desktop/src/snapShot/snapShot.test.ts @@ -332,6 +332,89 @@ describe("findAccessibleWindow", () => { bounds: { x: 100, y: 200, width: 800, height: 600 }, }; + const chromeCapture = { + title: "Issues · pingdotgg/t3code", + bounds: { x: 0, y: 33, width: 1470, height: 857 }, + platform: "darwin" as const, + bundleId: "com.google.Chrome", + }; + + it.each([ + ["com.google.Chrome", "Google Chrome"], + ["com.google.Chrome.beta", "Google Chrome"], + ["com.google.Chrome.dev", "Google Chrome"], + ["com.google.Chrome.canary", "Google Chrome"], + ["org.chromium.Chromium", "Chromium"], + ])("matches a macOS browser profile suffix for %s", (bundleId, browserName) => { + const window = { + name: `${chromeCapture.title} - ${browserName} – Profile`, + bounds: chromeCapture.bounds, + }; + expect(findAccessibleWindow([window], { ...chromeCapture, bundleId })).toBe(window); + }); + + it("matches a Chrome title without a profile suffix", () => { + const window = { + name: `${chromeCapture.title} - Google Chrome`, + bounds: chromeCapture.bounds, + }; + expect(findAccessibleWindow([window], chromeCapture)).toBe(window); + }); + + it.each([ + { platform: "linux" as const, bundleId: "com.google.Chrome" }, + { platform: "win32" as const, bundleId: "com.google.Chrome" }, + { platform: "darwin" as const, bundleId: "com.example.Editor" }, + { platform: "darwin" as const, bundleId: undefined }, + ])("keeps strict titles outside macOS Chrome: %j", (identity) => { + const window = { + name: `${chromeCapture.title} - Google Chrome – Profile`, + bounds: chromeCapture.bounds, + }; + expect(findAccessibleWindow([window], { ...chromeCapture, ...identity })).toBeUndefined(); + }); + + it.each([ + "Different page - Google Chrome – Profile", + "Issues · pingdotgg/t3code copied - Google Chrome – Profile", + "Issues · pingdotgg/t3code - Google ChromeSomething", + ])("rejects unrelated browser titles: %s", (name) => { + expect( + findAccessibleWindow([{ name, bounds: chromeCapture.bounds }], chromeCapture), + ).toBeUndefined(); + }); + + it("keeps bounds checks and rejects ambiguous browser suffix matches", () => { + const window = { + name: `${chromeCapture.title} - Google Chrome – Profile`, + bounds: chromeCapture.bounds, + }; + expect( + findAccessibleWindow([{ ...window, bounds: { ...window.bounds, x: 3 } }], chromeCapture), + ).toBeUndefined(); + expect( + findAccessibleWindow([window, { ...window, active: true }], chromeCapture), + ).toBeUndefined(); + expect(findAccessibleWindow([window], chromeCapture, "wayland")).toBeUndefined(); + }); + + it("prefers an exact title over a browser suffix match", () => { + const exact = { name: chromeCapture.title, bounds: chromeCapture.bounds }; + const browser = { + name: `${chromeCapture.title} - Google Chrome – Profile`, + bounds: chromeCapture.bounds, + active: true, + }; + expect(findAccessibleWindow([browser, exact], chromeCapture)).toBe(exact); + }); + + it("does not strip browser suffixes from captured page titles", () => { + const window = { name: "Page - Google Chrome – Profile", bounds: chromeCapture.bounds }; + expect( + findAccessibleWindow([window], { ...chromeCapture, title: "Page - Google Chrome" }), + ).toBeUndefined(); + }); + it("matches one window by its captured bounds", () => { const windows = [ { name: "Private", bounds: { x: 0, y: 0, width: 400, height: 300 } }, diff --git a/apps/desktop/src/snapShot/snapShot.ts b/apps/desktop/src/snapShot/snapShot.ts index 35c240c6e09a9..16609820e8a6c 100644 --- a/apps/desktop/src/snapShot/snapShot.ts +++ b/apps/desktop/src/snapShot/snapShot.ts @@ -478,6 +478,8 @@ export function findAccessibleWindow< readonly sourceTitle?: string; readonly bounds: WindowBounds; readonly clientBounds?: WindowBounds | undefined; + readonly platform?: NodeJS.Platform; + readonly bundleId?: string | undefined; }, matchMode: "screen-bounds" | "wayland" = "screen-bounds", ): T | undefined { @@ -499,19 +501,43 @@ export function findAccessibleWindow< matchMode === "wayland" && captured.clientBounds ? [captured.bounds, captured.clientBounds] : [captured.bounds]; - const matches = windows.filter((window) => { + const candidates = windows.filter((window) => { const bounds = window.bounds; return ( - titles.has(normalizeTitle(window.name ?? "")) && bounds !== null && candidateBounds.some((candidate) => boundsKeys.every((key) => Math.abs(bounds[key] - candidate[key]) <= 2), ) ); }); + const matches = candidates.filter((window) => titles.has(normalizeTitle(window.name ?? ""))); if (matches.length === 1) return matches[0]; - const activeMatches = matches.filter((window) => safeProperty(() => window.active) === true); - return activeMatches.length === 1 ? activeMatches[0] : undefined; + if (matches.length > 1) { + const activeMatches = matches.filter((window) => safeProperty(() => window.active) === true); + return activeMatches.length === 1 ? activeMatches[0] : undefined; + } + + // Chrome's macOS AX title can append the browser and profile to the captured page title. + if (captured.platform !== "darwin" || matchMode !== "screen-bounds") return undefined; + const browserName = /^com\.google\.Chrome(?:\.(?:beta|dev|canary))?$/.test( + captured.bundleId ?? "", + ) + ? "Google Chrome" + : captured.bundleId === "org.chromium.Chromium" + ? "Chromium" + : undefined; + if (!browserName) return undefined; + const browserMatches = candidates.filter((window) => { + const name = window.name?.trim() ?? ""; + return [...titles].some((title) => { + const prefix = `${title} - ${browserName}`; + return ( + name === prefix || + (name.startsWith(prefix) && /^ [–—-] \S.*$/u.test(name.slice(prefix.length))) + ); + }); + }); + return browserMatches.length === 1 ? browserMatches[0] : undefined; } const ELECTRON_KEY_NAMES: Readonly> = { From 21d356022005fa0934ec0da99b6fa94484831aa6 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Mon, 19 Oct 2026 09:16:28 +0000 Subject: [PATCH 2/3] test(desktop): consolidate Chrome snapshot title regressions --- .../src/snapShot/DesktopSnapShot.test.ts | 63 ++++++++++++ .../snapShot/SnapShotAccessibility.test.ts | 50 ---------- apps/desktop/src/snapShot/snapShot.test.ts | 99 +++++++------------ 3 files changed, 97 insertions(+), 115 deletions(-) delete mode 100644 apps/desktop/src/snapShot/SnapShotAccessibility.test.ts diff --git a/apps/desktop/src/snapShot/DesktopSnapShot.test.ts b/apps/desktop/src/snapShot/DesktopSnapShot.test.ts index d2520ee03d2f4..152de5f9478b8 100644 --- a/apps/desktop/src/snapShot/DesktopSnapShot.test.ts +++ b/apps/desktop/src/snapShot/DesktopSnapShot.test.ts @@ -2725,6 +2725,69 @@ it.each([ } }); +it.each([ + ["com.google.Chrome", "Google Chrome – Profile"], + ["org.chromium.Chromium", "Chromium"], +])("reads macOS %s page context with an accessibility title suffix", async (bundleId, suffix) => { + const title = "Issues · pingdotgg/t3code"; + const bounds = { x: 0, y: 33, width: 1470, height: 857 }; + const window = { + role: "window", + name: `${title} - ${suffix}`, + bounds, + tree: async () => ({ name: title, children: [{ name: "New issue", children: [] }] }), + children: async () => [{ role: "button", name: "New issue", bounds, children: async () => [] }], + }; + accessibilityByPidMock.mockReset().mockImplementation(async (pid: number) => ({ + children: async () => (pid === 42 ? [window] : []), + })); + + const context = await readAccessibleWindowContext( + { title, bounds, owner: { processId: 42, bundleId } }, + "darwin", + title, + ); + + assert.equal(context?.accessibleText, `${title}\nNew issue`); + assert.equal(context?.accessibility?.format, "element-tree"); + assert.equal( + context?.accessibility?.format === "element-tree" + ? context.accessibility.root.children[0]?.name + : undefined, + "New issue", + ); +}); + +it("does not borrow another Chrome process context when the captured PID has no windows", async () => { + const title = "Issues · pingdotgg/t3code"; + const bounds = { x: 0, y: 33, width: 1470, height: 857 }; + const otherWindow = { + role: "window", + name: `${title} - Google Chrome – Profile`, + bounds, + tree: async () => ({ + name: title, + children: [{ name: "Other process private text", children: [] }], + }), + children: async () => [], + }; + accessibilityByPidMock.mockReset().mockResolvedValue({ + pid: 42, + children: async () => [], + }); + accessibilityListMock.mockResolvedValue([ + { pid: 43, bundleId: "com.google.Chrome", children: async () => [otherWindow] }, + ]); + + assert.isUndefined( + await readAccessibleWindowContext( + { title, bounds, owner: { processId: 42, bundleId: "com.google.Chrome" } }, + "darwin", + title, + ), + ); +}); + it("reads Flatpak app text from the AT-SPI proxy when the compositor PID misses", async () => { vi.stubEnv("XDG_SESSION_TYPE", "wayland"); const bounds = { x: 0, y: 0, width: 1_920, height: 1_048 }; diff --git a/apps/desktop/src/snapShot/SnapShotAccessibility.test.ts b/apps/desktop/src/snapShot/SnapShotAccessibility.test.ts deleted file mode 100644 index 651eeb2698686..0000000000000 --- a/apps/desktop/src/snapShot/SnapShotAccessibility.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { expect, it } from "vite-plus/test"; - -import { readAccessibleWindowContextWithApp } from "./SnapShotAccessibility.ts"; - -const bounds = { x: 0, y: 33, width: 1470, height: 857 }; -const window = { - name: "Issues · pingdotgg/t3code - Google Chrome – Profile", - role: "window", - bounds, - children: async () => [], - tree: async () => ({ - role: "window", - name: "Issues · pingdotgg/t3code", - children: [{ role: "link", name: "Issues" }], - }), -}; -const App = { - byPid: async (pid: number) => ({ children: async () => (pid === 42 ? [window] : []) }), -} as unknown as Parameters[0]; - -const request = { - active: { - title: "Issues · pingdotgg/t3code", - bounds, - owner: { processId: 42, bundleId: "com.google.Chrome" }, - }, - platform: "darwin" as const, - sourceTitle: "Issues · pingdotgg/t3code", - imageSize: { width: 1470, height: 857 }, -}; - -it("includes Chrome accessibility context when macOS adds a profile to its title", async () => { - const context = await readAccessibleWindowContextWithApp(App, request); - expect(context?.accessibleText).toContain("Issues"); - expect(context?.accessibility).toBeDefined(); -}); - -it("does not borrow browser accessibility from another process or application", async () => { - for (const owner of [ - { processId: 43, bundleId: "com.google.Chrome" }, - { processId: 42, bundleId: "com.example.Editor" }, - ]) { - expect( - await readAccessibleWindowContextWithApp(App, { - ...request, - active: { ...request.active, owner }, - }), - ).toBeUndefined(); - } -}); diff --git a/apps/desktop/src/snapShot/snapShot.test.ts b/apps/desktop/src/snapShot/snapShot.test.ts index 08249330107ff..8f57ad55563cc 100644 --- a/apps/desktop/src/snapShot/snapShot.test.ts +++ b/apps/desktop/src/snapShot/snapShot.test.ts @@ -334,87 +334,56 @@ describe("findAccessibleWindow", () => { const chromeCapture = { title: "Issues · pingdotgg/t3code", - bounds: { x: 0, y: 33, width: 1470, height: 857 }, + bounds: captured.bounds, platform: "darwin" as const, bundleId: "com.google.Chrome", }; + const chromeWindow = { + name: `${chromeCapture.title} - Google Chrome – Profile`, + bounds: chromeCapture.bounds, + active: true, + }; - it.each([ - ["com.google.Chrome", "Google Chrome"], - ["com.google.Chrome.beta", "Google Chrome"], - ["com.google.Chrome.dev", "Google Chrome"], - ["com.google.Chrome.canary", "Google Chrome"], - ["org.chromium.Chromium", "Chromium"], - ])("matches a macOS browser profile suffix for %s", (bundleId, browserName) => { - const window = { - name: `${chromeCapture.title} - ${browserName} – Profile`, - bounds: chromeCapture.bounds, - }; - expect(findAccessibleWindow([window], { ...chromeCapture, bundleId })).toBe(window); - }); - - it("matches a Chrome title without a profile suffix", () => { - const window = { - name: `${chromeCapture.title} - Google Chrome`, - bounds: chromeCapture.bounds, - }; - expect(findAccessibleWindow([window], chromeCapture)).toBe(window); - }); - - it.each([ - { platform: "linux" as const, bundleId: "com.google.Chrome" }, - { platform: "win32" as const, bundleId: "com.google.Chrome" }, - { platform: "darwin" as const, bundleId: "com.example.Editor" }, - { platform: "darwin" as const, bundleId: undefined }, - ])("keeps strict titles outside macOS Chrome: %j", (identity) => { - const window = { - name: `${chromeCapture.title} - Google Chrome – Profile`, - bounds: chromeCapture.bounds, - }; - expect(findAccessibleWindow([window], { ...chromeCapture, ...identity })).toBeUndefined(); + it("uses a browser suffix only when no exact title matches", () => { + const exact = { name: chromeCapture.title, bounds: chromeCapture.bounds }; + expect(findAccessibleWindow([chromeWindow, exact], chromeCapture)).toBe(exact); + expect(findAccessibleWindow([chromeWindow], chromeCapture)).toBe(chromeWindow); }); - it.each([ - "Different page - Google Chrome – Profile", - "Issues · pingdotgg/t3code copied - Google Chrome – Profile", - "Issues · pingdotgg/t3code - Google ChromeSomething", - ])("rejects unrelated browser titles: %s", (name) => { + it("rejects browser suffix matches with wrong bounds, page titles, or ambiguous windows", () => { expect( - findAccessibleWindow([{ name, bounds: chromeCapture.bounds }], chromeCapture), + findAccessibleWindow( + [{ ...chromeWindow, bounds: { ...chromeWindow.bounds, x: 0 } }], + chromeCapture, + ), ).toBeUndefined(); - }); - - it("keeps bounds checks and rejects ambiguous browser suffix matches", () => { - const window = { - name: `${chromeCapture.title} - Google Chrome – Profile`, - bounds: chromeCapture.bounds, - }; expect( - findAccessibleWindow([{ ...window, bounds: { ...window.bounds, x: 3 } }], chromeCapture), + findAccessibleWindow([chromeWindow, { ...chromeWindow, active: false }], chromeCapture), ).toBeUndefined(); expect( - findAccessibleWindow([window, { ...window, active: true }], chromeCapture), + findAccessibleWindow([chromeWindow], { ...chromeCapture, title: "Issues" }), ).toBeUndefined(); - expect(findAccessibleWindow([window], chromeCapture, "wayland")).toBeUndefined(); - }); - - it("prefers an exact title over a browser suffix match", () => { - const exact = { name: chromeCapture.title, bounds: chromeCapture.bounds }; - const browser = { - name: `${chromeCapture.title} - Google Chrome – Profile`, - bounds: chromeCapture.bounds, - active: true, - }; - expect(findAccessibleWindow([browser, exact], chromeCapture)).toBe(exact); - }); - - it("does not strip browser suffixes from captured page titles", () => { - const window = { name: "Page - Google Chrome – Profile", bounds: chromeCapture.bounds }; expect( - findAccessibleWindow([window], { ...chromeCapture, title: "Page - Google Chrome" }), + findAccessibleWindow([chromeWindow], { + ...chromeCapture, + title: `${chromeCapture.title} - Google Chrome`, + }), ).toBeUndefined(); }); + it("keeps strict titles outside macOS Chrome and Chromium", () => { + for (const identity of [ + { platform: "linux" as const, bundleId: "com.google.Chrome" }, + { platform: "win32" as const, bundleId: "com.google.Chrome" }, + { platform: "darwin" as const, bundleId: "com.example.Editor" }, + { platform: "darwin" as const, bundleId: undefined }, + ]) { + expect( + findAccessibleWindow([chromeWindow], { ...chromeCapture, ...identity }), + ).toBeUndefined(); + } + }); + it("matches one window by its captured bounds", () => { const windows = [ { name: "Private", bounds: { x: 0, y: 0, width: 400, height: 300 } }, From 1a9e8c1ca2b464a35c6dfed7d4037e947fcab1ea Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Mon, 19 Oct 2026 09:18:31 +0000 Subject: [PATCH 3/3] fix(desktop): restrict Chrome title fallback to captured process --- apps/desktop/src/snapShot/SnapShotAccessibility.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/src/snapShot/SnapShotAccessibility.ts b/apps/desktop/src/snapShot/SnapShotAccessibility.ts index c58ef7188a2ed..3361aafc3c78a 100644 --- a/apps/desktop/src/snapShot/SnapShotAccessibility.ts +++ b/apps/desktop/src/snapShot/SnapShotAccessibility.ts @@ -135,7 +135,7 @@ async function readCapturedWindowAccessibility( bounds: active.bounds, clientBounds: active.clientBounds, platform, - bundleId: active.owner.bundleId, + bundleId: pidWindows.length > 0 ? active.owner.bundleId : undefined, }; const window = findAccessibleWindow( pidWindows.length > 0 || foreground !== undefined ? pidWindows : await windowsFromAppList(App),