RTCPeerConnection-ondatachannel.html (12587B)
1 <!doctype html> 2 <meta charset=utf-8> 3 <meta name="timeout" content="long"> 4 <title>RTCPeerConnection.prototype.ondatachannel</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 // Test is based on the following revision: 12 // https://rawgit.com/w3c/webrtc-pc/1cc5bfc3ff18741033d804c4a71f7891242fb5b3/webrtc.html 13 14 // The following helper functions are called from RTCPeerConnection-helper.js: 15 // exchangeIceCandidates 16 // exchangeOfferAnswer 17 // createDataChannelPair 18 19 /* 20 6.2. RTCDataChannel 21 When an underlying data transport is to be announced (the other peer created a channel with 22 negotiated unset or set to false), the user agent of the peer that did not initiate the 23 creation process MUST queue a task to run the following steps: 24 2. Let channel be a newly created RTCDataChannel object. 25 7. Set channel's [[ReadyState]] to open (but do not fire the open event, yet). 26 8. Fire a datachannel event named datachannel with channel at the RTCPeerConnection object. 27 28 6.3. RTCDataChannelEvent 29 Firing a datachannel event named e with an RTCDataChannel channel means that an event with the 30 name e, which does not bubble (except where otherwise stated) and is not cancelable (except 31 where otherwise stated), and which uses the RTCDataChannelEvent interface with the channel 32 attribute set to channel, MUST be created and dispatched at the given target. 33 34 interface RTCDataChannelEvent : Event { 35 readonly attribute RTCDataChannel channel; 36 }; 37 */ 38 promise_test(async (t) => { 39 const resolver = new Resolver(); 40 const pc1 = new RTCPeerConnection(); 41 const pc2 = new RTCPeerConnection(); 42 t.add_cleanup(() => pc1.close()); 43 t.add_cleanup(() => pc2.close()); 44 45 let eventCount = 0; 46 47 pc2.ondatachannel = t.step_func((event) => { 48 eventCount++; 49 assert_equals(eventCount, 1, 50 'Expect data channel event to fire exactly once'); 51 52 assert_true(event instanceof RTCDataChannelEvent, 53 'Expect event to be instance of RTCDataChannelEvent'); 54 55 assert_equals(event.bubbles, false); 56 assert_equals(event.cancelable, false); 57 58 const dc = event.channel; 59 assert_true(dc instanceof RTCDataChannel, 60 'Expect channel to be instance of RTCDataChannel'); 61 62 // The channel should be in the 'open' state already. 63 // See: https://github.com/w3c/webrtc-pc/pull/1851 64 assert_equals(dc.readyState, 'open', 65 'Expect channel ready state to be open'); 66 67 resolver.resolve(); 68 }); 69 70 pc1.createDataChannel('fire-me!'); 71 72 exchangeIceCandidates(pc1, pc2); 73 await exchangeOfferAnswer(pc1, pc2); 74 75 await resolver; 76 }, 'Data channel event should fire when new data channel is announced to the remote peer'); 77 78 /* 79 Since the channel should be in the 'open' state when dispatching via the 'datachannel' event, 80 we should be able to send data in the event handler. 81 */ 82 promise_test(async (t) => { 83 const resolver = new Resolver(); 84 const pc1 = new RTCPeerConnection(); 85 const pc2 = new RTCPeerConnection(); 86 t.add_cleanup(() => pc1.close()); 87 t.add_cleanup(() => pc2.close()); 88 89 const message = 'meow meow!'; 90 91 pc2.ondatachannel = t.step_func((event) => { 92 const dc2 = event.channel; 93 dc2.send(message); 94 }); 95 96 const dc1 = pc1.createDataChannel('fire-me!'); 97 dc1.onmessage = t.step_func((event) => { 98 assert_equals(event.data, message, 99 'Received data should be equal to sent data'); 100 101 resolver.resolve(); 102 }); 103 104 exchangeIceCandidates(pc1, pc2); 105 await exchangeOfferAnswer(pc1, pc2); 106 107 await resolver; 108 }, 'Should be able to send data in a datachannel event handler'); 109 110 /* 111 6.2. RTCDataChannel 112 When an underlying data transport is to be announced (the other peer created a channel with 113 negotiated unset or set to false), the user agent of the peer that did not initiate the 114 creation process MUST queue a task to run the following steps: 115 8. Fire a datachannel event named datachannel with channel at the RTCPeerConnection object. 116 9. If the channel's [[ReadyState]] is still open, announce the data channel as open. 117 */ 118 promise_test(async (t) => { 119 const resolver = new Resolver(); 120 const pc1 = new RTCPeerConnection(); 121 const pc2 = new RTCPeerConnection(); 122 t.add_cleanup(() => pc1.close()); 123 t.add_cleanup(() => pc2.close()); 124 125 pc2.ondatachannel = t.step_func((event) => { 126 const dc = event.channel; 127 dc.onopen = t.step_func(() => { 128 assert_unreached('Open event should not fire'); 129 }); 130 131 // This should prevent triggering the 'open' event 132 dc.close(); 133 134 // Wait a bit to ensure the 'open' event does NOT fire 135 t.step_timeout(() => resolver.resolve(), 500); 136 }); 137 138 pc1.createDataChannel('fire-me!'); 139 140 exchangeIceCandidates(pc1, pc2); 141 await exchangeOfferAnswer(pc1, pc2); 142 143 await resolver; 144 }, 'Open event should not be raised when closing the channel in the datachannel event'); 145 146 // Added this test as a result of the discussion in 147 // https://github.com/w3c/webrtc-pc/pull/1851#discussion_r185976747 148 promise_test(async (t) => { 149 const resolver = new Resolver(); 150 const pc1 = new RTCPeerConnection(); 151 const pc2 = new RTCPeerConnection(); 152 t.add_cleanup(() => pc1.close()); 153 t.add_cleanup(() => pc2.close()); 154 155 pc2.ondatachannel = t.step_func((event) => { 156 const dc = event.channel; 157 dc.onopen = t.step_func((event) => { 158 resolver.resolve(); 159 }); 160 161 // This should NOT prevent triggering the 'open' event since it enqueues at least two tasks 162 t.step_timeout(() => { 163 t.step_timeout(() => { 164 dc.close() 165 }, 1); 166 }, 1); 167 }); 168 169 pc1.createDataChannel('fire-me!'); 170 171 exchangeIceCandidates(pc1, pc2); 172 await exchangeOfferAnswer(pc1, pc2); 173 174 await resolver; 175 }, 'Open event should be raised when closing the channel in the datachannel event after ' + 176 'enqueuing a task'); 177 178 179 /* 180 Combination of the two tests above (send and close). 181 */ 182 promise_test(async (t) => { 183 const resolver = new Resolver(); 184 const pc1 = new RTCPeerConnection(); 185 const pc2 = new RTCPeerConnection(); 186 t.add_cleanup(() => pc1.close()); 187 t.add_cleanup(() => pc2.close()); 188 189 const message = 'meow meow!'; 190 191 pc2.ondatachannel = t.step_func((event) => { 192 const dc2 = event.channel; 193 dc2.onopen = t.step_func(() => { 194 assert_unreached('Open event should not fire'); 195 }); 196 197 // This should send but still prevent triggering the 'open' event 198 dc2.send(message); 199 dc2.close(); 200 }); 201 202 const dc1 = pc1.createDataChannel('fire-me!'); 203 dc1.onmessage = t.step_func((event) => { 204 assert_equals(event.data, message, 205 'Received data should be equal to sent data'); 206 207 resolver.resolve(); 208 }); 209 210 exchangeIceCandidates(pc1, pc2); 211 await exchangeOfferAnswer(pc1, pc2); 212 213 await resolver; 214 }, 'Open event should not be raised when sending and immediately closing the channel in the ' + 215 'datachannel event'); 216 217 /* 218 6.2. RTCDataChannel 219 220 interface RTCDataChannel : EventTarget { 221 readonly attribute USVString label; 222 readonly attribute boolean ordered; 223 readonly attribute unsigned short? maxPacketLifeTime; 224 readonly attribute unsigned short? maxRetransmits; 225 readonly attribute USVString protocol; 226 readonly attribute boolean negotiated; 227 readonly attribute unsigned short? id; 228 readonly attribute RTCDataChannelState readyState; 229 ... 230 }; 231 232 When an underlying data transport is to be announced (the other peer created a channel with 233 negotiated unset or set to false), the user agent of the peer that did not initiate the 234 creation process MUST queue a task to run the following steps: 235 2. Let channel be a newly created RTCDataChannel object. 236 3. Let configuration be an information bundle received from the other peer as a part of the 237 process to establish the underlying data transport described by the WebRTC DataChannel 238 Protocol specification [RTCWEB-DATA-PROTOCOL]. 239 4. Initialize channel's [[DataChannelLabel]], [[Ordered]], [[MaxPacketLifeTime]], 240 [[MaxRetransmits]], [[DataChannelProtocol]], and [[DataChannelId]] internal slots to the 241 corresponding values in configuration. 242 5. Initialize channel's [[Negotiated]] internal slot to false. 243 7. Set channel's [[ReadyState]] slot to connecting. 244 8. Fire a datachannel event named datachannel with channel at the RTCPeerConnection object. 245 246 Note: More exhaustive tests are defined in RTCDataChannel-dcep 247 */ 248 249 promise_test(async (t) => { 250 const resolver = new Resolver(); 251 const pc1 = new RTCPeerConnection(); 252 const pc2 = new RTCPeerConnection(); 253 t.add_cleanup(() => pc1.close()); 254 t.add_cleanup(() => pc2.close()); 255 256 const dc1 = pc1.createDataChannel('test', { 257 ordered: false, 258 maxRetransmits: 1, 259 protocol: 'custom' 260 }); 261 262 assert_equals(dc1.label, 'test'); 263 assert_equals(dc1.ordered, false); 264 assert_equals(dc1.maxPacketLifeTime, null); 265 assert_equals(dc1.maxRetransmits, 1); 266 assert_equals(dc1.protocol, 'custom'); 267 assert_equals(dc1.negotiated, false); 268 269 pc2.ondatachannel = t.step_func((event) => { 270 const dc2 = event.channel; 271 assert_true(dc2 instanceof RTCDataChannel, 272 'Expect channel to be instance of RTCDataChannel'); 273 274 assert_equals(dc2.label, 'test'); 275 assert_equals(dc2.ordered, false); 276 assert_equals(dc2.maxPacketLifeTime, null); 277 assert_equals(dc2.maxRetransmits, 1); 278 assert_equals(dc2.protocol, 'custom'); 279 assert_equals(dc2.negotiated, false); 280 assert_equals(dc2.id, dc1.id); 281 282 resolver.resolve(); 283 }); 284 285 exchangeIceCandidates(pc1, pc2); 286 await exchangeOfferAnswer(pc1, pc2); 287 288 await resolver; 289 }, 'In-band negotiated channel created on remote peer should match the same configuration as local ' + 290 'peer'); 291 292 promise_test(async (t) => { 293 const resolver = new Resolver(); 294 const pc1 = new RTCPeerConnection(); 295 const pc2 = new RTCPeerConnection(); 296 t.add_cleanup(() => pc1.close()); 297 t.add_cleanup(() => pc2.close()); 298 299 const dc1 = pc1.createDataChannel(''); 300 301 assert_equals(dc1.label, ''); 302 assert_equals(dc1.ordered, true); 303 assert_equals(dc1.maxPacketLifeTime, null); 304 assert_equals(dc1.maxRetransmits, null); 305 assert_equals(dc1.protocol, ''); 306 assert_equals(dc1.negotiated, false); 307 308 pc2.ondatachannel = t.step_func((event) => { 309 const dc2 = event.channel; 310 assert_true(dc2 instanceof RTCDataChannel, 311 'Expect channel to be instance of RTCDataChannel'); 312 313 assert_equals(dc2.label, ''); 314 assert_equals(dc2.ordered, true); 315 assert_equals(dc2.maxPacketLifeTime, null); 316 assert_equals(dc2.maxRetransmits, null); 317 assert_equals(dc2.protocol, ''); 318 assert_equals(dc2.negotiated, false); 319 assert_equals(dc2.id, dc1.id); 320 321 resolver.resolve(); 322 }); 323 324 exchangeIceCandidates(pc1, pc2); 325 await exchangeOfferAnswer(pc1, pc2); 326 327 await resolver; 328 }, 'In-band negotiated channel created on remote peer should match the same (default) ' + 329 'configuration as local peer'); 330 331 /* 332 6.2. RTCDataChannel 333 Dictionary RTCDataChannelInit Members 334 negotiated 335 The default value of false tells the user agent to announce the 336 channel in-band and instruct the other peer to dispatch a corresponding 337 RTCDataChannel object. If set to true, it is up to the application 338 to negotiate the channel and create a RTCDataChannel object with the 339 same id at the other peer. 340 */ 341 promise_test(async (t) => { 342 const resolver = new Resolver(); 343 const pc1 = new RTCPeerConnection(); 344 const pc2 = new RTCPeerConnection(); 345 t.add_cleanup(() => pc1.close()); 346 t.add_cleanup(() => pc2.close()); 347 348 pc2.ondatachannel = t.unreached_func('datachannel event should not be fired'); 349 350 pc1.createDataChannel('test', { 351 negotiated: true, 352 id: 42 353 }); 354 355 exchangeIceCandidates(pc1, pc2); 356 await exchangeOfferAnswer(pc1, pc2); 357 358 // Wait a bit to ensure the 'datachannel' event does NOT fire 359 t.step_timeout(() => resolver.resolve(), 500); 360 await resolver; 361 }, 'Negotiated channel should not fire datachannel event on remote peer'); 362 363 /* 364 Non-testable 365 6.2. RTCDataChannel 366 When an underlying data transport is to be announced 367 1. If the associated RTCPeerConnection object's [[isClosed]] slot 368 is true, abort these steps. 369 370 The above step is not testable because to reach it we would have to 371 close the peer connection just between receiving the in-band negotiated data 372 channel via DCEP and firing the datachannel event. 373 */ 374 </script>