Update WPT WebRTC tests up to a22a149
authoryouenn@apple.com <youenn@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 16 Mar 2019 16:35:16 +0000 (16:35 +0000)
committeryouenn@apple.com <youenn@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 16 Mar 2019 16:35:16 +0000 (16:35 +0000)
https://bugs.webkit.org/show_bug.cgi?id=195831

Reviewed by Eric Carlson.

LayoutTests/imported/w3c:

* web-platform-tests/webrtc: Resynced.

LayoutTests:

* tests-options.json:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@243041 268f45cc-cd09-0410-ab3c-d52691b4dbfc

89 files changed:
LayoutTests/ChangeLog
LayoutTests/TestExpectations
LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/web-platform-tests/webrtc/META.yml
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCConfiguration-rtcpMuxPolicy.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCDTMFSender-insertDTMF.https.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCDataChannel-bufferedAmount-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCDataChannel-bufferedAmount.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCDataChannel-send.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCDtlsTransport-state-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCDtlsTransport-state.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCError-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCError.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCIceCandidate-constructor-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCIceCandidate-constructor.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCIceConnectionState-candidate-pair.https-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCIceConnectionState-candidate-pair.https.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCIceTransport-extension.https-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCIceTransport-extension.https.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-addIceCandidate-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-addIceCandidate.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-connectionState-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-connectionState.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-getStats.https-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-getStats.https.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-helper.js
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-iceConnectionState.https-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-iceConnectionState.https.html [moved from LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-iceConnectionState.html with 73% similarity]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-iceGatheringState-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-iceGatheringState.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-onnegotiationneeded-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-onnegotiationneeded.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-onsignalingstatechanged.https-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-onsignalingstatechanged.https.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-remote-track-mute.https-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-remote-track-mute.https.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-removeTrack.https.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setLocalDescription-answer-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setLocalDescription-offer-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setLocalDescription-rollback.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setLocalDescription.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-nomsid-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-nomsid.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-offer-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-offer.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-rollback-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-rollback.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-tracks.https.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-track-stats.https-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-track-stats.https.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-transceivers.https-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-transceivers.https.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpParameters-encodings-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpParameters-encodings.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpParameters-helper.js
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpReceiver-getContributingSources.https-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpReceiver-getContributingSources.https.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpReceiver-getParameters-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpReceiver-getParameters.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpReceiver-getStats.https.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpReceiver-getSynchronizationSources.https-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpReceiver-getSynchronizationSources.https.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpSender-replaceTrack.https.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpSender-transport.https-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpSender-transport.https.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpTransceiver-stop-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpTransceiver-stop.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpTransceiver.https-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpTransceiver.https.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCTrackEvent-constructor.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCTrackEvent-fire-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCTrackEvent-fire.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/getstats-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/getstats.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/no-media-call.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/README.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/missing-fields-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/missing-fields.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/msid-parse-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/msid-parse.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/simulcast-offer-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/simulcast-offer.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/w3c-import.log
LayoutTests/imported/w3c/web-platform-tests/webrtc/rtcpeerconnection/rtcpeerconnection-idl-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/simplecall-no-ssrcs.https.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/simplecall.https.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/w3c-import.log
LayoutTests/tests-options.json

index 8ca4865..01bf628 100644 (file)
@@ -1,3 +1,12 @@
+2019-03-16  Youenn Fablet  <youenn@apple.com>
+
+        Update WPT WebRTC tests up to a22a149
+        https://bugs.webkit.org/show_bug.cgi?id=195831
+
+        Reviewed by Eric Carlson.
+
+        * tests-options.json:
+
 2019-03-15  Joseph Pecoraro  <pecoraro@apple.com>
 
         Web Inspector: HAR Extension for Resource Priority
index 6644083..2ad09a6 100644 (file)
@@ -1340,8 +1340,15 @@ webkit.org/b/171094 imported/w3c/web-platform-tests/webrtc/rtcpeerconnection/rtc
 webkit.org/b/172f21 imported/w3c/web-platform-tests/webrtc/getstats.html [ Failure ]
 imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setLocalDescription-offer.html [ Failure ]
 imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setLocalDescription-answer.html [ Failure ]
+imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-iceGatheringState.html [ Pass Failure ]
+
 # Skip timing out test
 imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-tracks.https.html [ Skip ]
+imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-remote-track-mute.https.html [ Skip ]
+imported/w3c/web-platform-tests/webrtc/RTCDataChannel-bufferedAmount.html [ Skip ]
+imported/w3c/web-platform-tests/webrtc/RTCTrackEvent-fire.html [ Skip ]
+imported/w3c/web-platform-tests/webrtc/rtcpeerconnection/iceGatheringState.html [ Skip ]
+imported/w3c/web-platform-tests/webrtc/RTCIceConnectionState-candidate-pair.https.html [ Skip ]
 
 # Uses legacy WebRTC API.
 imported/w3c/web-platform-tests/webrtc/rtcpeerconnection/setRemoteDescription.html [ Skip ]
index b024b81..567f481 100644 (file)
@@ -1,3 +1,12 @@
+2019-03-16  Youenn Fablet  <youenn@apple.com>
+
+        Update WPT WebRTC tests up to a22a149
+        https://bugs.webkit.org/show_bug.cgi?id=195831
+
+        Reviewed by Eric Carlson.
+
+        * web-platform-tests/webrtc: Resynced.
+
 2019-03-16  Sihui Liu  <sihui_liu@apple.com>
 
         Layout tests imported/w3c/web-platform-tests/IndexedDB/*-exception-order.html are failing
index 2025a11..eed4ac8 100644 (file)
@@ -3,10 +3,8 @@ suggested_reviewers:
   - snuggs
   - agouaillard
   - alvestrand
-  - dontcallmedom
   - guidou
   - henbos
-  - phoglund
   - youennf
   - rwaldron
   - jan-ivar
index 1d99aa2..7d0c81f 100644 (file)
@@ -3,6 +3,7 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="RTCConfiguration-helper.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
 <script>
   'use strict';
 
     const pc = new RTCPeerConnection({rtcpMuxPolicy: 'require'});
     t.add_cleanup(() => pc.close());
 
-    await pc.createOffer({offerToReceiveAudio: true})
-      .then(offer => pc.setLocalDescription(offer));
+    const offer = await generateAudioReceiveOnlyOffer(pc);
+    await pc.setLocalDescription(offer);
     return promise_rejects(t, 'InvalidAccessError', pc.setRemoteDescription({type: 'answer', sdp}));
   }, 'setRemoteDescription throws InvalidAccessError when called with an answer without rtcp-mux and rtcpMuxPolicy is set to require');
 </script>
index 8a6d645..8ac144b 100644 (file)
     const stream = await navigator.mediaDevices.getUserMedia({audio: true});
     t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
     const [track] = stream.getTracks();
-    callee.addTrack(track);
+    callee.addTrack(track, stream);
     const answer = await callee.createAnswer();
     await callee.setLocalDescription(answer);
     await caller.setRemoteDescription(answer);
index f7e9e6b..816a391 100644 (file)
@@ -1,6 +1,13 @@
 
+Harness Error (TIMEOUT), message = null
+
 FAIL bufferedAmount should increase to byte length of encoded unicode string sent assert_equals: Expect bufferedAmount to be the byte length of the unicode string expected 12 but got 0
 FAIL bufferedAmount should increase to byte length of buffer sent assert_equals: Expect bufferedAmount to increase to byte length of sent buffer expected 5 but got 0
 FAIL bufferedAmount should increase to size of blob sent promise_test: Unhandled rejection with value: object "NotSupportedError: The operation is not supported."
 FAIL bufferedAmount should increase by byte length for each message sent assert_unreached: Unexpected promise rejection: Error: assert_equals: Expect bufferedAmount to be the total length of all messages queued to send expected 5 but got 0 Reached unreachable code
+TIMEOUT Data channel bufferedamountlow event fires after send() is complete Test timed out
+NOTRUN Data channel bufferedamount is data.length on send(data) 
+NOTRUN Data channel bufferedamount returns the same amount if no more data is sent on the channel 
+NOTRUN Data channel bufferedamountlow event fires only once after multiple consecutive send() calls 
+NOTRUN Data channel bufferedamountlow event fires after each sent message 
 
index 2c37cc2..f4a7104 100644 (file)
       assert_unreached(`Unexpected promise rejection: ${err}`)));
   }, 'bufferedAmount should increase by byte length for each message sent');
 
+  promise_test(async t => {
+    const [channel1, channel2] = await createDataChannelPair();
+    channel1.addEventListener('bufferedamountlow', t.step_func_done(() => {
+      assert_true(channel1.bufferedAmount <= channel1.bufferedAmountLowThreshold);
+    }));
+    const eventWatcher = new EventWatcher(t, channel1, ['bufferedamountlow']);
+    channel1.send(helloString);
+    await eventWatcher.wait_for(['bufferedamountlow']);
+  }, 'Data channel bufferedamountlow event fires after send() is complete');
+
+  promise_test(async t => {
+    const [channel1, channel2] = await createDataChannelPair();
+    channel1.send(helloString);
+    assert_equals(channel1.bufferedAmount, helloString.length);
+    await awaitMessage(channel2);
+    assert_equals(channel1.bufferedAmount, 0);
+  }, 'Data channel bufferedamount is data.length on send(data)');
+
+  promise_test(async t => {
+    const [channel1, channel2] = await createDataChannelPair();
+    channel1.send(helloString);
+    assert_equals(channel1.bufferedAmount, helloString.length);
+    assert_equals(channel1.bufferedAmount, helloString.length);
+  }, 'Data channel bufferedamount returns the same amount if no more data is' +
+     ' sent on the channel');
+
+  promise_test(async t => {
+    const [channel1, channel2] = await createDataChannelPair();
+    let eventFireCount = 0;
+    channel1.addEventListener('bufferedamountlow', t.step_func(() => {
+      assert_true(channel1.bufferedAmount <= channel1.bufferedAmountLowThreshold);
+      assert_equals(++eventFireCount, 1);
+    }));
+    const eventWatcher = new EventWatcher(t, channel1, ['bufferedamountlow']);
+    channel1.send(helloString);
+    assert_equals(channel1.bufferedAmount, helloString.length);
+    channel1.send(helloString);
+    assert_equals(channel1.bufferedAmount, 2 * helloString.length);
+    await eventWatcher.wait_for(['bufferedamountlow']);
+  }, 'Data channel bufferedamountlow event fires only once after multiple' +
+    ' consecutive send() calls');
+
+  promise_test(async t => {
+    const [channel1, channel2] = await createDataChannelPair();
+    const eventWatcher = new EventWatcher(t, channel1, ['bufferedamountlow']);
+    channel1.send(helloString);
+    assert_equals(channel1.bufferedAmount, helloString.length);
+    await eventWatcher.wait_for(['bufferedamountlow']);
+    assert_equals(await awaitMessage(channel2), helloString);
+    channel1.send(helloString);
+    assert_equals(channel1.bufferedAmount, helloString.length);
+    await eventWatcher.wait_for(['bufferedamountlow']);
+    assert_equals(await awaitMessage(channel2), helloString);
+  }, 'Data channel bufferedamountlow event fires after each sent message');
+
 </script>
index 7cacaa4..cb8b4ed 100644 (file)
@@ -14,7 +14,7 @@
   //  createDataChannelPair
   //  awaitMessage
   //  blobToArrayBuffer
-  //  assert_equals_array_buffer
+  //  assert_equals_typed_array
 
   /*
     6.2.  RTCDataChannel
       assert_true(messageBuffer instanceof ArrayBuffer,
         'Expect messageBuffer to be an ArrayBuffer');
 
-      assert_equals_array_buffer(messageBuffer, helloBuffer.buffer);
+      assert_equals_typed_array(messageBuffer, helloBuffer.buffer);
     });
   }, 'Data channel should be able to send Uint8Array message and receive as ArrayBuffer');
 
       assert_true(messageBuffer instanceof ArrayBuffer,
         'Expect messageBuffer to be an ArrayBuffer');
 
-      assert_equals_array_buffer(messageBuffer, helloBuffer.buffer);
+      assert_equals_typed_array(messageBuffer, helloBuffer.buffer);
     });
   }, 'Data channel should be able to send ArrayBuffer message and receive as ArrayBuffer');
 
       assert_true(messageBuffer instanceof ArrayBuffer,
         'Expect messageBuffer to be an ArrayBuffer');
 
-      assert_equals_array_buffer(messageBuffer, helloBuffer.buffer);
+      assert_equals_typed_array(messageBuffer, helloBuffer.buffer);
     });
   }, 'Data channel should be able to send Blob message and receive as ArrayBuffer');
 
       assert_true(messageBuffer instanceof ArrayBuffer,
         'Expect messageBuffer to be an ArrayBuffer');
 
-      assert_equals_array_buffer(messageBuffer, helloBuffer.buffer);
+      assert_equals_typed_array(messageBuffer, helloBuffer.buffer);
     });
   }, 'Data channel should be able to send ArrayBuffer message and receive as Blob');
 
       assert_true(messageBuffer instanceof ArrayBuffer,
         'Expect messageBuffer to be an ArrayBuffer');
 
-      assert_equals_array_buffer(messageBuffer, helloBuffer.buffer);
+      assert_equals_typed_array(messageBuffer, helloBuffer.buffer);
     });
   }, 'Data channel binaryType should receive message as Blob by default');
 
       receivedMessages.push(data);
 
       if(receivedMessages.length === 3) {
-        assert_equals_array_buffer(receivedMessages[0], helloBuffer.buffer);
+        assert_equals_typed_array(receivedMessages[0], helloBuffer.buffer);
         assert_equals(receivedMessages[1], unicodeString);
-        assert_equals_array_buffer(receivedMessages[2], helloBuffer.buffer);
+        assert_equals_typed_array(receivedMessages[2], helloBuffer.buffer);
 
         t.done();
       }
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCDtlsTransport-state-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCDtlsTransport-state-expected.txt
new file mode 100644 (file)
index 0000000..cef1ce4
--- /dev/null
@@ -0,0 +1,5 @@
+
+FAIL DTLS transport goes to connected state promise_test: Unhandled rejection with value: object "ReferenceError: Can't find variable: RTCDtlsTransport"
+FAIL close() causes the local transport to close immediately promise_test: Unhandled rejection with value: object "TypeError: undefined is not an object (evaluating 'dtlstransport.state')"
+FAIL close() causes the other end's DTLS transport to close promise_test: Unhandled rejection with value: object "TypeError: undefined is not an object (evaluating 'dtlstransport.state')"
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCDtlsTransport-state.html b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCDtlsTransport-state.html
new file mode 100644 (file)
index 0000000..fd7215f
--- /dev/null
@@ -0,0 +1,122 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>RTCDtlsTransport</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script>
+'use strict';
+
+// The following helper functions are called from RTCPeerConnection-helper.js:
+//   exchangeIceCandidates
+//   doSignalingHandshake
+//   trackFactories.audio()
+
+/*
+    5.5.  RTCDtlsTransport Interface
+      interface RTCDtlsTransport : EventTarget {
+        readonly attribute RTCDtlsTransportState state;
+        sequence<ArrayBuffer> getRemoteCertificates();
+                 attribute EventHandler          onstatechange;
+                 attribute EventHandler          onerror;
+        ...
+      };
+
+      enum RTCDtlsTransportState {
+        "new",
+        "connecting",
+        "connected",
+        "closed",
+        "failed"
+      };
+
+*/
+function resolveWhen(t, dtlstransport, state) {
+  return new Promise((resolve, reject) => {
+    if (dtlstransport.state == state) { resolve(); }
+    dtlstransport.addEventListener('statechange', t.step_func(e => {
+      if (dtlstransport.state == state) {
+        resolve();
+      }
+    }));
+  });
+}
+
+// Helper class to exchange ice candidates between
+// two local peer connections
+class CandidateChannel {
+  constructor(source, dest) {
+    source.addEventListener('icecandidate', event => {
+      const { candidate } = event;
+      if (candidate && this.activated
+          && this.destination.signalingState !== 'closed') {
+        this.destination.addIceCandidate(candidate);
+      } else {
+        this.queue.push(candidate);
+      }
+    });
+    this.destination = dest;
+    this.activated = false;
+    this.queue = [];
+  }
+  activate() {
+    this.activated = true;
+    for (const candidate of this.queue) {
+      this.destination.addIceCandidate(candidate);
+    }
+  }
+}
+
+function coupleCandidates(pc1, pc2) {
+  const ch1 = new CandidateChannel(pc1, pc2);
+  const ch2 = new CandidateChannel(pc2, pc1);
+  return [ch1, ch2];
+}
+
+async function setupConnections(t) {
+  const pc1 = new RTCPeerConnection();
+  t.add_cleanup(() => pc1.close());
+  const pc2 = new RTCPeerConnection();
+  t.add_cleanup(() => pc2.close());
+
+  pc1.addTrack(trackFactories.audio());
+  const channels = coupleCandidates(pc1, pc2);
+  await doSignalingHandshake(pc1, pc2);
+  for (const channel of channels) {
+    channel.activate();
+  }
+  return [pc1, pc2];
+}
+
+promise_test(async t => {
+  const [pc1, pc2] = await setupConnections(t);
+  const dtlsTransport1 = pc1.getTransceivers()[0].sender.transport;
+  const dtlsTransport2 = pc2.getTransceivers()[0].sender.transport;
+  assert_true(dtlsTransport1 instanceof RTCDtlsTransport);
+  assert_true(dtlsTransport2 instanceof RTCDtlsTransport);
+  await Promise.all([resolveWhen(t, dtlsTransport1, 'connected'),
+                     resolveWhen(t, dtlsTransport2, 'connected')]);
+}, 'DTLS transport goes to connected state');
+
+promise_test(async t => {
+  const [pc1, pc2] = await setupConnections(t);
+
+  const dtlsTransport1 = pc1.getTransceivers()[0].sender.transport;
+  const dtlsTransport2 = pc2.getTransceivers()[0].sender.transport;
+  await Promise.all([resolveWhen(t, dtlsTransport1, 'connected'),
+                     resolveWhen(t, dtlsTransport2, 'connected')]);
+  pc1.close();
+  assert_equals(dtlsTransport1.state, 'closed');
+}, 'close() causes the local transport to close immediately');
+
+promise_test(async t => {
+  const [pc1, pc2] = await setupConnections(t);
+  const dtlsTransport1 = pc1.getTransceivers()[0].sender.transport;
+  const dtlsTransport2 = pc2.getTransceivers()[0].sender.transport;
+  await Promise.all([resolveWhen(t, dtlsTransport1, 'connected'),
+                     resolveWhen(t, dtlsTransport2, 'connected')]);
+  pc1.close();
+  await resolveWhen(t, dtlsTransport2, 'closed');
+}, 'close() causes the other end\'s DTLS transport to close');
+
+</script>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCError-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCError-expected.txt
new file mode 100644 (file)
index 0000000..86bb48e
--- /dev/null
@@ -0,0 +1,31 @@
+
+FAIL RTCError constructor with errorDetail and message Can't find variable: RTCError
+FAIL RTCError constructor's message argument is optional Can't find variable: RTCError
+FAIL RTCError constructor throws TypeError if arguments are missing assert_throws: function "() => {
+    new RTCError();
+  }" threw object "ReferenceError: Can't find variable: RTCError" ("ReferenceError") expected object "TypeError" ("TypeError")
+FAIL RTCError constructor throws TypeError if the errorDetail is invalid assert_throws: function "() => {
+    new RTCError({errorDetail:'invalid-error-detail'}, 'message');
+  }" threw object "ReferenceError: Can't find variable: RTCError" ("ReferenceError") expected object "TypeError" ("TypeError")
+FAIL RTCError.name is 'RTCError' Can't find variable: RTCError
+FAIL RTCError.code is 0 Can't find variable: RTCError
+FAIL RTCError.errorDetail is readonly. Can't find variable: RTCError
+FAIL RTCErrorInit.errorDetail is the only required attribute assert_throws: function "() => {
+    new RTCError({}, 'message');
+  }" threw object "ReferenceError: Can't find variable: RTCError" ("ReferenceError") expected object "TypeError" ("TypeError")
+FAIL RTCError.sdpLineNumber is null by default Can't find variable: RTCError
+FAIL RTCError.sdpLineNumber is settable by constructor Can't find variable: RTCError
+FAIL RTCError.sdpLineNumber is readonly Can't find variable: RTCError
+FAIL RTCError.httpRequestStatusCode is null by default Can't find variable: RTCError
+FAIL RTCError.httpRequestStatusCode is settable by constructor Can't find variable: RTCError
+FAIL RTCError.httpRequestStatusCode is readonly Can't find variable: RTCError
+FAIL RTCError.sctpCauseCode is null by default Can't find variable: RTCError
+FAIL RTCError.sctpCauseCode is settable by constructor Can't find variable: RTCError
+FAIL RTCError.sctpCauseCode is readonly Can't find variable: RTCError
+FAIL RTCError.receivedAlert is null by default Can't find variable: RTCError
+FAIL RTCError.receivedAlert is settable by constructor Can't find variable: RTCError
+FAIL RTCError.receivedAlert is readonly Can't find variable: RTCError
+FAIL RTCError.sentAlert is null by default Can't find variable: RTCError
+FAIL RTCError.sentAlert is settable by constructor Can't find variable: RTCError
+FAIL RTCError.sentAlert is readonly Can't find variable: RTCError
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCError.html b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCError.html
new file mode 100644 (file)
index 0000000..4d07260
--- /dev/null
@@ -0,0 +1,89 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCError and RTCErrorInit</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script>
+'use strict';
+
+test(() => {
+  const error = new RTCError({errorDetail:'data-channel-failure'}, 'message');
+  assert_equals(error.message, 'message');
+  assert_equals(error.errorDetail, 'data-channel-failure');
+}, 'RTCError constructor with errorDetail and message');
+
+test(() => {
+  const error = new RTCError({errorDetail:'data-channel-failure'});
+  assert_equals(error.message, '');
+}, 'RTCError constructor\'s message argument is optional');
+
+test(() => {
+  assert_throws(new TypeError(), () => {
+    new RTCError();
+  });
+  assert_throws(new TypeError(), () => {
+    new RTCError({});  // {errorDetail} is missing.
+  });
+}, 'RTCError constructor throws TypeError if arguments are missing');
+
+test(() => {
+  assert_throws(new TypeError(), () => {
+    new RTCError({errorDetail:'invalid-error-detail'}, 'message');
+  });
+}, 'RTCError constructor throws TypeError if the errorDetail is invalid');
+
+test(() => {
+  const error = new RTCError({errorDetail:'data-channel-failure'}, 'message');
+  assert_equals(error.name, 'RTCError');
+}, 'RTCError.name is \'RTCError\'');
+
+test(() => {
+  const error = new RTCError({errorDetail:'data-channel-failure'}, 'message');
+  assert_equals(error.code, 0);
+}, 'RTCError.code is 0');
+
+test(() => {
+  const error = new RTCError({errorDetail:'data-channel-failure'}, 'message');
+  assert_throws(new TypeError(), () => {
+    error.errorDetail = 'dtls-failure';
+  });
+}, 'RTCError.errorDetail is readonly.');
+
+test(() => {
+  // Infers what are valid RTCErrorInit objects by passing them to the RTCError
+  // constructor.
+  assert_throws(new TypeError(), () => {
+    new RTCError({}, 'message');
+  });
+  new RTCError({errorDetail:'data-channel-failure'}, 'message');
+}, 'RTCErrorInit.errorDetail is the only required attribute');
+
+// All of these are number types (long or unsigned long).
+const nullableAttributes = ['sdpLineNumber',
+                            'httpRequestStatusCode',
+                            'sctpCauseCode',
+                            'receivedAlert',
+                            'sentAlert'];
+
+nullableAttributes.forEach(attribute => {
+  test(() => {
+    const error = new RTCError({errorDetail:'data-channel-failure'}, 'message');
+    assert_equals(error[attribute], null);
+  }, 'RTCError.' + attribute + ' is null by default');
+
+  test(() => {
+    const error = new RTCError(
+        {errorDetail:'data-channel-failure', [attribute]: 0}, 'message');
+    assert_equals(error[attribute], 0);
+  }, 'RTCError.' + attribute + ' is settable by constructor');
+
+  test(() => {
+    const error = new RTCError({errorDetail:'data-channel-failure'}, 'message');
+    assert_throws(new TypeError(), () => {
+      error[attribute] = 42;
+    });
+  }, 'RTCError.' + attribute + ' is readonly');
+});
+
+</script>
index aac774d..cc1e94e 100644 (file)
@@ -6,14 +6,15 @@ PASS new RTCIceCandidate({ sdpMid: null, sdpMLineIndex: null })
 PASS new RTCIceCandidate({ candidate: '' }) 
 PASS new RTCIceCandidate({ candidate: null }) 
 PASS new RTCIceCandidate({ ... }) with valid candidate string only 
