diff --git a/webrtc/README.md b/webrtc/README.md new file mode 100644 index 00000000000000..4477e4f375fdc0 --- /dev/null +++ b/webrtc/README.md @@ -0,0 +1,12 @@ +# WebRTC + +This directory contains the WebRTC test suite. + +## Acknowledgements + +Some data channel tests are based on the [data channel conformance test +suite][nplab-webrtc-dc-playground] of the Network Programming Lab of the Münster +University of Applied Sciences. We would like to thank Peter Titz, Felix Weinrank and Timo +Völker for agreeing to contribute their test cases to this repository. + +[nplab-webrtc-dc-playground]: https://github.com/nplab/WebRTC-Data-Channel-Playground/tree/master/conformance-tests diff --git a/webrtc/RTCDataChannel-binaryType.html b/webrtc/RTCDataChannel-binaryType.html new file mode 100644 index 00000000000000..7df090f2a84925 --- /dev/null +++ b/webrtc/RTCDataChannel-binaryType.html @@ -0,0 +1,61 @@ + + +RTCDataChannel.prototype.binaryType + + + + diff --git a/webrtc/RTCDataChannel-bufferedAmount.html b/webrtc/RTCDataChannel-bufferedAmount.html index 2c37cc278f57c2..0226eceb466a85 100644 --- a/webrtc/RTCDataChannel-bufferedAmount.html +++ b/webrtc/RTCDataChannel-bufferedAmount.html @@ -5,165 +5,239 @@ diff --git a/webrtc/RTCDataChannel-bufferedAmountLowThreshold.html b/webrtc/RTCDataChannel-bufferedAmountLowThreshold.html new file mode 100644 index 00000000000000..bb003cd36a5762 --- /dev/null +++ b/webrtc/RTCDataChannel-bufferedAmountLowThreshold.html @@ -0,0 +1,185 @@ + + +RTCDataChannel.bufferedAmountLowThreshold + + + + diff --git a/webrtc/RTCDataChannel-close.html b/webrtc/RTCDataChannel-close.html new file mode 100644 index 00000000000000..9dc645fb8867de --- /dev/null +++ b/webrtc/RTCDataChannel-close.html @@ -0,0 +1,271 @@ + + +RTCDataChannel.close + + + + diff --git a/webrtc/RTCDataChannel-dcep.html b/webrtc/RTCDataChannel-dcep.html new file mode 100644 index 00000000000000..6f591d3f3ef74e --- /dev/null +++ b/webrtc/RTCDataChannel-dcep.html @@ -0,0 +1,271 @@ + + +RTCDataChannel DCEP + + + + diff --git a/webrtc/RTCDataChannel-id.html b/webrtc/RTCDataChannel-id.html index 18f70d8ce969d2..5ff21f49e6da57 100644 --- a/webrtc/RTCDataChannel-id.html +++ b/webrtc/RTCDataChannel-id.html @@ -3,60 +3,639 @@ RTCDataChannel id attribute + diff --git a/webrtc/RTCDataChannel-onopen.html b/webrtc/RTCDataChannel-onopen.html new file mode 100644 index 00000000000000..f871e39ed45d7f --- /dev/null +++ b/webrtc/RTCDataChannel-onopen.html @@ -0,0 +1,109 @@ + + +RTCDataChannel.onopen + + + + diff --git a/webrtc/RTCDataChannel-send-receive-stress.html b/webrtc/RTCDataChannel-send-receive-stress.html new file mode 100644 index 00000000000000..357295529c3541 --- /dev/null +++ b/webrtc/RTCDataChannel-send-receive-stress.html @@ -0,0 +1,177 @@ + + +RTCDataChannel send/receive stress test + + + + diff --git a/webrtc/RTCDataChannel-send-receive.html b/webrtc/RTCDataChannel-send-receive.html new file mode 100644 index 00000000000000..d2720e6008c68a --- /dev/null +++ b/webrtc/RTCDataChannel-send-receive.html @@ -0,0 +1,986 @@ + + +RTCDataChannel send/receive + + + + diff --git a/webrtc/RTCDataChannel-send.html b/webrtc/RTCDataChannel-send.html deleted file mode 100644 index 7cacaa4d37bb76..00000000000000 --- a/webrtc/RTCDataChannel-send.html +++ /dev/null @@ -1,299 +0,0 @@ - - -RTCDataChannel.prototype.send - - - - diff --git a/webrtc/RTCDataChannelEvent-constructor.html b/webrtc/RTCDataChannelEvent-constructor.html index 997f65c43801a9..5751620308c3f9 100644 --- a/webrtc/RTCDataChannelEvent-constructor.html +++ b/webrtc/RTCDataChannelEvent-constructor.html @@ -4,6 +4,9 @@ diff --git a/webrtc/RTCPeerConnection-close.html b/webrtc/RTCPeerConnection-close.html new file mode 100644 index 00000000000000..0541965bc93a87 --- /dev/null +++ b/webrtc/RTCPeerConnection-close.html @@ -0,0 +1,125 @@ + + +RTCPeerConnection.prototype.createDataChannel + + + + diff --git a/webrtc/RTCPeerConnection-createDataChannel.html b/webrtc/RTCPeerConnection-createDataChannel.html index 450a25002ed221..6cd19cad5ac899 100644 --- a/webrtc/RTCPeerConnection-createDataChannel.html +++ b/webrtc/RTCPeerConnection-createDataChannel.html @@ -3,44 +3,55 @@ RTCPeerConnection.prototype.createDataChannel + diff --git a/webrtc/RTCPeerConnection-helper.js b/webrtc/RTCPeerConnection-helper.js index b13e580998efce..ad6525485c97cd 100644 --- a/webrtc/RTCPeerConnection-helper.js +++ b/webrtc/RTCPeerConnection-helper.js @@ -105,7 +105,7 @@ function assert_session_desc_not_similar(sessionDesc1, sessionDesc2) { // Helper function to generate offer using a freshly created RTCPeerConnection // object with any audio, video, data media lines present -function generateOffer(options={}) { +function generateOffer(options = {}) { const { audio = false, video = false, @@ -191,39 +191,95 @@ function test_never_resolve(testFunc, testName) { }, testName); } +// Helper class to queue ICE candidates of a peer connection +// Can be passed to `exchangeIceCandidates` which will automatically empty the queue. +class IceCandidateQueue { + constructor(pc) { + this.pc = pc; + this._queue = []; + this._listener = (event) => this._handleIceCandidate(event); + + // Queue ICE candidates + this.pc.addEventListener('icecandidate', this._listener); + } + + // Unbind the event listener and return all queued candidates + disband() { + this.pc.removeEventListener('icecandidate', this._listener); + return this._queue; + } + + _handleIceCandidate(event) { + this._queue.push(event.candidate); + } +} + // Helper function to exchange ice candidates between // two local peer connections -function exchangeIceCandidates(pc1, pc2) { - // private function - function doExchange(localPc, remotePc) { - localPc.addEventListener('icecandidate', event => { - const { candidate } = event; +function exchangeIceCandidates(pc1OrQueue, pc2OrQueue) { + const handleCandidate = (remotePc, candidate) => { + // candidate may be null to indicate end of candidate gathering. + // 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 && remotePc.signalingState !== 'closed') { + remotePc.addIceCandidate(candidate); + } + }; - // candidate may be null to indicate end of candidate gathering. - // 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 && remotePc.signalingState !== 'closed') { - remotePc.addIceCandidate(candidate); + const exchangeCandidates = (localPcOrQueue, remotePcOrQueue) => { + let localPc = localPcOrQueue; + let remotePc = remotePcOrQueue; + if (remotePcOrQueue instanceof IceCandidateQueue) { + remotePc = remotePcOrQueue.pc; + } + + // Queue? Disband it first + if (localPcOrQueue instanceof IceCandidateQueue) { + localPc = localPcOrQueue.pc; + for (const candidate of localPcOrQueue.disband()) { + handleCandidate(remotePc, candidate); } + } + + // Exchange further candidates + localPc.addEventListener('icecandidate', event => { + const { candidate } = event; + handleCandidate(remotePc, candidate); }); - } + }; - doExchange(pc1, pc2); - doExchange(pc2, pc1); + exchangeCandidates(pc1OrQueue, pc2OrQueue); + exchangeCandidates(pc2OrQueue, pc1OrQueue); } // Helper function for doing one round of offer/answer exchange // betweeen two local peer connections -function doSignalingHandshake(localPc, remotePc) { +function doSignalingHandshake(localPc, remotePc, options={}) { return localPc.createOffer() - .then(offer => Promise.all([ - localPc.setLocalDescription(offer), - remotePc.setRemoteDescription(offer)])) + .then(offer => { + // Modify offer if callback has been provided + if (options.modifyOffer) { + offer = options.modifyOffer(offer); + } + + // Apply offer + return Promise.all([ + localPc.setLocalDescription(offer), + remotePc.setRemoteDescription(offer)]) + }) .then(() => remotePc.createAnswer()) - .then(answer => Promise.all([ - remotePc.setLocalDescription(answer), - localPc.setRemoteDescription(answer)])) + .then(answer => { + // Modify answer if callback has been provided + if (options.modifyAnswer) { + answer = options.modifyAnswer(answer); + } + + // Apply answer + return Promise.all([ + remotePc.setLocalDescription(answer), + localPc.setRemoteDescription(answer)]) + }); } // Helper function to create a pair of connected data channel. @@ -231,53 +287,146 @@ function doSignalingHandshake(localPc, remotePc) { // It does the heavy lifting of performing signaling handshake, // ICE candidate exchange, and waiting for data channel at two // end points to open. +// +// IMPORTANT: Due to a bug in Safari which leads to 'id' being 'null', we send +// the remote channel a message that requests the ID to be sent back +// to the local channel in order to identify the pair. +const remoteChannels = {}; function createDataChannelPair( - pc1=new RTCPeerConnection(), - pc2=new RTCPeerConnection()) + pc1 = new RTCPeerConnection(), + pc2 = new RTCPeerConnection(), + options = {}) { - const channel1 = pc1.createDataChannel(''); + options = Object.assign({}, { + channelLabel: '', + channelOptions: undefined, + doSignaling: true, + idRequestMessage: 'plz-send-me-id', + }, options); + + let channel1Options; + let channel2Options = null; + if (options.channelOptions instanceof Array) { + [channel1Options, channel2Options] = options.channelOptions; + } else { + channel1Options = options.channelOptions; + } - exchangeIceCandidates(pc1, pc2); + const channel1 = pc1.createDataChannel(options.channelLabel, channel1Options); return new Promise((resolve, reject) => { let channel2; let opened1 = false; let opened2 = false; + function cleanup() { + channel1.removeEventListener('open', onOpen1); + channel2.removeEventListener('open', onOpen2); + channel1.removeEventListener('error', onError); + channel2.removeEventListener('error', onError); + } + function onBothOpened() { + cleanup(); resolve([channel1, channel2]); } + function onError(...args) { + cleanup(); + reject(...args); + } + function onOpen1() { opened1 = true; - if(opened2) onBothOpened(); + + // Workaround for an annoying bug in Safari + if (channel1.id === null) { + channel1.addEventListener('message', onMessage1, { once: true }); + channel1.send(options.idRequestMessage); + return; + } + + if (opened2) { + onBothOpened(); + } } function onOpen2() { opened2 = true; - if(opened1) onBothOpened(); + if (opened1) { + onBothOpened(); + } } - function onDataChannel(event) { - channel2 = event.channel; - channel2.addEventListener('error', reject); + function onMessage1(event) { + const id = parseInt(event.data, 10); + channel2 = remoteChannels[id]; + delete remoteChannels[id]; + onDataChannelPairFound(); + } + + function onMessage2(event) { + if (event.data === options.idRequestMessage) { + this.send(this.id.toString()); + } + } + + function onDataChannelPairFound() { + channel2.addEventListener('error', onError, { once: true }); const { readyState } = channel2; - if(readyState === 'open') { + if (readyState === 'open') { onOpen2(); - } else if(readyState === 'connecting') { - channel2.addEventListener('open', onOpen2); + } else if (readyState === 'connecting') { + channel2.addEventListener('open', onOpen2, { once: true }); } else { - reject(new Error(`Unexpected ready state ${readyState}`)); + onError(new Error(`Unexpected ready state ${readyState}`)); } } - channel1.addEventListener('open', onOpen1); - channel1.addEventListener('error', reject); + function onDataChannel(event) { + const channel = event.channel; + + // Ignore remote channels that already have an ID request handler + if (remoteChannels[channel.id]) { + return; + } + + // Keep this check. Will prevent finding pairs where both local and remote + // id are not an integer (e.g. null). + if (!Number.isInteger(channel.id)) { + return; + } + + // Workaround for an annoying bug in Safari + if (channel1.id === null) { + remoteChannels[channel.id] = channel; + channel.addEventListener('message', onMessage2, { once: true }); + return; + } + + if (channel.id !== channel1.id) { + return; + } + + channel2 = channel; + onDataChannelPairFound(); + } - pc2.addEventListener('datachannel', onDataChannel); + channel1.addEventListener('open', onOpen1, { once: true }); + channel1.addEventListener('error', onError, { once: true }); - doSignalingHandshake(pc1, pc2); + if (channel2Options !== null) { + channel2 = pc2.createDataChannel(options.channelLabel, channel2Options); + onDataChannelPairFound(); + } else { + pc2.addEventListener('datachannel', onDataChannel); + } + + if (options.doSignaling) { + exchangeIceCandidates(pc1, pc2); + doSignalingHandshake(pc1, pc2, options); + } }); } @@ -309,23 +458,25 @@ function blobToArrayBuffer(blob) { }); } -// Assert that two ArrayBuffer objects have the same byte values -function assert_equals_array_buffer(buffer1, buffer2) { - assert_true(buffer1 instanceof ArrayBuffer, - 'Expect buffer to be instance of ArrayBuffer'); - - assert_true(buffer2 instanceof ArrayBuffer, - 'Expect buffer to be instance of ArrayBuffer'); +// Assert that two TypedArray or ArrayBuffer objects have the same byte values +function assert_equals_typed_array(array1, array2) { + const [view1, view2] = [array1, array2].map((array) => { + if (array instanceof ArrayBuffer) { + return new DataView(array); + } else { + assert_true(array.buffer instanceof ArrayBuffer, + 'Expect buffer to be instance of ArrayBuffer'); + return new DataView(array.buffer, array.byteOffset, array.byteLength); + } + }); - assert_equals(buffer1.byteLength, buffer2.byteLength, - 'Expect both array buffers to be of the same byte length'); + assert_equals(view1.byteLength, view2.byteLength, + 'Expect both arrays to be of the same byte length'); - const byteLength = buffer1.byteLength; - const byteArray1 = new Uint8Array(buffer1); - const byteArray2 = new Uint8Array(buffer2); + const byteLength = view1.byteLength; - for(let i=0; i { + resolve = resolve_; + reject = reject_; + if (executor) { + return executor(resolve_, reject_); + } }); - this.resolve = promiseResolve; - this.reject = promiseReject; + + this._done = false; + this._resolve = resolve; + this._reject = reject; + } + + /** + * Return whether the promise is done (resolved or rejected). + */ + get done() { + return this._done; + } + + /** + * Resolve the promise. + */ + resolve(...args) { + this._done = true; + return this._resolve(...args); + } + + /** + * Reject the promise. + */ + reject(...args) { + this._done = true; + return this._reject(...args); } } @@ -540,3 +718,23 @@ function findTransceiverForSender(pc, sender) { } return null; } + +// Contains a set of values and will yell at you if you try to add a value twice. +class UniqueSet extends Set { + constructor(items) { + super(); + if (items !== undefined) { + for (const item of items) { + this.add(item); + } + } + } + + add(value, message) { + if (message === undefined) { + message = `Value '${value}' needs to be unique but it is already in the set`; + } + assert_true(!this.has(value), message); + super.add(value); + } +} diff --git a/webrtc/RTCPeerConnection-ondatachannel.html b/webrtc/RTCPeerConnection-ondatachannel.html index 1070ee701d2336..2b11b0f33a027f 100644 --- a/webrtc/RTCPeerConnection-ondatachannel.html +++ b/webrtc/RTCPeerConnection-ondatachannel.html @@ -5,180 +5,408 @@ diff --git a/webrtc/RTCPeerConnection-remote-track-mute.https.html b/webrtc/RTCPeerConnection-remote-track-mute.https.html index 56fe761425096e..095fe50e305f85 100644 --- a/webrtc/RTCPeerConnection-remote-track-mute.https.html +++ b/webrtc/RTCPeerConnection-remote-track-mute.https.html @@ -39,7 +39,7 @@ }); }); await exchangeOfferAnswer(pc1, pc2); - await unmuteResolver.promise; + await unmuteResolver; }, 'ontrack: track goes from muted to unmuted'); promise_test(async t => { diff --git a/webrtc/RTCPeerConnection-setRemoteDescription-tracks.https.html b/webrtc/RTCPeerConnection-setRemoteDescription-tracks.https.html index 7179f1ef78ccd5..99da42a09d964b 100644 --- a/webrtc/RTCPeerConnection-setRemoteDescription-tracks.https.html +++ b/webrtc/RTCPeerConnection-setRemoteDescription-tracks.https.html @@ -95,7 +95,7 @@ ontrackEventResolvers[ontrackEventsFired++].resolve(e); }); await exchangeOffer(caller, callee); - let firstTrackEvent = await ontrackEventResolvers[0].promise; + let firstTrackEvent = await ontrackEventResolvers[0]; assert_equals(firstTrackEvent.track.id, localStreams[0].getTracks()[0].id, 'First ontrack\'s track ID matches first local track.'); @@ -104,7 +104,7 @@ assert_equals(firstTrackEvent.streams[0].id, localStreams[0].id, 'First ontrack\'s stream ID matches local stream.'); - let secondTrackEvent = await ontrackEventResolvers[1].promise; + let secondTrackEvent = await ontrackEventResolvers[1]; assert_equals(secondTrackEvent.track.id, localStreams[1].getTracks()[0].id, 'Second ontrack\'s track ID matches second local track.'); diff --git a/webrtc/RTCPeerConnection-track-stats.https.html b/webrtc/RTCPeerConnection-track-stats.https.html index 682e7e57e465cc..b8fa83ca25efe1 100644 --- a/webrtc/RTCPeerConnection-track-stats.https.html +++ b/webrtc/RTCPeerConnection-track-stats.https.html @@ -612,7 +612,7 @@ resolver.resolve(); } }; - return resolver.promise; + await resolver; } // Explores the stats graph starting from |stat|, validating each stat diff --git a/webrtc/RTCSctpTransport-constructor.html b/webrtc/RTCSctpTransport-constructor.html index c415c3fe180b67..18cfc6bde612b0 100644 --- a/webrtc/RTCSctpTransport-constructor.html +++ b/webrtc/RTCSctpTransport-constructor.html @@ -5,85 +5,121 @@ diff --git a/webrtc/RTCSctpTransport-maxMessageSize.html b/webrtc/RTCSctpTransport-maxMessageSize.html index 28d17eeaccf3f2..1781c6c69f24d4 100644 --- a/webrtc/RTCSctpTransport-maxMessageSize.html +++ b/webrtc/RTCSctpTransport-maxMessageSize.html @@ -8,11 +8,18 @@ diff --git a/webrtc/RTCSctpTransport-onstatechange.html b/webrtc/RTCSctpTransport-onstatechange.html new file mode 100644 index 00000000000000..46b77d6200bf7f --- /dev/null +++ b/webrtc/RTCSctpTransport-onstatechange.html @@ -0,0 +1,56 @@ + + +RTCSctpTransport constructor + + + + diff --git a/webrtc/datachannel-emptystring.html b/webrtc/datachannel-emptystring.html deleted file mode 100644 index 6af436a1d1bd4f..00000000000000 --- a/webrtc/datachannel-emptystring.html +++ /dev/null @@ -1,108 +0,0 @@ - - - - - - - RTCPeerConnection Data Channel Empty String Test - - -
-

Messages exchanged

-
- - - - - - - - - diff --git a/webrtc/historical.html b/webrtc/historical.html index d49503e16d6c5d..be011e8f518256 100644 --- a/webrtc/historical.html +++ b/webrtc/historical.html @@ -4,9 +4,14 @@