Bug 1213797: Refactor screen capture and SVG document support

Errors thrown by takeScreenshot used to be silently ignored.  When the
command started using the new dispatching technique in bug 1202663,
it was surfaced we do not support taking screen captures of SVG documents.

Since this is a requirement for Web Platform Tests, this patch corrects
the wrong assumptions about document body and document element.

This patch also significantly refactors the screen capture code, but
only uses the new implementation in contnent space, since some further
modifications are required to use it in chrome.

r=dburns
r=jgriffin

--HG--
extra : commitid : DdCIEpd5PEJ
extra : rebase_source : 7357010f992d7f995765c685000892cc59d9ec9a
This commit is contained in:
Andreas Tolfsen 2015-10-13 16:52:26 +01:00
parent 4e5128c472
commit 30ee512caa
5 changed files with 178 additions and 76 deletions

View File

@ -0,0 +1,142 @@
/* 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";
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 = {};
/**
* 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 doc = node.ownerDocument;
let win = doc.defaultView;
let rect = node.getBoundingClientRect();
return capture.canvas(
doc,
rect.left,
rect.top,
rect.width,
rect.height,
highlights);
};
/**
* Take a screenshot of the document's viewport, taking into account
* the current window's offset.
*
* @param {Document} document
* The DOM document providing the document element to capture,
* and a window for determining the offset of 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(document, highlights=[]) {
let win = document.defaultView;
let docEl = document.documentElement;
return capture.canvas(
document,
win.pageXOffset,
win.pageYOffset,
docEl.clientWidth,
docEl.clientHeight,
highlights);
};
/**
* Low-level interface to draw a rectangle off the framebuffer.
*
* @param {Document} document
* A DOM document providing the window used to the framebuffer,
* and 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(document, left, top, width, height, highlights=[]) {
let win = document.defaultView;
let canvas = document.createElementNS(XHTML_NS, "canvas");
canvas.width = width;
canvas.height = height;
let ctx = canvas.getContext(CONTEXT_2D);
ctx.drawWindow(win, left, top, width, height, BG_COLOUR);
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);
};

View File

@ -102,7 +102,6 @@ class Content(ScreenCaptureTestCase):
self.assertEqual(
self.body_scroll_dimensions, self.get_image_dimensions(string))
@skip("https://bugzilla.mozilla.org/show_bug.cgi?id=1213797")
def test_svg_document_element(self):
self.marionette.navigate(svg)
doc_el = self.document_element

View File

@ -2536,6 +2536,7 @@ GeckoDriver.prototype.clearImportedScripts = function(cmd, resp) {
*/
GeckoDriver.prototype.takeScreenshot = function(cmd, resp) {
let {id, highlights, full} = cmd.parameters;
highlights = highlights || [];
switch (this.context) {
case Context.CHROME:
@ -2579,7 +2580,7 @@ GeckoDriver.prototype.takeScreenshot = function(cmd, resp) {
break;
case Context.CONTENT:
return this.listener.takeScreenshot(id, highlights, full);
return this.listener.takeScreenshot(id, full, highlights);
break;
}
};

View File

@ -21,6 +21,7 @@ marionette.jar:
content/emulator.js (emulator.js)
content/modal.js (modal.js)
content/proxy.js (proxy.js)
content/capture.js (capture.js)
#ifdef ENABLE_TESTS
content/test.xul (client/marionette/chrome/test.xul)
content/test2.xul (client/marionette/chrome/test2.xul)

View File

@ -13,6 +13,7 @@ var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
loader.loadSubScript("chrome://marionette/content/simpletest.js");
loader.loadSubScript("chrome://marionette/content/common.js");
loader.loadSubScript("chrome://marionette/content/actions.js");
Cu.import("chrome://marionette/content/capture.js");
Cu.import("chrome://marionette/content/elements.js");
Cu.import("chrome://marionette/content/error.js");
Cu.import("resource://gre/modules/FileUtils.jsm");
@ -1997,89 +1998,47 @@ function importScript(msg) {
}
/**
* Takes a screen capture of the given web element if <code>id</code>
* property exists in the message's JSON object, or if null captures
* the bounding box of the current frame.
* Perform a screen capture in content context.
*
* If given an array of web element references in
* <code>msg.json.highlights</code>, a red box will be painted around
* them to highlight their position.
* @param {UUID=} id
* Optional web element reference of an element to take a screenshot
* of.
* @param {boolean=} full
* True to take a screenshot of the entire document element. Is not
* considered if {@code id} is not defined. Defaults to true.
* @param {Array.<UUID>=} highlights
* Draw a border around the elements found by their web element
* references.
*
* @return {string}
* Base64 encoded string of an image/png type.
*/
function takeScreenshot(id, highlights, full) {
let node = null;
if (id) {
node = elementManager.getKnownElement(id, curContainer)
} else {
node = curContainer.frame;
function takeScreenshot(id, full=true, highlights=[]) {
let canvas;
let highlightEls = [];
for (let h of highlights) {
let el = elementManager.getKnownElement(h, curContainer);
highlightEls.push(el);
}
let document = curContainer.frame.document;
let rect, win, width, height, left, top;
// viewport
if (!id && !full) {
canvas = capture.viewport(curContainer.frame.document, highlightEls);
// node can be either a window or an arbitrary DOM node
if (node == curContainer.frame) {
// node is a window
win = node;
if (full) {
// the full window
width = document.body.scrollWidth;
height = document.body.scrollHeight;
top = 0;
left = 0;
// element or full document element
} else {
let node;
if (id) {
node = elementManager.getKnownElement(id, curContainer);
} else {
// only the viewport
width = document.documentElement.clientWidth;
height = document.documentElement.clientHeight;
left = curContainer.frame.pageXOffset;
top = curContainer.frame.pageYOffset;
node = curContainer.frame.document.documentElement;
}
} else {
// node is an arbitrary DOM node
win = node.ownerDocument.defaultView;
rect = node.getBoundingClientRect();
width = rect.width;
height = rect.height;
top = rect.top;
left = rect.left;
canvas = capture.element(node, highlightEls);
}
let canvas = document.createElementNS(
"http://www.w3.org/1999/xhtml", "canvas");
canvas.width = width;
canvas.height = height;
let ctx = canvas.getContext("2d");
// draws the DOM contents of the window to the canvas
ctx.drawWindow(win, left, top, width, height, "rgb(255,255,255)");
// this section is for drawing a red rectangle around each element
// passed in via the highlights array
if (highlights) {
ctx.lineWidth = "2";
ctx.strokeStyle = "red";
ctx.save();
for (var i = 0; i < highlights.length; ++i) {
let elem = elementManager.getKnownElement(highlights[i], curContainer);
rect = elem.getBoundingClientRect();
let offsetY = -top;
let offsetX = -left;
// draw the rectangle
ctx.strokeRect(
rect.left + offsetX,
rect.top + offsetY,
rect.width,
rect.height);
}
}
// return the Base64 encoded string back to the client
// so that it can save the file to disk if it is required
let dataUrl = canvas.toDataURL("image/png", "");
let encoded = dataUrl.substring(dataUrl.indexOf(",") + 1);
return encoded;
return capture.toBase64(canvas);
}
// Call register self when we get loaded