-FAIL new RTCIceCandidate({ sdpMid: 'audio' }) assert_equals: expected (object) null but got (undefined) undefined
-FAIL new RTCIceCandidate({ sdpMLineIndex: 0 }) assert_equals: expected (object) null but got (undefined) undefined
-FAIL new RTCIceCandidate({ sdpMid: 'audio', sdpMLineIndex: 0 }) assert_equals: expected (object) null but got (undefined) undefined
-FAIL new RTCIceCandidate({ candidate: '', sdpMid: 'audio' } assert_equals: expected (object) null but got (undefined) undefined
-FAIL new RTCIceCandidate({ candidate: '', sdpMLineIndex: 0 } assert_equals: expected (object) null but got (undefined) undefined
-FAIL new RTCIceCandidate({ ... }) with valid candidate string and sdpMid assert_equals: expected (object) null but got (undefined) undefined
-FAIL new RTCIceCandidate({ ... }) with invalid candidate string and sdpMid assert_equals: expected (object) null but got (undefined) undefined
-FAIL new RTCIceCandidate({ ... }) with non default value for all fields assert_equals: expected (string) "test" but got (undefined) undefined
-FAIL new RTCIceCandidate({ ... }) with invalid sdpMid assert_equals: expected (object) null but got (undefined) undefined
-FAIL new RTCIceCandidate({ ... }) with invalid sdpMLineIndex assert_equals: expected (object) null but got (undefined) undefined
+FAIL new RTCIceCandidate({ sdpMid: 'audio' }) assert_equals: usernameFragment expected (object) null but got (undefined) undefined
+FAIL new RTCIceCandidate({ sdpMLineIndex: 0 }) assert_equals: usernameFragment expected (object) null but got (undefined) undefined
+FAIL new RTCIceCandidate({ sdpMid: 'audio', sdpMLineIndex: 0 }) assert_equals: usernameFragment expected (object) null but got (undefined) undefined
+FAIL new RTCIceCandidate({ candidate: '', sdpMid: 'audio' } assert_equals: usernameFragment expected (object) null but got (undefined) undefined
+FAIL new RTCIceCandidate({ candidate: '', sdpMLineIndex: 0 } assert_equals: usernameFragment expected (object) null but got (undefined) undefined
+FAIL new RTCIceCandidate({ ... }) with valid candidate string and sdpMid assert_equals: usernameFragment expected (object) null but got (undefined) undefined
+FAIL new RTCIceCandidate({ ... }) with invalid candidate string and sdpMid assert_equals: usernameFragment expected (object) null but got (undefined) undefined
+FAIL new RTCIceCandidate({ ... }) with nondefault values for all fields assert_equals: usernameFragment expected (string) "test" but got (undefined) undefined
+FAIL new RTCIceCandidate({ ... }) with nondefault values for all fields, tcp candidate assert_equals: usernameFragment expected (string) "user1" but got (undefined) undefined
+FAIL new RTCIceCandidate({ ... }) with invalid sdpMid assert_equals: usernameFragment expected (object) null but got (undefined) undefined
+FAIL new RTCIceCandidate({ ... }) with invalid sdpMLineIndex assert_equals: usernameFragment expected (object) null but got (undefined) undefined
 
index 9842593..344007d 100644 (file)
@@ -6,6 +6,7 @@
   'use strict';
 
   const candidateString = 'candidate:1905690388 1 udp 2113937151 192.168.0.1 58041 typ host generation 0 ufrag thC8 network-cost 50';
+  const candidateString2 = 'candidate:435653019 2 tcp 1845501695 192.168.0.196 4444 typ srflx raddr www.example.com rport 22222 tcptype active';
   const arbitraryString = '<arbitrary string[0] content>;';
 
   test(t => {
   test(t => {
     const candidate = new RTCIceCandidate({ sdpMid: 'audio' });
 
-    assert_equals(candidate.candidate, '');
-    assert_equals(candidate.sdpMid, 'audio');
-    assert_equals(candidate.sdpMLineIndex, null);
-    assert_equals(candidate.usernameFragment, null);
+    assert_equals(candidate.candidate, '', 'candidate');
+    assert_equals(candidate.sdpMid, 'audio', 'sdpMid');
+    assert_equals(candidate.sdpMLineIndex, null, 'sdpMLineIndex');
+    assert_equals(candidate.usernameFragment, null, 'usernameFragment');
   }, `new RTCIceCandidate({ sdpMid: 'audio' })`);
 
   test(t => {
     const candidate = new RTCIceCandidate({ sdpMLineIndex: 0 });
 
-    assert_equals(candidate.candidate, '');
-    assert_equals(candidate.sdpMid, null);
-    assert_equals(candidate.sdpMLineIndex, 0);
-    assert_equals(candidate.usernameFragment, null);
+    assert_equals(candidate.candidate, '', 'candidate');
+    assert_equals(candidate.sdpMid, null, 'sdpMid');
+    assert_equals(candidate.sdpMLineIndex, 0, 'sdpMLineIndex');
+    assert_equals(candidate.usernameFragment, null, 'usernameFragment');
   }, 'new RTCIceCandidate({ sdpMLineIndex: 0 })');
 
   test(t => {
       sdpMLineIndex: 0
     });
 
-    assert_equals(candidate.candidate, '');
-    assert_equals(candidate.sdpMid, 'audio');
-    assert_equals(candidate.sdpMLineIndex, 0);
-    assert_equals(candidate.usernameFragment, null);
+    assert_equals(candidate.candidate, '', 'candidate');
+    assert_equals(candidate.sdpMid, 'audio', 'sdpMid');
+    assert_equals(candidate.sdpMLineIndex, 0, 'sdpMLineIndex');
+    assert_equals(candidate.usernameFragment, null, 'usernameFragment');
   }, `new RTCIceCandidate({ sdpMid: 'audio', sdpMLineIndex: 0 })`);
 
   test(t => {
       sdpMid: 'audio'
     });
 
-    assert_equals(candidate.candidate, '');
-    assert_equals(candidate.sdpMid, 'audio');
-    assert_equals(candidate.sdpMLineIndex, null);
-    assert_equals(candidate.usernameFragment, null);
+    assert_equals(candidate.candidate, '', 'candidate');
+    assert_equals(candidate.sdpMid, 'audio', 'sdpMid');
+    assert_equals(candidate.sdpMLineIndex, null, 'sdpMLineIndex');
+    assert_equals(candidate.usernameFragment, null, 'usernameFragment');
   }, `new RTCIceCandidate({ candidate: '', sdpMid: 'audio' }`);
 
   test(t => {
       sdpMLineIndex: 0
     });
 
-    assert_equals(candidate.candidate, '');
-    assert_equals(candidate.sdpMid, null);
-    assert_equals(candidate.sdpMLineIndex, 0);
-    assert_equals(candidate.usernameFragment, null);
+    assert_equals(candidate.candidate, '', 'candidate');
+    assert_equals(candidate.sdpMid, null, 'sdpMid', 'sdpMid');
+    assert_equals(candidate.sdpMLineIndex, 0, 'sdpMLineIndex');
+    assert_equals(candidate.usernameFragment, null, 'usernameFragment');
   }, `new RTCIceCandidate({ candidate: '', sdpMLineIndex: 0 }`);
 
   test(t => {
       sdpMid: 'audio'
     });
 
-    assert_equals(candidate.candidate, candidateString);
-    assert_equals(candidate.sdpMid, 'audio');
-    assert_equals(candidate.sdpMLineIndex, null);
-    assert_equals(candidate.usernameFragment, null);
+    assert_equals(candidate.candidate, candidateString, 'candidate');
+    assert_equals(candidate.sdpMid, 'audio', 'sdpMid');
+    assert_equals(candidate.sdpMLineIndex, null, 'sdpMLineIndex');
+    assert_equals(candidate.usernameFragment, null, 'usernameFragment');
   }, 'new RTCIceCandidate({ ... }) with valid candidate string and sdpMid');
 
   test(t =>{
       sdpMid: 'audio'
     });
 
-    assert_equals(candidate.candidate, arbitraryString);
-    assert_equals(candidate.sdpMid, 'audio');
-    assert_equals(candidate.sdpMLineIndex, null);
-    assert_equals(candidate.usernameFragment, null);
+    assert_equals(candidate.candidate, arbitraryString, 'candidate');
+    assert_equals(candidate.sdpMid, 'audio', 'sdpMid');
+    assert_equals(candidate.sdpMLineIndex, null, 'sdpMLineIndex');
+    assert_equals(candidate.usernameFragment, null, 'usernameFragment');
   }, 'new RTCIceCandidate({ ... }) with invalid candidate string and sdpMid');
 
   test(t => {
       usernameFragment: 'test'
     });
 
-    assert_equals(candidate.candidate, candidateString);
-    assert_equals(candidate.sdpMid, 'video');
-    assert_equals(candidate.sdpMLineIndex, 1);
-    assert_equals(candidate.usernameFragment, 'test');
-  }, 'new RTCIceCandidate({ ... }) with non default value for all fields');
+    assert_equals(candidate.candidate, candidateString, 'candidate');
+    assert_equals(candidate.sdpMid, 'video', 'sdpMid');
+    assert_equals(candidate.sdpMLineIndex, 1, 'sdpMLineIndex');
+    assert_equals(candidate.usernameFragment, 'test', 'usernameFragment');
+
+    // The following fields should match those in the candidate field
+    assert_equals(candidate.foundation, '1905690388', 'foundation');
+    assert_equals(candidate.component, 'rtp', 'component');
+    assert_equals(candidate.priority, 2113937151, 'priority');
+    assert_equals(candidate.address, '192.168.0.1', 'address');
+    assert_equals(candidate.protocol, 'udp', 'protocol');
+    assert_equals(candidate.port, 58041, 'port');
+    assert_equals(candidate.type, 'host', 'type');
+    assert_equals(candidate.tcpType, '', 'tcpType');
+    assert_equals(candidate.relatedAddress, null, 'relatedAddress');
+    assert_equals(candidate.relatedPort, null, 'relatedPort');
+  }, 'new RTCIceCandidate({ ... }) with nondefault values for all fields');
 
+  test(t => {
+    const candidate = new RTCIceCandidate({
+      candidate: candidateString2,
+      sdpMid: 'video',
+      sdpMLineIndex: 1,
+      usernameFragment: 'user1'
+    });
+
+    assert_equals(candidate.candidate, candidateString2, 'candidate');
+    assert_equals(candidate.sdpMid, 'video', 'sdpMid');
+    assert_equals(candidate.sdpMLineIndex, 1, 'sdpMLineIndex');
+    assert_equals(candidate.usernameFragment, 'user1', 'usernameFragment');
+
+    // The following fields should match those in the candidate field
+    assert_equals(candidate.foundation, '435653019', 'foundation');
+    assert_equals(candidate.component, 'rtcp', 'component');
+    assert_equals(candidate.priority, 1845501695, 'priority');
+    assert_equals(candidate.address, '192.168.0.196', 'address');
+    assert_equals(candidate.protocol, 'tcp', 'protocol');
+    assert_equals(candidate.port, 4444, 'port');
+    assert_equals(candidate.type, 'srflx', 'type');
+    assert_equals(candidate.tcpType, 'active', 'tcpType');
+    assert_equals(candidate.relatedAddress, 'www.example.com', 'relatedAddress');
+    assert_equals(candidate.relatedPort, 22222, 'relatedPort');
+  }, 'new RTCIceCandidate({ ... }) with nondefault values for all fields, tcp candidate');
 
   test(t => {
     // sdpMid is not validated in RTCIceCandidate
       sdpMid: arbitraryString
     });
 
-    assert_equals(candidate.candidate, '');
-    assert_equals(candidate.sdpMid, arbitraryString);
-    assert_equals(candidate.sdpMLineIndex, null);
-    assert_equals(candidate.usernameFragment, null);
+    assert_equals(candidate.candidate, '', 'candidate');
+    assert_equals(candidate.sdpMid, arbitraryString, 'sdpMid');
+    assert_equals(candidate.sdpMLineIndex, null, 'sdpMLineIndex');
+    assert_equals(candidate.usernameFragment, null, 'usernameFragment');
   }, 'new RTCIceCandidate({ ... }) with invalid sdpMid');
 
 
       sdpMLineIndex: 65535
     });
 
-    assert_equals(candidate.candidate, '');
-    assert_equals(candidate.sdpMid, null);
-    assert_equals(candidate.sdpMLineIndex, 65535);
-    assert_equals(candidate.usernameFragment, null);
+    assert_equals(candidate.candidate, '', 'candidate');
+    assert_equals(candidate.sdpMid, null, 'sdpMid');
+    assert_equals(candidate.sdpMLineIndex, 65535, 'sdpMLineIndex');
+    assert_equals(candidate.usernameFragment, null, 'usernameFragment');
   }, 'new RTCIceCandidate({ ... }) with invalid sdpMLineIndex');
 
 </script>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCIceConnectionState-candidate-pair.https-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCIceConnectionState-candidate-pair.https-expected.txt
new file mode 100644 (file)
index 0000000..3693d32
--- /dev/null
@@ -0,0 +1,5 @@
+
+Harness Error (TIMEOUT), message = null
+
+TIMEOUT On ICE connected, getStats() contains a connected candidate-pair Test timed out
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCIceConnectionState-candidate-pair.https.html b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCIceConnectionState-candidate-pair.https.html
new file mode 100644 (file)
index 0000000..7280d04
--- /dev/null
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>RTCIceConnectionState and RTCIceCandidatePair</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script>
+'use strict';
+
+promise_test(async t => {
+  const caller = new RTCPeerConnection();
+  t.add_cleanup(() => caller.close());
+  const callee = new RTCPeerConnection();
+  t.add_cleanup(() => callee.close());
+
+  const stream = await navigator.mediaDevices.getUserMedia({audio:true});
+  const [track] = stream.getTracks();
+  caller.addTrack(track, stream);
+  exchangeIceCandidates(caller, callee);
+  await doSignalingHandshake(caller, callee);
+  await listenToIceConnected(t, caller);
+
+  const report = await caller.getStats();
+  let succeededPairFound = false;
+  report.forEach(stats => {
+    if (stats.type == 'candidate-pair' && stats.state == 'succeeded')
+      succeededPairFound = true;
+  });
+  assert_true(succeededPairFound, 'A succeeded candidate-pair should exist');
+}, 'On ICE connected, getStats() contains a connected candidate-pair');
+
+</script>
index 6c30f8a..3fadf20 100644 (file)
@@ -1,6 +1,3 @@
-CONSOLE MESSAGE: line 178: TypeError: Type error
-
-Harness Error (FAIL), message = TypeError: Type error
 
 FAIL RTCIceTransport constructor does not throw function is not a constructor (evaluating 'new RTCIceTransport()')
 FAIL RTCIceTransport initial properties are set function is not a constructor (evaluating 'new RTCIceTransport()')
@@ -20,4 +17,15 @@ FAIL start() throws if usernameFragment or password not set function is not a co
 FAIL start() does not transition state to 'checking' if no remote candidates added function is not a constructor (evaluating 'new RTCIceTransport()')
 FAIL start() with default role sets role attribute to 'controlled' function is not a constructor (evaluating 'new RTCIceTransport()')
 FAIL start() sets role attribute to 'controlling' function is not a constructor (evaluating 'new RTCIceTransport()')
+FAIL addRemoteCandidate() throws if closed function is not a constructor (evaluating 'new RTCIceTransport()')
+FAIL addRemoteCandidate() throws on invalid candidate function is not a constructor (evaluating 'new RTCIceTransport()')
+FAIL start() transitions state to 'checking' if one remote candidate had been added function is not a constructor (evaluating 'new RTCIceTransport()')
+FAIL addRemoteCandidate() transitions state to 'checking' if start() had been called before function is not a constructor (evaluating 'new RTCIceTransport()')
+FAIL start() throws if later called with a different role function is not a constructor (evaluating 'new RTCIceTransport()')
+FAIL start() flushes remote candidates and transitions state to 'new' if later called with different remote parameters function is not a constructor (evaluating 'new RTCIceTransport()')
+FAIL Two RTCIceTransports connect to each other promise_test: Unhandled rejection with value: object "TypeError: function is not a constructor (evaluating 'new RTCIceTransport()')"
+FAIL Two RTCIceTransports configured with the controlling role resolve the conflict in band and still connect. promise_test: Unhandled rejection with value: object "TypeError: function is not a constructor (evaluating 'new RTCIceTransport()')"
+FAIL Two RTCIceTransports configured with the controlled role resolve the conflict in band and still connect. promise_test: Unhandled rejection with value: object "TypeError: function is not a constructor (evaluating 'new RTCIceTransport()')"
+FAIL Selected candidate pair changes once the RTCIceTransports connect. promise_test: Unhandled rejection with value: object "TypeError: function is not a constructor (evaluating 'new RTCIceTransport()')"
+FAIL getSelectedCandidatePair() returns null once the RTCIceTransport is stopped. promise_test: Unhandled rejection with value: object "TypeError: function is not a constructor (evaluating 'new RTCIceTransport()')"
 
index 7803bde..206a4bb 100644 (file)
@@ -177,6 +177,7 @@ test(t => {
 
 const candidate1 = new RTCIceCandidate({
   candidate: 'candidate:1 1 udp 2113929471 203.0.113.100 10100 typ host',
+  sdpMid: '',
 });
 
 test(() => {
@@ -191,7 +192,7 @@ test(() => {
   const iceTransport = new RTCIceTransport();
   assert_throws('OperationError',
     () => iceTransport.addRemoteCandidate(
-      new RTCIceCandidate({ candidate: 'invalid' })));
+      new RTCIceCandidate({ candidate: 'invalid', sdpMid: '' })));
   assert_array_equals(iceTransport.getRemoteCandidates(), []);
 }, 'addRemoteCandidate() throws on invalid candidate');
 
@@ -254,14 +255,31 @@ promise_test(async t => {
   ]);
 }, 'Two RTCIceTransports connect to each other');
 
+['controlling', 'controlled'].forEach(role => {
+  promise_test(async t => {
+    const [ localTransport, remoteTransport ] =
+        makeAndGatherTwoIceTransports(t);
+    localTransport.start(remoteTransport.getLocalParameters(), role);
+    remoteTransport.start(localTransport.getLocalParameters(), role);
+    const localWatcher = new EventWatcher(t, localTransport, 'statechange');
+    const remoteWatcher = new EventWatcher(t, remoteTransport, 'statechange');
+    await Promise.all([
+      localWatcher.wait_for('statechange').then(() => {
+        assert_equals(localTransport.state, 'connected');
+      }),
+      remoteWatcher.wait_for('statechange').then(() => {
+        assert_equals(remoteTransport.state, 'connected');
+      }),
+    ]);
+  }, `Two RTCIceTransports configured with the ${role} role resolve the ` +
+      'conflict in band and still connect.');
+});
+
 promise_test(async t => {
-  async function waitForConnectedThenSelectedCandidatePairChange(t, transport,
+  async function waitForSelectedCandidatePairChangeThenConnected(t, transport,
       transportName) {
     const watcher = new EventWatcher(t, transport,
         [ 'statechange', 'selectedcandidatepairchange' ]);
-    await watcher.wait_for('statechange');
-    assert_equals(transport.state, 'connected',
-        `${transportName} state should be 'connected'`);
     await watcher.wait_for('selectedcandidatepairchange');
     const selectedCandidatePair = transport.getSelectedCandidatePair();
     assert_not_equals(selectedCandidatePair, null,
@@ -279,13 +297,16 @@ promise_test(async t => {
                 candidate === selectedCandidatePair.remote.candidate),
         `${transportName} selected candidate pair local should be in the ` +
         'list of remote candidates');
+    await watcher.wait_for('statechange');
+    assert_equals(transport.state, 'connected',
+        `${transportName} state should be 'connected'`);
   }
   const [ localTransport, remoteTransport ] =
       makeGatherAndStartTwoIceTransports(t);
   await Promise.all([
-    waitForConnectedThenSelectedCandidatePairChange(t, localTransport,
+    waitForSelectedCandidatePairChangeThenConnected(t, localTransport,
         'local transport'),
-    waitForConnectedThenSelectedCandidatePairChange(t, remoteTransport,
+    waitForSelectedCandidatePairChangeThenConnected(t, remoteTransport,
         'remote transport'),
   ]);
 }, 'Selected candidate pair changes once the RTCIceTransports connect.');
index 342a349..d099adb 100644 (file)
@@ -3,8 +3,8 @@ FAIL Add null candidate should reject with TypeError assert_unreached: Should ha
 FAIL Add ICE candidate before setting remote description should reject with InvalidStateError assert_unreached: Should have rejected: undefined Reached unreachable code
 PASS Add ICE candidate after setting remote description should succeed 
 PASS Add ICE candidate with RTCIceCandidate should succeed 
-FAIL Add candidate with only valid sdpMid should succeed promise_test: Unhandled rejection with value: object "OperationError: Expect line: candidate:<candidate-str>"
-FAIL Add candidate with only valid sdpMLineIndex should succeed promise_test: Unhandled rejection with value: object "OperationError: Expect line: candidate:<candidate-str>"
+PASS Add candidate with only valid sdpMid should succeed 
+PASS Add candidate with only valid sdpMLineIndex should succeed 
 FAIL addIceCandidate with first sdpMid and sdpMLineIndex add candidate to first media stream assert_true: Expect candidate line to be found between media lines m=audio and m=video expected true got false
 FAIL addIceCandidate with second sdpMid and sdpMLineIndex should add candidate to second media stream assert_true: Expect candidate line to be found after media line m=video expected true got false
 FAIL Add candidate for first media stream with null usernameFragment should add candidate to first media stream assert_true: Expect candidate line to be found between media lines m=audio and m=video expected true got false
index 1dd1350..2283c68 100644 (file)
@@ -61,9 +61,9 @@ a=rtcp-rsize
   const sessionDesc = { type: 'offer', sdp };
 
   // valid candidate attributes
-  const sdpMid = 'a1';
-  const sdpMLineIndex = 0;
-  const usernameFragment = 'ETEn';
+  const sdpMid1 = 'a1';
+  const sdpMLineIndex1 = 0;
+  const usernameFragment1 = 'ETEn';
 
   const sdpMid2 = 'v1';
   const sdpMLineIndex2 = 1;
@@ -138,7 +138,9 @@ a=rtcp-rsize
     return promise_rejects(t, 'InvalidStateError',
       pc.addIceCandidate({
         candidate: candidateStr1,
-        sdpMid, sdpMLineIndex, usernameFragment
+        sdpMid: sdpMid1,
+        sdpMLineIndex: sdpMLineIndex1,
+        usernameFragment: usernameFragment1
       }));
   }, 'Add ICE candidate before setting remote description should reject with InvalidStateError');
 
@@ -153,7 +155,9 @@ a=rtcp-rsize
     return pc.setRemoteDescription(sessionDesc)
     .then(() => pc.addIceCandidate({
       candidate: candidateStr1,
-      sdpMid, sdpMLineIndex, usernameFragment
+      sdpMid: sdpMid1,
+      sdpMLineIndex: sdpMLineIndex1,
+      usernameFragement: usernameFragment1
     }));
   }, 'Add ICE candidate after setting remote description should succeed');
 
