1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 ChromeUtils.defineESModuleGetters(lazy, {
8 Log: "chrome://remote/content/shared/Log.sys.mjs",
11 ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());
13 const CONTEXT_2D = "2d";
14 const BG_COLOUR = "rgb(255,255,255)";
15 const MAX_CANVAS_DIMENSION = 32767;
16 const MAX_CANVAS_AREA = 472907776;
17 const PNG_MIME = "image/png";
18 const XHTML_NS = "http://www.w3.org/1999/xhtml";
21 * Provides primitives to capture screenshots.
25 export const capture = {};
33 * Draw a rectangle off the framebuffer.
35 * @param {DOMWindow} win
36 * The DOM window used for the framebuffer, and providing the interfaces
37 * for creating an HTMLCanvasElement.
38 * @param {BrowsingContext} browsingContext
39 * The BrowsingContext from which the snapshot should be taken.
40 * @param {number} left
41 * The left, X axis offset of the rectangle.
43 * The top, Y axis offset of the rectangle.
44 * @param {number} width
45 * The width dimension of the rectangle to paint.
46 * @param {number} height
47 * The height dimension of the rectangle to paint.
48 * @param {object=} options
49 * @param {HTMLCanvasElement=} options.canvas
50 * Optional canvas to reuse for the screenshot.
51 * @param {number=} options.flags
52 * Optional integer representing flags to pass to drawWindow; these
53 * are defined on CanvasRenderingContext2D.
54 * @param {number=} options.dX
55 * Horizontal offset between the browser window and content area. Defaults to 0.
56 * @param {number=} options.dY
57 * Vertical offset between the browser window and content area. Defaults to 0.
58 * @param {boolean=} options.readback
59 * If true, read back a snapshot of the pixel data currently in the
60 * compositor/window. Defaults to false.
62 * @returns {HTMLCanvasElement}
63 * The canvas on which the selection from the window's framebuffer
64 * has been painted on.
66 capture.canvas = async function (
73 { canvas = null, flags = null, dX = 0, dY = 0, readback = false } = {}
75 // FIXME(bug 1761032): This looks a bit sketchy, overrideDPPX doesn't
76 // influence rendering...
77 const scale = win.browsingContext.overrideDPPX || win.devicePixelRatio;
79 let canvasHeight = height * scale;
80 let canvasWidth = width * scale;
82 // Cap the screenshot size for width and height at 2^16 pixels,
83 // which is the maximum allowed canvas size. Higher dimensions will
84 // trigger exceptions in Gecko.
85 if (canvasWidth > MAX_CANVAS_DIMENSION) {
87 "Limiting screen capture width to maximum allowed " +
88 MAX_CANVAS_DIMENSION +
91 width = Math.floor(MAX_CANVAS_DIMENSION / scale);
92 canvasWidth = width * scale;
95 if (canvasHeight > MAX_CANVAS_DIMENSION) {
97 "Limiting screen capture height to maximum allowed " +
98 MAX_CANVAS_DIMENSION +
101 height = Math.floor(MAX_CANVAS_DIMENSION / scale);
102 canvasHeight = height * scale;
105 // If the area is larger, reduce the height to keep the full width.
106 if (canvasWidth * canvasHeight > MAX_CANVAS_AREA) {
108 "Limiting screen capture area to maximum allowed " +
112 height = Math.floor(MAX_CANVAS_AREA / (canvasWidth * scale));
113 canvasHeight = height * scale;
116 if (canvas === null) {
117 canvas = win.document.createElementNS(XHTML_NS, "canvas");
118 canvas.width = canvasWidth;
119 canvas.height = canvasHeight;
122 const ctx = canvas.getContext(CONTEXT_2D);
125 if (flags === null) {
127 ctx.DRAWWINDOW_DRAW_CARET |
128 ctx.DRAWWINDOW_DRAW_VIEW |
129 ctx.DRAWWINDOW_USE_WIDGET_LAYERS;
132 // drawWindow doesn't take scaling into account.
133 ctx.scale(scale, scale);
134 ctx.drawWindow(win, left + dX, top + dY, width, height, BG_COLOUR, flags);
136 let rect = new DOMRect(left, top, width, height);
137 let snapshot = await browsingContext.currentWindowGlobal.drawSnapshot(
143 ctx.drawImage(snapshot, 0, 0);
145 // Bug 1574935 - Huge dimensions can trigger an OOM because multiple copies
146 // of the bitmap will exist in memory. Force the removal of the snapshot
147 // because it is no longer needed.
155 * Encode the contents of an HTMLCanvasElement to a Base64 encoded string.
157 * @param {HTMLCanvasElement} canvas
158 * The canvas to encode.
161 * A Base64 encoded string.
163 capture.toBase64 = function (canvas) {
164 let u = canvas.toDataURL(PNG_MIME);
165 return u.substring(u.indexOf(",") + 1);
169 * Hash the contents of an HTMLCanvasElement to a SHA-256 hex digest.
171 * @param {HTMLCanvasElement} canvas
172 * The canvas to encode.
175 * A hex digest of the SHA-256 hash of the base64 encoded string.
177 capture.toHash = function (canvas) {
178 let u = capture.toBase64(canvas);
179 let buffer = new TextEncoder().encode(u);
180 return crypto.subtle.digest("SHA-256", buffer).then(hash => hex(hash));
184 * Convert buffer into to hex.
186 * @param {ArrayBuffer} buffer
187 * The buffer containing the data to convert to hex.
190 * A hex digest of the input buffer.
192 function hex(buffer) {
194 let view = new DataView(buffer);
195 for (let i = 0; i < view.byteLength; i += 4) {
196 let value = view.getUint32(i);
197 let stringValue = value.toString(16);
198 let padding = "00000000";
199 let paddedValue = (padding + stringValue).slice(-padding.length);
200 hexCodes.push(paddedValue);
202 return hexCodes.join("");