Skip to content

Commit 0861b18

Browse files
committed
fix(engine): always pass clip to Page.captureScreenshot
Without an explicit clip, Chrome can resolve replaced-element sizing differently at dpr=1 when full-bleed absolute videos interact with overlay layers — producing anisotropic frame stretching on some compositor paths. Always passing clip with scale=dpr (including 1) ensures geometry is locked to the measured viewport dimensions. Credit: brian-t-allen (#837)
1 parent 8b18aa9 commit 0861b18

2 files changed

Lines changed: 9 additions & 12 deletions

File tree

packages/engine/src/services/screenshotService.test.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ describe("pageScreenshotCapture supersample plumbing", () => {
2525
const ONE_PIXEL_PNG_B64 =
2626
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII=";
2727

28-
it("omits `clip` when deviceScaleFactor is undefined (default 1)", async () => {
28+
it("passes `clip` with scale 1 when deviceScaleFactor is undefined (default 1)", async () => {
2929
const send = vi.fn().mockResolvedValue({ data: ONE_PIXEL_PNG_B64 });
3030
const page = makeFakePageWithCdp(send);
3131

@@ -39,11 +39,13 @@ describe("pageScreenshotCapture supersample plumbing", () => {
3939

4040
expect(send).toHaveBeenCalledWith(
4141
"Page.captureScreenshot",
42-
expect.not.objectContaining({ clip: expect.anything() }),
42+
expect.objectContaining({
43+
clip: { x: 0, y: 0, width: 1920, height: 1080, scale: 1 },
44+
}),
4345
);
4446
});
4547

46-
it("omits `clip` when deviceScaleFactor is exactly 1", async () => {
48+
it("passes `clip` with scale 1 when deviceScaleFactor is exactly 1", async () => {
4749
const send = vi.fn().mockResolvedValue({ data: ONE_PIXEL_PNG_B64 });
4850
const page = makeFakePageWithCdp(send);
4951

@@ -55,8 +57,8 @@ describe("pageScreenshotCapture supersample plumbing", () => {
5557
deviceScaleFactor: 1,
5658
});
5759

58-
const params = send.mock.calls[0]?.[1] as { clip?: unknown };
59-
expect(params.clip).toBeUndefined();
60+
const params = send.mock.calls[0]?.[1] as { clip?: { scale: number } };
61+
expect(params.clip).toEqual({ x: 0, y: 0, width: 1920, height: 1080, scale: 1 });
6062
});
6163

6264
it("passes `clip` with `scale = dpr` when deviceScaleFactor > 1 (the supersample contract)", async () => {

packages/engine/src/services/screenshotService.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -130,19 +130,14 @@ export async function pageScreenshotCapture(page: Page, options: CaptureOptions)
130130
const client = await getCdpSession(page);
131131
const isPng = options.format === "png";
132132
const dpr = options.deviceScaleFactor ?? 1;
133-
// When supersampling, pass an explicit clip with `scale` so Chrome emits a
134-
// screenshot at device-pixel dimensions (`width × height × dpr`). Without
135-
// this, `Page.captureScreenshot` returns at CSS dimensions regardless of
136-
// the viewport's deviceScaleFactor.
137-
const clip =
138-
dpr > 1 ? { x: 0, y: 0, width: options.width, height: options.height, scale: dpr } : undefined;
133+
const clip = { x: 0, y: 0, width: options.width, height: options.height, scale: dpr };
139134
const result = await client.send("Page.captureScreenshot", {
140135
format: isPng ? "png" : "jpeg",
141136
quality: isPng ? undefined : (options.quality ?? 80),
142137
fromSurface: true,
143138
captureBeyondViewport: false,
144139
optimizeForSpeed: !isPng,
145-
...(clip ? { clip } : {}),
140+
clip,
146141
});
147142
return Buffer.from(result.data, "base64");
148143
}

0 commit comments

Comments
 (0)