| <!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(); | |
| 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> |