From 5ee57c0b0901f43d6853f0548c8597d8b708bae3 Mon Sep 17 00:00:00 2001 From: Fernando Fiori Date: Fri, 4 Sep 2026 11:02:58 -0700 Subject: [PATCH] Allow focus moves within a document's own subtree 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 #11839. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- source | 90 ++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 72 insertions(+), 18 deletions(-) diff --git a/source b/source index 1d4de3a60e2..83bd8dbf17d 100644 --- a/source +++ b/source @@ -66978,7 +66978,8 @@ interface HTMLDialogElement : HTMLElement {
  1. If the allow focus steps given subject's node - document return false, then return.

  2. + document and subject's node document return false, then + return.

  3. Let control be null.

  4. @@ -86748,7 +86749,9 @@ dictionary CommandEventInit : EventInit {

    The focusing steps for an object new focus target that is either a focusable area, or an element that is not a focusable area, or a navigable, are as follows. They can optionally be run with a fallback - target and a string focus trigger.

    + target, a string focus trigger, and a Document-or-null + source (default null). They return false if the focus change was refused by the + allow focus steps, and true otherwise; true does not imply that the focus moved.

      @@ -86763,7 +86766,7 @@ dictionary CommandEventInit : EventInit {

      If new focus target is null:

        -
      1. If no fallback target was specified, then return.

      2. +
      3. If no fallback target was specified, then return true.

      4. Otherwise, set new focus target to the fallback target.

      5. @@ -86775,11 +86778,25 @@ dictionary CommandEventInit : EventInit { navigable's active document.

        +
      6. +

        If source is not null:

        + +
          +
        1. If new focus target is a Document, then let + targetDocument be new focus target. Otherwise, let + targetDocument be new focus target's DOM anchor's + node document.

        2. + +
        3. If the allow focus steps given targetDocument and + source return false, then return false.

        4. +
        +
      7. +
      8. If new focus target is a focusable area and its DOM - anchor is inert, then return.

      9. + anchor is inert, then return true.

      10. If new focus target is the currently focused area of a top-level - traversable, then return.

      11. + traversable, then return true.

      12. Let old chain be the current focus chain of the top-level traversable in which new focus @@ -86790,6 +86807,8 @@ dictionary CommandEventInit : EventInit {

      13. Run the focus update steps with old chain, new chain, and new focus target respectively.

      14. + +
      15. Return true.

      @@ -87402,10 +87421,8 @@ dictionary CommandEventInit : EventInit {
    1. If current is null, then return.

    2. -
    3. If the allow focus steps given current's active document return false, then return.

    4. - -
    5. Run the focusing steps with current.

    6. +
    7. If running the focusing steps with current, with the focus + source as the source, returns false, then return.

    8. If current is a top-level traversable, user agents are encouraged to trigger some sort of notification to indicate to the user that the page is attempting to gain @@ -87430,10 +87447,8 @@ dictionary CommandEventInit : EventInit { data-x="dom-focus">focus(options) method steps are:

        -
      1. If the allow focus steps given this's node document - return false, then return.

      2. - -
      3. Run the focusing steps for this.

      4. +
      5. If running the focusing steps for this, with this's + node document as the source, returns false, then return.

      6. If options["focusVisible"] is true, or does not CommandEventInit : EventInit {


        -

        The allow focus steps, given a Document object - target, are:

        +

        The focus source is the Document object or null returned by these + steps:

        + +
          +
        1. Let global be the incumbent global + object.

        2. + +
        3. If global is not a Window object, then return null.

        4. + +
        5. Return global's associated + Document.

        6. +
        +
        + +
        +

        The allow focus steps, given a Document object target + and a Document-or-null source, are:

        1. If target is allowed to use the "CommandEventInit : EventInit {

        2. If target's relevant global object has transient activation, then return true.

        3. +
        4. If target's node navigable is null, then return false.

        5. + +
        6. If running the has focus steps given target returns true, then + return true.

        7. + +
        8. If source is null or source's node navigable is null, + then return false.

        9. + +
        10. If source's inclusive descendant navigables does not contain target's node navigable, then + return false.

        11. + +
        12. If source is allowed to use the "focus-without-user-activation" feature, + then return true.

        13. + +
        14. If source's relevant global object has transient + activation, then return true.

        15. + +
        16. If running the has focus steps given source returns true, then + return true.

        17. +
        18. Return false.

        @@ -87563,8 +87615,8 @@ dictionary CommandEventInit : EventInit {
      7. If target's active sandboxing flag set has the sandboxed automatic features browsing context flag, then return.

      8. -
      9. If the allow focus steps given target return false, then - return.

      10. +
      11. If the allow focus steps given target and target return + false, then return.

      12. Let topDocument be target's node navigable's top-level traversable's active @@ -93006,7 +93058,8 @@ dictionary DragEventInit : MouseEventInit {

        1. If the allow focus steps given subject's node - document return false, then return.

        2. + document and subject's node document return false, then + return.

        3. If subject is a dialog element, then run the dialog focusing steps given subject and return.

        4. @@ -162133,6 +162186,7 @@ INSERT INTERFACES HERE Félix Sanz, Felix Sasaki, Fernando Altomare Serboncini, + Fernando Fiori, Forbes Lindesay, Francesco Schwarz, Francis Brosnan Blazquez,