374 lines
12 KiB
HTML
374 lines
12 KiB
HTML
<!doctype html>
|
|
<meta charset=utf-8>
|
|
<meta name="timeout" content="long">
|
|
<title>RTCPeerConnection.prototype.ondatachannel</title>
|
|
<script src="/resources/testharness.js"></script>
|
|
<script src="/resources/testharnessreport.js"></script>
|
|
<script src="RTCPeerConnection-helper.js"></script>
|
|
<script>
|
|
'use strict';
|
|
|
|
// Test is based on the following revision:
|
|
// https://rawgit.com/w3c/webrtc-pc/1cc5bfc3ff18741033d804c4a71f7891242fb5b3/webrtc.html
|
|
|
|
// The following helper functions are called from RTCPeerConnection-helper.js:
|
|
// exchangeIceCandidates
|
|
// exchangeOfferAnswer
|
|
// createDataChannelPair
|
|
|
|
/*
|
|
6.2. RTCDataChannel
|
|
When an underlying data transport is to be announced (the other peer created a channel with
|
|
negotiated unset or set to false), the user agent of the peer that did not initiate the
|
|
creation process MUST queue a task to run the following steps:
|
|
2. Let channel be a newly created RTCDataChannel object.
|
|
7. Set channel's [[ReadyState]] to open (but do not fire the open event, yet).
|
|
8. Fire a datachannel event named datachannel with channel at the RTCPeerConnection object.
|
|
|
|
6.3. RTCDataChannelEvent
|
|
Firing a datachannel event named e with an RTCDataChannel channel means that an event with the
|
|
name e, which does not bubble (except where otherwise stated) and is not cancelable (except
|
|
where otherwise stated), and which uses the RTCDataChannelEvent interface with the channel
|
|
attribute set to channel, MUST be created and dispatched at the given target.
|
|
|
|
interface RTCDataChannelEvent : Event {
|
|
readonly attribute RTCDataChannel channel;
|
|
};
|
|
*/
|
|
promise_test(async (t) => {
|
|
const resolver = new Resolver();
|
|
const pc1 = new RTCPeerConnection();
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
let eventCount = 0;
|
|
|
|
pc2.ondatachannel = t.step_func((event) => {
|
|
eventCount++;
|
|
assert_equals(eventCount, 1,
|
|
'Expect data channel event to fire exactly once');
|
|
|
|
assert_true(event instanceof RTCDataChannelEvent,
|
|
'Expect event to be instance of RTCDataChannelEvent');
|
|
|
|
assert_equals(event.bubbles, false);
|
|
assert_equals(event.cancelable, false);
|
|
|
|
const dc = event.channel;
|
|
assert_true(dc instanceof RTCDataChannel,
|
|
'Expect channel to be instance of RTCDataChannel');
|
|
|
|
// The channel should be in the 'open' state already.
|
|
// See: https://github.com/w3c/webrtc-pc/pull/1851
|
|
assert_equals(dc.readyState, 'open',
|
|
'Expect channel ready state to be open');
|
|
|
|
resolver.resolve();
|
|
});
|
|
|
|
pc1.createDataChannel('fire-me!');
|
|
|
|
exchangeIceCandidates(pc1, pc2);
|
|
await exchangeOfferAnswer(pc1, pc2);
|
|
|
|
await resolver;
|
|
}, 'Data channel event should fire when new data channel is announced to the remote peer');
|
|
|
|
/*
|
|
Since the channel should be in the 'open' state when dispatching via the 'datachannel' event,
|
|
we should be able to send data in the event handler.
|
|
*/
|
|
promise_test(async (t) => {
|
|
const resolver = new Resolver();
|
|
const pc1 = new RTCPeerConnection();
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
const message = 'meow meow!';
|
|
|
|
pc2.ondatachannel = t.step_func((event) => {
|
|
const dc2 = event.channel;
|
|
dc2.send(message);
|
|
});
|
|
|
|
const dc1 = pc1.createDataChannel('fire-me!');
|
|
dc1.onmessage = t.step_func((event) => {
|
|
assert_equals(event.data, message,
|
|
'Received data should be equal to sent data');
|
|
|
|
resolver.resolve();
|
|
});
|
|
|
|
exchangeIceCandidates(pc1, pc2);
|
|
await exchangeOfferAnswer(pc1, pc2);
|
|
|
|
await resolver;
|
|
}, 'Should be able to send data in a datachannel event handler');
|
|
|
|
/*
|
|
6.2. RTCDataChannel
|
|
When an underlying data transport is to be announced (the other peer created a channel with
|
|
negotiated unset or set to false), the user agent of the peer that did not initiate the
|
|
creation process MUST queue a task to run the following steps:
|
|
8. Fire a datachannel event named datachannel with channel at the RTCPeerConnection object.
|
|
9. If the channel's [[ReadyState]] is still open, announce the data channel as open.
|
|
*/
|
|
promise_test(async (t) => {
|
|
const resolver = new Resolver();
|
|
const pc1 = new RTCPeerConnection();
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
pc2.ondatachannel = t.step_func((event) => {
|
|
const dc = event.channel;
|
|
dc.onopen = t.step_func(() => {
|
|
assert_unreached('Open event should not fire');
|
|
});
|
|
|
|
// This should prevent triggering the 'open' event
|
|
dc.close();
|
|
|
|
// Wait a bit to ensure the 'open' event does NOT fire
|
|
t.step_timeout(() => resolver.resolve(), 500);
|
|
});
|
|
|
|
pc1.createDataChannel('fire-me!');
|
|
|
|
exchangeIceCandidates(pc1, pc2);
|
|
await exchangeOfferAnswer(pc1, pc2);
|
|
|
|
await resolver;
|
|
}, 'Open event should not be raised when closing the channel in the datachannel event');
|
|
|
|
// Added this test as a result of the discussion in
|
|
// https://github.com/w3c/webrtc-pc/pull/1851#discussion_r185976747
|
|
promise_test(async (t) => {
|
|
const resolver = new Resolver();
|
|
const pc1 = new RTCPeerConnection();
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
pc2.ondatachannel = t.step_func((event) => {
|
|
const dc = event.channel;
|
|
dc.onopen = t.step_func((event) => {
|
|
resolver.resolve();
|
|
});
|
|
|
|
// This should NOT prevent triggering the 'open' event since it enqueues at least two tasks
|
|
t.step_timeout(() => {
|
|
t.step_timeout(() => {
|
|
dc.close()
|
|
}, 1);
|
|
}, 1);
|
|
});
|
|
|
|
pc1.createDataChannel('fire-me!');
|
|
|
|
exchangeIceCandidates(pc1, pc2);
|
|
await exchangeOfferAnswer(pc1, pc2);
|
|
|
|
await resolver;
|
|
}, 'Open event should be raised when closing the channel in the datachannel event after ' +
|
|
'enqueuing a task');
|
|
|
|
|
|
/*
|
|
Combination of the two tests above (send and close).
|
|
*/
|
|
promise_test(async (t) => {
|
|
const resolver = new Resolver();
|
|
const pc1 = new RTCPeerConnection();
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
const message = 'meow meow!';
|
|
|
|
pc2.ondatachannel = t.step_func((event) => {
|
|
const dc2 = event.channel;
|
|
dc2.onopen = t.step_func(() => {
|
|
assert_unreached('Open event should not fire');
|
|
});
|
|
|
|
// This should send but still prevent triggering the 'open' event
|
|
dc2.send(message);
|
|
dc2.close();
|
|
});
|
|
|
|
const dc1 = pc1.createDataChannel('fire-me!');
|
|
dc1.onmessage = t.step_func((event) => {
|
|
assert_equals(event.data, message,
|
|
'Received data should be equal to sent data');
|
|
|
|
resolver.resolve();
|
|
});
|
|
|
|
exchangeIceCandidates(pc1, pc2);
|
|
await exchangeOfferAnswer(pc1, pc2);
|
|
|
|
await resolver;
|
|
}, 'Open event should not be raised when sending and immediately closing the channel in the ' +
|
|
'datachannel event');
|
|
|
|
/*
|
|
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;
|
|
...
|
|
};
|
|
|
|
When an underlying data transport is to be announced (the other peer created a channel with
|
|
negotiated unset or set to false), the user agent of the peer that did not initiate the
|
|
creation process MUST queue a task to run the following steps:
|
|
2. Let channel be a newly created RTCDataChannel object.
|
|
3. Let configuration be an information bundle received from the other peer as a part of the
|
|
process to establish the underlying data transport described by the WebRTC DataChannel
|
|
Protocol specification [RTCWEB-DATA-PROTOCOL].
|
|
4. Initialize channel's [[DataChannelLabel]], [[Ordered]], [[MaxPacketLifeTime]],
|
|
[[MaxRetransmits]], [[DataChannelProtocol]], and [[DataChannelId]] internal slots to the
|
|
corresponding values in configuration.
|
|
5. Initialize channel's [[Negotiated]] internal slot to false.
|
|
7. Set channel's [[ReadyState]] slot to connecting.
|
|
8. Fire a datachannel event named datachannel with channel at the RTCPeerConnection object.
|
|
|
|
Note: More exhaustive tests are defined in RTCDataChannel-dcep
|
|
*/
|
|
|
|
promise_test(async (t) => {
|
|
const resolver = new Resolver();
|
|
const pc1 = new RTCPeerConnection();
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
const dc1 = pc1.createDataChannel('test', {
|
|
ordered: false,
|
|
maxRetransmits: 1,
|
|
protocol: 'custom'
|
|
});
|
|
|
|
assert_equals(dc1.label, 'test');
|
|
assert_equals(dc1.ordered, false);
|
|
assert_equals(dc1.maxPacketLifeTime, null);
|
|
assert_equals(dc1.maxRetransmits, 1);
|
|
assert_equals(dc1.protocol, 'custom');
|
|
assert_equals(dc1.negotiated, false);
|
|
|
|
pc2.ondatachannel = t.step_func((event) => {
|
|
const dc2 = event.channel;
|
|
assert_true(dc2 instanceof RTCDataChannel,
|
|
'Expect channel to be instance of RTCDataChannel');
|
|
|
|
assert_equals(dc2.label, 'test');
|
|
assert_equals(dc2.ordered, false);
|
|
assert_equals(dc2.maxPacketLifeTime, null);
|
|
assert_equals(dc2.maxRetransmits, 1);
|
|
assert_equals(dc2.protocol, 'custom');
|
|
assert_equals(dc2.negotiated, false);
|
|
assert_equals(dc2.id, dc1.id);
|
|
|
|
resolver.resolve();
|
|
});
|
|
|
|
exchangeIceCandidates(pc1, pc2);
|
|
await exchangeOfferAnswer(pc1, pc2);
|
|
|
|
await resolver;
|
|
}, 'In-band negotiated channel created on remote peer should match the same configuration as local ' +
|
|
'peer');
|
|
|
|
promise_test(async (t) => {
|
|
const resolver = new Resolver();
|
|
const pc1 = new RTCPeerConnection();
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
const dc1 = pc1.createDataChannel('');
|
|
|
|
assert_equals(dc1.label, '');
|
|
assert_equals(dc1.ordered, true);
|
|
assert_equals(dc1.maxPacketLifeTime, null);
|
|
assert_equals(dc1.maxRetransmits, null);
|
|
assert_equals(dc1.protocol, '');
|
|
assert_equals(dc1.negotiated, false);
|
|
|
|
pc2.ondatachannel = t.step_func((event) => {
|
|
const dc2 = event.channel;
|
|
assert_true(dc2 instanceof RTCDataChannel,
|
|
'Expect channel to be instance of RTCDataChannel');
|
|
|
|
assert_equals(dc2.label, '');
|
|
assert_equals(dc2.ordered, true);
|
|
assert_equals(dc2.maxPacketLifeTime, null);
|
|
assert_equals(dc2.maxRetransmits, null);
|
|
assert_equals(dc2.protocol, '');
|
|
assert_equals(dc2.negotiated, false);
|
|
assert_equals(dc2.id, dc1.id);
|
|
|
|
resolver.resolve();
|
|
});
|
|
|
|
exchangeIceCandidates(pc1, pc2);
|
|
await exchangeOfferAnswer(pc1, pc2);
|
|
|
|
await resolver;
|
|
}, 'In-band negotiated channel created on remote peer should match the same (default) ' +
|
|
'configuration as local peer');
|
|
|
|
/*
|
|
6.2. RTCDataChannel
|
|
Dictionary RTCDataChannelInit Members
|
|
negotiated
|
|
The default value of false tells the user agent to announce the
|
|
channel in-band and instruct the other peer to dispatch a corresponding
|
|
RTCDataChannel object. If set to true, it is up to the application
|
|
to negotiate the channel and create a RTCDataChannel object with the
|
|
same id at the other peer.
|
|
*/
|
|
promise_test(async (t) => {
|
|
const resolver = new Resolver();
|
|
const pc1 = new RTCPeerConnection();
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
pc2.ondatachannel = t.unreached_func('datachannel event should not be fired');
|
|
|
|
pc1.createDataChannel('test', {
|
|
negotiated: true,
|
|
id: 42
|
|
});
|
|
|
|
exchangeIceCandidates(pc1, pc2);
|
|
await exchangeOfferAnswer(pc1, pc2);
|
|
|
|
// Wait a bit to ensure the 'datachannel' event does NOT fire
|
|
t.step_timeout(() => resolver.resolve(), 500);
|
|
await resolver;
|
|
}, 'Negotiated channel should not fire datachannel event on remote peer');
|
|
|
|
/*
|
|
Non-testable
|
|
6.2. RTCDataChannel
|
|
When an underlying data transport is to be announced
|
|
1. If the associated RTCPeerConnection object's [[isClosed]] slot
|
|
is true, abort these steps.
|
|
|
|
The above step is not testable because to reach it we would have to
|
|
close the peer connection just between receiving the in-band negotiated data
|
|
channel via DCEP and firing the datachannel event.
|
|
*/
|
|
</script>
|