Skip to content

Commit 65d8738

Browse files
natechapinchromium-wpt-export-bot
authored andcommitted
Implement NavigateEvent.redirect()
This is an experimental addition to the Navigation API's deferred commit proposal: https://github.com/wicg/navigation-api#deferred-commit Explainer: WICG/navigation-api#27 I2P: https://groups.google.com/a/chromium.org/g/blink-dev/c/Aef4fm1Wn18 Change-Id: I6c628074dd7fd30c436dddc685609c69a80bcc88 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6236600 Commit-Queue: Nate Chapin <[email protected]> Reviewed-by: Domenic Denicola <[email protected]> Cr-Commit-Position: refs/heads/main@{#1418269}
1 parent 3b3421c commit 65d8738

File tree

5 files changed

+316
-0
lines changed

5 files changed

+316
-0
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<!doctype html>
2+
<script src="/resources/testharness.js"></script>
3+
<script src="/resources/testharnessreport.js"></script>
4+
<body>
5+
<script>
6+
promise_test(async t => {
7+
// Wait for after the load event so that the navigation doesn't get converted
8+
// into a replace navigation.
9+
await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0));
10+
11+
let start_length = navigation.entries().length;
12+
let start_hash = location.hash;
13+
navigation.onnavigate = t.step_func(e => {
14+
e.intercept({
15+
handler: t.step_func(() => {
16+
assert_equals(location.hash, start_hash);
17+
assert_equals(new URL(e.destination.url).hash, "#push");
18+
19+
e.redirect("#redirect1");
20+
assert_equals(location.hash, start_hash);
21+
assert_equals(new URL(e.destination.url).hash, "#redirect1");
22+
23+
e.redirect("#redirect2");
24+
assert_equals(location.hash, start_hash);
25+
assert_equals(new URL(e.destination.url).hash, "#redirect2");
26+
27+
e.commit();
28+
assert_equals(location.hash, "#redirect2");
29+
assert_equals(new URL(e.destination.url).hash, "#redirect2");
30+
}),
31+
commit: "after-transition"
32+
});
33+
});
34+
await navigation.navigate("#push").committed;
35+
assert_equals(location.hash, "#redirect2");
36+
assert_equals(navigation.entries().length, start_length + 1);
37+
}, "redirect() then commit()");
38+
</script>
39+
</body>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<!doctype html>
2+
<script src="/resources/testharness.js"></script>
3+
<script src="/resources/testharnessreport.js"></script>
4+
<body>
5+
<script>
6+
promise_test(async t => {
7+
// Wait for after the load event so that the navigation doesn't get converted
8+
// into a replace navigation.
9+
await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0));
10+
11+
let start_length = navigation.entries().length;
12+
let start_hash = location.hash;
13+
navigation.onnavigate = t.step_func(e => {
14+
e.intercept({
15+
handler: t.step_func(() => {
16+
assert_equals(location.hash, start_hash);
17+
assert_equals(new URL(e.destination.url).hash, "#push");
18+
19+
e.redirect("#redirect1");
20+
assert_equals(location.hash, start_hash);
21+
assert_equals(new URL(e.destination.url).hash, "#redirect1");
22+
23+
e.redirect("#redirect2");
24+
assert_equals(location.hash, start_hash);
25+
assert_equals(new URL(e.destination.url).hash, "#redirect2");
26+
}),
27+
commit: "after-transition"
28+
});
29+
});
30+
await navigation.navigate("#push").committed;
31+
assert_equals(location.hash, "#redirect2");
32+
assert_equals(navigation.entries().length, start_length + 1);
33+
}, "redirect() push");
34+
</script>
35+
</body>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<!doctype html>
2+
<script src="/resources/testharness.js"></script>
3+
<script src="/resources/testharnessreport.js"></script>
4+
<body>
5+
<script>
6+
promise_test(async t => {
7+
// Wait for after the load event so that the navigation doesn't get converted
8+
// into a replace navigation.
9+
await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0));
10+
11+
let start_length = navigation.entries().length;
12+
let start_hash = location.hash;
13+
navigation.onnavigate = t.step_func(e => {
14+
e.intercept({
15+
handler: t.step_func(() => {
16+
assert_equals(location.hash, start_hash);
17+
assert_equals(new URL(e.destination.url).hash, "#replace");
18+
19+
e.redirect("#redirect1");
20+
assert_equals(location.hash, start_hash);
21+
assert_equals(new URL(e.destination.url).hash, "#redirect1");
22+
23+
e.redirect("#redirect2");
24+
assert_equals(location.hash, start_hash);
25+
assert_equals(new URL(e.destination.url).hash, "#redirect2");
26+
}),
27+
commit: "after-transition"
28+
});
29+
});
30+
await navigation.navigate("#replace", { history: "replace" }).committed;
31+
assert_equals(location.hash, "#redirect2");
32+
assert_equals(navigation.entries().length, start_length);
33+
}, "redirect() replace");
34+
</script>
35+
</body>
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
<!doctype html>
2+
<script src="/resources/testharness.js"></script>
3+
<script src="/resources/testharnessreport.js"></script>
4+
<body>
5+
<script>
6+
promise_test(async t => {
7+
navigation.onnavigate = t.step_func(e => {
8+
assert_throws_dom("InvalidStateError", () => e.redirect("#"));
9+
});
10+
await navigation.navigate("#").finished;
11+
}, "redirect() before intercept()");
12+
13+
promise_test(async t => {
14+
navigation.onnavigate = t.step_func(e => {
15+
e.intercept({ handler: t.step_func(() => {
16+
assert_throws_dom("InvalidStateError", () => e.redirect("#"));
17+
}) });
18+
});
19+
await navigation.navigate("#").finished;
20+
}, "redirect() without commit behavior specified");
21+
22+
promise_test(async t => {
23+
navigation.onnavigate = t.step_func(e => {
24+
e.intercept({
25+
handler: t.step_func(() => {
26+
assert_throws_dom("InvalidStateError", () => e.redirect("#"));
27+
}),
28+
commit: "immediate"
29+
});
30+
});
31+
await navigation.navigate("#").finished;
32+
}, "redirect() with { commit: immediate }");
33+
34+
promise_test(async t => {
35+
navigation.onnavigate = t.step_func(e => {
36+
e.intercept({ commit: "after-transition" });
37+
assert_throws_dom("InvalidStateError", () => e.redirect("#"));
38+
});
39+
await navigation.navigate("#").finished;
40+
}, "redirect() during event dispatch");
41+
42+
promise_test(async t => {
43+
let navigate_event;
44+
navigation.onnavigate = t.step_func(e => {
45+
e.intercept({ commit: "after-transition" });
46+
navigate_event = e;
47+
});
48+
await navigation.navigate("#").finished;
49+
assert_throws_dom("InvalidStateError", () => navigate_event.redirect("#"));
50+
}, "redirect() after finish");
51+
52+
promise_test(async t => {
53+
navigation.onnavigate = t.step_func(e => {
54+
e.intercept({
55+
handler: t.step_func(() => {
56+
e.commit();
57+
assert_throws_dom("InvalidStateError", () => e.redirect("#"));
58+
}),
59+
commit: "after-transition"
60+
});
61+
});
62+
await navigation.navigate("#").finished;
63+
}, "redirect() after commit()");
64+
65+
promise_test(async t => {
66+
// We need to grab an NavigationDestination to construct the event.
67+
navigation.onnavigate = t.step_func(e => {
68+
const event = new NavigateEvent("navigate", {
69+
destination: e.destination,
70+
signal: (new AbortController()).signal
71+
});
72+
73+
assert_throws_dom("SecurityError", () => event.redirect("#"));
74+
});
75+
await navigation.navigate("#").finished;
76+
}, "redirect() on synthetic NavigateEvent");
77+
78+
promise_test(async t => {
79+
let i = document.createElement("iframe");
80+
i.src = "about:blank";
81+
document.body.appendChild(i);
82+
i.contentWindow.navigation.onnavigate = t.step_func(e => {
83+
e.intercept({
84+
handler: t.step_func(() => {
85+
let iframe_constructor = i.contentWindow.DOMException;
86+
i.remove();
87+
assert_throws_dom("InvalidStateError", iframe_constructor, () => e.redirect("#"));
88+
}),
89+
commit: "after-transition"
90+
});
91+
});
92+
i.contentWindow.navigation.navigate("#");
93+
}, "redirect() in detached iframe");
94+
95+
promise_test(async t => {
96+
navigation.onnavigate = t.step_func(e => {
97+
e.intercept({ handler: t.step_func(() => {
98+
assert_throws_dom("SyntaxError", () => e.redirect("https://example.com\u0000mozilla.org"));
99+
}),
100+
commit: "after-transition" });
101+
});
102+
await navigation.navigate("#").finished;
103+
}, "redirect() to invalid url");
104+
105+
promise_test(async t => {
106+
navigation.onnavigate = t.step_func(e => {
107+
e.intercept({ handler: t.step_func(() => {
108+
assert_throws_dom("SecurityError", () => e.redirect("https://example.com"));
109+
}),
110+
commit: "after-transition" });
111+
});
112+
await navigation.navigate("#").finished;
113+
}, "redirect() to cross-origin url");
114+
115+
promise_test(async t => {
116+
navigation.onnavigate = t.step_func(e => {
117+
e.intercept({ handler: t.step_func(() => {
118+
assert_throws_dom("InvalidStateError", () => e.redirect("#"));
119+
}),
120+
commit: "after-transition" });
121+
});
122+
await navigation.reload().finished;
123+
}, "redirect() reload");
124+
125+
promise_test(async t => {
126+
// Wait for after the load event so that the navigation doesn't get converted
127+
// into a replace navigation.
128+
await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0));
129+
130+
await navigation.navigate("#forward").finished;
131+
132+
navigation.onnavigate = t.step_func(e => {
133+
e.intercept({ handler: t.step_func(() => {
134+
assert_throws_dom("InvalidStateError", () => e.redirect("#"));
135+
}),
136+
commit: "after-transition" });
137+
});
138+
await navigation.back().finished;
139+
}, "redirect() traverse");
140+
</script>
141+
</body>
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<!doctype html>
2+
<script src="/resources/testharness.js"></script>
3+
<script src="/resources/testharnessreport.js"></script>
4+
<meta name="variant" content="?no-currententrychange">
5+
<meta name="variant" content="?currententrychange">
6+
7+
<script type="module">
8+
import { Recorder, hasVariant } from "./resources/helpers.mjs";
9+
10+
promise_test(async t => {
11+
// Wait for after the load event so that the navigation doesn't get converted
12+
// into a replace navigation.
13+
await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0));
14+
15+
const from = navigation.currentEntry;
16+
17+
const recorder = new Recorder({
18+
skipCurrentChange: !hasVariant("currententrychange"),
19+
finalExpectedEvent: "transition.finished fulfilled"
20+
});
21+
22+
recorder.setUpNavigationAPIListeners();
23+
24+
navigation.addEventListener("navigate", e => {
25+
e.intercept({ commit: "after-transition",
26+
async handler() {
27+
recorder.record("handler start");
28+
await new Promise(r => t.step_timeout(r, 0));
29+
recorder.record("handler async step 1a");
30+
e.redirect("#2");
31+
recorder.record("handler async step 1b");
32+
await new Promise(r => t.step_timeout(r, 0));
33+
recorder.record("handler async step 2a");
34+
e.commit();
35+
recorder.record("handler async step 2b");
36+
await new Promise(r => t.step_timeout(r, 0));
37+
recorder.record("handler async step 3");
38+
}
39+
});
40+
});
41+
42+
const result = navigation.navigate("#1");
43+
recorder.setUpResultListeners(result);
44+
45+
Promise.resolve().then(() => recorder.record("promise microtask"));
46+
47+
await recorder.readyToAssert;
48+
49+
recorder.assert([
50+
/* event name, location.hash value, navigation.transition properties */
51+
["navigate", "", null],
52+
["handler start", "", { from, navigationType: "push" }],
53+
["promise microtask", "", { from, navigationType: "push" }],
54+
["handler async step 1a", "", { from, navigationType: "push" }],
55+
["handler async step 1b", "", { from, navigationType: "push" }],
56+
["handler async step 2a", "", { from, navigationType: "push" }],
57+
["currententrychange", "#2", { from, navigationType: "push" }],
58+
["handler async step 2b", "#2", { from, navigationType: "push" }],
59+
["committed fulfilled", "#2", { from, navigationType: "push" }],
60+
["handler async step 3", "#2", { from, navigationType: "push" }],
61+
["navigatesuccess", "#2", { from, navigationType: "push" }],
62+
["finished fulfilled", "#2", null],
63+
["transition.finished fulfilled", "#2", null],
64+
]);
65+
}, "event and promise ordering for same-document navigation.navigate() intercepted by intercept() with { commit: 'after-transition' } and a redirect()");
66+
</script>

0 commit comments

Comments
 (0)