555 lines
14 KiB
JavaScript
555 lines
14 KiB
JavaScript
/* 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"));
|
|
|
|
await Assert.rejects(
|
|
Page.captureScreenshot({ format: "foo" }),
|
|
err => err.message.includes(`Unsupported MIME type: image`),
|
|
"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
|
|
Assert.greater(
|
|
info100.length,
|
|
infoDefault.length,
|
|
"Size of quality 100 is larger than default"
|
|
);
|
|
Assert.less(
|
|
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;
|
|
|
|
await Assert.rejects(
|
|
Page.captureScreenshot({ clip }),
|
|
err => err.message.includes(`clip.${prop}: double value expected`),
|
|
`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");
|
|
}
|
|
}
|