Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update overlapping nav and traversal tests to match the spec rewrite #36364

Merged
merged 4 commits into from
Oct 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Overlapping navigation and traversal tests

These tests follow the behavior outlined in the
[session history rewrite](https://github.com/whatwg/html/pull/6315).

<https://github.com/whatwg/html/issues/6927> discusses these results.

We are not yet 100% sure on this behavior, especially for overlapping
traversal cases where the spec is complex and some of the tests don't
seem to match any browser. Please feel free to discuss on the spec
issue.
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@
let navigationsPromise = new Promise(resolve => {
onpopstate = () => {
navigations.push(location.hash);
if (navigations.length == 2)
resolve();
if (navigations.length === 2) {
resolve();
}
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
<script src="/resources/testharnessreport.js"></script>

<!--
According to the spec, "traverse the history by a delta" (e.g. history.back())
cancels any non-mature navigations.
According to the spec, "apply the history step" will set the ongoing
navigation to "traversal", canceling any non-mature navigations.
-->

<body>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

<!--
According to the spec, the "URL and history update steps" (used by
pushState()) and the fragment navigation steps, both cancel any ongoing
history traversals, but do *not* cancel any ongoing navigations.
pushState()) and the fragment navigation steps, do *not* modify the ongoing
navigation, i.e. do not cancel any navigations.
-->

<body>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
<script src="/resources/testharnessreport.js"></script>

<!--
According to the spec, "traverse the history by a delta" (e.g. history.back())
cancels any non-mature navigations.
According to the spec, "apply the history step" will set the ongoing
navigation to "traversal", canceling any navigation that is still processing
in parallel and hasn't yet reached "apply the history step".
-->

<body>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@
<script src="/resources/testharnessreport.js"></script>

<!--
Apparently if a cross-document traversal is in progress, a cross-document
navigation just gets ignored. (Instead of, as you might expect, a race.)
This does not match the spec. This test instead asserts browser behavior.
According to the spec, if ongoing navigation is "traversal", the navigation
fails and nothing happens.
-->

<body>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,22 @@
<script src="/resources/testharnessreport.js"></script>

<!--
In the spec, all traversals are queued. However, what "back" and "forward"
mean is computed synchronously. So per spec:

- back(), back(): go back 1.
- back(), forward(): go forward 1 (if you're not starting from the end).

This is not how browsers behave:

- Chrome seems to coalesce all traversals and apply them at once, i.e.:

- back(), back(): go back 2 at once.
- back(), forward(): go nowhere.

- Firefox seems to ignore traverals requests while a traversal is ongoing:

- back(), back(): go back 1.
- back(), forward(): go back 1.

We assert the Firefox behavior here for now.
In the spec, all traversals are queued, and that includes computing what
"back" and "forward" mean, based on the "current session history step". The
"current session history step" is updated at the end of "apply the history
step", at which point the queued steps in "traverse history by a delta" get to
run and compute what is back/forward. So the basic structure is:

- back(), back(): go back once, then again.
- back(), forward(): go back once, then go forward.

However, note that these observable effects (e.g., actually loading an
intermediate document) are done via queued tasks. Those tasks will end up not
running, once we switch the active document due to the second traversal. So
the end observable result looks like:

- back(), back(): go back -2.
- back(), forward(): go nowhere.
-->

<body>
Expand Down Expand Up @@ -57,14 +54,11 @@
iframe.contentWindow.history.forward();
assert_equals(iframe.contentWindow.location.search, "?2", "must not go forward synchronously");

await waitForLoad(iframe);
assert_equals(iframe.contentWindow.location.search, "?1", "first load event must be going back");

iframe.onload = t.unreached_func("second load event");

await waitForPotentialNetworkLoads(t);
assert_equals(iframe.contentWindow.location.search, "?1", "must stay on ?1");
}, "cross-document traversals in opposite directions: the second is ignored");
assert_equals(iframe.contentWindow.location.search, "?2", "must stay on ?2");
}, "cross-document traversals in opposite directions: the result is going nowhere");

promise_test(async t => {
const iframe = await createIframe(t);
Expand All @@ -86,14 +80,11 @@
iframe.contentWindow.history.forward();
assert_equals(iframe.contentWindow.location.search, "?2", "must not go forward synchronously");

await waitForLoad(iframe);
assert_equals(iframe.contentWindow.location.search, "?1", "first load event must be going back");

iframe.onload = t.unreached_func("second load event");

await waitForPotentialNetworkLoads(t);
assert_equals(iframe.contentWindow.location.search, "?1", "must stay on ?1");
}, "cross-document traversals in opposite directions, second traversal invalid at queuing time: the second is ignored");
assert_equals(iframe.contentWindow.location.search, "?2", "must stay on ?2");
}, "cross-document traversals in opposite directions, second traversal invalid at queuing time but valid at the time it is run: the result is going nowhere");
domenic marked this conversation as resolved.
Show resolved Hide resolved

