tor-browser

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

preload-error.sub.html (8604B)


      1 <!DOCTYPE html>
      2 <meta charset="utf-8">
      3 <head>
      4 <title>link rel=preload with various errors/non-errors</title>
      5 <meta name="timeout" content="long">
      6 <script src="/resources/testharness.js"></script>
      7 <script src="/resources/testharnessreport.js"></script>
      8 <script src="resources/preload_helper.js"></script>
      9 <meta http-equiv="Content-Security-Policy"
     10      content="default-src 'self' http://{{hosts[alt][]}}:{{ports[http][0]}} 'unsafe-inline'">
     11 <script>
     12 // For various error/non-error network responses,, this test checks
     13 // - load/error events fired on <link rel=preload>,
     14 // - load/error events on main requests (e.g. <img>), and
     15 // - preloads are reused for main requests
     16 //   (by verifyLoadedAndNoDoubleDownload()).
     17 //
     18 // While this test expects <link rel=preload> error events only for network errors
     19 // as specified in
     20 // https://html.spec.whatwg.org/multipage/links.html#link-type-preload:fetch-and-process-the-linked-resource
     21 // https://github.com/whatwg/html/pull/7799
     22 // the actual browsers' behavior is different, and the feasibility of changing
     23 // the behavior has not yet been investigated.
     24 // https://github.com/whatwg/html/issues/1142.
     25 
     26 setup({allow_uncaught_exception: true});
     27 
     28 function preload(t, as, url, shouldPreloadSucceed) {
     29  return new Promise(resolve => {
     30    const link = document.createElement('link');
     31    link.setAttribute('rel', 'preload');
     32    link.setAttribute('as', as);
     33    link.setAttribute('crossorigin', 'anonymous');
     34    link.setAttribute('href', url);
     35    link.onload = t.step_func_done(() => {
     36      resolve();
     37      if (!shouldPreloadSucceed) {
     38        assert_unreached('preload onload');
     39      }
     40    });
     41    link.onerror = t.step_func_done(() => {
     42      resolve();
     43      if (shouldPreloadSucceed) {
     44        assert_unreached('preload onerror');
     45      }
     46    });
     47    document.head.appendChild(link);
     48  });
     49 }
     50 
     51 function runTest(api, as, description, shouldPreloadSucceed, shouldMainLoadSucceed,
     52                 urlWithoutLabel) {
     53  description += ' (' + api + ')';
     54 
     55  const url = new URL(urlWithoutLabel, location.href);
     56  url.searchParams.set('label', api);
     57 
     58  const tPreload = async_test(description + ': preload events');
     59 
     60  promise_test(async t => {
     61    let messageOnTimeout = 'timeout';
     62    t.step_timeout(() => t.unreached_func(messageOnTimeout)(), 3000);
     63 
     64    const preloadPromise = preload(tPreload, as, url, shouldPreloadSucceed);
     65 
     66    // The main request is started just after preloading is started and thus
     67    // HTTP response headers and errors are not observed yet.
     68    let mainPromise;
     69    if (api === 'image') {
     70      mainPromise = new Promise(t.step_func((resolve, reject) => {
     71          const img = document.createElement('img');
     72          img.setAttribute('crossorigin', 'anonymous');
     73          img.onload = resolve;
     74          img.onerror = () => reject(new TypeError('img onerror'));
     75          img.src = url;
     76          document.head.appendChild(img);
     77      }));
     78    } else if (api === 'style') {
     79      mainPromise = new Promise(t.step_func((resolve, reject) => {
     80          const link = document.createElement('link');
     81          link.setAttribute('rel', 'stylesheet');
     82          link.setAttribute('crossorigin', 'anonymous');
     83          link.onload = resolve;
     84          link.onerror = () => reject(new TypeError('link rel=stylesheet onerror'));
     85          link.href = url;
     86          document.head.appendChild(link);
     87      }));
     88    } else if (api === 'script') {
     89      mainPromise = new Promise(t.step_func((resolve, reject) => {
     90          const script = document.createElement('script');
     91          script.setAttribute('crossorigin', 'anonymous');
     92          script.onload = resolve;
     93          script.onerror = () => reject(new TypeError('script onerror'));
     94          script.src = url;
     95          document.head.appendChild(script);
     96      }));
     97    } else if (api === 'xhr') {
     98      mainPromise = new Promise(t.step_func((resolve, reject) => {
     99          const xhr = new XMLHttpRequest();
    100          xhr.open('GET', url, true);
    101          xhr.onload = resolve;
    102          xhr.onerror = () => reject(new TypeError('XHR onerror'));
    103          xhr.onabort = t.unreached_func('XHR onabort');
    104          xhr.send();
    105      }));
    106    } else if (api === 'fetch') {
    107      mainPromise = fetch(url)
    108        .then(r => {
    109          messageOnTimeout = 'fetch() completed but text() timeout';
    110          return r.text();
    111        });
    112    } else {
    113      throw new Error('Unexpected api: ' + api);
    114    }
    115 
    116    if (shouldMainLoadSucceed) {
    117      await mainPromise;
    118    } else {
    119      await promise_rejects_js(t, TypeError, mainPromise);
    120    }
    121 
    122    // Wait also for <link rel=preload> events.
    123    // This deflakes `verifyLoadedAndNoDoubleDownload` results.
    124    await preloadPromise;
    125 
    126    verifyLoadedAndNoDoubleDownload(url);
    127  }, description + ': main');
    128 }
    129 
    130 const tests = {
    131  'image': {
    132    url: '/preload/resources/square.png',
    133    as: 'image',
    134    mainLoadWillFailIf404Returned: false
    135  },
    136  'style': {
    137    url: '/preload/resources/dummy.css',
    138    as: 'style',
    139 
    140    // https://html.spec.whatwg.org/multipage/semantics.html#default-fetch-and-process-the-linked-resource
    141    mainLoadWillFailIf404Returned: true
    142  },
    143  'script': {
    144    url: '/preload/resources/dummy.js',
    145    as: 'script',
    146 
    147    // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-script
    148    mainLoadWillFailIf404Returned: true
    149  },
    150  'xhr': {
    151    url: '/preload/resources/dummy.xml',
    152    as: 'fetch',
    153    mainLoadWillFailIf404Returned: false
    154  },
    155  'fetch': {
    156    url: '/preload/resources/dummy.xml',
    157    as: 'fetch',
    158    mainLoadWillFailIf404Returned: false
    159  }
    160 };
    161 
    162 for (const api in tests) {
    163  const url = tests[api].url;
    164  const as = tests[api].as;
    165 
    166  // Successful response.
    167  runTest(api, as, 'success', true, true, url);
    168 
    169  // Successful response: non-ok status is not considered as a network error,
    170  // but can fire error events on main requests.
    171  runTest(api, as, '404', true, !tests[api].mainLoadWillFailIf404Returned,
    172      url + '?pipe=status(404)');
    173 
    174  // Successful response: Successful CORS check.
    175  runTest(api, as, 'CORS', true, true,
    176      'http://{{hosts[alt][]}}:{{ports[http][0]}}' + url +
    177      '?pipe=header(Access-Control-Allow-Origin,*)');
    178 
    179  // A network error: Failed CORS check.
    180  runTest(api, as, 'CORS-error', false, false,
    181      'http://{{hosts[alt][]}}:{{ports[http][0]}}' + url);
    182 
    183  // A network error: Failed CSP check on redirect.
    184  runTest(api, as, 'CSP-error', false, false,
    185      '/common/redirect.py?location=http://{{hosts[alt][]}}:{{ports[http][1]}}' +
    186      url + '?pipe=header(Access-Control-Allow-Origin,*)');
    187 }
    188 
    189 // --------------------------------
    190 // Content error.
    191 
    192 // Successful response with corrupted image data.
    193 // Not a network error, but can fire error events for images:
    194 // https://html.spec.whatwg.org/multipage/images.html#update-the-image-data
    195 runTest('image', 'image', 'Decode-error', true, false,
    196    '/preload/resources/dummy.css?pipe=header(Content-Type,image/png)');
    197 runTest('style', 'style', 'Decode-error', true, true,
    198    '/preload/resources/dummy.xml?pipe=header(Content-Type,text/css)');
    199 runTest('script', 'script', 'Decode-error', true, true,
    200    '/preload/resources/dummy.xml?pipe=header(Content-Type,text/javascript)');
    201 
    202 // --------------------------------
    203 // MIME Type error.
    204 // Some MIME type mismatches are not network errors.
    205 runTest('image', 'image', 'MIME-error', true, true,
    206    '/preload/resources/square.png?pipe=header(Content-Type,text/notimage)');
    207 runTest('script', 'script', 'MIME-error', true, true,
    208    '/preload/resources/dummy.css?pipe=header(Content-Type,text/notjavascript)');
    209 // But they fire error events for <link rel=stylesheet>s.
    210 // https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet:process-the-linked-resource
    211 runTest('style', 'style', 'MIME-error', true, false,
    212    '/preload/resources/dummy.js?pipe=header(Content-Type,not/css)');
    213 
    214 // Other MIME type mismatches are network errors, due to:
    215 // https://fetch.spec.whatwg.org/#should-response-to-request-be-blocked-due-to-mime-type?
    216 runTest('script', 'script', 'MIME-blocked', false, false,
    217    '/preload/resources/dummy.css?pipe=header(Content-Type,image/not-javascript)');
    218 // https://fetch.spec.whatwg.org/#should-response-to-request-be-blocked-due-to-nosniff?
    219 runTest('style', 'style', 'MIME-blocked-nosniff', false, false,
    220    '/preload/resources/dummy.js?pipe=header(Content-Type,not/css)|header(X-Content-Type-Options,nosniff)');
    221 runTest('script', 'script', 'MIME-blocked-nosniff', false, false,
    222    '/preload/resources/dummy.css?pipe=header(Content-Type,text/notjavascript)|header(X-Content-Type-Options,nosniff)');
    223 </script>