Source code
Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
});
const CONTEXT_2D = "2d";
const BG_COLOUR = "rgb(255,255,255)";
const XHTML_NS = "http://www.w3.org/1999/xhtml";
/**
* Provides primitives to capture screenshots.
*
* @namespace
*/
export const capture = {};
capture.Format = {
Base64: 0,
Hash: 1,
};
/**
* Draw a rectangle off the framebuffer.
*
* @param {DOMWindow} win
* The DOM window used for the framebuffer, and providing the interfaces
* for creating an HTMLCanvasElement.
* @param {BrowsingContext} browsingContext
* The BrowsingContext from which the snapshot should be taken.
* @param {number} left
* The left, X axis offset of the rectangle.
* @param {number} top
* The top, Y axis offset of the rectangle.
* @param {number} width
* The width dimension of the rectangle to paint.
* @param {number} height
* The height dimension of the rectangle to paint.
* @param {object=} options
* @param {HTMLCanvasElement=} options.canvas
* Optional canvas to reuse for the screenshot.
* @param {number=} options.flags
* Optional integer representing flags to pass to drawWindow; these
* are defined on CanvasRenderingContext2D.
* @param {number=} options.dX
* Horizontal offset between the browser window and content area. Defaults to 0.
* @param {number=} options.dY
* Vertical offset between the browser window and content area. Defaults to 0.
* @param {boolean=} options.readback
* If true, read back a snapshot of the pixel data currently in the
* compositor/window. Defaults to false.
*
* @returns {HTMLCanvasElement}
* The canvas on which the selection from the window's framebuffer
* has been painted on.
*/
capture.canvas = async function (
win,
browsingContext,
left,
top,
width,
height,
{ canvas = null, flags = null, dX = 0, dY = 0, readback = false } = {}
) {
// FIXME(bug 1761032): This looks a bit sketchy, overrideDPPX doesn't
// influence rendering...
const scale = browsingContext.overrideDPPX || win.devicePixelRatio;
let canvasHeight = height * scale;
let canvasWidth = width * scale;
try {
if (canvas === null) {
canvas = win.document.createElementNS(XHTML_NS, "canvas");
canvas.width = canvasWidth;
canvas.height = canvasHeight;
}
const ctx = canvas.getContext(CONTEXT_2D);
if (readback) {
if (flags === null) {
flags =
ctx.DRAWWINDOW_DRAW_CARET |
ctx.DRAWWINDOW_DRAW_VIEW |
ctx.DRAWWINDOW_USE_WIDGET_LAYERS;
}
// drawWindow doesn't take scaling into account.
ctx.scale(scale, scale);
ctx.drawWindow(win, left + dX, top + dY, width, height, BG_COLOUR, flags);
} else {
let rect = new DOMRect(left, top, width, height);
let snapshot = await browsingContext.currentWindowGlobal.drawSnapshot(
rect,
scale,
BG_COLOUR
);
ctx.drawImage(snapshot, 0, 0);
// Bug 1574935 - Huge dimensions can trigger an OOM because multiple copies
// of the bitmap will exist in memory. Force the removal of the snapshot
// because it is no longer needed.
snapshot.close();
}
} catch (e) {
// If we failed to create the canvas or draw the snapshot (likely due to OOM
// or excessive dimensions), we treat this as an unsupported operation.
throw new lazy.error.UnsupportedOperationError(
`Unable to capture screenshot: ${e.message}`
);
}
return canvas;
};
/**
* Encode the contents of an HTMLCanvasElement to a Base64 encoded string.
*
* @param {HTMLCanvasElement} canvas
* The canvas to encode.
* @param {string} type
* The image format to output such as `image/png`.
* @param {number} encoderOptions
* A number between 0 and 1 representing the image quality. Defaults to `1.0`.
*
* @returns {string}
* A Base64 encoded string.
*/
capture.toBase64 = function (canvas, type, encoderOptions = 1.0) {
let u = canvas.toDataURL(type, encoderOptions);
return u.substring(u.indexOf(",") + 1);
};
/**
* Hash the contents of an HTMLCanvasElement to a SHA-256 hex digest.
*
* @param {HTMLCanvasElement} canvas
* The canvas to encode.
*
* @returns {string}
* A hex digest of the SHA-256 hash of the base64 encoded string.
*/
capture.toHash = function (canvas) {
let u = capture.toBase64(canvas, "image/png");
let buffer = new TextEncoder().encode(u);
return crypto.subtle.digest("SHA-256", buffer).then(hash => hex(hash));
};
/**
* Convert buffer into to hex.
*
* @param {ArrayBuffer} buffer
* The buffer containing the data to convert to hex.
*
* @returns {string}
* A hex digest of the input buffer.
*/
function hex(buffer) {
let hexCodes = [];
let view = new DataView(buffer);
for (let i = 0; i < view.byteLength; i += 4) {
let value = view.getUint32(i);
let stringValue = value.toString(16);
let padding = "00000000";
let paddedValue = (padding + stringValue).slice(-padding.length);
hexCodes.push(paddedValue);
}
return hexCodes.join("");
}