summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/navigation-api/navigation-methods/return-value
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /testing/web-platform/tests/navigation-api/navigation-methods/return-value
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/navigation-api/navigation-methods/return-value')
-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
74 files changed, 1843 insertions, 0 deletions
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>