diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /remote/shared/Capture.sys.mjs | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | remote/shared/Capture.sys.mjs | 203 |
1 files changed, 203 insertions, 0 deletions
diff --git a/remote/shared/Capture.sys.mjs b/remote/shared/Capture.sys.mjs new file mode 100644 index 0000000000..ec34d09aba --- /dev/null +++ b/remote/shared/Capture.sys.mjs @@ -0,0 +1,203 @@ +/* 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, { + Log: "chrome://remote/content/shared/Log.sys.mjs", +}); + +ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get()); + +const CONTEXT_2D = "2d"; +const BG_COLOUR = "rgb(255,255,255)"; +const MAX_CANVAS_DIMENSION = 32767; +const MAX_CANVAS_AREA = 472907776; +const PNG_MIME = "image/png"; +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 = win.browsingContext.overrideDPPX || win.devicePixelRatio; + + let canvasHeight = height * scale; + let canvasWidth = width * scale; + + // Cap the screenshot size for width and height at 2^16 pixels, + // which is the maximum allowed canvas size. Higher dimensions will + // trigger exceptions in Gecko. + if (canvasWidth > MAX_CANVAS_DIMENSION) { + lazy.logger.warn( + "Limiting screen capture width to maximum allowed " + + MAX_CANVAS_DIMENSION + + " pixels" + ); + width = Math.floor(MAX_CANVAS_DIMENSION / scale); + canvasWidth = width * scale; + } + + if (canvasHeight > MAX_CANVAS_DIMENSION) { + lazy.logger.warn( + "Limiting screen capture height to maximum allowed " + + MAX_CANVAS_DIMENSION + + " pixels" + ); + height = Math.floor(MAX_CANVAS_DIMENSION / scale); + canvasHeight = height * scale; + } + + // If the area is larger, reduce the height to keep the full width. + if (canvasWidth * canvasHeight > MAX_CANVAS_AREA) { + lazy.logger.warn( + "Limiting screen capture area to maximum allowed " + + MAX_CANVAS_AREA + + " pixels" + ); + height = Math.floor(MAX_CANVAS_AREA / (canvasWidth * scale)); + canvasHeight = height * scale; + } + + 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(); + } + + return canvas; +}; + +/** + * Encode the contents of an HTMLCanvasElement to a Base64 encoded string. + * + * @param {HTMLCanvasElement} canvas + * The canvas to encode. + * + * @returns {string} + * A Base64 encoded string. + */ +capture.toBase64 = function (canvas) { + let u = canvas.toDataURL(PNG_MIME); + 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); + 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(""); +} |