Support as=fetch in Early Hints preload

The spec doesn't prohibit as=fetch.

Bug: 1408649
Change-Id: I81c50de77b362c8fa1c6ecfcc8332bba4c5e5d66
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4177723
Reviewed-by: Yoav Weiss <yoavweiss@chromium.org>
Commit-Queue: Kenichi Ishibashi <bashi@chromium.org>
Reviewed-by: Takashi Toyoshima <toyoshim@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1098451}
diff --git a/loading/early-hints/preload-fetch.h2.window.js b/loading/early-hints/preload-fetch.h2.window.js
new file mode 100644
index 0000000..121913e
--- /dev/null
+++ b/loading/early-hints/preload-fetch.h2.window.js
@@ -0,0 +1,10 @@
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+    const preloads = [{
+        "url": "empty.json?" + Date.now(),
+        "as_attr": "fetch",
+        "crossorigin_attr": "",
+    }];
+    navigateToTestWithEarlyHints("resources/preload-fetch.html", preloads);
+});
diff --git a/loading/early-hints/resources/early-hints-helpers.sub.js b/loading/early-hints/resources/early-hints-helpers.sub.js
index 3991e8f..faf6119 100644
--- a/loading/early-hints/resources/early-hints-helpers.sub.js
+++ b/loading/early-hints/resources/early-hints-helpers.sub.js
@@ -14,6 +14,8 @@
  * @property {string} url - A URL to preload. Note: This is relative to the
  *     `test_url` parameter of `navigateToTestWithEarlyHints()`.
  * @property {string} as_attr - `as` attribute of this preload.
+ * @property {string} [crossorigin_attr] - `crossorigin` attribute of this
+ *     preload.
  *
  * @param {string} test_url - URL of a test after the Early Hints response.
  * @param {Array<Preload>} preloads  - Preloads included in the Early Hints response.
diff --git a/loading/early-hints/resources/early-hints-test-loader.h2.py b/loading/early-hints/resources/early-hints-test-loader.h2.py
index aa9188c..bb98720 100644
--- a/loading/early-hints/resources/early-hints-test-loader.h2.py
+++ b/loading/early-hints/resources/early-hints-test-loader.h2.py
@@ -18,6 +18,12 @@
     for encoded_preload in request.GET.get_list(b"preloads"):
         preload = json.loads(encoded_preload.decode("utf-8"))
         header = "<{}>; rel=preload; as={}".format(preload["url"], preload["as_attr"])
+        if "crossorigin_attr" in preload:
+            crossorigin = preload["crossorigin_attr"]
+            if crossorigin:
+                header += "; crossorigin={}".format(crossorigin)
+            else:
+                header += "; crossorigin"
         preload_headers.append(header.encode())
 
     # Send a 103 response.
diff --git a/loading/early-hints/resources/empty.json b/loading/early-hints/resources/empty.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/loading/early-hints/resources/empty.json
@@ -0,0 +1 @@
+{}
diff --git a/loading/early-hints/resources/empty.json.headers b/loading/early-hints/resources/empty.json.headers
new file mode 100644
index 0000000..1738466
--- /dev/null
+++ b/loading/early-hints/resources/empty.json.headers
@@ -0,0 +1,4 @@
+cache-control: max-age=600
+access-control-allow-origin: *
+timing-allow-origin: *
+cross-origin-resource-policy: cross-origin
diff --git a/loading/early-hints/resources/preload-fetch.html b/loading/early-hints/resources/preload-fetch.html
new file mode 100644
index 0000000..2e90f76
--- /dev/null
+++ b/loading/early-hints/resources/preload-fetch.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="early-hints-helpers.sub.js"></script>
+<body>
+<script>
+promise_test(async (t) => {
+    const preloads = getPreloadsFromSearchParams();
+    assert_equals(preloads.length, 1);
+    const preload = preloads[0];
+
+    await fetch(preload.url).then((response) => response.json());
+    const name = new URL(preload.url, window.location);
+    assert_true(isPreloadedByEarlyHints(name));
+}, "Ensure early hints preload works for fetch()");
+</script>
+</body>