Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add multiple tests for prefetch behavior #35707

Merged
merged 10 commits into from
Jan 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions preload/prefetch-cache.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!DOCTYPE html>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if all these files should go in https://github.com/web-platform-tests/wpt/tree/master/html/semantics/links/linktypes instead? Not sure, and I guess it's OK either way...

<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": "application/javascript",
content: "/**/"}, t);
const script = document.createElement("script");
script.src = href;
t.add_cleanup(() => script.remove());
const loaded = new Promise(resolve => script.addEventListener("load", resolve));
document.body.appendChild(script);
await loaded;
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 respect cache-control: ${cacheControl}`);
}
</script>
</body>
103 changes: 103 additions & 0 deletions preload/prefetch-document.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<!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 loaders = {
image: {
file: 'square.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_and_count_fetches(options, t) {
const {href, uid} = await prefetch({
file: "prefetch-exec.html",
type: "text/html",
corssOrigin: "anonymous",
...options});
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");
const requests = await get_prefetch_info(href);
return requests.length;
}

promise_test(async t => {
assert_equals(await prefetch_document_and_count_fetches({origin: ORIGIN}, t), 1);
}, "same origin document prefetch without 'as' should be consumed");

promise_test(async t => {
assert_equals(await prefetch_document_and_count_fetches({origin: REMOTE_ORIGIN}, t), 1);
}, "same-site different-origin document prefetch without 'as' should be consumed");

promise_test(async t => {
assert_equals(await prefetch_document_and_count_fetches({origin: HTTP_NOTSAMESITE_ORIGIN}, t), 2);
}, "different-site document prefetch without 'as' should not be consumed");

promise_test(async t => {
assert_equals(await prefetch_document_and_count_fetches({origin: HTTP_NOTSAMESITE_ORIGIN, as: "document"}, t), 2);
}, "different-site document prefetch with 'as=document' should not be 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>
97 changes: 97 additions & 0 deletions preload/prefetch-events.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<!DOCTYPE html>
<title>Ensures that prefetch respects HTTP cache semantics</title>
<meta name="timeout" content="long">
<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>
<script src="/common/get-host-info.sub.js"></script>
<body>
<script>
const {REMOTE_ORIGIN} = get_host_info();
async function prefetch(link, uid, t) {
link.rel = "prefetch";
document.head.appendChild(link);
const event = new Promise(resolve => {
link.addEventListener("error", () => resolve("error"));
link.addEventListener("load", () => resolve("load"));
t.step_timeout(() => resolve("timeout"), 1000);
});
return await event;
}

promise_test(async t => {
const uid = token();
const link = document.createElement("link");
link.href = `/preload/resources/prefetch-info.py?key=${uid}`;
const event = await prefetch(link, uid, t);
assert_equals(event, "load");
}, "Prefetch should fire the load event");

promise_test(async t => {
const uid = token();
const link = document.createElement("link");
link.href = `${REMOTE_ORIGIN}/preload/resources/prefetch-info.py?key=${uid}`;
const event = await prefetch(link, uid, t);
assert_equals(event, "load");
}, "Cross-origin prefetch should fire the load event");

promise_test(async t => {
const uid = token();
const link = document.createElement("link");
link.href = `/preload/resources/prefetch-info.py?key=${uid}&status=404`;
const event = await prefetch(link, uid, t);
assert_equals(event, "load");
}, "Prefetch should fire the load event for 404");

promise_test(async t => {
const uid = token();
const link = document.createElement("link");
link.href = `${REMOTE_ORIGIN}/preload/resources/prefetch-info.py?key=${uid}&status=404`;
const event = await prefetch(link, uid, t);
assert_equals(event, "load");
}, "Prefetch should fire the load event for 404 (cross-origin)");

promise_test(async t => {
const uid = token();
const link = document.createElement("link");
link.href = `/preload/resources/prefetch-info.py?key=${uid}&status=500`;
const event = await prefetch(link, uid, t);
assert_equals(event, "load");
}, "Prefetch should fire the load event for 500");

promise_test(async t => {
const uid = token();
const link = document.createElement("link");
link.href = `${REMOTE_ORIGIN}/preload/resources/prefetch-info.py?key=${uid}&status=500`;
const event = await prefetch(link, uid, t);
assert_equals(event, "load");
}, "Cross-origin prefetch should fire the load event for 500");

promise_test(async t => {
const uid = token();
const link = document.createElement("link");
link.crossOrigin = "anonymous";
link.href = `${REMOTE_ORIGIN}/preload/resources/prefetch-info.py?key=${uid}&cors=false`;
const event = await prefetch(link, uid, t);
assert_equals(event, "error");
}, "Prefetch should fire the error event for network errors");

promise_test(async t => {
const uid = token();
const link = document.createElement("link");
link.crossOrigin = "anonymous";
const event = await prefetch(link, uid, t);
assert_equals(event, "timeout");
}, "Prefetch should do nothing with an empty href");

promise_test(async t => {
const uid = token();
const link = document.createElement("link");
link.href = "https://example.com\u0000mozilla.org";
link.crossOrigin = "anonymous";
const event = await prefetch(link, uid, t);
assert_equals(event, "timeout");
}, "Prefetch should do nothing with an invalid href");
</script>
</body>
35 changes: 35 additions & 0 deletions preload/prefetch-headers.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!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_false("origin" in headers);
}, "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"}, t);
const [info] = await get_prefetch_info(href);
const {headers} = info;
assert_false("purpose" in headers);
assert_false("x-moz" in headers);
}, "Prefetch should not include proprietary headers (X-moz/Purpose)");

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>
13 changes: 13 additions & 0 deletions preload/prefetch-load-event.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<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>
73 changes: 73 additions & 0 deletions preload/prefetch-types.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<!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="/common/get-host-info.sub.js"></script>
<script src="resources/prefetch-helper.js"></script>
<body>
<script>
const host_info = get_host_info();
const loaders = {
"": {
file: "../../common/dummy.xml",
type: "text/xml",
load: fetch
},
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);
assert_equals(requests[0].headers["sec-fetch-dest"], "empty");
}, `Prefetch as=${as} should work when consumed as ${consumer} (${origin})`);
}
}

</script>
</body>
1 change: 1 addition & 0 deletions preload/resources/prefetch-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ async function get_prefetch_info(href) {
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();
Expand Down