@@ -165,7 +169,9 @@ a=rtcp-rsize
     return pc.setRemoteDescription(sessionDesc)
     .then(() => pc.addIceCandidate(new RTCIceCandidate({
       candidate: candidateStr1,
-      sdpMid, sdpMLineIndex, usernameFragment
+      sdpMid: sdpMid1,
+      sdpMLineIndex: sdpMLineIndex1,
+      usernameFragement: usernameFragment1
     })));
   }, 'Add ICE candidate with RTCIceCandidate should succeed');
 
@@ -175,7 +181,9 @@ a=rtcp-rsize
     t.add_cleanup(() => pc.close());
 
     return pc.setRemoteDescription(sessionDesc)
-      .then(() => pc.addIceCandidate({ sdpMid }));
+      .then(() => pc.addIceCandidate({
+        candidate: candidateStr1,
+        sdpMid: sdpMid1 }));
   }, 'Add candidate with only valid sdpMid should succeed');
 
   promise_test(t => {
@@ -184,7 +192,9 @@ a=rtcp-rsize
     t.add_cleanup(() => pc.close());
 
     return pc.setRemoteDescription(sessionDesc)
-      .then(() => pc.addIceCandidate({ sdpMLineIndex }));
+      .then(() => pc.addIceCandidate({
+        candidate: candidateStr1,
+        sdpMLineIndex: sdpMLineIndex1 }));
   }, 'Add candidate with only valid sdpMLineIndex should succeed');
 
   /*
@@ -206,7 +216,9 @@ a=rtcp-rsize
     return pc.setRemoteDescription(sessionDesc)
     .then(() => pc.addIceCandidate({
       candidate: candidateStr1,
-      sdpMid, sdpMLineIndex, usernameFragment
+      sdpMid: sdpMid1,
+      sdpMLineIndex: sdpMLineIndex1,
+      usernameFragement: usernameFragment1
     }))
     .then(() => {
       assert_candidate_line_between(pc.remoteDescription.sdp,
@@ -240,8 +252,9 @@ a=rtcp-rsize
     return pc.setRemoteDescription(sessionDesc)
     .then(() => pc.addIceCandidate({
       candidate: candidateStr1,
-      sdpMid, sdpMLineIndex,
-      usernameFragment: null
+      sdpMid: sdpMid1,
+      sdpMLineIndex: sdpMLineIndex1,
+      ufrag: null
     }))
     .then(() => {
       assert_candidate_line_between(pc.remoteDescription.sdp,
@@ -257,7 +270,9 @@ a=rtcp-rsize
     return pc.setRemoteDescription(sessionDesc)
     .then(() => pc.addIceCandidate({
       candidate: candidateStr1,
-      sdpMid, sdpMLineIndex, usernameFragment
+      sdpMid: sdpMid1,
+      sdpMLineIndex: sdpMLineIndex1,
+      usernameFragement: usernameFragment1
     }))
     .then(() => pc.addIceCandidate({
       candidate: candidateStr2,
@@ -296,12 +311,15 @@ a=rtcp-rsize
     return pc.setRemoteDescription(sessionDesc)
     .then(() => pc.addIceCandidate({
       candidate: candidateStr1,
-      sdpMid, sdpMLineIndex, usernameFragment
+      sdpMid: sdpMid1,
+      sdpMLineIndex: sdpMLineIndex1,
+      usernameFragement: usernameFragment1
     }))
     .then(() => pc.addIceCandidate({
       candidate: '',
-      sdpMid, sdpMLineIndex,
-      usernameFragment
+      sdpMid: sdpMid1,
+      sdpMLineIndex: sdpMLineIndex1,
+      usernameFragement: usernameFragment1
     }))
     .then(() => {
       assert_candidate_line_between(pc.remoteDescription.sdp,
@@ -404,7 +422,9 @@ a=rtcp-rsize
       promise_rejects(t, 'OperationError',
         pc.addIceCandidate({
           candidate: candidateStr1,
-          sdpMid: 'invalid', sdpMLineIndex, usernameFragment
+          sdpMid: 'invalid',
+          sdpMLineIndex: sdpMLineIndex1,
+          usernameFragement: usernameFragment1
         })));
   }, 'Add candidate with invalid sdpMid should reject with OperationError');
 
@@ -427,7 +447,7 @@ a=rtcp-rsize
         pc.addIceCandidate({
           candidate: candidateStr1,
           sdpMLineIndex: 2,
-          usernameFragment
+          usernameFragement: usernameFragment1
         })));
   }, 'Add candidate with invalid sdpMLineIndex should reject with OperationError');
 
@@ -441,9 +461,9 @@ a=rtcp-rsize
     return pc.setRemoteDescription(sessionDesc)
     .then(() => pc.addIceCandidate({
       candidate: candidateStr1,
-      sdpMid,
+      sdpMid: sdpMid1,
       sdpMLineIndex: 2,
-      usernameFragment
+      usernameFragement: usernameFragment1
     }));
   }, 'Invalid sdpMLineIndex should be ignored if valid sdpMid is provided');
 
@@ -482,8 +502,9 @@ a=rtcp-rsize
       promise_rejects(t, 'OperationError',
         pc.addIceCandidate({
           candidate: candidateStr1,
-          sdpMid, sdpMLineIndex,
-          usernameFragment: 'invalid'
+          sdpMid: sdpMid1,
+          sdpMLineIndex: sdpMLineIndex1,
+          ufrag: 'invalid'
         })));
   }, 'Add candidate with invalid usernameFragment should reject with OperationError');
 
@@ -504,7 +525,9 @@ a=rtcp-rsize
       promise_rejects(t, 'OperationError',
         pc.addIceCandidate({
           candidate: invalidCandidateStr,
-          sdpMid, sdpMLineIndex, usernameFragment
+          sdpMid: sdpMid1,
+          sdpMLineIndex: sdpMLineIndex1,
+          usernameFragement: usernameFragment1
         })));
   }, 'Add candidate with invalid candidate string should reject with OperationError');
 
@@ -520,7 +543,7 @@ a=rtcp-rsize
           candidate: candidateStr2,
           sdpMid: sdpMid2,
           sdpMLineIndex: sdpMLineIndex2,
-          usernameFragment
+          usernameFragement: usernameFragment1
         })));
   }, 'Add candidate with sdpMid belonging to different usernameFragment should reject with OperationError');
 
index ebb51fc..48f59fc 100644 (file)
@@ -1,4 +1,6 @@
 
 PASS Initial connectionState should be new 
-FAIL connection with one data channel should eventually have connected connection state undefined is not an object (evaluating 'sctpTransport.transport')
+PASS Closing the connection should set connectionState to closed 
+PASS connection with one data channel should eventually have connected connection state 
+FAIL connection with one data channel should eventually have transports in connected state undefined is not an object (evaluating 'sctpTransport.transport')
 
index 7ef7e4a..a461bf1 100644 (file)
@@ -6,7 +6,6 @@
 <script src="RTCPeerConnection-helper.js"></script>
 <script>
   'use strict';
-
   // Test is based on the following editor draft:
   // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.htm
 
     assert_equals(pc.connectionState, 'new');
   }, 'Initial connectionState should be new');
 
+  test(t => {
+    const pc = new RTCPeerConnection();
+    pc.close();
+    assert_equals(pc.connectionState, 'closed');
+  }, 'Closing the connection should set connectionState to closed');
+
   /*
     4.4.3.  RTCPeerConnectionState Enum
       connected
         pairs and found a connection. If consent checks [RFC7675] subsequently
         fail on all successful candidate pairs, the state transitions to "failed".
    */
+
   async_test(t => {
     const pc1 = new RTCPeerConnection();
     t.add_cleanup(() => pc1.close());
     const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc2.close());
+
+    let had_connecting = false;
+
+    const onConnectionStateChange = t.step_func(() => {
+      const {connectionState} = pc1;
+      if (connectionState === 'connecting') {
+        had_connecting = true;
+      } else if (connectionState === 'connected') {
+        assert_true(had_connecting, "state should pass connecting before reaching connected");
+        t.done();
+      }
+    });
+
+    pc1.createDataChannel('test');
+
+    pc1.addEventListener('connectionstatechange', onConnectionStateChange);
+
+    exchangeIceCandidates(pc1, pc2);
+    doSignalingHandshake(pc1, pc2);
+  }, 'connection with one data channel should eventually have connected connection state');
 
