Skip to content

Commit

Permalink
Support as=fetch in Early Hints preload
Browse files Browse the repository at this point in the history
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}
  • Loading branch information
bashi authored and Chromium LUCI CQ committed Jan 30, 2023
1 parent 250747c commit d18285c
Show file tree
Hide file tree
Showing 10 changed files with 64 additions and 8 deletions.
22 changes: 15 additions & 7 deletions content/browser/loader/navigation_early_hints_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ const net::NetworkTrafficAnnotationTag kEarlyHintsPreloadTrafficAnnotation =

network::mojom::CSPDirectiveName LinkAsAttributeToCSPDirective(
network::mojom::LinkAsAttribute attr) {
// https://w3c.github.io/webappsec-csp/#csp-directives
switch (attr) {
case network::mojom::LinkAsAttribute::kUnspecified:
return network::mojom::CSPDirectiveName::Unknown;
Expand All @@ -93,6 +94,8 @@ network::mojom::CSPDirectiveName LinkAsAttributeToCSPDirective(
return network::mojom::CSPDirectiveName::ScriptSrcElem;
case network::mojom::LinkAsAttribute::kStyleSheet:
return network::mojom::CSPDirectiveName::StyleSrcElem;
case network::mojom::LinkAsAttribute::kFetch:
return network::mojom::CSPDirectiveName::ConnectSrc;
}
NOTREACHED();
return network::mojom::CSPDirectiveName::Unknown;
Expand Down Expand Up @@ -131,8 +134,9 @@ bool CheckContentSecurityPolicyForPreload(
return true;
}

network::mojom::RequestDestination LinkAsAttributeToRequestDestination(
const network::mojom::LinkHeaderPtr& link) {
absl::optional<network::mojom::RequestDestination>
LinkAsAttributeToRequestDestination(const network::mojom::LinkHeaderPtr& link) {
// https://fetch.spec.whatwg.org/#concept-potential-destination-translate
switch (link->as) {
case network::mojom::LinkAsAttribute::kUnspecified:
// For modulepreload, the request destination should be "script" when `as`
Expand All @@ -141,7 +145,7 @@ network::mojom::RequestDestination LinkAsAttributeToRequestDestination(
if (link->rel == network::mojom::LinkRelAttribute::kModulePreload) {
return network::mojom::RequestDestination::kScript;
}
return network::mojom::RequestDestination::kEmpty;
return absl::nullopt;
case network::mojom::LinkAsAttribute::kImage:
return network::mojom::RequestDestination::kImage;
case network::mojom::LinkAsAttribute::kFont:
Expand All @@ -150,6 +154,8 @@ network::mojom::RequestDestination LinkAsAttributeToRequestDestination(
return network::mojom::RequestDestination::kScript;
case network::mojom::LinkAsAttribute::kStyleSheet:
return network::mojom::RequestDestination::kStyle;
case network::mojom::LinkAsAttribute::kFetch:
return network::mojom::RequestDestination::kEmpty;
}
}

Expand All @@ -166,6 +172,7 @@ net::RequestPriority CalculateRequestPriority(
case network::mojom::LinkAsAttribute::kScript:
return net::MEDIUM;
case network::mojom::LinkAsAttribute::kImage:
case network::mojom::LinkAsAttribute::kFetch:
return net::LOWEST;
case network::mojom::LinkAsAttribute::kUnspecified:
return net::IDLE;
Expand Down Expand Up @@ -513,12 +520,13 @@ void NavigationEarlyHintsManager::MaybePreloadHintedResource(
if (!ShouldHandleResourceHints(link))
return;

network::mojom::RequestDestination destination =
LinkAsAttributeToRequestDestination(link);
// Step 2. If options's destination is not a destination, then return null.
// https://html.spec.whatwg.org/multipage/semantics.html#create-a-link-request
if (destination == network::mojom::RequestDestination::kEmpty)
absl::optional<network::mojom::RequestDestination> destination =
LinkAsAttributeToRequestDestination(link);
if (!destination) {
return;
}

if (!CheckContentSecurityPolicyForPreload(link, content_security_policies))
return;
Expand All @@ -535,7 +543,7 @@ void NavigationEarlyHintsManager::MaybePreloadHintedResource(
network::ResourceRequest request;
request.method = net::HttpRequestHeaders::kGetMethod;
request.priority = CalculateRequestPriority(link);
request.destination = destination;
request.destination = *destination;
request.url = link->href;
request.site_for_cookies = site_for_cookies;
request.request_initiator = origin_;
Expand Down
2 changes: 2 additions & 0 deletions services/network/public/cpp/link_header_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ absl::optional<mojom::LinkAsAttribute> ParseAsAttribute(
return mojom::LinkAsAttribute::kScript;
} else if (value == "style") {
return mojom::LinkAsAttribute::kStyleSheet;
} else if (value == "fetch") {
return mojom::LinkAsAttribute::kFetch;
}
return absl::nullopt;
}
Expand Down
4 changes: 3 additions & 1 deletion services/network/public/cpp/link_header_parser_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -135,15 +135,17 @@ TEST(LinkHeaderParserTest, LinkAsAttribute) {
headers->AddHeader("link", "</image.jpg>; rel=preload; as=image");
headers->AddHeader("link", "</script.js>; rel=preload; as=script");
headers->AddHeader("link", "</style.css>; rel=preload; as=style");
headers->AddHeader("link", "</foo.json>; rel=preload; as=fetch; crossorigin");

std::vector<mojom::LinkHeaderPtr> parsed_headers =
ParseLinkHeaders(*headers, kBaseUrl);
ASSERT_EQ(parsed_headers.size(), 5UL);
ASSERT_EQ(parsed_headers.size(), 6UL);
EXPECT_EQ(parsed_headers[0]->as, mojom::LinkAsAttribute::kUnspecified);
EXPECT_EQ(parsed_headers[1]->as, mojom::LinkAsAttribute::kFont);
EXPECT_EQ(parsed_headers[2]->as, mojom::LinkAsAttribute::kImage);
EXPECT_EQ(parsed_headers[3]->as, mojom::LinkAsAttribute::kScript);
EXPECT_EQ(parsed_headers[4]->as, mojom::LinkAsAttribute::kStyleSheet);
EXPECT_EQ(parsed_headers[5]->as, mojom::LinkAsAttribute::kFetch);
}

TEST(LinkHeaderParserTest, CrossOriginAttribute) {
Expand Down
3 changes: 3 additions & 0 deletions services/network/public/mojom/link_header.mojom
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@ enum LinkRelAttribute {
};

// Represents subset of possible values for `as` attribute of the Link header.
// https://fetch.spec.whatwg.org/#concept-potential-destination
// https://fetch.spec.whatwg.org/#concept-request-destination
enum LinkAsAttribute {
kUnspecified,
kFont,
kImage,
kScript,
kStyleSheet,
kFetch,
};

// Represents subset of possible values for `crossorigin` attribute.
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const CROSS_ORIGIN_RESOURCES_URL = CROSS_ORIGIN + RESOURCES_PATH;
* @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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ def handle_headers(frame, request, response):
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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
cache-control: max-age=600
access-control-allow-origin: *
timing-allow-origin: *
cross-origin-resource-policy: cross-origin
Original file line number Diff line number Diff line change
@@ -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>

0 comments on commit d18285c

Please sign in to comment.