771 lines
26 KiB
HTML
771 lines
26 KiB
HTML
<!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, 'arraybuffer');
|
|
}, '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, 'arraybuffer');
|
|
|
|
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`);
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
const pc2 = new RTCPeerConnection();
|
|
const stream = await getNoiseStream({audio: true, video: true});
|
|
stream.getTracks().forEach((track) => pc1.addTrack(track, stream));
|
|
await createDataChannelPair(t, options, pc1, pc2);
|
|
|
|
await pc1.setLocalDescription();
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
}, `Renegotiation with audio/video and ${mode} should work properly`);
|
|
|
|
/*
|
|
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>
|