+  async_test(t => {
+    const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
+    const pc2 = new RTCPeerConnection();
     t.add_cleanup(() => pc2.close());
 
     const onConnectionStateChange = t.step_func(() => {
-      const { connectionState } = pc1;
-      if(connectionState === 'connected') {
+      const {connectionState} = pc1;
+      if (connectionState === 'connected') {
         const sctpTransport = pc1.sctp;
 
         const dtlsTransport = sctpTransport.transport;
 
     pc1.createDataChannel('test');
 
-    assert_equals(pc1.onconnectionstatechange, null,
-      'Expect connection to have connectionstatechange event');
-
     pc1.addEventListener('connectionstatechange', onConnectionStateChange);
 
     exchangeIceCandidates(pc1, pc2);
     doSignalingHandshake(pc1, pc2);
-  }, 'connection with one data channel should eventually have connected connection state');
+  }, 'connection with one data channel should eventually have transports in connected state');
 
   /*
     TODO
index 5c73d84..270ae91 100644 (file)
@@ -1,4 +1,6 @@
 
+Harness Error (TIMEOUT), message = null
+
 PASS getStats() with no argument should succeed 
 PASS getStats(null) should succeed 
 FAIL getStats() with track not added to connection should reject with InvalidAccessError assert_unreached: Should have rejected: undefined Reached unreachable code
@@ -9,7 +11,7 @@ FAIL getStats() with track associated with both sender and receiver should rejec
 PASS getStats() with no argument should return stats report containing peer-connection stats on an empty PC 
 FAIL getStats() with no argument should return stats report containing peer-connection stats and outbound-track-stats assert_true: Expect statsReport to contain stats object of type outbound-rtp expected true got false
 FAIL getStats() with no argument should return stats for no-stream tracks assert_true: Expect statsReport to contain stats object of type outbound-rtp expected true got false
-FAIL getStats() on track associated with RtpSender should return stats report containing outbound-rtp stats assert_true: Expect statsReport to contain stats object of type outbound-rtp expected true got false
-FAIL getStats() on track associated with RtpReceiver should return stats report containing inbound-rtp stats assert_true: Expect statsReport to contain stats object of type inbound-rtp expected true got false
+FAIL getStats() on track associated with RtpSender should return stats report containing outbound-rtp stats assert_equals: Expect dictionary.ip to be string expected "string" but got "undefined"
+TIMEOUT getStats() on track associated with RtpReceiver should return stats report containing inbound-rtp stats Test timed out
 FAIL getStats() with connected peer connections having tracks and data channel should return all mandatory to implement stats assert_unreached: test failed with error: Error: assert_true: Expect dictionary.dataChannelIdentifier to be integer expected true got false Reached unreachable code
 
index 247402b..c2c4e8e 100644 (file)
         - All stats objects referenced directly or indirectly by the RTCOutboundRTPStreamStats
           objects added.
    */
-  promise_test(t => {
-    const pc = new RTCPeerConnection();
-    t.add_cleanup(() => pc.close());
-    return getTrackFromUserMedia('audio')
-    .then(([track, mediaStream]) => {
-      pc.addTrack(track, mediaStream);
-
-      return pc.getStats(track)
-      .then(statsReport => {
-        validateStatsReport(statsReport);
-        assert_stats_report_has_stats(statsReport, ['outbound-rtp']);
-      });
-    });
+  promise_test(async t => {
+    const pc = createPeerConnectionWithCleanup(t);
+    const pc2 = createPeerConnectionWithCleanup(t);
+
+    let [track, mediaStream] = await getTrackFromUserMedia('audio');
+    pc.addTrack(track, mediaStream);
+    exchangeIceCandidates(pc, pc2);
+    await doSignalingHandshake(pc, pc2);
+    await listenToIceConnected(pc);
+    const stats = await pc.getStats(track);
+    validateStatsReport(stats);
+    assert_stats_report_has_stats(stats, ['outbound-rtp']);
   }, `getStats() on track associated with RtpSender should return stats report containing outbound-rtp stats`);
 
 
         - All stats objects referenced directly or indirectly by the RTCInboundRTPStreamStats
           added.
    */
-  promise_test(t => {
-    const pc = new RTCPeerConnection();
-    t.add_cleanup(() => pc.close());
-    const transceiver = pc.addTransceiver('audio');
-
-    return pc.getStats(transceiver.receiver.track)
-    .then(statsReport => {
-      validateStatsReport(statsReport);
-      assert_stats_report_has_stats(statsReport, ['inbound-rtp']);
+  promise_test(async t => {
+    const pc = createPeerConnectionWithCleanup(t);
+    const pc2 = createPeerConnectionWithCleanup(t);
+
+    let [track, mediaStream] = await getTrackFromUserMedia('audio');
+    pc.addTrack(track, mediaStream);
+    exchangeIceCandidates(pc, pc2);
+    await doSignalingHandshake(pc, pc2);
+    await new Promise(resolve => {
+      pc2.getReceivers()[0].track.addEventListener('unmute', resolve);
     });
+    const stats = await pc2.getStats(track);
+    validateStatsReport(stats);
+    assert_stats_report_has_stats(stats, ['inbound-rtp']);
   }, `getStats() on track associated with RtpReceiver should return stats report containing inbound-rtp stats`);
 
   /*
index df277ff..0b25df2 100644 (file)
@@ -182,16 +182,76 @@ function exchangeIceCandidates(pc1, pc2) {
 }
 
 // Helper function for doing one round of offer/answer exchange
-// betweeen two local peer connections
-function doSignalingHandshake(localPc, remotePc) {
-  return localPc.createOffer()
-  .then(offer => Promise.all([
-    localPc.setLocalDescription(offer),
-    remotePc.setRemoteDescription(offer)]))
-  .then(() => remotePc.createAnswer())
-  .then(answer => Promise.all([
-    remotePc.setLocalDescription(answer),
-    localPc.setRemoteDescription(answer)]))
+// between two local peer connections
+async function doSignalingHandshake(localPc, remotePc, options={}) {
+  let offer = await localPc.createOffer();
+  // Modify offer if callback has been provided
+  if (options.modifyOffer) {
+    offer = await options.modifyOffer(offer);
+  }
+
+  // Apply offer
+  await localPc.setLocalDescription(offer);
+  await remotePc.setRemoteDescription(offer);
+
+  let answer = await remotePc.createAnswer();
+  // Modify answer if callback has been provided
+  if (options.modifyAnswer) {
+    answer = await options.modifyAnswer(answer);
+  }
+
+  // Apply answer
+  await remotePc.setLocalDescription(answer);
+  await localPc.setRemoteDescription(answer);
+}
+
+// Returns a promise that resolves when |pc.iceConnectionState| is 'connected'
+// or 'completed'.
+function listenToIceConnected(pc) {
+  return new Promise((resolve) => {
+    function isConnected(pc) {
+      return pc.iceConnectionState == 'connected' ||
+            pc.iceConnectionState == 'completed';
+    }
+    if (isConnected(pc)) {
+      resolve();
+      return;
+    }
+    pc.oniceconnectionstatechange = () => {
+      if (isConnected(pc))
+        resolve();
+    };
+  });
+}
+
+// Returns a promise that resolves when |pc.connectionState| is 'connected'.
+function listenToConnected(pc) {
+  return new Promise((resolve) => {
+    if (pc.connectionState == 'connected') {
+      resolve();
+      return;
+    }
+    pc.onconnectionstatechange = () => {
+      if (pc.connectionState == 'connected')
+        resolve();
+    };
+  });
+}
+
+// Resolves when RTP packets have been received.
+function listenForSSRCs(t, receiver) {
+  return new Promise((resolve) => {
+    function listen() {
+      const ssrcs = receiver.getSynchronizationSources();
+      assert_true(ssrcs != undefined);
+      if (ssrcs.length > 0) {
+        resolve(ssrcs);
+        return;
+      }
+      t.step_timeout(listen, 0);
+    };
+    listen();
+  });
 }
 
 // Helper function to create a pair of connected data channel.
@@ -251,6 +311,8 @@ function createDataChannelPair(
 
 // Wait for RTP and RTCP stats to arrive
 async function waitForRtpAndRtcpStats(pc) {
+  // If remote stats are never reported, return after 5 seconds.
+  const startTime = performance.now();
   while (true) {
     const report = await pc.getStats();
     const stats = [...report.values()].filter(({type}) => type.endsWith("bound-rtp"));
@@ -259,6 +321,9 @@ async function waitForRtpAndRtcpStats(pc) {
     if (stats.length && stats.every(({localId, remoteId}) => localId || remoteId)) {
       break;
     }
+    if (performance.now() > startTime + 5000) {
+      break;
+    }
   }
 }
 
@@ -290,23 +355,25 @@ function blobToArrayBuffer(blob) {
   });
 }
 
-// Assert that two ArrayBuffer objects have the same byte values
-function assert_equals_array_buffer(buffer1, buffer2) {
-  assert_true(buffer1 instanceof ArrayBuffer,
-    'Expect buffer to be instance of ArrayBuffer');
-
-  assert_true(buffer2 instanceof ArrayBuffer,
-    'Expect buffer to be instance of ArrayBuffer');
+// Assert that two TypedArray or ArrayBuffer objects have the same byte values
+function assert_equals_typed_array(array1, array2) {
+  const [view1, view2] = [array1, array2].map((array) => {
+    if (array instanceof ArrayBuffer) {
+      return new DataView(array);
+    } else {
+      assert_true(array.buffer instanceof ArrayBuffer,
+        'Expect buffer to be instance of ArrayBuffer');
+      return new DataView(array.buffer, array.byteOffset, array.byteLength);
+    }
+  });
 
-  assert_equals(buffer1.byteLength, buffer2.byteLength,
-    'Expect both array buffers to be of the same byte length');
+  assert_equals(view1.byteLength, view2.byteLength,
+    'Expect both arrays to be of the same byte length');
 
-  const byteLength = buffer1.byteLength;
-  const byteArray1 = new Uint8Array(buffer1);
-  const byteArray2 = new Uint8Array(buffer2);
+  const byteLength = view1.byteLength;
 
-  for(let i=0; i<byteLength; i++) {
-    assert_equals(byteArray1[i], byteArray2[i],
+  for (let i = 0; i < byteLength; ++i) {
+    assert_equals(view1.getUint8(i), view2.getUint8(i),
       `Expect byte at buffer position ${i} to be equal`);
   }
 }
@@ -473,18 +540,45 @@ async function exchangeOfferAndListenToOntrack(t, caller, callee) {
   return ontrackPromise;
 }
 
-// The resolver has a |promise| that can be resolved or rejected using |resolve|
+// The resolver extends a |promise| that can be resolved or rejected using |resolve|
 // or |reject|.
-class Resolver {
-  constructor() {
-    let promiseResolve;
-    let promiseReject;
-    this.promise = new Promise(function(resolve, reject) {
-      promiseResolve = resolve;
-      promiseReject = reject;
+class Resolver extends Promise {
+  constructor(executor) {
+    let resolve, reject;
+    super((resolve_, reject_) => {
+      resolve = resolve_;
+      reject = reject_;
+      if (executor) {
+        return executor(resolve_, reject_);
+      }
     });
-    this.resolve = promiseResolve;
-    this.reject = promiseReject;
+
+    this._done = false;
+    this._resolve = resolve;
+    this._reject = reject;
+  }
+
+  /**
+   * Return whether the promise is done (resolved or rejected).
+   */
+  get done() {
+    return this._done;
+  }
+
+  /**
+   * Resolve the promise.
+   */
+  resolve(...args) {
+    this._done = true;
+    return this._resolve(...args);
+  }
+
+  /**
+   * Reject the promise.
+   */
+  reject(...args) {
+    this._done = true;
+    return this._reject(...args);
   }
 }
 
@@ -507,7 +601,7 @@ function createPeerConnectionWithCleanup(t) {
 async function createTrackAndStreamWithCleanup(t, kind = 'audio') {
   let constraints = {};
   constraints[kind] = true;
-  const stream = await navigator.mediaDevices.getUserMedia(constraints);
+  const stream = await getNoiseStream(constraints);
   const [track] = stream.getTracks();
   t.add_cleanup(() => track.stop());
   return [track, stream];
@@ -521,3 +615,23 @@ function findTransceiverForSender(pc, sender) {
   }
   return null;
 }
+
+// Contains a set of values and will yell at you if you try to add a value twice.
+class UniqueSet extends Set {
+  constructor(items) {
+    super();
+    if (items !== undefined) {
+      for (const item of items) {
+        this.add(item);
+      }
+    }
+  }
+
+  add(value, message) {
+    if (message === undefined) {
+      message = `Value '${value}' needs to be unique but it is already in the set`;
+    }
+    assert_true(!this.has(value), message);
+    super.add(value);
+  }
+}
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-iceConnectionState.https-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-iceConnectionState.https-expected.txt
new file mode 100644 (file)
index 0000000..0247389
--- /dev/null
@@ -0,0 +1,7 @@
+
+PASS Initial iceConnectionState should be new 
+PASS Closing the connection should set iceConnectionState to closed 
+PASS connection with one data channel should eventually have connected or completed connection state 
+FAIL connection with one data channel should eventually have connected connection state undefined is not an object (evaluating 'pc1.sctp.transport')
+PASS ICE can connect in a recvonly usecase 
+
     assert_equals(pc.iceConnectionState, 'new');
   }, 'Initial iceConnectionState should be new');
 
+  test(t => {
+    const pc = new RTCPeerConnection();
+    pc.close();
+    assert_equals(pc.iceConnectionState, 'closed');
+  }, 'Closing the connection should set iceConnectionState to closed');
+
   /*
     4.4.4 RTCIceConnectionState Enum
       checking
     const pc1 = new RTCPeerConnection();
     t.add_cleanup(() => pc1.close());
     const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc2.close());
+
+    let had_checking = false;
+
+    const onIceConnectionStateChange = t.step_func(() => {
+      const {iceConnectionState} = pc1;
+      if (iceConnectionState === 'checking') {
+        had_checking = true;
+      } else if (iceConnectionState === 'connected' ||
+                 iceConnectionState === 'completed') {
+        assert_true(had_checking, 'state should pass checking before' +
+                                  ' reaching connected or completed');
+        t.done();
+      }
+    });
+
+    pc1.createDataChannel('test');
+
+    pc1.addEventListener('iceconnectionstatechange', onIceConnectionStateChange);
+
+    exchangeIceCandidates(pc1, pc2);
+    doSignalingHandshake(pc1, pc2);
+  }, 'connection with one data channel should eventually have connected or ' +
+     'completed connection state');
+
+async_test(t => {
+    const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
+    const pc2 = new RTCPeerConnection();
 
     t.add_cleanup(() => pc2.close());
 
         const iceTransport = pc1.sctp.transport.transport;
 
         assert_equals(iceTransport.state, 'checking',
-          'Expect ICE transport to be in checking state when iceConnectionState is checking');
+          'Expect ICE transport to be in checking state when' +
+          ' iceConnectionState is checking');
 
       } else if(iceConnectionState === 'connected') {
         const iceTransport = pc1.sctp.transport.transport;
 
         assert_equals(iceTransport.state, 'connected',
-          'Expect ICE transport to be in connected state when iceConnectionState is connected');
+          'Expect ICE transport to be in connected state when' +
+          ' iceConnectionState is connected');
 
       } else if(iceConnectionState === 'completed') {
         const iceTransport = pc1.sctp.transport.transport;
 
         assert_equals(iceTransport.state, 'completed',
-          'Expect ICE transport to be in connected state when iceConnectionState is completed');
+          'Expect ICE transport to be in connected state when' +
+          ' iceConnectionState is completed');
       }
     });
 
 
     exchangeIceCandidates(pc1, pc2);
     doSignalingHandshake(pc1, pc2);
-  }, 'connection with one data channel should eventually have connected connection state');
+  }, 'connection with one data channel should eventually ' +
+     'have connected connection state');
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+
+    caller.addTransceiver('audio', {direction:'recvonly'});
+    const stream = await navigator.mediaDevices.getUserMedia({audio:true});
+    const [track] = stream.getTracks();
+    callee.addTrack(track, stream);
+    exchangeIceCandidates(caller, callee);
+    await doSignalingHandshake(caller, callee);
+
+    assert_equals(caller.getTransceivers().length, 1);
+    const [transceiver] = caller.getTransceivers();
+    assert_equals(transceiver.currentDirection, 'recvonly');
+
+    await listenToIceConnected(caller);
+  }, 'ICE can connect in a recvonly usecase');
 
   /*
     TODO
index b998aba..79ace31 100644 (file)
@@ -1,7 +1,5 @@
 
-Harness Error (TIMEOUT), message = null
-
 PASS Initial iceGatheringState should be new 
-TIMEOUT iceGatheringState should eventually become complete after setLocalDescription Test timed out
+PASS iceGatheringState should eventually become complete after setLocalDescription 
 FAIL connection with one data channel should eventually have connected connection state undefined is not an object (evaluating 'pc2.sctp.transport')
 
index fb9e514..e6d8d06 100644 (file)
@@ -11,8 +11,9 @@
   // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
 
   // The following helper functions are called from RTCPeerConnection-helper.js:
-  // exchangeIceCandidates
   // doSignalingHandshake
+  // exchangeIceCandidates
+  // generateAudioReceiveOnlyOffer
 
   /*
     4.3.2.  Interface Definition
@@ -72,7 +73,7 @@
 
     pc.addEventListener('icegatheringstatechange', onIceGatheringStateChange);
 
-    pc.createOffer({ offerToReceiveAudio: true })
+    generateAudioReceiveOnlyOffer(pc)
     .then(offer => pc.setLocalDescription(offer))
     .then(err => t.step_func(err =>
       assert_unreached(`Unhandled rejection ${err.name}: ${err.message}`)));
index a812574..7755d8f 100644 (file)
@@ -6,6 +6,6 @@ PASS calling createDataChannel twice should fire negotiationneeded event once
 PASS addTransceiver() should fire negotiationneeded event 
 FAIL Calling addTransceiver() twice should fire negotiationneeded event once assert_unreached: Pending promise should never be resolved. Instead it is fulfilled with: [object Object] Reached unreachable code
 FAIL Calling both addTransceiver() and createDataChannel() should fire negotiationneeded event once assert_unreached: Pending promise should never be resolved. Instead it is fulfilled with: [object Object] Reached unreachable code
-PASS negotiationneeded event should not fire if signaling state is not stable 
+FAIL negotiationneeded event should not fire if signaling state is not stable assert_unreached: Pending promise should never be resolved. Instead it is rejected with: Error: assert_equals: expected "have-local-offer" but got "stable" Reached unreachable code
 TIMEOUT negotiationneeded event should fire only after signaling state go back to stable Test timed out
 
index 8f9ea2b..f7bf8bd 100644 (file)
@@ -12,6 +12,7 @@
 
   // The following helper functions are called from RTCPeerConnection-helper.js:
   //   generateAnswer
+  //   generateAudioReceiveOnlyOffer
   //   test_never_resolve
 
   // Listen to the negotiationneeded event on a peer connection
    */
   test_never_resolve(t => {
     const pc = new RTCPeerConnection();
-    const negotiated = awaitNegotiation(pc);
+    let negotiated;
 
-    return pc.createOffer({ offerToReceiveAudio: true })
-    .then(offer => pc.setLocalDescription(offer))
+    return generateAudioReceiveOnlyOffer(pc)
+    .then(offer => {
+      pc.setLocalDescription(offer);
+      negotiated = awaitNegotiation(pc);
+    })
     .then(() => negotiated)
     .then(({nextPromise}) => {
       assert_equals(pc.signalingState, 'have-local-offer');
         2.  If connection's [[NegotiationNeeded]] slot is false, abort these steps.
         3.  Fire a simple event named negotiationneeded at connection.
    */
-  promise_test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
 
     t.add_cleanup(() => pc.close());
 
-    return assert_first_promise_fulfill_after_second(
-      awaitNegotiation(pc),
-      pc.createOffer({ offerToReceiveAudio: true })
-      .then(offer =>
-        pc.setLocalDescription(offer)
-        .then(() => {
-          pc.createDataChannel('test');
-          return generateAnswer(offer);
-        }))
-      .then(answer => pc.setRemoteDescription(answer)),
-      'Expect negotiationneeded promise to resolve after pc has set remote answer and go back to stable state');
+    pc.addTransceiver('audio');
+    const offer = await pc.createOffer();
+    await pc.setLocalDescription(offer);
+    let fired = false;
+    pc.onnegotiationneeded = e => fired = true;
+    pc.createDataChannel('test');
+    const answer = await generateAnswer(offer);
+    await pc.setRemoteDescription(answer);
+    assert_false(fired, "negotiationneeded should not fire until the next iteration of the event loop after returning to stable");
+    await new Promise(resolve => pc.onnegotiationneeded = resolve);
   }, 'negotiationneeded event should fire only after signaling state go back to stable');
 
   /*
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-onsignalingstatechanged.https-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-onsignalingstatechanged.https-expected.txt
new file mode 100644 (file)
index 0000000..d709704
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS RTCPeerConnection onsignalingstatechanged 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-onsignalingstatechanged.https.html b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-onsignalingstatechanged.https.html
new file mode 100644 (file)
index 0000000..fc3182d
--- /dev/null
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCPeerConnection onsignalingstatechanged</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script>
+
+promise_test(async t => {
+  const [track] = (await navigator.mediaDevices.getUserMedia({video: true})).getTracks();
+  t.add_cleanup(() => track.stop());
+  const pc1 = new RTCPeerConnection();
+  t.add_cleanup(() => pc1.close());
+  const pc2 = new RTCPeerConnection();
+  t.add_cleanup(() => pc2.close());
+  pc1.addTrack(track, new MediaStream());
+  await pc1.setLocalDescription(await pc1.createOffer());
+  await pc2.setRemoteDescription(pc1.localDescription);
+  const signalingStateChangeResolver = new Resolver();
+  pc2.onsignalingstatechange = t.step_func(e => {
+    const transceiver = pc2.getTransceivers()[0];
+    assert_equals(pc2.signalingState, "stable");
+    assert_equals(transceiver.currentDirection, "recvonly");
+    signalingStateChangeResolver.resolve();
+  });
+  await pc2.setLocalDescription(await pc2.createAnswer());
+  await signalingStateChangeResolver;
+});
+
+</script>
index b951790..458a4fb 100644 (file)
@@ -2,7 +2,7 @@
 Harness Error (TIMEOUT), message = null
 
 FAIL ontrack: track goes from muted to unmuted assert_true: track is muted in ontrack expected true got false
-PASS Changing transceiver direction to 'inactive' mutes the remote track 
-PASS Changing transceiver direction to 'sendrecv' unmutes the remote track 
-TIMEOUT pc.close() mutes remote tracks Test timed out
+TIMEOUT Changing transceiver direction to 'inactive' mutes the remote track Test timed out
+NOTRUN Changing transceiver direction to 'sendrecv' unmutes the remote track 
+NOTRUN pc.close() mutes remote tracks 
 
index 56fe761..c4c6b4f 100644 (file)
@@ -39,7 +39,7 @@ promise_test(async t => {
     });
   });
   await exchangeOfferAnswer(pc1, pc2);
-  await unmuteResolver.promise;
+  await unmuteResolver;
 }, 'ontrack: track goes from muted to unmuted');
 
 promise_test(async t => {
@@ -50,9 +50,13 @@ promise_test(async t => {
   exchangeIceCandidates(pc1, pc2);
 
   const e = await exchangeOfferAndListenToOntrack(t, pc1, pc2);
+  // Need to wait for the initial unmute event before renegotiating, otherwise
+  // there will be no transition from unmuted->muted.
+  const muteWatcher = new EventWatcher(t, e.track, ['mute', 'unmute']);
+  const unmutePromise = muteWatcher.wait_for('unmute');
   await exchangeAnswer(pc1, pc2);
+  await unmutePromise;
 
-  const muteWatcher = new EventWatcher(t, e.track, ['mute']);
   const mutePromise = muteWatcher.wait_for('mute');
   localTransceiver.direction = 'inactive';
   await exchangeOfferAnswer(pc1, pc2);
@@ -88,8 +92,13 @@ promise_test(async t => {
   exchangeIceCandidates(pc1, pc2);
 
   const e = await exchangeOfferAndListenToOntrack(t, pc1, pc2);
+  // Need to wait for the initial unmute event before closing, otherwise
+  // there will be no transition from unmuted->muted.
+  const muteWatcher = new EventWatcher(t, e.track, ['mute', 'unmute']);
+  const unmutePromise = muteWatcher.wait_for('unmute');
   await exchangeAnswer(pc1, pc2);
-  const muteWatcher = new EventWatcher(t, e.track, ['mute']);
+  await unmutePromise;
+
   const mutePromise = muteWatcher.wait_for('mute');
   pc2.close();
   await mutePromise;
index c98dbdb..ffb3fec 100644 (file)
@@ -61,6 +61,7 @@
 
   promise_test(async t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     const stream = await navigator.mediaDevices.getUserMedia({audio: true});
     t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
     const [track] = stream.getTracks();
@@ -92,6 +93,7 @@
    */
   promise_test(async t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     const stream = await navigator.mediaDevices.getUserMedia({audio: true});
     t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
     const [track] = stream.getTracks();
     const { sender } = transceiver;
 
     const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc2.close());
     assert_throws('InvalidAccessError', () => pc2.removeTrack(sender));
   }, 'addTransceiver - Calling removeTrack on different connection should throw InvalidAccessError');
 
     const sender = pc.addTrack(track, stream);
 
     const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc2.close());
     assert_throws('InvalidAccessError', () => pc2.removeTrack(sender));
   }, 'addTrack - Calling removeTrack on different connection should throw InvalidAccessError')
 
    */
   promise_test(async t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     const stream = await navigator.mediaDevices.getUserMedia({audio: true});
     t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
     const [track] = stream.getTracks();
     const offer = await caller.createOffer();
     await caller.setLocalDescription(offer);
     await callee.setRemoteDescription(offer);
-    callee.addTrack(track);
+    callee.addTrack(track, stream);
     const answer = await callee.createAnswer();
     await callee.setLocalDescription(answer);
     await caller.setRemoteDescription(answer);
     const offer = await caller.createOffer();
     await caller.setLocalDescription(offer);
     await callee.setRemoteDescription(offer);
-    callee.addTrack(track);
+    callee.addTrack(track, stream);
     const answer = await callee.createAnswer();
     await callee.setLocalDescription(answer);
     await caller.setRemoteDescription(answer);
index 8a04b5d..e2c055d 100644 (file)
@@ -1,7 +1,7 @@
 
 PASS setLocalDescription() with valid answer should succeed 
 FAIL setLocalDescription() with type answer and null sdp should use lastAnswer generated from createAnswer promise_test: Unhandled rejection with value: object "OperationError: Expect line: v="
-FAIL setLocalDescription() with answer not created by own createAnswer() should reject with InvalidModificationError assert_throws: function "function () { throw e }" threw object "OperationError: Failed to set local answer sdp: Failed to apply the description for 0: Local fingerprint does not match identity. Expected: sha-256 FD:12:D4:3F:20:8C:37:A8:9A:C0:CB:03:84:DF:97:20:40:24:C8:DB:B8:88:6C:8D:99:B6:25:48:5C:29:1E:0B Got: sha-256 CC:EB:A3:0C:D3:6C:7C:66:8B:E2:C8:A7:7B:00:92:56:A6:C8:C9:3E:D8:B9:CB:D0:5A:C9:7F:AE:10:4E:C7:CA" that is not a DOMException InvalidModificationError: property "code" is equal to 0, expected 13
+FAIL setLocalDescription() with answer not created by own createAnswer() should reject with InvalidModificationError assert_throws: function "function () { throw e }" threw object "OperationError: Failed to set local answer sdp: Failed to apply the description for 0: Local fingerprint does not match identity. Expected: sha-256 B8:FF:F5:1A:10:AE:9F:0F:19:5C:02:FC:AB:BD:65:CB:5A:EB:DC:70:C9:D9:6C:96:21:2D:68:88:A3:D8:4E:22 Got: sha-256 1F:7E:2D:05:21:41:BE:F0:4C:70:43:EC:E6:E1:79:F1:D9:BD:36:F7:63:5E:63:88:29:22:49:AB:42:1C:F2:D8" that is not a DOMException InvalidModificationError: property "code" is equal to 0, expected 13
 PASS Calling setLocalDescription(answer) from stable state should reject with InvalidStateError 
 PASS Calling setLocalDescription(answer) from have-local-offer state should reject with InvalidStateError 
 
index c3ddbf4..5cec0db 100644 (file)
@@ -1,7 +1,7 @@
 
 PASS setLocalDescription with valid offer should succeed 
 FAIL setLocalDescription with type offer and null sdp should use lastOffer generated from createOffer promise_test: Unhandled rejection with value: object "OperationError: Expect line: v="
-FAIL setLocalDescription() with offer not created by own createOffer() should reject with InvalidModificationError assert_throws: function "function () { throw e }" threw object "OperationError: Failed to set local offer sdp: Failed to apply the description for 0: Local fingerprint does not match identity. Expected: sha-256 55:AE:C9:0F:29:DE:0A:3F:C9:E9:D5:F5:54:FB:67:74:DB:2E:A8:40:8E:A0:22:7E:BE:DA:57:F9:53:1B:27:3E Got: sha-256 3A:DD:42:84:B0:04:62:C1:58:6B:DE:C5:D1:91:45:B8:87:57:26:31:C0:E8:40:91:36:3B:E8:7B:15:EF:F7:5D" that is not a DOMException InvalidModificationError: property "code" is equal to 0, expected 13
+FAIL setLocalDescription() with offer not created by own createOffer() should reject with InvalidModificationError assert_throws: function "function () { throw e }" threw object "OperationError: Failed to set local offer sdp: Failed to apply the description for 0: Local fingerprint does not match identity. Expected: sha-256 E6:E8:76:7F:DE:42:8F:0E:00:46:2E:18:43:C7:6F:85:04:B8:BE:C7:18:39:8E:12:1F:69:24:2C:26:0F:DA:A4 Got: sha-256 FC:1E:43:7D:F6:B9:B2:57:2E:FE:F4:12:4F:17:EC:20:DB:0F:70:A7:72:10:84:4D:10:3B:8B:D1:12:0C:80:FA" that is not a DOMException InvalidModificationError: property "code" is equal to 0, expected 13
 FAIL Set created offer other than last offer should reject with InvalidModificationError assert_unreached: Should have rejected: undefined Reached unreachable code
 PASS Creating and setting offer multiple times should succeed 
 
index 0147b31..630f84f 100644 (file)
@@ -12,6 +12,7 @@
 
   // The following helper functions are called from RTCPeerConnection-helper.js:
   //   assert_session_desc_similar
+  //   generateAudioReceiveOnlyOffer
 
   /*
     4.3.2.  Interface Definition
   promise_test(t => {
     const pc = new RTCPeerConnection();
     t.add_cleanup(() => pc.close());
-    return pc.createOffer({ offerToReceiveAudio: true })
+    return generateAudioReceiveOnlyOffer(pc)
     .then(offer =>
       pc.setRemoteDescription(offer)
       .then(() => pc.createAnswer()))
index 1030f49..2becbd3 100644 (file)
         })));
   }, 'Calling createOffer() and setLocalDescription() again after one round of local-offer/remote-answer should succeed');
 
-  promise_test(t => {
-    const pc = new RTCPeerConnection();
-    t.add_cleanup(() => pc.close());
+  promise_test(async t => {
+    const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
+
+    const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc2.close());
 
     const states = [];
-    pc.addEventListener('signalingstatechange', () => states.push(pc.signalingState));
+    pc1.addEventListener('signalingstatechange', () => states.push(pc1.signalingState));
 
-    return generateDataChannelOffer(pc)
-    .then(offer => pc.setRemoteDescription(offer))
-    .then(() => pc.createAnswer())
-    .then(answer =>
-      pc.setLocalDescription(answer)
-      .then(() => generateVideoReceiveOnlyOffer(pc))
-      .then(offer =>
-        pc.setLocalDescription(offer)
-        .then(() => {
-          assert_equals(pc.signalingState, 'have-local-offer');
-          assert_session_desc_similar(pc.localDescription, offer);
-          assert_session_desc_similar(pc.currentLocalDescription, answer);
-          assert_session_desc_similar(pc.pendingLocalDescription, offer);
+    assert_equals(pc1.localDescription, null);
+    assert_equals(pc1.currentLocalDescription, null);
+    assert_equals(pc1.pendingLocalDescription, null);
 
-          assert_array_equals(states, ['have-remote-offer', 'stable', 'have-local-offer']);
-        })));
+    pc1.createDataChannel('test');
+    const offer = await pc1.createOffer();
+
+    assert_equals(pc1.localDescription, null);
+    assert_equals(pc1.currentLocalDescription, null);
+    assert_equals(pc1.pendingLocalDescription, null);
+
+    await pc1.setLocalDescription(offer);
+
+    assert_session_desc_similar(pc1.localDescription, offer);
+    assert_equals(pc1.currentLocalDescription, null);
+    assert_session_desc_similar(pc1.pendingLocalDescription, offer);
+
+    await pc2.setRemoteDescription(offer);
+    const answer = await pc2.createAnswer();
+    await pc2.setLocalDescription(answer);
+    await pc1.setRemoteDescription(answer);
+
+    assert_equals(pc1.signalingState, 'stable');
+    assert_session_desc_similar(pc1.localDescription, offer);
+    assert_session_desc_similar(pc1.currentLocalDescription, offer);
+    assert_equals(pc1.pendingLocalDescription, null);
+
+    const stream = await getNoiseStream({audio:true});
+    pc2.addTrack(stream.getTracks()[0], stream);
+
+    const reoffer = await pc2.createOffer();
+    await pc2.setLocalDescription(reoffer);
+    await pc1.setRemoteDescription(reoffer);
+    const reanswer = await pc1.createAnswer();
+    await pc1.setLocalDescription(reanswer);
+
+    assert_session_desc_similar(pc1.localDescription, reanswer);
+    assert_session_desc_similar(pc1.currentLocalDescription, reanswer);
+    assert_equals(pc1.pendingLocalDescription, null);
   }, 'Switching role from answerer to offerer after going back to stable state should succeed');
 
   promise_test(async t => {
     };
     await pc.setLocalDescription(offer);
     eventSequence += 'setLocalDescription;';
-    await signalingstatechangeResolver.promise;
+    await signalingstatechangeResolver;
     assert_equals(eventSequence, 'onsignalingstatechange;setLocalDescription;');
   }, 'onsignalingstatechange fires before setLocalDescription resolves');
 
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-nomsid-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-nomsid-expected.txt
new file mode 100644 (file)
index 0000000..b23411f
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS setRemoteDescription with an SDP without a=msid lines triggers ontrack with a default stream. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-nomsid.html b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-nomsid.html
new file mode 100644 (file)
index 0000000..8a86bb0
--- /dev/null
@@ -0,0 +1,40 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCPeerConnection.prototype.setRemoteDescription - legacy streams without a=msid lines</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+'use strict';
+
+const FINGERPRINT_SHA256 = '00:00:00:00:00:00:00:00:00:00:00:00:00' +
+    ':00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00';
+const ICEUFRAG = 'someufrag';
+const ICEPWD = 'somelongpwdwithenoughrandomness';
+const SDP_BOILERPLATE = 'v=0\r\n' +
+    'o=- 166855176514521964 2 IN IP4 127.0.0.1\r\n' +
+    's=-\r\n' +
+    't=0 0\r\n';
+const MINIMAL_AUDIO_MLINE =
+    'm=audio 9 UDP/TLS/RTP/SAVPF 111\r\n' +
+    'c=IN IP4 0.0.0.0\r\n' +
+    'a=rtcp:9 IN IP4 0.0.0.0\r\n' +
+    'a=ice-ufrag:' + ICEUFRAG + '\r\n' +
+    'a=ice-pwd:' + ICEPWD + '\r\n' +
+    'a=fingerprint:sha-256 ' + FINGERPRINT_SHA256 + '\r\n' +
+    'a=setup:actpass\r\n' +
+    'a=mid:0\r\n' +
+    'a=sendrecv\r\n' +
+    'a=rtcp-mux\r\n' +
+    'a=rtcp-rsize\r\n' +
+    'a=rtpmap:111 opus/48000/2\r\n';
+
+  promise_test(async t => {
+    const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
+
+    const haveOntrack = new Promise(r => pc.ontrack = r);
+    await pc.setRemoteDescription({type: 'offer', sdp: SDP_BOILERPLATE + MINIMAL_AUDIO_MLINE});
+    assert_equals((await haveOntrack).streams.length, 1);
+  }, 'setRemoteDescription with an SDP without a=msid lines triggers ontrack with a default stream.');
+
+</script>
index eb8e956..db616bc 100644 (file)
@@ -1,7 +1,7 @@
 
 PASS setRemoteDescription with valid offer should succeed 
 PASS setRemoteDescription multiple times should succeed 
-FAIL setRemoteDescription multiple times with different offer should succeed assert_false: Expect both session descriptions to have different count of media lines expected false got true
+PASS setRemoteDescription multiple times with different offer should succeed 
 FAIL setRemoteDescription(offer) with invalid SDP should reject with RTCError assert_equals: Expect error detail field to set to sdp-syntax-error expected (string) "sdp-syntax-error" but got (undefined) undefined
 PASS setRemoteDescription(offer) from have-local-offer state should reject with InvalidStateError 
 
index 565fddc..cae62b2 100644 (file)
@@ -12,6 +12,7 @@
 
   // The following helper functions are called from RTCPeerConnection-helper.js:
   //   assert_session_desc_similar()
+  //   generateAudioReceiveOnlyOffer
 
   /*
     4.3.2.  Interface Definition
     .then(offer1 => {
       return pc1.setLocalDescription(offer1)
        .then(()=> {
-        return pc1.createOffer({ offerToReceiveAudio: true })
+        return generateAudioReceiveOnlyOffer(pc1)
         .then(offer2 => {
           assert_session_desc_not_similar(offer1, offer2);
 
index 0a38a74..60300ed 100644 (file)
@@ -2,4 +2,5 @@
 FAIL setRemoteDescription(rollback) in have-remote-offer state should revert to stable state promise_test: Unhandled rejection with value: object "InvalidStateError: Description type incompatible with current signaling state"
 PASS setRemoteDescription(rollback) from stable state should reject with InvalidStateError 
 FAIL setRemoteDescription(rollback) should ignore invalid sdp content and succeed promise_test: Unhandled rejection with value: object "InvalidStateError: Description type incompatible with current signaling state"
+FAIL local offer created before setRemoteDescription(remote offer) then rollback should still be usable promise_test: Unhandled rejection with value: object "InvalidStateError: Description type incompatible with current signaling state"
 
index ec90e56..f2dbf0b 100644 (file)
@@ -11,8 +11,9 @@
   // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
 
   // The following helper functions are called from RTCPeerConnection-helper.js:
-  //   generateDataChannelOffer
   //   assert_session_desc_similar
+  //   generateAudioReceiveOnlyOffer
+  //   generateDataChannelOffer
 
   /*
     4.3.2.  Interface Definition
   promise_test(t => {
     const pc = new RTCPeerConnection();
     t.add_cleanup(() => pc.close());
-    return pc.createOffer({ offerToReceiveAudio: true })
+    return generateAudioReceiveOnlyOffer(pc)
     .then(offer => pc.setRemoteDescription(offer))
     .then(() => pc.setRemoteDescription({
       type: 'rollback',
     }));
   }, `setRemoteDescription(rollback) should ignore invalid sdp content and succeed`);
 
+  promise_test(async t => {
+    const pc1 = new RTCPeerConnection();
+    const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
+    t.add_cleanup(() => pc2.close());
+
+    // We don't use this right away
+    const offer1 = await pc1.createOffer({offerToReceiveAudio: true});
+
+    // Create offer from pc2, apply and rollback on pc1
+    const offer2 = await pc2.createOffer({offerToReceiveAudio: true});
+    await pc1.setRemoteDescription(offer2);
+    await pc1.setRemoteDescription({type: "rollback"});
+
+    // Then try applying pc1's old offer
+    await pc1.setLocalDescription(offer1);
+  }, `local offer created before setRemoteDescription(remote offer) then rollback should still be usable`);
+
 </script>
index c9d2d12..4e9568f 100644 (file)
@@ -95,7 +95,7 @@
       ontrackEventResolvers[ontrackEventsFired++].resolve(e);
     });
     await exchangeOffer(caller, callee);
-    let firstTrackEvent = await ontrackEventResolvers[0].promise;
+    let firstTrackEvent = await ontrackEventResolvers[0];
     assert_equals(firstTrackEvent.track.id,
                   localStreams[0].getTracks()[0].id,
                   'First ontrack\'s track ID matches first local track.');
     assert_equals(firstTrackEvent.streams[0].id,
                   localStreams[0].id,
                   'First ontrack\'s stream ID matches local stream.');
-    let secondTrackEvent = await ontrackEventResolvers[1].promise;
+    let secondTrackEvent = await ontrackEventResolvers[1];
     assert_equals(secondTrackEvent.track.id,
                   localStreams[1].getTracks()[0].id,
                   'Second ontrack\'s track ID matches second local track.');
       if (!remoteStreams.includes(e.streams[0]))
         remoteStreams.push(e.streams[0]);
     };
+    exchangeIceCandidates(caller, callee);
     await exchangeOfferAnswer(caller, callee);
     assert_equals(remoteStreams.length, 1, 'One remote stream created.');
     assert_equals(remoteStreams[0].getTracks()[0].id,
       if (!remoteStreams.includes(e.streams[0]))
         remoteStreams.push(e.streams[0]);
     };
+    exchangeIceCandidates(caller, callee);
     await exchangeOfferAnswer(caller, callee);
     assert_equals(remoteStreams.length, 1, 'One remote stream created.');
     const onaddtrackPromise =
     const localStream =
         await getNoiseStream({audio: true});
     t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop()));
-    caller.addTrack(localStream.getTracks()[0]);
+    caller.addTrack(localStream.getTracks()[0], localStream);
     const ontrackPromise = addEventListenerPromise(t, callee, 'track', e => {
       assert_array_equals(callee.getReceivers(), [e.receiver],
                           'getReceivers() == [e.receiver].');
     const localStream =
         await getNoiseStream({audio: true});
     t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop()));
-    const sender = caller.addTrack(localStream.getTracks()[0]);
+    const sender = caller.addTrack(localStream.getTracks()[0], localStream);
     const ontrackPromise = addEventListenerPromise(t, callee, 'track');
+    exchangeIceCandidates(caller, callee);
     await exchangeOfferAnswer(caller, callee);
     await ontrackPromise;
     assert_equals(callee.getReceivers().length, 1, 'One receiver created.');
       assert_equals(e.streams.length, 1);
       return e.streams[0];
     });
+    exchangeIceCandidates(caller, callee);
     await exchangeOfferAnswer(caller, callee);
     const remoteStream = await ontrackPromise;
     const remoteTrack = remoteStream.getTracks()[0];
       assert_equals(e.streams.length, 1);
       return e.streams[0];
     });
+    exchangeIceCandidates(caller, callee);
     await exchangeOfferAnswer(caller, callee);
     const remoteStream = await ontrackPromise;
     const remoteTrack = remoteStream.getTracks()[0];
         await getNoiseStream({audio: true});
     t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop()));
     const sender = caller.addTrack(localStream.getTracks()[0], localStream);
-    const ontrackPromise = addEventListenerPromise(t, callee, 'track', e => {
-      assert_equals(e.streams.length, 1);
-      return e.streams[0];
+    exchangeIceCandidates(caller, callee);
+    const e = await exchangeOfferAndListenToOntrack(t, caller, callee);
+    const remoteTrack = e.track;
+
+    // Need to wait for unmute, otherwise there's no event for the transition
+    // back to muted.
+    const onunmutePromise =
+        addEventListenerPromise(t, remoteTrack, 'unmute', () => {
+      assert_false(remoteTrack.muted);
     });
-    await exchangeOfferAnswer(caller, callee);
-    const remoteStream = await ontrackPromise;
-    const remoteTrack = remoteStream.getTracks()[0];
+    await exchangeAnswer(caller, callee);
+    await onunmutePromise;
+
     const onmutePromise =
         addEventListenerPromise(t, remoteTrack, 'mute', () => {
       assert_true(remoteTrack.muted);
       assert_equals(e.streams.length, 1);
       return e.streams[0];
     });
-    await exchangeOfferAnswer(caller, callee);
-    const remoteStream = await ontrackPromise;
-    const remoteTrack = remoteStream.getTracks()[0];
+    exchangeIceCandidates(caller, callee);
+    const e = await exchangeOfferAndListenToOntrack(t, caller, callee);
+    const remoteTrack = e.track;
+
+    // Need to wait for unmute, otherwise there's no event for the transition
+    // back to muted.
+    const onunmutePromise =
+        addEventListenerPromise(t, remoteTrack, 'unmute', () => {
+      assert_false(remoteTrack.muted);
+    });
+    await exchangeAnswer(caller, callee);
+    await onunmutePromise;
+
     const onmutePromise =
         addEventListenerPromise(t, remoteTrack, 'mute', () => {
       eventSequence += 'track.onmute;';
index 182abea..464e16d 100644 (file)
     await pc.setLocalDescription(offer);
     assert_state('have-local-offer');
     await pc2.setRemoteDescription(offer);
-    await pc2.setLocalDescription(await pc2.createAnswer());
-    await pc.setRemoteDescription(pc2.localDescription);
+    await exchangeAnswer(pc, pc2);
     assert_state('stable');
-    await pc.setRemoteDescription(await pc2.createOffer());
+    await exchangeOffer(pc2, pc);
     assert_state('have-remote-offer');
   }, 'Negotiation should fire signalingsstate events');
 
     t.add_cleanup(() => pc2.close());
 
     const offer1 = await generateAudioReceiveOnlyOffer(pc2);
+    await pc2.setLocalDescription(offer1);
     await pc.setRemoteDescription(offer1);
-    await pc.setLocalDescription(await pc.createAnswer());
+    await exchangeAnswer(pc2, pc);
     const offer2 = await generateVideoReceiveOnlyOffer(pc2);
+    await pc2.setLocalDescription(offer2);
     await pc.setRemoteDescription(offer2);
     assert_session_desc_not_similar(offer1, offer2);
     assert_session_desc_similar(pc.remoteDescription, offer2);
     const answer = await pc2.createAnswer();
     await pc2.setLocalDescription(answer);
     await pc.setRemoteDescription(answer);
-    await pc.setRemoteDescription(await pc2.createOffer());
+    await exchangeOffer(pc2, pc);
     assert_equals(pc.remoteDescription.sdp, pc.pendingRemoteDescription.sdp);
     assert_session_desc_similar(pc.remoteDescription, offer);
     assert_session_desc_similar(pc.currentRemoteDescription, answer);
index 70ee718..2c90014 100644 (file)
@@ -1,12 +1,10 @@
 
-Harness Error (TIMEOUT), message = null
-
 PASS addTrack() without setLocalDescription() yields track stats 
 FAIL addTrack() without setLocalDescription() yields media stream stats assert_true: Has stats for stream expected true got false
 PASS addTrack() with setLocalDescription() yields track stats 
 FAIL addTrack() with setLocalDescription() yields media stream stats assert_true: Has stats for stream expected true got false
 FAIL addTrack(): Media stream stats references track stats assert_true: Has stats for track and stream expected true got false
-FAIL Legacy addStream(): Media stream stats references track stats pc.addStream is not a function. (In 'pc.addStream(stream)', 'pc.addStream' is undefined)
+FAIL Media stream stats references track stats assert_true: Has stats for track and stream expected true got false
 PASS O/A exchange yields outbound RTP stream stats for sending track 
 PASS O/A exchange yields inbound RTP stream stats for receiving track 
 FAIL replaceTrack() before offer: new track attachment stats present assert_true: Has stats for replaced track expected true got false
@@ -15,8 +13,8 @@ FAIL replaceTrack() after answer: new track attachment stats present assert_true
 FAIL replaceTrack(): original track attachment stats present after replacing assert_true: expected true got undefined
 FAIL RTCRtpSender.getStats() contains only outbound-rtp and related stats promise_test: Unhandled rejection with value: object "TypeError: null is not an object (evaluating 'inboundTrackStats.id')"
 FAIL RTCRtpReceiver.getStats() contains only inbound-rtp and related stats promise_test: Unhandled rejection with value: object "TypeError: null is not an object (evaluating 'inboundTrackStats.id')"
-TIMEOUT RTCPeerConnection.getStats(sendingTrack) is the same as RTCRtpSender.getStats() Test timed out
-NOTRUN RTCPeerConnection.getStats(receivingTrack) is the same as RTCRtpReceiver.getStats() 
-NOTRUN RTCPeerConnection.getStats(track) throws InvalidAccessError when there are zero senders or receivers for the track 
-NOTRUN RTCPeerConnection.getStats(track) throws InvalidAccessError when there are multiple senders for the track 
+PASS RTCPeerConnection.getStats(sendingTrack) is the same as RTCRtpSender.getStats() 
+PASS RTCPeerConnection.getStats(receivingTrack) is the same as RTCRtpReceiver.getStats() 
+FAIL RTCPeerConnection.getStats(track) throws InvalidAccessError when there are zero senders or receivers for the track assert_unreached: Should have rejected: undefined Reached unreachable code
+FAIL RTCPeerConnection.getStats(track) throws InvalidAccessError when there are multiple senders for the track assert_unreached: Should have rejected: undefined Reached unreachable code
 
index 3809530..2d0ce30 100644 (file)
@@ -25,8 +25,9 @@
     let track;
     return getUserMediaTracksAndStreams(1)
     .then(t.step_func(([tracks, streams]) => {
+      t.add_cleanup(() => tracks.forEach(track => track.stop()));
       track = tracks[0];
-      pc.addTrack(track);
+      pc.addTrack(track, streams[0]);
       return pc.getStats();
     }))
     .then(t.step_func(report => {
@@ -50,6 +51,7 @@
     let stream;
     return getUserMediaTracksAndStreams(1)
     .then(t.step_func(([tracks, streams]) => {
+      t.add_cleanup(() => tracks.forEach(track => track.stop()));
       let track = tracks[0];
       stream = streams[0];
       pc.addTrack(track, stream);
@@ -72,8 +74,9 @@
     let track;
     return getUserMediaTracksAndStreams(1)
     .then(t.step_func(([tracks, streams]) => {
+      t.add_cleanup(() => tracks.forEach(track => track.stop()));
       track = tracks[0];
-      pc.addTrack(track);
+      pc.addTrack(track, streams[0]);
       return pc.createOffer();
     }))
     .then(t.step_func(offer => {
     let stream;
     return getUserMediaTracksAndStreams(1)
     .then(t.step_func(([tracks, streams]) => {
+      t.add_cleanup(() => tracks.forEach(track => track.stop()));
       let track = tracks[0];
       stream = streams[0];
       pc.addTrack(track, stream);
     let stream;
     return getUserMediaTracksAndStreams(1)
     .then(t.step_func(([tracks, streams]) => {
+      t.add_cleanup(() => tracks.forEach(track => track.stop()));
       track = tracks[0];
       stream = streams[0];
       pc.addTrack(track, stream);
     }));
   }, 'addTrack(): Media stream stats references track stats');
 
-  // TODO(hbos): addStream() is legacy API not in the spec. Based on discussion
-  // whether to standardize in legacy section, consider removing this test or
-  // keeping it until addTrack() has wide support.
-  // https://github.com/w3c/webrtc-pc/issues/1705
-  // https://github.com/w3c/webrtc-pc/issues/1125
   async_test(t => {
     const pc = new RTCPeerConnection();
     t.add_cleanup(() => pc.close());
     let stream;
     return getUserMediaTracksAndStreams(1)
     .then(t.step_func(([tracks, streams]) => {
+      t.add_cleanup(() => tracks.forEach(track => track.stop()));
       track = tracks[0];
       stream = streams[0];
       stream.addTrack(track);
-      pc.addStream(stream);
+      for (const track of stream.getTracks())
+        pc.addTrack(track, stream);
       return pc.createOffer();
     }))
     .then(t.step_func(offer => {
     .catch(t.step_func(reason => {
       assert_unreached(reason);
     }));
-  }, 'Legacy addStream(): Media stream stats references track stats');
+  }, 'Media stream stats references track stats');
 
   async_test(t => {
     const caller = new RTCPeerConnection();
     let sendingTrack;
     return getUserMediaTracksAndStreams(1)
     .then(t.step_func(([tracks, streams]) => {
+      t.add_cleanup(() => tracks.forEach(track => track.stop()));
       sendingTrack = tracks[0];
-      caller.addTrack(sendingTrack);
+      caller.addTrack(sendingTrack, streams[0]);
       return doSignalingHandshake(caller, callee);
     }))
     .then(t.step_func(() => {
     };
     return getUserMediaTracksAndStreams(1)
     .then(t.step_func(([tracks, streams]) => {
-      caller.addTrack(tracks[0]);
+      t.add_cleanup(() => tracks.forEach(track => track.stop()));
+      caller.addTrack(tracks[0], streams[0]);
       return doSignalingHandshake(caller, callee);
     }))
     .then(t.step_func(() => {
     let sender;
     return getUserMediaTracksAndStreams(2)
     .then(t.step_func(([tracks, streams]) => {
+      t.add_cleanup(() => tracks.forEach(track => track.stop()));
       sendingTrack1 = tracks[0];
       sendingTrack2 = tracks[1];
-      sender = caller.addTrack(sendingTrack1);
+      sender = caller.addTrack(sendingTrack1, streams[0]);
       return sender.replaceTrack(sendingTrack2);
     }))
     .then(t.step_func(() => {
     let sender;
     return getUserMediaTracksAndStreams(2)
     .then(t.step_func(([tracks, streams]) => {
+      t.add_cleanup(() => tracks.forEach(track => track.stop()));
       sendingTrack1 = tracks[0];
       sendingTrack2 = tracks[1];
-      sender = caller.addTrack(sendingTrack1);
+      sender = caller.addTrack(sendingTrack1, streams[0]);
       return exchangeOffer(caller, callee);
     }))
     .then(t.step_func(() => {
     let sender;
     return getUserMediaTracksAndStreams(2)
     .then(t.step_func(([tracks, streams]) => {
+      t.add_cleanup(() => tracks.forEach(track => track.stop()));
       sendingTrack1 = tracks[0];
       sendingTrack2 = tracks[1];
-      sender = caller.addTrack(sendingTrack1);
+      sender = caller.addTrack(sendingTrack1, streams[0]);
       return doSignalingHandshake(caller, callee);
     }))
     .then(t.step_func(() => {
     let sender;
     return getUserMediaTracksAndStreams(2)
     .then(t.step_func(([tracks, streams]) => {
+      t.add_cleanup(() => tracks.forEach(track => track.stop()));
       sendingTrack1 = tracks[0];
       sendingTrack2 = tracks[1];
-      sender = caller.addTrack(sendingTrack1);
+      sender = caller.addTrack(sendingTrack1, streams[0]);
       return doSignalingHandshake(caller, callee);
     }))
     .then(t.step_func(() => {
     const callee = new RTCPeerConnection();
     t.add_cleanup(() => callee.close());
     let [tracks, streams] = await getUserMediaTracksAndStreams(2);
+    t.add_cleanup(() => tracks.forEach(track => track.stop()));
     let sender = caller.addTrack(tracks[0], streams[0]);
     callee.addTrack(tracks[1], streams[1]);
     exchangeIceCandidates(caller, callee);
     await doSignalingHandshake(caller, callee);
-    await onIceConnectionStateCompleted(caller);
+    await listenToConnected(caller);
     let receiver = caller.getReceivers()[0];
 
     // Obtain inbound and outbound RTP stream stats on a full stats report.
     const callee = new RTCPeerConnection();
     t.add_cleanup(() => callee.close());
     let [tracks, streams] = await getUserMediaTracksAndStreams(2);
+    t.add_cleanup(() => tracks.forEach(track => track.stop()));
     let sender = caller.addTrack(tracks[0], streams[0]);
     callee.addTrack(tracks[1], streams[1]);
     exchangeIceCandidates(caller, callee);
     await doSignalingHandshake(caller, callee);
-    await onIceConnectionStateCompleted(caller);
+    await listenToConnected(caller);
     let receiver = caller.getReceivers()[0];
 
     // Obtain inbound and outbound RTP stream stats on a full stats report.
     const callee = new RTCPeerConnection();
     t.add_cleanup(() => callee.close());
     let [tracks, streams] = await getUserMediaTracksAndStreams(2);
+    t.add_cleanup(() => tracks.forEach(track => track.stop()));
     let sender = caller.addTrack(tracks[0], streams[0]);
     callee.addTrack(tracks[1], streams[1]);
     exchangeIceCandidates(caller, callee);
     await doSignalingHandshake(caller, callee);
-    await onIceConnectionStateCompleted(caller);
+    await listenToIceConnected(caller);
 
     // Wait until RTCP has arrived so that it can not arrive between
     // the two get stats calls.
     const callee = new RTCPeerConnection();
     t.add_cleanup(() => callee.close());
     let [tracks, streams] = await getUserMediaTracksAndStreams(2);
+    t.add_cleanup(() => tracks.forEach(track => track.stop()));
     let sender = caller.addTrack(tracks[0], streams[0]);
     callee.addTrack(tracks[1], streams[1]);
     exchangeIceCandidates(caller, callee);
     await doSignalingHandshake(caller, callee);
-    await onIceConnectionStateCompleted(caller);
+    await listenToIceConnected(caller);
     let receiver = caller.getReceivers()[0];
 
     // Wait until RTCP has arrived so that it can not arrive between
     const pc = new RTCPeerConnection();
     t.add_cleanup(() => pc.close());
     let [tracks, streams] = await getUserMediaTracksAndStreams(1);
+    t.add_cleanup(() => tracks.forEach(track => track.stop()));
     await promise_rejects(t, 'InvalidAccessError', pc.getStats(tracks[0]));
   }, 'RTCPeerConnection.getStats(track) throws InvalidAccessError when there ' +
      'are zero senders or receivers for the track');
     const pc = new RTCPeerConnection();
     t.add_cleanup(() => pc.close());
     let [tracks, streams] = await getUserMediaTracksAndStreams(2);
-    let sender1 = pc.addTrack(tracks[0]);
-    let sender2 = pc.addTrack(tracks[1]);
+    t.add_cleanup(() => tracks.forEach(track => track.stop()));
+    let sender1 = pc.addTrack(tracks[0], streams[0]);
+    let sender2 = pc.addTrack(tracks[1], streams[1]);
     await sender2.replaceTrack(sender1.track);
     await promise_rejects(t, 'InvalidAccessError', pc.getStats(sender1.track));
   }, 'RTCPeerConnection.getStats(track) throws InvalidAccessError when there ' +
     return stats;
   }
 
-  // Returns a promise that is resolved when pc.iceConnectionState reaches the
-  // 'connected' or 'completed' state. This is when transport stats can be
-  // expected to have its selectedCandidatePairId defined.
-  async function onIceConnectionStateCompleted(pc) {
-    if (pc.iceConnectionState == 'connected' ||
-        pc.iceConnectionState == 'completed') {
-      return Promise.resolve();
-    }
-    let resolver = new Resolver();
-    pc.oniceconnectionstatechange = e => {
-      if (pc.iceConnectionState == 'connected' ||
-          pc.iceConnectionState == 'completed') {
-        resolver.resolve();
-      }
-    };
-    return resolver.promise;
-  }
-
   // Explores the stats graph starting from |stat|, validating each stat
   // (validateRtcStats) and asserting that all stats of the report were visited.
   function validateStatsGraph(report, stat) {
index a48b491..80b4ead 100644 (file)
@@ -11,7 +11,7 @@ PASS addTrack: transceiver's currentDirection is null
 PASS setLocalDescription(offer): transceiver gets associated with an m-section 
 PASS setLocalDescription(offer): transceiver.mid matches the offer SDP 
 PASS setRemoteDescription(offer): ontrack fires with a track 
-PASS setRemoteDescription(offer): ontrack's track.id is the same as track.id 
+PASS setRemoteDescription(offer): ontrack's stream.id is the same as stream.id 
 PASS setRemoteDescription(offer): ontrack fires with a transceiver. 
 PASS setRemoteDescription(offer): transceiver.mid is the same on both ends 
 PASS setRemoteDescription(offer): "transceiver == {sender,receiver}" 
index 1f980ea..9b3a9a8 100644 (file)
@@ -1,4 +1,5 @@
 <!doctype html>
+<meta name="timeout" content="long"/>
 <meta charset=utf-8>
 <title>RTCPeerConnection-transceivers.https.html</title>
 <script src="/resources/testharness.js"></script>
@@ -138,9 +139,13 @@ promise_test(async t => {
   const trackEvent = await exchangeOfferAndListenToOntrack(t, pc1, pc2);
   assert_true(trackEvent.track instanceof MediaStreamTrack,
               'trackEvent.track instanceof MediaStreamTrack');
-  assert_equals(trackEvent.track.id, track.id,
-                'trackEvent.track.id == track.id');
-}, 'setRemoteDescription(offer): ontrack\'s track.id is the same as track.id');
+  assert_equals(trackEvent.streams.length, 1,
+                'trackEvent contains a single stream');
+  assert_true(trackEvent.streams[0] instanceof MediaStream,
+              'trackEvent has a MediaStream');
+  assert_equals(trackEvent.streams[0].id, stream.id,
+                'trackEvent.streams[0].id == stream.id');
+}, 'setRemoteDescription(offer): ontrack\'s stream.id is the same as stream.id');
 
 promise_test(async t => {
   const pc1 = createPeerConnectionWithCleanup(t);
index 1fd6417..aff097b 100644 (file)
@@ -15,6 +15,8 @@ FAIL setParameters() with modified encoding.active should succeed with RTCRtpTra
 FAIL setParameters() with modified encoding.active should succeed without RTCRtpTransceiverInit assert_equals: Expect dictionary.rtcp to be plain object expected "object" but got "undefined"
 FAIL setParameters() with modified encoding.priority should succeed with RTCRtpTransceiverInit assert_equals: Expect dictionary.rtcp to be plain object expected "object" but got "undefined"
 FAIL setParameters() with modified encoding.priority should succeed without RTCRtpTransceiverInit assert_equals: Expect dictionary.rtcp to be plain object expected "object" but got "undefined"
+FAIL setParameters() with modified encoding.networkPriority should succeed with RTCRtpTransceiverInit assert_equals: Expect dictionary.rtcp to be plain object expected "object" but got "undefined"
+FAIL setParameters() with modified encoding.networkPriority should succeed without RTCRtpTransceiverInit assert_equals: Expect dictionary.rtcp to be plain object expected "object" but got "undefined"
 FAIL setParameters() with modified encoding.ptime should succeed with RTCRtpTransceiverInit assert_equals: Expect dictionary.rtcp to be plain object expected "object" but got "undefined"
 FAIL setParameters() with modified encoding.ptime should succeed without RTCRtpTransceiverInit assert_equals: Expect dictionary.rtcp to be plain object expected "object" but got "undefined"
 FAIL setParameters() with modified encoding.maxBitrate should succeed with RTCRtpTransceiverInit assert_equals: Expect dictionary.rtcp to be plain object expected "object" but got "undefined"
index b446dde..0d3d08a 100644 (file)
@@ -48,6 +48,7 @@
         RTCDtxStatus        dtx;
         boolean             active;
         RTCPriorityType     priority;
+        RTCPriorityType     networkPriority;
         unsigned long       ptime;
         unsigned long       maxBitrate;
         double              maxFramerate;
         dtx: 'enabled',
         active: false,
         priority: 'low',
+        networkPriority: 'low',
         ptime: 5,
         maxBitrate: 8,
         maxFramerate: 25,
     assert_equals(encoding.dtx, 'enabled');
     assert_equals(encoding.active, false);
     assert_equals(encoding.priority, 'low');
+    assert_equals(encoding.networkPriority, 'low');
     assert_equals(encoding.ptime, 5);
     assert_equals(encoding.maxBitrate, 8);
     assert_equals(encoding.maxFramerate, 25);
   promise_test(async t => {
     const pc = new RTCPeerConnection();
     t.add_cleanup(() => pc.close());
-    const { sender } = pc.addTransceiver('audio', {
-      sendEncodings: [{ rid: 'foo' }],
+    const { sender } = pc.addTransceiver('video', {
+      sendEncodings: [{ rid: 'foo' }, { rid: 'baz' }],
     });
     await doOfferAnswerExchange(t, pc);
 
     const encoding = getFirstEncoding(param);
 
     encoding.scaleResolutionDownBy = 0.5;
-    return promise_rejects(t, 'RangeError',
+    return promise_rejects(t, new RangeError(),
       sender.setParameters(param));
   }, `setParameters() with encoding.scaleResolutionDownBy field set to less than 1.0 should reject with RangeError`);
 
   test_modified_encoding('audio', 'priority', 'very-low', 'high',
     'setParameters() with modified encoding.priority should succeed');
 
+  test_modified_encoding('audio', 'networkPriority', 'very-low', 'high',
+    'setParameters() with modified encoding.networkPriority should succeed');
+
   test_modified_encoding('audio', 'ptime', 2, 4,
     'setParameters() with modified encoding.ptime should succeed');
 
index c4105de..d706949 100644 (file)
@@ -19,6 +19,8 @@ async function doOfferAnswerExchange(t, caller) {
   const answer = await callee.createAnswer();
   await callee.setLocalDescription(answer);
   await caller.setRemoteDescription(answer);
+
+  return callee;
 }
 
 /*
@@ -142,6 +144,7 @@ function validateRtpParameters(param) {
     RTCDtxStatus        dtx;
     boolean             active;
     RTCPriorityType     priority;
+    RTCPriorityType     networkPriority;
     unsigned long       ptime;
     unsigned long       maxBitrate;
     double              maxFramerate;
@@ -171,6 +174,8 @@ function validateEncodingParameters(encoding) {
   assert_optional_boolean_field(encoding, 'active');
   assert_optional_enum_field(encoding, 'priority',
     ['very-low', 'low', 'medium', 'high']);
+  assert_optional_enum_field(encoding, 'networkPriority',
+    ['very-low', 'low', 'medium', 'high']);
 
   assert_optional_unsigned_int_field(encoding, 'ptime');
   assert_optional_unsigned_int_field(encoding, 'maxBitrate');
index 3780f94..6e4304a 100644 (file)
@@ -1,3 +1,4 @@
 
-FAIL getContributingSources() should return list of CSRC after connection is established assert_greater_than: Expect CSRCs to be available after RTP connection is established expected a number greater than 0 but got 0
+PASS [audio] getContributingSources() returns an empty list in loopback call 
+PASS [video] getContributingSources() returns an empty list in loopback call 
 
index 8ddff5b..aa59f1b 100644 (file)
@@ -5,85 +5,31 @@
 <script src="/resources/testharnessreport.js"></script>
 <script src="RTCPeerConnection-helper.js"></script>
 <script>
-  'use strict';
+'use strict';
 
-  // Test is based on the following editor draft:
-  // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
+async function connectAndExpectNoCsrcs(t, kind) {
+  const pc1 = new RTCPeerConnection();
+  t.add_cleanup(() => pc1.close());
+  const pc2 = new RTCPeerConnection();
+  t.add_cleanup(() => pc2.close());
 
-  // The following helper function is called from RTCPeerConnection-helper.js
-  //   getTrackFromUserMedia
-  //   exchangeIceCandidates
-  //   doSignalingHandshake
+  const stream = await navigator.mediaDevices.getUserMedia({[kind]:true});
+  const [track] = stream.getTracks();
+  t.add_cleanup(() => track.stop());
+  pc1.addTrack(track, stream);
 
-  /*
-    5.3.  RTCRtpReceiver Interface
-      interface RTCRtpReceiver {
-        ...
-        sequence<RTCRtpContributingSource>    getContributingSources();
-      };
+  exchangeIceCandidates(pc1, pc2);
+  const trackEvent = await exchangeOfferAndListenToOntrack(t, pc1, pc2);
+  await exchangeAnswer(pc1, pc2);
 
-      interface RTCRtpContributingSource {
-        readonly attribute DOMHighResTimeStamp timestamp;
-        readonly attribute unsigned long       source;
-        readonly attribute byte?               audioLevel;
-      };
+  assert_array_equals(trackEvent.receiver.getContributingSources(), []);
+}
 
-      audioLevel
-        The audio level contained in the last RTP packet played from this source.
-        audioLevel will be the level value defined in [RFC6465] if the RFC 6465
-        header extension is present, and otherwise null. RFC 6465 defines the
-        level as a integral value from 0 to 127 representing the audio level in
-        negative decibels relative to the loudest signal that the system could
-        possibly encode. Thus, 0 represents the loudest signal the system could
-        possibly encode, and 127 represents silence.
-   */
-
-  promise_test(t => {
-    const pc1 = new RTCPeerConnection();
-    t.add_cleanup(() => pc1.close());
-    const pc2 = new RTCPeerConnection();
-
-    t.add_cleanup(() => pc2.close());
-
-    const ontrackPromise = new Promise(resolve => {
-      pc2.addEventListener('track', trackEvent => {
-        const { receiver } = trackEvent;
-        assert_true(receiver instanceof RTCRtpReceiver,
-          'Expect trackEvent.receiver to be instance of RTCRtpReceiver');
-
-        resolve(receiver);
-      });
-    });
-
-    return getTrackFromUserMedia('audio')
-    .then(([track, mediaStream]) => {
-      pc1.addTrack(track, mediaStream);
-      exchangeIceCandidates(pc1, pc2);
-      return doSignalingHandshake(pc1, pc2);
-    })
-    .then(() => ontrackPromise)
-    .then(receiver => {
-      const contributingSources = receiver.getContributingSources();
-      assert_greater_than(contributingSources.length, 0,
-        'Expect CSRCs to be available after RTP connection is established');
-
-      for(const csrc of contributingSources) {
-        assert_true(csrc instanceof RTCRtpContributingSource,
-          'Expect contributingSources elements to be instance of RTCRtpContributingSource');
-
-        assert_equals(typeof csrc.timestamp, 'number',
-          'Expect csrc.timestamp attribute to be DOMHighResTimeStamp');
-
-        assert_true(Number.isInteger(csrc.source) && csrc.source > 0,
-          'Expect CSRC identifier to be unsigned long');
-
-        if(csrc.audioLevel !== null) {
-          assert_true(Number.isInteger(csrc.audioLevel) &&
-             csrc.audioLevel >= 0 && csrc.audioLevel <= 127,
-            'Expect csrc.audioLevel to be either null or byte value from 0-127.');
-        }
-      }
-    });
-  }, `getContributingSources() should return list of CSRC after connection is established`);
+promise_test(async t => {
+  await connectAndExpectNoCsrcs(t, 'audio');
+}, '[audio] getContributingSources() returns an empty list in loopback call');
 
+promise_test(async t => {
+  await connectAndExpectNoCsrcs(t, 'video');
+}, '[video] getContributingSources() returns an empty list in loopback call');
 </script>
index a8d4402..f666ab8 100644 (file)
@@ -1,3 +1,5 @@
 
-FAIL RTCRtpReceiver.prototype.getParameters assert_true: Expect dictionary.encodings to be array expected true got false
+FAIL getParameters() with audio receiver assert_true: Expect dictionary.encodings to be array expected true got false
+FAIL getParameters() with video receiver assert_true: Expect dictionary.encodings to be array expected true got false
+FAIL getParameters() with simulcast video receiver assert_true: Expect dictionary.encodings to be array expected true got false
 
index 453844d..7f8ac67 100644 (file)
 
         - transactionId and degradationPreference are left undefined.
    */
-  test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
-    const { receiver } = pc.addTransceiver('audio');
-    const param = receiver.getParameters();
+    t.add_cleanup(() => pc.close());
+    pc.addTransceiver('audio');
+    const callee = await doOfferAnswerExchange(t, pc);
+    const param = callee.getReceivers()[0].getParameters();
     validateReceiverRtpParameters(param);
-  });
+
+    assert_greater_than(param.headerExtensions.length, 0);
+    assert_greater_than(param.codecs.length, 0);
+    assert_equals(param.encodings.length, 1);
+  }, 'getParameters() with audio receiver');
+
+  promise_test(async t => {
+    const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
+    pc.addTransceiver('video');
+    const callee = await doOfferAnswerExchange(t, pc);
+    const param = callee.getReceivers()[0].getParameters();
+    validateReceiverRtpParameters(param);
+
+    assert_greater_than(param.headerExtensions.length, 0);
+    assert_greater_than(param.codecs.length, 0);
+    assert_equals(param.encodings.length, 1);
+  }, 'getParameters() with video receiver');
+
+  promise_test(async t => {
+    const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
+    pc.addTransceiver('video', {
+      sendEncodings: [
+        { rid: "rid1" },
+        { rid: "rid2" }
+      ]
+    });
+    const callee = await doOfferAnswerExchange(t, pc);
+    const param = callee.getReceivers()[0].getParameters();
+    validateReceiverRtpParameters(param);
+
+    assert_greater_than(param.headerExtensions.length, 0);
+    assert_greater_than(param.codecs.length, 0);
+    assert_equals(param.encodings.length, 2, 'layer count must match');
+    assert_equals(param.encodings[0].rid, "rid1",
+      'simulcast rids must match');
+    assert_equals(param.encodings[1].rid, "rid2",
+      'simulcast rids must match');
+  }, 'getParameters() with simulcast video receiver');
 </script>
