transfer-datachannel.html (19181B)
1 <!doctype html> 2 <html> 3 <head> 4 <meta charset="utf-8"/> 5 <meta name="timeout" content="long"/> 6 <script src="/resources/testharness.js"></script> 7 <script src="/resources/testharnessreport.js"></script> 8 <script src="RTCPeerConnection-helper.js"></script> 9 <script src="../webrtc/RTCDataChannel-helper.js"></script> 10 <script src="RTCDataChannel-worker-shim.js"></script> 11 </head> 12 <body> 13 <script> 14 async function createConnections(test, firstConnectionCallback, secondConnectionCallback) 15 { 16 const pc1 = new RTCPeerConnection(); 17 const pc2 = new RTCPeerConnection(); 18 19 test.add_cleanup(() => pc1.close()); 20 test.add_cleanup(() => pc2.close()); 21 22 pc1.onicecandidate = (e) => pc2.addIceCandidate(e.candidate); 23 pc2.onicecandidate = (e) => pc1.addIceCandidate(e.candidate); 24 25 firstConnectionCallback(pc1); 26 27 const offer = await pc1.createOffer(); 28 await pc1.setLocalDescription(offer); 29 await pc2.setRemoteDescription(offer); 30 31 secondConnectionCallback(pc2); 32 33 const answer = await pc2.createAnswer(); 34 await pc2.setLocalDescription(answer); 35 await pc1.setRemoteDescription(answer); 36 } 37 38 async function waitForMessage(receiver, data) 39 { 40 while (true) { 41 const received = await new Promise(resolve => receiver.onmessage = (event) => resolve(event.data)); 42 if (data === received) 43 return; 44 } 45 } 46 47 promise_test(async (test) => { 48 let localChannel; 49 let remoteChannel; 50 51 const worker = new Worker('transfer-datachannel-worker.js'); 52 let data = new Promise(resolve => worker.onmessage = (event) => resolve(event.data)); 53 assert_equals(await data, "registered"); 54 55 await new Promise((resolve, reject) => { 56 createConnections(test, (firstConnection) => { 57 localChannel = firstConnection.createDataChannel('sendDataChannel'); 58 worker.postMessage({channel: localChannel}, [localChannel]); 59 data = new Promise(resolve => worker.onmessage = (event) => resolve(event.data)); 60 }, (secondConnection) => { 61 secondConnection.ondatachannel = (event) => { 62 remoteChannel = event.channel; 63 remoteChannel.onopen = resolve; 64 }; 65 }); 66 }); 67 68 assert_equals(await data, "opened"); 69 70 data = new Promise(resolve => worker.onmessage = (event) => resolve(event.data)); 71 remoteChannel.send("OK"); 72 assert_equals(await data, "OK"); 73 74 data = new Promise(resolve => remoteChannel.onmessage = (event) => resolve(event.data)); 75 worker.postMessage({message: "OK2"}); 76 assert_equals(await data, "OK2"); 77 }, "offerer data channel in workers"); 78 79 80 promise_test(async (test) => { 81 let localChannel; 82 let remoteChannel; 83 84 const worker = new Worker('transfer-datachannel-worker.js'); 85 let data = new Promise(resolve => worker.onmessage = (event) => resolve(event.data)); 86 assert_equals(await data, "registered"); 87 88 data = new Promise(resolve => worker.onmessage = (event) => resolve(event.data)); 89 await new Promise((resolve, reject) => { 90 createConnections(test, (firstConnection) => { 91 localChannel = firstConnection.createDataChannel('sendDataChannel'); 92 localChannel.onopen = resolve; 93 }, (secondConnection) => { 94 secondConnection.ondatachannel = (event) => { 95 remoteChannel = event.channel; 96 worker.postMessage({channel: remoteChannel}, [remoteChannel]); 97 }; 98 }); 99 }); 100 assert_equals(await data, "opened"); 101 102 data = new Promise(resolve => worker.onmessage = (event) => resolve(event.data)); 103 localChannel.send("OK"); 104 assert_equals(await data, "OK"); 105 106 data = new Promise(resolve => localChannel.onmessage = (event) => resolve(event.data)); 107 worker.postMessage({message: "OK2"}); 108 assert_equals(await data, "OK2"); 109 }, "answerer data channel in workers"); 110 111 promise_test(async (test) => { 112 let localChannel; 113 let remoteChannel; 114 115 const worker = new Worker('transfer-datachannel-worker.js'); 116 let data = new Promise(resolve => worker.onmessage = (event) => resolve(event.data)); 117 assert_equals(await data, "registered"); 118 119 data = new Promise(resolve => worker.onmessage = (event) => resolve(event.data)); 120 await new Promise((resolve, reject) => { 121 createConnections(test, (firstConnection) => { 122 localChannel = firstConnection.createDataChannel('sendDataChannel'); 123 worker.postMessage({channel: localChannel}, [localChannel]); 124 125 }, (secondConnection) => { 126 secondConnection.ondatachannel = (event) => { 127 remoteChannel = event.channel; 128 remoteChannel.onopen = resolve; 129 }; 130 }); 131 }); 132 assert_equals(await data, "opened"); 133 134 data = new Promise(resolve => worker.onmessage = (event) => resolve(event.data)); 135 remoteChannel.close(); 136 assert_equals(await data, "closed"); 137 138 }, "data channel close event in worker"); 139 140 promise_test(async (test) => { 141 let localChannel; 142 let remoteChannel; 143 144 const worker = new Worker('transfer-datachannel-worker.js'); 145 let data = new Promise(resolve => worker.onmessage = (event) => resolve(event.data)); 146 assert_equals(await data, "registered"); 147 148 await new Promise((resolve, reject) => { 149 createConnections(test, (firstConnection) => { 150 localChannel = firstConnection.createDataChannel('sendDataChannel'); 151 }, (secondConnection) => { 152 secondConnection.ondatachannel = (event) => { 153 remoteChannel = event.channel; 154 test.step_timeout(() => { 155 assert_throws_dom('DataCloneError', () => { 156 worker.postMessage({channel: remoteChannel}, [remoteChannel]); 157 }); 158 resolve(); 159 }, 0); 160 }; 161 }); 162 }); 163 }, "Failing to transfer a data channel"); 164 165 promise_test(async (test) => { 166 const pc = new RTCPeerConnection(); 167 test.add_cleanup(() => pc.close()); 168 const dc = pc.createDataChannel('foo'); 169 const dcShim = new WorkerBackedDataChannel(); 170 171 await promise_rejects_js(test, Error, dcShim.send('foo'), 172 'Sending before init should throw'); 173 await promise_rejects_js(test, Error, dcShim.close(), 174 'Closing before init should throw'); 175 await promise_rejects_js(test, Error, dcShim.updateState(), 176 'updateState before init should throw'); 177 178 assert_throws_js(TypeError, () => { 179 let foo = dcShim.ordered; 180 }, 'Using getter before init should throw'); 181 assert_throws_js(TypeError, () => { 182 dcShim.binaryType = 'blob'; 183 }, 'Using setter before init should throw'); 184 }, 'Sanity check worker shim: never initted'); 185 186 promise_test(async (test) => { 187 const pc = new RTCPeerConnection(); 188 test.add_cleanup(() => pc.close()); 189 const dc = pc.createDataChannel('foo'); 190 const dcShim = new WorkerBackedDataChannel(); 191 await pc.setLocalDescription(); 192 await promise_rejects_dom(test, 'DataCloneError', dcShim.init(dc)); 193 }, 'Sanity check worker shim: RTCDataChannel not transferable'); 194 195 async function transferAndShim(dc, worker) { 196 const shim = new WorkerBackedDataChannel(worker); 197 await shim.init(dc); 198 return shim; 199 } 200 201 promise_test(async (test) => { 202 const pc = new RTCPeerConnection(); 203 test.add_cleanup(() => pc.close()); 204 const dcShim1 = await transferAndShim(pc.createDataChannel('dupe')); 205 const dcShim2 = new WorkerBackedDataChannel(dcShim1.worker); 206 const dc2 = pc.createDataChannel('dupe'); 207 await promise_rejects_js(test, Error, dcShim2.init(dc2), 208 'Worker init throws on duplicate label'); 209 }, 'Sanity check worker shim: Worker code throws on duplicate label'); 210 211 promise_test(async (test) => { 212 const pc = new RTCPeerConnection(); 213 test.add_cleanup(() => pc.close()); 214 const dcShim = await transferAndShim(pc.createDataChannel('foo')); 215 await promise_rejects_js(test, Error, dcShim.send('oops'), 216 'RTCDataChannel errors propagate'); 217 }, 'Sanity check worker shim: RTCDataChannel can throw'); 218 219 promise_test(async (test) => { 220 // Use worker shim in RTCDataChannel-worker-shim.js 221 const pc = new RTCPeerConnection(); 222 test.add_cleanup(() => pc.close()); 223 const dcShim = await transferAndShim(pc.createDataChannel('foo')); 224 assert_equals(dcShim.label, 'foo'); 225 assert_equals(dcShim.ordered, true); 226 assert_equals(dcShim.maxPacketLifeTime, null); 227 assert_equals(dcShim.maxRetransmits, null); 228 assert_equals(dcShim.protocol, ''); 229 assert_equals(dcShim.negotiated, false); 230 assert_equals(dcShim.id, null); 231 assert_equals(dcShim.readyState, 'connecting'); 232 assert_equals(dcShim.bufferedAmount, 0); 233 assert_equals(dcShim.bufferedAmountLowThreshold, 0); 234 assert_equals(dcShim.binaryType, 'arraybuffer'); 235 }, 'Check that transferred RTCDataChannel has correct initial (default) attributes'); 236 237 promise_test(async (test) => { 238 // Use worker shim in RTCDataChannel-worker-shim.js 239 const pc = new RTCPeerConnection(); 240 test.add_cleanup(() => pc.close()); 241 const dcShim = await transferAndShim(pc.createDataChannel('foo', { 242 ordered: false, 243 maxPacketLifeTime: 500, 244 protocol: 'bar', 245 negotiated: true, 246 id: '17'})); 247 248 assert_equals(dcShim.label, 'foo'); 249 assert_equals(dcShim.ordered, false); 250 assert_equals(dcShim.maxPacketLifeTime, 500); 251 assert_equals(dcShim.maxRetransmits, null); 252 assert_equals(dcShim.protocol, 'bar'); 253 assert_equals(dcShim.negotiated, true); 254 assert_equals(dcShim.id, 17); 255 assert_equals(dcShim.readyState, 'connecting'); 256 assert_equals(dcShim.bufferedAmount, 0); 257 assert_equals(dcShim.bufferedAmountLowThreshold, 0); 258 assert_equals(dcShim.binaryType, 'arraybuffer'); 259 }, 'Check that transferred RTCDataChannel has correct initial (non-default) attributes'); 260 261 promise_test(async (test) => { 262 // Use worker shim in RTCDataChannel-worker-shim.js 263 const pc = new RTCPeerConnection(); 264 test.add_cleanup(() => pc.close()); 265 const dcShim = await transferAndShim(pc.createDataChannel('foo', { 266 ordered: false, 267 maxRetransmits: 5, 268 protocol: 'bar', 269 negotiated: true, 270 id: '17'})); 271 272 assert_equals(dcShim.label, 'foo'); 273 assert_equals(dcShim.ordered, false); 274 assert_equals(dcShim.maxPacketLifeTime, null); 275 assert_equals(dcShim.maxRetransmits, 5); 276 assert_equals(dcShim.protocol, 'bar'); 277 assert_equals(dcShim.negotiated, true); 278 assert_equals(dcShim.id, 17); 279 assert_equals(dcShim.readyState, 'connecting'); 280 assert_equals(dcShim.bufferedAmount, 0); 281 assert_equals(dcShim.bufferedAmountLowThreshold, 0); 282 assert_equals(dcShim.binaryType, 'arraybuffer'); 283 }, 'Check that transferred RTCDataChannel has correct initial (non-default) attributes (maxRetransmits variant)'); 284 285 promise_test(async (test) => { 286 // Use worker shim in RTCDataChannel-worker-shim.js 287 const pc = new RTCPeerConnection(); 288 test.add_cleanup(() => pc.close()); 289 const dcShim = await transferAndShim(pc.createDataChannel('foo')); 290 dcShim.bufferedAmountLowThreshold = 42; 291 dcShim.binaryType = 'blob'; 292 await dcShim.updateState(); 293 assert_equals(dcShim.bufferedAmountLowThreshold, 42); 294 assert_equals(dcShim.binaryType, 'blob'); 295 }, 'Check that transferred RTCDataChannel can set bufferedAmountLowThreshold/binaryType'); 296 297 for (const whichChannelShimmed of ['offerer', 'answerer']) { 298 promise_test(async (test) => { 299 const {shimmedChannel, nonShimmedChannel, offerer, answerer} = await openChannelPairWithShim(whichChannelShimmed); 300 test.add_cleanup(() => offerer.close()); 301 test.add_cleanup(() => answerer.close()); 302 }, `Check that transferred ${whichChannelShimmed} RTCDataChannel open event works`); 303 304 for (const message of ['hello', '', '世界你好']) { 305 promise_test(async (test) => { 306 307 const {shimmedChannel, nonShimmedChannel, offerer, answerer} = await openChannelPairWithShim(whichChannelShimmed); 308 test.add_cleanup(() => offerer.close()); 309 test.add_cleanup(() => answerer.close()); 310 311 shimmedChannel.send(message); 312 let bufferedAmountLowEvent; 313 if (message != '') { 314 // This comes from a queued task, so it is possible the message will get 315 // to mainthread first. 316 bufferedAmountLowEvent = new Promise(r => shimmedChannel.onbufferedamountlow = r); 317 } 318 319 const messageEvent1 = await new Promise(r => nonShimmedChannel.onmessage = r); 320 await bufferedAmountLowEvent; 321 322 assert_equals(messageEvent1.data, message); 323 324 nonShimmedChannel.send(message); 325 const messageEvent2 = await new Promise(r => shimmedChannel.onmessage = r); 326 assert_equals(messageEvent2.data, message); 327 assert_equals(messageEvent2.origin, messageEvent1.origin); 328 }, `Check that transferred ${whichChannelShimmed} RTCDataChannel can send and receive string "${message}"`); 329 } 330 331 // ASCII encoded buffer representation of the string 332 const helloBuffer = Uint8Array.of(0x68, 0x65, 0x6c, 0x6c, 0x6f); 333 const emptyBuffer = new Uint8Array(); 334 const helloBlob = new Blob([helloBuffer]); 335 336 // UTF-8 encoded buffer representation of the string 337 const unicodeBuffer = Uint8Array.of( 338 0xe4, 0xb8, 0x96, 0xe7, 0x95, 0x8c, 339 0xe4, 0xbd, 0xa0, 0xe5, 0xa5, 0xbd); 340 341 for (const message of [helloBlob, helloBuffer.buffer, helloBuffer]) { 342 promise_test(async (test) => { 343 const {shimmedChannel, nonShimmedChannel, offerer, answerer} = await openChannelPairWithShim(whichChannelShimmed); 344 test.add_cleanup(() => offerer.close()); 345 test.add_cleanup(() => answerer.close()); 346 shimmedChannel.binaryType = 'blob'; 347 nonShimmedChannel.binaryType = 'blob'; 348 349 shimmedChannel.send(message); 350 let bufferedAmountLowEvent; 351 if (message != '') { 352 // This comes from a queued task, so it is possible the message will get 353 // to mainthread first. 354 bufferedAmountLowEvent = new Promise(r => shimmedChannel.onbufferedamountlow = r); 355 } 356 357 const messageEvent1 = await new Promise(r => nonShimmedChannel.onmessage = r); 358 await bufferedAmountLowEvent; 359 360 assert_true(messageEvent1.data instanceof Blob); 361 const arrayBuf1 = await messageEvent1.data.arrayBuffer(); 362 assert_equals_typed_array(arrayBuf1, helloBuffer.buffer); 363 364 nonShimmedChannel.send(message); 365 const messageEvent2 = await new Promise(r => shimmedChannel.onmessage = r); 366 assert_true(messageEvent2.data instanceof Blob); 367 const arrayBuf2 = await messageEvent2.data.arrayBuffer(); 368 assert_equals_typed_array(arrayBuf2, helloBuffer.buffer); 369 370 assert_equals(messageEvent2.origin, messageEvent1.origin); 371 }, `Check that transferred ${whichChannelShimmed} RTCDataChannel can send ${message.constructor.name} that is received as Blob, and vice-versa`); 372 } 373 374 for (const message of [helloBlob, helloBuffer.buffer, helloBuffer]) { 375 promise_test(async (test) => { 376 const {shimmedChannel, nonShimmedChannel, offerer, answerer} = await openChannelPairWithShim(whichChannelShimmed); 377 test.add_cleanup(() => offerer.close()); 378 test.add_cleanup(() => answerer.close()); 379 shimmedChannel.send(message); 380 381 let bufferedAmountLowEvent; 382 if (message != '') { 383 // This comes from a queued task, so it is possible the message will get 384 // to mainthread first. 385 bufferedAmountLowEvent = new Promise(r => shimmedChannel.onbufferedamountlow = r); 386 } 387 388 const messageEvent1 = await new Promise(r => nonShimmedChannel.onmessage = r); 389 await bufferedAmountLowEvent; 390 391 assert_true(messageEvent1.data instanceof ArrayBuffer); 392 assert_equals_typed_array(messageEvent1.data, helloBuffer.buffer); 393 394 nonShimmedChannel.send(message); 395 const messageEvent2 = await new Promise(r => shimmedChannel.onmessage = r); 396 assert_true(messageEvent2.data instanceof ArrayBuffer); 397 assert_equals_typed_array(messageEvent2.data, helloBuffer.buffer); 398 399 assert_equals(messageEvent2.origin, messageEvent1.origin); 400 }, `Check that transferred ${whichChannelShimmed} RTCDataChannel can send ${message.constructor.name} that is received as ArrayBuffer, and vice-versa`); 401 } 402 403 promise_test(async (test) => { 404 const {shimmedChannel, nonShimmedChannel, offerer, answerer} = await openChannelPairWithShim(whichChannelShimmed); 405 test.add_cleanup(() => offerer.close()); 406 test.add_cleanup(() => answerer.close()); 407 shimmedChannel.close(); 408 await new Promise(r => shimmedChannel.onclose = r); 409 }, `Check that transferred ${whichChannelShimmed} RTCDataChannel onclose fires`); 410 411 promise_test(async (test) => { 412 const {shimmedChannel, nonShimmedChannel, offerer, answerer} = await openChannelPairWithShim(whichChannelShimmed); 413 test.add_cleanup(() => offerer.close()); 414 test.add_cleanup(() => answerer.close()); 415 nonShimmedChannel.close(); 416 await new Promise(r => shimmedChannel.onclosing = r); 417 }, `Check that transferred ${whichChannelShimmed} RTCDataChannel onclosing fires`); 418 } 419 420 promise_test(async (test) => { 421 const offerer = new RTCPeerConnection(); 422 const answerer = new RTCPeerConnection(); 423 test.add_cleanup(() => offerer.close()); 424 test.add_cleanup(() => answerer.close()); 425 426 // Just to make an m=application section 427 offerer.createDataChannel('foo'); 428 await negotiate(offerer, answerer); 429 430 // We're creating every combination of: 431 // {main, dedicated worker, shared worker} <- offerer 432 // {main, dedicated worker, shared worker} <- answerer 433 // {offerer created, answerer created} 434 435 const channelPairPromises = []; 436 const sharedOff = WorkerBackedDataChannel.makeWorker(); 437 const sharedAns = WorkerBackedDataChannel.makeWorker(); 438 439 function createShim(workerConfig, sharedWorker) { 440 switch (workerConfig) { 441 case "main": 442 return null; 443 case "dedicated": 444 return new WorkerBackedDataChannel(); 445 case "shared": 446 return new WorkerBackedDataChannel(sharedWorker); 447 } 448 } 449 450 // Make the rat's nest. There will be 18 channel pairs. 451 for (const offererWorker of ["main", "dedicated", "shared"]) { 452 for (const answererWorker of ["main", "dedicated", "shared"]) { 453 channelPairPromises.push(openChannelPair( 454 offerer, answerer, 455 `${offererWorker}, ${answererWorker}, offerer creates`, {}, 456 createShim(offererWorker, sharedOff), 457 createShim(answererWorker, sharedAns))); 458 459 channelPairPromises.push(openChannelPair( 460 answerer, offerer, 461 `${offererWorker}, ${answererWorker}, answerer creates`, {}, 462 createShim(answererWorker, sharedAns), 463 createShim(offererWorker, sharedOff))); 464 } 465 } 466 467 const channelPairs = await Promise.all(channelPairPromises); 468 const sendRecvPromises = channelPairs.map(async ([channel1, channel2]) => { 469 channel1.send(channel1.label); 470 const recv1Event = await new Promise(r => channel2.onmessage = r); 471 assert_equals(recv1Event.data, channel1.label); 472 channel2.send(channel2.label); 473 const recvEvent2 = await new Promise(r => channel1.onmessage = r); 474 assert_equals(recvEvent2.data, channel2.label); 475 }); 476 477 await Promise.all(sendRecvPromises); 478 }, 'Check that a variety of kinds of worker/non-worker DataChannel can work simultaneously'); 479 </script> 480 </body> 481 </html>