RTCPeerConnection-setLocalDescription-offer.html (9603B)
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 // generateDataChannelOffer 15 // assert_session_desc_not_similar 16 // assert_session_desc_similar 17 18 /* 19 4.3.2. Interface Definition 20 [Constructor(optional RTCConfiguration configuration)] 21 interface RTCPeerConnection : EventTarget { 22 Promise<void> setRemoteDescription( 23 RTCSessionDescriptionInit description); 24 25 readonly attribute RTCSessionDescription? remoteDescription; 26 readonly attribute RTCSessionDescription? currentRemoteDescription; 27 readonly attribute RTCSessionDescription? pendingRemoteDescription; 28 ... 29 }; 30 31 4.6.2. RTCSessionDescription Class 32 dictionary RTCSessionDescriptionInit { 33 required RTCSdpType type; 34 DOMString sdp = ""; 35 }; 36 37 4.6.1. RTCSdpType 38 enum RTCSdpType { 39 "offer", 40 "pranswer", 41 "answer", 42 "rollback" 43 }; 44 */ 45 46 /* 47 4.3.2. setLocalDescription 48 2. Let lastOffer be the result returned by the last call to createOffer. 49 5. If description.sdp is null and description.type is offer, set description.sdp 50 to lastOffer. 51 52 4.3.1.6. Set the RTCSessionSessionDescription 53 2.2.2. If description is set as a local description, then run one of the following 54 steps: 55 - If description is of type "offer", set connection.pendingLocalDescription 56 to description and signaling state to have-local-offer. 57 */ 58 promise_test(t => { 59 const pc = new RTCPeerConnection(); 60 t.add_cleanup(() => pc.close()); 61 62 const states = []; 63 pc.addEventListener('signalingstatechange', () => states.push(pc.signalingState)); 64 65 return generateAudioReceiveOnlyOffer(pc) 66 .then(offer => 67 pc.setLocalDescription(offer) 68 .then(() => { 69 assert_equals(pc.signalingState, 'have-local-offer'); 70 assert_session_desc_similar(pc.localDescription, offer); 71 assert_equals(pc.pendingLocalDescription, pc.localDescription); 72 assert_equals(pc.currentLocalDescription, null); 73 74 assert_array_equals(states, ['have-local-offer']); 75 })); 76 }, 'setLocalDescription with valid offer should succeed'); 77 78 /* 79 4.3.2. setLocalDescription 80 2. Let lastOffer be the result returned by the last call to createOffer. 81 5. If description.sdp is null and description.type is offer, set description.sdp 82 to lastOffer. 83 */ 84 promise_test(t => { 85 const pc = new RTCPeerConnection(); 86 t.add_cleanup(() => pc.close()); 87 return generateAudioReceiveOnlyOffer(pc) 88 .then(offer => 89 pc.setLocalDescription({ type: 'offer' }) 90 .then(() => { 91 assert_equals(pc.signalingState, 'have-local-offer'); 92 assert_session_desc_similar(pc.localDescription, offer); 93 assert_equals(pc.pendingLocalDescription, pc.localDescription); 94 assert_equals(pc.currentLocalDescription, null); 95 })); 96 }, 'setLocalDescription with type offer and null sdp should use lastOffer generated from createOffer'); 97 98 /* 99 4.3.2. setLocalDescription 100 2. Let lastOffer be the result returned by the last call to createOffer. 101 6. If description.type is offer and description.sdp does not match lastOffer, 102 reject the promise with a newly created InvalidModificationError and abort 103 these steps. 104 */ 105 promise_test(t => { 106 const pc = new RTCPeerConnection(); 107 t.add_cleanup(() => pc.close()); 108 const pc2 = new RTCPeerConnection(); 109 110 t.add_cleanup(() => pc2.close()); 111 112 return generateDataChannelOffer(pc) 113 .then(offer => pc2.setLocalDescription(offer)) 114 .then(() => t.unreached_func("setLocalDescription should have rejected"), 115 (error) => assert_equals(error.name, 'InvalidModificationError')); 116 }, 'setLocalDescription() with offer not created by own createOffer() should reject with InvalidModificationError'); 117 118 promise_test(t => { 119 // Create first offer with audio line, then second offer with 120 // both audio and video line. Since the second offer is the 121 // last offer, setLocalDescription would reject when setting 122 // with the first offer 123 const pc = new RTCPeerConnection(); 124 t.add_cleanup(() => pc.close()); 125 return generateAudioReceiveOnlyOffer(pc) 126 .then(offer1 => 127 generateVideoReceiveOnlyOffer(pc) 128 .then(offer2 => { 129 assert_session_desc_not_similar(offer1, offer2); 130 return promise_rejects_dom(t, 'InvalidModificationError', 131 pc.setLocalDescription(offer1)); 132 })); 133 }, 'Set created offer other than last offer should reject with InvalidModificationError'); 134 135 promise_test(t => { 136 const pc = new RTCPeerConnection(); 137 t.add_cleanup(() => pc.close()); 138 139 const states = []; 140 pc.addEventListener('signalingstatechange', () => states.push(pc.signalingState)); 141 142 return generateAudioReceiveOnlyOffer(pc) 143 .then(offer1 => 144 pc.setLocalDescription(offer1) 145 .then(() => 146 generateVideoReceiveOnlyOffer(pc) 147 .then(offer2 => 148 pc.setLocalDescription(offer2) 149 .then(() => { 150 assert_session_desc_not_similar(offer1, offer2); 151 assert_equals(pc.signalingState, 'have-local-offer'); 152 assert_session_desc_similar(pc.localDescription, offer2); 153 assert_session_desc_similar(pc.pendingLocalDescription, offer2); 154 assert_equals(pc.currentLocalDescription, null); 155 156 assert_array_equals(states, ['have-local-offer']); 157 })))); 158 }, 'Creating and setting offer multiple times should succeed'); 159 160 promise_test(async t => { 161 const pc1 = new RTCPeerConnection(); 162 t.add_cleanup(() => pc1.close()); 163 const pc2 = new RTCPeerConnection(); 164 t.add_cleanup(() => pc2.close()); 165 166 pc1.addTransceiver('audio', { direction: 'recvonly' }); 167 const offer = await pc1.createOffer(); // [[LastOffer]] set 168 pc2.addTransceiver('video', { direction: 'recvonly' }); 169 const offer2 = await pc2.createOffer(); 170 await pc1.setRemoteDescription(offer2); 171 await pc1.createAnswer(); // [[LastAnswer]] set 172 await pc1.setRemoteDescription({type: "rollback"}); 173 await pc1.setLocalDescription(offer); 174 }, "Setting previously generated offer after a call to createAnswer should work"); 175 176 promise_test(async t => { 177 const pc1 = new RTCPeerConnection(); 178 t.add_cleanup(() => pc1.close()); 179 const pc2 = new RTCPeerConnection(); 180 t.add_cleanup(() => pc2.close()); 181 182 pc1.addTransceiver('audio', { direction: 'recvonly' }); 183 await pc1.setLocalDescription(await pc1.createOffer()); 184 185 const offer = await pc1.createOffer(); 186 await pc1.setLocalDescription(offer); 187 await pc2.setRemoteDescription(offer); 188 const answer = await pc2.createAnswer(); 189 await pc2.setLocalDescription(answer); 190 await pc1.setRemoteDescription(answer); 191 192 assert_equals(pc1.getTransceivers().length, 1); 193 assert_equals(pc1.getTransceivers()[0].receiver.track.kind, "audio"); 194 assert_equals(pc2.getTransceivers().length, 1); 195 assert_equals(pc2.getTransceivers()[0].receiver.track.kind, "audio"); 196 }, "Negotiation works when there has been a repeated setLocalDescription(offer)"); 197 198 promise_test(async t => { 199 const pc = new RTCPeerConnection(); 200 t.add_cleanup(() => pc.close()); 201 202 pc.addTransceiver('audio', { direction: 'recvonly' }); 203 const sldPromise = pc.setLocalDescription(await pc.createOffer()); 204 205 assert_equals(pc.signalingState, "stable", "signalingState should not be set synchronously after a call to sLD"); 206 207 assert_equals(pc.pendingLocalDescription, null, "pendingRemoteDescription should never be set due to sLD"); 208 assert_equals(pc.pendingRemoteDescription, null, "pendingLocalDescription should not be set synchronously after a call to sLD"); 209 assert_equals(pc.currentLocalDescription, null, "currentLocalDescription should not be set synchronously after a call to sLD"); 210 assert_equals(pc.currentRemoteDescription, null, "currentRemoteDescription should not be set synchronously after a call to sLD"); 211 212 const statePromise = new Promise(resolve => { 213 pc.onsignalingstatechange = () => { 214 resolve(pc.signalingState); 215 } 216 }); 217 const raceValue = await Promise.race([statePromise, sldPromise]); 218 assert_equals(raceValue, "have-local-offer", "signalingstatechange event should fire before sLD resolves"); 219 assert_equals(pc.pendingRemoteDescription, null, "pendingRemoteDescription should never be set due to sLD"); 220 assert_not_equals(pc.pendingLocalDescription, null, "pendingLocalDescription should be updated before the signalingstatechange event"); 221 assert_equals(pc.pendingLocalDescription.type, "offer"); 222 assert_equals(pc.pendingLocalDescription, pc.localDescription); 223 assert_equals(pc.currentLocalDescription, null, "currentLocalDescription should never be updated due to sLD(offer)"); 224 assert_equals(pc.currentRemoteDescription, null, "currentRemoteDescription should never be updated due to sLD(offer)"); 225 226 await sldPromise; 227 }, "setLocalDescription(offer) should update internal state with a queued task, in the right order"); 228 229 </script>