Bug 1874684 - Part 33: Defer allocation of options object for CalendarDateFromFields...
[gecko.git] / remote / shared / Capture.sys.mjs
blobec34d09aba22b1422815fdabe9d957c99158e2c4
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 const lazy = {};
7 ChromeUtils.defineESModuleGetters(lazy, {
8   Log: "chrome://remote/content/shared/Log.sys.mjs",
9 });
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";
20 /**
21  * Provides primitives to capture screenshots.
22  *
23  * @namespace
24  */
25 export const capture = {};
27 capture.Format = {
28   Base64: 0,
29   Hash: 1,
32 /**
33  * Draw a rectangle off the framebuffer.
34  *
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.
42  * @param {number} top
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.
61  *
62  * @returns {HTMLCanvasElement}
63  *     The canvas on which the selection from the window's framebuffer
64  *     has been painted on.
65  */
66 capture.canvas = async function (
67   win,
68   browsingContext,
69   left,
70   top,
71   width,
72   height,
73   { canvas = null, flags = null, dX = 0, dY = 0, readback = false } = {}
74 ) {
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) {
86     lazy.logger.warn(
87       "Limiting screen capture width to maximum allowed " +
88         MAX_CANVAS_DIMENSION +
89         " pixels"
90     );
91     width = Math.floor(MAX_CANVAS_DIMENSION / scale);
92     canvasWidth = width * scale;
93   }
95   if (canvasHeight > MAX_CANVAS_DIMENSION) {
96     lazy.logger.warn(
97       "Limiting screen capture height to maximum allowed " +
98         MAX_CANVAS_DIMENSION +
99         " pixels"
100     );
101     height = Math.floor(MAX_CANVAS_DIMENSION / scale);
102     canvasHeight = height * scale;
103   }
105   // If the area is larger, reduce the height to keep the full width.
106   if (canvasWidth * canvasHeight > MAX_CANVAS_AREA) {
107     lazy.logger.warn(
108       "Limiting screen capture area to maximum allowed " +
109         MAX_CANVAS_AREA +
110         " pixels"
111     );
112     height = Math.floor(MAX_CANVAS_AREA / (canvasWidth * scale));
113     canvasHeight = height * scale;
114   }
116   if (canvas === null) {
117     canvas = win.document.createElementNS(XHTML_NS, "canvas");
118     canvas.width = canvasWidth;
119     canvas.height = canvasHeight;
120   }
122   const ctx = canvas.getContext(CONTEXT_2D);
124   if (readback) {
125     if (flags === null) {
126       flags =
127         ctx.DRAWWINDOW_DRAW_CARET |
128         ctx.DRAWWINDOW_DRAW_VIEW |
129         ctx.DRAWWINDOW_USE_WIDGET_LAYERS;
130     }
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);
135   } else {
136     let rect = new DOMRect(left, top, width, height);
137     let snapshot = await browsingContext.currentWindowGlobal.drawSnapshot(
138       rect,
139       scale,
140       BG_COLOUR
141     );
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.
148     snapshot.close();
149   }
151   return canvas;
155  * Encode the contents of an HTMLCanvasElement to a Base64 encoded string.
157  * @param {HTMLCanvasElement} canvas
158  *     The canvas to encode.
160  * @returns {string}
161  *     A Base64 encoded string.
162  */
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.
174  * @returns {string}
175  *     A hex digest of the SHA-256 hash of the base64 encoded string.
176  */
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.
189  * @returns {string}
190  *     A hex digest of the input buffer.
191  */
192 function hex(buffer) {
193   let hexCodes = [];
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);
201   }
202   return hexCodes.join("");