Skip to content

Commit

Permalink
Add multiple tests for prefetch behavior
Browse files Browse the repository at this point in the history
See whatwg/html#8111 (comment)

Tests that:
* preload/prefetch-cache.html
  No 5 minute rule (cache headers must be respected)

* preload/prefetch-document.html
  No special treatment of as=document
  Whether Accept is left as its default value.
  Storage partitioning is applied, including for documents

* preload/prefetch-load-event.html
  Does not block the load event

* preload/prefetch-headers.html
  Whether any additional headers (Sec-Purpose, Purpose, X-moz, X-Purpose) are sent (depending on what we put in the spec)

* preload/prefetch-types.html
  Always uses prefetch destination, and ignores as=""
  • Loading branch information
noamr committed Aug 30, 2022
1 parent 4530a81 commit bd647fb
Show file tree
Hide file tree
Showing 8 changed files with 303 additions and 0 deletions.
29 changes: 29 additions & 0 deletions preload/prefetch-cache.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!DOCTYPE html>
<title>Ensures that prefetch respects HTTP cache semantics</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/utils.js"></script>
<script src="resources/prefetch-helper.js"></script>
<body>
<script>

async function prefetch_and_count(cacheControl, t) {
const {href} = await prefetch({"cache-control": cacheControl, "type": "text/plain", content: "123"}, t);
await fetch(href);
const info = await get_prefetch_info(href);
return info.length;
}

promise_test(async t => {
const result = await prefetch_and_count("max-age=604800", t);
assert_equals(result, 1);
}, "Prefetch should populate the HTTP cache");

for (const cacheControl of ["no-cache", "no-store", "max-age=0"]) {
promise_test(async t => {
const result = await prefetch_and_count(cacheControl, t);
assert_equals(result, 2);
}, `Prefetch should not respsect cache-control: ${cacheControl}`);
}
</script>
</body>
104 changes: 104 additions & 0 deletions preload/prefetch-document.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<!DOCTYPE html>
<title>Ensures that prefetch works with documents</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/utils.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="/common/dispatcher/dispatcher.js"></script>
<script src="resources/prefetch-helper.js"></script>
<body>
<script>

const {ORIGIN, REMOTE_ORIGIN, HTTP_NOTSAMESITE_ORIGIN} = get_host_info();
const origins = {
"same origin": ORIGIN,
"same site": REMOTE_ORIGIN,
"different site": HTTP_NOTSAMESITE_ORIGIN
}
const loaders = {
image: {
file: '../../images/green.png',
type: 'image/png',
load: href => {
const image = document.createElement('img');
image.src = href;
document.body.appendChild(image);
return new Promise(resolve => image.addEventListener('load', resolve));
}
},
script: {
file: 'dummy.js',
type: 'application/javascript',
load: href => {
const script = document.createElement('script');
script.src = href;
document.body.appendChild(script);
return new Promise(resolve => script.addEventListener('load', resolve));
}
},
style: {
file: 'dummy.css',
type: 'text/css',
load: href => {
const link = document.createElement('link');
link.href = href;
link.rel = "stylesheet";
document.body.appendChild(link);
return new Promise(resolve => link.addEventListener('load', resolve));
}
},
document: {
file: 'empty.html',
type: 'text/html',
load: href => {
const iframe = document.createElement("iframe");
iframe.src = href;
document.body.appendChild(iframe);
return new Promise(resolve => iframe.addEventListener("load", resolve));
}
}
};

async function prefetch_document(origin, as, t) {
const {href, uid} = await prefetch({
file: "prefetch-exec.html",
type: "text/html",
corssOrigin: "anonymous",
origin: origins[origin], as});
const popup = window.open(href);
const remoteContext = new RemoteContext(uid);
t.add_cleanup(() => popup.close());
const result = await remoteContext.execute_script(() => "OK");
assert_equals(result, "OK");
return await get_prefetch_info(href);
}

async function test_prefetch_document({origin, as}, expected) {
promise_test(async t => {
const requests = await prefetch_document(origin, as, t);
const did_consume = requests.length === 1 ? "consumed" : "not consumed";
assert_equals(did_consume, expected);
}, `Prefetching a document (${origin}, as="${as}") should be ${expected}`);
}

test_prefetch_document({origin: "same origin"}, "consumed");
test_prefetch_document({origin: "same site"}, "consumed");
test_prefetch_document({as: "document", origin: "different site"}, "not consumed");

promise_test(async t => {
const {href, uid} = await prefetch({
file: "prefetch-exec.html",
type: "text/html",
corssOrigin: "anonymous",
origin: ORIGIN});
const popup = window.open(href + "&cache_bust=" + token());
const remoteContext = new RemoteContext(uid);
t.add_cleanup(() => popup.close());
await remoteContext.execute_script(() => "OK");
const results = await get_prefetch_info(href);
assert_equals(results.length, 2);
assert_equals(results[0].headers.accept, results[1].headers.accept);
}, "Document prefetch should send the exact Accept header as navigation")

</script>
</body>
27 changes: 27 additions & 0 deletions preload/prefetch-headers.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<title>Ensures that prefetch sends headers as per-spec</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/utils.js"></script>
<script src="resources/prefetch-helper.js"></script>
<body>
<script>

promise_test(async t => {
const {href} = await prefetch({"type": "image/png", file: "../../images/green.png"}, t);
const [info] = await get_prefetch_info(href);
const {headers} = info;
assert_equals(headers["sec-fetch-dest"], "empty");
assert_equals(headers["sec-purpose"], "prefetch");
assert_equals(headers["Origin"], undefined);
}, "Prefetch should include Sec-Purpose=prefetch and Sec-Fetch-Dest=empty headers");

promise_test(async t => {
const {href} = await prefetch({"type": "image/png", file: "../../images/green.png", crossOrigin: "anonymous"}, t);
const [info] = await get_prefetch_info(href);
const {headers} = info;
assert_equals(headers["Origin"], document.origin);
}, "Prefetch should respect CORS mode");

</script>
</body>
14 changes: 14 additions & 0 deletions preload/prefetch-load-event.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<title>Ensures that prefetch respects cache headers</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/utils.js"></script>
<link rel="prefetch" href="/xhr/resources/delay.py?ms=100000">
<body>
<script>

promise_test(() => new Promise(resolve => window.addEventListener("load", resolve)),
"Prefetch should not block the load event");

</script>
</body>
65 changes: 65 additions & 0 deletions preload/prefetch-types.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<!DOCTYPE html>
<title>Ensures that prefetch is not specific to resource types</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/utils.js"></script>
<script src="resources/prefetch-helper.js"></script>
<body>
<script>
const loaders = {
image: {
file: '../../images/green.png',
type: 'image/png',
load: href => {
const image = document.createElement('img');
image.src = href;
document.body.appendChild(image);
return new Promise(resolve => image.addEventListener('load', resolve));
}
},
script: {
file: 'dummy.js',
type: 'application/javascript',
load: href => {
const script = document.createElement('script');
script.src = href;
document.body.appendChild(script);
return new Promise(resolve => script.addEventListener('load', resolve));
}
},
style: {
file: 'dummy.css',
type: 'text/css',
load: href => {
const link = document.createElement('link');
link.href = href;
link.rel = "stylesheet";
document.body.appendChild(link);
return new Promise(resolve => link.addEventListener('load', resolve));
}
},
document: {
file: 'empty.html',
type: 'text/html',
load: href => {
const iframe = document.createElement("iframe");
iframe.src = href;
document.body.appendChild(iframe);
return new Promise(resolve => iframe.addEventListener("load", resolve));
}
}
};

for (const as in loaders) {
for (const consumer in loaders) {
const {file, type, load} = loaders[as]
promise_test(async t => {
const {href} = await prefetch({file, type, as, origin: host_info[origin]});
const requests = await get_prefetch_info(href);
assert_equals(requests.length, 1);
}, `Prefetch as=${as} should work when consumed as ${consumer} (${origin})`);
}
}

</script>
</body>
9 changes: 9 additions & 0 deletions preload/resources/prefetch-exec.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Message BC</title>
<script src="/common/dispatcher/dispatcher.js"></script>
<script>
"use strict";
const params = new URLSearchParams(location.search);
window.executor = new Executor(params.get("key"));
</script>
23 changes: 23 additions & 0 deletions preload/resources/prefetch-helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
async function get_prefetch_info(href) {
const response = await fetch(`${href}&mode=info`, {mode: "cors"});
return await response.json();
}

async function prefetch(p = {}, t) {
const link = document.createElement("link");
link.rel = "prefetch";
link.as = p.as;
if (p.crossOrigin)
link.setAttribute("crossorigin", p.crossOrigin);
const uid = token();
const params = new URLSearchParams();
params.set("key", uid);
for (const key in p)
params.set(key, p[key]);
const origin = p.origin || '.';
link.href = `${origin}/preload/resources/prefetch-info.py?${params.toString()}`;
document.head.appendChild(link);
while (!(await get_prefetch_info(link.href)).length) { }
return {href: link.href, uid};
}

32 changes: 32 additions & 0 deletions preload/resources/prefetch-info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import os
from wptserve.utils import isomorphic_encode
from json import dumps, loads

def main(request, response):
key = request.GET.first(b"key").decode("utf8")
mode = request.GET.first(b"mode", "content")
stash = request.server.stash
response.headers.set(b"Access-Control-Allow-Origin", b"*")
with stash.lock:
requests = loads(stash.take(key) or '[]')
if mode == b"info":
response.headers.set(b"Content-Type", "application/json")
json_reqs = dumps(requests)
response.content = json_reqs
stash.put(key, json_reqs)
return
else:
headers = {}
for header in request.headers:
headers[header.decode("utf8")] = request.headers[header].decode("utf8")
path = request.url
requests.append({"headers": headers, "url": request.url})
stash.put(key, dumps(requests))

response.headers.set(b"Content-Type", request.GET.first(b"type"))
response.headers.set(b"Cache-Control", request.GET.first(b"cache-control", b"max-age: 604800"))
if b"file" in request.GET:
path = os.path.join(os.path.dirname(isomorphic_encode(__file__)), request.GET.first(b"file"));
response.content = open(path, mode=u'rb').read()
else:
return request.GET.first(b"content")

0 comments on commit bd647fb

Please sign in to comment.