summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/speculation-rules/prefetch/resources
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/speculation-rules/prefetch/resources')
-rw-r--r--testing/web-platform/tests/speculation-rules/prefetch/resources/authenticate.py32
-rw-r--r--testing/web-platform/tests/speculation-rules/prefetch/resources/cookies.py41
-rw-r--r--testing/web-platform/tests/speculation-rules/prefetch/resources/executor.sub.html13
-rw-r--r--testing/web-platform/tests/speculation-rules/prefetch/resources/prefetch.py16
-rw-r--r--testing/web-platform/tests/speculation-rules/prefetch/resources/prefetch_nvs_hint.py34
-rw-r--r--testing/web-platform/tests/speculation-rules/prefetch/resources/redirect.py3
-rw-r--r--testing/web-platform/tests/speculation-rules/prefetch/resources/ruleset.py49
-rw-r--r--testing/web-platform/tests/speculation-rules/prefetch/resources/sw.js1
-rw-r--r--testing/web-platform/tests/speculation-rules/prefetch/resources/utils.sub.js176
9 files changed, 365 insertions, 0 deletions
diff --git a/testing/web-platform/tests/speculation-rules/prefetch/resources/authenticate.py b/testing/web-platform/tests/speculation-rules/prefetch/resources/authenticate.py
new file mode 100644
index 0000000000..037a7c144e
--- /dev/null
+++ b/testing/web-platform/tests/speculation-rules/prefetch/resources/authenticate.py
@@ -0,0 +1,32 @@
+
+def main(request, response):
+ def fmt(x):
+ return f'"{x.decode("utf-8")}"' if x is not None else "undefined"
+
+ purpose = request.headers.get("Purpose", b"").decode("utf-8")
+ sec_purpose = request.headers.get("Sec-Purpose", b"").decode("utf-8")
+
+ headers = [(b"Content-Type", b"text/html"), (b'WWW-Authenticate', 'Basic')]
+ status = 200 if request.auth.username is not None or sec_purpose.startswith(
+ "prefetch") else 401
+
+ content = f'''
+ <!DOCTYPE html>
+ <script src="/common/dispatcher/dispatcher.js"></script>
+ <script src="utils.sub.js"></script>
+ <script>
+ window.requestHeaders = {{
+ purpose: "{purpose}",
+ sec_purpose: "{sec_purpose}"
+ }};
+
+ window.requestCredentials = {{
+ username: {fmt(request.auth.username)},
+ password: {fmt(request.auth.password)}
+ }};
+
+ const uuid = new URLSearchParams(location.search).get('uuid');
+ window.executor = new Executor(uuid);
+ </script>
+ '''
+ return status, headers, content
diff --git a/testing/web-platform/tests/speculation-rules/prefetch/resources/cookies.py b/testing/web-platform/tests/speculation-rules/prefetch/resources/cookies.py
new file mode 100644
index 0000000000..3c2299aa3a
--- /dev/null
+++ b/testing/web-platform/tests/speculation-rules/prefetch/resources/cookies.py
@@ -0,0 +1,41 @@
+
+def main(request, response):
+ def get_cookie(key):
+ key = key.encode("utf-8")
+ if key in request.cookies:
+ return f'"{request.cookies[key].value.decode("utf-8")}"'
+ else:
+ return "undefined"
+
+ purpose = request.headers.get("Purpose", b"").decode("utf-8")
+ sec_purpose = request.headers.get("Sec-Purpose", b"").decode("utf-8")
+
+ cookie_count = int(
+ request.cookies[b"count"].value) if b"count" in request.cookies else 0
+ response.set_cookie("count", f"{cookie_count+1}",
+ secure=True, samesite="None")
+ response.set_cookie(
+ "type", "prefetch" if sec_purpose.startswith("prefetch") else "navigate")
+
+ headers = [(b"Content-Type", b"text/html")]
+
+ content = f'''
+ <!DOCTYPE html>
+ <script src="/common/dispatcher/dispatcher.js"></script>
+ <script src="utils.sub.js"></script>
+ <script>
+ window.requestHeaders = {{
+ purpose: "{purpose}",
+ sec_purpose: "{sec_purpose}"
+ }};
+
+ window.requestCookies = {{
+ count: {get_cookie("count")},
+ type: {get_cookie("type")}
+ }};
+
+ const uuid = new URLSearchParams(location.search).get('uuid');
+ window.executor = new Executor(uuid);
+ </script>
+ '''
+ return headers, content
diff --git a/testing/web-platform/tests/speculation-rules/prefetch/resources/executor.sub.html b/testing/web-platform/tests/speculation-rules/prefetch/resources/executor.sub.html
new file mode 100644
index 0000000000..ba1b3acb0c
--- /dev/null
+++ b/testing/web-platform/tests/speculation-rules/prefetch/resources/executor.sub.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="utils.sub.js"></script>
+<script>
+window.requestHeaders = {
+ purpose: "{{header_or_default(Purpose, )}}",
+ sec_purpose: "{{header_or_default(Sec-Purpose, )}}",
+ referer: "{{header_or_default(Referer, )}}",
+};
+
+const uuid = new URLSearchParams(location.search).get('uuid');
+window.executor = new Executor(uuid);
+</script>
diff --git a/testing/web-platform/tests/speculation-rules/prefetch/resources/prefetch.py b/testing/web-platform/tests/speculation-rules/prefetch/resources/prefetch.py
new file mode 100644
index 0000000000..4a0a7a3602
--- /dev/null
+++ b/testing/web-platform/tests/speculation-rules/prefetch/resources/prefetch.py
@@ -0,0 +1,16 @@
+from wptserve.handlers import json_handler
+
+@json_handler
+def main(request, response):
+ uuid = request.GET[b"uuid"]
+ prefetch = request.headers.get(
+ "Sec-Purpose", b"").decode("utf-8").startswith("prefetch")
+
+ n = request.server.stash.take(uuid)
+ if n is None:
+ n = 0
+ if prefetch:
+ n += 1
+ request.server.stash.put(uuid, n)
+
+ return n
diff --git a/testing/web-platform/tests/speculation-rules/prefetch/resources/prefetch_nvs_hint.py b/testing/web-platform/tests/speculation-rules/prefetch/resources/prefetch_nvs_hint.py
new file mode 100644
index 0000000000..09c5d2eb73
--- /dev/null
+++ b/testing/web-platform/tests/speculation-rules/prefetch/resources/prefetch_nvs_hint.py
@@ -0,0 +1,34 @@
+import time
+
+def main(request, response):
+ uuid = request.GET[b"uuid"]
+ prefetch = request.headers.get(
+ "Sec-Purpose", b"").decode("utf-8").startswith("prefetch")
+ if b"unblock" in request.GET:
+ request.server.stash.put(uuid, 0)
+ return ''
+
+ if b"nvs_header" in request.GET:
+ nvs_header = request.GET[b"nvs_header"]
+ response.headers.set("No-Vary-Search", nvs_header)
+
+ if prefetch:
+ nvswait = None
+ while nvswait is None:
+ time.sleep(0.1)
+ nvswait = request.server.stash.take(uuid)
+
+ content = (f'<!DOCTYPE html>\n'
+ f'<script src="/common/dispatcher/dispatcher.js"></script>\n'
+ f'<script src="utils.sub.js"></script>\n'
+ f'<script>\n'
+ f' window.requestHeaders = {{\n'
+ f' purpose: "{request.headers.get("Purpose", b"").decode("utf-8")}",\n'
+ f' sec_purpose: "{request.headers.get("Sec-Purpose", b"").decode("utf-8")}",\n'
+ f' referer: "{request.headers.get("Referer", b"").decode("utf-8")}",\n'
+ f' }};\n'
+ f' const uuid = new URLSearchParams(location.search).get("uuid");\n'
+ f' window.executor = new Executor(uuid);\n'
+ f'</script>\n')
+
+ return content
diff --git a/testing/web-platform/tests/speculation-rules/prefetch/resources/redirect.py b/testing/web-platform/tests/speculation-rules/prefetch/resources/redirect.py
new file mode 100644
index 0000000000..de7a4af987
--- /dev/null
+++ b/testing/web-platform/tests/speculation-rules/prefetch/resources/redirect.py
@@ -0,0 +1,3 @@
+def main(request, response):
+ new_url = request.url.replace("redirect", "prefetch").encode("utf-8")
+ return 301, [(b"Location", new_url)], b""
diff --git a/testing/web-platform/tests/speculation-rules/prefetch/resources/ruleset.py b/testing/web-platform/tests/speculation-rules/prefetch/resources/ruleset.py
new file mode 100644
index 0000000000..97de1cc1a0
--- /dev/null
+++ b/testing/web-platform/tests/speculation-rules/prefetch/resources/ruleset.py
@@ -0,0 +1,49 @@
+def main(request, response):
+ url = request.GET[b"url"].decode("utf-8")
+ uuid = request.GET[b"uuid"].decode("utf-8")
+ page = request.GET[b"page"].decode("utf-8")
+ valid_json = request.GET[b"valid_json"].decode("utf-8")
+ empty_json = request.GET[b"empty_json"].decode("utf-8")
+ fail_cors = request.GET[b"fail_cors"].decode("utf-8")
+ valid_encoding = request.GET[b"valid_encoding"].decode("utf-8")
+ redirect = request.GET[b"redirect"].decode("utf-8")
+ sec_fetch_dest = request.headers[b"Sec-Fetch-Dest"].decode(
+ "utf-8").lower() if b"Sec-Fetch-Dest" in request.headers else None
+ content_type = b"application/speculationrules+json" if request.GET[
+ b"valid_mime"].decode("utf-8") == "true" else b"application/json"
+ status = int(request.GET[b"status"])
+
+ if redirect == "true":
+ new_url = request.url.replace("redirect=true",
+ "redirect=false").encode("utf-8")
+ return 301, [(b"Location", new_url),
+ (b'Access-Control-Allow-Origin', b'*')], b""
+
+ encoding = "utf-8" if valid_encoding == "true" else "windows-1250"
+ content_type += f'; charset={encoding}'.encode('utf-8')
+ strparam = b'\xc3\xb7'.decode('utf-8')
+
+ content = f'''
+ {{
+ "prefetch": [
+ {{
+ "source":"list",
+ "urls":["{url}?uuid={uuid}&page={page}&str={strparam}"],
+ "requires":["anonymous-client-ip-when-cross-origin"]
+ }}
+ ]
+ }}
+ '''
+ if empty_json == "true":
+ content = ""
+ elif valid_json != "true":
+ content = "invalid json"
+ elif sec_fetch_dest is None or sec_fetch_dest != "script":
+ content = "normal document"
+
+ headers = [(b"Content-Type", content_type)]
+ if fail_cors != "true":
+ origin = request.headers[
+ b"Origin"] if b"Origin" in request.headers else b'*'
+ headers.append((b'Access-Control-Allow-Origin', origin))
+ return status, headers, content.encode(encoding)
diff --git a/testing/web-platform/tests/speculation-rules/prefetch/resources/sw.js b/testing/web-platform/tests/speculation-rules/prefetch/resources/sw.js
new file mode 100644
index 0000000000..db774f9d5b
--- /dev/null
+++ b/testing/web-platform/tests/speculation-rules/prefetch/resources/sw.js
@@ -0,0 +1 @@
+self.addEventListener('fetch', () => event.respondWith(fetch(event.request)));
diff --git a/testing/web-platform/tests/speculation-rules/prefetch/resources/utils.sub.js b/testing/web-platform/tests/speculation-rules/prefetch/resources/utils.sub.js
new file mode 100644
index 0000000000..9b3b630733
--- /dev/null
+++ b/testing/web-platform/tests/speculation-rules/prefetch/resources/utils.sub.js
@@ -0,0 +1,176 @@
+/**
+ * Utilities for initiating prefetch via speculation rules.
+ */
+
+// Resolved URL to find this script.
+const SR_PREFETCH_UTILS_URL = new URL(document.currentScript.src, document.baseURI);
+// Hostname for cross origin urls.
+const PREFETCH_PROXY_BYPASS_HOST = "{{hosts[alt][]}}";
+
+class PrefetchAgent extends RemoteContext {
+ constructor(uuid, t) {
+ super(uuid);
+ this.t = t;
+ }
+
+ getExecutorURL(options = {}) {
+ let {hostname, username, password, protocol, executor, ...extra} = options;
+ let params = new URLSearchParams({uuid: this.context_id, ...extra});
+ if(executor === undefined) {
+ executor = "executor.sub.html";
+ }
+ let url = new URL(`${executor}?${params}`, SR_PREFETCH_UTILS_URL);
+ if(hostname !== undefined) {
+ url.hostname = hostname;
+ }
+ if(username !== undefined) {
+ url.username = username;
+ }
+ if(password !== undefined) {
+ url.password = password;
+ }
+ if(protocol !== undefined) {
+ url.protocol = protocol;
+ url.port = protocol === "https" ? "{{ports[https][0]}}" : "{{ports[http][0]}}";
+ }
+ return url;
+ }
+
+ // Requests prefetch via speculation rules.
+ //
+ // In the future, this should also use browser hooks to force the prefetch to
+ // occur despite heuristic matching, etc., and await the completion of the
+ // prefetch.
+ async forceSinglePrefetch(url, extra = {}) {
+ await this.execute_script((url, extra) => {
+ insertSpeculationRules({ prefetch: [{source: 'list', urls: [url], ...extra}] });
+ }, [url, extra]);
+ return new Promise(resolve => this.t.step_timeout(resolve, 2000));
+ }
+
+ async navigate(url) {
+ await this.execute_script((url) => {
+ window.executor.suspend(() => {
+ location.href = url;
+ });
+ }, [url]);
+ url.username = '';
+ url.password = '';
+ assert_equals(
+ await this.execute_script(() => location.href),
+ url.toString(),
+ "expected navigation to reach destination URL");
+ await this.execute_script(() => {});
+ }
+
+ async getRequestHeaders() {
+ return this.execute_script(() => requestHeaders);
+ }
+
+ async getResponseCookies() {
+ return this.execute_script(() => {
+ let cookie = {};
+ document.cookie.split(/\s*;\s*/).forEach((kv)=>{
+ let [key, value] = kv.split(/\s*=\s*/);
+ cookie[key] = value;
+ });
+ return cookie;
+ });
+ }
+
+ async getRequestCookies() {
+ return this.execute_script(() => window.requestCookies);
+ }
+
+ async getRequestCredentials() {
+ return this.execute_script(() => window.requestCredentials);
+ }
+
+ async setReferrerPolicy(referrerPolicy) {
+ return this.execute_script(referrerPolicy => {
+ const meta = document.createElement("meta");
+ meta.name = "referrer";
+ meta.content = referrerPolicy;
+ document.head.append(meta);
+ }, [referrerPolicy]);
+ }
+
+ async getDeliveryType(){
+ return this.execute_script(() => {
+ return performance.getEntriesByType("navigation")[0].deliveryType;
+ });
+ }
+}
+
+// Produces a URL with a UUID which will record when it's prefetched.
+// |extra_params| can be specified to add extra search params to the generated
+// URL.
+function getPrefetchUrl(extra_params={}) {
+ let params = new URLSearchParams({ uuid: token(), ...extra_params });
+ return new URL(`prefetch.py?${params}`, SR_PREFETCH_UTILS_URL);
+}
+
+// Produces n URLs with unique UUIDs which will record when they are prefetched.
+function getPrefetchUrlList(n) {
+ return Array.from({ length: n }, () => getPrefetchUrl());
+}
+
+function getRedirectUrl() {
+ let params = new URLSearchParams({uuid: token()});
+ return new URL(`redirect.py?${params}`, SR_PREFETCH_UTILS_URL);
+}
+
+async function isUrlPrefetched(url) {
+ let response = await fetch(url, {redirect: 'follow'});
+ assert_true(response.ok);
+ return response.json();
+}
+
+// Must also include /common/utils.js and /common/dispatcher/dispatcher.js to use this.
+async function spawnWindow(t, options = {}, uuid = token()) {
+ let agent = new PrefetchAgent(uuid, t);
+ let w = window.open(agent.getExecutorURL(options), '_blank', options);
+ t.add_cleanup(() => w.close());
+ return agent;
+}
+
+function insertSpeculationRules(body) {
+ let script = document.createElement('script');
+ script.type = 'speculationrules';
+ script.textContent = JSON.stringify(body);
+ document.head.appendChild(script);
+}
+
+// Creates and appends <a href=|href|> to |insertion point|. If
+// |insertion_point| is not specified, document.body is used.
+function addLink(href, insertion_point=document.body) {
+ const a = document.createElement('a');
+ a.href = href;
+ insertion_point.appendChild(a);
+ return a;
+}
+
+// Inserts a prefetch document rule with |predicate|. |predicate| can be
+// undefined, in which case the default predicate will be used (i.e. all links
+// in document will match).
+function insertDocumentRule(predicate, extra_options={}) {
+ insertSpeculationRules({
+ prefetch: [{
+ source: 'document',
+ eagerness: 'eager',
+ where: predicate,
+ ...extra_options
+ }]
+ });
+}
+
+function assert_prefetched (requestHeaders, description) {
+ assert_in_array(requestHeaders.purpose, ["", "prefetch"], "The vendor-specific header Purpose, if present, must be 'prefetch'.");
+ assert_in_array(requestHeaders.sec_purpose,
+ ["prefetch", "prefetch;anonymous-client-ip"], description);
+}
+
+function assert_not_prefetched (requestHeaders, description){
+ assert_equals(requestHeaders.purpose, "", description);
+ assert_equals(requestHeaders.sec_purpose, "", description);
+}