diff --git a/spec.bs b/spec.bs index e30ffa49..42aeac02 100644 --- a/spec.bs +++ b/spec.bs @@ -30,6 +30,9 @@ spec: prerendering-revamped; urlPrefix: https://wicg.github.io/nav-speculation/p for: navigable text: loading mode; url: #navigable-loading-mode +spec: fetch; urlPrefix: https://fetch.spec.whatwg.org/ + type: dfn + text: queue a cross-origin embedder policy CORP violation report; url: queue-a-cross-origin-embedder-policy-corp-violation-report spec: html; urlPrefix: https://html.spec.whatwg.org/multipage/ type: dfn urlPrefix: browsers.html @@ -86,12 +89,36 @@ spec: html; urlPrefix: https://html.spec.whatwg.org/multipage/ urlPrefix: interaction.html text: activation notification; url: activation-notification text: consume user activation; url: consume-user-activation + text: activation; url: activation + text: click focusable; url: click-focusable + text: focusable area; url: focusable-area + text: sequential focus navigation; url: sequential-focus-navigation + text: focus; url: dom-window-focus + text: focus chain; url: focus-chain + text: focus update steps; url: focus-update-steps + text: focused; url: focused + text: gain focus; url: gains-focus + text: DOM anchor; url: dom-anchor + text: get the focusable area; url: get-the-focusable-area + text: currently focused area of a top-level traversable; url: currently-focused-area-of-a-top-level-traversable + text: focused area; url: focused-area-of-the-document + text: sequential navigation search algorithm; url: sequential-navigation-search-algorithm + urlPefix: infrastructure.html + text: immediately; url: immediately urlPrefix: nav-history-apis.html for: Window text: navigable; url: window-navigable -spec: fetch; urlPrefix: https://fetch.spec.whatwg.org/ - type: dfn - text: queue a cross-origin embedder policy CORP violation report; url: queue-a-cross-origin-embedder-policy-corp-violation-report + urlPrefix: interactive-elements.html + text: accesskey attribute command; url: using-the-accesskey-attribute-to-define-a-command-on-other-elements + text: previously focused element; url: previously-focused-element + urlPrefix: popover.html + text: hide popover algorithm; url: hide-popover-algorithm + urlPrefix: form-control-infrastructure.html + text: interactively validate the constraints; url: interactively-validate-the-constraints + urlPrefix: custom-elements.html + text: face validation anchor; url: face-validation-anchor + urlPrefix: webappapis.html + text: fire a click event; url: fire-a-click-event spec: RFC8941; urlPrefix: https://www.rfc-editor.org/rfc/rfc8941.html type: dfn text: structured header; url: #section-1 @@ -759,6 +786,185 @@ in the [[#nested-traversables-intro]]. 1. Return |navigables|. +

Modifications to the focusing algorithms

+ +The [[HTML]] standard defines how to handle focusing elements and {{Window}}s, both by user gesture +and through script-initiated APIs. Since fenced frames are designed to prevent communication across +a fenced frame boundary, we need to handle focusing carefully. This is because when focus is pulled +across a fenced frame boundary, contexts on both sides of the boundary can detect that change, which +can be used to open a communication channel between a <{fencedframe}> and its embedder. + +We do this is by not allowing the [=focusing steps=] to move script-initiated focus across a fenced +frame boundary. + +

When a user clicks on an element like a <{button}> inside a +<{fencedframe}> while another element *outside* of the <{fencedframe}> is [=focused=], because this +is a user-initiated action, the [=focusing steps=] will allow the <{button}> to [=gain focus=]. +Without allowing that, no element inside of a <{fencedframe}> would never be able [=gain +focus=].

+ +

If we were to continue blindly allowing all elements to +be [=focused=] via the {{HTMLOrSVGElement/focus()}} method as is the status quo before this +specification, a [=fenced navigable container=] and its [=fenced navigable container/fenced +navigable=] could use a sequence of {{HTMLOrSVGElement/focus()}} calls to send arbitrary data across +the fenced frame boundary, which is a privacy leak. To avoid this, we effectively "fence" the +{{HTMLOrSVGElement/focus()}} method, which sacrifices some functionality for privacy.