index 05ca9f3..7cd5983 100644 (file)
@@ -53,7 +53,7 @@
     const stream = await getNoiseStream({audio:true});
     t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
     const [track] = stream.getTracks();
-    callee.addTrack(track);
+    callee.addTrack(track, stream);
 
     const { receiver } = caller.addTransceiver('audio');
 
index d499f8b..54ddfb9 100644 (file)
@@ -1,3 +1,14 @@
 
-FAIL getContributingSources() should return list of CSRC after connection is established assert_greater_than: Expect SSRCs to be available after RTP connection is established expected a number greater than 0 but got 0
+PASS [audio] getSynchronizationSources() eventually returns a non-empty list 
+PASS [audio] RTCRtpSynchronizationSource.timestamp is a number 
+PASS [audio] getSynchronizationSources() does not contain SSRCs older than 10 seconds 
+FAIL [audio] RTCRtpSynchronizationSource.timestamp is comparable to performance.timeOrigin + performance.now() assert_true: expected true got false
+PASS [audio] RTCRtpSynchronizationSource.source is a number 
+PASS [video] getSynchronizationSources() eventually returns a non-empty list 
+PASS [video] RTCRtpSynchronizationSource.timestamp is a number 
+PASS [video] getSynchronizationSources() does not contain SSRCs older than 10 seconds 
+FAIL [video] RTCRtpSynchronizationSource.timestamp is comparable to performance.timeOrigin + performance.now() assert_true: expected true got false
+PASS [video] RTCRtpSynchronizationSource.source is a number 
+FAIL [audio-only] RTCRtpSynchronizationSource.audioLevel is a number [0, 1] assert_less_than_equal: expected a number less than or equal to 1 but got 23
+FAIL [audio-only] RTCRtpSynchronizationSource.voiceActivityFlag is a boolean assert_equals: expected "boolean" but got "undefined"
 
