Allow focus moves within a document's own subtree - #2
Draft
ffiori wants to merge 1 commit into
Draft
Conversation
The "focus-without-user-activation" feature previously refused any programmatic focus move into a document that was not allowed to use it, regardless of where the focus already was. That prevented an embedded document from managing focus inside itself once it had been focused, and it left the TPAC 2024 resolution that a parent may focus a child navigable unspecified. The allow focus steps now also succeed when the focus is already inside the target, and when the document responsible for the request could have taken the focus itself and the target is inside that document's subtree. To make "the document responsible for the request" well defined, the focusing steps take a source and return whether the change was refused. The source is named at each call site rather than derived from an ambient global, except for Window.focus(), where the same call is made both by a parent on a child and by a child on itself and so the responsible document cannot be recovered from the arguments. Because the check now runs after the focusing steps redirect a navigable container to its content navigable, focus() on an iframe element and focus() on that iframe's Window are gated identically; previously the element form was checked against the embedder and the Window form against the child. Closes whatwg#11839. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The "
focus-without-user-activation" feature currently refuses any programmatic focus move into a document that is not allowed to use it, regardless of where the focus already is. Two consequences:This closes both gaps. It supersedes whatwg#11519, which tried to close the first one by consulting the current global object inside the algorithm.
Discussion: whatwg#11839. Previous spec PR for this feature: whatwg#10672.
What changes
allow focus stepsnow also succeeds when the focus is already inside the target, and when the document responsible for the request could have taken the focus itself and the target is inside that document's subtree:Steps 5–9 read as: the source could have moved the focus to itself, and the target is inside the source's subtree. Steps 1 and 2 are unchanged from
mainand still evaluated first, so nothing that focuses today stops focusing.Where the source comes from
To make "the document responsible for the request" well defined,
focusing stepstakes asourceand returns whether the change was refused. The source is named at each call site rather than derived from an ambient global:HTMLOrSVGOrMathMLElement.focus()this's node documentautofocus, dialog focusing steps, popover focusing stepsWindow.focus()DocumentWindow.focus()is the one place an ambient lookup is needed, and it is needed there:someWindow.focus()is the same call whether a parent makes it on a child or a child makes it on itself, so the responsible document cannot be recovered from the arguments. It is derived from the incumbent for the same reasonlocation.assign()derives itssourceDocumentthat way.Side effect: the two focus APIs stop disagreeing
The check now runs after the step that redirects a navigable container to its content navigable, so it always evaluates the document the focus lands in. On
main,iframeB.focus()is checked against the embedder whileiframeB.contentWindow.focus()is checked against the child — which is the only reason focus delegation happens to work through one API and not the other. They are now gated identically.Behaviour
A hosts B hosts C, with the feature denied for B and C:
iframeCiframeB.focus()bWindow.focus()window.focus()on itselfFocus moves across top-level traversables are unaffected: a document whose node navigable has a null parent is always allowed to use a feature whose default allowlist is
'self', so step 1 short-circuits for any top-level target.Considered and not done
A fullscreen carve-out. Raised in whatwg#11519 for A → B → C where C is fullscreen and B takes the focus. It cannot be made effective here:
requestFullscreen()consumes activation, but any subsequent interaction with the fullscreen content runs the activation notification steps, which give every ancestorWindowtransient activation without consuming it — so step 2 short-circuits ahead of any guard. A guard placed before step 2 would instead stop a user-initiated move out of fullscreen. This looks like whatwg/fullscreen#15 rather than something for this feature; happy to add a carve-out if editors disagree.Deciding purely from the target. Would remove the need for a source entirely, but then
contentWindow.focus()could not be used for delegation, and the two focus APIs would stay inconsistent.Points that need an explicit decision
has focus stepsis true for a document when the focus is in any of its descendants, so a document may take the focus back from a child navigable the user was interacting with. This is intended, and is the behaviour embedders have asked for, but it is worth a resolution rather than falling out ofhas focus steps.(See WHATWG Working Mode: Changes for more details.)