Refresh WPT webrtc tests
authoryouenn@apple.com <youenn@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 3 Nov 2018 14:57:19 +0000 (14:57 +0000)
committeryouenn@apple.com <youenn@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 3 Nov 2018 14:57:19 +0000 (14:57 +0000)
https://bugs.webkit.org/show_bug.cgi?id=191133

Reviewed by Eric Carlson.

LayoutTests/imported/w3c:

* web-platform-tests/webrtc/: Refreshed.
* web-platform-tests/webrtc/w3c-import.log:

LayoutTests:

* TestExpectations:

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

138 files changed:
LayoutTests/ChangeLog
LayoutTests/TestExpectations
LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/web-platform-tests/webrtc/META.yml [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/OWNERS [deleted file]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCCertificate-postMessage-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCConfiguration-iceServers-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCConfiguration-iceServers.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCConfiguration-rtcpMuxPolicy-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCConfiguration-rtcpMuxPolicy.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCDTMFSender-helper.js
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCDTMFSender-insertDTMF.https-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCDTMFSender-insertDTMF.https.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCDTMFSender-ontonechange.https-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCDTMFSender-ontonechange.https.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCDataChannel-id.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCDtlsTransport-getRemoteCertificates.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCIceTransport-extension-helper.js [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCIceTransport-extension.https-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCIceTransport-extension.https.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCIceTransport.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-add-track-no-deadlock.https-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-add-track-no-deadlock.https.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-addIceCandidate.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-addTrack.https-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-addTrack.https.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-addTransceiver.https-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-addTransceiver.https.html [moved from LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-addTransceiver.html with 89% similarity]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-canTrickleIceCandidates.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-connectionState.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-createAnswer.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-createDataChannel.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-createOffer-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-createOffer-offerToReceive-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-createOffer-offerToReceive.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-createOffer.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-getIdentityAssertion.sub-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-getIdentityAssertion.sub.html [moved from LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-getIdentityAssertion.html with 98% similarity]
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.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-ontrack.https.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-peerIdentity.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-remote-track-mute.https-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-remote-track-mute.https.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-removeTrack.https-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-removeTrack.https.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setDescription-transceiver-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setDescription-transceiver.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setLocalDescription-answer-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setLocalDescription-answer.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setLocalDescription-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setLocalDescription-offer-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setLocalDescription-offer.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setLocalDescription-pranswer-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setLocalDescription-pranswer.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setLocalDescription-rollback-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-answer-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-answer.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-expected.txt
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-pranswer-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-pranswer.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-replaceTrack.https-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-replaceTrack.https.html [new file with mode: 0644]
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-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-tracks.https.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-track-stats.https-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-track-stats.https.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-transceivers.https-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-transceivers.https.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCQuicStream.https-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCQuicStream.https.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCQuicTransport-helper.js [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCQuicTransport.https-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCQuicTransport.https.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpCapabilities-helper.js
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpParameters-codecs-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpParameters-codecs.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpParameters-degradationPreference.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-headerExtensions.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpParameters-helper.js
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpParameters-rtcp.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpParameters-transactionId-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpParameters-transactionId.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpReceiver-getCapabilities-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpReceiver-getCapabilities.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpReceiver-getContributingSources.https.html
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.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpSender-getCapabilities-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpSender-getCapabilities.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpSender-getStats.https.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpSender-replaceTrack.https-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpSender-replaceTrack.https.html [moved from LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpSender-replaceTrack.html with 72% similarity]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpSender-setParameters.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpTransceiver-setDirection.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpTransceiver.https-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCRtpTransceiver.https.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCSctpTransport-constructor.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCSctpTransport-maxMessageSize-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCSctpTransport-maxMessageSize.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/coverage/w3c-import.log
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/identity-helper.sub.js [moved from LayoutTests/imported/w3c/web-platform-tests/webrtc/identity-helper.js with 69% similarity]
LayoutTests/imported/w3c/web-platform-tests/webrtc/idlharness.https.window-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/idlharness.https.window.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/idlharness.https.window.js [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/interfaces.https-expected.txt [deleted file]
LayoutTests/imported/w3c/web-platform-tests/webrtc/interfaces.https.html [deleted file]
LayoutTests/imported/w3c/web-platform-tests/webrtc/no-media-call.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/README.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/video-codecs.https-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/video-codecs.https.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/protocol/w3c-import.log [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/rtcpeerconnection/rtcpeerconnection-idl-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/simplecall.https-expected.txt
LayoutTests/imported/w3c/web-platform-tests/webrtc/simplecall.https.html
LayoutTests/imported/w3c/web-platform-tests/webrtc/tools/README.md [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/tools/codemod-peerconnection-addcleanup [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/tools/html-codemod.js [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/tools/package.json [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/tools/w3c-import.log [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webrtc/w3c-import.log

index 092e98d..07346fe 100644 (file)
@@ -1,3 +1,12 @@
+2018-11-03  Youenn Fablet  <youenn@apple.com>
+
+        Refresh WPT webrtc tests
+        https://bugs.webkit.org/show_bug.cgi?id=191133
+
+        Reviewed by Eric Carlson.
+
+        * TestExpectations:
+
 2018-11-03  Eric Carlson  <eric.carlson@apple.com>
 
         [MediaStream] enumerateDevices should not expose devices that are not available to getUserMedia
index eef5c16..f5f1a38 100644 (file)
@@ -1223,6 +1223,7 @@ 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-ontrack.https.html [ Skip ]
 imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setLocalDescription-offer.html [ Failure ]
+imported/w3c/web-platform-tests/webrtc/RTCRtpTransceiver.https.html [ Failure ]
 
 # Uses legacy WebRTC API.
 imported/w3c/web-platform-tests/webrtc/rtcpeerconnection/setRemoteDescription.html [ Skip ]
index 23c7b77..a33666e 100644 (file)
@@ -1,3 +1,13 @@
+2018-11-03  Youenn Fablet  <youenn@apple.com>
+
+        Refresh WPT webrtc tests
+        https://bugs.webkit.org/show_bug.cgi?id=191133
+
+        Reviewed by Eric Carlson.
+
+        * web-platform-tests/webrtc/: Refreshed.
+        * web-platform-tests/webrtc/w3c-import.log:
+
 2018-11-02  Ali Juma  <ajuma@chromium.org>
 
         Allow cross-document intersection observing
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/META.yml b/LayoutTests/imported/w3c/web-platform-tests/webrtc/META.yml
new file mode 100644 (file)
index 0000000..ea2846e
--- /dev/null
@@ -0,0 +1,11 @@
+spec: https://w3c.github.io/webrtc-pc/
+suggested_reviewers:
+  - snuggs
+  - agouaillard
+  - alvestrand
+  - dontcallmedom
+  - guidou
+  - henbos
+  - phoglund
+  - youennf
+  - rwaldron
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/OWNERS b/LayoutTests/imported/w3c/web-platform-tests/webrtc/OWNERS
deleted file mode 100644 (file)
index 378bf87..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-@snuggs
-@agouaillard
-@alvestrand
-@dontcallmedom
-@guidou
-@henbos
-@phoglund
-@youennf
-@rwaldron
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCCertificate-postMessage-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCCertificate-postMessage-expected.txt
new file mode 100644 (file)
index 0000000..9bf6433
--- /dev/null
@@ -0,0 +1,5 @@
+
+PASS Check same-origin RTCCertificate serialization 
+PASS Check cross-origin RTCCertificate serialization 
+PASS Check cross-origin created RTCCertificate 
+
index cd680aa..9767e09 100644 (file)
@@ -1,5 +1,7 @@
 
 PASS new RTCPeerConnection() should have default configuration.iceServers of undefined 
+PASS new RTCPeerConnection(config) - {} should succeed 
+PASS setConfiguration(config) - {} should succeed 
 PASS new RTCPeerConnection(config) - { iceServers: null } should throw TypeError 
 PASS setConfiguration(config) - { iceServers: null } should throw TypeError 
 PASS new RTCPeerConnection(config) - { iceServers: undefined } should succeed 
@@ -12,8 +14,6 @@ PASS new RTCPeerConnection(config) - { iceServers: [undefined] } should throw Ty
 PASS setConfiguration(config) - { iceServers: [undefined] } should throw TypeError 
 PASS new RTCPeerConnection(config) - { iceServers: [{}] } should throw TypeError 
 PASS setConfiguration(config) - { iceServers: [{}] } should throw TypeError 
-FAIL new RTCPeerConnection(config) - with empty list urls should succeed assert_equals: expected (string) "password" but got (undefined) undefined
-FAIL setConfiguration(config) - with empty list urls should succeed assert_equals: expected (string) "password" but got (undefined) undefined
 FAIL new RTCPeerConnection(config) - with stun server should succeed assert_array_equals: value is "stun:stun1.example.net", expected array
 FAIL setConfiguration(config) - with stun server should succeed assert_array_equals: value is "stun:stun1.example.net", expected array
 FAIL new RTCPeerConnection(config) - with stun server array should succeed assert_equals: expected (string) "password" but got (undefined) undefined
@@ -42,42 +42,62 @@ PASS new RTCPeerConnection(config) - with turns server and only username should
 PASS setConfiguration(config) - with turns server and only username should throw InvalidAccessError 
 PASS new RTCPeerConnection(config) - with turns server and only credential should throw InvalidAccessError 
 PASS setConfiguration(config) - with turns server and only credential should throw InvalidAccessError 
+FAIL new RTCPeerConnection(config) - with "" url should throw SyntaxError assert_throws: function "() =>
+      makePc({ iceServers: [{
+        urls: ''
+      }] })" threw object "NotSupportedError: ICE server protocol not supported" ("NotSupportedError") expected object "SyntaxError" ("SyntaxError")
+FAIL setConfiguration(config) - with "" url should throw SyntaxError assert_throws: function "() =>
+      makePc({ iceServers: [{
+        urls: ''
+      }] })" threw object "NotSupportedError: ICE server protocol not supported" ("NotSupportedError") expected object "SyntaxError" ("SyntaxError")
+FAIL new RTCPeerConnection(config) - with ["stun:stun1.example.net", ""] url should throw SyntaxError assert_throws: function "() =>
+      makePc({ iceServers: [{
+        urls: ['stun:stun1.example.net', '']
+      }] })" threw object "NotSupportedError: ICE server protocol not supported" ("NotSupportedError") expected object "SyntaxError" ("SyntaxError")
+FAIL setConfiguration(config) - with ["stun:stun1.example.net", ""] url should throw SyntaxError assert_throws: function "() =>
+      makePc({ iceServers: [{
+        urls: ['stun:stun1.example.net', '']
+      }] })" threw object "NotSupportedError: ICE server protocol not supported" ("NotSupportedError") expected object "SyntaxError" ("SyntaxError")
 FAIL new RTCPeerConnection(config) - with relative url should throw SyntaxError assert_throws: function "() =>
       makePc({ iceServers: [{
         urls: 'relative-url'
-      }] })" threw object "NotSupportedError: ICE server protocol not supported" that is not a DOMException SyntaxError: property "code" is equal to 9, expected 12
+      }] })" threw object "NotSupportedError: ICE server protocol not supported" ("NotSupportedError") expected object "SyntaxError" ("SyntaxError")
 FAIL setConfiguration(config) - with relative url should throw SyntaxError assert_throws: function "() =>
       makePc({ iceServers: [{
         urls: 'relative-url'
-      }] })" threw object "NotSupportedError: ICE server protocol not supported" that is not a DOMException SyntaxError: property "code" is equal to 9, expected 12
+      }] })" threw object "NotSupportedError: ICE server protocol not supported" ("NotSupportedError") expected object "SyntaxError" ("SyntaxError")
 FAIL new RTCPeerConnection(config) - with http url should throw SyntaxError assert_throws: function "() =>
       makePc({ iceServers: [{
         urls: 'http://example.com'
-      }] })" threw object "NotSupportedError: ICE server protocol not supported" that is not a DOMException SyntaxError: property "code" is equal to 9, expected 12
+      }] })" threw object "NotSupportedError: ICE server protocol not supported" ("NotSupportedError") expected object "SyntaxError" ("SyntaxError")
 FAIL setConfiguration(config) - with http url should throw SyntaxError assert_throws: function "() =>
       makePc({ iceServers: [{
         urls: 'http://example.com'
-      }] })" threw object "NotSupportedError: ICE server protocol not supported" that is not a DOMException SyntaxError: property "code" is equal to 9, expected 12
+      }] })" threw object "NotSupportedError: ICE server protocol not supported" ("NotSupportedError") expected object "SyntaxError" ("SyntaxError")
 FAIL new RTCPeerConnection(config) - with invalid turn url should throw SyntaxError assert_throws: function "() =>
       makePc({ iceServers: [{
         urls: 'turn://example.org/foo?x=y'
-      }] })" threw object "InvalidAccessError: TURN/TURNS server requires both username and credential" that is not a DOMException SyntaxError: property "code" is equal to 15, expected 12
+      }] })" threw object "InvalidAccessError: TURN/TURNS server requires both username and credential" ("InvalidAccessError") expected object "SyntaxError" ("SyntaxError")
 FAIL setConfiguration(config) - with invalid turn url should throw SyntaxError assert_throws: function "() =>
       makePc({ iceServers: [{
         urls: 'turn://example.org/foo?x=y'
-      }] })" threw object "InvalidAccessError: TURN/TURNS server requires both username and credential" that is not a DOMException SyntaxError: property "code" is equal to 15, expected 12
+      }] })" threw object "InvalidAccessError: TURN/TURNS server requires both username and credential" ("InvalidAccessError") expected object "SyntaxError" ("SyntaxError")
 FAIL new RTCPeerConnection(config) - with invalid stun url should throw SyntaxError assert_throws: function "() =>
       makePc({ iceServers: [{
         urls: 'stun://example.org/foo?x=y'
-      }] })" threw object "InvalidAccessError: Bad Configuration Parameters" that is not a DOMException SyntaxError: property "code" is equal to 15, expected 12
+      }] })" threw object "InvalidAccessError: Bad Configuration Parameters" ("InvalidAccessError") expected object "SyntaxError" ("SyntaxError")
 FAIL setConfiguration(config) - with invalid stun url should throw SyntaxError assert_throws: function "() =>
       makePc({ iceServers: [{
         urls: 'stun://example.org/foo?x=y'
-      }] })" threw object "InvalidAccessError: Bad Configuration Parameters" that is not a DOMException SyntaxError: property "code" is equal to 15, expected 12
-FAIL new RTCPeerConnection(config) - with empty urls and credentialType password should succeed assert_equals: expected (string) "password" but got (undefined) undefined
-FAIL setConfiguration(config) - with empty urls and credentialType password should succeed assert_equals: expected (string) "password" but got (undefined) undefined
-FAIL new RTCPeerConnection(config) - with empty urls and credentialType oauth should succeed assert_equals: expected (string) "oauth" but got (undefined) undefined
-FAIL setConfiguration(config) - with empty urls and credentialType oauth should succeed assert_equals: expected (string) "oauth" but got (undefined) undefined
+      }] })" threw object "InvalidAccessError: Bad Configuration Parameters" ("InvalidAccessError") expected object "SyntaxError" ("SyntaxError")
+FAIL new RTCPeerConnection(config) - with empty urls should throw SyntaxError assert_throws: function "() =>
+      makePc({ iceServers: [{
+        urls: []
+      }] })" did not throw
+FAIL setConfiguration(config) - with empty urls should throw SyntaxError assert_throws: function "() =>
+      makePc({ iceServers: [{
+        urls: []
+      }] })" did not throw
 FAIL new RTCPeerConnection(config) - with invalid credentialType should throw TypeError assert_throws: function "() =>
       makePc({ iceServers: [{
         urls: [],
@@ -142,8 +162,4 @@ FAIL new RTCPeerConnection(config) - with stun server, credentialType oauth, and
 FAIL setConfiguration(config) - with stun server, credentialType oauth, and string credential should succeed assert_array_equals: value is "stun:stun1.example.net", expected array
 FAIL new RTCPeerConnection(config) - with stun server, credentialType password, and RTCOAuthCredential credential should succeed assert_array_equals: value is "stun:stun1.example.net", expected array
 FAIL setConfiguration(config) - with stun server, credentialType password, and RTCOAuthCredential credential should succeed assert_array_equals: value is "stun:stun1.example.net", expected array
-FAIL new RTCPeerConnection(config) - with empty urls list, credentialType oauth, and string credential should succeed assert_equals: expected (string) "oauth" but got (undefined) undefined
-FAIL setConfiguration(config) - with empty urls list, credentialType oauth, and string credential should succeed assert_equals: expected (string) "oauth" but got (undefined) undefined
-FAIL new RTCPeerConnection(config) - with empty urls list, credentialType password, and RTCOAuthCredential credential should succeed assert_equals: expected (string) "password" but got (undefined) undefined
-FAIL setConfiguration(config) - with empty urls list, credentialType password, and RTCOAuthCredential credential should succeed assert_equals: expected (string) "password" but got (undefined) undefined
 
index 42bc896..3f5c367 100644 (file)
   }, 'new RTCPeerConnection() should have default configuration.iceServers of undefined');
 
   config_test(makePc => {
+    makePc({});
+  }, '{} should succeed');
+
+  config_test(makePc => {
     assert_throws(new TypeError(), () =>
       makePc({ iceServers: null }));
   }, '{ iceServers: null } should throw TypeError');
 
   config_test(makePc => {
     const pc = makePc({ iceServers: [{
-      urls: []
-    }] });
-
-    const { iceServers } = pc.getConfiguration();
-    assert_equals(iceServers.length, 1);
-
-    const server = iceServers[0];
-    assert_array_equals(server.urls, []);
-    assert_equals(server.credentialType, 'password');
-  }, 'with empty list urls should succeed');
-
-  config_test(makePc => {
-    const pc = makePc({ iceServers: [{
       urls: 'stun:stun1.example.net'
     }] });
 
   }, `with 2 stun servers should succeed`);
 
   config_test(makePc => {
-    const pc = new RTCPeerConnection({ iceServers: [{
+    const pc = makePc({ iceServers: [{
       urls: 'turn:turn.example.org',
       username: 'user',
       credential: 'cred'
       transport-ext = 1*unreserved
    */
   config_test(makePc => {
-    assert_throws('SyntaxError', () =>
+    assert_throws(new SyntaxError(), () =>
+      makePc({ iceServers: [{
+        urls: ''
+      }] }));
+  }, 'with "" url should throw SyntaxError');
+
+  config_test(makePc => {
+    assert_throws(new SyntaxError(), () =>
+      makePc({ iceServers: [{
+        urls: ['stun:stun1.example.net', '']
+      }] }));
+  }, 'with ["stun:stun1.example.net", ""] url should throw SyntaxError');
+
+  config_test(makePc => {
+    assert_throws(new SyntaxError(), () =>
       makePc({ iceServers: [{
         urls: 'relative-url'
       }] }));
   }, 'with relative url should throw SyntaxError');
 
   config_test(makePc => {
-    assert_throws('SyntaxError', () =>
+    assert_throws(new SyntaxError(), () =>
       makePc({ iceServers: [{
         urls: 'http://example.com'
       }] }));
   }, 'with http url should throw SyntaxError');
 
   config_test(makePc => {
-    assert_throws('SyntaxError', () =>
+    assert_throws(new SyntaxError(), () =>
       makePc({ iceServers: [{
         urls: 'turn://example.org/foo?x=y'
       }] }));
   }, 'with invalid turn url should throw SyntaxError');
 
   config_test(makePc => {
-    assert_throws('SyntaxError', () =>
+    assert_throws(new SyntaxError(), () =>
       makePc({ iceServers: [{
         urls: 'stun://example.org/foo?x=y'
       }] }));
   }, 'with invalid stun url should throw SyntaxError');
 
   config_test(makePc => {
-    const pc = makePc({ iceServers: [{
-      urls: [],
-      credentialType: 'password'
-    }] });
-
-    const { iceServers } = pc.getConfiguration();
-    assert_equals(iceServers.length, 1);
-
-    const server = iceServers[0];
-    assert_array_equals(server.urls, []);
-    assert_equals(server.credentialType, 'password');
-  }, `with empty urls and credentialType password should succeed`);
-
-  config_test(makePc => {
-    const pc = makePc({ iceServers: [{
-      urls: [],
-      credentialType: 'oauth'
-    }] });
-
-    const { iceServers } = pc.getConfiguration();
-    assert_equals(iceServers.length, 1);
-
-    const server = iceServers[0];
-    assert_array_equals(server.urls, []);
-    assert_equals(server.credentialType, 'oauth');
-  }, `with empty urls and credentialType oauth should succeed`);
+    assert_throws(new SyntaxError(), () =>
+      makePc({ iceServers: [{
+        urls: []
+      }] }));
+  }, `with empty urls should throw SyntaxError`);
 
   config_test(makePc => {
     assert_throws(new TypeError(), () =>
 
   }, 'with stun server, credentialType password, and RTCOAuthCredential credential should succeed');
 
-  // credential type validation is ignored when urls is empty and there is no scheme name
-  config_test(makePc => {
-    const pc = makePc({ iceServers: [{
-      urls: [],
-      credentialType: 'oauth',
-      username: 'user',
-      credential: 'cred'
-    }] });
-
-    const { iceServers } = pc.getConfiguration();
-    assert_equals(iceServers.length, 1);
-    const server = iceServers[0];
-
-    assert_array_equals(server.urls, []);
-    assert_equals(server.credentialType, 'oauth');
-    assert_equals(server.username, 'user');
-    assert_equals(server.credential, 'cred');
-
-  }, 'with empty urls list, credentialType oauth, and string credential should succeed');
-
-  // credential type validation is ignored when urls is empty and there is no scheme name
-  config_test(makePc => {
-    const pc = makePc({ iceServers: [{
-      urls: [],
-      credentialType: 'password',
-      username: 'user',
-      credential: {
-        macKey: '',
-        accessToken: ''
-      }
-    }] });
-
-    const { iceServers } = pc.getConfiguration();
-    assert_equals(iceServers.length, 1);
-
-    const server = iceServers[0];
-    assert_array_equals(server.urls, []);
-    assert_equals(server.credentialType, 'password');
-    assert_equals(server.username, 'user');
-
-    const { credential } = server;
-    assert_equals(credential.macKey, '');
-    assert_equals(credential.accessToken, '');
-
-  }, 'with empty urls list, credentialType password, and RTCOAuthCredential credential should succeed');
-
   /*
     Tested
       4.3.2.  To set a configuration
index e7e0789..8621d0d 100644 (file)
@@ -17,4 +17,6 @@ FAIL setConfiguration({ rtcpMuxPolicy: 'require' }) with initial rtcpMuxPolicy n
       pc.setConfiguration({ rtcpMuxPolicy: 'require' })" did not throw
 FAIL setConfiguration({}) with initial rtcpMuxPolicy negotiate should throw InvalidModificationError assert_throws: function "() =>
       pc.setConfiguration({})" did not throw
+FAIL setRemoteDescription throws InvalidAccessError when called with an offer without rtcp-mux and rtcpMuxPolicy is set to require assert_throws: function "function () { throw e }" threw object "OperationError: Failed to set remote offer sdp: The m= section:audio1 is invalid. RTCP-MUX is not enabled when it is required." that is not a DOMException InvalidAccessError: property "code" is equal to 0, expected 15
+FAIL setRemoteDescription throws InvalidAccessError when called with an answer without rtcp-mux and rtcpMuxPolicy is set to require assert_throws: function "function () { throw e }" threw object "OperationError: Failed to set remote answer sdp: The order of m-lines in answer doesn't match order in offer. Rejecting answer." that is not a DOMException InvalidAccessError: property "code" is equal to 0, expected 15
 
index 6b65a12..1d99aa2 100644 (file)
       Tested    2
       Total     2
    */
+  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';
+
+  promise_test(async t => {
+    // audio-only SDP offer without BUNDLE and rtcp-mux.
+    const sdp = 'v=0\r\n' +
+        'o=- 166855176514521964 2 IN IP4 127.0.0.1\r\n' +
+        's=-\r\n' +
+        't=0 0\r\n' +
+        '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:audio1\r\n' +
+        'a=sendonly\r\n' +
+        'a=rtcp-rsize\r\n' +
+        'a=rtpmap:111 opus/48000/2\r\n';
+    const pc = new RTCPeerConnection({rtcpMuxPolicy: 'require'});
+    t.add_cleanup(() => pc.close());
+
+    return promise_rejects(t, 'InvalidAccessError', pc.setRemoteDescription({type: 'offer', sdp}));
+  }, 'setRemoteDescription throws InvalidAccessError when called with an offer without rtcp-mux and rtcpMuxPolicy is set to require');
+
+  promise_test(async t => {
+    // audio-only SDP answer without BUNDLE and rtcp-mux.
+    // Also omitting a=mid in order to avoid parsing it from the offer as this needs to match.
+    const sdp = 'v=0\r\n' +
+        'o=- 166855176514521964 2 IN IP4 127.0.0.1\r\n' +
+        's=-\r\n' +
+        't=0 0\r\n' +
+        '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:active\r\n' +
+        'a=sendonly\r\n' +
+        'a=rtcp-rsize\r\n' +
+        'a=rtpmap:111 opus/48000/2\r\n';
+    const pc = new RTCPeerConnection({rtcpMuxPolicy: 'require'});
+    t.add_cleanup(() => pc.close());
+
+    await pc.createOffer({offerToReceiveAudio: true})
+      .then(offer => 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 70fc691..84cc771 100644 (file)
@@ -109,12 +109,16 @@ function test_tone_change_events(testFunc, toneChanges, desc) {
         const now = Date.now();
         const duration = now - lastEventTime;
 
-        assert_approx_equals(duration, expectedDuration, 250,
+        // We check that the delay is at least the expected one, but
+        // system load may cause random delay, so we do not put any
+        // realistic upper bound on the timing of the event.
+        assert_between_inclusive(duration, expectedDuration,
+                                 expectedDuration + 4000,
           `Expect tonechange event for "${tone}" to be fired approximately after ${expectedDuration} milliseconds`);
 
         lastEventTime = now;
 
-        if(toneChanges.length === 0) {
+        if (toneChanges.length === 0) {
           // Wait for same duration as last expected duration + 100ms
           // before passing test in case there are new tone events fired,
           // in which case the test should fail.
index 35dc84d..d259b51 100644 (file)
@@ -2,8 +2,8 @@
 FAIL insertDTMF() should succeed if tones contains valid DTMF characters promise_test: Unhandled rejection with value: object "ReferenceError: Can't find variable: RTCDTMFSender"
 FAIL insertDTMF() should throw InvalidCharacterError if tones contains invalid DTMF characters promise_test: Unhandled rejection with value: object "ReferenceError: Can't find variable: RTCDTMFSender"
 FAIL insertDTMF() should throw InvalidStateError if transceiver is stopped assert_throws: function "() => dtmfSender.insertDTMF('')" threw object "TypeError: undefined is not an object (evaluating 'dtmfSender.insertDTMF')" that is not a DOMException InvalidStateError: property "code" is equal to undefined, expected 11
-FAIL insertDTMF() should throw InvalidStateError if transceiver.currentDirection is recvonly assert_equals: expected (string) "inactive" but got (object) null
-FAIL insertDTMF() should throw InvalidStateError if transceiver.currentDirection is inactive assert_equals: expected (string) "inactive" but got (object) null
+FAIL insertDTMF() should throw InvalidStateError if transceiver.currentDirection is recvonly assert_throws: function "() => dtmfSender.insertDTMF('')" threw object "TypeError: undefined is not an object (evaluating 'dtmfSender.insertDTMF')" that is not a DOMException InvalidStateError: property "code" is equal to undefined, expected 11
+FAIL insertDTMF() should throw InvalidStateError if transceiver.currentDirection is inactive assert_throws: function "() => dtmfSender.insertDTMF('')" threw object "TypeError: undefined is not an object (evaluating 'dtmfSender.insertDTMF')" that is not a DOMException InvalidStateError: property "code" is equal to undefined, expected 11
 FAIL insertDTMF() should set toneBuffer to provided tones normalized, with old tones overridden promise_test: Unhandled rejection with value: object "ReferenceError: Can't find variable: RTCDTMFSender"
 FAIL insertDTMF() after remove and close should reject assert_throws: function "() =>
                       dtmfSender.insertDTMF('123')" threw object "TypeError: undefined is not an object (evaluating 'dtmfSender.insertDTMF')" that is not a DOMException InvalidStateError: property "code" is equal to undefined, expected 11
index 383977c..8a6d645 100644 (file)
     7.2.  insertDTMF
       4.  If transceiver.currentDirection is recvonly or inactive, throw an InvalidStateError.
    */
-  promise_test(t => {
-    const pc = new RTCPeerConnection();
-    const transceiver = pc.addTransceiver('audio', {
-      direction: 'recvonly'
-    });
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+    const transceiver =
+        caller.addTransceiver('audio', { direction: 'recvonly' });
     const dtmfSender = transceiver.sender.dtmf;
 
-    return pc.createOffer()
-    .then(offer =>
-      pc.setLocalDescription(offer)
-      .then(() => generateAnswer(offer)))
-    .then(() => {
-      assert_equals(transceiver.currentDirection, 'inactive');
-      assert_throws('InvalidStateError', () => dtmfSender.insertDTMF(''));
-    });
+    const offer = await caller.createOffer();
+    await caller.setLocalDescription(offer);
+    await callee.setRemoteDescription(offer);
+    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const [track] = stream.getTracks();
+    callee.addTrack(track);
+    const answer = await callee.createAnswer();
+    await callee.setLocalDescription(answer);
+    await caller.setRemoteDescription(answer);
+    assert_equals(transceiver.currentDirection, 'recvonly');
+    assert_throws('InvalidStateError', () => dtmfSender.insertDTMF(''));
   }, 'insertDTMF() should throw InvalidStateError if transceiver.currentDirection is recvonly');
 
-  promise_test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
-    const transceiver = pc.addTransceiver('audio', {
-      direction: 'inactive'
-    });
+    t.add_cleanup(() => pc.close());
+    const transceiver =
+        pc.addTransceiver('audio', { direction: 'inactive' });
     const dtmfSender = transceiver.sender.dtmf;
 
-    return pc.createOffer()
-    .then(offer =>
-      pc.setLocalDescription(offer)
-      .then(() => generateAnswer(offer)))
-    .then(() => {
-      assert_equals(transceiver.currentDirection, 'inactive');
-      assert_throws('InvalidStateError', () => dtmfSender.insertDTMF(''));
-    });
+    const offer = await pc.createOffer();
+    await pc.setLocalDescription(offer);
+    const answer = await generateAnswer(offer);
+    await pc.setRemoteDescription(answer);
+    assert_equals(transceiver.currentDirection, 'inactive');
+    assert_throws('InvalidStateError', () => dtmfSender.insertDTMF(''));
   }, 'insertDTMF() should throw InvalidStateError if transceiver.currentDirection is inactive');
 
   /*
 
       7.  Set the object's toneBuffer attribute to tones.
    */
-  promise_test(() => {
+  promise_test(t => {
     return createDtmfSender()
     .then(dtmfSender => {
       dtmfSender.insertDTMF('123');
     let dtmfSender;
     let sender;
     let pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     return getTrackFromUserMedia('audio')
       .then(([track, mediaStream]) => {
         sender = pc.addTrack(track, mediaStream);
index e9ce286..773a5cc 100644 (file)
@@ -10,4 +10,6 @@ FAIL Calling insertDTMF() in the middle of tonechange events should cause future
 FAIL Calling insertDTMF() multiple times in the middle of tonechange events should cause future tonechanges to be updated the last provided tones assert_unreached: Unexpected promise rejection: ReferenceError: Can't find variable: RTCDTMFSender Reached unreachable code
 FAIL Calling insertDTMF('') in the middle of tonechange events should stop future tonechange events from firing assert_unreached: Unexpected promise rejection: ReferenceError: Can't find variable: RTCDTMFSender Reached unreachable code
 FAIL Setting transceiver.currentDirection to recvonly in the middle of tonechange events should stop future tonechange events from firing undefined is not an object (evaluating 'dtmfSender.addEventListener')
+FAIL Tone change event constructor works Can't find variable: RTCDTMFToneChangeEvent
+FAIL Tone change event with unexpected name should not crash Can't find variable: RTCDTMFToneChangeEvent
 
index 9fa900c..ff6d117 100644 (file)
   test_tone_change_events((t, dtmfSender) => {
     dtmfSender.addEventListener('tonechange', ev => {
       if(ev.tone === 'B') {
-        // Set a timeout to make sure the toneBuffer
-        // is changed after the tonechange event listener
-        // by test_tone_change_events is called.
-        // This is to correctly test the expected toneBuffer.
-        t.step_timeout(() => {
-          dtmfSender.insertDTMF('12', 100, 70);
-        }, 10);
+        dtmfSender.insertDTMF('12', 100, 70);
       }
     });
 
   test_tone_change_events((t, dtmfSender) => {
     dtmfSender.addEventListener('tonechange', ev => {
       if(ev.tone === 'B') {
-        t.step_timeout(() => {
-          dtmfSender.insertDTMF('12', 100, 70);
-          dtmfSender.insertDTMF('34', 100, 70);
-        }, 10);
+        dtmfSender.insertDTMF('12', 100, 70);
+        dtmfSender.insertDTMF('34', 100, 70);
       }
     });
 
   test_tone_change_events((t, dtmfSender) => {
     dtmfSender.addEventListener('tonechange', ev => {
       if(ev.tone === 'B') {
-        t.step_timeout(() => {
-          dtmfSender.insertDTMF('');
-        }, 10);
+        dtmfSender.insertDTMF('');
       }
     });
 
    */
   async_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     const transceiver = pc.addTransceiver('audio', { direction: 'sendrecv' });
     const dtmfSender = transceiver.sender.dtmf;
 
 
     dtmfSender.addEventListener('tonechange', onToneChange);
     dtmfSender.insertDTMF('ABCD', 100, 70);
-
   }, `Setting transceiver.currentDirection to recvonly in the middle of tonechange events should stop future tonechange events from firing`);
 
+  /* Section 7.3 - Tone change event */
+  test(t => {
+    let ev = new RTCDTMFToneChangeEvent('tonechange', {'tone': '1'});
+    assert_equals(ev.type, 'tonechange');
+    assert_equals(ev.tone, '1');
+  }, 'Tone change event constructor works');
+
+  test(t => {
+    let ev = new RTCDTMFToneChangeEvent('worngname', {});
+  }, 'Tone change event with unexpected name should not crash');
+
 </script>
index 7128da8..18f70d8 100644 (file)
@@ -11,6 +11,7 @@
 // have IDs set according to the rules in rtcweb-data-channel.
 promise_test(test => {
   const pc = new RTCPeerConnection;
+  test.add_cleanup(() => pc.close());
   const channel = pc.createDataChannel('');
   return pc.createOffer()
   .then(offer => pc.setLocalDescription(offer))
@@ -35,6 +36,7 @@ promise_test(test => {
 
 promise_test(test => {
   const pc = new RTCPeerConnection;
+  test.add_cleanup(() => pc.close());
   const channel = pc.createDataChannel('');
   return pc.createOffer()
   .then(offer => pc.setLocalDescription(offer))
index 80d1bfa..0614364 100644 (file)
@@ -38,7 +38,9 @@
    */
   async_test(t => {
     const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
     const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc2.close());
 
     pc1.createDataChannel('test');
     exchangeIceCandidates(pc1, pc2);
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCIceTransport-extension-helper.js b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCIceTransport-extension-helper.js
new file mode 100644 (file)
index 0000000..659ec59
--- /dev/null
@@ -0,0 +1,42 @@
+'use strict';
+
+// Construct an RTCIceTransport instance. The instance will automatically be
+// cleaned up when the test finishes.
+function makeIceTransport(t) {
+  const iceTransport = new RTCIceTransport();
+  t.add_cleanup(() => iceTransport.stop());
+  return iceTransport;
+}
+
+// Construct two RTCIceTransport instances, configure them to exchange
+// candidates, then gather() them.
+// Returns a 2-list: [ RTCIceTransport, RTCIceTransport ]
+function makeAndGatherTwoIceTransports(t) {
+  const localTransport = makeIceTransport(t);
+  const remoteTransport = makeIceTransport(t);
+  localTransport.onicecandidate = e => {
+    if (e.candidate) {
+      remoteTransport.addRemoteCandidate(e.candidate);
+    }
+  };
+  remoteTransport.onicecandidate = e => {
+    if (e.candidate) {
+      localTransport.addRemoteCandidate(e.candidate);
+    }
+  };
+  localTransport.gather({});
+  remoteTransport.gather({});
+  return [ localTransport, remoteTransport ];
+}
+
+// Construct two RTCIceTransport instances, configure them to exchange
+// candidates and parameters, then gather() and start() them.
+// Returns a 2-list:
+//     [ controlling RTCIceTransport,
+//       controlled RTCIceTransport ]
+function makeGatherAndStartTwoIceTransports(t) {
+  const [ localTransport, remoteTransport ] = makeAndGatherTwoIceTransports(t);
+  localTransport.start(remoteTransport.getLocalParameters(), 'controlling');
+  remoteTransport.start(localTransport.getLocalParameters(), 'controlled');
+  return [ localTransport, remoteTransport ];
+}
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCIceTransport-extension.https-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCIceTransport-extension.https-expected.txt
new file mode 100644 (file)
index 0000000..6c30f8a
--- /dev/null
@@ -0,0 +1,23 @@
+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()')
+FAIL gather() with { iceServers: null } should throw TypeError function is not a constructor (evaluating 'new RTCIceTransport()')
+FAIL gather() with { iceServers: undefined } should succeed function is not a constructor (evaluating 'new RTCIceTransport()')
+FAIL gather() with one turns server, one turn server, username, credential should succeed function is not a constructor (evaluating 'new RTCIceTransport()')
+FAIL gather() with 2 stun servers should succeed function is not a constructor (evaluating 'new RTCIceTransport()')
+FAIL gather() throws if closed function is not a constructor (evaluating 'new RTCIceTransport()')
+FAIL gather() transitions gatheringState to 'gathering' function is not a constructor (evaluating 'new RTCIceTransport()')
+FAIL gather() throws if called twice function is not a constructor (evaluating 'new RTCIceTransport()')
+FAIL eventually transition gatheringState to 'complete' promise_test: Unhandled rejection with value: object "TypeError: function is not a constructor (evaluating 'new RTCIceTransport()')"
+FAIL onicecandidate fires with null candidate before gatheringState transitions to 'complete' promise_test: Unhandled rejection with value: object "TypeError: function is not a constructor (evaluating 'new RTCIceTransport()')"
+FAIL gather() returns at least one host candidate promise_test: Unhandled rejection with value: object "TypeError: function is not a constructor (evaluating 'new RTCIceTransport()')"
+FAIL gather() returns no candidates with { gatherPolicy: 'relay'} and no turn servers promise_test: Unhandled rejection with value: object "TypeError: function is not a constructor (evaluating 'new RTCIceTransport()')"
+FAIL start() throws if closed function is not a constructor (evaluating 'new RTCIceTransport()')
+FAIL start() throws if usernameFragment or password not set function is not a constructor (evaluating 'new RTCIceTransport()')
+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()')
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCIceTransport-extension.https.html b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCIceTransport-extension.https.html
new file mode 100644 (file)
index 0000000..7803bde
--- /dev/null
@@ -0,0 +1,302 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCIceTransport-extensions.https.html</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="RTCIceTransport-extension-helper.js"></script>
+<script>
+'use strict';
+
+// These tests are based on the following extension specification:
+// https://w3c.github.io/webrtc-ice/
+
+// The following helper functions are called from
+// RTCIceTransport-extension-helper.js:
+//   makeIceTransport
+//   makeGatherAndStartTwoIceTransports
+
+test(() => {
+  const iceTransport = new RTCIceTransport();
+}, 'RTCIceTransport constructor does not throw');
+
+test(() => {
+  const iceTransport = new RTCIceTransport();
+  assert_equals(iceTransport.role, null, 'Expect role to be null');
+  assert_equals(iceTransport.state, 'new', `Expect state to be 'new'`);
+  assert_equals(iceTransport.gatheringState, 'new',
+    `Expect gatheringState to be 'new'`);
+  assert_array_equals(iceTransport.getLocalCandidates(), [],
+    'Expect no local candidates');
+  assert_array_equals(iceTransport.getRemoteCandidates(), [],
+    'Expect no remote candidates');
+  assert_equals(iceTransport.getSelectedCandidatePair(), null,
+    'Expect no selected candidate pair');
+  assert_not_equals(iceTransport.getLocalParameters(), null,
+    'Expect local parameters generated');
+  assert_equals(iceTransport.getRemoteParameters(), null,
+    'Expect no remote parameters');
+}, 'RTCIceTransport initial properties are set');
+
+test(t => {
+  const iceTransport = makeIceTransport(t);
+  assert_throws(new TypeError(), () =>
+    iceTransport.gather({ iceServers: null }));
+}, 'gather() with { iceServers: null } should throw TypeError');
+
+test(t => {
+  const iceTransport = makeIceTransport(t);
+  iceTransport.gather({ iceServers: undefined });
+}, 'gather() with { iceServers: undefined } should succeed');
+
+test(t => {
+  const iceTransport = makeIceTransport(t);
+  iceTransport.gather({ iceServers: [{
+    urls: ['turns:turn.example.org', 'turn:turn.example.net'],
+    username: 'user',
+    credential: 'cred',
+  }] });
+}, 'gather() with one turns server, one turn server, username, credential' +
+    ' should succeed');
+
+test(t => {
+  const iceTransport = makeIceTransport(t);
+  iceTransport.gather({ iceServers: [{
+    urls: ['stun:stun1.example.net', 'stun:stun2.example.net'],
+  }] });
+}, 'gather() with 2 stun servers should succeed');
+
+test(t => {
+  const iceTransport = makeIceTransport(t);
+  iceTransport.stop();
+  assert_throws('InvalidStateError', () => iceTransport.gather({}));
+}, 'gather() throws if closed');
+
+test(t => {
+  const iceTransport = makeIceTransport(t);
+  iceTransport.gather({});
+  assert_equals(iceTransport.gatheringState, 'gathering');
+}, `gather() transitions gatheringState to 'gathering'`);
+
+test(t => {
+  const iceTransport = makeIceTransport(t);
+  iceTransport.gather({});
+  assert_throws('InvalidStateError', () => iceTransport.gather({}));
+}, 'gather() throws if called twice');
+
+promise_test(async t => {
+  const iceTransport = makeIceTransport(t);
+  const watcher = new EventWatcher(t, iceTransport, 'gatheringstatechange');
+  iceTransport.gather({});
+  await watcher.wait_for('gatheringstatechange');
+  assert_equals(iceTransport.gatheringState, 'complete');
+}, `eventually transition gatheringState to 'complete'`);
+
+promise_test(async t => {
+  const iceTransport = makeIceTransport(t);
+  const watcher = new EventWatcher(t, iceTransport,
+      [ 'icecandidate', 'gatheringstatechange' ]);
+  iceTransport.gather({});
+  let candidate;
+  do {
+    ({ candidate } = await watcher.wait_for('icecandidate'));
+  } while (candidate !== null);
+  assert_equals(iceTransport.gatheringState, 'gathering');
+  await watcher.wait_for('gatheringstatechange');
+  assert_equals(iceTransport.gatheringState, 'complete');
+}, 'onicecandidate fires with null candidate before gatheringState' +
+    ` transitions to 'complete'`);
+
+promise_test(async t => {
+  const iceTransport = makeIceTransport(t);
+  const watcher = new EventWatcher(t, iceTransport, 'icecandidate');
+  iceTransport.gather({});
+  const { candidate } = await watcher.wait_for('icecandidate');
+  assert_not_equals(candidate.candidate, '');
+  assert_array_equals(iceTransport.getLocalCandidates(), [candidate]);
+}, 'gather() returns at least one host candidate');
+
+promise_test(async t => {
+  const iceTransport = makeIceTransport(t);
+  const watcher = new EventWatcher(t, iceTransport, 'icecandidate');
+  iceTransport.gather({ gatherPolicy: 'relay' });
+  const { candidate } = await watcher.wait_for('icecandidate');
+  assert_equals(candidate, null);
+  assert_array_equals(iceTransport.getLocalCandidates(), []);
+}, `gather() returns no candidates with { gatherPolicy: 'relay'} and no turn` +
+    ' servers');
+
+const dummyRemoteParameters = {
+  usernameFragment: 'dummyUsernameFragment',
+  password: 'dummyPassword',
+};
+
+test(() => {
+  const iceTransport = new RTCIceTransport();
+  iceTransport.stop();
+  assert_throws('InvalidStateError',
+    () => iceTransport.start(dummyRemoteParameters));
+  assert_equals(iceTransport.getRemoteParameters(), null);
+}, `start() throws if closed`);
+
+test(() => {
+  const iceTransport = new RTCIceTransport();
+  assert_throws(new TypeError(), () => iceTransport.start({}));
+  assert_throws(new TypeError(),
+    () => iceTransport.start({ usernameFragment: 'dummy' }));
+  assert_throws(new TypeError(),
+    () => iceTransport.start({ password: 'dummy' }));
+  assert_equals(iceTransport.getRemoteParameters(), null);
+}, 'start() throws if usernameFragment or password not set');
+
+const assert_ice_parameters_equals = (a, b) => {
+  assert_equals(a.usernameFragment, b.usernameFragment,
+      'usernameFragments are equal');
+  assert_equals(a.password, b.password, 'passwords are equal');
+};
+
+test(t => {
+  const iceTransport = makeIceTransport(t);
+  iceTransport.start(dummyRemoteParameters);
+  assert_equals(iceTransport.state, 'new');
+  assert_ice_parameters_equals(iceTransport.getRemoteParameters(),
+      dummyRemoteParameters);
+}, `start() does not transition state to 'checking' if no remote candidates ` +
+    'added');
+
+test(t => {
+  const iceTransport = makeIceTransport(t);
+  iceTransport.start(dummyRemoteParameters);
+  assert_equals(iceTransport.role, 'controlled');
+}, `start() with default role sets role attribute to 'controlled'`);
+
+test(t => {
+  const iceTransport = makeIceTransport(t);
+  iceTransport.start(dummyRemoteParameters, 'controlling');
+  assert_equals(iceTransport.role, 'controlling');
+}, `start() sets role attribute to 'controlling'`);
+
+const candidate1 = new RTCIceCandidate({
+  candidate: 'candidate:1 1 udp 2113929471 203.0.113.100 10100 typ host',
+});
+
+test(() => {
+  const iceTransport = new RTCIceTransport();
+  iceTransport.stop();
+  assert_throws('InvalidStateError',
+    () => iceTransport.addRemoteCandidate(candidate1));
+  assert_array_equals(iceTransport.getRemoteCandidates(), []);
+}, 'addRemoteCandidate() throws if closed');
+
+test(() => {
+  const iceTransport = new RTCIceTransport();
+  assert_throws('OperationError',
+    () => iceTransport.addRemoteCandidate(
+      new RTCIceCandidate({ candidate: 'invalid' })));
+  assert_array_equals(iceTransport.getRemoteCandidates(), []);
+}, 'addRemoteCandidate() throws on invalid candidate');
+
+test(t => {
+  const iceTransport = makeIceTransport(t);
+  iceTransport.addRemoteCandidate(candidate1);
+  iceTransport.start(dummyRemoteParameters);
+  assert_equals(iceTransport.state, 'checking');
+  assert_array_equals(iceTransport.getRemoteCandidates(), [candidate1]);
+}, `start() transitions state to 'checking' if one remote candidate had been ` +
+    'added');
+
+test(t => {
+  const iceTransport = makeIceTransport(t);
+  iceTransport.start(dummyRemoteParameters);
+  iceTransport.addRemoteCandidate(candidate1);
+  assert_equals(iceTransport.state, 'checking');
+  assert_array_equals(iceTransport.getRemoteCandidates(), [candidate1]);
+}, `addRemoteCandidate() transitions state to 'checking' if start() had been ` +
+    'called before');
+
+test(t => {
+  const iceTransport = makeIceTransport(t);
+  iceTransport.start(dummyRemoteParameters);
+  assert_throws('InvalidStateError',
+    () => iceTransport.start(dummyRemoteParameters, 'controlling'));
+}, 'start() throws if later called with a different role');
+
+test(t => {
+  const iceTransport = makeIceTransport(t);
+  iceTransport.start({
+    usernameFragment: 'user',
+    password: 'pass',
+  });
+  iceTransport.addRemoteCandidate(candidate1);
+  const changedRemoteParameters = {
+    usernameFragment: 'user2',
+    password: 'pass',
+  };
+  iceTransport.start(changedRemoteParameters);
+  assert_equals(iceTransport.state, 'new');
+  assert_array_equals(iceTransport.getRemoteCandidates(), []);
+  assert_ice_parameters_equals(iceTransport.getRemoteParameters(),
+      changedRemoteParameters);
+}, `start() flushes remote candidates and transitions state to 'new' if ` +
+   'later called with different remote parameters');
+
+promise_test(async t => {
+  const [ localTransport, remoteTransport ] =
+      makeGatherAndStartTwoIceTransports(t);
+  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 connect to each other');
+
+promise_test(async t => {
+  async function waitForConnectedThenSelectedCandidatePairChange(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,
+        `${transportName} selected candidate pair should not be null once ` +
+        'the selectedcandidatepairchange event fires');
+    assert_true(
+        transport.getLocalCandidates().some(
+            ({ candidate }) =>
+                candidate === selectedCandidatePair.local.candidate),
+        `${transportName} selected candidate pair local should be in the ` +
+        'list of local candidates');
+    assert_true(
+        transport.getRemoteCandidates().some(
+            ({ candidate }) =>
+                candidate === selectedCandidatePair.remote.candidate),
+        `${transportName} selected candidate pair local should be in the ` +
+        'list of remote candidates');
+  }
+  const [ localTransport, remoteTransport ] =
+      makeGatherAndStartTwoIceTransports(t);
+  await Promise.all([
+    waitForConnectedThenSelectedCandidatePairChange(t, localTransport,
+        'local transport'),
+    waitForConnectedThenSelectedCandidatePairChange(t, remoteTransport,
+        'remote transport'),
+  ]);
+}, 'Selected candidate pair changes once the RTCIceTransports connect.');
+
+promise_test(async t => {
+  const [ transport, ] = makeGatherAndStartTwoIceTransports(t);
+  const watcher = new EventWatcher(t, transport, 'selectedcandidatepairchange');
+  await watcher.wait_for('selectedcandidatepairchange');
+  transport.stop();
+  assert_equals(transport.getSelectedCandidatePair(), null);
+}, 'getSelectedCandidatePair() returns null once the RTCIceTransport is ' +
+    'stopped.');
+
+</script>
index 57ecf07..17ae6dc 100644 (file)
     validateCandidateParameter(iceTransport.getRemoteParameters());
   }
 
-  promise_test(() => {
+  promise_test(t => {
     const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
     const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc2.close());
 
     return createDataChannelPair(pc1, pc2)
     .then(([channel1, channel2]) => {
     });
   }, 'Two connected iceTransports should has matching local/remote candidates returned');
 
-  promise_test(() => {
+  promise_test(t => {
     const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
     const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc2.close());
     pc1.createDataChannel('');
 
     // setRemoteDescription(answer) without the other peer
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-add-track-no-deadlock.https-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-add-track-no-deadlock.https-expected.txt
new file mode 100644 (file)
index 0000000..6a51632
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS RTCPeerConnection addTrack does not deadlock. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-add-track-no-deadlock.https.html b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-add-track-no-deadlock.https.html
new file mode 100644 (file)
index 0000000..81e3b73
--- /dev/null
@@ -0,0 +1,31 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCPeerConnection addTrack does not deadlock</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script>
+  'use strict';
+
+  // This test sets up two peer connections using a sequence of operations
+  // that triggered a deadlock in Chrome. See https://crbug.com/736725.
+  // If a deadlock is introduced again, this test times out.
+  promise_test(async t => {
+    const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
+    const stream = await getNoiseStream(
+      {audio: false, video: true});
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const videoTrack = stream.getVideoTracks()[0];
+    pc1.addTrack(videoTrack, stream);
+    const offer = await pc1.createOffer();
+    await pc1.setLocalDescription(offer);
+    const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc2.close());
+    const srdPromise = pc2.setRemoteDescription(offer);
+    pc2.addTrack(videoTrack, stream);
+    // The deadlock encountered in https://crbug.com/736725 occured here.
+    await srdPromise;
+    await pc2.createAnswer();
+  }, 'RTCPeerConnection addTrack does not deadlock.');
+</script>
index e55cc61..5e32168 100644 (file)
@@ -142,6 +142,8 @@ a=rtcp-rsize
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.setRemoteDescription(sessionDesc)
     .then(() =>
       promise_rejects(t, new TypeError(),
@@ -157,6 +159,8 @@ a=rtcp-rsize
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return promise_rejects(t, 'InvalidStateError',
       pc.addIceCandidate({
         candidate: candidateStr1,
@@ -170,6 +174,8 @@ a=rtcp-rsize
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.setRemoteDescription(sessionDesc)
     .then(() => pc.addIceCandidate({
       candidate: candidateStr1,
@@ -180,6 +186,8 @@ a=rtcp-rsize
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.setRemoteDescription(sessionDesc)
     .then(() => pc.addIceCandidate(new RTCIceCandidate({
       candidate: candidateStr1,
@@ -190,6 +198,8 @@ a=rtcp-rsize
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.setRemoteDescription(sessionDesc)
       .then(() => pc.addIceCandidate({ sdpMid }));
   }, 'Add candidate with only valid sdpMid should succeed');
@@ -197,6 +207,8 @@ a=rtcp-rsize
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.setRemoteDescription(sessionDesc)
       .then(() => pc.addIceCandidate({ sdpMLineIndex }));
   }, 'Add candidate with only valid sdpMLineIndex should succeed');
@@ -212,6 +224,8 @@ a=rtcp-rsize
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.setRemoteDescription(sessionDesc)
     .then(() => pc.addIceCandidate({
       candidate: candidateStr1,
@@ -226,6 +240,8 @@ a=rtcp-rsize
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.setRemoteDescription(sessionDesc)
     .then(() => pc.addIceCandidate({
       candidate: candidateStr2,
@@ -242,6 +258,8 @@ a=rtcp-rsize
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.setRemoteDescription(sessionDesc)
     .then(() => pc.addIceCandidate({
       candidate: candidateStr1,
@@ -257,6 +275,8 @@ a=rtcp-rsize
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.setRemoteDescription(sessionDesc)
     .then(() => pc.addIceCandidate({
       candidate: candidateStr1,
@@ -291,6 +311,8 @@ a=rtcp-rsize
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.setRemoteDescription(sessionDesc)
     .then(() => pc.addIceCandidate({
       candidate: candidateStr1,
@@ -318,6 +340,8 @@ a=rtcp-rsize
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.setRemoteDescription(sessionDesc)
     .then(() =>
       promise_rejects(t, new TypeError(),
@@ -331,6 +355,8 @@ a=rtcp-rsize
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.setRemoteDescription(sessionDesc)
     .then(() =>
       promise_rejects(t, new TypeError(),
@@ -342,6 +368,8 @@ a=rtcp-rsize
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.setRemoteDescription(sessionDesc)
     .then(() =>
       promise_rejects(t, new TypeError(),
@@ -355,6 +383,8 @@ a=rtcp-rsize
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.setRemoteDescription(sessionDesc)
     .then(() =>
       promise_rejects(t, new TypeError(),
@@ -364,6 +394,8 @@ a=rtcp-rsize
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.setRemoteDescription(sessionDesc)
     .then(() =>
       promise_rejects(t, new TypeError(),
@@ -385,6 +417,8 @@ a=rtcp-rsize
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.setRemoteDescription(sessionDesc)
     .then(() =>
       promise_rejects(t, 'OperationError',
@@ -405,6 +439,8 @@ a=rtcp-rsize
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.setRemoteDescription(sessionDesc)
     .then(() =>
       promise_rejects(t, 'OperationError',
@@ -420,6 +456,8 @@ a=rtcp-rsize
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.setRemoteDescription(sessionDesc)
     .then(() => pc.addIceCandidate({
       candidate: candidateStr1,
@@ -432,6 +470,8 @@ a=rtcp-rsize
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.setRemoteDescription(sessionDesc)
     .then(() => pc.addIceCandidate({
       candidate: candidateStr2,
@@ -455,6 +495,8 @@ a=rtcp-rsize
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.setRemoteDescription(sessionDesc)
     .then(() =>
       promise_rejects(t, 'OperationError',
@@ -475,6 +517,8 @@ a=rtcp-rsize
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.setRemoteDescription(sessionDesc)
     .then(() =>
       promise_rejects(t, 'OperationError',
@@ -487,6 +531,8 @@ a=rtcp-rsize
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.setRemoteDescription(sessionDesc)
     .then(() =>
       promise_rejects(t, 'OperationError',
index 067d643..a293776 100644 (file)
@@ -1,10 +1,11 @@
 
 PASS addTrack when pc is closed should throw InvalidStateError 
-PASS addTrack with single track argument and no mediaStream should succeed 
-PASS addTrack with single track argument and single mediaStream should succeed 
-PASS addTrack with single track argument and multiple mediaStreams should succeed 
+PASS addTrack with single track argument and no stream should succeed 
+PASS addTrack with single track argument and single stream should succeed 
+PASS addTrack with single track argument and multiple streams should succeed 
 PASS Adding the same track multiple times should throw InvalidAccessError 
 PASS addTrack with existing sender with null track, same kind, and recvonly direction should reuse sender 
-FAIL addTrack with existing sender with null track, same kind, and sendrecv direction should create new sender assert_not_equals: got disallowed value object "[object RTCRtpSender]"
+PASS addTrack with existing sender that has not been used to send should reuse the sender 
+PASS addTrack with existing sender that has been used to send should create new sender 
 PASS addTrack with existing sender with null track, different kind, and recvonly direction should create new sender 
 
index 406e2d7..2ec9a1f 100644 (file)
@@ -11,7 +11,7 @@
   // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
 
   // The following helper functions are called from RTCPeerConnection-helper.js:
-  // generateMediaStreamTrack
+  //   getNoiseStream()
 
   /*
     5.1.  RTCPeerConnection Interface Extensions
     5.1.  addTrack
       4.  If connection's [[isClosed]] slot is true, throw an InvalidStateError.
    */
-  promise_test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
 
-    return navigator.mediaDevices.getUserMedia({ audio: true })
-    .then(mediaStream => {
-      const tracks = mediaStream.getTracks();
-      assert_greater_than(tracks.length, 0,
-        'Expect getUserMedia to return at least one audio track');
+    const stream = await getNoiseStream({ audio: true });
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const [track] = stream.getTracks();
 
-      const track = tracks[0];
-
-      pc.close();
-      assert_throws('InvalidStateError', () => pc.addTrack(track, mediaStream))
-    });
+    pc.close();
+    assert_throws('InvalidStateError', () => pc.addTrack(track, stream))
   }, 'addTrack when pc is closed should throw InvalidStateError');
 
   /*
               transceiver be the result.
           4.  Add transceiver to connection's set of transceivers.
    */
-  promise_test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
 
-    return navigator.mediaDevices.getUserMedia({ audio: true })
-    .then(mediaStream => {
-      const tracks = mediaStream.getTracks();
-      assert_greater_than(tracks.length, 0,
-        'Expect getUserMedia to return at least one audio track');
+    const stream = await getNoiseStream({ audio: true });
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const [track] = stream.getTracks();
 
-      const track = tracks[0];
-      const sender = pc.addTrack(track);
+    const sender = pc.addTrack(track);
 
-      assert_true(sender instanceof RTCRtpSender,
-        'Expect sender to be instance of RTCRtpSender');
+    assert_true(sender instanceof RTCRtpSender,
+      'Expect sender to be instance of RTCRtpSender');
 
-      assert_equals(sender.track, track,
-        `Expect sender's track to be the added track`);
+    assert_equals(sender.track, track,
+      `Expect sender's track to be the added track`);
 
-      const transceivers = pc.getTransceivers();
-      assert_equals(transceivers.length, 1,
-        'Expect only one transceiver with sender added');
+    const transceivers = pc.getTransceivers();
+    assert_equals(transceivers.length, 1,
+      'Expect only one transceiver with sender added');
 
-      const [transceiver] = transceivers;
-      assert_equals(transceiver.sender, sender);
+    const [transceiver] = transceivers;
+    assert_equals(transceiver.sender, sender);
 
-      assert_array_equals([sender], pc.getSenders(),
-        'Expect only one sender with given track added');
+    assert_array_equals([sender], pc.getSenders(),
+      'Expect only one sender with given track added');
 
-      const { receiver } = transceiver;
-      assert_equals(receiver.track.kind, 'audio');
-      assert_array_equals([transceiver.receiver], pc.getReceivers(),
-        'Expect only one receiver associated with transceiver added');
-    });
-  }, 'addTrack with single track argument and no mediaStream should succeed');
+    const { receiver } = transceiver;
+    assert_equals(receiver.track.kind, 'audio');
+    assert_array_equals([transceiver.receiver], pc.getReceivers(),
+      'Expect only one receiver associated with transceiver added');
+  }, 'addTrack with single track argument and no stream should succeed');
 
-  promise_test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
 
-    return navigator.mediaDevices.getUserMedia({ audio: true })
-    .then(mediaStream => {
-      const tracks = mediaStream.getTracks();
-      assert_greater_than(tracks.length, 0,
-        'Expect getUserMedia to return at least one audio track');
+    const stream = await getNoiseStream({ audio: true });
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const [track] = stream.getTracks();
 
-      const track = tracks[0];
-      const sender = pc.addTrack(track, mediaStream);
+    const sender = pc.addTrack(track, stream);
 
-      assert_true(sender instanceof RTCRtpSender,
-        'Expect sender to be instance of RTCRtpSender');
+    assert_true(sender instanceof RTCRtpSender,
+      'Expect sender to be instance of RTCRtpSender');
 
-      assert_equals(sender.track, track,
-        `Expect sender's track to be the added track`);
-    });
-  }, 'addTrack with single track argument and single mediaStream should succeed');
+    assert_equals(sender.track, track,
+      `Expect sender's track to be the added track`);
+  }, 'addTrack with single track argument and single stream should succeed');
 
-  promise_test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
 
-    return navigator.mediaDevices.getUserMedia({ audio: true })
-    .then(mediaStream => {
-      const tracks = mediaStream.getTracks();
-      assert_greater_than(tracks.length, 0,
-        'Expect getUserMedia to return at least one audio track');
+    const stream = await getNoiseStream({ audio: true });
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const [track] = stream.getTracks();
 
-      const track = tracks[0];
-      const mediaStream2 = new MediaStream([track]);
-      const sender = pc.addTrack(track, mediaStream, mediaStream2);
+    const stream2 = new MediaStream([track]);
+    const sender = pc.addTrack(track, stream, stream2);
 
-      assert_true(sender instanceof RTCRtpSender,
-        'Expect sender to be instance of RTCRtpSender');
+    assert_true(sender instanceof RTCRtpSender,
+      'Expect sender to be instance of RTCRtpSender');
 
-      assert_equals(sender.track, track,
-        `Expect sender's track to be the added track`);
-    });
-  }, 'addTrack with single track argument and multiple mediaStreams should succeed');
+    assert_equals(sender.track, track,
+      `Expect sender's track to be the added track`);
+  }, 'addTrack with single track argument and multiple streams should succeed');
 
   /*
     5.1.  addTrack
           If an RTCRtpSender for track already exists in senders, throw an
           InvalidAccessError.
    */
-  promise_test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
 
-    return navigator.mediaDevices.getUserMedia({ audio: true })
-    .then(mediaStream => {
-      const tracks = mediaStream.getTracks();
-      assert_greater_than(tracks.length, 0,
-        'Expect getUserMedia to return at least one audio track');
-
-      const track = tracks[0];
+    const stream = await getNoiseStream({ audio: true });
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const [track] = stream.getTracks();
 
-      pc.addTrack(track, mediaStream);
-      assert_throws('InvalidAccessError', () => pc.addTrack(track, mediaStream));
-    });
+    pc.addTrack(track, stream);
+    assert_throws('InvalidAccessError', () => pc.addTrack(track, stream));
   }, 'Adding the same track multiple times should throw InvalidAccessError');
 
   /*
           3.  Enable sending direction on the RTCRtpTransceiver associated
               with sender.
    */
-  test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
 
     const transceiver = pc.addTransceiver('audio', { direction: 'recvonly' });
     assert_equals(transceiver.sender.track, null);
     assert_equals(transceiver.direction, 'recvonly');
 
-    const track = generateMediaStreamTrack('audio');
+    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const [track] = stream.getTracks();
     const sender = pc.addTrack(track);
 
     assert_equals(sender, transceiver.sender);
     assert_equals(sender.track, track);
     assert_equals(transceiver.direction, 'sendrecv');
     assert_array_equals([sender], pc.getSenders());
-
   }, 'addTrack with existing sender with null track, same kind, and recvonly direction should reuse sender');
 
-  test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
 
     const transceiver = pc.addTransceiver('audio');
     assert_equals(transceiver.sender.track, null);
     assert_equals(transceiver.direction, 'sendrecv');
 
-    const track = generateMediaStreamTrack('audio');
+    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const [track] = stream.getTracks();
     const sender = pc.addTrack(track);
 
     assert_equals(sender.track, track);
-    assert_not_equals(sender, transceiver.sender);
-
-    const senders = pc.getSenders();
-    assert_equals(senders.length, 2,
-      'Expect 2 senders added to connection');
-
-    assert_true(senders.includes(sender),
-      'Expect senders list to include sender');
-
-    assert_true(senders.includes(transceiver.sender),
-      `Expect senders list to include first transceiver's sender`);
+    assert_equals(sender, transceiver.sender);
+  }, 'addTrack with existing sender that has not been used to send should reuse the sender');
+
+  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});
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const [track] = stream.getTracks();
+    const transceiver = caller.addTransceiver(track);
+    {
+      const offer = await caller.createOffer();
+      await caller.setLocalDescription(offer);
+      await callee.setRemoteDescription(offer);
+      const answer = await callee.createAnswer();
+      await callee.setLocalDescription(answer);
+      await caller.setRemoteDescription(answer);
+    }
+    assert_equals(transceiver.currentDirection, 'sendonly');
+
+    caller.removeTrack(transceiver.sender);
+    {
+      const offer = await caller.createOffer();
+      await caller.setLocalDescription(offer);
+      await callee.setRemoteDescription(offer);
+      const answer = await callee.createAnswer();
+      await callee.setLocalDescription(answer);
+      await caller.setRemoteDescription(answer);
+    }
+    assert_equals(transceiver.direction, 'recvonly');
+    assert_equals(transceiver.currentDirection, 'inactive');
 
-  }, 'addTrack with existing sender with null track, same kind, and sendrecv direction should create new sender');
+    // |transceiver.sender| is currently not used for sending, but it should not
+    // be reused because it has been used for sending before.
+    const sender = caller.addTrack(track);
+    assert_true(sender != null);
+    assert_not_equals(sender, transceiver.sender);
+  }, 'addTrack with existing sender that has been used to send should create new sender');
 
-  test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
 
     const transceiver = pc.addTransceiver('video', { direction: 'recvonly' });
     assert_equals(transceiver.sender.track, null);
     assert_equals(transceiver.direction, 'recvonly');
 
-    const track = generateMediaStreamTrack('audio');
+    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const [track] = stream.getTracks();
     const sender = pc.addTrack(track);
 
     assert_equals(sender.track, track);
 
     assert_true(senders.includes(transceiver.sender),
       `Expect senders list to include first transceiver's sender`);
-
   }, 'addTrack with existing sender with null track, different kind, and recvonly direction should create new sender');
 
   /*
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-addTransceiver.https-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-addTransceiver.https-expected.txt
new file mode 100644 (file)
index 0000000..068a64f
--- /dev/null
@@ -0,0 +1,24 @@
+
+PASS addTransceiver() with string argument as invalid kind should throw TypeError 
+PASS addTransceiver('audio') should return an audio transceiver 
+PASS addTransceiver('video') should return a video transceiver 
+PASS addTransceiver() with direction sendonly should have result transceiver.direction be the same 
+PASS addTransceiver() with direction inactive should have result transceiver.direction be the same 
+PASS addTransceiver() with invalid direction should throw TypeError 
+PASS addTransceiver(track) should have result with sender.track be given track 
+PASS addTransceiver(track) multiple times should create multiple transceivers 
+FAIL addTransceiver() with rid containing invalid non-alphanumeric characters should throw TypeError assert_throws: function "() =>
+      pc.addTransceiver('audio', {
+        sendEncodings: [{
+          rid: '@Invalid!'
+        }]
+      })" did not throw
+FAIL addTransceiver() with rid longer than 16 characters should throw TypeError assert_throws: function "() =>
+      pc.addTransceiver('audio', {
+        sendEncodings: [{
+          rid: 'a'.repeat(17)
+        }]
+      })" did not throw
+PASS addTransceiver() with valid rid value should succeed 
+PASS addTransceiver() with valid sendEncodings should succeed 
+
@@ -3,16 +3,12 @@
 <title>RTCPeerConnection.prototype.addTransceiver</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="RTCPeerConnection-helper.js"></script>
 <script>
   'use strict';
 
   // Test is based on the following editor draft:
   // https://rawgit.com/w3c/webrtc-pc/cc8d80f455b86c8041d63bceb8b457f45c72aa89/webrtc.html
 
-  // The following helper functions are called from RTCPeerConnection-helper.js:
-  //   generateMediaStreamTrack()
-
   /*
     5.1.  RTCPeerConnection Interface Extensions
 
       'Expect receiver.track to be instance of MediaStreamTrack');
 
     assert_equals(track.kind, 'audio');
-    assert_equals(track.label, 'remote audio');
     assert_equals(track.readyState, 'live');
     assert_equals(track.muted, true);
 
       'Expect receiver.track to be instance of MediaStreamTrack');
 
     assert_equals(track.kind, 'video');
-    assert_equals(track.label, 'remote video');
     assert_equals(track.readyState, 'live');
     assert_equals(track.muted, true);
 
       5.  If the first argument is a MediaStreamTrack , let it be track and let
           kind be track.kind.
    */
-  test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
     t.add_cleanup(() => pc.close());
 
-    const track = generateMediaStreamTrack('audio');
+    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const [track] = stream.getTracks();
     const transceiver = pc.addTransceiver(track);
     const { sender, receiver } = transceiver;
 
     assert_equals(receiverTrack.kind, 'audio',
       `receiver.track should have the same kind as added track's kind`);
 
-    assert_equals(receiverTrack.label, 'remote audio');
     assert_equals(receiverTrack.readyState, 'live');
     assert_equals(receiverTrack.muted, true);
 
 
   }, 'addTransceiver(track) should have result with sender.track be given track');
 
-  test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
     t.add_cleanup(() => pc.close());
 
-    const track = generateMediaStreamTrack('audio');
+    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const [track] = stream.getTracks();
     const transceiver1 = pc.addTransceiver(track);
     const transceiver2 = pc.addTransceiver(track);
 
     });
   }, `addTransceiver() with valid rid value should succeed`);
 
-  /*
-    5.1.  addTransceiver
-      7.  If any RTCRtpEncodingParameters dictionary in sendEncodings contains a
-          read-only parameter other than rid, throw an InvalidAccessError.
-
-      - The sendEncodings argument can be used to specify the number of offered
-        simulcast encodings, and optionally their RIDs and encoding parameters.
-        Aside from rid , all read-only parameters in the RTCRtpEncodingParameters
-        dictionaries, such as ssrc, must be left unset, or an error will be thrown.
-   */
-  test(t => {
-    const pc = new RTCPeerConnection();
-    t.add_cleanup(() => pc.close());
-
-    assert_throws('InvalidAccessError', () =>
-      pc.addTransceiver('audio', {
-        sendEncodings: [{
-          ssrc: 2
-        }]
-      }));
-  }, `addTransceiver() with readonly ssrc set should throw InvalidAccessError`);
-
-  test(t => {
-    const pc = new RTCPeerConnection();
-    t.add_cleanup(() => pc.close());
-
-    assert_throws('InvalidAccessError', () =>
-      pc.addTransceiver('audio', {
-        sendEncodings: [{
-          rtx: {
-            ssrc: 2
-          }
-        }]
-      }));
-  }, `addTransceiver() with readonly rtx set should throw InvalidAccessError`);
-
-  test(t => {
-    const pc = new RTCPeerConnection();
-    t.add_cleanup(() => pc.close());
-
-    assert_throws('InvalidAccessError', () =>
-      pc.addTransceiver('audio', {
-        sendEncodings: [{
-          fec: {
-            ssrc: 2
-          }
-        }]
-      }));
-  }, `addTransceiver() with readonly fec set should throw InvalidAccessError`);
-
   test(t => {
     const pc = new RTCPeerConnection();
     t.add_cleanup(() => pc.close());
index 63dac8f..09ad677 100644 (file)
     assert_equals(pc.canTrickleIceCandidates, null, 'canTrickleIceCandidates property is null');
   }, 'canTrickleIceCandidates property is null prior to setRemoteDescription');
 
-  promise_test(function() {
+  promise_test(function(t) {
     var pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp}))
     .then(function() {
       assert_true(pc.canTrickleIceCandidates, 'canTrickleIceCandidates property is true after setRemoteDescription');
     })
   }, 'canTrickleIceCandidates property is true after setRemoteDescription with a=ice-options:trickle');
 
-  promise_test(function() {
+  promise_test(function(t) {
     var pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp.replace('a=ice-options:trickle\r\n', '')}))
     .then(function() {
       assert_false(pc.canTrickleIceCandidates, 'canTrickleIceCandidates property is false after setRemoteDescription');
index 1f2f1b7..d8e9411 100644 (file)
    */
   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') {
 
     exchangeIceCandidates(pc1, pc2);
     doSignalingHandshake(pc1, pc2);
-
   }, 'connection with one data channel should eventually have connected connection state');
 
   /*
index 19b3a45..abff1eb 100644 (file)
@@ -24,6 +24,7 @@
    */
   promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     return promise_rejects(t, 'InvalidStateError',
       pc.createAnswer());
   }, 'createAnswer() with null remoteDescription should reject with InvalidStateError');
@@ -36,6 +37,8 @@
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.createOffer({ offerToReceiveVideo: true })
     .then(offer => pc.setRemoteDescription(offer))
     .then(() => pc.createAnswer())
@@ -51,6 +54,8 @@
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return generateOffer({ pc, data: true })
     .then(offer => pc.setRemoteDescription(offer))
     .then(() => {
index 35e22f9..450a250 100644 (file)
@@ -362,6 +362,7 @@ test(() => {
  */
 promise_test(t => {
   const pc = new RTCPeerConnection();
+  t.add_cleanup(() => pc.close());
   const channel1 = pc.createDataChannel('channel');
   assert_equals(channel1.id, null,
     'Expect initial id to be null');
index 6cd558f..f679898 100644 (file)
@@ -6,5 +6,4 @@ FAIL When media stream is added when createOffer() is running in parallel, the r
 FAIL createOffer() with offerToReceiveAudio should add audio line to all subsequent created offers assert_equals: Expect created offer to have audio line expected 1 but got 0
 FAIL createOffer() with offerToReceiveVideo should add video line to all subsequent created offers assert_equals: Expect created offer to have video line expected 1 but got 0
 FAIL createOffer() with offerToReceiveAudio:true then offerToReceiveVideo:true should have result offer with both audio and video line assert_equals: Expect audio line to be found in created offer expected 1 but got 0
-PASS Test onsignalingstatechange event for createOffer() and then setLocalDescription() should succeed 
 
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-createOffer-offerToReceive-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-createOffer-offerToReceive-expected.txt
new file mode 100644 (file)
index 0000000..862f468
--- /dev/null
@@ -0,0 +1,17 @@
+
+PASS createOffer() with offerToReceiveAudio set to false should not create a transceiver 
+FAIL createOffer() with offerToReceiveAudio should create a "recvonly" transceiver assert_equals: Expect pc to have one transceiver expected 1 but got 0
+FAIL offerToReceiveAudio option should be ignored if a non-stopped "recvonly" transceiver exists assert_equals: Expect pc to have one transceiver expected 1 but got 0
+PASS offerToReceiveAudio option should be ignored if a non-stopped "sendrecv" transceiver exists 
+FAIL offerToReceiveAudio set to false with a track should create a "sendonly" transceiver assert_equals: Expect transceiver to have "sendonly" direction expected "sendonly" but got "sendrecv"
+FAIL offerToReceiveAudio set to false with a "recvonly" transceiver should change the direction to "inactive" assert_equals: Expect transceiver to have "inactive" direction expected "inactive" but got "recvonly"
+FAIL subsequent offerToReceiveAudio set to false with a track should change the direction to "sendonly" assert_equals: Expect transceiver to have "sendonly" direction expected "sendonly" but got "sendrecv"
+PASS createOffer() with offerToReceiveVideo set to false should not create a transceiver 
+FAIL createOffer() with offerToReceiveVideo should create a "recvonly" transceiver assert_equals: Expect pc to have one transceiver expected 1 but got 0
+FAIL offerToReceiveVideo option should be ignored if a non-stopped "recvonly" transceiver exists assert_equals: Expect pc to have one transceiver expected 1 but got 0
+PASS offerToReceiveVideo option should be ignored if a non-stopped "sendrecv" transceiver exists 
+FAIL offerToReceiveVideo set to false with a track should create a "sendonly" transceiver assert_equals: Expect transceiver to have "sendonly" direction expected "sendonly" but got "sendrecv"
+FAIL offerToReceiveVideo set to false with a "recvonly" transceiver should change the direction to "inactive" assert_equals: Expect transceiver to have "inactive" direction expected "inactive" but got "recvonly"
+FAIL subsequent offerToReceiveVideo set to false with a track should change the direction to "sendonly" assert_equals: Expect transceiver to have "sendonly" direction expected "sendonly" but got "sendrecv"
+FAIL offerToReceiveAudio and Video should create two "recvonly" transceivers assert_equals: Expect pc to have two transceivers expected 2 but got 0
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-createOffer-offerToReceive.html b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-createOffer-offerToReceive.html
new file mode 100644 (file)
index 0000000..dd1827a
--- /dev/null
@@ -0,0 +1,185 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test legacy offerToReceiveAudio/Video options</title>
+<link rel="help" href="https://w3c.github.io/webrtc-pc/#legacy-configuration-extensions">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script>
+  'use strict';
+
+  // Run some tests for both audio and video kinds
+  ['audio', 'video'].forEach((kind) => {
+    const capsKind = kind[0].toUpperCase() + kind.slice(1);
+
+    const offerToReceiveTrue = {};
+    offerToReceiveTrue[`offerToReceive${capsKind}`] = true;
+
+    const offerToReceiveFalse = {};
+    offerToReceiveFalse[`offerToReceive${capsKind}`] = false;
+
+    // Start testing
+    promise_test(t => {
+      const pc = new RTCPeerConnection();
+      t.add_cleanup(() => pc.close());
+      const dummy = pc.createDataChannel('foo'); // Just to have something to offer
+
+      return pc.createOffer(offerToReceiveFalse)
+      .then(() => {
+        assert_equals(pc.getTransceivers().length, 0,
+          'Expect pc to have no transceivers');
+      });
+    }, `createOffer() with offerToReceive${capsKind} set to false should not create a transceiver`);
+
+    promise_test(t => {
+      const pc = new RTCPeerConnection();
+
+      t.add_cleanup(() => pc.close());
+
+      return pc.createOffer(offerToReceiveTrue)
+      .then(() => {
+        assert_equals(pc.getTransceivers().length, 1,
+          'Expect pc to have one transceiver');
+
+        const transceiver = pc.getTransceivers()[0];
+        assert_equals(transceiver.direction, 'recvonly',
+          'Expect transceiver to have "recvonly" direction');
+      });
+    }, `createOffer() with offerToReceive${capsKind} should create a "recvonly" transceiver`);
+
+    promise_test(t => {
+      const pc = new RTCPeerConnection();
+
+      t.add_cleanup(() => pc.close());
+
+      return pc.createOffer(offerToReceiveTrue)
+      .then(() => {
+        assert_equals(pc.getTransceivers().length, 1,
+          'Expect pc to have one transceiver');
+
+        const transceiver = pc.getTransceivers()[0];
+        assert_equals(transceiver.direction, 'recvonly',
+          'Expect transceiver to have "recvonly" direction');
+      })
+      .then(() => pc.createOffer(offerToReceiveTrue))
+      .then(() => {
+        assert_equals(pc.getTransceivers().length, 1,
+          'Expect pc to still have only one transceiver');
+      })
+      ;
+    }, `offerToReceive${capsKind} option should be ignored if a non-stopped "recvonly" transceiver exists`);
+
+    promise_test(t => {
+      const pc = new RTCPeerConnection();
+
+      t.add_cleanup(() => pc.close());
+
+      return getTrackFromUserMedia(kind)
+      .then(([track, stream]) => {
+        pc.addTrack(track, stream);
+        return pc.createOffer();
+      })
+      .then(() => {
+        assert_equals(pc.getTransceivers().length, 1,
+          'Expect pc to have one transceiver');
+
+        const transceiver = pc.getTransceivers()[0];
+        assert_equals(transceiver.direction, 'sendrecv',
+          'Expect transceiver to have "sendrecv" direction');
+      })
+      .then(() => pc.createOffer(offerToReceiveTrue))
+      .then(() => {
+        assert_equals(pc.getTransceivers().length, 1,
+          'Expect pc to still have only one transceiver');
+      })
+      ;
+    }, `offerToReceive${capsKind} option should be ignored if a non-stopped "sendrecv" transceiver exists`);
+
+    promise_test(t => {
+      const pc = new RTCPeerConnection();
+
+      t.add_cleanup(() => pc.close());
+
+      return getTrackFromUserMedia(kind)
+      .then(([track, stream]) => {
+        pc.addTrack(track, stream);
+        return pc.createOffer(offerToReceiveFalse);
+      })
+      .then(() => {
+        assert_equals(pc.getTransceivers().length, 1,
+          'Expect pc to have one transceiver');
+
+        const transceiver = pc.getTransceivers()[0];
+        assert_equals(transceiver.direction, 'sendonly',
+          'Expect transceiver to have "sendonly" direction');
+      })
+      ;
+    }, `offerToReceive${capsKind} set to false with a track should create a "sendonly" transceiver`);
+
+    promise_test(t => {
+      const pc = new RTCPeerConnection();
+
+      t.add_cleanup(() => pc.close());
+
+      pc.addTransceiver(kind, {direction: 'recvonly'});
+
+      return pc.createOffer(offerToReceiveFalse)
+      .then(() => {
+        assert_equals(pc.getTransceivers().length, 1,
+          'Expect pc to have one transceiver');
+
+        const transceiver = pc.getTransceivers()[0];
+        assert_equals(transceiver.direction, 'inactive',
+          'Expect transceiver to have "inactive" direction');
+      })
+      ;
+    }, `offerToReceive${capsKind} set to false with a "recvonly" transceiver should change the direction to "inactive"`);
+
+    promise_test(t => {
+      const pc = new RTCPeerConnection();
+      t.add_cleanup(() => pc.close());
+      const pc2 = new RTCPeerConnection();
+
+      t.add_cleanup(() => pc2.close());
+
+      return getTrackFromUserMedia(kind)
+      .then(([track, stream]) => {
+        pc.addTrack(track, stream);
+        return pc.createOffer();
+      })
+      .then((offer) => pc.setLocalDescription(offer))
+      .then(() => pc2.setRemoteDescription(pc.localDescription))
+      .then(() => pc2.createAnswer())
+      .then((answer) => pc2.setLocalDescription(answer))
+      .then(() => pc.setRemoteDescription(pc2.localDescription))
+      .then(() => pc.createOffer(offerToReceiveFalse))
+      .then((offer) => {
+        assert_equals(pc.getTransceivers().length, 1,
+          'Expect pc to have one transceiver');
+
+        const transceiver = pc.getTransceivers()[0];
+        assert_equals(transceiver.direction, 'sendonly',
+          'Expect transceiver to have "sendonly" direction');
+      })
+      ;
+    }, `subsequent offerToReceive${capsKind} set to false with a track should change the direction to "sendonly"`);
+  });
+
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+
+    t.add_cleanup(() => pc.close());
+
+    return pc.createOffer({ offerToReceiveAudio: true, offerToReceiveVideo: true })
+    .then(() => {
+      assert_equals(pc.getTransceivers().length, 2,
+        'Expect pc to have two transceivers');
+
+      assert_equals(pc.getTransceivers()[0].direction, 'recvonly',
+        'Expect first transceiver to have "recvonly" direction');
+      assert_equals(pc.getTransceivers()[1].direction, 'recvonly',
+        'Expect second transceiver to have "recvonly" direction');
+    });
+  }, 'offerToReceiveAudio and Video should create two "recvonly" transceivers');
+
+</script>
index dfa4bdc..2fa3a05 100644 (file)
@@ -15,8 +15,7 @@
   //   generateAnswer()
   //   countAudioLine()
   //   countVideoLine()
-  //   test_state_change_event()
-  //   assert_session_desc_equals()
+  //   assert_session_desc_similar()
 
   /*
    *  4.3.2.  createOffer()
@@ -31,6 +30,8 @@
   promise_test(t => {
     const pc = new RTCPeerConnection()
 
+    t.add_cleanup(() => pc.close());
+
     return pc.createOffer()
     .then(offer => {
       assert_equals(typeof offer, 'object',
 
   promise_test(t => {
     const pc = new RTCPeerConnection();
-    test_state_change_event(t, pc, ['have-local-offer']);
+    t.add_cleanup(() => pc.close());
+
+    const states = [];
+    pc.addEventListener('signalingstatechange', () => states.push(pc.signalingState));
 
     return pc.createOffer({ offerToReceiveAudio: true })
     .then(offer =>
       pc.setLocalDescription(offer)
       .then(() => {
         assert_equals(pc.signalingState, 'have-local-offer');
-        assert_session_desc_equals(pc.localDescription, offer);
-        assert_session_desc_equals(pc.pendingLocalDescription, offer);
+        assert_session_desc_similar(pc.localDescription, offer);
+        assert_session_desc_similar(pc.pendingLocalDescription, offer);
         assert_equals(pc.currentLocalDescription, null);
+
+        assert_array_equals(states, ['have-local-offer']);
       }));
   }, 'createOffer() and then setLocalDescription() should succeed');
 
   promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     pc.close();
 
     return promise_rejects(t, 'InvalidStateError',
@@ -78,6 +85,7 @@
    */
   promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     const promise = pc.createOffer();
 
     pc.addTransceiver('audio');
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.createOffer({ offerToReceiveAudio: true })
     .then(offer1 => {
       assert_equals(countAudioLine(offer1.sdp), 1,
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.createOffer({ offerToReceiveVideo: true })
     .then(offer1 => {
       assert_equals(countVideoLine(offer1.sdp), 1,
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.createOffer({
       offerToReceiveAudio: true,
       offerToReceiveVideo: false
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-getIdentityAssertion.sub-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-getIdentityAssertion.sub-expected.txt
new file mode 100644 (file)
index 0000000..11d948a
--- /dev/null
@@ -0,0 +1,24 @@
+
+FAIL getIdentityAssertion() should load IdP proxy and return assertion generated undefined is not a function (near '...pc.setIdentityProvider...')
+FAIL getIdentityAssertion() should succeed if mock-idp.js return different domain and protocol in assertion undefined is not a function (near '...pc.setIdentityProvider...')
+FAIL getIdentityAssertion() should reject with RTCError('idp-execution-failure') if mock-idp.js throws error assert_equals: Expect initial pc.idpErrorInfo to be null expected (object) null but got (undefined) undefined
+FAIL getIdentityAssertion() should reject with RTCError('idp-bad-script-failure') if IdP proxy script do not register its callback undefined is not a function (near '...pc.setIdentityProvider...')
+FAIL getIdentityAssertion() should reject with OperationError if mock-idp.js return invalid result undefined is not a function (near '...pc.setIdentityProvider...')
+FAIL getIdentityAssertion() should reject with RTCError('idp-load-failure') if IdP cannot be loaded pc.setIdentityProvider is not a function. (In 'pc.setIdentityProvider('nonexistent.localhost', {
+      protocol: `non-existent`,
+      usernameHint: `alice@example.org`,
+    })', 'pc.setIdentityProvider' is undefined)
+FAIL getIdentityAssertion() should reject with RTCError('idp-need-login') when mock-idp.js requires login assert_equals: Expect initial pc.idpLoginUrl to be null expected (object) null but got (undefined) undefined
+FAIL setIdentityProvider() with no peerIdentity provided should use peerIdentity value from getConfiguration() pc.setIdentityProvider is not a function. (In 'pc.setIdentityProvider(idpHost, {
+      protocol: 'mock-idp.js'
+    })', 'pc.setIdentityProvider' is undefined)
+FAIL Calling setIdentityProvider() multiple times should reset identity assertions pc.setIdentityProvider is not a function. (In 'pc.setIdentityProvider(idpHost, {
+      protocol: 'mock-idp.js?mark=first'
+    })', 'pc.setIdentityProvider' is undefined)
+FAIL createOffer() should return SDP containing identity assertion string if identity provider is set pc.setIdentityProvider is not a function. (In 'pc.setIdentityProvider(hostString(idpDomain, port), {
+      protocol: 'mock-idp.js',
+      usernameHint: `alice@${idpDomain}`
+    })', 'pc.setIdentityProvider' is undefined)
+FAIL createOffer() should reject with NotReadableError if identitity assertion request fails undefined is not a function (near '...pc.setIdentityProvider...')
+FAIL createAnswer() should reject with NotReadableError if identitity assertion request fails undefined is not a function (near '...pc.setIdentityProvider...')
+
@@ -3,7 +3,7 @@
 <title>RTCPeerConnection.prototype.getIdentityAssertion</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="identity-helper.js"></script>
+<script src="identity-helper.sub.js"></script>
 <script>
   'use strict';
 
@@ -13,7 +13,7 @@
   // The tests here interacts with the mock identity provider located at
   //   /.well-known/idp-proxy/mock-idp.js
 
-  // The following helper functions are called from identity-helper.js
+  // The following helper functions are called from identity-helper.sub.js
   //   parseAssertionResult
   //   getIdpDomains
   //   assert_rtcerror_rejection
@@ -36,7 +36,7 @@
         DOMString peerIdentity;
       };
    */
-  promise_test(() => {
+  promise_test(t => {
     const pc = new RTCPeerConnection();
     const port = window.location.port;
 
@@ -95,7 +95,7 @@
 
   // When generating assertion, the RTCPeerConnection doesn't care if the returned assertion
   // represents identity of different domain
-  promise_test(() => {
+  promise_test(t => {
     const pc = new RTCPeerConnection();
     const port = window.location.port;
 
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
-    pc.setIdentityProvider('nonexistent-origin.web-platform.test', {
+    pc.setIdentityProvider('nonexistent.{{domains[]}}', {
       protocol: `non-existent`,
       usernameHint: `alice@example.org`,
     });
         value is provided for the peerIdentity member of RTCConfiguration, the value from
         RTCConfiguration is used.
   */
-  promise_test(() => {
+  promise_test(t => {
     const pc = new RTCPeerConnection({
       peerIdentity: 'bob@example.net'
     });
     9.6.  setIdentityProvider
       3.  If any identity provider value has changed, discard any stored identity assertion.
    */
-  promise_test(() => {
+  promise_test(t => {
     const pc = new RTCPeerConnection();
     const port = window.location.port;
     const [idpDomain] = getIdpDomains();
index 5c2d59d..03cb32b 100644 (file)
@@ -3,7 +3,7 @@ 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
 PASS getStats() with track added via addTrack should succeed 
-FAIL getStats() with track added via addTransceiver should succeed Type error
+PASS getStats() with track added via addTransceiver should succeed 
 FAIL getStats() with track associated with more than one sender should reject with InvalidAccessError assert_unreached: Should have rejected: undefined Reached unreachable code
 FAIL getStats() with track associated with both sender and receiver should reject with InvalidAccessError assert_unreached: Should have rejected: undefined Reached unreachable code
 FAIL getStats() with no argument should return stats report containing peer-connection stats on an empty PC assert_true: Expect statsReport to contain stats object of type peer-connection expected true got false
index 7313850..247402b 100644 (file)
         1.  Gather the stats indicated by selector according to the stats selection algorithm.
         2.  Resolve p with the resulting RTCStatsReport object, containing the gathered stats.
    */
-  promise_test(() => {
+  promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     return pc.getStats();
   }, 'getStats() with no argument should succeed');
 
-  promise_test(() => {
+  promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     return pc.getStats(null);
   }, 'getStats(null) should succeed');
 
@@ -59,6 +61,7 @@
    */
   promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     return getTrackFromUserMedia('audio')
     .then(([track, mediaStream]) => {
       return promise_rejects(t, 'InvalidAccessError', pc.getStats(track));
@@ -67,6 +70,7 @@
 
   promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     return getTrackFromUserMedia('audio')
     .then(([track, mediaStream]) => {
       pc.addTrack(track, mediaStream);
     });
   }, 'getStats() with track added via addTrack should succeed');
 
-  promise_test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
-    const track = generateMediaStreamTrack();
+    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();
     pc.addTransceiver(track);
 
     return pc.getStats(track);
@@ -84,6 +92,7 @@
 
   promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     return getTrackFromUserMedia('audio')
     .then(([track, mediaStream]) => {
       // addTransceiver allows adding same track multiple times
 
   promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     const transceiver1 = pc.addTransceiver('audio');
 
     // Create another transceiver that resends what
    */
   promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     return pc.getStats()
     .then(statsReport => {
       validateStatsReport(statsReport);
 
   promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     return getTrackFromUserMedia('audio')
     .then(([track, mediaStream]) => {
       pc.addTrack(track, mediaStream);
 
   promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     return getTrackFromUserMedia('audio')
     .then(([track, mediaStream]) => {
       pc.addTrack(track);
         - All stats objects referenced directly or indirectly by the RTCOutboundRTPStreamStats
           objects added.
    */
-  promise_test(() => {
+  promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     return getTrackFromUserMedia('audio')
     .then(([track, mediaStream]) => {
       pc.addTrack(track, mediaStream);
         - All stats objects referenced directly or indirectly by the RTCInboundRTPStreamStats
           added.
    */
-  promise_test(() => {
+  promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     const transceiver = pc.addTransceiver('audio');
 
     return pc.getStats(transceiver.receiver.track)
 
     const dataChannel = pc1.createDataChannel('test-channel');
 
-    return navigator.mediaDevices.getUserMedia({
+    return getNoiseStream({
       audio: true,
       video: true
     })
     .then(t.step_func(mediaStream => {
       const tracks = mediaStream.getTracks();
-      assert_equals(tracks.length, 2,
-        'Expect media stream to have one audio and one video track');
+      const [audioTrack] = mediaStream.getAudioTracks();
+      const [videoTrack] = mediaStream.getVideoTracks();
 
-      let audioTrack;
-      let videoTrack;
-
-      for (const track of tracks) {
+      for (const track of mediaStream.getTracks()) {
         t.add_cleanup(() => track.stop());
-
         pc1.addTrack(track, mediaStream);
-
-        if (track.kind === 'audio') {
-          audioTrack = track;
-        } else if (track.kind === 'video') {
-          videoTrack = track;
-        }
-      }
-
-      if (!audioTrack || ! videoTrack) {
-        assert_unreached('Expect mediaStream to have both audio and video streams');
       }
 
       const testStatsReport = (pc, statsReport) => {
index 8ccebbd..b13e580 100644 (file)
@@ -93,12 +93,12 @@ function isSimilarSessionDescription(sessionDesc1, sessionDesc2) {
   }
 }
 
-function assert_session_desc_equals(sessionDesc1, sessionDesc2) {
+function assert_session_desc_similar(sessionDesc1, sessionDesc2) {
   assert_true(isSimilarSessionDescription(sessionDesc1, sessionDesc2),
     'Expect both session descriptions to have the same count of media lines');
 }
 
-function assert_session_desc_not_equals(sessionDesc1, sessionDesc2) {
+function assert_session_desc_not_similar(sessionDesc1, sessionDesc2) {
   assert_false(isSimilarSessionDescription(sessionDesc1, sessionDesc2),
     'Expect both session descriptions to have different count of media lines');
 }
@@ -165,37 +165,11 @@ function generateOffer(options={}) {
 function generateAnswer(offer) {
   const pc = new RTCPeerConnection();
   return pc.setRemoteDescription(offer)
-  .then(() => pc.createAnswer());
-}
-
-// Wait for peer connection to fire onsignalingstatechange
-// event, compare and make sure the new state is the same
-// as expected state. It accepts an RTCPeerConnection object
-// and an array of expected state changes. The test passes
-// if all expected state change events have been fired, and
-// fail if the new state is different from the expected state.
-//
-// Note that the promise is never resolved if no change
-// event is fired. To avoid confusion with the main test
-// getting timed out, this is done in parallel as a separate
-// test
-function test_state_change_event(parentTest, pc, expectedStates) {
-  return async_test(t => {
-    pc.onsignalingstatechange = t.step_func(() => {
-      if(expectedStates.length === 0) {
-        return;
-      }
-
-      const newState = pc.signalingState;
-      const expectedState = expectedStates.shift();
-
-      assert_equals(newState, expectedState, 'New signaling state is different from expected.');
-
-      if(expectedStates.length === 0) {
-        t.done();
-      }
-    });
-  }, `Test onsignalingstatechange event for ${parentTest.name}`);
+  .then(() => pc.createAnswer())
+  .then((answer) => {
+    pc.close();
+    return answer;
+  });
 }
 
 // Run a test function that return a promise that should
@@ -229,7 +203,7 @@ function exchangeIceCandidates(pc1, pc2) {
       // There is ongoing discussion on w3c/webrtc-pc#1213
       // that there should be an empty candidate string event
       // for end of candidate for each m= section.
-      if(candidate) {
+      if(candidate && remotePc.signalingState !== 'closed') {
         remotePc.addIceCandidate(candidate);
       }
     });
@@ -356,41 +330,112 @@ function assert_equals_array_buffer(buffer1, buffer2) {
   }
 }
 
-// Generate a MediaStreamTrack for testing use.
-// We generate it by creating an anonymous RTCPeerConnection,
-// call addTransceiver(), and use the remote track
-// from RTCRtpReceiver. This track is supposed to
-// receive media from a remote peer and be played locally.
-// We use this approach instead of getUserMedia()
-// to bypass the permission dialog and fake media devices,
-// as well as being able to generate many unique tracks.
-function generateMediaStreamTrack(kind) {
-  const pc = new RTCPeerConnection();
+// These media tracks will be continually updated with deterministic "noise" in
+// order to ensure UAs do not cease transmission in response to apparent
+// silence.
+//
+// > Many codecs and systems are capable of detecting "silence" and changing
+// > their behavior in this case by doing things such as not transmitting any
+// > media.
+//
+// Source: https://w3c.github.io/webrtc-pc/#offer-answer-options
+const trackFactories = {
+  // Share a single context between tests to avoid exceeding resource limits
+  // without requiring explicit destruction.
+  audioContext: null,
+
+  /**
+   * Given a set of requested media types, determine if the user agent is
+   * capable of procedurally generating a suitable media stream.
+   *
+   * @param {object} requested
+   * @param {boolean} [requested.audio] - flag indicating whether the desired
+   *                                      stream should include an audio track
+   * @param {boolean} [requested.video] - flag indicating whether the desired
+   *                                      stream should include a video track
+   *
+   * @returns {boolean}
+   */
+  canCreate(requested) {
+    const supported = {
+      audio: !!window.MediaStreamAudioDestinationNode,
+      video: !!HTMLCanvasElement.prototype.captureStream
+    };
+
+    return (!requested.audio || supported.audio) &&
+      (!requested.video || supported.video);
+  },
+
+  audio() {
+    const ctx = trackFactories.audioContext = trackFactories.audioContext ||
+      new AudioContext();
+    const oscillator = ctx.createOscillator();
+    const dst = oscillator.connect(ctx.createMediaStreamDestination());
+    oscillator.start();
+    return dst.stream.getAudioTracks()[0];
+  },
+
+  video({width = 640, height = 480} = {}) {
+    const canvas = Object.assign(
+      document.createElement("canvas"), {width, height}
+    );
+    const ctx = canvas.getContext('2d');
+    const stream = canvas.captureStream();
+
+    let count = 0;
+    setInterval(() => {
+      ctx.fillStyle = `rgb(${count%255}, ${count*count%255}, ${count%255})`;
+      count += 1;
+
+      ctx.fillRect(0, 0, width, height);
+    }, 100);
+
+    if (document.body) {
+      document.body.appendChild(canvas);
+    } else {
+      document.addEventListener('DOMContentLoaded', () => {
+        document.body.appendChild(canvas);
+      });
+    }
 
-  assert_idl_attribute(pc, 'addTransceiver',
-    'Expect pc to have addTransceiver() method');
+    return stream.getVideoTracks()[0];
+  }
+};
+
+// Generate a MediaStream bearing the specified tracks.
+//
+// @param {object} [caps]
+// @param {boolean} [caps.audio] - flag indicating whether the generated stream
+//                                 should include an audio track
+// @param {boolean} [caps.video] - flag indicating whether the generated stream
+//                                 should include a video track
+async function getNoiseStream(caps = {}) {
+  if (!trackFactories.canCreate(caps)) {
+    return navigator.mediaDevices.getUserMedia(caps);
+  }
+  const tracks = [];
 
-  const transceiver = pc.addTransceiver(kind);
-  const { receiver } = transceiver;
-  const { track } = receiver;
+  if (caps.audio) {
+    tracks.push(trackFactories.audio());
+  }
 
-  assert_true(track instanceof MediaStreamTrack,
-    'Expect receiver track to be instance of MediaStreamTrack');
+  if (caps.video) {
+    tracks.push(trackFactories.video());
+  }
 
-  return track;
+  return new MediaStream(tracks);
 }
 
-// Obtain a MediaStreamTrack of kind using getUserMedia.
+// Obtain a MediaStreamTrack of kind using procedurally-generated streams (and
+// falling back to `getUserMedia` when the user agent cannot generate the
+// requested streams).
 // Return Promise of pair of track and associated mediaStream.
 // Assumes that there is at least one available device
 // to generate the track.
 function getTrackFromUserMedia(kind) {
-  return navigator.mediaDevices.getUserMedia({ [kind]: true })
+  return getNoiseStream({ [kind]: true })
   .then(mediaStream => {
-    const tracks = mediaStream.getTracks();
-    assert_greater_than(tracks.length, 0,
-      `Expect getUserMedia to return at least one track of kind ${kind}`);
-    const [ track ] = tracks;
+    const [track] = mediaStream.getTracks();
     return [track, mediaStream];
   });
 }
@@ -418,18 +463,34 @@ function getUserMediaTracksAndStreams(count, type = 'audio') {
   });
 }
 
-// Creates an offer for the caller, set it as the caller's local description and
-// then sets the callee's remote description to the offer. Returns the Promise
-// of the setRemoteDescription call.
-function performOffer(caller, callee) {
-  let sessionDescription;
-  return caller.createOffer()
-  .then(offer => {
-    sessionDescription = offer;
-    return caller.setLocalDescription(offer);
-  }).then(() => callee.setRemoteDescription(sessionDescription));
+// Performs an offer exchange caller -> callee.
+async function exchangeOffer(caller, callee) {
+  const offer = await caller.createOffer();
+  await caller.setLocalDescription(offer);
+  return callee.setRemoteDescription(offer);
+}
+// Performs an answer exchange caller -> callee.
+async function exchangeAnswer(caller, callee) {
+  const answer = await callee.createAnswer();
+  await callee.setLocalDescription(answer);
+  return caller.setRemoteDescription(answer);
+}
+async function exchangeOfferAnswer(caller, callee) {
+  await exchangeOffer(caller, callee);
+  return exchangeAnswer(caller, callee);
+}
+// The returned promise is resolved with caller's ontrack event.
+async function exchangeAnswerAndListenToOntrack(t, caller, callee) {
+  const ontrackPromise = addEventListenerPromise(t, caller, 'track');
+  await exchangeAnswer(caller, callee);
+  return ontrackPromise;
+}
+// The returned promise is resolved with callee's ontrack event.
+async function exchangeOfferAndListenToOntrack(t, caller, callee) {
+  const ontrackPromise = addEventListenerPromise(t, callee, 'track');
+  await exchangeOffer(caller, callee);
+  return ontrackPromise;
 }
-
 
 // The resolver has a |promise| that can be resolved or rejected using |resolve|
 // or |reject|.
@@ -445,3 +506,37 @@ class Resolver {
     this.reject = promiseReject;
   }
 }
+
+function addEventListenerPromise(t, target, type, listener) {
+  return new Promise((resolve, reject) => {
+    target.addEventListener(type, t.step_func(e => {
+      if (listener != undefined)
+        e = listener(e);
+      resolve(e);
+    }));
+  });
+}
+
+function createPeerConnectionWithCleanup(t) {
+  const pc = new RTCPeerConnection();
+  t.add_cleanup(() => pc.close());
+  return pc;
+}
+
+async function createTrackAndStreamWithCleanup(t, kind = 'audio') {
+  let constraints = {};
+  constraints[kind] = true;
+  const stream = await navigator.mediaDevices.getUserMedia(constraints);
+  const [track] = stream.getTracks();
+  t.add_cleanup(() => track.stop());
+  return [track, stream];
+}
+
+function findTransceiverForSender(pc, sender) {
+  const transceivers = pc.getTransceivers();
+  for (let i = 0; i < transceivers.length; ++i) {
+    if (transceivers[i].sender == sender)
+      return transceivers[i];
+  }
+  return null;
+}
index 59c964c..4071033 100644 (file)
    */
   async_test(t => {
     const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
     const pc2 = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc2.close());
+
     const onIceConnectionStateChange = t.step_func(() => {
       const { iceConnectionState } = pc1;
 
 
     exchangeIceCandidates(pc1, pc2);
     doSignalingHandshake(pc1, pc2);
-
   }, 'connection with one data channel should eventually have connected connection state');
 
   /*
index 4265b8b..fb9e514 100644 (file)
@@ -57,6 +57,8 @@
   async_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     const onIceGatheringStateChange = t.step_func(() => {
       const { iceGatheringState } = pc;
 
@@ -74,7 +76,6 @@
     .then(offer => pc.setLocalDescription(offer))
     .then(err => t.step_func(err =>
       assert_unreached(`Unhandled rejection ${err.name}: ${err.message}`)));
-
   }, 'iceGatheringState should eventually become complete after setLocalDescription');
 
   /*
    */
   async_test(t => {
     const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
     const pc2 = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc2.close());
+
     const onIceGatheringStateChange = t.step_func(() => {
       const { iceGatheringState } = pc2;
 
 
     exchangeIceCandidates(pc1, pc2);
     doSignalingHandshake(pc1, pc2);
-
   }, 'connection with one data channel should eventually have connected connection state');
 
   /*
index 4948d11..1070ee7 100644 (file)
    */
   async_test(t => {
     const localPc = new RTCPeerConnection();
+    t.add_cleanup(() => localPc.close());
     const remotePc = new RTCPeerConnection();
 
+    t.add_cleanup(() => remotePc.close());
+
     let eventCount = 0;
 
     const onDataChannel = t.step_func_done(event => {
@@ -69,7 +72,6 @@
     remotePc.addEventListener('datachannel', onDataChannel);
     exchangeIceCandidates(localPc, remotePc);
     doSignalingHandshake(localPc, remotePc);
-
   }, 'datachannel event should fire when new data channel is announced to the remote peer');
 
   /*
    */
   async_test(t => {
     const localPc = new RTCPeerConnection();
+    t.add_cleanup(() => localPc.close());
     const remotePc = new RTCPeerConnection();
 
+    t.add_cleanup(() => remotePc.close());
+
     const onDataChannel = t.step_func_done(event => {
       const remoteChannel = event.channel;
       assert_true(remoteChannel instanceof RTCDataChannel,
    */
   async_test(t => {
     const localPc = new RTCPeerConnection();
+    t.add_cleanup(() => localPc.close());
     const remotePc = new RTCPeerConnection();
 
+    t.add_cleanup(() => remotePc.close());
+
     const onDataChannel = t.unreached_func('datachannel event should not be fired');
 
     localPc.createDataChannel('test', {
     doSignalingHandshake(localPc, remotePc);
 
     t.step_timeout(t.step_func_done(), 200);
-
   }, 'Data channel created with negotiated set to true should not fire datachannel event on remote peer');
 
   /*
index a71625c..8f9ea2b 100644 (file)
@@ -82,6 +82,7 @@
    */
   promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     const negotiated = awaitNegotiation(pc);
 
     pc.createDataChannel('test');
    */
   promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     const negotiated = awaitNegotiation(pc);
 
     pc.addTransceiver('audio');
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return assert_first_promise_fulfill_after_second(
       awaitNegotiation(pc),
       pc.createOffer({ offerToReceiveAudio: true })
index 6610139..1021012 100644 (file)
@@ -98,6 +98,8 @@
   async_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     // Fail the test if the ontrack event handler is not implemented
     assert_idl_attribute(pc, 'ontrack', 'Expect pc to have ontrack event handler attribute');
 
@@ -151,6 +153,8 @@ a=ssrc:1001 cname:some
   async_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     assert_idl_attribute(pc, 'ontrack', 'Expect pc to have ontrack event handler attribute');
 
     const sdp = `v=0
@@ -182,13 +186,15 @@ a=ssrc:1001 cname:some
     .then(t.step_func(() => {
       t.step_timeout(t.step_func_done(), 100);
     }));
-
   }, 'setRemoteDescription() with m= line of recvonly direction should not trigger track event');
 
   async_test(t => {
     const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
     const pc2 = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc2.close());
+
     pc2.ontrack = t.step_func(trackEvent => {
       const { track } = trackEvent;
 
@@ -210,13 +216,15 @@ a=ssrc:1001 cname:some
     .catch(t.step_func(err => {
       assert_unreached('Error ' + err.name + ': ' + err.message);
     }));
-
   }, 'addTrack() should cause remote connection to fire ontrack when setRemoteDescription()');
 
   async_test(t => {
     const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
     const pc2 = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc2.close());
+
     pc2.ontrack = t.step_func(trackEvent => {
       const { track } = trackEvent;
 
@@ -235,13 +243,15 @@ a=ssrc:1001 cname:some
     .catch(t.step_func(err => {
       assert_unreached('Error ' + err.name + ': ' + err.message);
     }));
-
   }, `addTransceiver('video') should cause remote connection to fire ontrack when setRemoteDescription()`);
 
   async_test(t => {
     const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
     const pc2 = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc2.close());
+
     pc2.ontrack = t.step_func(trackEvent => {
       const { track } = trackEvent;
 
@@ -264,7 +274,6 @@ a=ssrc:1001 cname:some
     .then(t.step_func(() => {
       t.step_timeout(t.step_func_done(), 100);
     }));
-
   }, `addTransceiver() with inactive direction should not cause remote connection to fire ontrack when setRemoteDescription()`);
 
 </script>
index d5f9db9..64ad212 100644 (file)
@@ -3,7 +3,7 @@
 <title>RTCPeerConnection.prototype.peerIdentity</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="identity-helper.js"></script>
+<script src="identity-helper.sub.js"></script>
 <script>
   'use strict';
 
@@ -13,7 +13,7 @@
   // The tests here interacts with the mock identity provider located at
   //   /.well-known/idp-proxy/mock-idp.js
 
-  // The following helper functions are called from identity-helper.js
+  // The following helper functions are called from identity-helper.sub.js
   //   parseAssertionResult
   //   getIdpDomains
   //   assert_rtcerror_rejection
       is, there is a current value for peerIdentity ), then this also establishes a
       target peer identity.
    */
-  promise_test(() => {
+  promise_test(t => {
     const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
     const pc2 = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc2.close());
+
     const port = window.location.port;
     const [idpDomain] = getIdpDomains();
     const idpHost = hostString(idpDomain, port);
     const idpHost = hostString(idpDomain, port);
 
     const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
     const pc2 = new RTCPeerConnection({
       peerIdentity: `bob@${idpDomain}`
     });
 
+    t.add_cleanup(() => pc2.close());
+
     pc1.setIdentityProvider(idpHost, {
       protocol: 'mock-idp.js',
       usernameHint: `alice@${idpDomain}`
     const idpHost = hostString(idpDomain, port);
 
     const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
     const pc2 = new RTCPeerConnection({
       peerIdentity: `alice@${idpDomain}`
     });
 
+    t.add_cleanup(() => pc2.close());
+
     // Ask mockidp.js to return custom contents in validation result
     pc1.setIdentityProvider(idpHost, {
       protocol: 'mock-idp.js?validatorAction=return-custom-contents&contents=bogus',
     const idpHost1 = hostString(idpDomain1, port);
 
     const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
     const pc2 = new RTCPeerConnection({
       peerIdentity: `alice@${idpDomain2}`
     });
 
+    t.add_cleanup(() => pc2.close());
+
     // mock-idp.js will return assertion of domain2 identity
     // with domain1 in the idp.domain field
     pc1.setIdentityProvider(idpHost1, {
     const idpHost = hostString(idpDomain, port);
 
     const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
     const pc2 = new RTCPeerConnection({
       peerIdentity: `alice@${idpDomain}`
     });
 
+    t.add_cleanup(() => pc2.close());
+
     // Ask mock-idp.js to throw error during validation,
     // i.e. during pc2.setRemoteDescription()
     pc1.setIdentityProvider(idpHost, {
    */
   promise_test(t => {
     const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
     const pc2 = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc2.close());
+
     const port = window.location.port;
     const [idpDomain] = getIdpDomains();
     const idpHost = hostString(idpDomain, port);
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-remote-track-mute.https-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-remote-track-mute.https-expected.txt
new file mode 100644 (file)
index 0000000..aa7cf89
--- /dev/null
@@ -0,0 +1,8 @@
+
+Harness Error (TIMEOUT), message = null
+
+FAIL ontrack: track goes from muted to unmuted assert_true: track is muted in ontrack expected true got false
+FAIL Changing transceiver direction to 'inactive' mutes the remote track promise_test: Unhandled rejection with value: object "TypeError: Attempted to assign to readonly property."
+FAIL Changing transceiver direction to 'sendrecv' unmutes the remote track promise_test: Unhandled rejection with value: object "TypeError: Attempted to assign to readonly property."
+TIMEOUT pc.close() mutes remote tracks Test timed out
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-remote-track-mute.https.html b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-remote-track-mute.https.html
new file mode 100644 (file)
index 0000000..56fe761
--- /dev/null
@@ -0,0 +1,98 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCPeerConnection-transceivers.https.html</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:
+//   exchangeOffer
+//   exchangeOfferAndListenToOntrack
+//   exchangeAnswer
+//   exchangeAnswerAndListenToOntrack
+//   addEventListenerPromise
+//   createPeerConnectionWithCleanup
+//   createTrackAndStreamWithCleanup
+//   findTransceiverForSender
+
+promise_test(async t => {
+  const pc1 = createPeerConnectionWithCleanup(t);
+  pc1.addTrack(... await createTrackAndStreamWithCleanup(t));
+  const pc2 = createPeerConnectionWithCleanup(t);
+  exchangeIceCandidates(pc1, pc2);
+
+  const unmuteResolver = new Resolver();
+  let remoteTrack = null;
+  // The unmuting it timing sensitive so we hook up to the event directly
+  // instead of wrapping it in an EventWatcher which uses promises.
+  pc2.ontrack = t.step_func(e => {
+    remoteTrack = e.track;
+    assert_true(remoteTrack.muted, 'track is muted in ontrack');
+    remoteTrack.onunmute = t.step_func(e => {
+      assert_false(remoteTrack.muted, 'track is unmuted in onunmute');
+      unmuteResolver.resolve();
+    });
+    pc2.ontrack = t.step_func(e => {
+      assert_unreached('ontrack fired unexpectedly');
+    });
+  });
+  await exchangeOfferAnswer(pc1, pc2);
+  await unmuteResolver.promise;
+}, 'ontrack: track goes from muted to unmuted');
+
+promise_test(async t => {
+  const pc1 = createPeerConnectionWithCleanup(t);
+  const pc1Sender = pc1.addTrack(... await createTrackAndStreamWithCleanup(t));
+  const localTransceiver = findTransceiverForSender(pc1, pc1Sender);
+  const pc2 = createPeerConnectionWithCleanup(t);
+  exchangeIceCandidates(pc1, pc2);
+
+  const e = await exchangeOfferAndListenToOntrack(t, pc1, pc2);
+  await exchangeAnswer(pc1, pc2);
+
+  const muteWatcher = new EventWatcher(t, e.track, ['mute']);
+  const mutePromise = muteWatcher.wait_for('mute');
+  localTransceiver.direction = 'inactive';
+  await exchangeOfferAnswer(pc1, pc2);
+
+  await mutePromise;
+}, 'Changing transceiver direction to \'inactive\' mutes the remote track');
+
+promise_test(async t => {
+  const pc1 = createPeerConnectionWithCleanup(t);
+  const pc1Sender = pc1.addTrack(... await createTrackAndStreamWithCleanup(t));
+  const localTransceiver = findTransceiverForSender(pc1, pc1Sender);
+  const pc2 = createPeerConnectionWithCleanup(t);
+  exchangeIceCandidates(pc1, pc2);
+
+  const e = await exchangeOfferAndListenToOntrack(t, pc1, pc2);
+  await exchangeAnswer(pc1, pc2);
+  localTransceiver.direction = 'inactive';
+  await exchangeOfferAnswer(pc1, pc2);
+
+  const unmuteWatcher = new EventWatcher(t, e.track, ['unmute']);
+  const unmutePromise = unmuteWatcher.wait_for('unmute');
+  localTransceiver.direction = 'sendrecv';
+  await exchangeOfferAnswer(pc1, pc2);
+
+  await unmutePromise;
+}, 'Changing transceiver direction to \'sendrecv\' unmutes the remote track');
+
+promise_test(async t => {
+  const pc1 = createPeerConnectionWithCleanup(t);
+  const pc1Sender = pc1.addTrack(... await createTrackAndStreamWithCleanup(t));
+  const localTransceiver = findTransceiverForSender(pc1, pc1Sender);
+  const pc2 = createPeerConnectionWithCleanup(t);
+  exchangeIceCandidates(pc1, pc2);
+
+  const e = await exchangeOfferAndListenToOntrack(t, pc1, pc2);
+  await exchangeAnswer(pc1, pc2);
+  const muteWatcher = new EventWatcher(t, e.track, ['mute']);
+  const mutePromise = muteWatcher.wait_for('mute');
+  pc2.close();
+  await mutePromise;
+}, 'pc.close() mutes remote tracks');
+
+</script>
index b0943dd..3ca4167 100644 (file)
@@ -5,10 +5,10 @@ PASS addTransceiver - Calling removeTrack on different connection that is closed
 PASS addTrack - Calling removeTrack on different connection that is closed should throw InvalidStateError 
 FAIL addTransceiver - Calling removeTrack on different connection should throw InvalidAccessError assert_throws: function "() => pc2.removeTrack(sender)" did not throw
 FAIL addTrack - Calling removeTrack on different connection should throw InvalidAccessError assert_throws: function "() => pc2.removeTrack(sender)" did not throw
-FAIL addTransceiver - Calling removeTrack with valid sender should set sender.track to null assert_equals: direction should not be altered expected "sendrecv" but got "recvonly"
+PASS addTransceiver - Calling removeTrack with valid sender should set sender.track to null 
 PASS addTrack - Calling removeTrack with valid sender should set sender.track to null 
-FAIL Calling removeTrack with currentDirection sendrecv should set direction to recvonly assert_equals: expected "sendrecv" but got "sendonly"
+PASS Calling removeTrack with currentDirection sendrecv should set direction to recvonly 
 PASS Calling removeTrack with currentDirection sendonly should set direction to inactive 
-FAIL Calling removeTrack with currentDirection recvonly should not change direction assert_equals: expected "recvonly" but got "inactive"
+PASS Calling removeTrack with currentDirection recvonly should not change direction 
 PASS Calling removeTrack with currentDirection inactive should not change direction 
 
index e2da02b..c98dbdb 100644 (file)
@@ -12,7 +12,6 @@
 
   // The following helper functions are called from RTCPeerConnection-helper.js:
   // generateAnswer
-  // generateMediaStreamTrack
 
   /*
     5.1.  RTCPeerConnection Interface Extensions
     5.1.  removeTrack
       3.  If connection's [[isClosed]] slot is true, throw an InvalidStateError.
    */
-  test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
-    const track = generateMediaStreamTrack('audio');
+    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const [track] = stream.getTracks();
     const transceiver = pc.addTransceiver(track);
     const { sender } = transceiver;
 
     pc.close();
     assert_throws('InvalidStateError', () => pc.removeTrack(sender));
-
   }, 'addTransceiver - Calling removeTrack when connection is closed should throw InvalidStateError');
 
-  promise_test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
 
-    return navigator.mediaDevices.getUserMedia({ audio: true })
-    .then(mediaStream => {
-      const tracks = mediaStream.getTracks();
-      assert_greater_than(tracks.length, 0,
-        'Expect getUserMedia to return at least one audio track');
-
-      const track = tracks[0];
-      const sender = pc.addTrack(track, mediaStream);
+    const stream = await getNoiseStream({ audio: true });
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const [track] = stream.getTracks();
+    const sender = pc.addTrack(track, stream);
 
-      pc.close();
-      assert_throws('InvalidStateError', () => pc.removeTrack(sender));
-    });
+    pc.close();
+    assert_throws('InvalidStateError', () => pc.removeTrack(sender));
   }, 'addTrack - Calling removeTrack when connection is closed should throw InvalidStateError');
 
-  test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
-    const track = generateMediaStreamTrack('audio');
+    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const [track] = stream.getTracks();
     const transceiver = pc.addTransceiver(track);
     const { sender } = transceiver;
 
     const pc2 = new RTCPeerConnection();
     pc2.close();
     assert_throws('InvalidStateError', () => pc2.removeTrack(sender));
-
   }, 'addTransceiver - Calling removeTrack on different connection that is closed should throw InvalidStateError');
 
-  promise_test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
 
-    return navigator.mediaDevices.getUserMedia({ audio: true })
-    .then(mediaStream => {
-      const tracks = mediaStream.getTracks();
-      assert_greater_than(tracks.length, 0,
-        'Expect getUserMedia to return at least one audio track');
+    const stream = await getNoiseStream({ audio: true });
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const [track] = stream.getTracks();
+    const sender = pc.addTrack(track, stream);
 
-      const track = tracks[0];
-      const sender = pc.addTrack(track, mediaStream);
-
-      const pc2 = new RTCPeerConnection();
-      pc2.close();
-      assert_throws('InvalidStateError', () => pc2.removeTrack(sender));
-    });
+    const pc2 = new RTCPeerConnection();
+    pc2.close();
+    assert_throws('InvalidStateError', () => pc2.removeTrack(sender));
   }, 'addTrack - Calling removeTrack on different connection that is closed should throw InvalidStateError');
 
   /*
     5.1.  removeTrack
       4.  If sender was not created by connection, throw an InvalidAccessError.
    */
-  test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
-    const track = generateMediaStreamTrack('audio');
+    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const [track] = stream.getTracks();
     const transceiver = pc.addTransceiver(track);
     const { sender } = transceiver;
 
     const pc2 = new RTCPeerConnection();
     assert_throws('InvalidAccessError', () => pc2.removeTrack(sender));
-
   }, 'addTransceiver - Calling removeTrack on different connection should throw InvalidAccessError');
 
-  promise_test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
 
-    return navigator.mediaDevices.getUserMedia({ audio: true })
-    .then(mediaStream => {
-      const tracks = mediaStream.getTracks();
-      assert_greater_than(tracks.length, 0,
-        'Expect getUserMedia to return at least one audio track');
-
-      const track = tracks[0];
-      const sender = pc.addTrack(track, mediaStream);
+    const stream = await getNoiseStream({ audio: true });
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const [track] = stream.getTracks();
+    const sender = pc.addTrack(track, stream);
 
-      const pc2 = new RTCPeerConnection();
-      assert_throws('InvalidAccessError', () => pc2.removeTrack(sender));
-    });
+    const pc2 = new RTCPeerConnection();
+    assert_throws('InvalidAccessError', () => pc2.removeTrack(sender));
   }, 'addTrack - Calling removeTrack on different connection should throw InvalidAccessError')
 
   /*
     5.1.  removeTrack
       7.  Set sender.track to null.
    */
-  test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
-    const track = generateMediaStreamTrack('audio');
+    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const [track] = stream.getTracks();
     const transceiver = pc.addTransceiver(track);
     const { sender } = transceiver;
 
 
     pc.removeTrack(sender);
     assert_equals(sender.track, null);
-    assert_equals(transceiver.direction, 'sendrecv',
-      'direction should not be altered');
-
+    assert_equals(transceiver.direction, 'recvonly');
   }, 'addTransceiver - Calling removeTrack with valid sender should set sender.track to null');
 
-  promise_test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
 
-    return navigator.mediaDevices.getUserMedia({ audio: true })
-    .then(mediaStream => {
-      const tracks = mediaStream.getTracks();
-      assert_greater_than(tracks.length, 0,
-        'Expect getUserMedia to return at least one audio track');
-
-      const track = tracks[0];
-      const sender = pc.addTrack(track, mediaStream);
+    const stream = await getNoiseStream({ audio: true });
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const [track] = stream.getTracks();
+    const sender = pc.addTrack(track, stream);
 
-      assert_equals(sender.track, track);
+    assert_equals(sender.track, track);
 
-      pc.removeTrack(sender);
-      assert_equals(sender.track, null);
-    });
+    pc.removeTrack(sender);
+    assert_equals(sender.track, null);
   }, 'addTrack - Calling removeTrack with valid sender should set sender.track to null');
 
   /*
       10. If transceiver.currentDirection is sendrecv set transceiver.direction
           to recvonly.
    */
-  promise_test(t => {
-    const pc = new RTCPeerConnection();
-    const track = generateMediaStreamTrack('audio');
-    const transceiver = pc.addTransceiver(track);
+  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});
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const [track] = stream.getTracks();
+    const transceiver = caller.addTransceiver(track);
     const { sender } = transceiver;
 
     assert_equals(sender.track, track);
     assert_equals(transceiver.direction, 'sendrecv');
     assert_equals(transceiver.currentDirection, null);
 
-    return pc.createOffer()
-    .then(offer =>
-      pc.setLocalDescription(offer)
-      .then(() => generateAnswer(offer)))
-    .then(answer => pc.setRemoteDescription(answer))
-    .then(() => {
-      assert_equals(transceiver.currentDirection, 'sendrecv');
-
-      pc.removeTrack(sender);
-      assert_equals(sender.track, null);
-      assert_equals(transceiver.direction, 'recvonly');
-      assert_equals(transceiver.currentDirection, 'sendrecv',
-        'Expect currentDirection to not change');
-    });
+    const offer = await caller.createOffer();
+    await caller.setLocalDescription(offer);
+    await callee.setRemoteDescription(offer);
+    callee.addTrack(track);
+    const answer = await callee.createAnswer();
+    await callee.setLocalDescription(answer);
+    await caller.setRemoteDescription(answer);
+    assert_equals(transceiver.currentDirection, 'sendrecv');
+
+    caller.removeTrack(sender);
+    assert_equals(sender.track, null);
+    assert_equals(transceiver.direction, 'recvonly');
+    assert_equals(transceiver.currentDirection, 'sendrecv',
+      'Expect currentDirection to not change');
   }, 'Calling removeTrack with currentDirection sendrecv should set direction to recvonly');
 
   /*
       11. If transceiver.currentDirection is sendonly set transceiver.direction
           to inactive.
    */
-  promise_test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
-    const track = generateMediaStreamTrack('audio');
+    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 transceiver = pc.addTransceiver(track, { direction: 'sendonly' });
     const { sender } = transceiver;
 
     assert_equals(transceiver.direction, 'sendonly');
     assert_equals(transceiver.currentDirection, null);
 
-    return pc.createOffer()
-    .then(offer =>
-      pc.setLocalDescription(offer)
-      .then(() => generateAnswer(offer)))
-    .then(answer => pc.setRemoteDescription(answer))
-    .then(() => {
-      assert_equals(transceiver.currentDirection, 'sendonly');
-
-      pc.removeTrack(sender);
-      assert_equals(sender.track, null);
-      assert_equals(transceiver.direction, 'inactive');
-      assert_equals(transceiver.currentDirection, 'sendonly',
-        'Expect currentDirection to not change');
-    });
+    const offer = await pc.createOffer();
+    await pc.setLocalDescription(offer);
+    const answer = await generateAnswer(offer);
+    await pc.setRemoteDescription(answer);
+    assert_equals(transceiver.currentDirection, 'sendonly');
+
+    pc.removeTrack(sender);
+    assert_equals(sender.track, null);
+    assert_equals(transceiver.direction, 'inactive');
+    assert_equals(transceiver.currentDirection, 'sendonly',
+      'Expect currentDirection to not change');
   }, 'Calling removeTrack with currentDirection sendonly should set direction to inactive');
 
   /*
       9.  If transceiver.currentDirection is recvonly or inactive,
           then abort these steps.
    */
-  promise_test(t => {
-    const pc = new RTCPeerConnection();
-    const track = generateMediaStreamTrack('audio');
-    const transceiver = pc.addTransceiver(track, { direction: 'recvonly' });
+  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});
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const [track] = stream.getTracks();
+    const transceiver = caller.addTransceiver(track, { direction: 'recvonly' });
     const { sender } = transceiver;
 
     assert_equals(sender.track, track);
     assert_equals(transceiver.direction, 'recvonly');
     assert_equals(transceiver.currentDirection, null);
 
-    return pc.createOffer()
-    .then(offer =>
-      pc.setLocalDescription(offer)
-      .then(() => generateAnswer(offer)))
-    .then(answer => pc.setRemoteDescription(answer))
-    .then(() => {
-      assert_equals(transceiver.currentDirection, 'recvonly');
-
-      pc.removeTrack(sender);
-      assert_equals(sender.track, null);
-      assert_equals(transceiver.direction, 'recvonly');
-      assert_equals(transceiver.currentDirection, 'recvonly');
-    });
+    const offer = await caller.createOffer();
+    await caller.setLocalDescription(offer);
+    await callee.setRemoteDescription(offer);
+    callee.addTrack(track);
+    const answer = await callee.createAnswer();
+    await callee.setLocalDescription(answer);
+    await caller.setRemoteDescription(answer);
+    assert_equals(transceiver.currentDirection, 'recvonly');
+
+    caller.removeTrack(sender);
+    assert_equals(sender.track, null);
+    assert_equals(transceiver.direction, 'recvonly');
+    assert_equals(transceiver.currentDirection, 'recvonly');
   }, 'Calling removeTrack with currentDirection recvonly should not change direction');
 
   /*
       9.  If transceiver.currentDirection is recvonly or inactive,
           then abort these steps.
    */
-  promise_test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
-    const track = generateMediaStreamTrack('audio');
+    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 transceiver = pc.addTransceiver(track, { direction: 'inactive' });
     const { sender } = transceiver;
 
     assert_equals(transceiver.direction, 'inactive');
     assert_equals(transceiver.currentDirection, null);
 
-    return pc.createOffer()
-    .then(offer =>
-      pc.setLocalDescription(offer)
-      .then(() => generateAnswer(offer)))
-    .then(answer => pc.setRemoteDescription(answer))
-    .then(() => {
-      assert_equals(transceiver.currentDirection, 'inactive');
-
-      pc.removeTrack(sender);
-      assert_equals(sender.track, null);
-      assert_equals(transceiver.direction, 'inactive');
-      assert_equals(transceiver.currentDirection, 'inactive');
-    });
+    const offer = await pc.createOffer();
+    await pc.setLocalDescription(offer);
+    const answer = await generateAnswer(offer);
+    await pc.setRemoteDescription(answer);
+    assert_equals(transceiver.currentDirection, 'inactive');
+
+    pc.removeTrack(sender);
+    assert_equals(sender.track, null);
+    assert_equals(transceiver.direction, 'inactive');
+    assert_equals(transceiver.currentDirection, 'inactive');
   }, 'Calling removeTrack with currentDirection inactive should not change direction');
 
   /*
index 7b0d13d..6b88ae6 100644 (file)
@@ -1,6 +1,6 @@
 
 PASS setLocalDescription(offer) with m= section should assign mid to corresponding transceiver 
-FAIL setRemoteDescription(offer) with m= section and no existing transceiver should create corresponding transceiver promise_test: Unhandled rejection with value: object "TypeError: pc2.setRemoteDescrption is not a function. (In 'pc2.setRemoteDescrption(offer)', 'pc2.setRemoteDescrption' is undefined)"
+PASS setRemoteDescription(offer) with m= section and no existing transceiver should create corresponding transceiver 
 FAIL setLocalDescription(rollback) should unset transceiver.mid promise_test: Unhandled rejection with value: object "InvalidStateError: Description type incompatible with current signaling state"
 FAIL setLocalDescription(rollback) should only unset transceiver mids associated with current round promise_test: Unhandled rejection with value: object "InvalidStateError: Description type incompatible with current signaling state"
 FAIL setRemoteDescription(rollback) should remove newly created transceiver from transceiver list promise_test: Unhandled rejection with value: object "InvalidStateError: Description type incompatible with current signaling state"
index ed41d66..5fc957d 100644 (file)
@@ -64,8 +64,9 @@
         2.  Set transceiver's mid value to the mid of the corresponding media
             description.
    */
-  promise_test(() => {
+  promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     const transceiver = pc.addTransceiver('audio');
     assert_equals(transceiver.mid, null);
 
               transceiver be the result.
         3.  Set transceiver's mid value to the mid of the corresponding media description.
    */
-  promise_test(() => {
+  promise_test(t => {
     const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
     const pc2 = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc2.close());
+
     const transceiver1 = pc1.addTransceiver('audio');
     assert_array_equals(pc1.getTransceivers(), [transceiver1]);
     assert_array_equals(pc2.getTransceivers(), []);
     .then(offer => {
       return Promise.all([
         pc1.setLocalDescription(offer),
-        pc2.setRemoteDescrption(offer)
+        pc2.setRemoteDescription(offer)
       ])
       .then(() => {
         const transceivers = pc2.getTransceivers();
             the RTCSessionDescription that is being rolled back, set the mid value
             of that transceiver to null, as described by [JSEP] (section 4.1.8.2.).
    */
-  promise_test(() => {
+  promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     const transceiver = pc.addTransceiver('audio');
     assert_equals(transceiver.mid, null);
 
     });
   }, 'setLocalDescription(rollback) should unset transceiver.mid');
 
-  promise_test(() => {
+  promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     const transceiver1 = pc.addTransceiver('audio');
     assert_equals(transceiver1.mid, null);
 
             addTrack, remove that transceiver from connection's set of transceivers,
             as described by [JSEP] (section 4.1.8.2.).
    */
-  promise_test(() => {
+  promise_test(t => {
     const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
     const pc2 = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc2.close());
+
     pc1.addTransceiver('audio');
 
     return pc1.createOffer()
index ef2fe90..b19cc0f 100644 (file)
@@ -4,5 +4,4 @@ FAIL setLocalDescription() with type answer and null sdp should use lastAnswer g
 FAIL setLocalDescription() with answer not created by own createAnswer() should reject with InvalidModificationError assert_unreached: Should have rejected: undefined Reached unreachable code
 PASS Calling setLocalDescription(answer) from stable state should reject with InvalidStateError 
 PASS Calling setLocalDescription(answer) from have-local-offer state should reject with InvalidStateError 
-PASS Test onsignalingstatechange event for setLocalDescription() with valid answer should succeed 
 
index 99f0d48..4a50456 100644 (file)
@@ -13,8 +13,7 @@
   // The following helper functions are called from RTCPeerConnection-helper.js:
   //   generateOffer
   //   generateAnswer
-  //   assert_session_desc_equals
-  //   test_state_change_event
+  //   assert_session_desc_similar
 
   /*
     4.3.2.  Interface Definition
    */
   promise_test(t => {
     const pc = new RTCPeerConnection();
-    test_state_change_event(t, pc, ['have-remote-offer', 'stable']);
+    t.add_cleanup(() => pc.close());
+
+    const states = [];
+    pc.addEventListener('signalingstatechange', () => states.push(pc.signalingState));
 
     return pc.createOffer({ offerToReceiveVideo: true })
     .then(offer =>
         pc.setLocalDescription(answer)
         .then(() => {
           assert_equals(pc.signalingState, 'stable');
-          assert_session_desc_equals(pc.localDescription, answer);
-          assert_session_desc_equals(pc.remoteDescription, offer);
+          assert_session_desc_similar(pc.localDescription, answer);
+          assert_session_desc_similar(pc.remoteDescription, offer);
 
-          assert_session_desc_equals(pc.currentLocalDescription, answer);
-          assert_session_desc_equals(pc.currentRemoteDescription, offer);
+          assert_session_desc_similar(pc.currentLocalDescription, answer);
+          assert_session_desc_similar(pc.currentRemoteDescription, offer);
 
           assert_equals(pc.pendingLocalDescription, null);
           assert_equals(pc.pendingRemoteDescription, null);
+
+          assert_array_equals(states, ['have-remote-offer', 'stable']);
         })));
   }, 'setLocalDescription() with valid answer should succeed');
 
@@ -91,6 +95,8 @@
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.createOffer({ offerToReceiveVideo: true })
     .then(offer =>
       pc.setRemoteDescription(offer)
         pc.setLocalDescription({ type: 'answer' })
         .then(() => {
           assert_equals(pc.signalingState, 'stable');
-          assert_session_desc_equals(pc.localDescription, answer);
-          assert_session_desc_equals(pc.remoteDescription, offer);
+          assert_session_desc_similar(pc.localDescription, answer);
+          assert_session_desc_similar(pc.remoteDescription, offer);
 
-          assert_session_desc_equals(pc.currentLocalDescription, answer);
-          assert_session_desc_equals(pc.currentRemoteDescription, offer);
+          assert_session_desc_similar(pc.currentLocalDescription, answer);
+          assert_session_desc_similar(pc.currentRemoteDescription, offer);
 
           assert_equals(pc.pendingLocalDescription, null);
           assert_equals(pc.pendingRemoteDescription, null);
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.createOffer({ offerToReceiveVideo: true })
     .then(offer =>
       pc.setRemoteDescription(offer)
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.createOffer()
     .then(offer =>
       promise_rejects(t, 'InvalidStateError',
         pc.setLocalDescription({ type: 'answer', sdp: offer.sdp })));
-
   }, 'Calling setLocalDescription(answer) from stable state should reject with InvalidStateError');
 
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.createOffer()
     .then(offer =>
       pc.setLocalDescription(offer)
index cd18280..c6cfced 100644 (file)
@@ -1,6 +1,5 @@
 
 PASS Calling createOffer() and setLocalDescription() again after one round of local-offer/remote-answer should succeed 
 PASS Switching role from answerer to offerer after going back to stable state should succeed 
-PASS Test onsignalingstatechange event for Calling createOffer() and setLocalDescription() again after one round of local-offer/remote-answer should succeed 
-PASS Test onsignalingstatechange event for Switching role from answerer to offerer after going back to stable state should succeed 
+PASS onsignalingstatechange fires before setLocalDescription resolves 
 
index a8e3270..fdf979a 100644 (file)
@@ -1,9 +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 push down transport description for data: Local fingerprint does not match identity. Expected: sha-256 0D:E8:6D:13:47:D3:0D:40:43:17:84:0B:34:55:1C:15:97:4A:46:FE:8A:F2:6E:3B:0A:A2:CC:6A:70:BC:14:1D Got: sha-256 3D:F2:4E:C4:C0:AC:A1:04:C5:D3:3A:19:C4:92:8F:98:AA:E0:52:9E:05:84:20:19:71:76:EC:C4:02:45:27:21" 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 AD:6B:CA:05:12:E5:47:71:41:68:ED:52:9B:85:F2:0A:B9:BE:FE:28:BE:FF:38:17:D6:4A:6A:7F:A1:F2:2E:AC Got: sha-256 3F:A8:09:A6:A8:AD:A9:F8:A8:58:0A:79:0C:04:B4:AA:E1:56:9E:25:07:C8:84:34:9C:A5:78:C0:E8:22:84:DF" 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_false: Expect both session descriptions to have different count of media lines expected false got true
 FAIL Creating and setting offer multiple times should succeed assert_not_equals: Expect session description to be defined got disallowed value undefined
-PASS Test onsignalingstatechange event for setLocalDescription with valid offer should succeed 
-PASS Test onsignalingstatechange event for Creating and setting offer multiple times should succeed 
 
index 76df63a..9502f28 100644 (file)
@@ -12,9 +12,8 @@
 
   // The following helper functions are called from RTCPeerConnection-helper.js:
   //   generateOffer
-  //   assert_session_desc_not_equals
-  //   assert_session_desc_equals
-  //   test_state_change_event
+  //   assert_session_desc_not_similar
+  //   assert_session_desc_similar
 
   /*
     4.3.2.  Interface Definition
    */
   promise_test(t => {
     const pc = new RTCPeerConnection();
-    test_state_change_event(t, pc, ['have-local-offer']);
+    t.add_cleanup(() => pc.close());
+
+    const states = [];
+    pc.addEventListener('signalingstatechange', () => states.push(pc.signalingState));
 
     return pc.createOffer({ offerToReceiveAudio: true })
     .then(offer =>
       pc.setLocalDescription(offer)
       .then(() => {
         assert_equals(pc.signalingState, 'have-local-offer');
-        assert_session_desc_equals(pc.localDescription, offer);
-        assert_session_desc_equals(pc.pendingLocalDescription, offer);
+        assert_session_desc_similar(pc.localDescription, offer);
+        assert_session_desc_similar(pc.pendingLocalDescription, offer);
         assert_equals(pc.currentLocalDescription, null);
+
+        assert_array_equals(states, ['have-local-offer']);
       }));
   }, 'setLocalDescription with valid offer should succeed');
 
    */
   promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     return pc.createOffer({ offerToReceiveAudio: true })
     .then(offer =>
       pc.setLocalDescription({ type: 'offer' })
       .then(() => {
         assert_equals(pc.signalingState, 'have-local-offer');
-        assert_session_desc_equals(pc.localDescription, offer);
-        assert_session_desc_equals(pc.pendingLocalDescription, offer);
+        assert_session_desc_similar(pc.localDescription, offer);
+        assert_session_desc_similar(pc.pendingLocalDescription, offer);
         assert_equals(pc.currentLocalDescription, null);
       }));
   }, 'setLocalDescription with type offer and null sdp should use lastOffer generated from createOffer');
    */
   promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     const pc2 = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc2.close());
+
     return generateOffer({ pc, data: true })
     .then(offer =>
       promise_rejects(t, 'InvalidModificationError',
     // last offer, setLocalDescription would reject when setting
     // with the first offer
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     return pc.createOffer({ offerToReceiveAudio: true })
     .then(offer1 =>
       pc.createOffer({ offerToReceiveVideo: true })
       .then(offer2 => {
-        assert_session_desc_not_equals(offer1, offer2);
+        assert_session_desc_not_similar(offer1, offer2);
         return promise_rejects(t, 'InvalidModificationError',
           pc.setLocalDescription(offer1));
       }));
 
   promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
 
-    // Only one state change event should be fired
-    test_state_change_event(t, pc, ['have-local-offer']);
+    const states = [];
+    pc.addEventListener('signalingstatechange', () => states.push(pc.signalingState));
 
     return pc.createOffer({ offerToReceiveAudio: true })
     .then(offer1 =>
         .then(offer2 =>
           pc.setLocalDescription(offer2)
           .then(offer2 => {
-            assert_session_desc_not_equals(offer1, offer2);
+            assert_session_desc_not_similar(offer1, offer2);
             assert_equals(pc.signalingState, 'have-local-offer');
-            assert_session_desc_equals(pc.localDescription, offer2);
-            assert_session_desc_equals(pc.pendingLocalDescription, offer2);
+            assert_session_desc_similar(pc.localDescription, offer2);
+            assert_session_desc_similar(pc.pendingLocalDescription, offer2);
             assert_equals(pc.currentLocalDescription, null);
+
+            assert_array_equals(states, ['have-local-offer']);
           }))));
   }, 'Creating and setting offer multiple times should succeed');
 
index 8fb60eb..33aaa14 100644 (file)
@@ -3,7 +3,4 @@ PASS setLocalDescription(pranswer) from stable state should reject with InvalidS
 FAIL setLocalDescription(pranswer) should succeed assert_equals: expected null but got object "[object RTCSessionDescription]"
 PASS setLocalDescription(pranswer) can be applied multiple times while still in have-local-pranswer 
 PASS setLocalDescription(answer) from have-local-pranswer state should succeed 
-PASS Test onsignalingstatechange event for setLocalDescription(pranswer) should succeed 
-PASS Test onsignalingstatechange event for setLocalDescription(pranswer) can be applied multiple times while still in have-local-pranswer 
-PASS Test onsignalingstatechange event for setLocalDescription(answer) from have-local-pranswer state should succeed 
 
index 4d6b011..a145f95 100644 (file)
@@ -13,8 +13,7 @@
   // The following helper functions are called from RTCPeerConnection-helper.js:
   //   generateOffer
   //   generateAnswer
-  //   assert_session_desc_equals
-  //   test_state_change_event
+  //   assert_session_desc_similar
 
   /*
     4.3.2.  Interface Definition
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.createOffer()
     .then(offer =>
       promise_rejects(t, 'InvalidStateError',
         pc.setLocalDescription({ type: 'pranswer', sdp: offer.sdp })));
-
   }, 'setLocalDescription(pranswer) from stable state should reject with InvalidStateError');
 
   /*
    */
   promise_test(t => {
     const pc = new RTCPeerConnection();
-    test_state_change_event(t, pc, ['have-remote-offer', 'have-local-pranswer']);
+    t.add_cleanup(() => pc.close());
+
+    const states = [];
+    pc.addEventListener('signalingstatechange', () => states.push(pc.signalingState));
 
     return pc.createOffer({ offerToReceiveVideo: true })
     .then(offer =>
         .then(() => {
           assert_equals(pc.signalingState, 'have-local-pranswer');
 
-          assert_session_desc_equals(pc.remoteDescription, offer);
-          assert_session_desc_equals(pc.pendingRemoteDescription, offer);
+          assert_session_desc_similar(pc.remoteDescription, offer);
+          assert_session_desc_similar(pc.pendingRemoteDescription, offer);
           assert_equals(pc.currentRemoteDescription, null);
 
-          assert_session_desc_equals(pc.localDescription, pranswer);
-          assert_session_desc_equals(pc.pendingLocalDescription, pranswer);
+          assert_session_desc_similar(pc.localDescription, pranswer);
+          assert_session_desc_similar(pc.pendingLocalDescription, pranswer);
           assert_equals(pc.currentLocalDescription, null);
 
           assert_equals(pc.pendingRemoteDescription, null);
+
+          assert_array_equals(states, ['have-remote-offer', 'have-local-pranswer']);
         });
       }));
   }, 'setLocalDescription(pranswer) should succeed');
 
   promise_test(t => {
     const pc = new RTCPeerConnection();
-    test_state_change_event(t, pc, ['have-remote-offer', 'have-local-pranswer']);
+    t.add_cleanup(() => pc.close());
+
+    const states = [];
+    pc.addEventListener('signalingstatechange', () => states.push(pc.signalingState));
 
     return pc.createOffer({ offerToReceiveVideo: true })
     .then(offer =>
         const pranswer = { type: 'pranswer', sdp: answer.sdp };
 
         return pc.setLocalDescription(pranswer)
-        .then(() => pc.setLocalDescription(pranswer));
+        .then(() => pc.setLocalDescription(pranswer))
+        .then(() => {
+          assert_array_equals(states, ['have-remote-offer', 'have-local-pranswer']);
+        });
       }));
   }, 'setLocalDescription(pranswer) can be applied multiple times while still in have-local-pranswer');
 
   promise_test(t => {
     const pc = new RTCPeerConnection();
-    test_state_change_event(t, pc, ['have-remote-offer', 'have-local-pranswer', 'stable']);
+    t.add_cleanup(() => pc.close());
+
+    const states = [];
+    pc.addEventListener('signalingstatechange', () => states.push(pc.signalingState));
 
     return pc.createOffer({ offerToReceiveVideo: true })
     .then(offer =>
         .then(() => pc.setLocalDescription(answer))
         .then(() => {
           assert_equals(pc.signalingState, 'stable');
-          assert_session_desc_equals(pc.localDescription, answer);
-          assert_session_desc_equals(pc.remoteDescription, offer);
+          assert_session_desc_similar(pc.localDescription, answer);
+          assert_session_desc_similar(pc.remoteDescription, offer);
 
-          assert_session_desc_equals(pc.currentLocalDescription, answer);
-          assert_session_desc_equals(pc.currentRemoteDescription, offer);
+          assert_session_desc_similar(pc.currentLocalDescription, answer);
+          assert_session_desc_similar(pc.currentRemoteDescription, offer);
 
           assert_equals(pc.pendingLocalDescription, null);
           assert_equals(pc.pendingRemoteDescription, null);
+
+          assert_array_equals(states, ['have-remote-offer', 'have-local-pranswer', 'stable']);
         });
       }));
   }, 'setLocalDescription(answer) from have-local-pranswer state should succeed');
index 0b786dc..9566e4b 100644 (file)
@@ -1,9 +1,6 @@
 
-Harness Error (TIMEOUT), message = null
-
 FAIL setLocalDescription(rollback) from have-local-offer state should reset back to stable state promise_test: Unhandled rejection with value: object "InvalidStateError: Description type incompatible with current signaling state"
 PASS setLocalDescription(rollback) from stable state should reject with InvalidStateError 
 PASS setLocalDescription(rollback) after setting answer description should reject with InvalidStateError 
 FAIL setLocalDescription(rollback) should ignore invalid sdp content and succeed promise_test: Unhandled rejection with value: object "InvalidStateError: Description type incompatible with current signaling state"
-TIMEOUT Test onsignalingstatechange event for setLocalDescription(rollback) from have-local-offer state should reset back to stable state Test timed out
 
index 5cafb1e..0147b31 100644 (file)
@@ -11,8 +11,7 @@
   // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
 
   // The following helper functions are called from RTCPeerConnection-helper.js:
-  //   assert_session_desc_equals
-  //   test_state_change_event
+  //   assert_session_desc_similar
 
   /*
     4.3.2.  Interface Definition
    */
   promise_test(t=> {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
 
-    test_state_change_event(t, pc, ['have-local-offer', 'stable']);
+    const states = [];
+    pc.addEventListener('signalingstatechange', () => states.push(pc.signalingState));
 
     return pc.createOffer()
     .then(offer => pc.setLocalDescription(offer))
@@ -76,6 +77,8 @@
       assert_equals(pc.localDescription, null);
       assert_equals(pc.pendingLocalDescription, null);
       assert_equals(pc.currentLocalDescription, null);
+
+      assert_array_equals(states, ['have-local-offer', 'stable']);
     });
   }, 'setLocalDescription(rollback) from have-local-offer state should reset back to stable state');
 
    */
   promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     return promise_rejects(t, 'InvalidStateError',
       pc.setLocalDescription({ type: 'rollback' }));
   }, `setLocalDescription(rollback) from stable state should reject with InvalidStateError`);
 
   promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     return pc.createOffer({ offerToReceiveAudio: true })
     .then(offer =>
       pc.setRemoteDescription(offer)
 
   promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     return pc.createOffer()
     .then(offer => pc.setLocalDescription(offer))
     .then(() => pc.setLocalDescription({
index 72685bd..0ab2db1 100644 (file)
@@ -12,9 +12,8 @@
 
   // The following helper functions are called from RTCPeerConnection-helper.js:
   //   generateOffer
-  //   assert_session_desc_not_equals
-  //   assert_session_desc_equals
-  //   test_state_change_event
+  //   assert_session_desc_not_similar
+  //   assert_session_desc_similar
 
   /*
     4.3.2.  Interface Definition
 
   promise_test(t => {
     const pc = new RTCPeerConnection();
-    test_state_change_event(t, pc, ['have-local-offer', 'stable', 'have-local-offer']);
+    t.add_cleanup(() => pc.close());
+
+    const states = [];
+    pc.addEventListener('signalingstatechange', () => states.push(pc.signalingState));
 
     return pc.createOffer({ offerToReceiveAudio: true })
     .then(offer1 =>
         pc.setLocalDescription(offer2)
         .then(() => {
           assert_equals(pc.signalingState, 'have-local-offer');
-          assert_session_desc_not_equals(offer1, offer2);
-          assert_session_desc_equals(pc.localDescription, offer2);
-          assert_session_desc_equals(pc.currentLocalDescription, offer1);
-          assert_session_desc_equals(pc.pendingLocalDescription, offer2);
+          assert_session_desc_not_similar(offer1, offer2);
+          assert_session_desc_similar(pc.localDescription, offer2);
+          assert_session_desc_similar(pc.currentLocalDescription, offer1);
+          assert_session_desc_similar(pc.pendingLocalDescription, offer2);
+
+          assert_array_equals(states, ['have-local-offer', 'stable', 'have-local-offer']);
         })));
   }, 'Calling createOffer() and setLocalDescription() again after one round of local-offer/remote-answer should succeed');
 
   promise_test(t => {
     const pc = new RTCPeerConnection();
-    test_state_change_event(t, pc,
-      ['have-remote-offer', 'stable', 'have-local-offer']);
+    t.add_cleanup(() => pc.close());
+
+    const states = [];
+    pc.addEventListener('signalingstatechange', () => states.push(pc.signalingState));
 
     return generateOffer({ pc, data: true })
     .then(offer => pc.setRemoteDescription(offer))
         pc.setLocalDescription(offer)
         .then(() => {
           assert_equals(pc.signalingState, 'have-local-offer');
-          assert_session_desc_equals(pc.localDescription, offer);
-          assert_session_desc_equals(pc.currentLocalDescription, answer);
-          assert_session_desc_equals(pc.pendingLocalDescription, offer);
-        })));
+          assert_session_desc_similar(pc.localDescription, offer);
+          assert_session_desc_similar(pc.currentLocalDescription, answer);
+          assert_session_desc_similar(pc.pendingLocalDescription, offer);
 
+          assert_array_equals(states, ['have-remote-offer', 'stable', 'have-local-offer']);
+        })));
   }, 'Switching role from answerer to offerer after going back to stable state should succeed');
 
+  promise_test(async t => {
+    const pc = new RTCPeerConnection();
+    const offer = await pc.createOffer();
+    let eventSequence = '';
+    const signalingstatechangeResolver = new Resolver();
+    pc.onsignalingstatechange = () => {
+      eventSequence += 'onsignalingstatechange;';
+      signalingstatechangeResolver.resolve();
+    };
+    await pc.setLocalDescription(offer);
+    eventSequence += 'setLocalDescription;';
+    await signalingstatechangeResolver.promise;
+    assert_equals(eventSequence, 'onsignalingstatechange;setLocalDescription;');
+  }, 'onsignalingstatechange fires before setLocalDescription resolves');
+
   /*
     TODO
       4.3.2.  setLocalDescription
index ab2bee2..d133341 100644 (file)
@@ -2,5 +2,4 @@
 PASS setRemoteDescription() with valid state and answer should succeed 
 PASS Calling setRemoteDescription(answer) from stable state should reject with InvalidStateError 
 PASS Calling setRemoteDescription(answer) from have-remote-offer state should reject with InvalidStateError 
-PASS Test onsignalingstatechange event for setRemoteDescription() with valid state and answer should succeed 
 
index 75bbec8..ff55447 100644 (file)
@@ -13,8 +13,7 @@
   // The following helper functions are called from RTCPeerConnection-helper.js:
   //   generateOffer()
   //   generateAnswer()
-  //   assert_session_desc_equals()
-  //   test_state_change_event()
+  //   assert_session_desc_similar()
 
   /*
     4.3.2.  Interface Definition
    */
   promise_test(t => {
     const pc = new RTCPeerConnection();
-    test_state_change_event(t, pc, ['have-local-offer', 'stable']);
+    t.add_cleanup(() => pc.close());
+
+    const states = [];
+    pc.addEventListener('signalingstatechange', () => states.push(pc.signalingState));
 
     return pc.createOffer({ offerToReceiveVideo: true })
     .then(offer =>
         .then(() => {
           assert_equals(pc.signalingState, 'stable');
 
-          assert_session_desc_equals(pc.localDescription, offer);
-          assert_session_desc_equals(pc.remoteDescription, answer);
+          assert_session_desc_similar(pc.localDescription, offer);
+          assert_session_desc_similar(pc.remoteDescription, answer);
 
-          assert_session_desc_equals(pc.currentLocalDescription, offer);
-          assert_session_desc_equals(pc.currentRemoteDescription, answer);
+          assert_session_desc_similar(pc.currentLocalDescription, offer);
+          assert_session_desc_similar(pc.currentRemoteDescription, answer);
 
           assert_equals(pc.pendingLocalDescription, null);
           assert_equals(pc.pendingRemoteDescription, null);
+
+          assert_array_equals(states, ['have-local-offer', 'stable']);
         })));
   }, 'setRemoteDescription() with valid state and answer should succeed');
 
@@ -95,6 +99,8 @@
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.createOffer()
     .then(offer =>
       promise_rejects(t, 'InvalidStateError',
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.createOffer()
     .then(offer =>
       pc.setRemoteDescription(offer)
     .then(answer =>
       promise_rejects(t, 'InvalidStateError',
         pc.setRemoteDescription(answer)));
-
   }, 'Calling setRemoteDescription(answer) from have-remote-offer state should reject with InvalidStateError');
 
 </script>
index c2d9dd1..0454993 100644 (file)
@@ -1,8 +1,7 @@
 
 PASS setRemoteDescription with invalid type and invalid SDP should reject with TypeError 
 PASS setRemoteDescription() with invalid SDP and stable state should reject with InvalidStateError 
+PASS Negotiation should fire signalingsstate events 
 FAIL Calling setRemoteDescription() again after one round of remote-offer/local-answer should succeed assert_false: Expect both session descriptions to have different count of media lines expected false got true
 PASS Switching role from offerer to answerer after going back to stable state should succeed 
-PASS Test onsignalingstatechange event for Calling setRemoteDescription() again after one round of remote-offer/local-answer should succeed 
-PASS Test onsignalingstatechange event for Switching role from offerer to answerer after going back to stable state should succeed 
 
index 60e8a98..eb8e956 100644 (file)
@@ -1,12 +1,7 @@
 
-Harness Error (TIMEOUT), message = null
-
 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
 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 
-PASS Test onsignalingstatechange event for setRemoteDescription with valid offer should succeed 
-PASS Test onsignalingstatechange event for setRemoteDescription multiple times should succeed 
-TIMEOUT Test onsignalingstatechange event for setRemoteDescription multiple times with different offer should succeed Test timed out
 
index 449925c..565fddc 100644 (file)
@@ -11,8 +11,7 @@
   // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
 
   // The following helper functions are called from RTCPeerConnection-helper.js:
-  //   assert_session_desc_equals()
-  //   test_state_change_event()
+  //   assert_session_desc_similar()
 
   /*
     4.3.2.  Interface Definition
 
   promise_test(t => {
     const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
     pc1.createDataChannel('datachannel');
 
     const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc2.close());
 
-    test_state_change_event(t, pc2, ['have-remote-offer']);
+    const states = [];
+    pc2.addEventListener('signalingstatechange', () => states.push(pc2.signalingState));
 
     return pc1.createOffer()
      .then(offer => {
       return pc2.setRemoteDescription(offer)
       .then(() => {
         assert_equals(pc2.signalingState, 'have-remote-offer');
-        assert_session_desc_equals(pc2.remoteDescription, offer);
-        assert_session_desc_equals(pc2.pendingRemoteDescription, offer);
+        assert_session_desc_similar(pc2.remoteDescription, offer);
+        assert_session_desc_similar(pc2.pendingRemoteDescription, offer);
         assert_equals(pc2.currentRemoteDescription, null);
+
+        assert_array_equals(states, ['have-remote-offer']);
       });
     });
-   }, 'setRemoteDescription with valid offer should succeed');
+  }, 'setRemoteDescription with valid offer should succeed');
 
   promise_test(t => {
     const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
     pc1.createDataChannel('datachannel');
 
     const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc2.close());
 
-    // have-remote-offer event should only fire once
-    test_state_change_event(t, pc2, ['have-remote-offer']);
+    const states = [];
+    pc2.addEventListener('signalingstatechange', () => states.push(pc2.signalingState));
 
     return pc1.createOffer()
     .then(offer => {
       .then(() => pc2.setRemoteDescription(offer))
       .then(() => {
         assert_equals(pc2.signalingState, 'have-remote-offer');
-        assert_session_desc_equals(pc2.remoteDescription, offer);
-        assert_session_desc_equals(pc2.pendingRemoteDescription, offer);
+        assert_session_desc_similar(pc2.remoteDescription, offer);
+        assert_session_desc_similar(pc2.pendingRemoteDescription, offer);
         assert_equals(pc2.currentRemoteDescription, null);
+
+        assert_array_equals(states, ['have-remote-offer']);
       });
     });
   }, 'setRemoteDescription multiple times should succeed');
 
   promise_test(t => {
     const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
     pc1.createDataChannel('datachannel');
 
     const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc2.close());
 
-    // have-remote-offer event should only fire once
-    test_state_change_event(t, pc2, ['have-remote-offer']);
+    const states = [];
+    pc2.addEventListener('signalingstatechange', () => states.push(pc2.signalingState));
 
     return pc1.createOffer()
     .then(offer1 => {
        .then(()=> {
         return pc1.createOffer({ offerToReceiveAudio: true })
         .then(offer2 => {
-          assert_session_desc_not_equals(offer1, offer2);
+          assert_session_desc_not_similar(offer1, offer2);
 
           return pc2.setRemoteDescription(offer1)
           .then(() => pc2.setRemoteDescription(offer2))
           .then(() => {
             assert_equals(pc2.signalingState, 'have-remote-offer');
-            assert_session_desc_equals(pc2.remoteDescription, offer2);
-            assert_session_desc_equals(pc2.pendingRemoteDescription, offer2);
+            assert_session_desc_similar(pc2.remoteDescription, offer2);
+            assert_session_desc_similar(pc2.pendingRemoteDescription, offer2);
             assert_equals(pc2.currentRemoteDescription, null);
+
+            assert_array_equals(states, ['have-remote-offer']);
           });
         });
       });
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.setRemoteDescription({
       type: 'offer',
       sdp: 'Invalid SDP'
    */
   promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     return pc.createOffer()
     .then(offer => {
       return pc.setLocalDescription(offer)
index c682f36..4ff7eab 100644 (file)
@@ -3,7 +3,4 @@ PASS setRemoteDescription(pranswer) from stable state should reject with Invalid
 PASS setRemoteDescription(pranswer) from have-local-offer state should succeed 
 PASS setRemoteDescription(pranswer) multiple times should succeed 
 PASS setRemoteDescription(answer) from have-remote-pranswer state should succeed 
-PASS Test onsignalingstatechange event for setRemoteDescription(pranswer) from have-local-offer state should succeed 
-PASS Test onsignalingstatechange event for setRemoteDescription(pranswer) multiple times should succeed 
-PASS Test onsignalingstatechange event for setRemoteDescription(answer) from have-remote-pranswer state should succeed 
 
index 521290f..9197a35 100644 (file)
@@ -13,8 +13,7 @@
   // The following helper functions are called from RTCPeerConnection-helper.js:
   //   generateOffer
   //   generateAnswer
-  //   assert_session_desc_equals
-  //   test_state_change_event
+  //   assert_session_desc_similar
 
   /*
     4.3.2.  Interface Definition
@@ -64,6 +63,8 @@
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
+    t.add_cleanup(() => pc.close());
+
     return pc.createOffer()
     .then(offer =>
       promise_rejects(t, 'InvalidStateError',
    */
   promise_test(t => {
     const pc = new RTCPeerConnection();
-    test_state_change_event(t, pc, ['have-local-offer', 'have-remote-pranswer']);
+    t.add_cleanup(() => pc.close());
+
+    const states = [];
+    pc.addEventListener('signalingstatechange', () => states.push(pc.signalingState));
 
     return pc.createOffer({ offerToReceiveVideo: true })
     .then(offer =>
         .then(() => {
           assert_equals(pc.signalingState, 'have-remote-pranswer');
 
-          assert_session_desc_equals(pc.localDescription, offer);
-          assert_session_desc_equals(pc.pendingLocalDescription, offer);
+          assert_session_desc_similar(pc.localDescription, offer);
+          assert_session_desc_similar(pc.pendingLocalDescription, offer);
           assert_equals(pc.currentLocalDescription, null);
 
-          assert_session_desc_equals(pc.remoteDescription, pranswer);
-          assert_session_desc_equals(pc.pendingRemoteDescription, pranswer);
+          assert_session_desc_similar(pc.remoteDescription, pranswer);
+          assert_session_desc_similar(pc.pendingRemoteDescription, pranswer);
           assert_equals(pc.currentRemoteDescription, null);
+
+          assert_array_equals(states, ['have-local-offer', 'have-remote-pranswer']);
         });
       }));
   }, 'setRemoteDescription(pranswer) from have-local-offer state should succeed');
 
   promise_test(t => {
     const pc = new RTCPeerConnection();
-    test_state_change_event(t, pc, ['have-local-offer', 'have-remote-pranswer']);
+    t.add_cleanup(() => pc.close());
+
+    const states = [];
+    pc.addEventListener('signalingstatechange', () => states.push(pc.signalingState));
 
     return pc.createOffer({ offerToReceiveVideo: true })
     .then(offer =>
         const pranswer = { type: 'pranswer', sdp: answer.sdp };
 
         return pc.setRemoteDescription(pranswer)
-        .then(() => pc.setRemoteDescription(pranswer));
+        .then(() => pc.setRemoteDescription(pranswer))
+        .then(() => {
+          assert_array_equals(states, ['have-local-offer', 'have-remote-pranswer']);
+        });
       }));
   }, 'setRemoteDescription(pranswer) multiple times should succeed');
 
   promise_test(t => {
     const pc = new RTCPeerConnection();
-    test_state_change_event(t, pc, ['have-local-offer', 'have-remote-pranswer', 'stable']);
+    t.add_cleanup(() => pc.close());
+
+    const states = [];
+    pc.addEventListener('signalingstatechange', () => states.push(pc.signalingState));
 
     return pc.createOffer({ offerToReceiveVideo: true })
     .then(offer =>
         .then(() => {
           assert_equals(pc.signalingState, 'stable');
 
-          assert_session_desc_equals(pc.localDescription, offer);
-          assert_session_desc_equals(pc.currentLocalDescription, offer);
+          assert_session_desc_similar(pc.localDescription, offer);
+          assert_session_desc_similar(pc.currentLocalDescription, offer);
           assert_equals(pc.pendingLocalDescription, null);
 
-          assert_session_desc_equals(pc.remoteDescription, answer);
-          assert_session_desc_equals(pc.currentRemoteDescription, answer);
+          assert_session_desc_similar(pc.remoteDescription, answer);
+          assert_session_desc_similar(pc.currentRemoteDescription, answer);
           assert_equals(pc.pendingRemoteDescription, null);
+
+          assert_array_equals(states, ['have-local-offer', 'have-remote-pranswer', 'stable']);
         });
       }));
   }, 'setRemoteDescription(answer) from have-remote-pranswer state should succeed');
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-replaceTrack.https-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-replaceTrack.https-expected.txt
new file mode 100644 (file)
index 0000000..ef9636c
--- /dev/null
@@ -0,0 +1,10 @@
+
+Harness Error (TIMEOUT), message = null
+
+PASS replaceTrack() sets the track attribute to a new track. 
+PASS replaceTrack() sets the track attribute to null. 
+PASS replaceTrack() does not set the track synchronously. 
+PASS replaceTrack() rejects when the peer connection is closed. 
+FAIL replaceTrack() rejects when invoked after removeTrack(). assert_equals: expected "InvalidModificationError" but got "InvalidStateError"
+TIMEOUT replaceTrack() rejects after a subsequent removeTrack(). Test timed out
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-replaceTrack.https.html b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-replaceTrack.https.html
new file mode 100644 (file)
index 0000000..6c98eae
--- /dev/null
@@ -0,0 +1,145 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCPeerConnection.prototype.setRemoteDescription - replaceTrack</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:
+  //   getUserMediaTracksAndStreams
+
+  async_test(t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    return getUserMediaTracksAndStreams(2)
+    .then(t.step_func(([tracks, streams]) => {
+      const sender = caller.addTrack(tracks[0], streams[0]);
+      return sender.replaceTrack(tracks[1])
+      .then(t.step_func(() => {
+        assert_equals(sender.track, tracks[1]);
+        t.done();
+      }));
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'replaceTrack() sets the track attribute to a new track.');
+
+  async_test(t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    return getUserMediaTracksAndStreams(1)
+    .then(t.step_func(([tracks, streams]) => {
+      const sender = caller.addTrack(tracks[0], streams[0]);
+      return sender.replaceTrack(null)
+      .then(t.step_func(() => {
+        assert_equals(sender.track, null);
+        t.done();
+      }));
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'replaceTrack() sets the track attribute to null.');
+
+  async_test(t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    return getUserMediaTracksAndStreams(2)
+    .then(t.step_func(([tracks, streams]) => {
+      const sender = caller.addTrack(tracks[0], streams[0]);
+      assert_equals(sender.track, tracks[0]);
+      sender.replaceTrack(tracks[1]);
+      // replaceTrack() is asynchronous, there should be no synchronously
+      // observable effects.
+      assert_equals(sender.track, tracks[0]);
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'replaceTrack() does not set the track synchronously.');
+
+  async_test(t => {
+    const expectedException = 'InvalidStateError';
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    return getUserMediaTracksAndStreams(2)
+    .then(t.step_func(([tracks, streams]) => {
+      const sender = caller.addTrack(tracks[0], streams[0]);
+      caller.close();
+      return sender.replaceTrack(tracks[1])
+      .then(t.step_func(() => {
+        assert_unreached('Expected replaceTrack() to be rejected with ' +
+                         expectedException + ' but the promise was resolved.');
+      }),
+      t.step_func(e => {
+        assert_equals(e.name, expectedException);
+        t.done();
+      }));
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'replaceTrack() rejects when the peer connection is closed.');
+
+  async_test(t => {
+    const expectedException = 'InvalidModificationError';
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    return getUserMediaTracksAndStreams(2)
+    .then(t.step_func(([tracks, streams]) => {
+      const sender = caller.addTrack(tracks[0], streams[0]);
+      caller.removeTrack(sender);
+      // replaceTrack() should fail because the sender should be inactive after
+      // removeTrack().
+      return sender.replaceTrack(tracks[1])
+      .then(t.step_func(() => {
+        assert_unreached('Expected replaceTrack() to be rejected with ' +
+                         expectedException + ' but the promise was resolved.');
+      }),
+      t.step_func(e => {
+        assert_equals(e.name, expectedException);
+        t.done();
+      }));
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'replaceTrack() rejects when invoked after removeTrack().');
+
+  async_test(t => {
+    const expectedException = 'InvalidModificationError';
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    return getUserMediaTracksAndStreams(2)
+    .then(t.step_func(([tracks, streams]) => {
+      const sender = caller.addTrack(tracks[0], streams[0]);
+      let p = sender.replaceTrack(tracks[1])
+      caller.removeTrack(sender);
+      // replaceTrack() should fail because it executes steps in parallel and
+      // queues a task to execute after removeTrack() has occurred. The sender
+      // should be inactive. If this can be racy, update or remove the test.
+      // https://github.com/w3c/webrtc-pc/issues/1728
+      return p.then(t.step_func(() => {
+        assert_unreached('Expected replaceTrack() to be rejected with ' +
+                         expectedException + ' but the promise was resolved.');
+      }),
+      t.step_func(e => {
+        assert_equals(e.name, expectedException);
+        t.done();
+      }));
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'replaceTrack() rejects after a subsequent removeTrack().');
+
+  // TODO(hbos): Verify that replaceTrack() changes what media is received on
+  // the remote end of two connected peer connections. For video tracks, this
+  // requires Chromium's video tag to update on receiving frames when running
+  // content_shell. https://crbug.com/793808
+
+</script>
index 050a9ee..3887f38 100644 (file)
@@ -1,8 +1,5 @@
 
-Harness Error (TIMEOUT), message = null
-
 FAIL setRemoteDescription(rollback) in have-remote-offer state should revert to stable state undefined is not an object (evaluating 'pc.createDataChannel')
 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"
-TIMEOUT Test onsignalingstatechange event for setRemoteDescription(rollback) in have-remote-offer state should revert to stable state Test timed out
 
index 931a25f..01969dc 100644 (file)
@@ -12,8 +12,7 @@
 
   // The following helper functions are called from RTCPeerConnection-helper.js:
   //   generateOffer
-  //   assert_session_desc_equals
-  //   test_state_change_event
+  //   assert_session_desc_similar
 
   /*
     4.3.2.  Interface Definition
    */
   promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
 
-    test_state_change_event(t, pc, ['have-remote-offer', 'stable']);
+    const states = [];
+    pc.addEventListener('signalingstatechange', () => states.push(pc.signalingState));
 
     return generateOffer({ data: true })
     .then(offer => pc.setRemoteDescription(offer))
@@ -77,6 +78,8 @@
       assert_equals(pc.remoteDescription, null);
       assert_equals(pc.pendingRemoteDescription, null);
       assert_equals(pc.currentRemoteDescription, null);
+
+      assert_array_equals(states, ['have-remote-offer', 'stable']);
     });
   }, 'setRemoteDescription(rollback) in have-remote-offer state should revert to stable state');
 
    */
   promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     return promise_rejects(t, 'InvalidStateError',
       pc.setRemoteDescription({ type: 'rollback' }));
   }, `setRemoteDescription(rollback) from stable state should reject with InvalidStateError`);
 
   promise_test(t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
     return pc.createOffer({ offerToReceiveAudio: true })
     .then(offer => pc.setRemoteDescription(offer))
     .then(() => pc.setRemoteDescription({
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-tracks.https-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-tracks.https-expected.txt
new file mode 100644 (file)
index 0000000..08fce5f
--- /dev/null
@@ -0,0 +1,18 @@
+
+Harness Error (TIMEOUT), message = null
+
+PASS addTrack() with a track and no stream makes ontrack fire with a track and no stream. 
+PASS addTrack() with a track and a stream makes ontrack fire with a track and a stream. 
+PASS ontrack fires before setRemoteDescription resolves. 
+FAIL addTrack() with two tracks and one stream makes ontrack fire twice with the tracks and shared stream. assert_array_equals: The remote stream == [first track, second track]. property 0, expected object "[object MediaStreamTrack]" but got object "[object MediaStreamTrack]"
+PASS addTrack() for an existing stream makes stream.onaddtrack fire. 
+PASS stream.onaddtrack fires before setRemoteDescription resolves. 
+PASS addTrack() with a track and two streams makes ontrack fire with a track and two streams. 
+PASS ontrack's receiver matches getReceivers(). 
+PASS removeTrack() does not remove the receiver. 
+TIMEOUT removeTrack() makes stream.onremovetrack fire and the track to be removed from the stream. Test timed out
+NOTRUN stream.onremovetrack fires before setRemoteDescription resolves. 
+NOTRUN removeTrack() makes track.onmute fire and the track to be muted. 
+NOTRUN track.onmute fires before setRemoteDescription resolves. 
+NOTRUN removeTrack() twice is safe. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-tracks.https.html b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setRemoteDescription-tracks.https.html
new file mode 100644 (file)
index 0000000..7179f1e
--- /dev/null
@@ -0,0 +1,373 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCPeerConnection.prototype.setRemoteDescription - add/remove remote tracks</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:
+  //   addEventListenerPromise
+  //   exchangeOffer
+  //   exchangeOfferAnswer
+  //   Resolver
+
+  // These tests are concerned with the observable consequences of processing
+  // the addition or removal of remote tracks, including events firing and the
+  // states of RTCPeerConnection, MediaStream and MediaStreamTrack.
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+    const localStream =
+        await getNoiseStream({audio: true});
+    t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop()));
+    caller.addTrack(localStream.getTracks()[0]);
+    const ontrackPromise = addEventListenerPromise(t, callee, 'track', e => {
+      assert_equals(e.track.id, localStream.getTracks()[0].id,
+                    'Local and remote track IDs match.');
+      assert_equals(e.streams.length, 0, 'No remote stream created.');
+    });
+    await exchangeOffer(caller, callee);
+    await ontrackPromise;
+  }, 'addTrack() with a track and no stream makes ontrack fire with a track and no stream.');
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+    const localStream =
+        await getNoiseStream({audio: true});
+    t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop()));
+    caller.addTrack(localStream.getTracks()[0], localStream);
+    const ontrackPromise = addEventListenerPromise(t, callee, 'track', e => {
+      assert_equals(e.track.id, localStream.getTracks()[0].id,
+                    'Local and remote track IDs match.');
+      assert_equals(e.streams.length, 1, 'Created a single remote stream.');
+      assert_equals(e.streams[0].id, localStream.id,
+                    'Local and remote stream IDs match.');
+      assert_array_equals(e.streams[0].getTracks(), [e.track],
+                          'The remote stream contains the remote track.');
+    });
+    await exchangeOffer(caller, callee);
+    await ontrackPromise;
+  }, 'addTrack() with a track and a stream makes ontrack fire with a track and a stream.');
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+    let eventSequence = '';
+    const localStream =
+        await getNoiseStream({audio: true});
+    t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop()));
+    caller.addTrack(localStream.getTracks()[0], localStream);
+    const ontrackPromise = addEventListenerPromise(t, callee, 'track', e => {
+      eventSequence += 'ontrack;';
+    });
+    await exchangeOffer(caller, callee);
+    eventSequence += 'setRemoteDescription;';
+    await ontrackPromise;
+    assert_equals(eventSequence, 'ontrack;setRemoteDescription;');
+  }, 'ontrack fires before setRemoteDescription resolves.');
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+    const localStreams = await Promise.all([
+      getNoiseStream({audio: true}),
+      getNoiseStream({audio: true}),
+    ]);
+    t.add_cleanup(() => localStreams.forEach((stream) =>
+      stream.getTracks().forEach((track) => track.stop())));
+    caller.addTrack(localStreams[0].getTracks()[0], localStreams[0]);
+    caller.addTrack(localStreams[1].getTracks()[0], localStreams[0]);
+    let ontrackEventsFired = 0;
+    const ontrackEventResolvers = [ new Resolver(), new Resolver() ];
+    callee.ontrack = t.step_func(e => {
+      ontrackEventResolvers[ontrackEventsFired++].resolve(e);
+    });
+    await exchangeOffer(caller, callee);
+    let firstTrackEvent = await ontrackEventResolvers[0].promise;
+    assert_equals(firstTrackEvent.track.id,
+                  localStreams[0].getTracks()[0].id,
+                  'First ontrack\'s track ID matches first local track.');
+    assert_equals(firstTrackEvent.streams.length, 1,
+                  'First ontrack fires with a single stream.');
+    assert_equals(firstTrackEvent.streams[0].id,
+                  localStreams[0].id,
+                  'First ontrack\'s stream ID matches local stream.');
+    let secondTrackEvent = await ontrackEventResolvers[1].promise;
+    assert_equals(secondTrackEvent.track.id,
+                  localStreams[1].getTracks()[0].id,
+                  'Second ontrack\'s track ID matches second local track.');
+    assert_equals(secondTrackEvent.streams.length, 1,
+                  'Second ontrack fires with a single stream.');
+    assert_equals(secondTrackEvent.streams[0].id,
+                  localStreams[0].id,
+                  'Second ontrack\'s stream ID matches local stream.');
+    assert_array_equals(firstTrackEvent.streams, secondTrackEvent.streams,
+                        'ontrack was fired with the same streams both times.');
+    assert_array_equals(firstTrackEvent.streams[0].getTracks(),
+                        [firstTrackEvent.track, secondTrackEvent.track],
+                        'The remote stream == [first track, second track].');
+    assert_equals(ontrackEventsFired, 2, 'Unexpected number of track events.');
+  }, 'addTrack() with two tracks and one stream makes ontrack fire twice with the tracks and shared stream.');
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+    let eventSequence = '';
+    const localStreams = await Promise.all([
+      getNoiseStream({audio: true}),
+      getNoiseStream({audio: true}),
+    ]);
+    t.add_cleanup(() => localStreams.forEach((stream) =>
+      stream.getTracks().forEach((track) => track.stop())));
+    caller.addTrack(localStreams[0].getTracks()[0], localStreams[0]);
+    const remoteStreams = [];
+    callee.ontrack = e => {
+      if (!remoteStreams.includes(e.streams[0]))
+        remoteStreams.push(e.streams[0]);
+    };
+    await exchangeOfferAnswer(caller, callee);
+    assert_equals(remoteStreams.length, 1, 'One remote stream created.');
+    assert_equals(remoteStreams[0].getTracks()[0].id,
+                  localStreams[0].getTracks()[0].id,
+                  'First local and remote tracks have the same ID.');
+    const onaddtrackPromise =
+        addEventListenerPromise(t, remoteStreams[0], 'addtrack', e => {
+      assert_equals(e.track.id, localStreams[1].getTracks()[0].id,
+                    'Second local and remote tracks have the same ID.');
+    });
+    caller.addTrack(localStreams[1].getTracks()[0], localStreams[0]);
+    await exchangeOffer(caller, callee);
+    await onaddtrackPromise;
+    assert_equals(remoteStreams.length, 1, 'Still a single remote stream.');
+  }, 'addTrack() for an existing stream makes stream.onaddtrack fire.');
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+    let eventSequence = '';
+    const localStreams = await Promise.all([
+      getNoiseStream({audio: true}),
+      getNoiseStream({audio: true}),
+    ]);
+    t.add_cleanup(() => localStreams.forEach((stream) =>
+      stream.getTracks().forEach((track) => track.stop())));
+    caller.addTrack(localStreams[0].getTracks()[0], localStreams[0]);
+    const remoteStreams = [];
+    callee.ontrack = e => {
+      if (!remoteStreams.includes(e.streams[0]))
+        remoteStreams.push(e.streams[0]);
+    };
+    await exchangeOfferAnswer(caller, callee);
+    assert_equals(remoteStreams.length, 1, 'One remote stream created.');
+    const onaddtrackPromise =
+        addEventListenerPromise(t, remoteStreams[0], 'addtrack', e => {
+      eventSequence += 'stream.onaddtrack;';
+    });
+    caller.addTrack(localStreams[1].getTracks()[0], localStreams[0]);
+    await exchangeOffer(caller, callee);
+    eventSequence += 'setRemoteDescription;';
+    await onaddtrackPromise;
+    assert_equals(remoteStreams.length, 1, 'Still a single remote stream.');
+    assert_equals(eventSequence, 'stream.onaddtrack;setRemoteDescription;');
+  }, 'stream.onaddtrack fires before setRemoteDescription resolves.');
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+    const localStreams = await Promise.all([
+      getNoiseStream({audio: true}),
+      getNoiseStream({audio: true}),
+    ]);
+    t.add_cleanup(() => localStreams.forEach((stream) =>
+      stream.getTracks().forEach((track) => track.stop())));
+    caller.addTrack(localStreams[0].getTracks()[0],
+                    localStreams[0], localStreams[1]);
+    const ontrackPromise = addEventListenerPromise(t, callee, 'track', e => {
+      assert_equals(e.track.id, localStreams[0].getTracks()[0].id,
+                    'Local and remote track IDs match.');
+      assert_equals(e.streams.length, 2, 'Two remote stream created.');
+      assert_array_equals(e.streams[0].getTracks(), [e.track],
+                          'First remote stream == [remote track].');
+      assert_array_equals(e.streams[1].getTracks(), [e.track],
+                          'Second remote stream == [remote track].');
+      assert_equals(e.streams[0].id, localStreams[0].id,
+                    'First local and remote stream IDs match.');
+      assert_equals(e.streams[1].id, localStreams[1].id,
+                    'Second local and remote stream IDs match.');
+    });
+    await exchangeOffer(caller, callee);
+    await ontrackPromise;
+  }, 'addTrack() with a track and two streams makes ontrack fire with a track and two streams.');
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+    const localStream =
+        await getNoiseStream({audio: true});
+    t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop()));
+    caller.addTrack(localStream.getTracks()[0]);
+    const ontrackPromise = addEventListenerPromise(t, callee, 'track', e => {
+      assert_array_equals(callee.getReceivers(), [e.receiver],
+                          'getReceivers() == [e.receiver].');
+    });
+    await exchangeOffer(caller, callee);
+    await ontrackPromise;
+  }, 'ontrack\'s receiver matches getReceivers().');
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+    const localStream =
+        await getNoiseStream({audio: true});
+    t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop()));
+    const sender = caller.addTrack(localStream.getTracks()[0]);
+    const ontrackPromise = addEventListenerPromise(t, callee, 'track');
+    await exchangeOfferAnswer(caller, callee);
+    await ontrackPromise;
+    assert_equals(callee.getReceivers().length, 1, 'One receiver created.');
+    caller.removeTrack(sender);
+    await exchangeOffer(caller, callee);
+    assert_equals(callee.getReceivers().length, 1, 'Receiver not removed.');
+  }, 'removeTrack() does not remove the receiver.');
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+    const localStream =
+        await getNoiseStream({audio: true});
+    t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop()));
+    const [track] = localStream.getTracks();
+    const sender = caller.addTrack(track, localStream);
+    const ontrackPromise = addEventListenerPromise(t, callee, 'track', e => {
+      assert_equals(e.streams.length, 1);
+      return e.streams[0];
+    });
+    await exchangeOfferAnswer(caller, callee);
+    const remoteStream = await ontrackPromise;
+    const remoteTrack = remoteStream.getTracks()[0];
+    const onremovetrackPromise =
+        addEventListenerPromise(t, remoteStream, 'removetrack', e => {
+      assert_equals(e.track, remoteTrack);
+      assert_equals(remoteStream.getTracks().length, 0,
+                    'Remote stream emptied of tracks.');
+    });
+    caller.removeTrack(sender);
+    await exchangeOffer(caller, callee);
+    await onremovetrackPromise;
+  }, 'removeTrack() makes stream.onremovetrack fire and the track to be removed from the stream.');
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+    let eventSequence = '';
+    const localStream =
+        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];
+    });
+    await exchangeOfferAnswer(caller, callee);
+    const remoteStream = await ontrackPromise;
+    const remoteTrack = remoteStream.getTracks()[0];
+    const onremovetrackPromise =
+        addEventListenerPromise(t, remoteStream, 'removetrack', e => {
+      eventSequence += 'stream.onremovetrack;';
+    });
+    caller.removeTrack(sender);
+    await exchangeOffer(caller, callee);
+    eventSequence += 'setRemoteDescription;';
+    await onremovetrackPromise;
+    assert_equals(eventSequence, 'stream.onremovetrack;setRemoteDescription;');
+  }, 'stream.onremovetrack fires before setRemoteDescription resolves.');
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+    const localStream =
+        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];
+    });
+    await exchangeOfferAnswer(caller, callee);
+    const remoteStream = await ontrackPromise;
+    const remoteTrack = remoteStream.getTracks()[0];
+    const onmutePromise =
+        addEventListenerPromise(t, remoteTrack, 'mute', () => {
+      assert_true(remoteTrack.muted);
+    });
+    caller.removeTrack(sender);
+    await exchangeOffer(caller, callee);
+    await onmutePromise;
+  }, 'removeTrack() makes track.onmute fire and the track to be muted.');
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+    let eventSequence = '';
+    const localStream =
+        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];
+    });
+    await exchangeOfferAnswer(caller, callee);
+    const remoteStream = await ontrackPromise;
+    const remoteTrack = remoteStream.getTracks()[0];
+    const onmutePromise =
+        addEventListenerPromise(t, remoteTrack, 'mute', () => {
+      eventSequence += 'track.onmute;';
+    });
+    caller.removeTrack(sender);
+    await exchangeOffer(caller, callee);
+    eventSequence += 'setRemoteDescription;';
+    await onmutePromise;
+    assert_equals(eventSequence, 'track.onmute;setRemoteDescription;');
+  }, 'track.onmute fires before setRemoteDescription resolves.');
+
+  promise_test(async t => {
+    const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
+    const stream = await getNoiseStream({audio: true});
+    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+    const sender = pc.addTrack(stream.getTracks()[0]);
+    pc.removeTrack(sender);
+    pc.removeTrack(sender);
+  }, 'removeTrack() twice is safe.');
+</script>
index 2efd301..ad9cdff 100644 (file)
   // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
 
   // The following helper functions are called from RTCPeerConnection-helper.js:
-  //   generateOffer()
-  //   generateAnswer()
-  //   assert_session_desc_not_equals()
-  //   assert_session_desc_equals()
-  //   test_state_change_event()
+  //   assert_session_desc_not_similar()
+  //   assert_session_desc_similar()
 
   /*
     4.3.2.  Interface Definition
   /*
     4.6.1.  enum RTCSdpType
    */
-  promise_test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
 
     // SDP is validated after WebIDL validation
-    return promise_rejects(t, new TypeError(),
-      pc.setRemoteDescription({
-        type: 'bogus',
-        sdp: 'bogus'
-      }));
+    try {
+      await pc.setRemoteDescription({ type: 'bogus', sdp: 'bogus' });
+      t.unreached_func("Should have rejected.");
+    } catch (e) {
+      assert_throws(new TypeError(), () => { throw e });
+    }
   }, 'setRemoteDescription with invalid type and invalid SDP should reject with TypeError');
 
-  promise_test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
 
     // SDP is validated after validating type
-    return promise_rejects(t, 'InvalidStateError',
-      pc.setRemoteDescription({
-        type: 'answer',
-        sdp: 'invalid'
-      }));
+    try {
+      await pc.setRemoteDescription({ type: 'answer', sdp: 'invalid' });
+      t.unreached_func("Should have rejected.");
+    } catch (e) {
+      assert_throws('InvalidStateError', () => { throw e });
+    }
   }, 'setRemoteDescription() with invalid SDP and stable state should reject with InvalidStateError');
 
-  /* Operations after returning to stable state */
+  /* Dedicated signalingstate events test. */
 
-  promise_test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
     const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
+    t.add_cleanup(() => pc2.close());
+
+    let eventCount = 0;
+    const states = [
+      'stable', 'have-local-offer', 'stable', 'have-remote-offer',
+    ];
+    pc.onsignalingstatechange = t.step_func(() =>
+        assert_equals(pc.signalingState, states[++eventCount]));
+
+    const assert_state = state => {
+      assert_equals(state, pc.signalingState);
+      assert_equals(state, states[eventCount]);
+    };
+
+    const offer = await pc.createOffer({ offerToReceiveAudio: true });
+    assert_state('stable');
+    await pc.setLocalDescription(offer);
+    assert_state('have-local-offer');
+    await pc2.setRemoteDescription(offer);
+    await pc2.setLocalDescription(await pc2.createAnswer());
+    await pc.setRemoteDescription(pc2.localDescription);
+    assert_state('stable');
+    await pc.setRemoteDescription(await pc2.createOffer());
+    assert_state('have-remote-offer');
+  }, 'Negotiation should fire signalingsstate events');
 
-    test_state_change_event(t, pc,
-      ['have-remote-offer', 'stable', 'have-remote-offer']);
-
-    return pc2.createOffer({ offerToReceiveAudio: true })
-    .then(offer1 =>
-      pc.setRemoteDescription(offer1)
-      .then(() => pc.createAnswer())
-      .then(answer => pc.setLocalDescription(answer))
-      .then(() => pc2.createOffer({ offerToReceiveVideo: true }))
-      .then(offer2 => {
-        return pc.setRemoteDescription(offer2)
-        .then(() => {
-          assert_equals(pc.signalingState, 'have-remote-offer');
-          assert_session_desc_not_equals(offer1, offer2);
-          assert_session_desc_equals(pc.remoteDescription, offer2);
-          assert_session_desc_equals(pc.currentRemoteDescription, offer1);
-          assert_session_desc_equals(pc.pendingRemoteDescription, offer2);
-        });
-      }));
-  }, 'Calling setRemoteDescription() again after one round of remote-offer/local-answer should succeed');
+  /* Operations after returning to stable state */
 
-  promise_test(t => {
+  promise_test(async t => {
     const pc = new RTCPeerConnection();
+    const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
+    t.add_cleanup(() => pc2.close());
+
+    const offer1 = await pc2.createOffer({ offerToReceiveAudio: true });
+    await pc.setRemoteDescription(offer1);
+    await pc.setLocalDescription(await pc.createAnswer());
+    const offer2 = await pc2.createOffer({ offerToReceiveVideo: true });
+    await pc.setRemoteDescription(offer2);
+    assert_session_desc_not_similar(offer1, offer2);
+    assert_session_desc_similar(pc.remoteDescription, offer2);
+    assert_session_desc_similar(pc.currentRemoteDescription, offer1);
+    assert_session_desc_similar(pc.pendingRemoteDescription, offer2);
+  }, 'Calling setRemoteDescription() again after one round of remote-offer/local-answer should succeed');
 
-    test_state_change_event(t, pc,
-       ['have-local-offer', 'stable', 'have-remote-offer']);
-
-    return pc.createOffer({ offerToReceiveAudio: true })
-    .then(offer =>
-      pc.setLocalDescription(offer)
-      .then(() => generateAnswer(offer)))
-    .then(answer =>
-      pc.setRemoteDescription(answer)
-      .then(() => generateOffer({ pc, data: true }))
-      .then(offer =>
-        pc.setRemoteDescription(offer)
-        .then(() => {
-          assert_equals(pc.signalingState, 'have-remote-offer');
-          assert_session_desc_equals(pc.remoteDescription, offer);
-          assert_session_desc_equals(pc.currentRemoteDescription, answer);
-          assert_session_desc_equals(pc.pendingRemoteDescription, offer);
-        })));
+  promise_test(async t => {
+    const pc = new RTCPeerConnection();
+    const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
+    t.add_cleanup(() => pc2.close());
+
+    const offer = await pc.createOffer({ offerToReceiveAudio: true });
+    await pc.setLocalDescription(offer);
+    await pc2.setRemoteDescription(offer);
+    const answer = await pc2.createAnswer();
+    await pc2.setLocalDescription(answer);
+    await pc.setRemoteDescription(answer);
+    await pc.setRemoteDescription(await pc2.createOffer());
+    assert_equals(pc.remoteDescription.sdp, pc.pendingRemoteDescription.sdp);
+    assert_session_desc_similar(pc.remoteDescription, offer);
+    assert_session_desc_similar(pc.currentRemoteDescription, answer);
   }, 'Switching role from offerer to answerer after going back to stable state should succeed');
 
   /*
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-track-stats.https-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-track-stats.https-expected.txt
new file mode 100644 (file)
index 0000000..2aebc80
--- /dev/null
@@ -0,0 +1,20 @@
+
+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)
+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
+FAIL replaceTrack() after offer, before answer: new track attachment stats present assert_true: Has stats for replaced track expected true got false
+FAIL replaceTrack() after answer: new track attachment stats present assert_true: Has stats for replaced track expected true got false
+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')"
+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
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-track-stats.https.html b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-track-stats.https.html
new file mode 100644 (file)
index 0000000..682e7e5
--- /dev/null
@@ -0,0 +1,656 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCPeerConnection.prototype.getStats</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script src="dictionary-helper.js"></script>
+<script src="RTCStats-helper.js"></script>
+<script>
+  'use strict';
+
+  // The following helper functions are called from RTCPeerConnection-helper.js:
+  //   doSignalingHandshake
+  //   getUserMediaTracksAndStreams
+
+  // The following helper functions are called from RTCStats-helper.js
+  // (depends on dictionary-helper.js):
+  //   validateRtcStats
+
+  async_test(t => {
+    const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
+    let track;
+    return getUserMediaTracksAndStreams(1)
+    .then(t.step_func(([tracks, streams]) => {
+      track = tracks[0];
+      pc.addTrack(track);
+      return pc.getStats();
+    }))
+    .then(t.step_func(report => {
+      let trackStats = findStatsByTypeAndId(report, 'track', track.id);
+      assert_true(trackStats != null, 'Has stats for track');
+      // TODO(hbos): Here and elsewhere, validateRtcStats() only tests id,
+      // timestamp and type is correct type. Should validate based on stats type
+      // but it expects both audio and video members.
+      // https://github.com/web-platform-tests/wpt/issues/9010
+      validateRtcStats(report, trackStats);
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'addTrack() without setLocalDescription() yields track stats');
+
+  async_test(t => {
+    const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
+    let stream;
+    return getUserMediaTracksAndStreams(1)
+    .then(t.step_func(([tracks, streams]) => {
+      let track = tracks[0];
+      stream = streams[0];
+      pc.addTrack(track, stream);
+      return pc.getStats();
+    }))
+    .then(t.step_func(report => {
+      let streamStats = findStatsByTypeAndId(report, 'stream', stream.id);
+      assert_true(streamStats != null, 'Has stats for stream');
+      validateRtcStats(report, streamStats);
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'addTrack() without setLocalDescription() yields media stream stats');
+
+  async_test(t => {
+    const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
+    let track;
+    return getUserMediaTracksAndStreams(1)
+    .then(t.step_func(([tracks, streams]) => {
+      track = tracks[0];
+      pc.addTrack(track);
+      return pc.createOffer();
+    }))
+    .then(t.step_func(offer => {
+      return pc.setLocalDescription(offer);
+    }))
+    .then(t.step_func(() => {
+      return pc.getStats();
+    }))
+    .then(t.step_func(report => {
+      let trackStats = findStatsByTypeAndId(report, 'track', track.id);
+      assert_true(trackStats != null, 'Has stats for track');
+      validateRtcStats(report, trackStats);
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'addTrack() with setLocalDescription() yields track stats');
+
+  async_test(t => {
+    const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
+    let stream;
+    return getUserMediaTracksAndStreams(1)
+    .then(t.step_func(([tracks, streams]) => {
+      let track = tracks[0];
+      stream = streams[0];
+      pc.addTrack(track, stream);
+      return pc.createOffer();
+    }))
+    .then(t.step_func(offer => {
+      return pc.setLocalDescription(offer);
+    }))
+    .then(t.step_func(() => {
+      return pc.getStats();
+    }))
+    .then(t.step_func(report => {
+      let streamStats = findStatsByTypeAndId(report, 'stream', stream.id);
+      assert_true(streamStats != null, 'Has stats for stream');
+      validateRtcStats(report, streamStats);
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'addTrack() with setLocalDescription() yields media stream stats');
+
+  async_test(t => {
+    const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
+    let track;
+    let stream;
+    return getUserMediaTracksAndStreams(1)
+    .then(t.step_func(([tracks, streams]) => {
+      track = tracks[0];
+      stream = streams[0];
+      pc.addTrack(track, stream);
+      return pc.createOffer();
+    }))
+    .then(t.step_func(offer => {
+      return pc.setLocalDescription(offer);
+    }))
+    .then(t.step_func(() => {
+      return pc.getStats();
+    }))
+    .then(t.step_func(report => {
+      let trackStats = findStatsByTypeAndId(report, 'track', track.id);
+      let streamStats = findStatsByTypeAndId(report, 'stream', stream.id);
+      assert_true(trackStats != null && streamStats != null,
+                  'Has stats for track and stream');
+      assert_array_equals(streamStats.trackIds, [ trackStats.id ],
+                          'streamStats.trackIds == [ trackStats.id ]');
+      validateRtcStats(report, trackStats);
+      validateRtcStats(report, streamStats);
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, '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 track;
+    let stream;
+    return getUserMediaTracksAndStreams(1)
+    .then(t.step_func(([tracks, streams]) => {
+      track = tracks[0];
+      stream = streams[0];
+      stream.addTrack(track);
+      pc.addStream(stream);
+      return pc.createOffer();
+    }))
+    .then(t.step_func(offer => {
+      return pc.setLocalDescription(offer);
+    }))
+    .then(t.step_func(() => {
+      return pc.getStats();
+    }))
+    .then(t.step_func(report => {
+      let trackStats = findStatsByTypeAndId(report, 'track', track.id);
+      let streamStats = findStatsByTypeAndId(report, 'stream', stream.id);
+      assert_true(trackStats != null && streamStats != null,
+                  'Has stats for track and stream');
+      assert_array_equals(streamStats.trackIds, [ trackStats.id ],
+                          'streamStats.trackIds == [ trackStats.id ]');
+      validateRtcStats(report, trackStats);
+      validateRtcStats(report, streamStats);
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'Legacy addStream(): Media stream stats references track stats');
+
+  async_test(t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+    let sendingTrack;
+    return getUserMediaTracksAndStreams(1)
+    .then(t.step_func(([tracks, streams]) => {
+      sendingTrack = tracks[0];
+      caller.addTrack(sendingTrack);
+      return doSignalingHandshake(caller, callee);
+    }))
+    .then(t.step_func(() => {
+      return caller.getStats();
+    }))
+    .then(t.step_func(report => {
+      let trackStats = findStatsByTypeAndId(report, 'track', sendingTrack.id);
+      assert_true(trackStats != null, 'Has stats for sending track');
+      let outboundStats = findStatsByTypeAndMember(report, 'outbound-rtp',
+                                                   'trackId', trackStats.id);
+      assert_true(outboundStats != null, 'Has stats for outbound RTP stream');
+      validateRtcStats(report, trackStats);
+      validateRtcStats(report, outboundStats);
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'O/A exchange yields outbound RTP stream stats for sending track');
+
+  async_test(t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+    let receivingTrack;
+    callee.ontrack = trackEvent => {
+      assert_true(receivingTrack == undefined, 'ontrack has not fired before');
+      receivingTrack = trackEvent.track;
+    };
+    return getUserMediaTracksAndStreams(1)
+    .then(t.step_func(([tracks, streams]) => {
+      caller.addTrack(tracks[0]);
+      return doSignalingHandshake(caller, callee);
+    }))
+    .then(t.step_func(() => {
+      return callee.getStats();
+    }))
+    .then(t.step_func(report => {
+      assert_true(receivingTrack != null, 'Has a receiving track');
+      let trackStats = findStatsByTypeAndId(report, 'track', receivingTrack.id);
+      assert_true(trackStats != null, 'Has stats for receiving track');
+      let inboundStats = findStatsByTypeAndMember(report, 'inbound-rtp',
+                                                  'trackId', trackStats.id);
+      assert_true(inboundStats != null, 'Has stats for outbound RTP stream');
+      validateRtcStats(report, trackStats);
+      validateRtcStats(report, inboundStats);
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'O/A exchange yields inbound RTP stream stats for receiving track');
+
+  async_test(t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+    let sendingTrack1;
+    let sendingTrack2;
+    let sender;
+    return getUserMediaTracksAndStreams(2)
+    .then(t.step_func(([tracks, streams]) => {
+      sendingTrack1 = tracks[0];
+      sendingTrack2 = tracks[1];
+      sender = caller.addTrack(sendingTrack1);
+      return sender.replaceTrack(sendingTrack2);
+    }))
+    .then(t.step_func(() => {
+      return caller.getStats();
+    }))
+    .then(t.step_func(report => {
+      let trackStats = findStatsByTypeAndId(report, 'track', sendingTrack2.id);
+      assert_true(trackStats != null, 'Has stats for replaced track');
+      validateRtcStats(report, trackStats);
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'replaceTrack() before offer: new track attachment stats present');
+
+  async_test(t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+    let sendingTrack1;
+    let sendingTrack2;
+    let sender;
+    return getUserMediaTracksAndStreams(2)
+    .then(t.step_func(([tracks, streams]) => {
+      sendingTrack1 = tracks[0];
+      sendingTrack2 = tracks[1];
+      sender = caller.addTrack(sendingTrack1);
+      return exchangeOffer(caller, callee);
+    }))
+    .then(t.step_func(() => {
+      return sender.replaceTrack(sendingTrack2);
+    }))
+    .then(t.step_func(() => {
+      return caller.getStats();
+    }))
+    .then(t.step_func(report => {
+      let trackStats = findStatsByTypeAndId(report, 'track', sendingTrack2.id);
+      assert_true(trackStats != null, 'Has stats for replaced track');
+      let outboundStats = findStatsByTypeAndMember(report, 'outbound-rtp',
+                                                   'trackId', trackStats.id);
+      assert_true(outboundStats != null, 'Has stats for outbound RTP stream');
+      validateRtcStats(report, trackStats);
+      validateRtcStats(report, outboundStats);
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'replaceTrack() after offer, before answer: new track attachment stats ' +
+     'present');
+
+  async_test(t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+    let sendingTrack1;
+    let sendingTrack2;
+    let sender;
+    return getUserMediaTracksAndStreams(2)
+    .then(t.step_func(([tracks, streams]) => {
+      sendingTrack1 = tracks[0];
+      sendingTrack2 = tracks[1];
+      sender = caller.addTrack(sendingTrack1);
+      return doSignalingHandshake(caller, callee);
+    }))
+    .then(t.step_func(() => {
+      return sender.replaceTrack(sendingTrack2);
+    }))
+    .then(t.step_func(() => {
+      return caller.getStats();
+    }))
+    .then(t.step_func(report => {
+      let trackStats = findStatsByTypeAndId(report, 'track', sendingTrack2.id);
+      assert_true(trackStats != null, 'Has stats for replaced track');
+      let outboundStats = findStatsByTypeAndMember(report, 'outbound-rtp',
+                                                   'trackId', trackStats.id);
+      assert_true(outboundStats != null, 'Has stats for outbound RTP stream');
+      validateRtcStats(report, trackStats);
+      validateRtcStats(report, outboundStats);
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'replaceTrack() after answer: new track attachment stats present');
+
+  async_test(t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+    let sendingTrack1;
+    let sendingTrack2;
+    let sender;
+    return getUserMediaTracksAndStreams(2)
+    .then(t.step_func(([tracks, streams]) => {
+      sendingTrack1 = tracks[0];
+      sendingTrack2 = tracks[1];
+      sender = caller.addTrack(sendingTrack1);
+      return doSignalingHandshake(caller, callee);
+    }))
+    .then(t.step_func(() => {
+      return sender.replaceTrack(sendingTrack2);
+    }))
+    .then(t.step_func(() => {
+      return caller.getStats();
+    }))
+    .then(t.step_func(report => {
+      let trackStats = findStatsByTypeAndId(report, 'track', sendingTrack1.id);
+      assert_true(trackStats != null, 'Has stats for original track');
+      assert_true(trackStats.objectDeleted);
+      let outboundStats = findStatsByTypeAndMember(report, 'outbound-rtp',
+                                                   'trackId', trackStats.id);
+      assert_true(outboundStats == null,
+                  'The outbound RTP stream should no longer reference the ' +
+                  'original attachment');
+      t.done();
+    }))
+    .catch(t.step_func(reason => {
+      assert_unreached(reason);
+    }));
+  }, 'replaceTrack(): original track attachment stats present after replacing');
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+    let [tracks, streams] = await getUserMediaTracksAndStreams(2);
+    let sender = caller.addTrack(tracks[0], streams[0]);
+    callee.addTrack(tracks[1], streams[1]);
+    exchangeIceCandidates(caller, callee);
+    await doSignalingHandshake(caller, callee);
+    await onIceConnectionStateCompleted(caller);
+    let receiver = caller.getReceivers()[0];
+
+    // Obtain inbound and outbound RTP stream stats on a full stats report.
+    let fullReport = await caller.getStats();
+    let outboundTrackStats = findStatsByTypeAndId(
+        fullReport, 'track', sender.track.id);
+    let outboundStats = findStatsByTypeAndMember(
+        fullReport, 'outbound-rtp', 'trackId', outboundTrackStats.id);
+    assert_true(outboundStats != null, 'Has stats for outbound RTP stream');
+    let inboundTrackStats = findStatsByTypeAndId(
+        fullReport, 'track', receiver.track.id);
+    let inboundStats = findStatsByTypeAndMember(
+        fullReport, 'inbound-rtp', 'trackId', inboundTrackStats.id);
+    assert_true(inboundStats != null, 'Has stats for inbound RTP stream');
+
+    // Perform stats selection algorithm with sender selector. The result should
+    // contain the outbound-rtp but not the inbound-rtp.
+    let senderReport = await sender.getStats();
+    assert_true(senderReport.has(outboundStats.id));
+    assert_false(senderReport.has(inboundStats.id));
+
+    // Validate the stats graph, ensuring all stats objects are reachable and
+    // valid from the outbound-rtp stats.
+    validateStatsGraph(senderReport, senderReport.get(outboundStats.id));
+    // Ensure that the stats graph contains some expected dictionaries.
+    assert_equals(findStatsOfType(senderReport, 'track').length, 1,
+        'senderReport should contain track stats');
+    assert_equals(findStatsOfType(senderReport, 'transport').length, 1,
+        'senderReport should contain transport stats');
+    assert_equals(findStatsOfType(senderReport, 'candidate-pair').length, 1,
+        'senderReport should contain candidate-pair stats');
+    assert_equals(findStatsOfType(senderReport, 'local-candidate').length, 1,
+        'senderReport should contain local-candidate stats');
+    assert_equals(findStatsOfType(senderReport, 'remote-candidate').length, 1,
+        'senderReport should contain remote-candidate stats');
+  }, 'RTCRtpSender.getStats() contains only outbound-rtp and related stats');
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+    let [tracks, streams] = await getUserMediaTracksAndStreams(2);
+    let sender = caller.addTrack(tracks[0], streams[0]);
+    callee.addTrack(tracks[1], streams[1]);
+    exchangeIceCandidates(caller, callee);
+    await doSignalingHandshake(caller, callee);
+    await onIceConnectionStateCompleted(caller);
+    let receiver = caller.getReceivers()[0];
+
+    // Obtain inbound and outbound RTP stream stats on a full stats report.
+    let fullReport = await caller.getStats();
+    let outboundTrackStats = findStatsByTypeAndId(
+        fullReport, 'track', sender.track.id);
+    let outboundStats = findStatsByTypeAndMember(
+        fullReport, 'outbound-rtp', 'trackId', outboundTrackStats.id);
+    assert_true(outboundStats != null, 'Has stats for outbound RTP stream');
+    let inboundTrackStats = findStatsByTypeAndId(
+        fullReport, 'track', receiver.track.id);
+    let inboundStats = findStatsByTypeAndMember(
+        fullReport, 'inbound-rtp', 'trackId', inboundTrackStats.id);
+    assert_true(inboundStats != null, 'Has stats for inbound RTP stream');
+
+    // Perform stats selection algorithm with receiver selector. The result
+    // should contain the inbound-rtp but not the outbound-rtp.
+    let receiverReport = await receiver.getStats();
+    assert_true(receiverReport.has(inboundStats.id));
+    assert_false(receiverReport.has(outboundStats.id));
+
+    // Validate the stats graph, ensuring all stats objects are reachable and
+    // valid from the outbound-rtp stats.
+    validateStatsGraph(receiverReport, receiverReport.get(inboundStats.id));
+    // Ensure that the stats graph contains some expected dictionaries.
+    assert_equals(findStatsOfType(receiverReport, 'track').length, 1,
+        'receiverReport should contain track stats');
+    assert_equals(findStatsOfType(receiverReport, 'transport').length, 1,
+        'receiverReport should contain transport stats');
+    assert_equals(findStatsOfType(receiverReport, 'candidate-pair').length, 1,
+        'receiverReport should contain candidate-pair stats');
+    assert_equals(findStatsOfType(receiverReport, 'local-candidate').length, 1,
+        'receiverReport should contain local-candidate stats');
+    assert_equals(findStatsOfType(receiverReport, 'remote-candidate').length, 1,
+        'receiverReport should contain remote-candidate stats');
+  }, 'RTCRtpReceiver.getStats() contains only inbound-rtp and related stats');
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+    let [tracks, streams] = await getUserMediaTracksAndStreams(2);
+    let sender = caller.addTrack(tracks[0], streams[0]);
+    callee.addTrack(tracks[1], streams[1]);
+    exchangeIceCandidates(caller, callee);
+    await doSignalingHandshake(caller, callee);
+    await onIceConnectionStateCompleted(caller);
+
+    let senderReport = await sender.getStats();
+    let trackReport = await caller.getStats(sender.track);
+
+    // Verify the same stats objects are returned but don't compare each
+    // individual metric because timestamps and counters could have gone up
+    // between the two getStats() calls.
+    senderReport.forEach(senderReportStat => {
+      assert_true(trackReport.has(senderReportStat.id));
+    });
+    trackReport.forEach(trackReportStat => {
+      assert_true(senderReport.has(trackReportStat.id));
+    });
+  }, 'RTCPeerConnection.getStats(sendingTrack) is the same as ' +
+     'RTCRtpSender.getStats()');
+
+  promise_test(async t => {
+    const caller = new RTCPeerConnection();
+    t.add_cleanup(() => caller.close());
+    const callee = new RTCPeerConnection();
+    t.add_cleanup(() => callee.close());
+    let [tracks, streams] = await getUserMediaTracksAndStreams(2);
+    let sender = caller.addTrack(tracks[0], streams[0]);
+    callee.addTrack(tracks[1], streams[1]);
+    exchangeIceCandidates(caller, callee);
+    await doSignalingHandshake(caller, callee);
+    await onIceConnectionStateCompleted(caller);
+    let receiver = caller.getReceivers()[0];
+
+    let receiverReport = await receiver.getStats();
+    let trackReport = await caller.getStats(receiver.track);
+
+    // Verify the same stats objects are returned but don't compare each
+    // individual metric because timestamps and counters could have gone up
+    // between the two getStats() calls.
+    receiverReport.forEach(receiverReportStat => {
+      assert_true(trackReport.has(receiverReportStat.id));
+    });
+    trackReport.forEach(trackReportStat => {
+      assert_true(receiverReport.has(trackReportStat.id));
+    });
+  }, 'RTCPeerConnection.getStats(receivingTrack) is the same as ' +
+     'RTCRtpReceiver.getStats()');
+
+  promise_test(async t => {
+    const pc = new RTCPeerConnection();
+    t.add_cleanup(() => pc.close());
+    let [tracks, streams] = await getUserMediaTracksAndStreams(1);
+    await promise_rejects(t, 'InvalidAccessError', pc.getStats(tracks[0]));
+  }, 'RTCPeerConnection.getStats(track) throws InvalidAccessError when there ' +
+     'are zero senders or receivers for the track');
+
+  promise_test(async t => {
+    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]);
+    await sender2.replaceTrack(sender1.track);
+    await promise_rejects(t, 'InvalidAccessError', pc.getStats(sender1.track));
+  }, 'RTCPeerConnection.getStats(track) throws InvalidAccessError when there ' +
+     'are multiple senders for the track');
+
+  // Helpers.
+
+  function findStatsByTypeAndId(report, type, identifier) {
+    return findStats(report, stats => {
+      return stats.type == type && stats[type + 'Identifier'] == identifier;
+    });
+  }
+
+  function findStatsByTypeAndMember(report, type, member, value) {
+    return findStats(report, stats => {
+      return stats.type == type && stats[member] == value;
+    });
+  }
+
+  function findStats(report, findFunc) {
+    for (let it = report.values(), n = it.next(); !n.done; n = it.next()) {
+      if (findFunc(n.value))
+        return n.value;
+    }
+    return null;
+  }
+
+  function findStatsOfType(report, type) {
+    let stats = [];
+    for (let it = report.values(), n = it.next(); !n.done; n = it.next()) {
+      if (n.value.type == type)
+        stats.push(n.value);
+    }
+    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) {
+    let visitedIds = new Set();
+    validateStatsGraphRecursively(report, stat.id, visitedIds);
+    assert_equals(visitedIds.size, report.size,
+                  'Entire stats graph should have been explored.')
+  }
+
+  function validateStatsGraphRecursively(report, currentId, visitedIds) {
+    if (visitedIds.has(currentId))
+      return;
+    visitedIds.add(currentId);
+    assert_true(report.has(currentId), 'Broken reference.');
+    let stat = report.get(currentId);
+    validateRtcStats(report, stat);
+    for (let member in stat) {
+      if (member.endsWith('Id')) {
+        validateStatsGraphRecursively(report, stat[member], visitedIds);
+      } else if (member.endsWith('Ids')) {
+        let ids = stat[member];
+        for (let i = 0; i < ids.length; ++i) {
+          validateStatsGraphRecursively(report, ids[i], visitedIds);
+        }
+      }
+    }
+  }
+
+  async function async_assert_throws(exceptionName, promise, description) {
+    try {
+      await promise;
+    } catch (e) {
+      assert_equals(e.name, exceptionName);
+      return;
+    }
+    assert_unreached('No exception was thrown.');
+  }
+
+</script>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-transceivers.https-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-transceivers.https-expected.txt
new file mode 100644 (file)
index 0000000..75d5b73
--- /dev/null
@@ -0,0 +1,44 @@
+
+PASS addTrack: creates a transceiver for the sender 
+PASS addTrack: "transceiver == {sender,receiver}" 
+PASS addTrack: transceiver.sender is associated with the track 
+PASS addTrack: transceiver.receiver has its own track 
+PASS addTrack: transceiver.receiver's track is muted 
+PASS addTrack: transceiver is not associated with an m-section 
+PASS addTrack: transceiver is not stopped 
+PASS addTrack: transceiver's direction is sendrecv 
+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 fires with a transceiver. 
+PASS setRemoteDescription(offer): transceiver.mid is the same on both ends 
+PASS setRemoteDescription(offer): "transceiver == {sender,receiver}" 
+PASS setRemoteDescription(offer): transceiver.direction is recvonly 
+PASS setRemoteDescription(offer): transceiver.currentDirection is null 
+PASS setRemoteDescription(offer): transceiver.stopped is false 
+PASS setLocalDescription(answer): transceiver.currentDirection is recvonly 
+PASS setLocalDescription(answer): transceiver.currentDirection is sendonly 
+PASS addTransceiver(track): creates a transceiver for the track 
+PASS addTransceiver(track): "transceiver == {sender,receiver}" 
+PASS addTransceiver(track, init): initialize direction to inactive 
+FAIL addTransceiver(track, init): initialize sendEncodings[0].active to false assert_false: expected false got true
+PASS addTransceiver(0 streams): ontrack fires with no stream 
+FAIL addTransceiver(1 stream): ontrack fires with corresponding stream assert_equals: trackEvent.streams.length == 1 expected 1 but got 0
+FAIL addTransceiver(2 streams): ontrack fires with corresponding two streams assert_equals: trackEvent.streams.length == 2 expected 2 but got 0
+PASS addTrack(0 streams): ontrack fires with no stream 
+PASS addTrack(1 stream): ontrack fires with corresponding stream 
+PASS addTrack(2 streams): ontrack fires with corresponding two streams 
+PASS addTransceiver('audio'): creates a transceiver with direction sendrecv 
+PASS addTransceiver('audio'): transceiver.receiver.track.kind == 'audio' 
+PASS addTransceiver('video'): transceiver.receiver.track.kind == 'video' 
+PASS addTransceiver('audio'): transceiver.sender.track == null 
+PASS addTransceiver('audio'): transceiver.currentDirection is null 
+PASS addTransceiver('audio'): transceiver.stopped is false 
+PASS addTrack reuses reusable transceivers 
+PASS addTransceiver does not reuse reusable transceivers 
+PASS Can setup two-way call using a single transceiver 
+PASS Closing the PC stops the transceivers 
+FAIL Changing transceiver direction to 'sendrecv' makes ontrack fire promise_test: Unhandled rejection with value: object "TypeError: Attempted to assign to readonly property."
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-transceivers.https.html b/LayoutTests/imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-transceivers.https.html
new file mode 100644 (file)
index 0000000..1f980ea
--- /dev/null
@@ -0,0 +1,467 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCPeerConnection-transceivers.https.html</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:
+//   exchangeOffer
+//   exchangeOfferAndListenToOntrack
+//   exchangeAnswer
+//   exchangeAnswerAndListenToOntrack
+//   addEventListenerPromise
+//   createPeerConnectionWithCleanup
+//   createTrackAndStreamWithCleanup
+//   findTransceiverForSender
+
+promise_test(async t => {
+  const pc = createPeerConnectionWithCleanup(t);
+  const [track, stream] = await createTrackAndStreamWithCleanup(t);
+  const sender = pc.addTrack(track, stream);
+  const transceiver = findTransceiverForSender(pc, sender);
+  assert_true(transceiver instanceof RTCRtpTransceiver);
+  assert_true(transceiver.sender instanceof RTCRtpSender);
+  assert_equals(transceiver.sender, sender);
+}, 'addTrack: creates a transceiver for the sender');
+
+promise_test(async t => {
+  const pc = createPeerConnectionWithCleanup(t);
+  const [track, stream] = await createTrackAndStreamWithCleanup(t);
+  const transceiver = findTransceiverForSender(pc, pc.addTrack(track, stream));
+  assert_array_equals(pc.getTransceivers(), [transceiver],
+                      'pc.getTransceivers() equals [transceiver]');
+  assert_array_equals(pc.getSenders(), [transceiver.sender],
+                      'pc.getSenders() equals [transceiver.sender]');
+  assert_array_equals(pc.getReceivers(), [transceiver.receiver],
+                      'pc.getReceivers() equals [transceiver.receiver]');
+}, 'addTrack: "transceiver == {sender,receiver}"');
+
+promise_test(async t => {
+  const pc = createPeerConnectionWithCleanup(t);
+  const [track, stream] = await createTrackAndStreamWithCleanup(t);
+  const transceiver = findTransceiverForSender(pc, pc.addTrack(track, stream));
+  assert_true(transceiver.sender.track instanceof MediaStreamTrack,
+              'transceiver.sender.track instanceof MediaStreamTrack');
+  assert_equals(transceiver.sender.track, track,
+                'transceiver.sender.track == track');
+}, 'addTrack: transceiver.sender is associated with the track');
+
+promise_test(async t => {
+  const pc = createPeerConnectionWithCleanup(t);
+  const [track, stream] = await createTrackAndStreamWithCleanup(t);
+  const transceiver = findTransceiverForSender(pc, pc.addTrack(track, stream));
+  assert_true(transceiver.receiver instanceof RTCRtpReceiver,
+              'transceiver.receiver instanceof RTCRtpReceiver');
+  assert_true(transceiver.receiver.track instanceof MediaStreamTrack,
+              'transceiver.receiver.track instanceof MediaStreamTrack');
+  assert_not_equals(transceiver.receiver.track, track,
+                    'transceiver.receiver.track != track');
+}, 'addTrack: transceiver.receiver has its own track');
+
+promise_test(async t => {
+  const pc = createPeerConnectionWithCleanup(t);
+  const [track, stream] = await createTrackAndStreamWithCleanup(t);
+  const transceiver = findTransceiverForSender(pc, pc.addTrack(track, stream));
+  assert_true(transceiver.receiver.track.muted);
+}, 'addTrack: transceiver.receiver\'s track is muted');
+
+promise_test(async t => {
+  const pc = createPeerConnectionWithCleanup(t);
+  const [track, stream] = await createTrackAndStreamWithCleanup(t);
+  const transceiver = findTransceiverForSender(pc, pc.addTrack(track, stream));
+  assert_equals(transceiver.mid, null);
+}, 'addTrack: transceiver is not associated with an m-section');
+
+promise_test(async t => {
+  const pc = createPeerConnectionWithCleanup(t);
+  const [track, stream] = await createTrackAndStreamWithCleanup(t);
+  const transceiver = findTransceiverForSender(pc, pc.addTrack(track, stream));
+  assert_false(transceiver.stopped);
+}, 'addTrack: transceiver is not stopped');
+
+promise_test(async t => {
+  const pc = createPeerConnectionWithCleanup(t);
+  const [track, stream] = await createTrackAndStreamWithCleanup(t);
+  const transceiver = findTransceiverForSender(pc, pc.addTrack(track, stream));
+  assert_equals(transceiver.direction, 'sendrecv');
+}, 'addTrack: transceiver\'s direction is sendrecv');
+
+promise_test(async t => {
+  const pc = createPeerConnectionWithCleanup(t);
+  const [track, stream] = await createTrackAndStreamWithCleanup(t);
+  const transceiver = findTransceiverForSender(pc, pc.addTrack(track, stream));
+  assert_equals(transceiver.currentDirection, null);
+}, 'addTrack: transceiver\'s currentDirection is null');
+
+promise_test(async t => {
+  const pc = createPeerConnectionWithCleanup(t);
+  const [track, stream] = await createTrackAndStreamWithCleanup(t);
+  const transceiver = findTransceiverForSender(pc, pc.addTrack(track, stream));
+  await pc.setLocalDescription(await pc.createOffer());
+  assert_not_equals(transceiver.mid, null, 'transceiver.mid != null');
+}, 'setLocalDescription(offer): transceiver gets associated with an m-section');
+
+promise_test(async t => {
+  const pc = createPeerConnectionWithCleanup(t);
+  const [track, stream] = await createTrackAndStreamWithCleanup(t);
+  const transceiver = findTransceiverForSender(pc, pc.addTrack(track, stream));
+  const offer = await pc.createOffer();
+  await pc.setLocalDescription(offer);
+  let sdp = offer.sdp;
+  let sdpMidLineStart = sdp.indexOf('a=mid:');
+  let sdpMidLineEnd = sdp.indexOf('\r\n', sdpMidLineStart);
+  assert_true(sdpMidLineStart != -1 && sdpMidLineEnd != -1,
+              'Failed to parse offer SDP for a=mid');
+  let parsedMid = sdp.substring(sdpMidLineStart + 6, sdpMidLineEnd);
+  assert_equals(transceiver.mid, parsedMid, 'transceiver.mid == parsedMid');
+}, 'setLocalDescription(offer): transceiver.mid matches the offer SDP');
+
+promise_test(async t => {
+  const pc1 = createPeerConnectionWithCleanup(t);
+  pc1.addTrack(... await createTrackAndStreamWithCleanup(t));
+  const pc2 = createPeerConnectionWithCleanup(t);
+  const trackEvent = await exchangeOfferAndListenToOntrack(t, pc1, pc2);
+  assert_true(trackEvent instanceof RTCTrackEvent,
+              'trackEvent instanceof RTCTrackEvent');
+  assert_true(trackEvent.track instanceof MediaStreamTrack,
+              'trackEvent.track instanceof MediaStreamTrack');
+}, 'setRemoteDescription(offer): ontrack fires with a track');
+
+promise_test(async t => {
+  const pc1 = createPeerConnectionWithCleanup(t);
+  const [track, stream] = await createTrackAndStreamWithCleanup(t);
+  pc1.addTrack(track, stream);
+  const pc2 = createPeerConnectionWithCleanup(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');
+
+promise_test(async t => {
+  const pc1 = createPeerConnectionWithCleanup(t);
+  pc1.addTrack(... await createTrackAndStreamWithCleanup(t));
+  const pc2 = createPeerConnectionWithCleanup(t);
+  const trackEvent = await exchangeOfferAndListenToOntrack(t, pc1, pc2);
+  assert_true(trackEvent.transceiver instanceof RTCRtpTransceiver,
+              'trackEvent.transceiver instanceof RTCRtpTransceiver');
+}, 'setRemoteDescription(offer): ontrack fires with a transceiver.');
+
+promise_test(async t => {
+  const pc1 = createPeerConnectionWithCleanup(t);
+  const [track, stream] = await createTrackAndStreamWithCleanup(t);
+  const transceiver = findTransceiverForSender(pc1, pc1.addTrack(track, stream));
+  const pc2 = createPeerConnectionWithCleanup(t);
+  const trackEvent = await exchangeOfferAndListenToOntrack(t, pc1, pc2);
+  assert_equals(transceiver.mid, trackEvent.transceiver.mid);
+}, 'setRemoteDescription(offer): transceiver.mid is the same on both ends');
+
+promise_test(async t => {
+  const pc1 = createPeerConnectionWithCleanup(t);
+  pc1.addTrack(... await createTrackAndStreamWithCleanup(t));
+  const pc2 = createPeerConnectionWithCleanup(t);
+  const trackEvent = await exchangeOfferAndListenToOntrack(t, pc1, pc2);
+  const transceiver = trackEvent.transceiver;
+  assert_array_equals(pc2.getTransceivers(), [transceiver],
+                      'pc2.getTransceivers() equals [transceiver]');
+  assert_array_equals(pc2.getSenders(), [transceiver.sender],
+                      'pc2.getSenders() equals [transceiver.sender]');
+  assert_array_equals(pc2.getReceivers(), [transceiver.receiver],
+                      'pc2.getReceivers() equals [transceiver.receiver]');
+}, 'setRemoteDescription(offer): "transceiver == {sender,receiver}"');
+
+promise_test(async t => {
+  const pc1 = createPeerConnectionWithCleanup(t);
+  pc1.addTrack(... await createTrackAndStreamWithCleanup(t));
+  const pc2 = createPeerConnectionWithCleanup(t);
+  const trackEvent = await exchangeOfferAndListenToOntrack(t, pc1, pc2);
+  assert_equals(trackEvent.transceiver.direction, 'recvonly');
+}, 'setRemoteDescription(offer): transceiver.direction is recvonly');
+
+promise_test(async t => {
+  const pc1 = createPeerConnectionWithCleanup(t);
+  pc1.addTrack(... await createTrackAndStreamWithCleanup(t));
+  const pc2 = createPeerConnectionWithCleanup(t);
+  const trackEvent = await exchangeOfferAndListenToOntrack(t, pc1, pc2);
+  assert_equals(trackEvent.transceiver.currentDirection, null);
+}, 'setRemoteDescription(offer): transceiver.currentDirection is null');
+
+promise_test(async t => {
+  const pc1 = createPeerConnectionWithCleanup(t);
+  pc1.addTrack(... await createTrackAndStreamWithCleanup(t));
+  const pc2 = createPeerConnectionWithCleanup(t);
+  const trackEvent = await exchangeOfferAndListenToOntrack(t, pc1, pc2);
+  assert_false(trackEvent.transceiver.stopped);
+}, 'setRemoteDescription(offer): transceiver.stopped is false');
+
+promise_test(async t => {
+  const pc1 = createPeerConnectionWithCleanup(t);
+  pc1.addTrack(... await createTrackAndStreamWithCleanup(t));
+  const pc2 = createPeerConnectionWithCleanup(t);
+  const trackEvent = await exchangeOfferAndListenToOntrack(t, pc1, pc2);
+  const transceiver = trackEvent.transceiver;
+  assert_equals(transceiver.currentDirection, null,
+                'SRD(offer): transciever.currentDirection is null');
+  await pc2.setLocalDescription(await pc2.createAnswer());
+  assert_equals(transceiver.currentDirection, 'recvonly',
+                'SLD(answer): transciever.currentDirection is recvonly');
+}, 'setLocalDescription(answer): transceiver.currentDirection is recvonly');
+
+promise_test(async t => {
+  const pc1 = createPeerConnectionWithCleanup(t);
+  const [track, stream] = await createTrackAndStreamWithCleanup(t);
+  const transceiver = findTransceiverForSender(pc1, pc1.addTrack(track, stream));
+  const pc2 = createPeerConnectionWithCleanup(t);
+  await exchangeOffer(pc1, pc2);
+  assert_equals(transceiver.currentDirection, null,
+                'SLD(offer): transciever.currentDirection is null');
+  await exchangeAnswer(pc1, pc2);
+  assert_equals(transceiver.currentDirection, 'sendonly',
+                'SRD(answer): transciever.currentDirection is sendonly');
+}, 'setLocalDescription(answer): transceiver.currentDirection is sendonly');
+
+promise_test(async t => {
+  const pc = createPeerConnectionWithCleanup(t);
+  const [track, stream] = await createTrackAndStreamWithCleanup(t);
+  const transceiver = pc.addTransceiver(track);
+  assert_true(transceiver instanceof RTCRtpTransceiver);
+  assert_true(transceiver.sender instanceof RTCRtpSender);
+  assert_true(transceiver.receiver instanceof RTCRtpReceiver);
+  assert_equals(transceiver.sender.track, track);
+}, 'addTransceiver(track): creates a transceiver for the track');
+
+promise_test(async t => {
+  const pc = createPeerConnectionWithCleanup(t);
+  const [track, stream] = await createTrackAndStreamWithCleanup(t);
+  const transceiver = pc.addTransceiver(track);
+  assert_array_equals(pc.getTransceivers(), [transceiver],
+                      'pc.getTransceivers() equals [transceiver]');
+  assert_array_equals(pc.getSenders(), [transceiver.sender],
+                      'pc.getSenders() equals [transceiver.sender]');
+  assert_array_equals(pc.getReceivers(), [transceiver.receiver],
+                      'pc.getReceivers() equals [transceiver.receiver]');
+}, 'addTransceiver(track): "transceiver == {sender,receiver}"');
+
+promise_test(async t => {
+  const pc = createPeerConnectionWithCleanup(t);
+  const [track, stream] = await createTrackAndStreamWithCleanup(t);
+  const transceiver = pc.addTransceiver(track, {direction:'inactive'});
+  assert_equals(transceiver.direction, 'inactive');
+}, 'addTransceiver(track, init): initialize direction to inactive');
+
+promise_test(async t => {
+  const pc = createPeerConnectionWithCleanup(t);
+  const otherPc = createPeerConnectionWithCleanup(t);
+  const [track, stream] = await createTrackAndStreamWithCleanup(t);
+  const transceiver = pc.addTransceiver(track, {
+    sendEncodings: [{active:false}]
+  });
+
+  // Negotiate parameters.
+  const offer = await pc.createOffer();
+  await pc.setLocalDescription(offer);
+  await otherPc.setRemoteDescription(offer);
+  const answer = await otherPc.createAnswer();
+  await otherPc.setLocalDescription(answer);
+  await pc.setRemoteDescription(answer);
+
+  const params = transceiver.sender.getParameters();
+  assert_false(params.encodings[0].active);
+}, 'addTransceiver(track, init): initialize sendEncodings[0].active to false');
+
+promise_test(async t => {
+  const pc1 = createPeerConnectionWithCleanup(t);
+  const pc2 = createPeerConnectionWithCleanup(t);
+  const [track] = await createTrackAndStreamWithCleanup(t);
+  pc1.addTransceiver(track, {streams:[]});
+  const trackEvent = await exchangeOfferAndListenToOntrack(t, pc1, pc2);
+  assert_equals(trackEvent.streams.length, 0, 'trackEvent.streams.length == 0');
+}, 'addTransceiver(0 streams): ontrack fires with no stream');
+
+promise_test(async t => {
+  const pc1 = createPeerConnectionWithCleanup(t);
+  const pc2 = createPeerConnectionWithCleanup(t);
+  const [track] = await createTrackAndStreamWithCleanup(t);
+  const stream = new MediaStream();
+  pc1.addTransceiver(track, {streams:[stream]});
+  const trackEvent = await exchangeOfferAndListenToOntrack(t, pc1, pc2);
+  assert_equals(trackEvent.streams.length, 1, 'trackEvent.streams.length == 1');
+  assert_equals(trackEvent.streams[0].id, stream.id,
+                'trackEvent.streams[0].id == stream.id');
+}, 'addTransceiver(1 stream): ontrack fires with corresponding stream');
+
+promise_test(async t => {
+  const pc1 = createPeerConnectionWithCleanup(t);
+  const pc2 = createPeerConnectionWithCleanup(t);
+  const [track] = await createTrackAndStreamWithCleanup(t);
+  const stream0 = new MediaStream();
+  const stream1 = new MediaStream();
+  pc1.addTransceiver(track, {streams:[stream0, stream1]});
+  const trackEvent = await exchangeOfferAndListenToOntrack(t, pc1, pc2);
+  assert_equals(trackEvent.streams.length, 2, 'trackEvent.streams.length == 2');
+  assert_equals(trackEvent.streams[0].id, stream0.id,
+                'trackEvent.streams[0].id == stream0.id');
+  assert_equals(trackEvent.streams[1].id, stream1.id,
+                'trackEvent.streams[1].id == stream1.id');
+}, 'addTransceiver(2 streams): ontrack fires with corresponding two streams');
+
+promise_test(async t => {
+  const pc1 = createPeerConnectionWithCleanup(t);
+  const pc2 = createPeerConnectionWithCleanup(t);
+  const [track] = await createTrackAndStreamWithCleanup(t);
+  pc1.addTrack(track);
+  const trackEvent = await exchangeOfferAndListenToOntrack(t, pc1, pc2);
+  assert_equals(trackEvent.streams.length, 0, 'trackEvent.streams.length == 0');
+}, 'addTrack(0 streams): ontrack fires with no stream');
+
+promise_test(async t => {
+  const pc1 = createPeerConnectionWithCleanup(t);
+  const pc2 = createPeerConnectionWithCleanup(t);
+  const [track] = await createTrackAndStreamWithCleanup(t);
+  const stream = new MediaStream();
+  pc1.addTrack(track, stream);
+  const trackEvent = await exchangeOfferAndListenToOntrack(t, pc1, pc2);
+  assert_equals(trackEvent.streams.length, 1, 'trackEvent.streams.length == 1');
+  assert_equals(trackEvent.streams[0].id, stream.id,
+                'trackEvent.streams[0].i