| <!doctype html> | 
 | <meta charset=utf-8> | 
 | <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'); | 
 |  | 
 | // Based on https://bugzilla.mozilla.org/show_bug.cgi?id=1441723 | 
 | promise_test(async t => { | 
 |  const pc1 = new RTCPeerConnection(); | 
 |  const pc2 = new RTCPeerConnection(); | 
 |  t.add_cleanup(() => pc1.close()); | 
 |  t.add_cleanup(() => pc2.close()); | 
 |  | 
 |  await createDataChannelPair(pc1, pc2); | 
 |  | 
 |  const dc = pc1.createDataChannel(''); | 
 |  assert_equals(dc.readyState, 'connecting', 'Channel should be in the connecting state'); | 
 | }, 'New data channel should be in the connecting state after creation (after connection ' + | 
 |  'establishment)'); | 
 |  | 
 | promise_test(async t => { | 
 |  const pc1 = new RTCPeerConnection(); | 
 |  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); | 
 |  await createDataChannelPair(pc1, pc2); | 
 | }, 'addTrack, then createDataChannel, should negotiate properly'); | 
 |  | 
 | 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); | 
 |  await createDataChannelPair(pc1, pc2); | 
 | }, 'addTrack, then createDataChannel, should negotiate properly when max-bundle is used'); | 
 |  | 
 | 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(pc1, pc2); | 
 |  | 
 |  pc2.getTransceivers()[0].stop(); | 
 |  const dc1Closed = new Promise(r => dc1.onclose = r); | 
 |  await doSignalingHandshake(pc1, pc2); | 
 |  await dc1Closed; | 
 | }, 'Stopping the bundle-tag when there is a DataChannel 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> |