| <!doctype html> | |
| <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 src="/common/get-host-info.sub.js"></script> | |
| <script> | |
| const {HTTPS_REMOTE_ORIGIN} = get_host_info(); | |
| 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/font.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'}, | |
| 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 = { | |
| // The requested URL is from the same origin | |
| 'same-origin': {crossOrigin: false, attributes: {}}, | |
| // The requested URL is from a remote origin, without CORS | |
| 'no-cors': {crossOrigin: true, attributes: {}}, | |
| // The requested URL is from a remote origin, with CORS (anonymous) | |
| 'anonymous': {crossOrigin: true, attributes: {crossOrigin: 'anonymous'}}, | |
| // The requested URL is from a remote origin, with CORS (including credentials) | |
| '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 ? HTTPS_REMOTE_ORIGIN : '') + 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, resourceInfo] of Object.entries(resourceTypes)) { | |
| const configNames = resourceInfo.config ? [resourceInfo.config, 'same-origin'] : Object.keys(configs) | |
| for (const resourceConfigName of configNames) { | |
| for (const preloadConfigName of Object.keys(configs)) { | |
| // Same-origin requests ignore their CORS attributes, so no need to match all of them. | |
| if ((resourceConfigName === 'same-origin' && preloadConfigName === 'same-origin') || | |
| (resourceConfigName !== 'same-origin' && preloadConfigName !== 'same-origin')) | |
| preload_reuse_test(resourceTypeName, resourceInfo.as, resourceInfo.url, preloadConfigName, resourceConfigName); | |
| } | |
| } | |
| } | |
| </script> |