tor-browser

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

test_race_cache_with_network.js (10000B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/
      3 */
      4 
      5 "use strict";
      6 
      7 const { HttpServer } = ChromeUtils.importESModule(
      8  "resource://testing-common/httpd.sys.mjs"
      9 );
     10 
     11 var httpserver = new HttpServer();
     12 httpserver.start(-1);
     13 const PORT = httpserver.identity.primaryPort;
     14 
     15 function make_channel(url) {
     16  return NetUtil.newChannel({
     17    uri: url,
     18    loadUsingSystemPrincipal: true,
     19  }).QueryInterface(Ci.nsIHttpChannel);
     20 }
     21 
     22 let gResponseBody = "blahblah";
     23 let g200Counter = 0;
     24 let g304Counter = 0;
     25 function test_handler(metadata, response) {
     26  response.setHeader("Content-Type", "text/plain");
     27  response.setHeader("Cache-Control", "no-cache");
     28  response.setHeader("ETag", "test-etag1");
     29 
     30  let etag;
     31  try {
     32    etag = metadata.getHeader("If-None-Match");
     33  } catch (ex) {
     34    etag = "";
     35  }
     36 
     37  if (etag == "test-etag1") {
     38    response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
     39    g304Counter++;
     40  } else {
     41    response.setStatusLine(metadata.httpVersion, 200, "OK");
     42    response.bodyOutputStream.write(gResponseBody, gResponseBody.length);
     43    g200Counter++;
     44  }
     45 }
     46 
     47 function cached_handler(metadata, response) {
     48  response.setHeader("Content-Type", "text/plain");
     49  response.setHeader("Cache-Control", "max-age=3600");
     50  response.setHeader("ETag", "test-etag1");
     51 
     52  response.setStatusLine(metadata.httpVersion, 200, "OK");
     53  response.bodyOutputStream.write(gResponseBody, gResponseBody.length);
     54 
     55  g200Counter++;
     56 }
     57 
     58 let gResponseCounter = 0;
     59 let gIsFromCache = 0;
     60 function checkContent(request, buffer, context, isFromCache) {
     61  Assert.equal(buffer, gResponseBody);
     62  info(
     63    "isRacing: " +
     64      request.QueryInterface(Ci.nsICacheInfoChannel).isRacing() +
     65      "\n"
     66  );
     67  gResponseCounter++;
     68  if (isFromCache) {
     69    gIsFromCache++;
     70  }
     71  executeSoon(() => {
     72    testGenerator.next();
     73  });
     74 }
     75 
     76 function run_test() {
     77  do_get_profile();
     78  // In this test, we manually use |TriggerNetwork| to prove we could send
     79  // net and cache reqeust simultaneously. Therefore we should disable
     80  // racing in the HttpChannel first.
     81  Services.prefs.setBoolPref("network.http.rcwn.enabled", false);
     82  httpserver.registerPathHandler("/rcwn", test_handler);
     83  httpserver.registerPathHandler("/rcwn_cached", cached_handler);
     84  testGenerator.next();
     85  do_test_pending();
     86 }
     87 
     88 let testGenerator = testSteps();
     89 function* testSteps() {
     90  /*
     91   * In this test, we have a relatively low timeout of 200ms and an assertion that
     92   * the timer works properly by checking that the time was greater than 200ms.
     93   * With a timer precision of 100ms (for example) we will clamp downwards to 200
     94   * and cause the assertion to fail. To resolve this, we hardcode a precision of
     95   * 20ms.
     96   */
     97  Services.prefs.setBoolPref("privacy.reduceTimerPrecision", true);
     98  Services.prefs.setIntPref(
     99    "privacy.resistFingerprinting.reduceTimerPrecision.microseconds",
    100    20000
    101  );
    102 
    103  registerCleanupFunction(function () {
    104    Services.prefs.clearUserPref("privacy.reduceTimerPrecision");
    105    Services.prefs.clearUserPref(
    106      "privacy.resistFingerprinting.reduceTimerPrecision.microseconds"
    107    );
    108  });
    109 
    110  // Initial request. Stores the response in the cache.
    111  let channel = make_channel("http://localhost:" + PORT + "/rcwn");
    112  channel.asyncOpen(new ChannelListener(checkContent, null));
    113  yield undefined;
    114  equal(gResponseCounter, 1);
    115  equal(g200Counter, 1, "check number of 200 responses");
    116  equal(g304Counter, 0, "check number of 304 responses");
    117 
    118  // Checks that response is returned from the cache, after a 304 response.
    119  channel = make_channel("http://localhost:" + PORT + "/rcwn");
    120  channel.asyncOpen(new ChannelListener(checkContent, null));
    121  yield undefined;
    122  equal(gResponseCounter, 2);
    123  equal(g200Counter, 1, "check number of 200 responses");
    124  equal(g304Counter, 1, "check number of 304 responses");
    125 
    126  // Checks that delaying the response from the cache works.
    127  channel = make_channel("http://localhost:" + PORT + "/rcwn");
    128  channel
    129    .QueryInterface(Ci.nsIRaceCacheWithNetwork)
    130    .test_delayCacheEntryOpeningBy(200);
    131  let startTime = Date.now();
    132  channel.asyncOpen(new ChannelListener(checkContent, null));
    133  yield undefined;
    134  greaterOrEqual(
    135    Date.now() - startTime,
    136    200,
    137    "Check that timer works properly"
    138  );
    139  equal(gResponseCounter, 3);
    140  equal(g200Counter, 1, "check number of 200 responses");
    141  equal(g304Counter, 2, "check number of 304 responses");
    142 
    143  // Checks that we can trigger the cache open immediately, even if the cache delay is set very high.
    144  channel = make_channel("http://localhost:" + PORT + "/rcwn");
    145  channel
    146    .QueryInterface(Ci.nsIRaceCacheWithNetwork)
    147    .test_delayCacheEntryOpeningBy(100000);
    148  channel.asyncOpen(new ChannelListener(checkContent, null));
    149  do_timeout(50, function () {
    150    channel
    151      .QueryInterface(Ci.nsIRaceCacheWithNetwork)
    152      .test_triggerDelayedOpenCacheEntry();
    153  });
    154  yield undefined;
    155  equal(gResponseCounter, 4);
    156  equal(g200Counter, 1, "check number of 200 responses");
    157  equal(g304Counter, 3, "check number of 304 responses");
    158 
    159  // Sets a high delay for the cache fetch, and triggers the network activity.
    160  channel = make_channel("http://localhost:" + PORT + "/rcwn");
    161  channel
    162    .QueryInterface(Ci.nsIRaceCacheWithNetwork)
    163    .test_delayCacheEntryOpeningBy(100000);
    164  channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(50);
    165  channel.asyncOpen(new ChannelListener(checkContent, null));
    166  // Trigger network after 50 ms.
    167  yield undefined;
    168  equal(gResponseCounter, 5);
    169  equal(g200Counter, 2, "check number of 200 responses");
    170  equal(g304Counter, 3, "check number of 304 responses");
    171 
    172  // Sets a high delay for the cache fetch, and triggers the network activity.
    173  // While the network response is produced, we trigger the cache fetch.
    174  // Because the network response was the first, a non-conditional request is sent.
    175  channel = make_channel("http://localhost:" + PORT + "/rcwn");
    176  channel
    177    .QueryInterface(Ci.nsIRaceCacheWithNetwork)
    178    .test_delayCacheEntryOpeningBy(100000);
    179  channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(50);
    180  channel.asyncOpen(new ChannelListener(checkContent, null));
    181  yield undefined;
    182  equal(gResponseCounter, 6);
    183  equal(g200Counter, 3, "check number of 200 responses");
    184  equal(g304Counter, 3, "check number of 304 responses");
    185 
    186  // Triggers cache open before triggering network.
    187  channel = make_channel("http://localhost:" + PORT + "/rcwn");
    188  channel
    189    .QueryInterface(Ci.nsIRaceCacheWithNetwork)
    190    .test_delayCacheEntryOpeningBy(100000);
    191  channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(5000);
    192  channel.asyncOpen(new ChannelListener(checkContent, null));
    193  channel
    194    .QueryInterface(Ci.nsIRaceCacheWithNetwork)
    195    .test_triggerDelayedOpenCacheEntry();
    196  yield undefined;
    197  equal(gResponseCounter, 7);
    198  equal(
    199    g200Counter,
    200    3,
    201    `check number of 200 responses | 200: ${g200Counter}, 304: ${g304Counter}`
    202  );
    203  equal(
    204    g304Counter,
    205    4,
    206    `check number of 304 responses | 200: ${g200Counter}, 304: ${g304Counter}`
    207  );
    208 
    209  // Load the cached handler so we don't need to revalidate
    210  channel = make_channel("http://localhost:" + PORT + "/rcwn_cached");
    211  channel.asyncOpen(new ChannelListener(checkContent, null));
    212  yield undefined;
    213  equal(gResponseCounter, 8);
    214  equal(g200Counter, 4, "check number of 200 responses");
    215  equal(g304Counter, 4, "check number of 304 responses");
    216 
    217  // Make sure response is loaded from the cache, not the network
    218  channel = make_channel("http://localhost:" + PORT + "/rcwn_cached");
    219  channel.asyncOpen(new ChannelListener(checkContent, null));
    220  yield undefined;
    221  equal(gResponseCounter, 9);
    222  equal(g200Counter, 4, "check number of 200 responses");
    223  equal(g304Counter, 4, "check number of 304 responses");
    224 
    225  // Cache times out, so we trigger the network
    226  gIsFromCache = 0;
    227  channel = make_channel("http://localhost:" + PORT + "/rcwn_cached");
    228  channel
    229    .QueryInterface(Ci.nsIRaceCacheWithNetwork)
    230    .test_delayCacheEntryOpeningBy(100000);
    231  // trigger network after 50 ms
    232  channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(50);
    233  channel.asyncOpen(new ChannelListener(checkContent, null));
    234  yield undefined;
    235  equal(gResponseCounter, 10);
    236  equal(gIsFromCache, 0, "should be from the network");
    237  equal(g200Counter, 5, "check number of 200 responses");
    238  equal(g304Counter, 4, "check number of 304 responses");
    239 
    240  // Cache callback comes back right after network is triggered.
    241  channel = make_channel("http://localhost:" + PORT + "/rcwn_cached");
    242  channel
    243    .QueryInterface(Ci.nsIRaceCacheWithNetwork)
    244    .test_delayCacheEntryOpeningBy(55);
    245  channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(50);
    246  channel.asyncOpen(new ChannelListener(checkContent, null));
    247  yield undefined;
    248  equal(gResponseCounter, 11);
    249  info("IsFromCache: " + gIsFromCache + "\n");
    250  info("Number of 200 responses: " + g200Counter + "\n");
    251  equal(g304Counter, 4, "check number of 304 responses");
    252 
    253  // Set an increasingly high timeout to trigger opening the cache entry
    254  // This way we ensure that some of the entries we will get from the network,
    255  // and some we will get from the cache.
    256  gIsFromCache = 0;
    257  for (var i = 0; i < 50; i++) {
    258    channel = make_channel("http://localhost:" + PORT + "/rcwn_cached");
    259    channel
    260      .QueryInterface(Ci.nsIRaceCacheWithNetwork)
    261      .test_delayCacheEntryOpeningBy(i * 100);
    262    channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(10);
    263    channel.asyncOpen(new ChannelListener(checkContent, null));
    264    // This may be racy. The delay was chosen because the distribution of net-cache
    265    // results was around 25-25 on my machine.
    266    yield undefined;
    267  }
    268 
    269  greater(gIsFromCache, 0, "Some of the responses should be from the cache");
    270  less(gIsFromCache, 50, "Some of the responses should be from the net");
    271 
    272  httpserver.stop(do_test_finished);
    273 }