Bug 1032848 - Part 3: Add tests for HTMLCanvasElement::CaptureStream. r=mt, r=jgilbert, r=jesup

--HG--
extra : transplant_source : %10%FE%B2%29%FD%88%94%3Ax%11%F8%8D%A5%218e%F3%EA%E7%2B
This commit is contained in:
Andreas Pehrson 2015-05-13 14:04:51 +08:00
parent a0fcf04b67
commit c1f6180c92
14 changed files with 828 additions and 107 deletions

View File

@ -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);
};

View File

@ -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);
</script>
</pre>
</body>

View File

@ -28,11 +28,7 @@ function createCanvas(width, height) {
return c;
}
function testCanvasDrawImage(v) {
var c = createCanvas(v.width, v.height);
var ctx = c.getContext("2d");
ctx.drawImage(v, 0, 0);
function checkGetImageData(ctx, v) {
try {
var data = ctx.getImageData(0, 0, 1, 1);
ok(true, "drawImage '" + v.src + "' then getImageData with crossOrigin='" + v.crossOrigin + "' worked");
@ -42,22 +38,54 @@ function testCanvasDrawImage(v) {
}
}
function checkGetImageDataTainted(ctx, v) {
try {
var data = ctx.getImageData(0, 0, 1, 1);
ok(false, "changing the CORS mode should not allow reading data from remote videos");
} catch (error) {
ok(error.name === "SecurityError", "changing the CORS mode, drawImage '" + v.src + "' then getImageData with crossOrigin='" + v.crossOrigin + "' failed");
}
}
function checkCaptureStream(c, v) {
try {
var stream = c.captureStream(0);
ok(true, "drawImage '" + v.src + "' then captureStream with crossOrigin='" + v.crossOrigin + "' worked");
} catch(error) {
ok(!v.crossOrigin && error.name === "SecurityError", "drawImage '" + v.src + "' then captureStream with crossOrigin='" + v.crossOrigin + "' failed");
v.tainted = true;
}
}
function checkCaptureStreamTainted(c, v) {
try {
var stream = c.captureStream(0);
ok(false, "changing the CORS mode should not allow capturing a stream from remote videos");
} catch (error) {
ok(error.name === "SecurityError", "changing the CORS mode, drawImage '" + v.src + "' then captureStream with crossOrigin='" + v.crossOrigin + "' failed");
}
}
function testCanvasDrawImage(v) {
var c = createCanvas(v.width, v.height);
var ctx = c.getContext("2d");
ctx.drawImage(v, 0, 0);
checkGetImageData(ctx, v);
checkCaptureStream(c, v);
}
function testCanvasCreatePattern(v) {
var c = createCanvas(v.width, v.height);
var ctx = c.getContext("2d");
ctx.fillStyle = ctx.createPattern(v, "");
ctx.fillRect(0, 0, c.width, c.height);
try {
var data = ctx.getImageData(0, 0, 1, 1);
ok(true, "createPattern '" + v.src + "' then getImageData with crossOrigin='" + v.crossOrigin + "' worked");
} catch(error) {
ok(!v.crossOrigin && error.name === "SecurityError", "createPattern '" + v.src + "' then getImageData with crossOrigin='" + v.crossOrigin + "' failed");
v.tainted = true;
}
checkGetImageData(ctx, v);
checkCaptureStream(c, v);
}
function testWebGL(v) {
function testWebGL(gl, v) {
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
@ -75,12 +103,8 @@ function testTaintedCanvas(v) {
var ctx = c.getContext("2d");
ctx.drawImage(v, 0, 0);
try {
var data = ctx.getImageData(0, 0, 1, 1);
ok(false, "changing the CORS mode should not allow reading data from remote videos");
} catch (error) {
ok(error.name === "SecurityError", "changing the CORS mode, drawImage '" + v.src + "' then getImageData with crossOrigin='" + v.crossOrigin + "' failed");
}
checkGetImageDataTainted(ctx, v);
checkCaptureStreamTainted(c, v);
}
function vidDataSuccess(e) {
@ -88,8 +112,8 @@ function vidDataSuccess(e) {
testCanvasDrawImage(e.target);
testCanvasCreatePattern(e.target);
if (gl) {
testWebGL(e.target);
if (document.gl) {
testWebGL(document.gl, e.target);
}
// If we change the CORS mode after loading the file without CORS it should still throw a security error
if (e.target.tainted) {
@ -121,7 +145,7 @@ function startTest(test, token) {
v.crossOrigin = test.cors;
}
v.token = token;
manager.started(token);
document.manager.started(token);
v.autoplay = true;
v.preload = "auto";
v.style.display = "none";
@ -140,53 +164,59 @@ function startTest(test, token) {
function doneTest(e) {
var v = e.target;
v.parentNode.removeChild(v);
manager.finished(v.token);
document.manager.finished(v.token);
}
var videoFile = getPlayableVideo(gSmallTests);
if (!videoFile) {
SimpleTest.finish();
}
videoFile = "?name=tests/dom/media/test/" + videoFile.name + "&type=" + videoFile.type;
function beginTest() {
var videoFile = getPlayableVideo(gSmallTests);
if (!videoFile) {
SimpleTest.finish();
}
videoFile = "?name=tests/dom/media/test/" + videoFile.name + "&type=" + videoFile.type;
var gl;
try {
gl = createCanvas(16, 16).getContext("experimental-webgl");
} catch (ex) {
// Mac OS X 10.5 doesn't support WebGL, so we won't run the WebGL tests
document.manager = new MediaTestManager;
var corsTests = [];
const host = "http://example.com/tests/dom/canvas/test/crossorigin/video.sjs";
const serverAttrValues = [
[ "&cors=none", "none" ],
[ "&cors=anonymous", "anonymous" ],
[ "&cors=use-credentials", "use-credentials" ]
];
const clientAttrValues = [
[ "missing-value-default", "none" ],
[ "", "anonymous" ],
[ "just-crossOrigin-without-value", "anonymous" ],
[ "anonymous", "anonymous" ],
[ "use-credentials", "use-credentials" ],
[ "foobar", "anonymous" ]
];
// Build the video file test array
for (var i = 0; i < serverAttrValues.length; i++) {
for (var n = 0; n < clientAttrValues.length; n++) {
corsTests.push({
name: host + videoFile + serverAttrValues[i][0],
nameIntent: serverAttrValues[i][1],
cors: clientAttrValues[n][0],
corsIntent: clientAttrValues[n][1]
});
}
}
try {
document.gl = createCanvas(16, 16).getContext("experimental-webgl");
} catch (ex) {
// Mac OS X 10.5 doesn't support WebGL, so we won't run the WebGL tests
}
document.manager.runTests(corsTests, startTest);
}
var manager = new MediaTestManager;
var corsTests = [];
const host = "http://example.com/tests/dom/canvas/test/crossorigin/video.sjs";
const serverAttrValues = [
[ "&cors=none", "none" ],
[ "&cors=anonymous", "anonymous" ],
[ "&cors=use-credentials", "use-credentials" ]
];
const clientAttrValues = [
[ "missing-value-default", "none" ],
[ "", "anonymous" ],
[ "just-crossOrigin-without-value", "anonymous" ],
[ "anonymous", "anonymous" ],
[ "use-credentials", "use-credentials" ],
[ "foobar", "anonymous" ]
var prefs = [
[ "canvas.capturestream.enabled", true ],
];
// Build the video file test array
for (var i = 0; i < serverAttrValues.length; i++) {
for (var n = 0; n < clientAttrValues.length; n++) {
corsTests.push({
name: host + videoFile + serverAttrValues[i][0],
nameIntent: serverAttrValues[i][1],
cors: clientAttrValues[n][0],
corsIntent: clientAttrValues[n][1]
});
}
}
manager.runTests(corsTests, startTest);
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({ "set" : prefs }, beginTest);
</script>
</pre>

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 B

View File

@ -0,0 +1,2 @@
Access-Control-Allow-Origin: http://mochi.test:8888
Access-Control-Allow-Credentials: true

View File

@ -14,6 +14,8 @@ support-files =
image_green.png
image_red-16x16.png
image_red.png
image_red_crossorigin_credentials.png
image_red_crossorigin_credentials.png^headers^
image_redtransparent.png
image_rgrg-256x256.png
image_rrgg-256x256.png
@ -224,6 +226,8 @@ skip-if = (toolkit == 'gonk' && !debug) || os == 'win' #specialpowers.wrap
[test_hitregion_event.html]
skip-if = os == "android" || appname == "b2g"
[test_canvas_strokeStyle_getter.html]
[test_capture.html]
support-files = captureStream_common.js
[test_drawImageIncomplete.html]
[test_drawImage_document_domain.html]
[test_drawImage_edge_cases.html]

View File

@ -0,0 +1,111 @@
<!DOCTYPE HTML>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Canvas2D test: CaptureStream()</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="captureStream_common.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
<body>
<script>
var c; // Canvas element captured by streams.
var h; // CaptureStreamTestHelper holding utility test functions.
var vauto; // Video element with captureStream stream in automatic mode.
var vmanual; // Video element with captureStream stream in manual (fps 0) mode.
var vrate; // Video element with captureStream stream with fixed frame rate.
function checkDrawColorInitialRed() {
info("Checking that all video elements become red after first drawColor(red).");
h.drawColor(c, h.red);
vauto.mozSrcObject = c.captureStream();
vmanual.mozSrcObject = c.captureStream(0);
vrate.mozSrcObject = c.captureStream(10);
ok(h.testPixel(vauto, [0, 0, 0, 0], 0), "vauto hould not be drawn to before stable state");
ok(h.testPixel(vrate, [0, 0, 0, 0], 0), "vrate Should not be drawn to before stable state");
ok(h.testPixel(vmanual, [0, 0, 0, 0], 0), "vmanual Should not be drawn to before stable state");
return Promise.resolve()
.then(() => h.waitForPixel(vauto, h.red, 0, "should become red automatically"))
.then(() => h.waitForPixel(vrate, h.red, 0, "should become red automatically"))
.then(() => h.waitForPixel(vmanual, h.red, 0, "should become red when we get" +
" to stable state (first frame)"));
}
function checkDrawColorGreen() {
info("Checking that drawColor(green) propagates properly to video elements.");
h.drawColor(c, h.green);
return Promise.resolve()
.then(() => h.waitForPixel(vauto, h.green, 0, "should become green automatically"))
.then(() => h.waitForPixel(vrate, h.green, 0, "should become green automatically"))
.then(() => h.waitForPixel(vmanual, h.red, 0, "should still be red"))
.then(() => h.requestFrame(vmanual))
.then(() => h.waitForPixel(vmanual, h.green, 0, "should become green after requstFrame()"));
}
function checkRequestFrameOrderGuarantee() {
info("Checking that requestFrame() immediately before and after drawColor() " +
"calls results in the expected frame seen in the stream.");
return Promise.resolve()
.then(() => h.waitForPixel(vmanual, h.green, 0, "should still be green"))
.then(() => h.drawColor(c, h.red)) // 1. Draw canvas red
.then(() => h.requestFrame(vmanual)) // 2. Immediately request a frame
.then(() => h.drawColor(c, h.green)) // 3. Immediately draw canvas green
.then(() => h.waitForPixel(vmanual, h.red, 0, "should become red after call order test"))
.then(() => h.waitForPixelToTimeout(vmanual, h.green, 0, 500, "should not become green after call order test"));
}
function checkDrawImageNotCleanRed() {
info("Checking that drawImage with not origin-clean image renders streams useless.");
var ctx = c.getContext('2d');
var notCleanRed = new Image();
return new Promise((resolve, reject) => {
notCleanRed.onload = resolve;
notCleanRed.onerror = () => reject(new Error("Failed to load tainted image."));
notCleanRed.src = "http://example.com/tests/dom/canvas/test/image_red_crossorigin_credentials.png";
document.body.appendChild(notCleanRed);
})
.then(() => ctx.drawImage(notCleanRed, 0, 0, c.width, c.height))
.then(() => h.testNotClean(c))
.then(() => h.waitForPixelToTimeout(vauto, h.red, 0, 1000, "should not become red"))
.then(() => h.waitForPixelToTimeout(vrate, h.red, 0, 0, "should not become red"))
.then(() => h.waitForPixel(vmanual, h.green, 0, "should still be green"))
.then(() => h.requestFrame(vmanual))
.then(() => h.waitForPixelToTimeout(vmanual, h.red, 0, 1000, "should not become red"));
}
function finish() {
ok(true, 'Test complete.');
SimpleTest.finish();
}
function beginTest() {
h = new CaptureStreamTestHelper2D();
c = h.createAndAppendElement('canvas', 'c');
vauto = h.createAndAppendElement('video', 'vauto');
vmanual = h.createAndAppendElement('video', 'vmanual');
vrate = h.createAndAppendElement('video', 'vrate');
Promise.resolve()
.then(checkDrawColorInitialRed)
.then(checkDrawColorGreen)
.then(checkRequestFrameOrderGuarantee)
.then(checkDrawColorGreen) // Restore video elements to green.
.then(checkDrawImageNotCleanRed)
.then(finish);
}
SimpleTest.waitForExplicitFinish();
var prefs = [
[ "canvas.capturestream.enabled", true ],
];
SpecialPowers.pushPrefEnv({ "set" : prefs }, beginTest);
</script>

View File

@ -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]

View File

@ -0,0 +1,200 @@
<!DOCTYPE HTML>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>WebGL test: CaptureStream()</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
<script src="../captureStream_common.js">
<script src="driver-info.js"></script>
<script src="webgl-util.js"></script>
<script id="vs" type="x-shader/x-vertex">
attribute vec2 aVertCoord;
void main(void) {
gl_Position = vec4(aVertCoord, 0.0, 1.0);
}
</script>
<script id="fs" type="x-shader/x-fragment">
precision mediump float;
uniform vec4 uColor;
void main(void) {
gl_FragColor = uColor;
}
</script>
<body>
<script>
// Globals. Initialized during beginTest().
var c; // Canvas element captured by streams.
var gl; // WebGLContext of |c|.
var h; // CaptureStreamTestHelper holding utility test functions.
var vauto; // Video element with captureStream stream in automatic mode.
var vmanual; // Video element with captureStream stream in manual (fps 0) mode.
var vrate; // Video element with captureStream stream with fixed frame rate.
/* Fails the test if there was a GL error */
function checkGLError(info) {
var error = gl.getError();
// Comparing strings for sake of log output in hex format.
is("0x" + error.toString(16), "0x0", "WebGL error [" + info + "]");
}
function checkClearColorInitialRed() {
info("Checking that clearing to red works for first frame.");
h.clearColor(c, h.red);
vauto.mozSrcObject = c.captureStream();
vmanual.mozSrcObject = c.captureStream(0);
vrate.mozSrcObject = c.captureStream(10);
ok(h.testPixel(vauto, [0, 0, 0, 0], 0), "Should not be drawn to before stable state");
ok(h.testPixel(vrate, [0, 0, 0, 0], 0), "Should not be drawn to before stable state");
ok(h.testPixel(vmanual, [0, 0, 0, 0], 0), "Should not be drawn to before stable state");
return Promise.resolve()
.then(() => h.waitForPixel(vauto, h.red, 0, "should become red automatically"))
.then(() => h.waitForPixel(vrate, h.red, 0, "should become red automatically"))
.then(() => h.waitForPixel(vmanual, h.red, 0, "should become red when we get to stable state (first frame)"))
}
function checkDrawColorGreen() {
info("Checking that drawColor() results in green frames.");
h.drawColor(c, h.green);
checkGLError('after DrawColor');
return Promise.resolve()
.then(() => h.waitForPixel(vauto, h.green, 0, "should become green automatically"))
.then(() => h.waitForPixel(vrate, h.green, 0, "should become green automatically"))
.then(() => h.waitForPixel(vmanual, h.red, 0, "should still be red"))
.then(() => h.requestFrame(vmanual))
.then(() => h.waitForPixel(vmanual, h.green, 0, "should become green after requstFrame()"))
}
function checkClearColorRed() {
info("Checking that clearing to red works.");
h.clearColor(c, h.red);
return Promise.resolve()
.then(() => h.waitForPixel(vauto, h.red, 0, "should become red automatically"))
.then(() => h.waitForPixel(vrate, h.red, 0, "should become red automatically"))
.then(() => h.waitForPixel(vmanual, h.green, 0, "should still be green"))
.then(() => h.requestFrame(vmanual))
.then(() => h.waitForPixel(vmanual, h.red, 0, "should become red after requestFrame()"))
}
function checkRequestFrameOrderGuarantee() {
info("Checking that requestFrame() immediately before and after draw " +
"calls results in the expected frame seen in the stream.");
return Promise.resolve()
.then(() => h.waitForPixel(vmanual, h.red, 0, "should still be red"))
.then(() => h.drawColor(c, h.green)) // 1. Draw canvas green
.then(() => h.requestFrame(vmanual)) // 2. Immediately request a frame
.then(() => h.clearColor(c, h.red)) // 3. Immediately clear to red
.then(() => h.waitForPixel(vmanual, h.green, 0, "should become green after call order test"))
.then(() => h.waitForPixelToTimeout(vmanual, h.red, 0, 500, "should not become red after call order test"));
}
function checkCapturingForbidden() {
info("Checking that capturing a WebGL context with " +
"`preservDrawingBuffer: false` is forbidden.");
var c2 = h.createAndAppendElement("canvas", "c2");
var gl2 = WebGLUtil.getWebGL("c2", false, { preserveDrawingBuffer: false });
var checkThrows = function(f, expected, fName) {
try {
f();
ok(false, fName + " should throw when not preserving drawing buffer");
} catch(e) {
is(e.name, expected, fName + " forbidden when not preserving drawing buffer");
}
};
checkThrows(() => c2.captureStream(), "NS_ERROR_FAILURE", "captureStream()");
checkThrows(() => c2.captureStream(0), "NS_ERROR_FAILURE", "captureStream(0)");
checkThrows(() => c2.captureStream(10), "NS_ERROR_FAILURE", "captureStream(10)");
}
function finish() {
ok(true, 'Test complete.');
SimpleTest.finish();
}
function beginTest() {
h = new CaptureStreamTestHelperWebGL();
c = h.createAndAppendElement('canvas', 'c');
vauto = h.createAndAppendElement('video', 'vauto');
vmanual = h.createAndAppendElement('video', 'vmanual');
vrate = h.createAndAppendElement('video', 'vrate');
gl = WebGLUtil.getWebGL('c', false, { preserveDrawingBuffer: true });
if (!gl) {
todo(false, 'WebGL is unavailable.');
finish();
return;
}
function errorFunc(str) {
ok(false, 'Error: ' + str);
}
WebGLUtil.setErrorFunc(errorFunc);
WebGLUtil.setWarningFunc(errorFunc);
gl.disable(gl.DEPTH_TEST);
prog = WebGLUtil.createProgramByIds(gl, 'vs', 'fs');
if (!prog) {
ok(false, 'Program linking should succeed.');
return;
}
// Setup vertex coordinates for drawing a rectangle across the whole canvas.
prog.aVertCoord = gl.getAttribLocation(prog, "aVertCoord");
ok(prog.aVertCoord >= 0, '`aVertCoord` should be valid.');
var vertCoordArr = new Float32Array([
-1, -1,
1, -1,
-1, 1,
1, 1,
]);
var vertCoordBuff = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertCoordBuff);
gl.bufferData(gl.ARRAY_BUFFER, vertCoordArr, gl.STATIC_DRAW);
gl.useProgram(prog);
gl.enableVertexAttribArray(prog.aVertCoord);
gl.vertexAttribPointer(prog.aVertCoord, 2, gl.FLOAT, false, 0, 0);
// Setup the helper with a pointer to how to change fragment color.
var uColorLocation = gl.getUniformLocation(prog, "uColor");
h.setFragmentColorLocation(uColorLocation);
checkGLError('after setup');
// Run tests.
Promise.resolve()
.then(checkClearColorInitialRed)
.then(checkDrawColorGreen)
.then(checkClearColorRed)
.then(checkRequestFrameOrderGuarantee)
.then(checkCapturingForbidden)
.then(finish);
}
SimpleTest.waitForExplicitFinish();
var prefs = [
[ "canvas.capturestream.enabled", true ],
];
SpecialPowers.pushPrefEnv({ "set" : prefs }, beginTest);
</script>

View File

@ -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) {}
}

View File

@ -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],

View File

@ -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]

View File

@ -0,0 +1,55 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
<script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript;version=1.8">
createHTML({
bug: "1032848",
title: "Canvas(2D)::CaptureStream as video-only input to peerconnection",
visible: true
});
runNetworkTest(() => {
var test = new PeerConnectionTest();
var vremote;
var h = new CaptureStreamTestHelper2D();
var canvas = document.createElement('canvas');
canvas.id = 'source_canvas';
canvas.width = canvas.height = 10;
document.getElementById('content').appendChild(canvas);
test.setMediaConstraints([{video: true}], []);
test.chain.replace("PC_LOCAL_GUM", [
function DRAW_LOCAL_GREEN(test) {
h.drawColor(canvas, h.green);
},
function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
var stream = canvas.captureStream(10);
test.pcLocal.attachMedia(stream, 'video', 'local');
}
]);
test.chain.append([
function FIND_REMOTE_VIDEO() {
vremote = document.getElementById('pcRemote_remote1_video');
ok(!!vremote, "Should have remote video element for pcRemote");
},
function WAIT_FOR_REMOTE_GREEN() {
return h.waitForPixel(vremote, h.green, 128, "pcRemote's remote should become green");
},
function DRAW_LOCAL_RED() {
h.drawColor(canvas, h.red);
},
function WAIT_FOR_REMOTE_RED() {
return h.waitForPixel(vremote, h.red, 128, "pcRemote's remote should become red");
}
]);
test.run();
});
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,108 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
<script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
<script type="application/javascript" src="/tests/dom/canvas/test/webgl-mochitest/webgl-util.js"></script>
</head>
<body>
<pre id="test">
<script id="v-shader" type="x-shader/x-vertex">
attribute vec2 aPosition;
void main() {
gl_Position = vec4(aPosition, 0, 1);
}
</script>
<script id="f-shader" type="x-shader/x-fragment">
precision mediump float;
uniform vec4 uColor;
void main() { gl_FragColor = uColor; }
</script>
<script type="application/javascript;version=1.8">
createHTML({
bug: "1032848",
title: "Canvas(WebGL)::CaptureStream as video-only input to peerconnection"
});
runNetworkTest(() => {
var test = new PeerConnectionTest();
var vremote;
var h = new CaptureStreamTestHelperWebGL();
var canvas = document.createElement('canvas');
canvas.id = 'source_canvas';
canvas.width = canvas.height = 10;
document.getElementById('content').appendChild(canvas);
var gl = WebGLUtil.getWebGL(canvas.id, false, { preserveDrawingBuffer: true });
if (!gl) {
todo(false, "WebGL unavailable.");
networkTestFinished();
return;
}
var errorFunc = str => ok(false, 'Error: ' + str);
WebGLUtil.setErrorFunc(errorFunc);
WebGLUtil.setWarningFunc(errorFunc);
test.setMediaConstraints([{video: true}], []);
test.chain.replace("PC_LOCAL_GUM", [
function WEBGL_SETUP(test) {
var program = WebGLUtil.createProgramByIds(gl, 'v-shader', 'f-shader');
if (!program) {
ok(false, "Program should link");
return Promise.reject();
}
gl.useProgram(program);
var uColorLocation = gl.getUniformLocation(program, "uColor");
h.setFragmentColorLocation(uColorLocation);
var squareBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareBuffer);
var vertices = [ 0, 0,
-1, 0,
0, 1,
-1, 1 ];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
squareBuffer.itemSize = 2;
squareBuffer.numItems = 4;
program.aPosition = gl.getAttribLocation(program, "aPosition");
gl.enableVertexAttribArray(program.aPosition);
gl.vertexAttribPointer(program.aPosition, squareBuffer.itemSize, gl.FLOAT, false, 0, 0);
},
function DRAW_LOCAL_GREEN(test) {
h.drawColor(canvas, h.green);
},
function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
test.pcLocal.canvasStream = canvas.captureStream(0.0);
is(test.pcLocal.canvasStream.canvas, canvas, "Canvas attribute is correct");
test.pcLocal.attachMedia(test.pcLocal.canvasStream, 'video', 'local');
}
]);
test.chain.append([
function FIND_REMOTE_VIDEO() {
vremote = document.getElementById('pcRemote_remote1_video');
ok(!!vremote, "Should have remote video element for pcRemote");
},
function WAIT_FOR_REMOTE_GREEN() {
return h.waitForPixel(vremote, h.green, 128, "pcRemote's remote should become green");
},
function DRAW_LOCAL_RED() {
h.drawColor(canvas, h.red);
},
function REQUEST_FRAME(test) {
test.pcLocal.canvasStream.requestFrame();
},
function WAIT_FOR_REMOTE_RED() {
return h.waitForPixel(vremote, h.red, 128, "pcRemote's remote should become red");
}
]);
test.run();
});
</script>
</pre>
</body>
</html>