223 lines
8.4 KiB
HTML
223 lines
8.4 KiB
HTML
<!DOCTYPE html>
|
|
<meta charset="utf-8">
|
|
<head>
|
|
<title>link rel=preload with various errors/non-errors</title>
|
|
<meta name="timeout" content="long">
|
|
<script src="/resources/testharness.js"></script>
|
|
<script src="/resources/testharnessreport.js"></script>
|
|
<script src="resources/preload_helper.js"></script>
|
|
<meta http-equiv="Content-Security-Policy"
|
|
content="default-src 'self' http://{{hosts[alt][]}}:{{ports[http][0]}} 'unsafe-inline'">
|
|
<script>
|
|
// For various error/non-error network responses,, this test checks
|
|
// - load/error events fired on <link rel=preload>,
|
|
// - load/error events on main requests (e.g. <img>), and
|
|
// - preloads are reused for main requests
|
|
// (by verifyLoadedAndNoDoubleDownload()).
|
|
//
|
|
// While this test expects <link rel=preload> error events only for network errors
|
|
// as specified in
|
|
// https://html.spec.whatwg.org/multipage/links.html#link-type-preload:fetch-and-process-the-linked-resource
|
|
// https://github.com/whatwg/html/pull/7799
|
|
// the actual browsers' behavior is different, and the feasibility of changing
|
|
// the behavior has not yet been investigated.
|
|
// https://github.com/whatwg/html/issues/1142.
|
|
|
|
setup({allow_uncaught_exception: true});
|
|
|
|
function preload(t, as, url, shouldPreloadSucceed) {
|
|
return new Promise(resolve => {
|
|
const link = document.createElement('link');
|
|
link.setAttribute('rel', 'preload');
|
|
link.setAttribute('as', as);
|
|
link.setAttribute('crossorigin', 'anonymous');
|
|
link.setAttribute('href', url);
|
|
link.onload = t.step_func_done(() => {
|
|
resolve();
|
|
if (!shouldPreloadSucceed) {
|
|
assert_unreached('preload onload');
|
|
}
|
|
});
|
|
link.onerror = t.step_func_done(() => {
|
|
resolve();
|
|
if (shouldPreloadSucceed) {
|
|
assert_unreached('preload onerror');
|
|
}
|
|
});
|
|
document.head.appendChild(link);
|
|
});
|
|
}
|
|
|
|
function runTest(api, as, description, shouldPreloadSucceed, shouldMainLoadSucceed,
|
|
urlWithoutLabel) {
|
|
description += ' (' + api + ')';
|
|
|
|
const url = new URL(urlWithoutLabel, location.href);
|
|
url.searchParams.set('label', api);
|
|
|
|
const tPreload = async_test(description + ': preload events');
|
|
|
|
promise_test(async t => {
|
|
let messageOnTimeout = 'timeout';
|
|
t.step_timeout(() => t.unreached_func(messageOnTimeout)(), 3000);
|
|
|
|
const preloadPromise = preload(tPreload, as, url, shouldPreloadSucceed);
|
|
|
|
// The main request is started just after preloading is started and thus
|
|
// HTTP response headers and errors are not observed yet.
|
|
let mainPromise;
|
|
if (api === 'image') {
|
|
mainPromise = new Promise(t.step_func((resolve, reject) => {
|
|
const img = document.createElement('img');
|
|
img.setAttribute('crossorigin', 'anonymous');
|
|
img.onload = resolve;
|
|
img.onerror = () => reject(new TypeError('img onerror'));
|
|
img.src = url;
|
|
document.head.appendChild(img);
|
|
}));
|
|
} else if (api === 'style') {
|
|
mainPromise = new Promise(t.step_func((resolve, reject) => {
|
|
const link = document.createElement('link');
|
|
link.setAttribute('rel', 'stylesheet');
|
|
link.setAttribute('crossorigin', 'anonymous');
|
|
link.onload = resolve;
|
|
link.onerror = () => reject(new TypeError('link rel=stylesheet onerror'));
|
|
link.href = url;
|
|
document.head.appendChild(link);
|
|
}));
|
|
} else if (api === 'script') {
|
|
mainPromise = new Promise(t.step_func((resolve, reject) => {
|
|
const script = document.createElement('script');
|
|
script.setAttribute('crossorigin', 'anonymous');
|
|
script.onload = resolve;
|
|
script.onerror = () => reject(new TypeError('script onerror'));
|
|
script.src = url;
|
|
document.head.appendChild(script);
|
|
}));
|
|
} else if (api === 'xhr') {
|
|
mainPromise = new Promise(t.step_func((resolve, reject) => {
|
|
const xhr = new XMLHttpRequest();
|
|
xhr.open('GET', url, true);
|
|
xhr.onload = resolve;
|
|
xhr.onerror = () => reject(new TypeError('XHR onerror'));
|
|
xhr.onabort = t.unreached_func('XHR onabort');
|
|
xhr.send();
|
|
}));
|
|
} else if (api === 'fetch') {
|
|
mainPromise = fetch(url)
|
|
.then(r => {
|
|
messageOnTimeout = 'fetch() completed but text() timeout';
|
|
return r.text();
|
|
});
|
|
} else {
|
|
throw new Error('Unexpected api: ' + api);
|
|
}
|
|
|
|
if (shouldMainLoadSucceed) {
|
|
await mainPromise;
|
|
} else {
|
|
await promise_rejects_js(t, TypeError, mainPromise);
|
|
}
|
|
|
|
// Wait also for <link rel=preload> events.
|
|
// This deflakes `verifyLoadedAndNoDoubleDownload` results.
|
|
await preloadPromise;
|
|
|
|
verifyLoadedAndNoDoubleDownload(url);
|
|
}, description + ': main');
|
|
}
|
|
|
|
const tests = {
|
|
'image': {
|
|
url: '/preload/resources/square.png',
|
|
as: 'image',
|
|
mainLoadWillFailIf404Returned: false
|
|
},
|
|
'style': {
|
|
url: '/preload/resources/dummy.css',
|
|
as: 'style',
|
|
|
|
// https://html.spec.whatwg.org/multipage/semantics.html#default-fetch-and-process-the-linked-resource
|
|
mainLoadWillFailIf404Returned: true
|
|
},
|
|
'script': {
|
|
url: '/preload/resources/dummy.js',
|
|
as: 'script',
|
|
|
|
// https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-script
|
|
mainLoadWillFailIf404Returned: true
|
|
},
|
|
'xhr': {
|
|
url: '/preload/resources/dummy.xml',
|
|
as: 'fetch',
|
|
mainLoadWillFailIf404Returned: false
|
|
},
|
|
'fetch': {
|
|
url: '/preload/resources/dummy.xml',
|
|
as: 'fetch',
|
|
mainLoadWillFailIf404Returned: false
|
|
}
|
|
};
|
|
|
|
for (const api in tests) {
|
|
const url = tests[api].url;
|
|
const as = tests[api].as;
|
|
|
|
// Successful response.
|
|
runTest(api, as, 'success', true, true, url);
|
|
|
|
// Successful response: non-ok status is not considered as a network error,
|
|
// but can fire error events on main requests.
|
|
runTest(api, as, '404', true, !tests[api].mainLoadWillFailIf404Returned,
|
|
url + '?pipe=status(404)');
|
|
|
|
// Successful response: Successful CORS check.
|
|
runTest(api, as, 'CORS', true, true,
|
|
'http://{{hosts[alt][]}}:{{ports[http][0]}}' + url +
|
|
'?pipe=header(Access-Control-Allow-Origin,*)');
|
|
|
|
// A network error: Failed CORS check.
|
|
runTest(api, as, 'CORS-error', false, false,
|
|
'http://{{hosts[alt][]}}:{{ports[http][0]}}' + url);
|
|
|
|
// A network error: Failed CSP check on redirect.
|
|
runTest(api, as, 'CSP-error', false, false,
|
|
'/common/redirect.py?location=http://{{hosts[alt][]}}:{{ports[http][1]}}' +
|
|
url + '?pipe=header(Access-Control-Allow-Origin,*)');
|
|
}
|
|
|
|
// --------------------------------
|
|
// Content error.
|
|
|
|
// Successful response with corrupted image data.
|
|
// Not a network error, but can fire error events for images:
|
|
// https://html.spec.whatwg.org/multipage/images.html#update-the-image-data
|
|
runTest('image', 'image', 'Decode-error', true, false,
|
|
'/preload/resources/dummy.css?pipe=header(Content-Type,image/png)');
|
|
runTest('style', 'style', 'Decode-error', true, true,
|
|
'/preload/resources/dummy.xml?pipe=header(Content-Type,text/css)');
|
|
runTest('script', 'script', 'Decode-error', true, true,
|
|
'/preload/resources/dummy.xml?pipe=header(Content-Type,text/javascript)');
|
|
|
|
// --------------------------------
|
|
// MIME Type error.
|
|
// Some MIME type mismatches are not network errors.
|
|
runTest('image', 'image', 'MIME-error', true, true,
|
|
'/preload/resources/square.png?pipe=header(Content-Type,text/notimage)');
|
|
runTest('script', 'script', 'MIME-error', true, true,
|
|
'/preload/resources/dummy.css?pipe=header(Content-Type,text/notjavascript)');
|
|
// But they fire error events for <link rel=stylesheet>s.
|
|
// https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet:process-the-linked-resource
|
|
runTest('style', 'style', 'MIME-error', true, false,
|
|
'/preload/resources/dummy.js?pipe=header(Content-Type,not/css)');
|
|
|
|
// Other MIME type mismatches are network errors, due to:
|
|
// https://fetch.spec.whatwg.org/#should-response-to-request-be-blocked-due-to-mime-type?
|
|
runTest('script', 'script', 'MIME-blocked', false, false,
|
|
'/preload/resources/dummy.css?pipe=header(Content-Type,image/not-javascript)');
|
|
// https://fetch.spec.whatwg.org/#should-response-to-request-be-blocked-due-to-nosniff?
|
|
runTest('style', 'style', 'MIME-blocked-nosniff', false, false,
|
|
'/preload/resources/dummy.js?pipe=header(Content-Type,not/css)|header(X-Content-Type-Options,nosniff)');
|
|
runTest('script', 'script', 'MIME-blocked-nosniff', false, false,
|
|
'/preload/resources/dummy.css?pipe=header(Content-Type,text/notjavascript)|header(X-Content-Type-Options,nosniff)');
|
|
</script>
|