From 525563aa111e95c9507c35e624c836e919c3e1ef Mon Sep 17 00:00:00 2001
From: Andreas Pehrson
Date: Wed, 13 May 2015 14:04:51 +0800
Subject: [PATCH] Bug 1032848 - Part 3: Add tests for
HTMLCanvasElement::CaptureStream. r=mt, r=jgilbert, r=jesup
---
dom/canvas/test/captureStream_common.js | 174 +++++++++++++++
.../test_canvas2d_crossorigin.html | 114 ++++++----
.../crossorigin/test_video_crossorigin.html | 154 ++++++++------
.../image_red_crossorigin_credentials.png | Bin 0 -> 87 bytes
...e_red_crossorigin_credentials.png^headers^ | 2 +
dom/canvas/test/mochitest.ini | 4 +
dom/canvas/test/test_capture.html | 111 ++++++++++
dom/canvas/test/webgl-mochitest.ini | 2 +
.../test/webgl-mochitest/test_capture.html | 200 ++++++++++++++++++
dom/canvas/test/webgl-mochitest/webgl-util.js | 6 +-
dom/media/tests/mochitest/head.js | 1 +
dom/media/tests/mochitest/mochitest.ini | 4 +
...eerConnection_captureStream_canvas_2d.html | 55 +++++
...Connection_captureStream_canvas_webgl.html | 108 ++++++++++
14 files changed, 828 insertions(+), 107 deletions(-)
create mode 100644 dom/canvas/test/captureStream_common.js
create mode 100644 dom/canvas/test/image_red_crossorigin_credentials.png
create mode 100644 dom/canvas/test/image_red_crossorigin_credentials.png^headers^
create mode 100644 dom/canvas/test/test_capture.html
create mode 100644 dom/canvas/test/webgl-mochitest/test_capture.html
create mode 100644 dom/media/tests/mochitest/test_peerConnection_captureStream_canvas_2d.html
create mode 100644 dom/media/tests/mochitest/test_peerConnection_captureStream_canvas_webgl.html
diff --git a/dom/canvas/test/captureStream_common.js b/dom/canvas/test/captureStream_common.js
new file mode 100644
index 00000000000..a19f4725734
--- /dev/null
+++ b/dom/canvas/test/captureStream_common.js
@@ -0,0 +1,174 @@
+/* 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";
+
+/*
+ * Util base class to help test a captured canvas element. Initializes the
+ * output canvas (used for testing the color of video elements), and optionally
+ * overrides the default element |width| and |height|.
+ */
+function CaptureStreamTestHelper(width, height) {
+ this.cout = document.createElement('canvas');
+ if (width) {
+ this.elemWidth = width;
+ }
+ if (height) {
+ this.elemHeight = height;
+ }
+ this.cout.width = this.elemWidth;
+ this.cout.height = this.elemHeight;
+ document.body.appendChild(this.cout);
+}
+
+CaptureStreamTestHelper.prototype = {
+ /* Predefined colors for use in the methods below. */
+ black: { data: [0, 0, 0, 255], name: "black" },
+ green: { data: [0, 255, 0, 255], name: "green" },
+ red: { data: [255, 0, 0, 255], name: "red" },
+
+ /* Default element size for createAndAppendElement() */
+ elemWidth: 100,
+ elemHeight: 100,
+
+ /* Request a frame from the stream played by |video|. */
+ requestFrame: function (video) {
+ info("Requesting frame from " + video.id);
+ video.mozSrcObject.requestFrame();
+ },
+
+ /* Tests the top left pixel of |video| against |refData|. Format [R,G,B,A]. */
+ testPixel: function (video, refData, threshold) {
+ var ctxout = this.cout.getContext('2d');
+ ctxout.drawImage(video, 0, 0);
+ var pixel = ctxout.getImageData(0, 0, 1, 1).data;
+ return pixel.every((val, i) => Math.abs(val - refData[i]) <= threshold);
+ },
+
+ /*
+ * Returns a promise that resolves when the pixel matches. Use |threshold|
+ * for fuzzy matching the color on each channel, in the range [0,255].
+ */
+ waitForPixel: function (video, refColor, threshold, infoString) {
+ return new Promise(resolve => {
+ info("Testing " + video.id + " against [" + refColor.data.join(',') + "]");
+ CaptureStreamTestHelper2D.prototype.clear.call(this, this.cout);
+ video.ontimeupdate = () => {
+ if (this.testPixel(video, refColor.data, threshold)) {
+ ok(true, video.id + " " + infoString);
+ video.ontimeupdate = null;
+ resolve();
+ }
+ };
+ });
+ },
+
+ /*
+ * Returns a promise that resolves after |timeout| ms of playback or when a
+ * pixel of |video| becomes the color |refData|. The test is failed if the
+ * timeout is not reached.
+ */
+ waitForPixelToTimeout: function (video, refColor, threshold, timeout, infoString) {
+ return new Promise(resolve => {
+ info("Waiting for " + video.id + " to time out after " + timeout +
+ "ms against [" + refColor.data.join(',') + "] - " + refColor.name);
+ CaptureStreamTestHelper2D.prototype.clear.call(this, this.cout);
+ var startTime = video.currentTime;
+ video.ontimeupdate = () => {
+ if (this.testPixel(video, refColor.data, threshold)) {
+ ok(false, video.id + " " + infoString);
+ video.ontimeupdate = null;
+ resolve();
+ } else if (video.currentTime > startTime + (timeout / 1000.0)) {
+ ok(true, video.id + " " + infoString);
+ video.ontimeupdate = null;
+ resolve();
+ }
+ };
+ });
+ },
+
+ /* Create an element of type |type| with id |id| and append it to the body. */
+ createAndAppendElement: function (type, id) {
+ var e = document.createElement(type);
+ e.id = id;
+ e.width = this.elemWidth;
+ e.height = this.elemHeight;
+ if (type === 'video') {
+ e.autoplay = true;
+ }
+ document.body.appendChild(e);
+ return e;
+ },
+}
+
+/* Sub class holding 2D-Canvas specific helpers. */
+function CaptureStreamTestHelper2D(width, height) {
+ CaptureStreamTestHelper.call(this, width, height);
+}
+
+CaptureStreamTestHelper2D.prototype = Object.create(CaptureStreamTestHelper.prototype);
+CaptureStreamTestHelper2D.prototype.constructor = CaptureStreamTestHelper2D;
+
+/* Clear all drawn content on |canvas|. */
+CaptureStreamTestHelper2D.prototype.clear = function(canvas) {
+ var ctx = canvas.getContext('2d');
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+};
+
+/* Draw the color |color| to the source canvas |canvas|. Format [R,G,B,A]. */
+CaptureStreamTestHelper2D.prototype.drawColor = function(canvas, color) {
+ var ctx = canvas.getContext('2d');
+ var rgba = color.data.slice(); // Copy to not overwrite the original array
+ info("Drawing color " + rgba.join(','));
+ rgba[3] = rgba[3] / 255.0; // Convert opacity to double in range [0,1]
+ ctx.fillStyle = "rgba(" + rgba.join(',') + ")";
+
+ // Only fill top left corner to test that output is not flipped or rotated.
+ ctx.fillRect(0, 0, canvas.width / 2, canvas.height / 2);
+};
+
+/* Test that the given 2d canvas is NOT origin-clean. */
+CaptureStreamTestHelper2D.prototype.testNotClean = function(canvas) {
+ var ctx = canvas.getContext('2d');
+ var error = "OK";
+ try {
+ var data = ctx.getImageData(0, 0, 1, 1);
+ } catch(e) {
+ error = e.name;
+ }
+ is(error, "SecurityError",
+ "Canvas '" + canvas.id + "' should not be origin-clean");
+};
+
+/* Sub class holding WebGL specific helpers. */
+function CaptureStreamTestHelperWebGL(width, height) {
+ CaptureStreamTestHelper.call(this, width, height);
+}
+
+CaptureStreamTestHelperWebGL.prototype = Object.create(CaptureStreamTestHelper.prototype);
+CaptureStreamTestHelperWebGL.prototype.constructor = CaptureStreamTestHelperWebGL;
+
+/* Set the (uniform) color location for future draw calls. */
+CaptureStreamTestHelperWebGL.prototype.setFragmentColorLocation = function(colorLocation) {
+ this.colorLocation = colorLocation;
+};
+
+/* Clear the given WebGL context with |color|. */
+CaptureStreamTestHelperWebGL.prototype.clearColor = function(canvas, color) {
+ info("WebGL: clearColor(" + color.name + ")");
+ var gl = canvas.getContext('webgl');
+ var conv = color.data.map(i => i / 255.0);
+ gl.clearColor(conv[0], conv[1], conv[2], conv[3]);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+};
+
+/* Set an already setFragmentColorLocation() to |color| and drawArrays() */
+CaptureStreamTestHelperWebGL.prototype.drawColor = function(canvas, color) {
+ info("WebGL: drawArrays(" + color.name + ")");
+ var gl = canvas.getContext('webgl');
+ var conv = color.data.map(i => i / 255.0);
+ gl.uniform4f(this.colorLocation, conv[0], conv[1], conv[2], conv[3]);
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
+};
diff --git a/dom/canvas/test/crossorigin/test_canvas2d_crossorigin.html b/dom/canvas/test/crossorigin/test_canvas2d_crossorigin.html
index 6571542be9f..a12bd63960a 100644
--- a/dom/canvas/test/crossorigin/test_canvas2d_crossorigin.html
+++ b/dom/canvas/test/crossorigin/test_canvas2d_crossorigin.html
@@ -73,6 +73,17 @@ function testImage(url, crossOriginAttribute, expected_error) {
"drawImage then get image data on " + url +
" with crossOrigin=" + this.crossOrigin);
+ try {
+ c.captureStream(0);
+ actual_error = OK;
+ } catch (e) {
+ actual_error = e.name;
+ }
+
+ verifyError(actual_error, expected_error,
+ "drawImage then capture stream on " + url +
+ " with crossOrigin=" + this.crossOrigin);
+
// Now test patterns
c = document.createElement("canvas");
c.width = this.width;
@@ -91,6 +102,17 @@ function testImage(url, crossOriginAttribute, expected_error) {
"createPattern+fill then get image data on " + url +
" with crossOrigin=" + this.crossOrigin);
+ try {
+ c.captureStream(0);
+ actual_error = OK;
+ } catch (e) {
+ actual_error = e.name;
+ }
+
+ verifyError(actual_error, expected_error,
+ "createPattern+fill then capture stream on " + url +
+ " with crossOrigin=" + this.crossOrigin);
+
testDone();
};
@@ -130,56 +152,64 @@ const attrValues = [
[ "foobar", "anonymous" ]
];
-for (var imgIdx = 0; imgIdx < imageFiles.length; ++imgIdx) {
- for (var hostnameIdx = 0; hostnameIdx < hostnames.length; ++hostnameIdx) {
- var hostnameData = hostnames[hostnameIdx];
- var url = "http://" + hostnameData[0] + testPath + imageFiles[imgIdx][0];
- for (var attrValIdx = 0; attrValIdx < attrValues.length; ++attrValIdx) {
- var attrValData = attrValues[attrValIdx];
- // Now compute the expected result
- var expected_error;
- if (hostnameData[1] == "same-origin") {
- // Same-origin; these should all Just Work
- expected_error = OK;
- } else {
- // Cross-origin
- is(hostnameData[1], "cross-origin",
- "what sort of host is " + hostnameData[0]);
- var CORSMode = attrValData[1];
- if (CORSMode == "none") {
- // Doesn't matter what headers the server sends; we're not
- // using CORS on our end.
- expected_error = "SecurityError";
+function beginTest() {
+ for (var imgIdx = 0; imgIdx < imageFiles.length; ++imgIdx) {
+ for (var hostnameIdx = 0; hostnameIdx < hostnames.length; ++hostnameIdx) {
+ var hostnameData = hostnames[hostnameIdx];
+ var url = "http://" + hostnameData[0] + testPath + imageFiles[imgIdx][0];
+ for (var attrValIdx = 0; attrValIdx < attrValues.length; ++attrValIdx) {
+ var attrValData = attrValues[attrValIdx];
+ // Now compute the expected result
+ var expected_error;
+ if (hostnameData[1] == "same-origin") {
+ // Same-origin; these should all Just Work
+ expected_error = OK;
} else {
- // Check whether the server will let us talk to them
- var CORSHeaders = imageFiles[imgIdx][1];
- // We're going to look for CORS headers from the server
- if (CORSHeaders == "none") {
- // No CORS headers from server; load will fail.
- expected_error = BAD_URI_ERR;
- } else if (CORSHeaders == "allow-all-anon") {
- // Server only allows anonymous requests
- if (CORSMode == "anonymous") {
- expected_error = OK;
- } else {
- is(CORSMode, "use-credentials",
- "What other CORS modes are there?");
- // A load with credentials against a server that only
- // allows anonymous loads will fail.
- expected_error = BAD_URI_ERR;
- }
+ // Cross-origin
+ is(hostnameData[1], "cross-origin",
+ "what sort of host is " + hostnameData[0]);
+ var CORSMode = attrValData[1];
+ if (CORSMode == "none") {
+ // Doesn't matter what headers the server sends; we're not
+ // using CORS on our end.
+ expected_error = "SecurityError";
} else {
- is(CORSHeaders, "allow-single-server-creds",
- "What other CORS headers could there be?");
- // Our server should allow both anonymous and non-anonymous requests
- expected_error = OK;
+ // Check whether the server will let us talk to them
+ var CORSHeaders = imageFiles[imgIdx][1];
+ // We're going to look for CORS headers from the server
+ if (CORSHeaders == "none") {
+ // No CORS headers from server; load will fail.
+ expected_error = BAD_URI_ERR;
+ } else if (CORSHeaders == "allow-all-anon") {
+ // Server only allows anonymous requests
+ if (CORSMode == "anonymous") {
+ expected_error = OK;
+ } else {
+ is(CORSMode, "use-credentials",
+ "What other CORS modes are there?");
+ // A load with credentials against a server that only
+ // allows anonymous loads will fail.
+ expected_error = BAD_URI_ERR;
+ }
+ } else {
+ is(CORSHeaders, "allow-single-server-creds",
+ "What other CORS headers could there be?");
+ // Our server should allow both anonymous and non-anonymous requests
+ expected_error = OK;
+ }
}
}
+ testImage(url, attrValData[0], expected_error);
}
- testImage(url, attrValData[0], expected_error);
}
}
}
+
+var prefs = [
+ [ "canvas.capturestream.enabled", true ],
+];
+SpecialPowers.pushPrefEnv({ "set" : prefs }, beginTest);
+
+
+
diff --git a/dom/canvas/test/webgl-mochitest.ini b/dom/canvas/test/webgl-mochitest.ini
index 78339ed0757..b5e1582fdfa 100644
--- a/dom/canvas/test/webgl-mochitest.ini
+++ b/dom/canvas/test/webgl-mochitest.ini
@@ -9,6 +9,8 @@ support-files =
[webgl-mochitest/test_backbuffer_channels.html]
fail-if = (os == 'b2g')
[webgl-mochitest/test_depth_readpixels.html]
+[webgl-mochitest/test_capture.html]
+support-files = captureStream_common.js
[webgl-mochitest/test_draw.html]
[webgl-mochitest/test_fb_param.html]
[webgl-mochitest/test_fb_param_crash.html]
diff --git a/dom/canvas/test/webgl-mochitest/test_capture.html b/dom/canvas/test/webgl-mochitest/test_capture.html
new file mode 100644
index 00000000000..613cc852525
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/test_capture.html
@@ -0,0 +1,200 @@
+
+
+
+WebGL test: CaptureStream()
+
+
+
+
+
+
+
+
+
+
diff --git a/dom/canvas/test/webgl-mochitest/webgl-util.js b/dom/canvas/test/webgl-mochitest/webgl-util.js
index 4c756266ff1..e71d84cfe64 100644
--- a/dom/canvas/test/webgl-mochitest/webgl-util.js
+++ b/dom/canvas/test/webgl-mochitest/webgl-util.js
@@ -34,19 +34,19 @@ WebGLUtil = (function() {
// ---------------------------------------------------------------------------
// WebGL helpers
- function getWebGL(canvasId, requireConformant) {
+ function getWebGL(canvasId, requireConformant, attributes) {
// `requireConformant` will default to falsey if it is not supplied.
var canvas = document.getElementById(canvasId);
var gl = null;
try {
- gl = canvas.getContext('webgl');
+ gl = canvas.getContext('webgl', attributes);
} catch(e) {}
if (!gl && !requireConformant) {
try {
- gl = canvas.getContext('experimental-webgl');
+ gl = canvas.getContext('experimental-webgl', attributes);
} catch(e) {}
}
diff --git a/dom/media/tests/mochitest/head.js b/dom/media/tests/mochitest/head.js
index dc5a18a8e5c..4d82546ce41 100644
--- a/dom/media/tests/mochitest/head.js
+++ b/dom/media/tests/mochitest/head.js
@@ -130,6 +130,7 @@ function setupEnvironment() {
window.finish = () => SimpleTest.finish();
SpecialPowers.pushPrefEnv({
'set': [
+ ['canvas.capturestream.enabled', true],
['dom.messageChannel.enabled', true],
['media.peerconnection.enabled', true],
['media.peerconnection.identity.enabled', true],
diff --git a/dom/media/tests/mochitest/mochitest.ini b/dom/media/tests/mochitest/mochitest.ini
index ef497f1eb66..7611bb85962 100644
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -103,6 +103,10 @@ skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g emulator seems to be to
skip-if = buildapp == 'b2g' || buildapp == 'mulet' || os == 'android' # bug 1043403 # Bug 1141029 Mulet parity with B2G Desktop for TC
[test_peerConnection_capturedVideo.html]
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
+[test_peerConnection_captureStream_canvas_2d.html]
+skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
+[test_peerConnection_captureStream_canvas_webgl.html]
+skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
[test_peerConnection_close.html]
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
[test_peerConnection_errorCallbacks.html]
diff --git a/dom/media/tests/mochitest/test_peerConnection_captureStream_canvas_2d.html b/dom/media/tests/mochitest/test_peerConnection_captureStream_canvas_2d.html
new file mode 100644
index 00000000000..5f76a507bbb
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_captureStream_canvas_2d.html
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dom/media/tests/mochitest/test_peerConnection_captureStream_canvas_webgl.html b/dom/media/tests/mochitest/test_peerConnection_captureStream_canvas_webgl.html
new file mode 100644
index 00000000000..b3a753bc4e3
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_captureStream_canvas_webgl.html
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+