promise_test(async t => {
const iframe = await createIframe(t);
Expand All @@ -119,13 +110,13 @@
assert_equals(iframe.contentWindow.location.search, "?3", "must not go back synchronously (2)");

await waitForLoad(iframe);
assert_equals(iframe.contentWindow.location.search, "?2", "first load event must be going back");
assert_equals(iframe.contentWindow.location.search, "?1", "first load event must be going back");

iframe.onload = t.unreached_func("second load event");

await waitForPotentialNetworkLoads(t);
assert_equals(iframe.contentWindow.location.search, "?2", "must stay on ?2");
}, "cross-document traversals in the same (back) direction: the second is ignored");
assert_equals(iframe.contentWindow.location.search, "?1", "must stay on ?1");
}, "cross-document traversals in the same (back) direction: the result is going -2 with only one load event");

promise_test(async t => {
const iframe = await createIframe(t);
Expand Down Expand Up @@ -159,11 +150,11 @@
assert_equals(iframe.contentWindow.location.search, "?1", "must not go forward synchronously (2)");

await waitForLoad(iframe);
assert_equals(iframe.contentWindow.location.search, "?2", "first load event must be going forward");
assert_equals(iframe.contentWindow.location.search, "?3", "first load event must be going forward");

iframe.onload = t.unreached_func("second load event");

await waitForPotentialNetworkLoads(t);
assert_equals(iframe.contentWindow.location.search, "?2", "must stay on ?2");
}, "cross-document traversals in the same (forward) direction: the second is ignored");
assert_equals(iframe.contentWindow.location.search, "?3", "must stay on ?3");
}, "cross-document traversals in the same (forward) direction: the result is going +2 with only one load event");
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,8 @@
<script src="/resources/testharnessreport.js"></script>

<!--
The spec currently says that same-document navigations must stop traverals,
but this does not match browsers: https://github.com/whatwg/html/issues/6773.
Browsers also disagree on how far back this should take us. This test assumes
a behavior similar to Firefox's, although Firefox is inconsistent and only
applies this behavior for fragments, not for pushState().
The spec explicitly covers this case, with a Jake diagram:
https://whatpr.org/html/6315/browsing-the-web.html#example-sync-navigation-steps-queue-jumping-basic
-->

<body>
Expand Down Expand Up @@ -38,9 +35,9 @@
assert_equals(iframe.contentWindow.location.search, "?2");
assert_equals(iframe.contentWindow.location.hash, "#3");

// Eventually ends up on ?2
await t.step_wait(() => iframe.contentWindow.location.search === "?2" && iframe.contentWindow.location.hash === "");
}, "same-document traversals are not canceled by fragment navigations");
// Eventually ends up on ?1
await t.step_wait(() => iframe.contentWindow.location.search === "?1" && iframe.contentWindow.location.hash === "");
}, "same-document traversals + fragment navigations");
domfarolino marked this conversation as resolved.
Show resolved Hide resolved

promise_test(async t => {
const iframe = await createIframe(t);
Expand All @@ -63,7 +60,7 @@
iframe.contentWindow.history.pushState(null, "", "?3");
assert_equals(iframe.contentWindow.location.search, "?3");

// Eventually ends up on ?2
await t.step_wait(() => iframe.contentWindow.location.search === "?2");
}, "same-document traversals are not canceled by pushState()");
// Eventually ends up on ?1
await t.step_wait(() => iframe.contentWindow.location.search === "?1");
}, "same-document traversals + pushState()");
domfarolino marked this conversation as resolved.
Show resolved Hide resolved
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,8 @@
<script src="/resources/testharnessreport.js"></script>

<!--
In the spec, all traversals are queued. However, what "back" and "forward"
mean is computed synchronously. So per spec:

- back(), back(): go back 1.

This is not how browsers behave:

- Chrome seems to coalesce all traversals and apply them at once, i.e.:

- back(), back(): go back 2 at once.

- Firefox seems to ignore traverals requests while a traversal is ongoing:

- back(), back(): go back 1.

We assert the Firefox behavior here for now.
This case is not significantly different from
cross-document-traversal-cross-document-traversal.html.
-->

<body>
Expand Down Expand Up @@ -53,15 +39,15 @@

await waitForLoad(iframe);
assert_equals(iframe.contentWindow.location.search, "?1", "first load event must be going back (search)");
assert_equals(iframe.contentWindow.location.hash, "#2", "first load event must be going back (hash)");
assert_equals(iframe.contentWindow.location.hash, "", "first load event must be going back (hash)");

iframe.contentWindow.onhashchange = t.unreached_func("hashchange event");
iframe.onload = t.unreached_func("second load event");

await waitForPotentialNetworkLoads(t);
assert_equals(iframe.contentWindow.location.search, "?1", "must stay on ?1#2 (search)");
assert_equals(iframe.contentWindow.location.hash, "#2", "must stay on ?1#2 (hash)");
}, "traversals in the same (back) direction: the second is ignored");
assert_equals(iframe.contentWindow.location.search, "?1", "must stay on ?1 (search)");
assert_equals(iframe.contentWindow.location.hash, "", "must stay on ?1 (hash)");
}, "traversals in the same (back) direction: coalesced");

