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