From b33d44fd24d5021cb0f94701afa359d923a9f841 Mon Sep 17 00:00:00 2001 From: Fernando Fiori Date: Thu, 3 Sep 2026 14:49:13 -0700 Subject: [PATCH 1/4] Thread focus initiator through focus-producing APIs 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 --- source | 135 ++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 101 insertions(+), 34 deletions(-) diff --git a/source b/source index 1d4de3a60e2..0b6ec0e86dd 100644 --- a/source +++ b/source @@ -56934,7 +56934,8 @@ interface HTMLButtonElement : HTMLElement { validity be false.

  • If validity is true, then run the show popover - algorithm given target, false, and element.

  • + algorithm given target, false, element, and + element's node document.

  • Otherwise:

    @@ -56961,7 +56962,8 @@ interface HTMLButtonElement : HTMLElement { validity be false.

  • If validity is true, then run the show popover - algorithm given target, false, and element.

  • + algorithm given target, false, element, and + element's node document.

    @@ -57409,7 +57411,8 @@ interface HTMLSelectElement : HTMLElement { data-x="dom-MouseEvent-button">button attribute is not 0, then return.

  • Run the show popover algorithm given select's select - popover, false, and select.

  • + popover, false, select, and select's node + document.

    @@ -66407,6 +66410,10 @@ interface HTMLDialogElement : HTMLElement { method steps are:

      +
    1. Let focusSetterDocument be the incumbent global object's associated Document.

    2. +
    3. If this has an open attribute and is modal of this is false, then return.

    4. @@ -66441,14 +66448,23 @@ interface HTMLDialogElement : HTMLElement {
    5. Run hide popovers until given document, hideUntil, false, and true.

    6. -
    7. Run the dialog focusing steps given this.

    8. +
    9. Run the dialog focusing steps given this and + focusSetterDocument.

    The showModal() method steps are to show a modal - dialog given this and null.

    + data-x="dom-dialog-showModal">showModal() method steps are:

    + +
      +
    1. Let focusSetterDocument be the incumbent global object's associated Document.

    2. + +
    3. Show a modal dialog given this, null, and + focusSetterDocument.

    4. +
    @@ -66625,8 +66641,9 @@ interface HTMLDialogElement : HTMLElement {
    -

    To show a modal dialog given a dialog element subject and an - Element or null source:

    +

    To show a modal dialog given a dialog element subject, an + Element or null source, and a Document + focusSetterDocument:

    1. If subject has an open attribute and @@ -66700,7 +66717,8 @@ interface HTMLDialogElement : HTMLElement {

    2. Run hide popovers until given document, hideUntil, false, and true.

    3. -
    4. Run the dialog focusing steps given subject.

    5. +
    6. Run the dialog focusing steps given subject and + focusSetterDocument.

    @@ -66781,7 +66799,8 @@ interface HTMLDialogElement : HTMLElement {
  • If command is the Show Modal state and element does not have an open attribute, then - show a modal dialog given element and source.

  • + show a modal dialog given element, source, and + source's node document.

    @@ -66973,12 +66992,12 @@ interface HTMLDialogElement : HTMLElement {
    -

    The dialog focusing steps, given a dialog element subject, - are as follows:

    +

    The dialog focusing steps, given a dialog element subject + and a Document focusSetterDocument, are as follows:

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

    2. +
    3. If the allow focus steps given focusSetterDocument return false, + then return.

    4. Let control be null.

    5. @@ -87402,8 +87421,12 @@ dictionary CommandEventInit : EventInit {
    6. If current is null, then return.

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

    9. +
    10. Let focusSetterDocument be the incumbent global object's associated Document.

    11. + +
    12. If the allow focus steps given focusSetterDocument return false, + then return.

    13. Run the focusing steps with current.

    14. @@ -87430,8 +87453,12 @@ 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. Let focusSetterDocument be the incumbent global object's associated Document.

      4. + +
      5. If the allow focus steps given focusSetterDocument return false, + then return.

      6. Run the focusing steps for this.

      7. @@ -87469,17 +87496,44 @@ dictionary CommandEventInit : EventInit {

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

        + focusSetterDocument, are:

          -
        1. If target is allowed to use the "

          If focusSetterDocument is allowed to use the "focus-without-user-activation" feature, then return true.

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

        4. +
        5. If focusSetterDocument's relevant global object has + transient activation, then return true.

        6. -
        7. Return false.

        8. +
        9. If focusSetterDocument is not fully active, then return + false.

        10. + +
        11. Let focusSetterOrDescendantHasFocus be false.

        12. + +
        13. +

          For each navigable in the inclusive descendant navigables of + focusSetterDocument:

          + +
            +
          1. Let descendantDocument be navigable's active document.

          2. + +
          3. +

            If running the has focus steps given descendantDocument returns + true:

            + +
              +
            1. If descendantDocument's fullscreen element is not null, then + return false.

            2. + +
            3. Set focusSetterOrDescendantHasFocus to true.

            4. +
            +
          4. +
          +
        14. + +
        15. Return focusSetterOrDescendantHasFocus.

        @@ -92218,19 +92272,24 @@ dictionary DragEventInit : MouseEventInit { data-x="dom-showPopover">showPopover(options) method steps are:

          +
        1. Let focusSetterDocument be the incumbent global object's associated Document.

        2. +
        3. Let source be options["source"] if it exists; otherwise, null.

        4. -
        5. Run show popover given this, true, and - source.

        6. +
        7. Run show popover given this, true, source, and + focusSetterDocument.

    To show popover, given an HTML element - element, a boolean throwExceptions, and an HTML - element or null source:

    + element, a boolean throwExceptions, an HTML + element or null source, and a Document + focusSetterDocument:

    1. Let document be element's node document.

    2. @@ -92471,7 +92530,8 @@ dictionary DragEventInit : MouseEventInit {
    3. Set element's implicit anchor element to source.

    4. -
    5. Run the popover focusing steps given element.

    6. +
    7. Run the popover focusing steps given element and + focusSetterDocument.

    8. If shouldRestoreFocus is true and element's popover attribute is not in the DragEventInit : MouseEventInit { data-x="dom-togglePopover">togglePopover(options) method steps are:

        +
      1. Let focusSetterDocument be the incumbent global object's associated Document.

      2. +
      3. Let force be null.

      4. If options is a boolean, set force to options.

      5. @@ -92757,7 +92821,7 @@ dictionary DragEventInit : MouseEventInit { null.

      6. Otherwise, if force is null or true, then run show popover given - this, true, and source.

      7. + this, true, source, and focusSetterDocument.

      8. Otherwise:

        @@ -93002,14 +93066,15 @@ dictionary DragEventInit : MouseEventInit {

        To perform the popover focusing steps for an HTML - element subject:

        + element subject and a Document + focusSetterDocument:

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

        2. +
        3. If the allow focus steps given focusSetterDocument return false, + then return.

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

        5. + steps given subject and focusSetterDocument, and return.

        6. If subject has the autofocus attribute, then let control be subject. @@ -93292,7 +93357,8 @@ dictionary DragEventInit : MouseEventInit { validity be false.

        7. If validity is true, then run show popover given - popover, false, and node.

        8. + popover, false, node, and node's node + document.

      @@ -162133,6 +162199,7 @@ INSERT INTERFACES HERE Félix Sanz, Felix Sasaki, Fernando Altomare Serboncini, + Fernando Fiori, Forbes Lindesay, Francesco Schwarz, Francis Brosnan Blazquez, From c1fb9a9c5aba0e7f247399b11a058904d34d7513 Mon Sep 17 00:00:00 2001 From: Fernando Fiori Date: Thu, 3 Sep 2026 16:06:37 -0700 Subject: [PATCH 2/4] Guard dialog focus in inactive documents 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 --- source | 3 +++ 1 file changed, 3 insertions(+) diff --git a/source b/source index 0b6ec0e86dd..dcd281c8beb 100644 --- a/source +++ b/source @@ -66996,6 +66996,9 @@ interface HTMLDialogElement : HTMLElement { and a Document focusSetterDocument, are as follows:

        +
      1. If subject's node document is not fully active, then + return.

      2. +
      3. If the allow focus steps given focusSetterDocument return false, then return.

      4. From 6df89bea99fba0c520465e6b1777ed57b5d8d28b Mon Sep 17 00:00:00 2001 From: Fernando Fiori Date: Fri, 4 Sep 2026 10:51:03 -0700 Subject: [PATCH 3/4] Defer fullscreen focus handling 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 --- source | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/source b/source index dcd281c8beb..7165ea2ffb4 100644 --- a/source +++ b/source @@ -87512,8 +87512,6 @@ dictionary CommandEventInit : EventInit {
      5. If focusSetterDocument is not fully active, then return false.

      6. -
      7. Let focusSetterOrDescendantHasFocus be false.

      8. -
      9. For each navigable in the inclusive descendant navigables of focusSetterDocument:

        @@ -87522,21 +87520,12 @@ dictionary CommandEventInit : EventInit {
      10. Let descendantDocument be navigable's active document.

      11. -
      12. -

        If running the has focus steps given descendantDocument returns - true:

        - -
          -
        1. If descendantDocument's fullscreen element is not null, then - return false.

        2. - -
        3. Set focusSetterOrDescendantHasFocus to true.

        4. -
        -
      13. +
      14. If running the has focus steps given descendantDocument returns + true, then return true.

    9. -
    10. Return focusSetterOrDescendantHasFocus.

    11. +
    12. Return false.

    From 9b9b0f9f0454f3ba470fb013ebf0e164facdf51a Mon Sep 17 00:00:00 2001 From: Fernando Fiori Date: Fri, 4 Sep 2026 11:23:47 -0700 Subject: [PATCH 4/4] Check the target as well as the focus setter Builds on the threading in this branch. `allow focus steps` took only `focusSetterDocument`, so the document the focus would move *into* was never consulted. Two consequences. A document that holds the focus could move it anywhere. A (top, allowed) embeds cross-origin B and V, both denied. The user clicked into B once and the activation has expired, so the focus is in B. B runs `top.frames['v'].focus()`: B is not allowed and has no activation, but B holds the focus, so the steps returned true and the focus landed in V -- which was denied the feature precisely to stop that. V is not related to B at all. And three things that work on main stopped working, because element.focus() consulted the setter instead of the element's own document: * a denied frame calling `aWin.focus()` on its allowed parent; * a denied frame focusing an element of an allowed same-origin ancestor; * a same-origin helper frame driving focus through a shared ancestor's DOM, e.g. `parent.document.getElementById('fB').focus()`. Both are fixed by consulting the target first and scoping the setter's authority to its own subtree: 1. target is allowed to use the feature -> true 2. target's relevant global has transient activation -> true 3. target is not fully active -> false 4. has focus steps(target) -> true 5. focusSetterDocument is not fully active -> false 6. focusSetterDocument does not contain target -> false 7. focusSetterDocument is allowed to use the feature -> true 8. focusSetterDocument has transient activation -> true 9. has focus steps(focusSetterDocument) -> true 10. false Steps 1 and 2 are main's, unchanged and still first, so nothing that focuses today stops focusing. Steps 5-9 say: the setter could have moved the focus to itself, and the target is inside the setter's subtree. Scoping by containment is what stops the cousin case, and it is safe within the subtree because an embedder determines its descendants' permissions policy. The two focus APIs still agree. `iframeB.focus()` is checked against A with A as the setter and passes at step 1; `bWin.focus()` is checked against B with A as the setter and passes at step 7. A child calling `window.focus()` on itself is its own setter and passes none of 7-9. Also collapses the descendant loop. `has focus steps` walks down the focus chain from the top-level traversable and returns true as soon as it reaches its argument, so it is already true when the focus is in that document or in any of its descendant navigables. The loop over inclusive descendant navigables was exactly one `has focus steps` call. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- source | 55 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/source b/source index 7165ea2ffb4..0b76e283e62 100644 --- a/source +++ b/source @@ -66999,8 +66999,8 @@ interface HTMLDialogElement : HTMLElement {
  • If subject's node document is not fully active, then return.

  • -
  • If the allow focus steps given focusSetterDocument return false, - then return.

  • +
  • If the allow focus steps given subject's node + document and focusSetterDocument return false, then return.

  • Let control be null.

  • @@ -87428,7 +87428,8 @@ dictionary CommandEventInit : EventInit { data-x="concept-incumbent-global">incumbent global object's associated Document.

    -
  • If the allow focus steps given focusSetterDocument return false, +

  • If the allow focus steps given current's active document and focusSetterDocument return false, then return.

  • Run the focusing steps with current.

  • @@ -87460,8 +87461,8 @@ dictionary CommandEventInit : EventInit { data-x="concept-incumbent-global">incumbent global object's associated Document.

    -
  • If the allow focus steps given focusSetterDocument return false, - then return.

  • +
  • If the allow focus steps given this's node + document and focusSetterDocument return false, then return.

  • Run the focusing steps for this.

  • @@ -87498,32 +87499,38 @@ dictionary CommandEventInit : EventInit {
    -

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

    +

    The allow focus steps, given a Document object target + and a Document object focusSetterDocument, are:

      -
    1. If focusSetterDocument is allowed to use the "

      If target is allowed to use the "focus-without-user-activation" feature, then return true.

    2. -
    3. If focusSetterDocument's relevant global object has - transient activation, then return true.

    4. +
    5. If target's relevant global object has transient + activation, then return true.

    6. + +
    7. If target is not fully active, then return false.

    8. + +
    9. If running the has focus steps given target returns true, then + return true.

    10. If focusSetterDocument is not fully active, then return false.

    11. -
    12. -

      For each navigable in the inclusive descendant navigables of - focusSetterDocument:

      +
    13. If focusSetterDocument's inclusive descendant navigables does not + contain target's node navigable, then + return false.

    14. -
        -
      1. Let descendantDocument be navigable's active document.

      2. +
      3. If focusSetterDocument is allowed to use the "focus-without-user-activation" feature, + then return true.

      4. -
      5. If running the has focus steps given descendantDocument returns - true, then return true.

      6. -
      - +
    15. If focusSetterDocument's relevant global object has + transient activation, then return true.

    16. + +
    17. If running the has focus steps given focusSetterDocument returns + true, then return true.

    18. Return false.

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

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

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

  • Let topDocument be target's node navigable's top-level traversable's active @@ -93062,8 +93069,8 @@ dictionary DragEventInit : MouseEventInit { focusSetterDocument:

      -
    1. If the allow focus steps given focusSetterDocument return false, - then return.

    2. +
    3. If the allow focus steps given subject's node + document and focusSetterDocument return false, then return.

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