Skip to content

Commit 0366f3d

Browse files
Bug 2049838 [wpt PR 60823] - Navigation-Timing: destination-based TAO, a=testonly
Automatic update from web-platform-tests Navigation-Timing: destination-based TAO (#60823) This CL implements whatwg/html#12513 and whatwg/fetch#1931, in order to resolve w3c/navigation-timing#215. It enables developers to opt-in to get cross-origin redirect timings, through destination-based Timing-Allow-Origin headers. I2P: https://groups.google.com/a/chromium.org/g/blink-dev/c/GOPn8-wQlsk/m/x5Q3G6KuAgAJ Change-Id: Ib463010f1862af523a241d2e9180ff09bfedb101 Bug: 521861828 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7915892 Commit-Queue: Yoav Weiss (@Shopify) <yoavweiss@chromium.org> Reviewed-by: Noam Rosenthal <nrosenthal@google.com> Cr-Commit-Position: refs/heads/main@{#1650808} Co-authored-by: Yoav Weiss <yoavweiss@chromium.org> -- wpt-commits: 2b0c3ce064199c58603edca2a8562da285f8d82b wpt-pr: 60823
1 parent 824b50e commit 0366f3d

4 files changed

Lines changed: 145 additions & 2 deletions

File tree

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<!DOCTYPE html>
2+
<meta charset="utf-8">
3+
<title>Navigation Timing: redirect timing exposure for cross-origin redirect chains based on Timing-Allow-Origin and the destination origin</title>
4+
<link rel="author" title="Yoav Weiss" href="mailto:yoav@yoav.ws">
5+
<link rel="help" href="https://github.com/whatwg/fetch/pull/1931">
6+
<link rel="help" href="https://github.com/whatwg/html/pull/12513">
7+
<script src="/resources/testharness.js"></script>
8+
<script src="/resources/testharnessreport.js"></script>
9+
<script src="/common/utils.js"></script>
10+
<script src="resources/redirect-tao-helper.js"></script>
11+
<body>
12+
<script>
13+
const dest = DESTINATION_ORIGIN;
14+
15+
promise_test(async () => {
16+
const entry = await navigation_entry_after_redirects([dest]);
17+
assert_redirect_timing_exposed(entry, 1);
18+
}, "Exposed when a cross-origin redirect opts in to the destination origin");
19+
20+
promise_test(async () => {
21+
const entry = await navigation_entry_after_redirects(["*"]);
22+
assert_redirect_timing_exposed(entry, 1);
23+
}, "Exposed when a cross-origin redirect opts in with a wildcard");
24+
25+
promise_test(async () => {
26+
const entry = await navigation_entry_after_redirects([dest, dest]);
27+
assert_redirect_timing_exposed(entry, 2);
28+
}, "Exposed when every redirect in the chain opts in to the destination origin");
29+
30+
promise_test(async () => {
31+
const entry = await navigation_entry_after_redirects([
32+
"https://not-the-destination.example",
33+
]);
34+
assert_redirect_timing_hidden(entry);
35+
}, "Hidden when the redirect opts in to a non-destination origin");
36+
37+
promise_test(async () => {
38+
// First hop sends no Timing-Allow-Origin header; second hop opts in. Every
39+
// redirect in the chain must opt in for redirect timing to be exposed.
40+
const entry = await navigation_entry_after_redirects([null, dest]);
41+
assert_redirect_timing_hidden(entry);
42+
}, "Hidden when only part of the chain opts in to the destination origin");
43+
44+
promise_test(async () => {
45+
const entry = await navigation_entry_after_redirects([dest],
46+
{referrerPolicy: "no-referrer"});
47+
assert_redirect_timing_hidden(entry);
48+
}, "Hidden for a no-referrer navigation even when the redirect opts in");
49+
</script>
50+
</body>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Helpers for navigation redirect-timing TAO tests.
2+
//
3+
// These build a cross-origin server-side redirect chain (served from the "www"
4+
// subdomain, which is cross-origin to the test page) that finally lands back on
5+
// the test page's own origin -- the navigation's "destination origin". Each hop
6+
// is handled by resources/redirect-tao.py, which optionally emits a
7+
// Timing-Allow-Origin header.
8+
//
9+
// See:
10+
// https://github.com/whatwg/fetch/pull/1931
11+
// https://github.com/whatwg/html/pull/12513
12+
13+
// The navigation's destination origin (where the chain lands).
14+
const DESTINATION_ORIGIN = location.origin;
15+
16+
// The final, non-redirect document the chain resolves to (same-origin with the
17+
// test page, so its navigation timing entry is readable).
18+
const FINAL_URL =
19+
make_absolute_url({path: "/navigation-timing/resources/blank-page-green.html"});
20+
21+
// Builds a redirect-chain URL from `hops`, an array with one entry per redirect.
22+
// Each entry is the value to send in that redirect's Timing-Allow-Origin header,
23+
// or null to send no header (i.e. that hop does not opt in).
24+
function redirect_chain_url(hops) {
25+
let url = FINAL_URL;
26+
// Build from the last hop backwards, so each redirect points at the next one.
27+
for (let i = hops.length - 1; i >= 0; i--) {
28+
const tao = hops[i] === null ? "" : "tao=" + encodeURIComponent(hops[i]) + "&";
29+
url = make_absolute_url({
30+
subdomain: "www",
31+
path: "/navigation-timing/resources/redirect-tao.py",
32+
query: tao + "location=" + encodeURIComponent(url),
33+
});
34+
}
35+
return url;
36+
}
37+
38+
// Navigates an iframe through the redirect chain described by `hops` and resolves
39+
// with the iframe's PerformanceNavigationTiming entry. `referrerPolicy` is an
40+
// optional referrer policy to apply to the iframe (e.g. "no-referrer").
41+
function navigation_entry_after_redirects(hops, {referrerPolicy} = {}) {
42+
return new Promise(resolve => {
43+
const frame = document.createElement("iframe");
44+
frame.style.cssText = "width: 250px; height: 250px;";
45+
if (referrerPolicy) {
46+
frame.referrerPolicy = referrerPolicy;
47+
}
48+
frame.onload = () => {
49+
resolve(frame.contentWindow.performance.getEntriesByType("navigation")[0]);
50+
};
51+
frame.src = redirect_chain_url(hops);
52+
document.body.appendChild(frame);
53+
});
54+
}
55+
56+
// Asserts that redirect timing is exposed, with `expectedCount` redirects.
57+
function assert_redirect_timing_exposed(entry, expectedCount) {
58+
assert_equals(entry.type, "navigate", "navigation type");
59+
assert_equals(entry.redirectCount, expectedCount, "redirectCount");
60+
assert_greater_than(entry.redirectStart, 0, "redirectStart is exposed");
61+
assert_greater_than_equal(entry.redirectEnd, entry.redirectStart,
62+
"redirectEnd is greater than or equal to redirectStart");
63+
}
64+
65+
// Asserts that redirect timing is fully hidden (zeroed out).
66+
function assert_redirect_timing_hidden(entry) {
67+
assert_equals(entry.type, "navigate", "navigation type");
68+
assert_equals(entry.redirectCount, 0, "redirectCount is hidden");
69+
assert_equals(entry.redirectStart, 0, "redirectStart is hidden");
70+
assert_equals(entry.redirectEnd, 0, "redirectEnd is hidden");
71+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
def main(request, response):
2+
"""Redirect handler that optionally sets a Timing-Allow-Origin header.
3+
4+
Query parameters:
5+
status - The status code to use for the redirection. Defaults to 302.
6+
location - The (percent-encoded) resource to redirect to.
7+
tao - The value to send in the Timing-Allow-Origin response header. If
8+
absent, no Timing-Allow-Origin header is sent (i.e. the redirect
9+
does not opt in).
10+
"""
11+
status = 302
12+
if b"status" in request.GET:
13+
try:
14+
status = int(request.GET.first(b"status"))
15+
except ValueError:
16+
pass
17+
18+
response.status = status
19+
location = request.GET.first(b"location")
20+
response.headers.set(b"Location", location)
21+
if b"tao" in request.GET:
22+
response.headers.set(b"Timing-Allow-Origin", request.GET.first(b"tao"))

testing/web-platform/tests/navigation-timing/unload-event-same-origin-check.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
const cross_origin_start = host_info["HTTP_REMOTE_ORIGIN"] + start_page;
5454
const test_cases = [
5555
{ start_url : start_page, end_url: redirect_chain_partial_tao(), unload_exposed: false, redirects: 0, name: "Redirect chain with a partial TAO opt-in" },
56-
{ start_url : start_page, end_url: redirect_chain_full_tao(), unload_exposed: false, redirects: 0, name: "Redirect chain with full TAO opt-in" },
56+
{ start_url : start_page, end_url: redirect_chain_full_tao(), unload_exposed: false, redirects: 1, name: "Redirect chain with full TAO opt-in" },
5757
{ start_url : start_page, end_url: redirect_chain_no_tao(), unload_exposed: false, redirects: 0, name: "Same-cross-same redirect chain with no TAO opt-in" },
5858
{ start_url : cross_origin_start, end_url: redirect_chain_no_tao(), unload_exposed: false, redirects: 0, name: "cross-cross-same Redirect chain with no TAO opt-in" },
5959
{ start_url : cross_origin_start, end_url: end_page, unload_exposed: false, redirects: 0, name: "Previous document cross origin" },
@@ -62,7 +62,7 @@
6262
{ start_url : start_page, end_url: same_origin_redirect_chain(), unload_exposed: true, redirects: 1, name: "Same origin previous document with same origin redirect" },
6363
{ start_url : same_origin_redirect_chain(), end_url: null, unload_exposed: false, redirects: 1, name: "No previous document with same origin redirect" },
6464
{ start_url : redirect_chain_no_tao(), end_url: null, unload_exposed: false, redirects: 0, name: "No previous document with cross origin redirect" },
65-
{ start_url : redirect_chain_full_tao(), end_url: null, unload_exposed: false, redirects: 0, name: "No previous document with cross origin redirect with partial TAO" },
65+
{ start_url : redirect_chain_full_tao(), end_url: null, unload_exposed: false, redirects: 1, name: "No previous document with cross origin redirect with partial TAO" },
6666
{ start_url : redirect_chain_partial_tao(), end_url: null, unload_exposed: false, redirects: 0, name: "No previous document with cross origin redirect with TAO" },
6767
];
6868

0 commit comments

Comments
 (0)