Skip to content

Commit

Permalink
Add focusing changes + patches (#68)
Browse files Browse the repository at this point in the history
  • Loading branch information
blu25 authored Apr 17, 2023
1 parent 123ccb0 commit fa7833d
Showing 1 changed file with 209 additions and 3 deletions.
212 changes: 209 additions & 3 deletions spec.bs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -759,6 +786,185 @@ in the [[#nested-traversables-intro]].
1. Return |navigables|.
</div>

<h3 id=focusing-changes>Modifications to the focusing algorithms</h3>

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.

<p class=example id=user-initiated-focus>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=].</p>

<p class=example id=script-initiated-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.</p>

<div algorithm=focusing-steps-patch>
Modify the [=focusing steps=] to take a new optional [=boolean=] argument <dfn
lt="focus-unfenced">unfenced</dfn> 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 <var ignore>old chain</var> 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|.
</div>

<div algorithm=access-key-patch>
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. <a>Fire a `click` event</a> at the element.
</div>

<div algorithm=activation-patch>
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 <var ignore>focus trigger</var> set
to "`click`" and [=focus-unfenced|unfenced=] set to true.
</div>

<div algorithm=hide-popover-patch>
Modify step 10 of the [=hide popover algorithm=] to read:

10. If |previouslyFocusedElement| is not null, then:
1. Set <var ignore>element</var>'s [=previously focused element=] to null.
2. If <var ignore>focusPreviousElement</var> 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.
</div>

<div algorithm=interactively-validate-patch>
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.
</div>

<div algorithm=has-focus-steps>
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=].
</div>

<div algorithm=focus-chain>
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=].
</div>

<div algorithm=get-the-focusable-area>
Modify the [=get the focusable area=] algorithm. Add a new case to the switch statement:

<dl class="switch">
<dt>If <var ignore>focus target</var> is a [=fenced navigable container=] with a non-null [=fenced navigable container/fenced navigable=]</dt>

<dd><p>Return the [=fenced navigable container=]'s [=fenced navigable container/fenced
navigable=]'s [=navigable/active document=].</p></dd>
</dl>

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.
</div>

<div algorithm=currently-focused-area-of-a-top-level-traversable>
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.
</div>

<div algorithm=sequential-focus-navigation-patch>
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 <i>loop</i>.
</div>

<div algorithm=sequential-navigation-search-patch>
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, <var ignore>direction</var> as
the second, and <i>sequential</i> 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,
<var ignore>direction</var> as the second, and <i>sequential</i> as the third.

If |new candidate| is null, then let <var ignore>starting point</var>
be |candidate|, and return to the top of this algorithm. Otherwise, let
|candidate| be |new candidate|.
</div>

<h3 id=navigation-patch>Navigation</h3>

Expand Down

0 comments on commit fa7833d

Please sign in to comment.