| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 1 | <!doctype html> | 
|  | 2 | <title>Test RTCPeerConnection.prototype.addIceCandidate</title> | 
|  | 3 | <script src="/resources/testharness.js"></script> | 
|  | 4 | <script src="/resources/testharnessreport.js"></script> | 
|  | 5 | <script> | 
|  | 6 | 'use strict'; | 
|  | 7 |  | 
| Soares Chen | 25c2390 | 2017-06-27 15:32:57 | [diff] [blame] | 8 | // Test is based on the following editor draft: | 
|  | 9 | // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.htm | 
|  | 10 |  | 
| Soares Chen | 25c2390 | 2017-06-27 15:32:57 | [diff] [blame] | 11 | /* | 
|  | 12 | 4.3.2. Interface Definition | 
|  | 13 | interface RTCPeerConnection : EventTarget { | 
|  | 14 | ... | 
|  | 15 | Promise<void> addIceCandidate((RTCIceCandidateInit or RTCIceCandidate) candidate); | 
|  | 16 | }; | 
|  | 17 |  | 
|  | 18 | interface RTCIceCandidate { | 
|  | 19 | readonly attribute DOMString candidate; | 
|  | 20 | readonly attribute DOMString? sdpMid; | 
|  | 21 | readonly attribute unsigned short? sdpMLineIndex; | 
|  | 22 | readonly attribute DOMString? ufrag; | 
|  | 23 | ... | 
|  | 24 | }; | 
|  | 25 |  | 
|  | 26 | dictionary RTCIceCandidateInit { | 
|  | 27 | DOMString candidate = ""; | 
|  | 28 | DOMString? sdpMid = null; | 
|  | 29 | unsigned short? sdpMLineIndex = null; | 
|  | 30 | DOMString ufrag; | 
|  | 31 | }; | 
|  | 32 | */ | 
|  | 33 |  | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 34 | // SDP copied from JSEP Example 7.1 | 
|  | 35 | // It contains two media streams with different ufrags | 
|  | 36 | // to test if candidate is added to the correct stream | 
|  | 37 | const sdp = `v=0 | 
|  | 38 | o=- 4962303333179871722 1 IN IP4 0.0.0.0 | 
|  | 39 | s=- | 
|  | 40 | t=0 0 | 
|  | 41 | a=ice-options:trickle | 
|  | 42 | a=group:BUNDLE a1 v1 | 
|  | 43 | a=group:LS a1 v1 | 
|  | 44 | m=audio 10100 UDP/TLS/RTP/SAVPF 96 0 8 97 98 | 
|  | 45 | c=IN IP4 203.0.113.100 | 
|  | 46 | a=mid:a1 | 
|  | 47 | a=sendrecv | 
|  | 48 | a=rtpmap:96 opus/48000/2 | 
|  | 49 | a=rtpmap:0 PCMU/8000 | 
|  | 50 | a=rtpmap:8 PCMA/8000 | 
|  | 51 | a=rtpmap:97 telephone-event/8000 | 
|  | 52 | a=rtpmap:98 telephone-event/48000 | 
|  | 53 | a=maxptime:120 | 
|  | 54 | a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid | 
|  | 55 | a=extmap:2 urn:ietf:params:rtp-hdrext:ssrc-audio-level | 
|  | 56 | a=msid:47017fee-b6c1-4162-929c-a25110252400 f83006c5-a0ff-4e0a-9ed9-d3e6747be7d9 | 
|  | 57 | a=ice-ufrag:ETEn | 
|  | 58 | a=ice-pwd:OtSK0WpNtpUjkY4+86js7ZQl | 
|  | 59 | a=fingerprint:sha-256 19:E2:1C:3B:4B:9F:81:E6:B8:5C:F4:A5:A8:D8:73:04:BB:05:2F:70:9F:04:A9:0E:05:E9:26:33:E8:70:88:A2 | 
|  | 60 | a=setup:actpass | 
|  | 61 | a=dtls-id:1 | 
|  | 62 | a=rtcp:10101 IN IP4 203.0.113.100 | 
|  | 63 | a=rtcp-mux | 
|  | 64 | a=rtcp-rsize | 
|  | 65 | m=video 10102 UDP/TLS/RTP/SAVPF 100 101 | 
|  | 66 | c=IN IP4 203.0.113.100 | 
|  | 67 | a=mid:v1 | 
|  | 68 | a=sendrecv | 
|  | 69 | a=rtpmap:100 VP8/90000 | 
|  | 70 | a=rtpmap:101 rtx/90000 | 
|  | 71 | a=fmtp:101 apt=100 | 
|  | 72 | a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid | 
|  | 73 | a=rtcp-fb:100 ccm fir | 
|  | 74 | a=rtcp-fb:100 nack | 
|  | 75 | a=rtcp-fb:100 nack pli | 
|  | 76 | a=msid:47017fee-b6c1-4162-929c-a25110252400 f30bdb4a-5db8-49b5-bcdc-e0c9a23172e0 | 
|  | 77 | a=ice-ufrag:BGKk | 
|  | 78 | a=ice-pwd:mqyWsAjvtKwTGnvhPztQ9mIf | 
|  | 79 | a=fingerprint:sha-256 19:E2:1C:3B:4B:9F:81:E6:B8:5C:F4:A5:A8:D8:73:04:BB:05:2F:70:9F:04:A9:0E:05:E9:26:33:E8:70:88:A2 | 
|  | 80 | a=setup:actpass | 
|  | 81 | a=dtls-id:1 | 
|  | 82 | a=rtcp:10103 IN IP4 203.0.113.100 | 
|  | 83 | a=rtcp-mux | 
|  | 84 | a=rtcp-rsize | 
|  | 85 | `; | 
|  | 86 |  | 
|  | 87 | const sessionDesc = { type: 'offer', sdp }; | 
|  | 88 |  | 
|  | 89 | // valid candidate attributes | 
|  | 90 | const sdpMid = 'a1'; | 
|  | 91 | const sdpMLineIndex = 0; | 
|  | 92 | const ufrag = 'ETEn'; | 
|  | 93 |  | 
|  | 94 | const sdpMid2 = 'v1'; | 
|  | 95 | const sdpMLineIndex2 = 1; | 
|  | 96 | const ufrag2 = 'BGKk'; | 
|  | 97 |  | 
|  | 98 | const mediaLine1 = 'm=audio'; | 
|  | 99 | const mediaLine2 = 'm=video'; | 
|  | 100 |  | 
|  | 101 | const candidateStr1 = 'candidate:1 1 udp 2113929471 203.0.113.100 10100 typ host'; | 
|  | 102 | const candidateStr2 = 'candidate:1 2 udp 2113929470 203.0.113.100 10101 typ host'; | 
|  | 103 | const invalidCandidateStr = '(Invalid) candidate \r\n string'; | 
|  | 104 |  | 
|  | 105 | const candidateLine1 = `a=${candidateStr1}`; | 
|  | 106 | const candidateLine2 = `a=${candidateStr2}`; | 
|  | 107 | const endOfCandidateLine = 'a=end-of-candidates'; | 
|  | 108 |  | 
|  | 109 | // Copied from MDN | 
|  | 110 | function escapeRegExp(string) { | 
|  | 111 | return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | 
|  | 112 | } | 
|  | 113 |  | 
|  | 114 | // Check that a candidate line is found after the first media line | 
|  | 115 | // but before the second, i.e. it belongs to the first media stream | 
|  | 116 | function assert_candidate_line_between(sdp, beforeMediaLine, candidateLine, afterMediaLine) { | 
|  | 117 | const line1 = escapeRegExp(beforeMediaLine); | 
|  | 118 | const line2 = escapeRegExp(candidateLine); | 
|  | 119 | const line3 = escapeRegExp(afterMediaLine); | 
|  | 120 |  | 
|  | 121 | const regex = new RegExp(`${line1}[^]+${line2}[^]+${line3}`); | 
|  | 122 |  | 
|  | 123 | assert_true(regex.test(sdp), | 
|  | 124 | `Expect candidate line to be found between media lines ${beforeMediaLine} and ${afterMediaLine}`); | 
|  | 125 | } | 
|  | 126 |  | 
|  | 127 | // Check that a candidate line is found after the second media line | 
|  | 128 | // i.e. it belongs to the second media stream | 
|  | 129 | function assert_candidate_line_after(sdp, beforeMediaLine, candidateLine) { | 
|  | 130 | const line1 = escapeRegExp(beforeMediaLine); | 
|  | 131 | const line2 = escapeRegExp(candidateLine); | 
|  | 132 |  | 
|  | 133 | const regex = new RegExp(`${line1}[^]+${line2}`); | 
|  | 134 |  | 
|  | 135 | assert_true(regex.test(sdp), | 
|  | 136 | `Expect candidate line to be found after media line ${beforeMediaLine}`); | 
|  | 137 | } | 
|  | 138 |  | 
| Soares Chen | 25c2390 | 2017-06-27 15:32:57 | [diff] [blame] | 139 | // Reject because WebIDL for addIceCandidate does not allow null argument | 
|  | 140 | // null can be accidentally passed from onicecandidate event handler | 
|  | 141 | // when null is used to indicate end of candidate | 
|  | 142 | promise_test(t => { | 
|  | 143 | const pc = new RTCPeerConnection(); | 
|  | 144 |  | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame^] | 145 | t.add_cleanup(() => pc.close()); | 
|  | 146 |  | 
| Soares Chen | 25c2390 | 2017-06-27 15:32:57 | [diff] [blame] | 147 | return pc.setRemoteDescription(sessionDesc) | 
|  | 148 | .then(() => | 
|  | 149 | promise_rejects(t, new TypeError(), | 
|  | 150 | pc.addIceCandidate(null))); | 
|  | 151 | }, 'Add null candidate should reject with TypeError'); | 
|  | 152 |  | 
|  | 153 | /* | 
|  | 154 | 4.3.2. addIceCandidate | 
|  | 155 | 4. Return the result of enqueuing the following steps: | 
|  | 156 | 1. If remoteDescription is null return a promise rejected with a | 
|  | 157 | newly created InvalidStateError. | 
|  | 158 | */ | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 159 | promise_test(t => { | 
|  | 160 | const pc = new RTCPeerConnection(); | 
|  | 161 |  | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame^] | 162 | t.add_cleanup(() => pc.close()); | 
|  | 163 |  | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 164 | return promise_rejects(t, 'InvalidStateError', | 
|  | 165 | pc.addIceCandidate({ | 
|  | 166 | candidate: candidateStr1, | 
|  | 167 | sdpMid, sdpMLineIndex, ufrag | 
|  | 168 | })); | 
|  | 169 | }, 'Add ICE candidate before setting remote description should reject with InvalidStateError'); | 
|  | 170 |  | 
| Soares Chen | 25c2390 | 2017-06-27 15:32:57 | [diff] [blame] | 171 | /* | 
| Soares Chen | 25c2390 | 2017-06-27 15:32:57 | [diff] [blame] | 172 | Success cases | 
|  | 173 | */ | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 174 | promise_test(t => { | 
|  | 175 | const pc = new RTCPeerConnection(); | 
|  | 176 |  | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame^] | 177 | t.add_cleanup(() => pc.close()); | 
|  | 178 |  | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 179 | return pc.setRemoteDescription(sessionDesc) | 
|  | 180 | .then(() => pc.addIceCandidate({ | 
|  | 181 | candidate: candidateStr1, | 
|  | 182 | sdpMid, sdpMLineIndex, ufrag | 
|  | 183 | })); | 
|  | 184 | }, 'Add ICE candidate after setting remote description should succeed'); | 
|  | 185 |  | 
|  | 186 | promise_test(t => { | 
|  | 187 | const pc = new RTCPeerConnection(); | 
|  | 188 |  | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame^] | 189 | t.add_cleanup(() => pc.close()); | 
|  | 190 |  | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 191 | return pc.setRemoteDescription(sessionDesc) | 
|  | 192 | .then(() => pc.addIceCandidate(new RTCIceCandidate({ | 
|  | 193 | candidate: candidateStr1, | 
|  | 194 | sdpMid, sdpMLineIndex, ufrag | 
|  | 195 | }))); | 
|  | 196 | }, 'Add ICE candidate with RTCIceCandidate should succeed'); | 
|  | 197 |  | 
|  | 198 | promise_test(t => { | 
|  | 199 | const pc = new RTCPeerConnection(); | 
|  | 200 |  | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame^] | 201 | t.add_cleanup(() => pc.close()); | 
|  | 202 |  | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 203 | return pc.setRemoteDescription(sessionDesc) | 
| Soares Chen | 25c2390 | 2017-06-27 15:32:57 | [diff] [blame] | 204 | .then(() => pc.addIceCandidate({ sdpMid })); | 
|  | 205 | }, 'Add candidate with only valid sdpMid should succeed'); | 
|  | 206 |  | 
|  | 207 | promise_test(t => { | 
|  | 208 | const pc = new RTCPeerConnection(); | 
|  | 209 |  | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame^] | 210 | t.add_cleanup(() => pc.close()); | 
|  | 211 |  | 
| Soares Chen | 25c2390 | 2017-06-27 15:32:57 | [diff] [blame] | 212 | return pc.setRemoteDescription(sessionDesc) | 
|  | 213 | .then(() => pc.addIceCandidate({ sdpMLineIndex })); | 
|  | 214 | }, 'Add candidate with only valid sdpMLineIndex should succeed'); | 
|  | 215 |  | 
|  | 216 | /* | 
|  | 217 | 4.3.2. addIceCandidate | 
|  | 218 | 4.6.2. If candidate is applied successfully, the user agent MUST queue | 
|  | 219 | a task that runs the following steps: | 
|  | 220 | 2. Let remoteDescription be connection's pendingRemoteDescription | 
|  | 221 | if not null, otherwise connection's currentRemoteDescription. | 
|  | 222 | 3. Add candidate to remoteDescription. | 
|  | 223 | */ | 
|  | 224 | promise_test(t => { | 
|  | 225 | const pc = new RTCPeerConnection(); | 
|  | 226 |  | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame^] | 227 | t.add_cleanup(() => pc.close()); | 
|  | 228 |  | 
| Soares Chen | 25c2390 | 2017-06-27 15:32:57 | [diff] [blame] | 229 | return pc.setRemoteDescription(sessionDesc) | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 230 | .then(() => pc.addIceCandidate({ | 
|  | 231 | candidate: candidateStr1, | 
|  | 232 | sdpMid, sdpMLineIndex, ufrag | 
|  | 233 | })) | 
|  | 234 | .then(() => { | 
|  | 235 | assert_candidate_line_between(pc.remoteDescription.sdp, | 
|  | 236 | mediaLine1, candidateLine1, mediaLine2); | 
|  | 237 | }); | 
| Soares Chen | 25c2390 | 2017-06-27 15:32:57 | [diff] [blame] | 238 | }, 'addIceCandidate with first sdpMid and sdpMLineIndex add candidate to first media stream'); | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 239 |  | 
|  | 240 | promise_test(t => { | 
|  | 241 | const pc = new RTCPeerConnection(); | 
|  | 242 |  | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame^] | 243 | t.add_cleanup(() => pc.close()); | 
|  | 244 |  | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 245 | return pc.setRemoteDescription(sessionDesc) | 
|  | 246 | .then(() => pc.addIceCandidate({ | 
|  | 247 | candidate: candidateStr2, | 
|  | 248 | sdpMid: sdpMid2, | 
|  | 249 | sdpMLineIndex: sdpMLineIndex2, | 
|  | 250 | ufrag: ufrag2 | 
|  | 251 | })) | 
|  | 252 | .then(() => { | 
|  | 253 | assert_candidate_line_after(pc.remoteDescription.sdp, | 
|  | 254 | mediaLine2, candidateLine2); | 
|  | 255 | }); | 
| Soares Chen | 25c2390 | 2017-06-27 15:32:57 | [diff] [blame] | 256 | }, 'addIceCandidate with second sdpMid and sdpMLineIndex should add candidate to second media stream'); | 
|  | 257 |  | 
|  | 258 | promise_test(t => { | 
|  | 259 | const pc = new RTCPeerConnection(); | 
|  | 260 |  | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame^] | 261 | t.add_cleanup(() => pc.close()); | 
|  | 262 |  | 
| Soares Chen | 25c2390 | 2017-06-27 15:32:57 | [diff] [blame] | 263 | return pc.setRemoteDescription(sessionDesc) | 
|  | 264 | .then(() => pc.addIceCandidate({ | 
|  | 265 | candidate: candidateStr1, | 
|  | 266 | sdpMid, sdpMLineIndex, | 
|  | 267 | ufrag: null | 
|  | 268 | })) | 
|  | 269 | .then(() => { | 
|  | 270 | assert_candidate_line_between(pc.remoteDescription.sdp, | 
|  | 271 | mediaLine1, candidateLine1, mediaLine2); | 
|  | 272 | }); | 
|  | 273 | }, 'Add candidate for first media stream with null ufrag should add candidate to first media stream'); | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 274 |  | 
|  | 275 | promise_test(t => { | 
|  | 276 | const pc = new RTCPeerConnection(); | 
|  | 277 |  | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame^] | 278 | t.add_cleanup(() => pc.close()); | 
|  | 279 |  | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 280 | return pc.setRemoteDescription(sessionDesc) | 
|  | 281 | .then(() => pc.addIceCandidate({ | 
|  | 282 | candidate: candidateStr1, | 
|  | 283 | sdpMid, sdpMLineIndex, ufrag | 
|  | 284 | })) | 
|  | 285 | .then(() => pc.addIceCandidate({ | 
|  | 286 | candidate: candidateStr2, | 
|  | 287 | sdpMid: sdpMid2, | 
|  | 288 | sdpMLineIndex: sdpMLineIndex2, | 
|  | 289 | ufrag: ufrag2 | 
|  | 290 | })) | 
|  | 291 | .then(() => { | 
|  | 292 | assert_candidate_line_between(pc.remoteDescription.sdp, | 
|  | 293 | mediaLine1, candidateLine1, mediaLine2); | 
|  | 294 |  | 
|  | 295 | assert_candidate_line_after(pc.remoteDescription.sdp, | 
|  | 296 | mediaLine2, candidateLine2); | 
|  | 297 | }); | 
| Soares Chen | 25c2390 | 2017-06-27 15:32:57 | [diff] [blame] | 298 | }, 'Adding multiple candidates should add candidates to their corresponding media stream'); | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 299 |  | 
| Soares Chen | 25c2390 | 2017-06-27 15:32:57 | [diff] [blame] | 300 | /* | 
|  | 301 | 4.3.2. addIceCandidate | 
|  | 302 | 4.6. If candidate.candidate is an empty string, process candidate as an | 
|  | 303 | end-of-candidates indication for the corresponding media description | 
|  | 304 | and ICE candidate generation. | 
|  | 305 | 2. If candidate is applied successfully, the user agent MUST queue | 
|  | 306 | a task that runs the following steps: | 
|  | 307 | 2. Let remoteDescription be connection's pendingRemoteDescription | 
|  | 308 | if not null, otherwise connection's currentRemoteDescription. | 
|  | 309 | 3. Add candidate to remoteDescription. | 
|  | 310 | */ | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 311 | promise_test(t => { | 
|  | 312 | const pc = new RTCPeerConnection(); | 
|  | 313 |  | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame^] | 314 | t.add_cleanup(() => pc.close()); | 
|  | 315 |  | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 316 | return pc.setRemoteDescription(sessionDesc) | 
|  | 317 | .then(() => pc.addIceCandidate({ | 
|  | 318 | candidate: candidateStr1, | 
|  | 319 | sdpMid, sdpMLineIndex, ufrag | 
|  | 320 | })) | 
|  | 321 | .then(() => pc.addIceCandidate({ | 
|  | 322 | candidate: '', | 
|  | 323 | sdpMid, sdpMLineIndex, | 
|  | 324 | ufrag | 
|  | 325 | })) | 
|  | 326 | .then(() => { | 
|  | 327 | assert_candidate_line_between(pc.remoteDescription.sdp, | 
|  | 328 | mediaLine1, candidateLine1, mediaLine2); | 
|  | 329 |  | 
|  | 330 | assert_candidate_line_between(pc.remoteDescription.sdp, | 
|  | 331 | mediaLine1, endOfCandidateLine, mediaLine2); | 
|  | 332 | }); | 
|  | 333 | }, 'Add with empty candidate string (end of candidate) should succeed'); | 
|  | 334 |  | 
| Soares Chen | 25c2390 | 2017-06-27 15:32:57 | [diff] [blame] | 335 | /* | 
|  | 336 | 4.3.2. addIceCandidate | 
|  | 337 | 3. If both sdpMid and sdpMLineIndex are null, return a promise rejected | 
|  | 338 | with a newly created TypeError. | 
|  | 339 | */ | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 340 | promise_test(t => { | 
|  | 341 | const pc = new RTCPeerConnection(); | 
|  | 342 |  | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame^] | 343 | t.add_cleanup(() => pc.close()); | 
|  | 344 |  | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 345 | return pc.setRemoteDescription(sessionDesc) | 
|  | 346 | .then(() => | 
|  | 347 | promise_rejects(t, new TypeError(), | 
|  | 348 | pc.addIceCandidate({ | 
|  | 349 | candidate: candidateStr1, | 
|  | 350 | sdpMid: null, | 
|  | 351 | sdpMLineIndex: null | 
|  | 352 | }))); | 
|  | 353 | }, 'Add candidate with both sdpMid and sdpMLineIndex manually set to null should reject with TypeError'); | 
|  | 354 |  | 
|  | 355 | promise_test(t => { | 
|  | 356 | const pc = new RTCPeerConnection(); | 
|  | 357 |  | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame^] | 358 | t.add_cleanup(() => pc.close()); | 
|  | 359 |  | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 360 | return pc.setRemoteDescription(sessionDesc) | 
|  | 361 | .then(() => | 
| Soares Chen | 25c2390 | 2017-06-27 15:32:57 | [diff] [blame] | 362 | promise_rejects(t, new TypeError(), | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 363 | pc.addIceCandidate({ | 
| Soares Chen | 25c2390 | 2017-06-27 15:32:57 | [diff] [blame] | 364 | candidate: candidateStr1 | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 365 | }))); | 
| Soares Chen | 25c2390 | 2017-06-27 15:32:57 | [diff] [blame] | 366 | }, 'Add candidate with only valid candidate string should reject with TypeError'); | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 367 |  | 
|  | 368 | promise_test(t => { | 
|  | 369 | const pc = new RTCPeerConnection(); | 
|  | 370 |  | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame^] | 371 | t.add_cleanup(() => pc.close()); | 
|  | 372 |  | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 373 | return pc.setRemoteDescription(sessionDesc) | 
|  | 374 | .then(() => | 
|  | 375 | promise_rejects(t, new TypeError(), | 
|  | 376 | pc.addIceCandidate({ | 
|  | 377 | candidate: invalidCandidateStr, | 
|  | 378 | sdpMid: null, | 
|  | 379 | sdpMLineIndex: null | 
|  | 380 | }))); | 
|  | 381 | }, 'Add candidate with invalid candidate string and both sdpMid and sdpMLineIndex null should reject with TypeError'); | 
|  | 382 |  | 
|  | 383 | promise_test(t => { | 
|  | 384 | const pc = new RTCPeerConnection(); | 
|  | 385 |  | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame^] | 386 | t.add_cleanup(() => pc.close()); | 
|  | 387 |  | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 388 | return pc.setRemoteDescription(sessionDesc) | 
|  | 389 | .then(() => | 
|  | 390 | promise_rejects(t, new TypeError(), | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 391 | pc.addIceCandidate({}))); | 
|  | 392 | }, 'Add candidate with empty dict should reject with TypeError'); | 
|  | 393 |  | 
|  | 394 | promise_test(t => { | 
|  | 395 | const pc = new RTCPeerConnection(); | 
|  | 396 |  | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame^] | 397 | t.add_cleanup(() => pc.close()); | 
|  | 398 |  | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 399 | return pc.setRemoteDescription(sessionDesc) | 
|  | 400 | .then(() => | 
|  | 401 | promise_rejects(t, new TypeError(), | 
|  | 402 | pc.addIceCandidate({ | 
|  | 403 | candidate: '', | 
|  | 404 | sdpMid: null, | 
|  | 405 | sdpMLineIndex: null, | 
|  | 406 | ufrag: undefined | 
|  | 407 | }))); | 
|  | 408 | }, 'Add candidate with manually filled default values should reject with TypeError'); | 
|  | 409 |  | 
| Soares Chen | 25c2390 | 2017-06-27 15:32:57 | [diff] [blame] | 410 | /* | 
|  | 411 | 4.3.2. addIceCandidate | 
|  | 412 | 4.3. If candidate.sdpMid is not null, run the following steps: | 
|  | 413 | 1. If candidate.sdpMid is not equal to the mid of any media | 
|  | 414 | description in remoteDescription , reject p with a newly | 
|  | 415 | created OperationError and abort these steps. | 
|  | 416 | */ | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 417 | promise_test(t => { | 
|  | 418 | const pc = new RTCPeerConnection(); | 
|  | 419 |  | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame^] | 420 | t.add_cleanup(() => pc.close()); | 
|  | 421 |  | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 422 | return pc.setRemoteDescription(sessionDesc) | 
|  | 423 | .then(() => | 
|  | 424 | promise_rejects(t, 'OperationError', | 
|  | 425 | pc.addIceCandidate({ | 
|  | 426 | candidate: candidateStr1, | 
|  | 427 | sdpMid: 'invalid', sdpMLineIndex, ufrag | 
|  | 428 | }))); | 
|  | 429 | }, 'Add candidate with invalid sdpMid should reject with OperationError'); | 
|  | 430 |  | 
| Soares Chen | 25c2390 | 2017-06-27 15:32:57 | [diff] [blame] | 431 | /* | 
|  | 432 | 4.3.2. addIceCandidate | 
|  | 433 | 4.4. Else, if candidate.sdpMLineIndex is not null, run the following | 
|  | 434 | steps: | 
|  | 435 | 1. If candidate.sdpMLineIndex is equal to or larger than the | 
|  | 436 | number of media descriptions in remoteDescription , reject p | 
|  | 437 | with a newly created OperationError and abort these steps. | 
|  | 438 | */ | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 439 | promise_test(t => { | 
|  | 440 | const pc = new RTCPeerConnection(); | 
|  | 441 |  | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame^] | 442 | t.add_cleanup(() => pc.close()); | 
|  | 443 |  | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 444 | return pc.setRemoteDescription(sessionDesc) | 
|  | 445 | .then(() => | 
|  | 446 | promise_rejects(t, 'OperationError', | 
|  | 447 | pc.addIceCandidate({ | 
|  | 448 | candidate: candidateStr1, | 
|  | 449 | sdpMLineIndex: 2, | 
|  | 450 | ufrag | 
|  | 451 | }))); | 
|  | 452 | }, 'Add candidate with invalid sdpMLineIndex should reject with OperationError'); | 
|  | 453 |  | 
|  | 454 | // There is an "Else" for the statement: | 
|  | 455 | // "Else, if candidate.sdpMLineIndex is not null, ..." | 
|  | 456 | promise_test(t => { | 
|  | 457 | const pc = new RTCPeerConnection(); | 
|  | 458 |  | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame^] | 459 | t.add_cleanup(() => pc.close()); | 
|  | 460 |  | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 461 | return pc.setRemoteDescription(sessionDesc) | 
|  | 462 | .then(() => pc.addIceCandidate({ | 
|  | 463 | candidate: candidateStr1, | 
|  | 464 | sdpMid, | 
|  | 465 | sdpMLineIndex: 2, | 
|  | 466 | ufrag | 
|  | 467 | })); | 
|  | 468 | }, 'Invalid sdpMLineIndex should be ignored if valid sdpMid is provided'); | 
|  | 469 |  | 
|  | 470 | promise_test(t => { | 
|  | 471 | const pc = new RTCPeerConnection(); | 
|  | 472 |  | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame^] | 473 | t.add_cleanup(() => pc.close()); | 
|  | 474 |  | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 475 | return pc.setRemoteDescription(sessionDesc) | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 476 | .then(() => pc.addIceCandidate({ | 
|  | 477 | candidate: candidateStr2, | 
|  | 478 | sdpMid: sdpMid2, | 
|  | 479 | sdpMLineIndex: sdpMLineIndex2, | 
|  | 480 | ufrag: null | 
|  | 481 | })) | 
|  | 482 | .then(() => { | 
|  | 483 | assert_candidate_line_after(pc.remoteDescription.sdp, | 
|  | 484 | mediaLine2, candidateLine2); | 
|  | 485 | }); | 
|  | 486 | }, 'Add candidate for media stream 2 with null ufrag should succeed'); | 
|  | 487 |  | 
| Soares Chen | 25c2390 | 2017-06-27 15:32:57 | [diff] [blame] | 488 | /* | 
|  | 489 | 4.3.2. addIceCandidate | 
|  | 490 | 4.5. If candidate.ufrag is neither undefined nor null, and is not equal | 
|  | 491 | to any ufrag present in the corresponding media description of an | 
|  | 492 | applied remote description, reject p with a newly created | 
|  | 493 | OperationError and abort these steps. | 
|  | 494 | */ | 
|  | 495 | promise_test(t => { | 
|  | 496 | const pc = new RTCPeerConnection(); | 
|  | 497 |  | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame^] | 498 | t.add_cleanup(() => pc.close()); | 
|  | 499 |  | 
| Soares Chen | 25c2390 | 2017-06-27 15:32:57 | [diff] [blame] | 500 | return pc.setRemoteDescription(sessionDesc) | 
|  | 501 | .then(() => | 
|  | 502 | promise_rejects(t, 'OperationError', | 
|  | 503 | pc.addIceCandidate({ | 
|  | 504 | candidate: candidateStr1, | 
|  | 505 | sdpMid, sdpMLineIndex, | 
|  | 506 | ufrag: 'invalid' | 
|  | 507 | }))); | 
|  | 508 | }, 'Add candidate with invalid ufrag should reject with OperationError'); | 
|  | 509 |  | 
|  | 510 | /* | 
|  | 511 | 4.3.2. addIceCandidate | 
|  | 512 | 4.6.1. If candidate could not be successfully added the user agent MUST | 
|  | 513 | queue a task that runs the following steps: | 
|  | 514 | 2. Reject p with a DOMException object whose name attribute has | 
|  | 515 | the value OperationError and abort these steps. | 
|  | 516 | */ | 
|  | 517 | promise_test(t => { | 
|  | 518 | const pc = new RTCPeerConnection(); | 
|  | 519 |  | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame^] | 520 | t.add_cleanup(() => pc.close()); | 
|  | 521 |  | 
| Soares Chen | 25c2390 | 2017-06-27 15:32:57 | [diff] [blame] | 522 | return pc.setRemoteDescription(sessionDesc) | 
|  | 523 | .then(() => | 
|  | 524 | promise_rejects(t, 'OperationError', | 
|  | 525 | pc.addIceCandidate({ | 
|  | 526 | candidate: invalidCandidateStr, | 
|  | 527 | sdpMid, sdpMLineIndex, ufrag | 
|  | 528 | }))); | 
|  | 529 | }, 'Add candidate with invalid candidate string should reject with OperationError'); | 
|  | 530 |  | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 531 | promise_test(t => { | 
|  | 532 | const pc = new RTCPeerConnection(); | 
|  | 533 |  | 
| Philipp Hancke | 1622a02 | 2018-06-11 10:00:53 | [diff] [blame^] | 534 | t.add_cleanup(() => pc.close()); | 
|  | 535 |  | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 536 | return pc.setRemoteDescription(sessionDesc) | 
|  | 537 | .then(() => | 
|  | 538 | promise_rejects(t, 'OperationError', | 
|  | 539 | pc.addIceCandidate({ | 
|  | 540 | candidate: candidateStr2, | 
|  | 541 | sdpMid: sdpMid2, | 
|  | 542 | sdpMLineIndex: sdpMLineIndex2, | 
|  | 543 | ufrag: ufrag | 
|  | 544 | }))); | 
|  | 545 | }, 'Add candidate with sdpMid belonging to different ufrag should reject with OperationError'); | 
|  | 546 |  | 
| Soares Chen | 25c2390 | 2017-06-27 15:32:57 | [diff] [blame] | 547 | /* | 
|  | 548 | TODO | 
|  | 549 | 4.3.2. addIceCandidate | 
|  | 550 | 4.6. In parallel, add the ICE candidate candidate as described in [JSEP] | 
|  | 551 | (section 4.1.17.). Use candidate.ufrag to identify the ICE generation; | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 552 |  | 
| Soares Chen | 25c2390 | 2017-06-27 15:32:57 | [diff] [blame] | 553 | If the ufrag is null, process the candidate for the most recent ICE | 
|  | 554 | generation. | 
|  | 555 |  | 
|  | 556 | - Call with candidate string containing partial malformed syntax, i.e. malformed IP. | 
|  | 557 | Some browsers may ignore the syntax error and add it to the SDP regardless. | 
|  | 558 |  | 
|  | 559 | Non-Testable | 
|  | 560 | 4.3.2. addIceCandidate | 
|  | 561 | 4.6. (The steps are non-testable because the abort step in enqueue operation | 
|  | 562 | steps in before they can reach here): | 
|  | 563 | 1. If candidate could not be successfully added the user agent MUST | 
|  | 564 | queue a task that runs the following steps: | 
|  | 565 | 1. If connection's [[isClosed]] slot is true, then abort | 
|  | 566 | these steps. | 
|  | 567 |  | 
|  | 568 | 2. If candidate is applied successfully, the user agent MUST queue | 
|  | 569 | a task that runs the following steps: | 
|  | 570 | 1. If connection's [[isClosed]] slot is true, then abort these steps. | 
|  | 571 |  | 
|  | 572 | Issues | 
|  | 573 | w3c/webrtc-pc#1213 | 
|  | 574 | addIceCandidate end of candidates woes | 
|  | 575 |  | 
|  | 576 | w3c/webrtc-pc#1216 | 
|  | 577 | Clarify addIceCandidate behavior when adding candidate after end of candidate | 
|  | 578 |  | 
|  | 579 | w3c/webrtc-pc#1227 | 
|  | 580 | addIceCandidate may add ice candidate to the wrong remote description | 
|  | 581 |  | 
|  | 582 | w3c/webrtc-pc#1345 | 
|  | 583 | Make promise rejection/enqueing consistent | 
|  | 584 |  | 
|  | 585 | Coverage Report | 
|  | 586 | Total: 23 | 
|  | 587 | Tested: 19 | 
|  | 588 | Not Tested: 2 | 
|  | 589 | Non-Testable: 2 | 
|  | 590 | */ | 
| Soares Chen | b20cf6b | 2017-05-19 12:28:43 | [diff] [blame] | 591 | </script> |