-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add tests to verify preload caching behavior
Check that preloaded resources are reused/ignored based on the parameters available to a preload link (href, crossorigin, as), and do not rely on other parameters (e.g. whether a script is a module). See whatwg/html#7260 and whatwg/fetch#1342
- Loading branch information
Showing
3 changed files
with
200 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
<script src="/resources/testharness.js"></script> | ||
<script src="/resources/testharnessreport.js"></script> | ||
<script src="/common/utils.js"></script> | ||
<script src="/preload/resources/preload_helper.js"></script> | ||
<body> | ||
<script> | ||
|
||
const invalidImages = { | ||
'invalid data': '/preload/resources/echo-with-cors.py?type=image/svg+xml&content=junk', | ||
missing: '/nothing.png' | ||
} | ||
|
||
Object.entries(invalidImages).forEach(([name, url]) => { | ||
promise_test(async t => { | ||
const invalidImageURL = getAbsoluteURL(url) | ||
const link = document.createElement('link'); | ||
link.rel = 'preload'; | ||
link.as = 'image'; | ||
link.href = url; | ||
document.head.appendChild(link); | ||
t.add_cleanup(() => link.remove()); | ||
await new Promise(resolve => { | ||
const img = document.createElement('img'); | ||
img.src = url; | ||
img.onerror = resolve; | ||
document.body.appendChild(img); | ||
t.add_cleanup(() => img.remove()); | ||
}); | ||
verifyNumberOfResourceTimingEntries(url, 1); | ||
}, `Preloading an invalid image (${name}) should preload and not re-fetch`) | ||
}) | ||
|
||
</script> | ||
</body> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
<script src="/resources/testharness.js"></script> | ||
<script src="/resources/testharnessreport.js"></script> | ||
<script src="/common/utils.js"></script> | ||
<script src="/preload/resources/preload_helper.js"></script> | ||
<script> | ||
|
||
const crossOriginHost = 'https://{{host}}:{{ports[https][0]}}' | ||
|
||
function createEchoURL(text, type) { | ||
return `/preload/resources/echo-with-cors.py?type=${ | ||
encodeURIComponent(type)}&content=${ | ||
encodeURIComponent(text)}` | ||
} | ||
const urls = { | ||
image: createEchoURL('<svg xmlns="http://www.w3.org/2000/svg" width="2" height="2" />', 'image/svg+xml'), | ||
font: '/preload/resources/Ahem.ttf?x', | ||
text: createEchoURL('hello', 'text/plain'), | ||
script: createEchoURL('function dummy() { }', 'application/javascript'), | ||
style: createEchoURL('.cls { }', 'text/css'), | ||
} | ||
|
||
const resourceTypes = { | ||
image: {url: urls.image, as: 'image'}, | ||
invalidImage: {url: urls.invalidImage, as: 'image'}, | ||
font: {url: urls.font, as: 'font', config: 'anonymous'}, | ||
backgroundImage: {url: urls.image, as: 'image', config: 'no-cors'}, | ||
fetch: {url: urls.text, as: 'fetch'}, | ||
script: {url: urls.script, as: 'script'}, | ||
module: {url: urls.script, as: 'script'}, | ||
// style: {url: urls.style, as: 'style'}, | ||
} | ||
|
||
const configs = { | ||
'same-origin': {crossOrigin: false, attributes: {}}, | ||
'no-cors': {crossOrigin: true, attributes: {}}, | ||
'anonymous': {crossOrigin: true, attributes: {crossOrigin: 'anonymous'}}, | ||
'use-credentials': {crossOrigin: true, attributes: {crossOrigin: 'use-credentials'}}, | ||
} | ||
|
||
function preload(attributes, t) { | ||
const link = document.createElement('link'); | ||
link.rel = "preload"; | ||
Object.entries(attributes).forEach(([key, value]) => { | ||
if (value) | ||
link[key] = value; | ||
}); | ||
|
||
document.head.appendChild(link); | ||
// t.add_cleanup(() => link.remove()); | ||
return new Promise(resolve => link.addEventListener('load', resolve)); | ||
} | ||
|
||
const loaders = { | ||
image: (href, attr, t) => { | ||
const img = document.createElement('img'); | ||
Object.entries(attr).forEach(([key, value]) => { | ||
img[key] = value; | ||
}); | ||
|
||
img.src = href | ||
|
||
document.body.appendChild(img); | ||
t.add_cleanup(() => img.remove()); | ||
return new Promise(resolve => { | ||
img.addEventListener('load', resolve) | ||
img.addEventListener('error', resolve) | ||
}); | ||
}, | ||
font: (href, attr, t) => { | ||
const style = document.createElement('style'); | ||
style.innerHTML = `@font-face { | ||
font-family: 'MyFont'; | ||
src: url('${href}'); | ||
}`; | ||
|
||
document.head.appendChild(style); | ||
t.add_cleanup(() => style.remove()); | ||
const p = document.createElement('p'); | ||
p.style.fontFamily = 'MyFont'; | ||
document.body.appendChild(p); | ||
t.add_cleanup(() => p.remove()); | ||
}, | ||
shape: (href, attr, t) => { | ||
const div = document.createElement('div'); | ||
div.style.shapeOutside = `url(${href})`; | ||
document.body.appendChild(div); | ||
t.add_cleanup(() => div.remove()); | ||
}, | ||
backgroundImage: (href, attr, t) => { | ||
const div = document.createElement('div'); | ||
div.style.background = `url(${href})`; | ||
document.body.appendChild(div); | ||
t.add_cleanup(() => div.remove()); | ||
}, | ||
fetch: async (href, attr, t) => { | ||
const options = {mode: attr.crossOrigin ? 'cors' : 'no-cors', | ||
credentials: !attr.crossOrigin || attr.crossOrigin === 'anonymous' ? 'omit' : 'include'} | ||
|
||
const response = await fetch(href, options) | ||
await response.text(); | ||
}, | ||
script: async (href, attr, t) => { | ||
const script = document.createElement('script'); | ||
t.add_cleanup(() => script.remove()); | ||
if (attr.crossOrigin) | ||
script.setAttribute('crossorigin', attr.crossOrigin); | ||
script.src = href; | ||
document.body.appendChild(script); | ||
await new Promise(resolve => { script.onload = resolve }); | ||
}, | ||
module: async (href, attr, t) => { | ||
const script = document.createElement('script'); | ||
script.type = 'module'; | ||
t.add_cleanup(() => script.remove()); | ||
if (attr.crossOrigin) | ||
script.setAttribute('crossorigin', attr.crossOrigin); | ||
script.src = href; | ||
document.body.appendChild(script); | ||
await new Promise(resolve => { script.onload = resolve }); | ||
}, | ||
style: (href, attr, t) => { | ||
const style = document.createElement('link'); | ||
style.rel = 'stylesheet'; | ||
t.add_cleanup(() => style.remove()); | ||
if (attr.crossOrigin) | ||
style.setAttribute('crossorigin', attr.crossOrigin); | ||
document.body.appendChild(style); | ||
} | ||
|
||
} | ||
|
||
function preload_reuse_test(type, as, url, preloadConfig, resourceConfig) { | ||
const expected = (preloadConfig === resourceConfig) ? "reuse" : "discard"; | ||
const key = token(); | ||
const href = getAbsoluteURL(`${ | ||
(configs[resourceConfig].crossOrigin ? crossOriginHost : '') + url | ||
}&${token()}`) | ||
promise_test(async t => { | ||
await preload({href, as, ...configs[preloadConfig].attributes}, t); | ||
await loaders[as](href, configs[resourceConfig].attributes, t) | ||
verifyNumberOfResourceTimingEntries(href, expected === "reuse" ? 1 : 2) | ||
}, `Loading ${type} (${resourceConfig}) with link (${preloadConfig}) should ${expected} the preloaded response`); | ||
} | ||
|
||
for (const resourceTypeName in resourceTypes) { | ||
const resourceInfo = resourceTypes[resourceTypeName]; | ||
const configNames = resourceInfo.config ? [resourceInfo.config, 'same-origin'] : Object.keys(configs) | ||
for (const resourceConfigName of configNames) { | ||
for (const preloadConfigName of Object.keys(configs)) { | ||
if (resourceConfigName === 'same-origin' && preloadConfigName !== 'same-origin') | ||
continue; | ||
if (resourceConfigName !== 'same-origin' && preloadConfigName === 'same-origin') | ||
continue; | ||
preload_reuse_test(resourceTypeName, resourceInfo.as, resourceInfo.url, preloadConfigName, resourceConfigName); | ||
} | ||
} | ||
} | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
def main(request, response): | ||
response.headers.set(b"Content-Type", request.GET.first(b"type")) | ||
origin = request.headers.get('Origin') | ||
if origin is not None: | ||
response.headers.set(b"Access-Control-Allow-Origin", origin) | ||
response.headers.set(b"Access-Control-Allow-Credentials", b"true") | ||
|
||
return request.GET.first(b"content") |