Skip to content

Commit 84bbafc

Browse files
committed
Focus spec
1 parent 1d3923d commit 84bbafc

File tree

2 files changed

+41
-7
lines changed

2 files changed

+41
-7
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,7 @@ Like [accessibility technology announcements](#accessibility-technology-announce
567567
568568
App history navigation interception again gives us the tool to fix this.
569569
570-
By default, any navigation that is intercepted and converted into a single-page navigation using `navigateEvent.transitionWhile()` will cause focus to reset to the `<body>` element, or to the first element with the `autofocus=""` attribute set (if there is one). This focus reset will happen in the first animation frame after the promise passed to `transitionWhile()` settles. However, this focus reset will not take place if the user has manually changed focus while the promise was settling, and the element the user changed focus to is still visible and focusable.
570+
By default, any navigation that is intercepted and converted into a single-page navigation using `navigateEvent.transitionWhile()` will cause focus to reset to the `<body>` element, or to the first element with the `autofocus=""` attribute set (if there is one). This focus reset will happen after the promise passed to `transitionWhile()` settles. However, this focus reset will not take place if the user or developer has manually changed focus while the promise was settling, and that element is still visible and focusable.
571571
572572
This behavior can be customized using the second options argument to `transitionWhile()`:
573573

spec.bs

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,10 @@ During any given navigation, the {{AppHistory}} object needs to keep track of th
533533
<td>The event's {{AppHistoryNavigateEvent/signal}}
534534
<td>Until all promises passed to {{AppHistoryNavigateEvent/transitionWhile()}} have settled
535535
<td>So that if the navigation is canceled, we can [=AbortSignal/signal abort=].
536+
<tr>
537+
<td>Whether a new element was <a spec="HTML" lt="focusing steps">focused</a>
538+
<td>Until all promises passed to {{AppHistoryNavigateEvent/transitionWhile()}} have settled
539+
<td>So that if one was, focus is not reset TODO link
536540
<tr>
537541
<td>The {{AppHistoryEntry}} being navigated to
538542
<td>From when it is determined, until all promises passed to {{AppHistoryNavigateEvent/transitionWhile()}} have settled
@@ -602,6 +606,8 @@ Each {{AppHistory}} object has an associated <dfn for="AppHistory">ongoing navig
602606

603607
Each {{AppHistory}} object has an associated <dfn for="AppHistory">ongoing navigation signal</dfn>, which is an {{AbortSignal}} or null, initially null.
604608

609+
Each {{AppHistory}} object has an associated <dfn for="AppHistory">focus changed during ongoing navigation</dfn>, which is a boolean, initially false.
610+
605611
Each {{AppHistory}} object has an associated <dfn for="AppHistory">ongoing navigation</dfn>, which is an [=app history API navigation=] or null, initially null.
606612

607613
Each {{AppHistory}} object has an associated <dfn for="AppHistory">upcoming non-traverse navigation</dfn>, which is an [=app history API navigation=] or null, initially null.
@@ -619,7 +625,7 @@ An <dfn>app history API navigation</dfn> is a [=struct=] with the following [=st
619625
* A <dfn for="app history API navigation">finished promise</dfn>, a {{Promise}}
620626
* A <dfn for="app history API navigation">did finish before commit</dfn>, a boolean
621627

622-
<p class="note">We need to store the [=AppHistory/ongoing navigation signal=] separately from the [=app history API navigation=] struct, since it needs to be tracked even for navigations that are not via the app history APIs.
628+
<p class="note">We need to store the [=AppHistory/ongoing navigation signal=] and [=AppHistory/focus changed during ongoing navigation=] separately from the [=app history API navigation=] struct, since they need to be tracked even for navigations that are not via the app history APIs.
623629

624630
<div algorithm>
625631
To <dfn for="AppHistory">set the upcoming non-traverse navigation</dfn> given an {{AppHistory}} |appHistory|, a JavaScript value |info|, and a [=serialized state=]-or-null |serializedState|:
@@ -1114,13 +1120,11 @@ enum AppHistoryNavigationType {
11141120

11151121
The <dfn attribute for="AppHistoryNavigateEvent">navigationType</dfn>, <dfn attribute for="AppHistoryNavigateEvent">destination</dfn>, <dfn attribute for="AppHistoryNavigateEvent">canTransition</dfn>, <dfn attribute for="AppHistoryNavigateEvent">userInitiated</dfn>, <dfn attribute for="AppHistoryNavigateEvent">hashChange</dfn>, <dfn attribute for="AppHistoryNavigateEvent">signal</dfn>, <dfn attribute for="AppHistoryNavigateEvent">formData</dfn>, and <dfn attribute for="AppHistoryNavigateEvent">info</dfn> getter steps are to return the value that the corresponding attribute was initialized to.
11161122

1117-
An {{AppHistoryNavigateEvent}} has the following associated values which are only conditionally used:
1123+
An {{AppHistoryNavigateEvent}} has a <dfn for="AppHistoryNavigateEvent">classic history API serialized data</dfn>, a [=serialized state=]-or-null. It is only used in some cases where the event's {{AppHistoryNavigateEvent/navigationType}} is "{{AppHistoryNavigationType/reload}}", "{{AppHistoryNavigationType/push}}" or "{{AppHistoryNavigationType/replace}}", and is set appropriately when the event is [[#navigate-event-firing|fired]].
11181124

1119-
* <dfn for="AppHistoryNavigateEvent">classic history API serialized data</dfn>, a [=serialized state=]-or-null, used when its {{AppHistoryNavigateEvent/navigationType}} is "{{AppHistoryNavigationType/reload}}", "{{AppHistoryNavigationType/push}}" or "{{AppHistoryNavigationType/replace}}"
1125+
An {{AppHistoryNavigateEvent}} has a <dfn for="AppHistoryNavigateEvent">focus reset behavior</dfn>, an {{AppHistoryFocusReset}}-or-null, initially null.
11201126

1121-
This is set appropriately when the event is [[#navigate-event-firing|fired]].
1122-
1123-
An {{AppHistoryNavigateEvent}} also has an associated <dfn for="AppHistoryNavigateEvent">navigation action promises list</dfn>, which is a [=list=] of {{Promise}} objects, initially empty.
1127+
An {{AppHistoryNavigateEvent}} has an associated <dfn for="AppHistoryNavigateEvent">navigation action promises list</dfn>, which is a [=list=] of {{Promise}} objects, initially empty.
11241128

11251129
<div algorithm>
11261130
The <dfn method for="AppHistoryNavigateEvent">transitionWhile(|newNavigationAction|, |options|)</dfn> method steps are:
@@ -1131,6 +1135,8 @@ An {{AppHistoryNavigateEvent}} also has an associated <dfn for="AppHistoryNaviga
11311135
1. If [=this=]'s [=Event/dispatch flag=] is unset, then throw an "{{InvalidStateError}}" {{DOMException}}.
11321136
1. If [=this=]'s [=Event/canceled flag=] is set, then throw an "{{InvalidStateError}}" {{DOMException}}.
11331137
1. [=list/Append=] |newNavigationAction| to [=this=]'s [=AppHistoryNavigateEvent/navigation action promises list=].
1138+
1. If [=this=]'s [=AppHistoryNavigateEvent/focus reset behavior=] is not null, and it is not equal to |options|["{{AppHistoryTransitionWhileOptions/focusReset}}"], then the user agent may [=report a warning to the console=] indicating that the {{AppHistoryTransitionWhileOptions/focusReset}} option for a previous call to {{AppHistoryNavigateEvent/transitionWhile()}} was overridden by this new value, and the previous value will be ignored.
1139+
1. Set [=this=]'s [=AppHistoryNavigateEvent/focus reset behavior=] to |options|["{{AppHistoryTransitionWhileOptions/focusReset}}"].
11341140
</div>
11351141

11361142
<h3 id="navigate-event-destination">The {{AppHistoryDestination}} class</h3>
@@ -1284,6 +1290,7 @@ The <dfn attribute for="AppHistoryDestination">sameDocument</dfn> getter steps a
12841290
1. Set |appHistory|'s [=AppHistory/ongoing navigate event=] to |event|.
12851291
1. [=Assert=]: |appHistory|'s [=AppHistory/ongoing navigation signal=] is null.
12861292
1. Set |appHistory|'s [=AppHistory/ongoing navigation signal=] to |event|'s {{AppHistoryNavigateEvent/signal}}.
1293+
1. Set |appHistory|'s [=AppHistory/focus changed during ongoing navigation=] to false.
12871294
1. Let |dispatchResult| be the result of [=dispatching=] |event| at |appHistory|.
12881295
1. Set |appHistory|'s [=AppHistory/ongoing navigate event=] to null.
12891296
1. If |dispatchResult| is false:
@@ -1305,12 +1312,14 @@ The <dfn attribute for="AppHistoryDestination">sameDocument</dfn> getter steps a
13051312
1. [=Resolve=] |transition|'s [=AppHistoryTransition/finished promise=] with undefined.
13061313
1. If |appHistory|'s [=AppHistory/transition=] is |transition|, then set |appHistory|'s [=AppHistory/transition=] to null.
13071314
1. If |ongoingNavigation| is non-null, then [=app history API navigation/resolve the finished promise=] for |ongoingNavigation|.
1315+
1. [=Potentially reset the focus=] given |appHistory| and |event|.
13081316
and the following failure steps given reason |rejectionReason|:
13091317
1. If |event|'s {{AppHistoryNavigateEvent/signal}} is [=AbortSignal/aborted=], then abort these steps.
13101318
1. [=Fire an event=] named {{AppHistory/navigateerror}} at |appHistory| using {{ErrorEvent}}, with {{ErrorEvent/error}} initialized to |rejectionReason|, and {{ErrorEvent/message}}, {{ErrorEvent/filename}}, {{ErrorEvent/lineno}}, and {{ErrorEvent/colno}} initialized to appropriate values that can be extracted from |rejectionReason| in the same underspecified way the user agent typically does for the <a spec="HTML">report an exception</a> algorithm.
13111319
1. [=Reject=] |transition|'s [=AppHistoryTransition/finished promise=] with |rejectionReason|.
13121320
1. If |appHistory|'s [=AppHistory/transition=] is |transition|, then set |appHistory|'s [=AppHistory/transition=] to null.
13131321
1. If |ongoingNavigation| is non-null, then [=app history API navigation/reject the finished promise=] for |ongoingNavigation| with |rejectionReason|.
1322+
1. [=Potentially reset the focus=] given |appHistory| and |event|.
13141323
1. Otherwise, if |ongoingNavigation| is non-null, then:
13151324
1. Set |ongoingNavigation|'s [=app history API navigation/serialized state=] to null.
13161325
<p class="note">This ensures that any call to {{AppHistory/navigate()|appHistory.navigate()}} which triggered this algorithm does not overwrite the [=session history entry/app history state=] of the [=session history/current entry=] for cross-document navigations.
@@ -1325,6 +1334,7 @@ The <dfn attribute for="AppHistoryDestination">sameDocument</dfn> getter steps a
13251334
<div algorithm>
13261335
To <dfn>finalize with an aborted navigation error</dfn> given an {{AppHistory}} |appHistory|, an [=app history API navigation=] or null |ongoingNavigation|, and an optional {{DOMException}} |error|:
13271336

1337+
1. Set |appHistory|'s [=AppHistory/focus changed during ongoing navigation=] to false.
13281338
1. If |error| was not given, then set |error| to a [=new=] "{{AbortError}}" {{DOMException}}, created in |appHistory|'s [=relevant Realm=].
13291339
1. If |appHistory|'s [=AppHistory/ongoing navigate event=] is non-null, then:
13301340
1. Set |appHistory|'s [=AppHistory/ongoing navigate event=]'s [=Event/canceled flag=] to true.
@@ -1360,6 +1370,20 @@ The <dfn attribute for="AppHistoryDestination">sameDocument</dfn> getter steps a
13601370
1. For each |traversal| of |traversals|: [=finalize with an aborted navigation error=] given |appHistory| and |traversal|.
13611371
</div>
13621372

1373+
<div algorithm>
1374+
To <dfn>potentially reset the focus</dfn> given an {{AppHistory}} object |appHistory| and an {{AppHistoryNavigateEvent}} |event|:
1375+
1376+
1. Let |focusChanged| be |appHistory|'s [=AppHistory/focus changed during ongoing navigation=].
1377+
1. Set |appHistory|'s [=AppHistory/focus changed during ongoing navigation=] to false.
1378+
1. If |event|'s [=AppHistoryNavigateEvent/focus reset behavior=] is "{{AppHistoryFocusReset/manual}}", then return.
1379+
1. If |focusChanged| is true, then return.
1380+
1. Let |document| be |appHistory|'s [=relevant global object=]'s [=associated Document=].
1381+
1. Let |focusTarget| be the <a spec="HTML">autofocus delegate</a> for |document|.
1382+
1. If |focusTarget| is null, then set |focusTarget| to |document|'s <a spec="HTML" lt="the body element">body element</a>.
1383+
1. If |focusTarget| is null, then set |focusTarget| to |document|'s [=document element=].
1384+
1. Run the <a spec="HTML">focusing steps</a> for |focusTarget|, with |document|'s <a spec="CSS2">viewport</a> as the fallback target.
1385+
</div>
1386+
13631387
<h2 id="apphistoryentry-class">App history entries</h2>
13641388

13651389
<xmp class="idl">
@@ -1735,6 +1759,16 @@ So, update the HTML Standard to always store the policy container on the [=sessi
17351759

17361760
<h2 id="other-patches">Other patches</h2>
17371761

1762+
<h3 id="focus-patches">Focus tracking</h3>
1763+
1764+
To support the {{AppHistoryTransitionWhileOptions/focusReset}} option, the following patches need to be made:
1765+
1766+
Update the <a spec="HTML">focusing steps</a> to, right before they call the <a spec="HTML">focus update steps</a>, set the {{Document}}'s [=relevant global object=]'s [=Window/app history=]'s [=AppHistory/focus changed during ongoing navigation=] to true.
1767+
1768+
Update the <a spec="HTML">focus fixup rule</a> to additionally set the {{Document}}'s [=relevant global object=]'s [=Window/app history=]'s [=AppHistory/focus changed during ongoing navigation=] to false.
1769+
1770+
<p class="note">In combination, these ensure that the [=AppHistory/focus changed during ongoing navigation=] reflects any developer- or user-initiated focus changes, unless they were undone by the focus fixup rule. For example, if the user moved focus to an element which was removed from the DOM while the promise passed to {{AppHistoryNavigateEvent/transitionWhile()}} was settling, then that would not count as a focus change.
1771+
17381772
<h3 id="cancel-navigation">Canceling navigation and traversals</h3>
17391773

17401774
The existing HTML specification discusses canceling a navigation and traverals in a few places. However, the process is not very well-defined, and per <a href="https://github.com/whatwg/html/issues/6927">whatwg/html#6927</a>, is not very interoperable. We plan to make it more rigorous, after the <a href="https://github.com/whatwg/html/issues/5767">session history rewrite</a> lands.

0 commit comments

Comments
 (0)