tor-browser

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

RTCRtpTransceiver-stopping.https.html (9679B)


      1 <!doctype html>
      2 <meta charset=utf-8>
      3 <script src="/resources/testharness.js"></script>
      4 <script src="/resources/testharnessreport.js"></script>
      5 <script>
      6 'use strict';
      7 
      8 ['audio', 'video'].forEach((kind) => {
      9  promise_test(async t => {
     10    const pc1 = new RTCPeerConnection();
     11    t.add_cleanup(() => pc1.close());
     12    const pc2 = new RTCPeerConnection();
     13    t.add_cleanup(() => pc2.close());
     14 
     15    const transceiver = pc1.addTransceiver(kind);
     16 
     17    // Complete O/A exchange such that the transceiver gets associated.
     18    await pc1.setLocalDescription();
     19    await pc2.setRemoteDescription(pc1.localDescription);
     20    await pc2.setLocalDescription();
     21    await pc1.setRemoteDescription(pc2.localDescription);
     22    assert_not_equals(transceiver.mid, null, 'mid before stop()');
     23    assert_not_equals(transceiver.direction, 'stopped',
     24                      'direction before stop()');
     25    assert_not_equals(transceiver.currentDirection, 'stopped',
     26                      'currentDirection before stop()');
     27 
     28    // Stop makes it stopping, but not stopped.
     29    transceiver.stop();
     30    assert_not_equals(transceiver.mid, null, 'mid after stop()');
     31    assert_equals(transceiver.direction, 'stopped', 'direction after stop()');
     32    assert_not_equals(transceiver.currentDirection, 'stopped',
     33                      'currentDirection after stop()');
     34 
     35    // Negotiating makes it stopped.
     36    await pc1.setLocalDescription();
     37    await pc2.setRemoteDescription(pc1.localDescription);
     38    await pc2.setLocalDescription();
     39    await pc1.setRemoteDescription(pc2.localDescription);
     40    assert_equals(transceiver.mid, null, 'mid after negotiation');
     41    assert_equals(transceiver.direction, 'stopped',
     42                  'direction after negotiation');
     43    assert_equals(transceiver.currentDirection, 'stopped',
     44                  'currentDirection after negotiation');
     45  }, `[${kind}] Locally stopped transceiver goes from stopping to stopped`);
     46 
     47  promise_test(async t => {
     48    const pc = new RTCPeerConnection();
     49    t.add_cleanup(() => pc.close());
     50 
     51    const transceiver = pc.addTransceiver(kind);
     52    const trackEnded = new Promise(r => transceiver.receiver.track.onended = r);
     53    assert_equals(transceiver.receiver.track.readyState, 'live');
     54    transceiver.stop();
     55    // Stopping triggers ending the track, but this happens asynchronously.
     56    assert_equals(transceiver.receiver.track.readyState, 'live');
     57    await trackEnded;
     58    assert_equals(transceiver.receiver.track.readyState, 'ended');
     59  }, `[${kind}] Locally stopping a transceiver ends the track`);
     60 
     61  promise_test(async t => {
     62    const pc1 = new RTCPeerConnection();
     63    t.add_cleanup(() => pc1.close());
     64    const pc2 = new RTCPeerConnection();
     65    t.add_cleanup(() => pc2.close());
     66 
     67    const pc1Transceiver = pc1.addTransceiver(kind);
     68    await pc1.setLocalDescription();
     69    await pc2.setRemoteDescription(pc1.localDescription);
     70    await pc2.setLocalDescription();
     71    await pc1.setRemoteDescription(pc2.localDescription);
     72    const [pc2Transceiver] = pc2.getTransceivers();
     73 
     74    pc1Transceiver.stop();
     75 
     76    await pc1.setLocalDescription();
     77    assert_equals(pc2Transceiver.receiver.track.readyState, 'live');
     78    // Applying the remote offer immediately ends the track, we don't need to
     79    // create or apply an answer.
     80    await pc2.setRemoteDescription(pc1.localDescription);
     81    // sRD just resolved, so we're in the success task for sRD. The transition
     82    // from live -> ended is queued right now.
     83    assert_equals(pc2Transceiver.receiver.track.readyState, 'live');
     84    await new Promise(r => pc2Transceiver.receiver.track.onended = r);
     85    assert_equals(pc2Transceiver.receiver.track.readyState, 'ended');
     86  }, `[${kind}] Remotely stopping a transceiver ends the track`);
     87 
     88  promise_test(async t => {
     89    const pc1 = new RTCPeerConnection();
     90    t.add_cleanup(() => pc1.close());
     91    const pc2 = new RTCPeerConnection();
     92    t.add_cleanup(() => pc2.close());
     93 
     94    const pc1Transceiver = pc1.addTransceiver(kind);
     95 
     96    // Complete O/A exchange such that the transceiver gets associated.
     97    await pc1.setLocalDescription();
     98    await pc2.setRemoteDescription(pc1.localDescription);
     99    await pc2.setLocalDescription();
    100    await pc1.setRemoteDescription(pc2.localDescription);
    101    const [pc2Transceiver] = pc2.getTransceivers();
    102    assert_not_equals(pc2Transceiver.mid, null, 'mid before stop()');
    103    assert_not_equals(pc2Transceiver.direction, 'stopped',
    104                      'direction before stop()');
    105    assert_not_equals(pc2Transceiver.currentDirection, 'stopped',
    106                      'currentDirection before stop()');
    107 
    108    // Make the remote transceiver stopped.
    109    pc1Transceiver.stop();
    110 
    111    // Negotiating makes it stopped.
    112    assert_equals(pc2.getTransceivers().length, 1);
    113    await pc1.setLocalDescription();
    114    await pc2.setRemoteDescription(pc1.localDescription);
    115    // As soon as the remote offer is set, the transceiver is stopped but it is
    116    // not disassociated or removed until setting the local answer.
    117    assert_equals(pc2.getTransceivers().length, 1);
    118    assert_not_equals(pc2Transceiver.mid, null, 'mid during negotiation');
    119    assert_equals(pc2Transceiver.direction, 'stopped',
    120                  'direction during negotiation');
    121    assert_equals(pc2Transceiver.currentDirection, 'stopped',
    122                  'currentDirection during negotiation');
    123    await pc2.setLocalDescription();
    124    assert_equals(pc2.getTransceivers().length, 0);
    125    assert_equals(pc2Transceiver.mid, null, 'mid after negotiation');
    126    assert_equals(pc2Transceiver.direction, 'stopped',
    127                  'direction after negotiation');
    128    assert_equals(pc2Transceiver.currentDirection, 'stopped',
    129                  'currentDirection after negotiation');
    130  }, `[${kind}] Remotely stopped transceiver goes directly to stopped`);
    131 
    132  promise_test(async t => {
    133    const pc = new RTCPeerConnection();
    134    t.add_cleanup(() => pc.close());
    135 
    136    const transceiver = pc.addTransceiver(kind);
    137 
    138    // Rollback does not end the track, because the transceiver is not removed.
    139    await pc.setLocalDescription();
    140    await pc.setLocalDescription({type:'rollback'});
    141    assert_equals(transceiver.receiver.track.readyState, 'live');
    142  }, `[${kind}] Rollback when transceiver is not removed does not end track`);
    143 
    144  promise_test(async t => {
    145    const pc1 = new RTCPeerConnection();
    146    t.add_cleanup(() => pc1.close());
    147    const pc2 = new RTCPeerConnection();
    148    t.add_cleanup(() => pc2.close());
    149 
    150    const pc1Transceiver = pc1.addTransceiver(kind);
    151 
    152    // Start negotiation, causing a transceiver to be created.
    153    await pc1.setLocalDescription();
    154    await pc2.setRemoteDescription(pc1.localDescription);
    155    const [pc2Transceiver] = pc2.getTransceivers();
    156 
    157    // Rollback such that the transceiver is removed.
    158    await pc2.setRemoteDescription({type:'rollback'});
    159    assert_equals(pc2.getTransceivers().length, 0);
    160    // sRD just resolved, so we're in the success task for sRD. The transition
    161    // from live -> ended is queued right now.
    162    assert_equals(pc2Transceiver.receiver.track.readyState, 'live');
    163    await new Promise(r => pc2Transceiver.receiver.track.onended = r);
    164    assert_equals(pc2Transceiver.receiver.track.readyState, 'ended');
    165  }, `[${kind}] Rollback when removing transceiver does end the track`);
    166 
    167  // Same test as above but looking at direction and currentDirection.
    168  promise_test(async t => {
    169    const pc1 = new RTCPeerConnection();
    170    t.add_cleanup(() => pc1.close());
    171    const pc2 = new RTCPeerConnection();
    172    t.add_cleanup(() => pc2.close());
    173 
    174    const pc1Transceiver = pc1.addTransceiver(kind);
    175 
    176    // Start negotiation, causing a transceiver to be created.
    177    await pc1.setLocalDescription();
    178    await pc2.setRemoteDescription(pc1.localDescription);
    179    const [pc2Transceiver] = pc2.getTransceivers();
    180 
    181    // Rollback such that the transceiver is removed.
    182    await pc2.setRemoteDescription({type:'rollback'});
    183    assert_equals(pc2.getTransceivers().length, 0);
    184    // The removed transceiver is stopped.
    185    assert_equals(pc2Transceiver.currentDirection, 'stopped',
    186                  'currentDirection indicate stopped');
    187    // A stopped transceiver is necessarily also stopping.
    188    assert_equals(pc2Transceiver.direction, 'stopped',
    189                  'direction indicate stopping');
    190    // A stopped transceiver has no mid.
    191    assert_equals(pc2Transceiver.mid, null, 'not associated');
    192  }, `[${kind}] Rollback when removing transceiver makes it stopped`);
    193 
    194  promise_test(async t => {
    195    const pc1 = new RTCPeerConnection();
    196    t.add_cleanup(() => pc1.close());
    197    const pc2 = new RTCPeerConnection();
    198    t.add_cleanup(() => pc2.close());
    199 
    200    const constraints = {};
    201    constraints[kind] = true;
    202    const stream = await navigator.mediaDevices.getUserMedia(constraints);
    203    const [track] = stream.getTracks();
    204 
    205    pc1.addTrack(track);
    206    pc2.addTrack(track);
    207    const transceiver = pc2.getTransceivers()[0];
    208 
    209    const ontrackEvent = new Promise(r => {
    210      pc2.ontrack = e => r(e.track);
    211    });
    212 
    213    // Simulate glare: both peer connections set local offers.
    214    await pc1.setLocalDescription();
    215    await pc2.setLocalDescription();
    216    // Set remote offer, which implicitly rolls back the local offer. Because
    217    // `transceiver` is an addTrack-transceiver, it should get repurposed.
    218    await pc2.setRemoteDescription(pc1.localDescription);
    219    assert_equals(transceiver.receiver.track.readyState, 'live');
    220    // Sanity check: the track should still be live when ontrack fires.
    221    assert_equals((await ontrackEvent).readyState, 'live');
    222  }, `[${kind}] Glare when transceiver is not removed does not end track`);
    223 });
    224 </script>