tor-browser

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

RTCPeerConnection-getStats.https.html (14870B)


      1 <!doctype html>
      2 <meta charset=utf-8>
      3 <meta name="timeout" content="long">
      4 <title>RTCPeerConnection.prototype.getStats</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  // Test is based on the following editor draft:
     12  // webrtc-pc 20171130
     13  // webrtc-stats 20171122
     14 
     15  // The following helper function is called from RTCPeerConnection-helper.js
     16  //   getTrackFromUserMedia
     17 
     18  // The following helper function is called from RTCPeerConnection-helper.js
     19  //   exchangeIceCandidates
     20  //   exchangeOfferAnswer
     21 
     22  /*
     23    8.2.  getStats
     24      1.  Let selectorArg be the method's first argument.
     25      2.  Let connection be the RTCPeerConnection object on which the method was invoked.
     26      3.  If selectorArg is null, let selector be null.
     27      4.  If selectorArg is a MediaStreamTrack let selector be an RTCRtpSender
     28          or RTCRtpReceiver on connection which track member matches selectorArg.
     29          If no such sender or receiver exists, or if more than one sender or
     30          receiver fit this criteria, return a promise rejected with a newly
     31          created InvalidAccessError.
     32      5.  Let p be a new promise.
     33      6.  Run the following steps in parallel:
     34        1.  Gather the stats indicated by selector according to the stats selection algorithm.
     35        2.  Resolve p with the resulting RTCStatsReport object, containing the gathered stats.
     36   */
     37  promise_test(t => {
     38    const pc = new RTCPeerConnection();
     39    t.add_cleanup(() => pc.close());
     40    return pc.getStats();
     41  }, 'getStats() with no argument should succeed');
     42 
     43  promise_test(t => {
     44    const pc = new RTCPeerConnection();
     45    t.add_cleanup(() => pc.close());
     46    return pc.getStats(null);
     47  }, 'getStats(null) should succeed');
     48 
     49  /*
     50    8.2.  getStats
     51      4.  If selectorArg is a MediaStreamTrack let selector be an RTCRtpSender
     52          or RTCRtpReceiver on connection which track member matches selectorArg.
     53          If no such sender or receiver exists, or if more than one sender or
     54          receiver fit this criteria, return a promise rejected with a newly
     55          created InvalidAccessError.
     56   */
     57  promise_test(t => {
     58    const pc = new RTCPeerConnection();
     59    t.add_cleanup(() => pc.close());
     60    return getTrackFromUserMedia('audio')
     61    .then(([track, mediaStream]) => {
     62      return promise_rejects_dom(t, 'InvalidAccessError', pc.getStats(track));
     63    });
     64  }, 'getStats() with track not added to connection should reject with InvalidAccessError');
     65 
     66  promise_test(t => {
     67    const pc = new RTCPeerConnection();
     68    t.add_cleanup(() => pc.close());
     69    return getTrackFromUserMedia('audio')
     70    .then(([track, mediaStream]) => {
     71      pc.addTrack(track, mediaStream);
     72      return pc.getStats(track);
     73    });
     74  }, 'getStats() with track added via addTrack should succeed');
     75 
     76  promise_test(async t => {
     77    const pc = new RTCPeerConnection();
     78    t.add_cleanup(() => pc.close());
     79 
     80    const stream = await getNoiseStream({audio: true});
     81    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
     82    const [track] = stream.getTracks();
     83    pc.addTransceiver(track);
     84 
     85    return pc.getStats(track);
     86  }, 'getStats() with track added via addTransceiver should succeed');
     87 
     88  promise_test(t => {
     89    const pc = new RTCPeerConnection();
     90    t.add_cleanup(() => pc.close());
     91    const transceiver1 = pc.addTransceiver('audio');
     92 
     93    // Create another transceiver that resends what
     94    // is being received, kind of like echo
     95    const transceiver2 = pc.addTransceiver(transceiver1.receiver.track);
     96    assert_equals(transceiver1.receiver.track, transceiver2.sender.track);
     97 
     98    return promise_rejects_dom(t, 'InvalidAccessError', pc.getStats(transceiver1.receiver.track));
     99  }, 'getStats() with track associated with both sender and receiver should reject with InvalidAccessError');
    100 
    101  /*
    102    8.5.  The stats selection algorithm
    103      2.  If selector is null, gather stats for the whole connection, add them to result,
    104          return result, and abort these steps.
    105   */
    106  promise_test(t => {
    107    const pc = new RTCPeerConnection();
    108    t.add_cleanup(() => pc.close());
    109    return pc.getStats()
    110    .then(statsReport => {
    111      assert_true(!![...statsReport.values()].find(({type}) => type === 'peer-connection'));
    112    });
    113  }, 'getStats() with no argument should return stats report containing peer-connection stats on an empty PC');
    114 
    115  promise_test(async t => {
    116    const pc = createPeerConnectionWithCleanup(t);
    117    const pc2 = createPeerConnectionWithCleanup(t);
    118    const [sendtrack, mediaStream] = await getTrackFromUserMedia('audio');
    119    pc.addTrack(sendtrack, mediaStream);
    120    exchangeIceCandidates(pc, pc2);
    121    await Promise.all([
    122      exchangeOfferAnswer(pc, pc2),
    123      new Promise(r => pc2.ontrack = e => e.track.onunmute = r)
    124    ]);
    125    const statsReport = await pc.getStats();
    126    assert_true(!![...statsReport.values()].find(({type}) => type === 'peer-connection'));
    127    assert_true(!![...statsReport.values()].find(({type}) => type === 'outbound-rtp'));
    128  }, 'getStats() track with stream returns peer-connection and outbound-rtp stats');
    129 
    130  promise_test(async t => {
    131    const pc = createPeerConnectionWithCleanup(t);
    132    const pc2 = createPeerConnectionWithCleanup(t);
    133    const [sendtrack, mediaStream] = await getTrackFromUserMedia('audio');
    134    pc.addTrack(sendtrack);
    135    exchangeIceCandidates(pc, pc2);
    136    await Promise.all([
    137      exchangeOfferAnswer(pc, pc2),
    138      new Promise(r => pc2.ontrack = e => e.track.onunmute = r)
    139    ]);
    140    const statsReport = await pc.getStats();
    141    assert_true(!![...statsReport.values()].find(({type}) => type === 'peer-connection'));
    142    assert_true(!![...statsReport.values()].find(({type}) => type === 'outbound-rtp'));
    143  }, 'getStats() track without stream returns peer-connection and outbound-rtp stats');
    144 
    145  promise_test(async t => {
    146    const pc = createPeerConnectionWithCleanup(t);
    147    const pc2 = createPeerConnectionWithCleanup(t);
    148    const [sendtrack, mediaStream] = await getTrackFromUserMedia('audio');
    149    pc.addTrack(sendtrack, mediaStream);
    150    exchangeIceCandidates(pc, pc2);
    151    await Promise.all([
    152      exchangeOfferAnswer(pc, pc2),
    153      new Promise(r => pc2.ontrack = e => e.track.onunmute = r)
    154    ]);
    155    const statsReport = await pc.getStats();
    156    assert_true(!![...statsReport.values()].find(({type}) => type === 'outbound-rtp'));
    157  }, 'getStats() audio contains outbound-rtp stats');
    158 
    159  promise_test(async t => {
    160    const pc = createPeerConnectionWithCleanup(t);
    161    const pc2 = createPeerConnectionWithCleanup(t);
    162    const [sendtrack, mediaStream] = await getTrackFromUserMedia('video');
    163    pc.addTrack(sendtrack, mediaStream);
    164    exchangeIceCandidates(pc, pc2);
    165    await Promise.all([
    166      exchangeOfferAnswer(pc, pc2),
    167      new Promise(r => pc2.ontrack = e => e.track.onunmute = r)
    168    ]);
    169    const statsReport = await pc.getStats();
    170    assert_true(!![...statsReport.values()].find(({type}) => type === 'outbound-rtp'));
    171  }, 'getStats() video contains outbound-rtp stats');
    172 
    173  /*
    174    8.5.  The stats selection algorithm
    175      3.  If selector is an RTCRtpSender, gather stats for and add the following objects
    176          to result:
    177        - All RTCOutboundRtpStreamStats objects corresponding to selector.
    178        - All stats objects referenced directly or indirectly by the RTCOutboundRtpStreamStats
    179          objects added.
    180  */
    181  promise_test(async t => {
    182    const pc = createPeerConnectionWithCleanup(t);
    183    const pc2 = createPeerConnectionWithCleanup(t);
    184 
    185    let [sendtrack, mediaStream] = await getTrackFromUserMedia('audio');
    186    pc.addTrack(sendtrack, mediaStream);
    187    exchangeIceCandidates(pc, pc2);
    188    await Promise.all([
    189      exchangeOfferAnswer(pc, pc2),
    190      new Promise(r => pc2.ontrack = e => e.track.onunmute = r)
    191    ]);
    192    const statsReport = await pc.getStats(sendtrack);
    193    assert_true(!![...statsReport.values()].find(({type}) => type === 'outbound-rtp'));
    194  }, `getStats() on track associated with RTCRtpSender should return stats report containing outbound-rtp stats`);
    195 
    196  /*
    197    8.5.  The stats selection algorithm
    198      4.  If selector is an RTCRtpReceiver, gather stats for and add the following objects
    199          to result:
    200        - All RTCInboundRtpStreamStats objects corresponding to selector.
    201        - All stats objects referenced directly or indirectly by the RTCInboundRtpStreamStats
    202          added.
    203   */
    204  promise_test(async t => {
    205    const pc = createPeerConnectionWithCleanup(t);
    206    const pc2 = createPeerConnectionWithCleanup(t);
    207 
    208    let [track, mediaStream] = await getTrackFromUserMedia('audio');
    209    pc.addTrack(track, mediaStream);
    210    exchangeIceCandidates(pc, pc2);
    211    await exchangeOfferAnswer(pc, pc2);
    212    // Wait for unmute if the track is not already unmuted.
    213    // According to spec, it should be muted when being created, but this
    214    // is not what this test is testing, so allow it to be unmuted.
    215    if (pc2.getReceivers()[0].track.muted) {
    216      await new Promise(resolve => {
    217        pc2.getReceivers()[0].track.addEventListener('unmute', resolve);
    218      });
    219    }
    220    const statsReport = await pc2.getStats(pc2.getReceivers()[0].track);
    221    assert_true(!![...statsReport.values()].find(({type}) => type === 'inbound-rtp'));
    222  }, `getStats() on track associated with RTCRtpReceiver should return stats report containing inbound-rtp stats`);
    223 
    224  promise_test(async t => {
    225    const pc = createPeerConnectionWithCleanup(t);
    226    const pc2 = createPeerConnectionWithCleanup(t);
    227 
    228    let [track, mediaStream] = await getTrackFromUserMedia('audio');
    229    pc.addTrack(track, mediaStream);
    230    exchangeIceCandidates(pc, pc2);
    231    await exchangeOfferAnswer(pc, pc2);
    232    // Wait for unmute if the track is not already unmuted.
    233    // According to spec, it should be muted when being created, but this
    234    // is not what this test is testing, so allow it to be unmuted.
    235    if (pc2.getReceivers()[0].track.muted) {
    236      await new Promise(resolve => {
    237        pc2.getReceivers()[0].track.addEventListener('unmute', resolve);
    238      });
    239    }
    240    const statsReport = await pc2.getStats(pc2.getReceivers()[0].track);
    241    assert_true(!![...statsReport.values()].find(({type}) => type === 'inbound-rtp'));
    242  }, `getStats() audio contains inbound-rtp stats`);
    243 
    244  promise_test(async t => {
    245    const pc = createPeerConnectionWithCleanup(t);
    246    const pc2 = createPeerConnectionWithCleanup(t);
    247 
    248    function check_no_candidate_pair_stats(statsReport) {
    249      const candidatePairStats = [...(statsReport).values()].filter(({type}) => type === 'candidate-pair');
    250      assert_greater_than_equal(candidatePairStats.length, 0);
    251    }
    252 
    253    function check_candidate_pair_stats(statsReport, elapsed_time_ms) {
    254      assert_true(!![...statsReport.values()].find(({type}) => type === 'candidate-pair'));
    255      const candidatePairStats = [...(statsReport).values()].filter(({type}) => type === 'candidate-pair');
    256      assert_greater_than_equal(candidatePairStats.length, 1);
    257 
    258      for (let pairStats of candidatePairStats) {
    259        assert_not_equals(pairStats.responsesReceived, null, "responsesReceived should not be null");
    260        assert_not_equals(pairStats.totalRoundTripTime, null, "totalRoundTripTime should not be null");
    261        assert_not_equals(pairStats.currentRoundTripTime, null, "currentRoundTripTime should not be null");
    262 
    263        assert_greater_than_equal(pairStats.responsesReceived, 0);
    264        assert_greater_than_equal(pairStats.totalRoundTripTime, 0);
    265        assert_greater_than_equal(pairStats.currentRoundTripTime, 0);
    266 
    267        if (pairStats.responsesReceived < 2) {
    268          assert_equals(pairStats.totalRoundTripTime, pairStats.currentRoundTripTime);
    269        } else {
    270          assert_greater_than_equal(pairStats.totalRoundTripTime, pairStats.currentRoundTripTime);
    271        }
    272        assert_less_than_equal(pairStats.totalRoundTripTime*1000, elapsed_time_ms)
    273      }
    274    }
    275 
    276    let [track, mediaStream] = await getTrackFromUserMedia('video');
    277    pc.addTrack(track, mediaStream);
    278    exchangeIceCandidates(pc, pc2);
    279    assert_equals(pc2.getReceivers().length, 0);
    280    await exchangeOfferAnswer(pc, pc2);
    281 
    282    assert_equals(pc.getSenders().length, 1);
    283    assert_equals(pc2.getReceivers().length, 1);
    284    const t0 = performance.now();
    285 
    286    check_no_candidate_pair_stats(await pc.getStats(pc.getSenders()[0].track));
    287 
    288    // Wait for unmute if the track is not already unmuted.
    289    // According to spec, it should be muted when being created, but this
    290    // is not what this test is testing, so allow it to be unmuted.
    291    if (pc2.getReceivers()[0].track.muted) {
    292      await new Promise(resolve => {
    293        pc2.getReceivers()[0].track.addEventListener('unmute', resolve);
    294      });
    295    }
    296    // wait a bit longer for a few consent messages
    297    await new Promise(r => t.step_timeout(r, 8000));
    298 
    299    const t1 = performance.now();
    300 
    301    check_candidate_pair_stats(await pc.getStats(pc.getSenders()[0].track), t1-t0);
    302  }, `getStats() audio contains candidate-pair stats`);
    303 
    304  promise_test(async t => {
    305    const pc = new RTCPeerConnection();
    306    t.add_cleanup(() => pc.close());
    307    const [track, mediaStream] = await getTrackFromUserMedia('audio');
    308    pc.addTransceiver(track);
    309    pc.addTransceiver(track);
    310    await promise_rejects_dom(t, 'InvalidAccessError', pc.getStats(track));
    311  }, `getStats(track) should not work if multiple senders have the same track`);
    312 
    313  promise_test(async t => {
    314    const kMinimumTimeElapsedBetweenGetStatsCallsMs = 500;
    315    const pc = new RTCPeerConnection();
    316    t.add_cleanup(() => pc.close());
    317    const t0 = Math.floor(performance.now());
    318    const t0Stats = [...(await pc.getStats()).values()].find(({type}) => type === 'peer-connection');
    319    await new Promise(
    320        r => t.step_timeout(r, kMinimumTimeElapsedBetweenGetStatsCallsMs));
    321    const t1Stats = [...(await pc.getStats()).values()].find(({type}) => type === 'peer-connection');
    322    const t1 = Math.ceil(performance.now());
    323    const maximumTimeElapsedBetweenGetStatsCallsMs = t1 - t0;
    324    const deltaTimestampMs = t1Stats.timestamp - t0Stats.timestamp;
    325    // The delta must be at least the time we waited between calls.
    326    assert_greater_than_equal(deltaTimestampMs,
    327                              kMinimumTimeElapsedBetweenGetStatsCallsMs);
    328    // The delta must be at most the time elapsed before the first getStats()
    329    // call and after the second getStats() call.
    330    assert_less_than_equal(deltaTimestampMs,
    331                           maximumTimeElapsedBetweenGetStatsCallsMs);
    332  }, `RTCStats.timestamp increases with time passing`);
    333 
    334  promise_test(async t => {
    335    const pc1 = new RTCPeerConnection();
    336    pc1.close();
    337    await pc1.getStats();
    338  }, 'getStats succeeds on a closed peerconnection');
    339 
    340 </script>