mirror of
https://github.com/servo/servo.git
synced 2025-06-25 17:44:33 +01:00
396 lines
15 KiB
HTML
396 lines
15 KiB
HTML
<!doctype html>
|
|
<meta charset=utf-8>
|
|
<meta name="timeout" content="long">
|
|
<title>RTCPeerConnection.prototype.iceConnectionState</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 editor draft:
|
|
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
|
|
|
/*
|
|
4.3.2. Interface Definition
|
|
interface RTCPeerConnection : EventTarget {
|
|
...
|
|
readonly attribute RTCIceConnectionState iceConnectionState;
|
|
attribute EventHandler oniceconnectionstatechange;
|
|
};
|
|
|
|
4.4.4 RTCIceConnectionState Enum
|
|
enum RTCIceConnectionState {
|
|
"new",
|
|
"checking",
|
|
"connected",
|
|
"completed",
|
|
"failed",
|
|
"disconnected",
|
|
"closed"
|
|
};
|
|
|
|
5.6. RTCIceTransport Interface
|
|
interface RTCIceTransport {
|
|
readonly attribute RTCIceTransportState state;
|
|
attribute EventHandler onstatechange;
|
|
|
|
...
|
|
};
|
|
|
|
enum RTCIceTransportState {
|
|
"new",
|
|
"checking",
|
|
"connected",
|
|
"completed",
|
|
"failed",
|
|
"disconnected",
|
|
"closed"
|
|
};
|
|
*/
|
|
|
|
/*
|
|
4.4.4 RTCIceConnectionState Enum
|
|
new
|
|
Any of the RTCIceTransports are in the new state and none of them
|
|
are in the checking, failed or disconnected state, or all
|
|
RTCIceTransport s are in the closed state.
|
|
*/
|
|
test(t => {
|
|
const pc = new RTCPeerConnection();
|
|
assert_equals(pc.iceConnectionState, 'new');
|
|
}, 'Initial iceConnectionState should be new');
|
|
|
|
test(t => {
|
|
const pc = new RTCPeerConnection();
|
|
pc.close();
|
|
assert_equals(pc.iceConnectionState, 'closed');
|
|
}, 'Closing the connection should set iceConnectionState to closed');
|
|
|
|
/*
|
|
4.4.4 RTCIceConnectionState Enum
|
|
checking
|
|
Any of the RTCIceTransport s are in the checking state and none of
|
|
them are in the failed or disconnected state.
|
|
|
|
connected
|
|
All RTCIceTransport s are in the connected, completed or closed state
|
|
and at least one of them is in the connected state.
|
|
|
|
completed
|
|
All RTCIceTransport s are in the completed or closed state and at least
|
|
one of them is in the completed state.
|
|
|
|
checking
|
|
The RTCIceTransport has received at least one remote candidate and
|
|
is checking candidate pairs and has either not yet found a connection
|
|
or consent checks [RFC7675] have failed on all previously successful
|
|
candidate pairs. In addition to checking, it may also still be gathering.
|
|
|
|
5.6. enum RTCIceTransportState
|
|
connected
|
|
The RTCIceTransport has found a usable connection, but is still
|
|
checking other candidate pairs to see if there is a better connection.
|
|
It may also still be gathering and/or waiting for additional remote
|
|
candidates. If consent checks [RFC7675] fail on the connection in use,
|
|
and there are no other successful candidate pairs available, then the
|
|
state transitions to "checking" (if there are candidate pairs remaining
|
|
to be checked) or "disconnected" (if there are no candidate pairs to
|
|
check, but the peer is still gathering and/or waiting for additional
|
|
remote candidates).
|
|
|
|
completed
|
|
The RTCIceTransport has finished gathering, received an indication that
|
|
there are no more remote candidates, finished checking all candidate
|
|
pairs and found a connection. If consent checks [RFC7675] subsequently
|
|
fail on all successful candidate pairs, the state transitions to "failed".
|
|
*/
|
|
async_test(t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
let had_checking = false;
|
|
|
|
const onIceConnectionStateChange = t.step_func(() => {
|
|
const {iceConnectionState} = pc1;
|
|
if (iceConnectionState === 'checking') {
|
|
had_checking = true;
|
|
} else if (iceConnectionState === 'connected' ||
|
|
iceConnectionState === 'completed') {
|
|
assert_true(had_checking, 'state should pass checking before' +
|
|
' reaching connected or completed');
|
|
t.done();
|
|
} else if (iceConnectionState === 'failed') {
|
|
assert_unreached("ICE should not fail");
|
|
}
|
|
});
|
|
|
|
pc1.createDataChannel('test');
|
|
|
|
pc1.addEventListener('iceconnectionstatechange', onIceConnectionStateChange);
|
|
|
|
exchangeIceCandidates(pc1, pc2);
|
|
exchangeOfferAnswer(pc1, pc2);
|
|
}, 'connection with one data channel should eventually have connected or ' +
|
|
'completed connection state');
|
|
|
|
async_test(t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
const onIceConnectionStateChange = t.step_func(() => {
|
|
const { iceConnectionState } = pc1;
|
|
|
|
if(iceConnectionState === 'checking') {
|
|
const iceTransport = pc1.sctp.transport.iceTransport;
|
|
|
|
assert_equals(iceTransport.state, 'checking',
|
|
'Expect ICE transport to be in checking state when' +
|
|
' iceConnectionState is checking');
|
|
|
|
} else if(iceConnectionState === 'connected') {
|
|
const iceTransport = pc1.sctp.transport.iceTransport;
|
|
|
|
assert_equals(iceTransport.state, 'connected',
|
|
'Expect ICE transport to be in connected state when' +
|
|
' iceConnectionState is connected');
|
|
t.done();
|
|
} else if(iceConnectionState === 'completed') {
|
|
const iceTransport = pc1.sctp.transport.iceTransport;
|
|
|
|
assert_equals(iceTransport.state, 'completed',
|
|
'Expect ICE transport to be in connected state when' +
|
|
' iceConnectionState is completed');
|
|
t.done();
|
|
} else if (iceConnectionState === 'failed') {
|
|
assert_unreached("ICE should not fail");
|
|
}
|
|
});
|
|
|
|
pc1.createDataChannel('test');
|
|
|
|
assert_equals(pc1.oniceconnectionstatechange, null,
|
|
'Expect connection to have iceconnectionstatechange event');
|
|
|
|
pc1.addEventListener('iceconnectionstatechange', onIceConnectionStateChange);
|
|
|
|
exchangeIceCandidates(pc1, pc2);
|
|
exchangeOfferAnswer(pc1, pc2);
|
|
}, 'connection with one data channel should eventually ' +
|
|
'have connected connection state');
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
const stream = await getNoiseStream({audio: true});
|
|
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
|
|
stream.getTracks().forEach(track => pc1.addTrack(track, stream));
|
|
|
|
exchangeIceCandidates(pc1, pc2);
|
|
exchangeOfferAnswer(pc1, pc2);
|
|
await listenToIceConnected(pc1);
|
|
}, 'connection with audio track should eventually ' +
|
|
'have connected connection state');
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
const stream = await getNoiseStream({audio: true, video:true});
|
|
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
|
|
stream.getTracks().forEach(track => pc1.addTrack(track, stream));
|
|
|
|
exchangeIceCandidates(pc1, pc2);
|
|
exchangeOfferAnswer(pc1, pc2);
|
|
await listenToIceConnected(pc1);
|
|
}, 'connection with audio and video tracks should eventually ' +
|
|
'have connected connection state');
|
|
|
|
promise_test(async t => {
|
|
const caller = new RTCPeerConnection();
|
|
t.add_cleanup(() => caller.close());
|
|
const callee = new RTCPeerConnection();
|
|
t.add_cleanup(() => callee.close());
|
|
|
|
caller.addTransceiver('audio', {direction:'recvonly'});
|
|
const stream = await getNoiseStream({audio:true});
|
|
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
|
|
const [track] = stream.getTracks();
|
|
callee.addTrack(track, stream);
|
|
exchangeIceCandidates(caller, callee);
|
|
await exchangeOfferAnswer(caller, callee);
|
|
|
|
assert_equals(caller.getTransceivers().length, 1);
|
|
const [transceiver] = caller.getTransceivers();
|
|
assert_equals(transceiver.currentDirection, 'recvonly');
|
|
|
|
await listenToIceConnected(caller);
|
|
}, 'ICE can connect in a recvonly usecase');
|
|
|
|
/*
|
|
TODO
|
|
4.4.4 RTCIceConnectionState Enum
|
|
failed
|
|
Any of the RTCIceTransport s are in the failed state.
|
|
|
|
disconnected
|
|
Any of the RTCIceTransport s are in the disconnected state and none of
|
|
them are in the failed state.
|
|
|
|
closed
|
|
The RTCPeerConnection object's [[ isClosed]] slot is true.
|
|
|
|
5.6. enum RTCIceTransportState
|
|
new
|
|
The RTCIceTransport is gathering candidates and/or waiting for
|
|
remote candidates to be supplied, and has not yet started checking.
|
|
|
|
failed
|
|
The RTCIceTransport has finished gathering, received an indication that
|
|
there are no more remote candidates, finished checking all candidate pairs,
|
|
and all pairs have either failed connectivity checks or have lost consent.
|
|
|
|
disconnected
|
|
The ICE Agent has determined that connectivity is currently lost for this
|
|
RTCIceTransport . This is more aggressive than failed, and may trigger
|
|
intermittently (and resolve itself without action) on a flaky network.
|
|
The way this state is determined is implementation dependent.
|
|
|
|
Examples include:
|
|
Losing the network interface for the connection in use.
|
|
Repeatedly failing to receive a response to STUN requests.
|
|
|
|
Alternatively, the RTCIceTransport has finished checking all existing
|
|
candidates pairs and failed to find a connection (or consent checks
|
|
[RFC7675] once successful, have now failed), but it is still gathering
|
|
and/or waiting for additional remote candidates.
|
|
|
|
closed
|
|
The RTCIceTransport has shut down and is no longer responding to STUN requests.
|
|
*/
|
|
|
|
for (let bundle_policy of ['balanced', 'max-bundle', 'max-compat']) {
|
|
|
|
|
|
promise_test(async t => {
|
|
const caller = new RTCPeerConnection({bundlePolicy: bundle_policy});
|
|
t.add_cleanup(() => caller.close());
|
|
const stream = await getNoiseStream(
|
|
{audio: true, video:true});
|
|
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
|
|
const [track1, track2] = stream.getTracks();
|
|
const sender1 = caller.addTrack(track1);
|
|
const sender2 = caller.addTrack(track2);
|
|
caller.createDataChannel('datachannel');
|
|
const callee = new RTCPeerConnection();
|
|
t.add_cleanup(() => callee.close());
|
|
exchangeIceCandidates(caller, callee);
|
|
const offer = await caller.createOffer();
|
|
await caller.setLocalDescription(offer);
|
|
const [caller_transceiver1, caller_transceiver2] = caller.getTransceivers();
|
|
assert_equals(sender1.transport, caller_transceiver1.sender.transport);
|
|
await callee.setRemoteDescription(offer);
|
|
const [callee_transceiver1, callee_transceiver2] = callee.getTransceivers();
|
|
const answer = await callee.createAnswer();
|
|
await callee.setLocalDescription(answer);
|
|
await caller.setRemoteDescription(answer);
|
|
// At this point, we should have a single ICE transport, and it
|
|
// should eventually get to the "connected" state.
|
|
await waitForState(caller_transceiver1.receiver.transport.iceTransport,
|
|
'connected');
|
|
// The PeerConnection's iceConnectionState should therefore be 'connected'
|
|
assert_equals(caller.iceConnectionState, 'connected',
|
|
'PC.iceConnectionState:');
|
|
}, 'iceConnectionState changes at the right time, with bundle policy ' +
|
|
bundle_policy);
|
|
}
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
|
|
pc1.candidateBuffer = [];
|
|
pc2.onicecandidate = e => {
|
|
// Don't add candidate if candidate buffer is already used
|
|
if (pc1.candidateBuffer) {
|
|
pc1.candidateBuffer.push(e.candidate)
|
|
}
|
|
};
|
|
pc1.iceStates = [pc1.iceConnectionState];
|
|
pc2.iceStates = [pc2.iceConnectionState];
|
|
pc1.oniceconnectionstatechange = () => {
|
|
pc1.iceStates.push(pc1.iceConnectionState);
|
|
};
|
|
pc2.oniceconnectionstatechange = () => {
|
|
pc2.iceStates.push(pc2.iceConnectionState);
|
|
};
|
|
|
|
const localStream = await getNoiseStream({audio: true, video: true});
|
|
const localStream2 = await getNoiseStream({audio: true, video: true});
|
|
const remoteStream = await getNoiseStream({audio: true, video: true});
|
|
for (const stream of [localStream, localStream2, remoteStream]) {
|
|
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
|
|
}
|
|
localStream.getTracks().forEach(t => pc1.addTrack(t, localStream));
|
|
localStream2.getTracks().forEach(t => pc1.addTrack(t, localStream2));
|
|
remoteStream.getTracks().forEach(t => pc2.addTrack(t, remoteStream));
|
|
const offer = await pc1.createOffer();
|
|
await pc2.setRemoteDescription(offer);
|
|
await pc1.setLocalDescription(offer);
|
|
const answer = await pc2.createAnswer();
|
|
await pc2.setLocalDescription(answer);
|
|
await pc1.setRemoteDescription(answer);
|
|
pc1.candidateBuffer.forEach(c => pc1.addIceCandidate(c));
|
|
delete pc1.candidateBuffer;
|
|
await listenToIceConnected(pc1);
|
|
await listenToIceConnected(pc2);
|
|
// While we're waiting for pc2, pc1 may or may not have transitioned
|
|
// to "completed" state, so allow for both cases.
|
|
if (pc1.iceStates.length == 3) {
|
|
assert_array_equals(pc1.iceStates, ['new', 'checking', 'connected']);
|
|
} else {
|
|
assert_array_equals(pc1.iceStates, ['new', 'checking', 'connected',
|
|
'completed']);
|
|
}
|
|
assert_array_equals(pc2.iceStates, ['new', 'checking', 'connected']);
|
|
}, 'Responder ICE connection state behaves as expected');
|
|
|
|
/*
|
|
Test case for step 11 of PeerConnection.close().
|
|
...
|
|
11. Set connection's ICE connection state to "closed". This does not invoke
|
|
the "update the ICE connection state" procedure, and does not fire any
|
|
event.
|
|
...
|
|
*/
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
const stream = await getNoiseStream({ audio: true });
|
|
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
|
|
|
|
stream.getTracks().forEach(track => pc1.addTrack(track, stream));
|
|
exchangeIceCandidates(pc1, pc2);
|
|
exchangeOfferAnswer(pc1, pc2);
|
|
await listenToIceConnected(pc2);
|
|
|
|
pc2.oniceconnectionstatechange = t.unreached_func();
|
|
pc2.close();
|
|
assert_equals(pc2.iceConnectionState, 'closed');
|
|
await new Promise(r => t.step_timeout(r, 100));
|
|
}, 'Closing a PeerConnection should not fire iceconnectionstatechange event');
|
|
|
|
</script>
|