diff options
Diffstat (limited to 'remote/cdp/test/browser/page/browser_captureScreenshot.js')
-rw-r--r-- | remote/cdp/test/browser/page/browser_captureScreenshot.js | 557 |
1 files changed, 557 insertions, 0 deletions
diff --git a/remote/cdp/test/browser/page/browser_captureScreenshot.js b/remote/cdp/test/browser/page/browser_captureScreenshot.js new file mode 100644 index 0000000000..1ff296e784 --- /dev/null +++ b/remote/cdp/test/browser/page/browser_captureScreenshot.js @@ -0,0 +1,557 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function documentSmallerThanViewport({ client }) { + const { Page } = client; + + await loadURLWithElement(); + + info("Check that captureScreenshot() captures the viewport by default"); + const { data } = await Page.captureScreenshot(); + ok(!!data, "Screenshot data is not empty"); + + const scale = await getDevicePixelRatio(); + const viewport = await getViewportSize(); + const { mimeType, width, height } = await getImageDetails(data); + + is(mimeType, "image/png", "Screenshot has correct MIME type"); + is(width, (viewport.width - viewport.x) * scale, "Image has expected width"); + is( + height, + (viewport.height - viewport.y) * scale, + "Image has expected height" + ); +}); + +add_task(async function documentLargerThanViewport({ client }) { + const { Page } = client; + + await loadURL(toDataURL("<div style='margin: 100vh 100vw'>Hello world")); + + info("Check that captureScreenshot() captures the viewport by default"); + const { data } = await Page.captureScreenshot(); + ok(!!data, "Screenshot data is not empty"); + + const scale = await getDevicePixelRatio(); + const scrollbarSize = await getScrollbarSize(); + const viewport = await getViewportSize(); + const { mimeType, width, height } = await getImageDetails(data); + + is(mimeType, "image/png", "Screenshot has correct MIME type"); + is( + width, + (viewport.width - viewport.x - scrollbarSize.width) * scale, + "Image has expected width" + ); + is( + height, + (viewport.height - viewport.y - scrollbarSize.height) * scale, + "Image has expected height" + ); +}); + +add_task(async function invalidFormat({ client }) { + const { Page } = client; + await loadURL(toDataURL("<div>Hello world")); + + let errorThrown = false; + try { + await Page.captureScreenshot({ format: "foo" }); + } catch (e) { + errorThrown = true; + } + ok(errorThrown, "captureScreenshot raised error for invalid image format"); +}); + +add_task(async function asJPEGFormat({ client }) { + const { Page } = client; + await loadURL(toDataURL("<div>Hello world")); + + info("Check that captureScreenshot() captures as JPEG format"); + const { data } = await Page.captureScreenshot({ format: "jpeg" }); + ok(!!data, "Screenshot data is not empty"); + + const scale = await getDevicePixelRatio(); + const viewport = await getViewportSize(); + const { mimeType, height, width } = await getImageDetails(data); + + is(mimeType, "image/jpeg", "Screenshot has correct MIME type"); + is(width, (viewport.width - viewport.x) * scale); + is(height, (viewport.height - viewport.y) * scale); +}); + +add_task(async function asJPEGFormatAndQuality({ client }) { + const { Page } = client; + await loadURL(toDataURL("<div>Hello world")); + + info("Check that captureScreenshot() captures as JPEG format"); + const imageDefault = await Page.captureScreenshot({ format: "jpeg" }); + ok(!!imageDefault, "Screenshot data with default quality is not empty"); + + const image100 = await Page.captureScreenshot({ + format: "jpeg", + quality: 100, + }); + ok(!!image100, "Screenshot data with quality 100 is not empty"); + + const image10 = await Page.captureScreenshot({ + format: "jpeg", + quality: 10, + }); + ok(!!image10, "Screenshot data with quality 10 is not empty"); + + const infoDefault = await getImageDetails(imageDefault.data); + const info100 = await getImageDetails(image100.data); + const info10 = await getImageDetails(image10.data); + + // All screenshots are of mimeType JPEG + is( + infoDefault.mimeType, + "image/jpeg", + "Screenshot with default quality has correct MIME type" + ); + is( + info100.mimeType, + "image/jpeg", + "Screenshot with quality 100 has correct MIME type" + ); + is( + info10.mimeType, + "image/jpeg", + "Screenshot with quality 10 has correct MIME type" + ); + + const scale = await getDevicePixelRatio(); + const viewport = await getViewportSize(); + + // Images are all of the same dimension + is(infoDefault.width, (viewport.width - viewport.x) * scale); + is(infoDefault.height, (viewport.height - viewport.y) * scale); + + is(info100.width, (viewport.width - viewport.x) * scale); + is(info100.height, (viewport.height - viewport.y) * scale); + + is(info10.width, (viewport.width - viewport.x) * scale); + is(info10.height, (viewport.height - viewport.y) * scale); + + // Images of different quality result in different content sizes + ok( + info100.length > infoDefault.length, + "Size of quality 100 is larger than default" + ); + ok( + info10.length < infoDefault.length, + "Size of quality 10 is smaller than default" + ); +}); + +add_task(async function clipMissingProperties({ client }) { + const { Page } = client; + const contentSize = await getContentSize(); + + for (const prop of ["x", "y", "width", "height", "scale"]) { + console.info(`Check for missing ${prop}`); + + const clip = { + x: 0, + y: 0, + width: contentSize.width, + height: contentSize.height, + }; + clip[prop] = undefined; + + let errorThrown = false; + try { + await Page.captureScreenshot({ clip }); + } catch (e) { + errorThrown = true; + } + ok(errorThrown, `raised error for missing clip.${prop} property`); + } +}); + +add_task(async function clipOutOfBoundsXAndY({ client }) { + const { Page } = client; + + const ratio = await getDevicePixelRatio(); + const size = 50; + + await loadURLWithElement(); + const contentSize = await getContentSize(); + + var { data: refData } = await Page.captureScreenshot({ + clip: { + x: 0, + y: 0, + width: size, + height: size, + scale: 1, + }, + }); + + for (const x of [-1, contentSize.width]) { + console.info(`Check out-of-bounds x for ${x}`); + const { data } = await Page.captureScreenshot({ + clip: { + x, + y: 0, + width: size, + height: size, + scale: 1, + }, + }); + const { width, height } = await getImageDetails(data); + + is(width, size * ratio, "Image has expected width"); + is(height, size * ratio, "Image has expected height"); + is(data, refData, "Image is equal"); + } + + for (const y of [-1, contentSize.height]) { + console.info(`Check out-of-bounds y for ${y}`); + const { data } = await Page.captureScreenshot({ + clip: { + x: 0, + y, + width: size, + height: size, + scale: 1, + }, + }); + const { width, height } = await getImageDetails(data); + + is(width, size * ratio, "Image has expected width"); + is(height, size * ratio, "Image has expected height"); + is(data, refData, "Image is equal"); + } +}); + +add_task(async function clipOutOfBoundsWidthAndHeight({ client }) { + const { Page } = client; + const ratio = await getDevicePixelRatio(); + + await loadURL(toDataURL("<div style='margin: 100vh 100vw'>Hello world")); + const contentSize = await getContentSize(); + + var { data: refData } = await Page.captureScreenshot({ + clip: { + x: 0, + y: 0, + width: contentSize.width, + height: contentSize.height, + scale: 1, + }, + }); + + for (const value of [-1, 0]) { + console.info(`Check out-of-bounds width for ${value}`); + const clip = { + x: 0, + y: 0, + width: value, + height: contentSize.height, + scale: 1, + }; + + const { data } = await Page.captureScreenshot({ clip }); + const { width, height } = await getImageDetails(data); + is(width, contentSize.width * ratio, "Image has expected width"); + is(height, contentSize.height * ratio, "Image has expected height"); + is(data, refData, "Image is equal"); + } + + for (const value of [-1, 0]) { + console.info(`Check out-of-bounds height for ${value}`); + const clip = { + x: 0, + y: 0, + width: contentSize.width, + height: value, + scale: 1, + }; + + const { data } = await Page.captureScreenshot({ clip }); + const { width, height } = await getImageDetails(data); + is(width, contentSize.width * ratio, "Image has expected width"); + is(height, contentSize.height * ratio, "Image has expected height"); + is(data, refData, "Image is equal"); + } +}); + +add_task(async function clipOutOfBoundsScale({ client }) { + const { Page } = client; + const ratio = await getDevicePixelRatio(); + + await loadURLWithElement(); + const contentSize = await getContentSize(); + + var { data: refData } = await Page.captureScreenshot({ + clip: { + x: 0, + y: 0, + width: contentSize.width, + height: contentSize.height, + scale: 1, + }, + }); + + for (const value of [-1, 0]) { + console.info(`Check out-of-bounds scale for ${value}`); + var { data } = await Page.captureScreenshot({ + clip: { + x: 0, + y: 0, + width: 50, + height: 50, + scale: value, + }, + }); + + const { width, height } = await getImageDetails(data); + is(width, contentSize.width * ratio, "Image has expected width"); + is(height, contentSize.height * ratio, "Image has expected height"); + is(data, refData, "Image is equal"); + } +}); + +add_task(async function clipScale({ client }) { + const { Page } = client; + const ratio = await getDevicePixelRatio(); + + for (const scale of [1.5, 2]) { + console.info(`Check scale for ${scale}`); + await loadURLWithElement({ width: 100 * scale, height: 100 * scale }); + var { data: refData } = await Page.captureScreenshot({ + clip: { + x: 0, + y: 0, + width: 100 * scale, + height: 100 * scale, + scale: 1, + }, + }); + + await loadURLWithElement({ width: 100, height: 100 }); + var { data } = await Page.captureScreenshot({ + clip: { + x: 0, + y: 0, + width: 100, + height: 100, + scale, + }, + }); + + const { width, height } = await getImageDetails(data); + is(width, 100 * ratio * scale, "Image has expected width"); + is(height, 100 * ratio * scale, "Image has expected height"); + is(data, refData, "Image is equal"); + } +}); + +add_task(async function clipScaleAndDevicePixelRatio({ client }) { + const { Page } = client; + + const originalRatio = await getDevicePixelRatio(); + + const ratio = 2; + const scale = 1.5; + const size = 100; + + const expectedSize = size * ratio * scale; + + console.info(`Create reference screenshot: ${expectedSize}x${expectedSize}`); + await loadURLWithElement({ + width: expectedSize, + height: expectedSize, + }); + var { data: refData } = await Page.captureScreenshot({ + clip: { + x: 0, + y: 0, + width: expectedSize, + height: expectedSize, + scale: 1, + }, + }); + + await setDevicePixelRatio(originalRatio * ratio); + + await loadURLWithElement({ width: size, height: size }); + var { data } = await Page.captureScreenshot({ + clip: { + x: 0, + y: 0, + width: size, + height: size, + scale, + }, + }); + + const { width, height } = await getImageDetails(data); + is(width, expectedSize * originalRatio, "Image has expected width"); + is(height, expectedSize * originalRatio, "Image has expected height"); + is(data, refData, "Image is equal"); +}); + +add_task(async function clipPosition({ client }) { + const { Page } = client; + const ratio = await getDevicePixelRatio(); + + await loadURLWithElement(); + var { data: refData } = await Page.captureScreenshot({ + clip: { + x: 0, + y: 0, + width: 100, + height: 100, + scale: 1, + }, + }); + + for (const [x, y] of [ + [10, 20], + [20, 10], + [20, 20], + ]) { + console.info(`Check postion for ${x} and ${y}`); + await loadURLWithElement({ x, y }); + var { data } = await Page.captureScreenshot({ + clip: { + x, + y, + width: 100, + height: 100, + scale: 1, + }, + }); + + const { width, height } = await getImageDetails(data); + is(width, 100 * ratio, "Image has expected width"); + is(height, 100 * ratio, "Image has expected height"); + is(data, refData, "Image is equal"); + } +}); + +add_task(async function clipDimension({ client }) { + const { Page } = client; + const ratio = await getDevicePixelRatio(); + + for (const [width, height] of [ + [10, 20], + [20, 10], + [20, 20], + ]) { + console.info(`Check width and height for ${width} and ${height}`); + + // Get reference image as section from a larger image + await loadURLWithElement({ width: 50, height: 50 }); + var { data: refData } = await Page.captureScreenshot({ + clip: { + x: 0, + y: 0, + width, + height, + scale: 1, + }, + }); + + await loadURLWithElement({ width, height }); + var { data } = await Page.captureScreenshot({ + clip: { + x: 0, + y: 0, + width, + height, + scale: 1, + }, + }); + + const dimension = await getImageDetails(data); + is(dimension.width, width * ratio, "Image has expected width"); + is(dimension.height, height * ratio, "Image has expected height"); + is(data, refData, "Image is equal"); + } +}); + +async function loadURLWithElement(options = {}) { + const { x = 0, y = 0, width = 100, height = 100 } = options; + + const doc = ` + <style> + body { + margin: 0; + } + div { + margin-left: ${x}px; + margin-top: ${y}px; + width: ${width}px; + height: ${height}px; + background: green; + } + </style> + <body> + <div></div> + `; + + await loadURL(toDataURL(doc)); +} + +async function getDevicePixelRatio() { + return SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () { + return content.browsingContext.overrideDPPX || content.devicePixelRatio; + }); +} + +async function setDevicePixelRatio(dppx) { + gBrowser.selectedBrowser.browsingContext.overrideDPPX = dppx; +} + +async function getImageDetails(image) { + const mimeType = getMimeType(image); + + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [{ mimeType, image }], + async function ({ mimeType, image }) { + return new Promise(resolve => { + const img = new content.Image(); + img.addEventListener( + "load", + () => { + resolve({ + mimeType, + width: img.width, + height: img.height, + length: image.length, + }); + }, + { once: true } + ); + + img.src = `data:${mimeType};base64,${image}`; + }); + } + ); +} + +function getMimeType(image) { + // Decode from base64 and convert the first 4 bytes to hex + const raw = atob(image).slice(0, 4); + let magicBytes = ""; + for (let i = 0; i < raw.length; i++) { + magicBytes += raw.charCodeAt(i).toString(16).toUpperCase(); + } + + switch (magicBytes) { + case "89504E47": + return "image/png"; + case "FFD8FFDB": + case "FFD8FFE0": + return "image/jpeg"; + default: + throw new Error("Unknown MIME type"); + } +} |