Bug 1018299 - Test for changing principals in recorder. r=mt
authorJan-Ivar Bruaroey <jib@mozilla.com>
Tue, 05 Apr 2016 14:36:48 -0400
changeset 331902 fa4d407df25318173b500dea37441e1615296f58
parent 331901 3e00ab630419ec6696e92d6aef4dca1b788e0be8
child 331903 d0b697ec5a8ece93c61a01f128cda401bcb6947b
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmt
bugs1018299
milestone48.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 1018299 - Test for changing principals in recorder. r=mt MozReview-Commit-ID: 5z8AFKMSwgo
dom/media/test/dynamic_redirect.sjs
dom/media/test/mochitest.ini
dom/media/test/test_mediarecorder_principals.html
--- a/dom/media/test/dynamic_redirect.sjs
+++ b/dom/media/test/dynamic_redirect.sjs
@@ -1,54 +1,67 @@
-function parseQuery(request, key) {
-  var params = request.queryString.split('&');
-  for (var j = 0; j < params.length; ++j) {
-    var p = params[j];
-	if (p == key)
-	  return true;
-    if (p.indexOf(key + "=") == 0)
-	  return p.substring(key.length + 1);
-	if (p.indexOf("=") < 0 && key == "")
-	  return p;
+function parseQuery(query, key) {
+  for (let p of query.split('&')) {
+    if (p == key) {
+      return true;
+    }
+    if (p.startsWith(key + "=")) {
+      return p.substring(key.length + 1);
+    }
   }
-  return false;
 }
 
 // Return seek.ogv file content for the first request with a given key.
 // All subsequent requests return a redirect to a different-origin resource.
 function handleRequest(request, response)
 {
-  var key = parseQuery(request, "key");
-  var resource = parseQuery(request, "res");
+  var query = request.queryString;
+  var key = parseQuery(query, "key");
+  var resource = parseQuery(query, "res");
+  var nested = parseQuery(query, "nested") || false;
 
-  if (getState(key) == "redirect") {
-    var origin = request.host == "mochi.test" ? "example.org" : "mochi.test:8888";
-    response.setStatusLine(request.httpVersion, 303, "See Other");
-    response.setHeader("Location", "http://" + origin + "/tests/dom/media/test/" + resource);
-    response.setHeader("Content-Type", "text/html");
-    return;
+  dump("Received request for key = "+ key +"\n");
+  if (!nested) {
+    if (getState(key) == "redirect") {
+      var origin = request.host == "mochi.test" ? "example.org" : "mochi.test:8888";
+      response.setStatusLine(request.httpVersion, 303, "See Other");
+      let url = "http://" + origin +
+                "/tests/dom/media/test/dynamic_redirect.sjs?nested&" + query;
+      dump("Redirecting to "+ url + "\n");
+      response.setHeader("Location", url);
+      response.setHeader("Content-Type", "text/html");
+      return;
+    }
+    setState(key, "redirect");
   }
-
-  setState(key, "redirect");
-
   var file = Components.classes["@mozilla.org/file/directory_service;1"].
                         getService(Components.interfaces.nsIProperties).
                         get("CurWorkD", Components.interfaces.nsILocalFile);
   var fis  = Components.classes['@mozilla.org/network/file-input-stream;1'].
                         createInstance(Components.interfaces.nsIFileInputStream);
   var bis  = Components.classes["@mozilla.org/binaryinputstream;1"].
                         createInstance(Components.interfaces.nsIBinaryInputStream);
   var paths = "tests/dom/media/test/" + resource;
   var split = paths.split("/");
-  for(var i = 0; i < split.length; ++i) {
+  for (var i = 0; i < split.length; ++i) {
     file.append(split[i]);
   }
   fis.init(file, -1, -1, false);
-  dump("file=" + file + "\n");
+
   bis.setInputStream(fis);
   var bytes = bis.readBytes(bis.available());
+  let [from, to] = request.getHeader("range").split("=")[1].split("-").map(s => parseInt(s));
+  to = to || Math.max(from, bytes.length - 1);
+  byterange = bytes.substring(from, to + 1);
+
+  let contentRange = "bytes "+ from +"-"+ to +"/"+ bytes.length;
+  let contentLength = (to - from + 1).toString();
+  dump("Response Content-Range = "+ contentRange +"\n");
+  dump("Response Content-Length = "+ contentLength +"\n");
+
   response.setStatusLine(request.httpVersion, 206, "Partial Content");
-  response.setHeader("Content-Range", "bytes 0-" + (bytes.length - 1) + "/" + bytes.length);
-  response.setHeader("Content-Length", ""+bytes.length, false);
+  response.setHeader("Content-Range", contentRange);
+  response.setHeader("Content-Length", contentLength, false);
   response.setHeader("Content-Type", "video/ogg", false);
-  response.write(bytes, bytes.length);
+  response.setHeader("Accept-Ranges", "bytes", false);
+  response.write(byterange, byterange.length);
   bis.close();
 }
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -688,16 +688,18 @@ tags=msg
 skip-if = (toolkit == 'gonk' || toolkit == 'android') # B2G emulator is too slow to run this without timing out and Fennec does not support video recording
 tags=msg
 [test_mediarecorder_creation.html]
 tags=msg capturestream
 [test_mediarecorder_creation_fail.html]
 tags=msg
 [test_mediarecorder_getencodeddata.html]
 tags=msg
+[test_mediarecorder_principals.html]
+tags=msg
 [test_mediarecorder_record_4ch_audiocontext.html]
 tags=msg
 [test_mediarecorder_record_audiocontext.html]
 tags=msg
 [test_mediarecorder_record_audiocontext_mlk.html]
 tags=msg
 [test_mediarecorder_record_audionode.html]
 tags=msg
new file mode 100644
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_principals.html
@@ -0,0 +1,119 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=489415
+-->
+<head>
+  <title>Test for MediaRecorder Reaction to Principal Change</title>
+  <script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <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="auto"></video>
+<video id="v2" preload="auto"></video>
+
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var pushPrefs = (...p) => new Promise(r => SpecialPowers.pushPrefEnv({set: p}, r));
+var 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'). We rely on the
+// fact that Ogg will do a seek to the end of the resource, triggering a new
+// load with the same key which will return a same-origin resource.
+// 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.
+let key = Math.floor(Math.random()*100000000);
+let interval;
+
+function testPrincipals(resource) {
+  if (!resource) {
+    todo(false, "No types supported");
+    return;
+  }
+  // Reduce cache size and cache-ahead to make HTMLMediaElement do partial requests.
+  return pushPrefs(['media.cache_readahead_limit', 2],
+                   ['media.cache_size', 192])
+  .then(() => {
+    // First test: Load file from same-origin first, then get redirected to
+    // another origin before attempting to record stream.
+    let video = document.getElementById("v1");
+    video.src =
+        "http://mochi.test:8888/tests/dom/media/test/dynamic_redirect.sjs?key=v1_" +
+        key + "&res=" + resource.name;
+    video.load();
+    // To limit readahead, avoid racing with playback and "catching up" mode.
+    return new Promise(resolve => video.oncanplaythrough = resolve).then(() => {
+      video.play();
+      interval = setInterval(() => info("video.currentTime = "+ video.currentTime), 1000);
+
+      let msg = "mediaRecorder.start() must throw SecurityError";
+      return new Promise(resolve => video.onplaying = resolve)
+      .then(() => waitUntil(() => video.currentTime > resource.duration / 2))
+      // 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/dynamic_redirect.sjs?key=v2_" +
+        key + "&res=" + resource.name;
+    video.load();
+    let rec, hasStopped, hasEnded = new Promise(r => video.onended = r);
+    let data = [];
+
+    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();
+      hasStopped = new Promise(resolve => rec.onstop = resolve);
+      video.play();
+    })
+    .then(() => ok(true, msgNoThrow), e => is(e.name, null, msgNoThrow))
+    .then(() => Promise.race([
+      new Promise((_, reject) => rec.onerror = e => reject(new DOMException("", e.name))),
+      hasEnded
+    ]))
+    .then(() => ok(false, msgSecErr), e => is(e.name, "SecurityError", msgSecErr))
+    .then(() => Promise.race([hasStopped, hasEnded.then(() => Promise.reject())]))
+    .then(() => ok(true, msgOnStop), e => ok(false, msgOnStop))
+    .then(() => clearInterval(interval));
+  });
+}
+
+testPrincipals(getPlayableVideo(gSeekTests))
+.catch(e => throwOutside(e))
+.then(() => SimpleTest.finish())
+.catch(e => throwOutside(e));
+
+var stop = stream => stream.getTracks().forEach(track => track.stop());
+var wait = ms => new Promise(resolve => setTimeout(resolve, ms));
+var waitUntil = f => new Promise(resolve => {
+  var ival = setInterval(() => f() && resolve(clearInterval(ival)), 100);
+});
+
+</script>
+</pre>
+
+</body>
+</html>