Bug 1161913 - Part 3 - Relax requestFrame ordering guarantee in tests. r=mt
authorAndreas Pehrson <pehrsons@gmail.com>
Thu, 17 Sep 2015 11:37:05 +0800
changeset 297515 c36e4b73500652bae7f71ffe9382b2ee000068c2
parent 297514 444d8a31e89310e69a778d4df369d7e3cff098df
child 297516 af0ad20ecd0b29c4af5cac0fceef5d466858e716
push id962
push userjlund@mozilla.com
push dateFri, 04 Dec 2015 23:28:54 +0000
treeherdermozilla-release@23a2d286e80f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmt
bugs1161913
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1161913 - Part 3 - Relax requestFrame ordering guarantee in tests. r=mt We used to fully guarantee the order of requestFrame() and draw calls. For instance: ``` ctx.draw(red); stream.requestFrame(); ctx.draw(green); ``` would guarantee that a red frame ended up in the stream, and not the green unless another frame was requested. Now with frames being requested and pushed out on next refresh, we can only guarantee that everything up to the requestFrame() call is included in the next frame. Everything after the requestFrame() and before the next refresh (stable state in most cases) will now also be inevitably included.
dom/canvas/test/captureStream_common.js
dom/canvas/test/test_capture.html
dom/canvas/test/webgl-mochitest/test_capture.html
dom/media/tests/mochitest/test_peerConnection_captureStream_canvas_2d.html
dom/media/tests/mochitest/test_peerConnection_captureStream_canvas_webgl.html
--- a/dom/canvas/test/captureStream_common.js
+++ b/dom/canvas/test/captureStream_common.js
@@ -27,16 +27,30 @@ CaptureStreamTestHelper.prototype = {
   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,
 
+  /*
+   * Perform the drawing operation on each animation frame until stop is called
+   * on the returned object.
+   */
+  startDrawing: function (f) {
+    var stop = false;
+    var draw = () => {
+      f();
+      if (!stop) { window.requestAnimationFrame(draw); }
+    };
+    draw();
+    return { stop: () => stop = true };
+  },
+
   /* Request a frame from the stream played by |video|. */
   requestFrame: function (video) {
     info("Requesting frame from " + video.id);
     video.srcObject.requestFrame();
   },
 
   /* Tests the top left pixel of |video| against |refData|. Format [R,G,B,A]. */
   testPixel: function (video, refData, threshold) {
@@ -116,18 +130,18 @@ CaptureStreamTestHelper2D.prototype.clea
   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
+  rgba[3] = rgba[3] / 255.0; // Convert opacity to double in range [0,1]
   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) {
--- a/dom/canvas/test/test_capture.html
+++ b/dom/canvas/test/test_capture.html
@@ -10,17 +10,17 @@
 <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).");
+  info("Checking that all video elements become red when initiated just after the first drawColor(red).");
 
   h.drawColor(c, h.red);
 
   vauto.srcObject = c.captureStream();
   vmanual.srcObject = c.captureStream(0);
   vrate.srcObject = c.captureStream(10);
 
   ok(h.testPixel(vauto, [0, 0, 0, 0], 0), "vauto hould not be drawn to before stable state");
@@ -30,58 +30,62 @@ function checkDrawColorInitialRed() {
   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);
+  info("Checking that drawing green propagates properly to video elements.");
+
+  var drawing = h.startDrawing(() => 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()"));
+    .then(() => h.waitForPixel(vmanual, h.green, 0, "should become green after requstFrame()"))
+    .catch(err => ok(false, "checkDrawColorGreen failed: ", err))
+    .then(() => drawing.stop());
 }
 
 function checkRequestFrameOrderGuarantee() {
-  info("Checking that requestFrame() immediately before and after drawColor() " +
-       "calls results in the expected frame seen in the stream.");
+  info("Checking that requestFrame() immediately after a drawColor() " +
+       "call 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();
+  var drawing;
 
   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(() => drawing = h.startDrawing(() => 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"));
+    .then(() => h.waitForPixelToTimeout(vmanual, h.red, 0, 1000, "should not become red"))
+    .catch(err => ok(false, "checkDrawImageNotCleanRed failed: ", err))
+    .then(() => drawing.stop());
 }
 
 function finish() {
   ok(true, 'Test complete.');
   SimpleTest.finish();
 }
 
 function beginTest() {
--- a/dom/canvas/test/webgl-mochitest/test_capture.html
+++ b/dom/canvas/test/webgl-mochitest/test_capture.html
@@ -60,84 +60,64 @@ function checkClearColorInitialRed() {
 
   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);
+  info("Checking that drawing green results in green video frames.");
+  var drawing = h.startDrawing(h.drawColor.bind(h, 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()"))
+    .then(() => drawing.stop());
 }
 
 function checkClearColorRed() {
   info("Checking that clearing to red works.");
-  h.clearColor(c, h.red);
+  var drawing = h.startDrawing(h.clearColor.bind(h, 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()"))
+    .then(() => drawing.stop());
 }
 
 function checkRequestFrameOrderGuarantee() {
-  info("Checking that requestFrame() immediately before and after draw " +
-       "calls results in the expected frame seen in the stream.");
+  info("Checking that requestFrame() immediately after a draw " +
+       "call 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 });
+  gl = WebGLUtil.getWebGL('c', false);
   if (!gl) {
     todo(false, 'WebGL is unavailable.');
     finish();
     return;
   }
 
   function errorFunc(str) {
     ok(false, 'Error: ' + str);
@@ -181,17 +161,16 @@ function beginTest() {
 
   // Run tests.
 
   Promise.resolve()
     .then(checkClearColorInitialRed)
     .then(checkDrawColorGreen)
     .then(checkClearColorRed)
     .then(checkRequestFrameOrderGuarantee)
-    .then(checkCapturingForbidden)
     .then(finish);
 }
 
 SimpleTest.waitForExplicitFinish();
 
 var prefs = [
   [ "canvas.capturestream.enabled", true ],
 ];
--- a/dom/media/tests/mochitest/test_peerConnection_captureStream_canvas_2d.html
+++ b/dom/media/tests/mochitest/test_peerConnection_captureStream_canvas_2d.html
@@ -13,39 +13,44 @@ createHTML({
   visible: true
 });
 
 runNetworkTest(() => {
   var test = new PeerConnectionTest();
   var vremote;
   var h = new CaptureStreamTestHelper2D();
   var canvas = document.createElement('canvas');
+  var stream;
   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) {
+    function DRAW_INITIAL_LOCAL_GREEN(test) {
       h.drawColor(canvas, h.green);
     },
     function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
-      var stream = canvas.captureStream(10);
+      stream = canvas.captureStream(0);
       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() {
+      // After requesting a frame it will be captured at the time of next render.
+      // Next render will happen at next stable state, at the earliest,
+      // i.e., this order of `requestFrame(); draw();` should work.
+      stream.requestFrame();
       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();
 });
--- a/dom/media/tests/mochitest/test_peerConnection_captureStream_canvas_webgl.html
+++ b/dom/media/tests/mochitest/test_peerConnection_captureStream_canvas_webgl.html
@@ -26,19 +26,20 @@ createHTML({
 
 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;
+  canvas.style.display = 'none';
   document.getElementById('content').appendChild(canvas);
 
-  var gl = WebGLUtil.getWebGL(canvas.id, false, { preserveDrawingBuffer: true });
+  var gl = WebGLUtil.getWebGL(canvas.id, false);
   if (!gl) {
     todo(false, "WebGL unavailable.");
     networkTestFinished();
     return;
   }
 
   var errorFunc = str => ok(false, 'Error: ' + str);
   WebGLUtil.setErrorFunc(errorFunc);
@@ -85,22 +86,25 @@ runNetworkTest(() => {
   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 REQUEST_FRAME(test) {
+      // After requesting a frame it will be captured at the time of next render.
+      // Next render will happen at next stable state, at the earliest,
+      // i.e., this order of `requestFrame(); draw();` should work.
+      test.pcLocal.canvasStream.requestFrame();
+    },
     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>