mirror of
https://github.com/servo/servo.git
synced 2025-10-15 16:00:28 +01:00
278 lines
10 KiB
HTML
278 lines
10 KiB
HTML
<!doctype html>
|
|
<meta charset=utf-8>
|
|
<title>RTCPeerConnection.prototype.iceGatheringState</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
|
|
|
|
// The following helper functions are called from RTCPeerConnection-helper.js:
|
|
// exchangeOfferAnswer
|
|
// exchangeIceCandidates
|
|
// generateAudioReceiveOnlyOffer
|
|
|
|
/*
|
|
4.3.2. Interface Definition
|
|
interface RTCPeerConnection : EventTarget {
|
|
...
|
|
readonly attribute RTCIceGatheringState iceGatheringState;
|
|
attribute EventHandler onicegatheringstatechange;
|
|
};
|
|
|
|
4.4.2. RTCIceGatheringState Enum
|
|
enum RTCIceGatheringState {
|
|
"new",
|
|
"gathering",
|
|
"complete"
|
|
};
|
|
|
|
5.6. RTCIceTransport Interface
|
|
interface RTCIceTransport {
|
|
readonly attribute RTCIceGathererState gatheringState;
|
|
...
|
|
};
|
|
|
|
enum RTCIceGathererState {
|
|
"new",
|
|
"gathering",
|
|
"complete"
|
|
};
|
|
*/
|
|
|
|
/*
|
|
4.4.2. RTCIceGatheringState Enum
|
|
new
|
|
Any of the RTCIceTransport s are in the new gathering state and
|
|
none of the transports are in the gathering state, or there are
|
|
no transports.
|
|
*/
|
|
test(t => {
|
|
const pc = new RTCPeerConnection();
|
|
assert_equals(pc.iceGatheringState, 'new');
|
|
}, 'Initial iceGatheringState should be new');
|
|
|
|
async_test(t => {
|
|
const pc = new RTCPeerConnection();
|
|
|
|
t.add_cleanup(() => pc.close());
|
|
|
|
let reachedGathering = false;
|
|
const onIceGatheringStateChange = t.step_func(() => {
|
|
const { iceGatheringState } = pc;
|
|
|
|
if(iceGatheringState === 'gathering') {
|
|
reachedGathering = true;
|
|
} else if(iceGatheringState === 'complete') {
|
|
assert_true(reachedGathering, 'iceGatheringState should reach gathering before complete');
|
|
t.done();
|
|
}
|
|
});
|
|
|
|
assert_equals(pc.onicegatheringstatechange, null,
|
|
'Expect connection to have icegatheringstatechange event');
|
|
assert_equals(pc.iceGatheringState, 'new');
|
|
|
|
pc.addEventListener('icegatheringstatechange', onIceGatheringStateChange);
|
|
|
|
generateAudioReceiveOnlyOffer(pc)
|
|
.then(offer => pc.setLocalDescription(offer))
|
|
.then(err => t.step_func(err =>
|
|
assert_unreached(`Unhandled rejection ${err.name}: ${err.message}`)));
|
|
}, 'iceGatheringState should eventually become complete after setLocalDescription');
|
|
|
|
const iceGatheringStateTransitions = async (pc, ...states) => {
|
|
for (const state of states) {
|
|
await new Promise((resolve, reject) => {
|
|
pc.addEventListener('icegatheringstatechange', () => {
|
|
if (pc.iceGatheringState == state) {
|
|
resolve();
|
|
} else {
|
|
reject(`Unexpected gathering state: ${pc.iceGatheringState}, was expecting ${state}`);
|
|
}
|
|
}, {once: true});
|
|
});
|
|
}
|
|
};
|
|
|
|
const initialOfferAnswer = async (pc1, pc2, offerOptions) => {
|
|
await pc1.setLocalDescription(
|
|
await pc1.createOffer(offerOptions));
|
|
const pc1Transitions = iceGatheringStateTransitions(pc1, 'gathering', 'complete');
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
await pc2.setLocalDescription(await pc2.createAnswer());
|
|
const pc2Transitions = iceGatheringStateTransitions(pc2, 'gathering', 'complete');
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
|
|
await pc1Transitions;
|
|
await pc2Transitions;
|
|
};
|
|
|
|
const expectNoMoreGatheringStateChanges = async (t, pc) => {
|
|
pc.onicegatheringstatechange = t.step_func(() => assert_unreached('Should not get an icegatheringstatechange right now!'));
|
|
};
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
await initialOfferAnswer(pc1, pc2, {offerToReceiveAudio: true});
|
|
|
|
expectNoMoreGatheringStateChanges(t, pc1);
|
|
expectNoMoreGatheringStateChanges(t, pc2);
|
|
|
|
await pc1.setLocalDescription(await pc1.createOffer());
|
|
await pc2.setLocalDescription(await pc2.createOffer());
|
|
|
|
await new Promise(r => t.step_timeout(r, 500));
|
|
}, 'setLocalDescription(reoffer) with no new transports should not cause iceGatheringState to change');
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
await initialOfferAnswer(pc1, pc2, {offerToReceiveAudio: true});
|
|
await pc1.setLocalDescription(await pc1.createOffer({iceRestart: true}));
|
|
await iceGatheringStateTransitions(pc1, 'gathering', 'complete');
|
|
expectNoMoreGatheringStateChanges(t, pc1);
|
|
await pc1.setLocalDescription({type: 'rollback'});
|
|
await new Promise(r => t.step_timeout(r, 1000));
|
|
}, 'rolling back an ICE restart when gathering is complete should not result in iceGatheringState changes');
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
await initialOfferAnswer(pc1, pc2, {offerToReceiveAudio:true});
|
|
await pc1.setLocalDescription(await pc1.createOffer({iceRestart: true}));
|
|
await iceGatheringStateTransitions(pc1, 'gathering', 'complete');
|
|
}, 'setLocalDescription(reoffer) with a new transport should cause iceGatheringState to go to "checking" and then "complete"');
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
expectNoMoreGatheringStateChanges(t, pc2);
|
|
const offer = await pc1.createOffer({offerToReceiveAudio: true});
|
|
await pc2.setRemoteDescription(offer);
|
|
await pc2.setRemoteDescription(offer);
|
|
await pc2.setRemoteDescription({type: 'rollback'});
|
|
await pc2.setRemoteDescription(offer);
|
|
}, 'sRD does not cause ICE gathering state changes')
|
|
|
|
promise_test(async t => {
|
|
const pc = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc.close());
|
|
await pc.setLocalDescription(
|
|
await pc.createOffer({offerToReceiveAudio: true}));
|
|
await iceGatheringStateTransitions(pc, 'gathering', 'complete');
|
|
await pc.setLocalDescription({type: 'rollback'});
|
|
await iceGatheringStateTransitions(pc, 'new');
|
|
}, 'setLocalDescription(rollback) of original offer should cause iceGatheringState to reach "new" when starting in "complete"');
|
|
|
|
promise_test(async t => {
|
|
const pc = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc.close());
|
|
await pc.setLocalDescription(
|
|
await pc.createOffer({offerToReceiveAudio: true}));
|
|
await iceGatheringStateTransitions(pc, 'gathering');
|
|
await pc.setLocalDescription({type: 'rollback'});
|
|
// We might go directly to 'new', or we might go to 'complete' first,
|
|
// depending on timing. Allow either.
|
|
const results = await Promise.allSettled([
|
|
iceGatheringStateTransitions(pc, 'new'),
|
|
iceGatheringStateTransitions(pc, 'complete', 'new')]);
|
|
assert_true(results.some(result => result.status == 'fulfilled'),
|
|
'ICE gathering state should go back to "new", possibly through "complete"');
|
|
}, 'setLocalDescription(rollback) of original offer should cause iceGatheringState to reach "new" when starting in "gathering"');
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
await initialOfferAnswer(pc1, pc2, {offerToReceiveAudio: true});
|
|
|
|
pc1.getTransceivers()[0].stop();
|
|
await pc1.setLocalDescription(await pc1.createOffer());
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
await pc2.setLocalDescription(await pc2.createAnswer());
|
|
await iceGatheringStateTransitions(pc2, 'new');
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
await iceGatheringStateTransitions(pc1, 'new');
|
|
}, 'renegotiation that closes all transports should result in ICE gathering state "new"');
|
|
|
|
/*
|
|
4.4.2. RTCIceGatheringState Enum
|
|
gathering
|
|
Any of the RTCIceTransport s are in the gathering state.
|
|
|
|
complete
|
|
At least one RTCIceTransport exists, and all RTCIceTransports are
|
|
in the completed gathering state.
|
|
|
|
5.6. RTCIceGathererState
|
|
gathering
|
|
The RTCIceTransport is in the process of gathering candidates.
|
|
|
|
complete
|
|
The RTCIceTransport has completed gathering and the end-of-candidates
|
|
indication for this transport has been sent. It will not gather candidates
|
|
again until an ICE restart causes it to restart.
|
|
*/
|
|
async_test(t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
const onIceGatheringStateChange = t.step_func(() => {
|
|
const { iceGatheringState } = pc2;
|
|
|
|
if(iceGatheringState === 'gathering') {
|
|
const iceTransport = pc2.sctp.transport.iceTransport;
|
|
|
|
assert_equals(iceTransport.gatheringState, 'gathering',
|
|
'Expect ICE transport to be in checking gatheringState when iceGatheringState is checking');
|
|
|
|
} else if(iceGatheringState === 'complete') {
|
|
const iceTransport = pc2.sctp.transport.iceTransport;
|
|
|
|
assert_equals(iceTransport.gatheringState, 'complete',
|
|
'Expect ICE transport to be in complete gatheringState when iceGatheringState is complete');
|
|
|
|
t.done();
|
|
}
|
|
});
|
|
|
|
pc1.createDataChannel('test');
|
|
|
|
assert_equals(pc2.onicegatheringstatechange, null,
|
|
'Expect connection to have icegatheringstatechange event');
|
|
|
|
// Spec bug w3c/webrtc-pc#1382
|
|
// Because sctp is only defined when answer is set, we listen
|
|
// to pc2 so that we can be confident that sctp is defined
|
|
// when icegatheringstatechange event is fired.
|
|
pc2.addEventListener('icegatheringstatechange', onIceGatheringStateChange);
|
|
|
|
exchangeIceCandidates(pc1, pc2);
|
|
exchangeOfferAnswer(pc1, pc2);
|
|
}, 'connection with one data channel should eventually have connected connection state');
|
|
|
|
/*
|
|
TODO
|
|
5.6. RTCIceTransport Interface
|
|
new
|
|
The RTCIceTransport was just created, and has not started gathering
|
|
candidates yet.
|
|
*/
|
|
</script>
|