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/. */
5 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
9 ChromeUtils.defineESModuleGetters(lazy, {
10 Log: "chrome://remote/content/shared/Log.sys.mjs",
13 XPCOMUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());
15 const CONTEXT_2D = "2d";
16 const BG_COLOUR = "rgb(255,255,255)";
17 const MAX_CANVAS_DIMENSION = 32767;
18 const MAX_CANVAS_AREA = 472907776;
19 const PNG_MIME = "image/png";
20 const XHTML_NS = "http://www.w3.org/1999/xhtml";
23 * Provides primitives to capture screenshots.
27 export const capture = {};
35 * Draw a rectangle off the framebuffer.
37 * @param {DOMWindow} win
38 * The DOM window used for the framebuffer, and providing the interfaces
39 * for creating an HTMLCanvasElement.
40 * @param {BrowsingContext} browsingContext
41 * The BrowsingContext from which the snapshot should be taken.
42 * @param {number} left
43 * The left, X axis offset of the rectangle.
45 * The top, Y axis offset of the rectangle.
46 * @param {number} width
47 * The width dimension of the rectangle to paint.
48 * @param {number} height
49 * The height dimension of the rectangle to paint.
50 * @param {object=} options
51 * @param {HTMLCanvasElement=} options.canvas
52 * Optional canvas to reuse for the screenshot.
53 * @param {number=} options.flags
54 * Optional integer representing flags to pass to drawWindow; these
55 * are defined on CanvasRenderingContext2D.
56 * @param {number=} options.dX
57 * Horizontal offset between the browser window and content area. Defaults to 0.
58 * @param {number=} options.dY
59 * Vertical offset between the browser window and content area. Defaults to 0.
60 * @param {boolean=} options.readback
61 * If true, read back a snapshot of the pixel data currently in the
62 * compositor/window. Defaults to false.
64 * @returns {HTMLCanvasElement}
65 * The canvas on which the selection from the window's framebuffer
66 * has been painted on.
68 capture.canvas = async function (
75 { canvas = null, flags = null, dX = 0, dY = 0, readback = false } = {}
77 // FIXME(bug 1761032): This looks a bit sketchy, overrideDPPX doesn't
78 // influence rendering...
79 const scale = win.browsingContext.overrideDPPX || win.devicePixelRatio;
81 let canvasHeight = height * scale;
82 let canvasWidth = width * scale;
84 // Cap the screenshot size for width and height at 2^16 pixels,
85 // which is the maximum allowed canvas size. Higher dimensions will
86 // trigger exceptions in Gecko.
87 if (canvasWidth > MAX_CANVAS_DIMENSION) {
89 "Limiting screen capture width to maximum allowed " +
90 MAX_CANVAS_DIMENSION +
93 width = Math.floor(MAX_CANVAS_DIMENSION / scale);
94 canvasWidth = width * scale;
97 if (canvasHeight > MAX_CANVAS_DIMENSION) {
99 "Limiting screen capture height to maximum allowed " +
100 MAX_CANVAS_DIMENSION +
103 height = Math.floor(MAX_CANVAS_DIMENSION / scale);
104 canvasHeight = height * scale;
107 // If the area is larger, reduce the height to keep the full width.
108 if (canvasWidth * canvasHeight > MAX_CANVAS_AREA) {
110 "Limiting screen capture area to maximum allowed " +
114 height = Math.floor(MAX_CANVAS_AREA / (canvasWidth * scale));
115 canvasHeight = height * scale;
118 if (canvas === null) {
119 canvas = win.document.createElementNS(XHTML_NS, "canvas");
120 canvas.width = canvasWidth;
121 canvas.height = canvasHeight;
124 const ctx = canvas.getContext(CONTEXT_2D);
127 if (flags === null) {
129 ctx.DRAWWINDOW_DRAW_CARET |
130 ctx.DRAWWINDOW_DRAW_VIEW |
131 ctx.DRAWWINDOW_USE_WIDGET_LAYERS;
134 // drawWindow doesn't take scaling into account.
135 ctx.scale(scale, scale);
136 ctx.drawWindow(win, left + dX, top + dY, width, height, BG_COLOUR, flags);
138 let rect = new DOMRect(left, top, width, height);
139 let snapshot = await browsingContext.currentWindowGlobal.drawSnapshot(
145 ctx.drawImage(snapshot, 0, 0);
147 // Bug 1574935 - Huge dimensions can trigger an OOM because multiple copies
148 // of the bitmap will exist in memory. Force the removal of the snapshot
149 // because it is no longer needed.
157 * Encode the contents of an HTMLCanvasElement to a Base64 encoded string.
159 * @param {HTMLCanvasElement} canvas
160 * The canvas to encode.
163 * A Base64 encoded string.
165 capture.toBase64 = function (canvas) {
166 let u = canvas.toDataURL(PNG_MIME);
167 return u.substring(u.indexOf(",") + 1);
171 * Hash the contents of an HTMLCanvasElement to a SHA-256 hex digest.
173 * @param {HTMLCanvasElement} canvas
174 * The canvas to encode.
177 * A hex digest of the SHA-256 hash of the base64 encoded string.
179 capture.toHash = function (canvas) {
180 let u = capture.toBase64(canvas);
181 let buffer = new TextEncoder().encode(u);
182 return crypto.subtle.digest("SHA-256", buffer).then(hash => hex(hash));
186 * Convert buffer into to hex.
188 * @param {ArrayBuffer} buffer
189 * The buffer containing the data to convert to hex.
192 * A hex digest of the input buffer.
194 function hex(buffer) {
196 let view = new DataView(buffer);
197 for (let i = 0; i < view.byteLength; i += 4) {
198 let value = view.getUint32(i);
199 let stringValue = value.toString(16);
200 let padding = "00000000";
201 let paddedValue = (padding + stringValue).slice(-padding.length);
202 hexCodes.push(paddedValue);
204 return hexCodes.join("");