tor-browser

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

test_peerConnection_glean.html (75657B)


      1 <!DOCTYPE HTML>
      2 <html>
      3 
      4 <head>
      5  <script type="application/javascript" src="pc.js"></script>
      6  <script type="application/javascript" src="sdpUtils.js"></script>
      7  <script type="application/javascript" src="simulcast.js"></script>
      8  <script type="application/javascript" src="iceTestUtils.js"></script>
      9  <script type="application/javascript" src="helpers_from_wpt/sdp.js"></script>
     10 </head>
     11 
     12 <body>
     13  <pre id="test">
     14 <script type="application/javascript">
     15  createHTML({
     16    bug: "1401592",
     17    title: "Test that glean is recording stats as expected",
     18    visible: true
     19  });
     20 
     21  const { AppConstants } = SpecialPowers.ChromeUtils.importESModule(
     22    "resource://gre/modules/AppConstants.sys.mjs"
     23  );
     24 
     25  async function gleanResetTestValues() {
     26    return SpecialPowers.spawnChrome([], async () => {
     27      await Services.fog.testFlushAllChildren();
     28      Services.fog.testResetFOG();
     29      }
     30    )
     31  };
     32 
     33  async function gleanFlushChildren() {
     34    return SpecialPowers.spawnChrome([], async () => {
     35      await Services.fog.testFlushAllChildren();
     36      }
     37    )
     38  };
     39 
     40 
     41  async function getAllWarningRates() {
     42    return {
     43      warnNoGetparameters:
     44        await GleanTest.rtcrtpsenderSetparameters.warnNoGetparameters.testGetValue(),
     45      warnLengthChanged:
     46        await GleanTest.rtcrtpsenderSetparameters.warnLengthChanged.testGetValue(),
     47      warnNoTransactionid:
     48        await GleanTest.rtcrtpsenderSetparameters.warnNoTransactionid.testGetValue(),
     49    };
     50  }
     51 
     52  const tests = [
     53    // Keep these tests first; we do not want previous test-cases that might
     54    // still be doing DTLS handshake stuff while their PCs are closing.
     55    async function checkDtlsHandshakeSuccess() {
     56      const pc1 = new RTCPeerConnection();
     57      const pc2 = new RTCPeerConnection();
     58      await gleanResetTestValues();
     59      let client_successes = await GleanTest.webrtcdtls.clientHandshakeResult.SUCCESS.testGetValue() || 0;
     60      let server_successes = await GleanTest.webrtcdtls.serverHandshakeResult.SUCCESS.testGetValue() || 0;
     61      let cipher_count = await GleanTest.webrtcdtls.cipher["0x1301"].testGetValue() || 0;
     62      let srtp_cipher_count = await GleanTest.webrtcdtls.srtpCipher["0x0007"].testGetValue() || 0;
     63      is(client_successes, 0);
     64      is(server_successes, 0);
     65      is(cipher_count, 0);
     66      is(srtp_cipher_count, 0);
     67 
     68      const stream = await navigator.mediaDevices.getUserMedia({ video: true });
     69      pc1.addTrack(stream.getTracks()[0]);
     70 
     71      await connect(pc1, pc2, 32000, "DTLS connected", true, true);
     72      // This telemetry happens on STS/socket process
     73      await gleanFlushChildren();
     74 
     75      client_successes = await GleanTest.webrtcdtls.clientHandshakeResult.SUCCESS.testGetValue() || 0;
     76      server_successes = await GleanTest.webrtcdtls.serverHandshakeResult.SUCCESS.testGetValue() || 0;
     77      cipher_count = await GleanTest.webrtcdtls.cipher["0x1301"].testGetValue() || 0;
     78      srtp_cipher_count = await GleanTest.webrtcdtls.srtpCipher["0x0007"].testGetValue() || 0;
     79      is(client_successes, 1);
     80      is(server_successes, 1);
     81      is(cipher_count, 2);
     82      is(srtp_cipher_count, 2);
     83    },
     84 
     85    async function checkDtlsHandshakeFailure() {
     86      // We don't have many failures we can induce here, but messing up the
     87      // fingerprint is one way.
     88      const offerer = new RTCPeerConnection();
     89      const answerer = new RTCPeerConnection();
     90      await gleanResetTestValues();
     91      let client_failures = await GleanTest.webrtcdtls.clientHandshakeResult.SSL_ERROR_BAD_CERTIFICATE.testGetValue() || 0;
     92      let server_failures = await GleanTest.webrtcdtls.serverHandshakeResult.SSL_ERROR_BAD_CERT_ALERT.testGetValue() || 0;
     93      is(client_failures, 0);
     94      is(server_failures, 0);
     95 
     96      const stream = await navigator.mediaDevices.getUserMedia({ video: true });
     97      offerer.addTrack(stream.getTracks()[0]);
     98 
     99      trickleIce(offerer, answerer);
    100      trickleIce(answerer, offerer);
    101      await offerer.setLocalDescription();
    102      let badSdp = offerer.localDescription.sdp;
    103      // Tweak the last digit in the fingerprint sent to the answerer. Answerer
    104      // (which will be the DTLS client) will get an SSL_ERROR_BAD_CERTIFICATE
    105      // error, and the offerer (which will be the DTLS server) will get an
    106      // SSL_ERROR_BAD_CERT_ALERT.
    107      const lastDigit = badSdp.match(/a=fingerprint:.*([0-9A-F])$/m)[1];
    108      const newLastDigit = lastDigit == '0' ? '1' : '0';
    109      badSdp = badSdp.replace(/(a=fingerprint:.*)[0-9A-F]$/m, "$1" + newLastDigit);
    110      info(badSdp);
    111      await answerer.setRemoteDescription({sdp: badSdp, type: "offer"});
    112      await answerer.setLocalDescription();
    113      await offerer.setRemoteDescription(answerer.localDescription);
    114 
    115      const throwOnTimeout = async () => {
    116        await wait(32000);
    117        throw new Error(
    118          `ICE did not complete within ${timeout} ms`);
    119      };
    120 
    121      const connectionPromises = [connectionStateReached(offerer, "failed"),
    122                                  connectionStateReached(answerer, "failed")];
    123 
    124      await Promise.race([
    125        Promise.all(connectionPromises),
    126        throwOnTimeout()
    127      ]);
    128      // This telemetry happens on STS/socket process
    129      await gleanFlushChildren();
    130 
    131      client_failures = await GleanTest.webrtcdtls.clientHandshakeResult.SSL_ERROR_BAD_CERTIFICATE.testGetValue() || 0;
    132      server_failures = await GleanTest.webrtcdtls.serverHandshakeResult.SSL_ERROR_BAD_CERT_ALERT.testGetValue() || 0;
    133      is(client_failures, 1);
    134      is(server_failures, 1);
    135    },
    136 
    137    async function checkDtlsVersion1_3() {
    138      // 1.3 should be the default
    139          const pc1 = new RTCPeerConnection();
    140          const pc2 = new RTCPeerConnection();
    141          await gleanResetTestValues();
    142          let count1_0 = await GleanTest.webrtcdtls.protocolVersion["1.0"].testGetValue() || 0;
    143          let count1_2 = await GleanTest.webrtcdtls.protocolVersion["1.2"].testGetValue() || 0;
    144          let count1_3 = await GleanTest.webrtcdtls.protocolVersion["1.3"].testGetValue() || 0;
    145          is(count1_0, 0);
    146          is(count1_2, 0);
    147          is(count1_3, 0);
    148 
    149          const stream = await navigator.mediaDevices.getUserMedia({ video: true });
    150          pc1.addTrack(stream.getTracks()[0]);
    151 
    152          await connect(pc1, pc2, 32000, "DTLS connected", true, true);
    153          // This telemetry happens on STS/socket process
    154          await gleanFlushChildren();
    155 
    156          count1_0 = await GleanTest.webrtcdtls.protocolVersion["1.0"].testGetValue() || 0;
    157          count1_2 = await GleanTest.webrtcdtls.protocolVersion["1.2"].testGetValue() || 0;
    158          count1_3 = await GleanTest.webrtcdtls.protocolVersion["1.3"].testGetValue() || 0;
    159          is(count1_0, 0);
    160          is(count1_2, 0);
    161          is(count1_3, 2);
    162    },
    163 
    164    async function checkDtlsVersion1_2() {
    165      // Make 1.2 the default
    166      await withPrefs([["media.peerconnection.dtls.version.max", 771]],
    167        async () => {
    168          const pc1 = new RTCPeerConnection();
    169          const pc2 = new RTCPeerConnection();
    170          await gleanResetTestValues();
    171          let count1_0 = await GleanTest.webrtcdtls.protocolVersion["1.0"].testGetValue() || 0;
    172          let count1_2 = await GleanTest.webrtcdtls.protocolVersion["1.2"].testGetValue() || 0;
    173          let count1_3 = await GleanTest.webrtcdtls.protocolVersion["1.3"].testGetValue() || 0;
    174          is(count1_0, 0);
    175          is(count1_2, 0);
    176          is(count1_3, 0);
    177 
    178          const stream = await navigator.mediaDevices.getUserMedia({ video: true });
    179          pc1.addTrack(stream.getTracks()[0]);
    180 
    181          await connect(pc1, pc2, 32000, "DTLS connected", true, true);
    182          // This telemetry happens on STS/socket process
    183          await gleanFlushChildren();
    184 
    185          count1_0 = await GleanTest.webrtcdtls.protocolVersion["1.0"].testGetValue() || 0;
    186          count1_2 = await GleanTest.webrtcdtls.protocolVersion["1.2"].testGetValue() || 0;
    187          count1_3 = await GleanTest.webrtcdtls.protocolVersion["1.3"].testGetValue() || 0;
    188          is(count1_0, 0);
    189          is(count1_2, 2);
    190          is(count1_3, 0);
    191      });
    192    },
    193 
    194    async function checkDtlsVersion1_0() {
    195      // Make 1.0 the default
    196      await withPrefs([["media.peerconnection.dtls.version.max", 770],
    197                       ["media.peerconnection.dtls.version.min", 770]],
    198        async () => {
    199          const pc1 = new RTCPeerConnection();
    200          const pc2 = new RTCPeerConnection();
    201          await gleanResetTestValues();
    202          let count1_0 = await GleanTest.webrtcdtls.protocolVersion["1.0"].testGetValue() || 0;
    203          let count1_2 = await GleanTest.webrtcdtls.protocolVersion["1.2"].testGetValue() || 0;
    204          let count1_3 = await GleanTest.webrtcdtls.protocolVersion["1.3"].testGetValue() || 0;
    205          is(count1_0, 0);
    206          is(count1_2, 0);
    207          is(count1_3, 0);
    208 
    209          const stream = await navigator.mediaDevices.getUserMedia({ video: true });
    210          pc1.addTrack(stream.getTracks()[0]);
    211 
    212          await connect(pc1, pc2, 32000, "DTLS connected", true, true);
    213          // This telemetry happens on STS/socket process
    214          await gleanFlushChildren();
    215 
    216          count1_0 = await GleanTest.webrtcdtls.protocolVersion["1.0"].testGetValue() || 0;
    217          count1_2 = await GleanTest.webrtcdtls.protocolVersion["1.2"].testGetValue() || 0;
    218          count1_3 = await GleanTest.webrtcdtls.protocolVersion["1.3"].testGetValue() || 0;
    219          is(count1_0, 2);
    220          is(count1_2, 0);
    221          is(count1_3, 0);
    222      });
    223    },
    224 
    225    async function checkDtlsKEA() {
    226      // "media.webrtc.enable_pq_hybrid_kex" and "send_mlkem_keyshare" are enabled by default
    227      // By default we send 2 key shares, PQ and X25519
    228      // PQ Key share (ECDH Hybrid) has a higher preference, so it will be chosen as KEA
    229          const pc1 = new RTCPeerConnection();
    230          const pc2 = new RTCPeerConnection();
    231          await gleanResetTestValues();
    232          // SSL Handshake Key Exchange Algorithm (null=0, rsa=1, dh=2, ecdh=4, ecdh_hybrid=8)
    233          let keyExchangeValue = await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue() || 0;
    234          is(keyExchangeValue, 0, "Expected no keyExchange distribution defined");
    235 
    236          const stream = await navigator.mediaDevices.getUserMedia({ video: true });
    237          pc1.addTrack(stream.getTracks()[0]);
    238 
    239          await connect(pc1, pc2, 32000, "DTLS connected", true, true);
    240          // This telemetry happens on STS/socket process
    241          await gleanFlushChildren();
    242 
    243          let count1_0 = (await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue()).values["0"] || 0;
    244          let count1_1 = (await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue()).values["1"] || 0;
    245          let count1_2 = (await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue()).values["2"] || 0;
    246          let count1_4 = (await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue()).values["4"] || 0;
    247          let count1_8 = (await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue()).values["8"] || 0;
    248          is(count1_0, 0, "Expected 0 connections using NULL");
    249          is(count1_1, 0, "Expected 0 connections using RSA");
    250          is(count1_2, 0, "Expected 0 connections using DH");
    251          is(count1_4, 0, "Expected 0 connections using ECDH");
    252          is(count1_8, 2, "Expected 2 connections using ECDH Hybrid");
    253    },
    254 
    255    async function checkDtlsKEA_DTLSBelow13() {
    256        // DTLS1.2 does not use Kyber
    257        // In this case, X25519 (ECDH) key share will be used
    258        await withPrefs([["media.peerconnection.dtls.version.max", 771]],
    259        async () => {
    260          const pc1 = new RTCPeerConnection();
    261          const pc2 = new RTCPeerConnection();
    262          await gleanResetTestValues();
    263          // SSL Handshake Key Exchange Algorithm (null=0, rsa=1, dh=2, ecdh=4, ecdh_hybrid=8)
    264          let keyExchangeValue = await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue() || 0;
    265          is(keyExchangeValue, 0, "Expected no keyExchange distribution defined");
    266 
    267          const stream = await navigator.mediaDevices.getUserMedia({ video: true });
    268          pc1.addTrack(stream.getTracks()[0]);
    269 
    270          await connect(pc1, pc2, 32000, "DTLS connected", true, true);
    271          // This telemetry happens on STS/socket process
    272          await gleanFlushChildren();
    273 
    274          let count1_0 = (await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue()).values["0"] || 0;
    275          let count1_1 = (await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue()).values["1"] || 0;
    276          let count1_2 = (await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue()).values["2"] || 0;
    277          let count1_4 = (await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue()).values["4"] || 0;
    278          let count1_8 = (await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue()).values["8"] || 0;
    279          is(count1_0, 0, "Expected 0 connections using NULL");
    280          is(count1_1, 0, "Expected 0 connections using RSA");
    281          is(count1_2, 0, "Expected 0 connections using DH");
    282          is(count1_4, 2, "Expected 2 connections using ECDH");
    283          is(count1_8, 0, "Expected 0 connections using ECDH Hybrid");
    284    })},
    285 
    286    async function checkDtlsKEA_DTLS13DisablePQ() {
    287        await withPrefs([["media.webrtc.enable_pq_hybrid_kex", false]],
    288        async () => {
    289          const pc1 = new RTCPeerConnection();
    290          const pc2 = new RTCPeerConnection();
    291          await gleanResetTestValues();
    292          // SSL Handshake Key Exchange Algorithm (null=0, rsa=1, dh=2, ecdh=4, ecdh_hybrid=8)
    293          let keyExchangeValue = await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue() || 0;
    294          is(keyExchangeValue, 0, "Expected no keyExchange distribution defined");
    295 
    296          const stream = await navigator.mediaDevices.getUserMedia({ video: true });
    297          pc1.addTrack(stream.getTracks()[0]);
    298 
    299          await connect(pc1, pc2, 32000, "DTLS connected", true, true);
    300          // This telemetry happens on STS/socket process
    301          await gleanFlushChildren();
    302 
    303          let count1_0 = (await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue()).values["0"] || 0;
    304          let count1_1 = (await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue()).values["1"] || 0;
    305          let count1_2 = (await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue()).values["2"] || 0;
    306          let count1_4 = (await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue()).values["4"] || 0;
    307          let count1_8 = (await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue()).values["8"] || 0;
    308          is(count1_0, 0, "Expected 0 connections using NULL");
    309          is(count1_1, 0, "Expected 0 connections using RSA");
    310          is(count1_2, 0, "Expected 0 connections using DH");
    311          is(count1_4, 2, "Expected 2 connections using ECDH");
    312          is(count1_8, 0, "Expected 0 connections using ECDH Hybrid");
    313    })},
    314 
    315    async function checkDtlsKEA_DTLS13DisablePQEnablePQShare() {
    316        // Safety measures, when PQ is disabled, even if the sending ml-kem share is enabled
    317        // it should not be sent.
    318        await withPrefs([["media.webrtc.enable_pq_hybrid_kex", false],
    319            ["media.webrtc.send_mlkem_keyshare", true]
    320      ],
    321        async () => {
    322          const pc1 = new RTCPeerConnection();
    323          const pc2 = new RTCPeerConnection();
    324          await gleanResetTestValues();
    325          // SSL Handshake Key Exchange Algorithm (null=0, rsa=1, dh=2, ecdh=4, ecdh_hybrid=8)
    326          let keyExchangeValue = await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue() || 0;
    327          is(keyExchangeValue, 0, "Expected no keyExchange distribution defined");
    328 
    329          const stream = await navigator.mediaDevices.getUserMedia({ video: true });
    330          pc1.addTrack(stream.getTracks()[0]);
    331 
    332          await connect(pc1, pc2, 32000, "DTLS connected", true, true);
    333          // This telemetry happens on STS/socket process
    334          await gleanFlushChildren();
    335 
    336          let count1_0 = (await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue()).values["0"] || 0;
    337          let count1_1 = (await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue()).values["1"] || 0;
    338          let count1_2 = (await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue()).values["2"] || 0;
    339          let count1_4 = (await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue()).values["4"] || 0;
    340          let count1_8 = (await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue()).values["8"] || 0;
    341          is(count1_0, 0, "Expected 0 connections using NULL");
    342          is(count1_1, 0, "Expected 0 connections using RSA");
    343          is(count1_2, 0, "Expected 0 connections using DH");
    344          is(count1_4, 2, "Expected 2 connections using ECDH");
    345          is(count1_8, 0, "Expected 0 connections using ECDH Hybrid");
    346    })},
    347 
    348    async function checkDtlsKEA_DTLS13EnablePQDisablePQShare() {
    349        // We will still advertise PQ, but we won't send a key share.
    350        // See bug 1992457.
    351        await withPrefs([["media.webrtc.enable_pq_hybrid_kex", true],
    352            ["media.webrtc.send_mlkem_keyshare", false]
    353      ],
    354        async () => {
    355          const pc1 = new RTCPeerConnection();
    356          const pc2 = new RTCPeerConnection();
    357          await gleanResetTestValues();
    358          // SSL Handshake Key Exchange Algorithm (null=0, rsa=1, dh=2, ecdh=4, ecdh_hybrid=8)
    359          let keyExchangeValue = await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue() || 0;
    360          is(keyExchangeValue, 0, "Expected no keyExchange distribution defined");
    361 
    362          const stream = await navigator.mediaDevices.getUserMedia({ video: true });
    363          pc1.addTrack(stream.getTracks()[0]);
    364 
    365          await connect(pc1, pc2, 32000, "DTLS connected", true, true);
    366          // This telemetry happens on STS/socket process
    367          await gleanFlushChildren();
    368 
    369          let count1_0 = (await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue()).values["0"] || 0;
    370          let count1_1 = (await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue()).values["1"] || 0;
    371          let count1_2 = (await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue()).values["2"] || 0;
    372          let count1_4 = (await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue()).values["4"] || 0;
    373          let count1_8 = (await GleanTest.webrtcdtls.keyExchangeAlgorithm.testGetValue()).values["8"] || 0;
    374          is(count1_0, 0, "Expected 0 connections using NULL");
    375          is(count1_1, 0, "Expected 0 connections using RSA");
    376          is(count1_2, 0, "Expected 0 connections using DH");
    377          is(count1_4, 2, "Expected 2 connections using ECDH");
    378          is(count1_8, 0, "Expected 0 connections using ECDH Hybrid");
    379    })},
    380 
    381    async function checkRTCRtpSenderCount() {
    382      const pc = new RTCPeerConnection();
    383      const oldCount = await GleanTest.rtcrtpsender.count.testGetValue() ?? 0;
    384      const { sender } = pc.addTransceiver('video', {
    385        sendEncodings: [{ rid: "0" }, { rid: "1" }, { rid: "2" }]
    386      });
    387      const countDiff = await GleanTest.rtcrtpsender.count.testGetValue() - oldCount;
    388      is(countDiff, 1, "Glean should have recorded the creation of a single RTCRtpSender");
    389    },
    390 
    391    async function checkRTCRtpSenderSetParametersCompatCount() {
    392      await pushPrefs(
    393        ["media.peerconnection.allow_old_setParameters", true]);
    394      const pc = new RTCPeerConnection();
    395      const oldCount = await GleanTest.rtcrtpsender.countSetparametersCompat.testGetValue() ?? 0;
    396      const { sender } = pc.addTransceiver('video', {
    397        sendEncodings: [{ rid: "0" }, { rid: "1" }, { rid: "2" }]
    398      });
    399      const countDiff = await GleanTest.rtcrtpsender.countSetparametersCompat.testGetValue() - oldCount;
    400      is(countDiff, 1, "Glean should have recorded the creation of a single RTCRtpSender that uses the setParameters compat mode");
    401    },
    402 
    403    async function checkSendEncodings() {
    404      const pc = new RTCPeerConnection();
    405      const oldRate = await GleanTest.rtcrtpsender.usedSendencodings.testGetValue();
    406      const { sender } = pc.addTransceiver('video', {
    407        sendEncodings: [{ rid: "0" }, { rid: "1" }, { rid: "2" }]
    408      });
    409      const newRate = await GleanTest.rtcrtpsender.usedSendencodings.testGetValue();
    410      is(newRate.denominator, oldRate.denominator + 1, "Glean should have recorded the creation of a single RTCRtpSender");
    411      is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded the use of sendEncodings");
    412    },
    413 
    414    async function checkAddTransceiverNoSendEncodings() {
    415      const pc = new RTCPeerConnection();
    416      const oldRate = await GleanTest.rtcrtpsender.usedSendencodings.testGetValue();
    417      const { sender } = pc.addTransceiver('video');
    418      const newRate = await GleanTest.rtcrtpsender.usedSendencodings.testGetValue();
    419      is(newRate.denominator, oldRate.denominator + 1, "Glean should have recorded the creation of a single RTCRtpSender");
    420      is(newRate.numerator, oldRate.numerator, "Glean should not have recorded a use of sendEncodings");
    421    },
    422 
    423    async function checkAddTrack() {
    424      const pc = new RTCPeerConnection();
    425      const oldRate = await GleanTest.rtcrtpsender.usedSendencodings.testGetValue();
    426      const stream = await navigator.mediaDevices.getUserMedia({ video: true });
    427      const sender = pc.addTrack(stream.getTracks()[0]);
    428      const newRate = await GleanTest.rtcrtpsender.usedSendencodings.testGetValue();
    429      is(newRate.denominator, oldRate.denominator + 1, "Glean should have recorded the creation of a single RTCRtpSender");
    430      is(newRate.numerator, oldRate.numerator, "Glean should not have recorded a use of sendEncodings");
    431    },
    432 
    433    async function checkGoodSetParametersCompatMode() {
    434      await pushPrefs(
    435        ["media.peerconnection.allow_old_setParameters", true]);
    436      const pc = new RTCPeerConnection();
    437      const { sender } = pc.addTransceiver('video', {
    438        sendEncodings: [{ rid: "0" }, { rid: "1" }, { rid: "2" }]
    439      });
    440      const oldWarningRates = await getAllWarningRates();
    441      await sender.setParameters(sender.getParameters());
    442      const newWarningRates = await getAllWarningRates();
    443      isDeeply(oldWarningRates, newWarningRates);
    444    },
    445 
    446    async function checkBadSetParametersNoGetParametersWarning() {
    447      await pushPrefs(
    448        ["media.peerconnection.allow_old_setParameters", true]);
    449      const pc = new RTCPeerConnection();
    450      const { sender } = pc.addTransceiver('video', {
    451        sendEncodings: [{ rid: "0" }, { rid: "1" }, { rid: "2" }]
    452      });
    453 
    454      let oldRate = await GleanTest.rtcrtpsenderSetparameters.warnNoGetparameters.testGetValue();
    455 
    456      await sender.setParameters({ encodings: [{ rid: "0" }, { rid: "1" }, { rid: "2" }] });
    457 
    458      let newRate = await GleanTest.rtcrtpsenderSetparameters.warnNoGetparameters.testGetValue();
    459 
    460      is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded a warning in setParameters due to lack of a getParameters call");
    461 
    462      oldRate = newRate;
    463 
    464      // Glean should only record the warning once per sender!
    465      await sender.setParameters({ encodings: [{ rid: "0" }, { rid: "1" }, { rid: "2" }] });
    466 
    467      newRate = await GleanTest.rtcrtpsenderSetparameters.warnNoGetparameters.testGetValue();
    468 
    469      is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another warning in setParameters due to lack of a getParameters call");
    470    },
    471 
    472    async function checkBadSetParametersLengthChangedWarning() {
    473      await pushPrefs(
    474        ["media.peerconnection.allow_old_setParameters", true]);
    475      const pc = new RTCPeerConnection();
    476      const { sender } = pc.addTransceiver('video', {
    477        sendEncodings: [{ rid: "0" }, { rid: "1" }, { rid: "2" }]
    478      });
    479 
    480      let oldRate = await GleanTest.rtcrtpsenderSetparameters.warnLengthChanged.testGetValue();
    481 
    482      let params = sender.getParameters();
    483      params.encodings.pop();
    484      await sender.setParameters(params);
    485 
    486      let newRate = await GleanTest.rtcrtpsenderSetparameters.warnLengthChanged.testGetValue();
    487 
    488      is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded a warning due to a length change in encodings");
    489 
    490      oldRate = newRate;
    491 
    492      // Glean should only record the warning once per sender!
    493      params = sender.getParameters();
    494      params.encodings.pop();
    495      await sender.setParameters(params);
    496 
    497      newRate = await GleanTest.rtcrtpsenderSetparameters.warnLengthChanged.testGetValue();
    498 
    499      is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another warning due to a length change in encodings");
    500    },
    501 
    502    async function checkBadSetParametersRidChangedWarning() {
    503      // This pref does not let rid change errors slide anymore
    504      await pushPrefs(
    505        ["media.peerconnection.allow_old_setParameters", true]);
    506      const pc = new RTCPeerConnection();
    507      const { sender } = pc.addTransceiver('video', {
    508        sendEncodings: [{ rid: "0" }, { rid: "1" }, { rid: "2" }]
    509      });
    510 
    511      let oldRate = await GleanTest.rtcrtpsenderSetparameters.failRidChanged.testGetValue();
    512 
    513      let params = sender.getParameters();
    514      params.encodings[1].rid = "foo";
    515      try {
    516        await sender.setParameters(params);
    517      } catch (e) {
    518      }
    519      let newRate = await GleanTest.rtcrtpsenderSetparameters.failRidChanged.testGetValue();
    520      is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded an error due to a rid change in encodings");
    521 
    522      // Glean should only record the error once per sender!
    523      params = sender.getParameters();
    524      params.encodings[1].rid = "bar";
    525      oldRate = newRate;
    526      try {
    527        await sender.setParameters(params);
    528      } catch (e) {
    529      }
    530      newRate = await GleanTest.rtcrtpsenderSetparameters.failRidChanged.testGetValue();
    531      is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another error due to a rid change in encodings");
    532    },
    533 
    534    async function checkBadSetParametersNoTransactionIdWarning() {
    535      await pushPrefs(
    536        ["media.peerconnection.allow_old_setParameters", true]);
    537      const pc = new RTCPeerConnection();
    538      const { sender } = pc.addTransceiver('video', {
    539        sendEncodings: [{ rid: "0" }, { rid: "1" }, { rid: "2" }]
    540      });
    541 
    542      let oldRate = await GleanTest.rtcrtpsenderSetparameters.warnNoTransactionid.testGetValue();
    543 
    544      await sender.setParameters({ encodings: [{ rid: "0" }, { rid: "1" }, { rid: "2" }] });
    545 
    546      let newRate = await GleanTest.rtcrtpsenderSetparameters.warnNoTransactionid.testGetValue();
    547 
    548      is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded a warning due to missing transactionId in setParameters");
    549 
    550      oldRate = newRate;
    551 
    552      // Glean should only record the warning once per sender!
    553      await sender.setParameters({ encodings: [{ rid: "0" }, { rid: "1" }, { rid: "2" }] });
    554 
    555      newRate = await GleanTest.rtcrtpsenderSetparameters.warnNoTransactionid.testGetValue();
    556 
    557      is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another warning due to missing transactionId in setParameters");
    558    },
    559 
    560    async function checkBadSetParametersLengthChangedError() {
    561      await pushPrefs(
    562        ["media.peerconnection.allow_old_setParameters", false]);
    563      const pc = new RTCPeerConnection();
    564      const { sender } = pc.addTransceiver('video', {
    565        sendEncodings: [{ rid: "0" }, { rid: "1" }, { rid: "2" }]
    566      });
    567      let oldRate = await GleanTest.rtcrtpsenderSetparameters.failLengthChanged.testGetValue();
    568      let params = sender.getParameters();
    569      params.encodings.pop();
    570      try {
    571        await sender.setParameters(params);
    572      } catch (e) {
    573      }
    574      let newRate = await GleanTest.rtcrtpsenderSetparameters.failLengthChanged.testGetValue();
    575      is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded an error due to a length change in encodings");
    576 
    577      // Glean should only record the error once per sender!
    578      params = sender.getParameters();
    579      params.encodings.pop();
    580      oldRate = newRate;
    581      try {
    582        await sender.setParameters(params);
    583      } catch (e) {
    584      }
    585      newRate = await GleanTest.rtcrtpsenderSetparameters.failLengthChanged.testGetValue();
    586      is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another error due to a length change in encodings");
    587    },
    588 
    589    async function checkBadSetParametersRidChangedError() {
    590      await pushPrefs(
    591        ["media.peerconnection.allow_old_setParameters", false]);
    592      const pc = new RTCPeerConnection();
    593      const { sender } = pc.addTransceiver('video', {
    594        sendEncodings: [{ rid: "0" }, { rid: "1" }, { rid: "2" }]
    595      });
    596      let oldRate = await GleanTest.rtcrtpsenderSetparameters.failRidChanged.testGetValue();
    597      let params = sender.getParameters();
    598      params.encodings[1].rid = "foo";
    599      try {
    600        await sender.setParameters(params);
    601      } catch (e) {
    602      }
    603      let newRate = await GleanTest.rtcrtpsenderSetparameters.failRidChanged.testGetValue();
    604      is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded an error due to a rid change in encodings");
    605 
    606      // Glean should only record the error once per sender!
    607      params = sender.getParameters();
    608      params.encodings[1].rid = "bar";
    609      oldRate = newRate;
    610      try {
    611        await sender.setParameters(params);
    612      } catch (e) {
    613      }
    614      newRate = await GleanTest.rtcrtpsenderSetparameters.failRidChanged.testGetValue();
    615      is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another error due to a rid change in encodings");
    616    },
    617 
    618    async function checkBadSetParametersNoGetParametersError() {
    619      await pushPrefs(
    620        ["media.peerconnection.allow_old_setParameters", false]);
    621      const pc = new RTCPeerConnection();
    622      const { sender } = pc.addTransceiver('video', {
    623        sendEncodings: [{ rid: "0" }, { rid: "1" }, { rid: "2" }]
    624      });
    625      let oldRate = await GleanTest.rtcrtpsenderSetparameters.failNoGetparameters.testGetValue();
    626      try {
    627        await sender.setParameters({ encodings: [{ rid: "0" }, { rid: "1" }, { rid: "2" }] });
    628      } catch (e) {
    629      }
    630      let newRate = await GleanTest.rtcrtpsenderSetparameters.failNoGetparameters.testGetValue();
    631      is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded an error in setParameters due to lack of a getParameters call");
    632 
    633      // Glean should only record the error once per sender!
    634      oldRate = newRate;
    635      try {
    636        await sender.setParameters({ encodings: [{ rid: "0" }, { rid: "1" }, { rid: "2" }] });
    637      } catch (e) {
    638      }
    639      newRate = await GleanTest.rtcrtpsenderSetparameters.failNoGetparameters.testGetValue();
    640      is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another error in setParameters due to lack of a getParameters call");
    641    },
    642 
    643    async function checkBadSetParametersStaleTransactionIdError() {
    644      await pushPrefs(
    645        ["media.peerconnection.allow_old_setParameters", false]);
    646      const pc = new RTCPeerConnection();
    647      const { sender } = pc.addTransceiver('video', {
    648        sendEncodings: [{ rid: "0" }, { rid: "1" }, { rid: "2" }]
    649      });
    650      let oldRate = await GleanTest.rtcrtpsenderSetparameters.failStaleTransactionid.testGetValue();
    651      let params = sender.getParameters();
    652      // Cause transactionId to be stale
    653      await pc.createOffer();
    654      // ...but make sure there is a recent getParameters call
    655      sender.getParameters();
    656      try {
    657        await sender.setParameters(params);
    658      } catch (e) {
    659      }
    660      let newRate = await GleanTest.rtcrtpsenderSetparameters.failStaleTransactionid.testGetValue();
    661      is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded an error due to stale transactionId in setParameters");
    662 
    663      // Glean should only record the error once per sender!
    664      oldRate = newRate;
    665      params = sender.getParameters();
    666      // Cause transactionId to be stale
    667      await pc.createOffer();
    668      // ...but make sure there is a recent getParameters call
    669      sender.getParameters();
    670      try {
    671        await sender.setParameters(params);
    672      } catch (e) {
    673      }
    674      newRate = await GleanTest.rtcrtpsenderSetparameters.failStaleTransactionid.testGetValue();
    675      is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another error due to stale transactionId in setParameters");
    676    },
    677 
    678    async function checkBadSetParametersNoEncodingsError() {
    679      // If we do not allow the old setParameters, this will fail the length check
    680      // instead.
    681      await pushPrefs(
    682        ["media.peerconnection.allow_old_setParameters", true]);
    683      const pc = new RTCPeerConnection();
    684      const { sender } = pc.addTransceiver('video', {
    685        sendEncodings: [{ rid: "0" }, { rid: "1" }, { rid: "2" }]
    686      });
    687      let oldRate = await GleanTest.rtcrtpsenderSetparameters.failNoEncodings.testGetValue();
    688      let params = sender.getParameters();
    689      params.encodings = [];
    690      try {
    691        await sender.setParameters(params);
    692      } catch (e) {
    693      }
    694      let newRate = await GleanTest.rtcrtpsenderSetparameters.failNoEncodings.testGetValue();
    695      is(newRate.numerator, oldRate.numerator, "Glean should not have recorded an error due to empty encodings in setParameters");
    696 
    697      // Glean should only record the error once per sender!
    698      oldRate = newRate;
    699      params = sender.getParameters();
    700      params.encodings = [];
    701      try {
    702        await sender.setParameters(params);
    703      } catch (e) {
    704      }
    705      newRate = await GleanTest.rtcrtpsenderSetparameters.failNoEncodings.testGetValue();
    706      is(newRate.numerator, oldRate.numerator, "Glean should not have recorded an error due empty encodings in setParameters");
    707    },
    708 
    709    async function checkBadSetParametersOtherError() {
    710      const pc = new RTCPeerConnection();
    711      const { sender } = pc.addTransceiver('video', {
    712        sendEncodings: [{ rid: "0" }, { rid: "1" }, { rid: "2" }]
    713      });
    714      let oldRate = await GleanTest.rtcrtpsenderSetparameters.failOther.testGetValue();
    715      let params = sender.getParameters();
    716      params.encodings[0].scaleResolutionDownBy = 0.5;
    717      try {
    718        await sender.setParameters(params);
    719      } catch (e) {
    720      }
    721      let newRate = await GleanTest.rtcrtpsenderSetparameters.failOther.testGetValue();
    722      is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded an error due to some other failure");
    723 
    724      // Glean should only record the error once per sender!
    725      oldRate = newRate;
    726      params = sender.getParameters();
    727      params.encodings[0].scaleResolutionDownBy = 0.5;
    728      try {
    729        await sender.setParameters(params);
    730      } catch (e) {
    731      }
    732      newRate = await GleanTest.rtcrtpsenderSetparameters.failOther.testGetValue();
    733      is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another error due to some other failure");
    734    },
    735 
    736    async function checkUlpfecNegotiated() {
    737      const pc1 = new RTCPeerConnection();
    738      const pc2 = new RTCPeerConnection();
    739      await gleanResetTestValues();
    740 
    741      const stream = await navigator.mediaDevices.getUserMedia({ video: true });
    742      const sender = pc1.addTrack(stream.getTracks()[0]);
    743      let offer = await pc1.createOffer();
    744      await pc1.setLocalDescription(offer);
    745      await pc2.setRemoteDescription(offer);
    746      let answer = await pc2.createAnswer();
    747      await pc2.setLocalDescription(answer);
    748      await pc1.setRemoteDescription(answer);
    749 
    750      // Validate logging shows ulpfec negotiated and preferred video VP8
    751      let ulpfecNotNegotiated = await GleanTest.codecStats.ulpfecNegotiated.not_negotiated.testGetValue() || 0;
    752      ok(ulpfecNotNegotiated == 0, "checkUlpfecNegotiated glean should not count not_negotiated");
    753      let ulpfecNegotiated = await GleanTest.codecStats.ulpfecNegotiated.negotiated.testGetValue() || 0;
    754      ok(ulpfecNegotiated == 2, "checkUlpfecNegotiated glean should show ulpfec negotiated");
    755      let preferredVideoCodec = await GleanTest.codecStats.videoPreferredCodec.VP8.testGetValue() || 0;
    756      ok(preferredVideoCodec == 2, "checkUlpfecNegotiated glean should show preferred video codec VP8");
    757 
    758      // Validate negotiation event logging
    759      const sdps = await GleanTest.webrtcSignaling.sdpNegotiated.testGetValue() || [];
    760      is(sdps.length, 2, "Expected number of sdps"); // pc2 finished negotiation before pc1
    761      for (let i = 0; i < sdps.length; ++i) {
    762        const e = sdps[i].extra;
    763        is(e.pc_id, SpecialPowers.wrap(i == 0 ? pc2 : pc1).id.split(' ', 1)[0], `Expected RTCPeerConnection identifier for i=${i}`);
    764        is(e.negotiation_count, "1", `Expected number of negotiations for i=${i}`);
    765        is(e.is_remote_ice_lite, "false", `Expected remote ICE-Lite for i=${i}`);
    766        is(e.bundle_policy, "balanced", `Expected BundlePolicy for i=${i}`);
    767        is(e.ice_transport_policy, "all", `Expected IceTransportPolicy for i=${i}`);
    768        is(e.num_transports, "1", `Expected number of transports for i=${i}`);
    769        is(e.num_msections_audio_recvonly, "0", `Expected number of audio recvonly m-sections for i=${i}`);
    770        is(e.num_msections_audio_sendonly, "0", `Expected number of audio sendonly m-sections for i=${i}`);
    771        is(e.num_msections_audio_sendrecv, "0", `Expected number of audio sendrecv m-sections for i=${i}`);
    772        is(e.num_msections_video_recvonly, i == 0 ? "1" : "0", `Expected number of video recvonly m-sections for i=${i}`);
    773        is(e.num_msections_video_sendonly, i == 1 ? "1" : "0", `Expected number of video sendonly m-sections for i=${i}`);
    774        is(e.num_msections_video_sendrecv, "0", `Expected number of video sendrecv m-sections for i=${i}`);
    775        is(e.num_msections_data, "0", `Expected number of data m-sections for i=${i}`);
    776      }
    777 
    778      const audioMsections = await GleanTest.webrtcSignaling.audioMsectionNegotiated.testGetValue() || [];
    779      is(audioMsections.length, 0, "Expected number of audio m-sections"); // pc2 finished negotiation before pc1
    780 
    781      const videoMsections = await GleanTest.webrtcSignaling.videoMsectionNegotiated.testGetValue() || [];
    782      is(videoMsections.length, 2, "Expected number of video m-sections"); // pc2 finished negotiation before pc1
    783      for (let i = 0; i < videoMsections.length; ++i) {
    784        const e = videoMsections[i].extra;
    785        is(e.pc_id, SpecialPowers.wrap(i == 0 ? pc2 : pc1).id.split(' ', 1)[0], `Expected RTCPeerConnection identifier for i=${i}`);
    786        is(e.pc_negotiation_count, "1", `Expected number of negotiations for i=${i}`);
    787        is(e.has_rtcp_mux, "true", `Expected video rtcp-mux for i=${i}`);
    788        is(e.direction, i == 0 ? "recvonly" : "sendonly", `Expected video direction for i=${i}`);
    789        is(e.preferred_recv_codec, i == 0 ? "VP8" : undefined, `Expected preferred recv video codec for i=${i}`);
    790        is(e.preferred_send_codec, i == 0 ? undefined : "VP8", `Expected preferred send video codec for i=${i}`);
    791        ok(e.codecs.includes("VP8"), `VP8 codec present for i=${i}`);
    792        ok(e.codecs.includes("ulpfec"), `No ulpfec codec present for i=${i}`);
    793        is(e.num_send_simulcast_layers, i == 0 ? undefined : "1", `Expected number of simulcast layers for i=${i}`);
    794      }
    795 
    796      pc1.close();
    797      pc2.close();
    798    },
    799 
    800    async function checkNoUlpfecNegotiated() {
    801 
    802      const pc1 = new RTCPeerConnection();
    803      const pc2 = new RTCPeerConnection();
    804      await gleanResetTestValues();
    805      const stream = await navigator.mediaDevices.getUserMedia({ video: true });
    806      const sender = pc1.addTrack(stream.getTracks()[0]);
    807      await pc1.setLocalDescription();
    808      const offer = pc1.localDescription;
    809      const payloadType = sdputils.findCodecId(offer.sdp, "ulpfec");
    810      const sdp = sdputils.removeCodec(offer.sdp, payloadType);
    811      await pc2.setRemoteDescription({type: 'offer', sdp});
    812      let answer = await pc2.createAnswer();
    813      await pc2.setLocalDescription(answer);
    814      await pc1.setRemoteDescription(answer);
    815 
    816      // Validate logging shows ulpfec not negotiated and preferred video VP8
    817      const ulpfecNotNegotiated = await GleanTest.codecStats.ulpfecNegotiated.not_negotiated.testGetValue() || 0;
    818      is(ulpfecNotNegotiated, 2, "checkNoUlpfecNegotiated glean should count not_negotiated");
    819      const ulpfecNegotiated = await GleanTest.codecStats.ulpfecNegotiated.negotiated.testGetValue() || 0;
    820      is(ulpfecNegotiated, 0, "checkNoUlpfecNegotiated glean should not show ulpfec negotiated " + ulpfecNegotiated);
    821      const preferredVideoCodec = await GleanTest.codecStats.videoPreferredCodec.VP8.testGetValue() || 0;
    822      is(preferredVideoCodec, 2, "checkNoUlpfecNegotiated glean should show preferred video codec VP8");
    823 
    824      // Validate negotiation event logging
    825      const sdps = await GleanTest.webrtcSignaling.sdpNegotiated.testGetValue() || [];
    826      is(sdps.length, 2, "Expected number of sdps"); // pc2 finished negotiation before pc1
    827      for (let i = 0; i < sdps.length; ++i) {
    828        const e = sdps[i].extra;
    829        is(e.pc_id, SpecialPowers.wrap(i == 0 ? pc2 : pc1).id.split(' ', 1)[0], `Expected RTCPeerConnection identifier for i=${i}`);
    830        is(e.negotiation_count, "1", `Expected number of negotiations for i=${i}`);
    831        is(e.is_remote_ice_lite, "false", `Expected remote ICE-Lite for i=${i}`);
    832        is(e.bundle_policy, "balanced", `Expected BundlePolicy for i=${i}`);
    833        is(e.ice_transport_policy, "all", `Expected IceTransportPolicy for i=${i}`);
    834        is(e.num_transports, "1", `Expected number of transports for i=${i}`);
    835        is(e.num_msections_audio_recvonly, "0", `Expected number of audio recvonly m-sections for i=${i}`);
    836        is(e.num_msections_audio_sendonly, "0", `Expected number of audio sendonly m-sections for i=${i}`);
    837        is(e.num_msections_audio_sendrecv, "0", `Expected number of audio sendrecv m-sections for i=${i}`);
    838        is(e.num_msections_video_recvonly, i == 0 ? "1" : "0", `Expected number of video recvonly m-sections for i=${i}`);
    839        is(e.num_msections_video_sendonly, i == 1 ? "1" : "0", `Expected number of video sendonly m-sections for i=${i}`);
    840        is(e.num_msections_video_sendrecv, "0", `Expected number of video sendrecv m-sections for i=${i}`);
    841        is(e.num_msections_data, "0", `Expected number of data m-sections for i=${i}`);
    842      }
    843 
    844      const audioMsections = await GleanTest.webrtcSignaling.audioMsectionNegotiated.testGetValue() || [];
    845      is(audioMsections.length, 0, "Expected number of audio m-sections"); // pc2 finished negotiation before pc1
    846 
    847      const videoMsections = await GleanTest.webrtcSignaling.videoMsectionNegotiated.testGetValue() || [];
    848      is(videoMsections.length, 2, "Expected number of video m-sections"); // pc2 finished negotiation before pc1
    849      for (let i = 0; i < videoMsections.length; ++i) {
    850        const e = videoMsections[i].extra;
    851        is(e.pc_id, SpecialPowers.wrap(i == 0 ? pc2 : pc1).id.split(' ', 1)[0], `Expected RTCPeerConnection identifier for i=${i}`);
    852        is(e.pc_negotiation_count, "1", `Expected number of negotiations for i=${i}`);
    853        is(e.has_rtcp_mux, "true", `Expected video rtcp-mux for i=${i}`);
    854        is(e.direction, i == 0 ? "recvonly" : "sendonly", `Expected video direction for i=${i}`);
    855        is(e.preferred_recv_codec, i == 0 ? "VP8" : undefined, `Expected preferred recv video codec for i=${i}`);
    856        is(e.preferred_send_codec, i == 0 ? undefined : "VP8", `Expected preferred send video codec for i=${i}`);
    857        ok(e.codecs.includes("VP8"), `VP8 codec present for i=${i}`);
    858        ok(!e.codecs.includes("ulpfec"), `No ulpfec codec present for i=${i}`);
    859        is(e.num_send_simulcast_layers, i == 0 ? undefined : "1", `Expected number of simulcast layers for i=${i}`);
    860      }
    861 
    862      pc1.close();
    863      pc2.close();
    864    },
    865 
    866    async function checkFlexfecOffered() {
    867 
    868      const pc1 = new RTCPeerConnection();
    869      const pc2 = new RTCPeerConnection();
    870      await gleanResetTestValues();
    871      const stream = await navigator.mediaDevices.getUserMedia({ video: true });
    872      const sender = pc1.addTrack(stream.getTracks()[0]);
    873      await pc1.setLocalDescription();
    874      const offer = pc1.localDescription;
    875      const sdp = offer.sdp.replaceAll('VP8','flexfec');
    876      await pc2.setRemoteDescription({type: 'offer', sdp});
    877      let answer = await pc2.createAnswer();
    878      await pc2.setLocalDescription(answer);
    879      await pc1.setRemoteDescription(answer);
    880 
    881      // Validate logging shows flexfec counted once ulpfec negotiated twice
    882      // and preferred video VP9 since no VP8 was offered.
    883      const flexfecOffered = await GleanTest.codecStats.otherFecSignaled.flexfec.testGetValue() || 0;
    884      is(flexfecOffered, 1, "checkFlexfecOffered glean should count flexfec being offered" + flexfecOffered);
    885      const ulpfecNegotiated = await GleanTest.codecStats.ulpfecNegotiated.negotiated.testGetValue() || 0;
    886      is(ulpfecNegotiated, 2, "checkUlpfecNegotiated glean should show ulpfec negotiated");
    887      const preferredVideoCodec = await GleanTest.codecStats.videoPreferredCodec.VP9.testGetValue() || 0;
    888      is(preferredVideoCodec, 2, "checkFlexfecOffered glean should show preferred video codec VP9");
    889 
    890      // Validate negotiation event logging
    891      const sdps = await GleanTest.webrtcSignaling.sdpNegotiated.testGetValue() || [];
    892      is(sdps.length, 2, "Expected number of sdps"); // pc2 finished negotiation before pc1
    893      for (let i = 0; i < sdps.length; ++i) {
    894        const e = sdps[i].extra;
    895        is(e.pc_id, SpecialPowers.wrap(i == 0 ? pc2 : pc1).id.split(' ', 1)[0], `Expected RTCPeerConnection identifier for i=${i}`);
    896        is(e.negotiation_count, "1", `Expected number of negotiations for i=${i}`);
    897        is(e.is_remote_ice_lite, "false", `Expected remote ICE-Lite for i=${i}`);
    898        is(e.bundle_policy, "balanced", `Expected BundlePolicy for i=${i}`);
    899        is(e.ice_transport_policy, "all", `Expected IceTransportPolicy for i=${i}`);
    900        is(e.num_transports, "1", `Expected number of transports for i=${i}`);
    901        is(e.num_msections_audio_recvonly, "0", `Expected number of audio recvonly m-sections for i=${i}`);
    902        is(e.num_msections_audio_sendonly, "0", `Expected number of audio sendonly m-sections for i=${i}`);
    903        is(e.num_msections_audio_sendrecv, "0", `Expected number of audio sendrecv m-sections for i=${i}`);
    904        is(e.num_msections_video_recvonly, i == 0 ? "1" : "0", `Expected number of video recvonly m-sections for i=${i}`);
    905        is(e.num_msections_video_sendonly, i == 1 ? "1" : "0", `Expected number of video sendonly m-sections for i=${i}`);
    906        is(e.num_msections_video_sendrecv, "0", `Expected number of video sendrecv m-sections for i=${i}`);
    907        is(e.num_msections_data, "0", `Expected number of data m-sections for i=${i}`);
    908      }
    909 
    910      const audioMsections = await GleanTest.webrtcSignaling.audioMsectionNegotiated.testGetValue() || [];
    911      is(audioMsections.length, 0, "Expected number of audio m-sections"); // pc2 finished negotiation before pc1
    912 
    913      const videoMsections = await GleanTest.webrtcSignaling.videoMsectionNegotiated.testGetValue() || [];
    914      is(videoMsections.length, 2, "Expected number of video m-sections"); // pc2 finished negotiation before pc1
    915      for (let i = 0; i < videoMsections.length; ++i) {
    916        const e = videoMsections[i].extra;
    917        is(e.pc_id, SpecialPowers.wrap(i == 0 ? pc2 : pc1).id.split(' ', 1)[0], `Expected RTCPeerConnection identifier for i=${i}`);
    918        is(e.pc_negotiation_count, "1", `Expected number of negotiations for i=${i}`);
    919        is(e.has_rtcp_mux, "true", `Expected video rtcp-mux for i=${i}`);
    920        is(e.direction, i == 0 ? "recvonly" : "sendonly", `Expected video direction for i=${i}`);
    921        is(e.preferred_recv_codec, i == 0 ? "VP9" : undefined, `Expected preferred recv video codec for i=${i}`);
    922        is(e.preferred_send_codec, i == 0 ? undefined : "VP9", `Expected preferred send video codec for i=${i}`);
    923        ok(!e.codecs.includes("VP8"), `No VP8 codec present for i=${i}`);
    924        ok(!e.codecs.includes("flexfec"), `No flexfec codec present for i=${i}`);
    925        ok(e.codecs.includes("VP9"), `VP9 codec present for i=${i}`);
    926        ok(e.codecs.includes("ulpfec"), `ulpfec codec present for i=${i}`);
    927        is(e.num_send_simulcast_layers, i == 0 ? undefined : "1", `Expected number of simulcast layers for i=${i}`);
    928      }
    929 
    930      pc1.close();
    931      pc2.close();
    932    },
    933 
    934    async function checkPreferredVideoCodec() {
    935 
    936      const pc1 = new RTCPeerConnection();
    937      const pc2 = new RTCPeerConnection();
    938      await gleanResetTestValues();
    939      const stream = await navigator.mediaDevices.getUserMedia({ video: true });
    940      const sender = pc1.addTrack(stream.getTracks()[0]);
    941      await pc1.setLocalDescription();
    942      const offer = pc1.localDescription;
    943      // Set a video codec that does not exist to simulate receiving codecs we
    944      // dont support and verify it gets logged.
    945      const sdp = offer.sdp.replaceAll('VP8','AVADA1');
    946      await pc2.setRemoteDescription({type: 'offer', sdp});
    947      let answer = await pc2.createAnswer();
    948      await pc2.setLocalDescription(answer);
    949      await pc1.setRemoteDescription(answer);
    950 
    951      // We should show AVADA1 as the preferred codec from the offer and the
    952      // answer should prefer VP9 since VP8 was removed.
    953      const preferredVideoCodecAVADA1 = await GleanTest.codecStats.videoPreferredCodec.AVADA1.testGetValue() || 0;
    954      is(preferredVideoCodecAVADA1, 1, "checkPreferredVideoCodec glean should show preferred video codec AVADA1" + preferredVideoCodecAVADA1);
    955      const preferredVideoCodecVP9 = await GleanTest.codecStats.videoPreferredCodec.VP9.testGetValue() || 0;
    956      is(preferredVideoCodecVP9, 1, "checkPreferredVideoCodec glean should show preferred video codec VP9" + preferredVideoCodecVP9);
    957 
    958      // Validate negotiation event logging
    959      const sdps = await GleanTest.webrtcSignaling.sdpNegotiated.testGetValue() || [];
    960      is(sdps.length, 2, "Expected number of sdps"); // pc2 finished negotiation before pc1
    961      for (let i = 0; i < sdps.length; ++i) {
    962        const e = sdps[i].extra;
    963        is(e.pc_id, SpecialPowers.wrap(i == 0 ? pc2 : pc1).id.split(' ', 1)[0], `Expected RTCPeerConnection identifier for i=${i}`);
    964        is(e.negotiation_count, "1", `Expected number of negotiations for i=${i}`);
    965        is(e.is_remote_ice_lite, "false", `Expected remote ICE-Lite for i=${i}`);
    966        is(e.bundle_policy, "balanced", `Expected BundlePolicy for i=${i}`);
    967        is(e.ice_transport_policy, "all", `Expected IceTransportPolicy for i=${i}`);
    968        is(e.num_transports, "1", `Expected number of transports for i=${i}`);
    969        is(e.num_msections_audio_recvonly, "0", `Expected number of audio recvonly m-sections for i=${i}`);
    970        is(e.num_msections_audio_sendonly, "0", `Expected number of audio sendonly m-sections for i=${i}`);
    971        is(e.num_msections_audio_sendrecv, "0", `Expected number of audio sendrecv m-sections for i=${i}`);
    972        is(e.num_msections_video_recvonly, i == 0 ? "1" : "0", `Expected number of video recvonly m-sections for i=${i}`);
    973        is(e.num_msections_video_sendonly, i == 1 ? "1" : "0", `Expected number of video sendonly m-sections for i=${i}`);
    974        is(e.num_msections_video_sendrecv, "0", `Expected number of video sendrecv m-sections for i=${i}`);
    975        is(e.num_msections_data, "0", `Expected number of data m-sections for i=${i}`);
    976      }
    977 
    978      const audioMsections = await GleanTest.webrtcSignaling.audioMsectionNegotiated.testGetValue() || [];
    979      is(audioMsections.length, 0, "Expected number of audio m-sections"); // pc2 finished negotiation before pc1
    980 
    981      const videoMsections = await GleanTest.webrtcSignaling.videoMsectionNegotiated.testGetValue() || [];
    982      is(videoMsections.length, 2, "Expected number of video m-sections"); // pc2 finished negotiation before pc1
    983      for (let i = 0; i < videoMsections.length; ++i) {
    984        const e = videoMsections[i].extra;
    985        is(e.pc_id, SpecialPowers.wrap(i == 0 ? pc2 : pc1).id.split(' ', 1)[0], `Expected RTCPeerConnection identifier for i=${i}`);
    986        is(e.pc_negotiation_count, "1", `Expected number of negotiations for i=${i}`);
    987        is(e.has_rtcp_mux, "true", `Expected video rtcp-mux for i=${i}`);
    988        is(e.direction, i == 0 ? "recvonly" : "sendonly", `Expected video direction for i=${i}`);
    989        is(e.preferred_recv_codec, i == 0 ? "AVADA1" : undefined, `Expected preferred recv video codec for i=${i}`);
    990        is(e.preferred_send_codec, i == 0 ? undefined : "VP9", `Expected preferred send video codec for i=${i}`);
    991        ok(!e.codecs.includes("VP8"), `No VP8 codec present for i=${i}`);
    992        ok(!e.codecs.includes("AVADA1"), `No AVADA1 codec present for i=${i}`);
    993        ok(e.codecs.includes("VP9"), `VP9 codec present for i=${i}`);
    994        is(e.num_send_simulcast_layers, i == 0 ? undefined : "1", `Expected number of simulcast layers for i=${i}`);
    995      }
    996 
    997      pc1.close();
    998      pc2.close();
    999    },
   1000 
   1001    async function checkPreferredAudioCodec() {
   1002 
   1003      const pc1 = new RTCPeerConnection();
   1004      const pc2 = new RTCPeerConnection();
   1005      await gleanResetTestValues();
   1006      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
   1007      const sender = pc1.addTrack(stream.getTracks()[0]);
   1008      await pc1.setLocalDescription();
   1009      const offer = pc1.localDescription;
   1010      // Set an audio codec that does not exist to simulate receiving codecs we
   1011      // dont support and verify it gets logged.
   1012      const sdp = offer.sdp.replaceAll('opus','FAKECodec');
   1013      await pc2.setRemoteDescription({type: 'offer', sdp});
   1014      let answer = await pc2.createAnswer();
   1015      await pc2.setLocalDescription(answer);
   1016      await pc1.setRemoteDescription(answer);
   1017 
   1018      // We should show CN as the preferred codec from the offer and the answer
   1019      // should prefer G722 since opus was removed.
   1020      const preferredAudioCodecFAKECodec = await GleanTest.codecStats.audioPreferredCodec.FAKECodec.testGetValue() || 0;
   1021      is(preferredAudioCodecFAKECodec, 1, "checkPreferredAudioCodec Glean should show preferred audio codec FAKECodec " + preferredAudioCodecFAKECodec);
   1022      const preferredAudioCodecG722 = await GleanTest.codecStats.audioPreferredCodec.G722.testGetValue() || 0;
   1023      is(preferredAudioCodecG722, 1, "checkPreferredAudioCodec Glean should show preferred audio codec G722 " + preferredAudioCodecG722);
   1024 
   1025      // Validate negotiation event logging
   1026      const sdps = await GleanTest.webrtcSignaling.sdpNegotiated.testGetValue() || [];
   1027      is(sdps.length, 2, "Expected number of sdps"); // pc2 finished negotiation before pc1
   1028      for (let i = 0; i < sdps.length; ++i) {
   1029        const e = sdps[i].extra;
   1030        is(e.pc_id, SpecialPowers.wrap(i == 0 ? pc2 : pc1).id.split(' ', 1)[0], `Expected RTCPeerConnection identifier for i=${i}`);
   1031        is(e.negotiation_count, "1", `Expected number of negotiations for i=${i}`);
   1032        is(e.is_remote_ice_lite, "false", `Expected remote ICE-Lite for i=${i}`);
   1033        is(e.bundle_policy, "balanced", `Expected BundlePolicy for i=${i}`);
   1034        is(e.ice_transport_policy, "all", `Expected IceTransportPolicy for i=${i}`);
   1035        is(e.num_transports, "1", `Expected number of transports for i=${i}`);
   1036        is(e.num_msections_audio_recvonly, i == 0 ? "1" : "0", `Expected number of audio recvonly m-sections for i=${i}`);
   1037        is(e.num_msections_audio_sendonly, i == 0 ? "0" : "1", `Expected number of audio sendonly m-sections for i=${i}`);
   1038        is(e.num_msections_audio_sendrecv, "0", `Expected number of audio sendrecv m-sections for i=${i}`);
   1039        is(e.num_msections_video_recvonly, "0", `Expected number of video recvonly m-sections for i=${i}`);
   1040        is(e.num_msections_video_sendonly, "0", `Expected number of video sendonly m-sections for i=${i}`);
   1041        is(e.num_msections_video_sendrecv, "0", `Expected number of video sendrecv m-sections for i=${i}`);
   1042        is(e.num_msections_data, "0", `Expected number of data m-sections for i=${i}`);
   1043      }
   1044 
   1045      const audioMsections = await GleanTest.webrtcSignaling.audioMsectionNegotiated.testGetValue() || [];
   1046      is(audioMsections.length, 2, "Expected number of audio m-sections"); // pc2 finished negotiation before pc1
   1047      for (let i = 0; i < audioMsections.length; ++i) {
   1048        const e = audioMsections[i].extra;
   1049        is(e.pc_id, SpecialPowers.wrap(i == 0 ? pc2 : pc1).id.split(' ', 1)[0], `Expected RTCPeerConnection identifier for i=${i}`);
   1050        is(e.pc_negotiation_count, "1", `Expected number of negotiations for i=${i}`);
   1051        is(e.has_rtcp_mux, "true", `Expected audio rtcp-mux for i=${i}`);
   1052        is(e.direction, i == 0 ? "recvonly" : "sendonly", `Expected audio direction for i=${i}`);
   1053        is(e.preferred_recv_codec, i == 0 ? "FAKECodec" : undefined, `Expected preferred recv video codec for i=${i}`);
   1054        is(e.preferred_send_codec, i == 0 ? undefined : "G722", `Expected preferred send video codec for i=${i}`);
   1055        ok(!e.codecs.includes("opus"), `No opus present for i=${i}`);
   1056        ok(!e.codecs.includes("FAKECodec"), `No FAKECodec present for i=${i}`);
   1057        ok(e.codecs.includes("G722"), `VP9 codec present for i=${i}`);
   1058      }
   1059 
   1060      const videoMsections = await GleanTest.webrtcSignaling.videoMsectionNegotiated.testGetValue() || [];
   1061      is(videoMsections.length, 0, "Expected number of video m-sections"); // pc2 finished negotiation before pc1
   1062 
   1063      pc1.close();
   1064      pc2.close();
   1065    },
   1066 
   1067    async function checkLoggingMultipleTransceivers() {
   1068      const pc1 = new RTCPeerConnection();
   1069      const pc2 = new RTCPeerConnection();
   1070      await gleanResetTestValues();
   1071 
   1072      const stream = await navigator.mediaDevices.getUserMedia({ video: true });
   1073      const sender = pc1.addTrack(stream.getTracks()[0]);
   1074      pc1.addTransceiver(stream.getTracks()[0]);
   1075      pc1.addTransceiver(stream.getTracks()[0]);
   1076 
   1077      const offer = await pc1.createOffer();
   1078      await pc1.setLocalDescription(offer);
   1079      await pc2.setRemoteDescription(offer);
   1080      const answer = await pc2.createAnswer();
   1081      await pc2.setLocalDescription(answer);
   1082      await pc1.setRemoteDescription(answer);
   1083 
   1084      // Renegotiate
   1085      const newOffer = await pc1.createOffer();
   1086      await pc1.setLocalDescription(newOffer);
   1087      await pc2.setRemoteDescription(newOffer);
   1088      const newAnswer = await pc2.createAnswer();
   1089      await pc2.setLocalDescription(newAnswer);
   1090      await pc1.setRemoteDescription(newAnswer);
   1091 
   1092      // Validate logging shows for each transciever but is not duplicated with the renegotiation
   1093      const ulpfecNotNegotiated = await GleanTest.codecStats.ulpfecNegotiated.not_negotiated.testGetValue() || 0;
   1094      is(ulpfecNotNegotiated, 0, "checkLoggingMultipleTransceivers glean should not count not_negotiated");
   1095      const ulpfecNegotiated = await GleanTest.codecStats.ulpfecNegotiated.negotiated.testGetValue() || 0;
   1096      is(ulpfecNegotiated, 6, "checkLoggingMultipleTransceivers glean should show ulpfec negotiated " + ulpfecNegotiated);
   1097      const preferredVideoCodec = await GleanTest.codecStats.videoPreferredCodec.VP8.testGetValue() || 0;
   1098      is(preferredVideoCodec, 6, "checkLoggingMultipleTransceivers glean should show preferred video codec VP8 " + preferredVideoCodec);
   1099 
   1100      // Validate negotiation event logging
   1101      const sdps = await GleanTest.webrtcSignaling.sdpNegotiated.testGetValue() || [];
   1102      is(sdps.length, 4, "Expected number of sdps"); // pc2 finished negotiation before pc1
   1103      for (let i = 0; i < sdps.length; ++i) {
   1104        const e = sdps[i].extra;
   1105        is(e.pc_id, SpecialPowers.wrap(i % 2 == 0 ? pc2 : pc1).id.split(' ', 1)[0], `Expected RTCPeerConnection identifier for i=${i}`);
   1106        is(e.negotiation_count, i > 1 ? "2" : "1", `Expected number of negotiations for i=${i}`);
   1107        is(e.is_remote_ice_lite, "false", `Expected remote ICE-Lite for i=${i}`);
   1108        is(e.bundle_policy, "balanced", `Expected BundlePolicy for i=${i}`);
   1109        is(e.ice_transport_policy, "all", `Expected IceTransportPolicy for i=${i}`);
   1110        is(e.num_transports, "1", `Expected number of transports for i=${i}`);
   1111        is(e.num_msections_audio_recvonly, "0", `Expected number of audio recvonly m-sections for i=${i}`);
   1112        is(e.num_msections_audio_sendonly, "0", `Expected number of audio sendonly m-sections for i=${i}`);
   1113        is(e.num_msections_audio_sendrecv, "0", `Expected number of audio sendrecv m-sections for i=${i}`);
   1114        is(e.num_msections_video_recvonly, i % 2 == 0 ? "3" : "0", `Expected number of video recvonly m-sections for i=${i}`);
   1115        is(e.num_msections_video_sendonly, i % 2 == 1 ? "3" : "0", `Expected number of video sendonly m-sections for i=${i}`);
   1116        is(e.num_msections_video_sendrecv, "0", `Expected number of video sendrecv m-sections for i=${i}`);
   1117        is(e.num_msections_data, "0", `Expected number of data m-sections for i=${i}`);
   1118      }
   1119 
   1120      const audioMsections = await GleanTest.webrtcSignaling.audioMsectionNegotiated.testGetValue() || [];
   1121      is(audioMsections.length, 0, "Expected number of audio m-sections"); // pc2 finished negotiation before pc1
   1122 
   1123      const videoMsections = await GleanTest.webrtcSignaling.videoMsectionNegotiated.testGetValue() || [];
   1124      is(videoMsections.length, 12, "Expected number of video m-sections"); // pc2 finished negotiation before pc1
   1125      for (let i = 0; i < videoMsections.length; ++i) {
   1126        const e = videoMsections[i].extra;
   1127        is(e.pc_id, SpecialPowers.wrap(i % 6 < 3 ? pc2 : pc1).id.split(' ', 1)[0], `Expected RTCPeerConnection identifier for i=${i}`);
   1128        is(e.pc_negotiation_count, `${Math.floor(i / 6) + 1}`, `Expected number of negotiations for i=${i}`);
   1129        is(e.has_rtcp_mux, "true", `Expected video rtcp-mux for i=${i}`);
   1130        is(e.direction, i % 6 < 3 ? "recvonly" : "sendonly", `Expected video direction for i=${i}`);
   1131        is(e.preferred_recv_codec, i % 6 < 3 ? "VP8" : undefined, `Expected preferred recv video codec for i=${i}`);
   1132        is(e.preferred_send_codec, i % 6 < 3 ? undefined : "VP8", `Expected preferred send video codec for i=${i}`);
   1133        ok(e.codecs.includes("VP8"), `VP8 codec present for i=${i}`);
   1134        is(e.num_send_simulcast_layers, i % 6 < 3 ? undefined : "1", `Expected number of simulcast layers for i=${i}`);
   1135      }
   1136 
   1137      pc1.close();
   1138      pc2.close();
   1139    },
   1140 
   1141    async function checkSimulcastSendEncodings() {
   1142      const pc1 = new RTCPeerConnection();
   1143      const pc2 = new RTCPeerConnection();
   1144      await gleanResetTestValues();
   1145      const stream = await navigator.mediaDevices.getUserMedia({ video: true });
   1146      const { sender } = pc1.addTransceiver(stream.getTracks()[0], {
   1147        sendEncodings: [{ rid: "0" }, { rid: "1" }, { rid: "2" }]
   1148      });
   1149      const offer = await pc1.createOffer();
   1150      const mungedOffer = ridToMid(offer);
   1151      await pc1.setLocalDescription(offer);
   1152      await pc2.setRemoteDescription({type: 'offer', sdp: mungedOffer});
   1153      const answer = await pc2.createAnswer();
   1154      const mungedAnswer = midToRid(answer);
   1155      await pc2.setLocalDescription(answer);
   1156      await pc1.setRemoteDescription({type: 'answer', sdp: mungedAnswer});
   1157 
   1158      const preferredVideoCodec = await GleanTest.codecStats.videoPreferredCodec.VP8.testGetValue() || 0;
   1159      is(preferredVideoCodec, 4, "checkSimulcastSendEncodings glean should show preferred video codec VP8");
   1160 
   1161      // Validate negotiation event logging
   1162      const sdps = await GleanTest.webrtcSignaling.sdpNegotiated.testGetValue() || [];
   1163      is(sdps.length, 2, "Expected number of sdps"); // pc2 finished negotiation before pc1
   1164      for (let i = 0; i < sdps.length; ++i) {
   1165        const e = sdps[i].extra;
   1166        is(e.pc_id, SpecialPowers.wrap(i == 0 ? pc2 : pc1).id.split(' ', 1)[0], `Expected RTCPeerConnection identifier for i=${i}`);
   1167        is(e.negotiation_count, "1", `Expected number of negotiations for i=${i}`);
   1168        is(e.is_remote_ice_lite, "false", `Expected remote ICE-Lite for i=${i}`);
   1169        is(e.bundle_policy, "balanced", `Expected BundlePolicy for i=${i}`);
   1170        is(e.ice_transport_policy, "all", `Expected IceTransportPolicy for i=${i}`);
   1171        is(e.num_transports, "1", `Expected number of transports for i=${i}`);
   1172        is(e.num_msections_audio_recvonly, "0", `Expected number of audio recvonly m-sections for i=${i}`);
   1173        is(e.num_msections_audio_sendonly, "0", `Expected number of audio sendonly m-sections for i=${i}`);
   1174        is(e.num_msections_audio_sendrecv, "0", `Expected number of audio sendrecv m-sections for i=${i}`);
   1175        is(e.num_msections_video_recvonly, i == 0 ? "3" : "0", `Expected number of video recvonly m-sections for i=${i}`);
   1176        is(e.num_msections_video_sendonly, i == 1 ? "1" : "0", `Expected number of video sendonly m-sections for i=${i}`);
   1177        is(e.num_msections_video_sendrecv, "0", `Expected number of video sendrecv m-sections for i=${i}`);
   1178        is(e.num_msections_data, "0", `Expected number of data m-sections for i=${i}`);
   1179      }
   1180 
   1181      const audioMsections = await GleanTest.webrtcSignaling.audioMsectionNegotiated.testGetValue() || [];
   1182      is(audioMsections.length, 0, "Expected number of audio m-sections"); // pc2 finished negotiation before pc1
   1183 
   1184      const videoMsections = await GleanTest.webrtcSignaling.videoMsectionNegotiated.testGetValue() || [];
   1185      is(videoMsections.length, 4, "Expected number of video m-sections"); // pc2 finished negotiation before pc1
   1186      for (let i = 0; i < videoMsections.length; ++i) {
   1187        const e = videoMsections[i].extra;
   1188        is(e.pc_id, SpecialPowers.wrap(i < 3 ? pc2 : pc1).id.split(' ', 1)[0], `Expected RTCPeerConnection identifier for i=${i}`);
   1189        is(e.pc_negotiation_count, "1", `Expected number of negotiations for i=${i}`);
   1190        is(e.has_rtcp_mux, "true", `Expected video rtcp-mux for i=${i}`);
   1191        is(e.direction, i < 3 ? "recvonly" : "sendonly", `Expected video direction for i=${i}`);
   1192        is(e.preferred_recv_codec, i < 3 ? "VP8" : undefined, `Expected preferred recv video codec for i=${i}`);
   1193        is(e.preferred_send_codec, i < 3 ? undefined : "VP8", `Expected preferred send video codec for i=${i}`);
   1194        ok(e.codecs.includes("VP8"), `VP8 codec present for i=${i}`);
   1195        is(e.num_send_simulcast_layers, i < 3 ? undefined : "3", `Expected number of simulcast layers for i=${i}`);
   1196      }
   1197 
   1198      pc1.close();
   1199      pc2.close();
   1200    },
   1201 
   1202    async function checkSimulcastJsep() {
   1203      const pc1 = new RTCPeerConnection();
   1204      const pc2 = new RTCPeerConnection();
   1205      await gleanResetTestValues();
   1206      const stream = await navigator.mediaDevices.getUserMedia({ video: true });
   1207      pc2.addTransceiver('video', { direction: "recvonly" });
   1208      pc2.addTransceiver('video', { direction: "recvonly" });
   1209      const offer = await pc2.createOffer();
   1210      const mungedOffer = midToRid(offer);
   1211      await pc2.setLocalDescription(offer);
   1212      await pc1.setRemoteDescription({type: 'offer', sdp: mungedOffer});
   1213      pc1.addTrack(stream.getTracks()[0], stream);
   1214      const answer = await pc1.createAnswer();
   1215      const mungedAnswer = ridToMid(answer);
   1216      await pc2.setRemoteDescription({type: 'answer', sdp: mungedAnswer});
   1217      await pc1.setLocalDescription(answer);
   1218 
   1219      const preferredVideoCodec = await GleanTest.codecStats.videoPreferredCodec.VP8.testGetValue() || 0;
   1220      is(preferredVideoCodec, 3, "checkSimulcastJsep glean should show preferred video codec VP8");
   1221 
   1222      // Validate negotiation event logging
   1223      const sdps = await GleanTest.webrtcSignaling.sdpNegotiated.testGetValue() || [];
   1224      is(sdps.length, 2, "Expected number of sdps"); // pc2 finished negotiation before pc1
   1225      for (let i = 0; i < sdps.length; ++i) {
   1226        const e = sdps[i].extra;
   1227        is(e.pc_id, SpecialPowers.wrap(i == 0 ? pc2 : pc1).id.split(' ', 1)[0], `Expected RTCPeerConnection identifier for i=${i}`);
   1228        is(e.negotiation_count, "1", `Expected number of negotiations for i=${i}`);
   1229        is(e.is_remote_ice_lite, "false", `Expected remote ICE-Lite for i=${i}`);
   1230        is(e.bundle_policy, "balanced", `Expected BundlePolicy for i=${i}`);
   1231        is(e.ice_transport_policy, "all", `Expected IceTransportPolicy for i=${i}`);
   1232        is(e.num_transports, "1", `Expected number of transports for i=${i}`);
   1233        is(e.num_msections_audio_recvonly, "0", `Expected number of audio recvonly m-sections for i=${i}`);
   1234        is(e.num_msections_audio_sendonly, "0", `Expected number of audio sendonly m-sections for i=${i}`);
   1235        is(e.num_msections_audio_sendrecv, "0", `Expected number of audio sendrecv m-sections for i=${i}`);
   1236        is(e.num_msections_video_recvonly, i == 0 ? "2" : "0", `Expected number of video recvonly m-sections for i=${i}`);
   1237        is(e.num_msections_video_sendonly, i == 1 ? "1" : "0", `Expected number of video sendonly m-sections for i=${i}`);
   1238        is(e.num_msections_video_sendrecv, "0", `Expected number of video sendrecv m-sections for i=${i}`);
   1239        is(e.num_msections_data, "0", `Expected number of data m-sections for i=${i}`);
   1240      }
   1241 
   1242      const audioMsections = await GleanTest.webrtcSignaling.audioMsectionNegotiated.testGetValue() || [];
   1243      is(audioMsections.length, 0, "Expected number of audio m-sections"); // pc2 finished negotiation before pc1
   1244 
   1245      const videoMsections = await GleanTest.webrtcSignaling.videoMsectionNegotiated.testGetValue() || [];
   1246      is(videoMsections.length, 3, "Expected number of video m-sections"); // pc2 finished negotiation before pc1
   1247      for (let i = 0; i < videoMsections.length; ++i) {
   1248        const e = videoMsections[i].extra;
   1249        is(e.pc_id, SpecialPowers.wrap(i < 2 ? pc2 : pc1).id.split(' ', 1)[0], `Expected RTCPeerConnection identifier for i=${i}`);
   1250        is(e.pc_negotiation_count, "1", `Expected number of negotiations for i=${i}`);
   1251        is(e.has_rtcp_mux, "true", `Expected video rtcp-mux for i=${i}`);
   1252        is(e.direction, i < 2 ? "recvonly" : "sendonly", `Expected video direction for i=${i}`);
   1253        is(e.preferred_recv_codec, i < 2 ? "VP8" : undefined, `Expected preferred recv video codec for i=${i}`);
   1254        is(e.preferred_send_codec, i < 2 ? undefined : "VP8", `Expected preferred send video codec for i=${i}`);
   1255        ok(e.codecs.includes("VP8"), `VP8 codec present for i=${i}`);
   1256        is(e.num_send_simulcast_layers, i < 2 ? undefined : "2", `Expected number of simulcast layers for i=${i}`);
   1257      }
   1258 
   1259      pc1.close();
   1260      pc2.close();
   1261    },
   1262 
   1263    async function checkConfigurationNoBundle() {
   1264      const pc1 = new RTCPeerConnection({bundlePolicy: "max-compat"});
   1265      const pc2 = new RTCPeerConnection({iceTransportPolicy: "relay"});
   1266      await gleanResetTestValues();
   1267      const stream = await navigator.mediaDevices.getUserMedia({ video: true });
   1268      const sender1 = pc1.addTrack(stream.getTracks()[0]);
   1269      const sender2 = pc1.addTrack(sender1.track.clone());
   1270      await pc1.setLocalDescription();
   1271      const offer = pc1.localDescription;
   1272      const mungedOffer = offer.sdp.replace("BUNDLE", "BUNGLE");
   1273      await pc2.setRemoteDescription({ type: "offer", sdp: mungedOffer });
   1274      const answer = await pc2.createAnswer();
   1275      await pc2.setLocalDescription(answer);
   1276      await pc1.setRemoteDescription(answer);
   1277 
   1278      // Validate negotiation event logging
   1279      const sdps = await GleanTest.webrtcSignaling.sdpNegotiated.testGetValue() || [];
   1280      is(sdps.length, 2, "Expected number of sdps"); // pc2 finished negotiation before pc1
   1281      for (let i = 0; i < sdps.length; ++i) {
   1282        const e = sdps[i].extra;
   1283        is(e.pc_id, SpecialPowers.wrap(i == 0 ? pc2 : pc1).id.split(' ', 1)[0], `Expected RTCPeerConnection identifier for i=${i}`);
   1284        is(e.negotiation_count, "1", `Expected number of negotiations for i=${i}`);
   1285        is(e.is_remote_ice_lite, "false", `Expected remote ICE-Lite for i=${i}`);
   1286        is(e.bundle_policy, i == 0 ? "balanced" : "max-compat", `Expected BundlePolicy for i=${i}`);
   1287        is(e.ice_transport_policy, i == 0 ? "relay" : "all", `Expected IceTransportPolicy for i=${i}`);
   1288        is(e.num_transports, "2", `Expected number of transports for i=${i}`);
   1289        is(e.num_msections_audio_recvonly, "0", `Expected number of audio recvonly m-sections for i=${i}`);
   1290        is(e.num_msections_audio_sendonly, "0", `Expected number of audio sendonly m-sections for i=${i}`);
   1291        is(e.num_msections_audio_sendrecv, "0", `Expected number of audio sendrecv m-sections for i=${i}`);
   1292        is(e.num_msections_video_recvonly, i == 0 ? "2" : "0", `Expected number of video recvonly m-sections for i=${i}`);
   1293        is(e.num_msections_video_sendonly, i == 1 ? "2" : "0", `Expected number of video sendonly m-sections for i=${i}`);
   1294        is(e.num_msections_video_sendrecv, "0", `Expected number of video sendrecv m-sections for i=${i}`);
   1295        is(e.num_msections_data, "0", `Expected number of data m-sections for i=${i}`);
   1296      }
   1297 
   1298      pc1.close();
   1299      pc2.close();
   1300    },
   1301 
   1302    async function checkDataChannel() {
   1303      const pc1 = new RTCPeerConnection();
   1304      const pc2 = new RTCPeerConnection();
   1305      await gleanResetTestValues();
   1306      pc1.createDataChannel('test');
   1307      await pc1.setLocalDescription();
   1308      const offer = pc1.localDescription;
   1309      await pc2.setRemoteDescription(offer);
   1310      const answer = await pc2.createAnswer();
   1311      await pc2.setLocalDescription(answer);
   1312      await pc1.setRemoteDescription(answer);
   1313 
   1314      // Validate negotiation event logging
   1315      const sdps = await GleanTest.webrtcSignaling.sdpNegotiated.testGetValue() || [];
   1316      is(sdps.length, 2, "Expected number of sdps"); // pc2 finished negotiation before pc1
   1317      for (let i = 0; i < sdps.length; ++i) {
   1318        const e = sdps[i].extra;
   1319        is(e.pc_id, SpecialPowers.wrap(i == 0 ? pc2 : pc1).id.split(' ', 1)[0], `Expected RTCPeerConnection identifier for i=${i}`);
   1320        is(e.negotiation_count, "1", `Expected number of negotiations for i=${i}`);
   1321        is(e.is_remote_ice_lite, "false", `Expected remote ICE-Lite for i=${i}`);
   1322        is(e.bundle_policy, "balanced", `Expected BundlePolicy for i=${i}`);
   1323        is(e.ice_transport_policy, "all", `Expected IceTransportPolicy for i=${i}`);
   1324        is(e.num_transports, "1", `Expected number of transports for i=${i}`);
   1325        is(e.num_msections_audio_recvonly, "0", `Expected number of audio recvonly m-sections for i=${i}`);
   1326        is(e.num_msections_audio_sendonly, "0", `Expected number of audio sendonly m-sections for i=${i}`);
   1327        is(e.num_msections_audio_sendrecv, "0", `Expected number of audio sendrecv m-sections for i=${i}`);
   1328        is(e.num_msections_video_recvonly, "0", `Expected number of video recvonly m-sections for i=${i}`);
   1329        is(e.num_msections_video_sendonly, "0", `Expected number of video sendonly m-sections for i=${i}`);
   1330        is(e.num_msections_video_sendrecv, "0", `Expected number of video sendrecv m-sections for i=${i}`);
   1331        is(e.num_msections_data, "1", `Expected number of data m-sections for i=${i}`);
   1332      }
   1333 
   1334      pc1.close();
   1335      pc2.close();
   1336    },
   1337 
   1338  ];
   1339 
   1340  runNetworkTest(async () => {
   1341    for (const test of tests) {
   1342      info(`Running test: ${test.name}`);
   1343      await test();
   1344      info(`Done running test: ${test.name}`);
   1345    }
   1346  });
   1347 
   1348 </script>
   1349 </pre>
   1350 </body>
   1351 </html>