index 8068558..82ce3bd 100644 (file)
@@ -1,79 +1,96 @@
 <!doctype html>
 <meta charset=utf-8>
+<!-- This file contains two tests that wait for 10 seconds each. -->
+<meta name="timeout" content="long">
 <title>RTCRtpReceiver.prototype.getSynchronizationSources</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="RTCPeerConnection-helper.js"></script>
 <script>
-  'use strict';
+'use strict';
 
-  // Test is based on the following editor draft:
-  // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
+async function initiateSingleTrackCallAndReturnReceiver(t, kind) {
+  const pc1 = new RTCPeerConnection();
+  t.add_cleanup(() => pc1.close());
+  const pc2 = new RTCPeerConnection();
+  t.add_cleanup(() => pc2.close());
 
-  /*
-    5.3.  RTCRtpReceiver Interface
-      interface RTCRtpReceiver {
-        ...
-        sequence<RTCRtpSynchronizationSource> getSynchronizationSources();
-      };
+  const stream = await navigator.mediaDevices.getUserMedia({[kind]:true});
+  const [track] = stream.getTracks();
+  t.add_cleanup(() => track.stop());
+  pc1.addTrack(track, stream);
 
-      interface RTCRtpSynchronizationSource {
-        readonly attribute DOMHighResTimeStamp timestamp;
-        readonly attribute unsigned long       source;
-        readonly attribute byte                audioLevel;
-        readonly attribute boolean?            voiceActivityFlag;
-      };
-   */
+  exchangeIceCandidates(pc1, pc2);
+  const trackEvent = await exchangeOfferAndListenToOntrack(t, pc1, pc2);
+  await exchangeAnswer(pc1, pc2);
+  return trackEvent.receiver;
+}
 
-  promise_test(t => {
-    const pc1 = new RTCPeerConnection();
-    t.add_cleanup(() => pc1.close());
-    const pc2 = new RTCPeerConnection();
+for (const kind of ['audio', 'video']) {
+  promise_test(async t => {
+    const receiver = await initiateSingleTrackCallAndReturnReceiver(t, kind);
+    await listenForSSRCs(t, receiver);
+  }, '[' + kind + '] getSynchronizationSources() eventually returns a ' +
+     'non-empty list');
 
-    t.add_cleanup(() => pc2.close());
+  promise_test(async t => {
+    const startTime = performance.now();
+    const receiver = await initiateSingleTrackCallAndReturnReceiver(t, kind);
+    const [ssrc] = await listenForSSRCs(t, receiver);
+    assert_equals(typeof ssrc.timestamp, 'number');
+    assert_true(ssrc.timestamp >= startTime);
+  }, '[' + kind + '] RTCRtpSynchronizationSource.timestamp is a number');
 
-    const ontrackPromise = new Promise(resolve => {
-      pc2.addEventListener('track', trackEvent => {
-        const { receiver } = trackEvent;
-        assert_true(receiver instanceof RTCRtpReceiver,
-          'Expect trackEvent.receiver to be instance of RTCRtpReceiver');
+  promise_test(async t => {
+    const receiver = await initiateSingleTrackCallAndReturnReceiver(t, kind);
+    // Wait for packets to start flowing.
+    await listenForSSRCs(t, receiver);
+    // Wait for 10 seconds.
+    await new Promise(resolve => t.step_timeout(resolve, 10000));
+    let earliestTimestamp = undefined;
+    let latestTimestamp = undefined;
+    for (const ssrc of await listenForSSRCs(t, receiver)) {
+      if (earliestTimestamp == undefined || earliestTimestamp > ssrc.timestamp)
+        earliestTimestamp = ssrc.timestamp;
+      if (latestTimestamp == undefined || latestTimestamp < ssrc.timestamp)
+        latestTimestamp = ssrc.timestamp;
+    }
+    assert_true(latestTimestamp - earliestTimestamp <= 10000);
+  }, '[' + kind + '] getSynchronizationSources() does not contain SSRCs ' +
+     'older than 10 seconds');
 
-        resolve(receiver);
-      });
-    });
+  promise_test(async t => {
+    const startTime = performance.timeOrigin + performance.now();
+    const receiver = await initiateSingleTrackCallAndReturnReceiver(t, kind);
+    const [ssrc] = await listenForSSRCs(t, receiver);
+    const endTime = performance.timeOrigin + performance.now();
+    assert_true(startTime <= ssrc.timestamp && ssrc.timestamp <= endTime);
+  }, '[' + kind + '] RTCRtpSynchronizationSource.timestamp is comparable to ' +
+     'performance.timeOrigin + performance.now()');
 
-    return getTrackFromUserMedia('audio')
-    .then(([track, mediaStream]) => {
-      pc1.addTrack(track, mediaStream);
-      exchangeIceCandidates(pc1, pc2);
-      return doSignalingHandshake(pc1, pc2);
-    })
-    .then(() => ontrackPromise)
-    .then(receiver => {
-      const syncSources = receiver.getSynchronizationSources();
-      assert_greater_than(syncSources.length, 0,
-        'Expect SSRCs to be available after RTP connection is established');
+  promise_test(async t => {
+    const receiver = await initiateSingleTrackCallAndReturnReceiver(t, kind);
+    const [ssrc] = await listenForSSRCs(t, receiver);
+    assert_equals(typeof ssrc.source, 'number');
+  }, '[' + kind + '] RTCRtpSynchronizationSource.source is a number');
+}
 
-      for(const ssrc of syncSources) {
-        assert_true(ssrc instanceof RTCRtpSynchronizationSource,
-          'Expect synchronizationSources elements to be instance of RTCSynchronizationSource');
+promise_test(async t => {
+  const receiver = await initiateSingleTrackCallAndReturnReceiver(t, 'audio');
+  const [ssrc] = await listenForSSRCs(t, receiver);
+  assert_equals(typeof ssrc.audioLevel, 'number');
+  assert_greater_than_equal(ssrc.audioLevel, 0);
+  assert_less_than_equal(ssrc.audioLevel, 1);
+}, '[audio-only] RTCRtpSynchronizationSource.audioLevel is a number [0, 1]');
 
-        assert_equals(typeof ssrc.timestamp, 'number',
-          'Expect ssrc.timestamp attribute to be DOMHighResTimeStamp');
-
-        assert_true(Number.isInteger(ssrc.source) && ssrc.source > 0,
-          'Expect SSRC identifier to be unsigned long');
-
-        assert_true(Number.isInteger(ssrc.audioLevel) &&
-          ssrc.audioLevel >= 0 && ssrc.audioLevel <= 127,
-          'Expect ssrc.audioLevel to be byte value from 0-127.');
-
-        if(ssrc.voiceActivityFlag !== null) {
-          assert_equals(typeof ssrc.voiceActivityFlag, 'boolean',
-            'Expect ssrc.voiceActivity to be either null or boolean');
-        }
-      }
-    });
-  }, `getContributingSources() should return list of CSRC after connection is established`);
+// This test only passes if the implementation is sending the RFC 6464 extension
+// header and the "vad" extension attribute is not "off", otherwise
+// voiceActivityFlag is absent. TODO: Consider moving this test to an
+// optional-to-implement subfolder?
+promise_test(async t => {
+  const receiver = await initiateSingleTrackCallAndReturnReceiver(t, 'audio');
+  const [ssrc] = await listenForSSRCs(t, receiver);
+  assert_equals(typeof ssrc.voiceActivityFlag, 'boolean');
+}, '[audio-only] RTCRtpSynchronizationSource.voiceActivityFlag is a boolean');
 
 </script>
index 8227085..5018731 100644 (file)
@@ -3,6 +3,7 @@
 <title>RTCRtpSender.prototype.replaceTrack</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
 <script>
   'use strict';
 
