Skip to content

[focus-without-user-activation] Thread the focus initiator through focus-producing APIs  - #1

Draft
ffiori wants to merge 3 commits into
mainfrom
user/ffiori/focus-threaded-source
Draft

[focus-without-user-activation] Thread the focus initiator through focus-producing APIs #1
ffiori wants to merge 3 commits into
mainfrom
user/ffiori/focus-threaded-source

Conversation

@ffiori

@ffiori ffiori commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Summary

This updates the focus-without-user-activation checks to use the document that initiates a focus-producing operation, instead of implicitly using the document that owns the focus target.

It also allows a document with the policy denied to continue managing focus when that document, or one of its nested browsing contexts, already has focus. This preserves focus management in embedded applications without allowing an unfocused restricted frame to steal focus from its parent or a focused sibling.

This is a more complete version of whatwg#11519 and follows the design discussion in whatwg#11839, whatwg#12032, and whatwg#12470.

Motivation

The policy is intended to prevent unexpected cross-frame focus theft, rather than disable ordinary focus management after a host has already delegated focus into an embedded application.

Consider a top-level document A containing B, which in turn contains C. B and C have focus-without-user-activation denied.

Focus initiator Target Currently focused Expected
B Element in B B Allowed
B Child C B Allowed
B Element in B C Allowed
C Element in C A or sibling B Blocked

The first three cases are needed by applications that embed nested experiences, including Teams and other Microsoft 365 applications. The last case preserves the policy's anti-focus-theft behavior.

The current target-based checks also produce incorrect results for cross-document calls. For example, when B invokes focus() on an element owned by C, B is responsible for moving focus. C's policy or transient activation should not authorize or reject B's request.

Changes

This PR:

  • changes allow focus steps to operate on a focusSetterDocument;
  • checks the focus setter's Permissions Policy and transient user activation instead of the target's;
  • allows the request when the setter or an inclusive descendant already has focus;
  • obtains the setter from the incumbent global object at script API entry points;
  • explicitly threads the setter document through:
    • Window.focus();
    • HTMLOrSVGOrMathMLElement.focus();
    • HTMLDialogElement.show() and showModal();
    • the dialog focusing algorithms;
    • showPopover() and togglePopover();
    • the popover focusing algorithms;
  • uses the invoking element's document for button-command, popover-target, and select-picker paths;
  • keeps automatic document-load autofocus target-based, since it has no author-script incumbent;
  • prevents the focused-subtree fallback while a focused document in that subtree has a fullscreen element; and
  • avoids running dialog focusing steps when the dialog's document is not fully active.

Why use the incumbent global object?

The current global object can describe the realm associated with the method or target object instead of the script that initiated a cross-document call. That does not distinguish cases such as B invoking focus() on an element owned by C.

The incumbent global object represents the author script responsible for the operation, following the same general caller-attribution model used by APIs such as postMessage(). Capturing it at the API boundary also gives implementations an explicit source document to carry through cross-origin or cross-process dispatch, instead of trying to recover caller identity inside the target-side algorithm.

The incumbent is captured before nested dialog or popover algorithms run, so callbacks such as beforetoggle do not replace the original focus initiator.

Permissions Policy and activation

Authorization is based on the focus setter:

  1. If the setter is allowed to use focus-without-user-activation, allow the request.
  2. If the setter has transient user activation, allow the request.
  3. Otherwise, allow the request only while focus is already within the setter's inclusive navigable subtree.

This means:

  • an allowed parent can focus an element in a child whose policy is denied, matching the TPAC 2024 focus-delegation resolution;
  • activation on the target alone does not authorize a different restricted frame to move focus; and
  • a restricted frame that neither has activation nor owns the current focus chain remains unable to steal focus.

Dialogs, popovers, and autofocus

Dialogs and popovers can move focus as part of their algorithms. Threading the setter through those paths avoids making element.focus() caller-based while leaving showModal() or showPopover() target-based.

User-agent invocation paths use the document of the invoking element as their explicit source. Automatic document-load autofocus continues to use its target document because it can run from the event loop without an incumbent author script.

Fullscreen

The focused-subtree exception is not used when a focused document in the setter's subtree has a fullscreen element. This addresses the case where an ancestor attempts to retake focus from fullscreen embedded content after the activation used to enter fullscreen has expired.

Permissions Policy or fresh transient activation are evaluated before this fallback guard.

The exact fullscreen boundary, including whether focus movement entirely within the active fullscreen surface should remain allowed, is an area where further review and WPT coverage would be useful.

Tests

The intended direct-focus behavior is represented by the following WPTs:

Additional coverage is still needed for fullscreen, popup/opener behavior, cross-document dialogs and popovers, and automatic autofocus.

Review focus

The main policy questions for review are:

  • whether owning the current focus chain should authorize targets outside the setter's navigable subtree, including siblings, ancestors, and opener relationships;
  • whether dialog and popover focus should consistently use the initiating document;
  • whether the target's policy should ever act as an independent opt-out against an allowed setter; and
  • whether fullscreen should block only focus leaving the active fullscreen surface, rather than every use of the focused-subtree fallback.

Related

Carry the initiating document through direct focus, dialog, and popover algorithms. Use explicit invoking documents for user-agent paths and block the focused-subtree exception while a focused descendant is fullscreen.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e74da64d-6b71-41bf-8b15-4e34d5f7a6ec
Prevent caller-based authorization from reaching dialog focusing steps that dereference a null node navigable for detached or synthetic documents.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e74da64d-6b71-41bf-8b15-4e34d5f7a6ec
Keep the focus-without-user-activation change scoped to caller attribution and focused descendants. Fullscreen interaction can be specified separately once its exact boundary is agreed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e74da64d-6b71-41bf-8b15-4e34d5f7a6ec
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant