diff options
Diffstat (limited to 'testing/web-platform/tests/navigation-api')
409 files changed, 11951 insertions, 0 deletions
diff --git a/testing/web-platform/tests/navigation-api/META.yml b/testing/web-platform/tests/navigation-api/META.yml new file mode 100644 index 0000000000..de4f6c9a33 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/META.yml @@ -0,0 +1,4 @@ +spec: https://wicg.github.io/navigation-api/ +suggested_reviewers: + - domenic + - natechapin diff --git a/testing/web-platform/tests/navigation-api/commit-behavior/after-transition-new-navigation-before-commit.html b/testing/web-platform/tests/navigation-api/commit-behavior/after-transition-new-navigation-before-commit.html new file mode 100644 index 0000000000..2d09d40dc9 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/commit-behavior/after-transition-new-navigation-before-commit.html @@ -0,0 +1,25 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../navigation-methods/return-value/resources/helpers.js"></script> +<body> +<script> +promise_test(async t => { + navigation.addEventListener("navigate", e => e.intercept({ commit: "after-transition" }), { once: "true" }); + + let navigateerror_called = false; + navigation.onnavigateerror = t.step_func(() => { + navigateerror_called = true; + assert_equals(location.hash, ""); + }); + + let promises_should_not_commit = navigation.navigate("#ShouldNotCommit"); + let promises_fulfilled = navigation.navigate("#1"); + await assertBothRejectDOM(t, promises_should_not_commit, "AbortError"); + await assertBothFulfill(t, promises_fulfilled, navigation.currentEntry); + + assert_equals(location.hash, "#1"); + assert_true(navigateerror_called); +}, "Cancel a { commit: 'after-transition' } navigation before commit() by starting a new navigation"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/commit-behavior/after-transition-push.html b/testing/web-platform/tests/navigation-api/commit-behavior/after-transition-push.html new file mode 100644 index 0000000000..34bdcf7cac --- /dev/null +++ b/testing/web-platform/tests/navigation-api/commit-behavior/after-transition-push.html @@ -0,0 +1,23 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../navigation-methods/return-value/resources/helpers.js"></script> +<script src="resources/after-transition-commit-helpers.js"></script> +<body> +<script> +let tests = [ + { mode: "rejectBeforeCommit", description: "{ commit: 'after-transition' } for a push navigation, reject before commit" }, + { mode: "rejectAfterCommit", description: "{ commit: 'after-transition' } for a push navigation, reject after commit" }, + { mode: "successExplicitCommit", description: "{ commit: 'after-transition' } for a push navigation, explicit commit()" }, + { mode: "successNoExplicitCommit", description: "{ commit: 'after-transition' } for a push navigation, commit when handler resolves" } +]; + +let onload_promise = new Promise(resolve => window.onload = resolve); +for (let test of tests) { + promise_test(async t => { + await onload_promise; + await testAfterTransitionCommit(t, "push", test.mode); + }, test.description); +} +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/commit-behavior/after-transition-reload.html b/testing/web-platform/tests/navigation-api/commit-behavior/after-transition-reload.html new file mode 100644 index 0000000000..203150eb05 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/commit-behavior/after-transition-reload.html @@ -0,0 +1,19 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../navigation-methods/return-value/resources/helpers.js"></script> +<script src="resources/after-transition-commit-helpers.js"></script> +<body> +<script> +let tests = [ + { mode: "rejectBeforeCommit", description: "{ commit: 'after-transition' } for a reload navigation, reject before commit" }, + { mode: "rejectAfterCommit", description: "{ commit: 'after-transition' } for a reload navigation, reject after commit" }, + { mode: "successExplicitCommit", description: "{ commit: 'after-transition' } for a reload navigation, explicit commit()" }, + { mode: "successNoExplicitCommit", description: "{ commit: 'after-transition' } for a reload navigation, commit when handler resolves" } +]; + +for (let test of tests) { + promise_test(t => testAfterTransitionCommit(t, "reload", test.mode), test.description); +} +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/commit-behavior/after-transition-replace.html b/testing/web-platform/tests/navigation-api/commit-behavior/after-transition-replace.html new file mode 100644 index 0000000000..2fd48736a3 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/commit-behavior/after-transition-replace.html @@ -0,0 +1,19 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../navigation-methods/return-value/resources/helpers.js"></script> +<script src="resources/after-transition-commit-helpers.js"></script> +<body> +<script> +let tests = [ + { mode: "rejectBeforeCommit", description: "{ commit: 'after-transition' } for a replace navigation, reject before commit" }, + { mode: "rejectAfterCommit", description: "{ commit: 'after-transition' } for a replace navigation, reject after commit" }, + { mode: "successExplicitCommit", description: "{ commit: 'after-transition' } for a replace navigation, explicit commit()" }, + { mode: "successNoExplicitCommit", description: "{ commit: 'after-transition' } for a replace navigation, commit when handler resolves" } +]; + +for (let test of tests) { + promise_test(t => testAfterTransitionCommit(t, "replace", test.mode), test.description); +} +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/commit-behavior/after-transition-traversal-commit-new-navigation-before-commit.html b/testing/web-platform/tests/navigation-api/commit-behavior/after-transition-traversal-commit-new-navigation-before-commit.html new file mode 100644 index 0000000000..9e74e10676 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/commit-behavior/after-transition-traversal-commit-new-navigation-before-commit.html @@ -0,0 +1,36 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../navigation-methods/return-value/resources/helpers.js"></script> +<body> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + await navigation.navigate("#1").finished; + + let navigateerror_called = false; + navigation.onnavigateerror = t.step_func(() => { + navigateerror_called = true; + assert_equals(location.hash, "#1"); + }); + + // Go back and wait for the navigate event to fire. This traversal will be deferred. + let promises_should_not_commit = assertBothRejectDOM(t, navigation.back(), "AbortError"); + navigation.addEventListener("navigate", e => e.intercept({ commit: "after-transition", handler: () => new Promise(r => t.step_timeout(r, 1000)) }), { once: "true" }); + await new Promise(resolve => navigation.addEventListener("navigate", resolve, { once: "true" })); + + // While the traversal is deferred, start a new navigation and commit immediately. + navigation.addEventListener("navigate", e => e.intercept(), { once: "true" }); + let promises_fulfilled = navigation.navigate("#2"); + + await promises_should_not_commit; + await assertBothFulfill(t, promises_fulfilled, navigation.currentEntry); + + assert_equals(location.hash, "#2"); + assert_true(navigateerror_called); +}, "Cancel a { commit: 'after-transition' } traversal before commit() by starting a new navigation"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/commit-behavior/after-transition-traverse.html b/testing/web-platform/tests/navigation-api/commit-behavior/after-transition-traverse.html new file mode 100644 index 0000000000..7dd1226e54 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/commit-behavior/after-transition-traverse.html @@ -0,0 +1,29 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../navigation-methods/return-value/resources/helpers.js"></script> +<script src="resources/after-transition-commit-helpers.js"></script> +<body> +<script> +let tests = [ + { mode: "rejectBeforeCommit", destinationIndex: 0, description: "{ commit: 'after-transition' } for a traverse navigation, reject before commit" }, + { mode: "rejectAfterCommit", destinationIndex: 1, description: "{ commit: 'after-transition' } for a traverse navigation, reject after commit" }, + { mode: "successExplicitCommit", destinationIndex: 2, description: "{ commit: 'after-transition' } for a traverse navigation, explicit commit()" }, + { mode: "successNoExplicitCommit", destinationIndex: 3, description: "{ commit: 'after-transition' } for a traverse navigation, commit when handler resolves" } +]; + +// Push a bunch of history entries so each test case can target a unique entry. +history.pushState("", "", "#1"); +history.pushState("", "", "#2"); +history.pushState("", "", "#3"); +history.pushState("", "", "#4"); + +let onload_promise = new Promise(resolve => window.onload = resolve); +for (let test of tests) { + promise_test(async t => { + await onload_promise; + await testAfterTransitionCommit(t, "traverse", test.mode, test.destinationIndex); + }, test.description); +} +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/commit-behavior/after-transition-uncancelable.html b/testing/web-platform/tests/navigation-api/commit-behavior/after-transition-uncancelable.html new file mode 100644 index 0000000000..45bde9f090 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/commit-behavior/after-transition-uncancelable.html @@ -0,0 +1,23 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<body> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + await i.contentWindow.navigation.navigate("#1").finished; + + i.contentWindow.navigation.onnavigate = t.step_func(e => { + assert_false(e.cancelable); + // intercept() should throw with commit: "after-transition" because e.cancelable is false. + let iframe_constructor = i.contentWindow.DOMException; + assert_throws_dom("InvalidStateError", iframe_constructor, () => e.intercept({ commit: "after-transition" })); + }); + await i.contentWindow.navigation.back().finished; +}, "{ commit: 'after-transition' } for an uncancelable traverse navigation"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/commit-behavior/after-transition-window-stop-before-commit.html b/testing/web-platform/tests/navigation-api/commit-behavior/after-transition-window-stop-before-commit.html new file mode 100644 index 0000000000..0f5e57d5b6 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/commit-behavior/after-transition-window-stop-before-commit.html @@ -0,0 +1,33 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../navigation-methods/return-value/resources/helpers.js"></script> +<body> +<script> +promise_test(async t => { + // Wait for after the load event because window.stop() hangs the test harness + // if called before the load event. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + navigation.onnavigate = e => { + e.intercept({ commit: "after-transition" }); + }; + + navigation.onnavigatesuccess = t.unreached_func("navigatesuccess must not fire"); + let navigateerror_called = false; + navigation.onnavigateerror = t.step_func(() => { + navigateerror_called = true; + assert_equals(location.hash, ""); + }); + + let promises = navigation.navigate("#ShouldNotCommit"); + assert_equals(location.hash, ""); + + window.stop(); + await assertBothRejectDOM(t, promises, "AbortError"); + + assert_equals(location.hash, ""); + assert_true(navigateerror_called); +}, " { commit: 'after-transition' } with window.stop() before commit"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/commit-behavior/commit-throws.html b/testing/web-platform/tests/navigation-api/commit-behavior/commit-throws.html new file mode 100644 index 0000000000..54abdbfd0f --- /dev/null +++ b/testing/web-platform/tests/navigation-api/commit-behavior/commit-throws.html @@ -0,0 +1,95 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +promise_test(async t => { + navigation.onnavigate = t.step_func(e => { + assert_throws_dom("InvalidStateError", () => e.commit()); + }); + await navigation.navigate("#").finished; +}, "commit() before intercept()"); + +promise_test(async t => { + navigation.onnavigate = t.step_func(e => { + e.intercept({ handler: t.step_func(() => { + assert_throws_dom("InvalidStateError", () => e.commit()); + }) }); + }); + await navigation.navigate("#").finished; +}, "commit() without commit behavior specified"); + +promise_test(async t => { + navigation.onnavigate = t.step_func(e => { + e.intercept({ + handler: t.step_func(() => { + assert_throws_dom("InvalidStateError", () => e.commit()); + }), + commit: "immediate" + }); + }); + await navigation.navigate("#").finished; +}, "commit() with { commit: immediate }"); + +promise_test(async t => { + navigation.onnavigate = t.step_func(e => { + e.intercept({ commit: "after-transition" }); + assert_throws_dom("InvalidStateError", () => e.commit()); + }); + await navigation.navigate("#").finished; +}, "commit() during event dispatch"); + +promise_test(async t => { + let navigate_event; + navigation.onnavigate = t.step_func(e => { + e.intercept({ commit: "after-transition" }); + navigate_event = e; + }); + await navigation.navigate("#").finished; + assert_throws_dom("InvalidStateError", () => navigate_event.commit()); +}, "commit() after finish"); + +promise_test(async t => { + navigation.onnavigate = t.step_func(e => { + e.intercept({ + handler: t.step_func(() => { + e.commit(); + assert_throws_dom("InvalidStateError", () => e.commit()); + }), + commit: "after-transition" + }); + }); + await navigation.navigate("#").finished; +}, "commit() twice"); + +promise_test(async t => { + // We need to grab an NavigationDestination to construct the event. + navigation.onnavigate = t.step_func(e => { + const event = new NavigateEvent("navigate", { + destination: e.destination, + signal: (new AbortController()).signal + }); + + assert_throws_dom("SecurityError", () => event.commit()); + }); + await navigation.navigate("#").finished; +}, "commit() on synthetic NavigateEvent"); + +promise_test(async t => { + let i = document.createElement("iframe"); + i.src = "about:blank"; + document.body.appendChild(i); + i.contentWindow.navigation.onnavigate = t.step_func(e => { + e.intercept({ + handler: t.step_func(() => { + let iframe_constructor = i.contentWindow.DOMException; + i.remove(); + assert_throws_dom("InvalidStateError", iframe_constructor, () => e.commit()); + }), + commit: "after-transition" + }); + }); + i.contentWindow.navigation.navigate("#"); +}, "commit() in detached iframe"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/commit-behavior/multiple-intercept.html b/testing/web-platform/tests/navigation-api/commit-behavior/multiple-intercept.html new file mode 100644 index 0000000000..848af6a65d --- /dev/null +++ b/testing/web-platform/tests/navigation-api/commit-behavior/multiple-intercept.html @@ -0,0 +1,83 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div style="height: 1000px; width: 1000px;"></div> +<div id="frag"></div> +<script> +let i = 0; +async function urlDidChangeImmediately(listener, expected) { + navigation.addEventListener("navigate", listener, { once: true }); + let expected_hash = "#" + ++i; + assert_not_equals(location.hash, expected_hash); + let finished = navigation.navigate(expected_hash).finished; + assert_equals(location.hash === expected_hash, expected); + await finished; + assert_equals(location.hash, expected_hash); +} + +async function testUrlDidChangeImmediately(listener) { + await urlDidChangeImmediately(listener, true); +} + +async function testUrlDidNotChangeImmediately(listener) { + await urlDidChangeImmediately(listener, false); +} + +promise_test(async t => { + await testUrlDidNotChangeImmediately(e => { + e.intercept({ commit: "after-transition" }); + e.intercept({ commit: "after-transition" }); + }); +}, "after-transition + after-transition"); + +promise_test(async t => { + await testUrlDidNotChangeImmediately(e => { + e.intercept({ commit: "after-transition" }); + e.intercept(); + }); +}, "after-transition + (not provided)"); + +promise_test(async t => { + await testUrlDidChangeImmediately(e => { + e.intercept({ commit: "after-transition" }); + e.intercept({ commit: "immediate" }); + }); +}, "after-transition + immediate"); + +promise_test(async t => { + await testUrlDidNotChangeImmediately(e => { + e.intercept({ commit: "immediate" }); + e.intercept({ commit: "after-transition" }); + }); +}, "immediate + after-transition"); + +promise_test(async t => { + await testUrlDidChangeImmediately(e => { + e.intercept({ commit: "immediate" }); + e.intercept(); + }); +}, "immediate + (not provided)"); + +promise_test(async t => { + await testUrlDidChangeImmediately(e => { + e.intercept({ commit: "immediate" }); + e.intercept({ commit: "immediate" }); + }); +}, "immediate + immediate"); + +promise_test(async t => { + await testUrlDidNotChangeImmediately(e => { + e.intercept(); + e.intercept({ commit: "after-transition" }); + }); +}, "(not provided) + after-transition"); + +promise_test(async t => { + await testUrlDidChangeImmediately(e => { + e.intercept(); + e.intercept({ commit: "immediate" }); + }); +}, "(not provided) + immediate"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/commit-behavior/resources/after-transition-commit-helpers.js b/testing/web-platform/tests/navigation-api/commit-behavior/resources/after-transition-commit-helpers.js new file mode 100644 index 0000000000..664e8d7280 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/commit-behavior/resources/after-transition-commit-helpers.js @@ -0,0 +1,90 @@ +window.testAfterTransitionCommit = async (t, navigationType, mode, destinationIndex = 0) => { + let startHash = location.hash; + let destinationHash; + const err = new Error("boo!"); + + let popstate_fired = false; + window.addEventListener("popstate", () => popstate_fired = true, { once : true }); + let navigatesuccess_fired = false; + navigation.addEventListener("navigatesuccess", () => navigatesuccess_fired = true, { once : true }); + let navigateerror_fired = false; + navigation.addEventListener("navigateerror", () => navigateerror_fired = true, { once : true }); + + // mode-specific logic for the navigate event handler + let navigate_helpers = { + rejectBeforeCommit : async (e) => { + return Promise.reject(err); + }, + rejectAfterCommit : async (e) => { + e.commit(); + assert_equals(location.hash, destinationHash, "hash after commit"); + assert_true(popstate_fired, "popstate fired after commit"); + await new Promise(resolve => t.step_timeout(resolve, 0)); + return Promise.reject(err); + }, + successExplicitCommit : async (e) => { + e.commit(); + assert_equals(location.hash, destinationHash, "hash after commit"); + assert_true(popstate_fired, "popstate fired after commit"); + return new Promise(resolve => t.step_timeout(resolve, 0)); + }, + successNoExplicitCommit : async (e) => { + assert_equals(location.hash, startHash, "start has after first async step"); + assert_false(popstate_fired, "popstate fired after first async step"); + await new Promise(resolve => t.step_timeout(resolve, 0)); + assert_equals(location.hash, startHash, "start has after second async step"); + assert_false(popstate_fired, "popstate fired after second async step"); + return new Promise(resolve => t.step_timeout(resolve, 0)); + } + } + + navigation.addEventListener("navigate", e => { + e.intercept({ commit: "after-transition", + handler: t.step_func(async () => { + assert_equals(e.navigationType, navigationType); + assert_equals(location.hash, startHash, "start hash"); + assert_false(popstate_fired, "popstate fired at handler start"); + + await new Promise(resolve => t.step_timeout(resolve, 0)); + assert_equals(location.hash, startHash, "hash after first async step"); + assert_false(popstate_fired, "popstate fired after first async step"); + + return navigate_helpers[mode](e); + })}); + }, { once: true }); + + let promises; + if (navigationType === "push" || navigationType === "replace") { + destinationHash = (startHash === "" ? "#" : startHash) + "a"; + promises = navigation.navigate(destinationHash, { history: navigationType }); + } else if (navigationType === "reload") { + destinationHash = startHash; + promises = navigation.reload(); + } else if (navigationType === "traverse") { + let destinationEntry = navigation.entries()[destinationIndex]; + destinationHash = new URL(destinationEntry.url).hash; + promises = navigation.traverseTo(destinationEntry.key); + } + + if (mode === "rejectBeforeCommit") { + await assertBothRejectExactly(t, promises, err); + assert_equals(location.hash, startHash, "hash after promise resolution"); + assert_false(popstate_fired, "popstate fired after promise resolution"); + assert_false(navigatesuccess_fired, "navigatesuccess fired"); + assert_true(navigateerror_fired, "navigateerror fired"); + } else if (mode === "rejectAfterCommit") { + await promises.committed; + await assertCommittedFulfillsFinishedRejectsExactly(t, promises, navigation.currentEntry, err); + assert_equals(location.hash, destinationHash, "hash after promise resolution"); + assert_true(popstate_fired, "popstate fired after promise resolution"); + assert_false(navigatesuccess_fired, "navigatesuccess fired"); + assert_true(navigateerror_fired, "navigateerror fired"); + } else { + await promises.committed; + await assertBothFulfill(t, promises, navigation.currentEntry); + assert_equals(location.hash, destinationHash, "hash after promise resolution"); + assert_true(popstate_fired, "popstate fired after promise resolution"); + assert_true(navigatesuccess_fired, "navigatesuccess fired"); + assert_false(navigateerror_fired, "navigateerror fired"); + } +} diff --git a/testing/web-platform/tests/navigation-api/currententrychange-event/anchor-click.html b/testing/web-platform/tests/navigation-api/currententrychange-event/anchor-click.html new file mode 100644 index 0000000000..e0bf91166a --- /dev/null +++ b/testing/web-platform/tests/navigation-api/currententrychange-event/anchor-click.html @@ -0,0 +1,18 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<a id="a" href="#foo"></a> +<script> +test(t => { + let oncurrententrychange_called = false; + navigation.oncurrententrychange = t.step_func(e => { + oncurrententrychange_called = true; + assert_equals(e.from, navigation.entries()[0]); + assert_equals(e.from.index, 0); + assert_equals(e.navigationType, "push"); + assert_equals(navigation.currentEntry.index, 1); + }); + a.click(); + assert_true(oncurrententrychange_called); +}, "currententrychange fires for link click"); +</script> diff --git a/testing/web-platform/tests/navigation-api/currententrychange-event/constructor.html b/testing/web-platform/tests/navigation-api/currententrychange-event/constructor.html new file mode 100644 index 0000000000..b09e68e1c7 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/currententrychange-event/constructor.html @@ -0,0 +1,32 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +test(() => { + assert_throws_js(TypeError, () => { + new NavigationCurrentEntryChangeEvent("currententrychange"); + }); +}, "can't bypass required members by omitting the dictionary entirely"); + +test(() => { + assert_throws_js(TypeError, () => { + new NavigationCurrentEntryChangeEvent("currententrychange", { + navigationType: "push" + }); + }); +}, "from is required"); + +test(() => { + const event = new NavigationCurrentEntryChangeEvent("currententrychange", { + navigationType: "replace", + from: navigation.currentEntry + }); + assert_equals(event.navigationType, "replace"); + assert_equals(event.from, navigation.currentEntry); +}, "all properties are reflected back"); + +test(t => { + const event = new NavigationCurrentEntryChangeEvent("currententrychange", { from: navigation.currentEntry }); + assert_equals(event.navigationType, null); +}, "defaults are as expected"); +</script> diff --git a/testing/web-platform/tests/navigation-api/currententrychange-event/history-back-same-doc.html b/testing/web-platform/tests/navigation-api/currententrychange-event/history-back-same-doc.html new file mode 100644 index 0000000000..768805b752 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/currententrychange-event/history-back-same-doc.html @@ -0,0 +1,24 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + await navigation.navigate("#foo"); + assert_equals(navigation.entries().length, 2); + + let oncurrententrychange_called = false; + navigation.oncurrententrychange = t.step_func(e => { + oncurrententrychange_called = true; + assert_equals(e.from, navigation.entries()[1]); + assert_equals(e.navigationType, "traverse"); + assert_equals(navigation.currentEntry.index, 0); + }); + history.back(); + assert_false(oncurrententrychange_called); + await new Promise(resolve => window.onpopstate = resolve); + assert_true(oncurrententrychange_called); +}, "currententrychange fires for history.back()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/currententrychange-event/history-pushState.html b/testing/web-platform/tests/navigation-api/currententrychange-event/history-pushState.html new file mode 100644 index 0000000000..1a17a7cec0 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/currententrychange-event/history-pushState.html @@ -0,0 +1,16 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +test(t => { + let oncurrententrychange_called = false; + navigation.oncurrententrychange = t.step_func(e => { + oncurrententrychange_called = true; + assert_equals(e.from, navigation.entries()[0]); + assert_equals(e.navigationType, "push"); + assert_equals(navigation.currentEntry.index, 1); + }); + history.pushState(1, "", "#1"); + assert_true(oncurrententrychange_called); +}, "currententrychange fires for history.pushState()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/currententrychange-event/history-replaceState.html b/testing/web-platform/tests/navigation-api/currententrychange-event/history-replaceState.html new file mode 100644 index 0000000000..e8ae8ee57a --- /dev/null +++ b/testing/web-platform/tests/navigation-api/currententrychange-event/history-replaceState.html @@ -0,0 +1,18 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +test(t => { + let oncurrententrychange_called = false; + let original_currentEntry = navigation.currentEntry; + navigation.oncurrententrychange = t.step_func(e => { + oncurrententrychange_called = true; + assert_equals(e.from, original_currentEntry); + assert_equals(e.from.index, -1); + assert_equals(e.navigationType, "replace"); + assert_equals(navigation.currentEntry.index, 0); + }); + history.replaceState(1, "", "#1"); + assert_true(oncurrententrychange_called); +}, "currententrychange fires for history.replaceState()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/currententrychange-event/location-api.html b/testing/web-platform/tests/navigation-api/currententrychange-event/location-api.html new file mode 100644 index 0000000000..88ebd985a1 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/currententrychange-event/location-api.html @@ -0,0 +1,18 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +test(t => { + let oncurrententrychange_called = false; + let original_entry = navigation.currentEntry; + navigation.oncurrententrychange = t.step_func(e => { + oncurrententrychange_called = true; + assert_equals(e.from, original_entry); + assert_equals(e.from.index, -1); + assert_equals(e.navigationType, "replace"); + assert_equals(navigation.currentEntry.index, 0); + }); + location.hash = "#foo"; + assert_true(oncurrententrychange_called); +}, "currententrychange fires for location API navigations"); +</script> diff --git a/testing/web-platform/tests/navigation-api/currententrychange-event/navigate-from-initial-about-blank-same-doc-popup.html b/testing/web-platform/tests/navigation-api/currententrychange-event/navigate-from-initial-about-blank-same-doc-popup.html new file mode 100644 index 0000000000..2399fb2ef9 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/currententrychange-event/navigate-from-initial-about-blank-same-doc-popup.html @@ -0,0 +1,15 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async t => { + const w = window.open("about:blank#1"); + w.navigation.oncurrententrychange = t.unreached_func("currententrychange should not fire"); + + assert_equals(w.location.href, "about:blank#1"); + w.location.href = "about:blank#2"; + assert_equals(w.location.href, "about:blank#2"); + + await new Promise(resolve => t.step_timeout(resolve, 10)); +}, "currententrychange does not fire when navigating away from the initial about:blank (popup window)"); +</script> diff --git a/testing/web-platform/tests/navigation-api/currententrychange-event/navigate-from-initial-about-blank-same-doc.html b/testing/web-platform/tests/navigation-api/currententrychange-event/navigate-from-initial-about-blank-same-doc.html new file mode 100644 index 0000000000..f0fa9cdf57 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/currententrychange-event/navigate-from-initial-about-blank-same-doc.html @@ -0,0 +1,12 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i"></iframe> +<script> +promise_test(async t => { + i.contentWindow.navigation.oncurrententrychange = t.unreached_func("currententrychange should not fire"); + i.contentWindow.location.href = "about:blank#1"; + i.contentWindow.location.href = "about:blank#2"; + await new Promise(resolve => t.step_timeout(resolve, 10)); +}, "currententrychange does not fire when navigating away from the initial about:blank"); +</script> diff --git a/testing/web-platform/tests/navigation-api/currententrychange-event/navigate-from-initial-about-blank.html b/testing/web-platform/tests/navigation-api/currententrychange-event/navigate-from-initial-about-blank.html new file mode 100644 index 0000000000..56eaa1af26 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/currententrychange-event/navigate-from-initial-about-blank.html @@ -0,0 +1,11 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i"></iframe> +<script> +promise_test(async t => { + i.contentWindow.navigation.oncurrententrychange = t.unreached_func("currententrychange should not fire"); + i.contentWindow.navigation.navigate("/common/blank.html"); + await new Promise(resolve => t.step_timeout(resolve, 10)); +}, "currententrychange does not fire when navigating away from the initial about:blank"); +</script> diff --git a/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-back-forward-cross-doc.html b/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-back-forward-cross-doc.html new file mode 100644 index 0000000000..7416caa94b --- /dev/null +++ b/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-back-forward-cross-doc.html @@ -0,0 +1,20 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + i.contentWindow.navigation.navigate("/common/blank.html?1"); + await new Promise(resolve => i.onload = resolve); + assert_equals(i.contentWindow.navigation.entries().length, 2); + + i.contentWindow.navigation.oncurrententrychange = t.unreached_func("currententrychange should not fire for cross-document navigations"); + i.contentWindow.navigation.back(); + await new Promise(resolve => i.onload = resolve); + + i.contentWindow.navigation.oncurrententrychange = t.unreached_func("currententrychange should not fire for cross-document navigations"); + i.contentWindow.navigation.forward(); + await new Promise(resolve => i.onload = resolve); +}, "currententrychange does not fire for cross-document navigation.back() and navigation.forward()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-back-forward-same-doc.html b/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-back-forward-same-doc.html new file mode 100644 index 0000000000..8182673aa5 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-back-forward-same-doc.html @@ -0,0 +1,40 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + await navigation.navigate("#foo").committed; + assert_equals(navigation.entries().length, 2); + + let oncurrententrychange_back_called = false; + let back_committed = false; + navigation.oncurrententrychange = t.step_func(e => { + oncurrententrychange_back_called = true; + assert_equals(e.from, navigation.entries()[1]); + assert_equals(e.navigationType, "traverse"); + assert_equals(navigation.currentEntry.index, 0); + assert_false(back_committed); + }); + let back_result = navigation.back(); + assert_false(oncurrententrychange_back_called); + await back_result.committed.then(() => back_committed = true); + assert_true(oncurrententrychange_back_called); + + let oncurrententrychange_forward_called = false; + let forward_committed = false; + navigation.oncurrententrychange = t.step_func(e => { + oncurrententrychange_forward_called = true; + assert_equals(e.from, navigation.entries()[0]); + assert_equals(e.navigationType, "traverse"); + assert_equals(navigation.currentEntry.index, 1); + assert_false(forward_committed); + }); + let forward_result = navigation.forward(); + assert_false(oncurrententrychange_forward_called); + await forward_result.committed.then(() => forward_committed = true); + assert_true(oncurrententrychange_forward_called); +}, "currententrychange fires for same-document navigation.back() and navigation.forward()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-navigate-cross-doc.html b/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-navigate-cross-doc.html new file mode 100644 index 0000000000..81a4e239ba --- /dev/null +++ b/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-navigate-cross-doc.html @@ -0,0 +1,12 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + i.contentWindow.navigation.oncurrententrychange = t.unreached_func("currententrychange should not fire for cross-document navigations"); + i.contentWindow.navigation.navigate("/common/blank.html?1"); + await new Promise(resolve => i.onload = resolve); +}, "currententrychange does not fire for cross-document navigation.navigate()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-navigate-intercept.html b/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-navigate-intercept.html new file mode 100644 index 0000000000..af0fe104f9 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-navigate-intercept.html @@ -0,0 +1,21 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + let oncurrententrychange_called = false; + i.contentWindow.navigation.oncurrententrychange = t.step_func(e => { + oncurrententrychange_called = true; + assert_equals(e.from, i.contentWindow.navigation.entries()[0]); + assert_equals(e.navigationType, "push"); + assert_equals(i.contentWindow.navigation.currentEntry.index, 1); + }); + i.contentWindow.navigation.onnavigate = e => e.intercept(); + let result = i.contentWindow.navigation.navigate("/common/blank.html?1"); + assert_true(oncurrententrychange_called); + await result.committed; +}, "currententrychange fires for navigation.navigate() intercepted by intercept()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-navigate-preventDefault.html b/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-navigate-preventDefault.html new file mode 100644 index 0000000000..34b98353ba --- /dev/null +++ b/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-navigate-preventDefault.html @@ -0,0 +1,10 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async t => { + navigation.oncurrententrychange = t.unreached_func("currententrychange should not fire"); + navigation.onnavigate = e => e.preventDefault(); + await promise_rejects_dom(t, "AbortError", navigation.navigate("#foo").committed); +}, "currententrychange does not fire when onnavigate preventDefault() is called"); +</script> diff --git a/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-navigate-replace-cross-doc.html b/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-navigate-replace-cross-doc.html new file mode 100644 index 0000000000..ab762c04bc --- /dev/null +++ b/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-navigate-replace-cross-doc.html @@ -0,0 +1,12 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + i.contentWindow.navigation.oncurrententrychange = t.unreached_func("currententrychange should not fire for cross-document navigations"); + i.contentWindow.navigation.navigate("/common/blank.html?1", { history: "replace" }); + await new Promise(resolve => i.onload = resolve); +}, "currententrychange does not fire for cross-document navigation.navigate() with replace"); +</script> diff --git a/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-navigate-replace-intercept.html b/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-navigate-replace-intercept.html new file mode 100644 index 0000000000..33209202d8 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-navigate-replace-intercept.html @@ -0,0 +1,23 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + let oncurrententrychange_called = false; + let original_entry = i.contentWindow.navigation.currentEntry; + i.contentWindow.navigation.oncurrententrychange = t.step_func(e => { + oncurrententrychange_called = true; + assert_equals(e.from, original_entry); + assert_equals(e.from.index, -1); + assert_equals(e.navigationType, "replace"); + assert_equals(i.contentWindow.navigation.currentEntry.index, 0); + }); + i.contentWindow.navigation.onnavigate = e => e.intercept(); + let result = i.contentWindow.navigation.navigate("/common/blank.html?1", { history: "replace" }); + assert_true(oncurrententrychange_called); + await result.committed; +}, "currententrychange fires for navigation.navigate() with replace intercepted by intercept()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-navigate-replace-same-doc.html b/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-navigate-replace-same-doc.html new file mode 100644 index 0000000000..f993597305 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-navigate-replace-same-doc.html @@ -0,0 +1,23 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + let oncurrententrychange_called = false; + let original_entry = navigation.currentEntry; + navigation.oncurrententrychange = t.step_func(e => { + oncurrententrychange_called = true; + assert_equals(e.from, original_entry); + assert_equals(e.from.index, -1); + assert_equals(e.navigationType, "replace"); + assert_equals(navigation.currentEntry.index, 0); + }); + let result = navigation.navigate("#foo", { history: "replace" }); + assert_true(oncurrententrychange_called); + await result.committed; +}, "currententrychange fires for navigation.navigate() with replace"); +</script> diff --git a/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-navigate-same-doc.html b/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-navigate-same-doc.html new file mode 100644 index 0000000000..87fc28d174 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-navigate-same-doc.html @@ -0,0 +1,21 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + let oncurrententrychange_called = false; + navigation.oncurrententrychange = t.step_func(e => { + oncurrententrychange_called = true; + assert_equals(e.from, navigation.entries()[0]); + assert_equals(e.navigationType, "push"); + assert_equals(navigation.currentEntry.index, 1); + }); + let result = navigation.navigate("#foo"); + assert_true(oncurrententrychange_called); + await result.committed; +}, "currententrychange fires for navigation.navigate()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-reload-cross-doc.html b/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-reload-cross-doc.html new file mode 100644 index 0000000000..e590cab382 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-reload-cross-doc.html @@ -0,0 +1,12 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + i.contentWindow.navigation.oncurrententrychange = t.unreached_func("currententrychange should not fire for cross-document navigations"); + i.contentWindow.navigation.reload(); + await new Promise(resolve => i.onload = resolve); +}, "currententrychange does not fire for cross-document navigation.reload()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-reload-intercept.html b/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-reload-intercept.html new file mode 100644 index 0000000000..275e23363c --- /dev/null +++ b/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-reload-intercept.html @@ -0,0 +1,21 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + let oncurrententrychange_called = false; + i.contentWindow.navigation.oncurrententrychange = t.step_func(e => { + oncurrententrychange_called = true; + assert_equals(e.from, i.contentWindow.navigation.currentEntry); + assert_equals(e.navigationType, "reload"); + assert_equals(i.contentWindow.navigation.currentEntry.index, 0); + }); + i.contentWindow.navigation.onnavigate = e => e.intercept(); + let result = i.contentWindow.navigation.reload(); + assert_true(oncurrententrychange_called); + await result.committed; +}, "currententrychange fires for navigation.reload() intercepted by intercept()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-updateCurrentEntry.html b/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-updateCurrentEntry.html new file mode 100644 index 0000000000..4423b2bc90 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/currententrychange-event/navigation-updateCurrentEntry.html @@ -0,0 +1,20 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +test(t => { + let oncurrententrychange_count = 0; + navigation.oncurrententrychange = t.step_func(e => { + oncurrententrychange_count++; + assert_equals(e.from, navigation.currentEntry); + assert_equals(e.navigationType, null); + assert_equals(navigation.currentEntry.getState(), "newState"); + }); + navigation.updateCurrentEntry({ state: "newState" }); + assert_equals(oncurrententrychange_count, 1); + + // "Updating" the state to the current state should still fire currententrychange. + navigation.updateCurrentEntry({ state: navigation.currentEntry.getState() }); + assert_equals(oncurrententrychange_count, 2); +}, "currententrychange fires for navigation.updateCurrentEntry()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/currententrychange-event/not-on-load.html b/testing/web-platform/tests/navigation-api/currententrychange-event/not-on-load.html new file mode 100644 index 0000000000..8cc1688913 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/currententrychange-event/not-on-load.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<!-- Per https://github.com/WICG/navigation-api/issues/31, make sure nothing happens on page loads --> + +<script> +async_test(t => { + navigation.onnavigate = t.unreached_func("navigate must not fire"); + navigation.oncurrententrychange = t.unreached_func("currententrychange must not fire"); + + // pageshow is the latest event in the normal document loading cycle. + // Ensure nothing happens even 10 ms afterward. + window.addEventListener("pageshow", () => t.step_timeout(() => { + t.done(); + }, 10)); +}, "No navigation API events happen on initial page load"); +</script> diff --git a/testing/web-platform/tests/navigation-api/currententrychange-event/properties.html b/testing/web-platform/tests/navigation-api/currententrychange-event/properties.html new file mode 100644 index 0000000000..e862543bcc --- /dev/null +++ b/testing/web-platform/tests/navigation-api/currententrychange-event/properties.html @@ -0,0 +1,14 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + navigation.oncurrententrychange = t.step_func_done(e => { + assert_equals(e.constructor, NavigationCurrentEntryChangeEvent); + assert_false(e.bubbles); + assert_false(e.cancelable); + assert_true(e.isTrusted); + }); + location.href = "#1"; +}, "NavigationCurrentEntryChangeEvent's properties"); +</script> diff --git a/testing/web-platform/tests/navigation-api/focus-reset/autofocus.html b/testing/web-platform/tests/navigation-api/focus-reset/autofocus.html new file mode 100644 index 0000000000..6044447367 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/focus-reset/autofocus.html @@ -0,0 +1,185 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<button autofocus id="initialAutofocusTarget">Initial autofocus target</button> + +<script type="module"> +promise_setup(async () => { + // Get the overall autofocus processed flag to flip to true, so that + // we only test the navigation API-specific stuff. + await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r))); + assert_equals(document.activeElement, initialAutofocusTarget, "Non-navigation API autofocus was processed"); + initialAutofocusTarget.remove(); + assert_equals(document.activeElement, document.body); +}); + +promise_test(async t => { + const decoy = createAndAppend(t); + const autofocusTarget = createAndAppend(t, { autofocus: true }); + + assert_equals(document.activeElement, document.body, "Start on body"); + decoy.focus(); + assert_equals(document.activeElement, decoy, "focus() worked"); + + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); + + const { committed, finished } = navigation.navigate("#1"); + + await committed; + assert_equals(document.activeElement, decoy, "Focus stays on the non-autofocused button during the transition"); + + await finished; + assert_equals(document.activeElement, autofocusTarget, "Focus moves to the autofocused button after the transition"); +}, "An element with autofocus, present before navigation, gets focused"); + +promise_test(async t => { + const autofocusTarget = createAndAppend(t, { autofocus: true }); + const decoy = createAndAppend(t, { autofocus: true }); + + assert_equals(document.activeElement, document.body, "Start on body"); + decoy.focus(); + assert_equals(document.activeElement, decoy, "focus() worked"); + + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); + + const { committed, finished } = navigation.navigate("#1"); + + await committed; + assert_equals(document.activeElement, decoy, "Focus stays on the initially-focused button during the transition"); + + await finished; + assert_equals(document.activeElement, autofocusTarget, "Focus moves to the first autofocused button after the transition"); +}, "Two elements with autofocus, present before navigation; the first gets focused"); + +promise_test(async t => { + const decoy = createAndAppend(t); + const autofocusTarget = createAndAppend(t, { autofocus: true }); + + assert_equals(document.activeElement, document.body, "Start on body"); + decoy.focus(); + assert_equals(document.activeElement, decoy, "focus() worked"); + + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); + + const { committed, finished } = navigation.navigate("#1"); + + await committed; + assert_equals(document.activeElement, decoy, "Focus stays on the non-autofocused button during the transition"); + + autofocusTarget.disabled = true; + + await finished; + assert_equals(document.activeElement, document.body, "Focus gets reset after the transition"); +}, "An element with autofocus, present before navigation but disabled before finished, does not get focused"); + +promise_test(async t => { + const decoy = createAndAppend(t); + const autofocusTarget = createAndAppend(t, { autofocus: true }); + + assert_equals(document.activeElement, document.body, "Start on body"); + decoy.focus(); + assert_equals(document.activeElement, decoy, "focus() worked"); + + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); + + const { committed, finished } = navigation.navigate("#1"); + + await committed; + assert_equals(document.activeElement, decoy, "Focus stays on the non-autofocused button during the transition"); + + autofocusTarget.autofocus = false; + + await finished; + assert_equals(document.activeElement, document.body, "Focus gets reset after the transition"); +}, "An element with autofocus, present before navigation but with its autofocus attribute removed before finished, does not get focused"); + +promise_test(async t => { + const decoy = createAndAppend(t, { autofocus: true }); + const autofocusTarget = createAndAppend(t, { autofocus: true }); + + assert_equals(document.activeElement, document.body, "Start on body"); + decoy.focus(); + assert_equals(document.activeElement, decoy, "focus() worked"); + + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); + + const { committed, finished } = navigation.navigate("#1"); + + await committed; + assert_equals(document.activeElement, decoy, "Focus stays on the initially-focused button during the transition"); + + decoy.disabled = true; + assert_equals(document.activeElement, document.body, "Disabling the initially-focused button temporarily resets focus to the body"); + + await finished; + assert_equals(document.activeElement, autofocusTarget, "Focus moves to the second autofocused button after the transition"); +}, "Two elements with autofocus, present before navigation, but the first gets disabled; the second gets focused"); + +promise_test(async t => { + const decoy = createAndAppend(t); + + assert_equals(document.activeElement, document.body, "Start on body"); + decoy.focus(); + assert_equals(document.activeElement, decoy, "focus() worked"); + + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); + + const { committed, finished } = navigation.navigate("#1"); + + await committed; + assert_equals(document.activeElement, decoy, "Focus stays on the non-autofocused button during the transition"); + + const autofocusTarget = createAndAppend(t, { autofocus: true }); + + await finished; + assert_equals(document.activeElement, autofocusTarget, "Focus moves to the autofocused button after the transition"); +}, "An element with autofocus, introduced between committed and finished, gets focused"); + +promise_test(async t => { + const decoy = createAndAppend(t); + + assert_equals(document.activeElement, document.body, "Start on body"); + decoy.focus(); + assert_equals(document.activeElement, decoy, "focus() worked"); + + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); + + const { committed, finished } = navigation.navigate("#1"); + + await committed; + assert_equals(document.activeElement, decoy, "Focus stays on the non-autofocused button during the transition"); + + await finished; + assert_equals(document.activeElement, document.body, "Focus gets reset after the transition"); + + const autofocusTarget = createAndAppend(t, { autofocus: true }); + + await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r))); + assert_equals(document.activeElement, document.body, "Focus stays reset two animation frames after the transition"); +}, "An element with autofocus, introduced after finished, does not get focused"); + +function createAndAppend(t, props) { + const element = document.createElement("button"); + Object.assign(element, props); + + document.body.append(element); + t.add_cleanup(() => { element.remove(); }); + + return element; +} +</script> diff --git a/testing/web-platform/tests/navigation-api/focus-reset/basic.html b/testing/web-platform/tests/navigation-api/focus-reset/basic.html new file mode 100644 index 0000000000..f5a30972b0 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/focus-reset/basic.html @@ -0,0 +1,63 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<script type="module"> +import { testFocusWasReset, testFocusWasNotReset } from "./resources/helpers.mjs"; + +test(() => { + let throwAssertionHappened = false; + + navigation.addEventListener("navigate", e => { + assert_throws_js(TypeError, () => { + e.intercept({ focusReset: "invalid" }); + }); + throwAssertionHappened = true; + }, { once: true }); + + navigation.navigate("#1"); + assert_true(throwAssertionHappened); +}, "Invalid values for focusReset throw"); + +testFocusWasNotReset(() => { + // Intentionally left blank. +}, "Does not reset the focus when no navigate handler is present"); + +testFocusWasReset(t => { + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); +}, "Resets the focus when no focusReset option is provided"); + +testFocusWasReset(t => { + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); +}, "Resets the focus when focusReset is explicitly set to undefined"); + +testFocusWasReset(t => { + navigation.addEventListener("navigate", e => { + e.intercept({ handler: () => new Promise(r => t.step_timeout(r, 5)) }); + }, { once: true }); +}, "Resets the focus when no focusReset option is provided (nontrivial fulfilled promise)"); + +testFocusWasReset(t => { + navigation.addEventListener("navigate", e => { + e.intercept({ handler: () => Promise.reject() }); + }, { once: true }); +}, "Resets the focus when no focusReset option is provided (rejected promise)"); + +testFocusWasReset(t => { + navigation.addEventListener("navigate", e => { + e.intercept({ focusReset: "after-transition" }); + }, { once: true }); +}, "Resets the focus when focusReset is explicitly set to 'after-transition'"); + +testFocusWasNotReset(t => { + navigation.addEventListener("navigate", e => { + e.intercept({ focusReset: "manual" }); + }); +}, "Does not reset the focus when focusReset is set to 'manual'"); +</script> diff --git a/testing/web-platform/tests/navigation-api/focus-reset/change-focus-again-in-blur-during-intercept.html b/testing/web-platform/tests/navigation-api/focus-reset/change-focus-again-in-blur-during-intercept.html new file mode 100644 index 0000000000..a7339c9788 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/focus-reset/change-focus-again-in-blur-during-intercept.html @@ -0,0 +1,35 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +promise_test(async t => { + let intercept_resolve; + navigation.addEventListener("navigate", e => { + e.intercept({ handler: () => new Promise(resolve => intercept_resolve = resolve), + focusReset: "after-transition" }); + }, { once: true }); + + const button = document.body.appendChild(document.createElement("button")); + const button2 = document.body.appendChild(document.createElement("button")); + button2.tabIndex = 0; + t.add_cleanup(() => { + button.remove(); + button2.remove(); + }); + + assert_equals(document.activeElement, document.body, "Start on body"); + button.focus(); + assert_equals(document.activeElement, button, "focus() worked"); + + const finished = navigation.navigate("#1").finished; + button.onblur = () => button2.focus(); + button.blur(); + assert_equals(document.activeElement, button2, "focus() in blur worked"); + + intercept_resolve(); + await finished; + assert_equals(document.activeElement, button2, "Focus was not reset after the transition"); +}, ""); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/focus-reset/change-focus-back-to-origial-during-intercept.html b/testing/web-platform/tests/navigation-api/focus-reset/change-focus-back-to-origial-during-intercept.html new file mode 100644 index 0000000000..4e5b9dfb6a --- /dev/null +++ b/testing/web-platform/tests/navigation-api/focus-reset/change-focus-back-to-origial-during-intercept.html @@ -0,0 +1,36 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +promise_test(async t => { + let intercept_resolve; + navigation.addEventListener("navigate", e => { + e.intercept({ handler: () => new Promise(resolve => intercept_resolve = resolve), + focusReset: "after-transition" }); + }, { once: true }); + + const button = document.body.appendChild(document.createElement("button")); + const button2 = document.body.appendChild(document.createElement("button")); + button2.tabIndex = 0; + t.add_cleanup(() => { + button.remove(); + button2.remove(); + }); + + assert_equals(document.activeElement, document.body, "Start on body"); + button.focus(); + assert_equals(document.activeElement, button, "focus() worked"); + + const finished = navigation.navigate("#1").finished; + button2.focus(); + assert_equals(document.activeElement, button2, "focus() worked"); + button.focus(); + assert_equals(document.activeElement, button, "focus() worked"); + + intercept_resolve(); + await finished; + assert_equals(document.activeElement, button, "Focus was not reset after the transition"); +}, ""); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/focus-reset/change-focus-during-intercept.html b/testing/web-platform/tests/navigation-api/focus-reset/change-focus-during-intercept.html new file mode 100644 index 0000000000..0593231a39 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/focus-reset/change-focus-during-intercept.html @@ -0,0 +1,34 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +promise_test(async t => { + let intercept_resolve; + navigation.addEventListener("navigate", e => { + e.intercept({ handler: () => new Promise(resolve => intercept_resolve = resolve), + focusReset: "after-transition" }); + }, { once: true }); + + const button = document.body.appendChild(document.createElement("button")); + const button2 = document.body.appendChild(document.createElement("button")); + button2.tabIndex = 0; + t.add_cleanup(() => { + button.remove(); + button2.remove(); + }); + + assert_equals(document.activeElement, document.body, "Start on body"); + button.focus(); + assert_equals(document.activeElement, button, "focus() worked"); + + const finished = navigation.navigate("#1").finished; + button2.focus(); + assert_equals(document.activeElement, button2, "focus() worked"); + + intercept_resolve(); + await finished; + assert_equals(document.activeElement, button2, "Focus was not reset after the transition"); +}, ""); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/focus-reset/change-focus-then-remove-during-intercept.html b/testing/web-platform/tests/navigation-api/focus-reset/change-focus-then-remove-during-intercept.html new file mode 100644 index 0000000000..a5d8062ce0 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/focus-reset/change-focus-then-remove-during-intercept.html @@ -0,0 +1,40 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +promise_test(async t => { + let intercept_resolve; + navigation.addEventListener("navigate", e => { + e.intercept({ handler: () => new Promise(resolve => intercept_resolve = resolve), + focusReset: "after-transition" }); + }, { once: true }); + + const button = document.body.appendChild(document.createElement("button")); + const button2 = document.body.appendChild(document.createElement("button")); + button2.tabIndex = 0; + t.add_cleanup(() => { + button.remove(); + button2.remove(); + }); + + assert_equals(document.activeElement, document.body, "Start on body"); + button.focus(); + assert_equals(document.activeElement, button, "focus() worked"); + + const finished = navigation.navigate("#1").finished; + + let onfocus_called = false; + document.body.onfocus = onfocus_called = true; + button.remove(); + assert_equals(document.activeElement, document.body, "Removing the element reset focus"); + assert_true(onfocus_called); + + document.body.onfocus = t.unreached_func("onfocus shouldn't fire a second time due to focus reset"); + intercept_resolve(); + await finished; + assert_equals(document.activeElement, document.body, "Focus remains on document.body after promise fulfills"); + await new Promise(resolve => t.step_timeout(resolve, 10)); +}, ""); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/focus-reset/focus-reset-timing.html b/testing/web-platform/tests/navigation-api/focus-reset/focus-reset-timing.html new file mode 100644 index 0000000000..df9f03afe9 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/focus-reset/focus-reset-timing.html @@ -0,0 +1,61 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +promise_test(async t => { + navigation.addEventListener("navigate", e => { + e.intercept({ focusReset: "after-transition" }); + }, { once: true }); + + const button = document.body.appendChild(document.createElement("button")); + const button2 = document.body.appendChild(document.createElement("button")); + button2.tabIndex = 0; + t.add_cleanup(() => { + button.remove(); + button2.remove(); + }); + + assert_equals(document.activeElement, document.body, "Start on body"); + button.focus(); + assert_equals(document.activeElement, button, "focus() worked"); + + let navigatesuccess_called = false; + navigation.onnavigatesuccess = t.step_func(() => { + navigatesuccess_called = true; + assert_equals(document.activeElement, document.body, "Focus must be reset before navigatesuccess"); + }); + + await navigation.navigate("#1").finished; + assert_true(navigatesuccess_called); +}, "Focus should be reset before navigatesuccess"); + +promise_test(async t => { + navigation.addEventListener("navigate", e => { + e.intercept({ handler: () => Promise.reject(), + focusReset: "after-transition" }); + }, { once: true }); + + const button = document.body.appendChild(document.createElement("button")); + const button2 = document.body.appendChild(document.createElement("button")); + button2.tabIndex = 0; + t.add_cleanup(() => { + button.remove(); + button2.remove(); + }); + + assert_equals(document.activeElement, document.body, "Start on body"); + button.focus(); + assert_equals(document.activeElement, button, "focus() worked"); + + let navigateerror_called = false; + navigation.onnavigateerror = t.step_func(() => { + navigateerror_called = true; + assert_equals(document.activeElement, document.body, "Focus must be reset before navigateerror"); + }); + + await promise_rejects_exactly(t, undefined, navigation.navigate("#2").finished); + assert_true(navigateerror_called); +}, "Focus should be reset before navigateerror"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/focus-reset/multiple-intercept.html b/testing/web-platform/tests/navigation-api/focus-reset/multiple-intercept.html new file mode 100644 index 0000000000..75e38c98a4 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/focus-reset/multiple-intercept.html @@ -0,0 +1,69 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<script type="module"> +import { testFocusWasReset, testFocusWasNotReset } from "./resources/helpers.mjs"; + +testFocusWasReset(t => { + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); + + navigation.addEventListener("navigate", e => { + e.intercept({ focusReset: "after-transition" }); + }, { once: true }); +}, "(not provided) + after-transition"); + +testFocusWasNotReset(t => { + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); + + navigation.addEventListener("navigate", e => { + e.intercept({ focusReset: "manual" }); + }, { once: true }); +}, "(not provided) + manual"); + +testFocusWasNotReset(t => { + navigation.addEventListener("navigate", e => { + e.intercept({ focusReset: "after-transition" }); + }, { once: true }); + + navigation.addEventListener("navigate", e => { + e.intercept({ focusReset: "manual" }); + }, { once: true }); +}, "after-transition + manual"); + +testFocusWasReset(t => { + navigation.addEventListener("navigate", e => { + e.intercept({ focusReset: "after-transition" }); + }, { once: true }); + + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); +}, "after-transition + (not provided)"); + +testFocusWasReset(t => { + navigation.addEventListener("navigate", e => { + e.intercept({ focusReset: "manual" }); + }, { once: true }); + + navigation.addEventListener("navigate", e => { + e.intercept({ focusReset: "after-transition" }); + }, { once: true }); +}, "manual + after-transition"); + +testFocusWasNotReset(t => { + navigation.addEventListener("navigate", e => { + e.intercept({ focusReset: "manual" }); + }, { once: true }); + + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); +}, "manual + (not provided)"); +</script> diff --git a/testing/web-platform/tests/navigation-api/focus-reset/resources/helpers.mjs b/testing/web-platform/tests/navigation-api/focus-reset/resources/helpers.mjs new file mode 100644 index 0000000000..0a8a0439e1 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/focus-reset/resources/helpers.mjs @@ -0,0 +1,73 @@ +// Usage note: if you use these more than once in a given file, be sure to +// clean up any navigate event listeners, e.g. by using { once: true }, between +// tests. + +const TAB_KEY = "\uE004"; + +export function testFocusWasReset(setupFunc, description) { + promise_test(async t => { + setupFunc(t); + + const button = document.body.appendChild(document.createElement("button")); + const button2 = document.body.appendChild(document.createElement("button")); + button2.tabIndex = 0; + t.add_cleanup(() => { + button.remove(); + button2.remove(); + }); + + assert_equals(document.activeElement, document.body, "Start on body"); + button.focus(); + assert_equals(document.activeElement, button, "focus() worked"); + + const { committed, finished } = navigation.navigate("#" + location.hash.substring(1) + "1"); + + await committed; + assert_equals(document.activeElement, button, "Focus stays on the button during the transition"); + + await finished.catch(() => {}); + assert_equals(document.activeElement, document.body, "Focus reset after the transition"); + + button2.onfocus = t.unreached_func("button2 must not be focused after pressing Tab"); + const focusPromise = waitForFocus(t, button); + await test_driver.send_keys(document.body, TAB_KEY); + await focusPromise; + }, description); +} + +export function testFocusWasNotReset(setupFunc, description) { + promise_test(async t => { + setupFunc(t); + + const button = document.body.appendChild(document.createElement("button")); + const button2 = document.body.appendChild(document.createElement("button")); + button2.tabIndex = 0; + t.add_cleanup(() => { + button.remove(); + button2.remove(); + }); + + assert_equals(document.activeElement, document.body, "Start on body"); + button.focus(); + assert_equals(document.activeElement, button, "focus() worked"); + + const { committed, finished } = navigation.navigate("#" + location.hash.substring(1) + "1"); + + await committed; + assert_equals(document.activeElement, button, "Focus stays on the button during the transition"); + + await finished.catch(() => {}); + assert_equals(document.activeElement, button, "Focus stays on the button after the transition"); + + button.onfocus = t.unreached_func("button must not be focused after pressing Tab"); + const focusPromise = waitForFocus(t, button2); + await test_driver.send_keys(document.body, TAB_KEY); + await focusPromise; + }, description); +} + +function waitForFocus(t, target) { + return new Promise(resolve => { + target.addEventListener("focus", () => resolve(), { once: true }); + }); +} diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-origin-traversal-does-not-fire-navigate.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-origin-traversal-does-not-fire-navigate.html new file mode 100644 index 0000000000..36491be22c --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-origin-traversal-does-not-fire-navigate.html @@ -0,0 +1,25 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../navigation-methods/return-value/resources/helpers.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script> +promise_test(async t => { + // Open a cross-origin window. + let w = window.open(get_host_info().HTTP_REMOTE_ORIGIN + "/navigation-api/navigate-event/resources/opener-postMessage-onload.html"); + await new Promise(resolve => window.onmessage = resolve); + + // Navigate the opened window to this origin. + w.location = get_host_info().ORIGIN + "/navigation-api/navigate-event/resources/opener-postMessage-onload.html"; + await new Promise(resolve => window.onmessage = resolve); + assert_equals(w.navigation.entries().length, 1); + assert_equals(w.navigation.currentEntry.index, 0); + + // Go back. This should not fire a navigate event, because the traversal is + // cross-origin. Note that history.back() must be used instead of + // navigation.back() because navigation.back() can never go cross-origin. + w.navigation.onnavigate = t.unreached_func("navigate should not fire"); + w.history.back(); + await new Promise(resolve => window.onmessage = resolve); +}, "A cross-origin traversal does not fire the navigate event"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/click-crossdocument-crossorigin-sameorigindomain.sub.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/click-crossdocument-crossorigin-sameorigindomain.sub.html new file mode 100644 index 0000000000..004e1a75e3 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/click-crossdocument-crossorigin-sameorigindomain.sub.html @@ -0,0 +1,41 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> + +<script> +document.domain = "{{host}}"; +async_test(t => { + const url = new URL("resources/document-domain-setter.sub.html", location.href); + url.hostname = "{{domains[www1]}}"; + const iframe = document.createElement("iframe"); + iframe.name = "windowname"; + iframe.src = url; + document.body.append(iframe); + + url.search = "?foo"; + const link = document.createElement("a"); + link.href = url; + link.target = iframe.name; + document.body.append(link); + + navigation.onnavigate = t.unreached_func("onnavigate must not fire in the source window"); + window.onload = t.step_func(() => { + iframe.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "push", "navigationType"); + assert_true(e.cancelable, "cancelable"); + assert_true(e.canIntercept, "canIntercept"); + assert_false(e.userInitiated, "userInitiated"); + assert_false(e.hashChange, "hashChange"); + assert_equals(e.formData, null, "formData"); + assert_equals(e.destination.url, link.href, "destination.url"); + assert_false(e.destination.sameDocument, "destination.sameDocument"); + assert_equals(e.destination.key, "", "destination.key"); + assert_equals(e.destination.id, "", "destination.id"); + assert_equals(e.destination.index, -1, "destination.index"); + }); + + link.click(); + }); +}, "clicking on an <a> element that navigates cross-document targeting a same-origin-domain (but cross-origin) window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/click-crossdocument-crossorigin.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/click-crossdocument-crossorigin.html new file mode 100644 index 0000000000..2f40238912 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/click-crossdocument-crossorigin.html @@ -0,0 +1,30 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<body> + +<script> +async_test(t => { + const url = new URL("resources/cross-origin-iframe-helper.html", location.href); + url.hostname = get_host_info().REMOTE_HOST; + const iframe = document.createElement("iframe"); + iframe.src = url; + iframe.name = "windowname"; + document.body.append(iframe); + + url.search = "?postMessage-top-when-done"; + const link = document.createElement("a"); + link.href = url; + link.target = iframe.name; + document.body.append(link); + + window.onmessage = t.step_func_done(e => { + // If we hit onnavigate in the target window, we'll get a different message, and fail. + assert_equals(e.data, "DONE"); + }); + + navigation.onnavigate = t.unreached_func("onnavigate must not fire in the source window"); + window.onload = t.step_func(() => link.click()); +}, "clicking on an <a> element that navigates cross-document targeting a cross-origin window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/click-crossdocument-sameorigin.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/click-crossdocument-sameorigin.html new file mode 100644 index 0000000000..683875d006 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/click-crossdocument-sameorigin.html @@ -0,0 +1,29 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<a id="link" href="/common/blank.html?foo" target="windowname">Click me</a> +<iframe id="i" name="windowname" src="/common/blank.html"></iframe> + +<script> +async_test(t => { + navigation.onnavigate = t.unreached_func("onnavigate must not fire in the source window"); + window.onload = t.step_func(() => { + i.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "push", "navigationType"); + assert_true(e.cancelable, "cancelable"); + assert_true(e.canIntercept, "canIntercept"); + assert_false(e.userInitiated, "userInitiated"); + assert_false(e.hashChange, "hashChange"); + assert_equals(e.formData, null, "formData"); + assert_equals(e.destination.url, link.href, "destination.url"); + assert_false(e.destination.sameDocument, "destination.sameDocument"); + assert_equals(e.destination.key, "", "destination.key"); + assert_equals(e.destination.id, "", "destination.id"); + assert_equals(e.destination.index, -1, "destination.index"); + }); + + link.click(); + }); +}, "clicking on an <a> element that navigates cross-document targeting a same-origin window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/click-samedocument-crossorigin-sameorigindomain.sub.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/click-samedocument-crossorigin-sameorigindomain.sub.html new file mode 100644 index 0000000000..60d1653a3a --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/click-samedocument-crossorigin-sameorigindomain.sub.html @@ -0,0 +1,41 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> + +<script> +document.domain = "{{host}}"; +async_test(t => { + const url = new URL("resources/document-domain-setter.sub.html", location.href); + url.hostname = "{{domains[www1]}}"; + const iframe = document.createElement("iframe"); + iframe.name = "windowname"; + iframe.src = url; + document.body.append(iframe); + + url.hash = "#foo"; + const link = document.createElement("a"); + link.href = url; + link.target = iframe.name; + document.body.append(link); + + navigation.onnavigate = t.unreached_func("onnavigate must not fire in the source window"); + window.onload = t.step_func(() => { + iframe.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "push", "navigationType"); + assert_true(e.cancelable, "cancelable"); + assert_true(e.canIntercept, "canIntercept"); + assert_false(e.userInitiated, "userInitiated"); + assert_true(e.hashChange, "hashChange"); + assert_equals(e.formData, null, "formData"); + assert_equals(e.destination.url, link.href, "destination.url"); + assert_true(e.destination.sameDocument, "destination.sameDocument"); + assert_equals(e.destination.key, "", "destination.key"); + assert_equals(e.destination.id, "", "destination.id"); + assert_equals(e.destination.index, -1, "destination.index"); + }); + + link.click(); + }); +}, "clicking on an <a> element that navigates same-document targeting a same-origin-domain (but cross-origin) window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/click-samedocument-crossorigin.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/click-samedocument-crossorigin.html new file mode 100644 index 0000000000..ee01e7f1f2 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/click-samedocument-crossorigin.html @@ -0,0 +1,39 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<body> + +<script> +async_test(t => { + const url = new URL("resources/cross-origin-iframe-helper.html", location.href); + url.hostname = get_host_info().REMOTE_HOST; + const iframe = document.createElement("iframe") + iframe.src = url; + iframe.name = "windowname"; + document.body.append(iframe); + + url.hash = "#foo"; + const link = document.createElement("a"); + link.href = url; + link.target = iframe.name; + document.body.append(link); + + window.onmessage = t.step_func_done(e => { + assert_equals(e.data.navigationType, "push", "navigationType"); + assert_true(e.data.cancelable, "cancelable"); + assert_true(e.data.canIntercept, "canIntercept"); + assert_false(e.data.userInitiated, "userInitiated"); + assert_true(e.data.hashChange, "hashChange"); + assert_equals(e.data.formData, null, "formData"); + assert_equals(e.data.destination.url, link.href, "destination.url"); + assert_true(e.data.destination.sameDocument, "destination.sameDocument"); + assert_equals(e.data.destination.key, "", "destination.key"); + assert_equals(e.data.destination.id, "", "destination.id"); + assert_equals(e.data.destination.index, -1, "destination.index"); + }); + + navigation.onnavigate = t.unreached_func("onnavigate must not fire in the source window"); + window.onload = t.step_func(() => link.click()); +}, "clicking on an <a> element that navigates same-document targeting a cross-origin window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/click-samedocument-sameorigin.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/click-samedocument-sameorigin.html new file mode 100644 index 0000000000..eaa7b52345 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/click-samedocument-sameorigin.html @@ -0,0 +1,27 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<a id="link" href="/common/blank.html#foo" target="windowname">Click me</a> +<iframe id="i" name="windowname" src="/common/blank.html"></iframe> + +<script> +async_test(t => { + i.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "push", "navigationType"); + assert_true(e.cancelable, "cancelable"); + assert_true(e.canIntercept, "canIntercept"); + assert_false(e.userInitiated, "userInitiated"); + assert_true(e.hashChange, "hashChange"); + assert_equals(e.formData, null, "formData"); + assert_equals(e.destination.url, link.href, "destination.url"); + assert_true(e.destination.sameDocument, "destination.sameDocument"); + assert_equals(e.destination.key, "", "destination.key"); + assert_equals(e.destination.id, "", "destination.id"); + assert_equals(e.destination.index, -1, "destination.index"); + }); + + navigation.onnavigate = t.unreached_func("onnavigate must not fire in the source window"); + window.onload = t.step_func(() => link.click()); +}, "clicking on an <a> element that navigates same-document targeting a same-origin window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/location-crossdocument-crossorigin-sameorigindomain.sub.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/location-crossdocument-crossorigin-sameorigindomain.sub.html new file mode 100644 index 0000000000..cee7d95b9e --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/location-crossdocument-crossorigin-sameorigindomain.sub.html @@ -0,0 +1,34 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> + +<script> +document.domain = "{{host}}"; +async_test(t => { + const url = new URL("resources/document-domain-setter.sub.html", location.href); + url.hostname = "{{domains[www1]}}"; + const iframe = document.createElement("iframe"); + iframe.src = url; + document.body.append(iframe); + + navigation.onnavigate = t.unreached_func("onnavigate must not fire in the source window"); + window.onload = t.step_func(() => { + iframe.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "push", "navigationType"); + assert_true(e.cancelable, "cancelable"); + assert_true(e.canIntercept, "canIntercept"); + assert_false(e.userInitiated, "userInitiated"); + assert_false(e.hashChange, "hashChange"); + assert_equals(e.formData, null, "formData"); + assert_equals(e.destination.url, iframe.src + "?foo", "destination.url"); + assert_false(e.destination.sameDocument, "destination.sameDocument"); + assert_equals(e.destination.key, "", "destination.key"); + assert_equals(e.destination.id, "", "destination.id"); + assert_equals(e.destination.index, -1, "destination.index"); + }); + + iframe.contentWindow.location.href = url + "?foo"; + }); +}, "using location.href to navigate cross-document targeting a same-origin-domain (but cross-origin) window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/location-crossdocument-crossorigin.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/location-crossdocument-crossorigin.html new file mode 100644 index 0000000000..79df86fdc9 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/location-crossdocument-crossorigin.html @@ -0,0 +1,23 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<body> + +<script> +async_test(t => { + const iframeURL = new URL("resources/cross-origin-iframe-helper.html", location.href); + iframeURL.hostname = get_host_info().REMOTE_HOST; + const iframe = document.createElement("iframe"); + iframe.src = iframeURL; + document.body.append(iframe); + + window.onmessage = t.step_func_done(e => { + // If we hit onnavigate in the target window, we'll get a different message, and fail. + assert_equals(e.data, "DONE"); + }); + + navigation.onnavigate = t.unreached_func("onnavigate must not fire in the source window"); + window.onload = t.step_func(() => iframe.contentWindow.location.href = iframeURL + "?postMessage-top-when-done"); +}, "using location.href to navigate cross-document targeting a cross-origin window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/location-crossdocument-sameorigin.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/location-crossdocument-sameorigin.html new file mode 100644 index 0000000000..d0dad46b72 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/location-crossdocument-sameorigin.html @@ -0,0 +1,26 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +async_test(t => { + i.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "push", "navigationType"); + assert_true(e.cancelable, "cancelable"); + assert_true(e.canIntercept, "canIntercept"); + assert_false(e.userInitiated, "userInitiated"); + assert_false(e.hashChange, "hashChange"); + assert_equals(e.formData, null, "formData"); + assert_equals(e.destination.url, i.src + "?foo", "destination.url"); + assert_false(e.destination.sameDocument, "destination.sameDocument"); + assert_equals(e.destination.key, "", "destination.key"); + assert_equals(e.destination.id, "", "destination.id"); + assert_equals(e.destination.index, -1, "destination.index"); + }); + + navigation.onnavigate = t.unreached_func("onnavigate must not fire in the source window"); + window.onload = t.step_func(() => i.contentWindow.location.href = "/common/blank.html?foo"); +}, "using location.href to navigate cross-document targeting a same-origin window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/location-samedocument-crossorigin-sameorigindomain.sub.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/location-samedocument-crossorigin-sameorigindomain.sub.html new file mode 100644 index 0000000000..156d1cb3cf --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/location-samedocument-crossorigin-sameorigindomain.sub.html @@ -0,0 +1,34 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> + +<script> +document.domain = "{{host}}"; +async_test(t => { + const url = new URL("resources/document-domain-setter.sub.html", location.href); + url.hostname = "{{domains[www1]}}"; + const iframe = document.createElement("iframe"); + iframe.src = url; + document.body.append(iframe); + + navigation.onnavigate = t.unreached_func("onnavigate must not fire in the source window"); + window.onload = t.step_func(() => { + iframe.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "push", "navigationType"); + assert_true(e.cancelable, "cancelable"); + assert_true(e.canIntercept, "canIntercept"); + assert_false(e.userInitiated, "userInitiated"); + assert_true(e.hashChange, "hashChange"); + assert_equals(e.formData, null, "formData"); + assert_equals(e.destination.url, iframe.src + "#foo", "destination.url"); + assert_true(e.destination.sameDocument, "destination.sameDocument"); + assert_equals(e.destination.key, "", "destination.key"); + assert_equals(e.destination.id, "", "destination.id"); + assert_equals(e.destination.index, -1, "destination.index"); + }); + + iframe.contentWindow.location.href = url + "#foo"; + }); +}, "using location.href to navigate same-document targeting a same-origin-domain (but cross-origin) window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/location-samedocument-crossorigin.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/location-samedocument-crossorigin.html new file mode 100644 index 0000000000..6d73262dea --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/location-samedocument-crossorigin.html @@ -0,0 +1,32 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<body> + +<script> +async_test(t => { + const iframeURL = new URL("resources/cross-origin-iframe-helper.html", location.href); + iframeURL.hostname = get_host_info().REMOTE_HOST; + const iframe = document.createElement("iframe") + iframe.src = iframeURL; + document.body.append(iframe); + + window.onmessage = t.step_func_done(e => { + assert_equals(e.data.navigationType, "push", "navigationType"); + assert_true(e.data.cancelable, "cancelable"); + assert_true(e.data.canIntercept, "canIntercept"); + assert_false(e.data.userInitiated, "userInitiated"); + assert_true(e.data.hashChange, "hashChange"); + assert_equals(e.data.formData, null, "formData"); + assert_equals(e.data.destination.url, iframe.src + "#foo", "destination.url"); + assert_true(e.data.destination.sameDocument, "destination.sameDocument"); + assert_equals(e.data.destination.key, "", "destination.key"); + assert_equals(e.data.destination.id, "", "destination.id"); + assert_equals(e.data.destination.index, -1, "destination.index"); + }); + + navigation.onnavigate = t.unreached_func("onnavigate must not fire in the source window"); + window.onload = t.step_func(() => iframe.contentWindow.location.href = iframeURL + "#foo"); +}, "using location.href to navigate same-document targeting a cross-origin window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/location-samedocument-sameorigin.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/location-samedocument-sameorigin.html new file mode 100644 index 0000000000..6516a60239 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/location-samedocument-sameorigin.html @@ -0,0 +1,26 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +async_test(t => { + i.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "push", "navigationType"); + assert_true(e.cancelable, "cancelable"); + assert_true(e.canIntercept, "canIntercept"); + assert_false(e.userInitiated, "userInitiated"); + assert_true(e.hashChange, "hashChange"); + assert_equals(e.formData, null, "formData"); + assert_equals(e.destination.url, i.src + "#foo", "destination.url"); + assert_true(e.destination.sameDocument, "destination.sameDocument"); + assert_equals(e.destination.key, "", "destination.key"); + assert_equals(e.destination.id, "", "destination.id"); + assert_equals(e.destination.index, -1, "destination.index"); + }); + + navigation.onnavigate = t.unreached_func("onnavigate must not fire in the source window"); + window.onload = t.step_func(() => i.contentWindow.location.href = "/common/blank.html#foo"); +}, "using location.href to navigate same-document targeting a same-origin window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/open-crossdocument-crossorigin-sameorigindomain.sub.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/open-crossdocument-crossorigin-sameorigindomain.sub.html new file mode 100644 index 0000000000..1c411d9866 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/open-crossdocument-crossorigin-sameorigindomain.sub.html @@ -0,0 +1,35 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> + +<script> +document.domain = "{{host}}"; +async_test(t => { + const url = new URL("resources/document-domain-setter.sub.html", location.href); + url.hostname = "{{domains[www1]}}"; + const iframe = document.createElement("iframe"); + iframe.name = "windowname"; + iframe.src = url; + document.body.append(iframe); + + navigation.onnavigate = t.unreached_func("onnavigate must not fire in the source window"); + window.onload = t.step_func(() => { + iframe.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "push", "navigationType"); + assert_true(e.cancelable, "cancelable"); + assert_true(e.canIntercept, "canIntercept"); + assert_false(e.userInitiated, "userInitiated"); + assert_false(e.hashChange, "hashChange"); + assert_equals(e.formData, null, "formData"); + assert_equals(e.destination.url, url + "?foo", "destination.url"); + assert_false(e.destination.sameDocument, "destination.sameDocument"); + assert_equals(e.destination.key, "", "destination.key"); + assert_equals(e.destination.id, "", "destination.id"); + assert_equals(e.destination.index, -1, "destination.index"); + }); + + window.open(url + "?foo", iframe.name); + }); +}, "using window.open() to navigate cross-document targeting a same-origin-domain (but cross-origin) window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/open-crossdocument-crossorigin.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/open-crossdocument-crossorigin.html new file mode 100644 index 0000000000..73ede89cbf --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/open-crossdocument-crossorigin.html @@ -0,0 +1,24 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<body> + +<script> +async_test(t => { + const iframeURL = new URL("resources/cross-origin-iframe-helper.html", location.href); + iframeURL.hostname = get_host_info().REMOTE_HOST; + const iframe = document.createElement("iframe"); + iframe.src = iframeURL; + iframe.name = "windowname"; + document.body.append(iframe); + + window.onmessage = t.step_func_done(e => { + // If we hit onnavigate in the target window, we'll get a different message, and fail. + assert_equals(e.data, "DONE"); + }); + + navigation.onnavigate = t.unreached_func("onnavigate must not fire in the source window"); + window.onload = t.step_func(() => window.open(iframeURL + "?postMessage-top-when-done", "windowname")); +}, "using window.open() to navigate cross-document targeting a cross-origin window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/open-crossdocument-sameorigin.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/open-crossdocument-sameorigin.html new file mode 100644 index 0000000000..c91689341e --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/open-crossdocument-sameorigin.html @@ -0,0 +1,26 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<iframe id="i" name="windowname" src="/common/blank.html"></iframe> + +<script> +async_test(t => { + i.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "push", "navigationType"); + assert_true(e.cancelable, "cancelable"); + assert_true(e.canIntercept, "canIntercept"); + assert_false(e.userInitiated, "userInitiated"); + assert_false(e.hashChange, "hashChange"); + assert_equals(e.formData, null, "formData"); + assert_equals(e.destination.url, i.src + "?foo", "destination.url"); + assert_false(e.destination.sameDocument, "destination.sameDocument"); + assert_equals(e.destination.key, "", "destination.key"); + assert_equals(e.destination.id, "", "destination.id"); + assert_equals(e.destination.index, -1, "destination.index"); + }); + + navigation.onnavigate = t.unreached_func("onnavigate must not fire in the source window"); + window.onload = t.step_func(() => window.open("/common/blank.html?foo", "windowname")); +}, "using window.open() to navigate cross-document targeting a same-origin window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/open-samedocument-crossorigin-sameorigindomain.sub.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/open-samedocument-crossorigin-sameorigindomain.sub.html new file mode 100644 index 0000000000..74a6cae246 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/open-samedocument-crossorigin-sameorigindomain.sub.html @@ -0,0 +1,35 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> + +<script> +document.domain = "{{host}}"; +async_test(t => { + const url = new URL("resources/document-domain-setter.sub.html", location.href); + url.hostname = "{{domains[www1]}}"; + const iframe = document.createElement("iframe"); + iframe.name = "windowname"; + iframe.src = url; + document.body.append(iframe); + + navigation.onnavigate = t.unreached_func("onnavigate must not fire in the source window"); + window.onload = t.step_func(() => { + iframe.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "push", "navigationType"); + assert_true(e.cancelable, "cancelable"); + assert_true(e.canIntercept, "canIntercept"); + assert_false(e.userInitiated, "userInitiated"); + assert_true(e.hashChange, "hashChange"); + assert_equals(e.formData, null, "formData"); + assert_equals(e.destination.url, url + "#foo", "destination.url"); + assert_true(e.destination.sameDocument, "destination.sameDocument"); + assert_equals(e.destination.key, "", "destination.key"); + assert_equals(e.destination.id, "", "destination.id"); + assert_equals(e.destination.index, -1, "destination.index"); + }); + + window.open(url + "#foo", iframe.name); + }); +}, "using window.open() to navigate same-document targeting a same-origin-domain (but cross-origin) window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/open-samedocument-crossorigin.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/open-samedocument-crossorigin.html new file mode 100644 index 0000000000..9b31744ba3 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/open-samedocument-crossorigin.html @@ -0,0 +1,33 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<body> + +<script> +async_test(t => { + const iframeURL = new URL("resources/cross-origin-iframe-helper.html", location.href); + iframeURL.hostname = get_host_info().REMOTE_HOST; + const iframe = document.createElement("iframe") + iframe.src = iframeURL; + iframe.name = "windowname"; + document.body.append(iframe); + + window.onmessage = t.step_func_done(e => { + assert_equals(e.data.navigationType, "push", "navigationType"); + assert_true(e.data.cancelable, "cancelable"); + assert_true(e.data.canIntercept, "canIntercept"); + assert_false(e.data.userInitiated, "userInitiated"); + assert_true(e.data.hashChange, "hashChange"); + assert_equals(e.data.formData, null, "formData"); + assert_equals(e.data.destination.url, iframe.src + "#foo", "destination.url"); + assert_true(e.data.destination.sameDocument, "destination.sameDocument"); + assert_equals(e.data.destination.key, "", "destination.key"); + assert_equals(e.data.destination.id, "", "destination.id"); + assert_equals(e.data.destination.index, -1, "destination.index"); + }); + + navigation.onnavigate = t.unreached_func("onnavigate must not fire in the source window"); + window.onload = t.step_func(() => window.open(iframeURL + "#foo", "windowname")); +}, "using window.open() to navigate same-document targeting a cross-origin window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/open-samedocument-sameorigin.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/open-samedocument-sameorigin.html new file mode 100644 index 0000000000..c357072e09 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/open-samedocument-sameorigin.html @@ -0,0 +1,26 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<iframe id="i" name="windowname" src="/common/blank.html"></iframe> + +<script> +async_test(t => { + i.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "push", "navigationType"); + assert_true(e.cancelable, "cancelable"); + assert_true(e.canIntercept, "canIntercept"); + assert_false(e.userInitiated, "userInitiated"); + assert_true(e.hashChange, "hashChange"); + assert_equals(e.formData, null, "formData"); + assert_equals(e.destination.url, i.src + "#foo", "destination.url"); + assert_true(e.destination.sameDocument, "destination.sameDocument"); + assert_equals(e.destination.key, "", "destination.key"); + assert_equals(e.destination.id, "", "destination.id"); + assert_equals(e.destination.index, -1, "destination.index"); + }); + + navigation.onnavigate = t.unreached_func("onnavigate must not fire in the source window"); + window.onload = t.step_func(() => window.open("/common/blank.html#foo", "windowname")); +}, "using window.open() to navigate same-document targeting a same-origin window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/resources/cross-origin-iframe-helper.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/resources/cross-origin-iframe-helper.html new file mode 100644 index 0000000000..3393a2ecaa --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/resources/cross-origin-iframe-helper.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<script> +navigation.onnavigate = e => { + const message = { + navigationType: e.navigationType, + cancelable: e.cancelable, + canIntercept: e.canIntercept, + userInitiated: e.userInitiated, + hashChange: e.hashChange, + formData: e.formData, + destination: { + url: e.destination.url, + sameDocument: e.destination.sameDocument, + key: e.destination.key, + id: e.destination.id, + index: e.destination.index + } + }; + e.preventDefault(); + top.postMessage(message, "*"); +}; + +window.onload = () => { + if (location.href.includes("postMessage-top-when-done")) { + top.postMessage("DONE", "*"); + } +}; +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/resources/document-domain-setter.sub.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/resources/document-domain-setter.sub.html new file mode 100644 index 0000000000..abe32ad8fb --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/resources/document-domain-setter.sub.html @@ -0,0 +1,3 @@ +<script> +document.domain = "{{host}}"; +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/submit-crossdocument-crossorigin-sameorigindomain.sub.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/submit-crossdocument-crossorigin-sameorigindomain.sub.html new file mode 100644 index 0000000000..ffdaac7ccd --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/submit-crossdocument-crossorigin-sameorigindomain.sub.html @@ -0,0 +1,41 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> + +<script> +document.domain = "{{host}}"; +async_test(t => { + const url = new URL("resources/document-domain-setter.sub.html?start", location.href); + url.hostname = "{{domains[www1]}}"; + const iframe = document.createElement("iframe"); + iframe.name = "windowname"; + iframe.src = url; + document.body.append(iframe); + + url.search = ""; // setting to "?" actually erases it anyway + const form = document.createElement("form"); + form.action = url + "?"; + form.target = iframe.name; + document.body.append(form); + + navigation.onnavigate = t.unreached_func("onnavigate must not fire in the source window"); + window.onload = t.step_func(() => { + iframe.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "push", "navigationType"); + assert_true(e.cancelable, "cancelable"); + assert_true(e.canIntercept, "canIntercept"); + assert_false(e.userInitiated, "userInitiated"); + assert_false(e.hashChange, "hashChange"); + assert_equals(e.formData, null, "formData"); + assert_equals(e.destination.url, form.action, "destination.url"); + assert_false(e.destination.sameDocument, "destination.sameDocument"); + assert_equals(e.destination.key, "", "destination.key"); + assert_equals(e.destination.id, "", "destination.id"); + assert_equals(e.destination.index, -1, "destination.index"); + }); + + form.submit(); + }); +}, "submitting a <form> element that navigates cross-document targeting a same-origin-domain (but cross-origin) window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/submit-crossdocument-crossorigin.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/submit-crossdocument-crossorigin.html new file mode 100644 index 0000000000..007f58b1fb --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/submit-crossdocument-crossorigin.html @@ -0,0 +1,30 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<body> + +<script> +async_test(t => { + const url = new URL("resources/cross-origin-iframe-helper.html", location.href); + url.hostname = get_host_info().REMOTE_HOST; + const iframe = document.createElement("iframe") + iframe.src = url; + iframe.name = "windowname"; + document.body.append(iframe); + + const form = document.createElement("form"); + form.action = url; + form.target = iframe.name; + form.innerHTML = `<input type="hidden" name="postMessage-top-when-done" value="yes">`; + document.body.append(form); + + window.onmessage = t.step_func_done(e => { + // If we hit onnavigate in the target window, we'll get a different message, and fail. + assert_equals(e.data, "DONE"); + }); + + navigation.onnavigate = t.unreached_func("onnavigate must not fire in the source window"); + window.onload = t.step_func(() => form.submit()); +}, "submitting a <form> element that navigates cross-document targeting a cross-origin window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/submit-crossdocument-sameorigin.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/submit-crossdocument-sameorigin.html new file mode 100644 index 0000000000..b54a5a440b --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/submit-crossdocument-sameorigin.html @@ -0,0 +1,27 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<form id="form" action="/common/blank.html?" target="windowname"></form> +<iframe id="i" name="windowname" src="/common/blank.html?start"></iframe> + +<script> +async_test(t => { + i.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "push", "navigationType"); + assert_true(e.cancelable, "cancelable"); + assert_true(e.canIntercept, "canIntercept"); + assert_false(e.userInitiated, "userInitiated"); + assert_false(e.hashChange, "hashChange"); + assert_equals(e.formData, null, "formData"); + assert_equals(e.destination.url, form.action, "destination.url"); + assert_false(e.destination.sameDocument, "destination.sameDocument"); + assert_equals(e.destination.key, "", "destination.key"); + assert_equals(e.destination.id, "", "destination.id"); + assert_equals(e.destination.index, -1, "destination.index"); + }); + + navigation.onnavigate = t.unreached_func("onnavigate must not fire in the source window"); + window.onload = t.step_func(() => form.submit()); +}, "submitting a <form> element that navigates cross-document targeting a same-origin window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/submit-samedocument-crossorigin-sameorigindomain.sub.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/submit-samedocument-crossorigin-sameorigindomain.sub.html new file mode 100644 index 0000000000..e9ab17243e --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/submit-samedocument-crossorigin-sameorigindomain.sub.html @@ -0,0 +1,41 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> + +<script> +document.domain = "{{host}}"; +async_test(t => { + const url = new URL("resources/document-domain-setter.sub.html?", location.href); + url.hostname = "{{domains[www1]}}"; + const iframe = document.createElement("iframe"); + iframe.name = "windowname"; + iframe.src = url; + document.body.append(iframe); + + url.hash = "#foo"; + const form = document.createElement("form"); + form.action = url; + form.target = iframe.name; + document.body.append(form); + + navigation.onnavigate = t.unreached_func("onnavigate must not fire in the source window"); + window.onload = t.step_func(() => { + iframe.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "push", "navigationType"); + assert_true(e.cancelable, "cancelable"); + assert_true(e.canIntercept, "canIntercept"); + assert_false(e.userInitiated, "userInitiated"); + assert_true(e.hashChange, "hashChange"); + assert_equals(e.formData, null, "formData"); + assert_equals(e.destination.url, form.action, "destination.url"); + assert_true(e.destination.sameDocument, "destination.sameDocument"); + assert_equals(e.destination.key, "", "destination.key"); + assert_equals(e.destination.id, "", "destination.id"); + assert_equals(e.destination.index, -1, "destination.index"); + }); + + form.submit(); + }); +}, "submitting a <form> element that navigates cross-document targeting a same-origin-domain (but cross-origin) window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/submit-samedocument-crossorigin.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/submit-samedocument-crossorigin.html new file mode 100644 index 0000000000..69b12f2795 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/submit-samedocument-crossorigin.html @@ -0,0 +1,39 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<body> + +<script> +async_test(t => { + const url = new URL("resources/cross-origin-iframe-helper.html?", location.href); + url.hostname = get_host_info().REMOTE_HOST; + const iframe = document.createElement("iframe") + iframe.src = url; + iframe.name = "windowname"; + document.body.append(iframe); + + url.hash = "#foo"; + const form = document.createElement("form"); + form.action = url; + form.target = iframe.name; + document.body.append(form); + + window.onmessage = t.step_func_done(e => { + assert_equals(e.data.navigationType, "push", "navigationType"); + assert_true(e.data.cancelable, "cancelable"); + assert_true(e.data.canIntercept, "canIntercept"); + assert_false(e.data.userInitiated, "userInitiated"); + assert_true(e.data.hashChange, "hashChange"); + assert_equals(e.data.formData, null, "formData"); + assert_equals(e.data.destination.url, form.action, "destination.url"); + assert_true(e.data.destination.sameDocument, "destination.sameDocument"); + assert_equals(e.data.destination.key, "", "destination.key"); + assert_equals(e.data.destination.id, "", "destination.id"); + assert_equals(e.data.destination.index, -1, "destination.index"); + }); + + navigation.onnavigate = t.unreached_func("onnavigate must not fire in the source window"); + window.onload = t.step_func(() => form.submit()); +}, "submitting a <form> element that navigates same-document targeting a cross-origin window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/cross-window/submit-samedocument-sameorigin.html b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/submit-samedocument-sameorigin.html new file mode 100644 index 0000000000..8a0e1f1fb6 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/cross-window/submit-samedocument-sameorigin.html @@ -0,0 +1,27 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<form id="form" action="/common/blank.html?#foo" target="windowname"></form> +<iframe id="i" name="windowname" src="/common/blank.html?"></iframe> + +<script> +async_test(t => { + i.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "push", "navigationType"); + assert_true(e.cancelable, "cancelable"); + assert_true(e.canIntercept, "canIntercept"); + assert_false(e.userInitiated, "userInitiated"); + assert_true(e.hashChange, "hashChange"); + assert_equals(e.formData, null, "formData"); + assert_equals(e.destination.url, form.action, "destination.url"); + assert_true(e.destination.sameDocument, "destination.sameDocument"); + assert_equals(e.destination.key, "", "destination.key"); + assert_equals(e.destination.id, "", "destination.id"); + assert_equals(e.destination.index, -1, "destination.index"); + }); + + navigation.onnavigate = t.unreached_func("onnavigate must not fire in the source window"); + window.onload = t.step_func(() => form.submit()); +}, "submitting a <form> element that navigates same-document targeting a same-origin window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/defaultPrevented-navigation-preempted.html b/testing/web-platform/tests/navigation-api/navigate-event/defaultPrevented-navigation-preempted.html new file mode 100644 index 0000000000..1df4f56c5e --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/defaultPrevented-navigation-preempted.html @@ -0,0 +1,19 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +test(() => { + let navigateEvent; + let navigateError_called = false; + navigation.onnavigate = e => navigateEvent = e; + navigation.onnavigateerror = () => navigateError_called = true; + + navigation.navigate("#1"); + assert_false(navigateEvent.defaultPrevented); + assert_false(navigateError_called); + + navigation.navigate("#2"); + assert_false(navigateEvent.defaultPrevented); + assert_true(navigateError_called); +}, "navigateEvent.defaultPrevented isn't affected when the navigation is preempted after dispatch"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/defaultPrevented-window-stop-after-dispatch.html b/testing/web-platform/tests/navigation-api/navigate-event/defaultPrevented-window-stop-after-dispatch.html new file mode 100644 index 0000000000..da5de10ddd --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/defaultPrevented-window-stop-after-dispatch.html @@ -0,0 +1,16 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + window.onload = t.step_func_done(() => { + let navigateEvent; + navigation.onnavigate = e => navigateEvent = e; + navigation.navigate("?1"); + assert_false(navigateEvent.defaultPrevented); + + window.stop(); + assert_false(navigateEvent.defaultPrevented); + }); +}, "window.stop() doesn't affect navigateEvent.defaultPrevented after dispatch"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/event-constructor.html b/testing/web-platform/tests/navigation-api/navigate-event/event-constructor.html new file mode 100644 index 0000000000..065884240e --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/event-constructor.html @@ -0,0 +1,96 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +test(() => { + assert_throws_js(TypeError, () => { + new NavigateEvent("navigate"); + }); +}, "can't bypass required members by omitting the dictionary entirely"); + +test(() => { + assert_throws_js(TypeError, () => { + new NavigateEvent("navigate", { + navigationType: "push", + canIntercept: false, + userInitiated: false, + hashChange: false, + signal: (new AbortController()).signal, + formData: null, + downloadRequest: null, + info: null + }); + }); +}, "destination is required"); + +async_test(t => { + // We need to grab an NavigationDestination. + navigation.onnavigate = t.step_func_done(e => { + assert_throws_js(TypeError, () => { + new NavigateEvent("navigate", { + navigationType: "push", + destination: e.destination, + canIntercept: false, + userInitiated: false, + hashChange: false, + formData: null, + downloadRequest: null, + info: null + }); + }); + }); + history.pushState(1, null, "#1"); +}, "signal is required"); + +async_test(t => { + // We need to grab an NavigationDestination. + navigation.onnavigate = t.step_func_done(e => { + const info = { some: "object with identity" }; + const formData = new FormData(); + const signal = (new AbortController()).signal; + const downloadRequest = "abc"; + + const event = new NavigateEvent("navigate", { + navigationType: "replace", + destination: e.destination, + canIntercept: true, + userInitiated: true, + hashChange: true, + signal, + formData, + downloadRequest, + info + }); + + assert_equals(event.navigationType, "replace"); + assert_equals(event.destination, e.destination); + assert_equals(event.canIntercept, true); + assert_equals(event.userInitiated, true); + assert_equals(event.hashChange, true); + assert_equals(event.signal, signal); + assert_equals(event.formData, formData); + assert_equals(event.downloadRequest, downloadRequest); + assert_equals(event.info, info); + }); + history.pushState(2, null, "#2"); +}, "all properties are reflected back"); + +async_test(t => { + // We need to grab an NavigationDestination. + navigation.onnavigate = t.step_func_done(e => { + const event = new NavigateEvent("navigate", { + destination: e.destination, + signal: (new AbortController()).signal + }); + + assert_equals(event.navigationType, "push"); + assert_equals(event.canIntercept, false); + assert_equals(event.userInitiated, false); + assert_equals(event.hashChange, false); + assert_equals(event.formData, null); + assert_equals(event.downloadRequest, null); + assert_equals(event.info, undefined); + }); + history.pushState(3, null, "#3"); +}, "defaults are as expected"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/intercept-after-dispatch.html b/testing/web-platform/tests/navigation-api/navigate-event/intercept-after-dispatch.html new file mode 100644 index 0000000000..abb328050d --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/intercept-after-dispatch.html @@ -0,0 +1,16 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + navigation.onnavigate = t.step_func(e => { + t.step_timeout(t.step_func_done(() => { + assert_equals(e.eventPhase, Event.NONE); + assert_throws_dom("InvalidStateError", () => e.intercept()); + }), 0); + }); + + location.href = "#1"; +}, "event.intercept() throws if used after the dispatch phase"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/intercept-and-navigate.html b/testing/web-platform/tests/navigation-api/navigate-event/intercept-and-navigate.html new file mode 100644 index 0000000000..dfbb67b6b6 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/intercept-and-navigate.html @@ -0,0 +1,27 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(r => window.onload = () => t.step_timeout(r, 0)); + await navigation.navigate("#1").finished; + + navigation.onnavigate = e => e.intercept(); + navigation.oncurrententrychange = e => { + if (e.navigationType == "traverse") { + assert_equals(location.hash, ""); + assert_equals(navigation.currentEntry.index, 0); + assert_equals(navigation.entries().length, 2); + navigation.navigate("#2"); + } + }; + let back_result = navigation.back(); + await back_result.committed; + assert_equals(location.hash, "#2"); + await promise_rejects_dom(t, "AbortError", back_result.finished); + assert_equals(navigation.currentEntry.index, 1); + assert_equals(navigation.entries().length, 2); +}, "Using intercept() then navigate() in the ensuing currententrychange should abort the finished promise (but not the committed promise)"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/intercept-canceled-event.html b/testing/web-platform/tests/navigation-api/navigate-event/intercept-canceled-event.html new file mode 100644 index 0000000000..d4b9633c1a --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/intercept-canceled-event.html @@ -0,0 +1,17 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +test(t => { + let assertionHappened = false; + navigation.onnavigate = t.step_func_done(e => { + e.preventDefault(); + assert_throws_dom("InvalidStateError", () => e.intercept()); + assertionHappened = true; + }); + + location.href = "#1"; + assert_true(assertionHappened); +}, "event.intercept() throws if used on a canceled event"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/intercept-cross-document-same-origin.html b/testing/web-platform/tests/navigation-api/navigate-event/intercept-cross-document-same-origin.html new file mode 100644 index 0000000000..0d610cce4f --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/intercept-cross-document-same-origin.html @@ -0,0 +1,21 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script> +async_test(t => { + let target_url = location.href + "?1"; + navigation.onnavigate = t.step_func_done(e => { + assert_true(e.cancelable); + assert_true(e.canIntercept); + assert_false(e.userInitiated); + assert_false(e.hashChange); + e.intercept({ handler: async () => { + await Promise.resolve(); + t.step_func_done(() => assert_equals(location.href, target_url)); + }}); + }); + + window.onload = t.step_func(() => location.href = target_url); +}, "event.intercept() intercepts a same-origin cross-document navigation"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/intercept-cross-origin.html b/testing/web-platform/tests/navigation-api/navigate-event/intercept-cross-origin.html new file mode 100644 index 0000000000..b367df546c --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/intercept-cross-origin.html @@ -0,0 +1,18 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script> +async_test(t => { + navigation.onnavigate = t.step_func_done(e => { + assert_true(e.cancelable); + assert_false(e.canIntercept); + assert_false(e.userInitiated); + assert_false(e.hashChange); + assert_throws_dom("SecurityError", () => e.intercept()); + e.preventDefault(); + }); + + window.onload = t.step_func(() => location.href = get_host_info().HTTPS_REMOTE_ORIGIN); +}, "event.intercept() should throw if called for a cross origin navigation"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/intercept-detach-multiple.html b/testing/web-platform/tests/navigation-api/navigate-event/intercept-detach-multiple.html new file mode 100644 index 0000000000..5b6a623284 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/intercept-detach-multiple.html @@ -0,0 +1,18 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + let second_handler_run = false; + i.contentWindow.navigation.onnavigate = e => { + e.intercept({ handler() { i.remove(); }}); + e.intercept({ handler() { second_handler_run = true; }}); + }; + + i.contentWindow.location.href = "#1"; + t.step_timeout(t.step_func_done(() => assert_true(second_handler_run)), 0); + }); +}, "event.intercept() throws if used on an event from a detached iframe"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/intercept-detach.html b/testing/web-platform/tests/navigation-api/navigate-event/intercept-detach.html new file mode 100644 index 0000000000..b5ce45aa29 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/intercept-detach.html @@ -0,0 +1,17 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + i.contentWindow.navigation.onnavigate = t.step_func_done(e => { + let iframe_constructor = i.contentWindow.DOMException; + i.remove(); + assert_throws_dom("InvalidStateError", iframe_constructor, () => e.intercept()); + }); + + i.contentWindow.location.href = "#1"; + }); +}, "event.intercept() throws if used on an event from a detached iframe"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/intercept-handler-null-or-undefined.html b/testing/web-platform/tests/navigation-api/navigate-event/intercept-handler-null-or-undefined.html new file mode 100644 index 0000000000..7f5bd6b19f --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/intercept-handler-null-or-undefined.html @@ -0,0 +1,21 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + navigation.onnavigate = t.step_func_done(e => { + assert_throws_js(TypeError, () => e.intercept({ handler: null })); + }); + + location.href = "#1"; + assert_equals(location.hash, "#1"); +}, "event.intercept() should throw if the handler is null"); + +async_test(t => { + navigation.onnavigatesuccess = t.step_func_done(e => {}); + navigation.onnavigate = e => e.intercept({ handler: undefined }); + + location.href = "#2"; + assert_equals(location.hash, "#2"); +}, "event.intercept() should not throw if the handler is explicit undefined"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/intercept-handler-returns-non-promise.html b/testing/web-platform/tests/navigation-api/navigate-event/intercept-handler-returns-non-promise.html new file mode 100644 index 0000000000..96116e9295 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/intercept-handler-returns-non-promise.html @@ -0,0 +1,18 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + navigation.onnavigatesuccess = t.step_func_done(e => { + assert_equals(location.hash, "#1"); + assert_equals(e.constructor, Event); + assert_false(e.bubbles); + assert_false(e.cancelable); + }); + navigation.onnavigateerror = t.step_func_done(assert_unreached); + navigation.onnavigate = e => e.intercept({ handler() { return "123"; }}); + + location.href = "#1"; + assert_equals(location.hash, "#1"); +}, "event.intercept() should resolve immediately if the handler doesn't return a promise"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/intercept-handler-throws.html b/testing/web-platform/tests/navigation-api/navigate-event/intercept-handler-throws.html new file mode 100644 index 0000000000..769f675999 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/intercept-handler-throws.html @@ -0,0 +1,26 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + const err = new TypeError("a message"); + let start_href = location.href; + + navigation.onnavigatesuccess = t.step_func_done(assert_unreached); + navigation.onnavigateerror = t.step_func_done(e => { + assert_equals(location.hash, "#1"); + assert_equals(e.constructor, ErrorEvent); + assert_true(e.error === err); + assert_equals(e.message, "Uncaught TypeError: a message"); + assert_equals(e.filename, start_href); + assert_greater_than(e.colno, 0); + assert_greater_than(e.lineno, 0); + }); + navigation.onnavigate = e => { + e.intercept({ handler() { throw err; }}); + }; + + location.href = "#1"; + assert_equals(location.hash, "#1"); +}, "event.intercept() should abort if the handler throws"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/intercept-history-pushState.html b/testing/web-platform/tests/navigation-api/navigate-event/intercept-history-pushState.html new file mode 100644 index 0000000000..aad08b742e --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/intercept-history-pushState.html @@ -0,0 +1,23 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + let start_length = history.length; + navigation.onnavigate = t.step_func(e => { + e.intercept({ handler: async () => { + await new Promise(r => t.step_timeout(r, 0)); + t.step_timeout(t.step_func_done(() => { + assert_equals(location.hash, "#1"); + assert_equals(history.state, "update"); + assert_equals(history.length, start_length + 1); + }, 0)); + }}); + }); + + history.pushState("update", "", "#1"); + assert_equals(location.hash, "#1"); + assert_equals(history.state, "update"); + assert_equals(history.length, start_length + 1); +}, "event.intercept() should proceed if the given promise resolves"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/intercept-history-replaceState.html b/testing/web-platform/tests/navigation-api/navigate-event/intercept-history-replaceState.html new file mode 100644 index 0000000000..16edec8596 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/intercept-history-replaceState.html @@ -0,0 +1,23 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + let start_length = history.length; + navigation.onnavigate = t.step_func(e => { + e.intercept({ handler: async () => { + await new Promise(r => t.step_timeout(r, 0)); + t.step_timeout(t.step_func_done(() => { + assert_equals(location.hash, "#1"); + assert_equals(history.state, "update"); + assert_equals(history.length, start_length); + }, 0)); + }}); + }); + + history.replaceState("update", "", "#1"); + assert_equals(location.hash, "#1"); + assert_equals(history.state, "update"); + assert_equals(history.length, start_length); +}, "event.intercept() should proceed if the given promise resolves"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/intercept-multiple-times-reject.html b/testing/web-platform/tests/navigation-api/navigate-event/intercept-multiple-times-reject.html new file mode 100644 index 0000000000..0b0f1f0b8e --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/intercept-multiple-times-reject.html @@ -0,0 +1,38 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async t => { + const err = new TypeError("a message"); + let start_href = location.href; + + let onnavigateerror_called = false; + let caught_rejection = false; + navigation.onnavigatesuccess = t.step_func(assert_unreached); + navigation.onnavigateerror = t.step_func(e => { + onnavigateerror_called = true; + assert_equals(location.hash, "#1"); + assert_equals(e.constructor, ErrorEvent); + assert_equals(e.error, err); + assert_equals(e.message, "Uncaught TypeError: a message"); + assert_equals(e.filename, start_href); + assert_greater_than(e.colno, 0); + assert_greater_than(e.lineno, 0); + }); + navigation.onnavigate = t.step_func(e => { + e.intercept(); + e.intercept({ handler: async () => { + await new Promise(r => t.step_timeout(r, 1)); + return Promise.reject(err); + }}); + e.intercept({ handler: () => new Promise(resolve => t.step_timeout(resolve, 1)) }); + }); + + await navigation.navigate("#1").finished.catch(t.step_func(e => { + caught_rejection = true; + assert_equals(e, err); + })); + assert_true(onnavigateerror_called); + assert_true(caught_rejection); +}, "event.intercept() is called multiple times and one of the promises rejects"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/intercept-multiple-times.html b/testing/web-platform/tests/navigation-api/navigate-event/intercept-multiple-times.html new file mode 100644 index 0000000000..6deaeeb507 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/intercept-multiple-times.html @@ -0,0 +1,41 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async t => { + let p1_resolved = false; + let p2_resolved = false; + let p3_resolved = false; + navigation.onnavigate = t.step_func(e => { + e.intercept({ handler: async () => { + await Promise.resolve(); + assert_false(p2_resolved); + assert_false(p3_resolved); + p1_resolved = true; + }}); + e.intercept({ handler: async () => { + await new Promise(resolve => t.step_timeout(resolve, 1)); + assert_true(p1_resolved); + assert_false(p3_resolved); + p2_resolved = true; + }}); + e.intercept({ handler: async () => { + await new Promise(resolve => t.step_timeout(resolve, 1)); + assert_true(p1_resolved); + assert_true(p2_resolved); + p3_resolved = true; + }}); + }); + + let promise = navigation.navigate("#1").finished; + assert_equals(location.hash, "#1"); + assert_false(p1_resolved); + assert_false(p2_resolved); + assert_false(p3_resolved); + + await promise; + assert_true(p1_resolved); + assert_true(p2_resolved); + assert_true(p3_resolved); +}, "navigation.navigate() returns a finished promise that awaits all promises if event.intercept() is called multiple times"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/intercept-navigation-back.html b/testing/web-platform/tests/navigation-api/navigate-event/intercept-navigation-back.html new file mode 100644 index 0000000000..8babf2bcdc --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/intercept-navigation-back.html @@ -0,0 +1,19 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + window.onload = () => t.step_timeout(() => { + navigation.navigate("#foo").committed.then(() => { + assert_true(navigation.canGoBack); + navigation.onnavigate = t.step_func(e => e.intercept()); + navigation.back().committed.then(t.step_func_done(() => { + assert_equals(navigation.entries().length, 2); + assert_equals(navigation.currentEntry, navigation.entries()[0]); + })); + }); + }, 0); +}, "event.intercept() can intercept navigation.back()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/intercept-on-synthetic-event.html b/testing/web-platform/tests/navigation-api/navigate-event/intercept-on-synthetic-event.html new file mode 100644 index 0000000000..3a4b54de5e --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/intercept-on-synthetic-event.html @@ -0,0 +1,17 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + // We need to grab an NavigationDestination to construct the event. + navigation.onnavigate = t.step_func_done(e => { + const event = new NavigateEvent("navigate", { + destination: e.destination, + signal: (new AbortController()).signal + }); + + assert_throws_dom("SecurityError", () => event.intercept()); + }); + history.pushState(1, null, "#1"); +}, "event.intercept() throws if invoked on a synthetic event"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/intercept-popstate.html b/testing/web-platform/tests/navigation-api/navigate-event/intercept-popstate.html new file mode 100644 index 0000000000..f5f9d82be7 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/intercept-popstate.html @@ -0,0 +1,26 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + history.replaceState({ state: "foo"}, "", "#replace"); + + let onpopstate_fired = false; + let last_state; + window.onpopstate = e => { + onpopstate_fired = true; + last_state = e.state; + } + navigation.onnavigate = t.step_func(e => e.intercept()); + + await navigation.navigate("#").finished; + assert_true(navigation.canGoBack); + + await navigation.back().finished; + assert_true(onpopstate_fired); + assert_not_equals(last_state, null); +}, "event.intercept() should provide popstate with a valid state object"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/intercept-reject.html b/testing/web-platform/tests/navigation-api/navigate-event/intercept-reject.html new file mode 100644 index 0000000000..4c5ec0163d --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/intercept-reject.html @@ -0,0 +1,29 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + const err = new TypeError("a message"); + let start_href = location.href; + + navigation.onnavigatesuccess = t.step_func_done(assert_unreached); + navigation.onnavigateerror = t.step_func_done(e => { + assert_equals(location.hash, "#1"); + assert_equals(e.constructor, ErrorEvent); + assert_true(e.error === err); + assert_equals(e.message, "Uncaught TypeError: a message"); + assert_equals(e.filename, start_href); + assert_greater_than(e.colno, 0); + assert_greater_than(e.lineno, 0); + }); + navigation.onnavigate = e => { + e.intercept({ handler: async () => { + await new Promise(r => t.step_timeout(r, 0)); + return Promise.reject(err); + }}); + }; + + location.href = "#1"; + assert_equals(location.hash, "#1"); +}, "event.intercept() should abort if the given promise rejects"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/intercept-resolve.html b/testing/web-platform/tests/navigation-api/navigate-event/intercept-resolve.html new file mode 100644 index 0000000000..b60d89b5df --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/intercept-resolve.html @@ -0,0 +1,20 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + navigation.onnavigatesuccess = t.step_func_done(e => { + assert_equals(location.hash, "#1"); + assert_equals(e.constructor, Event); + assert_false(e.bubbles); + assert_false(e.cancelable); + }); + navigation.onnavigateerror = t.step_func_done(assert_unreached); + navigation.onnavigate = e => { + e.intercept({ handler: () => new Promise(resolve => t.step_timeout(resolve, 0)) }); + }; + + location.href = "#1"; + assert_equals(location.hash, "#1"); +}, "event.intercept() should proceed if the given promise resolves"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/intercept-same-document-history-back.html b/testing/web-platform/tests/navigation-api/navigate-event/intercept-same-document-history-back.html new file mode 100644 index 0000000000..6faabe1964 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/intercept-same-document-history-back.html @@ -0,0 +1,40 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + window.onload = () => t.step_timeout(() => { + let onnavigate_calls = 0; + let onnavigatesuccess_calls = 0; + navigation.onnavigate = e => { + onnavigate_calls++; + e.intercept(); + } + navigation.onnavigatesuccess = t.step_func(e => { + onnavigatesuccess_calls++; + if (onnavigatesuccess_calls == 3) { + assert_equals(navigation.entries().length, 3); + assert_equals(navigation.currentEntry.index, 1); + assert_equals(onnavigate_calls, 3); + history.back(); + } else if (onnavigatesuccess_calls == 4) { + assert_equals(navigation.entries().length, 3); + assert_equals(navigation.currentEntry.index, 0); + assert_equals(onnavigate_calls, 4); + t.done(); + } + }); + + navigation.navigate("?foo").finished + .then(t.step_func(() => navigation.navigate("?bar").finished)) + .then(t.step_func(() => { + assert_equals(navigation.entries().length, 3); + assert_equals(navigation.currentEntry.index, 2); + assert_equals(onnavigate_calls, 2); + history.back(); + })); + }, 0); +}, "event.intercept() can intercept same-document history.back()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-anchor-cross-origin.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-anchor-cross-origin.html new file mode 100644 index 0000000000..ee09924850 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-anchor-cross-origin.html @@ -0,0 +1,24 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<a id="a" href="https://does-not-exist/foo.html"></a> +<script> +async_test(t => { + navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "push"); + assert_true(e.cancelable); + assert_false(e.canIntercept); + assert_false(e.userInitiated); + assert_false(e.hashChange); + assert_equals(e.formData, null); + assert_equals(e.downloadRequest, null); + assert_equals(e.destination.url, "https://does-not-exist/foo.html"); + assert_false(e.destination.sameDocument); + assert_equals(e.destination.key, ""); + assert_equals(e.destination.id, ""); + assert_equals(e.destination.index, -1); + e.preventDefault(); + }); + a.click(); +}, "<a> cross-origin navigate event"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-anchor-download-userInitiated.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-anchor-download-userInitiated.html new file mode 100644 index 0000000000..b9506984da --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-anchor-download-userInitiated.html @@ -0,0 +1,29 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<a id="a" href="?1">Click me</a> +<script> +async_test(t => { + a.download = ""; + navigation.onnavigate = t.step_func(e => { + assert_equals(e.navigationType, "push"); + assert_true(e.cancelable); + assert_true(e.canIntercept); + assert_true(e.userInitiated); + assert_false(e.hashChange); + assert_equals(e.downloadRequest, ""); + assert_equals(e.formData, null); + assert_equals(new URL(e.destination.url).search, "?1"); + assert_false(e.destination.sameDocument); + assert_equals(e.destination.key, ""); + assert_equals(e.destination.id, ""); + assert_equals(e.destination.index, -1); + e.preventDefault(); + t.step_timeout(t.step_func_done(() => assert_equals(location.hash, "")), 0); + }); + test_driver.click(a); +}, "<a download> click fires navigate event"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-anchor-download.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-anchor-download.html new file mode 100644 index 0000000000..05fb0ecf2f --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-anchor-download.html @@ -0,0 +1,34 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +const tests = [["a", ""], ["a", "filename"], ["area", ""], ["area", "filename"]]; + +for (const [tag, download_attr] of tests) { + async_test(t => { + let a = document.createElement(tag); + a.href = "foo.html"; + a.download = download_attr; + document.body.appendChild(a); + navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "push"); + assert_true(e.cancelable); + assert_true(e.canIntercept); + assert_false(e.userInitiated); + assert_false(e.hashChange); + assert_equals(e.downloadRequest, download_attr); + assert_equals(e.formData, null); + assert_equals(new URL(e.destination.url).pathname, + "/navigation-api/navigate-event/foo.html"); + assert_false(e.destination.sameDocument); + assert_equals(e.destination.key, ""); + assert_equals(e.destination.id, ""); + assert_equals(e.destination.index, -1); + e.preventDefault(); + }); + a.click(); + }, `<${tag}> fires navigate and populates downloadRequest with '${download_attr}'`); +} +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-anchor-fragment.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-anchor-fragment.html new file mode 100644 index 0000000000..51221ebcad --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-anchor-fragment.html @@ -0,0 +1,25 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<a id="a" href="#1"></a> +<script> +async_test(t => { + navigation.onnavigate = t.step_func(e => { + assert_equals(e.navigationType, "push"); + assert_true(e.cancelable); + assert_true(e.canIntercept); + assert_false(e.userInitiated); + assert_true(e.hashChange); + assert_equals(e.downloadRequest, null); + assert_equals(e.formData, null); + assert_equals(new URL(e.destination.url).hash, "#1"); + assert_true(e.destination.sameDocument); + assert_equals(e.destination.key, ""); + assert_equals(e.destination.id, ""); + assert_equals(e.destination.index, -1); + e.preventDefault(); + t.step_timeout(t.step_func_done(() => assert_equals(location.hash, "")), 0); + }); + a.click(); +}, "Fragment <a> click fires navigate event"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-anchor-same-origin-cross-document.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-anchor-same-origin-cross-document.html new file mode 100644 index 0000000000..68f5bf0627 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-anchor-same-origin-cross-document.html @@ -0,0 +1,25 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<a id="a" href="foo.html"></a> +<script> +async_test(t => { + navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "push"); + assert_true(e.cancelable); + assert_true(e.canIntercept); + assert_false(e.userInitiated); + assert_false(e.hashChange); + assert_equals(e.formData, null); + assert_equals(e.downloadRequest, null); + assert_equals(new URL(e.destination.url).pathname, + "/navigation-api/navigate-event/foo.html"); + assert_false(e.destination.sameDocument); + assert_equals(e.destination.key, ""); + assert_equals(e.destination.id, ""); + assert_equals(e.destination.index, -1); + e.preventDefault(); + }); + a.click(); +}, "<a> cross-document (but same-origin) navigate event"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-anchor-userInitiated.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-anchor-userInitiated.html new file mode 100644 index 0000000000..39192c9151 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-anchor-userInitiated.html @@ -0,0 +1,29 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<a id="a" href="#1">Click me</a> +<script> +async_test(t => { + navigation.onnavigate = t.step_func(e => { + assert_equals(e.navigationType, "push"); + assert_true(e.cancelable); + assert_true(e.canIntercept); + assert_true(e.userInitiated); + assert_true(e.hashChange); + assert_equals(e.formData, null); + assert_equals(e.downloadRequest, null); + assert_equals(new URL(e.destination.url).hash, "#1"); + assert_true(e.destination.sameDocument); + assert_equals(e.destination.key, ""); + assert_equals(e.destination.id, ""); + assert_equals(e.destination.index, -1); + e.preventDefault(); + t.step_timeout(t.step_func_done(() => assert_equals(location.hash, "")), 0); + }); + + test_driver.click(a); +}, "Fragment <a> click fires navigate event"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-anchor-with-target.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-anchor-with-target.html new file mode 100644 index 0000000000..6407b963be --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-anchor-with-target.html @@ -0,0 +1,31 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="iframe" name="i" src="/common/blank.html"></iframe> +<a id="a" href="foo.html" target="i"></a> +<script> +async_test(t => { + window.onload = t.step_func(() => { + navigation.onnavigate = t.step_func_done(() => { + assert_unreached("onnavigate should not have fired in source window"); + }); + iframe.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "push"); + assert_true(e.cancelable); + assert_true(e.canIntercept); + assert_false(e.userInitiated); + assert_false(e.hashChange); + assert_equals(e.formData, null); + assert_equals(e.downloadRequest, null); + assert_equals(new URL(e.destination.url).pathname, + "/navigation-api/navigate-event/foo.html"); + assert_false(e.destination.sameDocument); + assert_equals(e.destination.key, ""); + assert_equals(e.destination.id, ""); + assert_equals(e.destination.index, -1); + e.preventDefault(); + }); + a.click(); + }); +}, "<a> with a target fires navigate event in target window but not source"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-destination-after-detach.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-destination-after-detach.html new file mode 100644 index 0000000000..1dcb6cac43 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-destination-after-detach.html @@ -0,0 +1,33 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + let destination_key = i.contentWindow.navigation.currentEntry.key; + let destination_id = i.contentWindow.navigation.currentEntry.id; + let destination_index = i.contentWindow.navigation.currentEntry.index; + await i.contentWindow.navigation.navigate("#1").finished; + + let back_destination; + i.contentWindow.navigation.onnavigate = e => back_destination = e.destination; + await i.contentWindow.navigation.back().finished; + + // Before detach, key/id/index are valid. + assert_equals(back_destination.key, destination_key); + assert_equals(back_destination.id, destination_id); + assert_equals(back_destination.index, destination_index); + + i.remove(); + + // After detach, key/id/index are invalid, but the url is still valid. + assert_equals(back_destination.key, ""); + assert_equals(back_destination.id, ""); + assert_equals(new URL(back_destination.url).pathname, "/common/blank.html"); + assert_equals(back_destination.index, -1); +}, "navigate event destination after iframe detach"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-destination-dynamic-index.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-destination-dynamic-index.html new file mode 100644 index 0000000000..2e0f1ea497 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-destination-dynamic-index.html @@ -0,0 +1,34 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../navigation-methods/return-value/resources/helpers.js"></script> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + await navigation.navigate("#1").finished; + + let back_destination; + navigation.addEventListener("navigate", t.step_func(e => { + back_destination = e.destination; + assert_equals(back_destination.index, 0); + }), { once: true }); + await navigation.back().finished; + + // Disposing the destination entry of back_destination should update + // back_destination.index, even though back_destination's navigation has + // completed. + await navigation.navigate("#clobber_back", { history: "replace" }).finished; + assert_equals(back_destination.index, -1); + + navigation.addEventListener("navigate", t.step_func(e => { + assert_equals(e.destination.index, 1); + + // Dispose the destination entry and destination.index should update. + navigation.navigate("#clobber_forward"); + assert_equals(e.destination.index, -1); + }), { once: true }); + await assertBothRejectDOM(t, navigation.forward(), "AbortError"); +}, "navigate event destination.index should be dynamic"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-destination-getState-back-forward.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-destination-getState-back-forward.html new file mode 100644 index 0000000000..c8b1043aba --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-destination-getState-back-forward.html @@ -0,0 +1,26 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + window.onload = () => t.step_timeout(t.step_func_done(() => { + let navState = { statevar: "state" }; + navigation.navigate("#foo", { history: "replace", state: navState }); + let target_key = navigation.currentEntry.key; + let target_id = navigation.currentEntry.id; + navigation.navigate("#bar"); + navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "traverse"); + assert_not_equals(e.destination, null); + assert_not_equals(e.destination.getState(), undefined); + assert_not_equals(e.destination.getState(), e.destination.getState()); + assert_equals(e.destination.key, ""); + assert_equals(e.destination.id, ""); + assert_equals(e.destination.index, -1); + }); + navigation.back(); + }), 0); +}, "navigate event destination.getState() on back/forward navigation"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-destination-getState-navigate.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-destination-getState-navigate.html new file mode 100644 index 0000000000..5dac40de56 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-destination-getState-navigate.html @@ -0,0 +1,23 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + window.onload = () => t.step_timeout(() => { + let navState = { statevar: "state" }; + navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "push"); + assert_not_equals(e.destination, null); + assert_not_equals(e.destination.getState(), undefined); + assert_equals(e.destination.getState().statevar, "state"); + assert_not_equals(e.destination.getState(), e.destination.getState()); + assert_equals(e.destination.key, ""); + assert_equals(e.destination.id, ""); + assert_equals(e.destination.index, -1); + }); + navigation.navigate("#foo", { state: navState }); + }, 0); +}, "navigate event destination.getState() should be the state given to navigate()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-destination-getState-reload.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-destination-getState-reload.html new file mode 100644 index 0000000000..a180e086a9 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-destination-getState-reload.html @@ -0,0 +1,25 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + window.onload = () => t.step_timeout(() => { + let navState = { statevar: "state" }; + navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "reload"); + assert_not_equals(e.destination, null); + assert_not_equals(e.destination.getState(), undefined); + assert_equals(e.destination.getState().statevar, "state"); + assert_not_equals(e.destination.getState(), e.destination.getState()); + assert_equals(e.destination.key, ""); + assert_equals(e.destination.id, ""); + assert_equals(e.destination.index, -1); + e.intercept(); + }); + navigation.updateCurrentEntry({ state: navState }); + location.reload(); + }, 0); +}, "navigate event destination.getState() on location.reload()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-form-get.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-form-get.html new file mode 100644 index 0000000000..87a102ddc0 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-form-get.html @@ -0,0 +1,27 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<form id="form" action=""></form> +<script> +async_test(t => { + navigation.onnavigate = t.step_func_done(e => { + e.preventDefault(); + + assert_equals(e.navigationType, "replace"); + assert_true(e.cancelable); + assert_true(e.canIntercept); + assert_false(e.userInitiated); + assert_false(e.hashChange); + assert_equals(e.downloadRequest, null); + assert_equals(e.destination.url, location.href + "?"); + assert_false(e.destination.sameDocument); + assert_equals(e.destination.key, ""); + assert_equals(e.destination.id, ""); + assert_equals(e.destination.index, -1); + + // Because it's a GET, not a POST + assert_equals(e.formData, null); + }); + window.onload = t.step_func(() => form.submit()); +}, "<form> submission with GET method fires navigate event but with formData null"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-form-reload.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-form-reload.html new file mode 100644 index 0000000000..f18a11ebda --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-form-reload.html @@ -0,0 +1,28 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="iframe" name="i" src="/common/blank.html"></iframe> +<form id="form" action="/common/blank.html?1" method="post" target="i"></form> +<script> +async_test(t => { + window.onload = t.step_func(() => { + navigation.onnavigate = t.step_func_done(() => { + assert_unreached("onnavigate should not have fired in source window"); + }); + iframe.contentWindow.navigation.onnavigate = t.step_func(e => { + assert_equals(e.navigationType, "push"); + assert_not_equals(e.formData, null); + + iframe.onload = t.step_func(() => { + iframe.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "reload"); + assert_equals(e.formData, null); + }); + + iframe.contentWindow.location.reload(); + }); + }); + form.submit(); + }); +}, "reloading a page created from form submission results in formData of null, not the original form data"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-form-traverse.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-form-traverse.html new file mode 100644 index 0000000000..d673537503 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-form-traverse.html @@ -0,0 +1,44 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="iframe" name="i" src="/common/blank.html"></iframe> +<form id="form" action="/common/blank.html?1" method="post" target="i"></form> +<script> +async_test(t => { + window.onload = t.step_func(() => { + navigation.onnavigate = t.step_func_done(() => { + assert_unreached("onnavigate should not have fired in source window"); + }); + iframe.contentWindow.navigation.onnavigate = t.step_func(e => { + assert_equals(e.navigationType, "push"); + assert_not_equals(e.formData, null); + + iframe.onload = t.step_func(() => { + // Avoid the replace behavior that occurs if you navigate during the load handler + t.step_timeout(() => { + iframe.contentWindow.navigation.onnavigate = t.step_func(e => { + assert_equals(e.navigationType, "push"); + assert_equals(e.formData, null); + }); + + iframe.contentWindow.onhashchange = t.step_func(() => { + iframe.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "traverse"); + assert_equals(e.formData, null); + }); + + // 3: go back + iframe.contentWindow.history.back(); + }); + + // 2: navigate from /common/blank.html?1-with-form-data to /common/blank.html?1#1-with-form-data + iframe.contentWindow.location.hash = "#1"; + }, 0); + }); + }); + + // 1: submit the form, navigating from /common/blank.html to /common/blank.html?1-with-form-data + form.submit(); + }); +}, "reloading a page created from form submission results in formData of null, not the original form data"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-form-userInitiated.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-form-userInitiated.html new file mode 100644 index 0000000000..40c5905447 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-form-userInitiated.html @@ -0,0 +1,30 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<form id="form" method="post" action=""> +<input id="submit" type="submit" value="Submit"> +</form> +<script> +async_test(t => { + navigation.onnavigate = t.step_func_done(e => { + e.preventDefault(); + + assert_equals(e.navigationType, "push"); + assert_true(e.cancelable); + assert_true(e.canIntercept); + assert_true(e.userInitiated); + assert_false(e.hashChange); + assert_equals(e.downloadRequest, null); + assert_equals(e.destination.url, location.href); + assert_false(e.destination.sameDocument); + assert_equals(e.destination.key, ""); + assert_equals(e.destination.id, ""); + assert_equals(e.destination.index, -1); + assert_not_equals(e.formData, null); + }); + window.onload = t.step_func(() => test_driver.click(submit)); +}, "<form> submission fires navigate event"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-form-with-target.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-form-with-target.html new file mode 100644 index 0000000000..f6fe05c938 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-form-with-target.html @@ -0,0 +1,29 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="iframe" name="i" src="/common/blank.html"></iframe> +<form id="form" method="post" action="foo.html" target="i"></form> +<script> +async_test(t => { + window.onload = t.step_func(() => { + navigation.onnavigate = t.unreached_func("onnavigate should not have fired in source window"); + + iframe.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "push"); + assert_true(e.cancelable); + assert_true(e.canIntercept); + assert_false(e.userInitiated); + assert_false(e.hashChange); + assert_equals(e.downloadRequest, null); + assert_equals(new URL(e.destination.url).pathname, + "/navigation-api/navigate-event/foo.html"); + assert_false(e.destination.sameDocument); + assert_equals(e.destination.key, ""); + assert_equals(e.destination.id, ""); + assert_equals(e.destination.index, -1); + assert_not_equals(e.formData, null); + }); + form.submit(); + }); +}, "<form> submission with a target fires navigate event in target window but not source"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-form.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-form.html new file mode 100644 index 0000000000..c57d72c3de --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-form.html @@ -0,0 +1,25 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<form id="form" method="post" action=""></form> +<script> +async_test(t => { + navigation.onnavigate = t.step_func_done(e => { + e.preventDefault(); + + assert_equals(e.navigationType, "replace"); + assert_true(e.cancelable); + assert_true(e.canIntercept); + assert_false(e.userInitiated); + assert_false(e.hashChange); + assert_equals(e.downloadRequest, null); + assert_equals(e.destination.url, location.href); + assert_false(e.destination.sameDocument); + assert_equals(e.destination.key, ""); + assert_equals(e.destination.id, ""); + assert_equals(e.destination.index, -1); + assert_not_equals(e.formData, null); + }); + window.onload = t.step_func(() => form.submit()); +}, "<form> submission fires navigate event"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-history-back-after-fragment.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-history-back-after-fragment.html new file mode 100644 index 0000000000..57a30c85b8 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-history-back-after-fragment.html @@ -0,0 +1,31 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + window.onload = () => t.step_timeout(() => { + let start_length = history.length; + let target_key = navigation.currentEntry.key; + let target_id = navigation.currentEntry.id; + location.hash = "#1"; + assert_equals(history.length, start_length + 1); + + navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "traverse"); + assert_true(e.cancelable); + assert_true(e.canIntercept); + assert_false(e.userInitiated); + assert_true(e.hashChange); + assert_equals(e.downloadRequest, null); + assert_equals(new URL(e.destination.url).hash, ""); + assert_true(e.destination.sameDocument); + assert_equals(e.destination.key, target_key); + assert_equals(e.destination.id, target_id); + assert_equals(e.destination.index, 0); + assert_equals(e.formData, null); + }); + + history.back(); + }, 0); +}, "history.back() fires the navigate event and sets hashChange when reversing a fragment navigation"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-history-back-after-pushState.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-history-back-after-pushState.html new file mode 100644 index 0000000000..bf2e6e4e30 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-history-back-after-pushState.html @@ -0,0 +1,31 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + window.onload = () => t.step_timeout(() => { + let start_length = history.length; + let target_key = navigation.currentEntry.key; + let target_id = navigation.currentEntry.id; + history.pushState(1, "", "pushState.html"); + assert_equals(history.length, start_length + 1); + + navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "traverse"); + assert_true(e.cancelable); + assert_true(e.canIntercept); + assert_false(e.userInitiated); + assert_false(e.hashChange); + assert_equals(e.downloadRequest, null); + assert_equals(new URL(e.destination.url).hash, ""); + assert_true(e.destination.sameDocument); + assert_equals(e.destination.key, target_key); + assert_equals(e.destination.id, target_id); + assert_equals(e.destination.index, 0); + assert_equals(e.formData, null); + }); + + history.back(); + }, 0); +}, "history.back() fires the navigate event when reversing a pushState()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-history-back-cross-document.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-history-back-cross-document.html new file mode 100644 index 0000000000..cd7be6e9ad --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-history-back-cross-document.html @@ -0,0 +1,32 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + let target_key = i.contentWindow.navigation.currentEntry.key; + let target_id = i.contentWindow.navigation.currentEntry.id; + i.contentWindow.navigation.navigate("?foo"); + i.onload = t.step_func(() => { + i.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "traverse"); + assert_false(e.cancelable); + assert_false(e.canIntercept); + assert_false(e.userInitiated); + assert_false(e.hashChange); + assert_equals(e.downloadRequest, null); + assert_equals(new URL(e.destination.url).pathname, "/common/blank.html"); + assert_false(e.destination.sameDocument); + assert_equals(e.destination.key, target_key); + assert_equals(e.destination.id, target_id); + assert_equals(e.destination.index, 0); + assert_equals(e.formData, null); + assert_equals(e.info, undefined); + }); + assert_true(i.contentWindow.navigation.canGoBack); + i.contentWindow.history.back(); + }) + }); +}, "navigate event for history.back() - cross-document"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-history-back-noop.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-history-back-noop.html new file mode 100644 index 0000000000..10763c93fd --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-history-back-noop.html @@ -0,0 +1,32 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + await i.contentWindow.navigation.navigate("#").finished; + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry.index, 1); + assert_equals(navigation.entries().length, 1); + assert_equals(navigation.currentEntry.index, 0); + assert_equals(history.length, 2); + + i.remove(); + assert_equals(navigation.entries().length, 1); + assert_equals(navigation.currentEntry.index, 0); + assert_equals(history.length, 2); + + // back() here should do nothing. The iframe that would have navigated has + // been removed. No navigate event should be fired. + navigation.onnavigate = t.unreached_func("navigate must not fire"); + navigation.oncurrententrychange = t.unreached_func("currententrychange must not fire"); + history.back(); + + // Give time for the navigation to proceed. + await new Promise(resolve => t.step_timeout(resolve, 20)); +}, "history.back() does not fire a navigate event when there's nothing to navigate"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-history-go-0.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-history-go-0.html new file mode 100644 index 0000000000..b1f41425b6 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-history-go-0.html @@ -0,0 +1,27 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + i.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "reload"); + assert_true(e.cancelable); + assert_true(e.canIntercept); + assert_false(e.userInitiated); + assert_false(e.hashChange); + assert_equals(e.downloadRequest, null); + assert_equals(new URL(e.destination.url).pathname, "/common/blank.html"); + assert_false(e.destination.sameDocument); + assert_equals(e.destination.key, ""); + assert_equals(e.destination.id, ""); + assert_equals(e.destination.index, -1); + assert_equals(e.formData, null); + e.preventDefault(); + }); + + i.contentWindow.history.go(0); + }); +}, "history.go(0) fires the navigate event"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-history-pushState.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-history-pushState.html new file mode 100644 index 0000000000..266309a79e --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-history-pushState.html @@ -0,0 +1,29 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + let start_length = history.length; + navigation.onnavigate = t.step_func(e => { + assert_equals(e.navigationType, "push"); + assert_true(e.cancelable); + assert_true(e.canIntercept); + assert_false(e.userInitiated); + assert_false(e.hashChange); + assert_equals(e.downloadRequest, null); + assert_equals(new URL(e.destination.url).hash, "#1"); + assert_true(e.destination.sameDocument); + assert_equals(e.destination.key, ""); + assert_equals(e.destination.id, ""); + assert_equals(e.destination.index, -1); + assert_equals(e.formData, null); + e.preventDefault(); + t.step_timeout(t.step_func_done(() => { + assert_equals(location.hash, ""); + assert_equals(history.state, null); + assert_equals(history.length, start_length); + }), 0); + }); + history.pushState(1, null, "#1"); +}, "history.pushState() fires the navigate event"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-history-replaceState.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-history-replaceState.html new file mode 100644 index 0000000000..ea6d3df455 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-history-replaceState.html @@ -0,0 +1,29 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + let start_length = history.length; + navigation.onnavigate = t.step_func(e => { + assert_equals(e.navigationType, "replace"); + assert_true(e.cancelable); + assert_true(e.canIntercept); + assert_false(e.userInitiated); + assert_false(e.hashChange); + assert_equals(e.downloadRequest, null); + assert_equals(new URL(e.destination.url).hash, "#1"); + assert_true(e.destination.sameDocument); + assert_equals(e.destination.key, ""); + assert_equals(e.destination.id, ""); + assert_equals(e.destination.index, -1); + assert_equals(e.formData, null); + e.preventDefault(); + t.step_timeout(t.step_func_done(() => { + assert_equals(location.hash, ""); + assert_equals(history.state, null); + assert_equals(history.length, start_length); + }), 0); + }); + history.replaceState(1, null, "#1"); +}, "history.replaceState() fires the navigate event"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-iframe-location.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-iframe-location.html new file mode 100644 index 0000000000..25d51476f3 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-iframe-location.html @@ -0,0 +1,30 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="iframe" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + navigation.onnavigate = t.step_func_done(() => { + assert_unreached("onnavigate should not have fired in source window"); + }); + iframe.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "push"); + assert_true(e.cancelable); + assert_true(e.canIntercept); + assert_false(e.userInitiated); + assert_true(e.hashChange); + assert_equals(e.downloadRequest, null); + assert_equals(new URL(e.destination.url).hash, "#1"); + assert_true(e.destination.sameDocument); + assert_equals(e.destination.key, ""); + assert_equals(e.destination.id, ""); + assert_equals(e.destination.index, -1); + assert_equals(e.formData, null); + e.preventDefault(); + t.step_timeout(t.step_func_done(() => assert_equals(location.hash, "")), 0); + }); + iframe.contentWindow.location.hash = "#1"; + }); +}, "location API on another window fires navigate event in the target window only"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-location.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-location.html new file mode 100644 index 0000000000..a4d0c60776 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-location.html @@ -0,0 +1,23 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<form id="form" action=""></form> +<script> +async_test(t => { + navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "replace"); + assert_true(e.cancelable); + assert_true(e.canIntercept); + assert_false(e.userInitiated); + assert_true(e.hashChange); + assert_equals(e.downloadRequest, null); + assert_equals(new URL(e.destination.url).hash, "#1"); + assert_true(e.destination.sameDocument); + assert_equals(e.destination.key, ""); + assert_equals(e.destination.id, ""); + assert_equals(e.destination.index, -1); + assert_equals(e.formData, null); + }); + location.href = "#1"; +}, "location API fires navigate event"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-meta-refresh.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-meta-refresh.html new file mode 100644 index 0000000000..9fa59b29f2 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-meta-refresh.html @@ -0,0 +1,27 @@ +<!doctype html> +<head> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="resources/meta-refresh.html"></iframe> +</head> +<script> +async_test(t => { + i.onload = t.step_func(() => { + i.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "reload"); + assert_true(e.cancelable); + assert_true(e.canIntercept); + assert_false(e.userInitiated); + assert_false(e.hashChange); + assert_equals(e.downloadRequest, null); + assert_equals(e.destination.url, i.contentWindow.location.href); + assert_false(e.destination.sameDocument); + assert_equals(e.destination.key, ""); + assert_equals(e.destination.id, ""); + assert_equals(e.destination.index, -1); + assert_equals(e.formData, null); + e.preventDefault(); + }); + }); +}, "<meta> refresh fires navigate event"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-navigation-back-cross-document.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-navigation-back-cross-document.html new file mode 100644 index 0000000000..2e1adbeee9 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-navigation-back-cross-document.html @@ -0,0 +1,35 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + let target_key = i.contentWindow.navigation.currentEntry.key; + let target_id = i.contentWindow.navigation.currentEntry.id; + i.contentWindow.navigation.navigate("?foo"); + i.onload = t.step_func(() => { + let beforeunload_called = false; + i.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_true(beforeunload_called); + assert_equals(e.navigationType, "traverse"); + assert_false(e.cancelable); + assert_false(e.canIntercept); + assert_false(e.userInitiated); + assert_false(e.hashChange); + assert_equals(e.downloadRequest, null); + assert_equals(new URL(e.destination.url).pathname, "/common/blank.html"); + assert_false(e.destination.sameDocument); + assert_equals(e.destination.key, target_key); + assert_equals(e.destination.id, target_id); + assert_equals(e.destination.index, 0); + assert_equals(e.formData, null); + assert_equals(e.info, "hi"); + }); + i.contentWindow.onbeforeunload = () => beforeunload_called = true; + assert_true(i.contentWindow.navigation.canGoBack); + i.contentWindow.navigation.back({ info: "hi" }); + }) + }); +}, "navigate event for navigation.back() - cross-document"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-navigation-back-same-document-in-iframe.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-navigation-back-same-document-in-iframe.html new file mode 100644 index 0000000000..cebd2f3693 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-navigation-back-same-document-in-iframe.html @@ -0,0 +1,33 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + let target_key = i.contentWindow.navigation.currentEntry.key; + let target_id = i.contentWindow.navigation.currentEntry.id; + await i.contentWindow.navigation.navigate("#").finished; + assert_true(i.contentWindow.navigation.canGoBack); + + i.contentWindow.navigation.onnavigate = e => { + assert_equals(e.navigationType, "traverse"); + assert_false(e.cancelable, "traversals in iframes should never be cancelable"); + assert_true(e.canIntercept); + assert_false(e.userInitiated); + assert_true(e.hashChange); + assert_equals(e.downloadRequest, null); + assert_equals(new URL(e.destination.url).hash, ""); + assert_true(e.destination.sameDocument); + assert_equals(e.destination.key, target_key); + assert_equals(e.destination.id, target_id); + assert_equals(e.destination.index, 0); + assert_equals(e.formData, null); + assert_equals(e.info, "hi"); + } + await i.contentWindow.navigation.back({ info: "hi" }).finished; +}, "navigate event for navigation.back() - same-document in an iframe"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-navigation-back-same-document.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-navigation-back-same-document.html new file mode 100644 index 0000000000..431d38449c --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-navigation-back-same-document.html @@ -0,0 +1,32 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + window.onload = () => t.step_timeout(() => { + let target_key = navigation.currentEntry.key; + let target_id = navigation.currentEntry.id; + navigation.navigate("#foo").committed.then(t.step_func(() => { + navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "traverse"); + assert_true(e.cancelable); + assert_true(e.canIntercept); + assert_false(e.userInitiated); + assert_true(e.hashChange); + assert_equals(e.downloadRequest, null); + assert_equals(new URL(e.destination.url).hash, ""); + assert_true(e.destination.sameDocument); + assert_equals(e.destination.key, target_key); + assert_equals(e.destination.id, target_id); + assert_equals(e.destination.index, 0); + assert_equals(e.formData, null); + assert_equals(e.info, "hi"); + }); + assert_true(navigation.canGoBack); + navigation.back({ info: "hi" }); + })); + }, 0); +}, "navigate event for navigation.back() - same-document"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-navigation-navigate.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-navigation-navigate.html new file mode 100644 index 0000000000..ffc8ea867f --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-navigation-navigate.html @@ -0,0 +1,22 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "replace"); + assert_true(e.cancelable); + assert_true(e.canIntercept); + assert_false(e.userInitiated); + assert_true(e.hashChange); + assert_equals(e.downloadRequest, null); + assert_equals(new URL(e.destination.url).hash, "#foo"); + assert_true(e.destination.sameDocument); + assert_equals(e.destination.key, ""); + assert_equals(e.destination.id, ""); + assert_equals(e.destination.index, -1); + assert_equals(e.formData, null); + }); + navigation.navigate("#foo"); +}, "navigate event for navigation.navigate()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-to-javascript.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-to-javascript.html new file mode 100644 index 0000000000..78f490d87b --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-to-javascript.html @@ -0,0 +1,18 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="iframe" name="i" src="/common/blank.html"></iframe> + +<script> +async_test(t => { + window.onload = t.step_func(() => { + navigation.onnavigate = t.unreached_func("onnavigate should not have fired in source window"); + + iframe.contentWindow.navigation.onnavigate = t.unreached_func("onnavigate should not have fired in iframe window"); + + iframe.contentWindow.location.href = "javascript:'foo'"; + + iframe.onload = () => t.done(); + }); +}, "navigate event does not fire for javascript: URL navigations"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-to-srcdoc.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-to-srcdoc.html new file mode 100644 index 0000000000..8bbb66a31f --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-to-srcdoc.html @@ -0,0 +1,34 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="iframe" name="i" src="/common/blank.html"></iframe> + +<script> +async_test(t => { + window.onload = t.step_func(() => { + navigation.onnavigate = t.unreached_func("onnavigate should not have fired in source window"); + + iframe.contentWindow.navigation.onnavigate = t.step_func(e => { + assert_equals(e.navigationType, "push"); + assert_true(e.cancelable, "cancelable"); + assert_false(e.canIntercept, "canIntercept"); + assert_false(e.userInitiated, "userInitiated"); + assert_false(e.hashChange, "hashChange"); + assert_equals(e.downloadRequest, null); + assert_equals(e.destination.url, "about:srcdoc"); + assert_false(e.destination.sameDocument); + assert_equals(e.destination.key, ""); + assert_equals(e.destination.id, ""); + assert_equals(e.destination.index, -1); + assert_equals(e.formData, null); + e.preventDefault(); + + // Make sure it doesn't navigate anyway. + iframe.onload = t.unreached_func("Must not load the srcdoc document"); + t.step_timeout(() => t.done(), 10); + }); + + iframe.srcdoc = "srcdoc contents"; + }); +}, "navigate event fires appropriately (and can be canceled) for adding the srcdoc attribute"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-window-open-self.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-window-open-self.html new file mode 100644 index 0000000000..a6e443fd5a --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-window-open-self.html @@ -0,0 +1,23 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "push"); + assert_true(e.cancelable); + assert_true(e.canIntercept); + assert_false(e.userInitiated); + assert_true(e.hashChange); + assert_equals(e.downloadRequest, null); + assert_equals(new URL(e.destination.url).hash, "#1"); + assert_true(e.destination.sameDocument); + assert_equals(e.destination.key, ""); + assert_equals(e.destination.id, ""); + assert_equals(e.destination.index, -1); + assert_equals(e.formData, null); + e.preventDefault(); + }); + window.onload = t.step_func(() => window.open("#1", "_self")); +}, "window.open() fires navigate event when targeting self"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigate-window-open.html b/testing/web-platform/tests/navigation-api/navigate-event/navigate-window-open.html new file mode 100644 index 0000000000..1fe2402bc9 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigate-window-open.html @@ -0,0 +1,30 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="iframe" name="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + navigation.onnavigate = t.step_func_done(() => { + assert_unreached("onnavigate should not have fired in source window"); + }); + iframe.contentWindow.navigation.onnavigate = t.step_func_done(e => { + assert_equals(e.navigationType, "push"); + assert_true(e.cancelable); + assert_true(e.canIntercept); + assert_false(e.userInitiated); + assert_true(e.hashChange); + assert_equals(e.downloadRequest, null); + assert_equals(new URL(e.destination.url).hash, "#1"); + assert_true(e.destination.sameDocument); + assert_equals(e.destination.key, ""); + assert_equals(e.destination.id, ""); + assert_equals(e.destination.index, -1); + assert_equals(e.formData, null); + e.preventDefault(); + }); + + window.open("/common/blank.html#1", "i"); + }); +}, "window.open() fires navigate event in target window but not source"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigatesuccess-cross-document.html b/testing/web-platform/tests/navigation-api/navigate-event/navigatesuccess-cross-document.html new file mode 100644 index 0000000000..1d528c1f5f --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigatesuccess-cross-document.html @@ -0,0 +1,14 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="resources/navigatesuccess-cross-document-helper.html"></iframe> +<script> +async_test(t => { + // The iframe will post a message if it receives a navigatesuccess. + window.onmessage = t.unreached_func("navigatesuccess received"); + window.onload = t.step_func(() => { + i.contentWindow.location.search = "?1"; + i.onload = t.step_func_done(() => assert_equals(i.contentWindow.location.search, "?1")); + }); +}, "navigatesuccess does not fire for a cross-document navigation"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigatesuccess-same-document.html b/testing/web-platform/tests/navigation-api/navigate-event/navigatesuccess-same-document.html new file mode 100644 index 0000000000..6007170ec1 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigatesuccess-same-document.html @@ -0,0 +1,10 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<a id="a" href="#1"></a> +<script> +async_test(t => { + navigation.onnavigatesuccess = t.step_func_done(() => assert_equals(location.hash, "#1")); + a.click(); +}, "navigatesuccess fires for a same-document navigation"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigation-back-cross-document-preventDefault.html b/testing/web-platform/tests/navigation-api/navigate-event/navigation-back-cross-document-preventDefault.html new file mode 100644 index 0000000000..0b5b750876 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigation-back-cross-document-preventDefault.html @@ -0,0 +1,33 @@ + +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../navigation-methods/return-value/resources/helpers.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + let w = window.open("resources/opener-postMessage-onload.html"); + await new Promise(resolve => window.onmessage = resolve); + // Navigate to a url that will notify us when the navigation is complete. + w.navigation.navigate("opener-postMessage-onload.html?1"); + + await new Promise(resolve => window.onmessage = resolve); + assert_equals(w.navigation.entries().length, 2); + assert_equals(w.navigation.currentEntry.index, 1); + let navigate_called = false; + w.navigation.onnavigate = t.step_func(e => { + navigate_called = true; + assert_false(e.destination.sameDocument); + assert_false(e.cancelable); + // Should do nothing. + e.preventDefault(); + }); + w.navigation.back(); + await new Promise(resolve => window.onmessage = resolve); + assert_equals(w.navigation.currentEntry.index, 0); + assert_true(navigate_called); +}, "navigation.back() cross-document cannot be cancelled with the navigate event"); +</script> + diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigation-back-same-document-preventDefault.html b/testing/web-platform/tests/navigation-api/navigate-event/navigation-back-same-document-preventDefault.html new file mode 100644 index 0000000000..7edb188823 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigation-back-same-document-preventDefault.html @@ -0,0 +1,26 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../navigation-methods/return-value/resources/helpers.js"></script> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + await navigation.navigate("#").finished; + assert_equals(navigation.entries().length, 2); + assert_equals(navigation.currentEntry.index, 1); + + navigation.onnavigate = e => e.preventDefault(); + + navigation.onnavigateerror = t.step_func(e => { + assert_equals(e.constructor, ErrorEvent); + assert_equals(e.filename, location.href); + navigateerror_called = true; + }); + await assertBothRejectDOM(t, navigation.back(), "AbortError"); + assert_equals(navigation.currentEntry.index, 1); + assert_true(navigateerror_called); +}, "navigation.back() same-document preventDefault"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigation-traverseTo-in-iframe-same-document-preventDefault.html b/testing/web-platform/tests/navigation-api/navigate-event/navigation-traverseTo-in-iframe-same-document-preventDefault.html new file mode 100644 index 0000000000..463746e8f7 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigation-traverseTo-in-iframe-same-document-preventDefault.html @@ -0,0 +1,43 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../navigation-methods/return-value/resources/helpers.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + // Navigate the iframe, then the top window, so that when the iframe goes back + // to its initial entry, the top window navigates as well. + await i.contentWindow.navigation.navigate("#").finished; + await navigation.navigate("#").finished; + assert_equals(navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(navigation.currentEntry.index, 1); + assert_equals(i.contentWindow.navigation.currentEntry.index, 1); + + // Ensure the top window, which is allowed to cancel the traversal, does so. + navigation.onnavigate = e => e.preventDefault(); + + let top_navigateerror_fired = false; + navigation.onnavigateerror = t.step_func(e => { + assert_equals(e.constructor, ErrorEvent); + assert_equals(e.filename, location.href); + top_navigateerror_fired = true; + }); + + i.contentWindow.navigation.onnavigate = t.unreached_func("navigate event should not fire in the iframe, because the traversal was cancelled in the top window"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("navigateerror event should not fire in the iframe, because the navigate event was not fired"); + + // When the top window blocks the traversal, it should be blocked in the + // iframe as well, and the traversal promises in the iframe should be rejected. + const iWindow = i.contentWindow; + const iDOMException = iWindow.DOMException; + await assertBothRejectDOM(t, i.contentWindow.navigation.traverseTo(i.contentWindow.navigation.entries()[0].key), "AbortError", iWindow, iDOMException); + assert_true(top_navigateerror_fired); + assert_equals(navigation.currentEntry.index, 1); + assert_equals(i.contentWindow.navigation.currentEntry.index, 1); +}, "navigation.traverseTo() in an iframe with same-document preventDefault in its parent"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigation-traverseTo-navigates-top-and-same-doc-child-and-cross-doc-child.html b/testing/web-platform/tests/navigation-api/navigate-event/navigation-traverseTo-navigates-top-and-same-doc-child-and-cross-doc-child.html new file mode 100644 index 0000000000..31cb54fca2 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigation-traverseTo-navigates-top-and-same-doc-child-and-cross-doc-child.html @@ -0,0 +1,49 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i1" src="/common/blank.html"></iframe> +<iframe id="i2" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + await navigation.navigate("#").finished; + await i1.contentWindow.navigation.navigate("#").finished; + i2.contentWindow.navigation.navigate("?"); + await new Promise(resolve => i2.onload = () => t.step_timeout(resolve, 0)); + + assert_equals(navigation.entries().length, 2); + assert_equals(i1.contentWindow.navigation.entries().length, 2); + assert_equals(i2.contentWindow.navigation.entries().length, 2); + assert_equals(navigation.currentEntry.index, 1); + assert_equals(i1.contentWindow.navigation.currentEntry.index, 1); + assert_equals(i2.contentWindow.navigation.currentEntry.index, 1); + + let navigate_event_count = 0; + navigation.onnavigate = t.step_func(e => { + assert_equals(navigate_event_count, 0); + navigate_event_count++; + assert_true(e.cancelable); + }); + i1.contentWindow.navigation.onnavigate = t.step_func(e => { + assert_true(navigate_event_count > 0); + navigate_event_count++; + assert_false(e.cancelable); + }); + i2.contentWindow.navigation.onnavigate = t.step_func(e => { + assert_true(navigate_event_count > 0); + navigate_event_count++; + assert_false(e.cancelable); + }); + + await navigation.traverseTo(navigation.entries()[0].key).finished; + // The top window will finish quickly, becuase it is same-document traversal. + // i2 will be slower because it is cross-document, so wait for its onload. + await new Promise(resolve => i2.onload = () => t.step_timeout(resolve, 0)); + assert_equals(navigate_event_count, 3); + assert_equals(navigation.currentEntry.index, 0); + assert_equals(i1.contentWindow.navigation.currentEntry.index, 0); + assert_equals(i2.contentWindow.navigation.currentEntry.index, 0); +}, "navigation.traverseTo() can navigate 3 frames of different types with correct navigate event cancelable values"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigation-traverseTo-same-document-preventDefault-multiple-windows.html b/testing/web-platform/tests/navigation-api/navigate-event/navigation-traverseTo-same-document-preventDefault-multiple-windows.html new file mode 100644 index 0000000000..9bb64fb7cc --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigation-traverseTo-same-document-preventDefault-multiple-windows.html @@ -0,0 +1,23 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + await navigation.navigate("#").finished; + await i.contentWindow.navigation.navigate("#").finished; + assert_equals(navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(navigation.currentEntry.index, 1); + assert_equals(i.contentWindow.navigation.currentEntry.index, 1); + + navigation.onnavigate = e => e.preventDefault(); + i.contentWindow.navigation.onnavigate = t.unreached_func("navigate event should not fire in the iframe, because the traversal was cancelled in the top window"); + await promise_rejects_dom(t, "AbortError", navigation.traverseTo(navigation.entries()[0].key).finished); + assert_equals(navigation.currentEntry.index, 1); + assert_equals(i.contentWindow.navigation.currentEntry.index, 1); +}, "navigation.traverseTo() - if a top window cancels the traversal, any iframes should not fire navigate"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/navigation-traverseTo-top-cancels-cross-document-child.html b/testing/web-platform/tests/navigation-api/navigate-event/navigation-traverseTo-top-cancels-cross-document-child.html new file mode 100644 index 0000000000..11f07afefc --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/navigation-traverseTo-top-cancels-cross-document-child.html @@ -0,0 +1,26 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../navigation-methods/return-value/resources/helpers.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + await navigation.navigate("#").finished; + i.contentWindow.navigation.navigate("?"); + await new Promise(resolve => i.onload = () => t.step_timeout(resolve, 0)); + + assert_equals(navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(navigation.currentEntry.index, 1); + assert_equals(i.contentWindow.navigation.currentEntry.index, 1); + + navigation.onnavigate = t.step_func(e => e.preventDefault()); + i.contentWindow.navigation.onnavigate = t.unreached_func("navigation should be cancelled before iframe fires navigate event"); + await assertBothRejectDOM(t, navigation.traverseTo(navigation.entries()[0].key), "AbortError"); + // Give the iframe time to navigate in case it was incorrectly permitted. + await new Promise(resolve => t.step_timeout(resolve, 50)); +}, "navigate.traverseTo() cancelled by top frame cancels cross-document iframe"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/replaceState-in-unload-then-remove-iframe.html b/testing/web-platform/tests/navigation-api/navigate-event/replaceState-in-unload-then-remove-iframe.html new file mode 100644 index 0000000000..e97b72b157 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/replaceState-in-unload-then-remove-iframe.html @@ -0,0 +1,16 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + i.contentWindow.onunload = t.step_func(() => { + i.contentWindow.history.replaceState(null, "", "#"); + i.remove(); + t.step_timeout(t.step_func_done(), 0); + }); + i.contentWindow.location = "/common/blank.html?1"; + }); +}, "reacting to the navigate event doesn't crash when replaceState is called in onunload"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/replaceState-inside-back-handler.html b/testing/web-platform/tests/navigation-api/navigate-event/replaceState-inside-back-handler.html new file mode 100644 index 0000000000..29409b5e2b --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/replaceState-inside-back-handler.html @@ -0,0 +1,15 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../navigation-methods/return-value/resources/helpers.js"></script> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + await navigation.navigate("#push").finished; + navigation.onnavigate = () => history.replaceState(null, "", "#"); + await assertBothRejectDOM(t, navigation.back(), "AbortError"); +}, "replaceState inside a navigate event for navigation.back()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/resources/meta-refresh.html b/testing/web-platform/tests/navigation-api/navigate-event/resources/meta-refresh.html new file mode 100644 index 0000000000..fd453e663f --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/resources/meta-refresh.html @@ -0,0 +1,4 @@ +<head> +<meta http-equiv="refresh" content="0"></meta> +</head> +<body></body> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/resources/navigatesuccess-cross-document-helper.html b/testing/web-platform/tests/navigation-api/navigate-event/resources/navigatesuccess-cross-document-helper.html new file mode 100644 index 0000000000..aabc5015a9 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/resources/navigatesuccess-cross-document-helper.html @@ -0,0 +1,6 @@ +<!doctype html> +<head> +<script> +navigation.onnavigatesuccess = () => top.postMessage("navigatesuccess received"); +</script> +</head> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/resources/opener-postMessage-onload.html b/testing/web-platform/tests/navigation-api/navigate-event/resources/opener-postMessage-onload.html new file mode 100644 index 0000000000..97e1d82058 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/resources/opener-postMessage-onload.html @@ -0,0 +1,6 @@ +<!doctype html> +<head> +<script> +window.onload = () => opener.postMessage("onload", "*"); +</script> +</head> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/same-url-replace-cross-document.html b/testing/web-platform/tests/navigation-api/navigate-event/same-url-replace-cross-document.html new file mode 100644 index 0000000000..0a976cd51f --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/same-url-replace-cross-document.html @@ -0,0 +1,23 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + // Wait for after the load event so that we are definitely testing the + // same URL as the cause of the rejections. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + assert_equals(navigation.entries().length, 1); + + navigation.onnavigate = t.step_func(e => { + e.intercept(); + assert_equals(e.navigationType, "replace"); + }); + navigation.onnavigateerror = t.unreached_func("onnavigateerror should not be called"); + + await navigation.navigate(location.href).finished; + assert_equals(navigation.entries().length, 1); + assert_equals(navigation.currentEntry.index, 0); +}, "navigate() to the current URL cross document should replace"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/same-url-replace-same-document.html b/testing/web-platform/tests/navigation-api/navigate-event/same-url-replace-same-document.html new file mode 100644 index 0000000000..839c687934 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/same-url-replace-same-document.html @@ -0,0 +1,23 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + // Wait for after the load event so that we are definitely testing the + // same URL as the cause of the rejections. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + assert_equals(navigation.entries().length, 1); + await navigation.navigate("#").finished; + assert_equals(navigation.entries().length, 2); + assert_equals(navigation.currentEntry.index, 1); + + navigation.onnavigate = t.step_func(e => { + assert_equals(e.navigationType, "replace"); + }); + await navigation.navigate(location.href).finished; + assert_equals(navigation.entries().length, 2); + assert_equals(navigation.currentEntry.index, 1); +}, "navigate() to the current URL same document should replace"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/signal-abort-detach-in-onnavigate.html b/testing/web-platform/tests/navigation-api/navigate-event/signal-abort-detach-in-onnavigate.html new file mode 100644 index 0000000000..467ea88899 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/signal-abort-detach-in-onnavigate.html @@ -0,0 +1,21 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + await new Promise(resolve => i.onload = resolve); + let iframe_constructor = i.contentWindow.DOMException; + let iframe_typeerror = i.contentWindow.TypeError; + let abort_signal; + let onabort_called = false; + i.contentWindow.navigation.onnavigate = t.step_func(e => { + abort_signal = e.signal; + abort_signal.onabort = () => onabort_called = true; + i.remove(); + }); + await promise_rejects_dom(t, 'AbortError', iframe_constructor, i.contentWindow.navigation.navigate("#1").committed); + assert_true(abort_signal.aborted); + assert_true(onabort_called); +}, "window detach inside a navigate event signals event.signal"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/signal-abort-intercept.html b/testing/web-platform/tests/navigation-api/navigate-event/signal-abort-intercept.html new file mode 100644 index 0000000000..1e92d8e445 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/signal-abort-intercept.html @@ -0,0 +1,18 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async t => { + let abort_signal; + let onabort_called = false; + navigation.onnavigate = t.step_func(e => { + abort_signal = e.signal; + abort_signal.onabort = () => onabort_called = true; + e.intercept(); + }); + + await navigation.navigate("?1").finished; + assert_false(abort_signal.aborted); + assert_false(onabort_called); +}, "event.intercept() does not signal event.signal"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/signal-abort-preventDefault.html b/testing/web-platform/tests/navigation-api/navigate-event/signal-abort-preventDefault.html new file mode 100644 index 0000000000..60fed90ce6 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/signal-abort-preventDefault.html @@ -0,0 +1,19 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async t => { + let abort_signal; + let events = []; + navigation.onnavigateerror = () => events.push("onnavigateerror"); + navigation.onnavigate = t.step_func(e => { + abort_signal = e.signal; + abort_signal.onabort = () => events.push("onabort"); + e.preventDefault(); + }); + + await promise_rejects_dom(t, 'AbortError', navigation.navigate("?1").committed); + assert_true(abort_signal.aborted); + assert_array_equals(events, ["onabort", "onnavigateerror"]); +}, "event.preventDefault() signals event.signal"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/signal-abort-window-stop-after-intercept.html b/testing/web-platform/tests/navigation-api/navigate-event/signal-abort-window-stop-after-intercept.html new file mode 100644 index 0000000000..51ba7753a8 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/signal-abort-window-stop-after-intercept.html @@ -0,0 +1,40 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + window.onload = t.step_func(() => { + let start_url = location.href; + let abort_signal; + let onabort_called = false; + let navigateErrorException; + navigation.onnavigateerror = t.step_func(e => { + assert_equals(e.constructor, ErrorEvent); + navigateErrorException = e.error; + assert_equals(e.filename, start_url); + assert_greater_than(e.lineno, 0); + assert_greater_than(e.colno, 0); + }); + navigation.onnavigatesuccess = t.unreached_func("onnavigatesuccess"); + navigation.onnavigate = t.step_func(e => { + abort_signal = e.signal; + abort_signal.onabort = () => onabort_called = true; + e.intercept({ handler: () => new Promise(resolve => t.step_timeout(resolve, 0)) }); + }); + let result = navigation.navigate("?1"); + window.stop(); + assert_true(abort_signal.aborted); + assert_true(onabort_called); + + result.committed.then(() => { + return promise_rejects_dom(t, 'AbortError', result.finished); + }).then(() => { + return result.finished.catch(e => assert_equals(e, navigateErrorException)); + }).then(() => { + // Complete the test asynchronously to ensure that onnavigatesuccess + // didn't fire on a microtask. + t.step_timeout(t.step_func_done(() => {}), 5); + }); + }); +}, "window.stop() cancels the navigate event's intercept() and signals event.signal"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/signal-abort-window-stop-in-onnavigate.html b/testing/web-platform/tests/navigation-api/navigate-event/signal-abort-window-stop-in-onnavigate.html new file mode 100644 index 0000000000..1b406c42d3 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/signal-abort-window-stop-in-onnavigate.html @@ -0,0 +1,24 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + window.onload = t.step_func_done(() => { + let abort_signal; + let onabort_called = false; + let canceled_in_second_handler = false; + navigation.addEventListener("navigate", t.step_func(e => { + abort_signal = e.signal; + abort_signal.onabort = () => onabort_called = true; + window.stop(); + })); + navigation.addEventListener("navigate", t.step_func(e => { + canceled_in_second_handler = e.defaultPrevented; + })); + navigation.navigate("?1"); + assert_true(abort_signal.aborted); + assert_true(onabort_called); + assert_true(canceled_in_second_handler); + }); +}, "window.stop() signals event.signal inside a navigate event handler"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigate-event/signal-abort-window-stop.html b/testing/web-platform/tests/navigation-api/navigate-event/signal-abort-window-stop.html new file mode 100644 index 0000000000..43e005e8b4 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigate-event/signal-abort-window-stop.html @@ -0,0 +1,23 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + window.onload = t.step_func(() => { + let abort_signal; + let onabort_called = false; + navigation.onnavigatesuccess = t.unreached_func("onnavigatesuccess"); + navigation.onnavigate = t.step_func(e => { + abort_signal = e.signal; + abort_signal.onabort = () => onabort_called = true; + }); + navigation.navigate("?1"); + window.stop(); + assert_true(abort_signal.aborted); + assert_true(onabort_called); + // Complete the test asynchronously to ensure that onnavigatesuccess + // didn't fire on a microtask. + t.step_timeout(t.step_func_done(() => {}), 0); + }); +}, "window.stop() signals event.signal"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/after-detach.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/after-detach.html new file mode 100644 index 0000000000..c4ecfec44d --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/after-detach.html @@ -0,0 +1,30 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + let i_navigation = i.contentWindow.navigation; + + await i_navigation.navigate("#1").finished; + await i_navigation.navigate("#2").finished; + await i_navigation.back().finished; + + assert_not_equals(i_navigation, null); + assert_not_equals(i_navigation.currentEntry, null); + assert_equals(i_navigation.entries().length, 3); + assert_true(i_navigation.canGoBack, "canGoBack"); + assert_true(i_navigation.canGoForward, "canGoForward"); + + i.remove(); + + assert_equals(i_navigation.currentEntry, null); + assert_equals(i_navigation.entries().length, 0); + assert_false(i_navigation.canGoBack); + assert_false(i_navigation.canGoForward); +}, "navigation.currentEntry/entries()/canGoBack/canGoForward after iframe removal"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/current-basic.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/current-basic.html new file mode 100644 index 0000000000..78bbbb0560 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/current-basic.html @@ -0,0 +1,107 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/is_uuid.js"></script> +<script> +test(() => { + let first_entry = navigation.currentEntry; + assert_not_equals(first_entry, null); + assert_not_equals(first_entry.key, null); + assert_true(isUUID(first_entry.key)); + assert_not_equals(first_entry.id, null); + assert_true(isUUID(first_entry.id)); + assert_equals(first_entry.url, location.href); + assert_true(first_entry.sameDocument); + assert_equals(navigation.entries().length, 1); + assert_equals(first_entry, navigation.entries()[0]); + + history.replaceState(2, "", "#2"); + let second_entry = navigation.currentEntry; + assert_not_equals(second_entry, first_entry); + assert_equals(second_entry.key, first_entry.key); + assert_true(isUUID(second_entry.key)); + assert_not_equals(second_entry.id, first_entry.id); + assert_true(isUUID(second_entry.id)); + assert_equals(second_entry.url, location.href); + assert_true(second_entry.sameDocument); + assert_equals(navigation.entries().length, 1); + assert_equals(second_entry, navigation.entries()[0]); + + history.pushState(3, "", "#3"); + let third_entry = navigation.currentEntry; + assert_not_equals(third_entry, second_entry); + assert_not_equals(third_entry.key, second_entry.key); + assert_true(isUUID(third_entry.key)); + assert_not_equals(third_entry.id, second_entry.id); + assert_true(isUUID(third_entry.id)); + assert_equals(third_entry.url, location.href); + assert_true(third_entry.sameDocument); + assert_equals(navigation.entries().length, 2); + assert_equals(third_entry, navigation.entries()[1]); + + history.pushState(4, ""); + let fourth_entry = navigation.currentEntry; + assert_not_equals(fourth_entry, third_entry); + assert_not_equals(fourth_entry.key, third_entry.key); + assert_true(isUUID(fourth_entry.key)); + assert_not_equals(fourth_entry.id, third_entry.id); + assert_true(isUUID(fourth_entry.id)); + assert_equals(fourth_entry.url, third_entry.url); + assert_true(fourth_entry.sameDocument); + assert_equals(navigation.entries().length, 3); + assert_equals(fourth_entry, navigation.entries()[2]); + + history.replaceState(5, ""); + let fifth_entry = navigation.currentEntry; + assert_not_equals(fifth_entry, fourth_entry); + assert_equals(fifth_entry.key, fourth_entry.key); + assert_true(isUUID(fifth_entry.key)); + assert_not_equals(fifth_entry.id, fourth_entry.id); + assert_true(isUUID(fifth_entry.id)); + assert_equals(fifth_entry.url, fourth_entry.url); + assert_true(fifth_entry.sameDocument); + assert_equals(navigation.entries().length, 3); + assert_equals(fifth_entry, navigation.entries()[2]); + + history.pushState(5, ""); + let fifth_entry_after_push = navigation.currentEntry; + assert_not_equals(fifth_entry_after_push, fifth_entry); + assert_not_equals(fifth_entry_after_push.key, fifth_entry.key); + assert_true(isUUID(fifth_entry_after_push.key)); + assert_not_equals(fifth_entry_after_push.id, fifth_entry.id); + assert_true(isUUID(fifth_entry_after_push.id)); + assert_equals(fifth_entry_after_push.url, fifth_entry.url); + assert_true(fifth_entry_after_push.sameDocument); + assert_equals(navigation.entries().length, 4); + assert_equals(fifth_entry_after_push, navigation.entries()[3]); + + history.replaceState(5, ""); + let fifth_entry_after_replace = navigation.currentEntry; + assert_not_equals(fifth_entry_after_replace, fifth_entry_after_push); + assert_equals(fifth_entry_after_replace.key, fifth_entry_after_push.key); + assert_true(isUUID(fifth_entry_after_replace.key)); + assert_not_equals(fifth_entry_after_replace.id, fifth_entry_after_push.id); + assert_true(isUUID(fifth_entry_after_replace.id)); + assert_equals(fifth_entry_after_replace.url, fifth_entry_after_push.url); + assert_true(fifth_entry_after_replace.sameDocument); + assert_equals(navigation.entries().length, 4); + assert_equals(fifth_entry_after_replace, navigation.entries()[3]); + + location.hash = "6"; + let sixth_entry = navigation.currentEntry; + assert_not_equals(sixth_entry, fifth_entry_after_replace); + assert_equals(sixth_entry.key, fifth_entry_after_replace.key); + assert_true(isUUID(sixth_entry.key)); + assert_not_equals(sixth_entry.id, fifth_entry_after_replace.id); + assert_true(isUUID(sixth_entry.id)); + assert_not_equals(sixth_entry.url, fifth_entry_after_replace.url); + assert_true(sixth_entry.sameDocument); + assert_equals(navigation.entries().length, 4); + assert_equals(sixth_entry, navigation.entries()[3]); + + navigation.entries().forEach(entry => { + assert_true(isUUID(entry.id)); + assert_true(isUUID(entry.key)); + }); +}, "Basic tests for navigation.currentEntry"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-across-origins.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-across-origins.html new file mode 100644 index 0000000000..447273bff2 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-across-origins.html @@ -0,0 +1,33 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="resources/is_uuid.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + let start_key = i.contentWindow.navigation.currentEntry.key; + let start_id = i.contentWindow.navigation.currentEntry.id; + + let cross_origin_url = new URL("resources/post-entries-length-to-top.html", location.href); + cross_origin_url.hostname = get_host_info().REMOTE_HOST; + i.contentWindow.location.assign(cross_origin_url.href); + + window.onmessage = t.step_func(e => { + assert_equals(e.data, 1); + + i.src = "/common/blank.html?2"; + i.onload = t.step_func_done(() => { + let entries = i.contentWindow.navigation.entries(); + assert_equals(entries.length, 1); + assert_equals(new URL(entries[0].url).search, "?2"); + assert_not_equals(entries[0].key, start_key); + assert_not_equals(entries[0].id, start_id); + assert_true(isUUID(entries[0].key)); + assert_true(isUUID(entries[0].id)); + }); + }); + }); +}, "navigation.entries() should only contain entries that are both same-origin and contiguous"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-bfcache-in-iframe.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-bfcache-in-iframe.html new file mode 100644 index 0000000000..b54a749950 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-bfcache-in-iframe.html @@ -0,0 +1,36 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js"></script> +<script src="resources/is_uuid.js"></script> + +<script> +// This test ensures that navigation.entries() in an iframe is properly updated +// when a page is restored from bfcache. +// First, create an iframe and do a fragment navigation in it, so that its +// navigation.entries().length == 2. Then go back, so that entries()[0] is +// current. Finally, navigate the main window (which should clobber the +// the iframe's entries()[1]), and come back via bfcache. If the iframe's +// entries() were updated, then its entries().length should have been reduced +// to 1. +runBfcacheTest({ + targetOrigin: originSameOrigin, + funcBeforeNavigation: async () => { + window.events = []; + let i = document.createElement("iframe"); + i.src = "/common/blank.html"; + document.body.appendChild(i); + await new Promise(resolve => i.onload = () => setTimeout(resolve, 0)); + await i.contentWindow.navigation.navigate("#foo"); + await i.contentWindow.navigation.back(); + window.frames[0].navigation.entries()[1].ondispose = () => events.push("dispose"); + window.frames[0].onpageshow = () => events.push("pageshow"); + }, + async funcAfterAssertion(pageA, pageB) { + assert_equals(await pageA.execute_script(() => window.frames[0].navigation.entries().length), 1); + assert_array_equals(await pageA.execute_script(() => window.events), ["pageshow", "dispose"]); + } +}, "entries() in an iframe must be updated after navigating back to a bfcached page"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-bfcache.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-bfcache.html new file mode 100644 index 0000000000..ef93d1e27e --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-bfcache.html @@ -0,0 +1,60 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js"></script> +<script src="resources/is_uuid.js"></script> + +<script> +// This test ensures that navigation.entries() is properly updated when a page +// is restored from bfcache. Before navigating away and back, entries() contains +// a single entry representing this document. When restored from bfcache, +// entries() should now have two entries: [0] should still be this document, but +// [1] should represent the document that we navigated to and back from +// (assuming that document is same-origin to this one). +runBfcacheTest({ + targetOrigin: originSameOrigin, + funcBeforeNavigation: () => { + window.originalEntry0 = navigation.entries()[0]; + }, + async funcAfterAssertion(pageA, pageB) { + const entryData = await pageA.execute_script(() => { + return navigation.entries().map(e => ({ + url: e.url, + key: e.key, + id: e.id, + index: e.index, + sameDocument: e.sameDocument + })); + }); + + assert_equals(entryData.length, 2); + + // Ensure that [1] has the proper url, and otherwise is initialized as + // a cross-document NavigationHistoryEntry ought to be. + assert_equals(entryData[0].url, pageA.url); + assert_equals(entryData[1].url, pageB.url); + + assert_true(isUUID(entryData[0].key)); + assert_true(isUUID(entryData[1].key)); + assert_not_equals(entryData[0].key, entryData[1].key); + + assert_true(isUUID(entryData[0].id)); + assert_true(isUUID(entryData[1].id)); + assert_not_equals(entryData[0].id, entryData[1].id); + + assert_equals(entryData[0].index, 0); + assert_equals(entryData[1].index, 1); + + assert_true(entryData[0].sameDocument); + assert_false(entryData[1].sameDocument); + + const currentIsZero = await pageA.execute_script(() => navigation.currentEntry === navigation.entries()[0]); + assert_true(currentIsZero); + + const zeroIsSameAsOriginal = await pageA.execute_script(() => navigation.currentEntry === window.originalEntry0); + assert_true(zeroIsSameAsOriginal); + } +}, "entries() must contain the forward-history page after navigating back to a bfcached page"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-blank-navigation-from-cross-origin.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-blank-navigation-from-cross-origin.html new file mode 100644 index 0000000000..d527637ed3 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-blank-navigation-from-cross-origin.html @@ -0,0 +1,22 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<body> +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + let i = document.createElement("iframe"); + i.src = get_host_info().HTTP_ORIGIN_WITH_DIFFERENT_PORT + "/common/blank.html"; + document.body.appendChild(i); + await new Promise(resolve => i.onload = () => t.step_timeout(resolve, 0)); + + i.contentWindow.location = "about:blank"; + await new Promise(resolve => i.onload = resolve); + let entries = i.contentWindow.navigation.entries(); + assert_equals(entries.length, 1); + assert_equals(entries[0].url, "about:blank"); +}, "entries() should not be leaked from cross-origin when navigating to about:blank"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-blank-navigation.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-blank-navigation.html new file mode 100644 index 0000000000..f54ae06e14 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-blank-navigation.html @@ -0,0 +1,25 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/is_uuid.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + i.onload = t.step_func_done(() => { + let entries = i.contentWindow.navigation.entries(); + assert_equals(entries.length, 2); + assert_not_equals(entries[1].key, entries[0].key); + assert_not_equals(entries[1].url, entries[0].url); + assert_equals(entries[1].url, "about:blank"); + assert_not_equals(entries[1].id, entries[0].id); + + assert_true(isUUID(entries[0].key)); + assert_true(isUUID(entries[0].id)); + assert_true(isUUID(entries[1].key)); + assert_true(isUUID(entries[1].id)); + }); + i.src = "about:blank"; + }); +}, "entries() after navigation to about:blank"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-blob-navigation.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-blob-navigation.html new file mode 100644 index 0000000000..67611f4d44 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-blob-navigation.html @@ -0,0 +1,25 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/is_uuid.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + i.onload = t.step_func_done(() => { + let entries = i.contentWindow.navigation.entries(); + assert_equals(entries.length, 2); + assert_not_equals(entries[1].key, entries[0].key); + assert_not_equals(entries[1].url, entries[0].url); + assert_equals(new URL(entries[1].url).protocol, "blob:"); + assert_not_equals(entries[1].id, entries[0].id); + + assert_true(isUUID(entries[0].key)); + assert_true(isUUID(entries[0].id)); + assert_true(isUUID(entries[1].key)); + assert_true(isUUID(entries[1].id)); + }); + i.src = URL.createObjectURL(new Blob(["<body></body>"])); + }); +}, "entries() after navigation to a blob: URL"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-cross-document-forward-pruning.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-cross-document-forward-pruning.html new file mode 100644 index 0000000000..ddd1ad571d --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-cross-document-forward-pruning.html @@ -0,0 +1,33 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/is_uuid.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async (t) => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(r => window.onload = () => t.step_timeout(r, 0)); + + i.contentWindow.location.search = "?2"; + await new Promise(r => i.onload = () => t.step_timeout(r, 0)); + + i.contentWindow.location.search = "?3"; + await new Promise(r => i.onload = () => t.step_timeout(r, 0)); + + i.contentWindow.history.back(); + await new Promise(r => i.onload = () => t.step_timeout(r, 0)); + + i.contentWindow.history.back(); + await new Promise(r => i.onload = () => t.step_timeout(r, 0)); + + i.contentWindow.location.search = "?fork"; + await new Promise(r => i.onload = () => t.step_timeout(r, 0)); + + const entries = i.contentWindow.navigation.entries(); + const searches = entries.map(e => (new URL(e.url)).search); + assert_array_equals(searches, ["", "?fork"]); + + assert_equals(i.contentWindow.navigation.entries().at(-1), i.contentWindow.navigation.currentEntry); +}, "navigation.entries() behavior after forward-pruning due to cross-document navs"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-javascript-url-navigation.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-javascript-url-navigation.html new file mode 100644 index 0000000000..c5ef7f33e4 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-javascript-url-navigation.html @@ -0,0 +1,38 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + let start_key = i.contentWindow.navigation.currentEntry.key; + let start_url = i.contentWindow.navigation.currentEntry.url; + let start_id = i.contentWindow.navigation.currentEntry.id; + let did_js_url_nav = false; + i.onload = t.step_func(() => { + if (!did_js_url_nav) { + assert_equals(i.contentWindow.navigation.entries().length, 2); + before_js_nav_key = i.contentWindow.navigation.currentEntry.key; + before_js_nav_url = i.contentWindow.navigation.currentEntry.url; + before_js_nav_id = i.contentWindow.navigation.currentEntry.id; + i.src = "javascript:'new content'"; + did_js_url_nav = true; + } else { + assert_equals(i.contentWindow.navigation.entries().length, 2); + let first_entry = i.contentWindow.navigation.entries()[0]; + let js_url_entry = i.contentWindow.navigation.entries()[1]; + assert_equals(first_entry.key, start_key); + assert_equals(first_entry.url, start_url); + assert_equals(first_entry.id, start_id); + + assert_equals(js_url_entry, i.contentWindow.navigation.currentEntry); + assert_equals(js_url_entry.key, before_js_nav_key); + assert_equals(js_url_entry.url, before_js_nav_url); + assert_not_equals(js_url_entry.id, before_js_nav_id); + t.done(); + } + }); + i.contentWindow.navigation.navigate("?1"); + }); +}, "entries() after navigation to a javascript: URL"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-navigations-in-multiple-windows.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-navigations-in-multiple-windows.html new file mode 100644 index 0000000000..d1d4d86eb3 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-navigations-in-multiple-windows.html @@ -0,0 +1,34 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/is_uuid.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + // The navigations in each window should have only added an navigation to + // their own window. + function assertExpectedEntries(entries, expected_url) { + assert_equals(entries.length, 2); + assert_not_equals(entries[1].key, entries[0].key); + assert_not_equals(entries[1].url, entries[0].url); + assert_not_equals(entries[1].id, entries[0].id); + assert_true(isUUID(entries[0].key)); + assert_true(isUUID(entries[0].id)); + assert_true(isUUID(entries[1].key)); + assert_true(isUUID(entries[1].id)); + + assert_equals(entries[1].url, expected_url); + } + + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + window.onload = () => t.step_timeout(t.step_func(() => { + location.hash = "#1"; + i.onload = t.step_func_done(() => { + assertExpectedEntries(navigation.entries(), location.href); + assertExpectedEntries(i.contentWindow.navigation.entries(), i.contentWindow.location.href); + }); + i.contentWindow.location = "/common/blank.html?2"; + }), 0); +}, "navigation.entries() behavior when multiple windows navigate."); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-srcdoc-navigation.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-srcdoc-navigation.html new file mode 100644 index 0000000000..b80e8aa0e2 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-after-srcdoc-navigation.html @@ -0,0 +1,25 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/is_uuid.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + i.onload = t.step_func_done(() => { + let entries = i.contentWindow.navigation.entries(); + assert_equals(entries.length, 2); + assert_not_equals(entries[1].key, entries[0].key); + assert_not_equals(entries[1].url, entries[0].url); + assert_equals(entries[1].url, "about:srcdoc"); + assert_not_equals(entries[1].id, entries[0].id); + + assert_true(isUUID(entries[0].key)); + assert_true(isUUID(entries[0].id)); + assert_true(isUUID(entries[1].key)); + assert_true(isUUID(entries[1].id)); + }); + i.srcdoc = "new"; + }); +}, "entries() after setting a srcdoc attribute"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-array-equality.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-array-equality.html new file mode 100644 index 0000000000..98efb6b20c --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-array-equality.html @@ -0,0 +1,8 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +test(() => { + assert_not_equals(navigation.entries(), navigation.entries()); +}, "navigation.entries() should not return an identical object on repeated invocations"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-in-new-javascript-url-iframe.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-in-new-javascript-url-iframe.html new file mode 100644 index 0000000000..6f217f5e3c --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-in-new-javascript-url-iframe.html @@ -0,0 +1,15 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="javascript:'foo'"></iframe> + +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + assert_not_equals(i.contentWindow.navigation.currentEntry, null); + assert_array_equals(i.contentWindow.navigation.entries(), [i.contentWindow.navigation.currentEntry]); + + assert_equals(i.contentWindow.navigation.currentEntry.url, "about:blank"); +}, "entries() and currentEntry should be set in a new javascript: URL iframe"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-in-new-srcdoc-iframe.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-in-new-srcdoc-iframe.html new file mode 100644 index 0000000000..a7e0f88d37 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-in-new-srcdoc-iframe.html @@ -0,0 +1,15 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" srcdoc="new"></iframe> + +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + assert_not_equals(i.contentWindow.navigation.currentEntry, null); + assert_array_equals(i.contentWindow.navigation.entries(), [i.contentWindow.navigation.currentEntry]); + + assert_equals(i.contentWindow.navigation.currentEntry.url, "about:srcdoc"); +}, "entries() and currentEntry should be set in a new srcdoc iframe"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-when-inactive.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-when-inactive.html new file mode 100644 index 0000000000..c70b6d8bf8 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/entries-when-inactive.html @@ -0,0 +1,16 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + const first_entry = navigation.entries()[0]; + history.pushState(1, "", "#1"); + assert_equals(navigation.entries()[0], first_entry); + history.back(); + window.onpopstate = t.step_func_done(() => { + const second_entry = navigation.entries()[1]; + history.replaceState(0, "", "#0"); + assert_equals(navigation.entries()[1], second_entry); + }); +}, "A non-active entry in navigation.entries() should not be modified when a different entry is modified"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/entry-after-detach.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/entry-after-detach.html new file mode 100644 index 0000000000..69c52d1409 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/entry-after-detach.html @@ -0,0 +1,20 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func_done(() => { + let i_entry = i.contentWindow.navigation.currentEntry; + assert_true(i_entry.sameDocument); + assert_not_equals(i_entry.url, null); + assert_not_equals(i_entry.key, ""); + assert_not_equals(i_entry.id, ""); + i.remove(); + assert_false(i_entry.sameDocument); + assert_equals(i_entry.url, null); + assert_equals(i_entry.key, ""); + assert_equals(i_entry.id, ""); + }); +}, "NavigationHistoryEntry properties after detach"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/index-not-in-entries.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/index-not-in-entries.html new file mode 100644 index 0000000000..a16d130ba1 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/index-not-in-entries.html @@ -0,0 +1,24 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + window.onload = () => t.step_timeout(t.step_func_done(() => { + // Remove the entry by replacing it. + let replaced_entry = navigation.currentEntry; + assert_equals(replaced_entry.index, 0); + navigation.navigate("#0", { history: "replace" }); + assert_equals(replaced_entry.index, -1); + + // Remove the entry by detaching its window. + let iframe_entry = i.contentWindow.navigation.currentEntry; + assert_equals(iframe_entry.index, 0); + i.remove(); + assert_equals(iframe_entry.index, -1); + t.done(); + }), 0); +}, "entry.index should return -1 when not in navigation.entries()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/key-id-back-cross-document.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/key-id-back-cross-document.html new file mode 100644 index 0000000000..2dd58c03e9 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/key-id-back-cross-document.html @@ -0,0 +1,15 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="resources/key-navigate-back-cross-document-helper.html"></iframe> +<script> +async_test(t => { + window.finish = t.step_func_done((end_key, end_id) => { + assert_equals(window.start_key, end_key); + assert_equals(window.start_id, end_id); + // The new history entry in the iframe should not add any entries to + // this window's navigation. + assert_equals(navigation.entries().length, 1); + }); +}, "NavigationHistoryEntry's key and id on cross-document back navigation"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/key-id-back-same-document.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/key-id-back-same-document.html new file mode 100644 index 0000000000..858b5fd2c8 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/key-id-back-same-document.html @@ -0,0 +1,24 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + let key = navigation.currentEntry.key; + let id = navigation.currentEntry.id; + assert_equals(navigation.entries().length, 1); + + history.pushState("hash", "", "#hash"); + assert_not_equals(key, navigation.currentEntry.key); + assert_not_equals(id, navigation.currentEntry.id); + assert_equals(navigation.entries().length, 2); + assert_equals(navigation.currentEntry.index, 1); + + window.onpopstate = t.step_func_done(() => { + assert_equals(key, navigation.currentEntry.key); + assert_equals(id, navigation.currentEntry.id); + assert_equals(navigation.entries().length, 2); + assert_equals(navigation.currentEntry.index, 0); + }); + history.back(); +}, "NavigationHistoryEntry's key and id on same-document back navigation"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/key-id-location-reload-intercept.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/key-id-location-reload-intercept.html new file mode 100644 index 0000000000..62ce097439 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/key-id-location-reload-intercept.html @@ -0,0 +1,23 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + await new Promise(resolve => { window.onload = resolve; }); + + + const original = i.contentWindow.navigation.currentEntry; + const { key, id } = original; + + i.contentWindow.navigation.addEventListener("navigate", e => e.intercept()); + + i.onload = t.unreached_func("the iframe must not reload"); + + await i.contentWindow.location.reload(); + + assert_equals(i.contentWindow.navigation.currentEntry, original); + assert_equals(i.contentWindow.navigation.currentEntry.key, key); + assert_equals(i.contentWindow.navigation.currentEntry.id, id); +}, "NavigationHistoryEntry's key and id after location.reload() intercepted by intercept()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/key-id-location-reload.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/key-id-location-reload.html new file mode 100644 index 0000000000..f950e2f3a2 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/key-id-location-reload.html @@ -0,0 +1,18 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + let key = i.contentWindow.navigation.currentEntry.key; + let id = i.contentWindow.navigation.currentEntry.id; + i.contentWindow.location.reload(); + i.onload = t.step_func_done(() => { + assert_equals(key, i.contentWindow.navigation.currentEntry.key); + assert_equals(id, i.contentWindow.navigation.currentEntry.id); + assert_equals(navigation.entries().length, 1); + }); + }); +}, "NavigationHistoryEntry's key and id after location.reload()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/key-id-location-replace-cross-origin.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/key-id-location-replace-cross-origin.html new file mode 100644 index 0000000000..65aff4a3ab --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/key-id-location-replace-cross-origin.html @@ -0,0 +1,17 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + let key_before_replace = i.contentWindow.navigation.currentEntry.key; + window.onmessage = t.step_func_done(e => assert_not_equals(key_before_replace, e.data)); + + let cross_origin_url = new URL("resources/post-key-to-top.html", location.href); + cross_origin_url.hostname = get_host_info().REMOTE_HOST; + i.contentWindow.location.replace(cross_origin_url.href); + }); +}, "NavigationHistoryEntry's key and id after location.replace()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/key-id-location-replace.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/key-id-location-replace.html new file mode 100644 index 0000000000..a58772a7f4 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/key-id-location-replace.html @@ -0,0 +1,18 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + let key = i.contentWindow.navigation.currentEntry.key; + let id = i.contentWindow.navigation.currentEntry.id; + i.contentWindow.location.replace("/common/blank.html?query"); + i.onload = t.step_func_done(() => { + assert_equals(key, i.contentWindow.navigation.currentEntry.key); + assert_not_equals(id, i.contentWindow.navigation.currentEntry.id); + assert_equals(navigation.entries().length, 1); + }); + }); +}, "NavigationHistoryEntry's key and id after location.replace()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/no-referrer-dynamic-url-censored.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/no-referrer-dynamic-url-censored.html new file mode 100644 index 0000000000..7a5544c419 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/no-referrer-dynamic-url-censored.html @@ -0,0 +1,33 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async (t) => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(r => window.onload = () => t.step_timeout(r, 0)); + + // The entry for the first document has a visible url. + i.contentWindow.navigation.navigate("/common/blank.html?2"); + await new Promise(r => i.onload = () => t.step_timeout(r, 0)); + assert_not_equals(i.contentWindow.navigation.entries()[0].url, null); + + // Apply no-referrer, the url should now be censored when no longer on that document. + i.contentWindow.navigation.back(); + await new Promise(r => i.onload = () => t.step_timeout(r, 0)); + i.contentDocument.head.innerHTML = `<meta name="referrer" content="no-referrer">`; + assert_not_equals(i.contentWindow.navigation.entries()[0].url, null); + i.contentWindow.navigation.forward(); + await new Promise(r => i.onload = () => t.step_timeout(r, 0)); + assert_equals(i.contentWindow.navigation.entries()[0].url, null); + + // Overwrite the referrer policy, the url should be visible again. + i.contentWindow.navigation.back(); + await new Promise(r => i.onload = () => t.step_timeout(r, 0)); + i.contentDocument.head.innerHTML = `<meta name="referrer" content="same-origin">`; + i.contentWindow.navigation.forward(); + await new Promise(r => i.onload = () => t.step_timeout(r, 0)); + assert_not_equals(i.contentWindow.navigation.entries()[0].url, null); +}, "The url of a document is censored by a no-referrer policy dynamically"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/no-referrer-from-meta-url-censored.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/no-referrer-from-meta-url-censored.html new file mode 100644 index 0000000000..fc563f509e --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/no-referrer-from-meta-url-censored.html @@ -0,0 +1,32 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="resources/no-referrer-meta.html"></iframe> +<script> +promise_test(async (t) => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(r => window.onload = () => t.step_timeout(r, 0)); + + await i.contentWindow.navigation.navigate("#hash"); + assert_equals(i.contentWindow.navigation.entries().length, 2); + + // The entries for no-referrer.html should have the url censored. + i.contentWindow.navigation.navigate("/common/blank.html"); + await new Promise(r => i.onload = () => t.step_timeout(r, 0)); + assert_equals(i.contentWindow.navigation.entries().length, 3); + assert_equals(i.contentWindow.navigation.currentEntry.index, 2); + assert_equals(i.contentWindow.navigation.entries()[0].url, null); + assert_equals(i.contentWindow.navigation.entries()[1].url, null); + + // Navigating back to no-referrer.html should uncensor the urls. + i.contentWindow.navigation.back(); + await new Promise(r => i.onload = () => t.step_timeout(r, 0)); + assert_equals(i.contentWindow.navigation.entries().length, 3); + assert_equals(i.contentWindow.navigation.currentEntry.index, 1); + assert_equals(new URL(i.contentWindow.navigation.entries()[0].url).pathname, + "/navigation-api/navigation-history-entry/resources/no-referrer-meta.html"); + assert_equals(new URL(i.contentWindow.navigation.entries()[1].url).pathname, + "/navigation-api/navigation-history-entry/resources/no-referrer-meta.html"); +}, "The url of a document with no-referrer referrer meta tag is censored in NavigationHistoryEntry"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/no-referrer-url-censored.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/no-referrer-url-censored.html new file mode 100644 index 0000000000..e7eb1afc7d --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/no-referrer-url-censored.html @@ -0,0 +1,32 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="resources/no-referrer.html"></iframe> +<script> +promise_test(async (t) => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(r => window.onload = () => t.step_timeout(r, 0)); + + await i.contentWindow.navigation.navigate("#hash"); + assert_equals(i.contentWindow.navigation.entries().length, 2); + + // The entries for no-referrer.html should have the url censored. + i.contentWindow.navigation.navigate("/common/blank.html"); + await new Promise(r => i.onload = () => t.step_timeout(r, 0)); + assert_equals(i.contentWindow.navigation.entries().length, 3); + assert_equals(i.contentWindow.navigation.currentEntry.index, 2); + assert_equals(i.contentWindow.navigation.entries()[0].url, null); + assert_equals(i.contentWindow.navigation.entries()[1].url, null); + + // Navigating back to no-referrer.html should uncensor the urls. + i.contentWindow.navigation.back(); + await new Promise(r => i.onload = () => t.step_timeout(r, 0)); + assert_equals(i.contentWindow.navigation.entries().length, 3); + assert_equals(i.contentWindow.navigation.currentEntry.index, 1); + assert_equals(new URL(i.contentWindow.navigation.entries()[0].url).pathname, + "/navigation-api/navigation-history-entry/resources/no-referrer.html"); + assert_equals(new URL(i.contentWindow.navigation.entries()[1].url).pathname, + "/navigation-api/navigation-history-entry/resources/no-referrer.html"); +}, "The url of a document with no-referrer referrer policy is censored in NavigationHistoryEntry"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/opaque-origin-data-url.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/opaque-origin-data-url.html new file mode 100644 index 0000000000..65123fa60b --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/opaque-origin-data-url.html @@ -0,0 +1,17 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + window.onmessage = t.step_func_done(e => { + assert_equals(e.data.length, 0); + assert_true(e.data.currentIsNull); + }); + i.src = "data:text/html,<script>top.postMessage({ length: navigation.entries().length, " + + "currentIsNull: navigation.currentEntry === null}, '*')</sc" + + "ript>"; + }); +}, "entries() and currentEntry after navigation to a data: URL (which has an opaque origin)"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/opaque-origin.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/opaque-origin.html new file mode 100644 index 0000000000..898ca27e4f --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/opaque-origin.html @@ -0,0 +1,9 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<iframe id="i" sandbox="allow-scripts" src="resources/opaque-origin-page.html"></iframe> + +<script> +fetch_tests_from_window(i.contentWindow); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/resources/is_uuid.js b/testing/web-platform/tests/navigation-api/navigation-history-entry/resources/is_uuid.js new file mode 100644 index 0000000000..3b855c01b0 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/resources/is_uuid.js @@ -0,0 +1,3 @@ +function isUUID(key) { + return /[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}/.test(key); +} diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/resources/key-navigate-back-cross-document-helper.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/resources/key-navigate-back-cross-document-helper.html new file mode 100644 index 0000000000..79f3c3da0a --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/resources/key-navigate-back-cross-document-helper.html @@ -0,0 +1,27 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script> + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. +window.onload = () => step_timeout(() => { + if (location.search == "?go-back") { + assert_equals(navigation.entries().length, 2); + assert_equals(navigation.currentEntry.index, 1); + // Step 2: Navigate back. + history.back(); + return; + } + if (top.start_key) { + assert_equals(navigation.entries().length, 2); + assert_equals(navigation.currentEntry.index, 0); + // Step 3: Notify parent, which will ensure the same key is used after back navigation. + top.finish(navigation.currentEntry.key, navigation.currentEntry.id); + return; + } + // Step 1: Record initial key and navigate. + assert_equals(navigation.entries().length, 1); + top.start_key = navigation.currentEntry.key; + top.start_id = navigation.currentEntry.id; + location.search = "go-back"; +}, 0); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/resources/no-referrer-meta.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/resources/no-referrer-meta.html new file mode 100644 index 0000000000..bd5ec391cc --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/resources/no-referrer-meta.html @@ -0,0 +1,2 @@ +<meta name="referrer" content="no-referrer"> +<body></body> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/resources/no-referrer.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/resources/no-referrer.html new file mode 100644 index 0000000000..c8b7661f42 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/resources/no-referrer.html @@ -0,0 +1 @@ +<body></body> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/resources/no-referrer.html.headers b/testing/web-platform/tests/navigation-api/navigation-history-entry/resources/no-referrer.html.headers new file mode 100644 index 0000000000..7ffbf17d6b --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/resources/no-referrer.html.headers @@ -0,0 +1 @@ +Referrer-Policy: no-referrer diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/resources/opaque-origin-page.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/resources/opaque-origin-page.html new file mode 100644 index 0000000000..98e2c1b317 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/resources/opaque-origin-page.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<!-- Put this page in a sandbox to give it an opaque origin --> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + location.hash = "#1"; + await new Promise(resolve => window.onhashchange = resolve); + location.hash = "#2"; + await new Promise(resolve => window.onhashchange = resolve); + history.back(); + await new Promise(resolve => window.onhashchange = resolve); + + assert_equals(location.hash, "#1"); + + assert_equals(navigation.currentEntry, null); + assert_equals(navigation.entries().length, 0); + assert_false(navigation.canGoBack); + assert_false(navigation.canGoForward); +}, "navigation.currentEntry/entries()/canGoBack/canGoForward in an opaque origin iframe"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/resources/post-entries-length-to-top.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/resources/post-entries-length-to-top.html new file mode 100644 index 0000000000..c8fe005d8e --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/resources/post-entries-length-to-top.html @@ -0,0 +1,7 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script> +// Wait for after the load event so that the navigation doesn't get converted +// into a replace navigation. +window.onload = () => step_timeout(() => top.postMessage(navigation.entries().length, "*"), 0); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/resources/post-key-to-top.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/resources/post-key-to-top.html new file mode 100644 index 0000000000..285f345dc1 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/resources/post-key-to-top.html @@ -0,0 +1,3 @@ +<script> +top.postMessage(navigation.currentEntry.key, "*"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/sameDocument-after-fragment-navigate.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/sameDocument-after-fragment-navigate.html new file mode 100644 index 0000000000..a197f825d8 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/sameDocument-after-fragment-navigate.html @@ -0,0 +1,30 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + window.onload = () => t.step_timeout(t.step_func_done(() => { + let entry1 = navigation.currentEntry; + assert_true(entry1.sameDocument); + + location = "#hash"; + let entry2 = navigation.currentEntry; + assert_not_equals(entry1, entry2); + assert_true(entry1.sameDocument); + + history.pushState("push", "", "#push"); + let entry3 = navigation.currentEntry; + assert_not_equals(entry1, entry3); + assert_not_equals(entry2, entry3); + assert_true(entry1.sameDocument); + assert_true(entry2.sameDocument); + + assert_equals(navigation.entries().length, 3); + assert_equals(navigation.entries()[0], entry1); + assert_equals(navigation.entries()[1], entry2); + assert_equals(navigation.entries()[2], entry3); + }), 0); +}, "entry.sameDocument after same-document navigations"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/sameDocument-after-navigate-restore.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/sameDocument-after-navigate-restore.html new file mode 100644 index 0000000000..fd21bc222f --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/sameDocument-after-navigate-restore.html @@ -0,0 +1,25 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + await i.contentWindow.navigation.navigate("#foo"); + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_true(i.contentWindow.navigation.entries()[0].sameDocument); + + i.contentWindow.navigation.navigate("/common/blank.html?bar"); + await new Promise(resolve => i.onload = resolve); + assert_equals(i.contentWindow.navigation.entries().length, 3); + assert_false(i.contentWindow.navigation.entries()[0].sameDocument); + assert_false(i.contentWindow.navigation.entries()[1].sameDocument); + + i.contentWindow.navigation.back(); + await new Promise(resolve => i.onload = resolve); + assert_equals(i.contentWindow.navigation.entries().length, 3); + assert_equals(i.contentWindow.navigation.currentEntry.index, 1); + assert_true(i.contentWindow.navigation.entries()[0].sameDocument); + assert_false(i.contentWindow.navigation.entries()[2].sameDocument); +}, "entry.sameDocument is properly restored after cross-document back"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/sameDocument-after-navigate.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/sameDocument-after-navigate.html new file mode 100644 index 0000000000..bfcb7c6599 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/sameDocument-after-navigate.html @@ -0,0 +1,14 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + let i_entry = i.contentWindow.navigation.currentEntry; + assert_true(i_entry.sameDocument); + i.onload = t.step_func_done(() => assert_false(i_entry.sameDocument)); + i.contentWindow.location = "about:blank"; + }); +}, "entry.sameDocument after cross-document navigation"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-history-entry/state-after-navigate-restore.html b/testing/web-platform/tests/navigation-api/navigation-history-entry/state-after-navigate-restore.html new file mode 100644 index 0000000000..097b1d5079 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-history-entry/state-after-navigate-restore.html @@ -0,0 +1,27 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + await i.contentWindow.navigation.navigate("#start", { history: "replace", state: "someState" }); + assert_equals(i.contentWindow.navigation.entries().length, 1); + assert_equals(i.contentWindow.navigation.entries()[0].getState(), "someState"); + + await i.contentWindow.navigation.navigate("#foo"); + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.entries()[0].getState(), "someState"); + + i.contentWindow.navigation.navigate("/common/blank.html?bar"); + await new Promise(resolve => i.onload = resolve); + assert_equals(i.contentWindow.navigation.entries().length, 3); + assert_equals(i.contentWindow.navigation.entries()[0].getState(), "someState"); + + i.contentWindow.navigation.back(); + await new Promise(resolve => i.onload = resolve); + assert_equals(i.contentWindow.navigation.entries().length, 3); + assert_equals(i.contentWindow.navigation.currentEntry.index, 1); + assert_equals(i.contentWindow.navigation.entries()[0].getState(), "someState"); +}, "entry.getState() is properly restored after cross-document back"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/back-forward-multiple-frames.html b/testing/web-platform/tests/navigation-api/navigation-methods/back-forward-multiple-frames.html new file mode 100644 index 0000000000..738bfd37dc --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/back-forward-multiple-frames.html @@ -0,0 +1,74 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + // Step 1 + assert_equals(navigation.entries().length, 1, "step 1 outer entries() length"); + assert_equals(i.contentWindow.navigation.entries().length, 1, "step 1 iframe entries() length"); + await navigation.navigate("#top").committed; + // Step 2: iframe at initial entry, top on second entry + assert_equals(navigation.entries().length, 2, "step 2 outer entries() length"); + assert_equals(i.contentWindow.navigation.entries().length, 1, "step 2 iframe entries() length"); + await i.contentWindow.navigation.navigate("#iframe").committed; + + // Step 3: Both windows on second entry. + assert_equals(navigation.entries().length, 2, "step 3 outer entries() length"); + assert_equals(i.contentWindow.navigation.entries().length, 2, "step 3 iframe entries() length"); + assert_equals(navigation.currentEntry.index, 1, "step 3 outer index"); + assert_equals(i.contentWindow.navigation.currentEntry.index, 1, "step 1 iframe index"); + + // NOTE: the order of navigation in the two windows is not guaranteed; we need to wait for both. + + // Going back in the iframe should go 3->2 (navigating iframe only) + await Promise.all([ + i.contentWindow.navigation.back().committed, + new Promise(resolve => i.contentWindow.onpopstate = resolve) + ]); + assert_equals(navigation.currentEntry.index, 1, "after iframe back() outer index"); + assert_equals(i.contentWindow.navigation.currentEntry.index, 0, "after iframe back() iframe index"); + + // Going forward in iframe should go 2->3 + await Promise.all([ + i.contentWindow.navigation.forward().commited, + new Promise(resolve => i.contentWindow.onpopstate = resolve) + ]); + assert_equals(navigation.currentEntry.index, 1, "after iframe forward() outer index"); + assert_equals(i.contentWindow.navigation.currentEntry.index, 1, "after iframe forward() iframe index"); + + // Going back in top should go 3->1 (navigating both windows). + await Promise.all([ + navigation.back().commited, + new Promise(resolve => i.contentWindow.onpopstate = resolve) + ]); + assert_equals(navigation.currentEntry.index, 0, "after outer back() outer index"); + assert_equals(i.contentWindow.navigation.currentEntry.index, 0, "after outer back() iframe index"); + + // Next two should not navigate the iframe + i.contentWindow.onpopstate = t.unreached_func("popstate must not be called"); + + // Going forward in top should go 1->2 (navigating top only) + await navigation.forward().committed; + await new Promise(resolve => t.step_timeout(resolve, 0)); + assert_equals(navigation.currentEntry.index, 1, "after outer forward() outer index"); + assert_equals(i.contentWindow.navigation.currentEntry.index, 0, "after outer forward() iframe index"); + + // Going back in top should go 2->1 + await navigation.back().committed; + await new Promise(resolve => t.step_timeout(resolve, 0)); + assert_equals(navigation.currentEntry.index, 0, "after outer second back() outer index"); + assert_equals(i.contentWindow.navigation.currentEntry.index, 0, "after outer second back() iframe index"); + + // Going forward in iframe should go 1->3 (navigating both windows) + await Promise.all([ + i.contentWindow.navigation.forward().commited, + new Promise(resolve => i.contentWindow.onpopstate = resolve) + ]); + assert_equals(navigation.currentEntry.index, 1, "after iframe second forward() outer index"); + assert_equals(i.contentWindow.navigation.currentEntry.index, 1, "after iframe second forward() iframe index"); +}, "navigation.back() and navigation.forward() can navigate multiple frames"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/disambigaute-back.html b/testing/web-platform/tests/navigation-api/navigation-methods/disambigaute-back.html new file mode 100644 index 0000000000..d44d435896 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/disambigaute-back.html @@ -0,0 +1,32 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + window.onload = () => t.step_timeout(() => { + assert_equals(navigation.entries().length, 1); + assert_equals(i.contentWindow.navigation.entries().length, 1); + navigation.navigate("#top"); + assert_equals(navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.entries().length, 1); + i.contentWindow.navigation.navigate("#1"); + assert_equals(navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(navigation.currentEntry.index, 1); + assert_equals(i.contentWindow.navigation.currentEntry.index, 1); + assert_true(navigation.canGoBack); + assert_true(i.contentWindow.navigation.canGoBack); + + // There are 2 joint session history entries containing the iframe's + // previous key. Navigate to the nearest one (which navigates the iframe + // but not the top window). + i.contentWindow.navigation.back().committed.then(t.step_func_done(() => { + assert_equals(navigation.currentEntry.index, 1); + assert_equals(i.contentWindow.navigation.currentEntry.index, 0); + })); + }, 0); +}, "navigation.back() goes to the nearest back entry"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/disambigaute-forward.html b/testing/web-platform/tests/navigation-api/navigation-methods/disambigaute-forward.html new file mode 100644 index 0000000000..e252e30994 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/disambigaute-forward.html @@ -0,0 +1,40 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + window.onload = () => t.step_timeout(() => { + assert_equals(navigation.entries().length, 1); + assert_equals(i.contentWindow.navigation.entries().length, 1); + navigation.navigate("#top"); + assert_equals(navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.entries().length, 1); + i.contentWindow.navigation.navigate("#1"); + assert_equals(navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(navigation.currentEntry.index, 1); + assert_equals(i.contentWindow.navigation.currentEntry.index, 1); + assert_true(navigation.canGoBack); + assert_true(i.contentWindow.navigation.canGoBack); + + i.contentWindow.navigation.back().committed.then(t.step_func(() => { + assert_equals(navigation.currentEntry.index, 1); + assert_equals(i.contentWindow.navigation.currentEntry.index, 0); + navigation.back().committed.then(t.step_func(() => { + assert_equals(navigation.currentEntry.index, 0); + assert_equals(i.contentWindow.navigation.currentEntry.index, 0); + // There are 2 joint session history entries containing the top window's + // final key. Navigate to the nearest one (which navigates only the + // top window). + navigation.forward().committed.then(t.step_func_done(() => { + assert_equals(navigation.currentEntry.index, 1); + assert_equals(i.contentWindow.navigation.currentEntry.index, 0); + })); + })); + })); + }, 0); +}, "navigation.forward() goes to the nearest forward entry"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/disambigaute-traverseTo-back-multiple.html b/testing/web-platform/tests/navigation-api/navigation-methods/disambigaute-traverseTo-back-multiple.html new file mode 100644 index 0000000000..03a8eb10ed --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/disambigaute-traverseTo-back-multiple.html @@ -0,0 +1,35 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + window.onload = () => t.step_timeout(() => { + assert_equals(navigation.entries().length, 1); + assert_equals(i.contentWindow.navigation.entries().length, 1); + navigation.navigate("#top"); + assert_equals(navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.entries().length, 1); + + let iframe_initial_key = i.contentWindow.navigation.currentEntry.key; + i.contentWindow.navigation.navigate("#1"); + i.contentWindow.navigation.navigate("#2"); + assert_equals(navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.entries().length, 3); + assert_equals(navigation.currentEntry.index, 1); + assert_equals(i.contentWindow.navigation.currentEntry.index, 2); + assert_true(navigation.canGoBack); + assert_true(i.contentWindow.navigation.canGoBack); + + // There are 2 joint session history entries containing the iframe's + // initial key. Navigate to the nearest one (which navigates the iframe + // but not the top window). + i.contentWindow.navigation.traverseTo(iframe_initial_key).committed.then(t.step_func_done(() => { + assert_equals(navigation.currentEntry.index, 1); + assert_equals(i.contentWindow.navigation.currentEntry.index, 0); + })); + }, 0); +}, "navigation.traverseTo() goes to the nearest entry when going back"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/disambigaute-traverseTo-forward-multiple.html b/testing/web-platform/tests/navigation-api/navigation-methods/disambigaute-traverseTo-forward-multiple.html new file mode 100644 index 0000000000..f8e78c4b1d --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/disambigaute-traverseTo-forward-multiple.html @@ -0,0 +1,39 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + assert_equals(navigation.entries().length, 1); + assert_equals(i.contentWindow.navigation.entries().length, 1); + let initial_key = navigation.currentEntry.key; + await navigation.navigate("#top1").committed; + await navigation.navigate("#top2").committed; + assert_equals(navigation.entries().length, 3); + assert_equals(i.contentWindow.navigation.entries().length, 1); + await i.contentWindow.navigation.navigate("#1").committed; + assert_equals(navigation.entries().length, 3); + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(navigation.currentEntry.index, 2); + assert_equals(i.contentWindow.navigation.currentEntry.index, 1); + assert_true(navigation.canGoBack); + assert_true(i.contentWindow.navigation.canGoBack); + let final_key = navigation.currentEntry.key; + + await i.contentWindow.navigation.back().committed; + assert_equals(navigation.currentEntry.index, 2); + assert_equals(i.contentWindow.navigation.currentEntry.index, 0); + await navigation.traverseTo(initial_key).committed; + assert_equals(navigation.currentEntry.index, 0); + assert_equals(i.contentWindow.navigation.currentEntry.index, 0); + // There are 2 joint session history entries containing the top window's + // final key. Navigate to the nearest one (which navigates only the + // top window). + await navigation.traverseTo(final_key).committed; + assert_equals(navigation.currentEntry.index, 2); + assert_equals(i.contentWindow.navigation.currentEntry.index, 0); +}, "navigation.traverseTo() goes to the nearest entry when going forward"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/forward-to-pruned-entry.html b/testing/web-platform/tests/navigation-api/navigation-methods/forward-to-pruned-entry.html new file mode 100644 index 0000000000..05f70c5c2d --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/forward-to-pruned-entry.html @@ -0,0 +1,33 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + await navigation.navigate("#foo").finished; + assert_equals(navigation.entries().length, 2); + await navigation.back().finished; + assert_equals(navigation.currentEntry.index, 0); + + // Traverse forward then immediately do a same-document push. This will + // truncate the back forward list, and by the time the traverse commits, the + // destination key will no longer be present in navigation.entries(). The + // traverse should abort. Because the traverse aborts before the navigate + // event fires, the navigateerror event should not fire. + navigation.onnavigateerror = t.unreached_func("navigateerror should not fire"); + let forward_value = navigation.forward(); + await navigation.navigate("#clobber").finished; + assert_equals(navigation.currentEntry.index, 1); + await promise_rejects_dom(t, "AbortError", forward_value.committed); + await promise_rejects_dom(t, "AbortError", forward_value.finished); + + // This leaves navigation.entries() in a consistent state where traversing + // back and forward still works. + await navigation.back().finished; + assert_equals(navigation.currentEntry.index, 0); + await navigation.forward().finished; + assert_equals(navigation.currentEntry.index, 1); +}, "If forward pruning clobbers the target of a traverse, abort"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/navigate-base-url.html b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-base-url.html new file mode 100644 index 0000000000..00fefa6608 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-base-url.html @@ -0,0 +1,15 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="resources/page-with-base-url-common.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + i.contentWindow.navigation.navigate("blank.html"); + i.onload = t.step_func_done(() => { + const iframeURL = new URL(i.contentWindow.navigation.currentEntry.url); + assert_equals(iframeURL.pathname, "/common/blank.html"); + }); + }); +}, "navigate() must resolve URLs relative to navigation object's base URL"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/navigate-from-initial-about-blank-gc.html b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-from-initial-about-blank-gc.html new file mode 100644 index 0000000000..d35121ec81 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-from-initial-about-blank-gc.html @@ -0,0 +1,18 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/gc.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<!-- + Regression test for https://bugs.chromium.org/p/chromium/issues/detail?id=1289864. +--> + +<script> +promise_test(t => { + i.contentWindow.navigation.navigate("/common/blank.html?1"); + + return garbageCollect(); +}, `navigate() from <iframe> with src="" but still on initial about:blank doesn't cause a crash on GC`); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/navigate-from-initial-about-blank-src.html b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-from-initial-about-blank-src.html new file mode 100644 index 0000000000..8e54943669 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-from-initial-about-blank-src.html @@ -0,0 +1,21 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> + +<!-- + Sort of a regression test for https://bugs.chromium.org/p/chromium/issues/detail?id=1289864, + but since that is GC-dependent this will probably not fail in codebases that exhibit that bug. + So it's really just adding some extra general coverage for navigation.navigate(). +--> + +<script> +async_test(t => { + i.contentWindow.navigation.navigate("/common/blank.html?1"); + i.onload = t.step_func_done(() => { + const iframeURL = new URL(i.contentWindow.navigation.currentEntry.url); + assert_equals(iframeURL.pathname, "/common/blank.html"); + assert_equals(iframeURL.search, "?1"); + }); +}, `navigate() from <iframe> with src="" but still on initial about:blank works`); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/navigate-from-initial-about-blank.html b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-from-initial-about-blank.html new file mode 100644 index 0000000000..00b2216166 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-from-initial-about-blank.html @@ -0,0 +1,22 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i"></iframe> + +<!-- + Sort of a regression test for https://bugs.chromium.org/p/chromium/issues/detail?id=1289864, + but since that is GC-dependent this will probably not fail in codebases that exhibit that bug. + So it's really just adding some extra general coverage for navigation.navigate(). +--> + + +<script> +async_test(t => { + i.contentWindow.navigation.navigate("/common/blank.html?1"); + i.onload = t.step_func_done(() => { + const iframeURL = new URL(i.contentWindow.navigation.currentEntry.url); + assert_equals(iframeURL.pathname, "/common/blank.html"); + assert_equals(iframeURL.search, "?1"); + }); +}, `navigate() from <iframe> still on initial about:blank works`); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/navigate-history-push-not-loaded.html b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-history-push-not-loaded.html new file mode 100644 index 0000000000..c1ecdaeeaf --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-history-push-not-loaded.html @@ -0,0 +1,20 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + // Purposefully do not wait until after the load event (unlike some sibling tests). + assert_equals(navigation.entries().length, 1); + assert_equals(document.readyState, "loading", "Document must not have loaded yet"); + + let navigateEventType; + navigation.onnavigate = e => navigateEventType = e.navigationType; + + await navigation.navigate("#1", { history: "push" }).finished; + assert_equals(navigateEventType, "push"); + assert_equals(navigation.entries().length, 2); + assert_equals(navigation.currentEntry.index, 1); +}, "navigate() with history: 'push' in a document that has not yet had its load event"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/navigate-history-push-same-url-cross-document.html b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-history-push-same-url-cross-document.html new file mode 100644 index 0000000000..2d07bdcede --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-history-push-same-url-cross-document.html @@ -0,0 +1,25 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation due to onload not having completed. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + assert_equals(i.contentWindow.navigation.entries().length, 1); + + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func("onnavigatesuccess should not be called"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("onnavigateerror should not be called"); + + let navigateEventType; + i.contentWindow.navigation.onnavigate = e => navigateEventType = e.navigationType; + + i.contentWindow.navigation.navigate(i.contentWindow.location, { history: "push" }); + await new Promise(resolve => i.onload = resolve); + assert_equals(navigateEventType, "push"); + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry.index, 1); +}, "navigate() to the current URL with history: 'push' and allow it to go cross document"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/navigate-history-push-same-url.html b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-history-push-same-url.html new file mode 100644 index 0000000000..339e8c3056 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-history-push-same-url.html @@ -0,0 +1,26 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation due to onload not having completed. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + assert_equals(navigation.entries().length, 1); + + + let navigateEventType; + navigation.onnavigate = e => { + navigateEventType = e.navigationType; + e.intercept(); + } + navigation.onnavigateerror = t.unreached_func("onnavigateerror should not be called"); + + await navigation.navigate(location.href, { history: "push" }).finished; + assert_equals(navigateEventType, "push"); + assert_equals(navigation.entries().length, 2); + assert_equals(navigation.currentEntry.index, 1); +}, "navigate() to the current URL with history: 'push' and intercept so it remains same-document"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/navigate-history-state-replace.html b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-history-state-replace.html new file mode 100644 index 0000000000..bf0af521da --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-history-state-replace.html @@ -0,0 +1,16 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async () => { + history.replaceState("state1", "", "#1"); + assert_equals(history.state, "state1"); + + let onnavigate_called = false; + navigation.onnavigate = () => onnavigate_called = true; + await navigation.navigate("#2", { history: "replace" }).committed; + assert_equals(location.hash, "#2"); + assert_true(onnavigate_called); + assert_equals(history.state, null); +}, "history.state should be nulled by navigate()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/navigate-history-state.html b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-history-state.html new file mode 100644 index 0000000000..0541636de5 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-history-state.html @@ -0,0 +1,16 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async () => { + history.replaceState("state1", "", "#1"); + assert_equals(history.state, "state1"); + + let onnavigate_called = false; + navigation.onnavigate = () => onnavigate_called = true; + await navigation.navigate("#2").committed; + assert_equals(location.hash, "#2"); + assert_true(onnavigate_called); + assert_equals(history.state, null); +}, "history.state should be nulled by navigate()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/navigate-info-and-state.html b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-info-and-state.html new file mode 100644 index 0000000000..828d7daea5 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-info-and-state.html @@ -0,0 +1,18 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async t => { + let navInfo = { nav : "info" }; + let navState = { statevar: "state" }; + let onnavigated_called = false; + navigation.onnavigate = t.step_func(e => { + onnavigated_called = true; + assert_equals(e.info, navInfo) + }); + await navigation.navigate("#1", { info: navInfo, state: navState }).committed; + assert_true(onnavigated_called); + assert_not_equals(navigation.currentEntry.getState(), navState); + assert_equals(navigation.currentEntry.getState().statevar, navState.statevar); +}, "navigate() with info and state"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/navigate-intercept-history-state.html b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-intercept-history-state.html new file mode 100644 index 0000000000..7faf6ffb5a --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-intercept-history-state.html @@ -0,0 +1,14 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async () => { + history.replaceState("state1", "", "#1"); + assert_equals(history.state, "state1"); + + navigation.onnavigate = e => e.intercept({ handler: () => Promise.resolve("r") }); + await navigation.navigate("#2").committed; + assert_equals(location.hash, "#2"); + assert_equals(history.state, null); +}, "history.story should be nulled by navigate() handled by intercept()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/navigate-relative-url.html b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-relative-url.html new file mode 100644 index 0000000000..cc95d5e003 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-relative-url.html @@ -0,0 +1,16 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + i.contentWindow.navigation.navigate("?1"); + i.onload = t.step_func_done(() => { + let iframe_url = new URL(i.contentWindow.navigation.currentEntry.url); + assert_equals(iframe_url.search, "?1"); + assert_equals(iframe_url.pathname, "/common/blank.html"); + }); + }); +}, "navigate() should resolve urls relative to navigation object, not the caller"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/navigate-replace-cross-document.html b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-replace-cross-document.html new file mode 100644 index 0000000000..7f016babe6 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-replace-cross-document.html @@ -0,0 +1,29 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation (which would defeat the point of the test). + window.onload = () => t.step_timeout(() => { + let start_history_length = history.length; + let start_entry_top = navigation.currentEntry; + let start_entry_iframe_id = i.contentWindow.navigation.currentEntry.id; + let start_entry_iframe_key = i.contentWindow.navigation.currentEntry.key; + + i.contentWindow.navigation.navigate("?1", { history: "replace" }); + i.onload = t.step_func_done(() => { + assert_equals(history.length, start_history_length); + + assert_equals(navigation.entries().length, 1); + assert_equals(navigation.currentEntry, start_entry_top); + + assert_equals(i.contentWindow.navigation.entries().length, 1); + let iframe_entry = i.contentWindow.navigation.currentEntry; + assert_not_equals(start_entry_iframe_id, iframe_entry.id); + assert_equals(start_entry_iframe_key, iframe_entry.key); + }); + }, 0); +}, "navigate() with history: 'replace' option"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/navigate-replace-same-document.html b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-replace-same-document.html new file mode 100644 index 0000000000..0d8493b788 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-replace-same-document.html @@ -0,0 +1,21 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + window.onload = () => t.step_timeout(t.step_func_done(async () => { + let start_history_length = history.length; + let key1 = navigation.currentEntry.key; + await navigation.navigate("#1").committed; + let key2 = navigation.currentEntry.key; + assert_not_equals(key1, key2); + await navigation.navigate("#2", { history: "replace" }).committed; + let key3 = navigation.currentEntry.key; + assert_equals(key2, key3); + assert_equals(navigation.entries().length, 2); + assert_equals(history.length, start_history_length + 1); + }), 0); +}, "navigate() with history: 'replace' option"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/navigate-same-document.html b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-same-document.html new file mode 100644 index 0000000000..9ffd8248f8 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-same-document.html @@ -0,0 +1,23 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="d"></div> +<script> +promise_test(async () => { + let onnavigate_called = false; + navigation.onnavigate = () => onnavigate_called = true; + await navigation.navigate("#d").committed; + assert_equals(location.hash, "#d"); + assert_true(onnavigate_called); + assert_equals(document.querySelector(":target"), d); +}, "navigate() navigates same-document and fires onnavigate (async)"); + +test(() => { + let onnavigate_called = false; + navigation.onnavigate = () => onnavigate_called = true; + navigation.navigate("#d"); + assert_equals(location.hash, "#d"); + assert_true(onnavigate_called); + assert_equals(document.querySelector(":target"), d); +}, "navigate() navigates same-document and fires onnavigate (sync)"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/navigate-state-repeated-await.html b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-state-repeated-await.html new file mode 100644 index 0000000000..539b3dc1da --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-state-repeated-await.html @@ -0,0 +1,13 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async t => { + navigation.onnavigate = e => e.intercept(); + + await navigation.navigate('/foo', {state: {foo: 1}}).committed; + assert_equals(navigation.currentEntry.getState().foo, 1); + await navigation.navigate('/foo', {state: {foo: 2}}).committed; + assert_equals(navigation.currentEntry.getState().foo, 2); +}, "navigate() with state should work correctly when called repeatedly - with awaits"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/navigate-state-repeated.html b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-state-repeated.html new file mode 100644 index 0000000000..227bbb0cfc --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/navigate-state-repeated.html @@ -0,0 +1,20 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async t => { + navigation.onnavigate = e => e.intercept(); + + let result1 = navigation.navigate('/foo', {state: {foo: 1}}); + assert_equals(navigation.currentEntry.getState().foo, 1); + + // Don't let the harness fail because of the "AbortError" DOMException that is caused by interrupting the navigation. + result1.finished.catch(() => {}); + + let result2 = navigation.navigate('/foo', {state: {foo: 2}}); + assert_equals(navigation.currentEntry.getState().foo, 2); + + await result1.committed; + await result2.committed; +}, "navigate() with state should work correctly when called repeatedly"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/reload-base-url.html b/testing/web-platform/tests/navigation-api/navigation-methods/reload-base-url.html new file mode 100644 index 0000000000..1e8d3b90c3 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/reload-base-url.html @@ -0,0 +1,16 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="resources/page-with-base-url-common.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + const startingURL = new URL(i.src); + i.contentWindow.navigation.reload(); + i.onload = t.step_func_done(() => { + const iframeURL = new URL(i.contentWindow.navigation.currentEntry.url); + assert_equals(iframeURL.pathname, startingURL.pathname); + }); + }); +}, "reload() must ignore the Navigation object's base URL"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/reload-info.html b/testing/web-platform/tests/navigation-api/navigation-methods/reload-info.html new file mode 100644 index 0000000000..637f6976ad --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/reload-info.html @@ -0,0 +1,47 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + const navState = { key: "value" }; + const navInfo = { infoKey: "infoValue" }; + i.contentWindow.navigation.navigate("#1", { state: navState }).committed.then(t.step_func(() => { + // Make sure that state setting worked + assert_equals(i.contentWindow.navigation.currentEntry.getState().key, "value"); + assert_not_equals(i.contentWindow.navigation.currentEntry.getState(), navState); + + let start_url = i.contentWindow.location.href; + let start_key = i.contentWindow.navigation.currentEntry.key; + let start_id = i.contentWindow.navigation.currentEntry.id; + let start_state = i.contentWindow.navigation.currentEntry.getState(); + let onnavigate_called = false; + let promise_settled = false; + i.contentWindow.navigation.onnavigate = t.step_func(e => { + onnavigate_called = true; + assert_equals(e.info, navInfo); + assert_equals(e.navigationType, "reload"); + assert_equals(e.destination.getState().key, "value"); + assert_not_equals(e.destination.getState(), start_state); + }); + i.contentWindow.navigation.reload({ info: navInfo }).committed.finally(() => { + promise_settled = true; + }); + i.onload = t.step_func(() => { + assert_true(onnavigate_called); + assert_equals(i.contentWindow.location.href, start_url); + assert_equals(i.contentWindow.navigation.currentEntry.key, start_key); + assert_equals(i.contentWindow.navigation.currentEntry.id, start_id); + assert_equals(i.contentWindow.navigation.currentEntry.getState().key, "value"); + assert_not_equals(i.contentWindow.navigation.currentEntry.getState(), start_state); + assert_equals(i.contentWindow.navigation.entries().length, 2); + + t.step_timeout(t.step_func_done(() => { + assert_equals(promise_settled, false); + }), 0); + }); + })); + }); +}, "reload() variant with only info"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/reload-navigation-timing.html b/testing/web-platform/tests/navigation-api/navigation-methods/reload-navigation-timing.html new file mode 100644 index 0000000000..ce03e7a7ca --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/reload-navigation-timing.html @@ -0,0 +1,15 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + i.contentWindow.navigation.reload(); + await new Promise(resolve => i.onload = resolve); + + const entries = i.contentWindow.performance.getEntriesByType("navigation"); + assert_equals(entries.length, 1); + assert_equals(entries[0].type, "reload"); +}, `reload() appears as a reload to navigation timing APIs`); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/reload-no-args.html b/testing/web-platform/tests/navigation-api/navigation-methods/reload-no-args.html new file mode 100644 index 0000000000..c94eae0b93 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/reload-no-args.html @@ -0,0 +1,46 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + const navState = { key: "value" }; + i.contentWindow.navigation.navigate("#1", { state: navState }).committed.then(t.step_func(() => { + // Make sure that state setting worked + assert_equals(i.contentWindow.navigation.currentEntry.getState().key, "value"); + assert_not_equals(i.contentWindow.navigation.currentEntry.getState(), navState); + + let start_url = i.contentWindow.location.href; + let start_key = i.contentWindow.navigation.currentEntry.key; + let start_id = i.contentWindow.navigation.currentEntry.id; + let start_state = i.contentWindow.navigation.currentEntry.getState(); + let onnavigate_called = false; + let promise_settled = false; + i.contentWindow.navigation.onnavigate = t.step_func(e => { + onnavigate_called = true; + assert_equals(e.info, undefined); + assert_equals(e.navigationType, "reload"); + assert_equals(e.destination.getState().key, "value"); + assert_not_equals(e.destination.getState(), start_state); + }); + i.contentWindow.navigation.reload().committed.finally(() => { + promise_settled = true; + }); + i.onload = t.step_func(() => { + assert_true(onnavigate_called); + assert_equals(i.contentWindow.location.href, start_url); + assert_equals(i.contentWindow.navigation.currentEntry.key, start_key); + assert_equals(i.contentWindow.navigation.currentEntry.id, start_id); + assert_equals(i.contentWindow.navigation.currentEntry.getState().key, "value"); + assert_not_equals(i.contentWindow.navigation.currentEntry.getState(), start_state); + assert_equals(i.contentWindow.navigation.entries().length, 2); + + t.step_timeout(t.step_func_done(() => { + assert_equals(promise_settled, false); + }), 0); + }); + })); + }); +}, "reload() variant with no state or info"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/reload-service-worker-fetch-event.html b/testing/web-platform/tests/navigation-api/navigation-methods/reload-service-worker-fetch-event.html new file mode 100644 index 0000000000..67535c70bf --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/reload-service-worker-fetch-event.html @@ -0,0 +1,28 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> + +<!-- Keep an eye on https://github.com/whatwg/fetch/issues/1280 --> + +<script> +const worker = "resources/fetch-event-test-worker.js"; + +promise_test(async t => { + const scope = "resources/service-worker-page.html?reload-service-worker-fetch-event"; + + const reg = await service_worker_unregister_and_register(t, worker, scope); + + await wait_for_state(t, reg.installing, "activated"); + const frame = await with_iframe(scope); + assert_equals(frame.contentDocument.body.textContent, "method = GET, isReloadNavigation = false"); + + frame.contentWindow.navigation.reload(); + await new Promise(resolve => frame.addEventListener("load", resolve, { once: true })); + assert_equals(frame.contentDocument.body.textContent, "method = GET, isReloadNavigation = true"); + + frame.remove(); + await reg.unregister(); +}, "reload() appears as a reload to service worker fetch event handlers"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/reload-state-and-info.html b/testing/web-platform/tests/navigation-api/navigation-methods/reload-state-and-info.html new file mode 100644 index 0000000000..1ba26ae3da --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/reload-state-and-info.html @@ -0,0 +1,40 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + const navState1 = { key: "value" }; + const navState2 = { key2: "value2" }; + const navInfo = { infoKey: "infoValue" }; + i.contentWindow.navigation.navigate("#1", { state: navState1 }).committed.then(t.step_func(() => { + // Make sure that state setting worked + assert_equals(i.contentWindow.navigation.currentEntry.getState().key, "value", "initial state setup"); + assert_not_equals(i.contentWindow.navigation.currentEntry.getState(), navState1); + + let start_url = i.contentWindow.location.href; + let start_key = i.contentWindow.navigation.currentEntry.key; + let start_id = i.contentWindow.navigation.currentEntry.id; + let onnavigate_called = false; + let promise_settled = false; + i.contentWindow.navigation.onnavigate = t.step_func(e => { + e.intercept(); + onnavigate_called = true; + assert_equals(e.info, navInfo); + assert_equals(e.navigationType, "reload"); + assert_equals(e.destination.getState().key2, "value2", "navigate event for the reload()"); + assert_not_equals(e.destination.getState(), navState2); + }); + i.contentWindow.navigation.reload({ info: navInfo, state: navState2 }).committed.then(t.step_func_done(() => { + assert_true(onnavigate_called); + assert_equals(i.contentWindow.location.href, start_url); + assert_equals(i.contentWindow.navigation.currentEntry.key, start_key); + assert_equals(i.contentWindow.navigation.currentEntry.id, start_id); + assert_equals(i.contentWindow.navigation.currentEntry.getState().key2, "value2", "currentEntry.getState() after the reload"); + assert_not_equals(i.contentWindow.navigation.currentEntry.getState(), navState2); + })); + })); + }); +}, "reload() variant with info and new state"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/reload-state-undefined.html b/testing/web-platform/tests/navigation-api/navigation-methods/reload-state-undefined.html new file mode 100644 index 0000000000..b8b34650ab --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/reload-state-undefined.html @@ -0,0 +1,39 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + const navState = { key: "value" }; + const navInfo = { infoKey: "infoValue" }; + i.contentWindow.navigation.navigate("#1", { state: navState }).committed.then(t.step_func(() => { + // Make sure that state setting worked + assert_equals(i.contentWindow.navigation.currentEntry.getState().key, "value"); + assert_not_equals(i.contentWindow.navigation.currentEntry.getState(), navState); + + let start_url = i.contentWindow.location.href; + let start_key = i.contentWindow.navigation.currentEntry.key; + let start_id = i.contentWindow.navigation.currentEntry.id; + let onnavigate_called = false; + let promise_settled = false; + i.contentWindow.navigation.onnavigate = t.step_func(e => { + e.intercept(); + onnavigate_called = true; + assert_equals(e.info, navInfo); + assert_equals(e.navigationType, "reload"); + assert_equals(e.destination.getState().key, "value", "destination.getState()"); + assert_not_equals(e.destination.getState(), navState); + }); + i.contentWindow.navigation.reload({ info: navInfo, state: undefined }).committed.then(t.step_func_done(() => { + assert_true(onnavigate_called); + assert_equals(i.contentWindow.location.href, start_url); + assert_equals(i.contentWindow.navigation.currentEntry.key, start_key); + assert_equals(i.contentWindow.navigation.currentEntry.id, start_id); + assert_equals(i.contentWindow.navigation.currentEntry.getState().key, "value", "destination.getState()"); + assert_not_equals(i.contentWindow.navigation.currentEntry.getState(), navState); + })); + })); + }); +}, "reload() variant with info and state: undefined counts the same as not present (because of Web IDL dictionary semantics), so preserves the state"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/resources/fetch-event-test-worker.js b/testing/web-platform/tests/navigation-api/navigation-methods/resources/fetch-event-test-worker.js new file mode 100644 index 0000000000..98b7dd0fb5 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/resources/fetch-event-test-worker.js @@ -0,0 +1,7 @@ +self.addEventListener('fetch', function(event) { + const request = event.request; + const body = + `method = ${request.method}, ` + + `isReloadNavigation = ${request.isReloadNavigation}`; + event.transitionWhile(new Response(body)); +}); diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/resources/navigate-parent.html b/testing/web-platform/tests/navigation-api/navigation-methods/resources/navigate-parent.html new file mode 100644 index 0000000000..8584bb5774 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/resources/navigate-parent.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<script> +window.navigateParent = () => { + return parent.navigation.navigate("#2"); +}; +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/resources/navigate-sibling.html b/testing/web-platform/tests/navigation-api/navigation-methods/resources/navigate-sibling.html new file mode 100644 index 0000000000..29a5ee7390 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/resources/navigate-sibling.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<script> +window.navigateSibling = () => { + return parent.frames[0].navigation.navigate("/common/blank.html?2"); +}; +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/resources/navigation-back.html b/testing/web-platform/tests/navigation-api/navigation-methods/resources/navigation-back.html new file mode 100644 index 0000000000..a1441801ac --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/resources/navigation-back.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<script> +window.doNavigationBack = () => { + return navigation.back(); +}; +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/resources/page-with-base-url-common.html b/testing/web-platform/tests/navigation-api/navigation-methods/resources/page-with-base-url-common.html new file mode 100644 index 0000000000..8d9fedcc2b --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/resources/page-with-base-url-common.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<base href="/common/"> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/resources/service-worker-page.html b/testing/web-platform/tests/navigation-api/navigation-methods/resources/service-worker-page.html new file mode 100644 index 0000000000..a10ff35dce --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/resources/service-worker-page.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<p>We can't use /common/blank.html because of scope restrictions. diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/resources/slow-no-store.py b/testing/web-platform/tests/navigation-api/navigation-methods/resources/slow-no-store.py new file mode 100644 index 0000000000..48e5fc0266 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/resources/slow-no-store.py @@ -0,0 +1,6 @@ +import time + +def main(request, response): + # Sleep for 1sec + time.sleep(1) + response.headers.set(b"Cache-Control", b"no-cache, no-store, must-revalidate"); diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-204-205-download.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-204-205-download.html new file mode 100644 index 0000000000..5bedbf21e8 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-204-205-download.html @@ -0,0 +1,52 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> +<script src="/common/utils.js"></script> + +<body> +<script> +const tests = [ + ["204s", "204"], + ["205s", "205"], + ["Content-Disposition: attachment responses", "download"] +]; + +for (const [description, action] of tests) { + promise_test(async t => { + const id = token(); + + const i = document.createElement("iframe"); + i.src = `resources/204-205-download-on-second-visit.py?id=${id}`; + document.body.append(i); + await new Promise(r => i.onload = r); + + // Configure it to return a 204 on the next visit + await fetch(i.src + `&action=${action}`, { method: "POST" }); + + // Now navigate elsewhere + i.contentWindow.location.href = "/common/blank.html"; + await new Promise(r => i.onload = r); + + // Now try going back. It should do nothing (and not tell us about the result). + + const indexBefore = i.contentWindow.navigation.currentEntry.index; + + // One might be surprised that navigate does not fire. (It does fire for the + // corresponding tests of navigation.navigate(), i.e., this is + // traversal-specific behavior.) See https://github.com/WICG/navigation-api/issues/207 + // for some discussion. + i.contentWindow.navigation.onnavigate = t.unreached_func("onnavigate should not be called"); + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func("onnavigatesuccess should not be called"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("onnavigateerror should not be called"); + + const result = i.contentWindow.navigation.back(); + + assertNeverSettles(t, result, i.contentWindow); + + await new Promise(resolve => t.step_timeout(resolve, 50)); + assert_equals(i.contentWindow.navigation.currentEntry.index, indexBefore); + assert_equals(i.contentWindow.navigation.transition, null); + }, `back() promises to ${description} never settle`); +} +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-already-detached.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-already-detached.html new file mode 100644 index 0000000000..f9ff04f923 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-already-detached.html @@ -0,0 +1,30 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + assert_equals(i.contentWindow.navigation.entries().length, 1); + let key = i.contentWindow.navigation.currentEntry.key; + + i.contentWindow.navigation.navigate("?1"); + await new Promise(resolve => i.onload = resolve); + + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry, i.contentWindow.navigation.entries()[1]); + + const iWindow = i.contentWindow; + const iDOMException = iWindow.DOMException; + + i.remove(); + + await assertBothRejectDOM(t, iWindow.navigation.back(), "InvalidStateError", iWindow, iDOMException); +}, "back() in a detached window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-beforeunload.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-beforeunload.html new file mode 100644 index 0000000000..82c1f589cc --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-beforeunload.html @@ -0,0 +1,42 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + assert_equals(i.contentWindow.navigation.entries().length, 1); + let key = i.contentWindow.navigation.currentEntry.key; + + i.contentWindow.navigation.navigate("?1"); + await new Promise(resolve => i.onload = resolve); + + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry, i.contentWindow.navigation.entries()[1]); + + let navigateEventCount = 0; + i.contentWindow.navigation.onnavigate = () => navigateEventCount++; + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func("onnavigatesuccess should not be called"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("onnavigateerror should not be called"); + + let assertionPromise; + // The iframe does not have sticky activation, so per + // https://html.spec.whatwg.org/#prompt-to-unload-a-document, no prompt is + // shown and the navigation will proceed. + i.contentWindow.onbeforeunload = t.step_func(() => { + assertionPromise = assertBothRejectDOM(t, i.contentWindow.navigation.back(), "InvalidStateError", i.contentWindow); + }); + i.contentWindow.navigation.navigate("?1"); + + assert_not_equals(assertionPromise, undefined); + await assertionPromise; + + assert_equals(navigateEventCount, 1); +}, "back() inside onbeforeunload"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-forward-initial-about-blank.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-forward-initial-about-blank.html new file mode 100644 index 0000000000..dfdb6611ab --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-forward-initial-about-blank.html @@ -0,0 +1,25 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +promise_test(async t => { + let i = document.createElement("iframe"); + document.body.append(i); + + i.contentWindow.navigation.onnavigate = t.unreached_func("onnavigate should not be called"); + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func("onnavigatesuccess should not be called"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("onnavigateerror should not be called"); + + // Since there's no way to do a non-replacement navigation on the initial + // about:blank, there's no way to actually get in a situation where we're on + // about:blank but there's something else backward/forward in the history + // list. So this test will almost certainly pass just because there's nothing + // to go back/forward to. Oh well; it's still reasonable coverage. + + await assertBothRejectDOM(t, i.contentWindow.navigation.back(), "InvalidStateError", i.contentWindow); + await assertBothRejectDOM(t, i.contentWindow.navigation.forward(), "InvalidStateError", i.contentWindow); +}, "back() and forward() in initial about:blank document"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-forward-opaque-origin.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-forward-opaque-origin.html new file mode 100644 index 0000000000..59849e961d --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-forward-opaque-origin.html @@ -0,0 +1,9 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<iframe id="i" sandbox="allow-scripts" src="resources/back-forward-opaque-origin-page.html"></iframe> + +<script> +fetch_tests_from_window(i.contentWindow); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-forward-out-of-bounds.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-forward-out-of-bounds.html new file mode 100644 index 0000000000..015c090bf9 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-forward-out-of-bounds.html @@ -0,0 +1,16 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + assert_equals(navigation.entries().length, 1); + assert_equals(navigation.entries()[0], navigation.currentEntry); + assert_false(navigation.canGoBack); + assert_false(navigation.canGoForward); + + await assertBothRejectDOM(t, navigation.back(), "InvalidStateError"); + await assertBothRejectDOM(t, navigation.forward(), "InvalidStateError"); +}, "back() and forward() out of bounds"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-intercept-rejected.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-intercept-rejected.html new file mode 100644 index 0000000000..9d4238087f --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-intercept-rejected.html @@ -0,0 +1,28 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + location.href = "#1"; + + assert_equals(navigation.entries().length, 2); + const [entry0, entry1] = navigation.entries(); + assert_equals((new URL(entry0.url)).hash, ""); + assert_equals((new URL(entry1.url)).hash, "#1"); + + const err = new Error("boo!"); + const promise = Promise.reject(err); + promise.catch(() => {}); // prevent unhandled rejection testharness.js errors + navigation.onnavigate = e => e.intercept({ handler: () => promise }); + + const result = navigation.back(); + await assertCommittedFulfillsFinishedRejectsExactly(t, result, entry0, err); + assert_equals(navigation.currentEntry, entry0); +}, "back() promise rejection with rejected intercept()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-intercept.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-intercept.html new file mode 100644 index 0000000000..2654045c2b --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-intercept.html @@ -0,0 +1,25 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + location.href = "#1"; + + assert_equals(navigation.entries().length, 2); + const [entry0, entry1] = navigation.entries(); + assert_equals((new URL(entry0.url)).hash, ""); + assert_equals((new URL(entry1.url)).hash, "#1"); + + navigation.onnavigate = e => e.intercept({ handler: () => Promise.resolve({ abc: "def" }) }); + + const result = navigation.back(); + await assertBothFulfill(t, result, entry0); + assert_equals(navigation.currentEntry, entry0); +}, "back() and intercept() with a fulfilled promise"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back.html new file mode 100644 index 0000000000..a2b13db901 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/back.html @@ -0,0 +1,23 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + location.href = "#1"; + + assert_equals(navigation.entries().length, 2); + const [entry0, entry1] = navigation.entries(); + assert_equals((new URL(entry0.url)).hash, ""); + assert_equals((new URL(entry1.url)).hash, "#1"); + + const result = navigation.back(); + await assertBothFulfill(t, result, entry0); + assert_equals(navigation.currentEntry, entry0); +}, "back() promises"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/forward-already-detached.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/forward-already-detached.html new file mode 100644 index 0000000000..4dfa74d9f9 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/forward-already-detached.html @@ -0,0 +1,33 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + assert_equals(i.contentWindow.navigation.entries().length, 1); + let key = i.contentWindow.navigation.currentEntry.key; + + i.contentWindow.navigation.navigate("?1"); + await new Promise(resolve => i.onload = resolve); + + i.contentWindow.navigation.back(); + await new Promise(resolve => i.onload = resolve); + + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry, i.contentWindow.navigation.entries()[0]); + + const iWindow = i.contentWindow; + const iDOMException = iWindow.DOMException; + + i.remove(); + + await assertBothRejectDOM(t, iWindow.navigation.forward(), "InvalidStateError", iWindow, iDOMException); +}, "forward() in a detached window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/forward-beforeunload.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/forward-beforeunload.html new file mode 100644 index 0000000000..87fa4baa93 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/forward-beforeunload.html @@ -0,0 +1,45 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + assert_equals(i.contentWindow.navigation.entries().length, 1); + let key = i.contentWindow.navigation.currentEntry.key; + + i.contentWindow.navigation.navigate("?1"); + await new Promise(resolve => i.onload = resolve); + + i.contentWindow.navigation.back(); + await new Promise(resolve => i.onload = resolve); + + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry, i.contentWindow.navigation.entries()[0]); + + let navigateEventCount = 0; + i.contentWindow.navigation.onnavigate = () => navigateEventCount++; + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func("onnavigatesuccess should not be called"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("onnavigateerror should not be called"); + + let assertionPromise; + // The iframe does not have sticky activation, so per + // https://html.spec.whatwg.org/#prompt-to-unload-a-document, no prompt is + // shown and the navigation will proceed. + i.contentWindow.onbeforeunload = t.step_func(() => { + assertionPromise = assertBothRejectDOM(t, i.contentWindow.navigation.forward(), "InvalidStateError", i.contentWindow); + }); + i.contentWindow.navigation.navigate("?1"); + + assert_not_equals(assertionPromise, undefined); + await assertionPromise; + + assert_equals(navigateEventCount, 1); +}, "forward() inside onbeforeunload"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/forward-intercept-rejected.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/forward-intercept-rejected.html new file mode 100644 index 0000000000..9e86dcca19 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/forward-intercept-rejected.html @@ -0,0 +1,29 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + location.href = "#1"; + await navigation.back().committed; + + assert_equals(navigation.entries().length, 2); + const [entry0, entry1] = navigation.entries(); + assert_equals((new URL(entry0.url)).hash, ""); + assert_equals((new URL(entry1.url)).hash, "#1"); + + const err = new Error("boo!"); + const promise = Promise.reject(err); + promise.catch(() => {}); // prevent unhandled rejection testharness.js errors + navigation.onnavigate = e => e.intercept({ handler: () => promise }); + + const result = navigation.forward(); + await assertCommittedFulfillsFinishedRejectsExactly(t, result, entry1, err); + assert_equals(navigation.currentEntry, entry1); +}, "forward() promise rejection with rejected intercept()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/forward-intercept.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/forward-intercept.html new file mode 100644 index 0000000000..ce4ab32170 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/forward-intercept.html @@ -0,0 +1,28 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + location.href = "#1"; + + assert_equals(navigation.entries().length, 2); + const [entry0, entry1] = navigation.entries(); + assert_equals((new URL(entry0.url)).hash, ""); + assert_equals((new URL(entry1.url)).hash, "#1"); + + await navigation.back().committed; + + const promise = Promise.resolve({ abc: "def" }); + navigation.onnavigate = e => e.intercept({ handler: () => promise }); + + const result = navigation.forward(); + await assertBothFulfill(t, result, entry1); + assert_equals(navigation.currentEntry, entry1); +}, "forward() and intercept() with a fulfilled promise"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/forward.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/forward.html new file mode 100644 index 0000000000..36bdba8daa --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/forward.html @@ -0,0 +1,24 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + location.href = "#1"; + await navigation.back().committed; + + assert_equals(navigation.entries().length, 2); + const [entry0, entry1] = navigation.entries(); + assert_equals((new URL(entry0.url)).hash, ""); + assert_equals((new URL(entry1.url)).hash, "#1"); + + const result = navigation.forward(); + await assertBothFulfill(t, result, entry1); + assert_equals(navigation.currentEntry, entry1); +}, "forward() promises"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-204-205-download.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-204-205-download.html new file mode 100644 index 0000000000..f1e89b6940 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-204-205-download.html @@ -0,0 +1,42 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +const tests = [ + ["204s", "/common/blank.html?pipe=status(204)"], + ["205s", "/common/blank.html?pipe=status(205)"], + ["Content-Disposition: attachment responses", "/common/blank.html?pipe=header(Content-Disposition,attachment)"] +]; + +for (const [description, url] of tests) { + promise_test(async t => { + const i = document.createElement("iframe"); + i.src = "/common/blank.html"; + document.body.append(i); + await new Promise(resolve => i.onload = resolve); + + // This seems to be important? Without it the (outer) window load event + // doesn't fire, and the test harness hangs forever. This is probably a + // Chromium bug, but maybe a testharness bug, especially since explicit_done + // doesn't seem to help? + t.add_cleanup(() => i.remove()); + + let navigateCount = 0; + i.contentWindow.navigation.onnavigate = () => { ++navigateCount; }; + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func("onnavigatesuccess should not be called"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("onnavigateerror should not be called"); + + const result = i.contentWindow.navigation.navigate(url); + + assert_equals(navigateCount, 1); + assertNeverSettles(t, result, i.contentWindow); + + await new Promise(resolve => t.step_timeout(resolve, 50)); + assert_equals(i.contentWindow.location.href, i.src); + assert_equals(i.contentWindow.navigation.transition, null); + }, `navigate() promises to ${description} never settle`); +} +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-already-detached.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-already-detached.html new file mode 100644 index 0000000000..33cdd6922d --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-already-detached.html @@ -0,0 +1,19 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + const iWindow = i.contentWindow; + const iDOMException = iWindow.DOMException; + + i.remove(); + + await assertBothRejectDOM(t, iWindow.navigation.navigate("?1"), "InvalidStateError", iWindow, iDOMException); +}, "navigate() in a detached window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-beforeunload.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-beforeunload.html new file mode 100644 index 0000000000..f0a9069677 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-beforeunload.html @@ -0,0 +1,31 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + let navigateEventCount = 0; + i.contentWindow.navigation.onnavigate = () => navigateEventCount++; + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func("onnavigatesuccess should not be called"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("onnavigateerror should not be called"); + + let assertionPromise; + // The iframe does not have sticky activation, so per + // https://html.spec.whatwg.org/#prompt-to-unload-a-document, no prompt is + // shown and the navigation will proceed. + i.contentWindow.onbeforeunload = t.step_func(() => { + assertionPromise = assertBothRejectDOM(t, i.contentWindow.navigation.navigate("#"), "InvalidStateError", i.contentWindow); + }); + i.contentWindow.navigation.navigate("?1"); + + assert_not_equals(assertionPromise, undefined); + await assertionPromise; + + assert_equals(navigateEventCount, 1); +}, "navigate() inside onbeforeunload"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-cross-document.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-cross-document.html new file mode 100644 index 0000000000..b83b1e941f --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-cross-document.html @@ -0,0 +1,16 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + const result = i.contentWindow.navigation.navigate("?1"); + assertNeverSettles(t, result, i.contentWindow); + + await new Promise(resolve => i.onload = () => t.step_timeout(resolve, 0)); +}, "cross-document navigate() promises never settle"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-detach-in-onnavigate.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-detach-in-onnavigate.html new file mode 100644 index 0000000000..0e80e1b130 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-detach-in-onnavigate.html @@ -0,0 +1,18 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + const iWindow = i.contentWindow; + const iDOMException = iWindow.DOMException; + i.contentWindow.navigation.onnavigate = () => i.remove(); + + await assertBothRejectDOM(t, i.contentWindow.navigation.navigate("#1"), "AbortError", iWindow, iDOMException); +}, "navigate() promise rejections when detaching an iframe inside onnavigate"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-detach-in-serialization.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-detach-in-serialization.html new file mode 100644 index 0000000000..14086c9f01 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-detach-in-serialization.html @@ -0,0 +1,24 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + const iWindow = i.contentWindow; + const iDOMException = iWindow.DOMException; + + const trappedState = { + get prop() { + i.remove(); + return "whatever"; + } + }; + + await assertBothRejectDOM(t, i.contentWindow.navigation.navigate("#1", { state: trappedState }), "InvalidStateError", iWindow, iDOMException); +}, "navigate() promise rejections when detaching an iframe inside state serialization"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-file-url.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-file-url.html new file mode 100644 index 0000000000..138f6ffc12 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-file-url.html @@ -0,0 +1,10 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + await assertBothRejectDOM(t, navigation.navigate("file://"), "AbortError"); +}, "navigate() to a file: URL"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-initial-about-blank-cross-document.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-initial-about-blank-cross-document.html new file mode 100644 index 0000000000..8ec76b11dd --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-initial-about-blank-cross-document.html @@ -0,0 +1,21 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +promise_test(async t => { + let i = document.createElement("iframe"); + document.body.append(i); + + i.contentWindow.navigation.onnavigate = t.unreached_func("onnavigate should not be called"); + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func("onnavigatesuccess should not be called"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("onnavigateerror should not be called"); + + // Cannot just use "/common/blank.html?1" since it doesn't resolve relative to about:blank. + const result = i.contentWindow.navigation.navigate(new URL("/common/blank.html?1", location.href).href); + assertNeverSettles(t, result, i.contentWindow); + await new Promise(resolve => t.step_timeout(resolve, 10)); +}, "navigate() in initial about:blank document (cross-document)"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-initial-about-blank.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-initial-about-blank.html new file mode 100644 index 0000000000..9734a09d01 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-initial-about-blank.html @@ -0,0 +1,20 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +promise_test(async t => { + let i = document.createElement("iframe"); + document.body.append(i); + + i.contentWindow.navigation.onnavigate = t.unreached_func("onnavigate should not be called"); + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func("onnavigatesuccess should not be called"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("onnavigateerror should not be called"); + + const result = i.contentWindow.navigation.navigate("#1"); + assertNeverSettles(t, result, i.contentWindow); + await new Promise(resolve => t.step_timeout(resolve, 10)); +}, "navigate() in initial about:blank document"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-intercept-interrupted.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-intercept-interrupted.html new file mode 100644 index 0000000000..36403c892c --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-intercept-interrupted.html @@ -0,0 +1,23 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + navigation.addEventListener("navigate", e => e.intercept()); + + const result1 = navigation.navigate("#1"); + const result2 = navigation.navigate("#2"); + + assert_equals(navigation.entries().length, 3); + assert_array_equals(navigation.entries().map(e => (new URL(e.url)).hash), ["", "#1", "#2"]); + + await assertCommittedFulfillsFinishedRejectsDOM(t, result1, navigation.entries()[1], "AbortError"); + await assertBothFulfill(t, result2, navigation.currentEntry); +}, "interrupted navigate() promises with intercept()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-intercept-rejected.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-intercept-rejected.html new file mode 100644 index 0000000000..79c8bd803e --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-intercept-rejected.html @@ -0,0 +1,17 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + const err = new Error("boo!"); + const promise = Promise.reject(err); + promise.catch(() => {}); // prevent unhandled rejection testharness.js errors + navigation.onnavigate = e => e.intercept({ handler: () => promise }); + + const result = navigation.navigate("#1"); + + await assertCommittedFulfillsFinishedRejectsExactly(t, result, navigation.currentEntry, err); +}, "navigate() and intercept() with a rejected promise"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-intercept.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-intercept.html new file mode 100644 index 0000000000..a9946c71ff --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-intercept.html @@ -0,0 +1,15 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + navigation.onnavigate = e => e.intercept({ handler: () => Promise.resolve({ abc: 'def' }) }); + + const result = navigation.navigate("#1"); + + await assertBothFulfill(t, result, navigation.currentEntry); + assert_equals((new URL(navigation.currentEntry.url)).hash, "#1"); +}, "navigate() and intercept() with a fulfilled promise"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-interrupted-within-onnavigate.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-interrupted-within-onnavigate.html new file mode 100644 index 0000000000..3db02c6931 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-interrupted-within-onnavigate.html @@ -0,0 +1,28 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + let result2; + navigation.onnavigate = t.step_func(e => { + if (e.info == 1) { + result2 = navigation.navigate("#2", { info: 2 }); + assert_true(e.defaultPrevented); + } + }); + + const result1 = navigation.navigate("#1", { info: 1 }); + + assert_equals(navigation.entries().length, 2); + assert_array_equals(navigation.entries().map(e => (new URL(e.url)).hash), ["", "#2"]); + + await assertBothRejectDOM(t, result1, "AbortError"); + await assertBothFulfill(t, result2, navigation.currentEntry); +}, "if navigate() is called inside onnavigate, the previous navigation and navigate event are cancelled"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-interrupted.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-interrupted.html new file mode 100644 index 0000000000..2c928254e0 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-interrupted.html @@ -0,0 +1,21 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + const result1 = navigation.navigate("#1"); + const result2 = navigation.navigate("#2"); + + assert_equals(navigation.entries().length, 3); + assert_array_equals(navigation.entries().map(e => (new URL(e.url)).hash), ["", "#1", "#2"]); + + await assertCommittedFulfillsFinishedRejectsDOM(t, result1, navigation.entries()[1], "AbortError"); + await assertBothFulfill(t, result2, navigation.currentEntry); +}, "interrupted navigate() promises"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-invalid-url.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-invalid-url.html new file mode 100644 index 0000000000..5b5b442c91 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-invalid-url.html @@ -0,0 +1,10 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + await assertBothRejectDOM(t, navigation.navigate("https://example.com\u0000mozilla.org"), "SyntaxError"); +}, "navigate() with an invalid URL"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-opaque-origin.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-opaque-origin.html new file mode 100644 index 0000000000..51500eb8d8 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-opaque-origin.html @@ -0,0 +1,9 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<iframe id="i" sandbox="allow-scripts" src="resources/navigate-opaque-origin-page.html"></iframe> + +<script> +fetch_tests_from_window(i.contentWindow); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-preventDefault.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-preventDefault.html new file mode 100644 index 0000000000..6257c5a03d --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-preventDefault.html @@ -0,0 +1,12 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + navigation.onnavigate = e => e.preventDefault(); + + await assertBothRejectDOM(t, navigation.navigate("#"), "AbortError"); +}, "navigate() when the onnavigate handler calls preventDefault()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-push-initial-about-blank.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-push-initial-about-blank.html new file mode 100644 index 0000000000..9d47c8d0b5 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-push-initial-about-blank.html @@ -0,0 +1,21 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<iframe id="i"></iframe> +<script> +promise_test(async t => { + // Wait for after the load event so that we are definitely testing the initial + // about:blank-ness as the cause of the rejections. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + i.contentWindow.navigation.onnavigate = t.unreached_func("onnavigate should not be called"); + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func("onnavigatesuccess should not be called"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("onnavigateerror should not be called"); + + const result = i.contentWindow.navigation.navigate("#1", { history: "push" }); + await assertBothRejectDOM(t, result, "NotSupportedError", i.contentWindow); +}, "navigate() with history: 'push' in initial about:blank document"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-push-javascript-url.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-push-javascript-url.html new file mode 100644 index 0000000000..e41e06a78d --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-push-javascript-url.html @@ -0,0 +1,19 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + // Wait for after the load event so that we are definitely testing the + // javascript: URL as the cause of the rejections. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + navigation.onnavigate = t.unreached_func("onnavigate should not be called"); + navigation.onnavigatesuccess = t.unreached_func("onnavigatesuccess should not be called"); + navigation.onnavigateerror = t.unreached_func("onnavigateerror should not be called"); + + const result = navigation.navigate("javascript:'foo'", { history: "push" }); + await assertBothRejectDOM(t, result, "NotSupportedError"); +}, "navigate() to a javascript: URL"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-beforeunload-unserializablestate.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-beforeunload-unserializablestate.html new file mode 100644 index 0000000000..878280a56e --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-beforeunload-unserializablestate.html @@ -0,0 +1,31 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + let navigateEventCount = 0; + i.contentWindow.navigation.onnavigate = () => navigateEventCount++; + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func('onnavigatesuccess should not be called'); + i.contentWindow.navigation.onnavigateerror = t.unreached_func('onnavigateerror should not be called'); + + let assertionPromise; + // The iframe does not have sticky activation, so per + // https://html.spec.whatwg.org/#prompt-to-unload-a-document, no prompt is + // shown and the navigation will proceed. + i.contentWindow.onbeforeunload = t.step_func(() => { + assertionPromise = assertBothRejectDOM(t, i.contentWindow.navigation.navigate("/common/blank.html?1", { state: document.body }), "DataCloneError", i.contentWindow); + }); + i.contentWindow.navigation.navigate("?1"); + + assert_not_equals(assertionPromise, undefined); + await assertionPromise; + + assert_equals(navigateEventCount, 1); +}, `navigate() with an unserializable state inside onbeforeunload "DataCloneError", not "InvalidStateError"`); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-detached-unserializablestate.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-detached-unserializablestate.html new file mode 100644 index 0000000000..2c6f9a28d4 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-detached-unserializablestate.html @@ -0,0 +1,20 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + const iWindow = i.contentWindow; + const iDOMException = iWindow.DOMException; + + i.remove(); + + iWindow.navigation.onnavigate = t.unreached_func("onnavigate"); + + await assertBothRejectDOM(t, iWindow.navigation.navigate("https://example.com/", { state: document.body }), "DataCloneError", iWindow, iDOMException); +}, `navigate() with unserializable state in a detached iframe throws "DataCloneError", not "InvalidStateError"`); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-invalidurl-beforeunload.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-invalidurl-beforeunload.html new file mode 100644 index 0000000000..4873e85a2f --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-invalidurl-beforeunload.html @@ -0,0 +1,31 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + let navigateEventCount = 0; + i.contentWindow.navigation.onnavigate = () => navigateEventCount++; + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func("onnavigatesuccess should not be called"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("onnavigateerror should not be called"); + + let assertionPromise; + // The iframe does not have sticky activation, so per + // https://html.spec.whatwg.org/#prompt-to-unload-a-document, no prompt is + // shown and the navigation will proceed. + i.contentWindow.onbeforeunload = t.step_func(() => { + assertionPromise = assertBothRejectDOM(t, i.contentWindow.navigation.navigate("https://example.com\u0000mozilla.org"), "SyntaxError", i.contentWindow); + }); + i.contentWindow.navigation.navigate("?1"); + + assert_not_equals(assertionPromise, undefined); + await assertionPromise; + + assert_equals(navigateEventCount, 1); +}, `navigate() with an invalid URL inside onbeforeunload throws "SyntaxError", not "InvalidStateError"`); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-invalidurl-detached.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-invalidurl-detached.html new file mode 100644 index 0000000000..2250a54114 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-invalidurl-detached.html @@ -0,0 +1,20 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + const iWindow = i.contentWindow; + const iDOMException = iWindow.DOMException; + + i.remove(); + + iWindow.navigation.onnavigate = t.unreached_func("onnavigate"); + + await assertBothRejectDOM(t, iWindow.navigation.navigate("https://example.com\u0000mozilla.org"), "SyntaxError", iWindow, iDOMException); +}, `navigate() with an invalid URL in a detached iframe throws "SyntaxError", not "InvalidStateError"`); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-invalidurl-unload.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-invalidurl-unload.html new file mode 100644 index 0000000000..d778dd662c --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-invalidurl-unload.html @@ -0,0 +1,27 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + let navigateEventCount = 0; + i.contentWindow.navigation.onnavigate = () => navigateEventCount++; + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func("onnavigatesuccess should not be called"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("onnavigateerror should not be called"); + + i.contentWindow.navigation.navigate("?1"); + + await new Promise(resolve => { + i.contentWindow.onunload = t.step_func(async () => { + await assertBothRejectDOM(t, i.contentWindow.navigation.navigate("https://example.com\u0000mozilla.org"), "SyntaxError", i.contentWindow); + assert_equals(navigateEventCount, 1); + resolve(); + }); + }); +}, `navigate() with an invalid URL inside onunload throws "SyntaxError", not "InvalidStateError"`); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-invalidurl-unserializablestate.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-invalidurl-unserializablestate.html new file mode 100644 index 0000000000..07e194ca41 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-invalidurl-unserializablestate.html @@ -0,0 +1,12 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + navigation.onnavigate = t.unreached_func("onnavigate"); + + await assertBothRejectDOM(t, navigation.navigate("https://example.com\u0000mozilla.org", { state: document.body }), "SyntaxError"); +}, `navigate() with an invalid URL and unserializable state throws "SyntaxError", not "DataCloneError"`); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-unload-unserializablestate.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-unload-unserializablestate.html new file mode 100644 index 0000000000..417dd27174 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-unload-unserializablestate.html @@ -0,0 +1,27 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + let navigateEventCount = 0; + i.contentWindow.navigation.onnavigate = () => navigateEventCount++; + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func("onnavigatesuccess should not be called"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("onnavigateerror should not be called"); + + i.contentWindow.navigation.navigate("?1"); + + await new Promise(resolve => { + i.contentWindow.onunload = t.step_func(async () => { + await assertBothRejectDOM(t, i.contentWindow.navigation.navigate("?2", { state: document.body }), "DataCloneError", i.contentWindow); + assert_equals(navigateEventCount, 1); + resolve(); + }); + }); +}, `navigate() with an unserializable state inside onunload throws "DataCloneError", not "InvalidStateError"`); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-unload.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-unload.html new file mode 100644 index 0000000000..fbc1fde6e9 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-unload.html @@ -0,0 +1,27 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + let navigateEventCount = 0; + i.contentWindow.navigation.onnavigate = () => navigateEventCount++; + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func("onnavigatesuccess should not be called"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("onnavigateerror should not be called"); + + i.contentWindow.navigation.navigate("?1"); + + await new Promise(resolve => { + i.contentWindow.onunload = t.step_func(async () => { + await assertBothRejectDOM(t, i.contentWindow.navigation.navigate("?2"), "InvalidStateError", i.contentWindow); + assert_equals(navigateEventCount, 1); + resolve(); + }); + }); +}, `navigate() inside onunload`); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-unserializable-state.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-unserializable-state.html new file mode 100644 index 0000000000..36464ec3c5 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-unserializable-state.html @@ -0,0 +1,21 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + await assertBothRejectDOM(t, navigation.navigate("#1", { state: new WritableStream() }), "DataCloneError"); + assert_equals(navigation.currentEntry.getState(), undefined); + assert_equals(location.hash, ""); +}, "navigate() with an unserializable state (WritableStream)"); + +promise_test(async t => { + // See https://github.com/whatwg/html/issues/5380 for why not `new SharedArrayBuffer()` + const buffer = new WebAssembly.Memory({ shared:true, initial:1, maximum:1 }).buffer; + + await assertBothRejectDOM(t, navigation.navigate("#2", { state: buffer }), "DataCloneError"); + assert_equals(navigation.currentEntry.getState(), undefined); + assert_equals(location.hash, ""); +}, "navigate() with an unserializable state (SharedArrayBuffer)"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate.html new file mode 100644 index 0000000000..34ff84f0e9 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate.html @@ -0,0 +1,12 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + const result = navigation.navigate("#1"); + await assertBothFulfill(t, result, navigation.currentEntry); + assert_equals((new URL(navigation.currentEntry.url)).hash, "#1"); +}, "navigate() promises"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-already-detached.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-already-detached.html new file mode 100644 index 0000000000..9b0780c4a9 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-already-detached.html @@ -0,0 +1,19 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + const iWindow = i.contentWindow; + const iDOMException = iWindow.DOMException; + + i.remove(); + + await assertBothRejectDOM(t, iWindow.navigation.reload(), "InvalidStateError", iWindow, iDOMException); +}, "reload() in a detached window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-beforeunload.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-beforeunload.html new file mode 100644 index 0000000000..05338acc5e --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-beforeunload.html @@ -0,0 +1,31 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + let navigateEventCount = 0; + i.contentWindow.navigation.onnavigate = () => navigateEventCount++; + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func("onnavigatesuccess should not be called"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("onnavigateerror should not be called"); + + let assertionPromise; + // The iframe does not have sticky activation, so per + // https://html.spec.whatwg.org/#prompt-to-unload-a-document, no prompt is + // shown and the navigation will proceed. + i.contentWindow.onbeforeunload = t.step_func(() => { + assertionPromise = assertBothRejectDOM(t, i.contentWindow.navigation.reload(), "InvalidStateError", i.contentWindow); + }); + i.contentWindow.navigation.navigate("?1"); + + assert_not_equals(assertionPromise, undefined); + await assertionPromise; + + assert_equals(navigateEventCount, 1); +}, "reload() inside onbeforeunload"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-detach-in-onnavigate.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-detach-in-onnavigate.html new file mode 100644 index 0000000000..621bb598f6 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-detach-in-onnavigate.html @@ -0,0 +1,18 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + const iWindow = i.contentWindow; + const iDOMException = iWindow.DOMException; + i.contentWindow.navigation.onnavigate = () => i.remove(); + + await assertBothRejectDOM(t, i.contentWindow.navigation.reload(), "AbortError", iWindow, iDOMException); +}, "reload() promise rejections when detaching an iframe inside onnavigate"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-detach-in-serialization.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-detach-in-serialization.html new file mode 100644 index 0000000000..d958d9cc84 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-detach-in-serialization.html @@ -0,0 +1,24 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + const iWindow = i.contentWindow; + const iDOMException = iWindow.DOMException; + + const trappedState = { + get prop() { + i.remove(); + return "whatever"; + } + }; + + await assertBothRejectDOM(t, i.contentWindow.navigation.reload({ state: trappedState }), "InvalidStateError", iWindow, iDOMException); +}, "reload() promise rejections when detaching an iframe inside state serialization"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-initial-about-blank.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-initial-about-blank.html new file mode 100644 index 0000000000..9e717fee48 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-initial-about-blank.html @@ -0,0 +1,20 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<body> +<script> +promise_test(async t => { + let i = document.createElement("iframe"); + document.body.append(i); + + i.contentWindow.navigation.onnavigate = t.unreached_func("onnavigate should not be called"); + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func("onnavigatesuccess should not be called"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("onnavigateerror should not be called"); + + const result = i.contentWindow.navigation.reload(); + assertNeverSettles(t, result, i.contentWindow); + await new Promise(resolve => t.step_timeout(resolve, 10)); +}, "reload() in initial about:blank document"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-intercept-rejected.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-intercept-rejected.html new file mode 100644 index 0000000000..93cd75c2b9 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-intercept-rejected.html @@ -0,0 +1,17 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + const err = new Error("boo!"); + const promise = Promise.reject(err); + promise.catch(() => {}); // prevent unhandled rejection testharness.js errors + navigation.onnavigate = e => e.intercept({ handler: () => promise }); + + const result = navigation.reload(); + + await assertCommittedFulfillsFinishedRejectsExactly(t, result, navigation.currentEntry, err); +}, "reload() and intercept() with a rejected promise"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-intercept.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-intercept.html new file mode 100644 index 0000000000..92f1636a6e --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-intercept.html @@ -0,0 +1,14 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + navigation.onnavigate = e => e.intercept({ handler: () => Promise.resolve({ abc: 'def' }) }); + + const result = navigation.reload(); + + await assertBothFulfill(t, result, navigation.currentEntry); +}, "reload() and intercept() with a fulfilled promise"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-preventDefault.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-preventDefault.html new file mode 100644 index 0000000000..747044b60f --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-preventDefault.html @@ -0,0 +1,12 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + navigation.onnavigate = e => e.preventDefault(); + + await assertBothRejectDOM(t, navigation.reload(), "AbortError"); +}, "reload() when the onnavigate handler calls preventDefault()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-rejection-order-beforeunload-unserializablestate.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-rejection-order-beforeunload-unserializablestate.html new file mode 100644 index 0000000000..950d665218 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-rejection-order-beforeunload-unserializablestate.html @@ -0,0 +1,31 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + let navigateEventCount = 0; + i.contentWindow.navigation.onnavigate = () => navigateEventCount++; + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func('onnavigatesuccess should not be called'); + i.contentWindow.navigation.onnavigateerror = t.unreached_func('onnavigateerror should not be called'); + + let assertionPromise; + // The iframe does not have sticky activation, so per + // https://html.spec.whatwg.org/#prompt-to-unload-a-document, no prompt is + // shown and the navigation will proceed. + i.contentWindow.onbeforeunload = t.step_func(() => { + assertionPromise = assertBothRejectDOM(t, i.contentWindow.navigation.reload({ state: document.body }), "DataCloneError", i.contentWindow); + }); + i.contentWindow.navigation.navigate("?1"); + + assert_not_equals(assertionPromise, undefined); + await assertionPromise; + + assert_equals(navigateEventCount, 1); +}, `reload() with an unserializable state inside onbeforeunload throws "DataCloneError", not "InvalidStateError"`); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-rejection-order-detached-unserializablestate.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-rejection-order-detached-unserializablestate.html new file mode 100644 index 0000000000..9206f18e6c --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-rejection-order-detached-unserializablestate.html @@ -0,0 +1,20 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + const iWindow = i.contentWindow; + const iDOMException = iWindow.DOMException; + + i.remove(); + + iWindow.navigation.onnavigate = t.unreached_func("onnavigate"); + + await assertBothRejectDOM(t, iWindow.navigation.reload({ state: document.body }), "DataCloneError", iWindow, iDOMException); +}, `reload() with unserializable state in a detached iframe throws "DataCloneError", not "InvalidStateError"`); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-rejection-order-unload-unserializablestate.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-rejection-order-unload-unserializablestate.html new file mode 100644 index 0000000000..9e7edcbba4 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-rejection-order-unload-unserializablestate.html @@ -0,0 +1,32 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + let navigateEventCount = 0; + i.contentWindow.navigation.onnavigate = () => navigateEventCount++; + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func("onnavigatesuccess should not be called"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("onnavigateerror should not be called"); + + i.contentWindow.navigation.navigate("?1"); + + let assertionPromise; + await new Promise(resolve => { + i.contentWindow.onunload = t.step_func(() => { + assertionPromise = assertBothRejectDOM(t, i.contentWindow.navigation.reload({ state: document.body }), "DataCloneError", i.contentWindow); + assert_equals(navigateEventCount, 1); + resolve(); + }); + }); + + assert_not_equals(assertionPromise, undefined); + await assertionPromise; + +}, `reload() with an unserializable state inside onunload throws "DataCloneError", not "InvalidStateError"`); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-unload.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-unload.html new file mode 100644 index 0000000000..1906659d1d --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-unload.html @@ -0,0 +1,27 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + let navigateEventCount = 0; + i.contentWindow.navigation.onnavigate = () => navigateEventCount++; + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func("onnavigatesuccess should not be called"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("onnavigateerror should not be called"); + + i.contentWindow.navigation.navigate("?1"); + + await new Promise(resolve => { + i.contentWindow.onunload = t.step_func(async () => { + await assertBothRejectDOM(t, i.contentWindow.navigation.reload(), "InvalidStateError", i.contentWindow); + assert_equals(navigateEventCount, 1); + resolve(); + }); + }); +}, `reload() inside onunload`); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-unserializable-state.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-unserializable-state.html new file mode 100644 index 0000000000..76fa870558 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-unserializable-state.html @@ -0,0 +1,21 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + await assertBothRejectDOM(t, navigation.reload({ state: new WritableStream() }), "DataCloneError"); + assert_equals(navigation.currentEntry.getState(), undefined); + assert_equals(location.hash, ""); +}, "reload() with an unserializable state (WritableStream)"); + +promise_test(async t => { + // See https://github.com/whatwg/html/issues/5380 for why not `new SharedArrayBuffer()` + const buffer = new WebAssembly.Memory({ shared:true, initial:1, maximum:1 }).buffer; + + await assertBothRejectDOM(t, navigation.reload({ state: buffer }), "DataCloneError"); + assert_equals(navigation.currentEntry.getState(), undefined); + assert_equals(location.hash, ""); +}, "reload() with an unserializable state (SharedArrayBuffer)"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload.html new file mode 100644 index 0000000000..388f0d94b5 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload.html @@ -0,0 +1,16 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + const result = i.contentWindow.navigation.reload(); + assertNeverSettles(t, result, i.contentWindow); + + await new Promise(resolve => i.onload = () => t.step_timeout(resolve, 0)); +}, "reload() promises never settle (without intercept())"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/resources/204-205-download-on-second-visit.py b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/resources/204-205-download-on-second-visit.py new file mode 100644 index 0000000000..c18b0dec3d --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/resources/204-205-download-on-second-visit.py @@ -0,0 +1,24 @@ +def main(request, response): + key = request.GET[b"id"] + + # If hit with a POST with ?action=X, store X in the stash + if request.method == "POST": + action = request.GET[b"action"] + request.server.stash.put(key, action) + + return (204, [], "") + + # If hit with a GET, either return a normal initial page, or the abnormal requested response + elif request.method == "GET": + action = request.server.stash.take(key) + + if action is None: + return (200, [("Content-Type", "text/html"), ("Cache-Control", "no-store")], "initial page") + if action == b"204": + return (204, [], "") + if action == b"205": + return (205, [], "") + if action == b"download": + return (200, [("Content-Type", "text/plain"), ("Content-Disposition", "attachment")], "some text to download") + + return (400, [], "") diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/resources/back-forward-opaque-origin-page.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/resources/back-forward-opaque-origin-page.html new file mode 100644 index 0000000000..ec63363952 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/resources/back-forward-opaque-origin-page.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="helpers.js"></script> +<!-- Put this page in a sandbox to give it an opaque origin --> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + navigation.onnavigate = t.unreached_func("onnavigate should not be called"); + navigation.onnavigatesuccess = t.unreached_func("onnavigatesuccess should not be called"); + navigation.onnavigateerror = t.unreached_func("onnavigateerror should not be called"); + + location.hash = "#1"; + await new Promise(resolve => window.onhashchange = resolve); + location.hash = "#2"; + await new Promise(resolve => window.onhashchange = resolve); + history.back(); + await new Promise(resolve => window.onhashchange = resolve); + + assert_equals(location.hash, "#1"); + + await assertBothRejectDOM(t, navigation.back(), "InvalidStateError"); + await assertBothRejectDOM(t, navigation.forward(), "InvalidStateError"); +}, "navigation.back()/forward() in an opaque origin iframe"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/resources/helpers.js b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/resources/helpers.js new file mode 100644 index 0000000000..63d706ed28 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/resources/helpers.js @@ -0,0 +1,127 @@ +window.assertReturnValue = (result, w = window) => { + assert_equals(Object.getPrototypeOf(result), w.Object.prototype, "result object must be from the right realm"); + assert_array_equals(Reflect.ownKeys(result), ["committed", "finished"]); + assert_true(result.committed instanceof w.Promise); + assert_true(result.finished instanceof w.Promise); + assert_not_equals(result.committed, result.finished); +}; + +window.assertNeverSettles = (t, result, w = window) => { + assertReturnValue(result, w); + result.committed.then( + t.unreached_func("committed must not fulfill"), + t.unreached_func("committed must not reject") + ); + + result.finished.then( + t.unreached_func("finished must not fulfill"), + t.unreached_func("finished must not reject") + ); +}; + +window.assertBothFulfillEntryNotAvailable = async (t, result, w = window) => { + assertReturnValue(result, w); + + // Don't use await here so that we can catch out-of-order settlements. + let committedValue; + result.committed.then( + t.step_func(v => { committedValue = v;}), + t.unreached_func("committed must not reject") + ); + + const finishedValue = await result.finished; + + assert_not_equals(committedValue, undefined, "committed must fulfill before finished"); + assert_equals(finishedValue, committedValue, "committed and finished must fulfill with the same value"); + assert_true(finishedValue instanceof w.NavigationHistoryEntry, "fulfillment value must be a NavigationHistoryEntry"); +}; + +window.assertBothFulfill = async (t, result, expected, w = window) => { + assertReturnValue(result, w); + + // Don't use await here so that we can catch out-of-order settlements. + let committedValue; + result.committed.then( + t.step_func(v => { committedValue = v; }), + t.unreached_func("committed must not reject") + ); + + const finishedValue = await result.finished; + + assert_not_equals(committedValue, undefined, "committed must fulfill before finished"); + assert_equals(finishedValue, committedValue, "committed and finished must fulfill with the same value"); + assert_true(finishedValue instanceof w.NavigationHistoryEntry, "fulfillment value must be a NavigationHistoryEntry"); + assert_equals(finishedValue, expected); +}; + +window.assertCommittedFulfillsFinishedRejectsExactly = async (t, result, expectedEntry, expectedRejection, w = window) => { + assertReturnValue(result, w); + + // Don't use await here so that we can catch out-of-order settlements. + let committedValue; + result.committed.then( + t.step_func(v => { committedValue = v; }), + t.unreached_func("committed must not reject") + ); + + await promise_rejects_exactly(t, expectedRejection, result.finished); + + assert_not_equals(committedValue, undefined, "committed must fulfill before finished rejects"); + assert_true(committedValue instanceof w.NavigationHistoryEntry, "fulfillment value must be a NavigationHistoryEntry"); + assert_equals(committedValue, expectedEntry); +}; + +window.assertCommittedFulfillsFinishedRejectsDOM = async (t, result, expectedEntry, expectedDOMExceptionCode, w = window, domExceptionConstructor = w.DOMException, navigationHistoryEntryConstuctor = w.NavigationHistoryEntry) => { + assertReturnValue(result, w); + + let committedValue; + result.committed.then( + t.step_func(v => { committedValue = v; }), + t.unreached_func("committed must not reject") + ); + + await promise_rejects_dom(t, expectedDOMExceptionCode, domExceptionConstructor, result.finished); + + assert_not_equals(committedValue, undefined, "committed must fulfill before finished rejects"); + assert_true(committedValue instanceof navigationHistoryEntryConstuctor, "fulfillment value must be an NavigationHistoryEntry"); + assert_equals(committedValue, expectedEntry); +}; + +window.assertBothRejectExactly = async (t, result, expectedRejection, w = window) => { + assertReturnValue(result, w); + + let committedReason, finishedReason; + await Promise.all([ + result.committed.then( + t.unreached_func("committed must not fulfill"), + t.step_func(r => { committedReason = r; }) + ), + result.finished.then( + t.unreached_func("finished must not fulfill"), + t.step_func(r => { finishedReason = r; }) + ) + ]); + + assert_equals(committedReason, finishedReason, "committed and finished must reject with the same value"); + assert_equals(expectedRejection, committedReason); +}; + +window.assertBothRejectDOM = async (t, result, expectedDOMExceptionCode, w = window, domExceptionConstructor = w.DOMException) => { + assertReturnValue(result, w); + + // Don't use await here so that we can catch out-of-order settlements. + let committedReason, finishedReason; + await Promise.all([ + result.committed.then( + t.unreached_func("committed must not fulfill"), + t.step_func(r => { committedReason = r; }) + ), + result.finished.then( + t.unreached_func("finished must not fulfill"), + t.step_func(r => { finishedReason = r; }) + ) + ]); + + assert_equals(committedReason, finishedReason, "committed and finished must reject with the same value"); + assert_throws_dom(expectedDOMExceptionCode, domExceptionConstructor, () => { throw committedReason; }); +}; diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/resources/navigate-opaque-origin-page.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/resources/navigate-opaque-origin-page.html new file mode 100644 index 0000000000..831eefdb61 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/resources/navigate-opaque-origin-page.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="helpers.js"></script> +<!-- Put this page in a sandbox to give it an opaque origin --> + +<script> +promise_test(async t => { + navigation.onnavigate = t.unreached_func("onnavigate should not be called"); + navigation.onnavigatesuccess = t.unreached_func("onnavigatesuccess should not be called"); + navigation.onnavigateerror = t.unreached_func("onnavigateerror should not be called"); + + const result = navigation.navigate("#1"); + assertNeverSettles(t, result); + await new Promise(resolve => t.step_timeout(resolve, 10)); +}, "navigation.navigate() in an opaque origin iframe"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-already-detached.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-already-detached.html new file mode 100644 index 0000000000..b974393df6 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-already-detached.html @@ -0,0 +1,20 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + const iWindow = i.contentWindow; + const iDOMException = iWindow.DOMException; + const key = iWindow.navigation.currentEntry.key; + + i.remove(); + + await assertBothRejectDOM(t, iWindow.navigation.traverseTo(key), "InvalidStateError", iWindow, iDOMException); +}, "traverseTo() in a detached window"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-beforeunload.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-beforeunload.html new file mode 100644 index 0000000000..3b2722235e --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-beforeunload.html @@ -0,0 +1,31 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + let navigateEventCount = 0; + i.contentWindow.navigation.onnavigate = () => navigateEventCount++; + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func("onnavigatesuccess should not be called"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("onnavigateerror should not be called"); + + let assertionPromise; + // The iframe does not have sticky activation, so per + // https://html.spec.whatwg.org/#prompt-to-unload-a-document, no prompt is + // shown and the navigation will proceed. + i.contentWindow.onbeforeunload = t.step_func(() => { + assertionPromise = assertBothRejectDOM(t, i.contentWindow.navigation.traverseTo(i.contentWindow.navigation.currentEntry.key), "InvalidStateError", i.contentWindow); + }); + i.contentWindow.navigation.navigate("?1"); + + assert_not_equals(assertionPromise, undefined); + await assertionPromise; + + assert_equals(navigateEventCount, 1); +}, "traverseTo() inside onbeforeunload"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-cross-document-preventDefault.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-cross-document-preventDefault.html new file mode 100644 index 0000000000..09c91ee647 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-cross-document-preventDefault.html @@ -0,0 +1,29 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + assert_equals(i.contentWindow.navigation.entries().length, 1); + let key = i.contentWindow.navigation.currentEntry.key; + + i.contentWindow.navigation.navigate("?1"); + await new Promise(resolve => i.onload = resolve); + + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry, i.contentWindow.navigation.entries()[1]); + + // This will be a noop, because navigate events are uncancelable for traversals. + i.contentWindow.navigation.onnavigate = e => e.preventDefault(); + + assertNeverSettles(t, i.contentWindow.navigation.traverseTo(key), i.contentWindow); + await new Promise(resolve => i.onload = () => t.step_timeout(resolve, 0)); +}, "traverseTo() promise never settle when preventDefault()ing the navigate event (cross-document)"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-current.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-current.html new file mode 100644 index 0000000000..212fe992cf --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-current.html @@ -0,0 +1,17 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + assert_equals(navigation.entries().length, 1); + const entry = navigation.currentEntry; + + const result = navigation.traverseTo(navigation.currentEntry.key); + await assertBothFulfill(t, result, entry); + + assert_equals(navigation.entries().length, 1); + assert_equals(navigation.currentEntry, entry); +}, "traverseTo() with current key"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-detach-cross-document-before-navigate-event.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-detach-cross-document-before-navigate-event.html new file mode 100644 index 0000000000..ed319f6223 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-detach-cross-document-before-navigate-event.html @@ -0,0 +1,32 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + assert_equals(i.contentWindow.navigation.entries().length, 1); + let key = i.contentWindow.navigation.currentEntry.key; + + i.contentWindow.navigation.navigate("?1"); + await new Promise(resolve => i.onload = resolve); + + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry, i.contentWindow.navigation.entries()[1]); + + const iWindow = i.contentWindow; + const iDOMException = iWindow.DOMException; + i.contentWindow.navigation.onnavigate = t.unreached_func("navigate should not fire"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("navigateerror should not fire"); + + let promises = i.contentWindow.navigation.traverseTo(key); + i.remove(); + await assertBothRejectDOM(t, promises, "AbortError", iWindow, iDOMException); +}, "traverseTo() promise rejections when detaching an iframe before onnavigate (cross-document)"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-detach-cross-document.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-detach-cross-document.html new file mode 100644 index 0000000000..8784313b70 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-detach-cross-document.html @@ -0,0 +1,29 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + assert_equals(i.contentWindow.navigation.entries().length, 1); + let key = i.contentWindow.navigation.currentEntry.key; + + i.contentWindow.navigation.navigate("?1"); + await new Promise(resolve => i.onload = resolve); + + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry, i.contentWindow.navigation.entries()[1]); + + const iWindow = i.contentWindow; + const iDOMException = iWindow.DOMException; + i.contentWindow.navigation.onnavigate = () => i.remove(); + + await assertBothRejectDOM(t, i.contentWindow.navigation.traverseTo(key), "AbortError", iWindow, iDOMException); +}, "traverseTo() promise rejections when detaching an iframe inside onnavigate (cross-document)"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-detach-same-document-before-navigate-event.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-detach-same-document-before-navigate-event.html new file mode 100644 index 0000000000..592ac6f199 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-detach-same-document-before-navigate-event.html @@ -0,0 +1,31 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + assert_equals(i.contentWindow.navigation.entries().length, 1); + let key = i.contentWindow.navigation.currentEntry.key; + + await i.contentWindow.navigation.navigate("#1").committed; + + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry, i.contentWindow.navigation.entries()[1]); + + const iWindow = i.contentWindow; + const iDOMException = iWindow.DOMException; + i.contentWindow.navigation.onnavigate = t.unreached_func("navigate should not fire"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("navigateerror should not fire"); + + let promises = i.contentWindow.navigation.traverseTo(key); + i.remove(); + await assertBothRejectDOM(t, promises, "AbortError", iWindow, iDOMException); +}, "traverseTo() promise rejections when detaching an iframe before onnavigate (same-document)"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-detach-same-document.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-detach-same-document.html new file mode 100644 index 0000000000..b0308b8df8 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-detach-same-document.html @@ -0,0 +1,28 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + assert_equals(i.contentWindow.navigation.entries().length, 1); + let key = i.contentWindow.navigation.currentEntry.key; + + await i.contentWindow.navigation.navigate("#1").committed; + + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry, i.contentWindow.navigation.entries()[1]); + + const iWindow = i.contentWindow; + const iDOMException = iWindow.DOMException; + i.contentWindow.navigation.onnavigate = () => i.remove(); + + await assertBothRejectDOM(t, i.contentWindow.navigation.traverseTo(key), "AbortError", iWindow, iDOMException); +}, "traverseTo() promise rejections when detaching an iframe inside onnavigate (same-document)"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-intercept-rejected.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-intercept-rejected.html new file mode 100644 index 0000000000..f934eae5d4 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-intercept-rejected.html @@ -0,0 +1,30 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + const key0 = navigation.currentEntry.key; + + location.href = "#1"; + + assert_equals(navigation.entries().length, 2); + const [entry0, entry1] = navigation.entries(); + assert_equals((new URL(entry0.url)).hash, ""); + assert_equals((new URL(entry1.url)).hash, "#1"); + + const err = new Error("boo!"); + const promise = Promise.reject(err); + promise.catch(() => {}); // prevent unhandled rejection testharness.js errors + navigation.onnavigate = e => e.intercept({ handler: () => promise }); + + const result = navigation.traverseTo(key0); + await assertCommittedFulfillsFinishedRejectsExactly(t, result, entry0, err); + assert_equals(navigation.currentEntry, entry0); +}, "traverseTo() promise rejection with rejected intercept()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-intercept.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-intercept.html new file mode 100644 index 0000000000..a4cb4e711d --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-intercept.html @@ -0,0 +1,27 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + const key0 = navigation.currentEntry.key; + + location.href = "#1"; + + assert_equals(navigation.entries().length, 2); + const [entry0, entry1] = navigation.entries(); + assert_equals((new URL(entry0.url)).hash, ""); + assert_equals((new URL(entry1.url)).hash, "#1"); + + navigation.onnavigate = e => e.intercept({ handler: () => Promise.resolve({ abc: "def" }) }); + + const result = navigation.traverseTo(key0); + await assertBothFulfill(t, result, entry0); + assert_equals(navigation.currentEntry, entry0); +}, "traverseTo() and intercept() with a fulfilled promise"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-invalid-key.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-invalid-key.html new file mode 100644 index 0000000000..42be40bfa7 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-invalid-key.html @@ -0,0 +1,10 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + await assertBothRejectDOM(t, navigation.traverseTo("not a real key"), "InvalidStateError"); +}, "traverseTo() with invalid key"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-repeated.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-repeated.html new file mode 100644 index 0000000000..d1754d6729 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-repeated.html @@ -0,0 +1,24 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + const key = navigation.currentEntry.key; + const entry = navigation.currentEntry; + await navigation.navigate("#1").committed; + + const result1 = navigation.traverseTo(key); + const result2 = navigation.traverseTo(key); + + await assertBothFulfill(t, result1, entry); + assert_not_equals(result1, result2); + assert_equals(result2.committed, result1.committed, "committed promises must be equal"); + assert_equals(result2.finished, result1.finished, "finished promises must be equal"); +}, "Repeated navigation.traverseTo() with the same key"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo.html b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo.html new file mode 100644 index 0000000000..8e00f5ba91 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo.html @@ -0,0 +1,25 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/helpers.js"></script> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + const key0 = navigation.currentEntry.key; + + location.href = "#1"; + + assert_equals(navigation.entries().length, 2); + const [entry0, entry1] = navigation.entries(); + assert_equals((new URL(entry0.url)).hash, ""); + assert_equals((new URL(entry1.url)).hash, "#1"); + + const result = navigation.traverseTo(key0); + await assertBothFulfill(t, result, entry0); + assert_equals(navigation.currentEntry, entry0); +}, "traverseTo() promises"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/sandboxing-back-parent.html b/testing/web-platform/tests/navigation-api/navigation-methods/sandboxing-back-parent.html new file mode 100644 index 0000000000..6eee3f917c --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/sandboxing-back-parent.html @@ -0,0 +1,38 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="return-value/resources/helpers.js"></script> +<iframe id="i" src="/common/blank.html?startI" sandbox="allow-scripts allow-same-origin"></iframe> + +<script> +// Intended setup: +// Step 0: +// - Parent: (current URL) +// - i: /common/blank.html?startI +// Step 1: +// - Parent: (current URL) +// - i: resources/navigation-back.html +// Step 2: +// - Parent: (current URL)#end +// - i: resources/navigation-back.html +// +// Then, calling navigation.back() in i will take is from step 2 to step 0, which would navigate the parent. +// That is not allowed, so the call to back() must reject. + +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + i.contentWindow.location.href = new URL("resources/navigation-back.html", location.href); + await new Promise(resolve => i.onload = resolve); + + location.hash = "#end"; + await new Promise(resolve => window.onhashchange = resolve); + + navigation.onnavigate = t.unreached_func("navigate must not fire"); + navigation.onnavigateerror = t.unreached_func("navigateerror must not fire"); + window.onpopstate = t.unreached_func("popstate must not fire"); + window.onhashchange = t.unreached_func("hashchange must not fire"); + + await assertBothRejectDOM(t, i.contentWindow.doNavigationBack(), "SecurityError", i.contentWindow); +}, "A sandboxed iframe cannot navigate its parent via its own navigation object by using back()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/sandboxing-back-sibling.html b/testing/web-platform/tests/navigation-api/navigation-methods/sandboxing-back-sibling.html new file mode 100644 index 0000000000..97c03ea0c2 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/sandboxing-back-sibling.html @@ -0,0 +1,44 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="return-value/resources/helpers.js"></script> +<iframe id="i" src="/common/blank.html?startI" sandbox="allow-same-origin"></iframe> +<iframe id="i2" src="/common/blank.html?startI2" sandbox="allow-scripts allow-same-origin"></iframe> + +<script> +// Intended setup: +// Step 0: +// - Parent: (current URL) +// - i: /common/blank.html?startI +// - i2: /common/blank.html?startI2 +// Step 1: +// - Parent: (current URL) +// - i: /common/blank.html?startI +// - i2: resources/navigation-back.html +// Step 2: +// - Parent: (current URL) +// - i: /common/blank.html?endI +// - i2: resources/navigation-back.html +// +// Then, calling navigation.back() in i2 will take is from step 2 to step 0, which would navigate i. +// That is not allowed, so the call to back() must reject. + +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + i2.contentWindow.location.href = new URL("resources/navigation-back.html", location.href); + await new Promise(resolve => i2.onload = resolve); + + i.contentWindow.location.href = "/common/blank.html?endI"; + await new Promise(resolve => i.onload = resolve); + + i.contentWindow.navigation.onnavigate = t.unreached_func("navigate must not fire"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("navigateerror must not fire"); + i.contentWindow.onbeforeunload = t.unreached_func("beforeunload must not fire"); + i.contentWindow.onunload = t.unreached_func("unload must not fire"); + i.contentWindow.onpagehide = t.unreached_func("pagehide must not fire"); + i.contentWindow.onpopstate = t.unreached_func("popstate must not fire"); + + await assertBothRejectDOM(t, i2.contentWindow.doNavigationBack(), "SecurityError", i2.contentWindow); +}, "A sandboxed iframe cannot navigate its sibling via its own navigation object by using back()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/sandboxing-navigate-parent.html b/testing/web-platform/tests/navigation-api/navigation-methods/sandboxing-navigate-parent.html new file mode 100644 index 0000000000..b7af32443f --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/sandboxing-navigate-parent.html @@ -0,0 +1,16 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="resources/navigate-parent.html" sandbox="allow-scripts allow-same-origin"></iframe> + +<script> +async_test(t => { + window.onload = t.step_func_done(() => { + i.contentWindow.navigateParent(); + + const destinationURL = (new URL("#2", location.href)).href; + assert_equals(location.href, destinationURL); + assert_equals(navigation.currentEntry.url, destinationURL); + }); +}, "A sandboxed iframe can use its sibling's navigation object to call navigate(), as long as allow-same-origin is present"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/sandboxing-navigate-sibling.html b/testing/web-platform/tests/navigation-api/navigation-methods/sandboxing-navigate-sibling.html new file mode 100644 index 0000000000..c8e76fc4a6 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/sandboxing-navigate-sibling.html @@ -0,0 +1,19 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html" sandbox="allow-same-origin"></iframe> +<iframe id="i2" src="resources/navigate-sibling.html" sandbox="allow-scripts allow-same-origin"></iframe> + +<script> +async_test(t => { + window.onload = t.step_func(() => { + i2.contentWindow.navigateSibling(); + + i.onload = t.step_func_done(() => { + const destinationURL = (new URL("/common/blank.html?2", location.href)).href; + assert_equals(i.contentWindow.location.href, destinationURL); + assert_equals(i.contentWindow.navigation.currentEntry.url, destinationURL); + }); + }); +}, "A sandboxed iframe can use its parent's navigation object to call navigate(), as long as allow-same-origin is present"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-after-adding-iframe.html b/testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-after-adding-iframe.html new file mode 100644 index 0000000000..5ab2820551 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-after-adding-iframe.html @@ -0,0 +1,34 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +async_test(t => { + let start_length = history.length; + + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + window.onload = () => t.step_timeout(() => { + navigation.navigate("#top"); + assert_equals(navigation.entries().length, 2); + + let i = document.createElement("iframe"); + i.src = "/common/blank.html"; + document.body.appendChild(i); + i.onload = () => t.step_timeout(() => { + assert_equals(i.contentWindow.navigation.entries().length, 1); + i.contentWindow.navigation.navigate("#fragment"); + assert_equals(i.contentWindow.navigation.entries().length, 2); + i.contentWindow.navigation.back().committed.then(t.step_func(() => { + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry.index, 0); + return i.contentWindow.navigation.forward().committed; + })).then(t.step_func_done(() => { + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry.index, 1); + })); + }, 0); + }, 0); +}, "navigation.traverseTo() should work in an iframe that is not present in all history entries"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-after-data-url.html b/testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-after-data-url.html new file mode 100644 index 0000000000..0b21a741d3 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-after-data-url.html @@ -0,0 +1,33 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +async_test(t => { + let i = document.createElement("iframe"); + i.src = "data:text/html,"; + document.body.appendChild(i); + let start_length = history.length; + + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + window.onload = () => t.step_timeout(() => { + i.contentWindow.location = "/common/blank.html"; + i.onload = () => t.step_timeout(() => { + assert_equals(history.length, start_length + 1); + assert_equals(i.contentWindow.navigation.entries().length, 1); + i.contentWindow.navigation.navigate("#fragment"); + assert_equals(i.contentWindow.navigation.entries().length, 2); + i.contentWindow.navigation.back().committed.then(t.step_func(() => { + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry.index, 0); + return i.contentWindow.navigation.forward().committed; + })).then(t.step_func_done(() => { + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry.index, 1); + })); + }, 0); + }, 0); +}, "navigation.traverseTo() should work in an iframe that started at a data: url"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-cross-document.html b/testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-cross-document.html new file mode 100644 index 0000000000..2540639bad --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-cross-document.html @@ -0,0 +1,41 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + window.onload = () => t.step_timeout(() => { + assert_equals(i.contentWindow.navigation.entries().length, 1); + let next_step = "goto"; + let key = i.contentWindow.navigation.currentEntry.key; + i.contentWindow.navigation.navigate("?1"); + i.onload = t.step_func(() => { + if (next_step == "goto") { + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry, i.contentWindow.navigation.entries()[1]); + i.contentWindow.navigation.traverseTo(key); + next_step = "forward"; + } else if (next_step == "forward") { + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(key, i.contentWindow.navigation.currentEntry.key); + assert_equals(i.contentWindow.navigation.currentEntry, i.contentWindow.navigation.entries()[0]); + assert_true(i.contentWindow.navigation.canGoForward); + i.contentWindow.navigation.forward(); + next_step = "back"; + } else if (next_step == "back") { + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry, i.contentWindow.navigation.entries()[1]); + assert_true(i.contentWindow.navigation.canGoBack); + i.contentWindow.navigation.back(); + next_step = "finish"; + } else if (next_step == "finish") { + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry, i.contentWindow.navigation.entries()[0]); + t.done(); + } + }); + }, 0); +}, "cross-document traverseTo(), back(), and forward()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-detach-between-navigate-and-navigatesuccess.html b/testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-detach-between-navigate-and-navigatesuccess.html new file mode 100644 index 0000000000..43bde9a103 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-detach-between-navigate-and-navigatesuccess.html @@ -0,0 +1,54 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="return-value/resources/helpers.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +async_test(t => { + window.onload = t.step_func(() => { + const iWindow = i.contentWindow; + const iDOMException = iWindow.DOMException; + const iNavigationHistoryEntry = iWindow.NavigationHistoryEntry; + + i.contentWindow.navigation.navigate("#1").finished.then(t.step_func(() => { + assert_equals(i.contentWindow.navigation.entries().length, 2); + let key = i.contentWindow.navigation.entries()[0].key; + + let onnavigateerror_called = false; + let result; + + i.contentWindow.navigation.onnavigate = t.step_func(e => { + // 1. The intercept handler runs. + // 2. "t.step_timeout(handlerRunResolve, 0)" executes handlerRunResolve in a macro task. + // 3. In the next microtask, the iframe is removed. + // 4. "t.step_timeout(resolve, 5)" executes and the intercept handler promise resolves. + let handlerRunResolve; + new Promise(r => handlerRunResolve = r).then(() => i.remove()); + e.intercept({ handler() { + t.step_timeout(handlerRunResolve, 0); + return new Promise(resolve => t.step_timeout(resolve, 5)); + }}); + }); + + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func("navigatesuccess must not fire"); + + i.contentWindow.navigation.onnavigateerror = t.step_func(e => { + assert_false(onnavigateerror_called); + onnavigateerror_called = true; + assert_equals(e.filename, location.href); + assert_greater_than(e.lineno, 0); + assert_greater_than(e.colno, 0); + + assertCommittedFulfillsFinishedRejectsExactly(t, result, iWindow.navigation.currentEntry, e.error, iWindow, iDOMException, iNavigationHistoryEntry).then( + () => t.done(), + t.step_func(err => { throw err; }) + ); + }); + + result = i.contentWindow.navigation.traverseTo(key); + })); + }); +}, "Detach a window between when a traverseTo() fires navigate and navigatesuccess"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-multiple-steps.html b/testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-multiple-steps.html new file mode 100644 index 0000000000..059eb214a8 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-multiple-steps.html @@ -0,0 +1,25 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + assert_equals(navigation.entries().length, 1); + let key0 = navigation.currentEntry.key; + await navigation.navigate("#1").committed; + await navigation.navigate("#2").committed; + let key2 = navigation.currentEntry.key; + assert_equals(navigation.entries().length, 3); + + await navigation.traverseTo(key0).committed; + assert_equals(navigation.entries().length, 3); + assert_equals(navigation.currentEntry, navigation.entries()[0]); + assert_equals(key0, navigation.currentEntry.key); + await navigation.traverseTo(key2).committed; + assert_equals(navigation.entries().length, 3); + assert_equals(navigation.currentEntry, navigation.entries()[2]); + assert_equals(key2, navigation.currentEntry.key); +}, "goto() can precisely traverse multiple steps in the joint session history"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-navigates-multiple-iframes.html b/testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-navigates-multiple-iframes.html new file mode 100644 index 0000000000..3d6adb5341 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-navigates-multiple-iframes.html @@ -0,0 +1,45 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<iframe id="i1" src="/common/blank.html"></iframe> +<iframe id="i2" src="resources/slow-no-store.py"></iframe> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + i1.src = "/common/blank.html?navigated"; + await new Promise(resolve => i1.onload = resolve); + i2.src = "/common/blank.html?navigated"; + await new Promise(resolve => i2.onload = resolve); + assert_equals(navigation.entries().length, 1); + assert_equals(i1.contentWindow.navigation.entries().length, 2); + assert_equals(i2.contentWindow.navigation.entries().length, 2); + assert_equals(i1.contentWindow.navigation.currentEntry.index, 1); + assert_equals(i2.contentWindow.navigation.currentEntry.index, 1); + + function collectKeysAndIds(win) { + return win.navigation.entries().map(e => [e.key, e.id]).flat(); + } + let i1_keys_and_ids_before_back = collectKeysAndIds(i1.contentWindow); + let i2_keys_and_ids_before_back = collectKeysAndIds(i2.contentWindow); + + // Go back to a point that requires both frames to navigate. Because i2 is + // going back to a slow, un-cached document, i1 will likely complete before + // the server sends the response for i2. This combination of a slow and fast + // traversal is less common than the case where multiple iframes navigate at + // similar speeds, and caused a bug in chromium. + i1.contentWindow.navigation.traverseTo(i1.contentWindow.navigation.entries()[0].key); + await Promise.all( + [ new Promise(resolve => i1.onload = resolve), + new Promise(resolve => i2.onload = resolve) ]); + assert_equals(i1.contentWindow.navigation.currentEntry.index, 0); + assert_equals(i2.contentWindow.navigation.currentEntry.index, 0); + + assert_array_equals(i1_keys_and_ids_before_back, collectKeysAndIds(i1.contentWindow)); + assert_array_equals(i2_keys_and_ids_before_back, collectKeysAndIds(i2.contentWindow)); +}, "entries() should be correct after a traversal that navigates multiple browsing contexts"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-same-document.html b/testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-same-document.html new file mode 100644 index 0000000000..1a32bce99c --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-same-document.html @@ -0,0 +1,43 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + window.onload = () => t.step_timeout(() => { + assert_equals(navigation.entries().length, 1); + + let onnavigate_count = 0; + navigation.onnavigate = () => onnavigate_count++; + + let key = navigation.currentEntry.key; + navigation.navigate("#").committed + .then(t.step_func(() => { + assert_equals(navigation.entries().length, 2); + assert_equals(navigation.currentEntry, navigation.entries()[1]); + assert_not_equals(key, navigation.currentEntry.key); + return navigation.traverseTo(key).committed; + })) + .then(t.step_func(() => { + assert_equals(navigation.entries().length, 2); + assert_equals(navigation.currentEntry, navigation.entries()[0]); + assert_equals(key, navigation.currentEntry.key); + assert_true(navigation.canGoForward); + return navigation.forward().committed; + })) + .then(t.step_func(() => { + assert_equals(navigation.entries().length, 2); + assert_equals(navigation.currentEntry, navigation.entries()[1]); + assert_true(navigation.canGoBack); + return navigation.back().committed; + })) + .then(t.step_func_done(() => { + assert_equals(navigation.entries().length, 2); + assert_equals(navigation.currentEntry, navigation.entries()[0]); + assert_equals(key, navigation.currentEntry.key); + assert_equals(onnavigate_count, 4); + })); + }, 0); +}, "same-document navigate.traverseTo(), back(), and forward()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-with-cross-origin-in-history.html b/testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-with-cross-origin-in-history.html new file mode 100644 index 0000000000..a6b7745584 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-with-cross-origin-in-history.html @@ -0,0 +1,37 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<body> +<script> +async_test(t => { + let cross_origin_url = new URL(get_host_info().HTTPS_REMOTE_ORIGIN); + cross_origin_url.pathname = "/common/blank.html"; + let i = document.createElement("iframe"); + i.src = cross_origin_url; + document.body.appendChild(i); + + let start_length = history.length; + + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + window.onload = () => t.step_timeout(() => { + i.contentWindow.location = "/common/blank.html"; + i.onload = () => t.step_timeout(() => { + assert_equals(history.length, start_length + 1); + assert_equals(i.contentWindow.navigation.entries().length, 1); + i.contentWindow.navigation.navigate("#fragment"); + assert_equals(i.contentWindow.navigation.entries().length, 2); + i.contentWindow.navigation.back().committed.then(t.step_func(() => { + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry.index, 0); + return i.contentWindow.navigation.forward().committed; + })).then(t.step_func_done(() => { + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry.index, 1); + })); + }, 0); + }, 0); +}, "navigation.traverseTo() should work in an iframe that has navigated across origins"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/README.md b/testing/web-platform/tests/navigation-api/ordering-and-transition/README.md new file mode 100644 index 0000000000..628b22ec48 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/README.md @@ -0,0 +1,26 @@ +# Navigation API ordering/transition tests + +These are meant to test the ordering between various events and promises, as +well as in some cases how the `navigation.transition` values changes. + +Some of them use the `Recorder` framework in `resources/helpers.mjs`, and others +test tricky cases (e.g. reentrancy) in a more ad-hoc way. + +<https://github.com/WICG/navigation-api/#complete-event-sequence> is a useful +reference for the intent of these tests. + +Note: + +* Variants specifically exist for `currententrychange` because an event listener + existing for `currententrychange` causes code to run, and thus microtasks to run, + at a very specific point in the navigation-commit lifecycle. We want to test + that it doesn't impact the ordering. +* Similarly we test that `intercept()` does not change + the ordering compared to no `intercept()` call, for same-document + navigations, by trying to ensure most variants have appropriate + `intercept()` counterparts with similar orderings. + +TODOs: + +* Also test `popstate` and `hashchange` once + <https://github.com/whatwg/html/issues/1792> is fixed. diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/anchor-download-intercept-reject.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/anchor-download-intercept-reject.html new file mode 100644 index 0000000000..4869cedd25 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/anchor-download-intercept-reject.html @@ -0,0 +1,54 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<meta name="variant" content=""> +<meta name="variant" content="?currententrychange"> + +<script type="module"> +import { Recorder, hasVariant } from "./resources/helpers.mjs"; + +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + const from = navigation.currentEntry; + const expectedError = new Error("boo"); + + const recorder = new Recorder({ + skipCurrentChange: !hasVariant("currententrychange"), + finalExpectedEvent: "transition.finished rejected" + }); + + recorder.setUpNavigationAPIListeners(); + + navigation.addEventListener("navigate", e => { + e.intercept({ handler() { + recorder.record("handler run"); + return Promise.reject(expectedError); + }}); + }); + + let a = document.createElement("a"); + a.href = "/common/blank.html#1"; + a.download = ""; + document.body.appendChild(a); + a.click(); + + Promise.resolve().then(() => recorder.record("promise microtask")); + + await recorder.readyToAssert; + + recorder.assert([ + /* event name, location.hash value, navigation.transition properties */ + ["navigate", "", null], + ["currententrychange", "#1", { from, navigationType: "push" }], + ["handler run", "#1", { from, navigationType: "push" }], + ["promise microtask", "#1", { from, navigationType: "push" }], + ["navigateerror", "#1", { from, navigationType: "push" }], + ["transition.finished rejected", "#1", null], + ]); + + recorder.assertErrorsAre(expectedError); +}, "event and promise ordering for <a download> intercepted by passing a rejected promise to intercept()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/anchor-download-intercept.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/anchor-download-intercept.html new file mode 100644 index 0000000000..23326c827b --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/anchor-download-intercept.html @@ -0,0 +1,48 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<meta name="variant" content=""> +<meta name="variant" content="?currententrychange"> + +<script type="module"> +import { Recorder, hasVariant } from "./resources/helpers.mjs"; + +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + const from = navigation.currentEntry; + + const recorder = new Recorder({ + skipCurrentChange: !hasVariant("currententrychange"), + finalExpectedEvent: "transition.finished fulfilled" + }); + + recorder.setUpNavigationAPIListeners(); + + navigation.addEventListener("navigate", e => { + e.intercept({ handler() { recorder.record("handler run"); } }); + }); + + let a = document.createElement("a"); + a.href = "/common/blank.html#1"; + a.download = ""; + document.body.appendChild(a); + a.click(); + + Promise.resolve().then(() => recorder.record("promise microtask")); + + await recorder.readyToAssert; + + recorder.assert([ + /* event name, location.hash value, navigation.transition properties */ + ["navigate", "", null], + ["currententrychange", "#1", { from, navigationType: "push" }], + ["handler run", "#1", { from, navigationType: "push" }], + ["promise microtask", "#1", { from, navigationType: "push" }], + ["navigatesuccess", "#1", { from, navigationType: "push" }], + ["transition.finished fulfilled", "#1", null], + ]); +}, "event and promise ordering for <a download> intercepted by intercept()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/anchor-download.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/anchor-download.html new file mode 100644 index 0000000000..c6af13c3e0 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/anchor-download.html @@ -0,0 +1,24 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +promise_test(async t => { + await new Promise(resolve => window.onload = resolve); + + let navigate_called = false; + i.contentWindow.navigation.onnavigate = () => navigate_called = true; + navigation.onnavigate = t.unreached_func("navigate must not fire"); + + let a = i.contentDocument.createElement("a"); + a.href = "?1"; + a.download = ""; + i.contentDocument.body.appendChild(a); + a.click(); + + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func("navigatesuccess must not fire"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("navigateerror must not fire"); + await new Promise(resolve => t.step_timeout(resolve, 20)); + assert_true(navigate_called); +}, "<a download> fires navigate, but not navigatesuccess or navigateerror when not intercepted by intercept()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/back-same-document-intercept-reject.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/back-same-document-intercept-reject.html new file mode 100644 index 0000000000..c0d4f55027 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/back-same-document-intercept-reject.html @@ -0,0 +1,54 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<meta name="variant" content=""> +<meta name="variant" content="?currententrychange"> + +<script type="module"> +import { Recorder, hasVariant } from "./resources/helpers.mjs"; + +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + await navigation.navigate("#1").finished; + + const from = navigation.currentEntry; + const expectedError = new Error("boo"); + + const recorder = new Recorder({ + skipCurrentChange: !hasVariant("currententrychange"), + finalExpectedEvent: "transition.finished rejected" + }); + + recorder.setUpNavigationAPIListeners(); + + navigation.addEventListener("navigate", e => { + e.intercept({ handler() { + recorder.record("handler run"); + return Promise.reject(expectedError); + }}); + }); + + const result = navigation.back(); + recorder.setUpResultListeners(result); + + Promise.resolve().then(() => recorder.record("promise microtask")); + + await recorder.readyToAssert; + + recorder.assert([ + /* event name, location.hash value, navigation.transition properties */ + ["promise microtask", "#1", null], + ["navigate", "#1", null], + ["currententrychange", "", { from, navigationType: "traverse" }], + ["handler run", "", { from, navigationType: "traverse" }], + ["committed fulfilled", "", { from, navigationType: "traverse" }], + ["navigateerror", "", { from, navigationType: "traverse" }], + ["finished rejected", "", null], + ["transition.finished rejected", "", null] + ]); + + recorder.assertErrorsAre(expectedError); +}, "event and promise ordering for same-document navigation.back() intercepted by passing a rejected promise to intercept()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/back-same-document-intercept.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/back-same-document-intercept.html new file mode 100644 index 0000000000..7bd248bc9c --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/back-same-document-intercept.html @@ -0,0 +1,48 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<meta name="variant" content=""> +<meta name="variant" content="?currententrychange"> + +<script type="module"> +import { Recorder, hasVariant } from "./resources/helpers.mjs"; + +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + await navigation.navigate("#1").finished; + + const from = navigation.currentEntry; + + const recorder = new Recorder({ + skipCurrentChange: !hasVariant("currententrychange"), + finalExpectedEvent: "transition.finished fulfilled" + }); + + recorder.setUpNavigationAPIListeners(); + + navigation.addEventListener("navigate", e => { + e.intercept({ handler() { recorder.record("handler run"); } }); + }); + + const result = navigation.back(); + recorder.setUpResultListeners(result); + + Promise.resolve().then(() => recorder.record("promise microtask")); + + await recorder.readyToAssert; + + recorder.assert([ + /* event name, location.hash value, navigation.transition properties */ + ["promise microtask", "#1", null], + ["navigate", "#1", null], + ["currententrychange", "", { from, navigationType: "traverse" }], + ["handler run", "", { from, navigationType: "traverse" }], + ["committed fulfilled", "", { from, navigationType: "traverse" }], + ["navigatesuccess", "", { from, navigationType: "traverse" }], + ["finished fulfilled", "", null], + ["transition.finished fulfilled", "", null] + ]); +}, "event and promise ordering for same-document navigation.back() intercepted by intercept()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/back-same-document.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/back-same-document.html new file mode 100644 index 0000000000..76c3e99311 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/back-same-document.html @@ -0,0 +1,40 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<meta name="variant" content=""> +<meta name="variant" content="?currententrychange"> + +<script type="module"> +import { Recorder, hasVariant } from "./resources/helpers.mjs"; + +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + await navigation.navigate("#1").finished; + + const recorder = new Recorder({ + skipCurrentChange: !hasVariant("currententrychange"), + finalExpectedEvent: "finished fulfilled" + }); + + recorder.setUpNavigationAPIListeners(); + + const result = navigation.back(); + recorder.setUpResultListeners(result); + + Promise.resolve().then(() => recorder.record("promise microtask")); + + await recorder.readyToAssert; + + recorder.assert([ + /* event name, location.hash value, navigation.transition properties */ + ["promise microtask", "#1", null], + ["navigate", "#1", null], + ["currententrychange", "", null], + ["committed fulfilled", "", null], + ["navigatesuccess", "", null], + ["finished fulfilled", "", null], + ]); +}, "event and promise ordering for same-document navigation.back()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/currententrychange-before-popstate-intercept.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/currententrychange-before-popstate-intercept.html new file mode 100644 index 0000000000..c51c7c444c --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/currententrychange-before-popstate-intercept.html @@ -0,0 +1,48 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + await navigation.navigate("#foo").committed; + assert_equals(navigation.entries().length, 2); + + navigation.onnavigate = e => e.intercept(); + + let oncurrententrychange_back_called = false; + let onpopstate_back_called = false; + window.onpopstate = t.step_func(e => { + onpopstate_back_called = true; + assert_true(oncurrententrychange_back_called); + }); + navigation.oncurrententrychange = t.step_func(e => { + oncurrententrychange_back_called = true; + assert_false(onpopstate_back_called); + }); + let back_result = navigation.back(); + assert_false(oncurrententrychange_back_called); + assert_false(onpopstate_back_called); + await back_result.finished; + assert_true(oncurrententrychange_back_called); + assert_true(onpopstate_back_called); + + let oncurrententrychange_forward_called = false; + let onpopstate_forward_called = false; + window.onpopstate = t.step_func(e => { + onpopstate_forward_called = true; + assert_true(oncurrententrychange_forward_called); + }); + navigation.oncurrententrychange = t.step_func(e => { + oncurrententrychange_forward_called = true; + assert_false(onpopstate_forward_called); + }); + let forward_result = navigation.forward(); + assert_false(oncurrententrychange_forward_called); + assert_false(onpopstate_forward_called); + await forward_result.finished; + assert_true(oncurrententrychange_back_called); + assert_true(onpopstate_forward_called); +}, "currententrychange fires before popstate for navigation.back() and navigation.forward()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/currententrychange-dispose-ordering.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/currententrychange-dispose-ordering.html new file mode 100644 index 0000000000..4ca9ba2980 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/currententrychange-dispose-ordering.html @@ -0,0 +1,26 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +test(t => { + let oncurrententrychange_called = false; + let ondispose_called = false; + + let original_entry = navigation.currentEntry; + original_entry.ondispose = t.step_func(() => { + assert_true(oncurrententrychange_called); + ondispose_called = true; + }); + + navigation.oncurrententrychange = t.step_func(e => { + oncurrententrychange_called = true; + assert_equals(e.from, original_entry); + assert_equals(e.from.index, -1); + assert_equals(e.navigationType, "replace"); + assert_equals(navigation.currentEntry.index, 0); + }); + navigation.navigate("#foo", { history: "replace" }); + assert_true(oncurrententrychange_called); + assert_true(ondispose_called); +}, "Ordering between Navigation currententrychange and NavigationHistoryEntry dispose events"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/intercept-async.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/intercept-async.html new file mode 100644 index 0000000000..f2ca096950 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/intercept-async.html @@ -0,0 +1,55 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<meta name="variant" content=""> +<meta name="variant" content="?currententrychange"> + +<script type="module"> +import { Recorder, hasVariant } from "./resources/helpers.mjs"; + +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + const from = navigation.currentEntry; + + const recorder = new Recorder({ + skipCurrentChange: !hasVariant("currententrychange"), + finalExpectedEvent: "transition.finished fulfilled" + }); + + recorder.setUpNavigationAPIListeners(); + + navigation.addEventListener("navigate", e => { + e.intercept({ async handler() { + recorder.record("handler sync"); + await Promise.resolve(); + recorder.record("handler after microtask"); + await new Promise(r => t.step_timeout(r, 0)); + recorder.record("handler after setTimeout"); + } }); + }); + + const result = navigation.navigate("#1"); + recorder.setUpResultListeners(result); + + Promise.resolve().then(() => recorder.record("promise microtask")); + + await recorder.readyToAssert; + + recorder.assert([ + /* event name, location.hash value, navigation.transition properties */ + ["navigate", "", null], + ["currententrychange", "#1", { from, navigationType: "push" }], + ["handler sync", "#1", { from, navigationType: "push" }], + ["handler after microtask", "#1", { from, navigationType: "push" }], + ["committed fulfilled", "#1", { from, navigationType: "push" }], + ["promise microtask", "#1", { from, navigationType: "push" }], + ["handler after setTimeout", "#1", { from, navigationType: "push" }], + ["navigatesuccess", "#1", { from, navigationType: "push" }], + ["finished fulfilled", "#1", null], + ["transition.finished fulfilled", "#1", null], + ]); +}, "ordering when intercept() handler has sync and async blocks"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/location-href-canceled.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/location-href-canceled.html new file mode 100644 index 0000000000..eef10cd173 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/location-href-canceled.html @@ -0,0 +1,39 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script type="module"> +import { Recorder } from "./resources/helpers.mjs"; + +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + const recorder = new Recorder({ + finalExpectedEvent: "promise microtask" + }); + + recorder.setUpNavigationAPIListeners(); + + navigation.addEventListener("navigate", t.step_func(e => { + e.preventDefault(); + })); + + location.href = "/common/blank.html#1"; + + Promise.resolve().then(() => recorder.record("promise microtask")); + + await recorder.readyToAssert; + + recorder.assert([ + /* event name, location.hash value, navigation.transition properties */ + ["navigate", "", null], + ["AbortSignal abort", "", null], + ["navigateerror", "", null], + ["promise microtask", "", null] + ]); + + recorder.assertErrorsAreAbortErrors(); +}, "event and promise ordering for the location.href setter where the navigate event is canceled"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/location-href-double-intercept.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/location-href-double-intercept.html new file mode 100644 index 0000000000..36ae5ce4ab --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/location-href-double-intercept.html @@ -0,0 +1,61 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<meta name="variant" content=""> +<meta name="variant" content="?currententrychange"> + +<script type="module"> +import { Recorder, hasVariant } from "./resources/helpers.mjs"; +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + const fromStart = navigation.currentEntry; + let fromHash1; + + const recorder = new Recorder({ + skipCurrentChange: !hasVariant("currententrychange"), + finalExpectedEvent: "transition.finished fulfilled" + }); + + recorder.setUpNavigationAPIListeners(); + + navigation.addEventListener("navigate", e => { + e.intercept({ handler() { + recorder.record("handler run"); + return new Promise(r => t.step_timeout(r, 1)); + }}); + + if (location.hash === "#1") { + fromHash1 = navigation.currentEntry; + } + }); + + location.href = "/common/blank.html#1"; + location.href = "/common/blank.html#2"; + + Promise.resolve().then(() => recorder.record("promise microtask")); + + await recorder.readyToAssert; + + recorder.assert([ + /* event name, location.hash value, navigation.transition properties */ + ["navigate", "", null], + ["currententrychange", "#1", { from: fromStart, navigationType: "push" }], + ["handler run", "#1", { from: fromStart, navigationType: "push" }], + ["AbortSignal abort", "#1", { from: fromStart, navigationType: "push" }], + ["navigateerror", "#1", { from: fromStart, navigationType: "push" }], + + ["navigate", "#1", null], + ["currententrychange", "#2", { from: fromHash1, navigationType: "push" }], + ["handler run", "#2", { from: fromHash1, navigationType: "push" }], + ["transition.finished rejected", "#2", { from: fromHash1, navigationType: "push" }], + ["promise microtask", "#2", { from: fromHash1, navigationType: "push" }], + ["navigatesuccess", "#2", { from: fromHash1, navigationType: "push" }], + ["transition.finished fulfilled", "#2", null] + ]); + + recorder.assertErrorsAreAbortErrors(); +}, "event and promise ordering when location.href is set repeatedly and handled by intercept()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/location-href-intercept-reentrant.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/location-href-intercept-reentrant.html new file mode 100644 index 0000000000..3fabf52bfb --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/location-href-intercept-reentrant.html @@ -0,0 +1,60 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<meta name="variant" content=""> +<meta name="variant" content="?currententrychange"> + +<script type="module"> +import { Recorder, hasVariant } from "./resources/helpers.mjs"; + +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + const from = navigation.currentEntry; + let firstNavigate = true; + + const recorder = new Recorder({ + skipCurrentChange: !hasVariant("currententrychange"), + finalExpectedEvent: "transition.finished fulfilled" + }); + + recorder.setUpNavigationAPIListeners(); + + navigation.addEventListener("navigate", e => { + e.intercept({ handler() { + recorder.record("handler run"); + return new Promise(resolve => t.step_timeout(resolve, 2)); + }}); + + if (firstNavigate) { + firstNavigate = false; + + location.href = "/common/blank.html#2"; + } + }); + + location.href = "/common/blank.html#1"; + + Promise.resolve().then(() => recorder.record("promise microtask")); + + await recorder.readyToAssert; + + recorder.assert([ + /* event name, location.hash value, navigation.transition properties */ + ["navigate", "", null], + ["AbortSignal abort", "", null], + ["navigateerror", "", null], + + ["navigate", "", null], + ["currententrychange", "#2", { from, navigationType: "push" }], + ["handler run", "#2", { from, navigationType: "push" }], + ["promise microtask", "#2", { from, navigationType: "push" }], + ["navigatesuccess", "#2", { from, navigationType: "push" }], + ["transition.finished fulfilled", "#2", null] + ]); + + recorder.assertErrorsAreAbortErrors(); +}, "event and promise ordering for the location.href setter intercepted by intercept() where we set location.href again inside the navigate handler"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/location-href-intercept-reject.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/location-href-intercept-reject.html new file mode 100644 index 0000000000..b4aeb726b3 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/location-href-intercept-reject.html @@ -0,0 +1,50 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<meta name="variant" content=""> +<meta name="variant" content="?currententrychange"> + +<script type="module"> +import { Recorder, hasVariant } from "./resources/helpers.mjs"; + +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + const from = navigation.currentEntry; + const expectedError = new Error("boo"); + + const recorder = new Recorder({ + skipCurrentChange: !hasVariant("currententrychange"), + finalExpectedEvent: "transition.finished rejected" + }); + + recorder.setUpNavigationAPIListeners(); + + navigation.addEventListener("navigate", e => { + e.intercept({ handler() { + recorder.record("handler run"); + return Promise.reject(expectedError); + }}); + }); + + location.href = "/common/blank.html#1"; + + Promise.resolve().then(() => recorder.record("promise microtask")); + + await recorder.readyToAssert; + + recorder.assert([ + /* event name, location.hash value, navigation.transition properties */ + ["navigate", "", null], + ["currententrychange", "#1", { from, navigationType: "push" }], + ["handler run", "#1", { from, navigationType: "push" }], + ["promise microtask", "#1", { from, navigationType: "push" }], + ["navigateerror", "#1", { from, navigationType: "push" }], + ["transition.finished rejected", "#1", null], + ]); + + recorder.assertErrorsAre(expectedError); +}, "event and promise ordering for location.href setter intercepted by passing a rejected promise to intercept()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/location-href-intercept.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/location-href-intercept.html new file mode 100644 index 0000000000..bb861d37ae --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/location-href-intercept.html @@ -0,0 +1,44 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<meta name="variant" content=""> +<meta name="variant" content="?currententrychange"> + +<script type="module"> +import { Recorder, hasVariant } from "./resources/helpers.mjs"; + +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + const from = navigation.currentEntry; + + const recorder = new Recorder({ + skipCurrentChange: !hasVariant("currententrychange"), + finalExpectedEvent: "transition.finished fulfilled" + }); + + recorder.setUpNavigationAPIListeners(); + + navigation.addEventListener("navigate", e => { + e.intercept({ handler() { recorder.record("handler run"); } }); + }); + + location.href = "/common/blank.html#1"; + + Promise.resolve().then(() => recorder.record("promise microtask")); + + await recorder.readyToAssert; + + recorder.assert([ + /* event name, location.hash value, navigation.transition properties */ + ["navigate", "", null], + ["currententrychange", "#1", { from, navigationType: "push" }], + ["handler run", "#1", { from, navigationType: "push" }], + ["promise microtask", "#1", { from, navigationType: "push" }], + ["navigatesuccess", "#1", { from, navigationType: "push" }], + ["transition.finished fulfilled", "#1", null], + ]); +}, "event and promise ordering for location.href setter intercepted by intercept()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-204-205-download-then-same-document.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-204-205-download-then-same-document.html new file mode 100644 index 0000000000..b7b6283fa7 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-204-205-download-then-same-document.html @@ -0,0 +1,66 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script type="module"> +import { Recorder } from "./resources/helpers.mjs"; + +const tests = [ + ["204s", "/common/blank.html?pipe=status(204)"], + ["205s", "/common/blank.html?pipe=status(205)"], + ["Content-Disposition: attachment responses", "/common/blank.html?pipe=header(Content-Disposition,attachment)"] +]; + +for (const [description, url] of tests) { + promise_test(async t => { + const i = document.createElement("iframe"); + i.src = "/common/blank.html"; + document.body.append(i); + await new Promise(resolve => i.onload = () => t.step_timeout(resolve, 0)); + + const fromStart = i.contentWindow.navigation.currentEntry; + + const recorder = new Recorder({ + window: i.contentWindow, + finalExpectedEvent: "finished fulfilled 2" + }); + + recorder.setUpNavigationAPIListeners(); + + const result1 = i.contentWindow.navigation.navigate(url); + recorder.setUpResultListeners(result1, " 1"); + + // Give the server time to send the response. This is not strictly + // necessary (the expectations are the same either way) but it's better + // coverage if the server is done responding by this time; it guarantees + // we're hitting the code path for "got a 204/etc. and ignored it" instead + // of "didn't get a response yet". + await new Promise(resolve => t.step_timeout(resolve, 50)); + + const result2 = i.contentWindow.navigation.navigate("#1"); + recorder.setUpResultListeners(result2, " 2"); + + Promise.resolve().then(() => recorder.record("promise microtask")); + + await recorder.readyToAssert; + + recorder.assert([ + /* event name, location.hash value, navigation.transition properties */ + ["navigate", "", null], + ["AbortSignal abort", "", null], + ["navigateerror", "", null], + + ["navigate", "", null], + ["currententrychange", "#1", null], + ["committed rejected 1", "#1", null], + ["finished rejected 1", "#1", null], + ["committed fulfilled 2", "#1", null], + ["promise microtask", "#1", null], + ["navigatesuccess", "#1", null], + ["finished fulfilled 2", "#1", null] + ]); + + recorder.assertErrorsAreAbortErrors(); + }, `event and promise ordering when navigate() is to a ${description} and then to a same-document navigation`); +} +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-canceled.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-canceled.html new file mode 100644 index 0000000000..2604a60e37 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-canceled.html @@ -0,0 +1,42 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script type="module"> +import { Recorder } from "./resources/helpers.mjs"; + +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + const recorder = new Recorder({ + finalExpectedEvent: "finished rejected" + }); + + recorder.setUpNavigationAPIListeners(); + + navigation.addEventListener("navigate", t.step_func(e => { + e.preventDefault(); + })); + + const result = navigation.navigate("/common/blank.html#1"); + recorder.setUpResultListeners(result); + + Promise.resolve().then(() => recorder.record("promise microtask")); + + await recorder.readyToAssert; + + recorder.assert([ + /* event name, location.hash value, navigation.transition properties */ + ["navigate", "", null], + ["AbortSignal abort", "", null], + ["navigateerror", "", null], + ["committed rejected", "", null], + ["finished rejected", "", null], + ["promise microtask", "", null] + ]); + + recorder.assertErrorsAreAbortErrors(); +}, "event and promise ordering for navigation.navigate() where the navigate event is canceled"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-commit-after-transition-intercept.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-commit-after-transition-intercept.html new file mode 100644 index 0000000000..70a840e692 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-commit-after-transition-intercept.html @@ -0,0 +1,60 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<meta name="variant" content=""> +<meta name="variant" content="?currententrychange"> + +<script type="module"> +import { Recorder, hasVariant } from "./resources/helpers.mjs"; + +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + const from = navigation.currentEntry; + + const recorder = new Recorder({ + skipCurrentChange: !hasVariant("currententrychange"), + finalExpectedEvent: "transition.finished fulfilled" + }); + + recorder.setUpNavigationAPIListeners(); + + navigation.addEventListener("navigate", e => { + e.intercept({ commit: "after-transition", + async handler() { + recorder.record("handler start"); + await new Promise(r => t.step_timeout(r, 0)); + recorder.record("handler async step 1a"); + e.commit(); + recorder.record("handler async step 1b"); + await new Promise(r => t.step_timeout(r, 0)); + recorder.record("handler async step 2"); + } + }); + }); + + const result = navigation.navigate("#1"); + recorder.setUpResultListeners(result); + + Promise.resolve().then(() => recorder.record("promise microtask")); + + await recorder.readyToAssert; + + recorder.assert([ + /* event name, location.hash value, navigation.transition properties */ + ["navigate", "", null], + ["handler start", "", { from, navigationType: "push" }], + ["promise microtask", "", { from, navigationType: "push" }], + ["handler async step 1a", "", { from, navigationType: "push" }], + ["currententrychange", "#1", { from, navigationType: "push" }], + ["handler async step 1b", "#1", { from, navigationType: "push" }], + ["committed fulfilled", "#1", { from, navigationType: "push" }], + ["handler async step 2", "#1", { from, navigationType: "push" }], + ["navigatesuccess", "#1", { from, navigationType: "push" }], + ["finished fulfilled", "#1", null], + ["transition.finished fulfilled", "#1", null], + ]); +}, "event and promise ordering for same-document navigation.navigate() intercepted by intercept() with { commit: 'after-transition' }"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-cross-document-double.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-cross-document-double.html new file mode 100644 index 0000000000..262809a0ad --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-cross-document-double.html @@ -0,0 +1,52 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i"></iframe> + + +<script type="module"> +import { Recorder } from "./resources/helpers.mjs"; + +promise_test(async t => { + await new Promise(resolve => { + i.src = "/common/blank.html"; + i.onload = () => t.step_timeout(resolve, 0) + }); + + const fromStart = i.contentWindow.navigation.currentEntry; + + const recorder = new Recorder({ + window: i.contentWindow, + finalExpectedEvent: "promise microtask" + }); + + recorder.setUpNavigationAPIListeners(); + + // Use https://web-platform-tests.org/writing-tests/server-pipes.html to make + // sure the response doesn't come back quickly, since once the response comes + // back the page would be unloaded and that would break our test. + const result1 = i.contentWindow.navigation.navigate("?pipe=trickle(d100)"); + recorder.setUpResultListeners(result1, " 1"); + + const result2 = i.contentWindow.navigation.navigate("?2"); + recorder.setUpResultListeners(result2, " 2"); + + Promise.resolve().then(() => recorder.record("promise microtask")); + + await recorder.readyToAssert; + + recorder.assert([ + /* event name, location.hash value, navigation.transition properties */ + ["navigate", "", null], + ["AbortSignal abort", "", null], + ["navigateerror", "", null], + + ["navigate", "", null], + ["committed rejected 1", "", null], + ["finished rejected 1", "", null], + ["promise microtask", "", null] + ]); + + recorder.assertErrorsAreAbortErrors(); +}, "event and promise ordering when navigate() is called to a cross-document destination, interrupting another navigate() to a cross-document destination"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-cross-document-event-order.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-cross-document-event-order.html new file mode 100644 index 0000000000..34a9b79fb5 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-cross-document-event-order.html @@ -0,0 +1,32 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="resources/notify-top-early.html"></iframe> +<script> +async_test(t => { + let events = []; + function finish() { + assert_array_equals(events, ["onnavigate", "readystateinteractive", "domcontentloaded", "readystatecomplete", "onload", "onpageshow"]); + t.done(); + }; + + window.onload = t.step_func(() => { + window.childStarted = () => { + i.contentWindow.navigation.onnavigatesuccess = () => events.push("onnavigatesuccess"); + i.contentWindow.navigation.onnavigateerror = () => events.push("onnavigateerror"); + i.contentWindow.onpageshow = () => events.push("onpageshow"); + i.contentWindow.onhashchange = () => events.push("onhashchange"); + i.contentWindow.onpopstate = () => events.push("onpopstate"); + i.onload = t.step_func(() => { + events.push("onload"); + t.step_timeout(finish, 0); + }); + i.contentDocument.addEventListener("DOMContentLoaded", () => events.push("domcontentloaded")); + i.contentDocument.onreadystatechange = () => events.push("readystate" + i.contentDocument.readyState); + }; + i.contentWindow.navigation.onnavigate = () => events.push("onnavigate"); + i.contentWindow.navigation.navigate("?1").committed.then( + () => events.push("promisefulfilled"), () => events.push("promiserejected")); + }); +}, "navigate() event ordering for cross-document navigation"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-double-intercept.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-double-intercept.html new file mode 100644 index 0000000000..6ce67b9af6 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-double-intercept.html @@ -0,0 +1,68 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<meta name="variant" content=""> +<meta name="variant" content="?currententrychange"> + +<script type="module"> +import { Recorder, hasVariant } from "./resources/helpers.mjs"; +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + const fromStart = navigation.currentEntry; + let fromHash1; + + const recorder = new Recorder({ + skipCurrentChange: !hasVariant("currententrychange"), + finalExpectedEvent: "transition.finished fulfilled" + }); + + recorder.setUpNavigationAPIListeners(); + + navigation.addEventListener("navigate", e => { + e.intercept({ handler() { + recorder.record("handler run"); + return new Promise(r => t.step_timeout(r, 1)); + }}); + + if (location.hash === "#1") { + fromHash1 = navigation.currentEntry; + } + }); + + const result1 = navigation.navigate("/common/blank.html#1"); + recorder.setUpResultListeners(result1, " 1"); + + const result2 = navigation.navigate("/common/blank.html#2"); + recorder.setUpResultListeners(result2, " 2"); + + Promise.resolve().then(() => recorder.record("promise microtask")); + + await recorder.readyToAssert; + + recorder.assert([ + /* event name, location.hash value, navigation.transition properties */ + ["navigate", "", null], + ["currententrychange", "#1", { from: fromStart, navigationType: "push" }], + ["handler run", "#1", { from: fromStart, navigationType: "push" }], + ["AbortSignal abort", "#1", { from: fromStart, navigationType: "push" }], + ["navigateerror", "#1", { from: fromStart, navigationType: "push" }], + + ["navigate", "#1", null], + ["currententrychange", "#2", { from: fromHash1, navigationType: "push" }], + ["handler run", "#2", { from: fromHash1, navigationType: "push" }], + ["committed fulfilled 1", "#2", { from: fromHash1, navigationType: "push" }], + ["finished rejected 1", "#2", { from: fromHash1, navigationType: "push" }], + ["transition.finished rejected", "#2", { from: fromHash1, navigationType: "push" }], + ["committed fulfilled 2", "#2", { from: fromHash1, navigationType: "push" }], + ["promise microtask", "#2", { from: fromHash1, navigationType: "push" }], + ["navigatesuccess", "#2", { from: fromHash1, navigationType: "push" }], + ["finished fulfilled 2", "#2", null], + ["transition.finished fulfilled", "#2", null] + ]); + + recorder.assertErrorsAreAbortErrors(); +}, "event and promise ordering when navigate() is called repeatedly and handled by intercept()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-in-transition-finished.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-in-transition-finished.html new file mode 100644 index 0000000000..9251cfe049 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-in-transition-finished.html @@ -0,0 +1,69 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<meta name="variant" content=""> +<meta name="variant" content="?currententrychange"> + +<script type="module"> +import { Recorder, hasVariant } from "./resources/helpers.mjs"; +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + const fromStart = navigation.currentEntry; + let fromHash1; + + const recorder = new Recorder({ + skipCurrentChange: !hasVariant("currententrychange"), + finalExpectedEvent: "transition.finished fulfilled", + finalExpectedEventCount: 2 + }); + + recorder.setUpNavigationAPIListeners(); + + navigation.addEventListener("navigatesuccess", t.step_func(() => { + if (location.hash === "#1") { + navigation.transition.finished.then(() => { + const result2 = navigation.navigate("/common/blank.html#2"); + recorder.setUpResultListeners(result2, " 2"); + }); + } + })); + + navigation.addEventListener("navigate", e => { + e.intercept({ handler() { recorder.record("handler run"); } }); + + if (location.hash === "#1") { + fromHash1 = navigation.currentEntry; + } + }); + + const result1 = navigation.navigate("/common/blank.html#1"); + recorder.setUpResultListeners(result1, " 1"); + + Promise.resolve().then(() => recorder.record("promise microtask")); + + await recorder.readyToAssert; + + recorder.assert([ + /* event name, location.hash value, navigation.transition properties */ + ["navigate", "", null], + ["currententrychange", "#1", { from: fromStart, navigationType: "push" }], + ["handler run", "#1", { from: fromStart, navigationType: "push" }], + ["committed fulfilled 1", "#1", { from: fromStart, navigationType: "push" }], + ["promise microtask", "#1", { from: fromStart, navigationType: "push" }], + ["navigatesuccess", "#1", { from: fromStart, navigationType: "push" }], + ["finished fulfilled 1", "#1", null], + ["transition.finished fulfilled", "#1", null], + + ["navigate", "#1", null], + ["currententrychange", "#2", { from: fromHash1, navigationType: "push" }], + ["handler run", "#2", { from: fromHash1, navigationType: "push" }], + ["committed fulfilled 2", "#2", { from: fromHash1, navigationType: "push" }], + ["navigatesuccess", "#2", { from: fromHash1, navigationType: "push" }], + ["finished fulfilled 2", "#2", null], + ["transition.finished fulfilled", "#2", null] + ]); +}, "event and promise ordering when navigate() is called inside the transition.finished promise handler"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-intercept-stop.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-intercept-stop.html new file mode 100644 index 0000000000..5d126a8c2f --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-intercept-stop.html @@ -0,0 +1,52 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<meta name="variant" content=""> +<meta name="variant" content="?currententrychange"> + +<script type="module"> +import { Recorder, hasVariant } from "./resources/helpers.mjs"; + +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + const from = navigation.currentEntry; + + const recorder = new Recorder({ + skipCurrentChange: !hasVariant("currententrychange"), + finalExpectedEvent: "transition.finished rejected" + }); + + recorder.setUpNavigationAPIListeners(); + + navigation.addEventListener("navigate", e => { + e.intercept({ handler() { recorder.record("handler run"); } }); + }); + + const result = navigation.navigate("/common/blank.html#1"); + recorder.setUpResultListeners(result); + + Promise.resolve().then(() => recorder.record("promise microtask")); + + window.stop(); + + await recorder.readyToAssert; + + recorder.assert([ + /* event name, location.hash value, navigation.transition properties */ + ["navigate", "", null], + ["currententrychange", "#1", { from, navigationType: "push" }], + ["handler run", "#1", { from, navigationType: "push" }], + ["AbortSignal abort", "#1", { from, navigationType: "push" }], + ["navigateerror", "#1", { from, navigationType: "push" }], + ["committed fulfilled", "#1", null], + ["promise microtask", "#1", null], + ["finished rejected", "#1", null], + ["transition.finished rejected", "#1", null], + ]); + + recorder.assertErrorsAreAbortErrors(); +}, "event and promise ordering for navigation.navigate() intercepted by intercept() but then stopped using window.stop()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-intercept.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-intercept.html new file mode 100644 index 0000000000..f76f20bf85 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-intercept.html @@ -0,0 +1,47 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<meta name="variant" content=""> +<meta name="variant" content="?currententrychange"> + +<script type="module"> +import { Recorder, hasVariant } from "./resources/helpers.mjs"; + +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + const from = navigation.currentEntry; + + const recorder = new Recorder({ + skipCurrentChange: !hasVariant("currententrychange"), + finalExpectedEvent: "transition.finished fulfilled" + }); + + recorder.setUpNavigationAPIListeners(); + + navigation.addEventListener("navigate", e => { + e.intercept({ handler() { recorder.record("handler run"); } }); + }); + + const result = navigation.navigate("#1"); + recorder.setUpResultListeners(result); + + Promise.resolve().then(() => recorder.record("promise microtask")); + + await recorder.readyToAssert; + + recorder.assert([ + /* event name, location.hash value, navigation.transition properties */ + ["navigate", "", null], + ["currententrychange", "#1", { from, navigationType: "push" }], + ["handler run", "#1", { from, navigationType: "push" }], + ["committed fulfilled", "#1", { from, navigationType: "push" }], + ["promise microtask", "#1", { from, navigationType: "push" }], + ["navigatesuccess", "#1", { from, navigationType: "push" }], + ["finished fulfilled", "#1", null], + ["transition.finished fulfilled", "#1", null], + ]); +}, "event and promise ordering for same-document navigation.navigate() intercepted by intercept()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-same-document-intercept-reentrant.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-same-document-intercept-reentrant.html new file mode 100644 index 0000000000..86f4e06731 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-same-document-intercept-reentrant.html @@ -0,0 +1,66 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<meta name="variant" content=""> +<meta name="variant" content="?currententrychange"> + +<script type="module"> +import { Recorder, hasVariant } from "./resources/helpers.mjs"; + +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + const from = navigation.currentEntry; + let firstNavigate = true; + + const recorder = new Recorder({ + skipCurrentChange: !hasVariant("currententrychange"), + finalExpectedEvent: "transition.finished fulfilled" + }); + + recorder.setUpNavigationAPIListeners(); + + navigation.addEventListener("navigate", e => { + e.intercept({ handler() { + recorder.record("handler run"); + return new Promise(r => t.step_timeout(r, 2)); + }}); + + if (firstNavigate) { + firstNavigate = false; + + const result2 = navigation.navigate("#2"); + recorder.setUpResultListeners(result2, " 2"); + } + }); + + const result1 = navigation.navigate("#1"); + recorder.setUpResultListeners(result1, " 1"); + + Promise.resolve().then(() => recorder.record("promise microtask")); + + await recorder.readyToAssert; + + recorder.assert([ + /* event name, location.hash value, navigation.transition properties */ + ["navigate", "", null], + ["AbortSignal abort", "", null], + ["navigateerror", "", null], + + ["navigate", "", null], + ["currententrychange", "#2", { from, navigationType: "push" }], + ["handler run", "#2", { from, navigationType: "push" }], + ["committed fulfilled 2", "#2", { from, navigationType: "push" }], + ["committed rejected 1", "#2", { from, navigationType: "push" }], + ["finished rejected 1", "#2", { from, navigationType: "push" }], + ["promise microtask", "#2", { from, navigationType: "push" }], + ["navigatesuccess", "#2", { from, navigationType: "push" }], + ["finished fulfilled 2", "#2", null], + ["transition.finished fulfilled", "#2", null] + ]); + + recorder.assertErrorsAreAbortErrors(); +}, "event and promise ordering for same-document navigation.navigate() inside the navigate handler"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-same-document-intercept-reject.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-same-document-intercept-reject.html new file mode 100644 index 0000000000..88c668e0e9 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-same-document-intercept-reject.html @@ -0,0 +1,53 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<meta name="variant" content=""> +<meta name="variant" content="?currententrychange"> + +<script type="module"> +import { Recorder, hasVariant } from "./resources/helpers.mjs"; + +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + const from = navigation.currentEntry; + const expectedError = new Error("boo"); + + const recorder = new Recorder({ + skipCurrentChange: !hasVariant("currententrychange"), + finalExpectedEvent: "transition.finished rejected" + }); + + recorder.setUpNavigationAPIListeners(); + + navigation.addEventListener("navigate", e => { + e.intercept({ handler() { + recorder.record("handler run"); + return Promise.reject(expectedError); + }}); + }); + + const result = navigation.navigate("#1"); + recorder.setUpResultListeners(result); + + Promise.resolve().then(() => recorder.record("promise microtask")); + + await recorder.readyToAssert; + + recorder.assert([ + /* event name, location.hash value, navigation.transition properties */ + ["navigate", "", null], + ["currententrychange", "#1", { from, navigationType: "push" }], + ["handler run", "#1", { from, navigationType: "push" }], + ["committed fulfilled", "#1", { from, navigationType: "push" }], + ["promise microtask", "#1", { from, navigationType: "push" }], + ["navigateerror", "#1", { from, navigationType: "push" }], + ["finished rejected", "#1", null], + ["transition.finished rejected", "#1", null], + ]); + + recorder.assertErrorsAre(expectedError); +}, "event and promise ordering for same-document navigation.navigate() intercepted by passing a rejected promise to intercept()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-same-document.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-same-document.html new file mode 100644 index 0000000000..589e1105e6 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/navigate-same-document.html @@ -0,0 +1,39 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<meta name="variant" content=""> +<meta name="variant" content="?currententrychange"> + +<script type="module"> +import { Recorder, hasVariant } from "./resources/helpers.mjs"; + +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + const recorder = new Recorder({ + skipCurrentChange: !hasVariant("currententrychange"), + finalExpectedEvent: "finished fulfilled" + }); + + recorder.setUpNavigationAPIListeners(); + + const result = navigation.navigate("#1"); + recorder.setUpResultListeners(result); + + Promise.resolve().then(() => recorder.record("promise microtask")); + + await recorder.readyToAssert; + + recorder.assert([ + /* event name, location.hash value, navigation.transition properties */ + ["navigate", "", null], + ["currententrychange", "#1", null], + ["committed fulfilled", "#1", null], + ["promise microtask", "#1", null], + ["navigatesuccess", "#1", null], + ["finished fulfilled", "#1", null], + ]); +}, "event and promise ordering for same-document navigation.navigate()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/reload-canceled.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/reload-canceled.html new file mode 100644 index 0000000000..3e9e24e777 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/reload-canceled.html @@ -0,0 +1,42 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script type="module"> +import { Recorder } from "./resources/helpers.mjs"; + +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + const recorder = new Recorder({ + finalExpectedEvent: "finished rejected" + }); + + recorder.setUpNavigationAPIListeners(); + + navigation.addEventListener("navigate", t.step_func(e => { + e.preventDefault(); + })); + + const result = navigation.reload(); + recorder.setUpResultListeners(result); + + Promise.resolve().then(() => recorder.record("promise microtask")); + + await recorder.readyToAssert; + + recorder.assert([ + /* event name, location.hash value, navigation.transition properties */ + ["navigate", "", null], + ["AbortSignal abort", "", null], + ["navigateerror", "", null], + ["committed rejected", "", null], + ["finished rejected", "", null], + ["promise microtask", "", null] + ]); + + recorder.assertErrorsAreAbortErrors(); +}, "event and promise ordering for navigation.reload() where the navigate event is canceled"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/reload-intercept-reject.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/reload-intercept-reject.html new file mode 100644 index 0000000000..334d2108ec --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/reload-intercept-reject.html @@ -0,0 +1,53 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<meta name="variant" content=""> +<meta name="variant" content="?currententrychange"> + +<script type="module"> +import { Recorder, hasVariant } from "./resources/helpers.mjs"; + +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + const from = navigation.currentEntry; + const expectedError = new Error("boo"); + + const recorder = new Recorder({ + skipCurrentChange: !hasVariant("currententrychange"), + finalExpectedEvent: "transition.finished rejected" + }); + + recorder.setUpNavigationAPIListeners(); + + navigation.addEventListener("navigate", e => { + e.intercept({ handler() { + recorder.record("handler run"); + return Promise.reject(expectedError); + }}); + }); + + const result = navigation.reload(); + recorder.setUpResultListeners(result); + + Promise.resolve().then(() => recorder.record("promise microtask")); + + await recorder.readyToAssert; + + recorder.assert([ + /* event name, location.hash value, navigation.transition properties */ + ["navigate", "", null], + ["currententrychange", "", { from, navigationType: "reload" }], + ["handler run", "", { from, navigationType: "reload" }], + ["committed fulfilled", "", { from, navigationType: "reload" }], + ["promise microtask", "", { from, navigationType: "reload" }], + ["navigateerror", "", { from, navigationType: "reload" }], + ["finished rejected", "", null], + ["transition.finished rejected", "", null], + ]); + + recorder.assertErrorsAre(expectedError); +}, "event and promise ordering for navigation.reload() intercepted by intercept()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/reload-intercept.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/reload-intercept.html new file mode 100644 index 0000000000..8d4160a4d9 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/reload-intercept.html @@ -0,0 +1,47 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<meta name="variant" content=""> +<meta name="variant" content="?currententrychange"> + +<script type="module"> +import { Recorder, hasVariant } from "./resources/helpers.mjs"; + +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + const from = navigation.currentEntry; + + const recorder = new Recorder({ + skipCurrentChange: !hasVariant("currententrychange"), + finalExpectedEvent: "transition.finished fulfilled" + }); + + recorder.setUpNavigationAPIListeners(); + + navigation.addEventListener("navigate", e => { + e.intercept({ handler() { recorder.record("handler run"); } }); + }); + + const result = navigation.reload(); + recorder.setUpResultListeners(result); + + Promise.resolve().then(() => recorder.record("promise microtask")); + + await recorder.readyToAssert; + + recorder.assert([ + /* event name, location.hash value, navigation.transition properties */ + ["navigate", "", null], + ["currententrychange", "", { from, navigationType: "reload" }], + ["handler run", "", { from, navigationType: "reload" }], + ["committed fulfilled", "", { from, navigationType: "reload" }], + ["promise microtask", "", { from, navigationType: "reload" }], + ["navigatesuccess", "", { from, navigationType: "reload" }], + ["finished fulfilled", "", null], + ["transition.finished fulfilled", "", null], + ]); +}, "event and promise ordering for navigation.reload() intercepted by intercept()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/resources/helpers.mjs b/testing/web-platform/tests/navigation-api/ordering-and-transition/resources/helpers.mjs new file mode 100644 index 0000000000..341befc105 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/resources/helpers.mjs @@ -0,0 +1,193 @@ +const variants = new Set((new URLSearchParams(location.search)).keys()); + +export function hasVariant(name) { + return variants.has(name); +} + +export class Recorder { + #events = []; + #errors = []; + #navigationAPI; + #domExceptionConstructor; + #location; + #skipCurrentChange; + #finalExpectedEvent; + #finalExpectedEventCount; + #currentFinalEventCount = 0; + + #readyToAssertResolve; + #readyToAssertPromise = new Promise(resolve => { this.#readyToAssertResolve = resolve; }); + + constructor({ window = self, skipCurrentChange = false, finalExpectedEvent, finalExpectedEventCount = 1 }) { + assert_equals(typeof finalExpectedEvent, "string", "Must pass a string for finalExpectedEvent"); + + this.#navigationAPI = window.navigation; + this.#domExceptionConstructor = window.DOMException; + this.#location = window.location; + + this.#skipCurrentChange = skipCurrentChange; + this.#finalExpectedEvent = finalExpectedEvent; + this.#finalExpectedEventCount = finalExpectedEventCount; + } + + setUpNavigationAPIListeners() { + this.#navigationAPI.addEventListener("navigate", e => { + this.record("navigate"); + + e.signal.addEventListener("abort", () => { + this.recordWithError("AbortSignal abort", e.signal.reason); + }); + }); + + this.#navigationAPI.addEventListener("navigateerror", e => { + this.recordWithError("navigateerror", e.error); + + this.#navigationAPI.transition?.finished.then( + () => this.record("transition.finished fulfilled"), + err => this.recordWithError("transition.finished rejected", err) + ); + }); + + this.#navigationAPI.addEventListener("navigatesuccess", () => { + this.record("navigatesuccess"); + + this.#navigationAPI.transition?.finished.then( + () => this.record("transition.finished fulfilled"), + err => this.recordWithError("transition.finished rejected", err) + ); + }); + + if (!this.#skipCurrentChange) { + this.#navigationAPI.addEventListener("currententrychange", () => this.record("currententrychange")); + } + } + + setUpResultListeners(result, suffix = "") { + result.committed.then( + () => this.record(`committed fulfilled${suffix}`), + err => this.recordWithError(`committed rejected${suffix}`, err) + ); + + result.finished.then( + () => this.record(`finished fulfilled${suffix}`), + err => this.recordWithError(`finished rejected${suffix}`, err) + ); + } + + record(name) { + const transitionProps = this.#navigationAPI.transition === null ? null : { + from: this.#navigationAPI.transition.from, + navigationType: this.#navigationAPI.transition.navigationType + }; + + this.#events.push({ name, location: this.#location.hash, transitionProps }); + + if (name === this.#finalExpectedEvent && ++this.#currentFinalEventCount === this.#finalExpectedEventCount) { + this.#readyToAssertResolve(); + } + } + + recordWithError(name, errorObject) { + this.record(name); + this.#errors.push({ name, errorObject }); + } + + get readyToAssert() { + return this.#readyToAssertPromise; + } + + // Usage: + // recorder.assert([ + // /* event name, location.hash value, navigation.transition properties */ + // ["currententrychange", "", null], + // ["committed fulfilled", "#1", { from, navigationType }], + // ... + // ]); + // + // The array format is to avoid repitition at the call site, but I recommend + // you document it like above. + // + // This will automatically also assert that any error objects recorded are + // equal to each other. Use the other assert functions to check the actual + // contents of the error objects. + assert(expectedAsArray) { + if (this.#skipCurrentChange) { + expectedAsArray = expectedAsArray.filter(expected => expected[0] !== "currententrychange"); + } + + // Doing this up front gives nicer error messages because + // assert_array_equals is nice. + const recordedNames = this.#events.map(e => e.name); + const expectedNames = expectedAsArray.map(e => e[0]); + assert_array_equals(recordedNames, expectedNames); + + for (let i = 0; i < expectedAsArray.length; ++i) { + const recorded = this.#events[i]; + const expected = expectedAsArray[i]; + + assert_equals( + recorded.location, + expected[1], + `event ${i} (${recorded.name}): location.hash value` + ); + + if (expected[2] === null) { + assert_equals( + recorded.transitionProps, + null, + `event ${i} (${recorded.name}): navigation.transition expected to be null` + ); + } else { + assert_not_equals( + recorded.transitionProps, + null, + `event ${i} (${recorded.name}): navigation.transition expected not to be null` + ); + assert_equals( + recorded.transitionProps.from, + expected[2].from, + `event ${i} (${recorded.name}): navigation.transition.from` + ); + assert_equals( + recorded.transitionProps.navigationType, + expected[2].navigationType, + `event ${i} (${recorded.name}): navigation.transition.navigationType` + ); + } + } + + if (this.#errors.length > 1) { + for (let i = 1; i < this.#errors.length; ++i) { + assert_equals( + this.#errors[i].errorObject, + this.#errors[0].errorObject, + `error objects must match: error object for ${this.#errors[i].name} did not match the one for ${this.#errors[0].name}` + ); + } + } + } + + assertErrorsAreAbortErrors() { + assert_greater_than( + this.#errors.length, + 0, + "No errors were recorded but assertErrorsAreAbortErrors() was called" + ); + + // Assume assert() has been called so all error objects are the same. + const { errorObject } = this.#errors[0]; + assert_throws_dom("AbortError", this.#domExceptionConstructor, () => { throw errorObject; }); + } + + assertErrorsAre(expectedErrorObject) { + assert_greater_than( + this.#errors.length, + 0, + "No errors were recorded but assertErrorsAre() was called" + ); + + // Assume assert() has been called so all error objects are the same. + const { errorObject } = this.#errors[0]; + assert_equals(errorObject, expectedErrorObject); + } +} diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/resources/notify-top-early.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/resources/notify-top-early.html new file mode 100644 index 0000000000..0dd796f609 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/resources/notify-top-early.html @@ -0,0 +1,6 @@ +<head> +<script> +if (top.childStarted) + top.childStarted(); +</script> +</head> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/transition-cross-document.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/transition-cross-document.html new file mode 100644 index 0000000000..4a14a1083d --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/transition-cross-document.html @@ -0,0 +1,44 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(r => window.onload = () => t.step_timeout(r, 0)); + + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func("navigatesuccess must not fire"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("navigateerror must not fire"); + + assert_equals(i.contentWindow.navigation.transition, null); + i.contentWindow.navigation.reload(); + assert_equals(i.contentWindow.navigation.transition, null); + + await new Promise(r => i.onload = () => t.step_timeout(r, 0)); +}, "cross-document reload() must leave transition null"); + +promise_test(async t => { + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func("navigatesuccess must not fire"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("navigateerror must not fire"); + + assert_equals(i.contentWindow.navigation.transition, null); + i.contentWindow.navigation.navigate("?1"); + assert_equals(i.contentWindow.navigation.transition, null); + + await new Promise(r => i.onload = () => t.step_timeout(r, 0)); +}, "cross-document navigate() must leave transition null"); + +promise_test(async t => { + i.contentWindow.navigation.onnavigatesuccess = t.unreached_func("navigatesuccess must not fire"); + i.contentWindow.navigation.onnavigateerror = t.unreached_func("navigateerror must not fire"); + + assert_equals(i.contentWindow.navigation.transition, null); + i.contentWindow.navigation.back(); + assert_equals(i.contentWindow.navigation.transition, null); + + await new Promise(r => i.onload = r); +}, "cross-document back() must leave transition null"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/transition-finished-mark-as-handled.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/transition-finished-mark-as-handled.html new file mode 100644 index 0000000000..fa38b82216 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/transition-finished-mark-as-handled.html @@ -0,0 +1,20 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +async_test(t => { + navigation.addEventListener("navigate", e => { + e.intercept({ handler: () => Promise.reject(new Error("oh no!")) }); + }); + + window.onunhandledrejection = t.unreached_func("unhandledrejection must not fire"); + + location.href = "?1"; + + // Make sure to trigger the getter to ensure the promise materializes! + navigation.transition.finished; + + t.step_timeout(() => t.done(), 10); +}, "navigation.transition.finished must not trigger unhandled rejections"); +</script> diff --git a/testing/web-platform/tests/navigation-api/ordering-and-transition/transition-realms-and-identity.html b/testing/web-platform/tests/navigation-api/ordering-and-transition/transition-realms-and-identity.html new file mode 100644 index 0000000000..f1b3ead980 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/ordering-and-transition/transition-realms-and-identity.html @@ -0,0 +1,41 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<iframe id="i" src="/common/blank.html"></iframe> + +<script> +promise_test(async () => { + await new Promise(resolve => window.onload = resolve); + + i.contentWindow.navigation.addEventListener("navigate", e => { + e.intercept(); + }); + + const returnValueFinished1 = i.contentWindow.navigation.navigate("?1").finished; + const transition1 = i.contentWindow.navigation.transition; + const transitionFinished1 = transition1.finished; + + assert_true(returnValueFinished1 instanceof i.contentWindow.Promise); + assert_true(transition1 instanceof i.contentWindow.NavigationTransition); + assert_true(transitionFinished1 instanceof i.contentWindow.Promise); + + assert_not_equals(returnValueFinished1, transitionFinished1); + + // Ensure the getters aren't generating new objects each time. + assert_equals(i.contentWindow.navigation.transition, transition1); + assert_equals(i.contentWindow.navigation.transition.finished, transitionFinished1); + + assert_equals(await transitionFinished1, undefined); + + // Ensure stuff does change after another navigation. + const committed2 = i.contentWindow.navigation.navigate("?2").committed; + const transition2 = i.contentWindow.navigation.transition; + const transitionFinished2 = transition2.finished; + + assert_not_equals(transition2, transition1); + assert_not_equals(transitionFinished2, transitionFinished1); + + await committed2; +}, "Realm and identity of the navigation.transition object and its finished promise"); +</script> diff --git a/testing/web-platform/tests/navigation-api/per-entry-events/dispose-after-bfcache.html b/testing/web-platform/tests/navigation-api/per-entry-events/dispose-after-bfcache.html new file mode 100644 index 0000000000..7d3ef4f81e --- /dev/null +++ b/testing/web-platform/tests/navigation-api/per-entry-events/dispose-after-bfcache.html @@ -0,0 +1,32 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js"></script> +<script src="resources/is_uuid.js"></script> + +<script> +// This test: +// * Does a fragment navigation, then goes back (same-document). +// * Navigates away, then back via bfcache. +// When navigating away, navigation.entries()[1] will be overwritten. +// When returning after bfcache restore, navigation.entries()[1] should represent +// pageB, and the original navigation.entries()[1] should have been disposed. +runBfcacheTest({ + targetOrigin: originSameOrigin, + funcBeforeNavigation: async () => { + window.events = []; + await navigation.navigate("#1"); + await navigation.back(); + window.originalEntry1 = navigation.entries()[1]; + window.originalEntry1.ondispose = () => events.push("dispose"); + window.onpageshow = () => events.push("pageshow"); + }, + async funcAfterAssertion(pageA, pageB) { + assert_equals(await pageA.execute_script(() => navigation.entries().length), 2); + assert_false(await pageA.execute_script(() => window.originalEntry1 === navigation.entries[1])); + assert_array_equals(await pageA.execute_script(() => window.events), ["pageshow", "dispose"]); + } +}, "entries() must contain the forward-history page after navigating back to a bfcached page"); +</script> diff --git a/testing/web-platform/tests/navigation-api/per-entry-events/dispose-cross-document.html b/testing/web-platform/tests/navigation-api/per-entry-events/dispose-cross-document.html new file mode 100644 index 0000000000..67f54da48d --- /dev/null +++ b/testing/web-platform/tests/navigation-api/per-entry-events/dispose-cross-document.html @@ -0,0 +1,37 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="iframe" src="/common/blank.html"></iframe> +<script> +promise_test(async (t) => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(r => window.onload = () => t.step_timeout(r, 0)); + + iframe.contentWindow.location.search = "?1"; + await new Promise(r => iframe.onload = () => t.step_timeout(r, 0)); + + const keyFor1 = iframe.contentWindow.navigation.currentEntry.key; + + iframe.contentWindow.location.search = "?2"; + await new Promise(r => iframe.onload = () => t.step_timeout(r, 0)); + iframe.contentWindow.location.search = "?3"; + await new Promise(r => iframe.onload = () => t.step_timeout(r, 0)); + + iframe.contentWindow.navigation.traverseTo(keyFor1); + await new Promise(r => iframe.onload = () => t.step_timeout(r, 0)); + + assert_equals((new URL(iframe.contentWindow.location.href)).search, "?1"); + + assert_equals(iframe.contentWindow.navigation.entries().length, 4); + const [, entry2, entry3] = iframe.contentWindow.navigation.entries(); + + entry2.ondispose = t.unreached_func("entry2 dispose must not fire"); + entry2.addEventListener("dispose", t.unreached_func("entry3 dispose must not fire")); + + iframe.contentWindow.navigation.navigate("/common/blank.html?fork"); + await new Promise(r => iframe.onload = r); + + // Test passes if we reached this point with no dispose events firing. +}, "No dispose events are fired due to cross-document forward pruning"); +</script> diff --git a/testing/web-platform/tests/navigation-api/per-entry-events/dispose-for-full-session-history.tentative.html b/testing/web-platform/tests/navigation-api/per-entry-events/dispose-for-full-session-history.tentative.html new file mode 100644 index 0000000000..9bed225cf3 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/per-entry-events/dispose-for-full-session-history.tentative.html @@ -0,0 +1,20 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async (t) => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(r => window.onload = () => t.step_timeout(r, 0)); + assert_equals(navigation.entries().length, 1); + + let dispose_promise = new Promise(r => navigation.entries()[0].ondispose = r); + + // There is no spec for the maximum number of joint session history entries + // (hence this is a .tentative.html test). However, all(?) browsers have a + // settled on a maximum of 50. + for (let i = 0; i < 50; i++) + await navigation.navigate("#" + i).finished; + await dispose_promise; +}, "Dispose should fire when an entry is removed from session history due to too many entries"); +</script> diff --git a/testing/web-platform/tests/navigation-api/per-entry-events/dispose-for-navigation-in-child.html b/testing/web-platform/tests/navigation-api/per-entry-events/dispose-for-navigation-in-child.html new file mode 100644 index 0000000000..2482877085 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/per-entry-events/dispose-for-navigation-in-child.html @@ -0,0 +1,41 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="iframe" src="/common/blank.html"></iframe> +<script> +promise_test(async (t) => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(r => window.onload = () => t.step_timeout(r, 0)); + + await iframe.contentWindow.navigation.navigate("#a"); + await navigation.navigate("#1").finished; + await navigation.navigate("#2").finished; + await navigation.navigate("#3").finished; + await iframe.contentWindow.navigation.back().finished; + assert_equals(navigation.entries().length, 4); + assert_equals(iframe.contentWindow.navigation.entries().length, 2); + assert_equals(navigation.currentEntry.index, 0); + assert_equals(iframe.contentWindow.navigation.currentEntry.index, 0); + + let top_entry_0_before = navigation.entries()[0]; + let dispose_promises = []; + for (let i = 1; i < navigation.entries().length; i++) { + dispose_promises.push(new Promise(r => navigation.entries()[i].ondispose = r)) + } + + // This push navigation should truncate in the top window. + await iframe.contentWindow.navigation.navigate("#b").finished; + + // entries() should be updated in both frames, and all forward entries in the + // top window should have dispose events, even though that wasn't the window + // that navigated. + await Promise.all(dispose_promises); + assert_equals(navigation.entries().length, 1); + assert_equals(iframe.contentWindow.navigation.entries().length, 2); + assert_equals(navigation.currentEntry.index, 0); + assert_equals(iframe.contentWindow.navigation.currentEntry.index, 1); + assert_equals(navigation.entries()[0], top_entry_0_before); + +}, "Dispose events should fire when entries are removed by a navigation in a different frame"); +</script> diff --git a/testing/web-platform/tests/navigation-api/per-entry-events/dispose-same-document-intercept.html b/testing/web-platform/tests/navigation-api/per-entry-events/dispose-same-document-intercept.html new file mode 100644 index 0000000000..44aa096aaf --- /dev/null +++ b/testing/web-platform/tests/navigation-api/per-entry-events/dispose-same-document-intercept.html @@ -0,0 +1,71 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async (t) => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(r => window.onload = () => t.step_timeout(r, 0)); + + location.hash = "#1"; + location.hash = "#2"; + location.hash = "#3"; + + assert_equals(navigation.entries().length, 4); + const [entry0, entry1, entry2, entry3] = navigation.entries(); + assert_equals((new URL(entry2.url)).hash, "#2"); + assert_equals((new URL(entry3.url)).hash, "#3"); + + let dispose2Called = false; + entry2.ondispose = t.step_func(e => { + dispose2Called = true; + + assert_equals(e.constructor, Event); + assert_equals(e.bubbles, false); + assert_equals(e.cancelable, false); + assert_equals(e.composed, false); + + assert_array_equals( + navigation.entries(), + [entry0, entry1, navigation.currentEntry], + "entries() is updated during dispose for entry 2"); + assert_not_equals(navigation.currentEntry, entry1, "current entry must be updated during dispose for entry 3"); + assert_true(navigation.canGoBack, "canGoBack is still true during dispose for entry 2"); + assert_false(navigation.canGoForward, "canGoForward is still false during beforedispose for entry 2"); + assert_equals(navigation.transition.navigationType, "push", "transition navigationType during dispose for entry 2"); + assert_equals(navigation.transition.from, entry1, "transition from during dispose for entry 2"); + assert_equals(location.hash, "#fork", "location.hash is updated during dispose for entry 2"); + }); + + entry3.addEventListener("dispose", t.step_func_done(e => { + assert_true(dispose2Called, "dispose for entry 2 must have happened before entry 3"); + + assert_array_equals( + navigation.entries(), + [entry0, entry1, navigation.currentEntry], + "entries() is updated during dispose for entry 3"); + assert_not_equals(navigation.currentEntry, entry1, "current entry must be updated during dispose for entry 3"); + assert_true(navigation.canGoBack, "canGoBack is still true during dispose for entry 3"); + assert_false(navigation.canGoForward, "canGoForward is still false during beforedispose for entry 3"); + assert_equals(navigation.transition.navigationType, "push", "transition navigationType during dispose for entry 3"); + assert_equals(navigation.transition.from, entry1, "transition from during dispose for entry 3"); + assert_equals(location.hash, "#fork", "location.hash is updated during dispose for entry 3"); + })); + + await navigation.traverseTo(entry1.key).committed; + + navigation.addEventListener("navigate", e => { + e.intercept(); + }); + + navigation.navigate("#fork"); + + assert_equals(navigation.entries().length, 3); + const [finalEntry0, finalEntry1, finalEntry2] = navigation.entries(); + assert_equals(finalEntry0, entry0); + assert_equals(finalEntry1, entry1); + assert_not_equals(finalEntry2, entry2); + assert_equals(navigation.currentEntry, finalEntry2); + assert_equals((new URL(finalEntry2.url)).hash, "#fork"); +}, "dispose events when forward-pruning same-document entries"); +</script> diff --git a/testing/web-platform/tests/navigation-api/per-entry-events/dispose-same-document-navigate-during.html b/testing/web-platform/tests/navigation-api/per-entry-events/dispose-same-document-navigate-during.html new file mode 100644 index 0000000000..59d9b3cce3 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/per-entry-events/dispose-same-document-navigate-during.html @@ -0,0 +1,50 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async (t) => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(r => window.onload = () => t.step_timeout(r, 0)); + + location.hash = "#1"; + location.hash = "#2"; + location.hash = "#3"; + + assert_equals(navigation.entries().length, 4); + const [entry0, entry1, entry2, entry3] = navigation.entries(); + assert_equals((new URL(entry2.url)).hash, "#2"); + assert_equals((new URL(entry3.url)).hash, "#3"); + + let dispose3Called = 0; + let spoonPromise; + entry3.addEventListener("dispose", t.step_func(e => { + ++dispose3Called; + + spoonPromise = navigation.navigate("#spoon").committed; + })); + + await navigation.traverseTo(entry1.key).committed; + + const forkPromise = navigation.navigate("#fork").committed; + assert_equals(dispose3Called, 1, "dispose for entry 3 must happen exactly once (first check)") + + // The navigation to #fork will *not* be finished by the time the navigation + // to #spoon kicks off, so the finished promise will reject. But, the + // committed promise should still fulfill, since as we see below, #fork ends + // up in the history list. + await Promise.all([forkPromise, spoonPromise]); + + assert_equals(dispose3Called, 1, "dispose for entry 3 must happen exactly once (final check)"); + + assert_equals(navigation.entries().length, 4); + const [finalEntry0, finalEntry1, finalEntry2, finalEntry3] = navigation.entries(); + assert_equals(finalEntry0, entry0); + assert_equals(finalEntry1, entry1); + assert_not_equals(finalEntry2, entry2); + assert_not_equals(finalEntry3, entry3); + assert_equals(navigation.currentEntry, finalEntry3); + assert_equals((new URL(finalEntry2.url)).hash, "#fork"); + assert_equals((new URL(finalEntry3.url)).hash, "#spoon"); +}, "navigate() during a same-document-navigation-initiated dispose works (since it's after the previous navigation)"); +</script> diff --git a/testing/web-platform/tests/navigation-api/per-entry-events/dispose-same-document-reload-with-intercept.html b/testing/web-platform/tests/navigation-api/per-entry-events/dispose-same-document-reload-with-intercept.html new file mode 100644 index 0000000000..1e083714f6 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/per-entry-events/dispose-same-document-reload-with-intercept.html @@ -0,0 +1,17 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +promise_test(async (t) => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(r => window.onload = () => t.step_timeout(r, 0)); + + navigation.currentEntry.ondispose = t.unreached_func("dispose must not happen for reloads"); + + navigation.addEventListener("navigate", e => e.intercept()); + + await navigation.reload().finished; +}, "dispose events are not fired when doing a same-document reload using navigation.reload() and intercept()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/per-entry-events/dispose-same-document-replace-with-intercept.html b/testing/web-platform/tests/navigation-api/per-entry-events/dispose-same-document-replace-with-intercept.html new file mode 100644 index 0000000000..4e492e30ae --- /dev/null +++ b/testing/web-platform/tests/navigation-api/per-entry-events/dispose-same-document-replace-with-intercept.html @@ -0,0 +1,36 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async (t) => { + // Wait for after the load event so that we're definitely testing intentional, + // navigate()-caused replacement and not the replacement that happens + // automatically before the load event completes. + await new Promise(r => window.onload = () => t.step_timeout(r, 0)); + + const entriesBefore = navigation.entries(); + const currentBefore = navigation.currentEntry; + + let disposeCalled = false; + navigation.currentEntry.ondispose = t.step_func(e => { + disposeCalled = true; + + assert_equals(e.constructor, Event); + assert_equals(e.bubbles, false); + assert_equals(e.cancelable, false); + assert_equals(e.composed, false); + + assert_not_equals(navigation.currentEntry, currentBefore); + assert_array_equals(navigation.entries(), [navigation.currentEntry]); + assert_equals((new URL(navigation.currentEntry.url)).search, "?replacement"); + assert_equals(navigation.transition.navigationType, "replace"); + assert_equals(navigation.transition.from, entriesBefore[0]); + assert_equals(location.search, "?replacement"); + }); + + navigation.addEventListener("navigate", e => e.intercept()); + + navigation.navigate("?replacement", { history: "replace" }); + assert_true(disposeCalled); +}, "dispose events when doing a same-document replace using navigation.navigate() and intercept()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/per-entry-events/dispose-same-document-replaceState.html b/testing/web-platform/tests/navigation-api/per-entry-events/dispose-same-document-replaceState.html new file mode 100644 index 0000000000..a6197260a2 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/per-entry-events/dispose-same-document-replaceState.html @@ -0,0 +1,28 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +test(t => { + const entriesBefore = navigation.entries(); + const currentBefore = navigation.currentEntry; + + let disposeCalled = false; + navigation.currentEntry.ondispose = t.step_func(e => { + disposeCalled = true; + + assert_equals(e.constructor, Event); + assert_equals(e.bubbles, false); + assert_equals(e.cancelable, false); + assert_equals(e.composed, false); + + assert_not_equals(navigation.currentEntry, currentBefore); + assert_array_equals(navigation.entries(), [navigation.currentEntry]); + assert_equals((new URL(navigation.currentEntry.url)).search, "?replacement"); + assert_equals(navigation.transition, null); + assert_equals(location.search, "?replacement"); + }); + + history.replaceState(null, "", "?replacement"); + assert_true(disposeCalled); +}, "dispose events when doing a same-document replace using history.replaceState()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/per-entry-events/dispose-same-document.html b/testing/web-platform/tests/navigation-api/per-entry-events/dispose-same-document.html new file mode 100644 index 0000000000..27806ce3c8 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/per-entry-events/dispose-same-document.html @@ -0,0 +1,65 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async (t) => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(r => window.onload = () => t.step_timeout(r, 0)); + + location.hash = "#1"; + location.hash = "#2"; + location.hash = "#3"; + + assert_equals(navigation.entries().length, 4); + const [entry0, entry1, entry2, entry3] = navigation.entries(); + assert_equals((new URL(entry2.url)).hash, "#2"); + assert_equals((new URL(entry3.url)).hash, "#3"); + + let dispose2Called = false; + entry2.ondispose = t.step_func(e => { + dispose2Called = true; + + assert_equals(e.constructor, Event); + assert_equals(e.bubbles, false); + assert_equals(e.cancelable, false); + assert_equals(e.composed, false); + + assert_array_equals( + navigation.entries(), + [entry0, entry1, navigation.currentEntry], + "entries() is updated during dispose for entry 2"); + assert_not_equals(navigation.currentEntry, entry1, "current entry must be updated during dispose for entry 3"); + assert_true(navigation.canGoBack, "canGoBack is still true during dispose for entry 2"); + assert_false(navigation.canGoForward, "canGoForward is still false during beforedispose for entry 2"); + assert_equals(navigation.transition, null, "transition during dispose for entry 2"); + assert_equals(location.hash, "#fork", "location.hash is updated during dispose for entry 2"); + }); + + entry3.addEventListener("dispose", t.step_func_done(e => { + assert_true(dispose2Called, "dispose for entry 2 must have happened before entry 3"); + + assert_array_equals( + navigation.entries(), + [entry0, entry1, navigation.currentEntry], + "entries() is updated during dispose for entry 3"); + assert_not_equals(navigation.currentEntry, entry1, "current entry must be updated during dispose for entry 3"); + assert_true(navigation.canGoBack, "canGoBack is still true during dispose for entry 3"); + assert_false(navigation.canGoForward, "canGoForward is still false during beforedispose for entry 3"); + assert_equals(navigation.transition, null, "transition during dispose for entry 2"); + assert_equals(location.hash, "#fork", "location.hash is updated during dispose for entry 3"); + })); + + await navigation.traverseTo(entry1.key).committed; + + navigation.navigate("#fork"); + + assert_equals(navigation.entries().length, 3); + const [finalEntry0, finalEntry1, finalEntry2] = navigation.entries(); + assert_equals(finalEntry0, entry0); + assert_equals(finalEntry1, entry1); + assert_not_equals(finalEntry2, entry2); + assert_equals(navigation.currentEntry, finalEntry2); + assert_equals((new URL(finalEntry2.url)).hash, "#fork"); +}, "dispose events when forward-pruning same-document entries"); +</script> diff --git a/testing/web-platform/tests/navigation-api/per-entry-events/dispose-skip-current-on-truncate.html b/testing/web-platform/tests/navigation-api/per-entry-events/dispose-skip-current-on-truncate.html new file mode 100644 index 0000000000..56ec4d301f --- /dev/null +++ b/testing/web-platform/tests/navigation-api/per-entry-events/dispose-skip-current-on-truncate.html @@ -0,0 +1,40 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(async (t) => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(r => window.onload = () => t.step_timeout(r, 0)); + await navigation.navigate("#1").finished; + assert_equals(navigation.entries().length, 2); + + let iframe = document.createElement("iframe"); + iframe.src = "/common/blank.html"; + document.body.appendChild(iframe); + await new Promise(r => iframe.onload = () => t.step_timeout(r, 0)); + assert_equals(iframe.contentWindow.navigation.entries().length, 1); + + // Go back to before the iframe was added. The iframe will still be in the + // document, but we will be at a joint session history entry that was created + // before the iframe was added to the document. + await navigation.back().finished; + assert_equals(navigation.entries().length, 2); + assert_equals(iframe.contentWindow.navigation.entries().length, 1); + assert_equals(navigation.currentEntry.index, 0); + assert_equals(iframe.contentWindow.navigation.currentEntry.index, 0); + + // A push navigation in the top window will truncate-then-push the joint + // session history. This should dispose the forward entry in the top window, + // but should not interfere with the currentEntry in the iframe. + let dispose_promise = new Promise(r => navigation.entries()[1].ondispose = r); + iframe.contentWindow.navigation.currentEntry.ondispose = t.unreached_func("iframe entry must not be disposed"); + await navigation.navigate("#b").finished; + + await dispose_promise; + assert_equals(navigation.entries().length, 2); + assert_equals(iframe.contentWindow.navigation.entries().length, 1); + assert_equals(navigation.currentEntry.index, 1); + assert_equals(iframe.contentWindow.navigation.currentEntry.index, 0); +}, "Removing a currentEntry from the joint session history shouldn't dispose it"); +</script> diff --git a/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-basic.html b/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-basic.html new file mode 100644 index 0000000000..8ee4cc395b --- /dev/null +++ b/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-basic.html @@ -0,0 +1,20 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div style="height: 1000px; width: 1000px;"></div> +<div id="frag"></div> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + assert_equals(window.scrollY, 0); + await navigation.navigate("#frag").finished; + assert_not_equals(window.scrollY, 0); + navigation.onnavigate = e => e.intercept({ scroll: "after-transition" }); + await navigation.back().finished; + assert_equals(window.scrollY, 0); +}, "scroll: after-transition should scroll when back completes"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-change-history-scroll-restoration-during-promise.html b/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-change-history-scroll-restoration-during-promise.html new file mode 100644 index 0000000000..6ededdeebf --- /dev/null +++ b/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-change-history-scroll-restoration-during-promise.html @@ -0,0 +1,32 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div style="height: 1000px; width: 1000px;"></div> +<div id="frag"></div> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + assert_equals(window.scrollY, 0); + + history.scrollRestoration = "manual"; + await navigation.navigate("#frag").finished; + assert_not_equals(window.scrollY, 0); + + let intercept_resolve; + navigation.onnavigate = e => { + e.intercept({ handler: () => new Promise(r => intercept_resolve = r), + scroll: "after-transition" }); + }; + + let back_promises = navigation.back(); + await back_promises.committed; + history.scrollRestoration = "auto"; + intercept_resolve(); + await back_promises.finished; + assert_equals(window.scrollY, 0); +}, "scroll: after-transition should ignore history.scrollRestoration even if it changes in the middle of the navigation"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-explicit-scroll.html b/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-explicit-scroll.html new file mode 100644 index 0000000000..4b7d075474 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-explicit-scroll.html @@ -0,0 +1,29 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div style="height: 1000px; width: 1000px;"></div> +<div id="frag"></div> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + assert_equals(window.scrollY, 0); + await navigation.navigate("#frag").finished; + assert_not_equals(window.scrollY, 0); + navigation.onnavigate = t.step_func(e => { + e.intercept({ + scroll: "after-transition", + handler: t.step_func(() => { + assert_not_equals(window.scrollY, 0); + e.scroll(); + assert_equals(window.scrollY, 0); + }) + }); + }); + await navigation.back().finished; + assert_equals(window.scrollY, 0); +}, "scroll: scroll() should preempt after-transition"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-intercept-handler-modifies.html b/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-intercept-handler-modifies.html new file mode 100644 index 0000000000..78d1692104 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-intercept-handler-modifies.html @@ -0,0 +1,33 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div id="main" style="height: 1000px; width: 1000px;"></div> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + assert_equals(window.scrollY, 0); + window.scrollTo(0, 100); + assert_equals(window.scrollY, 100); + navigation.onnavigate = e => e.intercept( + { scroll: "after-transition", + handler: async () => { + if (e.navigationType == "push") { + // Inserting this <div> should scroll *after* the scroll position + // is saved, so that it doesn't break scroll restoration when going + // back. + let div = document.createElement("div"); + div.style = "height: 1000px; width: 1000px;"; + document.body.insertBefore(div, main); + } + } + } + ); + await navigation.navigate("?go").finished; + await navigation.back().finished; + assert_equals(window.scrollY, 100); +}, "scroll: state should be saved before intercept handlers run"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-push.html b/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-push.html new file mode 100644 index 0000000000..f3ee1827bc --- /dev/null +++ b/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-push.html @@ -0,0 +1,29 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div style="height: 1000px; width: 1000px;"></div> +<div id="frag"></div> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + assert_equals(window.scrollY, 0); + + let intercept_resolve; + let navigate_event; + navigation.onnavigate = e => { + navigate_event = e; + e.intercept({ handler: () => new Promise(r => intercept_resolve = r), + scroll: "after-transition" }); + }; + let promises = navigation.navigate("#frag"); + await promises.committed; + assert_equals(window.scrollY, 0); + intercept_resolve(); + await promises.finished; + assert_not_equals(window.scrollY, 0); +}, "scroll: after-transition should work on a push navigation"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-reject.html b/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-reject.html new file mode 100644 index 0000000000..5880dbb331 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-reject.html @@ -0,0 +1,23 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div style="height: 1000px; width: 1000px;"></div> +<div id="frag"></div> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + assert_equals(window.scrollY, 0); + await navigation.navigate("#frag").finished; + assert_not_equals(window.scrollY, 0); + navigation.onnavigate = + e => e.intercept({ handler: () => Promise.reject(), + scroll: "after-transition" }); + + await promise_rejects_exactly(t, undefined, navigation.back().finished); + assert_not_equals(window.scrollY, 0); +}, "scroll: after-transition should not scroll when the intercept() handler rejects"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-reload.html b/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-reload.html new file mode 100644 index 0000000000..badb7e7f41 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-reload.html @@ -0,0 +1,41 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div id="buffer" style="height: 1000px; width: 1000px;"></div> +<div id="frag"></div> +<div style="height: 1000px; width: 1000px;"></div> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + assert_equals(window.scrollY, 0); + await navigation.navigate("#frag").finished; + // Scroll down 10px from #frag + window.scrollBy(0, 10); + let scrollY_frag_plus_10 = window.scrollY; + + let intercept_resolve; + let navigate_event; + navigation.onnavigate = e => { + navigate_event = e; + e.intercept({ handler: () => new Promise(r => intercept_resolve = r), + scroll: "after-transition" }); + }; + let reload_promises = navigation.reload(); + await reload_promises.committed; + + // removing the <div id="buffer"> should scroll up 1000px. + assert_equals(window.scrollY, scrollY_frag_plus_10); + buffer.remove(); + let scrollY_after_buffer_remove = window.scrollY; + assert_equals(scrollY_after_buffer_remove, scrollY_frag_plus_10 - 1000); + + // Finishing should scroll to #frag, which is 10px up from the current location + intercept_resolve(); + await reload_promises.finished; + assert_equals(window.scrollY, scrollY_after_buffer_remove - 10); +}, "scroll: after-transition should work on a reload navigation"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-replace.html b/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-replace.html new file mode 100644 index 0000000000..48f153b99e --- /dev/null +++ b/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-replace.html @@ -0,0 +1,29 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div style="height: 1000px; width: 1000px;"></div> +<div id="frag"></div> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + assert_equals(window.scrollY, 0); + + let intercept_resolve; + let navigate_event; + navigation.onnavigate = e => { + navigate_event = e; + e.intercept({ handler: () => new Promise(r => intercept_resolve = r), + scroll: "after-transition" }); + }; + let promises = navigation.navigate("#frag", { history: "replace" }); + await promises.committed; + assert_equals(window.scrollY, 0); + intercept_resolve(); + await promises.finished; + assert_not_equals(window.scrollY, 0); +}, "scroll: after-transition should work on a replace navigation"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-timing.html b/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-timing.html new file mode 100644 index 0000000000..88ba82102a --- /dev/null +++ b/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-timing.html @@ -0,0 +1,48 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div style="height: 1000px; width: 1000px;"></div> +<div id="frag"></div> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + assert_equals(window.scrollY, 0); + await navigation.navigate("#frag").finished; + assert_not_equals(window.scrollY, 0); + + window.onpopstate = t.step_func(() => assert_not_equals(window.scrollY, 0)); + window.onhashchange = t.step_func(() => assert_not_equals(window.scrollY, 0)); + + // The scroll restore should take place before navigatesuccess fires. + let navigatesuccess_called = false; + navigation.onnavigatesuccess = t.step_func(() => { + navigatesuccess_called = true; + assert_equals(window.scrollY, 0); + }); + + let back_promises = navigation.back(); + navigation.onnavigate = t.step_func(e => { + e.intercept({ scroll: "after-transition", + handler: async () => { + // The ordering here should be: + // * intercept() is called + // * back_promises.committed is resolved + // * this handler runs. + // If this handler incorrectly blocks back_promises.committed, + // this test will hang. + await back_promises.committed; + assert_not_equals(window.scrollY, 0); + } + }); + assert_not_equals(window.scrollY, 0); + }); + + await back_promises.finished; + assert_equals(window.scrollY, 0); + assert_true(navigatesuccess_called); +}, "scroll: after-transition should scroll when back completes, just before navigatesuccess"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-with-history-scroll-restoration-manual.html b/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-with-history-scroll-restoration-manual.html new file mode 100644 index 0000000000..bc2ce230ce --- /dev/null +++ b/testing/web-platform/tests/navigation-api/scroll-behavior/after-transition-with-history-scroll-restoration-manual.html @@ -0,0 +1,23 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div style="height: 1000px; width: 1000px;"></div> +<div id="frag"></div> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + assert_equals(window.scrollY, 0); + + history.scrollRestoration = "manual"; + await navigation.navigate("#frag").finished; + assert_not_equals(window.scrollY, 0); + + navigation.onnavigate = e => e.intercept({ scroll: "after-transition" }); + await navigation.back().finished; + assert_equals(window.scrollY, 0); +}, "scroll: after-transition should ignore history.scrollRestoration"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/scroll-behavior/manual-basic.html b/testing/web-platform/tests/navigation-api/scroll-behavior/manual-basic.html new file mode 100644 index 0000000000..e6ae29ad86 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/scroll-behavior/manual-basic.html @@ -0,0 +1,20 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div style="height: 1000px; width: 1000px;"></div> +<div id="frag"></div> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + assert_equals(window.scrollY, 0); + await navigation.navigate("#frag").finished; + assert_not_equals(window.scrollY, 0); + navigation.onnavigate = e => e.intercept({ scroll: "manual" }); + await navigation.back().finished; + assert_not_equals(window.scrollY, 0); +}, "scroll: manual should prevent auto scroll on back."); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/scroll-behavior/manual-immediate-scroll.html b/testing/web-platform/tests/navigation-api/scroll-behavior/manual-immediate-scroll.html new file mode 100644 index 0000000000..bafcf6b256 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/scroll-behavior/manual-immediate-scroll.html @@ -0,0 +1,24 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div style="height: 1000px; width: 1000px;"></div> +<div id="frag"></div> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + assert_equals(window.scrollY, 0); + await navigation.navigate("#frag").finished; + assert_not_equals(window.scrollY, 0); + navigation.onnavigate = e => { + e.intercept({ scroll: "manual" }); + assert_throws_dom("InvalidStateError", () => e.scroll()); + assert_not_equals(window.scrollY, 0); + } + await navigation.back().finished; + assert_not_equals(window.scrollY, 0); +}, "scroll: scroll() should not work inside a navigate event handler"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-after-dispatch.html b/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-after-dispatch.html new file mode 100644 index 0000000000..8b4a58c7a2 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-after-dispatch.html @@ -0,0 +1,33 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div style="height: 1000px; width: 1000px;"></div> +<div id="frag"></div> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + assert_equals(window.scrollY, 0); + await navigation.navigate("#frag").finished; + assert_not_equals(window.scrollY, 0); + + let intercept_resolve; + let navigate_event; + navigation.onnavigate = e => { + navigate_event = e; + e.intercept({ handler: () => new Promise(r => intercept_resolve = r), + scroll: "manual" }); + }; + let back_promises = navigation.back(); + await back_promises.committed; + assert_not_equals(window.scrollY, 0); + navigate_event.scroll(); + assert_equals(window.scrollY, 0); + intercept_resolve(); + await back_promises.finished; + assert_equals(window.scrollY, 0); +}, "scroll: scroll() should work after a navigate event dispatch"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-after-resolve.html b/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-after-resolve.html new file mode 100644 index 0000000000..244c93af90 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-after-resolve.html @@ -0,0 +1,27 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div style="height: 1000px; width: 1000px;"></div> +<div id="frag"></div> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + assert_equals(window.scrollY, 0); + await navigation.navigate("#frag").finished; + assert_not_equals(window.scrollY, 0); + + let navigate_event; + navigation.onnavigate = e => { + navigate_event = e; + e.intercept({ scroll: "manual" }); + }; + await navigation.back().finished; + assert_not_equals(window.scrollY, 0); + assert_throws_dom("InvalidStateError", () => navigate_event.scroll()); + assert_not_equals(window.scrollY, 0); +}, "scroll: scroll() should throw after a navigation finished promise fulfills"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-before-after-transition-commit.html b/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-before-after-transition-commit.html new file mode 100644 index 0000000000..3b32e72bb1 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-before-after-transition-commit.html @@ -0,0 +1,32 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div style="height: 1000px; width: 1000px;"></div> +<div id="frag"></div> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + assert_equals(window.scrollY, 0); + await navigation.navigate("#frag").finished; + assert_not_equals(window.scrollY, 0); + navigation.onnavigate = e => { + e.intercept({ + scroll: "manual", + commit: "after-transition", + handler: t.step_func(() => { + assert_throws_dom("InvalidStateError", () => e.scroll()); + assert_not_equals(window.scrollY, 0); + e.commit(); + e.scroll(); + assert_equals(window.scrollY, 0); + }) + }); + } + await navigation.back().finished; + assert_equals(window.scrollY, 0); +}, "scroll: scroll() before commit()"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-fragment-does-not-exist.html b/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-fragment-does-not-exist.html new file mode 100644 index 0000000000..1ca582787e --- /dev/null +++ b/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-fragment-does-not-exist.html @@ -0,0 +1,32 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div style="height: 1000px; width: 1000px;"></div> +<div id="frag"></div> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + await navigation.navigate("#frag").finished; + assert_not_equals(window.scrollY, 0); + + let intercept_resolve; + let navigate_event; + navigation.onnavigate = e => { + navigate_event = e; + e.intercept({ handler: () => new Promise(r => intercept_resolve = r), + scroll: "manual" }); + }; + let promises = navigation.navigate("#does-not-exist"); + await promises.committed; + assert_not_equals(window.scrollY, 0); + navigate_event.scroll(); + assert_not_equals(window.scrollY, 0); + intercept_resolve(); + await promises.finished; + assert_not_equals(window.scrollY, 0); +}, "scroll: scroll() should do nothing when the fragment does not exist"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-push.html b/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-push.html new file mode 100644 index 0000000000..3c29365178 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-push.html @@ -0,0 +1,31 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div style="height: 1000px; width: 1000px;"></div> +<div id="frag"></div> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + assert_equals(window.scrollY, 0); + + let intercept_resolve; + let navigate_event; + navigation.onnavigate = e => { + navigate_event = e; + e.intercept({ handler: () => new Promise(r => intercept_resolve = r), + scroll: "manual" }); + }; + let promises = navigation.navigate("#frag"); + await promises.committed; + assert_equals(window.scrollY, 0); + navigate_event.scroll(); + assert_not_equals(window.scrollY, 0); + intercept_resolve(); + await promises.finished; + assert_not_equals(window.scrollY, 0); +}, "scroll: scroll() should work on a push navigation"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-reload.html b/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-reload.html new file mode 100644 index 0000000000..6dd3fd8a38 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-reload.html @@ -0,0 +1,45 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div id="buffer" style="height: 1000px; width: 1000px;"></div> +<div id="frag"></div> +<div style="height: 1000px; width: 1000px;"></div> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + assert_equals(window.scrollY, 0); + await navigation.navigate("#frag").finished; + // Scroll down 10px from #frag + window.scrollBy(0, 10); + let scrollY_frag_plus_10 = window.scrollY; + + let intercept_resolve; + let navigate_event; + navigation.onnavigate = e => { + navigate_event = e; + e.intercept({ handler: () => new Promise(r => intercept_resolve = r), + scroll: "manual" }); + }; + let reload_promises = navigation.reload(); + await reload_promises.committed; + + // removing the <div id="buffer"> should scroll up 1000px. + assert_equals(window.scrollY, scrollY_frag_plus_10); + buffer.remove(); + let scrollY_after_buffer_remove = window.scrollY; + assert_equals(scrollY_after_buffer_remove, scrollY_frag_plus_10 - 1000); + + // scroll() should scroll to #frag, which is 10px up from the current location + navigate_event.scroll(); + assert_equals(window.scrollY, scrollY_after_buffer_remove - 10); + + // Finishing should not scroll. + intercept_resolve(); + await reload_promises.finished; + assert_equals(window.scrollY, scrollY_after_buffer_remove - 10); +}, "scroll: scroll() should work on a reload navigation"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-repeated.html b/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-repeated.html new file mode 100644 index 0000000000..1239146088 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-repeated.html @@ -0,0 +1,29 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div style="height: 1000px; width: 1000px;"></div> +<div id="frag"></div> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + assert_equals(window.scrollY, 0); + await navigation.navigate("#frag").finished; + assert_not_equals(window.scrollY, 0); + navigation.onnavigate = e => { + e.intercept({ + scroll: "manual", + handler: t.step_func(() => { + e.scroll(); + assert_equals(window.scrollY, 0); + assert_throws_dom("InvalidStateError", () => e.scroll()); + }) + }); + }; + await navigation.back().finished; + assert_equals(window.scrollY, 0); +}, "scroll: scroll() should throw if called a second time"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-replace.html b/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-replace.html new file mode 100644 index 0000000000..db580229a8 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-replace.html @@ -0,0 +1,31 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div style="height: 1000px; width: 1000px;"></div> +<div id="frag"></div> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + assert_equals(window.scrollY, 0); + + let intercept_resolve; + let navigate_event; + navigation.onnavigate = e => { + navigate_event = e; + e.intercept({ handler: () => new Promise(r => intercept_resolve = r), + scroll: "manual" }); + }; + let promises = navigation.navigate("#frag", { history: "replace" }); + await promises.committed; + assert_equals(window.scrollY, 0); + navigate_event.scroll(); + assert_not_equals(window.scrollY, 0); + intercept_resolve(); + await promises.finished; + assert_not_equals(window.scrollY, 0); +}, "scroll: scroll() should work on a replace navigation"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-resets-when-no-fragment.html b/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-resets-when-no-fragment.html new file mode 100644 index 0000000000..5401976f64 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/scroll-behavior/manual-scroll-resets-when-no-fragment.html @@ -0,0 +1,33 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div style="height: 1000px; width: 1000px;"></div> +<div id="frag"></div> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + let start_url = location.href; + await navigation.navigate("#frag").finished; + assert_not_equals(window.scrollY, 0); + + let intercept_resolve; + let navigate_event; + navigation.onnavigate = e => { + navigate_event = e; + e.intercept({ handler: () => new Promise(r => intercept_resolve = r), + scroll: "manual" }); + }; + let promises = navigation.navigate(start_url); + await promises.committed; + assert_not_equals(window.scrollY, 0); + navigate_event.scroll(); + assert_equals(window.scrollY, 0); + intercept_resolve(); + await promises.finished; + assert_equals(window.scrollY, 0); +}, "scroll: scroll() should reset scroll position when the destination url contains no fragment"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/scroll-behavior/scroll-after-preventDefault.html b/testing/web-platform/tests/navigation-api/scroll-behavior/scroll-after-preventDefault.html new file mode 100644 index 0000000000..d83d341feb --- /dev/null +++ b/testing/web-platform/tests/navigation-api/scroll-behavior/scroll-after-preventDefault.html @@ -0,0 +1,21 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div style="height: 1000px; width: 1000px;"></div> +<div id="frag"></div> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + navigation.addEventListener("navigate", t.step_func(e => { + e.intercept(); + e.preventDefault(); + assert_throws_dom("InvalidStateError", () => e.scroll()); + }), { once : true }); + await promise_rejects_dom(t, "AbortError", navigation.navigate("#frag").finished); +}, "scroll: scroll() should throw after preventDefault"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/scroll-behavior/scroll-on-synthetic-event.html b/testing/web-platform/tests/navigation-api/scroll-behavior/scroll-on-synthetic-event.html new file mode 100644 index 0000000000..7efc2d1d98 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/scroll-behavior/scroll-on-synthetic-event.html @@ -0,0 +1,19 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +promise_test(async t => { + // We need to grab an NavigationDestination to construct the event. + navigation.onnavigate = t.step_func_done(e => { + const event = new NavigateEvent("navigate", { + destination: e.destination, + signal: (new AbortController()).signal + }); + + assert_throws_dom("SecurityError", () => event.scroll()); + }); + history.pushState(1, null, "#1"); +}, "scroll: scroll() should throw if invoked on a synthetic event."); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/scroll-behavior/scroll-without-intercept.html b/testing/web-platform/tests/navigation-api/scroll-behavior/scroll-without-intercept.html new file mode 100644 index 0000000000..b3958352c7 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/scroll-behavior/scroll-without-intercept.html @@ -0,0 +1,19 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<div style="height: 1000px; width: 1000px;"></div> +<div id="frag"></div> +<script> +promise_test(async t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + + navigation.addEventListener("navigate", t.step_func(e => { + assert_throws_dom("InvalidStateError", () => e.scroll()); + }), { once : true }); + await navigation.navigate("#frag").finished; +}, "scroll: scroll() should throw for non-intercept"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/state/cross-document-away-and-back.html b/testing/web-platform/tests/navigation-api/state/cross-document-away-and-back.html new file mode 100644 index 0000000000..cccaebdf7b --- /dev/null +++ b/testing/web-platform/tests/navigation-api/state/cross-document-away-and-back.html @@ -0,0 +1,31 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + i.contentWindow.navigation.navigate("#", { history: "replace", state: { data : "value" } }); + assert_equals(i.contentWindow.navigation.entries().length, 1); + assert_equals(i.contentWindow.navigation.currentEntry.getState().data, "value"); + + let navigated_back = false; + i.contentWindow.navigation.navigate("?1"); + i.onload = t.step_func(() => { + if (navigated_back) { + let back_entry = i.contentWindow.navigation.currentEntry; + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(back_entry.index, 0); + assert_equals(back_entry.getState().data, "value"); + t.done(); + } else { + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry, i.contentWindow.navigation.entries()[1]); + assert_equals(i.contentWindow.navigation.currentEntry.getState(), undefined); + history.back(); + navigated_back = true; + } + }); + }); +}, "entry.getState() behavior after navigating away and back"); +</script> diff --git a/testing/web-platform/tests/navigation-api/state/cross-document-getState-undefined.html b/testing/web-platform/tests/navigation-api/state/cross-document-getState-undefined.html new file mode 100644 index 0000000000..cbd5cdd52e --- /dev/null +++ b/testing/web-platform/tests/navigation-api/state/cross-document-getState-undefined.html @@ -0,0 +1,17 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + assert_equals(i.contentWindow.navigation.entries().length, 1); + i.contentWindow.location.href = "?1"; + i.onload = t.step_func_done(() => { + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry.index, 1); + assert_equals(i.contentWindow.navigation.entries()[0].getState(), undefined); + }); + }); +}, "Default behavior for entry.getState() for a non-current cross-document entry"); +</script> diff --git a/testing/web-platform/tests/navigation-api/state/cross-document-getState.html b/testing/web-platform/tests/navigation-api/state/cross-document-getState.html new file mode 100644 index 0000000000..aedbc471e0 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/state/cross-document-getState.html @@ -0,0 +1,19 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + i.contentWindow.navigation.updateCurrentEntry({ state: { data: "value" } }); + assert_equals(i.contentWindow.navigation.entries().length, 1); + i.contentWindow.location.href = "?1"; + i.onload = t.step_func_done(() => { + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry.index, 1); + assert_not_equals(i.contentWindow.navigation.entries()[0].getState(), undefined); + assert_equals(i.contentWindow.navigation.entries()[0].getState().data, "value"); + }); + }); +}, "entry.getState() still works for a non-current cross-document entry"); +</script> diff --git a/testing/web-platform/tests/navigation-api/state/cross-document-location-api.html b/testing/web-platform/tests/navigation-api/state/cross-document-location-api.html new file mode 100644 index 0000000000..395d95c6fb --- /dev/null +++ b/testing/web-platform/tests/navigation-api/state/cross-document-location-api.html @@ -0,0 +1,20 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + i.contentWindow.navigation.navigate("#", { history: "replace", state: { data: "value" } }); + assert_equals(i.contentWindow.navigation.entries().length, 1); + assert_equals(i.contentWindow.navigation.currentEntry.getState().data, "value"); + + i.contentWindow.location.href = "?1"; + i.onload = t.step_func_done(() => { + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry.index, 1); + assert_equals(i.contentWindow.navigation.currentEntry.getState(), undefined); + }); + }); +}, "entry.getState() behavior after cross-document location API navigation"); +</script> diff --git a/testing/web-platform/tests/navigation-api/state/history-pushState.html b/testing/web-platform/tests/navigation-api/state/history-pushState.html new file mode 100644 index 0000000000..7d3c79ba6c --- /dev/null +++ b/testing/web-platform/tests/navigation-api/state/history-pushState.html @@ -0,0 +1,11 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +test(() => { + navigation.navigate("#", { history: "replace", state: { data: "value" } }); + assert_equals(navigation.currentEntry.getState().data, "value"); + history.pushState(1, "", "#push"); + assert_equals(navigation.currentEntry.getState(), undefined); +}, "entry.getState() after history.pushState()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/state/history-replaceState.html b/testing/web-platform/tests/navigation-api/state/history-replaceState.html new file mode 100644 index 0000000000..bdf3561639 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/state/history-replaceState.html @@ -0,0 +1,11 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +test(() => { + navigation.navigate("#", { history: "replace", state: { data: "value" } }); + assert_equals(navigation.currentEntry.getState().data, "value"); + history.replaceState(1, "", "#replace"); + assert_equals(navigation.currentEntry.getState(), undefined); +}, "entry.getState() after history.replaceState()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/state/location-reload.html b/testing/web-platform/tests/navigation-api/state/location-reload.html new file mode 100644 index 0000000000..bf1bc105fb --- /dev/null +++ b/testing/web-platform/tests/navigation-api/state/location-reload.html @@ -0,0 +1,19 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + i.contentWindow.navigation.navigate("#", { history: "replace", state: { data: "value" } }); + assert_equals(i.contentWindow.navigation.entries().length, 1); + assert_equals(i.contentWindow.navigation.currentEntry.getState().data, "value"); + + i.contentWindow.location.reload(); + i.onload = t.step_func_done(() => { + assert_equals(i.contentWindow.navigation.entries().length, 1); + assert_equals(i.contentWindow.navigation.currentEntry.getState().data, "value"); + }); + }); +}, "entry.getState() after location.reload()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/state/same-document-away-and-back-location-api.html b/testing/web-platform/tests/navigation-api/state/same-document-away-and-back-location-api.html new file mode 100644 index 0000000000..d161df8b52 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/state/same-document-away-and-back-location-api.html @@ -0,0 +1,47 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + window.onload = () => t.step_timeout(async () => { + let entry0 = navigation.currentEntry; + + let navState = { statevar: "state" }; + await navigation.navigate("#1", { state: navState }); + let entry1 = navigation.currentEntry; + + location.href = "#2"; + let entry2 = navigation.currentEntry; + + assert_equals(navigation.entries().length, 3); + assert_equals(entry0, navigation.entries()[0]); + assert_equals(entry1, navigation.entries()[1]); + assert_equals(entry2, navigation.entries()[2]); + + assert_equals(entry0.getState(), undefined); + + let state1 = entry1.getState(); + assert_not_equals(state1, undefined); + assert_not_equals(state1, navState); + assert_equals(state1.statevar, "state"); + + let state2 = entry2.getState(); + assert_not_equals(state2, undefined); + assert_not_equals(state2, navState); + assert_equals(state2.statevar, "state"); + + history.back(); + window.onpopstate = t.step_func_done(() => { + assert_equals(navigation.entries().length, 3); + let back_entry = navigation.currentEntry; + assert_equals(back_entry, navigation.entries()[1]); + let back_state = back_entry.getState(); + assert_not_equals(back_state, state1); + assert_not_equals(back_state, state2); + assert_equals(back_state.statevar, "state"); + }); + }, 0); +}, "entry.getState() behavior after navigating away using a fragment change, then back"); +</script> diff --git a/testing/web-platform/tests/navigation-api/state/same-document-away-and-back-navigation-api.html b/testing/web-platform/tests/navigation-api/state/same-document-away-and-back-navigation-api.html new file mode 100644 index 0000000000..220908e4c4 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/state/same-document-away-and-back-navigation-api.html @@ -0,0 +1,53 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + window.onload = () => t.step_timeout(async () => { + let entry0 = navigation.currentEntry; + + let navState = { statevar: "state" }; + await navigation.navigate("#1", { state: navState }).committed; + let entry1 = navigation.currentEntry; + + navState.startvar2 = "otherstate"; + await navigation.navigate("#2", { state: navState }).committed; + let entry2 = navigation.currentEntry; + + t.step_func(() => { + assert_equals(navigation.entries().length, 3); + assert_equals(entry0, navigation.entries()[0]); + assert_equals(entry1, navigation.entries()[1]); + assert_equals(entry2, navigation.entries()[2]); + + assert_equals(entry0.getState(), undefined); + + let state1 = entry1.getState(); + assert_not_equals(state1, undefined); + assert_not_equals(state1, navState); + assert_equals(state1.statevar, "state"); + assert_equals(state1.startvar2, undefined); + + let state2 = entry2.getState(); + assert_not_equals(state2, undefined); + assert_not_equals(state2, navState); + assert_equals(state2.statevar, "state"); + assert_equals(state2.startvar2, "otherstate"); + + navigation.back(); + window.onpopstate = t.step_func_done(() => { + assert_equals(navigation.entries().length, 3); + let back_entry = navigation.currentEntry; + assert_equals(back_entry, navigation.entries()[1]); + let back_state = back_entry.getState(); + assert_not_equals(back_state, state1); + assert_not_equals(back_state, state2); + assert_equals(back_state.statevar, "state"); + assert_equals(back_state.startvar2, undefined); + }); + })(); + }, 0); +}, "entry.getState() behavior after navigating away using the navigation API, then back"); +</script> diff --git a/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/basic.html b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/basic.html new file mode 100644 index 0000000000..b4a49e5bf9 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/basic.html @@ -0,0 +1,25 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +async_test(t => { + navigation.onnavigate = t.unreached_func("navigate must not fire"); + navigation.onnavigatesuccess = t.unreached_func("navigatesuccess must not fire"); + navigation.onnavigateerror = t.unreached_func("navigateerror must not fire"); + + assert_equals(navigation.currentEntry.getState(), undefined, "Navigation API state starts out as undefined"); + assert_equals(history.state, null, "history.state starts out as null"); + + const newState = { key: "value" }; + + navigation.updateCurrentEntry({ state: newState }); + + assert_equals(navigation.currentEntry.getState().key, "value"); + assert_not_equals(navigation.currentEntry.getState(), newState); + assert_equals(history.state, null); + + // Wait a tick to make sure no events fire asynchronously. + t.step_timeout(() => t.done(), 0); +}, "updateCurrentEntry() works as expected"); +</script> diff --git a/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/cross-document-away-and-back.html b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/cross-document-away-and-back.html new file mode 100644 index 0000000000..c37d5e979a --- /dev/null +++ b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/cross-document-away-and-back.html @@ -0,0 +1,31 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + i.contentWindow.navigation.updateCurrentEntry({ state: { data: "value" } }); + assert_equals(i.contentWindow.navigation.entries().length, 1); + assert_equals(i.contentWindow.navigation.currentEntry.getState().data, "value"); + + let navigated_back = false; + i.contentWindow.navigation.navigate("?1"); + i.onload = t.step_func(() => { + if (navigated_back) { + let back_entry = i.contentWindow.navigation.currentEntry; + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(back_entry.index, 0); + assert_equals(back_entry.getState().data, "value"); + t.done(); + } else { + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry, i.contentWindow.navigation.entries()[1]); + assert_equals(i.contentWindow.navigation.currentEntry.getState(), undefined); + history.back(); + navigated_back = true; + } + }); + }); +}, "entry.getState() behavior after navigating away and back"); +</script> diff --git a/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/cross-document-location-api.html b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/cross-document-location-api.html new file mode 100644 index 0000000000..26191fb876 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/cross-document-location-api.html @@ -0,0 +1,20 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + i.contentWindow.navigation.updateCurrentEntry({ state: { data: "value" } }); + assert_equals(i.contentWindow.navigation.entries().length, 1); + assert_equals(i.contentWindow.navigation.currentEntry.getState().data, "value"); + + i.contentWindow.location.href = "?1"; + i.onload = t.step_func_done(() => { + assert_equals(i.contentWindow.navigation.entries().length, 2); + assert_equals(i.contentWindow.navigation.currentEntry.index, 1); + assert_equals(i.contentWindow.navigation.currentEntry.getState(), undefined); + }); + }); +}, "entry.getState() behavior after cross-document location API navigation"); +</script> diff --git a/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/exception-order-initial-about-blank-unserializablestate.html b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/exception-order-initial-about-blank-unserializablestate.html new file mode 100644 index 0000000000..010632a40f --- /dev/null +++ b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/exception-order-initial-about-blank-unserializablestate.html @@ -0,0 +1,13 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="iframe"></iframe> + +<script> +test(() => { + assert_throws_dom("InvalidStateError", iframe.contentWindow.DOMException, () => { + iframe.contentWindow.navigation.updateCurrentEntry({ state: document.body }); + }); + assert_equals(navigation.currentEntry.getState(), undefined); +}, `updateCurrentEntry() with unserializable state on the initial about:blank must throw an "InvalidStateError", not a "DataCloneError"`); +</script> diff --git a/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/exception-order-not-fully-active-unserializablestate.html b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/exception-order-not-fully-active-unserializablestate.html new file mode 100644 index 0000000000..1e1c1e2bae --- /dev/null +++ b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/exception-order-not-fully-active-unserializablestate.html @@ -0,0 +1,20 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="iframe" src="/common/blank.html"></iframe> + +<script> +async_test(t => { + window.onload = t.step_func_done(() => { + const wNavigation = iframe.contentWindow.navigation; + const wDOMException = iframe.contentWindow.DOMException; + + iframe.remove(); + + assert_throws_dom("InvalidStateError", wDOMException, () => { + wNavigation.updateCurrentEntry({ state: document.body }); + }); + assert_equals(navigation.currentEntry.getState(), undefined); + }); +}, `updateCurrentEntry() with unserializable state while not fully active must throw an "InvalidStateError", not a "DataCloneError"`); +</script> diff --git a/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/history-pushState.html b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/history-pushState.html new file mode 100644 index 0000000000..852294c64f --- /dev/null +++ b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/history-pushState.html @@ -0,0 +1,11 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +test(() => { + navigation.updateCurrentEntry({ state : { data : "value" } }); + assert_equals(navigation.currentEntry.getState().data, "value"); + history.pushState(1, "", "#push"); + assert_equals(navigation.currentEntry.getState(), undefined); +}, "entry.getState() after history.pushState()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/history-replaceState.html b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/history-replaceState.html new file mode 100644 index 0000000000..3eb91a9a80 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/history-replaceState.html @@ -0,0 +1,11 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +test(() => { + navigation.updateCurrentEntry({ state : { data : "value" } }); + assert_equals(navigation.currentEntry.getState().data, "value"); + history.replaceState(1, "", "#replace"); + assert_equals(navigation.currentEntry.getState(), undefined); +}, "entry.getState() after history.replaceState()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/initial-about-blank.html b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/initial-about-blank.html new file mode 100644 index 0000000000..c28137c082 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/initial-about-blank.html @@ -0,0 +1,13 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="iframe"></iframe> + +<script> +test(() => { + assert_throws_dom("InvalidStateError", iframe.contentWindow.DOMException, () => { + iframe.contentWindow.navigation.updateCurrentEntry({ state: 1 }); + }); + assert_equals(navigation.currentEntry.getState(), undefined); +}, "updateCurrentEntry() must throw if the document is still on the initial about:blank"); +</script> diff --git a/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/location-reload.html b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/location-reload.html new file mode 100644 index 0000000000..8589eeb694 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/location-reload.html @@ -0,0 +1,19 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="i" src="/common/blank.html"></iframe> +<script> +async_test(t => { + window.onload = t.step_func(() => { + i.contentWindow.navigation.updateCurrentEntry({ state: { data: "value" } }); + assert_equals(i.contentWindow.navigation.entries().length, 1); + assert_equals(i.contentWindow.navigation.currentEntry.getState().data, "value"); + + i.contentWindow.location.reload(); + i.onload = t.step_func_done(() => { + assert_equals(i.contentWindow.navigation.entries().length, 1); + assert_equals(i.contentWindow.navigation.currentEntry.getState().data, "value"); + }); + }); +}, "entry.getState() after location.reload()"); +</script> diff --git a/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/no-args.html b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/no-args.html new file mode 100644 index 0000000000..3fd011e3d3 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/no-args.html @@ -0,0 +1,15 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +test(() => { + assert_throws_js(TypeError, () => { + navigation.updateCurrentEntry(); + }, "no args"); + + assert_throws_js(TypeError, () => { + navigation.updateCurrentEntry({}); + }, "empty dictionary"); +}, "updateCurrentEntry() must throw if state is not given"); +</script> diff --git a/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/not-fully-active.html b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/not-fully-active.html new file mode 100644 index 0000000000..fce5e72c8d --- /dev/null +++ b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/not-fully-active.html @@ -0,0 +1,20 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="iframe" src="/common/blank.html"></iframe> + +<script> +async_test(t => { + window.onload = t.step_func_done(() => { + const wNavigation = iframe.contentWindow.navigation; + const wDOMException = iframe.contentWindow.DOMException; + + iframe.remove(); + + assert_throws_dom("InvalidStateError", wDOMException, () => { + wNavigation.updateCurrentEntry({ state: 1 }); + }); + assert_equals(navigation.currentEntry.getState(), undefined); + }); +}, "updateCurrentEntry() must throw if the document is not fully active"); +</script> diff --git a/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/opaque-origin.html b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/opaque-origin.html new file mode 100644 index 0000000000..898ca27e4f --- /dev/null +++ b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/opaque-origin.html @@ -0,0 +1,9 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<iframe id="i" sandbox="allow-scripts" src="resources/opaque-origin-page.html"></iframe> + +<script> +fetch_tests_from_window(i.contentWindow); +</script> diff --git a/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/resources/opaque-origin-page.html b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/resources/opaque-origin-page.html new file mode 100644 index 0000000000..59931458a6 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/resources/opaque-origin-page.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<!-- Put this page in a sandbox to give it an opaque origin --> + +<script> +test(t => { + assert_throws_dom("InvalidStateError", () => { + navigation.updateCurrentEntry({ state: 1 }); + }); +}, "navigation.updateCurrentEntry() in an opaque origin iframe"); +</script> diff --git a/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/same-document-away-and-back-location-api.html b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/same-document-away-and-back-location-api.html new file mode 100644 index 0000000000..47b1904f4f --- /dev/null +++ b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/same-document-away-and-back-location-api.html @@ -0,0 +1,40 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + window.onload = () => t.step_timeout(async () => { + let entry0 = navigation.currentEntry; + + let navState = { statevar: "state" }; + navigation.updateCurrentEntry({ state: navState }); + assert_equals(navigation.currentEntry, entry0); + + location.href = "#2"; + let entry1 = navigation.currentEntry; + + assert_equals(navigation.entries().length, 2); + assert_equals(entry0, navigation.entries()[0]); + assert_equals(entry1, navigation.entries()[1]); + + assert_equals(entry0.getState().statevar, "state"); + assert_not_equals(entry0.getState(), navState); + + assert_equals(entry1.getState().statevar, "state"); + assert_not_equals(entry1.getState(), navState); + assert_not_equals(entry1.getState(), entry0.getState()); + + history.back(); + window.onpopstate = t.step_func_done(() => { + assert_equals(navigation.entries().length, 2); + let back_entry = navigation.currentEntry; + assert_equals(back_entry, entry0); + let back_state = back_entry.getState(); + assert_not_equals(back_state, navState); + assert_equals(back_state.statevar, "state"); + }); + }, 0); +}, "entry.getState() behavior after navigating away using the location API, then back"); +</script> diff --git a/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/unserializable.html b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/unserializable.html new file mode 100644 index 0000000000..596ab16d62 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/updateCurrentEntry-method/unserializable.html @@ -0,0 +1,29 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe id="iframe" src="/common/blank.html"></iframe> + +<script> +setup({ explicit_done: true }); + +window.onload = () => { + test(() => { + assert_throws_dom("DataCloneError", iframe.contentWindow.DOMException, () => { + iframe.contentWindow.navigation.updateCurrentEntry({ state: new WritableStream() }); + }); + assert_equals(navigation.currentEntry.getState(), undefined); + }, "updateCurrentEntry() must throw if state is unserializable (WritableStream)"); + + test(() => { + // See https://github.com/whatwg/html/issues/5380 for why not `new SharedArrayBuffer()` + const buffer = new WebAssembly.Memory({ shared:true, initial:1, maximum:1 }).buffer; + + assert_throws_dom("DataCloneError", iframe.contentWindow.DOMException, () => { + iframe.contentWindow.navigation.updateCurrentEntry({ state: buffer }); + }); + assert_equals(navigation.currentEntry.getState(), undefined); + }, "updateCurrentEntry() must throw if state is unserializable (SharedArrayBuffer)"); + + done(); +}; +</script> |