<!doctype html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
'use strict';
// In this test, the promises should resolve in the execution order
// (setLocalDescription, setLocalDescription, addIceCandidate) as is ensured by
// the Operations Chain; if an operation is pending, executing another operation
// will queue it. This test will fail if an Operations Chain is not implemented,
// but it gives the implementation some slack: it only ensures that
// addIceCandidate() is not resolved first, allowing timing issues in resolving
// promises where the test still passes even if addIceCandidate() is resolved
// *before* the second setLocalDescription().
//
// This test covers Chrome issue (https://crbug.com/1019222), but does not
// require setLocalDescription-promises to resolve immediately which is another
// Chrome bug (https://crbug.com/1019232). The true order is covered by the next
// test.
// TODO(https://crbug.com/1019232): Delete this test when the next test passes
// in Chrome.
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');
const candidatePromise = new Promise(resolve => {
caller.onicecandidate = e => resolve(e.candidate);
});
await caller.setLocalDescription(await caller.createOffer());
await callee.setRemoteDescription(caller.localDescription);
const candidate = await candidatePromise;
// Chain setLocalDescription(), setLocalDescription() and addIceCandidate()
// without performing await between the calls.
const pendingPromises = [];
const resolveOrder = [];
pendingPromises.push(callee.setLocalDescription().then(() => {
resolveOrder.push('setLocalDescription 1');
}));
pendingPromises.push(callee.setLocalDescription().then(() => {
resolveOrder.push('setLocalDescription 2');
}));
pendingPromises.push(callee.addIceCandidate(candidate).then(() => {
resolveOrder.push('addIceCandidate');
}));
await Promise.all(pendingPromises);
assert_equals(resolveOrder[0], 'setLocalDescription 1');
}, 'addIceCandidate is not resolved first if 2x setLocalDescription ' +
'operations are pending');
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');
const candidatePromise = new Promise(resolve => {
caller.onicecandidate = e => resolve(e.candidate);
});
await caller.setLocalDescription(await caller.createOffer());
await callee.setRemoteDescription(caller.localDescription);
const candidate = await candidatePromise;
// Chain setLocalDescription(), setLocalDescription() and addIceCandidate()
// without performing await between the calls.
const pendingPromises = [];
const resolveOrder = [];
pendingPromises.push(callee.setLocalDescription().then(() => {
resolveOrder.push('setLocalDescription 1');
}));
pendingPromises.push(callee.setLocalDescription().then(() => {
resolveOrder.push('setLocalDescription 2');
}));
pendingPromises.push(callee.addIceCandidate(candidate).then(() => {
resolveOrder.push('addIceCandidate');
}));
await Promise.all(pendingPromises);
// This test verifies that both issues described in https://crbug.com/1019222
// and https://crbug.com/1019232 are fixed. If this test passes in Chrome, the
// ICE candidate exchange issues described in
// https://github.com/web-platform-tests/wpt/issues/19866 should be resolved.
assert_array_equals(
resolveOrder,
['setLocalDescription 1', 'setLocalDescription 2', 'addIceCandidate']);
}, 'addIceCandidate and setLocalDescription are resolved in the correct ' +
'order, as defined by the operations chain specification');
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');
let events = [];
let pendingPromises = [];
const onCandidatePromise = new Promise(resolve => {
caller.onicecandidate = () => {
events.push('candidate generated');
resolve();
}
});
pendingPromises.push(onCandidatePromise);
pendingPromises.push(caller.setLocalDescription().then(() => {
events.push('setLocalDescription');
}));
await Promise.all(pendingPromises);
assert_array_equals(events, ['setLocalDescription', 'candidate generated']);
}, 'onicecandidate fires after resolving setLocalDescription in offerer');
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');
let events = [];
let pendingPromises = [];
caller.onicecandidate = (ev) => {
if (ev.candidate) {
callee.addIceCandidate(ev.candidate);
}
}
const offer = await caller.createOffer();
const onCandidatePromise = new Promise(resolve => {
callee.onicecandidate = () => {
events.push('candidate generated');
resolve();
}
});
await callee.setRemoteDescription(offer);
const answer = await callee.createAnswer();
pendingPromises.push(onCandidatePromise);
pendingPromises.push(callee.setLocalDescription(answer).then(() => {
events.push('setLocalDescription');
}));
await Promise.all(pendingPromises);
assert_array_equals(events, ['setLocalDescription', 'candidate generated']);
}, 'onicecandidate fires after resolving setLocalDescription in answerer');
</script>