summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webrtc/RTCPeerConnection-addIceCandidate.html
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/webrtc/RTCPeerConnection-addIceCandidate.html')
-rw-r--r--testing/web-platform/tests/webrtc/RTCPeerConnection-addIceCandidate.html631
1 files changed, 631 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webrtc/RTCPeerConnection-addIceCandidate.html b/testing/web-platform/tests/webrtc/RTCPeerConnection-addIceCandidate.html
new file mode 100644
index 0000000000..d8e24d608b
--- /dev/null
+++ b/testing/web-platform/tests/webrtc/RTCPeerConnection-addIceCandidate.html
@@ -0,0 +1,631 @@
+<!doctype html>
+<title>Test RTCPeerConnection.prototype.addIceCandidate</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script>
+ 'use strict';
+
+ // SDP copied from JSEP Example 7.1
+ // It contains two media streams with different ufrags
+ // to test if candidate is added to the correct stream
+ const sdp = `v=0
+o=- 4962303333179871722 1 IN IP4 0.0.0.0
+s=-
+t=0 0
+a=ice-options:trickle
+a=group:BUNDLE a1 v1
+a=group:LS a1 v1
+m=audio 10100 UDP/TLS/RTP/SAVPF 96 0 8 97 98
+c=IN IP4 203.0.113.100
+a=mid:a1
+a=sendrecv
+a=rtpmap:96 opus/48000/2
+a=rtpmap:0 PCMU/8000
+a=rtpmap:8 PCMA/8000
+a=rtpmap:97 telephone-event/8000
+a=rtpmap:98 telephone-event/48000
+a=maxptime:120
+a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid
+a=extmap:2 urn:ietf:params:rtp-hdrext:ssrc-audio-level
+a=msid:47017fee-b6c1-4162-929c-a25110252400 f83006c5-a0ff-4e0a-9ed9-d3e6747be7d9
+a=ice-ufrag:ETEn
+a=ice-pwd:OtSK0WpNtpUjkY4+86js7ZQl
+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
+a=setup:actpass
+a=dtls-id:1
+a=rtcp:10101 IN IP4 203.0.113.100
+a=rtcp-mux
+a=rtcp-rsize
+m=video 10102 UDP/TLS/RTP/SAVPF 100 101
+c=IN IP4 203.0.113.100
+a=mid:v1
+a=sendrecv
+a=rtpmap:100 VP8/90000
+a=rtpmap:101 rtx/90000
+a=fmtp:101 apt=100
+a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid
+a=rtcp-fb:100 ccm fir
+a=rtcp-fb:100 nack
+a=rtcp-fb:100 nack pli
+a=msid:47017fee-b6c1-4162-929c-a25110252400 f30bdb4a-5db8-49b5-bcdc-e0c9a23172e0
+a=ice-ufrag:BGKk
+a=ice-pwd:mqyWsAjvtKwTGnvhPztQ9mIf
+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
+a=setup:actpass
+a=dtls-id:1
+a=rtcp:10103 IN IP4 203.0.113.100
+a=rtcp-mux
+a=rtcp-rsize
+`;
+
+ const sessionDesc = { type: 'offer', sdp };
+
+ // valid candidate attributes
+ const sdpMid1 = 'a1';
+ const sdpMLineIndex1 = 0;
+ const usernameFragment1 = 'ETEn';
+
+ const sdpMid2 = 'v1';
+ const sdpMLineIndex2 = 1;
+ const usernameFragment2 = 'BGKk';
+
+ const mediaLine1 = 'm=audio';
+ const mediaLine2 = 'm=video';
+
+ const candidateStr1 = 'candidate:1 1 udp 2113929471 203.0.113.100 10100 typ host';
+ const candidateStr2 = 'candidate:1 2 udp 2113929470 203.0.113.100 10101 typ host';
+ const invalidCandidateStr = '(Invalid) candidate \r\n string';
+
+ const candidateLine1 = `a=${candidateStr1}`;
+ const candidateLine2 = `a=${candidateStr2}`;
+ const endOfCandidateLine = 'a=end-of-candidates';
+
+ // Copied from MDN
+ function escapeRegExp(string) {
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ }
+
+ function is_candidate_line_between(sdp, beforeMediaLine, candidateLine, afterMediaLine) {
+ const line1 = escapeRegExp(beforeMediaLine);
+ const line2 = escapeRegExp(candidateLine);
+ const line3 = escapeRegExp(afterMediaLine);
+
+ const regex = new RegExp(`${line1}[^]+${line2}[^]+${line3}`);
+ return regex.test(sdp);
+ }
+
+ // Check that a candidate line is found after the first media line
+ // but before the second, i.e. it belongs to the first media stream
+ function assert_candidate_line_between(sdp, beforeMediaLine, candidateLine, afterMediaLine) {
+ assert_true(is_candidate_line_between(sdp, beforeMediaLine, candidateLine, afterMediaLine),
+ `Expect candidate line to be found between media lines ${beforeMediaLine} and ${afterMediaLine}`);
+ }
+
+ // Check that a candidate line is found after the second media line
+ // i.e. it belongs to the second media stream
+ function is_candidate_line_after(sdp, beforeMediaLine, candidateLine) {
+ const line1 = escapeRegExp(beforeMediaLine);
+ const line2 = escapeRegExp(candidateLine);
+
+ const regex = new RegExp(`${line1}[^]+${line2}`);
+
+ return regex.test(sdp);
+ }
+
+ function assert_candidate_line_after(sdp, beforeMediaLine, candidateLine) {
+ assert_true(is_candidate_line_after(sdp, beforeMediaLine, candidateLine),
+ `Expect candidate line to be found after media line ${beforeMediaLine}`);
+ }
+
+ /*
+ 4.4.2. addIceCandidate
+ 4. Return the result of enqueuing the following steps:
+ 1. If remoteDescription is null return a promise rejected with a
+ newly created InvalidStateError.
+ */
+ promise_test(t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ return promise_rejects_dom(t, 'InvalidStateError',
+ pc.addIceCandidate({
+ candidate: candidateStr1,
+ sdpMid: sdpMid1,
+ sdpMLineIndex: sdpMLineIndex1,
+ usernameFragment: usernameFragment1
+ }));
+ }, 'Add ICE candidate before setting remote description should reject with InvalidStateError');
+
+ /*
+ Success cases
+ */
+
+ // All of these should work, because all of these end up being equivalent to the
+ // same thing; an end-of-candidates signal for all levels/mids/ufrags.
+ [
+ // This is just the default. Everything else here is equivalent to this.
+ {
+ candidate: '',
+ sdpMid: null,
+ sdpMLineIndex: null,
+ usernameFragment: undefined
+ },
+ // The arg is optional, so when passing undefined we'll just get the default
+ undefined,
+ // The arg is optional, but not nullable, so we get the default again
+ null,
+ // Members in the dictionary take their default values
+ {}
+ ].forEach(init => {
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ await pc.setRemoteDescription(sessionDesc);
+ await pc.addIceCandidate(init);
+ }, `addIceCandidate(${JSON.stringify(init)}) works`);
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ await pc.setRemoteDescription(sessionDesc);
+ await pc.addIceCandidate(init);
+ assert_candidate_line_between(pc.remoteDescription.sdp,
+ mediaLine1, endOfCandidateLine, mediaLine2);
+ assert_candidate_line_after(pc.remoteDescription.sdp,
+ mediaLine2, endOfCandidateLine);
+ }, `addIceCandidate(${JSON.stringify(init)}) adds a=end-of-candidates to both m-sections`);
+ });
+
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ await pc.setRemoteDescription(sessionDesc);
+ await pc.setLocalDescription(await pc.createAnswer());
+ await pc.addIceCandidate({});
+ assert_candidate_line_between(pc.remoteDescription.sdp,
+ mediaLine1, endOfCandidateLine, mediaLine2);
+ assert_candidate_line_after(pc.remoteDescription.sdp,
+ mediaLine2, endOfCandidateLine);
+ }, 'addIceCandidate({}) in stable should work, and add a=end-of-candidates to both m-sections');
+
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ await pc.setRemoteDescription(sessionDesc);
+ await pc.addIceCandidate({
+ usernameFragment: usernameFragment1,
+ sdpMid: sdpMid1
+ });
+ assert_candidate_line_between(pc.remoteDescription.sdp,
+ mediaLine1, endOfCandidateLine, mediaLine2);
+ assert_false(is_candidate_line_after(pc.remoteDescription.sdp,
+ mediaLine2, endOfCandidateLine));
+ }, 'addIceCandidate({usernameFragment: usernameFragment1, sdpMid: sdpMid1}) should work, and add a=end-of-candidates to the first m-section');
+
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ await pc.setRemoteDescription(sessionDesc);
+ await pc.addIceCandidate({
+ usernameFragment: usernameFragment2,
+ sdpMLineIndex: 1
+ });
+ assert_false(is_candidate_line_between(pc.remoteDescription.sdp,
+ mediaLine1, endOfCandidateLine, mediaLine2));
+ assert_true(is_candidate_line_after(pc.remoteDescription.sdp,
+ mediaLine2, endOfCandidateLine));
+ }, 'addIceCandidate({usernameFragment: usernameFragment2, sdpMLineIndex: 1}) should work, and add a=end-of-candidates to the first m-section');
+
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ await pc.setRemoteDescription(sessionDesc);
+ await promise_rejects_dom(t, 'OperationError',
+ pc.addIceCandidate({usernameFragment: "no such ufrag"}));
+ }, 'addIceCandidate({usernameFragment: "no such ufrag"}) should not work');
+
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ await pc.setRemoteDescription(sessionDesc)
+ await pc.addIceCandidate({
+ candidate: candidateStr1,
+ sdpMid: sdpMid1,
+ sdpMLineIndex: sdpMLineIndex1,
+ usernameFragement: usernameFragment1
+ });
+ assert_candidate_line_after(pc.remoteDescription.sdp,
+ mediaLine1, candidateStr1);
+ }, 'Add ICE candidate after setting remote description should succeed');
+
+ promise_test(t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ return pc.setRemoteDescription(sessionDesc)
+ .then(() => pc.addIceCandidate(new RTCIceCandidate({
+ candidate: candidateStr1,
+ sdpMid: sdpMid1,
+ sdpMLineIndex: sdpMLineIndex1,
+ usernameFragement: usernameFragment1
+ })));
+ }, 'Add ICE candidate with RTCIceCandidate should succeed');
+
+ promise_test(t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+ return pc.setRemoteDescription(sessionDesc)
+ .then(() => pc.addIceCandidate({
+ candidate: candidateStr1,
+ sdpMid: sdpMid1 }));
+ }, 'Add candidate with only valid sdpMid should succeed');
+
+ promise_test(t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ return pc.setRemoteDescription(sessionDesc)
+ .then(() => pc.addIceCandidate(new RTCIceCandidate({
+ candidate: candidateStr1,
+ sdpMid: sdpMid1 })));
+ }, 'Add candidate with only valid sdpMid and RTCIceCandidate should succeed');
+
+ promise_test(t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ return pc.setRemoteDescription(sessionDesc)
+ .then(() => pc.addIceCandidate({
+ candidate: candidateStr1,
+ sdpMLineIndex: sdpMLineIndex1 }));
+ }, 'Add candidate with only valid sdpMLineIndex should succeed');
+
+ /*
+ 4.4.2. addIceCandidate
+ 4.6.2. If candidate is applied successfully, the user agent MUST queue
+ a task that runs the following steps:
+ 2. If connection.pendingRemoteDescription is non-null, and represents
+ the ICE generation for which candidate was processed, add
+ candidate to connection.pendingRemoteDescription.
+ 3. If connection.currentRemoteDescription is non-null, and represents
+ the ICE generation for which candidate was processed, add
+ candidate to connection.currentRemoteDescription.
+ */
+ promise_test(t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ return pc.setRemoteDescription(sessionDesc)
+ .then(() => pc.addIceCandidate({
+ candidate: candidateStr1,
+ sdpMid: sdpMid1,
+ sdpMLineIndex: sdpMLineIndex1,
+ usernameFragement: usernameFragment1
+ }))
+ .then(() => {
+ assert_candidate_line_between(pc.remoteDescription.sdp,
+ mediaLine1, candidateLine1, mediaLine2);
+ });
+ }, 'addIceCandidate with first sdpMid and sdpMLineIndex add candidate to first media stream');
+
+ promise_test(t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ return pc.setRemoteDescription(sessionDesc)
+ .then(() => pc.addIceCandidate({
+ candidate: candidateStr2,
+ sdpMid: sdpMid2,
+ sdpMLineIndex: sdpMLineIndex2,
+ usernameFragment: usernameFragment2
+ }))
+ .then(() => {
+ assert_candidate_line_after(pc.remoteDescription.sdp,
+ mediaLine2, candidateLine2);
+ });
+ }, 'addIceCandidate with second sdpMid and sdpMLineIndex should add candidate to second media stream');
+
+ promise_test(t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ return pc.setRemoteDescription(sessionDesc)
+ .then(() => pc.addIceCandidate({
+ candidate: candidateStr1,
+ sdpMid: sdpMid1,
+ sdpMLineIndex: sdpMLineIndex1,
+ usernameFragment: null
+ }))
+ .then(() => {
+ assert_candidate_line_between(pc.remoteDescription.sdp,
+ mediaLine1, candidateLine1, mediaLine2);
+ });
+ }, 'Add candidate for first media stream with null usernameFragment should add candidate to first media stream');
+
+ promise_test(t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ return pc.setRemoteDescription(sessionDesc)
+ .then(() => pc.addIceCandidate({
+ candidate: candidateStr1,
+ sdpMid: sdpMid1,
+ sdpMLineIndex: sdpMLineIndex1,
+ usernameFragement: usernameFragment1
+ }))
+ .then(() => pc.addIceCandidate({
+ candidate: candidateStr2,
+ sdpMid: sdpMid2,
+ sdpMLineIndex: sdpMLineIndex2,
+ usernameFragment: usernameFragment2
+ }))
+ .then(() => {
+ assert_candidate_line_between(pc.remoteDescription.sdp,
+ mediaLine1, candidateLine1, mediaLine2);
+
+ assert_candidate_line_after(pc.remoteDescription.sdp,
+ mediaLine2, candidateLine2);
+ });
+ }, 'Adding multiple candidates should add candidates to their corresponding media stream');
+
+ /*
+ 4.4.2. addIceCandidate
+ 4.6. If candidate.candidate is an empty string, process candidate as an
+ end-of-candidates indication for the corresponding media description
+ and ICE candidate generation.
+ 2. If candidate is applied successfully, the user agent MUST queue
+ a task that runs the following steps:
+ 2. If connection.pendingRemoteDescription is non-null, and represents
+ the ICE generation for which candidate was processed, add
+ candidate to connection.pendingRemoteDescription.
+ 3. If connection.currentRemoteDescription is non-null, and represents
+ the ICE generation for which candidate was processed, add
+ candidate to connection.currentRemoteDescription.
+ */
+ promise_test(t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ return pc.setRemoteDescription(sessionDesc)
+ .then(() => pc.addIceCandidate({
+ candidate: candidateStr1,
+ sdpMid: sdpMid1,
+ sdpMLineIndex: sdpMLineIndex1,
+ usernameFragement: usernameFragment1
+ }))
+ .then(() => pc.addIceCandidate({
+ candidate: '',
+ sdpMid: sdpMid1,
+ sdpMLineIndex: sdpMLineIndex1,
+ usernameFragement: usernameFragment1
+ }))
+ .then(() => {
+ assert_candidate_line_between(pc.remoteDescription.sdp,
+ mediaLine1, candidateLine1, mediaLine2);
+
+ assert_candidate_line_between(pc.remoteDescription.sdp,
+ mediaLine1, endOfCandidateLine, mediaLine2);
+ });
+ }, 'Add with empty candidate string (end of candidates) should succeed');
+
+ /*
+ 4.4.2. addIceCandidate
+ 3. If both sdpMid and sdpMLineIndex are null, return a promise rejected
+ with a newly created TypeError.
+ */
+ promise_test(t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ return pc.setRemoteDescription(sessionDesc)
+ .then(() =>
+ promise_rejects_js(t, TypeError,
+ pc.addIceCandidate({
+ candidate: candidateStr1,
+ sdpMid: null,
+ sdpMLineIndex: null
+ })));
+ }, 'Add candidate with both sdpMid and sdpMLineIndex manually set to null should reject with TypeError');
+
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ await pc.setRemoteDescription(sessionDesc);
+ promise_rejects_js(t, TypeError,
+ pc.addIceCandidate({candidate: candidateStr1}));
+ }, 'addIceCandidate with a candidate and neither sdpMid nor sdpMLineIndex should reject with TypeError');
+
+ promise_test(t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ return pc.setRemoteDescription(sessionDesc)
+ .then(() =>
+ promise_rejects_js(t, TypeError,
+ pc.addIceCandidate({
+ candidate: candidateStr1
+ })));
+ }, 'Add candidate with only valid candidate string should reject with TypeError');
+
+ promise_test(t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ return pc.setRemoteDescription(sessionDesc)
+ .then(() =>
+ promise_rejects_js(t, TypeError,
+ pc.addIceCandidate({
+ candidate: invalidCandidateStr,
+ sdpMid: null,
+ sdpMLineIndex: null
+ })));
+ }, 'Add candidate with invalid candidate string and both sdpMid and sdpMLineIndex null should reject with TypeError');
+
+ /*
+ 4.4.2. addIceCandidate
+ 4.3. If candidate.sdpMid is not null, run the following steps:
+ 1. If candidate.sdpMid is not equal to the mid of any media
+ description in remoteDescription , reject p with a newly
+ created OperationError and abort these steps.
+ */
+ promise_test(t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ return pc.setRemoteDescription(sessionDesc)
+ .then(() =>
+ promise_rejects_dom(t, 'OperationError',
+ pc.addIceCandidate({
+ candidate: candidateStr1,
+ sdpMid: 'invalid',
+ sdpMLineIndex: sdpMLineIndex1,
+ usernameFragement: usernameFragment1
+ })));
+ }, 'Add candidate with invalid sdpMid should reject with OperationError');
+
+ /*
+ 4.4.2. addIceCandidate
+ 4.4. Else, if candidate.sdpMLineIndex is not null, run the following
+ steps:
+ 1. If candidate.sdpMLineIndex is equal to or larger than the
+ number of media descriptions in remoteDescription , reject p
+ with a newly created OperationError and abort these steps.
+ */
+ promise_test(t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ return pc.setRemoteDescription(sessionDesc)
+ .then(() =>
+ promise_rejects_dom(t, 'OperationError',
+ pc.addIceCandidate({
+ candidate: candidateStr1,
+ sdpMLineIndex: 2,
+ usernameFragement: usernameFragment1
+ })));
+ }, 'Add candidate with invalid sdpMLineIndex should reject with OperationError');
+
+ // There is an "Else" for the statement:
+ // "Else, if candidate.sdpMLineIndex is not null, ..."
+ promise_test(t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ return pc.setRemoteDescription(sessionDesc)
+ .then(() => pc.addIceCandidate({
+ candidate: candidateStr1,
+ sdpMid: sdpMid1,
+ sdpMLineIndex: 2,
+ usernameFragement: usernameFragment1
+ }));
+ }, 'Invalid sdpMLineIndex should be ignored if valid sdpMid is provided');
+
+ promise_test(t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ return pc.setRemoteDescription(sessionDesc)
+ .then(() => pc.addIceCandidate({
+ candidate: candidateStr2,
+ sdpMid: sdpMid2,
+ sdpMLineIndex: sdpMLineIndex2,
+ usernameFragment: null
+ }))
+ .then(() => {
+ assert_candidate_line_after(pc.remoteDescription.sdp,
+ mediaLine2, candidateLine2);
+ });
+ }, 'Add candidate for media stream 2 with null usernameFragment should succeed');
+
+ /*
+ 4.3.2. addIceCandidate
+ 4.5. If candidate.usernameFragment is neither undefined nor null, and is not equal
+ to any usernameFragment present in the corresponding media description of an
+ applied remote description, reject p with a newly created
+ OperationError and abort these steps.
+ */
+ promise_test(t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ return pc.setRemoteDescription(sessionDesc)
+ .then(() =>
+ promise_rejects_dom(t, 'OperationError',
+ pc.addIceCandidate({
+ candidate: candidateStr1,
+ sdpMid: sdpMid1,
+ sdpMLineIndex: sdpMLineIndex1,
+ usernameFragment: 'invalid'
+ })));
+ }, 'Add candidate with invalid usernameFragment should reject with OperationError');
+
+ /*
+ 4.4.2. addIceCandidate
+ 4.6.1. If candidate could not be successfully added the user agent MUST
+ queue a task that runs the following steps:
+ 2. Reject p with a DOMException object whose name attribute has
+ the value OperationError and abort these steps.
+ */
+ promise_test(t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ return pc.setRemoteDescription(sessionDesc)
+ .then(() =>
+ promise_rejects_dom(t, 'OperationError',
+ pc.addIceCandidate({
+ candidate: invalidCandidateStr,
+ sdpMid: sdpMid1,
+ sdpMLineIndex: sdpMLineIndex1,
+ usernameFragement: usernameFragment1
+ })));
+ }, 'Add candidate with invalid candidate string should reject with OperationError');
+
+ promise_test(t => {
+ const pc = new RTCPeerConnection();
+
+ t.add_cleanup(() => pc.close());
+
+ return pc.setRemoteDescription(sessionDesc)
+ .then(() =>
+ promise_rejects_dom(t, 'OperationError',
+ pc.addIceCandidate({
+ candidate: candidateStr2,
+ sdpMid: sdpMid2,
+ sdpMLineIndex: sdpMLineIndex2,
+ usernameFragment: usernameFragment1
+ })));
+ }, 'Add candidate with sdpMid belonging to different usernameFragment should reject with OperationError');
+</script>