<!doctype html>
<meta charset=utf-8>
<meta name="timeout" content="long">
<title>RTCDataChannel.prototype.close</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="RTCPeerConnection-helper.js"></script>
<script>
'use strict';
for (const options of [{}, {negotiated: true, id: 0}]) {
const mode = `${options.negotiated? "negotiated " : ""}datachannel`;
promise_test(async t => {
const [channel1, channel2] = await createDataChannelPair(t, options);
const haveClosed = new Promise(r => channel2.onclose = r);
let closingSeen = false;
channel1.onclosing = t.unreached_func();
channel2.onclosing = () => {
assert_equals(channel2.readyState, 'closing');
closingSeen = true;
};
channel2.addEventListener('error', t.unreached_func());
channel1.close();
await haveClosed;
assert_equals(channel2.readyState, 'closed');
assert_true(closingSeen, 'Closing event was seen');
}, `Close ${mode} causes onclosing and onclose to be called`);
promise_test(async t => {
// This is the same test as above, but using addEventListener
// rather than the "onclose" attribute.
const [channel1, channel2] = await createDataChannelPair(t, options);
const haveClosed = new Promise(r => channel2.addEventListener('close', r));
let closingSeen = false;
channel1.addEventListener('closing', t.unreached_func());
channel2.addEventListener('closing', () => {
assert_equals(channel2.readyState, 'closing');
closingSeen = true;
});
channel2.addEventListener('error', t.unreached_func());
channel1.close();
await haveClosed;
assert_equals(channel2.readyState, 'closed');
assert_true(closingSeen, 'Closing event was seen');
}, `Close ${mode} causes closing and close event to be called`);
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const mainChannel1 = pc1.createDataChannel('not-counted', options);
exchangeIceCandidates(pc1, pc2);
await exchangeOfferAnswer(pc1, pc2);
if (!options.negotiated) {
await new Promise(r => pc2.ondatachannel = r);
}
for (let i = 1; i <= 10; i++) {
if ('id' in options) {
options.id = i;
}
const sender = pc1.createDataChannel(`dc ${i}`, options);
const senderOpen = new Promise(r => sender.onopen = r);
let receiver;
if (options.negotiated) {
receiver = pc2.createDataChannel(`dc ${i}`, options);
} else {
receiver = (await new Promise(r => pc2.ondatachannel = r)).channel;
}
receiver.onmessage = ({data}) => receiver.send(data);
await senderOpen;
sender.send(`ping ${i}`);
const {data} = await new Promise(r => sender.onmessage = r);
assert_equals(data, `ping ${i}`);
sender.close();
await new Promise(r => receiver.onclose = r);
}
}, `Repeated open/send/echo/close ${mode} works`);
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const [channel1, channel2] = await createDataChannelPair(t, options, pc1);
const events = [];
let error = null;
channel2.addEventListener('error', t.step_func(event => {
events.push('error');
assert_true(event instanceof RTCErrorEvent);
error = event.error;
}));
const haveClosed = new Promise(r => channel2.addEventListener('close', () => {
events.push('close');
r();
}));
pc1.close();
await haveClosed;
// Error should fire before close.
assert_array_equals(events, ['error', 'close']);
assert_true(error instanceof RTCError);
assert_equals(error.name, 'OperationError');
assert_equals(error.errorDetail, 'sctp-failure');
// Expects the sctpErrorCode is either null or 12 (User-Initiated Abort) as it is
// optional in the SCTP specification.
assert_in_array(error.sctpCauseCode, [null, 12]);
}, `Close peerconnection causes close event and error to be called on ${mode}`);
promise_test(async t => {
let pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
let [channel1, channel2] = await createDataChannelPair(t, options, pc1);
// The expected sequence of events when closing a DC is that
// channel1 goes to closing, channel2 fires onclose, and when
// the close is confirmed, channel1 fires onclose.
// After that, no more events should fire.
channel1.onerror = t.unreached_func();
let close2Handler = new Promise(resolve => {
channel2.onclose = event => {
resolve();
};
});
let close1Handler = new Promise(resolve => {
channel1.onclose = event => {
resolve();
};
});
channel1.close();
await close2Handler;
await close1Handler;
channel1.onclose = t.unreached_func();
channel2.onclose = t.unreached_func();
channel2.onerror = t.unreached_func();
pc1.close();
await new Promise(resolve => t.step_timeout(resolve, 10));
}, `Close peerconnection after ${mode} close causes no events`);
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
pc1.createDataChannel('not-counted', options);
const tokenDataChannel = new Promise(resolve => {
pc2.ondatachannel = resolve;
});
exchangeIceCandidates(pc1, pc2);
await exchangeOfferAnswer(pc1, pc2);
if (!options.negotiated) {
await tokenDataChannel;
}
let closeExpectedCount = 0;
let errorExpectedCount = 0;
let resolveCountIsZero;
let waitForCountIsZero = new Promise(resolve => {
resolveCountIsZero = resolve;
});
for (let i = 1; i <= 10; i++) {
if ('id' in options) {
options.id = i;
}
pc1.createDataChannel('', options);
if (options.negotiated) {
const channel = pc2.createDataChannel('', options);
channel.addEventListener('error', t.step_func(event => {
assert_true(event instanceof RTCErrorEvent, 'error event ' + event);
errorExpectedCount -= 1;
}));
channel.addEventListener('close', t.step_func(event => {
closeExpectedCount -= 1;
if (closeExpectedCount == 0) {
resolveCountIsZero();
}
}));
} else {
await new Promise(resolve => {
pc2.ondatachannel = ({channel}) => {
channel.addEventListener('error', t.step_func(event => {
assert_true(event instanceof RTCErrorEvent);
errorExpectedCount -= 1;
}));
channel.addEventListener('close', t.step_func(event => {
closeExpectedCount -= 1;
if (closeExpectedCount == 0) {
resolveCountIsZero();
}
}));
resolve();
}
});
}
++closeExpectedCount;
++errorExpectedCount;
}
assert_equals(closeExpectedCount, 10);
// We have to wait until SCTP is connected before we close, otherwise
// there will be no signal.
// The state is not available under Plan B, and unreliable on negotiated
// channels.
// TODO(bugs.webrtc.org/12259): Remove dependency on "negotiated"
if (pc1.sctp && !options.negotiated) {
waitForState(pc1.sctp, 'connected');
} else {
// Under plan B, we don't have a dtls transport to wait on, so just
// wait a bit.
await new Promise(resolve => t.step_timeout(resolve, 100));
}
pc1.close();
await waitForCountIsZero;
assert_equals(closeExpectedCount, 0);
assert_equals(errorExpectedCount, 0);
}, `Close peerconnection causes close event and error on many channels, ${mode}`);
}
</script>