tor-browser

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

browser_WebrtcGlobalInformation.js (14984B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 const ProcessTools = Cc["@mozilla.org/processtools-service;1"].getService(
      8  Ci.nsIProcessToolsService
      9 );
     10 
     11 let getStatsReports = async (filter = "") => {
     12  let { reports } = await new Promise(r =>
     13    WebrtcGlobalInformation.getAllStats(r, filter)
     14  );
     15 
     16  ok(Array.isArray(reports), "|reports| is an array");
     17 
     18  let sanityCheckReport = report => {
     19    isnot(report.pcid, "", "pcid is non-empty");
     20    if (filter.length) {
     21      is(report.pcid, filter, "pcid matches filter");
     22    }
     23 
     24    // Check for duplicates
     25    const checkForDuplicateId = statsArray => {
     26      ok(Array.isArray(statsArray), "|statsArray| is an array");
     27      const ids = new Set();
     28      statsArray.forEach(stat => {
     29        is(typeof stat.id, "string", "|stat.id| is a string");
     30        ok(
     31          !ids.has(stat.id),
     32          `Id ${stat.id} should appear only once. Stat was ${JSON.stringify(
     33            stat
     34          )}`
     35        );
     36        ids.add(stat.id);
     37      });
     38    };
     39 
     40    checkForDuplicateId(report.inboundRtpStreamStats);
     41    checkForDuplicateId(report.outboundRtpStreamStats);
     42    checkForDuplicateId(report.remoteInboundRtpStreamStats);
     43    checkForDuplicateId(report.remoteOutboundRtpStreamStats);
     44    checkForDuplicateId(report.rtpContributingSourceStats);
     45    checkForDuplicateId(report.iceCandidatePairStats);
     46    checkForDuplicateId(report.iceCandidateStats);
     47    checkForDuplicateId(report.trickledIceCandidateStats);
     48    checkForDuplicateId(report.dataChannelStats);
     49    checkForDuplicateId(report.codecStats);
     50  };
     51 
     52  reports.forEach(sanityCheckReport);
     53  return reports;
     54 };
     55 
     56 const getStatsHistoryPcIds = async () => {
     57  return new Promise(r => WebrtcGlobalInformation.getStatsHistoryPcIds(r));
     58 };
     59 
     60 const getStatsHistorySince = async (pcid, after, sdpAfter) => {
     61  return new Promise(r =>
     62    WebrtcGlobalInformation.getStatsHistorySince(r, pcid, after, sdpAfter)
     63  );
     64 };
     65 
     66 let getLogging = async () => {
     67  let logs = await new Promise(r => WebrtcGlobalInformation.getLogging("", r));
     68  ok(Array.isArray(logs), "|logs| is an array");
     69  return logs;
     70 };
     71 
     72 let checkStatsReportCount = async (count, filter = "") => {
     73  let reports = await getStatsReports(filter);
     74  is(reports.length, count, `|reports| should have length ${count}`);
     75  if (reports.length != count) {
     76    info(`reports = ${JSON.stringify(reports)}`);
     77  }
     78  return reports;
     79 };
     80 
     81 let checkLoggingEmpty = async () => {
     82  let logs = await getLogging();
     83  is(logs.length, 0, "Logging is empty");
     84  if (logs.length) {
     85    info(`logs = ${JSON.stringify(logs)}`);
     86  }
     87  return logs;
     88 };
     89 
     90 let checkLoggingNonEmpty = async () => {
     91  let logs = await getLogging();
     92  isnot(logs.length, 0, "Logging is not empty");
     93  return logs;
     94 };
     95 
     96 let clearAndCheck = async () => {
     97  WebrtcGlobalInformation.clearAllStats();
     98  WebrtcGlobalInformation.clearLogging();
     99  await checkStatsReportCount(0);
    100  await checkLoggingEmpty();
    101 };
    102 
    103 let openTabInNewProcess = async file => {
    104  let rootDir = getRootDirectory(gTestPath);
    105  rootDir = rootDir.replace(
    106    "chrome://mochitests/content/",
    107    "https://example.com/"
    108  );
    109  let absoluteURI = rootDir + file;
    110 
    111  return BrowserTestUtils.openNewForegroundTab({
    112    gBrowser,
    113    opening: absoluteURI,
    114    forceNewProcess: true,
    115  });
    116 };
    117 
    118 let killTabProcess = async tab => {
    119  await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
    120    ChromeUtils.privateNoteIntentionalCrash();
    121  });
    122  ProcessTools.kill(tab.linkedBrowser.frameLoader.remoteTab.osPid);
    123 };
    124 
    125 add_task(async () => {
    126  info("Test that clearAllStats is callable");
    127  WebrtcGlobalInformation.clearAllStats();
    128  ok(true, "clearAllStats returns");
    129 });
    130 
    131 add_task(async () => {
    132  info("Test that clearLogging is callable");
    133  WebrtcGlobalInformation.clearLogging();
    134  ok(true, "clearLogging returns");
    135 });
    136 
    137 add_task(async () => {
    138  info(
    139    "Test that getAllStats is callable, and returns 0 results when no RTCPeerConnections have existed"
    140  );
    141  await checkStatsReportCount(0);
    142 });
    143 
    144 add_task(async () => {
    145  info(
    146    "Test that getLogging is callable, and returns 0 results when no RTCPeerConnections have existed"
    147  );
    148  await checkLoggingEmpty();
    149 });
    150 
    151 add_task(async () => {
    152  info("Test that we can get stats/logging for a PC on the parent process");
    153  await clearAndCheck();
    154  let pc = new RTCPeerConnection();
    155  await pc.setLocalDescription(
    156    await pc.createOffer({ offerToReceiveAudio: true })
    157  );
    158  // Let ICE stack go quiescent
    159  await new Promise(r => {
    160    pc.onicegatheringstatechange = () => {
    161      if (pc.iceGatheringState == "complete") {
    162        r();
    163      }
    164    };
    165  });
    166  await checkStatsReportCount(1);
    167  await checkLoggingNonEmpty();
    168  pc.close();
    169  pc = null;
    170  // Closing a PC should not do anything to the ICE logging
    171  await checkLoggingNonEmpty();
    172  // There's just no way to get a signal that the ICE stack has stopped logging
    173  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    174  await new Promise(r => setTimeout(r, 2000));
    175  await clearAndCheck();
    176 });
    177 
    178 add_task(async () => {
    179  info("Test that we can get stats/logging for a PC on a content process");
    180  await clearAndCheck();
    181  let tab = await openTabInNewProcess("single_peerconnection.html");
    182  await checkStatsReportCount(1);
    183  await checkLoggingNonEmpty();
    184  await killTabProcess(tab);
    185  BrowserTestUtils.removeTab(tab);
    186  await clearAndCheck();
    187 });
    188 
    189 add_task(async () => {
    190  info(
    191    "Test that we can get stats/logging for two connected PCs on a content process"
    192  );
    193  await clearAndCheck();
    194  let tab = await openTabInNewProcess("peerconnection_connect.html");
    195  await checkStatsReportCount(2);
    196  await checkLoggingNonEmpty();
    197  await killTabProcess(tab);
    198  BrowserTestUtils.removeTab(tab);
    199  await clearAndCheck();
    200 });
    201 
    202 add_task(async () => {
    203  info("Test filtering for stats reports (parent process)");
    204  await clearAndCheck();
    205  let pc1 = new RTCPeerConnection();
    206  let pc2 = new RTCPeerConnection();
    207  let allReports = await checkStatsReportCount(2);
    208  await checkStatsReportCount(1, allReports[0].pcid);
    209  pc1.close();
    210  pc2.close();
    211  pc1 = null;
    212  pc2 = null;
    213  await checkStatsReportCount(1, allReports[0].pcid);
    214  await clearAndCheck();
    215 });
    216 
    217 add_task(async () => {
    218  info("Test filtering for stats reports (content process)");
    219  await clearAndCheck();
    220  let tab1 = await openTabInNewProcess("single_peerconnection.html");
    221  let tab2 = await openTabInNewProcess("single_peerconnection.html");
    222  let allReports = await checkStatsReportCount(2);
    223  await checkStatsReportCount(1, allReports[0].pcid);
    224  await killTabProcess(tab1);
    225  BrowserTestUtils.removeTab(tab1);
    226  await killTabProcess(tab2);
    227  BrowserTestUtils.removeTab(tab2);
    228  await checkStatsReportCount(1, allReports[0].pcid);
    229  await clearAndCheck();
    230 });
    231 
    232 add_task(async () => {
    233  info("Test that stats/logging persists when PC is closed (parent process)");
    234  await clearAndCheck();
    235  let pc = new RTCPeerConnection();
    236  // This stuff will generate logging
    237  await pc.setLocalDescription(
    238    await pc.createOffer({ offerToReceiveAudio: true })
    239  );
    240  // Once gathering is done, the ICE stack should go quiescent
    241  await new Promise(r => {
    242    pc.onicegatheringstatechange = () => {
    243      if (pc.iceGatheringState == "complete") {
    244        r();
    245      }
    246    };
    247  });
    248  let reports = await checkStatsReportCount(1);
    249  isnot(
    250    window.browsingContext.browserId,
    251    undefined,
    252    "browserId is defined for parent process"
    253  );
    254  is(
    255    reports[0].browserId,
    256    window.browsingContext.browserId,
    257    "browserId for stats report matches parent process"
    258  );
    259  await checkLoggingNonEmpty();
    260  pc.close();
    261  pc = null;
    262  await checkStatsReportCount(1);
    263  await checkLoggingNonEmpty();
    264  await clearAndCheck();
    265 });
    266 
    267 add_task(async () => {
    268  info("Test that stats/logging persists when PC is closed (content process)");
    269  await clearAndCheck();
    270  let tab = await openTabInNewProcess("single_peerconnection.html");
    271  let { browserId } = tab.linkedBrowser;
    272  let reports = await checkStatsReportCount(1);
    273  is(reports[0].browserId, browserId, "browserId for stats report matches tab");
    274  isnot(
    275    browserId,
    276    window.browsingContext.browserId,
    277    "tab browser id is not the same as parent process browser id"
    278  );
    279  await checkLoggingNonEmpty();
    280  await killTabProcess(tab);
    281  BrowserTestUtils.removeTab(tab);
    282  await checkStatsReportCount(1);
    283  await checkLoggingNonEmpty();
    284  await clearAndCheck();
    285 });
    286 
    287 const set_int_pref_returning_unsetter = (pref, num) => {
    288  const value = Services.prefs.getIntPref(pref);
    289  Services.prefs.setIntPref(pref, num);
    290  return () => Services.prefs.setIntPref(pref, value);
    291 };
    292 
    293 const stats_history_is_enabled = () => {
    294  return Services.prefs.getBoolPref("media.aboutwebrtc.hist.enabled");
    295 };
    296 
    297 const set_max_histories_to_retain = num =>
    298  set_int_pref_returning_unsetter(
    299    "media.aboutwebrtc.hist.closed_stats_to_retain",
    300    num
    301  );
    302 
    303 const set_history_storage_window_s = num =>
    304  set_int_pref_returning_unsetter(
    305    "media.aboutwebrtc.hist.storage_window_s",
    306    num
    307  );
    308 
    309 add_task(async () => {
    310  if (!stats_history_is_enabled()) {
    311    return;
    312  }
    313  info(
    314    "Test that stats history is available after close until clearLongTermStats is called"
    315  );
    316  await clearAndCheck();
    317  const pc = new RTCPeerConnection();
    318 
    319  const ids = await getStatsHistoryPcIds();
    320  is(ids.length, 1, "There is a single PeerConnection Id for stats history.");
    321 
    322  let firstLen = 0;
    323  // I "don't love" this but we don't have a anything we can await on ... yet.
    324  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    325  await new Promise(r => setTimeout(r, 2000));
    326  {
    327    const history = await getStatsHistorySince(ids[0]);
    328    firstLen = history.reports.length;
    329    ok(
    330      history.reports.length,
    331      "There is at least a single PeerConnection stats history before close."
    332    );
    333  }
    334  // I "don't love" this but we don't have a anything we can await on ... yet.
    335  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    336  await new Promise(r => setTimeout(r, 2000));
    337  {
    338    const history = await getStatsHistorySince(ids[0]);
    339    const secondLen = history.reports.length;
    340    Assert.greater(
    341      secondLen,
    342      firstLen,
    343      "After waiting there are more history entries available."
    344    );
    345  }
    346  pc.close();
    347  // After close for final stats and pc teardown to settle
    348  // I "don't love" this but we don't have a anything we can await on ... yet.
    349  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    350  await new Promise(r => setTimeout(r, 2000));
    351  {
    352    const history = await getStatsHistorySince(ids[0]);
    353    ok(
    354      history.reports.length,
    355      "There is at least a single PeerConnection stats history after close."
    356    );
    357  }
    358  await clearAndCheck();
    359  {
    360    const history = await getStatsHistorySince(ids[0]);
    361    is(
    362      history.reports.length,
    363      0,
    364      "After PC.close and clearing the stats there are no history reports"
    365    );
    366  }
    367  {
    368    const ids1 = await getStatsHistoryPcIds();
    369    is(
    370      ids1.length,
    371      0,
    372      "After PC.close and clearing the stats there are no history pcids"
    373    );
    374  }
    375  {
    376    const pc2 = new RTCPeerConnection();
    377    const pc3 = new RTCPeerConnection();
    378    let idsN = await getStatsHistoryPcIds();
    379    is(
    380      idsN.length,
    381      2,
    382      "There are two pcIds after creating two PeerConnections"
    383    );
    384    pc2.close();
    385    // After close for final stats and pc teardown to settle
    386    // I "don't love" this but we don't have a anything we can await on ... yet.
    387    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    388    await new Promise(r => setTimeout(r, 2000));
    389    await WebrtcGlobalInformation.clearAllStats();
    390    idsN = await getStatsHistoryPcIds();
    391    is(
    392      idsN.length,
    393      1,
    394      "There is one pcIds after closing one of two PeerConnections and clearing stats"
    395    );
    396    pc3.close();
    397    // After close for final stats and pc teardown to settle
    398    // I "don't love" this but we don't have a anything we can await on ... yet.
    399    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    400    await new Promise(r => setTimeout(r, 2000));
    401  }
    402 });
    403 
    404 add_task(async () => {
    405  if (!stats_history_is_enabled()) {
    406    return;
    407  }
    408  const restoreHistRetainPref = set_max_histories_to_retain(7);
    409  info("Test that the proper number of pcIds are available");
    410  await clearAndCheck();
    411  const pc01 = new RTCPeerConnection();
    412  const pc02 = new RTCPeerConnection();
    413  const pc03 = new RTCPeerConnection();
    414  const pc04 = new RTCPeerConnection();
    415  const pc05 = new RTCPeerConnection();
    416  const pc06 = new RTCPeerConnection();
    417  const pc07 = new RTCPeerConnection();
    418  const pc08 = new RTCPeerConnection();
    419  const pc09 = new RTCPeerConnection();
    420  const pc10 = new RTCPeerConnection();
    421  const pc11 = new RTCPeerConnection();
    422  const pc12 = new RTCPeerConnection();
    423  const pc13 = new RTCPeerConnection();
    424  // I "don't love" this but we don't have a anything we can await on ... yet.
    425  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    426  await new Promise(r => setTimeout(r, 2000));
    427  {
    428    const ids = await getStatsHistoryPcIds();
    429    is(ids.length, 13, "There is are 13 PeerConnection Ids for stats history.");
    430  }
    431  pc01.close();
    432  pc02.close();
    433  pc03.close();
    434  pc04.close();
    435  pc05.close();
    436  pc06.close();
    437  pc07.close();
    438  pc08.close();
    439  pc09.close();
    440  pc10.close();
    441  pc11.close();
    442  pc12.close();
    443  pc13.close();
    444  // I "don't love" this but we don't have a anything we can await on ... yet.
    445  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    446  await new Promise(r => setTimeout(r, 5000));
    447  {
    448    const ids = await getStatsHistoryPcIds();
    449    is(
    450      ids.length,
    451      7,
    452      "After closing 13 PCs there are no more than the max closed (7) PeerConnection Ids for stats history."
    453    );
    454  }
    455  restoreHistRetainPref();
    456  await clearAndCheck();
    457 });
    458 
    459 add_task(async () => {
    460  if (!stats_history_is_enabled()) {
    461    return;
    462  }
    463  // If you change this, please check if the setTimeout should be updated.
    464  // NOTE: the unit here is _integer_ seconds.
    465  const STORAGE_WINDOW_S = 1;
    466  const restoreStorageWindowPref =
    467    set_history_storage_window_s(STORAGE_WINDOW_S);
    468  info("Test that history items are being aged out");
    469  await clearAndCheck();
    470  const pc = new RTCPeerConnection();
    471  // I "don't love" this but we don't have a anything we can await on ... yet.
    472  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    473  await new Promise(r => setTimeout(r, STORAGE_WINDOW_S * 2 * 1000));
    474  const ids = await getStatsHistoryPcIds();
    475  const { reports } = await getStatsHistorySince(ids[0]);
    476  const first = reports[0];
    477  const last = reports.at(-1);
    478  Assert.lessOrEqual(
    479    last.timestamp - first.timestamp,
    480    STORAGE_WINDOW_S * 1000,
    481    "History reports should be aging out according to the storage window pref"
    482  );
    483  pc.close();
    484  restoreStorageWindowPref();
    485  await clearAndCheck();
    486 });