tor-browser

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

RTCPeerConnection-mandatory-getStats.https.html (8235B)


      1 <!doctype html>
      2 <meta charset=utf-8>
      3 <meta name="timeout" content="long">
      4 <title>Mandatory-to-implement stats compliance (a subset of webrtc-stats)</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 // From https://w3c.github.io/webrtc-pc/#mandatory-to-implement-stats
     12 
     13 const mandatory = {
     14  RTCRtpStreamStats: [
     15    "ssrc",
     16    "kind",
     17    "transportId",
     18    "codecId",
     19  ],
     20  RTCReceivedRtpStreamStats: [
     21    "packetsReceived",
     22    "packetsLost",
     23    "jitter",
     24  ],
     25  RTCInboundRtpStreamStats: [
     26    "trackIdentifier",
     27    "remoteId",
     28    "framesDecoded",
     29    "framesDropped",
     30    "nackCount",
     31    "framesReceived",
     32    "bytesReceived",
     33    "totalAudioEnergy",
     34    "totalSamplesDuration",
     35    "packetsDiscarded",
     36  ],
     37  RTCRemoteInboundRtpStreamStats: [
     38    "localId",
     39    "roundTripTime",
     40  ],
     41  RTCSentRtpStreamStats: [
     42    "packetsSent",
     43    "bytesSent"
     44  ],
     45  RTCOutboundRtpStreamStats: [
     46    "remoteId",
     47    "framesEncoded",
     48    "nackCount",
     49    "framesSent"
     50  ],
     51  RTCRemoteOutboundRtpStreamStats: [
     52    "localId",
     53    "remoteTimestamp",
     54  ],
     55  RTCPeerConnectionStats: [
     56    "dataChannelsOpened",
     57    "dataChannelsClosed",
     58  ],
     59  RTCDataChannelStats: [
     60    "label",
     61    "protocol",
     62    "dataChannelIdentifier",
     63    "state",
     64    "messagesSent",
     65    "bytesSent",
     66    "messagesReceived",
     67    "bytesReceived",
     68  ],
     69  RTCMediaSourceStats: [
     70    "trackIdentifier",
     71     "kind"
     72  ],
     73  RTCAudioSourceStats: [
     74    "totalAudioEnergy",
     75     "totalSamplesDuration"
     76  ],
     77  RTCVideoSourceStats: [
     78    "width",
     79    "height",
     80    "framesPerSecond"
     81  ],
     82  RTCCodecStats: [
     83    "payloadType",
     84    /* codecType is part of MTI but is not systematically set
     85       per https://www.w3.org/TR/webrtc-stats/#dom-rtccodecstats-codectype
     86       If the dictionary member is not present, it means that
     87      this media format can be both encoded and decoded. */
     88    // "codecType",
     89    "mimeType",
     90    "clockRate",
     91    "channels",
     92    "sdpFmtpLine",
     93  ],
     94  RTCTransportStats: [
     95    "bytesSent",
     96    "bytesReceived",
     97    "selectedCandidatePairId",
     98    "localCertificateId",
     99    "remoteCertificateId",
    100  ],
    101  RTCIceCandidatePairStats: [
    102    "transportId",
    103    "localCandidateId",
    104    "remoteCandidateId",
    105    "state",
    106    "nominated",
    107    "bytesSent",
    108    "bytesReceived",
    109    "totalRoundTripTime",
    110    "responsesReceived",
    111    "currentRoundTripTime"
    112  ],
    113  RTCIceCandidateStats: [
    114    "address",
    115    "port",
    116    "protocol",
    117    "candidateType",
    118    /* url requires a STUN or TURN server so is not testable. */
    119    // "url",
    120  ],
    121  RTCCertificateStats: [
    122    "fingerprint",
    123    "fingerprintAlgorithm",
    124    "base64Certificate",
    125    /* issuerCertificateId is part of MTI but is not systematically set
    126       per https://www.w3.org/TR/webrtc-stats/#dom-rtccertificatestats-issuercertificateid
    127       If the current certificate is at the end of the chain
    128       (i.e. a self-signed certificate), this will not be set. */
    129    // "issuerCertificateId",
    130  ],
    131 };
    132 
    133 // From https://w3c.github.io/webrtc-stats/webrtc-stats.html#rtcstatstype-str*
    134 
    135 const dictionaryNames = {
    136  "codec": "RTCCodecStats",
    137  "inbound-rtp": "RTCInboundRtpStreamStats",
    138  "outbound-rtp": "RTCOutboundRtpStreamStats",
    139  "remote-inbound-rtp": "RTCRemoteInboundRtpStreamStats",
    140  "remote-outbound-rtp": "RTCRemoteOutboundRtpStreamStats",
    141  "csrc": "RTCRtpContributingSourceStats",
    142  "peer-connection": "RTCPeerConnectionStats",
    143  "data-channel": "RTCDataChannelStats",
    144  "media-source": {
    145    audio: "RTCAudioSourceStats",
    146    video: "RTCVideoSourceStats"
    147  },
    148  "track": {
    149    video: "RTCSenderVideoTrackAttachmentStats",
    150    audio: "RTCSenderAudioTrackAttachmentStats"
    151  },
    152  "sender": {
    153    audio: "RTCAudioSenderStats",
    154    video: "RTCVideoSenderStats"
    155  },
    156  "receiver": {
    157    audio: "RTCAudioReceiverStats",
    158    video: "RTCVideoReceiverStats",
    159  },
    160  "transport": "RTCTransportStats",
    161  "candidate-pair": "RTCIceCandidatePairStats",
    162  "local-candidate": "RTCIceCandidateStats",
    163  "remote-candidate": "RTCIceCandidateStats",
    164  "certificate": "RTCCertificateStats",
    165 };
    166 
    167 // From https://w3c.github.io/webrtc-stats/webrtc-stats.html (webidl)
    168 
    169 const parents = {
    170  RTCVideoSourceStats: "RTCMediaSourceStats",
    171  RTCAudioSourceStats: "RTCMediaSourceStats",
    172  RTCReceivedRtpStreamStats: "RTCRtpStreamStats",
    173  RTCInboundRtpStreamStats: "RTCReceivedRtpStreamStats",
    174  RTCRemoteInboundRtpStreamStats: "RTCReceivedRtpStreamStats",
    175  RTCSentRtpStreamStats: "RTCRtpStreamStats",
    176  RTCOutboundRtpStreamStats: "RTCSentRtpStreamStats",
    177  RTCRemoteOutboundRtpStreamStats : "RTCSentRtpStreamStats",
    178 };
    179 
    180 const remaining = JSON.parse(JSON.stringify(mandatory));
    181 for (const dictName in remaining) {
    182  remaining[dictName] = new Set(remaining[dictName]);
    183 }
    184 
    185 async function getAllStats(t, pc) {
    186  // Try to obtain as many stats as possible, waiting up to 20 seconds for
    187  // roundTripTime of RTCRemoteInboundRtpStreamStats and
    188  // remoteTimestamp of RTCRemoteOutboundRtpStreamStats which can take
    189  // several RTCP messages to calculate.
    190  let stats;
    191  let remoteInboundFound = false;
    192  let remoteOutboundFound = false;
    193  for (let i = 0; i < 20; i++) {
    194    stats = await pc.getStats();
    195    const values = [...stats.values()];
    196    const [remoteInboundAudio, remoteInboundVideo] = ["audio", "video"].map(
    197      kind => values.find(s =>
    198        s.type == "remote-inbound-rtp" && s.kind == kind));
    199    if (remoteInboundAudio && "roundTripTime" in remoteInboundAudio &&
    200        remoteInboundVideo && "roundTripTime" in remoteInboundVideo) {
    201      remoteInboundFound = true;
    202    }
    203    const [remoteOutboundAudio, remoteOutboundVideo] = ["audio", "video"].map(
    204      kind => values.find(s =>
    205        s.type == "remote-outbound-rtp" && s.kind == kind));
    206    if (remoteOutboundAudio && "remoteTimestamp" in remoteOutboundAudio &&
    207        remoteOutboundVideo && "remoteTimestamp" in remoteOutboundVideo) {
    208      remoteOutboundFound = true;
    209    }
    210    if (remoteInboundFound && remoteOutboundFound) {
    211      return stats;
    212    }
    213    await new Promise(r => t.step_timeout(r, 1000));
    214  }
    215  return stats;
    216 }
    217 
    218 promise_test(async t => {
    219  const pc1 = new RTCPeerConnection();
    220  t.add_cleanup(() => pc1.close());
    221  const pc2 = new RTCPeerConnection();
    222  t.add_cleanup(() => pc2.close());
    223 
    224  const dc1 = pc1.createDataChannel("dummy", {negotiated: true, id: 0});
    225  const dc2 = pc2.createDataChannel("dummy", {negotiated: true, id: 0});
    226 
    227  const stream = await getNoiseStream({video: true, audio:true});
    228  for (const track of stream.getTracks()) {
    229    pc1.addTrack(track, stream);
    230    pc2.addTrack(track, stream);
    231    t.add_cleanup(() => track.stop());
    232  }
    233  exchangeIceCandidates(pc1, pc2);
    234  await exchangeOfferAnswer(pc1, pc2);
    235  const stats = await getAllStats(t, pc1);
    236 
    237  // The focus of this test is not API correctness, but rather to provide an
    238  // accessible metric of implementation progress by dictionary member. We count
    239  // whether we've seen each dictionary's mandatory members in getStats().
    240 
    241  test(t => {
    242    for (const stat of stats.values()) {
    243      let dictName = dictionaryNames[stat.type];
    244      if (!dictName) continue;
    245      if (typeof dictName == "object") {
    246        dictName = dictName[stat.kind];
    247      }
    248 
    249      assert_equals(typeof dictName, "string", "Test error. String.");
    250      if (dictName && mandatory[dictName]) {
    251        do {
    252          const memberNames = mandatory[dictName];
    253          const remainingNames = remaining[dictName];
    254          assert_true(memberNames.length > 0, "Test error. Parent not found.");
    255          for (const memberName of memberNames) {
    256            if (memberName in stat) {
    257              assert_not_equals(stat[memberName], undefined, "Not undefined");
    258              remainingNames.delete(memberName);
    259            }
    260          }
    261          dictName = parents[dictName];
    262        } while (dictName);
    263      }
    264    }
    265  }, "Validating stats");
    266 
    267  for (const dictName in mandatory) {
    268    for (const memberName of mandatory[dictName]) {
    269      test(t => {
    270        assert_true(!remaining[dictName].has(memberName),
    271                    `Is ${memberName} present`);
    272      }, `${dictName}'s ${memberName}`);
    273    }
    274  }
    275 }, 'getStats succeeds');
    276 
    277 </script>