tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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>