RTCDataChannel-close.html (8358B)
1 <!doctype html> 2 <meta charset=utf-8> 3 <meta name="timeout" content="long"> 4 <title>RTCDataChannel.prototype.close</title> 5 <script src="/resources/testharness.js"></script> 6 <script src="/resources/testharnessreport.js"></script> 7 <script src="RTCPeerConnection-helper.js"></script> 8 <script> 9 'use strict'; 10 11 for (const options of [{}, {negotiated: true, id: 0}]) { 12 const mode = `${options.negotiated? "negotiated " : ""}datachannel`; 13 14 promise_test(async t => { 15 const [channel1, channel2] = await createDataChannelPairWithLabel(t, 'dc_close_causes_remote_events_1', options); 16 const haveClosed = new Promise(r => channel2.onclose = r); 17 let closingSeen = false; 18 channel1.onclosing = t.unreached_func(); 19 channel2.onclosing = () => { 20 assert_equals(channel2.readyState, 'closing'); 21 closingSeen = true; 22 }; 23 channel2.addEventListener('error', t.unreached_func()); 24 channel1.close(); 25 await haveClosed; 26 assert_equals(channel2.readyState, 'closed'); 27 assert_true(closingSeen, 'Closing event was seen'); 28 }, `Close ${mode} causes onclosing and onclose to be called`); 29 30 promise_test(async t => { 31 // This is the same test as above, but using addEventListener 32 // rather than the "onclose" attribute. 33 const [channel1, channel2] = await createDataChannelPairWithLabel(t, 'dc_close_causes_remote_events_2', options); 34 const haveClosed = new Promise(r => channel2.addEventListener('close', r)); 35 let closingSeen = false; 36 channel1.addEventListener('closing', t.unreached_func()); 37 channel2.addEventListener('closing', () => { 38 assert_equals(channel2.readyState, 'closing'); 39 closingSeen = true; 40 }); 41 channel2.addEventListener('error', t.unreached_func()); 42 channel1.close(); 43 await haveClosed; 44 assert_equals(channel2.readyState, 'closed'); 45 assert_true(closingSeen, 'Closing event was seen'); 46 }, `Close ${mode} causes closing and close event to be called`); 47 48 promise_test(async t => { 49 const pc1 = new RTCPeerConnection(); 50 t.add_cleanup(() => pc1.close()); 51 const pc2 = new RTCPeerConnection(); 52 t.add_cleanup(() => pc2.close()); 53 const mainChannel1 = pc1.createDataChannel('not-counted', options); 54 exchangeIceCandidates(pc1, pc2); 55 await exchangeOfferAnswer(pc1, pc2); 56 if (!options.negotiated) { 57 await new Promise(r => pc2.ondatachannel = r); 58 } 59 60 for (let i = 1; i <= 10; i++) { 61 let optionsCopy = structuredClone(options); 62 if ('id' in optionsCopy) { 63 optionsCopy.id = i; 64 } 65 const sender = pc1.createDataChannel(`dc ${i}`, optionsCopy); 66 const senderOpen = new Promise(r => sender.onopen = r); 67 let receiver; 68 if (optionsCopy.negotiated) { 69 receiver = pc2.createDataChannel(`dc ${i}`, optionsCopy); 70 } else { 71 receiver = (await new Promise(r => pc2.ondatachannel = r)).channel; 72 } 73 if (receiver.readyState == 'connecting') { 74 await new Promise(r => receiver.onopen = r); 75 } 76 assert_equals(receiver.readyState, 'open'); 77 receiver.onmessage = ({data}) => receiver.send(data); 78 await senderOpen; 79 sender.send(`ping ${i}`); 80 const {data} = await new Promise(r => sender.onmessage = r); 81 assert_equals(data, `ping ${i}`); 82 sender.close(); 83 await new Promise(r => receiver.onclose = r); 84 } 85 }, `Repeated open/send/echo/close ${mode} works`); 86 87 promise_test(async t => { 88 const pc1 = new RTCPeerConnection(); 89 t.add_cleanup(() => pc1.close()); 90 const [channel1, channel2] = await createDataChannelPairWithLabel(t, 'pc_close_causes_remote_events', options, pc1); 91 const events = []; 92 let error = null; 93 channel2.addEventListener('error', t.step_func(event => { 94 events.push('error'); 95 assert_true(event instanceof RTCErrorEvent); 96 error = event.error; 97 })); 98 const haveClosed = new Promise(r => channel2.addEventListener('close', () => { 99 events.push('close'); 100 r(); 101 })); 102 pc1.close(); 103 await haveClosed; 104 // Error should fire before close. 105 assert_array_equals(events, ['error', 'close']); 106 assert_true(error instanceof RTCError); 107 assert_equals(error.name, 'OperationError'); 108 assert_equals(error.errorDetail, 'sctp-failure'); 109 // Expects the sctpErrorCode is either null or 12 (User-Initiated Abort) as it is 110 // optional in the SCTP specification. 111 assert_in_array(error.sctpCauseCode, [null, 12]); 112 }, `Close peerconnection causes close event and error to be called on ${mode}`); 113 114 promise_test(async t => { 115 let pc1 = new RTCPeerConnection(); 116 t.add_cleanup(() => pc1.close()); 117 let [channel1, channel2] = await createDataChannelPairWithLabel(t, 'pc_close_after_dc_close', options, pc1); 118 // The expected sequence of events when closing a DC is that 119 // channel1 goes to closing, channel2 fires onclose, and when 120 // the close is confirmed, channel1 fires onclose. 121 // After that, no more events should fire. 122 channel1.onerror = t.unreached_func(); 123 let close2Handler = new Promise(resolve => { 124 channel2.onclose = event => { 125 resolve(); 126 }; 127 }); 128 let close1Handler = new Promise(resolve => { 129 channel1.onclose = event => { 130 resolve(); 131 }; 132 }); 133 channel1.close(); 134 await close2Handler; 135 await close1Handler; 136 channel1.onclose = t.unreached_func(); 137 channel2.onclose = t.unreached_func(); 138 channel2.onerror = t.unreached_func(); 139 pc1.close(); 140 await new Promise(resolve => t.step_timeout(resolve, 10)); 141 }, `Close peerconnection after ${mode} close causes no events`); 142 143 promise_test(async t => { 144 const pc1 = new RTCPeerConnection(); 145 t.add_cleanup(() => pc1.close()); 146 const pc2 = new RTCPeerConnection(); 147 t.add_cleanup(() => pc2.close()); 148 pc1.createDataChannel('not-counted', options); 149 const tokenDataChannel = new Promise(resolve => { 150 pc2.ondatachannel = resolve; 151 }); 152 exchangeIceCandidates(pc1, pc2); 153 await exchangeOfferAnswer(pc1, pc2); 154 if (!options.negotiated) { 155 await tokenDataChannel; 156 } 157 158 let closeExpectedCount = 0; 159 let errorExpectedCount = 0; 160 let resolveCountIsZero; 161 let waitForCountIsZero = new Promise(resolve => { 162 resolveCountIsZero = resolve; 163 }); 164 for (let i = 1; i <= 10; i++) { 165 if ('id' in options) { 166 options.id = i; 167 } 168 pc1.createDataChannel(`dc${i} pc1`, options); 169 if (options.negotiated) { 170 const channel = pc2.createDataChannel(`dc${i} pc2`, options); 171 channel.addEventListener('error', t.step_func(event => { 172 assert_true(event instanceof RTCErrorEvent, 'error event ' + event); 173 errorExpectedCount -= 1; 174 })); 175 channel.addEventListener('close', t.step_func(event => { 176 closeExpectedCount -= 1; 177 if (closeExpectedCount == 0) { 178 resolveCountIsZero(); 179 } 180 })); 181 } else { 182 await new Promise(resolve => { 183 pc2.ondatachannel = ({channel}) => { 184 channel.addEventListener('error', t.step_func(event => { 185 assert_true(event instanceof RTCErrorEvent); 186 errorExpectedCount -= 1; 187 })); 188 channel.addEventListener('close', t.step_func(event => { 189 closeExpectedCount -= 1; 190 if (closeExpectedCount == 0) { 191 resolveCountIsZero(); 192 } 193 })); 194 resolve(); 195 } 196 }); 197 } 198 ++closeExpectedCount; 199 ++errorExpectedCount; 200 } 201 assert_equals(closeExpectedCount, 10); 202 // We have to wait until SCTP is connected before we close, otherwise 203 // there will be no signal. 204 // The state is not available under Plan B, and unreliable on negotiated 205 // channels. 206 // TODO(bugs.webrtc.org/12259): Remove dependency on "negotiated" 207 if (pc1.sctp && !options.negotiated) { 208 waitForState(pc1.sctp, 'connected'); 209 } else { 210 // Under plan B, we don't have a dtls transport to wait on, so just 211 // wait a bit. 212 await new Promise(resolve => t.step_timeout(resolve, 100)); 213 } 214 pc1.close(); 215 await waitForCountIsZero; 216 assert_equals(closeExpectedCount, 0); 217 assert_equals(errorExpectedCount, 0); 218 }, `Close peerconnection causes close event and error on many channels, ${mode}`); 219 } 220 </script>