summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/navigation-api/navigation-methods
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/navigation-api/navigation-methods')
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/back-forward-multiple-frames.html74
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/disambigaute-back.html32
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/disambigaute-forward.html40
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/disambigaute-traverseTo-back-multiple.html35
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/disambigaute-traverseTo-forward-multiple.html39
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/forward-to-pruned-entry.html33
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/navigate-base-url.html15
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/navigate-from-initial-about-blank-gc.html18
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/navigate-from-initial-about-blank-src.html21
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/navigate-from-initial-about-blank.html22
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/navigate-history-push-not-loaded.html20
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/navigate-history-push-same-url-cross-document.html25
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/navigate-history-push-same-url.html26
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/navigate-history-state-replace.html16
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/navigate-history-state.html16
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/navigate-info-and-state.html18
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/navigate-intercept-history-state.html14
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/navigate-relative-url.html16
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/navigate-replace-cross-document.html29
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/navigate-replace-same-document.html21
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/navigate-same-document.html23
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/navigate-state-repeated-await.html13
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/navigate-state-repeated.html20
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/reload-base-url.html16
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/reload-info.html47
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/reload-navigation-timing.html15
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/reload-no-args.html46
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/reload-service-worker-fetch-event.html28
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/reload-state-and-info.html40
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/reload-state-undefined.html39
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/resources/fetch-event-test-worker.js7
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/resources/navigate-parent.html6
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/resources/navigate-sibling.html6
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/resources/navigation-back.html6
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/resources/page-with-base-url-common.html2
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/resources/service-worker-page.html2
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/resources/slow-no-store.py6
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-204-205-download.html52
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-already-detached.html30
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-beforeunload.html42
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-forward-initial-about-blank.html25
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-forward-opaque-origin.html9
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-forward-out-of-bounds.html16
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-intercept-rejected.html28
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/back-intercept.html25
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/back.html23
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/forward-already-detached.html33
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/forward-beforeunload.html45
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/forward-intercept-rejected.html29
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/forward-intercept.html28
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/forward.html24
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-204-205-download.html42
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-already-detached.html19
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-beforeunload.html31
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-cross-document.html16
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-detach-in-onnavigate.html18
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-detach-in-serialization.html24
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-file-url.html10
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-initial-about-blank-cross-document.html21
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-initial-about-blank.html20
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-intercept-interrupted.html23
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-intercept-rejected.html17
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-intercept.html15
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-interrupted-within-onnavigate.html28
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-interrupted.html21
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-invalid-url.html10
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-opaque-origin.html9
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-preventDefault.html12
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-push-initial-about-blank.html21
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-push-javascript-url.html19
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-beforeunload-unserializablestate.html31
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-detached-unserializablestate.html20
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-invalidurl-beforeunload.html31
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-invalidurl-detached.html20
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-invalidurl-unload.html27
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-invalidurl-unserializablestate.html12
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-rejection-order-unload-unserializablestate.html27
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-unload.html27
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate-unserializable-state.html21
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/navigate.html12
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-already-detached.html19
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-beforeunload.html31
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-detach-in-onnavigate.html18
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-detach-in-serialization.html24
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-initial-about-blank.html20
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-intercept-rejected.html17
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-intercept.html14
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-preventDefault.html12
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-rejection-order-beforeunload-unserializablestate.html31
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-rejection-order-detached-unserializablestate.html20
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-rejection-order-unload-unserializablestate.html32
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-unload.html27
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload-unserializable-state.html21
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/reload.html16
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/resources/204-205-download-on-second-visit.py24
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/resources/back-forward-opaque-origin-page.html28
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/resources/helpers.js127
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/resources/navigate-opaque-origin-page.html16
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-already-detached.html20
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-beforeunload.html31
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-cross-document-preventDefault.html29
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-current.html17
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-detach-cross-document-before-navigate-event.html32
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-detach-cross-document.html29
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-detach-same-document-before-navigate-event.html31
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-detach-same-document.html28
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-intercept-rejected.html30
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-intercept.html27
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-invalid-key.html10
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo-repeated.html24
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/return-value/traverseTo.html25
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/sandboxing-back-parent.html38
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/sandboxing-back-sibling.html44
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/sandboxing-navigate-parent.html16
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/sandboxing-navigate-sibling.html19
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-after-adding-iframe.html34
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-after-data-url.html33
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-cross-document.html41
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-detach-between-navigate-and-navigatesuccess.html54
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-multiple-steps.html25
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-navigates-multiple-iframes.html45
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-same-document.html43
-rw-r--r--testing/web-platform/tests/navigation-api/navigation-methods/traverseTo-with-cross-origin-in-history.html37
123 files changed, 3124 insertions, 0 deletions
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>