Blame testing/marionette/capture.js

Packit f0b94e
/* This Source Code Form is subject to the terms of the Mozilla Public
Packit f0b94e
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
Packit f0b94e
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
Packit f0b94e
Packit f0b94e
"use strict";
Packit f0b94e
Packit f0b94e
const {InvalidArgumentError} = ChromeUtils.import("chrome://marionette/content/error.js", {});
Packit f0b94e
Packit f0b94e
Cu.importGlobalProperties(["crypto"]);
Packit f0b94e
Packit f0b94e
this.EXPORTED_SYMBOLS = ["capture"];
Packit f0b94e
Packit f0b94e
const CONTEXT_2D = "2d";
Packit f0b94e
const BG_COLOUR = "rgb(255,255,255)";
Packit f0b94e
const PNG_MIME = "image/png";
Packit f0b94e
const XHTML_NS = "http://www.w3.org/1999/xhtml";
Packit f0b94e
Packit f0b94e
/**
Packit f0b94e
 * Provides primitives to capture screenshots.
Packit f0b94e
 *
Packit f0b94e
 * @namespace
Packit f0b94e
 */
Packit f0b94e
this.capture = {};
Packit f0b94e
Packit f0b94e
capture.Format = {
Packit f0b94e
  Base64: 0,
Packit f0b94e
  Hash: 1,
Packit f0b94e
};
Packit f0b94e
Packit f0b94e
/**
Packit f0b94e
 * Take a screenshot of a single element.
Packit f0b94e
 *
Packit f0b94e
 * @param {Node} node
Packit f0b94e
 *     The node to take a screenshot of.
Packit f0b94e
 * @param {Array.<Node>=} highlights
Packit f0b94e
 *     Optional array of nodes, around which a border will be marked to
Packit f0b94e
 *     highlight them in the screenshot.
Packit f0b94e
 *
Packit f0b94e
 * @return {HTMLCanvasElement}
Packit f0b94e
 *     The canvas element where the element has been painted on.
Packit f0b94e
 */
Packit f0b94e
capture.element = function(node, highlights = []) {
Packit f0b94e
  let win = node.ownerGlobal;
Packit f0b94e
  let rect = node.getBoundingClientRect();
Packit f0b94e
Packit f0b94e
  return capture.canvas(
Packit f0b94e
      win,
Packit f0b94e
      rect.left,
Packit f0b94e
      rect.top,
Packit f0b94e
      rect.width,
Packit f0b94e
      rect.height,
Packit f0b94e
      {highlights});
Packit f0b94e
};
Packit f0b94e
Packit f0b94e
/**
Packit f0b94e
 * Take a screenshot of the window's viewport by taking into account
Packit f0b94e
 * the current offsets.
Packit f0b94e
 *
Packit f0b94e
 * @param {DOMWindow} win
Packit f0b94e
 *     The DOM window providing the document element to capture,
Packit f0b94e
 *     and the offsets for the viewport.
Packit f0b94e
 * @param {Array.<Node>=} highlights
Packit f0b94e
 *     Optional array of nodes, around which a border will be marked to
Packit f0b94e
 *     highlight them in the screenshot.
Packit f0b94e
 *
Packit f0b94e
 * @return {HTMLCanvasElement}
Packit f0b94e
 *     The canvas element where the viewport has been painted on.
Packit f0b94e
 */
Packit f0b94e
capture.viewport = function(win, highlights = []) {
Packit f0b94e
  let rootNode = win.document.documentElement;
Packit f0b94e
Packit f0b94e
  return capture.canvas(
Packit f0b94e
      win,
Packit f0b94e
      win.pageXOffset,
Packit f0b94e
      win.pageYOffset,
Packit f0b94e
      rootNode.clientWidth,
Packit f0b94e
      rootNode.clientHeight,
Packit f0b94e
      {highlights});
Packit f0b94e
};
Packit f0b94e
Packit f0b94e
/**
Packit f0b94e
 * Low-level interface to draw a rectangle off the framebuffer.
Packit f0b94e
 *
Packit f0b94e
 * @param {DOMWindow} win
Packit f0b94e
 *     The DOM window used for the framebuffer, and providing the interfaces
Packit f0b94e
 *     for creating an HTMLCanvasElement.
Packit f0b94e
 * @param {number} left
Packit f0b94e
 *     The left, X axis offset of the rectangle.
Packit f0b94e
 * @param {number} top
Packit f0b94e
 *     The top, Y axis offset of the rectangle.
Packit f0b94e
 * @param {number} width
Packit f0b94e
 *     The width dimension of the rectangle to paint.
Packit f0b94e
 * @param {number} height
Packit f0b94e
 *     The height dimension of the rectangle to paint.
Packit f0b94e
 * @param {Array.<Node>=} highlights
Packit f0b94e
 *     Optional array of nodes, around which a border will be marked to
Packit f0b94e
 *     highlight them in the screenshot.
Packit f0b94e
 * @param {HTMLCanvasElement=} canvas
Packit f0b94e
 *     Optional canvas to reuse for the screenshot.
Packit f0b94e
 * @param {number=} flags
Packit f0b94e
 *     Optional integer representing flags to pass to drawWindow; these
Packit f0b94e
 *     are defined on CanvasRenderingContext2D.
Packit f0b94e
 *
Packit f0b94e
 * @return {HTMLCanvasElement}
Packit f0b94e
 *     The canvas on which the selection from the window's framebuffer
Packit f0b94e
 *     has been painted on.
Packit f0b94e
 */
