ladybird/Tests/LibWeb/Text/input/HTML/focus-viewport-fallback.html
Andreas Kling 94302775bf LibWeb: Map document element focus to the viewport
Apply the focusing steps' get-the-focusable-area mapping before
rejecting a non-focusable target. This preserves documentElement.focus()
by mapping the non-focusable document element to the Document viewport.

Also map rendered navigable containers with content navigables to their
active document, while leaving hidden containers unfocused. Preserve
Window focus events for child document viewports reached through iframe
focus, while still suppressing the top-level viewport surrogate events.

Treat rendered object elements as focusable through their default
non-null tabindex, even when they show fallback or image content instead
of a child navigable.

Keep the spec focus-chain common-tail handling intact for viewport
focus. The Document object is only our surrogate for the viewport, so
designate viewport focus from the new focus target without dispatching
Window focus/focusin events for that top-level surrogate.

Pass that viewport surrogate as the fallback target for fragment
scrolling and NavigateEvent focus reset, so unfocusable body or fragment
targets still clear stale element focus.

Cover documentElement.focus() in both the activeElement and focus-chain
tests, including a tabindex document element that remains focused as an
element. Cover object focus with and without a child navigable, hidden
object focus attempts, iframe focus events, hidden iframe focus, and
blurring a focused iframe after it becomes hidden. Also cover viewport
fallback for intercepted navigation focus reset and fragment scrolling
to an unfocusable target.
2026-05-21 08:56:05 +02:00

47 lines
1.4 KiB
HTML

<!DOCTYPE html>
<script src="../include.js"></script>
<style>
#fragment-target {
margin-top: 500px;
height: 50px;
margin-bottom: 1000px;
}
</style>
<input id="before">
<div id="navigation-target"></div>
<div id="fragment-target"></div>
<script>
function activeElementName() {
return document.activeElement.id || document.activeElement.localName;
}
asyncTest(async done => {
await new Promise(resolve => window.addEventListener("load", resolve, { once: true }));
await timeout(0);
const before = document.getElementById("before");
const fragmentTarget = document.getElementById("fragment-target");
navigation.addEventListener("navigate", event => {
event.intercept({
scroll: "manual",
});
}, { once: true });
before.focus();
navigation.navigate("#navigation-target", { history: "push" });
await timeout(0);
println(`after intercepted navigation focus reset: ${activeElementName()}`);
before.focus();
const scrolled = new Promise(resolve => {
document.addEventListener("scroll", () => resolve(), { once: true });
});
location.hash = fragmentTarget.id;
await scrolled;
println(`after fragment focus fallback: ${activeElementName()}`);
done();
});
</script>