tor-browser

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

entry-invariants.js (14524B)


      1 const await_with_timeout = async (delay, message, promise, cleanup = ()=>{}) => {
      2  let timeout_id;
      3  const timeout = new Promise((_, reject) => {
      4    timeout_id = step_timeout(() =>
      5      reject(new DOMException(message, "TimeoutError")), delay)
      6  });
      7  let result = null;
      8  try {
      9    result = await Promise.race([promise, timeout]);
     10    clearTimeout(timeout_id);
     11  } finally {
     12    cleanup();
     13  }
     14  return result;
     15 };
     16 
     17 // Asserts that the given attributes are present in 'entry' and hold equal
     18 // values.
     19 const assert_all_equal_ = (entry, attributes) => {
     20  let first = attributes[0];
     21  attributes.slice(1).forEach(other => {
     22    assert_equals(entry[first], entry[other],
     23      `${first} should be equal to ${other}`);
     24  });
     25 }
     26 
     27 // Asserts that the given attributes are present in 'entry' and hold values
     28 // that are sorted in the same order as given in 'attributes'.
     29 const assert_ordered_ = (entry, attributes) => {
     30  let before = attributes[0];
     31  attributes.slice(1).forEach(after => {
     32    assert_greater_than_equal(entry[after], entry[before],
     33      `${after} should be greater than ${before}`);
     34    before = after;
     35  });
     36 }
     37 
     38 // Asserts that the given attributes are present in 'entry' and hold a value of
     39 // 0.
     40 const assert_zeroed_ = (entry, attributes) => {
     41  attributes.forEach(attribute => {
     42    assert_equals(entry[attribute], 0, `${attribute} should be 0`);
     43  });
     44 }
     45 
     46 // Asserts that the given attributes are present in 'entry' and hold a value of
     47 // 0 or more.
     48 const assert_not_negative_ = (entry, attributes) => {
     49  attributes.forEach(attribute => {
     50    assert_greater_than_equal(entry[attribute], 0,
     51      `${attribute} should be greater than or equal to 0`);
     52  });
     53 }
     54 
     55 // Asserts that the given attributes are present in 'entry' and hold a value
     56 // greater than 0.
     57 const assert_positive_ = (entry, attributes) => {
     58  attributes.forEach(attribute => {
     59    assert_greater_than(entry[attribute], 0,
     60      `${attribute} should be greater than 0`);
     61  });
     62 }
     63 
     64 const invariants = {
     65  // Asserts that attributes of the given PerformanceResourceTiming entry match
     66  // what the spec dictates for any resource fetched over HTTP without
     67  // redirects but passing the Timing-Allow-Origin checks.
     68  assert_tao_pass_no_redirect_http: entry => {
     69    assert_ordered_(entry, [
     70      "fetchStart",
     71      "domainLookupStart",
     72      "domainLookupEnd",
     73      "connectStart",
     74      "connectEnd",
     75      "requestStart",
     76      "responseStart",
     77      "responseEnd",
     78    ]);
     79 
     80    assert_zeroed_(entry, [
     81      "workerStart",
     82      "secureConnectionStart",
     83      "redirectStart",
     84      "redirectEnd",
     85    ]);
     86 
     87    assert_not_negative_(entry, [
     88      "duration",
     89    ]);
     90 
     91    assert_positive_(entry, [
     92      "fetchStart",
     93      "transferSize",
     94    ]);
     95  },
     96 
     97  // Like assert_tao_pass_no_redirect_http but for empty response bodies.
     98  assert_tao_pass_no_redirect_http_empty: entry => {
     99    assert_ordered_(entry, [
    100      "fetchStart",
    101      "domainLookupStart",
    102      "domainLookupEnd",
    103      "connectStart",
    104      "connectEnd",
    105      "requestStart",
    106      "responseStart",
    107      "responseEnd",
    108    ]);
    109 
    110    assert_zeroed_(entry, [
    111      "workerStart",
    112      "secureConnectionStart",
    113      "redirectStart",
    114      "redirectEnd",
    115    ]);
    116 
    117    assert_not_negative_(entry, [
    118      "duration",
    119    ]);
    120 
    121    assert_positive_(entry, [
    122      "fetchStart",
    123      "transferSize",
    124    ]);
    125  },
    126 
    127  // Like assert_tao_pass_no_redirect_http but for resources fetched over HTTPS
    128  assert_tao_pass_no_redirect_https: entry => {
    129    assert_ordered_(entry, [
    130      "fetchStart",
    131      "domainLookupStart",
    132      "domainLookupEnd",
    133      "secureConnectionStart",
    134      "connectStart",
    135      "connectEnd",
    136      "requestStart",
    137      "responseStart",
    138      "responseEnd",
    139    ]);
    140 
    141    assert_zeroed_(entry, [
    142      "workerStart",
    143      "redirectStart",
    144      "redirectEnd",
    145    ]);
    146 
    147    assert_not_negative_(entry, [
    148      "duration",
    149    ]);
    150 
    151    assert_positive_(entry, [
    152      "fetchStart",
    153      "transferSize",
    154    ]);
    155  },
    156 
    157  // Like assert_tao_pass_no_redirect_https but for resources that did encounter
    158  // at least one HTTP redirect.
    159  assert_tao_pass_with_redirect_https: entry => {
    160    assert_ordered_(entry, [
    161      "fetchStart",
    162      "redirectStart",
    163      "redirectEnd",
    164      "domainLookupStart",
    165      "domainLookupEnd",
    166      "secureConnectionStart",
    167      "connectStart",
    168      "connectEnd",
    169      "requestStart",
    170      "responseStart",
    171      "responseEnd",
    172    ]);
    173 
    174    assert_zeroed_(entry, [
    175      "workerStart",
    176    ]);
    177 
    178    assert_not_negative_(entry, [
    179      "duration",
    180    ]);
    181 
    182    assert_positive_(entry, [
    183      "fetchStart",
    184      "transferSize",
    185    ]);
    186  },
    187 
    188  // Like assert_tao_pass_no_redirect_http but, since the resource's bytes
    189  // won't be retransmitted, the encoded and decoded sizes must be zero.
    190  assert_tao_pass_304_not_modified_http: entry => {
    191    assert_ordered_(entry, [
    192      "fetchStart",
    193      "domainLookupStart",
    194      "domainLookupEnd",
    195      "connectStart",
    196      "connectEnd",
    197      "requestStart",
    198      "responseStart",
    199      "responseEnd",
    200    ]);
    201 
    202    assert_zeroed_(entry, [
    203      "workerStart",
    204      "secureConnectionStart",
    205      "redirectStart",
    206      "redirectEnd",
    207    ]);
    208 
    209    assert_not_negative_(entry, [
    210      "duration",
    211    ]);
    212 
    213    assert_positive_(entry, [
    214      "fetchStart",
    215      "transferSize",
    216    ]);
    217  },
    218 
    219  // Like assert_tao_pass_304_not_modified_http but for resources fetched over
    220  // HTTPS.
    221  assert_tao_pass_304_not_modified_https: entry => {
    222    assert_ordered_(entry, [
    223      "fetchStart",
    224      "domainLookupStart",
    225      "domainLookupEnd",
    226      "secureConnectionStart",
    227      "connectStart",
    228      "connectEnd",
    229      "requestStart",
    230      "responseStart",
    231      "responseEnd",
    232    ]);
    233 
    234    assert_zeroed_(entry, [
    235      "workerStart",
    236      "redirectStart",
    237      "redirectEnd",
    238    ]);
    239 
    240    assert_not_negative_(entry, [
    241      "duration",
    242    ]);
    243 
    244    assert_positive_(entry, [
    245      "fetchStart",
    246      "transferSize",
    247    ]);
    248  },
    249 
    250  // Asserts that attributes of the given PerformanceResourceTiming entry match
    251  // what the spec dictates for any resource subsequently fetched over a
    252  // persistent connection. When this happens, we expect that certain
    253  // attributes describing transport layer behaviour will be equal.
    254  assert_connection_reused: entry => {
    255    assert_all_equal_(entry, [
    256      "fetchStart",
    257      "connectStart",
    258      "connectEnd",
    259      "domainLookupStart",
    260      "domainLookupEnd",
    261    ]);
    262  },
    263 
    264  // Asserts that attributes of the given PerformanceResourceTiming entry match
    265  // what the spec dictates for any resource fetched over HTTP through an HTTP
    266  // redirect.
    267  assert_same_origin_redirected_resource: entry => {
    268    assert_positive_(entry, [
    269      "redirectStart",
    270    ]);
    271 
    272    assert_equals(entry.redirectStart, entry.startTime,
    273      "redirectStart should be equal to startTime");
    274 
    275    assert_ordered_(entry, [
    276      "redirectStart",
    277      "redirectEnd",
    278      "fetchStart",
    279      "domainLookupStart",
    280      "domainLookupEnd",
    281      "connectStart",
    282    ]);
    283  },
    284 
    285  // Asserts that attributes of the given PerformanceResourceTiming entry match
    286  // what the spec dictates for any resource fetched over HTTPS through a
    287  // cross-origin redirect.
    288  // (e.g. GET http://remote.com/foo => 302 Location: https://remote.com/foo)
    289  assert_cross_origin_redirected_resource: entry => {
    290    assert_zeroed_(entry, [
    291      "redirectStart",
    292      "redirectEnd",
    293      "domainLookupStart",
    294      "domainLookupEnd",
    295      "connectStart",
    296      "connectEnd",
    297      "secureConnectionStart",
    298      "requestStart",
    299      "responseStart",
    300    ]);
    301 
    302    assert_positive_(entry, [
    303      "fetchStart",
    304      "responseEnd",
    305    ]);
    306 
    307    assert_ordered_(entry, [
    308      "fetchStart",
    309      "responseEnd",
    310    ]);
    311  },
    312 
    313  // Asserts that attributes of the given PerformanceResourceTiming entry match
    314  // what the spec dictates when
    315  // 1. An HTTP request is made for a same-origin resource.
    316  // 2. The response to 1 is an HTTP redirect (like a 302).
    317  // 3. The location from 2 is a cross-origin HTTPS URL.
    318  // 4. The response to fetching the URL from 3 does not set a matching TAO header.
    319  assert_http_to_cross_origin_redirected_resource: entry => {
    320    assert_zeroed_(entry, [
    321      "redirectStart",
    322      "redirectEnd",
    323      "domainLookupStart",
    324      "domainLookupEnd",
    325      "connectStart",
    326      "connectEnd",
    327      "secureConnectionStart",
    328      "requestStart",
    329      "responseStart",
    330    ]);
    331 
    332    assert_positive_(entry, [
    333      "fetchStart",
    334      "responseEnd",
    335    ]);
    336 
    337    assert_ordered_(entry, [
    338      "fetchStart",
    339      "responseEnd",
    340    ]);
    341  },
    342 
    343  // Asserts that attributes of the given PerformanceResourceTiming entry match
    344  // what the spec dictates when
    345  // 1. An HTTPS request is made for a same-origin resource.
    346  // 2. The response to 1 is an HTTP redirect (like a 302).
    347  // 3. The location from 2 is a cross-origin HTTPS URL.
    348  // 4. The response to fetching the URL from 3 sets a matching TAO header.
    349  assert_tao_enabled_cross_origin_redirected_resource: entry => {
    350    assert_positive_(entry, [
    351      "redirectStart",
    352    ]);
    353    assert_ordered_(entry, [
    354      "redirectStart",
    355      "redirectEnd",
    356      "fetchStart",
    357      "domainLookupStart",
    358      "domainLookupEnd",
    359      "connectStart",
    360      "secureConnectionStart",
    361      "connectEnd",
    362      "requestStart",
    363      "responseStart",
    364      "responseEnd",
    365    ]);
    366  },
    367 
    368  // Asserts that attributes of the given PerformanceResourceTiming entry match
    369  // what the spec dictates when
    370  // 1. An HTTP request is made for a same-origin resource
    371  // 2. The response to 1 is an HTTP redirect (like a 302).
    372  // 3. The location from 2 is a cross-origin HTTPS URL.
    373  // 4. The response to fetching the URL from 3 sets a matching TAO header.
    374  assert_http_to_tao_enabled_cross_origin_https_redirected_resource: entry => {
    375    assert_zeroed_(entry, [
    376      // Note that, according to the spec, the secureConnectionStart attribute
    377      // should describe the connection for the first resource request when
    378      // there are redirects. Since the initial request is over HTTP,
    379      // secureConnectionStart must be 0.
    380      "secureConnectionStart",
    381    ]);
    382    assert_positive_(entry, [
    383      "redirectStart",
    384    ]);
    385    assert_ordered_(entry, [
    386      "redirectStart",
    387      "redirectEnd",
    388      "fetchStart",
    389      "domainLookupStart",
    390      "domainLookupEnd",
    391      "connectStart",
    392      "connectEnd",
    393      "requestStart",
    394      "responseStart",
    395      "responseEnd",
    396    ]);
    397  },
    398 
    399  assert_same_origin_redirected_from_cross_origin_resource: entry => {
    400    assert_zeroed_(entry, [
    401      "workerStart",
    402      "redirectStart",
    403      "redirectEnd",
    404      "domainLookupStart",
    405      "domainLookupEnd",
    406      "connectStart",
    407      "connectEnd",
    408      "secureConnectionStart",
    409      "requestStart",
    410      "responseStart",
    411      "transferSize",
    412    ]);
    413 
    414    assert_ordered_(entry, [
    415      "fetchStart",
    416      "responseEnd",
    417    ]);
    418 
    419    assert_equals(entry.fetchStart, entry.startTime,
    420      "fetchStart must equal startTime");
    421  },
    422 
    423  assert_tao_failure_resource: entry => {
    424    assert_equals(entry.entryType, "resource", "entryType must always be 'resource'");
    425 
    426    assert_positive_(entry, [
    427      "startTime",
    428    ]);
    429 
    430    assert_not_negative_(entry, [
    431      "duration",
    432    ]);
    433 
    434    assert_zeroed_(entry, [
    435      "redirectStart",
    436      "redirectEnd",
    437      "domainLookupStart",
    438      "domainLookupEnd",
    439      "connectStart",
    440      "connectEnd",
    441      "secureConnectionStart",
    442      "requestStart",
    443      "responseStart",
    444      "transferSize",
    445    ]);
    446  }
    447 
    448 };
    449 
    450 const attribute_test_internal = (loader, path, validator, run_test, test_label) => {
    451  promise_test(
    452    async () => {
    453      let loaded_entry = new Promise((resolve, reject) => {
    454        new PerformanceObserver((entry_list, self) => {
    455          try {
    456            const name_matches = entry_list.getEntries().forEach(entry => {
    457              if (entry.name.includes(path)) {
    458                resolve(entry);
    459              }
    460            });
    461          } catch(e) {
    462            // By surfacing exceptions through the Promise interface, tests can
    463            // fail fast with a useful message instead of timing out.
    464            reject(e);
    465          }
    466        }).observe({"type": "resource"});
    467      });
    468 
    469      await loader(path, validator);
    470      const entry = await await_with_timeout(2000,
    471        "Timeout was reached before entry fired",
    472        loaded_entry);
    473      assert_not_equals(entry, null, 'No entry was received');
    474      run_test(entry);
    475  }, test_label);
    476 };
    477 
    478 // Given a resource-loader, a path (a relative path or absolute URL), and a
    479 // PerformanceResourceTiming test, applies the loader to the resource path
    480 // and tests the resulting PerformanceResourceTiming entry.
    481 const attribute_test = (loader, path, run_test, test_label) => {
    482  attribute_test_internal(loader, path, () => {}, run_test, test_label);
    483 };
    484 
    485 // Similar to attribute test, but on top of that, validates the added element,
    486 // to ensure the test does what it intends to do.
    487 const attribute_test_with_validator = (loader, path, validator, run_test, test_label) => {
    488  attribute_test_internal(loader, path, validator, run_test, test_label);
    489 };
    490 
    491 const network_error_entry_test = (originalURL, args, label, loader) => {
    492  const url = new URL(originalURL, location.href);
    493  const search = new URLSearchParams(url.search.substr(1));
    494  const timeBefore = performance.now();
    495 
    496  // Load using `fetch()`, unless we're given a specific loader for this test.
    497  loader ??= () => new Promise(resolve => fetch(url, args).catch(resolve));
    498 
    499  attribute_test(
    500    loader, url,
    501    () => {
    502      const timeAfter = performance.now();
    503      const names = performance.getEntriesByType('resource').filter(e => e.initiatorType === 'fetch').map(e => e.name);
    504      const entries = performance.getEntriesByName(url.toString());
    505      assert_equals(entries.length, 1, 'resource timing entry for network error');
    506      const entry = entries[0]
    507      assert_equals(entry.startTime, entry.fetchStart, 'startTime and fetchStart should be equal');
    508      assert_greater_than_equal(entry.startTime, timeBefore, 'startTime and fetchStart should be greater than the time before fetching');
    509      assert_greater_than_equal(timeAfter, entry.responseEnd, 'endTime should be less than the time right after returning from the fetch');
    510      invariants.assert_tao_failure_resource(entry);
    511  }, `A ResourceTiming entry should be created for network error of type ${label}`);
    512 }