diff --git a/preload/prefetch-cache.html b/preload/prefetch-cache.html
new file mode 100644
index 00000000000000..d7e32ea49283ac
--- /dev/null
+++ b/preload/prefetch-cache.html
@@ -0,0 +1,29 @@
+
+
Ensures that prefetch respects HTTP cache semantics
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/preload/prefetch-document.html b/preload/prefetch-document.html
new file mode 100644
index 00000000000000..dc6d3503387194
--- /dev/null
+++ b/preload/prefetch-document.html
@@ -0,0 +1,104 @@
+
+Ensures that prefetch works with documents
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/preload/prefetch-headers.html b/preload/prefetch-headers.html
new file mode 100644
index 00000000000000..8ac576f47dc078
--- /dev/null
+++ b/preload/prefetch-headers.html
@@ -0,0 +1,27 @@
+
+Ensures that prefetch sends headers as per-spec
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/preload/prefetch-load-event.html b/preload/prefetch-load-event.html
new file mode 100644
index 00000000000000..e7282c882d425a
--- /dev/null
+++ b/preload/prefetch-load-event.html
@@ -0,0 +1,14 @@
+
+Ensures that prefetch respects cache headers
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/preload/prefetch-types.html b/preload/prefetch-types.html
new file mode 100644
index 00000000000000..9011feb7abd125
--- /dev/null
+++ b/preload/prefetch-types.html
@@ -0,0 +1,65 @@
+
+Ensures that prefetch is not specific to resource types
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/preload/resources/prefetch-exec.html b/preload/resources/prefetch-exec.html
new file mode 100644
index 00000000000000..1d6765bc93e449
--- /dev/null
+++ b/preload/resources/prefetch-exec.html
@@ -0,0 +1,9 @@
+
+
+Message BC
+
+
diff --git a/preload/resources/prefetch-helper.js b/preload/resources/prefetch-helper.js
new file mode 100644
index 00000000000000..d3b0bbe33a0801
--- /dev/null
+++ b/preload/resources/prefetch-helper.js
@@ -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};
+}
+
diff --git a/preload/resources/prefetch-info.py b/preload/resources/prefetch-info.py
new file mode 100644
index 00000000000000..723e1f297bdf09
--- /dev/null
+++ b/preload/resources/prefetch-info.py
@@ -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")