diff --git a/dom/canvas/test/captureStream_common.js b/dom/canvas/test/captureStream_common.js new file mode 100644 index 000000000000..a19f4725734d --- /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 6571542be9fd..a12bd63960a4 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 78339ed07574..b5e1582fdfa0 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 000000000000..613cc852525c --- /dev/null +++ b/dom/canvas/test/webgl-mochitest/test_capture.html @@ -0,0 +1,200 @@ + + + ++ ++ + 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 000000000000..b3a753bc4e31 --- /dev/null +++ b/dom/media/tests/mochitest/test_peerConnection_captureStream_canvas_webgl.html @@ -0,0 +1,108 @@ + + + + + + + + +
+ + + ++ +