tor-browser

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

sw.https.window.js (8427B)


      1 // META: script=../../../service-workers/service-worker/resources/test-helpers.sub.js
      2 // META: script=/common/utils.js
      3 // META: script=/common/get-host-info.sub.js
      4 // META: script=resources/utils.js
      5 
      6 const { REMOTE_HOST } = get_host_info();
      7 const BASE_SCOPE = 'resources/basic.html?';
      8 
      9 async function cleanup() {
     10  for (const iframe of document.querySelectorAll('.test-iframe')) {
     11    iframe.parentNode.removeChild(iframe);
     12  }
     13 
     14  for (const reg of await navigator.serviceWorker.getRegistrations()) {
     15    await reg.unregister();
     16  }
     17 }
     18 
     19 async function setupRegistration(t, scope) {
     20  await cleanup();
     21  const reg = await navigator.serviceWorker.register('resources/range-sw.js', { scope });
     22  await wait_for_state(t, reg.installing, 'activated');
     23  return reg;
     24 }
     25 
     26 function awaitMessage(obj, id) {
     27  return new Promise(resolve => {
     28    obj.addEventListener('message', function listener(event) {
     29      if (event.data.id !== id) return;
     30      obj.removeEventListener('message', listener);
     31      resolve(event.data);
     32    });
     33  });
     34 }
     35 
     36 promise_test(async t => {
     37  const scope = BASE_SCOPE + Math.random();
     38  const reg = await setupRegistration(t, scope);
     39  const iframe = await with_iframe(scope);
     40  const w = iframe.contentWindow;
     41 
     42  // Trigger a cross-origin range request using media
     43  const url = new URL('long-wav.py?action=range-header-filter-test', w.location);
     44  url.hostname = REMOTE_HOST;
     45  appendAudio(w.document, url);
     46 
     47  // See rangeHeaderFilterTest in resources/range-sw.js
     48  await fetch_tests_from_worker(reg.active);
     49 }, `Defer range header filter tests to service worker`);
     50 
     51 promise_test(async t => {
     52  const scope = BASE_SCOPE + Math.random();
     53  const reg = await setupRegistration(t, scope);
     54  const iframe = await with_iframe(scope);
     55  const w = iframe.contentWindow;
     56 
     57  // Trigger a cross-origin range request using media
     58  const url = new URL('long-wav.py', w.location);
     59  url.searchParams.set('action', 'range-header-passthrough-test');
     60  url.searchParams.set('range-received-key', token());
     61  url.hostname = REMOTE_HOST;
     62  appendAudio(w.document, url);
     63 
     64  // See rangeHeaderPassthroughTest in resources/range-sw.js
     65  await fetch_tests_from_worker(reg.active);
     66 }, `Defer range header passthrough tests to service worker`);
     67 
     68 promise_test(async t => {
     69  const scope = BASE_SCOPE + Math.random();
     70  await setupRegistration(t, scope);
     71  const iframe = await with_iframe(scope);
     72  const w = iframe.contentWindow;
     73  const id = Math.random() + '';
     74  const storedRangeResponse = awaitMessage(w.navigator.serviceWorker, id);
     75 
     76  // Trigger a cross-origin range request using media
     77  const url = new URL('partial-script.py', w.location);
     78  url.searchParams.set('require-range', '1');
     79  url.searchParams.set('action', 'store-ranged-response');
     80  url.searchParams.set('id', id);
     81  url.hostname = REMOTE_HOST;
     82 
     83  appendAudio(w.document, url);
     84 
     85  await storedRangeResponse;
     86 
     87  // Fetching should reject
     88  const fetchPromise = w.fetch('?action=use-stored-ranged-response', { mode: 'no-cors' });
     89  await promise_rejects_js(t, w.TypeError, fetchPromise);
     90 
     91  // Script loading should error too
     92  const loadScriptPromise = loadScript('?action=use-stored-ranged-response', { doc: w.document });
     93  await promise_rejects_js(t, Error, loadScriptPromise);
     94 
     95  await loadScriptPromise.catch(() => {});
     96 
     97  assert_false(!!w.scriptExecuted, `Partial response shouldn't be executed`);
     98 }, `Ranged response not allowed following no-cors ranged request`);
     99 
    100 promise_test(async t => {
    101  const scope = BASE_SCOPE + Math.random();
    102  await setupRegistration(t, scope);
    103  const iframe = await with_iframe(scope);
    104  const w = iframe.contentWindow;
    105  const id = Math.random() + '';
    106  const storedRangeResponse = awaitMessage(w.navigator.serviceWorker, id);
    107 
    108  // Trigger a range request using media
    109  const url = new URL('partial-script.py', w.location);
    110  url.searchParams.set('require-range', '1');
    111  url.searchParams.set('action', 'store-ranged-response');
    112  url.searchParams.set('id', id);
    113 
    114  appendAudio(w.document, url);
    115 
    116  await storedRangeResponse;
    117 
    118  // This should not throw
    119  await w.fetch('?action=use-stored-ranged-response');
    120 
    121  // This shouldn't throw either
    122  await loadScript('?action=use-stored-ranged-response', { doc: w.document });
    123 
    124  assert_true(w.scriptExecuted, `Partial response should be executed`);
    125 }, `Non-opaque ranged response executed`);
    126 
    127 promise_test(async t => {
    128  const scope = BASE_SCOPE + Math.random();
    129  await setupRegistration(t, scope);
    130  const iframe = await with_iframe(scope);
    131  const w = iframe.contentWindow;
    132  const fetchId = Math.random() + '';
    133  const fetchBroadcast = awaitMessage(w.navigator.serviceWorker, fetchId);
    134  const audioId = Math.random() + '';
    135  const audioBroadcast = awaitMessage(w.navigator.serviceWorker, audioId);
    136 
    137  const url = new URL('long-wav.py', w.location);
    138  url.searchParams.set('action', 'broadcast-accept-encoding');
    139  url.searchParams.set('id', fetchId);
    140 
    141  await w.fetch(url, {
    142    headers: { Range: 'bytes=0-10' }
    143  });
    144 
    145  assert_equals((await fetchBroadcast).acceptEncoding, null, "Accept-Encoding should not be set for fetch");
    146 
    147  url.searchParams.set('id', audioId);
    148  appendAudio(w.document, url);
    149 
    150  assert_equals((await audioBroadcast).acceptEncoding, null, "Accept-Encoding should not be set for media");
    151 }, `Accept-Encoding should not appear in a service worker`);
    152 
    153 promise_test(async t => {
    154  const scope = BASE_SCOPE + Math.random();
    155  await setupRegistration(t, scope);
    156  const iframe = await with_iframe(scope);
    157  const w = iframe.contentWindow;
    158  const length = 100;
    159  const count = 3;
    160  const counts = {};
    161 
    162  // test a single range request size
    163  async function testSizedRange(size, partialResponseCode) {
    164    const rangeId = Math.random() + '';
    165    const rangeBroadcast = awaitMessage(w.navigator.serviceWorker, rangeId);
    166 
    167    // Create a bogus audio element to trick the browser into sending
    168    // cross-origin range requests that can be manipulated by the service worker.
    169    const sound_url = new URL('partial-text.py', w.location);
    170    sound_url.hostname = REMOTE_HOST;
    171    sound_url.searchParams.set('action', 'record-media-range-request');
    172    sound_url.searchParams.set('length', length);
    173    sound_url.searchParams.set('size', size);
    174    sound_url.searchParams.set('partial', partialResponseCode);
    175    sound_url.searchParams.set('id', rangeId);
    176    sound_url.searchParams.set('type', 'audio/mp4');
    177    appendAudio(w.document, sound_url);
    178 
    179    // wait for the range requests to happen
    180    await rangeBroadcast;
    181 
    182    // Create multiple preload requests and count the number of resource timing
    183    // entries that get created to make sure 206 and 416 range responses are treated
    184    // the same.
    185    const url = new URL('partial-text.py', w.location);
    186    url.searchParams.set('action', 'use-media-range-request');
    187    url.searchParams.set('size', size);
    188    url.searchParams.set('type', 'audio/mp4');
    189    counts['size' + size] = 0;
    190    for (let i = 0; i < count; i++) {
    191      await preloadImage(url, { doc: w.document });
    192    }
    193  }
    194 
    195  // Test range requests from 1 smaller than the correct size to 1 larger than
    196  // the correct size to exercise the various permutations using the default 206
    197  // response code for successful range requests.
    198  for (let size = length - 1; size <= length + 1; size++) {
    199    await testSizedRange(size, '206');
    200  }
    201 
    202  // Test a successful range request using a 200 response.
    203  await testSizedRange(length - 2, '200');
    204 
    205  // Check the resource timing entries and count the reported number of fetches of each type
    206  const resources = w.performance.getEntriesByType("resource");
    207  for (const entry of resources) {
    208    const url = new URL(entry.name);
    209    if (url.searchParams.has('action') &&
    210        url.searchParams.get('action') == 'use-media-range-request' &&
    211        url.searchParams.has('size')) {
    212      counts['size' + url.searchParams.get('size')]++;
    213    }
    214  }
    215 
    216  // Make sure there are a non-zero number of preload requests and they are all the same
    217  let counts_valid = true;
    218  const first = 'size' + (length - 2);
    219  for (let size = length - 2; size <= length + 1; size++) {
    220    let key = 'size' + size;
    221    if (!(key in counts) || counts[key] <= 0 || counts[key] != counts[first]) {
    222      counts_valid = false;
    223      break;
    224    }
    225  }
    226 
    227  assert_true(counts_valid, `Opaque range request preloads were different for error and success`);
    228 }, `Opaque range preload successes and failures should be indistinguishable`);