Bug 1838739 - Initialize result of SetAsGPUOutOfMemoryError. r=webgpu-reviewers,nical
[gecko.git] / remote / shared / Capture.sys.mjs
blob1d0ef00e7563a84b5abec8fbe412a3a6b57bb5b7
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";
7 const lazy = {};
9 ChromeUtils.defineESModuleGetters(lazy, {
10   Log: "chrome://remote/content/shared/Log.sys.mjs",
11 });
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";
22 /**
23  * Provides primitives to capture screenshots.
24  *
25  * @namespace
26  */
27 export const capture = {};
29 capture.Format = {
30   Base64: 0,
31   Hash: 1,
34 /**
35  * Draw a rectangle off the framebuffer.
36  *
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.
44  * @param {number} top
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.
63  *
64  * @returns {HTMLCanvasElement}
65  *     The canvas on which the selection from the window's framebuffer
66  *     has been painted on.
67  */
68 capture.canvas = async function (
69   win,
70   browsingContext,
71   left,
72   top,
73   width,
74   height,
75   { canvas = null, flags = null, dX = 0, dY = 0, readback = false } = {}
76 ) {
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) {
88     lazy.logger.warn(
89       "Limiting screen capture width to maximum allowed " +
90         MAX_CANVAS_DIMENSION +
91         " pixels"
92     );
93     width = Math.floor(MAX_CANVAS_DIMENSION / scale);
94     canvasWidth = width * scale;
95   }
97   if (canvasHeight > MAX_CANVAS_DIMENSION) {
98     lazy.logger.warn(
99       "Limiting screen capture height to maximum allowed " +
100         MAX_CANVAS_DIMENSION +
101         " pixels"
102     );
103     height = Math.floor(MAX_CANVAS_DIMENSION / scale);
104     canvasHeight = height * scale;
105   }
107   // If the area is larger, reduce the height to keep the full width.
108   if (canvasWidth * canvasHeight > MAX_CANVAS_AREA) {
109     lazy.logger.warn(
110       "Limiting screen capture area to maximum allowed " +
111         MAX_CANVAS_AREA +
112         " pixels"
113     );
114     height = Math.floor(MAX_CANVAS_AREA / (canvasWidth * scale));
115     canvasHeight = height * scale;
116   }
118   if (canvas === null) {
119     canvas = win.document.createElementNS(XHTML_NS, "canvas");
120     canvas.width = canvasWidth;
121     canvas.height = canvasHeight;
122   }
124   const ctx = canvas.getContext(CONTEXT_2D);
126   if (readback) {
127     if (flags === null) {
128       flags =
129         ctx.DRAWWINDOW_DRAW_CARET |
130         ctx.DRAWWINDOW_DRAW_VIEW |
131         ctx.DRAWWINDOW_USE_WIDGET_LAYERS;
132     }
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);
137   } else {
138     let rect = new DOMRect(left, top, width, height);
139     let snapshot = await browsingContext.currentWindowGlobal.drawSnapshot(
140       rect,
141       scale,
142       BG_COLOUR
143     );
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.
150     snapshot.close();
151   }
153   return canvas;
157  * Encode the contents of an HTMLCanvasElement to a Base64 encoded string.
159  * @param {HTMLCanvasElement} canvas
160  *     The canvas to encode.
162  * @returns {string}
163  *     A Base64 encoded string.
164  */
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.
176  * @returns {string}
177  *     A hex digest of the SHA-256 hash of the base64 encoded string.
178  */
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.
191  * @returns {string}
192  *     A hex digest of the input buffer.
193  */
194 function hex(buffer) {
195   let hexCodes = [];
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);
203   }
204   return hexCodes.join("");