tor-browser

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

rtp-stats-lifetime.https.html (9819B)


      1 <!doctype html>
      2 <meta charset=utf-8>
      3 <meta name="timeout" content="long">
      4 <script src="/resources/testharness.js"></script>
      5 <script src="/resources/testharnessreport.js"></script>
      6 <script src="RTCPeerConnection-helper.js"></script>
      7 <script src="third_party/sdp/sdp.js"></script>
      8 <script src="simulcast/simulcast.js"></script>
      9 <script>
     10 'use strict';
     11 
     12 async function hasStats(pc, type) {
     13  const report = await pc.getStats();
     14  for (const stats of report.values()) {
     15    if (stats.type == type) {
     16      return true;
     17    }
     18  }
     19  return false;
     20 }
     21 
     22 async function getStatsTypePollUntilItExists(pc, type, kTimeoutMs = 10000) {
     23  const t0 = performance.now();
     24  while (performance.now() - t0 < kTimeoutMs) {
     25    const report = await pc.getStats();
     26    for (const stats of report.values()) {
     27      if (stats.type == type) {
     28        return stats;
     29      }
     30    }
     31  }
     32  return null;
     33 }
     34 
     35 promise_test(async t => {
     36  const pc = new RTCPeerConnection();
     37  t.add_cleanup(() => pc.close());
     38 
     39  pc.addTransceiver('video');
     40  assert_false(await hasStats(pc, 'outbound-rtp'),
     41               'outbound-rtp does not exist after addTransceiver');
     42  await pc.setLocalDescription();
     43  assert_false(await hasStats(pc, 'outbound-rtp'),
     44              'outbound-rtp does not exist in have-local-offer');
     45 }, `RTCOutboundRtpStreamStats does not exist as early as have-local-offer`);
     46 
     47 // This test does not exchange ICE candidates, meaning no packets are sent.
     48 // We should still see outbound-rtp stats because they are created by the O/A.
     49 promise_test(async t => {
     50  const pc1 = new RTCPeerConnection();
     51  t.add_cleanup(() => pc1.close());
     52  const pc2 = new RTCPeerConnection();
     53  t.add_cleanup(() => pc2.close());
     54 
     55  // Offer to send. See previous test for assertions that the outbound-rtp is
     56  // not created this early, which this test does not care about.
     57  pc1.addTransceiver('video');
     58  await pc1.setLocalDescription();
     59 
     60  // Answer to send.
     61  await pc2.setRemoteDescription(pc1.localDescription);
     62  const [transceiver] = pc2.getTransceivers();
     63  transceiver.direction = 'sendrecv';
     64  assert_false(await hasStats(pc2, 'outbound-rtp'),
     65               'outbound-rtp does not exist in has-remote-offer');
     66  await pc2.setLocalDescription();
     67  assert_true(await hasStats(pc2, 'outbound-rtp'),
     68              'outbound-rtp exists after answerer returns to stable');
     69 
     70  // Complete offerer negotiation.
     71  await pc1.setRemoteDescription(pc2.localDescription);
     72  assert_true(await hasStats(pc1, 'outbound-rtp'),
     73              'outbound-rtp exists after offerer returns to stable');
     74 }, `RTCOutboundRtpStreamStats exists after returning to stable`);
     75 
     76 promise_test(async t => {
     77  const pc1 = new RTCPeerConnection();
     78  t.add_cleanup(() => pc1.close());
     79  const pc2 = new RTCPeerConnection();
     80  t.add_cleanup(() => pc2.close());
     81 
     82  pc1.addTransceiver('video', {sendEncodings: [{rid: 'foo'}, {rid: 'bar'}]});
     83  // O/A with tweaks to accept simulcast.
     84  await doOfferToSendSimulcastAndAnswer(pc1, pc2, ['foo', 'bar']);
     85 
     86  // Despite not sending anything (ICE not connected) both outbound-rtp stats
     87  // objects should appear immediately.
     88  const report = await pc1.getStats();
     89  const outboundRtpFoo =
     90      report.values().find(s => s.type == 'outbound-rtp' && s.rid == 'foo');
     91  const outboundRtpBar =
     92      report.values().find(s => s.type == 'outbound-rtp' && s.rid == 'bar');
     93  assert_not_equals(outboundRtpFoo, undefined);
     94  assert_not_equals(outboundRtpFoo.ssrc, undefined);
     95  assert_not_equals(outboundRtpBar, undefined);
     96  assert_not_equals(outboundRtpBar.ssrc, undefined);
     97 }, `RTCOutboundRtpStreamStats exists per simulcast encoding`);
     98 
     99 promise_test(async t => {
    100  const pc1 = new RTCPeerConnection();
    101  t.add_cleanup(() => pc1.close());
    102  const pc2 = new RTCPeerConnection();
    103  t.add_cleanup(() => pc2.close());
    104  pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
    105  pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
    106 
    107  // Negotaite to send, but don't send anything yet (track is null).
    108  const {sender} = pc1.addTransceiver('video');
    109  await pc1.setLocalDescription();
    110  await pc2.setRemoteDescription(pc1.localDescription);
    111  await pc2.setLocalDescription();
    112  await pc1.setRemoteDescription(pc2.localDescription);
    113  assert_false(await hasStats(pc2, 'inbound-rtp'),
    114               'inbound-rtp does not exist before packets are received');
    115 
    116  // Start sending. This results in inbound-rtp being created.
    117  const stream = await getNoiseStream({video:true});
    118  const [track] = stream.getTracks();
    119  t.add_cleanup(() => track.stop());
    120  await sender.replaceTrack(track);
    121  const inboundRtp = await getStatsTypePollUntilItExists(pc2, 'inbound-rtp');
    122  assert_not_equals(
    123      inboundRtp, null,
    124      'inbound-rtp should be created in response to the sender having a track');
    125  assert_greater_than(
    126      inboundRtp.packetsReceived, 0,
    127      'inbound-rtp must only exist after packets have been received');
    128 }, `RTCInboundRtpStreamStats are created by packet reception`);
    129 
    130 [{name: "pc", statsSource: pc => pc, kind: "audio"},
    131 {name: "sender", statsSource: pc => pc.getSenders()[0], kind: "audio"},
    132 {name: "pc", statsSource: pc => pc, kind: "video"},
    133 {name: "sender", statsSource: pc => pc.getSenders()[0], kind: "video"}
    134 ].forEach(({name, statsSource, kind}) => promise_test(async t => {
    135  const pc1 = new RTCPeerConnection();
    136  t.add_cleanup(() => pc1.close());
    137  const pc2 = new RTCPeerConnection();
    138  t.add_cleanup(() => pc2.close());
    139  pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
    140  pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
    141 
    142  // Check statsSource for the outbound-rtp stats object.
    143  const getOutboundRtpStats = async () =>
    144    await getStatsTypePollUntilItExists(statsSource(pc1), 'outbound-rtp');
    145 
    146  pc1.addTransceiver(kind);
    147  await pc1.setLocalDescription();
    148  await pc2.setRemoteDescription(pc1.localDescription);
    149  await pc2.setLocalDescription();
    150  await pc1.setRemoteDescription(pc2.localDescription);
    151 
    152  {
    153    const stats = await getOutboundRtpStats();
    154    assert_not_equals(
    155      stats, null,
    156      `outbound-rtp should be created in ${name}.getStats() in response to the pc.addTransceiver("${kind}")`);
    157  }
    158 }, `RTCOutboundRtpStreamStats exist in ${name}.getStats() after created with a null track through pc.addTransceiver("${kind}")`));
    159 
    160 [{name: "pc", statsSource: pc => pc},
    161 {name: "sender", statsSource: pc => pc.getSenders()[0]}
    162 ].forEach(({name, statsSource}) => promise_test(async t => {
    163  const pc1 = new RTCPeerConnection();
    164  t.add_cleanup(() => pc1.close());
    165  const pc2 = new RTCPeerConnection();
    166  t.add_cleanup(() => pc2.close());
    167  pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
    168  pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
    169 
    170  // Check statsSource for the outbound-rtp stats object.
    171  const getOutboundRtpStats = async () =>
    172    await getStatsTypePollUntilItExists(statsSource(pc1), 'outbound-rtp');
    173 
    174  const stream = await getNoiseStream({video:true});
    175 
    176  const {sender} = pc1.addTransceiver(stream.getTracks()[0]);
    177  await pc1.setLocalDescription();
    178  await pc2.setRemoteDescription(pc1.localDescription);
    179  await pc2.setLocalDescription();
    180  await pc1.setRemoteDescription(pc2.localDescription);
    181 
    182  // Wait for packets to be received
    183  {
    184    const stats = await getOutboundRtpStats();
    185    assert_not_equals(
    186      stats, null,
    187      `outbound-rtp should be created in ${name}.getStats() in response to the sender having a track`);
    188  }
    189 
    190  // Stop sending. This should not remove the outbound-rtp stats object.
    191  await sender.replaceTrack(null);
    192  // Check that the outbound-rtp stats object remains.
    193  {
    194    const stats = await getOutboundRtpStats();
    195    assert_not_equals(
    196        stats, null,
    197        `outbound-rtp should remain in ${name}.getStats() after sender.replaceTrack(null)`);
    198  }
    199 }, `RTCOutboundRtpStreamStats remain in ${name}.getStats() after sender.replaceTrack(null)`));
    200 
    201 promise_test(async t => {
    202  const pc1 = new RTCPeerConnection();
    203  t.add_cleanup(() => pc1.close());
    204  const pc2 = new RTCPeerConnection();
    205  t.add_cleanup(() => pc2.close());
    206  pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
    207  pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
    208 
    209  // Negotiate a dummy m-section for DTLS to be established.
    210  pc1.addTransceiver('video', {direction:'sendonly'});
    211  await pc1.setLocalDescription();
    212  await pc2.setRemoteDescription(pc1.localDescription);
    213  await pc2.setLocalDescription();
    214  await pc1.setRemoteDescription(pc2.localDescription);
    215 
    216  // Offer to receive.
    217  pc1.addTransceiver('video', {direction:'recvonly'});
    218  await pc1.setLocalDescription();
    219  await pc2.setRemoteDescription(pc1.localDescription);
    220  assert_false(await hasStats(pc1, 'inbound-rtp'),
    221               'inbound-rtp does not exist before packets are received');
    222  // Answer to send.
    223  const transceivers = pc2.getTransceivers();
    224  assert_equals(transceivers.length, 2);
    225  const transceiver = transceivers[1];
    226  const stream = await getNoiseStream({video:true});
    227  const [track] = stream.getTracks();
    228  t.add_cleanup(() => track.stop());
    229  await transceiver.sender.replaceTrack(track);
    230  transceiver.direction = 'sendonly';
    231  await pc2.setLocalDescription();
    232 
    233  // We never set the remote answer.
    234  assert_equals(pc1.signalingState, 'have-local-offer');
    235  // But because of early media, we're still able to receive packets.
    236  // - Whether or not we unmute the track in response to this is outside the
    237  //   scope of this test.
    238  const inboundRtp = await getStatsTypePollUntilItExists(pc1, 'inbound-rtp');
    239  assert_not_equals(
    240      inboundRtp, null,
    241      'inbound-rtp should be created in the early media use case');
    242  assert_greater_than(
    243      inboundRtp.packetsReceived, 0,
    244      'inbound-rtp must only exist after packets have been received');
    245 }, `RTCInboundRtpStreamStats exists for early media`);
    246 </script>