Use the frame `Accept` header for prefetches.

Special-casing prefetches makes it so that `Vary: Accept`
makes the browser erroneously reject the prefetched response.

Also changed the SXG ?q= value for the frame accept header, as it was
done for prefetch (and should have been done for the frame accept
header).

For now CORS prefetches remain untouched, as changing them to the
>128bytes frame accept headers would cause incompatibility due to a
CORS-preflight requirement.

This was previously unspecified,
See https://github.com/whatwg/fetch/pull/1485

Bug: 626081
Change-Id: I2c99f4f1abd2556fdf456d877588b346a22fd677
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4069726
Reviewed-by: Matt Menke <mmenke@chromium.org>
Commit-Queue: Noam Rosenthal <nrosenthal@chromium.org>
Auto-Submit: Noam Rosenthal <nrosenthal@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1083806}
diff --git a/preload/prefetch-accept.html b/preload/prefetch-accept.html
new file mode 100644
index 0000000..3820b9b
--- /dev/null
+++ b/preload/prefetch-accept.html
@@ -0,0 +1,26 @@
+<!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/dispatcher/dispatcher.js"></script>
+<script src="resources/prefetch-helper.js"></script>
+<body>
+<script>
+
+promise_test(async t => {
+    const {href, uid} = await prefetch({
+        file: "prefetch-exec.html",
+        type: "text/html",
+        origin: document.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>
diff --git a/preload/resources/prefetch-exec.html b/preload/resources/prefetch-exec.html
new file mode 100644
index 0000000..1d6765b
--- /dev/null
+++ b/preload/resources/prefetch-exec.html
@@ -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>
diff --git a/preload/resources/prefetch-helper.js b/preload/resources/prefetch-helper.js
new file mode 100644
index 0000000..cf0e0d5
--- /dev/null
+++ b/preload/resources/prefetch-helper.js
@@ -0,0 +1,21 @@
+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";
+    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 0000000..04d942b
--- /dev/null
+++ b/preload/resources/prefetch-info.py
@@ -0,0 +1,37 @@
+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")
+    status = int(request.GET.first(b"status", b"200"))
+    stash = request.server.stash
+    cors = request.GET.first(b"cors", "true")
+    if cors == "true" or mode == b"info":
+        response.headers.set(b"Access-Control-Allow-Origin", b"*")
+
+    response.status = status
+    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, value in request.headers.items():
+                headers[header.decode("utf8")] = value[0].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", "text/plain"))
+    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__)), os.path.basename(request.GET.first(b"file")))
+        response.content = open(path, mode=u'rb').read()
+    else:
+        return request.GET.first(b"content", "123")