Skip to content

Commit

Permalink
App history: basic focusReset support
Browse files Browse the repository at this point in the history
Based on the draft at WICG/navigation-api#201.

This includes handling multiple conflicting focusReset options, with a console warning if necessary.

Limitations, to be addressed in future commits:

* This does not take into account autofocus="" elements.

* This will reset focus even if the user has moved focus in the meantime.

Bug: 1183545
Change-Id: I1eae4fc58af653fa7e463a1346a9bebc9536a59f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3441109
Reviewed-by: Nate Chapin <japhet@chromium.org>
Commit-Queue: Domenic Denicola <domenic@chromium.org>
Cr-Commit-Position: refs/heads/main@{#970838}
  • Loading branch information
domenic authored and chromium-wpt-export-bot committed Feb 14, 2022
1 parent af3b994 commit e6bbf05
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 0 deletions.
64 changes: 64 additions & 0 deletions app-history/focus-reset/basic.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<!doctype html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>

<script type="module">
import { testFocusWasReset, testFocusWasNotReset } from "./resources/helpers.mjs";

test(() => {
let throwAssertionHappened = false;

appHistory.addEventListener("navigate", e => {
assert_throws_js(TypeError, () => {
e.transitionWhile(Promise.resolve(), { focusReset: "invalid" });
});
throwAssertionHappened = true;
}, { once: true });

appHistory.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 => {
appHistory.addEventListener("navigate", e => {
e.transitionWhile(Promise.resolve());
}, { once: true });
}, "Resets the focus when no focusReset option is provided");

testFocusWasReset(t => {
appHistory.addEventListener("navigate", e => {
e.transitionWhile(Promise.resolve());
}, { once: true });
}, "Resets the focus when focusReset is explicitly set to undefined");

testFocusWasReset(t => {
appHistory.addEventListener("navigate", e => {
e.transitionWhile(new Promise(r => t.step_timeout(r, 5)));
}, { once: true });
}, "Resets the focus when no focusReset option is provided (nontrivial fulfilled promise)");

testFocusWasReset(t => {
appHistory.addEventListener("navigate", e => {
e.transitionWhile(Promise.reject());
}, { once: true });
}, "Resets the focus when no focusReset option is provided (rejected promise)");

testFocusWasReset(t => {
appHistory.addEventListener("navigate", e => {
e.transitionWhile(
Promise.resolve(),
{ focusReset: "after-transition" }
);
}, { once: true });
}, "Resets the focus when focusReset is explicitly set to 'after-transition'");

testFocusWasNotReset(t => {
appHistory.addEventListener("navigate", e => {
e.transitionWhile(Promise.resolve(), { focusReset: "manual" });
});
}, "Does not reset the focus when focusReset is set to 'manual'");
</script>
67 changes: 67 additions & 0 deletions app-history/focus-reset/multiple-transitionWhile.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<!doctype html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>

<script type="module">
import { testFocusWasReset, testFocusWasNotReset } from "./resources/helpers.mjs";

testFocusWasReset(t => {
appHistory.addEventListener("navigate", e => {
e.transitionWhile(Promise.resolve());
}, { once: true });

appHistory.addEventListener("navigate", e => {
e.transitionWhile(Promise.resolve(), { focusReset: "after-transition" });
}, { once: true });
}, "(not provided) + after-transition");

testFocusWasNotReset(t => {
appHistory.addEventListener("navigate", e => {
e.transitionWhile(Promise.resolve());
}, { once: true });

appHistory.addEventListener("navigate", e => {
e.transitionWhile(Promise.resolve(), { focusReset: "manual" });
}, { once: true });
}, "(not provided) + manual");

testFocusWasNotReset(t => {
appHistory.addEventListener("navigate", e => {
e.transitionWhile(Promise.resolve(), { focusReset: "after-transition" });
}, { once: true });

appHistory.addEventListener("navigate", e => {
e.transitionWhile(Promise.resolve(), { focusReset: "manual" });
}, { once: true });
}, "after-transition + manual");

testFocusWasReset(t => {
appHistory.addEventListener("navigate", e => {
e.transitionWhile(Promise.resolve(), { focusReset: "after-transition" });
}, { once: true });

appHistory.addEventListener("navigate", e => {
e.transitionWhile(Promise.resolve());
}, { once: true });
}, "after-transition + (not provided)");

testFocusWasReset(t => {
appHistory.addEventListener("navigate", e => {
e.transitionWhile(Promise.resolve(), { focusReset: "manual" });
}, { once: true });

appHistory.addEventListener("navigate", e => {
e.transitionWhile(Promise.resolve(), { focusReset: "after-transition" });
}, { once: true });
}, "manual + after-transition");

testFocusWasNotReset(t => {
appHistory.addEventListener("navigate", e => {
e.transitionWhile(Promise.resolve(), { focusReset: "manual" });
}, { once: true });

appHistory.addEventListener("navigate", e => {
e.transitionWhile(Promise.resolve());
}, { once: true });
}, "manual + (not provided)");
</script>
45 changes: 45 additions & 0 deletions app-history/focus-reset/resources/helpers.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// 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.

export function testFocusWasReset(setupFunc, description) {
promise_test(async t => {
setupFunc(t);

const button = document.body.appendChild(document.createElement("button"));
t.add_cleanup(() => { button.remove(); });

assert_equals(document.activeElement, document.body, "Start on body");
button.focus();
assert_equals(document.activeElement, button, "focus() worked");

const { committed, finished } = appHistory.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");
}, description);
}

export function testFocusWasNotReset(setupFunc, description) {
promise_test(async t => {
setupFunc(t);

const button = document.body.appendChild(document.createElement("button"));
t.add_cleanup(() => { button.remove(); });

assert_equals(document.activeElement, document.body, "Start on body");
button.focus();
assert_equals(document.activeElement, button, "focus() worked");

const { committed, finished } = appHistory.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");
}, description);
}

0 comments on commit e6bbf05

Please sign in to comment.