gecko-dev/testing/marionette/capture.js
Andreas Tolfsen 0be64b42c5 Bug 1467744 - Lazily import globals in Marionette. r=whimboo
Calling Cu.importGlobalProperties immediately defines the import
properties and any prototypes that they require.  Aside from CPU
overhead, this also tends to consume a lot of memory, especially
for objects with complex prototypes.  And it does this once for
every global you call it in.  This is especially a problem for
content processes, since we get this memory overhead in each and
every content process.

This patch moves Marionette to use the new
XPCOMUtils.defineLazyGlobalGetters so that symbols are constructed
only when actually needed.

MozReview-Commit-ID: 3RYWTcdO7FM

--HG--
extra : rebase_source : 0c450c8900e5de5446796b34ae4ab6bdf0fe9118
2018-06-08 13:16:29 +01:00

220 lines
5.9 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";
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
const {InvalidArgumentError} = ChromeUtils.import("chrome://marionette/content/error.js", {});
XPCOMUtils.defineLazyGlobalGetters(this, ["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.
*
* @namespace
*/
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.ownerGlobal;
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.
* @param {HTMLCanvasElement=} canvas
* Optional canvas to reuse for the screenshot.
* @param {number=} flags
* Optional integer representing flags to pass to drawWindow; these
* are defined on CanvasRenderingContext2D.
*
* @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 = [], canvas = null, flags = null} = {}) {
const scale = win.devicePixelRatio;
if (canvas === null) {
canvas = win.document.createElementNS(XHTML_NS, "canvas");
canvas.width = width * scale;
canvas.height = height * scale;
}
let ctx = canvas.getContext(CONTEXT_2D);
if (flags === null) {
flags = ctx.DRAWWINDOW_DRAW_CARET;
// TODO(ato): https://bugzil.la/1377335
//
// 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);
if (highlights.length) {
ctx = capture.highlight_(ctx, highlights, top, left);
}
return canvas;
};
capture.highlight_ = function(context, highlights, top = 0, left = 0) {
if (typeof highlights == "undefined") {
throw new InvalidArgumentError("Missing highlights");
}
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("");
}