RTCPeerConnection-setLocalDescription-answer.html (9546B)
1 <!doctype html> 2 <meta charset=utf-8> 3 <title>RTCPeerConnection.prototype.setLocalDescription</title> 4 <script src="/resources/testharness.js"></script> 5 <script src="/resources/testharnessreport.js"></script> 6 <script src="RTCPeerConnection-helper.js"></script> 7 <script> 8 'use strict'; 9 10 // Test is based on the following editor draft: 11 // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html 12 13 // The following helper functions are called from RTCPeerConnection-helper.js: 14 // generateAnswer 15 // assert_session_desc_similar 16 17 /* 18 4.3.2. Interface Definition 19 [Constructor(optional RTCConfiguration configuration)] 20 interface RTCPeerConnection : EventTarget { 21 Promise<void> setRemoteDescription( 22 RTCSessionDescriptionInit description); 23 24 readonly attribute RTCSessionDescription? remoteDescription; 25 readonly attribute RTCSessionDescription? currentRemoteDescription; 26 readonly attribute RTCSessionDescription? pendingRemoteDescription; 27 ... 28 }; 29 30 4.6.2. RTCSessionDescription Class 31 dictionary RTCSessionDescriptionInit { 32 required RTCSdpType type; 33 DOMString sdp = ""; 34 }; 35 36 4.6.1. RTCSdpType 37 enum RTCSdpType { 38 "offer", 39 "pranswer", 40 "answer", 41 "rollback" 42 }; 43 */ 44 45 /* 46 4.3.1.6. Set the RTCSessionSessionDescription 47 2.2.2. If description is set as a local description, then run one of the following 48 steps: 49 50 - If description is of type "answer", then this completes an offer answer 51 negotiation. 52 53 Set connection's currentLocalDescription to description and 54 currentRemoteDescription to the value of pendingRemoteDescription. 55 56 Set both pendingRemoteDescription and pendingLocalDescription to null. 57 58 Finally set connection's signaling state to stable. 59 */ 60 promise_test(t => { 61 const pc = new RTCPeerConnection(); 62 t.add_cleanup(() => pc.close()); 63 64 const states = []; 65 pc.addEventListener('signalingstatechange', () => states.push(pc.signalingState)); 66 67 return generateVideoReceiveOnlyOffer(pc) 68 .then(offer => 69 pc.setRemoteDescription(offer) 70 .then(() => pc.createAnswer()) 71 .then(answer => 72 pc.setLocalDescription(answer) 73 .then(() => { 74 assert_equals(pc.signalingState, 'stable'); 75 assert_session_desc_similar(pc.localDescription, answer); 76 assert_session_desc_similar(pc.remoteDescription, offer); 77 78 assert_equals(pc.currentLocalDescription, pc.localDescription); 79 assert_equals(pc.currentRemoteDescription, pc.remoteDescription); 80 81 assert_equals(pc.pendingLocalDescription, null); 82 assert_equals(pc.pendingRemoteDescription, null); 83 84 assert_array_equals(states, ['have-remote-offer', 'stable']); 85 }))); 86 }, 'setLocalDescription() with valid answer should succeed'); 87 88 /* 89 4.3.2. setLocalDescription 90 3. Let lastAnswer be the result returned by the last call to createAnswer. 91 4. If description.sdp is null and description.type is answer, set description.sdp 92 to lastAnswer. 93 */ 94 promise_test(t => { 95 const pc = new RTCPeerConnection(); 96 97 t.add_cleanup(() => pc.close()); 98 99 return generateVideoReceiveOnlyOffer(pc) 100 .then(offer => 101 pc.setRemoteDescription(offer) 102 .then(() => pc.createAnswer()) 103 .then(answer => 104 pc.setLocalDescription({ type: 'answer' }) 105 .then(() => { 106 assert_equals(pc.signalingState, 'stable'); 107 assert_session_desc_similar(pc.localDescription, answer); 108 assert_session_desc_similar(pc.remoteDescription, offer); 109 110 assert_equals(pc.currentLocalDescription, pc.localDescription); 111 assert_equals(pc.currentRemoteDescription, pc.remoteDescription); 112 113 assert_equals(pc.pendingLocalDescription, null); 114 assert_equals(pc.pendingRemoteDescription, null); 115 }))); 116 }, 'setLocalDescription() with type answer and null sdp should use lastAnswer generated from createAnswer'); 117 118 /* 119 4.3.2. setLocalDescription 120 3. Let lastAnswer be the result returned by the last call to createAnswer. 121 7. If description.type is answer and description.sdp does not match lastAnswer, 122 reject the promise with a newly created InvalidModificationError and abort these 123 steps. 124 */ 125 promise_test(t => { 126 const pc = new RTCPeerConnection(); 127 128 t.add_cleanup(() => pc.close()); 129 130 return generateVideoReceiveOnlyOffer(pc) 131 .then(offer => 132 pc.setRemoteDescription(offer) 133 .then(() => generateAnswer(offer)) 134 .then(answer => pc.setLocalDescription(answer)) 135 .then(() => t.unreached_func("setLocalDescription should have rejected"), 136 (error) => assert_equals(error.name, 'InvalidModificationError'))); 137 }, 'setLocalDescription() with answer not created by own createAnswer() should reject with InvalidModificationError'); 138 139 /* 140 4.3.1.6. Set the RTCSessionSessionDescription 141 2.3. If the description's type is invalid for the current signaling state of 142 connection, then reject p with a newly created InvalidStateError and abort 143 these steps. 144 145 [jsep] 146 5.5. If the type is "pranswer" or "answer", the PeerConnection 147 state MUST be either "have-remote-offer" or "have-local-pranswer". 148 */ 149 promise_test(t => { 150 const pc = new RTCPeerConnection(); 151 152 t.add_cleanup(() => pc.close()); 153 154 return pc.createOffer() 155 .then(offer => 156 promise_rejects_dom(t, 'InvalidStateError', 157 pc.setLocalDescription({ type: 'answer', sdp: offer.sdp }))); 158 }, 'Calling setLocalDescription(answer) from stable state should reject with InvalidStateError'); 159 160 promise_test(t => { 161 const pc = new RTCPeerConnection(); 162 163 t.add_cleanup(() => pc.close()); 164 165 return pc.createOffer() 166 .then(offer => 167 pc.setLocalDescription(offer) 168 .then(() => generateAnswer(offer))) 169 .then(answer => 170 promise_rejects_dom(t, 'InvalidStateError', 171 pc.setLocalDescription(answer))); 172 }, 'Calling setLocalDescription(answer) from have-local-offer state should reject with InvalidStateError'); 173 174 promise_test(async t => { 175 const pc1 = new RTCPeerConnection(); 176 t.add_cleanup(() => pc1.close()); 177 const pc2 = new RTCPeerConnection(); 178 t.add_cleanup(() => pc2.close()); 179 180 pc1.addTransceiver('audio', { direction: 'recvonly' }); 181 const offer = await pc1.createOffer(); 182 await pc2.setRemoteDescription(offer); 183 const answer = await pc2.createAnswer(); // [[LastAnswer]] slot set 184 await pc2.setRemoteDescription({type: "rollback"}); 185 pc2.addTransceiver('video', { direction: 'recvonly' }); 186 await pc2.createOffer(); // [[LastOffer]] slot set 187 await pc2.setRemoteDescription(offer); 188 await pc2.setLocalDescription(answer); // Should check against [[LastAnswer]], not [[LastOffer]] 189 }, "Setting previously generated answer after a call to createOffer should work"); 190 191 promise_test(async t => { 192 const pc1 = new RTCPeerConnection(); 193 t.add_cleanup(() => pc1.close()); 194 const pc2 = new RTCPeerConnection(); 195 t.add_cleanup(() => pc2.close()); 196 197 pc1.addTransceiver('audio', { direction: 'recvonly' }); 198 await pc2.setRemoteDescription(await pc1.createOffer()); 199 const answer = await pc2.createAnswer(); 200 const sldPromise = pc2.setLocalDescription(answer); 201 202 assert_equals(pc2.signalingState, "have-remote-offer", "signalingState should not be set synchronously after a call to sLD"); 203 204 assert_equals(pc2.pendingLocalDescription, null, "pendingLocalDescription should never be set due to sLD(answer)"); 205 assert_not_equals(pc2.pendingRemoteDescription, null, "pendingRemoteDescription should not be set synchronously after a call to sLD"); 206 assert_equals(pc2.pendingRemoteDescription.type, "offer"); 207 assert_equals(pc2.remoteDescription, pc2.pendingRemoteDescription); 208 assert_equals(pc2.currentLocalDescription, null, "currentLocalDescription should not be set synchronously after a call to sLD"); 209 assert_equals(pc2.currentRemoteDescription, null, "currentRemoteDescription should not be set synchronously after a call to sLD"); 210 211 const stablePromise = new Promise(resolve => { 212 pc2.onsignalingstatechange = () => { 213 resolve(pc2.signalingState); 214 } 215 }); 216 const raceValue = await Promise.race([stablePromise, sldPromise]); 217 assert_equals(raceValue, "stable", "signalingstatechange event should fire before sLD resolves"); 218 assert_equals(pc2.pendingLocalDescription, null, "pendingLocalDescription should never be set due to sLD(answer)"); 219 assert_equals(pc2.pendingRemoteDescription, null, "pendingRemoteDescription should be updated before the signalingstatechange event"); 220 assert_not_equals(pc2.currentLocalDescription, null, "currentLocalDescription should be updated before the signalingstatechange event"); 221 assert_equals(pc2.currentLocalDescription.type, "answer"); 222 assert_equals(pc2.currentLocalDescription, pc2.localDescription); 223 assert_not_equals(pc2.currentRemoteDescription, null, "currentRemoteDescription should be updated before the signalingstatechange event"); 224 assert_equals(pc2.currentRemoteDescription.type, "offer"); 225 assert_equals(pc2.currentRemoteDescription, pc2.remoteDescription); 226 227 await sldPromise; 228 }, "setLocalDescription(answer) should update internal state with a queued task, in the right order"); 229 230 </script>