| <!doctype html> |
| <meta charset=utf-8> |
| <title>RTCPeerConnection.prototype.createDataChannel</title> |
| <script src=/resources/testharness.js></script> |
| <script src=/resources/testharnessreport.js></script> |
| <script> |
| 'use strict'; |
| |
| // Test is based on the following editor draft: |
| // https://rawgit.com/w3c/webrtc-pc/cc8d80f455b86c8041d63bceb8b457f45c72aa89/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 RTCPriorityType priority; |
| readonly attribute RTCDataChannelState readyState; |
| }; |
| |
| dictionary RTCDataChannelInit { |
| boolean ordered = true; |
| unsigned short maxPacketLifeTime; |
| unsigned short maxRetransmits; |
| USVString protocol = ""; |
| boolean negotiated = false; |
| [EnforceRange] |
| unsigned short id; |
| RTCPriorityType priority = "low"; |
| }; |
| |
| 4.9.1. RTCPriorityType Enum |
| |
| enum RTCPriorityType { |
| "very-low", |
| "low", |
| "medium", |
| "high" |
| }; |
| */ |
| |
| test(() => { |
| const pc = new RTCPeerConnection(); |
| assert_equals(pc.createDataChannel.length, 1); |
| assert_throws(new 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(() => { |
| const pc = new RTCPeerConnection(); |
| pc.close(); |
| assert_equals(pc.signalingState, 'closed', 'signaling state'); |
| assert_throws('InvalidStateError', () => pc.createDataChannel('')); |
| }, 'createDataChannel with closed connection should throw InvalidStateError'); |
| |
| /* |
| 6.1. createDataChannel |
| 4. Let channel have a [[Label]] internal slot initialized to the value of the |
| first argument. |
| 5. Let options be the second argument. |
| 6. Let channel have an [[MaxPacketLifeTime]] internal slot initialized to |
| option's maxPacketLifeTime member, if present, otherwise null. |
| 7. Let channel have an [[MaxRetransmits]] internal slot initialized to |
| option's maxRetransmits member, if present, otherwise null. |
| 8. Let channel have an [[DataChannelId]] internal slot initialized to |
| option's id member, if present, otherwise null. |
| 9. Let channel have an [[Ordered]] internal slot initialized to option's |
| ordered member. |
| 10. Let channel have an [[Protocol]] internal slot initialized to option's |
| protocol member. |
| 11. Let channel have an [[Negotiated]] internal slot initialized to option's |
| negotiated member. |
| 12. Let channel have an [[DataChannelPriority]] internal slot initialized |
| to option's priority member. |
| |
| 6.2. RTCDataChannel |
| |
| A RTCDataChannel, created with createDataChannel or dispatched via a |
| RTCDataChannelEvent, MUST initially be in the connecting state |
| |
| binaryType |
| When a RTCDataChannel object is created, the binaryType attribute MUST |
| be initialized to the string "blob". |
| */ |
| test(() => { |
| const pc = new RTCPeerConnection(); |
| const channel = pc.createDataChannel(''); |
| assert_true(channel instanceof RTCDataChannel, 'is RTCDataChannel'); |
| assert_equals(channel.label, ''); |
| assert_equals(channel.ordered, true); |
| assert_equals(channel.maxPacketLifeTime, null); |
| assert_equals(channel.maxRetransmits, null); |
| assert_equals(channel.protocol, ''); |
| assert_equals(channel.negotiated, false); |
| |
| // Since no offer/answer exchange has occurred yet, the DTLS role is unknown |
| // and so the ID should be null. |
| assert_equals(channel.id, null); |
| assert_equals(channel.priority, 'low'); |
| |
| assert_equals(channel.readyState, 'connecting'); |
| assert_equals(channel.binaryType, 'blob'); |
| |
| }, 'createDataChannel attribute default values'); |
| |
| test(() => { |
| const pc = new RTCPeerConnection(); |
| const channel = pc.createDataChannel('test', { |
| ordered: false, |
| maxPacketLifeTime: null, |
| maxRetransmits: 1, |
| protocol: 'custom', |
| negotiated: true, |
| id: 3, |
| priority: 'high' |
| }); |
| |
| assert_true(channel instanceof RTCDataChannel, 'is RTCDataChannel'); |
| assert_equals(channel.label, 'test'); |
| assert_equals(channel.ordered, false); |
| assert_equals(channel.maxPacketLifeTime, null); |
| assert_equals(channel.maxRetransmits, 1); |
| assert_equals(channel.protocol, 'custom'); |
| assert_equals(channel.negotiated, true); |
| assert_equals(channel.id, 3); |
| assert_equals(channel.priority, 'high'); |
| assert_equals(channel.readyState, 'connecting'); |
| assert_equals(channel.binaryType, 'blob'); |
| |
| }, 'createDataChannel with provided parameters should initialize attributes to provided values'); |
| |
| /* |
| 6.2. createDataChannel |
| 4. Let channel have a [[Label]] 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(() => { |
| const pc = new RTCPeerConnection; |
| const channel = pc.createDataChannel(label); |
| assert_equals(channel.label, expected); |
| }, `createDataChannel with label ${description} should succeed`); |
| } |
| |
| /* |
| 6.2. RTCDataChannel |
| createDataChannel |
| 9. Let channel have an [[Ordered]] internal slot initialized to option's |
| ordered member. |
| */ |
| test(() => { |
| const pc = new RTCPeerConnection(); |
| const channel = pc.createDataChannel('', { ordered: false }); |
| assert_equals(channel.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(() => { |
| const pc = new RTCPeerConnection(); |
| const channel1 = pc.createDataChannel('', { ordered: null }); |
| assert_equals(channel1.ordered, false); |
| const channel2 = pc.createDataChannel('', { ordered: undefined }); |
| assert_equals(channel2.ordered, true); |
| }, 'createDataChannel with ordered null/undefined should succeed'); |
| |
| /* |
| 6.2. RTCDataChannel |
| createDataChannel |
| 6. Let channel have an [[MaxPacketLifeTime]] internal slot initialized to |
| option's maxPacketLifeTime member, if present, otherwise null. |
| */ |
| test(() => { |
| const pc = new RTCPeerConnection; |
| const channel = pc.createDataChannel('', { maxPacketLifeTime: 0 }); |
| assert_equals(channel.maxPacketLifeTime, 0); |
| }, 'createDataChannel with maxPacketLifeTime 0 should succeed'); |
| |
| /* |
| 6.2. RTCDataChannel |
| createDataChannel |
| 7. Let channel have an [[MaxRetransmits]] internal slot initialized to |
| option's maxRetransmits member, if present, otherwise null. |
| */ |
| test(() => { |
| const pc = new RTCPeerConnection; |
| const channel = pc.createDataChannel('', { maxRetransmits: 0 }); |
| assert_equals(channel.maxRetransmits, 0); |
| }, 'createDataChannel with maxRetransmits 0 should succeed'); |
| |
| /* |
| 6.2. createDataChannel |
| 15. If both [[MaxPacketLifeTime]] and [[MaxRetransmits]] attributes are set |
| (not null), throw a TypeError. |
| */ |
| test(() => { |
| const pc = new RTCPeerConnection; |
| assert_throws(new TypeError(), () => pc.createDataChannel('', { |
| maxPacketLifeTime: 0, |
| maxRetransmits: 0 |
| })); |
| }, 'createDataChannel with both maxPacketLifeTime and maxRetransmits should throw SyntaxError'); |
| |
| /* |
| 6.2. RTCDataChannel |
| createDataChannel |
| 10. Let channel have an [[Protocol]] 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(() => { |
| const pc = new RTCPeerConnection; |
| const channel = pc.createDataChannel('', { protocol }); |
| assert_equals(channel.protocol, expected); |
| }, `createDataChannel with protocol ${description} should succeed`); |
| } |
| |
| /* |
| 6.2. RTCDataChannel |
| createDataChannel |
| 11. Let channel have an [[Negotiated]] internal slot initialized to option's |
| negotiated member. |
| */ |
| test(() => { |
| const pc = new RTCPeerConnection; |
| const channel = pc.createDataChannel('', { negotiated: true }); |
| assert_equals(channel.negotiated, true); |
| }, 'createDataChannel with negotiated true should succeed'); |
| |
| |
| /* |
| 6.2. RTCDataChannel |
| createDataChannel |
| 10. If id 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]) { |
| test(() => { |
| const pc = new RTCPeerConnection(); |
| const channel = pc.createDataChannel('', { id }); |
| assert_equals(channel.id, id); |
| }, `createDataChannel with id ${id} should succeed`); |
| } |
| |
| for (const id of [-1, 65535, 65536]) { |
| test(() => { |
| const pc = new RTCPeerConnection(); |
| assert_throws(new TypeError(), () => pc.createDataChannel('', { id })); |
| }, `createDataChannel with id ${id} should throw TypeError`); |
| } |
| |
| /* |
| 6.2. RTCDataChannel |
| createDataChannel |
| 12. Let channel have an [[DataChannelPriority]] internal slot initialized |
| to option's priority member. |
| |
| */ |
| test(() => { |
| const pc = new RTCPeerConnection(); |
| const channel = pc.createDataChannel('', { priority: 'high' }); |
| assert_equals(channel.priority, 'high'); |
| }, 'createDataChannel with priority "high" should succeed'); |
| |
| test(() => { |
| const pc = new RTCPeerConnection(); |
| assert_throws(new TypeError(), |
| () => pc.createDataChannel('', { priority: 'invalid' })); |
| }, 'createDataChannel with invalid priority should throw TypeError'); |
| |
| /* |
| 6.2. createDataChannel |
| 13. If [[Negotiated]] is false and [[Label]] is longer than 65535 bytes |
| long, throw a TypeError. */ |
| test(() => { |
| const pc = new RTCPeerConnection(); |
| assert_throws(new TypeError(), () => |
| pc.createDataChannel('', { |
| label: ' '.repeat(65536), |
| negotiated: false |
| })); |
| }, 'createDataChannel with negotiated false and long label should throw TypeError'); |
| |
| /* |
| 6.2. createDataChannel |
| 14. If [[Negotiated]] is false and [[Protocol]] is longer than 65535 bytes long, |
| throw a TypeError. |
| */ |
| test(() => { |
| const pc = new RTCPeerConnection(); |
| assert_throws(new TypeError(), () => |
| pc.createDataChannel('', { |
| protocol: ' '.repeat(65536), |
| negotiated: false |
| })); |
| }, 'createDataChannel with negotiated false and long protocol should throw TypeError'); |
| |
| test(() => { |
| const pc = new RTCPeerConnection(); |
| const label = ' '.repeat(65536) |
| |
| const channel = pc.createDataChannel('', { |
| label, |
| protocol: ' '.repeat(65536), |
| negotiated: true |
| }); |
| |
| assert_equals(channel.label, label); |
| }, 'createDataChannel with negotiated true and long label and long protocol should succeed'); |
| |
| /* |
| 4.4.1.6. Set the RTCSessionSessionDescription |
| 2.2.6. If description is of type "answer" or "pranswer", then run the |
| following steps: |
| 1. If description initiates the establishment of a new SCTP association, |
| as defined in [SCTP-SDP], Sections 10.3 and 10.4, set the value of |
| connection's [[sctpTransport]] internal slot to a newly created RTCSctpTransport. |
| 2. 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.2. createDataChannel |
| 18. If the [[DataChannelId]] slot is null (due to no ID being passed into |
| createDataChannel), 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]. |
| */ |
| promise_test(t => { |
| const pc = new RTCPeerConnection(); |
| t.add_cleanup(() => pc.close()); |
| const channel1 = pc.createDataChannel('channel'); |
| assert_equals(channel1.id, null, |
| 'Expect initial id to be null'); |
| |
| return pc.createOffer() |
| .then(offer => |
| pc.setLocalDescription(offer) |
| .then(() => generateAnswer(offer))) |
| .then(answer => pc.setRemoteDescription(answer)) |
| .then(() => { |
| assert_not_equals(channel1.id, null, |
| 'Expect channel1.id to be assigned'); |
| |
| assert_greater_than_equal(channel1.id, 0, |
| 'Expect channel1.id to be set to valid unsigned short'); |
| |
| assert_less_than(channel1.id, 65535, |
| 'Expect channel1.id to be set to valid unsigned short'); |
| |
| const channel2 = pc.createDataChannel('channel'); |
| |
| assert_not_equals(channel2.id, null, |
| 'Expect channel2.id to be assigned'); |
| |
| assert_greater_than_equal(channel2.id, 0, |
| 'Expect channel2.id to be set to valid unsigned short'); |
| |
| assert_less_than(channel2.id, 65535, |
| 'Expect channel2.id to be set to valid unsigned short'); |
| |
| assert_not_equals(channel2, channel1, |
| 'Expect channels created from same label to be different'); |
| |
| assert_equals(channel2.label, channel1.label, |
| 'Expect different channnels can have the same label but different id'); |
| |
| assert_not_equals(channel2.id, channel1.id, |
| 'Expect different channnels can have the same label but different id'); |
| }); |
| }, 'Channels created after SCTP transport is established should have id assigned'); |
| |
| /* |
| TODO |
| 6.1. createDataChannel |
| 18. If no available ID could be generated, or if the value of the |
| id member of the dictionary is taken by an existing RTCDataChannel, throw |
| a ResourceInUse exception. |
| |
| Untestable |
| 6.1. createDataChannel |
| 16. 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. |
| |
| 20. Create channel's associated underlying data transport and configure |
| it according to the relevant properties of channel. |
| |
| Tested in RTCPeerConnection-onnegotiationneeded.html |
| 21. If channel was the first RTCDataChannel created on connection, update |
| the negotiation-needed flag for connection. |
| |
| Issues |
| w3c/webrtc-pc#1412 |
| ResourceInUse exception is not defined |
| |
| Coverage Report |
| Tested 22 |
| Not Tested 1 |
| Untestable 2 |
| Total 25 |
| */ |
| </script> |