summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webrtc/RTCPeerConnection-createDataChannel.html
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/webrtc/RTCPeerConnection-createDataChannel.html
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/webrtc/RTCPeerConnection-createDataChannel.html')
-rw-r--r--testing/web-platform/tests/webrtc/RTCPeerConnection-createDataChannel.html758
1 files changed, 758 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webrtc/RTCPeerConnection-createDataChannel.html b/testing/web-platform/tests/webrtc/RTCPeerConnection-createDataChannel.html
new file mode 100644
index 0000000000..7ad8bf7d46
--- /dev/null
+++ b/testing/web-platform/tests/webrtc/RTCPeerConnection-createDataChannel.html
@@ -0,0 +1,758 @@
+<!doctype html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>RTCPeerConnection.prototype.createDataChannel</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script>
+'use strict';
+
+const stopTracks = (...streams) => {
+ streams.forEach(stream => stream.getTracks().forEach(track => track.stop()));
+};
+
+// Test is based on the following revision:
+// https://rawgit.com/w3c/webrtc-pc/1cc5bfc3ff18741033d804c4a71f7891242fb5b3/webrtc.html
+
+/*
+ 6.1. RTCPeerConnection Interface Extensions
+
+ partial interface RTCPeerConnection {
+ [...]
+ RTCDataChannel createDataChannel(USVString label,
+ optional RTCDataChannelInit dataChannelDict);
+ [...]
+ };
+
+ 6.2. RTCDataChannel
+
+ interface RTCDataChannel : EventTarget {
+ readonly attribute USVString label;
+ readonly attribute boolean ordered;
+ readonly attribute unsigned short? maxPacketLifeTime;
+ readonly attribute unsigned short? maxRetransmits;
+ readonly attribute USVString protocol;
+ readonly attribute boolean negotiated;
+ readonly attribute unsigned short? id;
+ readonly attribute RTCDataChannelState readyState;
+ readonly attribute unsigned long bufferedAmount;
+ attribute unsigned long bufferedAmountLowThreshold;
+ [...]
+ attribute DOMString binaryType;
+ [...]
+ };
+
+ dictionary RTCDataChannelInit {
+ boolean ordered = true;
+ unsigned short maxPacketLifeTime;
+ unsigned short maxRetransmits;
+ USVString protocol = "";
+ boolean negotiated = false;
+ [EnforceRange]
+ unsigned short id;
+ };
+ */
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ assert_equals(pc.createDataChannel.length, 1);
+ assert_throws_js(TypeError, () => pc.createDataChannel());
+}, 'createDataChannel with no argument should throw TypeError');
+
+/*
+ 6.2. createDataChannel
+ 2. If connection's [[isClosed]] slot is true, throw an InvalidStateError.
+ */
+test(t => {
+ const pc = new RTCPeerConnection();
+ pc.close();
+ assert_equals(pc.signalingState, 'closed', 'signaling state');
+ assert_throws_dom('InvalidStateError', () => pc.createDataChannel(''));
+}, 'createDataChannel with closed connection should throw InvalidStateError');
+
+/*
+ 6.1. createDataChannel
+ 4. Let channel have a [[DataChannelLabel]] internal slot initialized to the value of the
+ first argument.
+ 6. Let options be the second argument.
+ 7. Let channel have an [[MaxPacketLifeTime]] internal slot initialized to
+ option's maxPacketLifeTime member, if present, otherwise null.
+ 8. Let channel have a [[ReadyState]] internal slot initialized to "connecting".
+ 9. Let channel have a [[BufferedAmount]] internal slot initialized to 0.
+ 10. Let channel have an [[MaxRetransmits]] internal slot initialized to
+ option's maxRetransmits member, if present, otherwise null.
+ 11. Let channel have an [[Ordered]] internal slot initialized to option's
+ ordered member.
+ 12. Let channel have a [[DataChannelProtocol]] internal slot initialized to option's
+ protocol member.
+ 14. Let channel have a [[Negotiated]] internal slot initialized to option's negotiated
+ member.
+ 15. Let channel have an [[DataChannelId]] internal slot initialized to option's id
+ member, if it is present and [[Negotiated]] is true, otherwise null.
+ 21. If the [[DataChannelId]] slot is null (due to no ID being passed into
+ createDataChannel, or [[Negotiated]] being false), and the DTLS role of the SCTP
+ transport has already been negotiated, then initialize [[DataChannelId]] to a value
+ generated by the user agent, according to [RTCWEB-DATA-PROTOCOL], and skip
+ to the next step. If no available ID could be generated, or if the value of the
+ [[DataChannelId]] slot is being used by an existing RTCDataChannel, throw an
+ OperationError exception.
+
+ Note
+ If the [[DataChannelId]] slot is null after this step, it will be populated once
+ the DTLS role is determined during the process of setting an RTCSessionDescription.
+ 22. If channel is the first RTCDataChannel created on connection, update the
+ negotiation-needed flag for connection.
+
+
+ 6.2. RTCDataChannel
+
+ A RTCDataChannel, created with createDataChannel or dispatched via a
+ RTCDataChannelEvent, MUST initially be in the connecting state
+
+ bufferedAmountLowThreshold
+ [...] The bufferedAmountLowThreshold is initially zero on each new RTCDataChannel,
+ but the application may change its value at any time.
+
+ binaryType
+ [...] When a RTCDataChannel object is created, the binaryType attribute MUST
+ be initialized to the string "blob".
+ */
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ const dc = pc.createDataChannel('');
+
+ assert_true(dc instanceof RTCDataChannel, 'is RTCDataChannel');
+ assert_equals(dc.label, '');
+ assert_equals(dc.ordered, true);
+ assert_equals(dc.maxPacketLifeTime, null);
+ assert_equals(dc.maxRetransmits, null);
+ assert_equals(dc.protocol, '');
+ assert_equals(dc.negotiated, false);
+ // Since no offer/answer exchange has occurred yet, the DTLS role is unknown
+ // and so the ID should be null.
+ assert_equals(dc.id, null);
+ assert_equals(dc.readyState, 'connecting');
+ assert_equals(dc.bufferedAmount, 0);
+ assert_equals(dc.bufferedAmountLowThreshold, 0);
+ assert_equals(dc.binaryType, 'blob');
+}, 'createDataChannel attribute default values');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ const dc = pc.createDataChannel('test', {
+ ordered: false,
+ maxRetransmits: 1,
+ // Note: maxPacketLifeTime is not set in this test.
+ protocol: 'custom',
+ negotiated: true,
+ id: 3
+ });
+
+ assert_true(dc instanceof RTCDataChannel, 'is RTCDataChannel');
+ assert_equals(dc.label, 'test');
+ assert_equals(dc.ordered, false);
+ assert_equals(dc.maxPacketLifeTime, null);
+ assert_equals(dc.maxRetransmits, 1);
+ assert_equals(dc.protocol, 'custom');
+ assert_equals(dc.negotiated, true);
+ assert_equals(dc.id, 3);
+ assert_equals(dc.readyState, 'connecting');
+ assert_equals(dc.bufferedAmount, 0);
+ assert_equals(dc.bufferedAmountLowThreshold, 0);
+ assert_equals(dc.binaryType, 'blob');
+
+ const dc2 = pc.createDataChannel('test2', {
+ ordered: false,
+ maxPacketLifeTime: 42
+ });
+ assert_equals(dc2.label, 'test2');
+ assert_equals(dc2.maxPacketLifeTime, 42);
+ assert_equals(dc2.maxRetransmits, null);
+}, 'createDataChannel with provided parameters should initialize attributes to provided values');
+
+/*
+ 6.2. createDataChannel
+ 4. Let channel have a [[DataChannelLabel]] internal slot initialized to the value of the
+ first argument.
+
+ [ECMA262] 7.1.12. ToString(argument)
+ undefined -> "undefined"
+ null -> "null"
+
+ [WebIDL] 3.10.15. Convert a DOMString to a sequence of Unicode scalar values
+ */
+const labels = [
+ ['"foo"', 'foo', 'foo'],
+ ['null', null, 'null'],
+ ['undefined', undefined, 'undefined'],
+ ['lone surrogate', '\uD800', '\uFFFD'],
+];
+for (const [description, label, expected] of labels) {
+ test(t => {
+ const pc = new RTCPeerConnection;
+ t.add_cleanup(() => pc.close());
+
+ const dc = pc.createDataChannel(label);
+ assert_equals(dc.label, expected);
+ }, `createDataChannel with label ${description} should succeed`);
+}
+
+/*
+ 6.2. RTCDataChannel
+ createDataChannel
+ 11. Let channel have an [[Ordered]] internal slot initialized to option's
+ ordered member.
+ */
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ const dc = pc.createDataChannel('', { ordered: false });
+ assert_equals(dc.ordered, false);
+}, 'createDataChannel with ordered false should succeed');
+
+// true as the default value of a boolean is confusing because null is converted
+// to false while undefined is converted to true.
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ const dc1 = pc.createDataChannel('', { ordered: null });
+ assert_equals(dc1.ordered, false);
+ const dc2 = pc.createDataChannel('', { ordered: undefined });
+ assert_equals(dc2.ordered, true);
+}, 'createDataChannel with ordered null/undefined should succeed');
+
+/*
+ 6.2. RTCDataChannel
+ createDataChannel
+ 7. Let channel have an [[MaxPacketLifeTime]] internal slot initialized to
+ option's maxPacketLifeTime member, if present, otherwise null.
+ */
+test(t => {
+ const pc = new RTCPeerConnection;
+ t.add_cleanup(() => pc.close());
+
+ const dc = pc.createDataChannel('', { maxPacketLifeTime: 0 });
+ assert_equals(dc.maxPacketLifeTime, 0);
+}, 'createDataChannel with maxPacketLifeTime 0 should succeed');
+
+/*
+ 6.2. RTCDataChannel
+ createDataChannel
+ 10. Let channel have an [[MaxRetransmits]] internal slot initialized to
+ option's maxRetransmits member, if present, otherwise null.
+ */
+test(t => {
+ const pc = new RTCPeerConnection;
+ t.add_cleanup(() => pc.close());
+
+ const dc = pc.createDataChannel('', { maxRetransmits: 0 });
+ assert_equals(dc.maxRetransmits, 0);
+}, 'createDataChannel with maxRetransmits 0 should succeed');
+
+/*
+ 6.2. createDataChannel
+ 18. If both [[MaxPacketLifeTime]] and [[MaxRetransmits]] attributes are set (not null),
+ throw a TypeError.
+ */
+test(t => {
+ const pc = new RTCPeerConnection;
+ t.add_cleanup(() => pc.close());
+
+ pc.createDataChannel('', {
+ maxPacketLifeTime: undefined,
+ maxRetransmits: undefined
+ });
+}, 'createDataChannel with both maxPacketLifeTime and maxRetransmits undefined should succeed');
+
+test(t => {
+ const pc = new RTCPeerConnection;
+ t.add_cleanup(() => pc.close());
+
+ assert_throws_js(TypeError, () => pc.createDataChannel('', {
+ maxPacketLifeTime: 0,
+ maxRetransmits: 0
+ }));
+ assert_throws_js(TypeError, () => pc.createDataChannel('', {
+ maxPacketLifeTime: 42,
+ maxRetransmits: 42
+ }));
+}, 'createDataChannel with both maxPacketLifeTime and maxRetransmits should throw TypeError');
+
+/*
+ 6.2. RTCDataChannel
+ createDataChannel
+ 12. Let channel have a [[DataChannelProtocol]] internal slot initialized to option's
+ protocol member.
+ */
+const protocols = [
+ ['"foo"', 'foo', 'foo'],
+ ['null', null, 'null'],
+ ['undefined', undefined, ''],
+ ['lone surrogate', '\uD800', '\uFFFD'],
+];
+for (const [description, protocol, expected] of protocols) {
+ test(t => {
+ const pc = new RTCPeerConnection;
+ t.add_cleanup(() => pc.close());
+
+ const dc = pc.createDataChannel('', { protocol });
+ assert_equals(dc.protocol, expected);
+ }, `createDataChannel with protocol ${description} should succeed`);
+}
+
+/*
+ 6.2. RTCDataChannel
+ createDataChannel
+ 20. If [[DataChannelId]] is equal to 65535, which is greater than the maximum allowed
+ ID of 65534 but still qualifies as an unsigned short, throw a TypeError.
+ */
+for (const id of [0, 1, 65534, 65535]) {
+ test((t) => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const dc = pc.createDataChannel('', { id });
+ assert_equals(dc.id, null);
+ }, `createDataChannel with id ${id} and negotiated not set should succeed, but not set the channel's id`);
+}
+
+for (const id of [0, 1, 65534]) {
+ test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ const dc = pc.createDataChannel('', { 'negotiated': true, 'id': id });
+ assert_equals(dc.id, id);
+ }, `createDataChannel with id ${id} and negotiated true should succeed, and set the channel's id`);
+}
+
+for (const id of [-1, 65536]) {
+ test((t) => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ assert_throws_js(TypeError, () => pc.createDataChannel('', { id }));
+ }, `createDataChannel with id ${id} and negotiated not set should throw TypeError`);
+}
+
+for (const id of [-1, 65535, 65536]) {
+ test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ assert_throws_js(TypeError, () => pc.createDataChannel('',
+ { 'negotiated': true, 'id': id }));
+ }, `createDataChannel with id ${id} should throw TypeError`);
+}
+
+/*
+ 6.2. createDataChannel
+ 5. If [[DataChannelLabel]] is longer than 65535 bytes, throw a TypeError.
+ */
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ assert_throws_js(TypeError, () =>
+ pc.createDataChannel('l'.repeat(65536)));
+
+ assert_throws_js(TypeError, () =>
+ pc.createDataChannel('l'.repeat(65536), {
+ negotiated: true,
+ id: 42
+ }));
+}, 'createDataChannel with too long label should throw TypeError');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ assert_throws_js(TypeError, () =>
+ pc.createDataChannel('\u00b5'.repeat(32768)));
+
+ assert_throws_js(TypeError, () =>
+ pc.createDataChannel('\u00b5'.repeat(32768), {
+ negotiated: true,
+ id: 42
+ }));
+}, 'createDataChannel with too long label (2 byte unicode) should throw TypeError');
+
+/*
+ 6.2. label
+ [...] Scripts are allowed to create multiple RTCDataChannel objects with the same label.
+ [...]
+ */
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ const label = 'test';
+
+ pc.createDataChannel(label);
+ pc.createDataChannel(label);
+}, 'createDataChannel with same label used twice should not throw');
+
+/*
+ 6.2. createDataChannel
+ 13. If [[DataChannelProtocol]] is longer than 65535 bytes long, throw a TypeError.
+ */
+
+test(t => {
+ const pc = new RTCPeerConnection;
+ t.add_cleanup(() => pc.close());
+ const channel = pc.createDataChannel('', { negotiated: true, id: 42 });
+ assert_equals(channel.negotiated, true);
+}, 'createDataChannel with negotiated true and id should succeed');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ assert_throws_js(TypeError, () =>
+ pc.createDataChannel('', {
+ protocol: 'p'.repeat(65536)
+ }));
+
+ assert_throws_js(TypeError, () =>
+ pc.createDataChannel('', {
+ protocol: 'p'.repeat(65536),
+ negotiated: true,
+ id: 42
+ }));
+}, 'createDataChannel with too long protocol should throw TypeError');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ assert_throws_js(TypeError, () =>
+ pc.createDataChannel('', {
+ protocol: '\u00b6'.repeat(32768)
+ }));
+
+ assert_throws_js(TypeError, () =>
+ pc.createDataChannel('', {
+ protocol: '\u00b6'.repeat(32768),
+ negotiated: true,
+ id: 42
+ }));
+}, 'createDataChannel with too long protocol (2 byte unicode) should throw TypeError');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ const label = 'l'.repeat(65535);
+ const protocol = 'p'.repeat(65535);
+
+ const dc = pc.createDataChannel(label, {
+ protocol: protocol
+ });
+
+ assert_equals(dc.label, label);
+ assert_equals(dc.protocol, protocol);
+}, 'createDataChannel with maximum length label and protocol should succeed');
+
+/*
+ 6.2 createDataChannel
+ 15. Let channel have an [[DataChannelId]] internal slot initialized to option's id member,
+ if it is present and [[Negotiated]] is true, otherwise null.
+
+ NOTE
+ This means the id member will be ignored if the data channel is negotiated in-band; this
+ is intentional. Data channels negotiated in-band should have IDs selected based on the
+ DTLS role, as specified in [RTCWEB-DATA-PROTOCOL].
+ */
+test(t => {
+ const pc = new RTCPeerConnection;
+ t.add_cleanup(() => pc.close());
+
+ const dc = pc.createDataChannel('', {
+ negotiated: false,
+ });
+ assert_equals(dc.negotiated, false, 'Expect dc.negotiated to be false');
+}, 'createDataChannel with negotiated false should succeed');
+
+test(t => {
+ const pc = new RTCPeerConnection;
+ t.add_cleanup(() => pc.close());
+
+ const dc = pc.createDataChannel('', {
+ negotiated: false,
+ id: 42
+ });
+ assert_equals(dc.negotiated, false, 'Expect dc.negotiated to be false');
+ assert_equals(dc.id, null, 'Expect dc.id to be ignored (null)');
+}, 'createDataChannel with negotiated false and id 42 should ignore the id');
+
+/*
+ 6.2. createDataChannel
+ 16. If [[Negotiated]] is true and [[DataChannelId]] is null, throw a TypeError.
+ */
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ assert_throws_js(TypeError, () =>
+ pc.createDataChannel('test', {
+ negotiated: true
+ }));
+}, 'createDataChannel with negotiated true and id not defined should throw TypeError');
+
+/*
+ 4.4.1.6. Set the RTCSessionSessionDescription
+ 2.2.6. If description is of type "answer" or "pranswer", then run the
+ following steps:
+ 3. If description negotiates the DTLS role of the SCTP transport, and there is an
+ RTCDataChannel with a null id, then generate an ID according to
+ [RTCWEB-DATA-PROTOCOL]. [...]
+
+ 6.1. createDataChannel
+ 21. If the [[DataChannelId]] slot is null (due to no ID being passed into
+ createDataChannel, or [[Negotiated]] being false), and the DTLS role of the SCTP
+ transport has already been negotiated, then initialize [[DataChannelId]] to a value
+ generated by the user agent, according to [RTCWEB-DATA-PROTOCOL], and skip
+ to the next step. If no available ID could be generated, or if the value of the
+ [[DataChannelId]] slot is being used by an existing RTCDataChannel, throw an
+ OperationError exception.
+
+ Note
+ If the [[DataChannelId]] slot is null after this step, it will be populated once
+ the DTLS role is determined during the process of setting an RTCSessionDescription.
+ */
+promise_test(async t => {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ t.add_cleanup(() => pc2.close());
+
+ const negotiatedDc = pc1.createDataChannel('negotiated-channel', {
+ negotiated: true,
+ id: 42,
+ });
+ assert_equals(negotiatedDc.id, 42, 'Expect negotiatedDc.id to be 42');
+
+ const dc1 = pc1.createDataChannel('channel');
+ assert_equals(dc1.id, null, 'Expect initial id to be null');
+
+ const offer = await pc1.createOffer();
+ await Promise.all([pc1.setLocalDescription(offer), pc2.setRemoteDescription(offer)]);
+ const answer = await pc2.createAnswer();
+ await pc1.setRemoteDescription(answer);
+
+ assert_not_equals(dc1.id, null,
+ 'Expect dc1.id to be assigned after remote description has been set');
+
+ assert_greater_than_equal(dc1.id, 0,
+ 'Expect dc1.id to be set to valid unsigned short');
+
+ assert_less_than(dc1.id, 65535,
+ 'Expect dc1.id to be set to valid unsigned short');
+
+ const dc2 = pc1.createDataChannel('channel');
+
+ assert_not_equals(dc2.id, null,
+ 'Expect dc2.id to be assigned after remote description has been set');
+
+ assert_greater_than_equal(dc2.id, 0,
+ 'Expect dc2.id to be set to valid unsigned short');
+
+ assert_less_than(dc2.id, 65535,
+ 'Expect dc2.id to be set to valid unsigned short');
+
+ assert_not_equals(dc2, dc1,
+ 'Expect channels created from same label to be different');
+
+ assert_equals(dc2.label, dc1.label,
+ 'Expect different channels can have the same label but different id');
+
+ assert_not_equals(dc2.id, dc1.id,
+ 'Expect different channels can have the same label but different id');
+
+ assert_equals(negotiatedDc.id, 42,
+ 'Expect negotiatedDc.id to be 42 after remote description has been set');
+}, 'Channels created (after setRemoteDescription) should have id assigned');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ const dc1 = pc.createDataChannel('channel-1', {
+ negotiated: true,
+ id: 42,
+ });
+ assert_equals(dc1.id, 42,
+ 'Expect dc1.id to be 42');
+
+ const dc2 = pc.createDataChannel('channel-2', {
+ negotiated: true,
+ id: 43,
+ });
+ assert_equals(dc2.id, 43,
+ 'Expect dc2.id to be 43');
+
+ assert_throws_dom('OperationError', () =>
+ pc.createDataChannel('channel-3', {
+ negotiated: true,
+ id: 42,
+ }));
+
+}, 'Reusing a data channel id that is in use should throw OperationError');
+
+// We've seen implementations behaving differently before and after the connection has been
+// established.
+promise_test(async t => {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ t.add_cleanup(() => pc2.close());
+
+ const dc1 = pc1.createDataChannel('channel-1', {
+ negotiated: true,
+ id: 42,
+ });
+ assert_equals(dc1.id, 42, 'Expect dc1.id to be 42');
+
+ const dc2 = pc1.createDataChannel('channel-2', {
+ negotiated: true,
+ id: 43,
+ });
+ assert_equals(dc2.id, 43, 'Expect dc2.id to be 43');
+
+ const offer = await pc1.createOffer();
+ await Promise.all([pc1.setLocalDescription(offer), pc2.setRemoteDescription(offer)]);
+ const answer = await pc2.createAnswer();
+ await pc1.setRemoteDescription(answer);
+
+ assert_equals(dc1.id, 42, 'Expect dc1.id to be 42');
+
+ assert_equals(dc2.id, 43, 'Expect dc2.id to be 43');
+
+ assert_throws_dom('OperationError', () =>
+ pc1.createDataChannel('channel-3', {
+ negotiated: true,
+ id: 42,
+ }));
+}, 'Reusing a data channel id that is in use (after setRemoteDescription) should throw ' +
+ 'OperationError');
+
+promise_test(async t => {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ t.add_cleanup(() => pc2.close());
+
+ const dc1 = pc1.createDataChannel('channel-1');
+
+ const offer = await pc1.createOffer();
+ await Promise.all([pc1.setLocalDescription(offer), pc2.setRemoteDescription(offer)]);
+ const answer = await pc2.createAnswer();
+ await pc1.setRemoteDescription(answer);
+
+ assert_not_equals(dc1.id, null,
+ 'Expect dc1.id to be assigned after remote description has been set');
+
+ assert_throws_dom('OperationError', () =>
+ pc1.createDataChannel('channel-2', {
+ negotiated: true,
+ id: dc1.id,
+ }));
+}, 'Reusing a data channel id that is in use (after setRemoteDescription, negotiated via DCEP) ' +
+ 'should throw OperationError');
+
+
+for (const options of [{}, {negotiated: true, id: 0}]) {
+ const mode = `${options.negotiated? "negotiated " : ""}datachannel`;
+
+ // Based on https://bugzilla.mozilla.org/show_bug.cgi?id=1441723
+ promise_test(async t => {
+ const pc1 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+
+ await createDataChannelPair(t, options, pc1);
+
+ const dc = pc1.createDataChannel('');
+ assert_equals(dc.readyState, 'connecting', 'Channel should be in the connecting state');
+ }, `New ${mode} should be in the connecting state after creation ` +
+ `(after connection establishment)`);
+
+ promise_test(async t => {
+ const pc1 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ const stream = await getNoiseStream({audio: true, video: true});
+ t.add_cleanup(() => stopTracks(stream));
+ const audio = stream.getAudioTracks()[0];
+ const video = stream.getVideoTracks()[0];
+ pc1.addTrack(audio, stream);
+ pc1.addTrack(video, stream);
+ await createDataChannelPair(t, options, pc1);
+ }, `addTrack, then creating ${mode}, should negotiate properly`);
+
+ promise_test(async t => {
+ const pc1 = new RTCPeerConnection({bundlePolicy: "max-bundle"});
+ t.add_cleanup(() => pc1.close());
+ const stream = await getNoiseStream({audio: true, video: true});
+ t.add_cleanup(() => stopTracks(stream));
+ const audio = stream.getAudioTracks()[0];
+ const video = stream.getVideoTracks()[0];
+ pc1.addTrack(audio, stream);
+ pc1.addTrack(video, stream);
+ await createDataChannelPair(t, options, pc1);
+ }, `addTrack, then creating ${mode}, should negotiate properly when max-bundle is used`);
+
+/*
+This test is disabled until https://github.com/w3c/webrtc-pc/issues/2562
+has been resolved; it presupposes that stopping the first transceiver
+breaks the transport.
+
+ promise_test(async t => {
+ const pc1 = new RTCPeerConnection({bundlePolicy: "max-bundle"});
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ t.add_cleanup(() => pc2.close());
+ const stream = await getNoiseStream({audio: true, video: true});
+ t.add_cleanup(() => stopTracks(stream));
+ const audio = stream.getAudioTracks()[0];
+ const video = stream.getVideoTracks()[0];
+ pc1.addTrack(audio, stream);
+ pc1.addTrack(video, stream);
+ const [dc1, dc2] = await createDataChannelPair(t, options, pc1, pc2);
+
+ pc2.getTransceivers()[0].stop();
+ const dc1Closed = new Promise(r => dc1.onclose = r);
+ await exchangeOfferAnswer(pc1, pc2);
+ await dc1Closed;
+ }, `Stopping the bundle-tag when there is a ${mode} in the bundle ` +
+ `should kill the DataChannel`);
+*/
+}
+
+/*
+ Untestable
+ 6.1. createDataChannel
+ 19. If a setting, either [[MaxPacketLifeTime]] or [[MaxRetransmits]], has been set to
+ indicate unreliable mode, and that value exceeds the maximum value supported
+ by the user agent, the value MUST be set to the user agents maximum value.
+
+ 23. Return channel and continue the following steps in parallel.
+ 24. Create channel's associated underlying data transport and configure
+ it according to the relevant properties of channel.
+
+ Tested in RTCPeerConnection-onnegotiationneeded.html
+ 22. If channel is the first RTCDataChannel created on connection, update the
+ negotiation-needed flag for connection.
+
+ Tested in RTCDataChannel-id.html
+ - Odd/even rules for '.id'
+
+ Tested in RTCDataChannel-dcep.html
+ - Transmission of '.label' and further options
+*/
+</script>