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 @@