summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webrtc/RTCPeerConnection-setRemoteDescription-offer.html
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/webrtc/RTCPeerConnection-setRemoteDescription-offer.html
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/webrtc/RTCPeerConnection-setRemoteDescription-offer.html')
-rw-r--r--testing/web-platform/tests/webrtc/RTCPeerConnection-setRemoteDescription-offer.html356
1 files changed, 356 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webrtc/RTCPeerConnection-setRemoteDescription-offer.html b/testing/web-platform/tests/webrtc/RTCPeerConnection-setRemoteDescription-offer.html
new file mode 100644
index 0000000000..d5acb7e1c9
--- /dev/null
+++ b/testing/web-platform/tests/webrtc/RTCPeerConnection-setRemoteDescription-offer.html
@@ -0,0 +1,356 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCPeerConnection.prototype.setRemoteDescription - offer</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script>
+ 'use strict';
+
+ // The following helper functions are called from RTCPeerConnection-helper.js:
+ // assert_session_desc_similar()
+ // generateAudioReceiveOnlyOffer
+
+ /*
+ 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.3.1.6. Set the RTCSessionSessionDescription
+ 2.2.3. Otherwise, if description is set as a remote description, then run one of
+ the following steps:
+ - If description is of type "offer", set connection.pendingRemoteDescription
+ attribute to description and signaling state to have-remote-offer.
+ */
+
+ promise_test(t => {
+ const pc1 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ pc1.createDataChannel('datachannel');
+
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc2.close());
+
+ const states = [];
+ pc2.addEventListener('signalingstatechange', () => states.push(pc2.signalingState));
+
+ return pc1.createOffer()
+ .then(offer => {
+ return pc2.setRemoteDescription(offer)
+ .then(() => {
+ assert_equals(pc2.signalingState, 'have-remote-offer');
+ assert_session_desc_similar(pc2.remoteDescription, offer);
+ assert_session_desc_similar(pc2.pendingRemoteDescription, offer);
+ assert_equals(pc2.currentRemoteDescription, null);
+
+ assert_array_equals(states, ['have-remote-offer']);
+ });
+ });
+ }, 'setRemoteDescription with valid offer should succeed');
+
+ promise_test(t => {
+ const pc1 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ pc1.createDataChannel('datachannel');
+
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc2.close());
+
+ const states = [];
+ pc2.addEventListener('signalingstatechange', () => states.push(pc2.signalingState));
+
+ return pc1.createOffer()
+ .then(offer => {
+ return pc2.setRemoteDescription(offer)
+ .then(() => pc2.setRemoteDescription(offer))
+ .then(() => {
+ assert_equals(pc2.signalingState, 'have-remote-offer');
+ assert_session_desc_similar(pc2.remoteDescription, offer);
+ assert_session_desc_similar(pc2.pendingRemoteDescription, offer);
+ assert_equals(pc2.currentRemoteDescription, null);
+
+ assert_array_equals(states, ['have-remote-offer']);
+ });
+ });
+ }, 'setRemoteDescription multiple times should succeed');
+
+ promise_test(t => {
+ const pc1 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ pc1.createDataChannel('datachannel');
+
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc2.close());
+
+ const states = [];
+ pc2.addEventListener('signalingstatechange', () => states.push(pc2.signalingState));
+
+ return pc1.createOffer()
+ .then(offer1 => {
+ return pc1.setLocalDescription(offer1)
+ .then(()=> {
+ return generateAudioReceiveOnlyOffer(pc1)
+ .then(offer2 => {
+ assert_session_desc_not_similar(offer1, offer2);
+
+ return pc2.setRemoteDescription(offer1)
+ .then(() => pc2.setRemoteDescription(offer2))
+ .then(() => {
+ assert_equals(pc2.signalingState, 'have-remote-offer');
+ assert_session_desc_similar(pc2.remoteDescription, offer2);
+ assert_session_desc_similar(pc2.pendingRemoteDescription, offer2);
+ assert_equals(pc2.currentRemoteDescription, null);
+
+ assert_array_equals(states, ['have-remote-offer']);
+ });
+ });
+ });
+ });
+ }, 'setRemoteDescription multiple times with different offer should succeed');
+
+ /*
+ 4.3.1.6. Set the RTCSessionSessionDescription
+ 2.1.4. If the content of description is not valid SDP syntax, then reject p with
+ an RTCError (with errorDetail set to "sdp-syntax-error" and the
+ sdpLineNumber attribute set to the line number in the SDP where the syntax
+ error was detected) and abort these steps.
+ */
+ promise_test(t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ return pc.setRemoteDescription({
+ type: 'offer',
+ sdp: 'Invalid SDP'
+ })
+ .then(() => {
+ assert_unreached('Expect promise to be rejected');
+ }, err => {
+ assert_equals(err.errorDetail, 'sdp-syntax-error',
+ 'Expect error detail field to set to sdp-syntax-error');
+
+ assert_true(err instanceof RTCError,
+ 'Expect err to be instance of RTCError');
+ });
+ }, 'setRemoteDescription(offer) with invalid SDP should reject with RTCError');
+
+ promise_test(async t => {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ t.add_cleanup(() => pc2.close());
+ await pc1.setLocalDescription(await pc1.createOffer());
+ await pc1.setRemoteDescription(await pc2.createOffer());
+ assert_equals(pc1.signalingState, 'have-remote-offer');
+ }, 'setRemoteDescription(offer) from have-local-offer should roll back and succeed');
+
+ promise_test(async t => {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ t.add_cleanup(() => pc2.close());
+ await pc1.setLocalDescription(await pc1.createOffer());
+ const p = pc1.setRemoteDescription(await pc2.createOffer());
+ await new Promise(r => pc1.onsignalingstatechange = r);
+ assert_equals(pc1.signalingState, 'stable');
+ assert_equals(pc1.pendingLocalDescription, null);
+ assert_equals(pc1.pendingRemoteDescription, null);
+ await new Promise(r => pc1.onsignalingstatechange = r);
+ assert_equals(pc1.signalingState, 'have-remote-offer');
+ assert_equals(pc1.pendingLocalDescription, null);
+ assert_equals(pc1.pendingRemoteDescription.type, 'offer');
+ await p;
+ }, 'setRemoteDescription(offer) from have-local-offer fires signalingstatechange twice');
+
+ promise_test(async t => {
+ const pc1 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc2.close());
+
+ pc1.addTransceiver('audio', { direction: 'recvonly' });
+ const srdPromise = pc2.setRemoteDescription(await pc1.createOffer());
+
+ assert_equals(pc2.signalingState, "stable", "signalingState should not be set synchronously after a call to sRD");
+
+ assert_equals(pc2.pendingRemoteDescription, null, "pendingRemoteDescription should not be set synchronously after a call to sRD");
+ assert_equals(pc2.currentRemoteDescription, null, "currentRemoteDescription should not be set synchronously after a call to sRD");
+
+ const statePromise = new Promise(resolve => {
+ pc2.onsignalingstatechange = () => {
+ resolve(pc2.signalingState);
+ }
+ });
+
+ const raceValue = await Promise.race([statePromise, srdPromise]);
+ assert_equals(raceValue, "have-remote-offer", "signalingstatechange event should fire before sRD resolves");
+ assert_not_equals(pc2.pendingRemoteDescription, null, "pendingRemoteDescription should be updated before the signalingstatechange event");
+ assert_equals(pc2.pendingRemoteDescription.type, "offer");
+ assert_equals(pc2.pendingRemoteDescription.sdp, pc2.remoteDescription.sdp);
+ assert_equals(pc2.currentRemoteDescription, null, "currentRemoteDescription should not be set after a call to sRD(offer)");
+
+ await srdPromise;
+ }, "setRemoteDescription(offer) in stable should update internal state with a queued task, in the right order");
+
+ promise_test(async t => {
+ const pc1 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc2.close());
+
+ pc2.addTransceiver('audio', { direction: 'recvonly' });
+ await pc2.setLocalDescription(await pc2.createOffer());
+
+ // Implicit rollback!
+ pc1.addTransceiver('audio', { direction: 'recvonly' });
+ const srdPromise = pc2.setRemoteDescription(await pc1.createOffer());
+
+ assert_equals(pc2.signalingState, "have-local-offer", "signalingState should not be set synchronously after a call to sRD");
+
+ assert_equals(pc2.pendingRemoteDescription, null, "pendingRemoteDescription should not be set synchronously after a call to sRD");
+ assert_not_equals(pc2.pendingLocalDescription, null, "pendingLocalDescription should not be set synchronously after a call to sRD");
+ assert_equals(pc2.pendingLocalDescription.type, "offer");
+ assert_equals(pc2.pendingLocalDescription.sdp, pc2.localDescription.sdp);
+
+ // First, we should go through stable (the implicit rollback part)
+ const stablePromise = new Promise(resolve => {
+ pc2.onsignalingstatechange = () => {
+ resolve(pc2.signalingState);
+ }
+ });
+
+ let raceValue = await Promise.race([stablePromise, srdPromise]);
+ assert_equals(raceValue, "stable", "signalingstatechange event should fire before sRD resolves");
+ assert_equals(pc2.pendingLocalDescription, null, "pendingLocalDescription should be updated before the signalingstatechange event");
+ assert_equals(pc2.pendingRemoteDescription, null, "pendingRemoteDescription should be updated before the signalingstatechange event");
+
+ const haveRemoteOfferPromise = new Promise(resolve => {
+ pc2.onsignalingstatechange = () => {
+ resolve(pc2.signalingState);
+ }
+ });
+
+ raceValue = await Promise.race([haveRemoteOfferPromise, srdPromise]);
+ assert_equals(raceValue, "have-remote-offer", "signalingstatechange event should fire before sRD resolves");
+ assert_not_equals(pc2.pendingRemoteDescription, null, "pendingRemoteDescription should be updated before the signalingstatechange event");
+ assert_equals(pc2.pendingRemoteDescription.type, "offer");
+ assert_equals(pc2.pendingRemoteDescription.sdp, pc2.remoteDescription.sdp);
+ assert_equals(pc2.pendingLocalDescription, null, "pendingLocalDescription should be updated before the signalingstatechange event");
+
+ await srdPromise;
+ }, "setRemoteDescription(offer) in have-local-offer should update internal state with a queued task, in the right order");
+
+ promise_test(async t => {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ t.add_cleanup(() => pc2.close());
+ await pc1.setLocalDescription(await pc1.createOffer());
+ const offer = await pc2.createOffer();
+ const p1 = pc1.setLocalDescription({type: 'rollback'});
+ await new Promise(r => pc1.onsignalingstatechange = r);
+ assert_equals(pc1.signalingState, 'stable');
+ const p2 = pc1.addIceCandidate();
+ const p3 = pc1.setRemoteDescription(offer);
+ await promise_rejects_dom(t, 'InvalidStateError', p2);
+ await p1;
+ await p3;
+ assert_equals(pc1.signalingState, 'have-remote-offer');
+ }, 'Naive rollback approach is not glare-proof (control)');
+
+ promise_test(async t => {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ t.add_cleanup(() => pc2.close());
+ await pc1.setLocalDescription(await pc1.createOffer());
+ const p = pc1.setRemoteDescription(await pc2.createOffer());
+ await new Promise(r => pc1.onsignalingstatechange = r);
+ assert_equals(pc1.signalingState, 'stable');
+ await pc1.addIceCandidate();
+ await p;
+ assert_equals(pc1.signalingState, 'have-remote-offer');
+ }, 'setRemoteDescription(offer) from have-local-offer is glare-proof');
+
+ promise_test(async t => {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ t.add_cleanup(() => pc2.close());
+ await pc1.setLocalDescription(await pc1.createOffer());
+ const p = pc1.setRemoteDescription({type: 'offer', sdp: 'Invalid SDP'});
+ await new Promise(r => pc1.onsignalingstatechange = r);
+ assert_equals(pc1.signalingState, 'stable');
+ assert_equals(pc1.pendingLocalDescription, null);
+ assert_equals(pc1.pendingRemoteDescription, null);
+ await promise_rejects_dom(t, 'RTCError', p);
+ }, 'setRemoteDescription(invalidOffer) from have-local-offer does not undo rollback');
+
+ promise_test(async t => {
+ const pc1 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc2.close());
+ pc1.addTransceiver('video');
+ const offer = await pc1.createOffer();
+ await pc2.setRemoteDescription(offer);
+ assert_equals(pc2.getTransceivers().length, 1);
+ await pc2.setRemoteDescription(offer);
+ assert_equals(pc2.getTransceivers().length, 1);
+ await pc1.setLocalDescription(offer);
+ const answer = await pc2.createAnswer();
+ await pc2.setLocalDescription(answer);
+ await pc1.setRemoteDescription(answer);
+ }, 'repeated sRD(offer) works');
+
+ promise_test(async t => {
+ const pc1 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc2.close());
+ pc1.addTransceiver('video');
+ await exchangeOfferAnswer(pc1, pc2);
+ await waitForIceGatheringState(pc1, ['complete']);
+ await exchangeOfferAnswer(pc1, pc2);
+ await waitForIceStateChange(pc2, ['connected', 'completed']);
+ }, 'sRD(reoffer) with candidates and without trickle works');
+
+ promise_test(async t => {
+ const pc1 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc2.close());
+ pc1.addTransceiver('video');
+ const offer = await pc1.createOffer();
+ const srdPromise = pc2.setRemoteDescription(offer);
+ assert_equals(pc2.getTransceivers().length, 0);
+ await srdPromise;
+ assert_equals(pc2.getTransceivers().length, 1);
+ }, 'Transceivers added by sRD(offer) should not show up until sRD resolves');
+
+</script>