promise_test(async t => {
const iframe = await createIframe(t);
Expand Down Expand Up @@ -96,13 +82,13 @@

await waitForLoad(iframe);
assert_equals(iframe.contentWindow.location.search, "?2", "first load event must be going forward (search)");
assert_equals(iframe.contentWindow.location.hash, "", "first load event must be going forward (hash)");
assert_equals(iframe.contentWindow.location.hash, "#3", "first load event must be going forward (hash)");

iframe.contentWindow.onhashchange = t.unreached_func("hashchange event");
iframe.onload = t.unreached_func("second load event");

await waitForPotentialNetworkLoads(t);
assert_equals(iframe.contentWindow.location.search, "?2", "must stay on ?2");
assert_equals(iframe.contentWindow.location.hash, "", "must stay on ?2");
}, "traversals in the same (forward) direction: the second is ignored");
assert_equals(iframe.contentWindow.location.hash, "#3", "must stay on ?2");
}, "traversals in the same (forward) direction: coalesced");
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
<script src="/resources/testharnessreport.js"></script>

<!--
The spec currently says that stop() must stop traverals, but this does not
match browsers: https://github.com/whatwg/html/issues/6905. This test assumes
browser behavior.
The spec says that stop() must not stop traverals.

(Note: the spec also says the UI "stop" button must not stop traversals, but
that does not match browsers. See https://github.com/whatwg/html/issues/6905.
But that is not what's under test here.)
-->

<body>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,4 @@
await new Promise(r => t.step_timeout(r, 20));
assert_equals(location.hash, "#clobber");
}, "If forward pruning clobbers the target of a traverse, abort");

</script>
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

<body>

<iframe src="http://{{domains[www1]}}:{{ports[http][0]}}/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/tentative/resources/nav-cancelation-2-helper.html"></iframe>
<iframe src="http://{{domains[www1]}}:{{ports[http][0]}}/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/resources/nav-cancelation-2-helper.html"></iframe>

<script>
promise_test(async t => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,18 @@ export function waitForPopstate(obj) {
// This is used when we want to end the test by asserting some load doesn't
// happen, but we're not sure how long to wait. We could just wait a long-ish
// time (e.g. a second), but that makes the tests slow. Instead, assume that
// network loads take roughly the same time, so by waiting for 2x the duration
// of a separate iframe load, we would have caught any problems.
// network loads take roughly the same time. Then, you can use this function to
// wait a small multiple of the duration of a separate iframe load; this should
// be long enough to catch any problems.
export async function waitForPotentialNetworkLoads(t) {
const before = performance.now();

// Sometimes we're doing something, like a traversal, which cancels our first
// attempt at iframe loading. In that case we bail out after 100 ms and try
// again. (Better ideas welcome...)
await Promise.race([createIframe(t), delay(t, 100)]);
await createIframe(t);

const after = performance.now();
await delay(t, after - before);
domenic marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,15 @@
<script src="/resources/testharnessreport.js"></script>

<!--
The spec currently says that cross-document navigations cancel traversals
only once they mature. So the traversal and navigation both go through.
We use slow.py to ensure the traversal finishes first (although it'd be pretty
likely even with /common/blank.html, since same-document traverals should be
fast).
The spec says that navigations are ignored if there is an ongoing traversal.
-->

<body>
<script type="module">
import { createIframe, delay } from "./resources/helpers.mjs";
import { createIframe, delay, waitForPotentialNetworkLoads } from "./resources/helpers.mjs";

promise_test(async t => {
const iframe = await createIframe(t);
const slowURL = (new URL("resources/slow.py", location.href)).href;

// Setup
iframe.contentWindow.location.hash = "#1";
Expand All @@ -31,14 +26,17 @@

assert_equals(iframe.contentWindow.location.hash, "#2", "must not go back synchronously");

iframe.contentWindow.location.href = slowURL;
iframe.contentWindow.location.search = "?1";
assert_equals(iframe.contentWindow.location.search, "", "must not navigate synchronously (search)");
assert_equals(iframe.contentWindow.location.hash, "#2", "must not navigate synchronously (hash)");

// Eventually ends up on #2
await t.step_wait(() => iframe.contentWindow.location.hash === "#2", "traversal");
// Eventually ends up on #1.
await t.step_wait(() => iframe.contentWindow.location.hash === "#1", "traversal");

// And then slow.py
await t.step_wait(() => iframe.contentWindow.location.href === slowURL, "navigation");
// Never loads a different document.
iframe.onload = t.unreached_func("load event");
await waitForPotentialNetworkLoads(t);
assert_equals(iframe.contentWindow.location.search, "", "must stay on #2 (search)");
assert_equals(iframe.contentWindow.location.hash, "#2", "must stay on #2 (hash)");
}, "same-document traversals are not canceled by cross-document navigations");
</script>
Loading