summaryrefslogtreecommitdiffstats
path: root/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters.html
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters.html')
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters.html470
1 files changed, 470 insertions, 0 deletions
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters.html
new file mode 100644
index 0000000000..5df97e39f5
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters.html
@@ -0,0 +1,470 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1230184",
+ title: "Set parameters on sender",
+ visible: true
+});
+
+const simulcastOffer = `v=0
+o=- 3840232462471583827 0 IN IP4 127.0.0.1
+s=-
+t=0 0
+a=group:BUNDLE 0
+a=msid-semantic: WMS
+m=video 9 UDP/TLS/RTP/SAVPF 96
+c=IN IP4 0.0.0.0
+a=rtcp:9 IN IP4 0.0.0.0
+a=ice-ufrag:Li6+
+a=ice-pwd:3C05CTZBRQVmGCAq7hVasHlT
+a=ice-options:trickle
+a=fingerprint:sha-256 5B:D3:8E:66:0E:7D:D3:F3:8E:E6:80:28:19:FC:55:AD:58:5D:B9:3D:A8:DE:45:4A:E7:87:02:F8:3C:0B:3B:B3
+a=setup:actpass
+a=mid:0
+a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
+a=recvonly
+a=rtcp-mux
+a=rtpmap:96 VP8/90000
+a=rtcp-fb:96 goog-remb
+a=rtcp-fb:96 transport-cc
+a=rtcp-fb:96 ccm fir
+a=rid:foo recv
+a=rid:bar recv
+a=simulcast:recv foo;bar
+`;
+
+function buildMaximumSendEncodings() {
+ const sendEncodings = [];
+ while (true) {
+ // isDeeply does not see identical string primitives and String objects
+ // as the same, so we make this a string primitive.
+ sendEncodings.push({rid: `${sendEncodings.length}`});
+ const pc = new RTCPeerConnection();
+ const {sender} = pc.addTransceiver('video', {sendEncodings});
+ const {encodings} = sender.getParameters();
+ if (encodings.length < sendEncodings.length) {
+ sendEncodings.pop();
+ return sendEncodings;
+ }
+ }
+}
+
+async function queueAWebrtcTask() {
+ const pc = new RTCPeerConnection();
+ pc.addTransceiver('audio');
+ await new Promise(r => pc.onnegotiationneeded = r);
+ pc.close();
+}
+
+// setParameters is mostly tested in wpt, but we test a few
+// implementation-specific things here. Other mochitests check whether the
+// set parameters actually have the desired effect on the media streams.
+const tests = [
+
+ // wpt currently does not assume support for 3 encodings, which limits the
+ // effectiveness of its powers-of-2 test (since it can test only for 1 and 2)
+ async function checkScaleResolutionDownByAutoFillPowersOf2() {
+ const pc = new RTCPeerConnection();
+ const {sender} = pc.addTransceiver('video', {
+ sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
+ });
+ const {encodings} = sender.getParameters();
+ const scaleValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy);
+ isDeeply(scaleValues, [4, 2, 1]);
+ },
+
+ // wpt currently does not assume support for 3 encodings, which limits the
+ // effectiveness of its fill-with-1 test
+ async function checkScaleResolutionDownByAutoFillWith1() {
+ const pc = new RTCPeerConnection();
+ const {sender} = pc.addTransceiver('video', {
+ sendEncodings: [
+ {rid: "0"},{rid: "1", scaleResolutionDownBy: 3},{rid: "2"}
+ ]
+ });
+ const {encodings} = sender.getParameters();
+ const scaleValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy);
+ isDeeply(scaleValues, [1, 3, 1]);
+ },
+
+ async function checkVideoEncodingLimit() {
+ const pc = new RTCPeerConnection();
+ const maxSendEncodings = buildMaximumSendEncodings();
+ const sendEncodings = maxSendEncodings.concat({rid: "a"});
+ const {sender} = pc.addTransceiver('video', {sendEncodings});
+ const {encodings} = sender.getParameters();
+
+ const rids = encodings.map(({rid}) => rid);
+ const expectedRids = maxSendEncodings.map(({rid}) => rid);
+ isDeeply(rids, expectedRids);
+
+ const scaleValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy);
+ const expectedScaleValues = [];
+ let scale = 1;
+ while (expectedScaleValues.length < maxSendEncodings.length) {
+ expectedScaleValues.push(scale);
+ scale *= 2;
+ }
+ isDeeply(scaleValues, expectedScaleValues.reverse());
+ },
+
+ async function checkScaleDownByInTrimmedEncoding() {
+ const pc = new RTCPeerConnection();
+ const maxSendEncodings = buildMaximumSendEncodings();
+ const sendEncodings = maxSendEncodings.concat({rid: "a", scaleResolutionDownBy: 3});
+ const {sender} = pc.addTransceiver('video', {sendEncodings});
+ const {encodings} = sender.getParameters();
+ const rids = encodings.map(({rid}) => rid);
+ const expectedRids = maxSendEncodings.map(({rid}) => rid);
+ isDeeply(rids, expectedRids);
+ const scaleValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy);
+ const expectedScaleValues = maxSendEncodings.map(() => 1);
+ isDeeply(scaleValues, expectedScaleValues);
+ },
+
+ async function checkLibwebrtcRidLengthLimit() {
+ const pc = new RTCPeerConnection();
+ try {
+ pc.addTransceiver('video', {
+ sendEncodings: [{rid: "wibblywobblyjeremybearimy"}]}
+ );
+ ok(false, "Rid should be too long for libwebrtc!");
+ } catch (e) {
+ is(e.name, "TypeError",
+ "Rid that is too long for libwebrtc should result in a TypeError");
+ }
+ },
+
+ async function checkErrorsInTrimmedEncodings() {
+ const pc = new RTCPeerConnection();
+ const maxSendEncodings = buildMaximumSendEncodings();
+ try {
+ const sendEncodings = maxSendEncodings.concat({rid: "foo-bar"});
+ pc.addTransceiver('video', { sendEncodings });
+ ok(false, "Should throw due to invalid rid characters");
+ } catch (e) {
+ is(e.name, "TypeError")
+ }
+ try {
+ const sendEncodings = maxSendEncodings.concat({rid: "wibblywobblyjeremybearimy"});
+ pc.addTransceiver('video', { sendEncodings });
+ ok(false, "Should throw because rid too long");
+ } catch (e) {
+ is(e.name, "TypeError")
+ }
+ try {
+ const sendEncodings = maxSendEncodings.concat({scaleResolutionDownBy: 2});
+ pc.addTransceiver('video', { sendEncodings });
+ ok(false, "Should throw due to missing rid");
+ } catch (e) {
+ is(e.name, "TypeError")
+ }
+ try {
+ const sendEncodings = maxSendEncodings.concat(maxSendEncodings[0]);
+ pc.addTransceiver('video', { sendEncodings });
+ ok(false, "Should throw due to duplicate rid");
+ } catch (e) {
+ is(e.name, "TypeError")
+ }
+ try {
+ const sendEncodings = maxSendEncodings.concat({rid: maxSendEncodings.length, scaleResolutionDownBy: 0});
+ pc.addTransceiver('video', { sendEncodings });
+ ok(false, "Should throw due to invalid scaleResolutionDownBy");
+ } catch (e) {
+ is(e.name, "RangeError")
+ }
+ try {
+ const sendEncodings = maxSendEncodings.concat({rid: maxSendEncodings.length, maxFramerate: -1});
+ pc.addTransceiver('video', { sendEncodings });
+ ok(false, "Should throw due to invalid maxFramerate");
+ } catch (e) {
+ is(e.name, "RangeError")
+ }
+ },
+
+ async function checkCompatModeUnicastSetParametersAllowsSimulcastOffer() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", true]);
+ const pc1 = new RTCPeerConnection();
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ const sender = pc1.addTrack(stream.getTracks()[0]);
+ const parameters = sender.getParameters();
+ parameters.encodings[0].scaleResolutionDownBy = 3.0;
+ await sender.setParameters(parameters);
+
+ await pc1.setRemoteDescription({type: "offer", sdp: simulcastOffer});
+
+ const {encodings} = sender.getParameters();
+ const rids = encodings.map(({rid}) => rid);
+ isDeeply(rids, ["foo", "bar"]);
+ is(encodings[0].scaleResolutionDownBy, 2.0);
+ is(encodings[1].scaleResolutionDownBy, 1.0);
+ },
+
+ async function checkCompatModeUnicastSetParametersInterruptAllowsSimulcastOffer() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", true]);
+ const pc1 = new RTCPeerConnection();
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ const sender = pc1.addTrack(stream.getTracks()[0]);
+ const parameters = sender.getParameters();
+ parameters.encodings[0].scaleResolutionDownBy = 3.0;
+
+ const offerDone = pc1.setRemoteDescription({type: "offer", sdp: simulcastOffer});
+ await sender.setParameters(parameters);
+ await offerDone;
+
+ const {encodings} = sender.getParameters();
+ const rids = encodings.map(({rid}) => rid);
+ isDeeply(rids, ["foo", "bar"]);
+ is(encodings[0].scaleResolutionDownBy, 2.0);
+ is(encodings[1].scaleResolutionDownBy, 1.0);
+ },
+
+ async function checkCompatModeSimulcastSetParametersSetsSimulcastEnvelope() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", true]);
+ const pc1 = new RTCPeerConnection();
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ const sender = pc1.addTrack(stream.getTracks()[0]);
+ const parameters = sender.getParameters();
+ parameters.encodings[0].rid = "1";
+ parameters.encodings.push({rid: "2"});
+ await sender.setParameters(parameters);
+
+ await pc1.setRemoteDescription({type: "offer", sdp: simulcastOffer});
+
+ const {encodings} = sender.getParameters();
+ const rids = encodings.map(({rid}) => rid);
+ // No overlap in rids -> unicast
+ isDeeply(rids, [undefined]);
+ },
+
+ async function checkCompatModeSimulcastSetParametersRacesLocalUnicastOffer() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", true]);
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ const sender = pc1.addTrack(stream.getTracks()[0]);
+ // unicast offer
+ const offer = await pc1.createOffer();
+ const aTask = queueAWebrtcTask();
+ const sldPromise = pc1.setLocalDescription(offer);
+
+ // Right now, we have aTask queued. The success task for sLD is not queued
+ // yet, because Firefox performs the initial steps on the microtask queue,
+ // which we have not allowed to run yet. Awaiting aTask will first clear
+ // the microtask queue, then run the task queue until aTask is finished.
+ // That _should_ result in the success task for sLD(offer) being queued.
+ await aTask;
+
+ const parameters = sender.getParameters();
+ parameters.encodings[0].rid = "foo";
+ parameters.encodings.push({rid: "bar"});
+ // simulcast setParameters; the task to update [[SendEncodings]] should be
+ // queued after the success task for sLD(offer)
+ await sender.setParameters(parameters);
+ await sldPromise;
+
+ const {encodings} = sender.getParameters();
+ const rids = encodings.map(({rid}) => rid);
+ // Compat mode lets this slide, but won't try to negotiate it since we've
+ // already applied a unicast local offer.
+ isDeeply(rids, ["foo", "bar"]);
+
+ // Let negotiation finish, so we can generate a new offer
+ await pc2.setRemoteDescription(pc1.localDescription);
+ await pc2.setLocalDescription();
+ await pc1.setRemoteDescription(pc2.localDescription);
+
+ const reoffer = await pc1.createOffer();
+ ok(!reoffer.sdp.includes("a=simulcast"), "reoffer should be unicast");
+ },
+
+ async function checkCompatModeSimulcastSetParametersRacesRemoteOffer() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", true]);
+ const pc1 = new RTCPeerConnection();
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ const sender = pc1.addTrack(stream.getTracks()[0]);
+ const parameters = sender.getParameters();
+ parameters.encodings[0].rid = "foo";
+ parameters.encodings.push({rid: "bar"});
+ const p = sender.setParameters(parameters);
+ await pc1.setRemoteDescription({type: "offer", sdp: simulcastOffer});
+ await p;
+ const answer = await pc1.createAnswer();
+
+ const {encodings} = sender.getParameters();
+ const rids = encodings.map(({rid}) => rid);
+ isDeeply(rids, ["foo", "bar"]);
+ ok(answer.sdp.includes("a=simulcast:send foo;bar"), "answer should be simulcast");
+ },
+
+ async function checkCompatModeSimulcastSetParametersRacesLocalAnswer() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", true]);
+ // We do an initial negotiation, and while the local answer is pending,
+ // perform a setParameters on a not-yet-negotiated video sender. The intent
+ // here is to have the success task for sLD(answer) run while the
+ // setParameters is pending.
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+
+ const audioStream = await navigator.mediaDevices.getUserMedia({audio: true});
+ // We use this later on, but set it up now so we don't inadvertently
+ // crank the event loop more than we intend below.
+ const videoStream = await navigator.mediaDevices.getUserMedia({video: true});
+ pc2.addTrack(audioStream.getTracks()[0]);
+ await pc2.setLocalDescription();
+ await pc1.setRemoteDescription(pc2.localDescription);
+ const answer = await pc1.createAnswer();
+ const aTask = queueAWebrtcTask();
+ const sldPromise = pc1.setLocalDescription(answer);
+
+ // Right now, we have aTask queued. The success task for sLD is not queued
+ // yet, because Firefox performs the initial steps on the microtask queue,
+ // which we have not allowed to run yet. Awaiting aTask will first clear
+ // the microtask queue, then run the task queue until aTask is finished.
+ // That _should_ result in the success task for sLD(answer) being queued.
+ await aTask;
+
+ // The success task for sLD(answer) should be queued now. Don't relinquish
+ // the event loop!
+
+ // New sender that has nothing to do with the negotiation in progress.
+ const sender = pc1.addTrack(videoStream.getTracks()[0]);
+ const parameters = sender.getParameters();
+ parameters.encodings[0].rid = "foo";
+ parameters.encodings.push({rid: "bar"});
+
+ // We have not relinquished the event loop, so the sLD(answer) should still
+ // be queued. The task that updates [[SendEncodings]] (from setParameters)
+ // should be queued behind it. Let them both run.
+ await sender.setParameters(parameters);
+ await sldPromise;
+
+ const offer = await pc1.createOffer();
+
+ const {encodings} = sender.getParameters();
+ const rids = encodings.map(({rid}) => rid);
+ isDeeply(rids, ["foo", "bar"]);
+ ok(offer.sdp.includes("a=simulcast:send foo;bar"), "offer should be simulcast");
+ },
+
+ async function checkCompatModeSimulcastSetParametersRacesRemoteAnswer() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", true]);
+ // We do an initial negotiation, and while the remote answer is pending,
+ // perform a setParameters on a not-yet-negotiated video sender. The intent
+ // here is to have the success task for sRD(answer) run while the
+ // setParameters is pending.
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+
+ const audioStream = await navigator.mediaDevices.getUserMedia({audio: true});
+ // We use this later on, but set it up now so we don't inadvertently
+ // crank the event loop more than we intend below.
+ const videoStream = await navigator.mediaDevices.getUserMedia({video: true});
+ pc1.addTrack(audioStream.getTracks()[0]);
+ await pc1.setLocalDescription();
+ await pc2.setRemoteDescription(pc1.localDescription);
+ await pc2.setLocalDescription();
+ const aTask = queueAWebrtcTask();
+ const srdPromise = pc1.setRemoteDescription(pc2.localDescription);
+
+ // Right now, we have aTask queued. The success task for sRD is not queued
+ // yet, because Firefox performs the initial steps on the microtask queue,
+ // which we have not allowed to run yet. Awaiting aTask will first clear
+ // the microtask queue, then run the task queue until aTask is finished.
+ // That _should_ result in the success task for sRD(answer) being queued.
+ await aTask;
+
+ // The success task for sRD(answer) should be queued now. Don't relinquish
+ // the event loop!
+
+ const sender = pc1.addTrack(videoStream.getTracks()[0]);
+ const parameters = sender.getParameters();
+ parameters.encodings[0].rid = "foo";
+ parameters.encodings.push({rid: "bar"});
+
+ // We have not relinquished the event loop, so the sRD(answer) should still
+ // be queued. The task that updates [[SendEncodings]] (from setParameters)
+ // should be queued behind it. Let them both run.
+ await sender.setParameters(parameters);
+ await srdPromise;
+
+ const offer = await pc1.createOffer();
+
+ const {encodings} = sender.getParameters();
+ const rids = encodings.map(({rid}) => rid);
+ isDeeply(rids, ["foo", "bar"]);
+ ok(offer.sdp.includes("a=simulcast:send foo;bar"), "offer should be simulcast");
+ },
+
+ async function checkCompatModeSimulcastRidlessSetParametersRacesLocalOffer() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", true]);
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ const sender = pc1.addTrack(stream.getTracks()[0]);
+ // unicast offer
+ const aTask = queueAWebrtcTask();
+ const sldPromise = pc1.setLocalDescription();
+
+ // Right now, we have aTask queued. The success task for sLD is not queued
+ // yet, because Firefox performs the initial steps on the microtask queue,
+ // which we have not allowed to run yet. Awaiting aTask will first clear
+ // the microtask queue, then run the task queue until aTask is finished.
+ // That _should_ result in the success task for sLD(offer) being queued.
+ await aTask;
+
+ // simulcast setParameters; the task to update [[SendEncodings]] should be
+ // queued after the success task for sLD(offer)
+ try {
+ await sender.setParameters({"encodings": [{}, {}]});
+ ok(false, "setParameters with two ridless encodings should fail");
+ } catch (e) {
+ ok(true, "setParameters with two ridless encodings should fail");
+ }
+ await sldPromise;
+
+ const {encodings} = sender.getParameters();
+ const rids = encodings.map(({rid}) => rid);
+ // Compat mode lets this slide, but won't try to negotiate it since we've
+ // already applied a unicast local offer.
+ isDeeply(rids, [undefined]);
+
+ // Let negotiation finish, so we can generate a new offer
+ await pc2.setRemoteDescription(pc1.localDescription);
+ await pc2.setLocalDescription();
+ await pc1.setRemoteDescription(pc2.localDescription);
+
+ const reoffer = await pc1.createOffer();
+ ok(!reoffer.sdp.includes("a=simulcast"), "reoffer should be unicast");
+ },
+
+];
+
+runNetworkTest(async () => {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", false]);
+ for (const test of tests) {
+ info(`Running test: ${test.name}`);
+ await test();
+ info(`Done running test: ${test.name}`);
+ }
+});
+
+</script>
+</pre>
+</body>
+</html>