@@ -30,7 +31,7 @@
   promise_test(async t => {
     const pc = new RTCPeerConnection();
     t.add_cleanup(() => pc.close());
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
     const [track] = stream.getTracks();
 
@@ -51,7 +52,7 @@
   promise_test(async t => {
     const pc = new RTCPeerConnection();
     t.add_cleanup(() => pc.close());
-    const stream = await navigator.mediaDevices.getUserMedia({video: true});
+    const stream = await getNoiseStream({video: true});
     t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
     const [track] = stream.getTracks();
 
@@ -70,7 +71,7 @@
   promise_test(async t => {
     const pc = new RTCPeerConnection();
     t.add_cleanup(() => pc.close());
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
     const [track] = stream.getTracks();
 
@@ -91,7 +92,7 @@
   promise_test(async t => {
     const pc = new RTCPeerConnection();
     t.add_cleanup(() => pc.close());
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
     const [track] = stream.getTracks();
 
   promise_test(async t => {
     const pc = new RTCPeerConnection();
     t.add_cleanup(() => pc.close());
-    const stream1 = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream1 = await getNoiseStream({audio: true});
     t.add_cleanup(() => stream1.getTracks().forEach(track => track.stop()));
     const [track1] = stream1.getTracks();
-    const stream2 = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream2 = await getNoiseStream({audio: true});
     t.add_cleanup(() => stream2.getTracks().forEach(track => track.stop()));
     const [track2] = stream2.getTracks();
 
   promise_test(async t => {
     const pc = new RTCPeerConnection();
     t.add_cleanup(() => pc.close());
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
     const [track] = stream.getTracks();
 
   promise_test(async t => {
     const pc = new RTCPeerConnection();
     t.add_cleanup(() => pc.close());
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
     const [track] = stream.getTracks();
 
   promise_test(async t => {
     const pc = new RTCPeerConnection();
     t.add_cleanup(() => pc.close());
-    const stream1 = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream1 = await getNoiseStream({audio: true});
     t.add_cleanup(() => stream1.getTracks().forEach(track => track.stop()));
     const [track1] = stream1.getTracks();
-    const stream2 = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream2 = await getNoiseStream({audio: true});
     t.add_cleanup(() => stream2.getTracks().forEach(track => track.stop()));
     const [track2] = stream1.getTracks();
 
   promise_test(async t => {
     const pc = new RTCPeerConnection();
     t.add_cleanup(() => pc.close());
-    const stream1 = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream1 = await getNoiseStream({audio: true});
     t.add_cleanup(() => stream1.getTracks().forEach(track => track.stop()));
     const [track1] = stream1.getTracks();
-    const stream2 = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream2 = await getNoiseStream({audio: true});
     t.add_cleanup(() => stream2.getTracks().forEach(track => track.stop()));
     const [track2] = stream1.getTracks();
 
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpSender-transport.https-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpSender-transport.https-expected.txt
new file mode 100644 (file)
index 0000000..6cec398
--- /dev/null
@@ -0,0 +1,7 @@
+
+FAIL RTCRtpSender.transport is null when unconnected assert_equals: expected (object) null but got (undefined) undefined
+FAIL RTCRtpSender/receiver.transport has a value when connected promise_test: Unhandled rejection with value: object "TypeError: undefined is not an object (evaluating 'sender.transport.iceTransport')"
+FAIL RTCRtpSender/receiver.transport at the right time, with bundle policy balanced assert_equals: expected (object) null but got (undefined) undefined
+FAIL RTCRtpSender/receiver.transport at the right time, with bundle policy max-bundle assert_equals: expected (object) null but got (undefined) undefined
+FAIL RTCRtpSender/receiver.transport at the right time, with bundle policy max-compat assert_equals: expected (object) null but got (undefined) undefined
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpSender-transport.https.html b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpSender-transport.https.html
new file mode 100644 (file)
index 0000000..e74495f
--- /dev/null
@@ -0,0 +1,90 @@
+<!doctype html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>RTCRtpSender.transport</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="dictionary-helper.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script>
+  'use strict';
+
+  // Spec link: http://w3c.github.io/webrtc-pc/#dom-rtcrtpsender-transport
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const [track] = stream.getTracks();
+    const sender = caller.addTrack(track);
+    assert_equals(sender.transport, null);
+  }, 'RTCRtpSender.transport is null when unconnected');
+
+  // Test for the simple/happy path of connecting a single track
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const [track] = stream.getTracks();
+    const sender = caller.addTrack(track);
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+    exchangeIceCandidates(caller, callee);
+    await exchangeOfferAndListenToOntrack(t, caller, callee);
+    assert_not_equals(sender.transport, null);
+    const [transceiver] = caller.getTransceivers();
+    assert_equals(transceiver.sender.transport,
+                  transceiver.receiver.transport);
+    assert_not_equals(sender.transport.iceTransport, null);
+  }, 'RTCRtpSender/receiver.transport has a value when connected');
+
+  // Test with multiple tracks, and checking details of when things show up
+  // for different bundle policies.
+  for (let bundle_policy of ['balanced', 'max-bundle', 'max-compat']) {
+    promise_test(async t => {
+        const caller = new RTCPeerConnection({bundlePolicy: bundle_policy});
+      t.add_cleanup(() => caller.close());
+      const stream = await navigator.mediaDevices.getUserMedia(
+          {audio: true, video:true});
+      t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+      const [track1, track2] = stream.getTracks();
+      const sender1 = caller.addTrack(track1);
+      const sender2 = caller.addTrack(track2);
+      const callee = new RTCPeerConnection();
+      t.add_cleanup(() => callee.close());
+      exchangeIceCandidates(caller, callee);
+      const offer = await caller.createOffer();
+      assert_equals(sender1.transport, null);
+      assert_equals(sender2.transport, null);
+      await caller.setLocalDescription(offer);
+      assert_not_equals(sender1.transport, null);
+      assert_not_equals(sender2.transport, null);
+      const [caller_transceiver1, caller_transceiver2] = caller.getTransceivers();
+      assert_equals(sender1.transport, caller_transceiver1.sender.transport);
+      if (bundle_policy == 'max-bundle') {
+        assert_equals(caller_transceiver1.sender.transport,
+                      caller_transceiver2.sender.transport);
+      } else {
+        assert_not_equals(caller_transceiver1.sender.transport,
+                          caller_transceiver2.sender.transport);
+      }
+      await callee.setRemoteDescription(offer);
+      const [callee_transceiver1, callee_transceiver2] = callee.getTransceivers();
+      // According to spec, setRemoteDescription only updates the transports
+      // if the remote description is an answer.
+      assert_equals(callee_transceiver1.receiver.transport, null);
+      assert_equals(callee_transceiver2.receiver.transport, null);
+      const answer = await callee.createAnswer();
+      await callee.setLocalDescription(answer);
+      assert_not_equals(callee_transceiver1.receiver.transport, null);
+      assert_not_equals(callee_transceiver2.receiver.transport, null);
+      // At this point, bundle should have kicked in.
+      assert_equals(callee_transceiver1.receiver.transport,
+                    callee_transceiver2.receiver.transport);
+      await caller.setRemoteDescription(answer);
+      assert_equals(caller_transceiver1.receiver.transport,
+                    caller_transceiver2.receiver.transport);
+    }, 'RTCRtpSender/receiver.transport at the right time, with bundle policy ' + bundle_policy);
+  }
+ </script>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpTransceiver-stop-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpTransceiver-stop-expected.txt
new file mode 100644 (file)
index 0000000..40b6747
--- /dev/null
@@ -0,0 +1,6 @@
+
+PASS A transceiver added and stopped before the initial offer generation should not trigger an offer m-section generation 
+PASS During renegotiation, adding and stopping a transceiver should not trigger a renegotiated offer m-section generation 
+PASS A stopped sendonly transceiver should generate an inactive m-section in the offer 
+PASS A stopped inactive transceiver should generate an inactive m-section in the offer 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpTransceiver-stop.html b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpTransceiver-stop.html
new file mode 100644 (file)
index 0000000..b45e03b
--- /dev/null
@@ -0,0 +1,82 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCRtpTransceiver.prototype.stop</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script>
+// FIXME: Add a test adding a transceiver, stopping it and trying to create an empty offer.
+
+promise_test(async (t)=> {
+    const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
+
+    pc1.addTransceiver("audio", { direction: "sendonly" });
+    pc1.addTransceiver("video");
+    pc1.getTransceivers()[0].stop();
+
+    const offer = await pc1.createOffer();
+
+    assert_false(offer.sdp.includes("m=audio"), "offer should not contain an audio m-section");
+    assert_true(offer.sdp.includes("m=video"), "offer should contain a video m-section");
+}, "A transceiver added and stopped before the initial offer generation should not trigger an offer m-section generation");
+
+promise_test(async (t)=> {
+    const pc1 = new RTCPeerConnection();
+    const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
+    t.add_cleanup(() => pc2.close());
+
+    pc1.addTransceiver("audio");
+
+    await exchangeOfferAnswer(pc1, pc2);
+
+    pc1.addTransceiver("video");
+
+    pc1.getTransceivers()[0].stop();
+    pc1.getTransceivers()[1].stop();
+
+    const offer = await pc1.createOffer();
+
+    assert_true(offer.sdp.includes("m=audio"), "offer should contain an audio m-section");
+    assert_true(offer.sdp.includes("m=audio 0"), "The audio m-section should be rejected");
+
+    assert_false(offer.sdp.includes("m=video"), "offer should not contain a video m-section");
+}, "During renegotiation, adding and stopping a transceiver should not trigger a renegotiated offer m-section generation");
+
+promise_test(async (t)=> {
+    const pc1 = new RTCPeerConnection();
+    const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
+    t.add_cleanup(() => pc2.close());
+
+    pc1.addTransceiver("audio");
+
+    await exchangeOfferAnswer(pc1, pc2);
+
+    pc1.getTransceivers()[0].direction = "sendonly";
+    pc1.getTransceivers()[0].stop();
+
+    const offer = await pc1.createOffer();
+
+    assert_true(offer.sdp.includes("a=sendonly"), "The audio m-section should be sendonly");
+}, "A stopped sendonly transceiver should generate an inactive m-section in the offer");
+
+promise_test(async (t)=> {
+    const pc1 = new RTCPeerConnection();
+    const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
+    t.add_cleanup(() => pc2.close());
+
+    pc1.addTransceiver("audio");
+
+    await exchangeOfferAnswer(pc1, pc2);
+
+    pc1.getTransceivers()[0].direction = "inactive";
+    pc1.getTransceivers()[0].stop();
+
+    const offer = await pc1.createOffer();
+
+    assert_true(offer.sdp.includes("a=inactive"), "The audio m-section should be inactive");
+}, "A stopped inactive transceiver should generate an inactive m-section in the offer");
+</script>
index 92bcf0e..d180762 100644 (file)
@@ -5,10 +5,12 @@ PASS checkAddTransceiverNoTrack
 PASS checkAddTransceiverWithTrack 
 PASS checkAddTransceiverWithAddTrack 
 PASS checkAddTransceiverWithDirection 
+FAIL checkMsidNoTrackId promise_test: Unhandled rejection with value: object "SyntaxError: Expects 2 fields."
 PASS checkAddTransceiverWithSetRemoteOfferSending 
 PASS checkAddTransceiverWithSetRemoteOfferNoSend 
 PASS checkAddTransceiverBadKind 
 PASS checkNoMidOffer 
+PASS checkNoMidAnswer 
 PASS checkSetDirection 
 PASS checkCurrentDirection 
 PASS checkSendrecvWithNoSendTrack 
index 27eecfc..b5f3c55 100644 (file)
     const pc = new RTCPeerConnection();
     t.add_cleanup(() => pc.close());
 
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
+    const stream = await getNoiseStream({audio: true, video: true});
     t.add_cleanup(() => stopTracks(stream));
     const audio = stream.getAudioTracks()[0];
     const video = stream.getVideoTracks()[0];
     const pc = new RTCPeerConnection();
     t.add_cleanup(() => pc.close());
 
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
+    const stream = await getNoiseStream({audio: true, video: true});
     t.add_cleanup(() => stopTracks(stream));
     const audio = stream.getAudioTracks()[0];
     const video = stream.getVideoTracks()[0];
     t.add_cleanup(() => pc1.close());
     t.add_cleanup(() => pc2.close());
 
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(stream));
     const track = stream.getAudioTracks()[0];
     pc1.addTransceiver(track, {streams: [stream]});
     t.add_cleanup(() => pc1.close());
     t.add_cleanup(() => pc2.close());
 
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(stream));
     const track = stream.getAudioTracks()[0];
     pc1.addTransceiver(track);
     hasProps(pc.getTransceivers(), []);
   };
 
+  const checkMsidNoTrackId = async t => {
+    const pc1 = new RTCPeerConnection();
+    const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
+    t.add_cleanup(() => pc2.close());
+    const stream = await getNoiseStream({audio: true});
+    t.add_cleanup(() => stopTracks(stream));
+    const track = stream.getAudioTracks()[0];
+    pc1.addTrack(track, stream);
+    const offer = await pc1.createOffer();
+    await pc1.setLocalDescription(offer);
+    // Remove track-id from msid
+    offer.sdp = offer.sdp.replace(/(a=msid:[^ \t]+).*\r\n/g, "$1\r\n");
+    assert_true(offer.sdp.includes(`a=msid:${stream.id}\r\n`));
+    await pc2.setRemoteDescription(offer);
+    const answer = await pc2.createAnswer();
+    await pc1.setRemoteDescription(answer);
+    await pc2.setLocalDescription(answer);
+  };
+
   const checkNoMidOffer = async t => {
     const pc1 = new RTCPeerConnection();
     const pc2 = new RTCPeerConnection();
     t.add_cleanup(() => pc1.close());
     t.add_cleanup(() => pc2.close());
 
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(stream));
     const track = stream.getAudioTracks()[0];
     pc1.addTrack(track, stream);
     await pc1.setRemoteDescription(answer);
   };
 
+  const checkNoMidAnswer = async t => {
+    const pc1 = new RTCPeerConnection();
+    const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
+    t.add_cleanup(() => pc2.close());
+
+    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    t.add_cleanup(() => stopTracks(stream));
+    const track = stream.getAudioTracks()[0];
+    pc1.addTrack(track, stream);
+
+    const offer = await pc1.createOffer();
+    await pc1.setLocalDescription(offer);
+    await pc2.setRemoteDescription(offer);
+    let answer = await pc2.createAnswer();
+    // Remove mid attr
+    answer.sdp = answer.sdp.replace("a=mid:", "a=unknownattr:");
+    // Remove group attr also
+    answer.sdp = answer.sdp.replace("a=group:", "a=unknownattr:");
+    await pc1.setRemoteDescription(answer);
+
+    hasPropsAndUniqueMids(pc1.getTransceivers(),
+      [
+        {
+          receiver: {track: {kind: "audio"}},
+          sender: {track: {kind: "audio"}},
+          direction: "sendrecv",
+          currentDirection: "sendonly",
+          stopped: false
+        }
+      ]);
+
+    const reoffer = await pc1.createOffer();
+    await pc1.setLocalDescription(reoffer);
+  };
+
   const checkAddTransceiverNoTrackDoesntPair = async t => {
     const pc1 = new RTCPeerConnection();
     const pc2 = new RTCPeerConnection();
     t.add_cleanup(() => pc2.close());
     pc1.addTransceiver("audio");
 
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(stream));
     const track = stream.getAudioTracks()[0];
     pc2.addTransceiver(track);
     pc1.addTransceiver("audio");
     pc2.addTransceiver("audio");
 
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(stream));
     const track = stream.getAudioTracks()[0];
-    pc2.getTransceivers()[0].sender.replaceTrack(track);
+    await pc2.getTransceivers()[0].sender.replaceTrack(track);
 
     const offer = await pc1.createOffer();
     const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer);
     pc1.addTransceiver("audio");
     pc2.addTransceiver("audio");
 
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(stream));
     const track = stream.getAudioTracks()[0];
     pc2.addTrack(track, stream);
     t.add_cleanup(() => pc2.close());
     pc1.addTransceiver("audio");
 
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(stream));
     const track = stream.getAudioTracks()[0];
     pc2.addTrack(track, stream);
     t.add_cleanup(() => pc2.close());
     pc1.addTransceiver("audio");
 
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(stream));
     const track = stream.getAudioTracks()[0];
     pc2.addTrack(track, stream);
-    pc2.getTransceivers()[0].sender.replaceTrack(null);
+    await pc2.getTransceivers()[0].sender.replaceTrack(null);
 
     const offer = await pc1.createOffer();
     const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer);
     const pc2 = new RTCPeerConnection();
     t.add_cleanup(() => pc1.close());
     t.add_cleanup(() => pc2.close());
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(stream));
     const track = stream.getAudioTracks()[0];
     pc1.addTrack(track, stream);
     const pc = new RTCPeerConnection();
     t.add_cleanup(() => pc.close());
     pc.addTransceiver("audio");
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     const audio = stream.getAudioTracks()[0];
     let sender = pc.addTrack(audio, stream);
     pc.removeTrack(sender);
     const pc2 = new RTCPeerConnection();
     t.add_cleanup(() => pc1.close());
     t.add_cleanup(() => pc2.close());
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
+    const stream = await getNoiseStream({audio: true, video: true});
     t.add_cleanup(() => stopTracks(stream));
     const audio = stream.getAudioTracks()[0];
     pc1.addTrack(audio, stream);
     t.add_cleanup(() => pc1.close());
     t.add_cleanup(() => pc2.close());
 
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(stream));
     const track = stream.getAudioTracks()[0];
     pc1.addTrack(track, stream);
     t.add_cleanup(() => pc1.close());
     t.add_cleanup(() => pc2.close());
 
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(stream));
     const track = stream.getAudioTracks()[0];
     pc1.addTransceiver("audio");
   const checkMute = async t => {
     const pc1 = new RTCPeerConnection();
     t.add_cleanup(() => pc1.close());
-    const stream1 = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
+    const stream1 = await getNoiseStream({audio: true, video: true});
     t.add_cleanup(() => stopTracks(stream1));
     const audio1 = stream1.getAudioTracks()[0];
     pc1.addTrack(audio1, stream1);
 
     const pc2 = new RTCPeerConnection();
     t.add_cleanup(() => pc2.close());
-    const stream2 = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
+    const stream2 = await getNoiseStream({audio: true, video: true});
     t.add_cleanup(() => stopTracks(stream2));
     const audio2 = stream2.getAudioTracks()[0];
     pc2.addTrack(audio2, stream2);
   const checkStop = async t => {
     const pc1 = new RTCPeerConnection();
     t.add_cleanup(() => pc1.close());
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(stream));
     const track = stream.getAudioTracks()[0];
     pc1.addTrack(track, stream);
                         transceiver.sender.getParameters()),
                 "InvalidStateError", "setParameters on stopped transceiver");
 
-    const stream2 = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream2 = await getNoiseStream({audio: true});
     const track2 = stream.getAudioTracks()[0];
     checkThrows(() => transceiver.sender.replaceTrack(track2),
                 "InvalidStateError", "replaceTrack on stopped transceiver");
     t.add_cleanup(() => pc1.close());
     t.add_cleanup(() => pc2.close());
 
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(stream));
     const track = stream.getAudioTracks()[0];
     pc1.addTrack(track, stream);
     t.add_cleanup(() => pc1.close());
     t.add_cleanup(() => pc2.close());
 
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(stream));
     const track = stream.getAudioTracks()[0];
     pc1.addTrack(track, stream);
     t.add_cleanup(() => pc1.close());
     t.add_cleanup(() => pc2.close());
 
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(stream));
     const track = stream.getAudioTracks()[0];
     pc1.addTrack(track, stream);
     t.add_cleanup(() => pc1.close());
     t.add_cleanup(() => pc2.close());
 
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(stream));
     const track = stream.getAudioTracks()[0];
     pc1.addTrack(track, stream);
     t.add_cleanup(() => pc1.close());
     t.add_cleanup(() => pc2.close());
 
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(stream));
     const track = stream.getAudioTracks()[0];
     pc1.addTrack(track, stream);
     t.add_cleanup(() => pc1.close());
     t.add_cleanup(() => pc2.close());
 
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(stream));
     const track = stream.getAudioTracks()[0];
     pc1.addTrack(track, stream);
     const pc = new RTCPeerConnection();
     t.add_cleanup(() => pc.close());
 
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(stream));
     const track = stream.getAudioTracks()[0];
     pc.addTrack(track, stream);
 
     // Verify that rollback doesn't stomp things it should not
     pc.getTransceivers()[0].direction = "sendonly";
-    const stream2 = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream2 = await getNoiseStream({audio: true});
     const track2 = stream2.getAudioTracks()[0];
     await pc.getTransceivers()[0].sender.replaceTrack(track2);
 
     const pc1 = new RTCPeerConnection();
     t.add_cleanup(() => pc1.close());
 
-    const audioStream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const audioStream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(audioStream));
     const audioTrack = audioStream.getAudioTracks()[0];
     pc1.addTrack(audioTrack, audioStream);
     const pc2 = new RTCPeerConnection();
     t.add_cleanup(() => pc2.close());
 
-    const videoStream = await navigator.mediaDevices.getUserMedia({video: true});
+    const videoStream = await getNoiseStream({video: true});
     t.add_cleanup(() => stopTracks(videoStream));
     const videoTrack = videoStream.getVideoTracks()[0];
     pc2.addTrack(videoTrack, videoStream);
     const pc1 = new RTCPeerConnection();
     t.add_cleanup(() => pc1.close());
 
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(stream));
     const track = stream.getAudioTracks()[0];
     pc1.addTrack(track, stream);
     const mid0 = pc2.getTransceivers()[0].mid;
 
     // Give pc2 a track with replaceTrack
-    const stream2 = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream2 = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(stream2));
     const track2 = stream2.getAudioTracks()[0];
     await pc2.getTransceivers()[0].sender.replaceTrack(track2);
     t.add_cleanup(() => pc1.close());
     t.add_cleanup(() => pc2.close());
 
-    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(stream));
     const track = stream.getAudioTracks()[0];
     pc1.addTrack(track, stream);
       ]);
 
     // Check that m-section is reused on both ends
-    const stream2 = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream2 = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(stream2));
     const track2 = stream2.getAudioTracks()[0];
 
     // new one for the new track)
     const stoppedMid1 = pc1.getTransceivers()[1].mid;
     pc1.getTransceivers()[1].stop();
-    const stream3 = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream3 = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(stream3));
     const track3 = stream3.getAudioTracks()[0];
     pc1.addTrack(track3, stream3);
       ]);
 
     // Add _another_ track; this should reuse the disabled m-section
-    const stream4 = await navigator.mediaDevices.getUserMedia({audio: true});
+    const stream4 = await getNoiseStream({audio: true});
     t.add_cleanup(() => stopTracks(stream4));
     const track4 = stream4.getAudioTracks()[0];
     pc2.addTrack(track4, stream4);
