Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
- This WPT test may be referenced by the following Test IDs:
- /html/semantics/document-metadata/the-link-element/link-multiple-load-events.html - WPT Dashboard Interop Dashboard
<!DOCTYPE html>
<link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/utils.js"></script>
<script>
function getTimeoutPromise(t, msg) {
return new Promise((resolve, reject) => {
t.step_timeout(() => reject(`Timeout reached: ${msg}`), 2000);
});
}
// The tests generated from this loop are important to ensure that a link's load
// or error event handler never incurs infinite recursion by virtue of
// re-setting a link element's attribute to the value it already has. This is
// Chromium.
const attributes_to_test = ['rel', 'href', 'type', 'media'];
for (const attribute of attributes_to_test) {
promise_test(async t => {
const id = token();
const link = document.createElement('link');
let count = 0;
let response = null;
link.onerror = t.unreached_func('Sheet should load successfully');
const firstLoadPromise = new Promise(resolve => {
link.onload = resolve;
});
// Set all attributes to a sensible default, so when we re-assign one of
// them (`attribute`) idempotently later, the value doesn't change.
link.rel = 'stylesheet';
link.media = 'all';
link.type = 'text/css';
link.href = new URL(`stylesheet.py?id=${id}`, location.href);
document.head.append(link);
t.add_cleanup(() => {
link.remove();
});
await Promise.race([firstLoadPromise, getTimeoutPromise(t, 'first resource')]);
response = await fetch(`stylesheet.py?id=${id}&count=foo`);
count = await response.text();
assert_equals(count, '1', "server sees first style sheet request");
const secondLoadPromise = new Promise((resolve, reject) => {
link.onload = () => reject('second load event unexpectedly fired');
});
const expectedTimeoutPromise = new Promise(resolve => {
t.step_timeout(resolve, 2000);
});
link[attribute] = link[attribute];
await Promise.race([expectedTimeoutPromise, secondLoadPromise]);
response = await fetch(`stylesheet.py?id=${id}&count=foo`);
count = await response.text();
assert_equals(count, '0',
"server does not see second request to the same style sheet");
}, `<link> cannot request the same resource twice by touching the ` +
`'${attribute}' attribute, if its value never changes`);
}
promise_test(async t => {
const id = token();
const link = document.createElement('link');
link.rel = 'preload';
link.as = 'style';
document.head.append(link);
t.add_cleanup(() => {
link.remove();
});
let count = 0;
let response = null;
link.onerror = t.unreached_func('Sheet should load successfully');
const firstLoadPromise = new Promise(resolve => {
link.onload = resolve;
});
link.href = `stylesheet.py?id=${id}`;
await Promise.race([firstLoadPromise, getTimeoutPromise(t, 'first resource')]);
response = await fetch(`stylesheet.py?id=${id}&count=foo`);
count = await response.text();
assert_equals(count, '1', "server sees preload request");
const secondLoadPromise = new Promise(resolve => {
link.onload = resolve;
});
// Switching from `rel=preload` => `rel=stylesheet` triggers the stylesheet
// processing model. The resource loads from the preload cache and never
// touches the network, but the load event does fire again.
link.rel = 'stylesheet';
await Promise.race([secondLoadPromise,
getTimeoutPromise(t, 'rel=stylesheet using preload cache')]);
response = await fetch(`stylesheet.py?id=${id}&count=foo`);
count = await response.text();
assert_equals(count, '0', "server does not see second request to the same style sheet");
}, "<link> load event can fire twice for the same href resource, based on " +
"'rel' attribute mutations");
promise_test(async t => {
const link = document.createElement('link');
link.rel = 'stylesheet';
document.head.append(link);
t.add_cleanup(() => {
link.remove();
});
link.onerror = t.unreached_func('Sheet should load successfully');
const firstLoadPromise = new Promise(resolve => {
link.onload = resolve;
});
link.href = 'style.css?first';
await Promise.race([firstLoadPromise, getTimeoutPromise(t, 'first resource')]);
const secondLoadPromise = new Promise(resolve => {
link.onload = resolve;
});
link.href = 'style.css?second';
await Promise.race([secondLoadPromise, getTimeoutPromise(t, 'second resource')]);
const thirdLoadPromise = new Promise(resolve => {
link.onload = resolve;
});
link.href = 'style.css?third';
await Promise.race([thirdLoadPromise, getTimeoutPromise(t, 'third resource')]);
}, "<link> load event fires for each DIFFERENT stylesheet it loads");
</script>
</head>
</html>