Skip to content

Commit 7ece8d7

Browse files
authored
Allow traversals to be cancelable
This follows the explainer update in a430943. In the process, this closes #241 by rewriting the appropriate parts of the spec. This mostly solves #32, but we will leave that open until we add the explainer and spec for consumable sticky activation, which will complete the feature.
1 parent a430943 commit 7ece8d7

File tree

1 file changed

+92
-15
lines changed

1 file changed

+92
-15
lines changed

spec.bs

Lines changed: 92 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,13 @@ spec: html; urlPrefix: https://whatpr.org/html/6315/
5858
text: current session history step; for: traversable navigable; url: history.html#tn-current-session-history-step
5959
text: get all history steps; for: traversable navigable; url: history.html#getting-all-history-steps
6060
text: step; for: session history entry; url: history.html#she-step
61-
text: apply the history step; url: history.html#apply-the-history-step
61+
text: apply the history step; url: browsing-the-web.html#apply-the-history-step
62+
text: check if unloading is user-canceled; url: browsing-the-web.html#checking-if-unloading-is-user-canceled
63+
text: getting the target history entry; url: browsing-the-web.html#getting-the-target-history-entry
64+
text: get all navigables whose current session history entry will change or reload; url: browsing-the-web.html#get-all-navigables-whose-current-session-history-entry-will-change-or-reload
6265
text: containing navigable; for: browsing context; url: browsers.html#bc-navigable
63-
text: navigable; url: history.html#navigable
66+
text: navigable; url: document-sequences.html#navigable
67+
text: update-only; url: browsing-the-web.html#changing-nav-continuation-update-only
6468
text: URL and history update steps; url: browsers.html#url-and-history-update-steps
6569
text: shared history push/replace state steps; url: browsers.html#shared-history-push/replace-state-steps
6670
text: update document for history step application; url: history.html#update-document-for-history-step-application
@@ -84,12 +88,13 @@ spec: html; urlPrefix: https://whatpr.org/html/6315/
8488
for: Document
8589
text: indicated part; url: browsing-the-web.html#the-indicated-part-of-the-document
8690
for: navigable
87-
text: active window; url: browsers.html#nav-window
88-
text: active document; url: history.html#nav-document
89-
text: traversable navigable; url: history.html#nav-traversable
90-
text: current session history entry; url: history.html#nav-current-history-entry
91-
text: active session history entry; url: history.html#nav-active-history-entry
92-
text: get the session history entries; url: history.html#getting-session-history-entries
91+
text: active window; url: document-sequences.html#nav-window
92+
text: active document; url: document-sequences.html#nav-document
93+
text: traversable navigable; url: document-sequences.html#nav-traversable
94+
text: parent; url: document-sequences.html#nav-parent
95+
text: current session history entry; url: document-sequences.html#nav-current-history-entry
96+
text: active session history entry; url: document-sequences.html#nav-active-history-entry
97+
text: get the session history entries; url: document-sequences.html#getting-session-history-entries
9398
text: navigable; for: Window; url: browsers.html#window-navigable
9499
spec: uuid; type: dfn; urlPrefix: https://wicg.github.io/uuid/
95100
text: generate a random UUID; url: #dfn-generate-a-random-uuid
@@ -1231,6 +1236,8 @@ A {{NavigateEvent}} has a <dfn for="NavigateEvent">did process scroll behavior</
12311236

12321237
A {{NavigateEvent}} has a <dfn for="NavigateEvent">was intercepted</dfn>, a boolean, initially false.
12331238

1239+
A {{NavigateEvent}} has a <dfn for="NavigateEvent">needs continue</dfn>, a boolean, initially false.
1240+
12341241
A {{NavigateEvent}} has a <dfn for="NavigateEvent">navigation handler list</dfn>, which is a [=list=] of {{NavigationInterceptHandler}} callbacks, initially empty.
12351242

12361243
<div algorithm>
@@ -1362,8 +1369,7 @@ The <dfn attribute for="NavigationDestination">sameDocument</dfn> getter steps a
13621369
1. Set |destination|'s [=NavigationDestination/index=] to &minus;1.
13631370
1. Set |destination|'s [=NavigationDestination/state=] to null.
13641371
1. Set |destination|'s [=NavigationDestination/is same document=] to true if |destinationEntry|'s [=session history entry/document=] is equal to |navigation|'s [=relevant global object=]'s [=associated Document=]; otherwise false.
1365-
1. Let |result| be the result of performing the [=inner navigate event firing algorithm=] given |navigation|, "{{NavigationType/traverse}}", |event|, |destination|, |userInvolvement|, null, and null.
1366-
1. [=Assert=]: |result| is true (traversals are never cancelable).
1372+
1. Return the result of performing the [=inner navigate event firing algorithm=] given |navigation|, "{{NavigationType/traverse}}", |event|, |destination|, |userInvolvement|, null, and null.
13671373
</div>
13681374

