authorgravatar for saphid@gmail.comAlex Southwell <saphid@gmail.com> 2026-10-06 20:22:39+11:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-06 04:22:39-05:00
log411cef0008fa8e1e5251a8aae2cc6a215f700a9c
treef21e1dc7a20d5d76ab9863cf0d70d99d52a47b66
parentd0dc45885c1441da5bd46dcf13124d94ed728b0a
signature Signed by PGP key B5690EEEBB952194

fix(desktop): prevent browser screenshot filename collisions (#14784)


2 files changed, 35 insertions(+), 1 deletions(-)

apps/desktop/src/preview/Manager.test.ts+34
......@@ -2367,6 +2367,40 @@ describe("PreviewManager", () => {
23672367 ),
23682368 );
23692369
2370 effectIt.effect("preserves both screenshots of the same site in the same millisecond", () =>
2371 withManager((manager) =>
2372 Effect.gen(function* () {
2373 const firstPng = Buffer.from("first-preview-png");
2374 const secondPng = Buffer.from("second-preview-png");
2375 const pngImage = (png: Buffer) => ({
2376 toPNG: () => png,
2377 toJPEG: () => png,
2378 getSize: () => ({ width: 1280, height: 720 }),
2379 });
2380 const capturePage = vi
2381 .fn<() => Promise<ReturnType<typeof pngImage>>>()
2382 .mockResolvedValueOnce(pngImage(firstPng))
2383 .mockResolvedValueOnce(pngImage(secondPng));
2384 fromId.mockReturnValue(makeTestPreviewWebContents(capturePage));
2385 yield* manager.createTab("tab_1");
2386 yield* manager.registerWebview("tab_1", 42);
2387 // it.effect keeps TestClock fixed until explicitly advanced.
2388 const first = yield* manager.captureScreenshot("tab_1");
2389 const second = yield* manager.captureScreenshot("tab_1");
2390
2391 expect(first.createdAt).toBe(second.createdAt);
2392 expect(first.id).not.toBe(second.id);
2393 expect(first.path).not.toBe(second.path);
2394 expect(first.id).toMatch(/^browser-screenshot-example-com-[a-z0-9]+-[0-9a-f]{8}$/);
2395 expect(second.id).toMatch(/^browser-screenshot-example-com-[a-z0-9]+-[0-9a-f]{8}$/);
2396 expect(writeFile.mock.calls).toEqual([
2397 [first.path, firstPng],
2398 [second.path, secondPng],
2399 ]);
2400 }),
2401 ),
2402 );
2403
23702404 effectIt.effect("keeps every recorded guest unthrottled until its frame capture stops", () =>
23712405 withManager((manager) =>
23722406 Effect.gen(function* () {
apps/desktop/src/preview/Manager.ts+1-1
......@@ -2974,7 +2974,7 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function
29742974 wc,
29752975 ),
29762976 ]);
2977 const id = `browser-screenshot-${artifactSiteSlug(wc.getURL())}-${millis.toString(36)}`;
2977 const id = `browser-screenshot-${artifactSiteSlug(wc.getURL())}-${millis.toString(36)}-${NodeCrypto.randomUUID().slice(0, 8)}`;
29782978 const artifactPath = path.join(resolvedArtifactDirectory, `${id}.png`);
29792979 const data = image.toPNG();
29802980 yield* fileSystem.makeDirectory(resolvedArtifactDirectory, { recursive: true }).pipe(