RTCPeerConnection-addIceCandidate.html (22642B)
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 src="RTCPeerConnection-helper.js"></script> 6 <script> 7 'use strict'; 8 9 // SDP copied from JSEP Example 7.1 10 // It contains two media streams with different ufrags 11 // to test if candidate is added to the correct stream 12 const sdp = `v=0 13 o=- 4962303333179871722 1 IN IP4 0.0.0.0 14 s=- 15 t=0 0 16 a=ice-options:trickle 17 a=group:BUNDLE a1 v1 18 a=group:LS a1 v1 19 m=audio 10100 UDP/TLS/RTP/SAVPF 96 0 8 97 98 20 c=IN IP4 203.0.113.100 21 a=mid:a1 22 a=sendrecv 23 a=rtpmap:96 opus/48000/2 24 a=rtpmap:0 PCMU/8000 25 a=rtpmap:8 PCMA/8000 26 a=rtpmap:97 telephone-event/8000 27 a=rtpmap:98 telephone-event/48000 28 a=maxptime:120 29 a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid 30 a=extmap:2 urn:ietf:params:rtp-hdrext:ssrc-audio-level 31 a=msid:47017fee-b6c1-4162-929c-a25110252400 f83006c5-a0ff-4e0a-9ed9-d3e6747be7d9 32 a=ice-ufrag:ETEn 33 a=ice-pwd:OtSK0WpNtpUjkY4+86js7ZQl 34 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 35 a=setup:actpass 36 a=dtls-id:1 37 a=rtcp:10101 IN IP4 203.0.113.100 38 a=rtcp-mux 39 a=rtcp-rsize 40 m=video 10102 UDP/TLS/RTP/SAVPF 100 101 41 c=IN IP4 203.0.113.100 42 a=mid:v1 43 a=sendrecv 44 a=rtpmap:100 VP8/90000 45 a=rtpmap:101 rtx/90000 46 a=fmtp:101 apt=100 47 a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid 48 a=rtcp-fb:100 ccm fir 49 a=rtcp-fb:100 nack 50 a=rtcp-fb:100 nack pli 51 a=msid:47017fee-b6c1-4162-929c-a25110252400 f30bdb4a-5db8-49b5-bcdc-e0c9a23172e0 52 a=ice-ufrag:BGKk 53 a=ice-pwd:mqyWsAjvtKwTGnvhPztQ9mIf 54 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 55 a=setup:actpass 56 a=dtls-id:1 57 a=rtcp:10103 IN IP4 203.0.113.100 58 a=rtcp-mux 59 a=rtcp-rsize 60 `; 61 62 const sessionDesc = { type: 'offer', sdp }; 63 64 // valid candidate attributes 65 const sdpMid1 = 'a1'; 66 const sdpMLineIndex1 = 0; 67 const usernameFragment1 = 'ETEn'; 68 69 const sdpMid2 = 'v1'; 70 const sdpMLineIndex2 = 1; 71 const usernameFragment2 = 'BGKk'; 72 73 const mediaLine1 = 'm=audio'; 74 const mediaLine2 = 'm=video'; 75 76 const candidateStr1 = 'candidate:1 1 udp 2113929471 203.0.113.100 10100 typ host'; 77 const candidateStr2 = 'candidate:1 2 udp 2113929470 203.0.113.100 10101 typ host'; 78 const invalidCandidateStr = '(Invalid) candidate \r\n string'; 79 80 const candidateLine1 = `a=${candidateStr1}`; 81 const candidateLine2 = `a=${candidateStr2}`; 82 const endOfCandidateLine = 'a=end-of-candidates'; 83 84 // Copied from MDN 85 function escapeRegExp(string) { 86 return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); 87 } 88 89 function is_candidate_line_between(sdp, beforeMediaLine, candidateLine, afterMediaLine) { 90 const line1 = escapeRegExp(beforeMediaLine); 91 const line2 = escapeRegExp(candidateLine); 92 const line3 = escapeRegExp(afterMediaLine); 93 94 const regex = new RegExp(`${line1}[^]+${line2}[^]+${line3}`); 95 return regex.test(sdp); 96 } 97 98 // Check that a candidate line is found after the first media line 99 // but before the second, i.e. it belongs to the first media stream 100 function assert_candidate_line_between(sdp, beforeMediaLine, candidateLine, afterMediaLine) { 101 assert_true(is_candidate_line_between(sdp, beforeMediaLine, candidateLine, afterMediaLine), 102 `Expect candidate line to be found between media lines ${beforeMediaLine} and ${afterMediaLine}`); 103 } 104 105 // Check that a candidate line is found after the second media line 106 // i.e. it belongs to the second media stream 107 function is_candidate_line_after(sdp, beforeMediaLine, candidateLine) { 108 const line1 = escapeRegExp(beforeMediaLine); 109 const line2 = escapeRegExp(candidateLine); 110 111 const regex = new RegExp(`${line1}[^]+${line2}`); 112 113 return regex.test(sdp); 114 } 115 116 function assert_candidate_line_after(sdp, beforeMediaLine, candidateLine) { 117 assert_true(is_candidate_line_after(sdp, beforeMediaLine, candidateLine), 118 `Expect candidate line to be found after media line ${beforeMediaLine}`); 119 } 120 121 /* 122 4.4.2. addIceCandidate 123 4. Return the result of enqueuing the following steps: 124 1. If remoteDescription is null return a promise rejected with a 125 newly created InvalidStateError. 126 */ 127 promise_test(t => { 128 const pc = new RTCPeerConnection(); 129 130 t.add_cleanup(() => pc.close()); 131 132 return promise_rejects_dom(t, 'InvalidStateError', 133 pc.addIceCandidate({ 134 candidate: candidateStr1, 135 sdpMid: sdpMid1, 136 sdpMLineIndex: sdpMLineIndex1, 137 usernameFragment: usernameFragment1 138 })); 139 }, 'Add ICE candidate before setting remote description should reject with InvalidStateError'); 140 141 promise_test(t => { 142 const pc = new RTCPeerConnection(); 143 pc.setRemoteDescription(sessionDesc); 144 pc.close(); 145 return promise_rejects_dom(t, 'InvalidStateError', 146 pc.addIceCandidate({ 147 candidate: candidateStr1, 148 sdpMid: sdpMid1, 149 sdpMLineIndex: sdpMLineIndex1, 150 usernameFragment: usernameFragment1 151 })); 152 }, 'addIceCandidate after close should reject with InvalidStateError'); 153 154 /* 155 Success cases 156 */ 157 158 // All of these should work, because all of these end up being equivalent to the 159 // same thing; an end-of-candidates signal for all levels/mids/ufrags. 160 [ 161 // This is just the default. Everything else here is equivalent to this. 162 { 163 candidate: '', 164 sdpMid: null, 165 sdpMLineIndex: null, 166 usernameFragment: undefined 167 }, 168 // The arg is optional, so when passing undefined we'll just get the default 169 undefined, 170 // The arg is optional, but not nullable, so we get the default again 171 null, 172 // Members in the dictionary take their default values 173 {} 174 ].forEach(init => { 175 promise_test(async t => { 176 const pc = new RTCPeerConnection(); 177 178 t.add_cleanup(() => pc.close()); 179 180 await pc.setRemoteDescription(sessionDesc); 181 await pc.addIceCandidate(init); 182 }, `addIceCandidate(${JSON.stringify(init)}) works`); 183 promise_test(async t => { 184 const pc = new RTCPeerConnection(); 185 186 t.add_cleanup(() => pc.close()); 187 188 await pc.setRemoteDescription(sessionDesc); 189 await pc.addIceCandidate(init); 190 assert_candidate_line_between(pc.remoteDescription.sdp, 191 mediaLine1, endOfCandidateLine, mediaLine2); 192 assert_candidate_line_after(pc.remoteDescription.sdp, 193 mediaLine2, endOfCandidateLine); 194 }, `addIceCandidate(${JSON.stringify(init)}) adds a=end-of-candidates to both m-sections`); 195 }); 196 197 promise_test(async t => { 198 const pc = new RTCPeerConnection(); 199 t.add_cleanup(() => pc.close()); 200 await pc.setRemoteDescription(sessionDesc); 201 await pc.setLocalDescription(await pc.createAnswer()); 202 await pc.addIceCandidate({}); 203 assert_candidate_line_between(pc.remoteDescription.sdp, 204 mediaLine1, endOfCandidateLine, mediaLine2); 205 assert_candidate_line_after(pc.remoteDescription.sdp, 206 mediaLine2, endOfCandidateLine); 207 }, 'addIceCandidate({}) in stable should work, and add a=end-of-candidates to both m-sections'); 208 209 promise_test(async t => { 210 const pc = new RTCPeerConnection(); 211 212 t.add_cleanup(() => pc.close()); 213 214 await pc.setRemoteDescription(sessionDesc); 215 await pc.addIceCandidate({ 216 usernameFragment: usernameFragment1, 217 sdpMid: sdpMid1 218 }); 219 assert_candidate_line_between(pc.remoteDescription.sdp, 220 mediaLine1, endOfCandidateLine, mediaLine2); 221 assert_false(is_candidate_line_after(pc.remoteDescription.sdp, 222 mediaLine2, endOfCandidateLine)); 223 }, 'addIceCandidate({usernameFragment: usernameFragment1, sdpMid: sdpMid1}) should work, and add a=end-of-candidates to the first m-section'); 224 225 promise_test(async t => { 226 const pc = new RTCPeerConnection(); 227 228 t.add_cleanup(() => pc.close()); 229 230 await pc.setRemoteDescription(sessionDesc); 231 await pc.addIceCandidate({ 232 usernameFragment: usernameFragment2, 233 sdpMLineIndex: 1 234 }); 235 assert_false(is_candidate_line_between(pc.remoteDescription.sdp, 236 mediaLine1, endOfCandidateLine, mediaLine2)); 237 assert_true(is_candidate_line_after(pc.remoteDescription.sdp, 238 mediaLine2, endOfCandidateLine)); 239 }, 'addIceCandidate({usernameFragment: usernameFragment2, sdpMLineIndex: 1}) should work, and add a=end-of-candidates to the first m-section'); 240 241 promise_test(async t => { 242 const pc = new RTCPeerConnection(); 243 244 t.add_cleanup(() => pc.close()); 245 246 await pc.setRemoteDescription(sessionDesc); 247 await promise_rejects_dom(t, 'OperationError', 248 pc.addIceCandidate({usernameFragment: "no such ufrag"})); 249 }, 'addIceCandidate({usernameFragment: "no such ufrag"}) should not work'); 250 251 promise_test(async t => { 252 const pc = new RTCPeerConnection(); 253 254 t.add_cleanup(() => pc.close()); 255 256 await pc.setRemoteDescription(sessionDesc) 257 await pc.addIceCandidate({ 258 candidate: candidateStr1, 259 sdpMid: sdpMid1, 260 sdpMLineIndex: sdpMLineIndex1, 261 usernameFragement: usernameFragment1 262 }); 263 assert_candidate_line_after(pc.remoteDescription.sdp, 264 mediaLine1, candidateStr1); 265 }, 'Add ICE candidate after setting remote description should succeed'); 266 267 promise_test(t => { 268 const pc = new RTCPeerConnection(); 269 270 t.add_cleanup(() => pc.close()); 271 272 return pc.setRemoteDescription(sessionDesc) 273 .then(() => pc.addIceCandidate(new RTCIceCandidate({ 274 candidate: candidateStr1, 275 sdpMid: sdpMid1, 276 sdpMLineIndex: sdpMLineIndex1, 277 usernameFragement: usernameFragment1 278 }))); 279 }, 'Add ICE candidate with RTCIceCandidate should succeed'); 280 281 promise_test(t => { 282 const pc = new RTCPeerConnection(); 283 284 t.add_cleanup(() => pc.close()); 285 return pc.setRemoteDescription(sessionDesc) 286 .then(() => pc.addIceCandidate({ 287 candidate: candidateStr1, 288 sdpMid: sdpMid1 })); 289 }, 'Add candidate with only valid sdpMid should succeed'); 290 291 promise_test(t => { 292 const pc = new RTCPeerConnection(); 293 294 t.add_cleanup(() => pc.close()); 295 296 return pc.setRemoteDescription(sessionDesc) 297 .then(() => pc.addIceCandidate(new RTCIceCandidate({ 298 candidate: candidateStr1, 299 sdpMid: sdpMid1 }))); 300 }, 'Add candidate with only valid sdpMid and RTCIceCandidate should succeed'); 301 302 promise_test(t => { 303 const pc = new RTCPeerConnection(); 304 305 t.add_cleanup(() => pc.close()); 306 307 return pc.setRemoteDescription(sessionDesc) 308 .then(() => pc.addIceCandidate({ 309 candidate: candidateStr1, 310 sdpMLineIndex: sdpMLineIndex1 })); 311 }, 'Add candidate with only valid sdpMLineIndex should succeed'); 312 313 /* 314 4.4.2. addIceCandidate 315 4.6.2. If candidate is applied successfully, the user agent MUST queue 316 a task that runs the following steps: 317 2. If connection.pendingRemoteDescription is non-null, and represents 318 the ICE generation for which candidate was processed, add 319 candidate to connection.pendingRemoteDescription. 320 3. If connection.currentRemoteDescription is non-null, and represents 321 the ICE generation for which candidate was processed, add 322 candidate to connection.currentRemoteDescription. 323 */ 324 promise_test(t => { 325 const pc = new RTCPeerConnection(); 326 327 t.add_cleanup(() => pc.close()); 328 329 return pc.setRemoteDescription(sessionDesc) 330 .then(() => pc.addIceCandidate({ 331 candidate: candidateStr1, 332 sdpMid: sdpMid1, 333 sdpMLineIndex: sdpMLineIndex1, 334 usernameFragement: usernameFragment1 335 })) 336 .then(() => { 337 assert_candidate_line_between(pc.remoteDescription.sdp, 338 mediaLine1, candidateLine1, mediaLine2); 339 }); 340 }, 'addIceCandidate with first sdpMid and sdpMLineIndex add candidate to first media stream'); 341 342 promise_test(t => { 343 const pc = new RTCPeerConnection(); 344 345 t.add_cleanup(() => pc.close()); 346 347 return pc.setRemoteDescription(sessionDesc) 348 .then(() => pc.addIceCandidate({ 349 candidate: candidateStr2, 350 sdpMid: sdpMid2, 351 sdpMLineIndex: sdpMLineIndex2, 352 usernameFragment: usernameFragment2 353 })) 354 .then(() => { 355 assert_candidate_line_after(pc.remoteDescription.sdp, 356 mediaLine2, candidateLine2); 357 }); 358 }, 'addIceCandidate with second sdpMid and sdpMLineIndex should add candidate to second media stream'); 359 360 promise_test(t => { 361 const pc = new RTCPeerConnection(); 362 363 t.add_cleanup(() => pc.close()); 364 365 return pc.setRemoteDescription(sessionDesc) 366 .then(() => pc.addIceCandidate({ 367 candidate: candidateStr1, 368 sdpMid: sdpMid1, 369 sdpMLineIndex: sdpMLineIndex1, 370 usernameFragment: null 371 })) 372 .then(() => { 373 assert_candidate_line_between(pc.remoteDescription.sdp, 374 mediaLine1, candidateLine1, mediaLine2); 375 }); 376 }, 'Add candidate for first media stream with null usernameFragment should add candidate to first media stream'); 377 378 promise_test(t => { 379 const pc = new RTCPeerConnection(); 380 381 t.add_cleanup(() => pc.close()); 382 383 return pc.setRemoteDescription(sessionDesc) 384 .then(() => pc.addIceCandidate({ 385 candidate: candidateStr1, 386 sdpMid: sdpMid1, 387 sdpMLineIndex: sdpMLineIndex1, 388 usernameFragement: usernameFragment1 389 })) 390 .then(() => pc.addIceCandidate({ 391 candidate: candidateStr2, 392 sdpMid: sdpMid2, 393 sdpMLineIndex: sdpMLineIndex2, 394 usernameFragment: usernameFragment2 395 })) 396 .then(() => { 397 assert_candidate_line_between(pc.remoteDescription.sdp, 398 mediaLine1, candidateLine1, mediaLine2); 399 400 assert_candidate_line_after(pc.remoteDescription.sdp, 401 mediaLine2, candidateLine2); 402 }); 403 }, 'Adding multiple candidates should add candidates to their corresponding media stream'); 404 405 /* 406 4.4.2. addIceCandidate 407 4.6. If candidate.candidate is an empty string, process candidate as an 408 end-of-candidates indication for the corresponding media description 409 and ICE candidate generation. 410 2. If candidate is applied successfully, the user agent MUST queue 411 a task that runs the following steps: 412 2. If connection.pendingRemoteDescription is non-null, and represents 413 the ICE generation for which candidate was processed, add 414 candidate to connection.pendingRemoteDescription. 415 3. If connection.currentRemoteDescription is non-null, and represents 416 the ICE generation for which candidate was processed, add 417 candidate to connection.currentRemoteDescription. 418 */ 419 promise_test(t => { 420 const pc = new RTCPeerConnection(); 421 422 t.add_cleanup(() => pc.close()); 423 424 return pc.setRemoteDescription(sessionDesc) 425 .then(() => pc.addIceCandidate({ 426 candidate: candidateStr1, 427 sdpMid: sdpMid1, 428 sdpMLineIndex: sdpMLineIndex1, 429 usernameFragement: usernameFragment1 430 })) 431 .then(() => pc.addIceCandidate({ 432 candidate: '', 433 sdpMid: sdpMid1, 434 sdpMLineIndex: sdpMLineIndex1, 435 usernameFragement: usernameFragment1 436 })) 437 .then(() => { 438 assert_candidate_line_between(pc.remoteDescription.sdp, 439 mediaLine1, candidateLine1, mediaLine2); 440 441 assert_candidate_line_between(pc.remoteDescription.sdp, 442 mediaLine1, endOfCandidateLine, mediaLine2); 443 }); 444 }, 'Add with empty candidate string (end of candidates) should succeed'); 445 446 /* 447 4.4.2. addIceCandidate 448 3. If both sdpMid and sdpMLineIndex are null, return a promise rejected 449 with a newly created TypeError. 450 */ 451 promise_test(t => { 452 const pc = new RTCPeerConnection(); 453 454 t.add_cleanup(() => pc.close()); 455 456 return pc.setRemoteDescription(sessionDesc) 457 .then(() => 458 promise_rejects_js(t, TypeError, 459 pc.addIceCandidate({ 460 candidate: candidateStr1, 461 sdpMid: null, 462 sdpMLineIndex: null 463 }))); 464 }, 'Add candidate with both sdpMid and sdpMLineIndex manually set to null should reject with TypeError'); 465 466 promise_test(async t => { 467 const pc = new RTCPeerConnection(); 468 t.add_cleanup(() => pc.close()); 469 470 await pc.setRemoteDescription(sessionDesc); 471 promise_rejects_js(t, TypeError, 472 pc.addIceCandidate({candidate: candidateStr1})); 473 }, 'addIceCandidate with a candidate and neither sdpMid nor sdpMLineIndex should reject with TypeError'); 474 475 promise_test(t => { 476 const pc = new RTCPeerConnection(); 477 478 t.add_cleanup(() => pc.close()); 479 480 return pc.setRemoteDescription(sessionDesc) 481 .then(() => 482 promise_rejects_js(t, TypeError, 483 pc.addIceCandidate({ 484 candidate: candidateStr1 485 }))); 486 }, 'Add candidate with only valid candidate string should reject with TypeError'); 487 488 promise_test(t => { 489 const pc = new RTCPeerConnection(); 490 491 t.add_cleanup(() => pc.close()); 492 493 return pc.setRemoteDescription(sessionDesc) 494 .then(() => 495 promise_rejects_js(t, TypeError, 496 pc.addIceCandidate({ 497 candidate: invalidCandidateStr, 498 sdpMid: null, 499 sdpMLineIndex: null 500 }))); 501 }, 'Add candidate with invalid candidate string and both sdpMid and sdpMLineIndex null should reject with TypeError'); 502 503 /* 504 4.4.2. addIceCandidate 505 4.3. If candidate.sdpMid is not null, run the following steps: 506 1. If candidate.sdpMid is not equal to the mid of any media 507 description in remoteDescription , reject p with a newly 508 created OperationError and abort these steps. 509 */ 510 promise_test(t => { 511 const pc = new RTCPeerConnection(); 512 513 t.add_cleanup(() => pc.close()); 514 515 return pc.setRemoteDescription(sessionDesc) 516 .then(() => 517 promise_rejects_dom(t, 'OperationError', 518 pc.addIceCandidate({ 519 candidate: candidateStr1, 520 sdpMid: 'invalid', 521 sdpMLineIndex: sdpMLineIndex1, 522 usernameFragement: usernameFragment1 523 }))); 524 }, 'Add candidate with invalid sdpMid should reject with OperationError'); 525 526 /* 527 4.4.2. addIceCandidate 528 4.4. Else, if candidate.sdpMLineIndex is not null, run the following 529 steps: 530 1. If candidate.sdpMLineIndex is equal to or larger than the 531 number of media descriptions in remoteDescription , reject p 532 with a newly created OperationError and abort these steps. 533 */ 534 promise_test(t => { 535 const pc = new RTCPeerConnection(); 536 537 t.add_cleanup(() => pc.close()); 538 539 return pc.setRemoteDescription(sessionDesc) 540 .then(() => 541 promise_rejects_dom(t, 'OperationError', 542 pc.addIceCandidate({ 543 candidate: candidateStr1, 544 sdpMLineIndex: 2, 545 usernameFragement: usernameFragment1 546 }))); 547 }, 'Add candidate with invalid sdpMLineIndex should reject with OperationError'); 548 549 // There is an "Else" for the statement: 550 // "Else, if candidate.sdpMLineIndex is not null, ..." 551 promise_test(t => { 552 const pc = new RTCPeerConnection(); 553 554 t.add_cleanup(() => pc.close()); 555 556 return pc.setRemoteDescription(sessionDesc) 557 .then(() => pc.addIceCandidate({ 558 candidate: candidateStr1, 559 sdpMid: sdpMid1, 560 sdpMLineIndex: 2, 561 usernameFragement: usernameFragment1 562 })); 563 }, 'Invalid sdpMLineIndex should be ignored if valid sdpMid is provided'); 564 565 promise_test(t => { 566 const pc = new RTCPeerConnection(); 567 568 t.add_cleanup(() => pc.close()); 569 570 return pc.setRemoteDescription(sessionDesc) 571 .then(() => pc.addIceCandidate({ 572 candidate: candidateStr2, 573 sdpMid: sdpMid2, 574 sdpMLineIndex: sdpMLineIndex2, 575 usernameFragment: null 576 })) 577 .then(() => { 578 assert_candidate_line_after(pc.remoteDescription.sdp, 579 mediaLine2, candidateLine2); 580 }); 581 }, 'Add candidate for media stream 2 with null usernameFragment should succeed'); 582 583 /* 584 4.3.2. addIceCandidate 585 4.5. If candidate.usernameFragment is neither undefined nor null, and is not equal 586 to any usernameFragment present in the corresponding media description of an 587 applied remote description, reject p with a newly created 588 OperationError and abort these steps. 589 */ 590 promise_test(t => { 591 const pc = new RTCPeerConnection(); 592 593 t.add_cleanup(() => pc.close()); 594 595 return pc.setRemoteDescription(sessionDesc) 596 .then(() => 597 promise_rejects_dom(t, 'OperationError', 598 pc.addIceCandidate({ 599 candidate: candidateStr1, 600 sdpMid: sdpMid1, 601 sdpMLineIndex: sdpMLineIndex1, 602 usernameFragment: 'invalid' 603 }))); 604 }, 'Add candidate with invalid usernameFragment should reject with OperationError'); 605 606 /* 607 4.4.2. addIceCandidate 608 4.6.1. If candidate could not be successfully added the user agent MUST 609 queue a task that runs the following steps: 610 2. Reject p with a DOMException object whose name attribute has 611 the value OperationError and abort these steps. 612 */ 613 promise_test(t => { 614 const pc = new RTCPeerConnection(); 615 616 t.add_cleanup(() => pc.close()); 617 618 return pc.setRemoteDescription(sessionDesc) 619 .then(() => 620 promise_rejects_dom(t, 'OperationError', 621 pc.addIceCandidate({ 622 candidate: invalidCandidateStr, 623 sdpMid: sdpMid1, 624 sdpMLineIndex: sdpMLineIndex1, 625 usernameFragement: usernameFragment1 626 }))); 627 }, 'Add candidate with invalid candidate string should reject with OperationError'); 628 629 promise_test(t => { 630 const pc = new RTCPeerConnection(); 631 632 t.add_cleanup(() => pc.close()); 633 634 return pc.setRemoteDescription(sessionDesc) 635 .then(() => 636 promise_rejects_dom(t, 'OperationError', 637 pc.addIceCandidate({ 638 candidate: candidateStr2, 639 sdpMid: sdpMid2, 640 sdpMLineIndex: sdpMLineIndex2, 641 usernameFragment: usernameFragment1 642 }))); 643 }, 'Add candidate with sdpMid belonging to different usernameFragment should reject with OperationError'); 644 645 promise_test(async t => { 646 const pc = new RTCPeerConnection(); 647 t.add_cleanup(() => pc.close()); 648 await pc.setRemoteDescription(sessionDesc); 649 const recognized = []; 650 await pc.addIceCandidate({ 651 candidate: candidateStr1, 652 sdpMid: sdpMid1, 653 get relayProtocol() { 654 recognized.push("relayProtocol"); 655 return null; 656 }, 657 get url() { 658 recognized.push("url"); 659 return null; 660 }, 661 get usernameFragment() { 662 recognized.push("usernameFragment"); 663 return null; 664 }, 665 }); 666 assert_array_equals(recognized, ['usernameFragment']); 667 }, 'addIceCandidate should not recognize relayProtocol or url'); 668 </script>