--- a/dom/media/test/test_mediarecorder_principals.html
+++ b/dom/media/test/test_mediarecorder_principals.html
@@ -10,109 +10,128 @@ https://bugzilla.mozilla.org/show_bug.cg
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="text/javascript" src="manifest.js"></script>
</head>
<body>
<div>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1018299">Test for MediaRecorder Principal Handling</a>
</div>
-<video id="v1" preload="metadata"></video>
-<video id="v2" preload="metadata"></video>
-
<pre id="test">
<script type="text/javascript">
SimpleTest.waitForExplicitFinish();
let throwOutside = e => setTimeout(() => { throw e; });
-// Generate a random key. The first load with that key will return
-// data, the second and subsequent loads with that key will return a redirect
-// to a different origin ('localhost:8888' will be redirected to 'example.org',
-// and 'example.org' will be redirected to 'localhost:8888').
-// Loading data from two different origins should be detected by the media
-// cache and result in a null principal so that the MediaRecorder usages below
-// fail.
-// This test relies on that preloading the metadata then forcing another load
-// via video.load() will result in two requests taking place to retreive the
-// resource.
-let key = Math.floor(Math.random()*100000000);
-let interval;
+// Loading data from a resource that changes origins while streaming should
+// be detected by the media cache and result in a null principal so that the
+// MediaRecorder usages below fail.
-function testPrincipals(resource) {
+// This test relies on midflight-redirect.sjs returning the the first quarter of
+// the resource as a byte range response, and then hanging up, and when Firefox
+// requests the remainder midflight-redirect.sjs serves a redirect to another origin.
+
+// Note: the URL contains a random element to ensure the resources aren't served
+// from Necko's cache, which would avoiding hitting the SJS and so avoiding the redirect.
+
+async function testPrincipals(resource) {
if (!resource) {
todo(false, "No types supported");
return;
}
- // First test: Load file from same-origin first, then get redirected to
- // another origin before attempting to record stream.
- let video = document.getElementById("v1");
+ await testPrincipals1(resource);
+ await testPrincipals2(resource);
+}
+
+function makeVideo() {
+ let video = document.createElement("video");
+ video.preload = "metadata";
+ video.controls = true;
+ document.body.appendChild(video);
+ return video;
+}
+
+// First test: Load file from same-origin first, then get redirected to
+// another origin before attempting to record stream.
+async function testPrincipals1(resource) {
+ let video = makeVideo();
+ let key = Math.floor(Math.random()*100000000);
video.src =
"http://mochi.test:8888/tests/dom/media/test/midflight-redirect.sjs?key=v1_" +
key + "&resource=" + resource.name + "&type=" + resource.type;
- return new Promise(resolve => video.onloadedmetadata = resolve).then(() => {
- video.load();
- video.play();
- interval = setInterval(() => info("video.currentTime = "+ video.currentTime), 1000);
+
+ let errorBarrier = once(video, "error");
+ // Wait for the video to load to metadata. We can then start capturing.
+ // Must also handle the download bursting and hitting the error before we
+ // reach loadedmetadata. Normally we reach loadedmetadata first, but
+ // rarely we hit the redirect first.
+ await Promise.race([once(video, "loadedmetadata"), errorBarrier]);
+
+ let rec = new MediaRecorder(video.mozCaptureStreamUntilEnded());
+ video.play();
+
+ // Wait until we hit a playback error. This means our download has hit the redirect.
+ await errorBarrier;
- let msg = "mediaRecorder.start() must throw SecurityError";
- return new Promise(resolve => video.onplaying = resolve)
- .then(() => waitUntil(() => video.currentTime > resource.duration / 5))
- // Test failure of the next step only, so "catch-bypass" any errors above.
- .then(() => Promise.resolve()
- .then(() => new MediaRecorder(video.mozCaptureStreamUntilEnded()).start())
- .then(() => ok(false, msg), e => is(e.name, "SecurityError", msg)), 0)
- .then(() => clearInterval(interval));
- })
- .then(() => {
- // Second test: Load file from same-origin first, but record ASAP, before
- // getting redirected to another origin.
- let video = document.getElementById("v2");
- video.src =
- "http://mochi.test:8888/tests/dom/media/test/midflight-redirect.sjs?key=v2_" +
- key + "&resource=" + resource.name + "&type=" + resource.type;
- let rec, hasStopped, hasEnded = new Promise(r => video.onended = r);
- let data = [];
+ // Try to record, it should be blocked with a security error.
+ try {
+ rec.start();
+ ok(false, "mediaRecorder.start() must throw SecurityError, but didn't throw at all");
+ } catch (ex) {
+ is(ex.name, "SecurityError", "mediaRecorder.start() must throw SecurityError");
+ }
+ removeNodeAndSource(video);
+}
+
+// Second test: Load file from same-origin first, but record ASAP, before
+// getting redirected to another origin.
+async function testPrincipals2(resource) {
+ let video = makeVideo();
+ let key = Math.floor(Math.random()*100000000);
+ video.src =
+ "http://mochi.test:8888/tests/dom/media/test/midflight-redirect.sjs?key=v2_" +
+ key + "&resource=" + resource.name + "&type=" + resource.type;
- let msgNoThrow = "mediaRecorder.start() should not throw here";
- let msgSecErr = "mediaRecorder.onerror must fire SecurityError";
- let msgOnStop = "mediaRecorder.onstop must also have fired";
- return new Promise(resolve => video.onloadedmetadata = resolve).then(() => {
- rec = new MediaRecorder(video.mozCaptureStreamUntilEnded());
- rec.ondataavailable = e => data.push(e.data);
- rec.start();
- video.load();
- hasStopped = new Promise(resolve => rec.onstop = resolve);
- video.play();
- })
- .then(() => ok(true, msgNoThrow), e => is(e.error.name, null, msgNoThrow))
- .then(() => Promise.race([
- new Promise((_, reject) => rec.onerror = e => reject(e.error)),
- hasEnded
- ]))
- .then(() => ok(false, msgSecErr), e => {
- is(e.name, "SecurityError", msgSecErr);
- ok(e.stack.includes('test_mediarecorder_principals.html'),
- 'Events fired from onerror should include an error with a stack trace indicating ' +
- 'an error in this test');
- })
- .then(() => Promise.race([hasStopped, hasEnded.then(() => Promise.reject())]))
- .then(() => ok(true, msgOnStop), e => ok(false, msgOnStop))
- .then(() => clearInterval(interval));
- });
+ // Wait for the video to load to metadata. We can then start capturing.
+ // Must also handle the download bursting and hitting the error before we
+ // reach loadedmetadata. Normally we reach loadedmetadata first, but
+ // rarely we hit the redirect first.
+ await Promise.race([once(video, "loadedmetadata"), once(video, "error")]);
+
+ let ended = false;
+ once(video, "ended", () => ended = true);
+
+ // Start capturing. It should work.
+ let rec;
+ let errorBarrier;
+ try {
+ rec = new MediaRecorder(video.mozCaptureStreamUntilEnded());
+ errorBarrier = once(rec, "error");
+ rec.start();
+ ok(true, "mediaRecorder.start() should not throw here, and didn't");
+ } catch (ex) {
+ ok(false, "mediaRecorder.start() unexpectedly threw " + ex.name + " (" + ex.message + ")");
+ }
+
+ // Play the video, this should result in a SecurityError on the recorder.
+ let hasStopped = once(rec, "stop");
+ video.play();
+ let error = await errorBarrier;
+ is(error.name, "SecurityError", "mediaRecorder.onerror must fire SecurityError");
+ ok(error.stack.includes('test_mediarecorder_principals.html'),
+ 'Events fired from onerror should include an error with a stack trace indicating ' +
+ 'an error in this test');
+ is(ended, false, "Playback should not have reached end");
+ await hasStopped;
+ is(ended, false, "Playback should definitely not have reached end");
+
+ removeNodeAndSource(video);
}
testPrincipals({ name:"pixel_aspect_ratio.mp4", type:"video/mp4", duration:28 })
-.catch(e => throwOutside(e))
-.then(() => SimpleTest.finish())
-.catch(e => throwOutside(e));
-
-let stop = stream => stream.getTracks().forEach(track => track.stop());
-let wait = ms => new Promise(resolve => setTimeout(resolve, ms));
-let waitUntil = f => new Promise(resolve => {
- let ival = setInterval(() => f() && resolve(clearInterval(ival)), 100);
-});
+ .catch(e => throwOutside(e))
+ .then(() => SimpleTest.finish());
</script>
</pre>
</body>
</html>