From 18b7c269181ca72dc5dc799b03479d03cde02fe5 Mon Sep 17 00:00:00 2001 From: Fernando Fiori Date: Thu, 10 Sep 2026 17:24:05 -0700 Subject: [PATCH 1/4] Base focus permission on the initiating document Check policy and transient activation on the initiating document, and let --- source | 129 ++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 95 insertions(+), 34 deletions(-) diff --git a/source b/source index 1d4de3a60e2..34e5d989704 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,15 @@ 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 subject's node document is not fully active, then + return.

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

    6. Let control be null.

    7. @@ -87402,8 +87424,15 @@ dictionary CommandEventInit : EventInit {
    8. If current is null, then return.

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

    11. +
    12. If current's active document is not + fully active, then return.

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

    15. + +
    16. If the allow focus steps given focusSetterDocument return false, + then return.

    17. Run the focusing steps with current.

    18. @@ -87430,8 +87459,15 @@ 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. If this's node document is not fully active, then + return.

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

      6. + +
      7. If the allow focus steps given focusSetterDocument return false, + then return.

      8. Run the focusing steps for this.

      9. @@ -87469,18 +87505,27 @@ 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 not fully active, then return + false.

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

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

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

        8. -
        9. Return false.

        10. +
        11. Return the result of running the has focus steps given + focusSetterDocument.

        + +

        These steps do not restrict the destination of an authorized focus change. The + has focus steps include focus in descendant navigables and require system + focus. Once focus leaves the setter's subtree, this focus-based allowance no longer + applies.

    @@ -92218,19 +92263,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 +92521,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 +92812,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 +93057,18 @@ 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 subject's node document is not fully active, then + return.

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

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

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

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

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

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

      @@ -162133,6 +162193,7 @@ INSERT INTERFACES HERE Félix Sanz, Felix Sasaki, Fernando Altomare Serboncini, + Fernando Fiori, Forbes Lindesay, Francesco Schwarz, Francis Brosnan Blazquez, From 6349732fc97dbfb8af1bdad8c9c6e2aebdf2d73e Mon Sep 17 00:00:00 2001 From: Fernando Fiori Date: Thu, 10 Sep 2026 17:47:04 -0700 Subject: [PATCH 2/4] Preserve focus authority across callbacks and deferred autofocus Snapshot the invoking document before firing a button's command event. Forward that document through popover and dialog command handlers so adopting the invoker cannot change the authority for a pending action. Recheck an autofocus candidate's document when flushing the queue. A candidate must not reclaim focus using an allowance that ended after handoff or activation expiry. Clarify the current-focus and system-focus requirements in the permission algorithm's note. The proposal remains setter-only: a fully active initiator is authorized by its permissions policy, transient activation, or current focus chain. There is no destination-policy grant or destination-containment gate. Destination validity is checked separately by the callers, and fullscreen-specific policy remains deferred. Related: https://github.com/whatwg/html/issues/12032 Related: https://github.com/whatwg/html/issues/12470 Related: https://github.com/whatwg/html/issues/11839 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e74da64d-6b71-41bf-8b15-4e34d5f7a6ec --- source | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/source b/source index 34e5d989704..43d78b96b30 100644 --- a/source +++ b/source @@ -56881,6 +56881,9 @@ interface HTMLButtonElement : HTMLElement {

      If target is not null:

        +
      1. Let focusSetterDocument be element's node + document.

      2. +
      3. Let command be element's command attribute.

      4. @@ -56935,7 +56938,7 @@ interface HTMLButtonElement : HTMLElement {
      5. If validity is true, then run the show popover algorithm given target, false, element, and - element's node document.

      6. + focusSetterDocument.

      7. Otherwise:

        @@ -56963,14 +56966,14 @@ interface HTMLButtonElement : HTMLElement {
      8. If validity is true, then run the show popover algorithm given target, false, element, and - element's node document.

      9. + focusSetterDocument.

    9. Otherwise, if this standard defines command steps for target's local name, then run the corresponding command steps given target, element, - and command.

    10. + command, and focusSetterDocument.

    @@ -66776,8 +66779,9 @@ interface HTMLDialogElement : HTMLElement {

    The command steps for dialog elements, given an element - element, an element source, and a command attribute command, are:

    + element, an element source, a command attribute command, and a + Document focusSetterDocument, are:

    1. If element is in the HTMLDialogElement : HTMLElement { data-x="attr-button-command-show-modal-state">Show Modal state and element does not have an open attribute, then show a modal dialog given element, source, and - source's node document.

    2. + focusSetterDocument.

    @@ -87523,8 +87527,10 @@ dictionary CommandEventInit : EventInit {

    These steps do not restrict the destination of an authorized focus change. The - has focus steps include focus in descendant navigables and require system - focus. Once focus leaves the setter's subtree, this focus-based allowance no longer + has focus steps include focus in focusSetterDocument's descendant + navigables and require its node navigable's top-level + traversable to have system focus. Once focus leaves + focusSetterDocument and those navigables, this focus-based allowance no longer applies.

    @@ -87683,6 +87689,9 @@ dictionary CommandEventInit : EventInit {
  • Remove element from candidates.

  • +
  • If the allow focus steps given doc return false, then + continue.

  • +
  • Let inclusiveAncestorDocuments be a list consisting of the active document of doc's inclusive ancestor navigables.

  • From 1812262781e428a1b47b5d713e6fb9047728fa79 Mon Sep 17 00:00:00 2001 From: Fernando Fiori Date: Fri, 11 Sep 2026 11:56:00 -0700 Subject: [PATCH 3/4] Drop redundant popover validity check and focus note Show popover already checks destination validity after author callbacks. Do not repeat its fully-active precondition in popover focusing steps. Remove the explanatory note to keep the specification change concise. Retain the setter, Window, element, and dialog guards: their other steps do not guarantee that the relevant documents are fully active. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e74da64d-6b71-41bf-8b15-4e34d5f7a6ec --- source | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/source b/source index 43d78b96b30..e33c13b35c9 100644 --- a/source +++ b/source @@ -87525,13 +87525,6 @@ dictionary CommandEventInit : EventInit {
  • Return the result of running the has focus steps given focusSetterDocument.

  • - -

    These steps do not restrict the destination of an authorized focus change. The - has focus steps include focus in focusSetterDocument's descendant - navigables and require its node navigable's top-level - traversable to have system focus. Once focus leaves - focusSetterDocument and those navigables, this focus-based allowance no longer - applies.

    @@ -93070,9 +93063,6 @@ dictionary DragEventInit : MouseEventInit { focusSetterDocument:

      -
    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 ba10787c47ad8afbef641d863d563bed315d7ff9 Mon Sep 17 00:00:00 2001 From: Fernando Fiori Date: Wed, 16 Sep 2026 13:25:46 -0700 Subject: [PATCH 4/4] Avoid unnecessary focus initiator captures Capture the focus setter Document only in togglePopover()'s showing branch, and after show()'s existing already-open checks. Keep capture ahead of author callbacks and preserve the existing focus permission checks and forwarded arguments. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e74da64d-6b71-41bf-8b15-4e34d5f7a6ec --- source | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/source b/source index e33c13b35c9..6a98c80c634 100644 --- a/source +++ b/source @@ -66413,16 +66413,16 @@ 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. If this has an open attribute, then throw an "InvalidStateError" DOMException.

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

      7. +
      8. If the result of firing an event named beforetoggle, using ToggleEvent, with the cancelable attribute initialized to true, 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. @@ -92813,8 +92809,18 @@ dictionary DragEventInit : MouseEventInit { the hide popover algorithm given this, true, true, true, and null.

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

        7. +
        8. +

          Otherwise, if force is null or true:

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

          2. + +
          3. Run show popover given this, true, source, and + focusSetterDocument.

          4. +
          +
        9. Otherwise: