blob: 450a25002ed2212e7f1dd5cb97ff985e3f957aa1 [file] [log] [blame]
<!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>