Mypal/testing/marionette/capture.js

193 lines
5.2 KiB
JavaScript

/* 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.<Node>=} 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.<Node>=} 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.<Node>=} 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("");
};