@@ -2131,10 +2187,12 @@ const tests = [
   checkAddTransceiverWithTrack,
   checkAddTransceiverWithAddTrack,
   checkAddTransceiverWithDirection,
+  checkMsidNoTrackId,
   checkAddTransceiverWithSetRemoteOfferSending,
   checkAddTransceiverWithSetRemoteOfferNoSend,
   checkAddTransceiverBadKind,
   checkNoMidOffer,
+  checkNoMidAnswer,
   checkSetDirection,
   checkCurrentDirection,
   checkSendrecvWithNoSendTrack,
index 9579dd4..a0c031e 100644 (file)
@@ -41,7 +41,7 @@
     assert_equals(trackEvent.receiver, receiver);
     assert_equals(trackEvent.track, track);
     assert_array_equals(trackEvent.streams, []);
-    assert_equals(trackEvent.streams, trackEvent.streams); // [SameObject]
+    assert_equals(trackEvent.streams, trackEvent.streams, '[SameObject]');
     assert_equals(trackEvent.transceiver, transceiver);
 
     assert_equals(trackEvent.type, 'track');
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCTrackEvent-fire-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCTrackEvent-fire-expected.txt
new file mode 100644 (file)
index 0000000..1078fd9
--- /dev/null
@@ -0,0 +1,6 @@
+
+Harness Error (TIMEOUT), message = null
+
+TIMEOUT Applying a remote description with removed msid should trigger firing a removetrack event on the corresponding stream Test timed out
+NOTRUN Applying a remote description with a new msid should trigger firing an event with populated streams 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCTrackEvent-fire.html b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCTrackEvent-fire.html
new file mode 100644 (file)
index 0000000..2e226c3
--- /dev/null
@@ -0,0 +1,80 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Change of msid in remote description should trigger related track events</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+const sdpBase =`v=0
+o=- 5511237691691746 2 IN IP4 127.0.0.1
+s=-
+t=0 0
+a=group:BUNDLE 0
+a=ice-options:trickle
+a=ice-lite
+a=msid-semantic:WMS *
+m=audio 9 UDP/TLS/RTP/SAVPF 111 103 9 102 0 8 105 13 110 113 126
+c=IN IP6 ::
+a=rtcp:9 IN IP6 ::
+a=rtcp-mux
+a=mid:0
+a=sendrecv
+a=ice-ufrag:z0i8R3C9C4hPRWls
+a=ice-pwd:O7bPpOFAqasqoidV4yxnFVbc
+a=ice-lite
+a=fingerprint:sha-256 B7:9C:0D:C9:D1:42:57:97:82:4D:F9:B7:93:75:49:C3:42:21:5A:DD:9C:B5:ED:53:53:F0:B4:C8:AE:88:7A:E7
+a=setup:actpass
+a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
+a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
+a=rtpmap:0 PCMU/8000`;
+
+const sdp0 = sdpBase + `
+`;
+
+const sdp1 = sdpBase + `
+a=msid:1 2
+a=ssrc:3 cname:4
+a=ssrc:3 msid:1 2
+`;
+
+async function applyRemoteDescriptionAndReturnRemoteTrackAndStreams(pc, sdp)
+{
+    const testTrackPromise = new Promise(resolve  => {
+        pc.ontrack = (event) => { resolve([event.track, event.streams]); };
+    });
+    await pc.setRemoteDescription({type: 'offer', sdp: sdp});
+    return testTrackPromise;
+}
+
+promise_test(async test => {
+    const pc = new RTCPeerConnection();
+    test.add_cleanup(() => pc.close());
+
+    const [track, streams] = await applyRemoteDescriptionAndReturnRemoteTrackAndStreams(pc, sdp1);
+    assert_equals(streams.length, 1, "track event has a stream");
+    assert_equals(streams[0].id, "1", "msid should match");
+    const stream = streams[0];
+
+    await pc.setLocalDescription(await pc.createAnswer());
+
+    const testTrackPromise = new Promise((resolve) => { stream.onremovetrack = resolve; });
+    await pc.setRemoteDescription({type: 'offer', 'sdp': sdp0});
+    await testTrackPromise;
+
+    assert_equals(stream.getAudioTracks().length, 0, "stream should be empty");
+}, "Applying a remote description with removed msid should trigger firing a removetrack event on the corresponding stream");
+
+promise_test(async test => {
+    const pc = new RTCPeerConnection();
+    test.add_cleanup(() => pc.close());
+
+    let [track0, streams0] = await applyRemoteDescriptionAndReturnRemoteTrackAndStreams(pc, sdp0);
+
+    await pc.setLocalDescription(await pc.createAnswer());
+
+    let [track1, streams1] = await applyRemoteDescriptionAndReturnRemoteTrackAndStreams(pc, sdp1);
+
+    assert_equals(streams1.length, 1, "track event has a stream");
+    assert_equals(streams1[0].id, "1", "msid should match");
+    assert_equals(streams1[0].getTracks()[0], track0, "track should match");
+}, "Applying a remote description with a new msid should trigger firing an event with populated streams");
+</script>
index 98c9ab2..633ab6c 100644 (file)
@@ -3,125 +3,8 @@ PASS Can get stats from a basic WebRTC call.
 Retrieved stats info
 
   
-{
-  "RTCCertificate_E6:B4:F7:7C:4A:9E:86:FB:65:54:55:DE:E7:04:81:CA:A5:FD:21:9F:B2:92:EE:21:EF:C2:44:E7:1D:4E:E8:86": {
-    "id": "RTCCertificate_E6:B4:F7:7C:4A:9E:86:FB:65:54:55:DE:E7:04:81:CA:A5:FD:21:9F:B2:92:EE:21:EF:C2:44:E7:1D:4E:E8:86",
-    "timestamp": 1542138647360.0002,
-    "type": "certificate",
-    "base64Certificate": "MIIBFzCBvaADAgECAgkAmUOKhXabECcwCgYIKoZIzj0EAwIwETEPMA0GA1UEAwwGV2ViUlRDMB4XDTE4MTExMjE5NTA0N1oXDTE4MTIxMzE5NTA0N1owETEPMA0GA1UEAwwGV2ViUlRDMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpv51bCUk/FtnnFXwLUaHKwVoBX/fjpq0wXV5QuxjLd4GLBJb1nD014bmUlw63Pizl4nRoQBIfyOFAdbW9UUKHzAKBggqhkjOPQQDAgNJADBGAiEAjhJk8wni+BCZZeHqX0nGwANgtIm1ZwAywWaa5+selbICIQCwGDEehbP+MfZ62tfej4avrohy4ykO5xC1+5fL0zmZQQ==",
-    "fingerprint": "E6:B4:F7:7C:4A:9E:86:FB:65:54:55:DE:E7:04:81:CA:A5:FD:21:9F:B2:92:EE:21:EF:C2:44:E7:1D:4E:E8:86",
-    "fingerprintAlgorithm": "sha-256"
-  },
-  "RTCCertificate_FF:81:96:D2:F0:D3:75:95:AE:08:99:64:8A:95:F3:1F:27:51:38:3E:83:C5:2D:17:1D:41:5B:F5:36:1E:22:AC": {
-    "id": "RTCCertificate_FF:81:96:D2:F0:D3:75:95:AE:08:99:64:8A:95:F3:1F:27:51:38:3E:83:C5:2D:17:1D:41:5B:F5:36:1E:22:AC",
-    "timestamp": 1542138647360.0002,
-    "type": "certificate",
-    "base64Certificate": "MIIBFjCBvaADAgECAgkA0YyL/axVxyUwCgYIKoZIzj0EAwIwETEPMA0GA1UEAwwGV2ViUlRDMB4XDTE4MTExMjE5NTA0N1oXDTE4MTIxMzE5NTA0N1owETEPMA0GA1UEAwwGV2ViUlRDMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHUTz7lUs8fxFqSOEMl04JHlyj2PItG98Q6C/W2+vwKlflcVRV3wGUsRLwwENbuDEH95U4VY0F7KWNgqG8W5eEzAKBggqhkjOPQQDAgNIADBFAiBiJZQlXIgiIsCUAoJv8uzAsUl/US6MiknEkA9OABXDbwIhANaTPbugE7/Ly/9R0zqlu21V1YfF/v6H34oX/wiOyf2p",
-    "fingerprint": "FF:81:96:D2:F0:D3:75:95:AE:08:99:64:8A:95:F3:1F:27:51:38:3E:83:C5:2D:17:1D:41:5B:F5:36:1E:22:AC",
-    "fingerprintAlgorithm": "sha-256"
-  },
-  "RTCDataChannel_1": {
-    "id": "RTCDataChannel_1",
-    "timestamp": 1542138647360.0002,
-    "type": "data-channel",
-    "bytesReceived": 0,
-    "bytesSent": 0,
-    "datachannelid": 1,
-    "label": "channel",
-    "messagesReceived": 0,
-    "messagesSent": 0,
-    "protocol": "",
-    "state": "open"
-  },
-  "RTCIceCandidatePair_z6z3iTpo_9P4G3ojB": {
-    "id": "RTCIceCandidatePair_z6z3iTpo_9P4G3ojB",
-    "timestamp": 1542138647360.0002,
-    "type": "candidate-pair",
-    "bytesReceived": 694,
-    "bytesSent": 1309,
-    "currentRoundTripTime": 0.003,
-    "localCandidateId": "RTCIceCandidate_z6z3iTpo",
-    "nominated": true,
-    "priority": 9079290933572287000,
-    "remoteCandidateId": "RTCIceCandidate_9P4G3ojB",
-    "requestsReceived": 1,
-    "requestsSent": 1,
-    "responsesReceived": 1,
-    "responsesSent": 1,
-    "state": "succeeded",
-    "totalRoundTripTime": 0.003,
-    "transportId": "RTCTransport_0_1",
-    "writable": true
-  },
-  "RTCIceCandidatePair_z6z3iTpo_OlinD0xX": {
-    "id": "RTCIceCandidatePair_z6z3iTpo_OlinD0xX",
-    "timestamp": 1542138647360.0002,
-    "type": "candidate-pair",
-    "bytesReceived": 0,
-    "bytesSent": 0,
-    "localCandidateId": "RTCIceCandidate_z6z3iTpo",
-    "nominated": false,
-    "priority": 9079290933572287000,
-    "remoteCandidateId": "RTCIceCandidate_OlinD0xX",
-    "requestsReceived": 0,
-    "requestsSent": 0,
-    "responsesReceived": 0,
-    "responsesSent": 0,
-    "state": "waiting",
-    "totalRoundTripTime": 0,
-    "transportId": "RTCTransport_0_1",
-    "writable": false
-  },
-  "RTCIceCandidate_9P4G3ojB": {
-    "id": "RTCIceCandidate_9P4G3ojB",
-    "timestamp": 1542138647360.0002,
-    "type": "remote-candidate",
-    "candidateType": "host",
-    "deleted": false,
-    "port": 62426,
-    "priority": 2113937151,
-    "protocol": "udp",
-    "transportId": "RTCTransport_0_1"
-  },
-  "RTCIceCandidate_OlinD0xX": {
-    "id": "RTCIceCandidate_OlinD0xX",
-    "timestamp": 1542138647360.0002,
-    "type": "remote-candidate",
-    "candidateType": "host",
-    "deleted": false,
-    "port": 54933,
-    "priority": 2113937151,
-    "protocol": "udp",
-    "transportId": "RTCTransport_0_1"
-  },
-  "RTCIceCandidate_z6z3iTpo": {
-    "id": "RTCIceCandidate_z6z3iTpo",
-    "timestamp": 1542138647360.0002,
-    "type": "local-candidate",
-    "candidateType": "host",
-    "deleted": false,
-    "port": 53616,
-    "priority": 2113937151,
-    "protocol": "udp",
-    "transportId": "RTCTransport_0_1"
-  },
-  "RTCPeerConnection": {
-    "id": "RTCPeerConnection",
-    "timestamp": 1542138647360.0002,
-    "type": "peer-connection",
-    "dataChannelsClosed": 0,
-    "dataChannelsOpened": 1
-  },
-  "RTCTransport_0_1": {
-    "id": "RTCTransport_0_1",
-    "timestamp": 1542138647360.0002,
-    "type": "transport",
-    "bytesReceived": 694,
-    "bytesSent": 1309,
-    "localCertificateId": "RTCCertificate_E6:B4:F7:7C:4A:9E:86:FB:65:54:55:DE:E7:04:81:CA:A5:FD:21:9F:B2:92:EE:21:EF:C2:44:E7:1D:4E:E8:86",
-    "remoteCertificateId": "RTCCertificate_FF:81:96:D2:F0:D3:75:95:AE:08:99:64:8A:95:F3:1F:27:51:38:3E:83:C5:2D:17:1D:41:5B:F5:36:1E:22:AC",
-    "selectedCandidatePairId": "RTCIceCandidatePair_z6z3iTpo_9P4G3ojB"
-  }
-}
+  
+
+  
 
   
index 053d909..979e99c 100644 (file)
@@ -12,6 +12,7 @@ This test uses data only, and thus does not require fake media devices.
   <div id="log"></div>
   <h2>Retrieved stats info</h2>
   <pre>
+  <input type="button" onclick="showStats()" value="Show stats"></input>
   <div id="stats">
   </div>
   </pre>
@@ -21,7 +22,7 @@ This test uses data only, and thus does not require fake media devices.
   <script src="/resources/testharnessreport.js"></script>
   <script type="text/javascript">
   var test = async_test('Can get stats from a basic WebRTC call.');
-
+  var statsToShow;
   var gFirstConnection = null;
   var gSecondConnection = null;
 
@@ -56,13 +57,11 @@ This test uses data only, and thus does not require fake media devices.
     }
     gFirstConnection.getStats()
     .then(function(report) {
-      // Show the retrieved stats info
-      var showStats = document.getElementById('stats');
       let reportDictionary = {};
       for (let stats of report.values()) {
         reportDictionary[stats.id] = stats;
       }
-      showStats.innerHTML = JSON.stringify(reportDictionary, null, 2);
+      statsToShow = JSON.stringify(reportDictionary, null, 2);
       // Check the stats properties.
       assert_not_equals(report, null, 'No report');
       let sessionStat = getStatsRecordByType(report, 'peer-connection');
@@ -121,6 +120,13 @@ This test uses data only, and thus does not require fake media devices.
                        ' happened at step ' + atStep);
     }));
   });
+
+  function showStats() {
+    // Show the retrieved stats info
+    var showStats = document.getElementById('stats');
+    showStats.innerHTML = statsToShow;
+  }
+
 </script>
 
 </body>
index 0f2e2a3..10933fa 100644 (file)
@@ -1,7 +1,4 @@
 <!doctype html>
-<!--
-This test uses the legacy callback API with no media, and thus does not require fake media devices.
--->
 
 <html>
 <head>
@@ -37,7 +34,6 @@ This test uses the legacy callback API with no media, and thus does not require
 
     var parsedOffer = new RTCSessionDescription({ type: 'offer',
                                                   sdp: offerSdp });
-    // These functions use the legacy interface extensions to RTCPeerConnection.
     gSecondConnection.setRemoteDescription(parsedOffer).then(
       function() {
         gSecondConnection.createAnswer().then(onAnswerCreated,
index 062db85..1310717 100644 (file)
@@ -14,6 +14,8 @@ The main specifications are given in the following internet-drafts:
 - draft-ietf-rtcweb-fec
 - draft-ietf-rtcweb-data-protocol
 - draft-ietf-rtcweb-data-channel
+- draft-ietf-mmusic-sdp-simulcast
+- draft-ietf-mmusic-rid
 
 - RFC 7742, "WebRTC Video Processing and Codec Requirements"
 - RFC 7874, "WebRTC Audio Codec and Processing Requirements"
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/missing-fields-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/missing-fields-expected.txt
new file mode 100644 (file)
index 0000000..d1cf968
--- /dev/null
@@ -0,0 +1,4 @@
+
+PASS Offer description with no mid is accepted 
+PASS Answer description with no mid is accepted 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/missing-fields.html b/LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/missing-fields.html
new file mode 100644 (file)
index 0000000..d5aafd2
--- /dev/null
@@ -0,0 +1,47 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCPeerconnection SDP parse tests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../RTCPeerConnection-helper.js"></script>
+<script>
+'use strict';
+
+function removeSdpLines(description, toRemove) {
+  const edited = description.sdp.split('\n').filter(function(line) {
+    return (!line.startsWith(toRemove));
+  }).join('\n');
+  return {type: description.type, sdp: edited};
+}
+
+promise_test(async t => {
+  const caller = new RTCPeerConnection();
+  const callee = new RTCPeerConnection();
+  t.add_cleanup(() => caller.close());
+  t.add_cleanup(() => callee.close());
+  caller.addTrack(trackFactories.audio());
+  const offer = await caller.createOffer();
+  await caller.setLocalDescription(offer);
+  let remote_offer = removeSdpLines(offer, 'a=mid:');
+  remote_offer = removeSdpLines(remote_offer, 'a=group:');
+  await callee.setRemoteDescription(remote_offer);
+  const answer = await callee.createAnswer();
+  await caller.setRemoteDescription(answer);
+}, 'Offer description with no mid is accepted');
+
+promise_test(async t => {
+  const caller = new RTCPeerConnection();
+  const callee = new RTCPeerConnection();
+  t.add_cleanup(() => caller.close());
+  t.add_cleanup(() => callee.close());
+  caller.addTrack(trackFactories.audio());
+  const offer = await caller.createOffer();
+  await caller.setLocalDescription(offer);
+  await callee.setRemoteDescription(offer);
+  const answer = await callee.createAnswer();
+  let remote_answer = removeSdpLines(answer, 'a=mid:');
+  remote_answer = removeSdpLines(remote_answer, 'a=group:');
+  await caller.setRemoteDescription(remote_answer);
+}, 'Answer description with no mid is accepted');
+
+</script>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/msid-parse-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/msid-parse-expected.txt
new file mode 100644 (file)
index 0000000..c1c6b4c
--- /dev/null
@@ -0,0 +1,6 @@
+
+PASS Description with no msid produces a track with a stream 
+PASS Description with msid:- appid produces a track with no stream 
+PASS Description with msid:foo bar produces a stream with id foo 
+PASS Description with two msid produces two streams 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/msid-parse.html b/LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/msid-parse.html
new file mode 100644 (file)
index 0000000..e0d3eb7
--- /dev/null
@@ -0,0 +1,69 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCPeerconnection MSID parsing</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../RTCPeerConnection-helper.js"></script>
+<script>
+'use strict';
+const preamble = `v=0
+o=- 0 3 IN IP4 127.0.0.1
+s=-
+t=0 0
+a=fingerprint:sha-256 A7:24:72:CA:6E:02:55:39:BA:66:DF:6E:CC:4C:D8:B0:1A:BF:1A:56:65:7D:F4:03:AD:7E:77:43:2A:29:EC:93
+a=ice-ufrag:6HHHdzzeIhkE0CKj
+a=ice-pwd:XYDGVpfvklQIEnZ6YnyLsAew
+m=video 1 RTP/SAVPF 100
+a=rtcp-mux
+a=sendonly
+a=mid:video
+a=rtpmap:100 VP8/30
+`;
+
+
+promise_test(async t => {
+  const pc = new RTCPeerConnection();
+  t.add_cleanup(() => pc.close());
+  const ontrackPromise = addEventListenerPromise(t, pc, 'track');
+  await pc.setRemoteDescription({type: 'offer', sdp: preamble});
+  const trackevent = await ontrackPromise;
+  assert_equals(pc.getReceivers().length, 1);
+  assert_equals(trackevent.streams.length, 1, 'Stream count');
+}, 'Description with no msid produces a track with a stream');
+
+promise_test(async t => {
+  const pc = new RTCPeerConnection();
+  t.add_cleanup(() => pc.close());
+  const ontrackPromise = addEventListenerPromise(t, pc, 'track');
+  await pc.setRemoteDescription({type: 'offer',
+                                 sdp: preamble + 'a=msid:- foobar\n'});
+  const trackevent = await ontrackPromise;
+  assert_equals(pc.getReceivers().length, 1);
+  assert_equals(trackevent.streams.length, 0);
+}, 'Description with msid:- appid produces a track with no stream');
+
+promise_test(async t => {
+  const pc = new RTCPeerConnection();
+  t.add_cleanup(() => pc.close());
+  const ontrackPromise = addEventListenerPromise(t, pc, 'track');
+  await pc.setRemoteDescription({type: 'offer',
+                                 sdp: preamble + 'a=msid:foo bar\n'});
+  const trackevent = await ontrackPromise;
+  assert_equals(pc.getReceivers().length, 1);
+  assert_equals(trackevent.streams.length, 1);
+  assert_equals(trackevent.streams[0].id, 'foo');
+}, 'Description with msid:foo bar produces a stream with id foo');
+
+promise_test(async t => {
+  const pc = new RTCPeerConnection();
+  t.add_cleanup(() => pc.close());
+  const ontrackPromise = addEventListenerPromise(t, pc, 'track');
+  await pc.setRemoteDescription({type: 'offer',
+                                 sdp: preamble + 'a=msid:foo bar\n'
+                                               + 'a=msid:baz bar\n'});
+  const trackevent = await ontrackPromise;
+  assert_equals(pc.getReceivers().length, 1);
+  assert_equals(trackevent.streams.length, 2);
+}, 'Description with two msid produces two streams');
+
+</script>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/simulcast-offer-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/simulcast-offer-expected.txt
new file mode 100644 (file)
index 0000000..dd4c729
--- /dev/null
@@ -0,0 +1,3 @@
+
+FAIL createOffer() with multiple send encodings should create simulcast offer assert_not_equals: RID attribute for 'foo' missing. got disallowed value undefined
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/simulcast-offer.html b/LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/simulcast-offer.html
new file mode 100644 (file)
index 0000000..77ae7f9
--- /dev/null
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCPeerConnection Simulcast Offer</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+'use strict';
+
+// Tests for the construction of offers with simulcast according to:
+// draft-ietf-mmusic-sdp-simulcast-13
+// draft-ietf-mmusic-rid-15
+promise_test(async t => {
+  const pc = new RTCPeerConnection();
+  t.add_cleanup(() => pc.close());
+  const expected_rids = ['foo', 'bar', 'baz'];
+  pc.addTransceiver('video', {
+    sendEncodings: expected_rids.map(rid => ({rid}))
+  });
+
+  const offer = await pc.createOffer();
+  let offer_lines = offer.sdp.split('\r\n');
+  // Check for a RID line for each layer.
+  for (const rid of expected_rids) {
+    let result = offer_lines.find(line => line.startsWith(`a=rid:${rid}`));
+    assert_not_equals(result, undefined, `RID attribute for '${rid}' missing.`);
+  }
+
+  // Check for simulcast attribute with send direction and all RIDs.
+  let result = offer_lines.find(
+      line => line.startsWith(`a=simulcast:send ${expected_rids.join(';')}`));
+  assert_not_equals(result, undefined, "Could not find simulcast attribute.");
+}, 'createOffer() with multiple send encodings should create simulcast offer');
+</script>
index 9bb9ee5..b324267 100644 (file)
@@ -16,4 +16,7 @@ None
 List of files:
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/README.txt
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/jsep-initial-offer.https.html
+/LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/missing-fields.html
+/LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/msid-parse.html
+/LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/simulcast-offer.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/video-codecs.https.html
index 4779d94..fa6be2b 100644 (file)
@@ -3,6 +3,8 @@ Description
 This test verifies the availability of the RTCPeerConnection interface.
 
 
+Harness Error (FAIL), message = Not enough arguments
+
 PASS Partial interface RTCPeerConnection: original interface defined 
 PASS Partial interface RTCPeerConnection[2]: original interface defined 
 PASS Partial interface RTCPeerConnection[3]: original interface defined 
index 8777910..d79f6af 100644 (file)
@@ -17,7 +17,7 @@
   <script src="/resources/testharness.js"></script>
   <script src="/resources/testharnessreport.js"></script>
   <script type="text/javascript">
-  var test = async_test('Can set up a basic WebRTC call without announcing ssrcs.', {timeout: 5000});
+  var test = async_test('Can set up a basic WebRTC call without announcing ssrcs.');
 
   var gFirstConnection = null;
   var gSecondConnection = null;
index 681c42d..291437a 100644 (file)
@@ -17,7 +17,7 @@
   <script src="/resources/testharness.js"></script>
   <script src="/resources/testharnessreport.js"></script>
   <script type="text/javascript">
-  var test = async_test('Can set up a basic WebRTC call.', {timeout: 5000});
+  var test = async_test('Can set up a basic WebRTC call.');
 
   var gFirstConnection = null;
   var gSecondConnection = null;
index 3e6e729..a7f3c86 100644 (file)
@@ -32,7 +32,10 @@ List of files:
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCDataChannel-send.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCDataChannelEvent-constructor.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCDtlsTransport-getRemoteCertificates.html
+/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCDtlsTransport-state.html
+/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCError.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCIceCandidate-constructor.html
+/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCIceConnectionState-candidate-pair.https.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCIceTransport-extension-helper.js
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCIceTransport-extension.https.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCIceTransport.html
@@ -51,10 +54,11 @@ List of files:
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-getStats.https.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-getTransceivers.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-helper.js
-/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-iceConnectionState.html
+/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-iceConnectionState.https.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-iceGatheringState.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-ondatachannel.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-onnegotiationneeded.html
+/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-onsignalingstatechanged.https.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-ontrack.https.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-remote-track-mute.https.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-removeTrack.https.html
@@ -65,6 +69,7 @@ List of files:
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setLocalDescription-rollback.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setLocalDescription.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-answer.html
+/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-nomsid.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-offer.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-pranswer.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-replaceTrack.https.html
@@ -74,9 +79,6 @@ List of files:
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-track-stats.https.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-transceivers.https.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnectionIceEvent-constructor.html
-/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCQuicStream.https.html
-/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCQuicTransport-helper.js
-/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCQuicTransport.https.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpCapabilities-helper.js
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpParameters-codecs.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpParameters-degradationPreference.html
@@ -94,13 +96,16 @@ List of files:
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpSender-getStats.https.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpSender-replaceTrack.https.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpSender-setParameters.html
+/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpSender-transport.https.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpTransceiver-direction.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpTransceiver-setCodecPreferences.html
+/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpTransceiver-stop.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpTransceiver.https.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCSctpTransport-constructor.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCSctpTransport-maxMessageSize.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCStats-helper.js
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCTrackEvent-constructor.html
+/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCTrackEvent-fire.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/datachannel-emptystring.html
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/dictionary-helper.js
 /LayoutTests/imported/w3c/web-platform-tests/webrtc/getstats.html
index a3cde88..ffa1115 100644 (file)
     "imported/w3c/web-platform-tests/webrtc/RTCDTMFSender-ontonechange-long.https.html": [
         "slow"
     ],
+    "imported/w3c/web-platform-tests/webrtc/RTCIceConnectionState-candidate-pair.https.html": [
+        "slow"
+    ],
     "imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-track-stats.https.html": [
         "slow"
     ],
+    "imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-transceivers.https.html": [
+        "slow"
+    ],
+    "imported/w3c/web-platform-tests/webrtc/RTCRtpReceiver-getSynchronizationSources.https.html": [
+        "slow"
+    ],
+    "imported/w3c/web-platform-tests/webrtc/RTCRtpSender-transport.https.html": [
+        "slow"
+    ],
     "imported/w3c/web-platform-tests/xhr/progress-events-response-data-gzip.htm": [
         "slow"
     ],