Skip to content

Commit 4328867

Browse files
committed
fix: respect screenshot bounds on HiDPI displays
1 parent f8572f0 commit 4328867

2 files changed

Lines changed: 54 additions & 3 deletions

File tree

src/tools/screenshot.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,17 +63,25 @@ function computeDownscaleClip(
6363
box: BoundingBox,
6464
maxWidth: number | undefined,
6565
maxHeight: number | undefined,
66+
devicePixelRatio: number,
6667
): ScreenshotClip | undefined {
6768
const widthScale =
68-
maxWidth !== undefined ? Math.min(1, maxWidth / box.width) : 1;
69+
maxWidth !== undefined
70+
? Math.min(1, maxWidth / (box.width * devicePixelRatio))
71+
: 1;
6972
const heightScale =
70-
maxHeight !== undefined ? Math.min(1, maxHeight / box.height) : 1;
73+
maxHeight !== undefined
74+
? Math.min(1, maxHeight / (box.height * devicePixelRatio))
75+
: 1;
7176
const scale = Math.min(widthScale, heightScale);
7277
if (scale >= 1) {
7378
return undefined;
7479
}
7580
// Skip degenerate sub-pixel results.
76-
if (Math.round(box.width * scale) < 1 || Math.round(box.height * scale) < 1) {
81+
if (
82+
Math.round(box.width * devicePixelRatio * scale) < 1 ||
83+
Math.round(box.height * devicePixelRatio * scale) < 1
84+
) {
7785
return undefined;
7886
}
7987
return {
@@ -172,10 +180,14 @@ export const screenshot = definePageTool(args => {
172180
) {
173181
const box = await getSourceBox(page, element, fullPage);
174182
if (box) {
183+
const devicePixelRatio = await page.evaluate(
184+
() => window.devicePixelRatio,
185+
);
175186
clip = computeDownscaleClip(
176187
box,
177188
screenshotMaxWidth,
178189
screenshotMaxHeight,
190+
devicePixelRatio,
179191
);
180192
}
181193
}

tests/tools/screenshot.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,45 @@ describe('screenshot', () => {
401401
});
402402
});
403403

404+
it('honors screenshotMaxWidth at device scale factors above 1', async () => {
405+
const tool = screenshot({
406+
screenshotMaxWidth: 100,
407+
} as ParsedArguments);
408+
await withMcpContext(
409+
async (response, context) => {
410+
const page = context.getSelectedMcpPage().pptrPage;
411+
assert.equal(page.viewport(), null);
412+
await page.setContent(
413+
html`<div style="width:100vw;height:100vh;background:red"></div>`,
414+
);
415+
const source = await page.evaluate(() => ({
416+
width: window.innerWidth,
417+
height: window.innerHeight,
418+
devicePixelRatio: window.devicePixelRatio,
419+
}));
420+
assert.equal(source.devicePixelRatio, 2);
421+
422+
await tool.handler(
423+
{params: {format: 'png'}, page: context.getSelectedMcpPage()},
424+
response,
425+
context,
426+
);
427+
428+
assert.equal(response.images.length, 1);
429+
const buf = Buffer.from(response.images[0].data, 'base64');
430+
assert.equal(pngWidth(buf), 100);
431+
const expectedHeight = Math.round(
432+
source.height * (100 / source.width),
433+
);
434+
assert.ok(
435+
Math.abs(pngHeight(buf) - expectedHeight) <= 1,
436+
`expected height ~${expectedHeight}, got ${pngHeight(buf)}`,
437+
);
438+
},
439+
{args: ['--force-device-scale-factor=2']},
440+
);
441+
});
442+
404443
it('downscales viewport screenshot when no viewport is emulated', async () => {
405444
const tool = screenshot({
406445
screenshotMaxWidth: 100,

0 commit comments

Comments
 (0)