Packit f0b94e
capture.canvas = function(win, left, top, width, height,
Packit f0b94e
    {highlights = [], canvas = null, flags = null} = {}) {
Packit f0b94e
  const scale = win.devicePixelRatio;
Packit f0b94e
Packit f0b94e
  if (canvas === null) {
Packit f0b94e
    canvas = win.document.createElementNS(XHTML_NS, "canvas");
Packit f0b94e
    canvas.width = width * scale;
Packit f0b94e
    canvas.height = height * scale;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  let ctx = canvas.getContext(CONTEXT_2D);
Packit f0b94e
  if (flags === null) {
Packit f0b94e
    flags = ctx.DRAWWINDOW_DRAW_CARET;
Packit f0b94e
    // TODO(ato): https://bugzil.la/1377335
Packit f0b94e
    //
Packit f0b94e
    // Disabled in bug 1243415 for webplatform-test
Packit f0b94e
    // failures due to out of view elements.  Needs
Packit f0b94e
    // https://github.com/w3c/web-platform-tests/issues/4383 fixed.
Packit f0b94e
    /*
Packit f0b94e
    ctx.DRAWWINDOW_DRAW_VIEW;
Packit f0b94e
    */
Packit f0b94e
    // Bug 1009762 - Crash in [@ mozilla::gl::ReadPixelsIntoDataSurface]
Packit f0b94e
    /*
Packit f0b94e
    ctx.DRAWWINDOW_USE_WIDGET_LAYERS;
Packit f0b94e
    */
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  ctx.scale(scale, scale);
Packit f0b94e
  ctx.drawWindow(win, left, top, width, height, BG_COLOUR, flags);
Packit f0b94e
  if (highlights.length) {
Packit f0b94e
    ctx = capture.highlight_(ctx, highlights, top, left);
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  return canvas;
Packit f0b94e
};
Packit f0b94e
Packit f0b94e
capture.highlight_ = function(context, highlights, top = 0, left = 0) {
Packit f0b94e
  if (typeof highlights == "undefined") {
Packit f0b94e
    throw new InvalidArgumentError("Missing highlights");
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  context.lineWidth = "2";
Packit f0b94e
  context.strokeStyle = "red";
Packit f0b94e
  context.save();
Packit f0b94e
Packit f0b94e
  for (let el of highlights) {
Packit f0b94e
    let rect = el.getBoundingClientRect();
Packit f0b94e
    let oy = -top;
Packit f0b94e
    let ox = -left;
Packit f0b94e
Packit f0b94e
    context.strokeRect(
Packit f0b94e
        rect.left + ox,
Packit f0b94e
        rect.top + oy,
Packit f0b94e
        rect.width,
Packit f0b94e
        rect.height);
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  return context;
Packit f0b94e
};
Packit f0b94e
Packit f0b94e
/**
Packit f0b94e
 * Encode the contents of an HTMLCanvasElement to a Base64 encoded string.
Packit f0b94e
 *
Packit f0b94e
 * @param {HTMLCanvasElement} canvas
Packit f0b94e
 *     The canvas to encode.
Packit f0b94e
 *
Packit f0b94e
 * @return {string}
Packit f0b94e
 *     A Base64 encoded string.
Packit f0b94e
 */
Packit f0b94e
capture.toBase64 = function(canvas) {
Packit f0b94e
  let u = canvas.toDataURL(PNG_MIME);
Packit f0b94e
  return u.substring(u.indexOf(",") + 1);
Packit f0b94e
};
Packit f0b94e
Packit f0b94e
/**
Packit f0b94e
* Hash the contents of an HTMLCanvasElement to a SHA-256 hex digest.
Packit f0b94e
*
Packit f0b94e
* @param {HTMLCanvasElement} canvas
Packit f0b94e
*     The canvas to encode.
Packit f0b94e
*
Packit f0b94e
* @return {string}
Packit f0b94e
*     A hex digest of the SHA-256 hash of the base64 encoded string.
Packit f0b94e
*/
Packit f0b94e
capture.toHash = function(canvas) {
Packit f0b94e
  let u = capture.toBase64(canvas);
Packit f0b94e
  let buffer = new TextEncoder("utf-8").encode(u);
Packit f0b94e
  return crypto.subtle.digest("SHA-256", buffer).then(hash => hex(hash));
Packit f0b94e
};
Packit f0b94e
Packit f0b94e
/**
Packit f0b94e
* Convert buffer into to hex.
Packit f0b94e
*
Packit f0b94e
* @param {ArrayBuffer} buffer
Packit f0b94e
*     The buffer containing the data to convert to hex.
Packit f0b94e
*
Packit f0b94e
* @return {string}
Packit f0b94e
*     A hex digest of the input buffer.
Packit f0b94e
*/
Packit f0b94e
function hex(buffer) {
Packit f0b94e
  let hexCodes = [];
Packit f0b94e
  let view = new DataView(buffer);
Packit f0b94e
  for (let i = 0; i < view.byteLength; i += 4) {
Packit f0b94e
    let value = view.getUint32(i);
Packit f0b94e
    let stringValue = value.toString(16);
Packit f0b94e
    let padding = "00000000";
Packit f0b94e
    let paddedValue = (padding + stringValue).slice(-padding.length);
Packit f0b94e
    hexCodes.push(paddedValue);
Packit f0b94e
  }
Packit f0b94e
  return hexCodes.join("");
Packit f0b94e
}