+ +
+ Modify the [=focusing steps=] to take a new optional [=boolean=] argument unfenced that defaults to false. + + Add a new step after step 3 of the algorithm (that changes new focus target) that reads: + + 4. If |new focus target| is a [=fenced navigable container=] with non-null + [=fenced navigable container/fenced navigable=], then set |new focus target| to the + [=fenced navigable container/fenced navigable=]'s [=navigable/active document=]. + + Add a new step after the step that defines the |new chain| variable, that reads: + + 9. If [=focus-unfenced|unfenced=] is false, |new chain| [=list/contains=] a {{Document}} + |document| whose [=node navigable=]'s [=navigable/traversable navigable=] is a + [=fenced navigable container/fenced navigable=], and old chain does not also + [=list/contain=] |document|, then return. + + Note: This is how we bail-out early just before calling the [=focus update steps=], in the case + where focus is trying to cross the fence. + + Modify the user agent sentence after the algorithm steps in [=focusing steps=] to read: + + User agents must [=immediately=] run the [=focusing steps=] for a [=focusable area=] or + [=navigable=] |candidate| with [=focus-unfenced|unfenced=] set to true whenever the user attempts to + move the focus to |candidate|. +
+ +
+ Modify the action of the [=accesskey attribute command=] algorithm to be: + + 1. Run the [=focusing steps=] for the element with [=focus-unfenced|unfenced=] set to true. + + 1. Fire a `click` event at the element. +
+ +
+ Modify the behavior when a user [=activation|activates=] a [=click focusable=] [=focusable area=] + to be: + + When a user [=activation|activates=] a [=click focusable=] [=focusable area=], the user agent must + run the [=focusing steps=] on the [=focusable area=] with focus trigger set + to "`click`" and [=focus-unfenced|unfenced=] set to true. +
+ +
+ Modify step 10 of the [=hide popover algorithm=] to read: + + 10. If |previouslyFocusedElement| is not null, then: + 1. Set element's [=previously focused element=] to null. + 2. If focusPreviousElement is true, then run the [=focusing steps=] for + |previouslyFocusedElement| with [=focus-unfenced|unfenced=] set to true; the viewport + should not be scrolled by doing this step. + + Note: Although dismissing a popover manually is a user-initiated gesture, the + [=focusing steps=] will be called with [=focus-unfenced|unfenced=] set to false regardless + of whether this was called from user gesture or via a script call. +
+ +
+ Modify the first bullet point of step 3 of the [=interactively validate the constraints=] + algorithm to read: + + * User agents may focus one of those elements in the process, by running the + [=focusing steps=] for that element, and may change the scrolling position of the + document, or perform some other action that brings the element to the user's attention. + If these steps were invoked by user gesture, [=focusing steps=] can be called with + [=focus-unfenced|unfenced=] set to true. For elements that are + [=form-associated custom elements=], user agents should use their [=face validation anchor=] + instead, for the purposes of these actions. +
+ +
+ Add a step after step 2 of the while loop in the [=has focus steps=] algorithm that reads: + + 3. If the [=focused area=] of |candidate| is a [=fenced navigable container=] with a non-null + [=fenced navigable container/fenced navigable=], then set |candidate| to the [=navigable/active + document=] of that [=fenced navigable container=]'s [=fenced navigable container/fenced + navigable=]. +
+ +
+ Modify step 3 of the while loop in the [=focus chain=] algorithm to read: + 3. If |currentObject| is a [=focusable area=], then set |currentObject| to |currentObject|'s [=DOM + anchor=]'s [=Node/node document=]. + + Otherwise, if |currentObject| is a {{Document}} whose [=node navigable=]'s [=navigable/parent=] + is non-null, then set |currentObject| to |currentObject|'s [=node navigable=]'s + [=navigable/parent=]. + + Otherwise, if |currentObject| is a {{Document}} whose [=node navigable=] is a [=traversable + navigable=] whose [=traversable navigable/unfenced parent=] is non-null, then set + |currentObject| to |currentObject|'s [=node navigable=]'s [=traversable navigable/unfenced + parent=]. + + Otherwise, [=iteration/break=]. +
+ +
+ Modify the [=get the focusable area=] algorithm. Add a new case to the switch statement: + +
+
If focus target is a [=fenced navigable container=] with a non-null [=fenced navigable container/fenced navigable=]
+ +

Return the [=fenced navigable container=]'s [=fenced navigable container/fenced + navigable=]'s [=navigable/active document=].

+
+ + Note: This algorithm can unconditionally "jump the fence" boundary because its return value always + feeds into an algorithm that _does_ carefully consider the fence boundary. +
+ +
+ Modify step 3 of the [=currently focused area of a top-level traversable=] algorithm to read: + + 3. While |candidate|'s [=focused area=] is either a [=navigable container=] with a non-null + [=navigable container/content navigable=] or a [=fenced navigable container=] with a non-null + [=fenced navigable container/fenced navigable=]: set |candidate| to the + [=navigable/active document=] of either that [=navigable container=]'s + [=navigable container/content navigable=] or that [=fenced navigable container=]'s + [=fenced navigable container/fenced navigable=], whichever is non-null. +
+ +
+ Modify step 6 of the [=sequential focus navigation=] algorithm to read: + + 6. If |candidate| is not null, then run the [=focusing steps=] for + |candidate| with [=focus-unfenced|unfenced=] set to true and return. + + Modify step 9 of the [=sequential focus navigation=] algorithm to read: + + 9. Otherwise, |starting point| is a [=focusable area=] in a [=child navigable=] or + [=fenced navigable container/fenced navigable=]. Set |starting point| to that + [=child navigable=] or [=fenced navigable container/fenced navigable=]'s + [=traversable navigable/unfenced parent=] and return to the step labeled loop. +
+ +
+ Modify step 2 of the [=sequential navigation search algorithm=] to read: + + 2. If |candidate| is a [=navigable container=] with a non-null + [=navigable container/content navigable=], then let |new candidate| be the result of running the + [=sequential navigation search algorithm=] with |candidate|'s + [=navigable container/content navigable=] as the first argument, direction as + the second, and sequential as the third. + + If |candidate| is a [=fenced navigable container=] with a non-null + [=fenced navigable container/fenced navigable=], then let |new candidate| be the result of + running the [=sequential navigation search algorithm=] with |candidate|'s + [=fenced navigable container/fenced navigable=] as the first argument, + direction as the second, and sequential as the third. + + If |new candidate| is null, then let starting point + be |candidate|, and return to the top of this algorithm. Otherwise, let + |candidate| be |new candidate|. +