RTCPeerConnection-connectionState.https.html (12147B)
1 <!doctype html> 2 <meta charset=utf-8> 3 <title>RTCPeerConnection.prototype.connectionState</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 // Test is based on the following editor draft: 10 // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.htm 11 12 // The following helper functions are called from RTCPeerConnection-helper.js: 13 // exchangeIceCandidates 14 // exchangeOfferAnswer 15 16 /* 17 4.3.2. Interface Definition 18 interface RTCPeerConnection : EventTarget { 19 ... 20 readonly attribute RTCPeerConnectionState connectionState; 21 attribute EventHandler onconnectionstatechange; 22 }; 23 24 4.4.3. RTCPeerConnectionState Enum 25 enum RTCPeerConnectionState { 26 "new", 27 "connecting", 28 "connected", 29 "disconnected", 30 "failed", 31 "closed" 32 }; 33 34 5.5. RTCDtlsTransport Interface 35 interface RTCDtlsTransport { 36 readonly attribute RTCIceTransport iceTransport; 37 readonly attribute RTCDtlsTransportState state; 38 ... 39 }; 40 41 enum RTCDtlsTransportState { 42 "new", 43 "connecting", 44 "connected", 45 "closed", 46 "failed" 47 }; 48 49 5.6. RTCIceTransport Interface 50 interface RTCIceTransport { 51 readonly attribute RTCIceTransportState state; 52 ... 53 }; 54 55 enum RTCIceTransportState { 56 "new", 57 "checking", 58 "connected", 59 "completed", 60 "failed", 61 "disconnected", 62 "closed" 63 }; 64 */ 65 66 /* 67 4.4.3. RTCPeerConnectionState Enum 68 new 69 Any of the RTCIceTransports or RTCDtlsTransports are in the new 70 state and none of the transports are in the connecting, checking, 71 failed or disconnected state, or all transports are in the closed state. 72 */ 73 test(t => { 74 const pc = new RTCPeerConnection(); 75 assert_equals(pc.connectionState, 'new'); 76 }, 'Initial connectionState should be new'); 77 78 test(t => { 79 const pc = new RTCPeerConnection(); 80 pc.close(); 81 assert_equals(pc.connectionState, 'closed'); 82 }, 'Closing the connection should set connectionState to closed'); 83 84 /* 85 4.4.3. RTCPeerConnectionState Enum 86 connected 87 All RTCIceTransports and RTCDtlsTransports are in the connected, 88 completed or closed state and at least of them is in the connected 89 or completed state. 90 91 5.5. RTCDtlsTransportState 92 connected 93 DTLS has completed negotiation of a secure connection. 94 95 5.6. RTCIceTransportState 96 connected 97 The RTCIceTransport has found a usable connection, but is still 98 checking other candidate pairs to see if there is a better connection. 99 It may also still be gathering and/or waiting for additional remote 100 candidates. If consent checks [RFC7675] fail on the connection in use, 101 and there are no other successful candidate pairs available, then the 102 state transitions to "checking" (if there are candidate pairs remaining 103 to be checked) or "disconnected" (if there are no candidate pairs to 104 check, but the peer is still gathering and/or waiting for additional 105 remote candidates). 106 107 completed 108 The RTCIceTransport has finished gathering, received an indication that 109 there are no more remote candidates, finished checking all candidate 110 pairs and found a connection. If consent checks [RFC7675] subsequently 111 fail on all successful candidate pairs, the state transitions to "failed". 112 */ 113 114 async_test(t => { 115 const pc1 = new RTCPeerConnection(); 116 t.add_cleanup(() => pc1.close()); 117 const pc2 = new RTCPeerConnection(); 118 t.add_cleanup(() => pc2.close()); 119 120 let had_connecting = false; 121 122 const onConnectionStateChange = t.step_func(() => { 123 const {connectionState} = pc1; 124 if (connectionState === 'connecting') { 125 had_connecting = true; 126 } else if (connectionState === 'connected') { 127 assert_true(had_connecting, "state should pass connecting before reaching connected"); 128 t.done(); 129 } 130 }); 131 132 pc1.createDataChannel('test'); 133 134 pc1.addEventListener('connectionstatechange', onConnectionStateChange); 135 136 exchangeIceCandidates(pc1, pc2); 137 exchangeOfferAnswer(pc1, pc2); 138 }, 'connection with one data channel should eventually have connected connection state'); 139 140 async_test(t => { 141 const pc1 = new RTCPeerConnection(); 142 t.add_cleanup(() => pc1.close()); 143 const pc2 = new RTCPeerConnection(); 144 t.add_cleanup(() => pc2.close()); 145 146 const onConnectionStateChange = t.step_func(() => { 147 const {connectionState} = pc1; 148 if (connectionState === 'connected') { 149 const sctpTransport = pc1.sctp; 150 151 const dtlsTransport = sctpTransport.transport; 152 assert_equals(dtlsTransport.state, 'connected', 153 'Expect DTLS transport to be in connected state'); 154 155 const iceTransport = dtlsTransport.iceTransport 156 assert_true(iceTransport.state === 'connected' || 157 iceTransport.state === 'completed', 158 'Expect ICE transport to be in connected or completed state'); 159 160 t.done(); 161 } 162 }); 163 164 pc1.createDataChannel('test'); 165 166 pc1.addEventListener('connectionstatechange', onConnectionStateChange); 167 168 exchangeIceCandidates(pc1, pc2); 169 exchangeOfferAnswer(pc1, pc2); 170 }, 'connection with one data channel should eventually have transports in connected state'); 171 172 /* 173 TODO 174 4.4.3. RTCPeerConnectionState Enum 175 connecting 176 Any of the RTCIceTransports or RTCDtlsTransports are in the 177 connecting or checking state and none of them is in the failed state. 178 179 disconnected 180 Any of the RTCIceTransports or RTCDtlsTransports are in the disconnected 181 state and none of them are in the failed or connecting or checking state. 182 183 failed 184 Any of the RTCIceTransports or RTCDtlsTransports are in a failed state. 185 186 closed 187 The RTCPeerConnection object's [[isClosed]] slot is true. 188 189 5.5. RTCDtlsTransportState 190 new 191 DTLS has not started negotiating yet. 192 193 connecting 194 DTLS is in the process of negotiating a secure connection. 195 196 closed 197 The transport has been closed. 198 199 failed 200 The transport has failed as the result of an error (such as a failure 201 to validate the remote fingerprint). 202 203 5.6. RTCIceTransportState 204 new 205 The RTCIceTransport is gathering candidates and/or waiting for 206 remote candidates to be supplied, and has not yet started checking. 207 208 checking 209 The RTCIceTransport has received at least one remote candidate and 210 is checking candidate pairs and has either not yet found a connection 211 or consent checks [RFC7675] have failed on all previously successful 212 candidate pairs. In addition to checking, it may also still be gathering. 213 214 failed 215 The RTCIceTransport has finished gathering, received an indication that 216 there are no more remote candidates, finished checking all candidate pairs, 217 and all pairs have either failed connectivity checks or have lost consent. 218 219 disconnected 220 The ICE Agent has determined that connectivity is currently lost for this 221 RTCIceTransport . This is more aggressive than failed, and may trigger 222 intermittently (and resolve itself without action) on a flaky network. 223 The way this state is determined is implementation dependent. 224 225 Examples include: 226 Losing the network interface for the connection in use. 227 Repeatedly failing to receive a response to STUN requests. 228 229 Alternatively, the RTCIceTransport has finished checking all existing 230 candidates pairs and failed to find a connection (or consent checks 231 [RFC7675] once successful, have now failed), but it is still gathering 232 and/or waiting for additional remote candidates. 233 234 closed 235 The RTCIceTransport has shut down and is no longer responding to STUN requests. 236 */ 237 promise_test(async t => { 238 const caller = new RTCPeerConnection(); 239 t.add_cleanup(() => caller.close()); 240 const callee = new RTCPeerConnection(); 241 t.add_cleanup(() => callee.close()); 242 const stream = await getNoiseStream({audio: true}); 243 t.add_cleanup(() => stream.getTracks().forEach(track => track.stop())); 244 const [track] = stream.getTracks(); 245 caller.addTrack(track, stream); 246 247 await exchangeOfferAnswer(caller, callee); 248 249 assert_equals(caller.iceConnectionState, 'new'); 250 assert_equals(callee.iceConnectionState, 'new'); 251 }, 'connectionState remains new when not adding remote ice candidates'); 252 253 promise_test(async t => { 254 255 const caller = new RTCPeerConnection(); 256 t.add_cleanup(() => caller.close()); 257 const callee = new RTCPeerConnection(); 258 t.add_cleanup(() => callee.close()); 259 const stream = await getNoiseStream({audio: true}); 260 t.add_cleanup(() => stream.getTracks().forEach(track => track.stop())); 261 const [track] = stream.getTracks(); 262 caller.addTrack(track, stream); 263 264 const states = []; 265 caller.addEventListener('connectionstatechange', () => states.push(caller.connectionState)); 266 exchangeIceCandidates(caller, callee); 267 await exchangeOfferAnswer(caller, callee); 268 269 await listenToConnected(caller); 270 271 assert_array_equals(states, ['connecting', 'connected']); 272 }, 'connectionState transitions to connected via connecting'); 273 274 275 // Make the callee act as if not bundle-aware 276 async function exchangeOfferAnswerUnbundled(caller, callee) { 277 const offer = await caller.createOffer(); 278 const sdp = offer.sdp.replace('BUNDLE', 'SOMETHING') 279 .replace(/rtp-hdrext:sdes/g, 'rtp-hdrext:something') 280 .replace(/a=ssrc:/g, 'a=notssrc'); 281 await caller.setLocalDescription(offer); 282 await callee.setRemoteDescription({type: 'offer', sdp}); 283 284 await exchangeAnswer(caller, callee); 285 } 286 287 promise_test(async t => { 288 const pc1 = new RTCPeerConnection({bundlePolicy: 'max-compat'}); 289 t.add_cleanup(() => pc1.close()); 290 const pc2 = new RTCPeerConnection(); 291 t.add_cleanup(() => pc2.close()); 292 const stream = await getNoiseStream({ audio: true }); 293 t.add_cleanup(() => stream.getTracks().forEach(track => track.stop())); 294 stream.getTracks().forEach(track => pc1.addTrack(track, stream)); 295 exchangeIceCandidates(pc1, pc2); 296 exchangeOfferAnswerUnbundled(pc1, pc2); 297 await listenToConnected(pc1); 298 299 // https://github.com/w3c/webrtc-pc/issues/2678#issuecomment-948554126 300 let had_intermediary_connecting = false 301 let channel; 302 const onConnectionStateChange = t.step_func(() => { 303 const {connectionState, iceConnectionState} = pc1; 304 if (connectionState === 'connecting') { 305 had_intermediary_connecting = true; 306 } 307 }); 308 309 pc1.addEventListener('connectionstatechange', onConnectionStateChange); 310 channel = pc1.createDataChannel('test'); 311 await exchangeOfferAnswer(pc1, pc2); 312 await listenToConnected(pc1); 313 314 assert_true(had_intermediary_connecting, "state should re-pass connecting before reaching connected"); 315 }, 'when adding a datachannel to an existing unbundled connected PC, it should go through a connecting state'); 316 317 318 promise_test(async t => { 319 const pc1 = new RTCPeerConnection(); 320 t.add_cleanup(() => pc1.close()); 321 const pc2 = new RTCPeerConnection(); 322 const stream = await getNoiseStream({ audio: true }); 323 t.add_cleanup(() => stream.getTracks().forEach(track => track.stop())); 324 325 stream.getTracks().forEach(track => pc1.addTrack(track, stream)); 326 exchangeIceCandidates(pc1, pc2); 327 exchangeOfferAnswer(pc1, pc2); 328 await listenToIceConnected(pc2); 329 330 pc2.onconnectionstatechange = t.unreached_func(); 331 pc2.close(); 332 assert_equals(pc2.connectionState, 'closed'); 333 await new Promise(r => t.step_timeout(r, 100)); 334 }, 'Closing a PeerConnection should not fire connectionstatechange event'); 335 </script>