diff --git a/README.md b/README.md index ec68e09..a3480e9 100644 --- a/README.md +++ b/README.md @@ -1337,9 +1337,9 @@ interface AppHistory : EventTarget { Promise navigate(USVString url, optional AppHistoryNavigateOptions options = {}); Promise reload(optional AppHistoryReloadOptions options = {}); - Promise goTo(DOMString key, optional AppHistoryNavigationOptions = {}); - Promise back(optional AppHistoryNavigationOptions = {}); - Promise forward(optional AppHistoryNavigationOptions = {}); + Promise goTo(DOMString key, optional AppHistoryNavigationOptions options = {}); + Promise back(optional AppHistoryNavigationOptions options = {}); + Promise forward(optional AppHistoryNavigationOptions options = {}); attribute EventHandler onnavigate; attribute EventHandler onnavigatesuccess; diff --git a/spec.bs b/spec.bs index eeecf1b..059b38b 100644 --- a/spec.bs +++ b/spec.bs @@ -58,10 +58,7 @@ spec: html; urlPrefix: https://whatpr.org/html/6315/ text: get all history steps; for: traversable navigable; url: history.html#getting-all-history-steps text: step; for: session history entry; url: history.html#she-step text: apply the history step; url: history.html#apply-the-history-step - text: checkForUserCancelation; for: apply the history step; url: history.html#apply-the-history-step - text: initiatorToCheck; for: apply the history step; url: history.html#apply-the-history-step text: containing navigable; for: browsing context; url: browsers.html#bc-navigable - text: session; for: browsing context group; url: browsers.html#bcg-session text: active document; for: navigable; url: history.html#nav-document text: navigable; url: history.html#navigable spec: uuid; type: dfn; urlPrefix: https://wicg.github.io/uuid/ @@ -157,6 +154,10 @@ interface AppHistory : EventTarget { Promise navigate(USVString url, optional AppHistoryNavigateOptions options = {}); Promise reload(optional AppHistoryReloadOptions options = {}); + Promise goTo(DOMString key, optional AppHistoryNavigationOptions options = {}); + Promise back(optional AppHistoryNavigationOptions options = {}); + Promise forward(optional AppHistoryNavigationOptions options = {}); + attribute EventHandler onnavigate; attribute EventHandler onnavigatesuccess; attribute EventHandler onnavigateerror; @@ -436,6 +437,139 @@ Each {{AppHistory}} object has an associated navigate meth

Unlike {{Location/assign()|location.assign()}} and friends, which are exposed across [=same origin-domain|origin-domain=] boundaries, {{AppHistory/navigate()|appHistory.navigate()}} and {{AppHistory/reload()|appHistory.reload()}} can only be accessed by code with direct synchronous access to the {{Window/appHistory}} property. Thus, we avoid the complications around tracking source browsing contexts, and we don't need to deal with the allowed to navigate check and its accompanying [=navigate/exceptionsEnabled=] flag. We just treat all navigations as being initiated by the {{AppHistory}} object itself. +

Traversing

+ +
+
await {{Window/appHistory}} . {{AppHistory/goTo(key)|goTo}}(key) +
await {{Window/appHistory}} . {{AppHistory/goTo(key, options)|goTo}}(key, { {{AppHistoryNavigationOptions/navigateInfo}} }) +
+

Traverses the joint session history to the closest joint session history entry that matches the {{AppHistoryEntry}} with the given key. {{AppHistoryNavigationOptions/navigateInfo}} can be set to any value; it will populate the {{AppHistoryNavigateEvent/info}} property of the corresponding {{AppHistory/navigate}} event. + +

The returned promise will behave as follows: + + * If there is no {{AppHistoryEntry}} in {{AppHistory/entries|appHistory.entries}} with the given key, it will reject with an "{{InvalidStateError}}" {{DOMException}}. + * For same-document traversals intercepted by the {{AppHistory/navigate}} event's {{AppHistoryNavigateEvent/respondWith()}} method, it will fulfill or reject according to the promise passed to {{AppHistoryNavigateEvent/respondWith()}}. + * For non-intercepted same-document traversals, it will fulfill after the traversal completes. + * For cross-document traversals, it will never settle. +

+ +
await {{Window/appHistory}} . {{AppHistory/back()|back}}() +
await {{Window/appHistory}} . {{AppHistory/back(options)|back}}({ {{AppHistoryNavigationOptions/navigateInfo}} }) +
+

Traverse the joint session history to the closest previous joint session history entry which results in this frame navigating, i.e. results in {{AppHistory/current|appHistory.current}} updating. {{AppHistoryNavigationOptions/navigateInfo}} can be set to any value; it will populate the {{AppHistoryNavigateEvent/info}} property of the corresponding {{AppHistory/navigate}} event. + +

The returned promise will behave as follows: + + * If {{AppHistory/canGoBack|appHistory.canGoBack}} is false, it will reject with an "{{InvalidStateError}}" {{DOMException}}. + * For same-document traversals intercepted by the {{AppHistory/navigate}} event's {{AppHistoryNavigateEvent/respondWith()}} method, it will fulfill or reject according to the promise passed to {{AppHistoryNavigateEvent/respondWith()}}. + * For non-intercepted same-document traversals, it will fulfill after the traversal completes. + * For cross-document traversals, it will never settle. +

+ +
await {{Window/appHistory}} . {{AppHistory/forward()|forward}}() +
await {{Window/appHistory}} . {{AppHistory/forward(options)|forward}}({ {{AppHistoryNavigationOptions/navigateInfo}} }) +
+

Traverse the joint session history to the closest forward joint session history entry which results in this frame navigating, i.e. results in {{AppHistory/current|appHistory.current}} updating. {{AppHistoryNavigationOptions/navigateInfo}} can be set to any value; it will populate the {{AppHistoryNavigateEvent/info}} property of the corresponding {{AppHistory/navigate}} event. + +

The returned promise will behave as follows: + + * If {{AppHistory/canGoBack|appHistory.canGoForward}} is false, it will reject with an "{{InvalidStateError}}" {{DOMException}}. + * For same-document traversals intercepted by the {{AppHistory/navigate}} event's {{AppHistoryNavigateEvent/respondWith()}} method, it will fulfill or reject according to the promise passed to {{AppHistoryNavigateEvent/respondWith()}}. + * For non-intercepted same-document traversals, it will fulfill after the traversal completes. + * For cross-document traversals, it will never settle. +

+
+ +
+ The goTo(|key|, |options|) method steps are: + + 1. If [=this=]'s [=AppHistory/current index=] is −1, then return [=a promise rejected with=] an "{{InvalidStateError}}" {{DOMException}}. + + 1. If [=this=]'s [=AppHistory/entry list=] does not contain any {{AppHistoryEntry}} whose [=AppHistoryEntry/session history entry=]'s [=session history entry/app history key=] equals |key|, then return [=a promise rejected with=] an "{{InvalidStateError}}" {{DOMException}}. + + 1. Return the result of [=performing an app history traversal=] given [=this=], |key|, and |options|. +
+ +
+ The back(|options|) method steps are: + + 1. If [=this=]'s [=AppHistory/current index=] is −1 or 0, then return [=a promise rejected with=] an "{{InvalidStateError}}" {{DOMException}}. + + 1. Let |key| be [=this=]'s [=AppHistory/entry list=][[=this=]'s [=AppHistory/current index=] − 1]'s [=AppHistoryEntry/session history entry=]'s [=session history entry/app history key=]. + + 1. Return the result of [=performing an app history traversal=] given [=this=], |key|, and |options|. +
+ +
+ The forward(|options|) method steps are: + + 1. If [=this=]'s [=AppHistory/current index=] is −1 or is equal to [=this=]'s [=AppHistory/entry list=]'s [=list/size=] − 1, then return [=a promise rejected with=] an "{{InvalidStateError}}" {{DOMException}}. + + 1. Let |key| be [=this=]'s [=AppHistory/entry list=][[=this=]'s [=AppHistory/current index=] + 1]'s [=AppHistoryEntry/session history entry=]'s [=session history entry/app history key=]. + + 1. Return the result of [=performing an app history traversal=] given [=this=], |key|, and |options|. +
+ +
+

The following algorithm is specified in terms of the session history rewrite pull request against the HTML Standard, because the existing session history traversal infrastructure is broken enough that it's hard to build on. It is expected to track that work as it continues.

+ + To perform an app history traversal given an {{AppHistory}} object |appHistory|, a string |key|, and an {{AppHistoryNavigationOptions}} |options|: + + 1. If |appHistory|'s [=relevant global object=]'s [=associated Document=] is not [=Document/fully active=], then return [=a promise rejected with=] an "{{InvalidStateError}}" {{DOMException}}. + + 1. If |appHistory|'s [=relevant global object=]'s [=associated Document=]'s unload counter is greater than 0, then return [=a promise rejected with=] an "{{InvalidStateError}}" {{DOMException}}. + + 1. If |appHistory|'s [=AppHistory/entry list=][|appHistory|'s [=AppHistory/current index=]]'s [=AppHistoryEntry/session history entry=]'s [=session history entry/app history key=] equals |key|, then return [=a promise resolved with=] undefined. + + 1. Let |navigateInfo| be |options|["{{AppHistoryNavigationOptions/navigateInfo}}"] if it [=map/exists=], or undefined otherwise. + + 1. Let |navigable| be |appHistory|'s [=relevant global object=]'s [=Window/browsing context=]'s [=browsing context/containing navigable=]. + + 1. Let |traversable| be |navigable|'s [=navigable/traversable navigable=]. + + 1. Let |initiatorBC| be |appHistory|'s [=relevant global object=]'s [=Window/browsing context=]. + + 1. Let |promise| be [=a new promise=] created in |appHistory|'s [=relevant Realm=]. + + 1. [=parallel queue/Enqueue the following steps=] on |traversable|'s [=traversable navigable/session history traversal queue=]: + + 1. Let |navigableEntries| be the result of [=navigable/getting the session history entries=] given |navigable|. + + 1. Let |targetEntry| be the [=session history entry=] in |navigableEntries| whose [=session history entry/app history key=] equals |key|. If no such entry exists, then: + + 1. [=Reject=] |promise| with an "{{InvalidStateError}}" {{DOMException}}. + + 1. Abort these steps. + +

This can occur if the |appHistory| object's view of session history is outdated, which can happen for brief periods while all the relevant threads and processes are being synchronized in reaction to a history change (such as the user clearing their history). + + 1. Assert: |targetEntry| is not |navigable|'s [=navigable/active session history entry=]. + + 1. Let |targetStep| be null. + + 1. If |targetEntry|'s [=session history entry/step=] is greater than |traversable|'s [=traversable navigable/current session history step=], then set |targetStep| to |targetEntry|'s [=session history entry/step=]. + + 1. Otherwise: + + 1. Let |afterTarget| be the [=session history entry=] after |targetEntry| in |navigableEntries|. + + 1. Let |allSteps| be the result of [=traversable navigable/getting all history steps=] that are part of the target session TODO. + + 1. Set |targetStep| to the greatest number in |allSteps| that is less than |afterTarget|'s [=session history entry/step=]. + + 1. [=Apply the history step=] |targetStep| to |traversable|, with true, |initiatorBC|, "[=user navigation involvement/none=]", |navigateInfo|. + + - If this aborts due to user-canceled unloading or the navigate event being canceled, then [=reject=] |promise| with an "{{AbortError}}" {{DOMException}}. + + - If this aborts due to the initiator allowed-to-navigate check, then [=reject=] |promise| with a "{{SecurityError}}" {{DOMException}}. + + - TODO: deal with another traversal aborting this one. (The HTML spec PR doesn't support this yet.) Should result in "{{AbortError}}" {{DOMException}}. + +

Eventually [=apply the history step=] will have well-specified hooks for communicating these conditions back to its caller.

+
+ +TODO need to stash info and then use it appropriately. +

Event handlers

The following are the [=event handlers=] (and their corresponding [=event handler event types=]) that must be supported, as [=event handler IDL attributes=], by objects implementing the {{AppHistory}} interface: @@ -626,7 +760,7 @@ The sameDocument getter steps a
- To fire a traversal `navigate` event at an {{AppHistory}} |appHistory| given a [=session history entry=] |destinationEntry|, an optional [=user navigation involvement=] |userInvolvement| (default "[=user navigation involvement/none=]"), and an optional JavaScript value |info| (default undefined): + To fire a traversal `navigate` event at an {{AppHistory}} |appHistory| given a [=session history entry=] |destinationEntry|, an optional [=user navigation involvement=] |userInvolvement| (default "[=user navigation involvement/none=]"), and an optional JavaScript value |info| (default undefined): 1. Let |destinationURL| be |destinationEntry|'s [=session history entry/URL=]. 1. Let |destinationState| be |destinationEntry|'s [=session history entry/app history state=]. @@ -760,8 +894,7 @@ interface AppHistoryEntry : EventTarget {

A [=user agent=]-generated random UUID string representing this app history entry's place in the app history list. This value will be reused by other {{AppHistoryEntry}} instances that replace this one due to replace-style navigations. This value will survive session restores. - -

This is useful for navigating back to this location in the app history entry list, using `appHistory.goTo(key)`. +

This is useful for navigating back to this location in the app history entry list, using {{AppHistory/goTo(key)|appHistory.goTo(key)}}.

entry . {{AppHistoryEntry/id}}