tor-browser

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

browser_net_initiator.js (7958B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 const {
      6  getUrlBaseName,
      7 } = require("resource://devtools/client/netmonitor/src/utils/request-utils.js");
      8 /**
      9 * Tests if request initiator is reported correctly.
     10 */
     11 
     12 const INITIATOR_FILE_NAME = "html_cause-test-page.html";
     13 const INITIATOR_URL = HTTPS_EXAMPLE_URL + INITIATOR_FILE_NAME;
     14 
     15 const EXPECTED_REQUESTS = [
     16  {
     17    method: "GET",
     18    url: INITIATOR_URL,
     19    causeType: "document",
     20    causeUri: null,
     21    stack: false,
     22  },
     23  {
     24    method: "GET",
     25    url: HTTPS_EXAMPLE_URL + "stylesheet_request",
     26    causeType: "stylesheet",
     27    causeUri: INITIATOR_URL,
     28    stack: false,
     29  },
     30  {
     31    method: "GET",
     32    url: HTTPS_EXAMPLE_URL + "img_request",
     33    causeType: "img",
     34    causeUri: INITIATOR_URL,
     35    stack: false,
     36  },
     37  {
     38    method: "GET",
     39    url: HTTPS_EXAMPLE_URL + "img_srcset_request",
     40    causeType: "imageset",
     41    causeUri: INITIATOR_URL,
     42    stack: false,
     43  },
     44  {
     45    method: "GET",
     46    url: HTTPS_EXAMPLE_URL + "xhr_request",
     47    causeType: "xhr",
     48    causeUri: INITIATOR_URL,
     49    stack: [{ fn: "performXhrRequestCallback", file: INITIATOR_URL, line: 32 }],
     50  },
     51  {
     52    method: "GET",
     53    url: HTTPS_EXAMPLE_URL + "fetch_request",
     54    causeType: "fetch",
     55    causeUri: INITIATOR_URL,
     56    stack: [{ fn: "performFetchRequest", file: INITIATOR_URL, line: 37 }],
     57  },
     58  {
     59    method: "GET",
     60    url: HTTPS_EXAMPLE_URL + "promise_fetch_request",
     61    causeType: "fetch",
     62    causeUri: INITIATOR_URL,
     63    stack: [
     64      {
     65        fn: "performPromiseFetchRequestCallback",
     66        file: INITIATOR_URL,
     67        line: 43,
     68      },
     69      {
     70        fn: "performPromiseFetchRequest",
     71        file: INITIATOR_URL,
     72        line: 42,
     73        asyncCause: "promise callback",
     74      },
     75    ],
     76  },
     77  {
     78    method: "GET",
     79    url: HTTPS_EXAMPLE_URL + "timeout_fetch_request",
     80    causeType: "fetch",
     81    causeUri: INITIATOR_URL,
     82    stack: [
     83      {
     84        fn: "performTimeoutFetchRequestCallback2",
     85        file: INITIATOR_URL,
     86        line: 50,
     87      },
     88      {
     89        fn: "performTimeoutFetchRequestCallback1",
     90        file: INITIATOR_URL,
     91        line: 49,
     92        asyncCause: "setTimeout handler",
     93      },
     94    ],
     95  },
     96  {
     97    method: "GET",
     98    url: HTTPS_EXAMPLE_URL + "favicon_request",
     99    causeType: "img",
    100    causeUri: INITIATOR_URL,
    101    // The favicon request is triggered in FaviconLoader.sys.mjs module, so it should
    102    // NOT be shown in the stack (See bug 1280266).
    103    stack: false,
    104  },
    105  {
    106    method: "GET",
    107    url: HTTPS_EXAMPLE_URL + "lazy_img_request",
    108    causeType: "lazy-img",
    109    causeUri: INITIATOR_URL,
    110    stack: false,
    111  },
    112  {
    113    method: "GET",
    114    url: HTTPS_EXAMPLE_URL + "lazy_img_srcset_request",
    115    causeType: "lazy-imageset",
    116    causeUri: INITIATOR_URL,
    117    stack: false,
    118  },
    119  {
    120    method: "POST",
    121    url: HTTPS_EXAMPLE_URL + "beacon_request",
    122    causeType: "beacon",
    123    causeUri: INITIATOR_URL,
    124    stack: [{ fn: "performBeaconRequest", file: INITIATOR_URL, line: 82 }],
    125  },
    126 ];
    127 
    128 add_task(async function () {
    129  // the initNetMonitor function clears the network request list after the
    130  // page is loaded. That's why we first load a bogus page from SIMPLE_URL,
    131  // and only then load the real thing from INITIATOR_URL - we want to catch
    132  // all the requests the page is making, not only the XHRs.
    133  // We can't use about:blank here, because initNetMonitor checks that the
    134  // page has actually made at least one request.
    135  const { tab, monitor } = await initNetMonitor(SIMPLE_URL, {
    136    requestCount: 1,
    137  });
    138 
    139  const { document, store, windowRequire } = monitor.panelWin;
    140  const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
    141  const { getSortedRequests } = windowRequire(
    142    "devtools/client/netmonitor/src/selectors/index"
    143  );
    144 
    145  store.dispatch(Actions.batchEnable(false));
    146 
    147  const wait = waitForNetworkEvents(monitor, EXPECTED_REQUESTS.length);
    148  BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, INITIATOR_URL);
    149 
    150  registerFaviconNotifier(tab.linkedBrowser);
    151 
    152  await wait;
    153 
    154  // For all expected requests
    155  for (const [index, { stack }] of EXPECTED_REQUESTS.entries()) {
    156    if (!stack) {
    157      continue;
    158    }
    159 
    160    EventUtils.sendMouseEvent(
    161      { type: "mousedown" },
    162      document.querySelectorAll(
    163        ".request-list-item .requests-list-initiator-lastframe"
    164      )[index]
    165    );
    166 
    167    // Clicking on the initiator column should open the Stack Trace panel
    168    const onStackTraceRendered = waitUntil(() =>
    169      document.querySelector("#stack-trace-panel .stack-trace .frame-link")
    170    );
    171    await onStackTraceRendered;
    172  }
    173 
    174  is(
    175    store.getState().requests.requests.length,
    176    EXPECTED_REQUESTS.length,
    177    "All the page events should be recorded."
    178  );
    179 
    180  await validateRequests(EXPECTED_REQUESTS, monitor);
    181 
    182  // Sort the requests by initiator and check the order
    183  EventUtils.sendMouseEvent(
    184    { type: "click" },
    185    document.querySelector("#requests-list-initiator-button")
    186  );
    187 
    188  const expectedOrder = EXPECTED_REQUESTS.sort(initiatorSortPredicate).map(
    189    r => {
    190      let isChromeFrames = false;
    191      const lastFrameExists = !!r.stack;
    192      let initiator = "";
    193      let lineNumber = "";
    194      if (lastFrameExists) {
    195        const { file, line: _lineNumber } = r.stack[0];
    196        initiator = getUrlBaseName(file);
    197        lineNumber = ":" + _lineNumber;
    198        isChromeFrames = file.startsWith("resource:///");
    199      }
    200      const causeStr = lastFrameExists ? " (" + r.causeType + ")" : r.causeType;
    201      return {
    202        initiatorStr: initiator + lineNumber + causeStr,
    203        isChromeFrames,
    204      };
    205    }
    206  );
    207 
    208  expectedOrder.forEach((expectedInitiator, i) => {
    209    const request = getSortedRequests(store.getState())[i];
    210    let initiator;
    211    // In cases of chrome frames, we shouldn't have stack.
    212    if (
    213      request.cause.stacktraceAvailable &&
    214      !expectedInitiator.isChromeFrames
    215    ) {
    216      const { filename, lineNumber } = request.cause.lastFrame;
    217      initiator =
    218        getUrlBaseName(filename) +
    219        ":" +
    220        lineNumber +
    221        " (" +
    222        request.cause.type +
    223        ")";
    224    } else {
    225      initiator = request.cause.type;
    226    }
    227 
    228    if (expectedInitiator.isChromeFrames) {
    229      todo_is(
    230        initiator,
    231        expectedInitiator.initiatorStr,
    232        `The request #${i} has the expected initiator after sorting`
    233      );
    234    } else {
    235      is(
    236        initiator,
    237        expectedInitiator.initiatorStr,
    238        `The request #${i} has the expected initiator after sorting`
    239      );
    240    }
    241  });
    242 
    243  await teardown(monitor);
    244 });
    245 
    246 // derived from devtools/client/netmonitor/src/utils/sort-predicates.js
    247 function initiatorSortPredicate(first, second) {
    248  const firstLastFrame = first.stack ? first.stack[0] : null;
    249  const secondLastFrame = second.stack ? second.stack[0] : null;
    250 
    251  let firstInitiator = "";
    252  let firstInitiatorLineNumber = 0;
    253 
    254  if (firstLastFrame) {
    255    firstInitiator = getUrlBaseName(firstLastFrame.file);
    256    firstInitiatorLineNumber = firstLastFrame.line;
    257  }
    258 
    259  let secondInitiator = "";
    260  let secondInitiatorLineNumber = 0;
    261 
    262  if (secondLastFrame) {
    263    secondInitiator = getUrlBaseName(secondLastFrame.file);
    264    secondInitiatorLineNumber = secondLastFrame.line;
    265  }
    266 
    267  let result;
    268  // if both initiators don't have a stack trace, compare their causes
    269  if (!firstInitiator && !secondInitiator) {
    270    result = compareValues(first.causeType, second.causeType);
    271  } else if (!firstInitiator || !secondInitiator) {
    272    // if one initiator doesn't have a stack trace but the other does, former should precede the latter
    273    result = compareValues(firstInitiatorLineNumber, secondInitiatorLineNumber);
    274  } else {
    275    result = compareValues(firstInitiator, secondInitiator);
    276    if (result === 0) {
    277      result = compareValues(
    278        firstInitiatorLineNumber,
    279        secondInitiatorLineNumber
    280      );
    281    }
    282  }
    283  return result;
    284 }