tor-browser

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

RTCPeerConnection-setRemoteDescription-tracks.https.html (17255B)


      1 <!doctype html>
      2 <meta charset=utf-8>
      3 <meta name="timeout" content="long">
      4 <title>RTCPeerConnection.prototype.setRemoteDescription - add/remove remote tracks</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  // The following helper functions are called from RTCPeerConnection-helper.js:
     12  //   addEventListenerPromise
     13  //   exchangeOffer
     14  //   exchangeOfferAnswer
     15  //   Resolver
     16 
     17  // These tests are concerned with the observable consequences of processing
     18  // the addition or removal of remote tracks, including events firing and the
     19  // states of RTCPeerConnection, MediaStream and MediaStreamTrack.
     20 
     21  promise_test(async t => {
     22    const caller = new RTCPeerConnection();
     23    t.add_cleanup(() => caller.close());
     24    const callee = new RTCPeerConnection();
     25    t.add_cleanup(() => callee.close());
     26    const localStream =
     27        await getNoiseStream({audio: true});
     28    t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop()));
     29    caller.addTrack(localStream.getTracks()[0]);
     30    const ontrackPromise = addEventListenerPromise(t, callee, 'track', e => {
     31      assert_equals(e.streams.length, 0, 'No remote stream created.');
     32    });
     33    await exchangeOffer(caller, callee);
     34    await ontrackPromise;
     35  }, 'addTrack() with a track and no stream makes ontrack fire with a track and no stream.');
     36 
     37  promise_test(async t => {
     38    const caller = new RTCPeerConnection();
     39    t.add_cleanup(() => caller.close());
     40    const callee = new RTCPeerConnection();
     41    t.add_cleanup(() => callee.close());
     42    const localStream =
     43        await getNoiseStream({audio: true});
     44    t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop()));
     45    caller.addTrack(localStream.getTracks()[0], localStream);
     46    const ontrackPromise = addEventListenerPromise(t, callee, 'track', e => {
     47      assert_equals(e.streams.length, 1, 'Created a single remote stream.');
     48      assert_equals(e.streams[0].id, localStream.id,
     49                    'Local and remote stream IDs match.');
     50      assert_array_equals(e.streams[0].getTracks(), [e.track],
     51                          'The remote stream contains the remote track.');
     52    });
     53    await exchangeOffer(caller, callee);
     54    await ontrackPromise;
     55  }, 'addTrack() with a track and a stream makes ontrack fire with a track and a stream.');
     56 
     57  promise_test(async t => {
     58    const caller = new RTCPeerConnection();
     59    t.add_cleanup(() => caller.close());
     60    const callee = new RTCPeerConnection();
     61    t.add_cleanup(() => callee.close());
     62    let eventSequence = '';
     63    const localStream =
     64        await getNoiseStream({audio: true});
     65    t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop()));
     66    caller.addTrack(localStream.getTracks()[0], localStream);
     67    const ontrackPromise = addEventListenerPromise(t, callee, 'track', e => {
     68      eventSequence += 'ontrack;';
     69    });
     70    await exchangeOffer(caller, callee);
     71    eventSequence += 'setRemoteDescription;';
     72    await ontrackPromise;
     73    assert_equals(eventSequence, 'ontrack;setRemoteDescription;');
     74  }, 'ontrack fires before setRemoteDescription resolves.');
     75 
     76  promise_test(async t => {
     77    const caller = new RTCPeerConnection();
     78    t.add_cleanup(() => caller.close());
     79    const callee = new RTCPeerConnection();
     80    t.add_cleanup(() => callee.close());
     81    const localStreams = await Promise.all([
     82      getNoiseStream({audio: true}),
     83      getNoiseStream({audio: true}),
     84    ]);
     85    t.add_cleanup(() => localStreams.forEach((stream) =>
     86      stream.getTracks().forEach((track) => track.stop())));
     87    caller.addTrack(localStreams[0].getTracks()[0], localStreams[0]);
     88    caller.addTrack(localStreams[1].getTracks()[0], localStreams[0]);
     89    let ontrackEventsFired = 0;
     90    const ontrackEventResolvers = [ new Resolver(), new Resolver() ];
     91    callee.ontrack = t.step_func(e => {
     92      ontrackEventResolvers[ontrackEventsFired++].resolve(e);
     93    });
     94    await exchangeOffer(caller, callee);
     95    let firstTrackEvent = await ontrackEventResolvers[0];
     96    assert_equals(firstTrackEvent.streams.length, 1,
     97                  'First ontrack fires with a single stream.');
     98    assert_equals(firstTrackEvent.streams[0].id,
     99                  localStreams[0].id,
    100                  'First ontrack\'s stream ID matches local stream.');
    101    let secondTrackEvent = await ontrackEventResolvers[1];
    102    assert_equals(secondTrackEvent.streams.length, 1,
    103                  'Second ontrack fires with a single stream.');
    104    assert_equals(secondTrackEvent.streams[0].id,
    105                  localStreams[0].id,
    106                  'Second ontrack\'s stream ID matches local stream.');
    107    assert_array_equals(firstTrackEvent.streams, secondTrackEvent.streams,
    108                        'ontrack was fired with the same streams both times.');
    109 
    110    assert_equals(firstTrackEvent.streams[0].getTracks().length, 2, "stream should have two tracks");
    111    assert_true(firstTrackEvent.streams[0].getTracks().includes(firstTrackEvent.track), "remoteStream should have the first track");
    112    assert_true(firstTrackEvent.streams[0].getTracks().includes(secondTrackEvent.track), "remoteStream should have the second track");
    113    assert_equals(ontrackEventsFired, 2, 'Unexpected number of track events.');
    114 
    115    assert_equals(ontrackEventsFired, 2, 'Unexpected number of track events.');
    116  }, 'addTrack() with two tracks and one stream makes ontrack fire twice with the tracks and shared stream.');
    117 
    118  promise_test(async t => {
    119    const caller = new RTCPeerConnection();
    120    t.add_cleanup(() => caller.close());
    121    const callee = new RTCPeerConnection();
    122    t.add_cleanup(() => callee.close());
    123    let eventSequence = '';
    124    const localStreams = await Promise.all([
    125      getNoiseStream({audio: true}),
    126      getNoiseStream({audio: true}),
    127    ]);
    128    t.add_cleanup(() => localStreams.forEach((stream) =>
    129      stream.getTracks().forEach((track) => track.stop())));
    130    caller.addTrack(localStreams[0].getTracks()[0], localStreams[0]);
    131    const remoteStreams = [];
    132    callee.ontrack = e => {
    133      if (!remoteStreams.includes(e.streams[0]))
    134        remoteStreams.push(e.streams[0]);
    135    };
    136    exchangeIceCandidates(caller, callee);
    137    await exchangeOfferAnswer(caller, callee);
    138    assert_equals(remoteStreams.length, 1, 'One remote stream created.');
    139    assert_equals(remoteStreams[0].id, localStreams[0].id,
    140                  'First local and remote streams have the same ID.');
    141    const firstRemoteTrack = remoteStreams[0].getTracks()[0];
    142    const onaddtrackPromise = addEventListenerPromise(t, remoteStreams[0], 'addtrack');
    143    caller.addTrack(localStreams[1].getTracks()[0], localStreams[0]);
    144    await exchangeOffer(caller, callee);
    145    const e = await onaddtrackPromise;
    146    assert_equals(remoteStreams[0].getTracks().length, 2, 'stream has two tracks');
    147    assert_not_equals(e.track.id, firstRemoteTrack.id,
    148                      'addtrack event has a new track');
    149    assert_equals(remoteStreams.length, 1, 'Still a single remote stream.');
    150  }, 'addTrack() for an existing stream makes stream.onaddtrack fire.');
    151 
    152  promise_test(async t => {
    153    const caller = new RTCPeerConnection();
    154    t.add_cleanup(() => caller.close());
    155    const callee = new RTCPeerConnection();
    156    t.add_cleanup(() => callee.close());
    157    let eventSequence = '';
    158    const localStreams = await Promise.all([
    159      getNoiseStream({audio: true}),
    160      getNoiseStream({audio: true}),
    161    ]);
    162    t.add_cleanup(() => localStreams.forEach((stream) =>
    163      stream.getTracks().forEach((track) => track.stop())));
    164    caller.addTrack(localStreams[0].getTracks()[0], localStreams[0]);
    165    const remoteStreams = [];
    166    callee.ontrack = e => {
    167      if (!remoteStreams.includes(e.streams[0]))
    168        remoteStreams.push(e.streams[0]);
    169    };
    170    exchangeIceCandidates(caller, callee);
    171    await exchangeOfferAnswer(caller, callee);
    172    assert_equals(remoteStreams.length, 1, 'One remote stream created.');
    173    const onaddtrackPromise =
    174        addEventListenerPromise(t, remoteStreams[0], 'addtrack', e => {
    175      eventSequence += 'stream.onaddtrack;';
    176    });
    177    caller.addTrack(localStreams[1].getTracks()[0], localStreams[0]);
    178    await exchangeOffer(caller, callee);
    179    eventSequence += 'setRemoteDescription;';
    180    await onaddtrackPromise;
    181    assert_equals(remoteStreams.length, 1, 'Still a single remote stream.');
    182    assert_equals(eventSequence, 'stream.onaddtrack;setRemoteDescription;');
    183  }, 'stream.onaddtrack fires before setRemoteDescription resolves.');
    184 
    185  promise_test(async t => {
    186    const caller = new RTCPeerConnection();
    187    t.add_cleanup(() => caller.close());
    188    const callee = new RTCPeerConnection();
    189    t.add_cleanup(() => callee.close());
    190    const localStreams = await Promise.all([
    191      getNoiseStream({audio: true}),
    192      getNoiseStream({audio: true}),
    193    ]);
    194    t.add_cleanup(() => localStreams.forEach((stream) =>
    195      stream.getTracks().forEach((track) => track.stop())));
    196    caller.addTrack(localStreams[0].getTracks()[0],
    197                    localStreams[0], localStreams[1]);
    198    const ontrackPromise = addEventListenerPromise(t, callee, 'track', e => {
    199      assert_equals(e.streams.length, 2, 'Two remote stream created.');
    200      assert_array_equals(e.streams[0].getTracks(), [e.track],
    201                          'First remote stream == [remote track].');
    202      assert_array_equals(e.streams[1].getTracks(), [e.track],
    203                          'Second remote stream == [remote track].');
    204      assert_equals(e.streams[0].id, localStreams[0].id,
    205                    'First local and remote stream IDs match.');
    206      assert_equals(e.streams[1].id, localStreams[1].id,
    207                    'Second local and remote stream IDs match.');
    208    });
    209    await exchangeOffer(caller, callee);
    210    await ontrackPromise;
    211  }, 'addTrack() with a track and two streams makes ontrack fire with a track and two streams.');
    212 
    213  promise_test(async t => {
    214    const caller = new RTCPeerConnection();
    215    t.add_cleanup(() => caller.close());
    216    const callee = new RTCPeerConnection();
    217    t.add_cleanup(() => callee.close());
    218    const localStream =
    219        await getNoiseStream({audio: true});
    220    t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop()));
    221    caller.addTrack(localStream.getTracks()[0], localStream);
    222    const ontrackPromise = addEventListenerPromise(t, callee, 'track', e => {
    223      assert_array_equals(callee.getReceivers(), [e.receiver],
    224                          'getReceivers() == [e.receiver].');
    225    });
    226    await exchangeOffer(caller, callee);
    227    await ontrackPromise;
    228  }, 'ontrack\'s receiver matches getReceivers().');
    229 
    230  promise_test(async t => {
    231    const caller = new RTCPeerConnection();
    232    t.add_cleanup(() => caller.close());
    233    const callee = new RTCPeerConnection();
    234    t.add_cleanup(() => callee.close());
    235    const localStream =
    236        await getNoiseStream({audio: true});
    237    t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop()));
    238    const sender = caller.addTrack(localStream.getTracks()[0], localStream);
    239    const ontrackPromise = addEventListenerPromise(t, callee, 'track');
    240    exchangeIceCandidates(caller, callee);
    241    await exchangeOfferAnswer(caller, callee);
    242    await ontrackPromise;
    243    assert_equals(callee.getReceivers().length, 1, 'One receiver created.');
    244    caller.removeTrack(sender);
    245    await exchangeOffer(caller, callee);
    246    assert_equals(callee.getReceivers().length, 1, 'Receiver not removed.');
    247  }, 'removeTrack() does not remove the receiver.');
    248 
    249  promise_test(async t => {
    250    const caller = new RTCPeerConnection();
    251    t.add_cleanup(() => caller.close());
    252    const callee = new RTCPeerConnection();
    253    t.add_cleanup(() => callee.close());
    254    const localStream =
    255        await getNoiseStream({audio: true});
    256    t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop()));
    257    const [track] = localStream.getTracks();
    258    const sender = caller.addTrack(track, localStream);
    259    const ontrackPromise = addEventListenerPromise(t, callee, 'track', e => {
    260      assert_equals(e.streams.length, 1);
    261      return e.streams[0];
    262    });
    263    exchangeIceCandidates(caller, callee);
    264    await exchangeOfferAnswer(caller, callee);
    265    const remoteStream = await ontrackPromise;
    266    const remoteTrack = remoteStream.getTracks()[0];
    267    const onremovetrackPromise =
    268        addEventListenerPromise(t, remoteStream, 'removetrack', e => {
    269      assert_equals(e.track, remoteTrack);
    270      assert_equals(remoteStream.getTracks().length, 0,
    271                    'Remote stream emptied of tracks.');
    272    });
    273    caller.removeTrack(sender);
    274    await exchangeOffer(caller, callee);
    275    await onremovetrackPromise;
    276  }, 'removeTrack() makes stream.onremovetrack fire and the track to be removed from the stream.');
    277 
    278  promise_test(async t => {
    279    const caller = new RTCPeerConnection();
    280    t.add_cleanup(() => caller.close());
    281    const callee = new RTCPeerConnection();
    282    t.add_cleanup(() => callee.close());
    283    let eventSequence = '';
    284    const localStream =
    285        await getNoiseStream({audio: true});
    286    t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop()));
    287    const sender = caller.addTrack(localStream.getTracks()[0], localStream);
    288    const ontrackPromise = addEventListenerPromise(t, callee, 'track', e => {
    289      assert_equals(e.streams.length, 1);
    290      return e.streams[0];
    291    });
    292    exchangeIceCandidates(caller, callee);
    293    await exchangeOfferAnswer(caller, callee);
    294    const remoteStream = await ontrackPromise;
    295    const remoteTrack = remoteStream.getTracks()[0];
    296    const onremovetrackPromise =
    297        addEventListenerPromise(t, remoteStream, 'removetrack', e => {
    298      eventSequence += 'stream.onremovetrack;';
    299    });
    300    caller.removeTrack(sender);
    301    await exchangeOffer(caller, callee);
    302    eventSequence += 'setRemoteDescription;';
    303    await onremovetrackPromise;
    304    assert_equals(eventSequence, 'stream.onremovetrack;setRemoteDescription;');
    305  }, 'stream.onremovetrack fires before setRemoteDescription resolves.');
    306 
    307  promise_test(async t => {
    308    const caller = new RTCPeerConnection();
    309    t.add_cleanup(() => caller.close());
    310    const callee = new RTCPeerConnection();
    311    t.add_cleanup(() => callee.close());
    312    const localStream =
    313        await getNoiseStream({audio: true});
    314    t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop()));
    315    const sender = caller.addTrack(localStream.getTracks()[0], localStream);
    316    exchangeIceCandidates(caller, callee);
    317    const e = await exchangeOfferAndListenToOntrack(t, caller, callee);
    318    const remoteTrack = e.track;
    319 
    320    // Need to wait for unmute, otherwise there's no event for the transition
    321    // back to muted.
    322    const onunmutePromise =
    323        addEventListenerPromise(t, remoteTrack, 'unmute', () => {
    324      assert_false(remoteTrack.muted);
    325    });
    326    await exchangeAnswer(caller, callee);
    327    await onunmutePromise;
    328 
    329    const onmutePromise =
    330        addEventListenerPromise(t, remoteTrack, 'mute', () => {
    331      assert_true(remoteTrack.muted);
    332    });
    333    caller.removeTrack(sender);
    334    await exchangeOffer(caller, callee);
    335    await onmutePromise;
    336  }, 'removeTrack() makes track.onmute fire and the track to be muted.');
    337 
    338  promise_test(async t => {
    339    const caller = new RTCPeerConnection();
    340    t.add_cleanup(() => caller.close());
    341    const callee = new RTCPeerConnection();
    342    t.add_cleanup(() => callee.close());
    343    let eventSequence = '';
    344    const localStream =
    345        await getNoiseStream({audio: true});
    346    t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop()));
    347    const sender = caller.addTrack(localStream.getTracks()[0], localStream);
    348    const ontrackPromise = addEventListenerPromise(t, callee, 'track', e => {
    349      assert_equals(e.streams.length, 1);
    350      return e.streams[0];
    351    });
    352    exchangeIceCandidates(caller, callee);
    353    const e = await exchangeOfferAndListenToOntrack(t, caller, callee);
    354    const remoteTrack = e.track;
    355 
    356    // Need to wait for unmute, otherwise there's no event for the transition
    357    // back to muted.
    358    const onunmutePromise =
    359        addEventListenerPromise(t, remoteTrack, 'unmute', () => {
    360      assert_false(remoteTrack.muted);
    361    });
    362    await exchangeAnswer(caller, callee);
    363    await onunmutePromise;
    364 
    365    const onmutePromise =
    366        addEventListenerPromise(t, remoteTrack, 'mute', () => {
    367      eventSequence += 'track.onmute;';
    368    });
    369    caller.removeTrack(sender);
    370    await exchangeOffer(caller, callee);
    371    eventSequence += 'setRemoteDescription;';
    372    await onmutePromise;
    373    assert_equals(eventSequence, 'track.onmute;setRemoteDescription;');
    374  }, 'track.onmute fires before setRemoteDescription resolves.');
    375 
    376  promise_test(async t => {
    377    const pc = new RTCPeerConnection();
    378    t.add_cleanup(() => pc.close());
    379    const stream = await getNoiseStream({audio: true});
    380    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    381    const sender = pc.addTrack(stream.getTracks()[0]);
    382    pc.removeTrack(sender);
    383    pc.removeTrack(sender);
    384  }, 'removeTrack() twice is safe.');
    385 </script>