499 lines
22 KiB
HTML
499 lines
22 KiB
HTML
<!doctype html>
|
|
<meta charset=utf-8>
|
|
<meta name="timeout" content="long">
|
|
<title>RTCIceTransport</title>
|
|
<script src="/resources/testharness.js"></script>
|
|
<script src="/resources/testharnessreport.js"></script>
|
|
<script src="RTCPeerConnection-helper.js"></script>
|
|
<script src='RTCConfiguration-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:
|
|
// createDataChannelPair
|
|
// awaitMessage
|
|
|
|
/*
|
|
5.6. RTCIceTransport Interface
|
|
interface RTCIceTransport {
|
|
readonly attribute RTCIceRole role;
|
|
readonly attribute RTCIceComponent component;
|
|
readonly attribute RTCIceTransportState state;
|
|
readonly attribute RTCIceGathererState gatheringState;
|
|
sequence<RTCIceCandidate> getLocalCandidates();
|
|
sequence<RTCIceCandidate> getRemoteCandidates();
|
|
RTCIceCandidatePair? getSelectedCandidatePair();
|
|
RTCIceParameters? getLocalParameters();
|
|
RTCIceParameters? getRemoteParameters();
|
|
...
|
|
};
|
|
|
|
getLocalCandidates
|
|
Returns a sequence describing the local ICE candidates gathered for this
|
|
RTCIceTransport and sent in onicecandidate
|
|
|
|
getRemoteCandidates
|
|
Returns a sequence describing the remote ICE candidates received by this
|
|
RTCIceTransport via addIceCandidate()
|
|
|
|
getSelectedCandidatePair
|
|
Returns the selected candidate pair on which packets are sent, or null if
|
|
there is no such pair.
|
|
|
|
getLocalParameters
|
|
Returns the local ICE parameters received by this RTCIceTransport via
|
|
setLocalDescription , or null if the parameters have not yet been received.
|
|
|
|
getRemoteParameters
|
|
Returns the remote ICE parameters received by this RTCIceTransport via
|
|
setRemoteDescription or null if the parameters have not yet been received.
|
|
*/
|
|
function getIceTransportFromSctp(pc) {
|
|
const sctpTransport = pc.sctp;
|
|
assert_true(sctpTransport instanceof RTCSctpTransport,
|
|
'Expect pc.sctp to be instantiated from RTCSctpTransport');
|
|
|
|
const dtlsTransport = sctpTransport.transport;
|
|
assert_true(dtlsTransport instanceof RTCDtlsTransport,
|
|
'Expect sctp.transport to be an RTCDtlsTransport');
|
|
|
|
const {iceTransport} = dtlsTransport;
|
|
assert_true(iceTransport instanceof RTCIceTransport,
|
|
'Expect dtlsTransport.transport to be an RTCIceTransport');
|
|
|
|
return iceTransport;
|
|
}
|
|
|
|
function validateCandidates(candidates) {
|
|
assert_greater_than(candidates.length, 0,
|
|
'Expect at least one ICE candidate returned from get*Candidates()');
|
|
|
|
for(const candidate of candidates) {
|
|
assert_true(candidate instanceof RTCIceCandidate,
|
|
'Expect candidate elements to be instance of RTCIceCandidate');
|
|
}
|
|
}
|
|
|
|
function validateCandidateParameter(param) {
|
|
assert_not_equals(param, null,
|
|
'Expect candidate parameter to be non-null after data channels are connected');
|
|
|
|
assert_equals(typeof param.usernameFragment, 'string',
|
|
'Expect param.usernameFragment to be set with string value');
|
|
assert_equals(typeof param.password, 'string',
|
|
'Expect param.password to be set with string value');
|
|
}
|
|
|
|
function validateConnectedIceTransport(iceTransport) {
|
|
const { state, gatheringState, role, component } = iceTransport;
|
|
|
|
assert_true(role === 'controlling' || role === 'controlled',
|
|
'Expect RTCIceRole to be either controlling or controlled, found ' + role);
|
|
|
|
assert_true(component === 'rtp' || component === 'rtcp',
|
|
'Expect RTCIceComponent to be either rtp or rtcp');
|
|
|
|
assert_true(state === 'connected' || state === 'completed',
|
|
'Expect ICE transport to be in connected or completed state after data channels are connected');
|
|
|
|
assert_true(gatheringState === 'gathering' || gatheringState === 'completed',
|
|
'Expect ICE transport to be in gathering or completed gatheringState after data channels are connected');
|
|
|
|
validateCandidates(iceTransport.getLocalCandidates());
|
|
validateCandidates(iceTransport.getRemoteCandidates());
|
|
|
|
const candidatePair = iceTransport.getSelectedCandidatePair();
|
|
assert_not_equals(candidatePair, null,
|
|
'Expect selected candidate pair to be non-null after ICE transport is connected');
|
|
|
|
assert_true(candidatePair.local instanceof RTCIceCandidate,
|
|
'Expect candidatePair.local to be instance of RTCIceCandidate');
|
|
|
|
assert_true(candidatePair.remote instanceof RTCIceCandidate,
|
|
'Expect candidatePair.remote to be instance of RTCIceCandidate');
|
|
|
|
validateCandidateParameter(iceTransport.getLocalParameters());
|
|
validateCandidateParameter(iceTransport.getRemoteParameters());
|
|
}
|
|
|
|
promise_test(t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
const pc1Candidates = new Set();
|
|
const pc2Candidates = new Set();
|
|
pc1.addEventListener('icecandidate', e => { if (e.candidate) pc1Candidates.add(e.candidate.candidate); });
|
|
pc2.addEventListener('icecandidate', e => { if (e.candidate) pc2Candidates.add(e.candidate.candidate); });
|
|
|
|
return createDataChannelPair(t, {}, pc1, pc2)
|
|
.then(([channel1, channel2]) => {
|
|
// Send a ping message and wait for it just to make sure
|
|
// that the connection is fully working before testing
|
|
channel1.send('ping');
|
|
return awaitMessage(channel2);
|
|
})
|
|
.then(() => {
|
|
const iceTransport1 = getIceTransportFromSctp(pc1);
|
|
const iceTransport2 = getIceTransportFromSctp(pc2);
|
|
|
|
test(() => {
|
|
const candidatePair1 = iceTransport1.getSelectedCandidatePair();
|
|
const candidatePair2 = iceTransport2.getSelectedCandidatePair();
|
|
|
|
assert_equals(candidatePair1.local.candidate, candidatePair2.remote.candidate,
|
|
'Expect selected local candidate of one pc is the selected remote candidate or another');
|
|
|
|
assert_equals(candidatePair1.remote.candidate, candidatePair2.local.candidate,
|
|
'Expect selected local candidate of one pc is the selected remote candidate or another');
|
|
|
|
assert_true(pc1Candidates.has(candidatePair1.local.candidate), "pc1 selected local candidate in pc1 candidates");
|
|
assert_true(pc1Candidates.has(candidatePair2.remote.candidate), "pc2 selected remote candidate in pc1 candidates");
|
|
assert_true(pc2Candidates.has(candidatePair2.local.candidate), "pc2 selected local candidate in pc2 candidates");
|
|
assert_true(pc2Candidates.has(candidatePair1.remote.candidate), "pc1 selected remote candidate in pc2 candidates");
|
|
}, "Validate selected candidate pair");
|
|
|
|
validateConnectedIceTransport(iceTransport1);
|
|
validateConnectedIceTransport(iceTransport2);
|
|
|
|
assert_equals(
|
|
iceTransport1.getLocalCandidates().length,
|
|
iceTransport2.getRemoteCandidates().length,
|
|
`Expect iceTransport1 to have same number of local candidate as iceTransport2's remote candidates`);
|
|
|
|
assert_equals(
|
|
iceTransport1.getRemoteCandidates().length,
|
|
iceTransport2.getLocalCandidates().length,
|
|
`Expect iceTransport1 to have same number of remote candidate as iceTransport2's local candidates`);
|
|
|
|
assert_equals(iceTransport1.role, 'controlling',
|
|
`Expect offerer's iceTransport to take the controlling role`);
|
|
|
|
assert_equals(iceTransport2.role, 'controlled',
|
|
`Expect answerer's iceTransport to take the controlled role`);
|
|
});
|
|
}, 'Two connected iceTransports should have matching local/remote candidates returned');
|
|
|
|
promise_test(t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
pc1.createDataChannel('');
|
|
|
|
// setRemoteDescription(answer) without the other peer
|
|
// setting answer it's localDescription
|
|
return pc1.createOffer()
|
|
.then(offer =>
|
|
pc1.setLocalDescription(offer)
|
|
.then(() => pc2.setRemoteDescription(offer))
|
|
.then(() => pc2.createAnswer()))
|
|
.then(answer => pc1.setRemoteDescription(answer))
|
|
.then(() => {
|
|
const iceTransport = getIceTransportFromSctp(pc1);
|
|
|
|
assert_array_equals(iceTransport.getRemoteCandidates(), [],
|
|
'Expect iceTransport to not have any remote candidate');
|
|
|
|
assert_equals(iceTransport.getSelectedCandidatePair(), null,
|
|
'Expect selectedCandidatePair to be null');
|
|
});
|
|
}, 'Unconnected iceTransport should have empty remote candidates and selected pair');
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const transceiver = pc1.addTransceiver('audio');
|
|
await pc1.setLocalDescription();
|
|
const {iceTransport} = transceiver.sender.transport;
|
|
assert_equals(iceTransport.state, 'new');
|
|
assert_equals(iceTransport.gatheringState, 'new');
|
|
}, 'RTCIceTransport should be in state "new" initially');
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const transceiver = pc1.addTransceiver('audio');
|
|
await pc1.setLocalDescription();
|
|
const {iceTransport} = transceiver.sender.transport;
|
|
assert_equals(await nextGatheringState(iceTransport), 'gathering');
|
|
assert_equals(await nextGatheringState(iceTransport), 'complete');
|
|
}, 'RTCIceTransport should transition to "gathering" then "complete", after sLD');
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const transceiver = pc1.addTransceiver('audio');
|
|
await pc1.setLocalDescription();
|
|
const {iceTransport} = transceiver.sender.transport;
|
|
assert_equals(await nextGatheringState(iceTransport), 'gathering');
|
|
pc1.close();
|
|
assert_equals(iceTransport.gatheringState, 'gathering');
|
|
const result = await Promise.race([
|
|
gatheringStateReached(iceTransport, 'complete'),
|
|
new Promise(r => t.step_timeout(r, 1000))]);
|
|
assert_equals(result, undefined, `Did not expect a statechange after PC.close(), but got one. state is "${result}"`);
|
|
}, 'PC.close() should not cause the RTCIceTransport gathering state to transition to "complete"');
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection({bundlePolicy: 'max-bundle'});
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
pc1.createDataChannel('test');
|
|
// TODO: If the spec settles on exposing the sctp transport in
|
|
// have-local-offer, we won't need this audio transceiver hack.
|
|
// See https://github.com/w3c/webrtc-pc/issues/2898 and
|
|
// https://github.com/w3c/webrtc-pc/issues/2899
|
|
const transceiver = pc1.addTransceiver('audio');
|
|
await pc1.setLocalDescription();
|
|
const {iceTransport} = transceiver.sender.transport;
|
|
assert_equals(await nextGatheringState(iceTransport), 'gathering');
|
|
assert_equals(await nextGatheringState(iceTransport), 'complete');
|
|
// TODO: Maybe, maybe not.
|
|
assert_not_equals(pc1.sctp, null, 'pc1.sctp should be set after sLD');
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
assert_equals(pc1.sctp.transport.iceTransport, transceiver.sender.transport.iceTransport);
|
|
}, 'RTCIceTransport should transition to "gathering", then "complete" after sLD (DataChannel case)');
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
const {sender} = pc1.addTransceiver('audio');
|
|
await pc1.setLocalDescription();
|
|
// Copy the SDP before it has candidate attrs
|
|
const offer = pc1.localDescription;
|
|
const checkingReached = connectionStateReached(sender.transport.iceTransport, 'checking');
|
|
|
|
let result = await Promise.race([checkingReached, new Promise(r => t.step_timeout(r, 1000))]);
|
|
assert_equals(result, undefined, `Did not expect a statechange right after sLD(offer), but got one. state is "${result}"`);
|
|
|
|
await pc2.setRemoteDescription(offer);
|
|
|
|
const firstPc2CandidatePromise =
|
|
new Promise(r => pc2.onicecandidate = e => r(e.candidate));
|
|
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
|
|
result = await Promise.race([checkingReached, new Promise(r => t.step_timeout(r, 1000))]);
|
|
assert_equals(result, undefined, `Did not expect a statechange callback after sRD(answer), but got one. state is "${result}"`);
|
|
|
|
const candidate = await firstPc2CandidatePromise;
|
|
pc1.addIceCandidate(candidate);
|
|
|
|
await checkingReached;
|
|
}, 'RTCIceTransport should not transition to "checking" until after the answer is set _and_ the first remote candidate is received');
|
|
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
const {sender} = pc1.addTransceiver('audio');
|
|
exchangeIceCandidates(pc1, pc2);
|
|
const gatheringDone = Promise.all([gatheringStateReached(pc1, 'complete'), gatheringStateReached(pc2, 'complete')]);
|
|
await pc1.setLocalDescription();
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
assert_equals(await nextConnectionState(sender.transport.iceTransport), 'checking');
|
|
assert_equals(await nextConnectionState(sender.transport.iceTransport), 'connected');
|
|
await gatheringDone;
|
|
pc2.close();
|
|
await connectionStateReached(sender.transport.iceTransport, 'disconnected');
|
|
}, 'RTCIceTransport should transition to "disconnected" if packets stop flowing');
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
pc1.createDataChannel('test');
|
|
exchangeIceCandidates(pc1, pc2);
|
|
const gatheringDone = Promise.all([gatheringStateReached(pc1, 'complete'), gatheringStateReached(pc2, 'complete')]);
|
|
await pc1.setLocalDescription();
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
const {sctp} = pc1;
|
|
assert_equals(await nextConnectionState(sctp.transport.iceTransport), 'checking');
|
|
assert_equals(await nextConnectionState(sctp.transport.iceTransport), 'connected');
|
|
await gatheringDone;
|
|
pc2.close();
|
|
await connectionStateReached(sctp.transport.iceTransport, 'disconnected');
|
|
}, 'RTCIceTransport should transition to "disconnected" if packets stop flowing (DataChannel case)');
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
const {sender} = pc1.addTransceiver('audio');
|
|
await pc1.setLocalDescription();
|
|
const {iceTransport} = sender.transport;
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
|
|
pc1.restartIce();
|
|
|
|
await pc1.setLocalDescription();
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
|
|
assert_equals(sender.transport.iceTransport, iceTransport, 'ICE restart should not result in a different ICE transport');
|
|
}, 'Local ICE restart should not result in a different ICE transport');
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
pc1.createDataChannel('test');
|
|
await pc1.setLocalDescription();
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
const {iceTransport} = pc1.sctp.transport;
|
|
|
|
pc1.restartIce();
|
|
|
|
await pc1.setLocalDescription();
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
|
|
assert_equals(pc1.sctp.transport.iceTransport, iceTransport, 'ICE restart should not result in a different ICE transport');
|
|
}, 'Local ICE restart should not result in a different ICE transport (DataChannel case)');
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
const {sender} = pc1.addTransceiver('audio');
|
|
|
|
await pc1.setLocalDescription();
|
|
const {iceTransport} = sender.transport;
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
|
|
pc2.restartIce();
|
|
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc1.localDescription);
|
|
|
|
assert_equals(sender.transport.iceTransport, iceTransport, 'ICE restart should not result in a different ICE transport');
|
|
}, 'Remote ICE restart should not result in a different ICE transport');
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
pc1.createDataChannel('test');
|
|
|
|
await pc1.setLocalDescription();
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
const {iceTransport} = pc1.sctp.transport;
|
|
|
|
pc2.restartIce();
|
|
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc1.localDescription);
|
|
|
|
assert_equals(pc1.sctp.transport.iceTransport, iceTransport, 'ICE restart should not result in a different ICE transport');
|
|
}, 'Remote ICE restart should not result in a different ICE transport (DataChannel case)');
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
// Add two transceivers, one audio and one video. The default bundlePolicy
|
|
// ("balanced") will result in each being offered with its own transport,
|
|
// but allowing the answerer to bundle the second transceiver on the
|
|
// transport of the first, which the answerer will do by default.
|
|
const audioTransceiver = pc1.addTransceiver('audio');
|
|
const videoTransceiver = pc1.addTransceiver('video');
|
|
pc1.createDataChannel('test');
|
|
|
|
await pc1.setLocalDescription();
|
|
const audioIceTransport = audioTransceiver.sender.transport.iceTransport;
|
|
const videoIceTransport = videoTransceiver.sender.transport.iceTransport;
|
|
|
|
assert_not_equals(audioIceTransport, videoIceTransport, 'audio and video should start out with different transports');
|
|
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
const sctpIceTransport = pc1.sctp.transport.iceTransport;
|
|
|
|
assert_equals(videoTransceiver.sender.transport.iceTransport, audioIceTransport, 'After negotiation, the video sender should use the bundle ICE transport from the audio sender');
|
|
assert_equals(pc1.sctp.transport.iceTransport, audioIceTransport, 'After negotiation, the datachannel should use the bundle ICE transport from the audio sender');
|
|
assert_not_equals(videoIceTransport.state, 'closed', 'Completion of offer/answer should not close the unused ICE transport immediately');
|
|
|
|
await connectionStateReached(videoIceTransport, 'closed');
|
|
}, 'RTCIceTransport should transition to "closed" if the underlying transport is closed because the answer used bundle');
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
const {sender} = pc1.addTransceiver('audio');
|
|
exchangeIceCandidates(pc1, pc2);
|
|
const gatheringDone = Promise.all([gatheringStateReached(pc1, 'complete'), gatheringStateReached(pc2, 'complete')]);
|
|
await pc1.setLocalDescription();
|
|
const {iceTransport} = sender.transport;
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
assert_equals(await nextConnectionState(iceTransport), 'checking');
|
|
assert_equals(await nextConnectionState(iceTransport), 'connected');
|
|
await gatheringDone;
|
|
|
|
const closedEvent = connectionStateReached(iceTransport, 'closed');
|
|
pc1.close();
|
|
assert_equals(sender.transport.iceTransport, iceTransport, 'PC.close() should not unset the sender transport');
|
|
assert_equals(iceTransport.state, 'closed', 'pc.close() should close the sender transport synchronously');
|
|
const result = await Promise.race([closedEvent, new Promise(r => t.step_timeout(r, 1000))]);
|
|
assert_equals(result, undefined, 'statechange event should not fire when transitioning to closed due to PC.close()');
|
|
}, 'RTCIceTransport should synchronously transition to "closed" with no event if the underlying transport is closed due to PC.close()');
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
const {sender} = pc1.addTransceiver('audio');
|
|
|
|
// Candidates only go in one direction, like in webrtc-stats/getStats-remote-candidate-address.html
|
|
// This means the remote candidates are peer-reflexive candidates.
|
|
pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
|
|
exchangeOfferAnswer(pc1, pc2);
|
|
await listenToConnected(pc1);
|
|
|
|
assert_equals(sender.transport.iceTransport.getRemoteCandidates().length, 0);
|
|
}, 'RTCIceTransport does not expose remote peer-reflexive candidates.');
|
|
|
|
</script>
|