13691375
<div algorithm="fire a non-traversal navigate event">
@@ -1406,9 +1412,16 @@ The <dfn attribute for="NavigationDestination">sameDocument</dfn> getter steps a
14061412

14071413
<p class="note">In this case the [=navigation API method navigation/committed promise=] and [=navigation API method navigation/finished promise=] will never fulfill, since we never create {{NavigationHistoryEntry}}s for the initial `about:blank` {{Document}} so we have nothing to [=resolve=] them with.
14081414
1. Return true.
1415+
1. Let |browsingContext| be |navigation|'s [=relevant global object=]'s [=browsing context=].
14091416
1. Let |document| be |navigation|'s [=relevant global object=]'s [=associated document=].
14101417
1. If |document| <a spec="HTML">can have its URL rewritten</a> to |destination|'s [=NavigationDestination/URL=], and either |destination|'s [=NavigationDestination/is same document=] is true or |navigationType| is not "{{NavigationType/traverse}}", then initialize |event|'s {{NavigateEvent/canIntercept}} to true. Otherwise, initialize it to false.
1411-
1. If |navigationType| is not "{{NavigationType/traverse}}", then initialize |event|'s {{Event/cancelable}} to true. Otherwise, initialize it to false.
1418+
1. Let |traverseCanBeCanceled| be true if |userInvolvement| is not "<code>[=user navigation involvement/browser UI=]</code>" or |navigation|'s [=relevant global object=] has [=transient activation=]; otherwise, false.
1419+
<p class="note">We are exploring the possibility of introducing the concept of a "consumable user activation" (i.e., a user activation that can be consumed like a transient user activation, but does not have the timeout of a transient user activation) instead of using transient activation here. This does not have a spec yet.
1420+
1. If any of the following are true:
1421+
* |navigationType| is not "{{NavigationType/traverse}}"; or
1422+
* |browsingContext| is a [=top-level browsing context=] and |traverseCanBeCanceled| is true
1423+
1424+
then initialize |event|'s {{Event/cancelable}} to true. Otherwise, initialize it to false.
14121425
1. Initialize |event|'s {{Event/type}} to "{{Navigation/navigate}}".
14131426
1. Initialize |event|'s {{NavigateEvent/navigationType}} to |navigationType|.
14141427
1. Initialize |event|'s {{NavigateEvent/destination}} to |destination|.
@@ -1432,11 +1445,23 @@ The <dfn attribute for="NavigationDestination">sameDocument</dfn> getter steps a
14321445
1. Set |navigation|'s [=Navigation/focus changed during ongoing navigation=] to false.
14331446
1. Set |navigation|'s [=Navigation/suppress normal scroll restoration during ongoing navigation=] to false.
14341447
1. Let |dispatchResult| be the result of [=dispatching=] |event| at |navigation|.
1435-
1. Set |navigation|'s [=Navigation/ongoing navigate event=] to null.
14361448
1. If |dispatchResult| is false:
14371449
1. If |navigationType| is not "{{NavigationType/traverse}}" and |event|'s {{NavigateEvent/signal}} is not [=AbortSignal/aborted=], then [=finalize with an aborted navigation error=] given |navigation| and |ongoingNavigation|.
14381450
<p class="note">If |navigationType| is "{{NavigationType/traverse}}", then we will [=finalize with an aborted navigation error=] in [=perform a navigation API traversal=].
14391451
1. Return false.
1452+
1453+
1. If |destination|'s [=NavigationDestination/is same document=] is true and |navigationType| is "{{NavigationType/traverse}}":
1454+
1. Set |event|'s [=NavigateEvent/needs continue=] to true.
1455+
1. Return true.
1456+
1. Return the result of [=reacting to navigate event result=] given |navigation| and |event|.
1457+
</div>
1458+
1459+
<div algorithm>
1460+
To <dfn>react to `navigate` event result</dfn>, given a {{Navigation}} |navigation| and a {{NavigateEvent}} |event|:
1461+
1. Let |document| be |navigation|'s [=relevant global object=]'s [=associated document=].
1462+
1. Let |destination| be |event|'s {{NavigateEvent/destination}}.
1463+
1. Let |ongoingNavigation| be |navigation|'s [=Navigation/ongoing navigation=].
1464+
1. Let |navigationType| be |event|'s {{NavigateEvent/navigationType}}.
14401465
1. Let |endResultIsSameDocument| be true if |event|'s [=NavigateEvent/was intercepted=] is true or |destination|'s [=NavigationDestination/is same document=] is true.
14411466
1. [=Prepare to run script=] given |event|'s [=relevant settings object=].
14421467
<div class="note" id="note-suppress-microtasks-during-navigation-events">
@@ -1463,6 +1488,7 @@ The <dfn attribute for="NavigationDestination">sameDocument</dfn> getter steps a
14631488
<p class="note">There is a subtle timing difference between how [=waiting for all=] schedules its success and failure steps when given zero promises versus &geq;1 promises. For most uses of [=waiting for all=], this does not matter. However, with this API, there are so many events and promise handlers which could fire around the same time that the difference is pretty easily observable: it can cause the event/promise handler sequence to vary. (Some of the events and promises involved include: {{Navigation/navigatesuccess}} / {{Navigation/navigateerror}}, {{Navigation/currententrychange}}, {{NavigationHistoryEntry/dispose}}, |ongoingNavigation|'s promises, and the {{NavigationTransition/finished|navigation.transition.finished}} promise.)
14641489
1. [=Wait for all=] of |promisesList|, with the following success steps:
14651490
1. If |event|'s {{NavigateEvent/signal}} is [=AbortSignal/aborted=], then abort these steps.
1491+
1. If |event| equals |navigation|'s [=Navigation/ongoing navigate event=], set |navigation|'s [=Navigation/ongoing navigate event=] to null.
14661492
1. [=Fire an event=] named {{Navigation/navigatesuccess}} at |navigation|.
14671493
1. If |navigation|'s [=Navigation/transition=] is not null, then [=resolve=] |navigation|'s [=Navigation/transition=]'s [=NavigationTransition/finished promise=] with undefined.
14681494
1. Set |navigation|'s [=Navigation/transition=] to null.
@@ -1480,10 +1506,16 @@ The <dfn attribute for="NavigationDestination">sameDocument</dfn> getter steps a
14801506
1. Otherwise, if |ongoingNavigation| is non-null, then [=navigation API method navigation/clean up=] |ongoingNavigation|.
14811507
1. [=Clean up after running script=].
14821508
<p class="note">Per the <a href="#note-suppress-microtasks-during-navigation-events">previous note</a>, this stops suppressing any potential promise handler microtasks, causing them to run at this point or later.</p>
1483-
1. If |event|'s [=NavigateEvent/was intercepted=] is true and |navigationType| is "{{NavigationType/push}}", "{{NavigationType/replace}}", or "{{NavigationType/reload}}", then return false.
1509+
1. If |event|'s [=NavigateEvent/was intercepted=] is true, then return false.
14841510
1. Return true.
14851511
</div>
14861512

1513+
<div algorithm>
1514+
To <dfn>maybe continue the `navigate` event</dfn>, given a {{Navigation}} |navigation|:
1515+
1. If |navigation|'s [=Navigation/ongoing navigate event=] is null, or |navigation|'s [=Navigation/ongoing navigate event=]'s [=NavigateEvent/needs continue=] is false, then return.
1516+
1. [=React to navigate event result=] given |navigation| and |navigation|'s [=Navigation/ongoing navigate event=].
1517+
</div>
1518+
14871519
<div algorithm>
14881520
To <dfn>finalize with an aborted navigation error</dfn> given a {{Navigation}} |navigation|, a [=navigation API method navigation=] or null |ongoingNavigation|, and an optional {{DOMException}} |error|:
14891521

@@ -1843,12 +1875,57 @@ Expand the section of the navigation/traversal response handling which deals wit
18431875

18441876
<h3 id="navigate-event-traversal-patches">History traversal updates</h3>
18451877

1878+
<div algorithm="check if unloading is user-canceled">
1879+
Modify the [=check if unloading is user-canceled=] algorithm as follows:
1880+
1881+
To [=check if unloading is user-canceled=] given a [=list=] of [=navigables=] |navigablesCrossingDocuments|, an optional [=navigable=] |traversable|, an optional integer |targetStep| (default null), and an optional [=user navigation involvement=] |userInvolvement| (default "<code>[=user navigation involvement/none=]</code>"):
1882+
1. Let |unloadPromptShown| be false.
1883+
1. Let |unloadPromptCanceled| be false.
1884+
1. Let |changingNavigables| be a [=list=] of [=navigables=], initially empty.
1885+
1. If |traversable| is not null and |targetStep| is not null, append the result of [=get all navigables whose current session history entry will change or reload=] given |traversable| and |targetStep| to |changingNavigables|.
1886+
1. Otherwise, append |navigablesCrossingDocuments| to |changingNavigables|.
1887+
1. Let |totalTasks| be the size of |changingNavigables|.
1888+
1. Let |completedTasks| be 0.
1889+
1. If |traversable| is not null and |changingNavigables| [=list/contains=] |traversable|:
1890+
1. Let |targetEntry| be the result of [=getting the target history entry=] given |traversable| and |targetStep|.
1891+
1. Let |isSameOrigin| be true if |targetEntry|'s [=session history entry/origin=] is the [=same origin|same=] as |traversable|'s [=navigable/current session history entry=]'s [=session history entry/origin=]; otherwise, false.
1892+
1. [=Queue a global task=] on the [=navigation and traversal task source=] given |traversable|'s [=navigable/active window=] to run the following steps:
1893+
1. If |isSameOrigin| is true:
1894+
1. Let |navigateEventResult| be the result of [=firing a traversal navigate event=] at |traversable|'s [=navigable/active window=]'s [=Window/navigation API=] with <i>[=fire a traversal navigate event/destinationEntry=]</i> set to |targetEntry| and <i>[=fire a traversal navigate event/userInvolvement=]</i> set to |userInvolvement|.
1895+
1. If |navigateEventResult| is false, then set |unloadPromptCanceled| to true.
1896+
1. If |unloadPromptCanceled| is false and |navigablesCrossingDocuments| [=list/contains=] |traversable|:
1897+
1. Run the steps to fire a beforeunload event (currently steps 6.1 thru 6.8 in the [=check if unloading is user-canceled=] steps).
1898+
1. Increment |completedTasks|.
1899+
1. Wait for |completedTasks| to be 1.
1900+
1. If |unloadPromptCanceled| is true, then return "<code>refuse</code>";
1901+
1. For each |navigable| of |changingNavigables|:
1902+
1. If |navigable| equals |traversable|, then [=iteration/continue=].
1903+
1. Let |targetEntry| be the result of [=getting the target history entry=] given |traversable| and |targetStep|.
1904+
1. Let |isSameOrigin| be true if |targetEntry|'s [=session history entry/origin=] is the [=same origin|same=] as |navigable|'s [=navigable/current session history entry=]'s [=session history entry/origin=]; otherwise, false.
1905+
1. [=Queue a global task=] on the [=navigation and traversal task source=] given |navigable|'s [=navigable/active window=] to run the following steps:
1906+
1. If |isSameOrigin| is true:
1907+
1. Let |navigateEventResult| be the result of [=firing a traversal navigate event=] at |navigable|'s [=navigable/active window=]'s [=Window/navigation API=] with <i>[=fire a traversal navigate event/destinationEntry=]</i> set to |targetEntry| and <i>[=fire a traversal navigate event/userInvolvement=]</i> set to |userInvolvement|.
1908+
1. [=Assert=]: |navigateEventResult| is true.
1909+
1. If |navigablesCrossingDocuments| [=list/contains=] |navigable|:
1910+
1. Run the steps to fire a beforeunload event (currently steps 6.1 thru 6.8 in the [=check if unloading is user-canceled=] steps).
1911+
1. Increment |completedTasks|.
1912+
1. Wait for |completedTasks| to be |totalTasks|.
1913+
1. If |unloadPromptCanceled| is true, then return "<code>refuse</code>".
1914+
1. If |unloadPromptShown| is true, then return "<code>confirm</code>".
1915+
1. Return "<code>no-prompt</code>".
1916+
</div>
1917+
18461918
<div algorithm="apply the history step">
1847-
Modify the [=apply the history step=] algorithm as follows. Near the end of the algorithm, inside the queued task over each global (currently step 14.10), before potentially unloading or activating the history entry, add the following step:
1919+
Modify the [=apply the history step=] algorithm as follows:
18481920

1849-
1. [=Fire a traversal navigate event=] at <var ignore>displayedDocument</var>'s [=relevant global object=]'s [=Window/navigation API=] with <i>[=fire a traversal navigate event/destinationEntry=]</i> set to <var ignore>targetEntry</var> and <i>[=fire a traversal navigate event/userInvolvement=]</i> set to <var ignore>userInvolvement</var>.
1921+
Change step 5 (which runs the [=check if unloading is user-canceled=] steps) to:
1922+
1. If <var ignore>checkForUserCancelation</var> is true, and the result of [=checking if unloading is user-canceled=] given <var ignore>navigablesCrossingDocuments</var>, <var ignore>traversable</var>, <var ignore>targetStep</var> and <var ignore>userInvolvement</var> is "<code>refuse</code>", then return.
18501923

18511924
(Recall that we introduced the <var ignore>userInvolvement</var> parameter as part of [[#user-initiated-patches]].)
1925+
1926+
Before the update document for history step application (currently step 14.10.3), add the following step:
1927+
1. If <var ignore>changingNavigableContinuation</var>'s [=update-only=] is true, [=maybe continue the navigate event=] given <var ignore>displayedDocument</var>'s [=relevant global object=]'s [=Window/navigation API=].
1928+
18521929
</div>
18531930

18541931
<h3 id="navigate-event-download-patches">Download a hyperlink updates</h3>

0 commit comments

Comments
 (0)