tor-browser

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

browser_net_statistics-content.js (8689B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 const {
      7  FILTER_TAGS,
      8 } = require("resource://devtools/client/netmonitor/src/constants.js");
      9 const {
     10  Filters,
     11 } = require("resource://devtools/client/netmonitor/src/utils/filter-predicates.js");
     12 const {
     13  getSizeWithDecimals,
     14 } = require("resource://devtools/client/netmonitor/src/utils/format-utils.js");
     15 const {
     16  responseIsFresh,
     17 } = require("resource://devtools/client/netmonitor/src/utils/request-utils.js");
     18 /**
     19 * Tests that the content rendered in the Statistics Panel is displayed correctly.
     20 */
     21 add_task(async function () {
     22  const { monitor } = await initNetMonitor(STATISTICS_URL, { requestCount: 1 });
     23  info("Starting test... ");
     24 
     25  const panel = monitor.panelWin;
     26  const { document, store, windowRequire, connector } = panel;
     27  const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
     28  const { getDisplayedRequests } = windowRequire(
     29    "devtools/client/netmonitor/src/selectors/index"
     30  );
     31 
     32  ok(
     33    document.querySelector(".monitor-panel"),
     34    "The current main panel is correct."
     35  );
     36 
     37  info("Displaying statistics panel");
     38  store.dispatch(Actions.openStatistics(connector, true));
     39 
     40  ok(
     41    document.querySelector(".statistics-panel"),
     42    "The current main panel is correct."
     43  );
     44 
     45  await waitForAllNetworkUpdateEvents();
     46 
     47  info("Waiting for chart to display");
     48  await waitUntil(() => {
     49    return (
     50      document.querySelectorAll(".pie-chart-container:not([placeholder=true])")
     51        .length == 2 &&
     52      document.querySelectorAll(
     53        ".table-chart-container:not([placeholder=true])"
     54      ).length == 2
     55    );
     56  });
     57 
     58  // Wait a bit for the charts and tables to fully render
     59  // This is needed as, unfortunately just waiting rendered elements won't work,
     60  // as they may be rendered but not yet with all the needed information.
     61  await wait(2000);
     62 
     63  const allRequests = getDisplayedRequests(store.getState());
     64  // Assert the primed cache data
     65  const expectedPrimedCacheData = generateExpectedData(allRequests, false);
     66  await assertChartContent(
     67    document,
     68    "Primed Cache",
     69    "primed-cache-chart",
     70    expectedPrimedCacheData
     71  );
     72 
     73  // Assert the empty cache data
     74  const expectedEmptyCacheData = generateExpectedData(allRequests, true);
     75  await assertChartContent(
     76    document,
     77    "Empty Cache",
     78    "empty-cache-chart",
     79    expectedEmptyCacheData
     80  );
     81 
     82  await teardown(monitor);
     83 });
     84 
     85 // The expected data is generated based of the list of displayed requests
     86 function generateExpectedData(requests, isForEmptyCache) {
     87  const expectedData = { slices: [] };
     88  const requestsGroupedAsTypes = {};
     89  // Group the request based on the types
     90  requests.forEach(request => {
     91    // If non of the filter types are matched, default to "others"
     92    let dataType = 8;
     93    for (const type of FILTER_TAGS) {
     94      if (Filters[type](request)) {
     95        dataType = type;
     96      }
     97      if (Filters.xhr(request)) {
     98        // Verify XHR last, to categorize other mime types in their own blobs.
     99        // "xhr"
    100        dataType = "xhr";
    101      }
    102    }
    103    if (!requestsGroupedAsTypes[dataType]) {
    104      requestsGroupedAsTypes[dataType] = [];
    105    }
    106    requestsGroupedAsTypes[dataType].push(request);
    107  });
    108 
    109  let totalRequests = 0,
    110    totalSize = 0,
    111    totalTransferredSize = 0,
    112    totalRequestsFromCache = 0;
    113  for (const type in requestsGroupedAsTypes) {
    114    const requestsForType = requestsGroupedAsTypes[type];
    115    if (!requestsForType.length) {
    116      return expectedData;
    117    }
    118 
    119    const size = accumulate(requestsForType, "contentSize");
    120    const transferred = accumulate(
    121      requestsForType,
    122      "transferredSize",
    123      request => isForEmptyCache || !responseIsFresh(request)
    124    );
    125 
    126    const data = {
    127      pieSize: "",
    128      pieName: type,
    129      count: String(requestsForType.length),
    130      size: `${getSizeWithDecimals(size / 1000)} kB`,
    131      type,
    132      transferred: `${getSizeWithDecimals(transferred / 1000)} kB`,
    133      // temp size data which would be used to calculate the pieSize
    134      // Should be deleted after.
    135      _size: size,
    136    };
    137 
    138    totalRequests += requestsForType.length;
    139    totalSize += size;
    140    totalTransferredSize += transferred;
    141    for (const request of requestsForType) {
    142      const isCached = request.fromCache || request.status === "304";
    143      totalRequestsFromCache = isCached
    144        ? totalRequestsFromCache + 1
    145        : totalRequestsFromCache;
    146    }
    147    expectedData.slices.push(data);
    148  }
    149 
    150  expectedData.totals = {
    151    cachedResponses: isForEmptyCache ? 0 : totalRequestsFromCache,
    152    requests: totalRequests,
    153    size: `${getSizeWithDecimals(totalSize / 1000)} kB`,
    154    transferred: `${getSizeWithDecimals(totalTransferredSize / 1000)} kB`,
    155  };
    156 
    157  // Calculate the expected % size of the pies in the chart for each type
    158  // This can only be done after we have calculated the totalSize
    159  for (const slice of expectedData.slices) {
    160    const pieSize = (slice._size * 100) / totalSize;
    161    // No need to apply precision to the pie size if its 0 or 100%
    162    slice.pieSize =
    163      pieSize == 0 || pieSize == 100 ? pieSize : pieSize.toPrecision(4);
    164    delete slice._size;
    165  }
    166  return expectedData;
    167 }
    168 
    169 async function assertChartContent(
    170  doc,
    171  name,
    172  chartClassName,
    173  expectedChartData
    174 ) {
    175  info(`Assert the displayed data for the ${name} chart`);
    176  const chartRootEl = doc.querySelector(`.${chartClassName}`);
    177  for (const slice of expectedChartData.slices) {
    178    info(`Assert the ${slice.type} pie slice`);
    179    const el = chartRootEl.querySelector(
    180      `svg.pie-chart-container a#${slice.type}-slice`
    181    );
    182    // Slices may not be displayed, if the % are too low
    183    if (el) {
    184      isSimilar(
    185        Math.ceil(el.getAttribute("aria-label").match(/[0-9\.]+/g)[0]),
    186        Math.ceil(slice.pieSize),
    187        1,
    188        `The size of the '${slice.type}' pie slice is correct`
    189      );
    190      const pieChartLabel = el.querySelector(".pie-chart-label");
    191      if (pieChartLabel) {
    192        is(
    193          pieChartLabel.textContent,
    194          slice.pieName,
    195          `The pie name for the '${slice.type}' pie slice is correct.`
    196        );
    197      }
    198    }
    199 
    200    info(`Assert the '${slice.type} table content`);
    201    const tableRow = chartRootEl.querySelector(
    202      `div.table-chart-container tr[data-statistic-name="${slice.type}"]`
    203    );
    204    is(
    205      tableRow.querySelector("td[name='count']").innerText,
    206      slice.count,
    207      `The no of ${slice.type} requests is correct`
    208    );
    209    is(
    210      tableRow.querySelector("td[name='label']").innerText,
    211      slice.type,
    212      `The type label of ${slice.type} requests is correct`
    213    );
    214    is(
    215      tableRow.querySelector("td[name='size']").innerText,
    216      `${slice.size}`,
    217      `The size of the ${slice.type} requests  correct`
    218    );
    219    is(
    220      tableRow.querySelector("td[name='transferredSize']").innerText,
    221      `${slice.transferred}`,
    222      `The transferred size of the ${slice.type} requests is correct`
    223    );
    224  }
    225 
    226  info("Assert the chart totals");
    227  const totalsRootEl = chartRootEl.querySelector(
    228    `.table-chart-container  div.table-chart-totals`
    229  );
    230  is(
    231    totalsRootEl.querySelector("span[name='cached']").innerText,
    232    `Cached responses: ${expectedChartData.totals.cachedResponses}`,
    233    `The total no of cached responses is correct`
    234  );
    235  is(
    236    totalsRootEl.querySelector("span[name='count']").innerText,
    237    `Total requests: ${expectedChartData.totals.requests}`,
    238    `The total no of requests is correct`
    239  );
    240  is(
    241    totalsRootEl.querySelector("span[name='size']").innerText,
    242    `Size: ${expectedChartData.totals.size}`,
    243    `The total size of requests is correct`
    244  );
    245  is(
    246    totalsRootEl.querySelector("span[name='transferredSize']").innerText,
    247    `Transferred Size: ${expectedChartData.totals.transferred}`,
    248    `The total transferred size is correct`
    249  );
    250 }
    251 
    252 /**
    253 * Sums together a specified field from a list of requests based
    254 * on the condition.
    255 *
    256 * @param {Array} requests
    257 * @param {string} field
    258 * @param {Function} condition
    259 * @returns {number}
    260 */
    261 function accumulate(requests, field, condition = () => true) {
    262  return requests.reduce((acc, request) => {
    263    if (condition(request)) {
    264      return acc + request[field];
    265    }
    266    return acc;
    267  }, 0);
    268 }
    269 /**
    270 * Assert that two values are equal or different by up to a specific margin
    271 *
    272 * @param {number} actual
    273 * @param {number} expected
    274 * @param {number} margin
    275 * @param {string} comment
    276 */
    277 function isSimilar(actual, expected, margin, comment) {
    278  Assert.greaterOrEqual(actual, expected - margin, comment);
    279  Assert.lessOrEqual(actual, expected + margin, comment);
    280 }