ladybird/Tests/LibWeb/Text/input/HTML/focus-shadow-delegates.html
Andreas Kling 30aae77901 LibWeb: Delegate focus from shadow hosts
Handle the delegatesFocus branch of get-the-focusable-area before
rejecting non-focusable focus targets. This lets host.focus() move focus
to the first focusable delegate in the shadow tree, or preserve an
already-focused descendant.

Treat delegatesFocus shadow hosts as focus delegates even when tabindex
would otherwise make the host focusable. Skip inert delegate candidates,
and reject inert shadow hosts before looking for a delegate. Check focus
inertness through shadow-host ancestry so direct focus cannot enter
inert shadow subtrees.

Rebaseline the disabled delegatesFocus WPT now that this behavior
passes. Cover delegated focus, hidden and inert delegate candidates,
tabindex hosts, inert hosts and direct delegates inside inert hosts, and
hosts without any focusable delegate.
2026-05-21 08:56:05 +02:00

57 lines
2.6 KiB
HTML

<!DOCTYPE html>
<script src="../include.js"></script>
<input id="before">
<div id="host"></div>
<div id="tabindex-host" tabindex="0"></div>
<div id="inert-host" inert></div>
<div id="empty-host"></div>
<script>
test(() => {
const before = document.getElementById("before");
const host = document.getElementById("host");
const shadowRoot = host.attachShadow({ mode: "open", delegatesFocus: true });
shadowRoot.innerHTML = `
<input id="hidden" style="display: none">
<input id="inert-delegate" inert>
<input id="delegate">
`;
before.focus();
host.focus();
println(`after delegatesFocus host.focus(): document=${document.activeElement.id}, shadow=${shadowRoot.activeElement.id}`);
before.focus();
shadowRoot.getElementById("delegate").focus();
host.focus();
println(`after delegatesFocus already focused host.focus(): document=${document.activeElement.id}, shadow=${shadowRoot.activeElement.id}`);
const tabindexHost = document.getElementById("tabindex-host");
const tabindexShadowRoot = tabindexHost.attachShadow({ mode: "open", delegatesFocus: true });
tabindexShadowRoot.innerHTML = `<input id="tabindex-delegate">`;
before.focus();
tabindexHost.focus();
println(`after delegatesFocus tabindex host.focus(): document=${document.activeElement.id}, shadow=${tabindexShadowRoot.activeElement.id}`);
const inertHost = document.getElementById("inert-host");
const inertShadowRoot = inertHost.attachShadow({ mode: "open", delegatesFocus: true });
inertShadowRoot.innerHTML = `<input id="inert-host-delegate">`;
before.focus();
inertHost.focus();
println(`after delegatesFocus inert host.focus(): document=${document.activeElement.id}, shadow=${inertShadowRoot.activeElement ? inertShadowRoot.activeElement.id : "null"}`);
before.focus();
inertShadowRoot.getElementById("inert-host-delegate").focus();
println(`after direct inert host delegate.focus(): document=${document.activeElement.id}, shadow=${inertShadowRoot.activeElement ? inertShadowRoot.activeElement.id : "null"}`);
const emptyHost = document.getElementById("empty-host");
const emptyShadowRoot = emptyHost.attachShadow({ mode: "open", delegatesFocus: true });
emptyShadowRoot.innerHTML = `<span>not focusable</span>`;
before.focus();
emptyHost.focus();
println(`after delegatesFocus empty host.focus(): ${document.activeElement.id}`);
});
</script>