tor-browser

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

browser_resources_network_events.js (14999B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 // Test the ResourceCommand API around NETWORK_EVENT
      7 
      8 const ResourceCommand = require("resource://devtools/shared/commands/resource/resource-command.js");
      9 
     10 // We are borrowing tests from the netmonitor frontend
     11 const NETMONITOR_TEST_FOLDER =
     12  "https://example.com/browser/devtools/client/netmonitor/test/";
     13 const CSP_URL = `${NETMONITOR_TEST_FOLDER}html_csp-test-page.html`;
     14 const JS_CSP_URL = `${NETMONITOR_TEST_FOLDER}js_websocket-worker-test.js`;
     15 const CSS_CSP_URL = `${NETMONITOR_TEST_FOLDER}internal-loaded.css`;
     16 
     17 const CSP_BLOCKED_REASON_CODE = 4000;
     18 
     19 add_task(async function testContentProcessRequests() {
     20  info(`Tests for NETWORK_EVENT resources fired from the content process`);
     21 
     22  const expectedNetworkEvents = [
     23    {
     24      url: CSP_URL,
     25      method: "GET",
     26      isNavigationRequest: true,
     27      chromeContext: false,
     28      requestCookiesAvailable: true,
     29      requestHeadersAvailable: true,
     30    },
     31    {
     32      url: JS_CSP_URL,
     33      method: "GET",
     34      blockedReason: CSP_BLOCKED_REASON_CODE,
     35      isNavigationRequest: false,
     36      chromeContext: false,
     37      requestCookiesAvailable: true,
     38      requestHeadersAvailable: true,
     39    },
     40    {
     41      url: CSS_CSP_URL,
     42      method: "GET",
     43      blockedReason: CSP_BLOCKED_REASON_CODE,
     44      isNavigationRequest: false,
     45      chromeContext: false,
     46      requestCookiesAvailable: true,
     47      requestHeadersAvailable: true,
     48    },
     49  ];
     50 
     51  const expectedUpdates = {
     52    [CSP_URL]: {
     53      responseStart: {
     54        status: "200",
     55        mimeType: "text/html",
     56        responseCookiesAvailable: true,
     57        responseHeadersAvailable: true,
     58        responseStartAvailable: true,
     59      },
     60      eventTimingsAvailable: {
     61        totalTime: 12,
     62        eventTimingsAvailable: true,
     63      },
     64      securityInfoAvailable: {
     65        securityState: "secure",
     66        isRacing: false,
     67        securityInfoAvailable: true,
     68      },
     69      responseContentAvailable: {
     70        contentSize: 200,
     71        transferredSize: 343,
     72        mimeType: "text/html",
     73        blockedReason: 0,
     74        responseContentAvailable: true,
     75      },
     76      responseEndAvailable: {
     77        responseEndAvailable: true,
     78      },
     79    },
     80    [JS_CSP_URL]: {
     81      responseStart: {
     82        status: "200",
     83        mimeType: "text/html",
     84        responseCookiesAvailable: true,
     85        responseHeadersAvailable: true,
     86        responseStartAvailable: true,
     87      },
     88      eventTimingsAvailable: {
     89        totalTime: 12,
     90        eventTimingsAvailable: true,
     91      },
     92      securityInfoAvailable: {
     93        securityState: "secure",
     94        isRacing: false,
     95        securityInfoAvailable: true,
     96      },
     97      responseContentAvailable: {
     98        contentSize: 200,
     99        transferredSize: 343,
    100        mimeType: "text/html",
    101        blockedReason: 0,
    102        responseContentAvailable: true,
    103        responseEndAvailable: {
    104          responseEndAvailable: true,
    105        },
    106      },
    107    },
    108    [CSS_CSP_URL]: {
    109      responseStart: {
    110        status: "200",
    111        mimeType: "text/html",
    112        responseCookiesAvailable: true,
    113        responseHeadersAvailable: true,
    114        responseStartAvailable: true,
    115      },
    116      eventTimingsAvailable: {
    117        totalTime: 12,
    118        eventTimingsAvailable: true,
    119      },
    120      securityInfoAvailable: {
    121        securityState: "secure",
    122        isRacing: false,
    123        securityInfoAvailable: true,
    124      },
    125      responseContentAvailable: {
    126        contentSize: 200,
    127        transferredSize: 343,
    128        mimeType: "text/html",
    129        blockedReason: 0,
    130        responseContentAvailable: true,
    131      },
    132      responseEndAvailable: {
    133        responseEndAvailable: true,
    134      },
    135    },
    136  };
    137 
    138  await assertNetworkResourcesOnPage(
    139    CSP_URL,
    140    expectedNetworkEvents,
    141    expectedUpdates
    142  );
    143 });
    144 
    145 add_task(async function testCanceledRequest() {
    146  info(`Tests for NETWORK_EVENT resources with a canceled request`);
    147 
    148  // Do a XHR request that we cancel against a slow loading page
    149  const requestUrl =
    150    "https://example.org/document-builder.sjs?delay=1000&html=foo";
    151  const html =
    152    "<!DOCTYPE html><script>(" +
    153    function (xhrUrl) {
    154      const xhr = new XMLHttpRequest();
    155      xhr.open("GET", xhrUrl);
    156      xhr.send(null);
    157    } +
    158    ")(" +
    159    JSON.stringify(requestUrl) +
    160    ")</script>";
    161  const pageUrl =
    162    "https://example.org/document-builder.sjs?html=" + encodeURIComponent(html);
    163 
    164  const expectedNetworkEvents = [
    165    {
    166      url: pageUrl,
    167      method: "GET",
    168      isNavigationRequest: true,
    169      chromeContext: false,
    170      requestCookiesAvailable: true,
    171      requestHeadersAvailable: true,
    172    },
    173    {
    174      url: requestUrl,
    175      method: "GET",
    176      isNavigationRequest: false,
    177      blockedReason: "NS_BINDING_ABORTED",
    178      chromeContext: false,
    179      requestCookiesAvailable: true,
    180      requestHeadersAvailable: true,
    181    },
    182  ];
    183 
    184  const expectedUpdates = {
    185    [pageUrl]: {
    186      responseStart: {
    187        status: "200",
    188        mimeType: "text/html",
    189        responseCookiesAvailable: true,
    190        responseHeadersAvailable: true,
    191        responseStartAvailable: true,
    192      },
    193      eventTimingsAvailable: {
    194        totalTime: 12,
    195        eventTimingsAvailable: true,
    196      },
    197      securityInfoAvailable: {
    198        securityState: "secure",
    199        isRacing: false,
    200        securityInfoAvailable: true,
    201      },
    202      responseContentAvailable: {
    203        contentSize: 200,
    204        transferredSize: 343,
    205        mimeType: "text/html",
    206        blockedReason: 0,
    207        responseContentAvailable: true,
    208      },
    209      responseEndAvailable: {
    210        responseEndAvailable: true,
    211      },
    212    },
    213    [requestUrl]: {
    214      responseStart: {
    215        status: "200",
    216        mimeType: "text/html",
    217        responseCookiesAvailable: true,
    218        responseHeadersAvailable: true,
    219        responseStartAvailable: true,
    220      },
    221      eventTimingsAvailable: {
    222        totalTime: 12,
    223        eventTimingsAvailable: true,
    224      },
    225      securityInfoAvailable: {
    226        securityState: "secure",
    227        isRacing: false,
    228        securityInfoAvailable: true,
    229      },
    230      responseContentAvailable: {
    231        contentSize: 200,
    232        transferredSize: 343,
    233        mimeType: "text/html",
    234        blockedReason: 0,
    235        responseContentAvailable: true,
    236      },
    237      responseEndAvailable: {
    238        responseEndAvailable: true,
    239      },
    240    },
    241  };
    242 
    243  // Register a one-off listener to cancel the XHR request
    244  // Using XMLHttpRequest's abort() method from the content process
    245  // isn't reliable and would introduce many race condition in the test.
    246  // Canceling the request via nsIRequest.cancel privileged method,
    247  // from the parent process is much more reliable.
    248  const observer = {
    249    QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
    250    observe(subject) {
    251      subject = subject.QueryInterface(Ci.nsIHttpChannel);
    252      if (subject.URI.spec == requestUrl) {
    253        subject.cancel(Cr.NS_BINDING_ABORTED);
    254        Services.obs.removeObserver(observer, "http-on-modify-request");
    255      }
    256    },
    257  };
    258  Services.obs.addObserver(observer, "http-on-modify-request");
    259 
    260  await assertNetworkResourcesOnPage(
    261    pageUrl,
    262    expectedNetworkEvents,
    263    expectedUpdates
    264  );
    265 });
    266 
    267 add_task(async function testIframeRequest() {
    268  info(`Tests for NETWORK_EVENT resources with an iframe`);
    269 
    270  // Do a XHR request that we cancel against a slow loading page
    271  const iframeRequestUrl =
    272    "https://example.org/document-builder.sjs?html=iframe-request";
    273  const iframeHtml = `iframe<script>fetch("${iframeRequestUrl}")</script>`;
    274  const iframeUrl =
    275    "https://example.org/document-builder.sjs?html=" +
    276    encodeURIComponent(iframeHtml);
    277  const html = `top-document<iframe src="${iframeUrl}"></iframe>`;
    278  const pageUrl =
    279    "https://example.org/document-builder.sjs?html=" + encodeURIComponent(html);
    280 
    281  const expectedNetworkEvents = [
    282    // The top level navigation request relates to the previous top level target.
    283    // Unfortunately, it is hard to test because it is racy.
    284    // The target front might be destroyed and `targetFront.url` will be null.
    285    // Or not just yet and be equal to "about:blank".
    286    {
    287      url: pageUrl,
    288      method: "GET",
    289      chromeContext: false,
    290      isNavigationRequest: true,
    291      requestCookiesAvailable: true,
    292      requestHeadersAvailable: true,
    293    },
    294    {
    295      url: iframeUrl,
    296      method: "GET",
    297      isNavigationRequest: false,
    298      targetFrontUrl: pageUrl,
    299      chromeContext: false,
    300      requestCookiesAvailable: true,
    301      requestHeadersAvailable: true,
    302    },
    303    {
    304      url: iframeRequestUrl,
    305      method: "GET",
    306      isNavigationRequest: false,
    307      targetFrontUrl: iframeUrl,
    308      chromeContext: false,
    309      requestCookiesAvailable: true,
    310      requestHeadersAvailable: true,
    311    },
    312  ];
    313 
    314  const expectedUpdates = {
    315    [pageUrl]: {
    316      responseStart: {
    317        status: "200",
    318        mimeType: "text/html",
    319        responseCookiesAvailable: true,
    320        responseHeadersAvailable: true,
    321        responseStartAvailable: true,
    322      },
    323      eventTimingsAvailable: {
    324        totalTime: 12,
    325        eventTimingsAvailable: true,
    326      },
    327      securityInfoAvailable: {
    328        securityState: "secure",
    329        isRacing: false,
    330        securityInfoAvailable: true,
    331      },
    332      responseContentAvailable: {
    333        contentSize: 200,
    334        transferredSize: 343,
    335        mimeType: "text/html",
    336        blockedReason: 0,
    337        responseContentAvailable: true,
    338      },
    339      responseEndAvailable: {
    340        responseEndAvailable: true,
    341      },
    342    },
    343    [iframeUrl]: {
    344      responseStart: {
    345        status: "200",
    346        mimeType: "text/html",
    347        responseCookiesAvailable: true,
    348        responseHeadersAvailable: true,
    349        responseStartAvailable: true,
    350      },
    351      eventTimingsAvailable: {
    352        totalTime: 12,
    353        eventTimingsAvailable: true,
    354      },
    355      securityInfoAvailable: {
    356        securityState: "secure",
    357        isRacing: false,
    358        securityInfoAvailable: true,
    359      },
    360      responseContentAvailable: {
    361        contentSize: 200,
    362        transferredSize: 343,
    363        mimeType: "text/html",
    364        blockedReason: 0,
    365        responseContentAvailable: true,
    366      },
    367      responseEndAvailable: {
    368        responseEndAvailable: true,
    369      },
    370    },
    371    [iframeRequestUrl]: {
    372      responseStart: {
    373        status: "200",
    374        mimeType: "text/html",
    375        responseCookiesAvailable: true,
    376        responseHeadersAvailable: true,
    377        responseStartAvailable: true,
    378      },
    379      eventTimingsAvailable: {
    380        totalTime: 12,
    381        eventTimingsAvailable: true,
    382      },
    383      securityInfoAvailable: {
    384        securityState: "secure",
    385        isRacing: false,
    386        securityInfoAvailable: true,
    387      },
    388      responseContentAvailable: {
    389        contentSize: 200,
    390        transferredSize: 343,
    391        mimeType: "text/html",
    392        blockedReason: 0,
    393        responseContentAvailable: true,
    394      },
    395      responseEndAvailable: {
    396        responseEndAvailable: true,
    397      },
    398    },
    399  };
    400 
    401  await assertNetworkResourcesOnPage(
    402    pageUrl,
    403    expectedNetworkEvents,
    404    expectedUpdates
    405  );
    406 });
    407 
    408 async function assertNetworkResourcesOnPage(
    409  url,
    410  expectedNetworkEvents,
    411  expectedUpdates
    412 ) {
    413  // First open a blank document to avoid spawning any request
    414  const tab = await addTab("about:blank");
    415 
    416  const commands = await CommandsFactory.forTab(tab);
    417  await commands.targetCommand.startListening();
    418  const { resourceCommand } = commands;
    419 
    420  const matchedRequests = {};
    421 
    422  const onAvailable = resources => {
    423    for (const resource of resources) {
    424      // Immediately assert the resource, as the same resource object
    425      // will be notified to onUpdated and so if we assert it later
    426      // we will not highlight attributes that aren't set yet from onAvailable.
    427      if (matchedRequests[resource.url] !== undefined) {
    428        return;
    429      }
    430      const idx = expectedNetworkEvents.findIndex(e => e.url === resource.url);
    431      Assert.notEqual(
    432        idx,
    433        -1,
    434        "Found a matching available notification for: " + resource.url
    435      );
    436      // Track already matched resources in case there is many requests with the same url
    437      if (idx >= 0) {
    438        matchedRequests[resource.url] = 0;
    439      }
    440 
    441      assertNetworkResources(resource, expectedNetworkEvents[idx]);
    442    }
    443  };
    444 
    445  const onUpdated = updates => {
    446    for (const {
    447      resource,
    448      update: { resourceUpdates },
    449    } of updates) {
    450      const idx = expectedNetworkEvents.findIndex(e => e.url === resource.url);
    451      Assert.notEqual(
    452        idx,
    453        -1,
    454        "Found a matching available notification for the update: " +
    455          resource.url
    456      );
    457 
    458      matchedRequests[resource.url] = matchedRequests[resource.url] + 1;
    459      assertNetworkUpdateResources(
    460        resourceUpdates,
    461        expectedUpdates[resource.url]
    462      );
    463    }
    464  };
    465 
    466  // Start observing for network events before loading the test page
    467  await resourceCommand.watchResources([resourceCommand.TYPES.NETWORK_EVENT], {
    468    onAvailable,
    469    onUpdated,
    470  });
    471 
    472  // Load the test page that fires network requests
    473  const onLoaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
    474  BrowserTestUtils.startLoadingURIString(gBrowser.selectedBrowser, url);
    475  await onLoaded;
    476 
    477  // Make sure we processed all the expected request updates
    478  await waitFor(
    479    () => Object.keys(matchedRequests).length == expectedNetworkEvents.length,
    480    "Wait for all expected available notifications"
    481  );
    482 
    483  resourceCommand.unwatchResources([resourceCommand.TYPES.NETWORK_EVENT], {
    484    onAvailable,
    485    onUpdated,
    486  });
    487 
    488  await commands.destroy();
    489  BrowserTestUtils.removeTab(tab);
    490 }
    491 
    492 function assertNetworkResources(actual, expected) {
    493  is(
    494    actual.resourceType,
    495    ResourceCommand.TYPES.NETWORK_EVENT,
    496    "The resource type is correct"
    497  );
    498  is(
    499    typeof actual.innerWindowId,
    500    "number",
    501    "All requests have an innerWindowId attribute"
    502  );
    503  ok(
    504    actual.targetFront.isTargetFront,
    505    "All requests have a targetFront attribute"
    506  );
    507 
    508  for (const name in expected) {
    509    if (name == "targetFrontUrl") {
    510      is(
    511        actual.targetFront.url,
    512        expected[name],
    513        "The request matches the right target front"
    514      );
    515    } else {
    516      is(actual[name], expected[name], `The '${name}' attribute is correct`);
    517    }
    518  }
    519 }
    520 
    521 // Assert that the correct resource information are available for the resource update type
    522 function assertNetworkUpdateResources(actual, expected) {
    523  const updateTypes = Object.keys(expected);
    524  const expectedUpdateType = updateTypes.find(
    525    type => actual[`${type}Available`]
    526  );
    527  const expectedUpdates = expected[expectedUpdateType];
    528  for (const name in expectedUpdates) {
    529    is(
    530      expectedUpdates[name],
    531      actual[name],
    532      `The resource update "${name}" contains the expected value "${actual[name]}"`
    533    );
    534  }
    535 }