diff options
Diffstat (limited to 'testing/web-platform/tests/navigation-api/focus-reset')
9 files changed, 596 insertions, 0 deletions
diff --git a/testing/web-platform/tests/navigation-api/focus-reset/autofocus.html b/testing/web-platform/tests/navigation-api/focus-reset/autofocus.html new file mode 100644 index 0000000000..6044447367 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/focus-reset/autofocus.html @@ -0,0 +1,185 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<button autofocus id="initialAutofocusTarget">Initial autofocus target</button> + +<script type="module"> +promise_setup(async () => { + // Get the overall autofocus processed flag to flip to true, so that + // we only test the navigation API-specific stuff. + await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r))); + assert_equals(document.activeElement, initialAutofocusTarget, "Non-navigation API autofocus was processed"); + initialAutofocusTarget.remove(); + assert_equals(document.activeElement, document.body); +}); + +promise_test(async t => { + const decoy = createAndAppend(t); + const autofocusTarget = createAndAppend(t, { autofocus: true }); + + assert_equals(document.activeElement, document.body, "Start on body"); + decoy.focus(); + assert_equals(document.activeElement, decoy, "focus() worked"); + + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); + + const { committed, finished } = navigation.navigate("#1"); + + await committed; + assert_equals(document.activeElement, decoy, "Focus stays on the non-autofocused button during the transition"); + + await finished; + assert_equals(document.activeElement, autofocusTarget, "Focus moves to the autofocused button after the transition"); +}, "An element with autofocus, present before navigation, gets focused"); + +promise_test(async t => { + const autofocusTarget = createAndAppend(t, { autofocus: true }); + const decoy = createAndAppend(t, { autofocus: true }); + + assert_equals(document.activeElement, document.body, "Start on body"); + decoy.focus(); + assert_equals(document.activeElement, decoy, "focus() worked"); + + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); + + const { committed, finished } = navigation.navigate("#1"); + + await committed; + assert_equals(document.activeElement, decoy, "Focus stays on the initially-focused button during the transition"); + + await finished; + assert_equals(document.activeElement, autofocusTarget, "Focus moves to the first autofocused button after the transition"); +}, "Two elements with autofocus, present before navigation; the first gets focused"); + +promise_test(async t => { + const decoy = createAndAppend(t); + const autofocusTarget = createAndAppend(t, { autofocus: true }); + + assert_equals(document.activeElement, document.body, "Start on body"); + decoy.focus(); + assert_equals(document.activeElement, decoy, "focus() worked"); + + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); + + const { committed, finished } = navigation.navigate("#1"); + + await committed; + assert_equals(document.activeElement, decoy, "Focus stays on the non-autofocused button during the transition"); + + autofocusTarget.disabled = true; + + await finished; + assert_equals(document.activeElement, document.body, "Focus gets reset after the transition"); +}, "An element with autofocus, present before navigation but disabled before finished, does not get focused"); + +promise_test(async t => { + const decoy = createAndAppend(t); + const autofocusTarget = createAndAppend(t, { autofocus: true }); + + assert_equals(document.activeElement, document.body, "Start on body"); + decoy.focus(); + assert_equals(document.activeElement, decoy, "focus() worked"); + + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); + + const { committed, finished } = navigation.navigate("#1"); + + await committed; + assert_equals(document.activeElement, decoy, "Focus stays on the non-autofocused button during the transition"); + + autofocusTarget.autofocus = false; + + await finished; + assert_equals(document.activeElement, document.body, "Focus gets reset after the transition"); +}, "An element with autofocus, present before navigation but with its autofocus attribute removed before finished, does not get focused"); + +promise_test(async t => { + const decoy = createAndAppend(t, { autofocus: true }); + const autofocusTarget = createAndAppend(t, { autofocus: true }); + + assert_equals(document.activeElement, document.body, "Start on body"); + decoy.focus(); + assert_equals(document.activeElement, decoy, "focus() worked"); + + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); + + const { committed, finished } = navigation.navigate("#1"); + + await committed; + assert_equals(document.activeElement, decoy, "Focus stays on the initially-focused button during the transition"); + + decoy.disabled = true; + assert_equals(document.activeElement, document.body, "Disabling the initially-focused button temporarily resets focus to the body"); + + await finished; + assert_equals(document.activeElement, autofocusTarget, "Focus moves to the second autofocused button after the transition"); +}, "Two elements with autofocus, present before navigation, but the first gets disabled; the second gets focused"); + +promise_test(async t => { + const decoy = createAndAppend(t); + + assert_equals(document.activeElement, document.body, "Start on body"); + decoy.focus(); + assert_equals(document.activeElement, decoy, "focus() worked"); + + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); + + const { committed, finished } = navigation.navigate("#1"); + + await committed; + assert_equals(document.activeElement, decoy, "Focus stays on the non-autofocused button during the transition"); + + const autofocusTarget = createAndAppend(t, { autofocus: true }); + + await finished; + assert_equals(document.activeElement, autofocusTarget, "Focus moves to the autofocused button after the transition"); +}, "An element with autofocus, introduced between committed and finished, gets focused"); + +promise_test(async t => { + const decoy = createAndAppend(t); + + assert_equals(document.activeElement, document.body, "Start on body"); + decoy.focus(); + assert_equals(document.activeElement, decoy, "focus() worked"); + + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); + + const { committed, finished } = navigation.navigate("#1"); + + await committed; + assert_equals(document.activeElement, decoy, "Focus stays on the non-autofocused button during the transition"); + + await finished; + assert_equals(document.activeElement, document.body, "Focus gets reset after the transition"); + + const autofocusTarget = createAndAppend(t, { autofocus: true }); + + await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r))); + assert_equals(document.activeElement, document.body, "Focus stays reset two animation frames after the transition"); +}, "An element with autofocus, introduced after finished, does not get focused"); + +function createAndAppend(t, props) { + const element = document.createElement("button"); + Object.assign(element, props); + + document.body.append(element); + t.add_cleanup(() => { element.remove(); }); + + return element; +} +</script> diff --git a/testing/web-platform/tests/navigation-api/focus-reset/basic.html b/testing/web-platform/tests/navigation-api/focus-reset/basic.html new file mode 100644 index 0000000000..f5a30972b0 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/focus-reset/basic.html @@ -0,0 +1,63 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<script type="module"> +import { testFocusWasReset, testFocusWasNotReset } from "./resources/helpers.mjs"; + +test(() => { + let throwAssertionHappened = false; + + navigation.addEventListener("navigate", e => { + assert_throws_js(TypeError, () => { + e.intercept({ focusReset: "invalid" }); + }); + throwAssertionHappened = true; + }, { once: true }); + + navigation.navigate("#1"); + assert_true(throwAssertionHappened); +}, "Invalid values for focusReset throw"); + +testFocusWasNotReset(() => { + // Intentionally left blank. +}, "Does not reset the focus when no navigate handler is present"); + +testFocusWasReset(t => { + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); +}, "Resets the focus when no focusReset option is provided"); + +testFocusWasReset(t => { + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); +}, "Resets the focus when focusReset is explicitly set to undefined"); + +testFocusWasReset(t => { + navigation.addEventListener("navigate", e => { + e.intercept({ handler: () => new Promise(r => t.step_timeout(r, 5)) }); + }, { once: true }); +}, "Resets the focus when no focusReset option is provided (nontrivial fulfilled promise)"); + +testFocusWasReset(t => { + navigation.addEventListener("navigate", e => { + e.intercept({ handler: () => Promise.reject() }); + }, { once: true }); +}, "Resets the focus when no focusReset option is provided (rejected promise)"); + +testFocusWasReset(t => { + navigation.addEventListener("navigate", e => { + e.intercept({ focusReset: "after-transition" }); + }, { once: true }); +}, "Resets the focus when focusReset is explicitly set to 'after-transition'"); + +testFocusWasNotReset(t => { + navigation.addEventListener("navigate", e => { + e.intercept({ focusReset: "manual" }); + }); +}, "Does not reset the focus when focusReset is set to 'manual'"); +</script> diff --git a/testing/web-platform/tests/navigation-api/focus-reset/change-focus-again-in-blur-during-intercept.html b/testing/web-platform/tests/navigation-api/focus-reset/change-focus-again-in-blur-during-intercept.html new file mode 100644 index 0000000000..a7339c9788 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/focus-reset/change-focus-again-in-blur-during-intercept.html @@ -0,0 +1,35 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +promise_test(async t => { + let intercept_resolve; + navigation.addEventListener("navigate", e => { + e.intercept({ handler: () => new Promise(resolve => intercept_resolve = resolve), + focusReset: "after-transition" }); + }, { once: true }); + + const button = document.body.appendChild(document.createElement("button")); + const button2 = document.body.appendChild(document.createElement("button")); + button2.tabIndex = 0; + t.add_cleanup(() => { + button.remove(); + button2.remove(); + }); + + assert_equals(document.activeElement, document.body, "Start on body"); + button.focus(); + assert_equals(document.activeElement, button, "focus() worked"); + + const finished = navigation.navigate("#1").finished; + button.onblur = () => button2.focus(); + button.blur(); + assert_equals(document.activeElement, button2, "focus() in blur worked"); + + intercept_resolve(); + await finished; + assert_equals(document.activeElement, button2, "Focus was not reset after the transition"); +}, ""); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/focus-reset/change-focus-back-to-origial-during-intercept.html b/testing/web-platform/tests/navigation-api/focus-reset/change-focus-back-to-origial-during-intercept.html new file mode 100644 index 0000000000..4e5b9dfb6a --- /dev/null +++ b/testing/web-platform/tests/navigation-api/focus-reset/change-focus-back-to-origial-during-intercept.html @@ -0,0 +1,36 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +promise_test(async t => { + let intercept_resolve; + navigation.addEventListener("navigate", e => { + e.intercept({ handler: () => new Promise(resolve => intercept_resolve = resolve), + focusReset: "after-transition" }); + }, { once: true }); + + const button = document.body.appendChild(document.createElement("button")); + const button2 = document.body.appendChild(document.createElement("button")); + button2.tabIndex = 0; + t.add_cleanup(() => { + button.remove(); + button2.remove(); + }); + + assert_equals(document.activeElement, document.body, "Start on body"); + button.focus(); + assert_equals(document.activeElement, button, "focus() worked"); + + const finished = navigation.navigate("#1").finished; + button2.focus(); + assert_equals(document.activeElement, button2, "focus() worked"); + button.focus(); + assert_equals(document.activeElement, button, "focus() worked"); + + intercept_resolve(); + await finished; + assert_equals(document.activeElement, button, "Focus was not reset after the transition"); +}, ""); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/focus-reset/change-focus-during-intercept.html b/testing/web-platform/tests/navigation-api/focus-reset/change-focus-during-intercept.html new file mode 100644 index 0000000000..0593231a39 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/focus-reset/change-focus-during-intercept.html @@ -0,0 +1,34 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +promise_test(async t => { + let intercept_resolve; + navigation.addEventListener("navigate", e => { + e.intercept({ handler: () => new Promise(resolve => intercept_resolve = resolve), + focusReset: "after-transition" }); + }, { once: true }); + + const button = document.body.appendChild(document.createElement("button")); + const button2 = document.body.appendChild(document.createElement("button")); + button2.tabIndex = 0; + t.add_cleanup(() => { + button.remove(); + button2.remove(); + }); + + assert_equals(document.activeElement, document.body, "Start on body"); + button.focus(); + assert_equals(document.activeElement, button, "focus() worked"); + + const finished = navigation.navigate("#1").finished; + button2.focus(); + assert_equals(document.activeElement, button2, "focus() worked"); + + intercept_resolve(); + await finished; + assert_equals(document.activeElement, button2, "Focus was not reset after the transition"); +}, ""); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/focus-reset/change-focus-then-remove-during-intercept.html b/testing/web-platform/tests/navigation-api/focus-reset/change-focus-then-remove-during-intercept.html new file mode 100644 index 0000000000..a5d8062ce0 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/focus-reset/change-focus-then-remove-during-intercept.html @@ -0,0 +1,40 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +promise_test(async t => { + let intercept_resolve; + navigation.addEventListener("navigate", e => { + e.intercept({ handler: () => new Promise(resolve => intercept_resolve = resolve), + focusReset: "after-transition" }); + }, { once: true }); + + const button = document.body.appendChild(document.createElement("button")); + const button2 = document.body.appendChild(document.createElement("button")); + button2.tabIndex = 0; + t.add_cleanup(() => { + button.remove(); + button2.remove(); + }); + + assert_equals(document.activeElement, document.body, "Start on body"); + button.focus(); + assert_equals(document.activeElement, button, "focus() worked"); + + const finished = navigation.navigate("#1").finished; + + let onfocus_called = false; + document.body.onfocus = onfocus_called = true; + button.remove(); + assert_equals(document.activeElement, document.body, "Removing the element reset focus"); + assert_true(onfocus_called); + + document.body.onfocus = t.unreached_func("onfocus shouldn't fire a second time due to focus reset"); + intercept_resolve(); + await finished; + assert_equals(document.activeElement, document.body, "Focus remains on document.body after promise fulfills"); + await new Promise(resolve => t.step_timeout(resolve, 10)); +}, ""); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/focus-reset/focus-reset-timing.html b/testing/web-platform/tests/navigation-api/focus-reset/focus-reset-timing.html new file mode 100644 index 0000000000..df9f03afe9 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/focus-reset/focus-reset-timing.html @@ -0,0 +1,61 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +promise_test(async t => { + navigation.addEventListener("navigate", e => { + e.intercept({ focusReset: "after-transition" }); + }, { once: true }); + + const button = document.body.appendChild(document.createElement("button")); + const button2 = document.body.appendChild(document.createElement("button")); + button2.tabIndex = 0; + t.add_cleanup(() => { + button.remove(); + button2.remove(); + }); + + assert_equals(document.activeElement, document.body, "Start on body"); + button.focus(); + assert_equals(document.activeElement, button, "focus() worked"); + + let navigatesuccess_called = false; + navigation.onnavigatesuccess = t.step_func(() => { + navigatesuccess_called = true; + assert_equals(document.activeElement, document.body, "Focus must be reset before navigatesuccess"); + }); + + await navigation.navigate("#1").finished; + assert_true(navigatesuccess_called); +}, "Focus should be reset before navigatesuccess"); + +promise_test(async t => { + navigation.addEventListener("navigate", e => { + e.intercept({ handler: () => Promise.reject(), + focusReset: "after-transition" }); + }, { once: true }); + + const button = document.body.appendChild(document.createElement("button")); + const button2 = document.body.appendChild(document.createElement("button")); + button2.tabIndex = 0; + t.add_cleanup(() => { + button.remove(); + button2.remove(); + }); + + assert_equals(document.activeElement, document.body, "Start on body"); + button.focus(); + assert_equals(document.activeElement, button, "focus() worked"); + + let navigateerror_called = false; + navigation.onnavigateerror = t.step_func(() => { + navigateerror_called = true; + assert_equals(document.activeElement, document.body, "Focus must be reset before navigateerror"); + }); + + await promise_rejects_exactly(t, undefined, navigation.navigate("#2").finished); + assert_true(navigateerror_called); +}, "Focus should be reset before navigateerror"); +</script> +</body> diff --git a/testing/web-platform/tests/navigation-api/focus-reset/multiple-intercept.html b/testing/web-platform/tests/navigation-api/focus-reset/multiple-intercept.html new file mode 100644 index 0000000000..75e38c98a4 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/focus-reset/multiple-intercept.html @@ -0,0 +1,69 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<script type="module"> +import { testFocusWasReset, testFocusWasNotReset } from "./resources/helpers.mjs"; + +testFocusWasReset(t => { + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); + + navigation.addEventListener("navigate", e => { + e.intercept({ focusReset: "after-transition" }); + }, { once: true }); +}, "(not provided) + after-transition"); + +testFocusWasNotReset(t => { + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); + + navigation.addEventListener("navigate", e => { + e.intercept({ focusReset: "manual" }); + }, { once: true }); +}, "(not provided) + manual"); + +testFocusWasNotReset(t => { + navigation.addEventListener("navigate", e => { + e.intercept({ focusReset: "after-transition" }); + }, { once: true }); + + navigation.addEventListener("navigate", e => { + e.intercept({ focusReset: "manual" }); + }, { once: true }); +}, "after-transition + manual"); + +testFocusWasReset(t => { + navigation.addEventListener("navigate", e => { + e.intercept({ focusReset: "after-transition" }); + }, { once: true }); + + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); +}, "after-transition + (not provided)"); + +testFocusWasReset(t => { + navigation.addEventListener("navigate", e => { + e.intercept({ focusReset: "manual" }); + }, { once: true }); + + navigation.addEventListener("navigate", e => { + e.intercept({ focusReset: "after-transition" }); + }, { once: true }); +}, "manual + after-transition"); + +testFocusWasNotReset(t => { + navigation.addEventListener("navigate", e => { + e.intercept({ focusReset: "manual" }); + }, { once: true }); + + navigation.addEventListener("navigate", e => { + e.intercept(); + }, { once: true }); +}, "manual + (not provided)"); +</script> diff --git a/testing/web-platform/tests/navigation-api/focus-reset/resources/helpers.mjs b/testing/web-platform/tests/navigation-api/focus-reset/resources/helpers.mjs new file mode 100644 index 0000000000..0a8a0439e1 --- /dev/null +++ b/testing/web-platform/tests/navigation-api/focus-reset/resources/helpers.mjs @@ -0,0 +1,73 @@ +// Usage note: if you use these more than once in a given file, be sure to +// clean up any navigate event listeners, e.g. by using { once: true }, between +// tests. + +const TAB_KEY = "\uE004"; + +export function testFocusWasReset(setupFunc, description) { + promise_test(async t => { + setupFunc(t); + + const button = document.body.appendChild(document.createElement("button")); + const button2 = document.body.appendChild(document.createElement("button")); + button2.tabIndex = 0; + t.add_cleanup(() => { + button.remove(); + button2.remove(); + }); + + assert_equals(document.activeElement, document.body, "Start on body"); + button.focus(); + assert_equals(document.activeElement, button, "focus() worked"); + + const { committed, finished } = navigation.navigate("#" + location.hash.substring(1) + "1"); + + await committed; + assert_equals(document.activeElement, button, "Focus stays on the button during the transition"); + + await finished.catch(() => {}); + assert_equals(document.activeElement, document.body, "Focus reset after the transition"); + + button2.onfocus = t.unreached_func("button2 must not be focused after pressing Tab"); + const focusPromise = waitForFocus(t, button); + await test_driver.send_keys(document.body, TAB_KEY); + await focusPromise; + }, description); +} + +export function testFocusWasNotReset(setupFunc, description) { + promise_test(async t => { + setupFunc(t); + + const button = document.body.appendChild(document.createElement("button")); + const button2 = document.body.appendChild(document.createElement("button")); + button2.tabIndex = 0; + t.add_cleanup(() => { + button.remove(); + button2.remove(); + }); + + assert_equals(document.activeElement, document.body, "Start on body"); + button.focus(); + assert_equals(document.activeElement, button, "focus() worked"); + + const { committed, finished } = navigation.navigate("#" + location.hash.substring(1) + "1"); + + await committed; + assert_equals(document.activeElement, button, "Focus stays on the button during the transition"); + + await finished.catch(() => {}); + assert_equals(document.activeElement, button, "Focus stays on the button after the transition"); + + button.onfocus = t.unreached_func("button must not be focused after pressing Tab"); + const focusPromise = waitForFocus(t, button2); + await test_driver.send_keys(document.body, TAB_KEY); + await focusPromise; + }, description); +} + +function waitForFocus(t, target) { + return new Promise(resolve => { + target.addEventListener("focus", () => resolve(), { once: true }); + }); +} |