<!doctype html>
<meta charset=utf-8>
<title>RTCPeerConnection.prototype.setRemoteDescription</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:
// assert_session_desc_not_similar()
// assert_session_desc_similar()
/*
4.3.2. Interface Definition
[Constructor(optional RTCConfiguration configuration)]
interface RTCPeerConnection : EventTarget {
Promise<void> setRemoteDescription(
RTCSessionDescriptionInit description);
readonly attribute RTCSessionDescription? remoteDescription;
readonly attribute RTCSessionDescription? currentRemoteDescription;
readonly attribute RTCSessionDescription? pendingRemoteDescription;
...
};
4.6.2. RTCSessionDescription Class
dictionary RTCSessionDescriptionInit {
required RTCSdpType type;
DOMString sdp = "";
};
4.6.1. RTCSdpType
enum RTCSdpType {
"offer",
"pranswer",
"answer",
"rollback"
};
*/
/*
4.6.1. enum RTCSdpType
*/
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
// SDP is validated after WebIDL validation
try {
await pc.setRemoteDescription({ type: 'bogus', sdp: 'bogus' });
t.unreached_func("Should have rejected.");
} catch (e) {
assert_throws_js(TypeError, () => { throw e });
}
}, 'setRemoteDescription with invalid type and invalid SDP should reject with TypeError');
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
// SDP is validated after validating type
try {
await pc.setRemoteDescription({ type: 'answer', sdp: 'invalid' });
t.unreached_func("Should have rejected.");
} catch (e) {
assert_throws_dom('InvalidStateError', () => { throw e });
}
}, 'setRemoteDescription() with invalid SDP and stable state should reject with InvalidStateError');
/* Dedicated signalingstate events test. */
promise_test(async t => {
const pc = new RTCPeerConnection();
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
t.add_cleanup(() => pc2.close());
let eventCount = 0;
const states = [
'stable', 'have-local-offer', 'stable', 'have-remote-offer',
];
pc.onsignalingstatechange = t.step_func(() =>
assert_equals(pc.signalingState, states[++eventCount]));
const assert_state = state => {
assert_equals(state, pc.signalingState);
assert_equals(state, states[eventCount]);
};
const offer = await generateAudioReceiveOnlyOffer(pc);
assert_state('stable');
await pc.setLocalDescription(offer);
assert_state('have-local-offer');
await pc2.setRemoteDescription(offer);
await exchangeAnswer(pc, pc2);
assert_state('stable');
await exchangeOffer(pc2, pc);
assert_state('have-remote-offer');
}, 'Negotiation should fire signalingsstate events');
/* Operations after returning to stable state */
promise_test(async t => {
const pc = new RTCPeerConnection();
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
t.add_cleanup(() => pc2.close());
const offer1 = await generateAudioReceiveOnlyOffer(pc2);
await pc2.setLocalDescription(offer1);
await pc.setRemoteDescription(offer1);
await exchangeAnswer(pc2, pc);
const offer2 = await generateVideoReceiveOnlyOffer(pc2);
await pc2.setLocalDescription(offer2);
await pc.setRemoteDescription(offer2);
assert_session_desc_not_similar(offer1, offer2);
assert_session_desc_similar(pc.remoteDescription, offer2);
assert_session_desc_similar(pc.currentRemoteDescription, offer1);
assert_session_desc_similar(pc.pendingRemoteDescription, offer2);
}, 'Calling setRemoteDescription() again after one round of remote-offer/local-answer should succeed');
promise_test(async t => {
const pc = new RTCPeerConnection();
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
t.add_cleanup(() => pc2.close());
const offer = await generateAudioReceiveOnlyOffer(pc);
await pc.setLocalDescription(offer);
await pc2.setRemoteDescription(offer);
const answer = await pc2.createAnswer();
await pc2.setLocalDescription(answer);
await pc.setRemoteDescription(answer);
await exchangeOffer(pc2, pc);
assert_equals(pc.remoteDescription, pc.pendingRemoteDescription);
assert_session_desc_similar(pc.remoteDescription, offer);
assert_session_desc_similar(pc.currentRemoteDescription, answer);
}, 'Switching role from offerer to answerer after going back to stable state should succeed');
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const offer = await pc.createOffer();
const p = Promise.race([
pc.setRemoteDescription(offer),
new Promise(r => t.step_timeout(() => r("timeout"), 200))
]);
pc.close();
assert_equals(await p, "timeout");
assert_equals(pc.signalingState, "closed", "In closed state");
}, 'Closing on setRemoteDescription() neither resolves nor rejects');
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const p = Promise.race([
pc.setRemoteDescription(offer),
new Promise(r => t.step_timeout(() => r("timeout"), 200))
]);
pc.close();
assert_equals(await p, "timeout");
assert_equals(pc.signalingState, "closed", "In closed state");
}, 'Closing on rollback neither resolves nor rejects');
</script>