tor-browser

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

fetch-event.https.html (41615B)


      1 <!DOCTYPE html>
      2 <meta name=timeout content=long>
      3 <script src="/resources/testharness.js"></script>
      4 <script src="/resources/testharnessreport.js"></script>
      5 <script src="/common/get-host-info.sub.js"></script>
      6 <script src="/common/utils.js"></script>
      7 <script src="resources/test-helpers.sub.js"></script>
      8 <body>
      9 <script>
     10 var worker = 'resources/fetch-event-test-worker.js';
     11 function wait(ms) {
     12    return new Promise(resolve => step_timeout(resolve, ms));
     13 }
     14 
     15 promise_test(async t => {
     16    const scope = 'resources/';
     17    const registration =
     18        await service_worker_unregister_and_register(t, worker, scope);
     19    await wait_for_state(t, registration.installing, 'activated');
     20 
     21    // This will happen after all other tests
     22    promise_test(t => {
     23        return registration.unregister();
     24      }, 'restore global state');
     25  }, 'global setup');
     26 
     27 promise_test(t => {
     28    const page_url = 'resources/simple.html?headers';
     29    return with_iframe(page_url)
     30      .then(function(frame) {
     31          t.add_cleanup(() => { frame.remove(); });
     32          const headers = JSON.parse(frame.contentDocument.body.textContent);
     33          const header_names = {};
     34          for (const [name, value] of headers) {
     35            header_names[name] = true;
     36          }
     37 
     38          assert_true(
     39            header_names.hasOwnProperty('accept'),
     40            'request includes "Accept" header as inserted by Fetch'
     41          );
     42        });
     43  }, 'Service Worker headers in the request of a fetch event');
     44 
     45 promise_test(t => {
     46    const page_url = 'resources/simple.html?string';
     47    return with_iframe(page_url)
     48      .then(function(frame) {
     49          t.add_cleanup(() => { frame.remove(); });
     50          assert_equals(
     51            frame.contentDocument.body.textContent,
     52            'Test string',
     53            'Service Worker should respond to fetch with a test string');
     54          assert_equals(
     55            frame.contentDocument.contentType,
     56            'text/plain',
     57            'The content type of the response created with a string should be text/plain');
     58          assert_equals(
     59            frame.contentDocument.characterSet,
     60            'UTF-8',
     61            'The character set of the response created with a string should be UTF-8');
     62        });
     63  }, 'Service Worker responds to fetch event with string');
     64 
     65 promise_test(t => {
     66    const page_url = 'resources/simple.html?string';
     67    var frame;
     68    return with_iframe(page_url)
     69      .then(function(f) {
     70        frame = f;
     71        t.add_cleanup(() => { frame.remove(); });
     72        return frame.contentWindow.fetch(page_url + "#foo")
     73      })
     74      .then(function(response) { return response.text() })
     75      .then(function(text) {
     76          assert_equals(
     77            text,
     78            'Test string',
     79            'Service Worker should respond to fetch with a test string');
     80        });
     81  }, 'Service Worker responds to fetch event using request fragment with string');
     82 
     83 promise_test(t => {
     84    const page_url = 'resources/simple.html?blob';
     85    return with_iframe(page_url)
     86      .then(frame => {
     87          t.add_cleanup(() => { frame.remove(); });
     88          assert_equals(
     89            frame.contentDocument.body.textContent,
     90            'Test blob',
     91            'Service Worker should respond to fetch with a test string');
     92        });
     93  }, 'Service Worker responds to fetch event with blob body');
     94 
     95 promise_test(t => {
     96    const page_url = 'resources/simple.html?referrer';
     97      return with_iframe(page_url)
     98      .then(frame => {
     99          t.add_cleanup(() => { frame.remove(); });
    100          assert_equals(
    101            frame.contentDocument.body.textContent,
    102            'Referrer: ' + document.location.href,
    103            'Service Worker should respond to fetch with the referrer URL');
    104        });
    105  }, 'Service Worker responds to fetch event with the referrer URL');
    106 
    107 promise_test(t => {
    108    const page_url = 'resources/simple.html?clientId';
    109    var frame;
    110    return with_iframe(page_url)
    111      .then(function(f) {
    112          frame = f;
    113          t.add_cleanup(() => { frame.remove(); });
    114          assert_equals(
    115            frame.contentDocument.body.textContent,
    116            'Client ID Not Found',
    117            'Service Worker should respond to fetch with a client id');
    118          return frame.contentWindow.fetch('resources/other.html?clientId');
    119        })
    120      .then(function(response) { return response.text(); })
    121      .then(function(response_text) {
    122          assert_equals(
    123            response_text.substr(0, 15),
    124            'Client ID Found',
    125            'Service Worker should respond to fetch with an existing client id');
    126        });
    127  }, 'Service Worker responds to fetch event with an existing client id');
    128 
    129 promise_test(t => {
    130    const page_url = 'resources/simple.html?resultingClientId';
    131    const expected_found = 'Resulting Client ID Found';
    132    const expected_not_found = 'Resulting Client ID Not Found';
    133    return with_iframe(page_url)
    134      .then(function(frame) {
    135          t.add_cleanup(() => { frame.remove(); });
    136          assert_equals(
    137            frame.contentDocument.body.textContent.substr(0, expected_found.length),
    138            expected_found,
    139            'Service Worker should respond with an existing resulting client id for non-subresource requests');
    140          return frame.contentWindow.fetch('resources/other.html?resultingClientId');
    141        })
    142      .then(function(response) { return response.text(); })
    143      .then(function(response_text) {
    144          assert_equals(
    145            response_text.substr(0),
    146            expected_not_found,
    147            'Service Worker should respond with an empty resulting client id for subresource requests');
    148        });
    149  }, 'Service Worker responds to fetch event with the correct resulting client id');
    150 
    151 promise_test(t => {
    152    const page_url = 'resources/simple.html?ignore';
    153    return with_iframe(page_url)
    154      .then(function(frame) {
    155          t.add_cleanup(() => { frame.remove(); });
    156          assert_equals(frame.contentDocument.body.textContent,
    157                        'Here\'s a simple html file.\n',
    158                        'Response should come from fallback to native fetch');
    159        });
    160  }, 'Service Worker does not respond to fetch event');
    161 
    162 promise_test(t => {
    163    const page_url = 'resources/simple.html?null';
    164    return with_iframe(page_url)
    165      .then(function(frame) {
    166          t.add_cleanup(() => { frame.remove(); });
    167          assert_equals(frame.contentDocument.body.textContent,
    168                        '',
    169                        'Response should be the empty string');
    170        });
    171  }, 'Service Worker responds to fetch event with null response body');
    172 
    173 promise_test(t => {
    174    const page_url = 'resources/simple.html?fetch';
    175    return with_iframe(page_url)
    176      .then(function(frame) {
    177          t.add_cleanup(() => { frame.remove(); });
    178          assert_equals(frame.contentDocument.body.textContent,
    179                        'Here\'s an other html file.\n',
    180                        'Response should come from fetched other file');
    181        });
    182  }, 'Service Worker fetches other file in fetch event');
    183 
    184 // Creates a form and an iframe and does a form submission that navigates the
    185 // frame to |action_url|. Returns the frame after navigation.
    186 function submit_form(action_url) {
    187  return new Promise(resolve => {
    188      const frame = document.createElement('iframe');
    189      frame.name = 'post-frame';
    190      document.body.appendChild(frame);
    191      const form = document.createElement('form');
    192      form.target = frame.name;
    193      form.action = action_url;
    194      form.method = 'post';
    195      const input1 = document.createElement('input');
    196      input1.type = 'text';
    197      input1.value = 'testValue1';
    198      input1.name = 'testName1'
    199      form.appendChild(input1);
    200      const input2 = document.createElement('input');
    201      input2.type = 'text';
    202      input2.value = 'testValue2';
    203      input2.name = 'testName2'
    204      form.appendChild(input2);
    205      document.body.appendChild(form);
    206      frame.onload = function() {
    207        form.remove();
    208        resolve(frame);
    209      };
    210      form.submit();
    211    });
    212 }
    213 
    214 promise_test(t => {
    215    const page_url = 'resources/simple.html?form-post';
    216    return submit_form(page_url)
    217      .then(frame => {
    218          t.add_cleanup(() => { frame.remove(); });
    219          assert_equals(frame.contentDocument.body.textContent,
    220                        'POST:application/x-www-form-urlencoded:' +
    221                        'testName1=testValue1&testName2=testValue2');
    222        });
    223  }, 'Service Worker responds to fetch event with POST form');
    224 
    225 promise_test(t => {
    226    // Add '?ignore' so the service worker falls back to network.
    227    const page_url = 'resources/echo-content.py?ignore';
    228    return submit_form(page_url)
    229      .then(frame => {
    230          t.add_cleanup(() => { frame.remove(); });
    231          assert_equals(frame.contentDocument.body.textContent,
    232                        'testName1=testValue1&testName2=testValue2');
    233        });
    234  }, 'Service Worker falls back to network in fetch event with POST form');
    235 
    236 promise_test(t => {
    237    const page_url = 'resources/simple.html?multiple-respond-with';
    238    return with_iframe(page_url)
    239      .then(frame => {
    240          t.add_cleanup(() => { frame.remove(); });
    241          assert_equals(
    242            frame.contentDocument.body.textContent,
    243            '(0)(1)[InvalidStateError](2)[InvalidStateError]',
    244            'Multiple calls of respondWith must throw InvalidStateErrors.');
    245        });
    246  }, 'Multiple calls of respondWith must throw InvalidStateErrors');
    247 
    248 promise_test(t => {
    249    const page_url = 'resources/simple.html?used-check';
    250    var first_frame;
    251    return with_iframe(page_url)
    252      .then(function(frame) {
    253          assert_equals(frame.contentDocument.body.textContent,
    254                        'Here\'s an other html file.\n',
    255                        'Response should come from fetched other file');
    256          first_frame = frame;
    257          t.add_cleanup(() => { first_frame.remove(); });
    258          return with_iframe(page_url);
    259        })
    260      .then(function(frame) {
    261          t.add_cleanup(() => { frame.remove(); });
    262          // When we access to the page_url in the second time, the content of the
    263          // response is generated inside the ServiceWorker. The body contains
    264          // the value of bodyUsed of the first response which is already
    265          // consumed by FetchEvent.respondWith method.
    266          assert_equals(
    267            frame.contentDocument.body.textContent,
    268            'bodyUsed: true',
    269            'event.respondWith must set the used flag.');
    270        });
    271  }, 'Service Worker event.respondWith must set the used flag');
    272 
    273 promise_test(t => {
    274    const page_url = 'resources/simple.html?fragment-check';
    275    var fragment = '#/some/fragment';
    276    var first_frame;
    277    return with_iframe(page_url + fragment)
    278      .then(function(frame) {
    279          t.add_cleanup(() => { frame.remove(); });
    280          assert_equals(
    281            frame.contentDocument.body.textContent,
    282            'Fragment Found :' + fragment,
    283            'Service worker should expose URL fragments in request.');
    284        });
    285  }, 'Service Worker should expose FetchEvent URL fragments.');
    286 
    287 promise_test(t => {
    288    const page_url = 'resources/simple.html?cache';
    289    var frame;
    290    var cacheTypes = [
    291      undefined, 'default', 'no-store', 'reload', 'no-cache', 'force-cache', 'only-if-cached'
    292    ];
    293    return with_iframe(page_url)
    294      .then(function(f) {
    295          frame = f;
    296          t.add_cleanup(() => { frame.remove(); });
    297          assert_equals(frame.contentWindow.document.body.textContent, 'default');
    298          var tests = cacheTypes.map(function(type) {
    299            return new Promise(function(resolve, reject) {
    300                var init = {cache: type};
    301                if (type === 'only-if-cached') {
    302                  // For privacy reasons, for the time being, only-if-cached
    303                  // requires the mode to be same-origin.
    304                  init.mode = 'same-origin';
    305                }
    306                return frame.contentWindow.fetch(page_url + '=' + type, init)
    307                  .then(function(response) { return response.text(); })
    308                  .then(function(response_text) {
    309                      var expected = (type === undefined) ? 'default' : type;
    310                      assert_equals(response_text, expected,
    311                                    'Service Worker should respond to fetch with the correct type');
    312                    })
    313                  .then(resolve)
    314                  .catch(reject);
    315              });
    316          });
    317          return Promise.all(tests);
    318        })
    319      .then(function() {
    320          return new Promise(function(resolve, reject) {
    321            frame.addEventListener('load', function onLoad() {
    322              frame.removeEventListener('load', onLoad);
    323              try {
    324                assert_equals(frame.contentWindow.document.body.textContent,
    325                              'no-cache');
    326                resolve();
    327              } catch (e) {
    328                reject(e);
    329              }
    330            });
    331            frame.contentWindow.location.reload();
    332          });
    333        });
    334  }, 'Service Worker responds to fetch event with the correct cache types');
    335 
    336 promise_test(t => {
    337    const page_url = 'resources/simple.html?eventsource';
    338    var frame;
    339 
    340    function test_eventsource(opts) {
    341      return new Promise(function(resolve, reject) {
    342        var eventSource = new frame.contentWindow.EventSource(page_url, opts);
    343        eventSource.addEventListener('message', function(msg) {
    344          eventSource.close();
    345          try {
    346            var data = JSON.parse(msg.data);
    347            assert_equals(data.mode, 'cors',
    348                          'EventSource should make CORS requests.');
    349            assert_equals(data.cache, 'no-store',
    350                          'EventSource should bypass the http cache.');
    351            var expectedCredentials = opts.withCredentials ? 'include'
    352                                                           : 'same-origin';
    353            assert_equals(data.credentials, expectedCredentials,
    354                          'EventSource should pass correct credentials mode.');
    355            resolve();
    356          } catch (e) {
    357            reject(e);
    358          }
    359        });
    360        eventSource.addEventListener('error', function(e) {
    361          eventSource.close();
    362          reject('The EventSource fired an error event.');
    363        });
    364      });
    365    }
    366 
    367    return with_iframe(page_url)
    368      .then(function(f) {
    369          frame = f;
    370          t.add_cleanup(() => { frame.remove(); });
    371          return test_eventsource({ withCredentials: false });
    372        })
    373      .then(function() {
    374          return test_eventsource({ withCredentials: true });
    375        });
    376  }, 'Service Worker should intercept EventSource');
    377 
    378 promise_test(t => {
    379    const page_url = 'resources/simple.html?integrity';
    380    var frame;
    381    var integrity_metadata = 'gs0nqru8KbsrIt5YToQqS9fYao4GQJXtcId610g7cCU=';
    382 
    383    return with_iframe(page_url)
    384      .then(function(f) {
    385          frame = f;
    386          t.add_cleanup(() => { frame.remove(); });
    387          // A request has associated integrity metadata (a string).
    388          // Unless stated otherwise, it is the empty string.
    389          assert_equals(
    390            frame.contentDocument.body.textContent, '');
    391 
    392          return frame.contentWindow.fetch(page_url, {'integrity': integrity_metadata});
    393        })
    394      .then(response => {
    395          return response.text();
    396        })
    397      .then(response_text => {
    398          assert_equals(response_text, integrity_metadata, 'integrity');
    399        });
    400  }, 'Service Worker responds to fetch event with the correct integrity_metadata');
    401 
    402 // Test that the service worker can read FetchEvent#body when it is a string.
    403 // It responds with request body it read.
    404 promise_test(t => {
    405    // Set page_url to "?ignore" so the service worker falls back to network
    406    // for the main resource request, and add a suffix to avoid colliding
    407    // with other tests.
    408    const page_url = 'resources/simple.html?ignore-for-request-body-string';
    409    let frame;
    410 
    411    return with_iframe(page_url)
    412      .then(f => {
    413          frame = f;
    414          t.add_cleanup(() => { frame.remove(); });
    415          return frame.contentWindow.fetch('simple.html?request-body', {
    416              method: 'POST',
    417              body: 'i am the request body'
    418            });
    419        })
    420      .then(response => {
    421          return response.text();
    422        })
    423      .then(response_text => {
    424          assert_equals(response_text, 'i am the request body');
    425        });
    426  }, 'FetchEvent#body is a string');
    427 
    428 // Test that the service worker can read FetchEvent#body when it is made from
    429 // a ReadableStream. It responds with request body it read.
    430 promise_test(async t => {
    431    const rs = new ReadableStream({start(c) {
    432      c.enqueue('i a');
    433      c.enqueue('m the request');
    434      step_timeout(t.step_func(() => {
    435        c.enqueue(' body');
    436        c.close();
    437      }, 10));
    438    }});
    439 
    440    // Set page_url to "?ignore" so the service worker falls back to network
    441    // for the main resource request, and add a suffix to avoid colliding
    442    // with other tests.
    443    const page_url = `resources/simple.html?ignore&id=${token()}`;
    444 
    445    const frame = await with_iframe(page_url);
    446    t.add_cleanup(() => { frame.remove(); });
    447    const res = await frame.contentWindow.fetch('simple.html?request-body', {
    448      method: 'POST',
    449      body: rs.pipeThrough(new TextEncoderStream()),
    450      duplex: 'half',
    451    });
    452    assert_equals(await res.text(), 'i am the request body');
    453  }, 'FetchEvent#body is a ReadableStream');
    454 
    455 // Test that the request body is sent to network upon network fallback,
    456 // for a string body.
    457 promise_test(t => {
    458    // Set page_url to "?ignore" so the service worker falls back to network
    459    // for the main resource request, and add a suffix to avoid colliding
    460    // with other tests.
    461    const page_url = 'resources/?ignore-for-request-body-fallback-string';
    462    let frame;
    463 
    464    return with_iframe(page_url)
    465      .then(f => {
    466          frame = f;
    467          t.add_cleanup(() => { frame.remove(); });
    468          // Add "?ignore" so the service worker falls back to echo-content.py.
    469          const echo_url = '/fetch/api/resources/echo-content.py?ignore';
    470          return frame.contentWindow.fetch(echo_url, {
    471              method: 'POST',
    472              body: 'i am the request body'
    473            });
    474        })
    475      .then(response => {
    476          return response.text();
    477        })
    478      .then(response_text => {
    479          assert_equals(
    480              response_text,
    481              'i am the request body',
    482              'the network fallback request should include the request body');
    483        });
    484  }, 'FetchEvent#body is a string and is passed to network fallback');
    485 
    486 // Test that the request body is sent to network upon network fallback,
    487 // for a ReadableStream body.
    488 promise_test(async t => {
    489    const rs = new ReadableStream({start(c) {
    490      c.enqueue('i a');
    491      c.enqueue('m the request');
    492      t.step_timeout(t.step_func(() => {
    493        c.enqueue(' body');
    494        c.close();
    495      }, 10));
    496    }});
    497    // Set page_url to "?ignore" so the service worker falls back to network
    498    // for the main resource request, and add a suffix to avoid colliding
    499    // with other tests.
    500    const page_url = 'resources/?ignore-for-request-body-fallback-string';
    501    const frame = await with_iframe(page_url);
    502    t.add_cleanup(() => { frame.remove(); });
    503    // Add "?ignore" so the service worker falls back to echo-content.py.
    504    const echo_url = '/fetch/api/resources/echo-content.py?ignore';
    505    const w = frame.contentWindow;
    506    await promise_rejects_js(t, w.TypeError,  w.fetch(echo_url, {
    507        method: 'POST',
    508        body: rs
    509    }));
    510  }, 'FetchEvent#body is a none Uint8Array ReadableStream and is passed to a service worker');
    511 
    512 // Test that the request body is sent to network upon network fallback even when
    513 // the request body is used in the service worker, for a string body.
    514 promise_test(async t => {
    515    // Set page_url to "?ignore" so the service worker falls back to network
    516    // for the main resource request, and add a suffix to avoid colliding
    517    // with other tests.
    518    const page_url = 'resources/?ignore-for-request-body-fallback-string';
    519 
    520    const frame = await with_iframe(page_url);
    521    t.add_cleanup(() => { frame.remove(); });
    522    // Add "?use-and-ignore" so the service worker falls back to echo-content.py.
    523    const echo_url = '/fetch/api/resources/echo-content.py?use-and-ignore';
    524    const response = await frame.contentWindow.fetch(echo_url, {
    525        method: 'POST',
    526        body: 'i am the request body'
    527    });
    528    const text = await response.text();
    529    assert_equals(
    530        text,
    531        'i am the request body',
    532        'the network fallback request should include the request body');
    533  }, 'FetchEvent#body is a string, used and passed to network fallback');
    534 
    535 // Test that the request body is sent to network upon network fallback even when
    536 // the request body is used by clone() in the service worker, for a string body.
    537 promise_test(async t => {
    538    // Set page_url to "?ignore" so the service worker falls back to network
    539    // for the main resource request, and add a suffix to avoid colliding
    540    // with other tests.
    541    const page_url = 'resources/?ignore-for-request-body-fallback-string';
    542 
    543    const frame = await with_iframe(page_url);
    544    t.add_cleanup(() => { frame.remove(); });
    545    // Add "?clone-and-ignore" so the service worker falls back to
    546    // echo-content.py.
    547    const echo_url = '/fetch/api/resources/echo-content.py?clone-and-ignore';
    548    const response = await frame.contentWindow.fetch(echo_url, {
    549        method: 'POST',
    550        body: 'i am the request body'
    551    });
    552    const text = await response.text();
    553    assert_equals(
    554        text,
    555        'i am the request body',
    556        'the network fallback request should include the request body');
    557  }, 'FetchEvent#body is a string, cloned and passed to network fallback');
    558 
    559 // Test that the service worker can read FetchEvent#body when it is a blob.
    560 // It responds with request body it read.
    561 promise_test(t => {
    562    // Set page_url to "?ignore" so the service worker falls back to network
    563    // for the main resource request, and add a suffix to avoid colliding
    564    // with other tests.
    565    const page_url = 'resources/simple.html?ignore-for-request-body-blob';
    566    let frame;
    567 
    568    return with_iframe(page_url)
    569      .then(f => {
    570          frame = f;
    571          t.add_cleanup(() => { frame.remove(); });
    572          const blob = new Blob(['it\'s me the blob', ' ', 'and more blob!']);
    573          return frame.contentWindow.fetch('simple.html?request-body', {
    574              method: 'POST',
    575              body: blob
    576            });
    577        })
    578      .then(response => {
    579          return response.text();
    580        })
    581      .then(response_text => {
    582          assert_equals(response_text, 'it\'s me the blob and more blob!');
    583        });
    584  }, 'FetchEvent#body is a blob');
    585 
    586 // Test that the request body is sent to network upon network fallback,
    587 // for a blob body.
    588 promise_test(t => {
    589    // Set page_url to "?ignore" so the service worker falls back to network
    590    // for the main resource request, and add a suffix to avoid colliding
    591    // with other tests.
    592    const page_url = 'resources/simple.html?ignore-for-request-body-fallback-blob';
    593    let frame;
    594 
    595    return with_iframe(page_url)
    596      .then(f => {
    597          frame = f;
    598          t.add_cleanup(() => { frame.remove(); });
    599          const blob = new Blob(['it\'s me the blob', ' ', 'and more blob!']);
    600          // Add "?ignore" so the service worker falls back to echo-content.py.
    601          const echo_url = '/fetch/api/resources/echo-content.py?ignore';
    602          return frame.contentWindow.fetch(echo_url, {
    603              method: 'POST',
    604              body: blob
    605            });
    606        })
    607      .then(response => {
    608          return response.text();
    609        })
    610      .then(response_text => {
    611          assert_equals(
    612              response_text,
    613              'it\'s me the blob and more blob!',
    614              'the network fallback request should include the request body');
    615        });
    616  }, 'FetchEvent#body is a blob and is passed to network fallback');
    617 
    618 promise_test(async (t) => {
    619    const page_url = 'resources/simple.html?keepalive';
    620    const frame = await with_iframe(page_url);
    621    t.add_cleanup(() => { frame.remove(); });
    622    assert_equals(frame.contentDocument.body.textContent, 'false');
    623    const response = await frame.contentWindow.fetch(page_url, {keepalive: true});
    624    const text = await response.text();
    625    assert_equals(text, 'true');
    626  }, 'Service Worker responds to fetch event with the correct keepalive value');
    627 
    628 promise_test(async (t) => {
    629    const page_url = 'resources/simple.html?isReloadNavigation';
    630    const frame = await with_iframe(page_url);
    631    t.add_cleanup(() => { frame.remove(); });
    632    assert_equals(frame.contentDocument.body.textContent,
    633                  'method = GET, isReloadNavigation = false');
    634    await new Promise((resolve) => {
    635      frame.addEventListener('load', resolve);
    636      frame.contentWindow.location.reload();
    637    });
    638    assert_equals(frame.contentDocument.body.textContent,
    639                  'method = GET, isReloadNavigation = true');
    640  }, 'FetchEvent#request.isReloadNavigation is true (location.reload())');
    641 
    642 promise_test(async (t) => {
    643    const page_url = 'resources/simple.html?isReloadNavigation';
    644    const frame = await with_iframe(page_url);
    645    t.add_cleanup(() => { frame.remove(); });
    646    assert_equals(frame.contentDocument.body.textContent,
    647                  'method = GET, isReloadNavigation = false');
    648    await new Promise((resolve) => {
    649      frame.addEventListener('load', resolve);
    650      frame.contentWindow.history.go(0);
    651    });
    652    assert_equals(frame.contentDocument.body.textContent,
    653                  'method = GET, isReloadNavigation = true');
    654  }, 'FetchEvent#request.isReloadNavigation is true (history.go(0))');
    655 
    656 promise_test(async (t) => {
    657    const page_url = 'resources/simple.html?isReloadNavigation';
    658    const frame = await with_iframe(page_url);
    659    t.add_cleanup(() => { frame.remove(); });
    660    assert_equals(frame.contentDocument.body.textContent,
    661                  'method = GET, isReloadNavigation = false');
    662    await new Promise((resolve) => {
    663      frame.addEventListener('load', resolve);
    664      const form = frame.contentDocument.createElement('form');
    665      form.method = 'POST';
    666      form.name = 'form';
    667      form.action = new Request(page_url).url;
    668      frame.contentDocument.body.appendChild(form);
    669      form.submit();
    670    });
    671    assert_equals(frame.contentDocument.body.textContent,
    672                  'method = POST, isReloadNavigation = false');
    673    await new Promise((resolve) => {
    674      frame.addEventListener('load', resolve);
    675      frame.contentWindow.location.reload();
    676    });
    677    assert_equals(frame.contentDocument.body.textContent,
    678                  'method = POST, isReloadNavigation = true');
    679  }, 'FetchEvent#request.isReloadNavigation is true (POST + location.reload())');
    680 
    681 promise_test(async (t) => {
    682    const page_url = 'resources/simple.html?isReloadNavigation';
    683    const anotherUrl = new Request('resources/simple.html').url;
    684    let frame = await with_iframe(page_url);
    685    t.add_cleanup(() => { frame.remove(); });
    686    assert_equals(frame.contentDocument.body.textContent,
    687                  'method = GET, isReloadNavigation = false');
    688    // Use step_timeout(0) to ensure the history entry is created for Blink
    689    // and WebKit. See https://bugs.webkit.org/show_bug.cgi?id=42861.
    690    await wait(0);
    691    await new Promise((resolve) => {
    692      frame.addEventListener('load', resolve);
    693      frame.src = anotherUrl;
    694    });
    695    assert_equals(frame.contentDocument.body.textContent, "Here's a simple html file.\n");
    696    await new Promise((resolve) => {
    697      frame.addEventListener('load', resolve);
    698      frame.contentWindow.history.go(-1);
    699    });
    700    assert_equals(frame.contentDocument.body.textContent,
    701                  'method = GET, isReloadNavigation = false');
    702    await new Promise((resolve) => {
    703      frame.addEventListener('load', resolve);
    704      frame.contentWindow.history.go(0);
    705    });
    706    assert_equals(frame.contentDocument.body.textContent,
    707                  'method = GET, isReloadNavigation = true');
    708    await new Promise((resolve) => {
    709      frame.addEventListener('load', resolve);
    710      frame.contentWindow.history.go(1);
    711    });
    712    assert_equals(frame.contentDocument.body.textContent, "Here's a simple html file.\n");
    713  }, 'FetchEvent#request.isReloadNavigation is true (with history traversal)');
    714 
    715 promise_test(async (t) => {
    716    const page_url = 'resources/simple.html?isHistoryNavigation';
    717    const anotherUrl = new Request('resources/simple.html?ignore').url;
    718    const frame = await with_iframe(page_url);
    719    t.add_cleanup(() => { frame.remove(); });
    720    assert_equals(frame.contentDocument.body.textContent,
    721                  'method = GET, isHistoryNavigation = false');
    722    // Use step_timeout(0) to ensure the history entry is created for Blink
    723    // and WebKit. See https://bugs.webkit.org/show_bug.cgi?id=42861.
    724    await wait(0);
    725    await new Promise((resolve) => {
    726      frame.addEventListener('load', resolve);
    727      frame.src = anotherUrl;
    728    });
    729    assert_equals(frame.contentDocument.body.textContent, "Here's a simple html file.\n");
    730    await new Promise((resolve) => {
    731      frame.addEventListener('load', resolve);
    732      frame.contentWindow.history.go(-1);
    733    });
    734    assert_equals(frame.contentDocument.body.textContent,
    735                  'method = GET, isHistoryNavigation = true');
    736  }, 'FetchEvent#request.isHistoryNavigation is true (with history.go(-1))');
    737 
    738 promise_test(async (t) => {
    739    const page_url = 'resources/simple.html?isHistoryNavigation';
    740    const anotherUrl = new Request('resources/simple.html?ignore').url;
    741    const frame = await with_iframe(anotherUrl);
    742    t.add_cleanup(() => { frame.remove(); });
    743    assert_equals(frame.contentDocument.body.textContent, "Here's a simple html file.\n");
    744    // Use step_timeout(0) to ensure the history entry is created for Blink
    745    // and WebKit. See https://bugs.webkit.org/show_bug.cgi?id=42861.
    746    await wait(0);
    747    await new Promise((resolve) => {
    748      frame.addEventListener('load', resolve);
    749      frame.src = page_url;
    750    });
    751    assert_equals(frame.contentDocument.body.textContent,
    752                  'method = GET, isHistoryNavigation = false');
    753    await new Promise((resolve) => {
    754      frame.addEventListener('load', resolve);
    755      frame.contentWindow.history.go(-1);
    756    });
    757    await new Promise((resolve) => {
    758      frame.addEventListener('load', resolve);
    759      frame.contentWindow.history.go(1);
    760    });
    761    assert_equals(frame.contentDocument.body.textContent,
    762                  'method = GET, isHistoryNavigation = true');
    763  }, 'FetchEvent#request.isHistoryNavigation is true (with history.go(1))');
    764 
    765 promise_test(async (t) => {
    766    const page_url = 'resources/simple.html?isHistoryNavigation';
    767    const anotherUrl = new Request('resources/simple.html?ignore').url;
    768    const frame =  await with_iframe(anotherUrl);
    769    t.add_cleanup(() => { frame.remove(); });
    770    assert_equals(frame.contentDocument.body.textContent, "Here's a simple html file.\n");
    771    // Use step_timeout(0) to ensure the history entry is created for Blink
    772    // and WebKit. See https://bugs.webkit.org/show_bug.cgi?id=42861.
    773    await wait(0);
    774    await new Promise((resolve) => {
    775      frame.addEventListener('load', resolve);
    776      frame.src = page_url;
    777    });
    778    assert_equals(frame.contentDocument.body.textContent,
    779                  'method = GET, isHistoryNavigation = false');
    780    await new Promise((resolve) => {
    781      frame.addEventListener('load', resolve);
    782      frame.contentWindow.history.go(-1);
    783    });
    784    await new Promise((resolve) => {
    785      frame.addEventListener('load', resolve);
    786      frame.contentWindow.history.go(1);
    787    });
    788    assert_equals(frame.contentDocument.body.textContent,
    789                  'method = GET, isHistoryNavigation = true');
    790    await new Promise((resolve) => {
    791      frame.addEventListener('load', resolve);
    792      frame.contentWindow.history.go(0);
    793    });
    794    assert_equals(frame.contentDocument.body.textContent,
    795                  'method = GET, isHistoryNavigation = false');
    796  }, 'FetchEvent#request.isHistoryNavigation is false (with history.go(0))');
    797 
    798 promise_test(async (t) => {
    799    const page_url = 'resources/simple.html?isHistoryNavigation';
    800    const anotherUrl = new Request('resources/simple.html?ignore').url;
    801    const frame = await with_iframe(anotherUrl);
    802    t.add_cleanup(() => { frame.remove(); });
    803    assert_equals(frame.contentDocument.body.textContent, "Here's a simple html file.\n");
    804    // Use step_timeout(0) to ensure the history entry is created for Blink
    805    // and WebKit. See https://bugs.webkit.org/show_bug.cgi?id=42861.
    806    await wait(0);
    807    await new Promise((resolve) => {
    808      frame.addEventListener('load', resolve);
    809      frame.src = page_url;
    810    });
    811    assert_equals(frame.contentDocument.body.textContent,
    812                  'method = GET, isHistoryNavigation = false');
    813    await new Promise((resolve) => {
    814      frame.addEventListener('load', resolve);
    815      frame.contentWindow.history.go(-1);
    816    });
    817    await new Promise((resolve) => {
    818      frame.addEventListener('load', resolve);
    819      frame.contentWindow.history.go(1);
    820    });
    821    assert_equals(frame.contentDocument.body.textContent,
    822                  'method = GET, isHistoryNavigation = true');
    823    await new Promise((resolve) => {
    824      frame.addEventListener('load', resolve);
    825      frame.contentWindow.location.reload();
    826    });
    827    assert_equals(frame.contentDocument.body.textContent,
    828                  'method = GET, isHistoryNavigation = false');
    829  }, 'FetchEvent#request.isHistoryNavigation is false (with location.reload)');
    830 
    831 promise_test(async (t) => {
    832    const page_url = 'resources/simple.html?isHistoryNavigation';
    833    const anotherUrl = new Request('resources/simple.html?ignore').url;
    834    const oneAnotherUrl = new Request('resources/simple.html?ignore2').url;
    835 
    836    const frame = await with_iframe(page_url);
    837    t.add_cleanup(() => { frame.remove(); });
    838    assert_equals(frame.contentDocument.body.textContent,
    839                  'method = GET, isHistoryNavigation = false');
    840    // Use step_timeout(0) to ensure the history entry is created for Blink
    841    // and WebKit. See https://bugs.webkit.org/show_bug.cgi?id=42861.
    842    await wait(0);
    843    await new Promise((resolve) => {
    844      frame.addEventListener('load', resolve);
    845      frame.src = anotherUrl;
    846    });
    847    assert_equals(frame.contentDocument.body.textContent, "Here's a simple html file.\n");
    848    await wait(0);
    849    await new Promise((resolve) => {
    850      frame.addEventListener('load', resolve);
    851      frame.src = oneAnotherUrl;
    852    });
    853    assert_equals(frame.contentDocument.body.textContent, "Here's a simple html file.\n");
    854    await new Promise((resolve) => {
    855      frame.addEventListener('load', resolve);
    856      frame.contentWindow.history.go(-2);
    857    });
    858    assert_equals(frame.contentDocument.body.textContent,
    859                  'method = GET, isHistoryNavigation = true');
    860  }, 'FetchEvent#request.isHistoryNavigation is true (with history.go(-2))');
    861 
    862 promise_test(async (t) => {
    863    const page_url = 'resources/simple.html?isHistoryNavigation';
    864    const anotherUrl = new Request('resources/simple.html?ignore').url;
    865    const oneAnotherUrl = new Request('resources/simple.html?ignore2').url;
    866    const frame = await with_iframe(anotherUrl);
    867    t.add_cleanup(() => { frame.remove(); });
    868    assert_equals(frame.contentDocument.body.textContent, "Here's a simple html file.\n");
    869    // Use step_timeout(0) to ensure the history entry is created for Blink
    870    // and WebKit. See https://bugs.webkit.org/show_bug.cgi?id=42861.
    871    await wait(0);
    872    await new Promise((resolve) => {
    873      frame.addEventListener('load', resolve);
    874      frame.src = oneAnotherUrl;
    875    });
    876    assert_equals(frame.contentDocument.body.textContent, "Here's a simple html file.\n");
    877    await wait(0);
    878    await new Promise((resolve) => {
    879      frame.addEventListener('load', resolve);
    880      frame.src = page_url;
    881    });
    882    assert_equals(frame.contentDocument.body.textContent,
    883                  'method = GET, isHistoryNavigation = false');
    884    await new Promise((resolve) => {
    885      frame.addEventListener('load', resolve);
    886      frame.contentWindow.history.go(-2);
    887    });
    888    await new Promise((resolve) => {
    889      frame.addEventListener('load', resolve);
    890      frame.contentWindow.history.go(2);
    891    });
    892    assert_equals(frame.contentDocument.body.textContent,
    893                  'method = GET, isHistoryNavigation = true');
    894  }, 'FetchEvent#request.isHistoryNavigation is true (with history.go(2))');
    895 
    896 promise_test(async (t) => {
    897    const page_url = 'resources/simple.html?isHistoryNavigation';
    898    const anotherUrl = new Request('resources/simple.html?ignore').url;
    899    const frame = await with_iframe(page_url);
    900    t.add_cleanup(() => { frame.remove(); });
    901    assert_equals(frame.contentDocument.body.textContent,
    902                  'method = GET, isHistoryNavigation = false');
    903    await new Promise((resolve) => {
    904      frame.addEventListener('load', resolve);
    905      const form = frame.contentDocument.createElement('form');
    906      form.method = 'POST';
    907      form.name = 'form';
    908      form.action = new Request(page_url).url;
    909      frame.contentDocument.body.appendChild(form);
    910      form.submit();
    911    });
    912    assert_equals(frame.contentDocument.body.textContent,
    913                  'method = POST, isHistoryNavigation = false');
    914    // Use step_timeout(0) to ensure the history entry is created for Blink
    915    // and WebKit. See https://bugs.webkit.org/show_bug.cgi?id=42861.
    916    await wait(0);
    917    await new Promise((resolve) => {
    918      frame.addEventListener('load', resolve);
    919      frame.src = anotherUrl;
    920    });
    921    assert_equals(frame.contentDocument.body.textContent, "Here's a simple html file.\n");
    922    await wait(0);
    923    await new Promise((resolve) => {
    924      frame.addEventListener('load', resolve);
    925      frame.contentWindow.history.go(-1);
    926    });
    927    assert_equals(frame.contentDocument.body.textContent,
    928                  'method = POST, isHistoryNavigation = true');
    929  }, 'FetchEvent#request.isHistoryNavigation is true (POST + history.go(-1))');
    930 
    931 // When service worker responds with a Response, no XHR upload progress
    932 // events are delivered.
    933 promise_test(async t => {
    934    const page_url = 'resources/simple.html?ignore-for-request-body-string';
    935    const frame = await with_iframe(page_url);
    936    t.add_cleanup(() => { frame.remove(); });
    937 
    938    const xhr = new frame.contentWindow.XMLHttpRequest();
    939    xhr.open('POST', 'simple.html?request-body');
    940    xhr.upload.addEventListener('progress', t.unreached_func('progress'));
    941    xhr.upload.addEventListener('error', t.unreached_func('error'));
    942    xhr.upload.addEventListener('abort', t.unreached_func('abort'));
    943    xhr.upload.addEventListener('timeout', t.unreached_func('timeout'));
    944    xhr.upload.addEventListener('load', t.unreached_func('load'));
    945    xhr.upload.addEventListener('loadend', t.unreached_func('loadend'));
    946    xhr.send('i am the request body');
    947 
    948    await new Promise((resolve) => xhr.addEventListener('load', resolve));
    949  }, 'XHR upload progress events for response coming from SW');
    950 
    951 // Upload progress events should be delivered for the network fallback case.
    952 promise_test(async t => {
    953    const page_url = 'resources/simple.html?ignore-for-request-body-string';
    954    const frame = await with_iframe(page_url);
    955    t.add_cleanup(() => { frame.remove(); });
    956 
    957    let progress = false;
    958    let load = false;
    959    let loadend = false;
    960 
    961    const xhr = new frame.contentWindow.XMLHttpRequest();
    962    xhr.open('POST', '/fetch/api/resources/echo-content.py?ignore');
    963    xhr.upload.addEventListener('progress', () => progress = true);
    964    xhr.upload.addEventListener('error', t.unreached_func('error'));
    965    xhr.upload.addEventListener('abort', t.unreached_func('abort'));
    966    xhr.upload.addEventListener('timeout', t.unreached_func('timeout'));
    967    xhr.upload.addEventListener('load', () => load = true);
    968    xhr.upload.addEventListener('loadend', () => loadend = true);
    969    xhr.send('i am the request body');
    970 
    971    await new Promise((resolve) => xhr.addEventListener('load', resolve));
    972    assert_true(progress, 'progress');
    973    assert_true(load, 'load');
    974    assert_true(loadend, 'loadend');
    975  }, 'XHR upload progress events for network fallback');
    976 
    977 promise_test(async t => {
    978    // Set page_url to "?ignore" so the service worker falls back to network
    979    // for the main resource request, and add a suffix to avoid colliding
    980    // with other tests.
    981    const page_url = 'resources/?ignore-for-request-body-fallback-string';
    982 
    983    const frame = await with_iframe(page_url);
    984    t.add_cleanup(() => { frame.remove(); });
    985    // Add "?clone-and-ignore" so the service worker falls back to
    986    // echo-content.py.
    987    const echo_url = '/fetch/api/resources/echo-content.py?status=421';
    988    const response = await frame.contentWindow.fetch(echo_url, {
    989        method: 'POST',
    990        body: 'text body'
    991    });
    992    assert_equals(response.status, 421);
    993    const text = await response.text();
    994    assert_equals(
    995        text,
    996        'text body. Request was sent 1 times.',
    997        'the network fallback request should include the request body');
    998  }, 'Fetch with POST with text on sw 421 response should not be retried.');
    999 </script>
   1000 </body>