/* 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/. */ "use strict"; const {utils: Cu} = Components; Cu.importGlobalProperties(["crypto"]); this.EXPORTED_SYMBOLS = ["capture"]; const CONTEXT_2D = "2d"; const BG_COLOUR = "rgb(255,255,255)"; const PNG_MIME = "image/png"; const XHTML_NS = "http://www.w3.org/1999/xhtml"; /** Provides primitives to capture screenshots. */ this.capture = {}; capture.Format = { Base64: 0, Hash: 1, }; /** * Take a screenshot of a single element. * * @param {Node} node * The node to take a screenshot of. * @param {Array.=} highlights * Optional array of nodes, around which a border will be marked to * highlight them in the screenshot. * * @return {HTMLCanvasElement} * The canvas element where the element has been painted on. */ capture.element = function (node, highlights = []) { let win = node.ownerDocument.defaultView; let rect = node.getBoundingClientRect(); return capture.canvas( win, rect.left, rect.top, rect.width, rect.height, highlights); }; /** * Take a screenshot of the window's viewport by taking into account * the current offsets. * * @param {DOMWindow} win * The DOM window providing the document element to capture, * and the offsets for the viewport. * @param {Array.=} highlights * Optional array of nodes, around which a border will be marked to * highlight them in the screenshot. * * @return {HTMLCanvasElement} * The canvas element where the viewport has been painted on. */ capture.viewport = function (win, highlights = []) { let rootNode = win.document.documentElement; return capture.canvas( win, win.pageXOffset, win.pageYOffset, rootNode.clientWidth, rootNode.clientHeight, highlights); }; /** * Low-level interface to 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 {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 {Array.=} highlights * Optional array of nodes, around which a border will be marked to * highlight them in the screenshot. * * @return {HTMLCanvasElement} * The canvas on which the selection from the window's framebuffer * has been painted on. */ capture.canvas = function (win, left, top, width, height, highlights = []) { let scale = win.devicePixelRatio; let canvas = win.document.createElementNS(XHTML_NS, "canvas"); canvas.width = width * scale; canvas.height = height * scale; let ctx = canvas.getContext(CONTEXT_2D); let flags = ctx.DRAWWINDOW_DRAW_CARET; // Disabled in bug 1243415 for webplatform-test failures due to out of view elements. // Needs https://github.com/w3c/web-platform-tests/issues/4383 fixed. // ctx.DRAWWINDOW_DRAW_VIEW; // Bug 1009762 - Crash in [@ mozilla::gl::ReadPixelsIntoDataSurface] // ctx.DRAWWINDOW_USE_WIDGET_LAYERS; ctx.scale(scale, scale); ctx.drawWindow(win, left, top, width, height, BG_COLOUR, flags); ctx = capture.highlight_(ctx, highlights, top, left); return canvas; }; capture.highlight_ = function (context, highlights, top = 0, left = 0) { if (!highlights) { return; } context.lineWidth = "2"; context.strokeStyle = "red"; context.save(); for (let el of highlights) { let rect = el.getBoundingClientRect(); let oy = -top; let ox = -left; context.strokeRect( rect.left + ox, rect.top + oy, rect.width, rect.height); } return context; }; /** * Encode the contents of an HTMLCanvasElement to a Base64 encoded string. * * @param {HTMLCanvasElement} canvas * The canvas to encode. * * @return {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. * * @return {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("utf-8").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. * * @return {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(""); };