tor-browser

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

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>