You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This replaces our previous broken idea of using currentchange for duration tracking. Closes#59. Closes#33. Closes#14 by removing currentchange entirely for now (we can always add it back later if we see a use case).
The new `currentchange` event fires whenever the current history entry changes, and includes the time it took for a single-page application nav to settle:
51
+
A new extension to the [performance timeline API](https://developer.mozilla.org/en-US/docs/Web/API/Performance_Timeline) allows you to calculate the duration of single-page navigations, including any asynchronous work performed by the `navigate` handler:
@@ -225,15 +224,6 @@ The current entry can be replaced with a new entry, with a new `AppHistoryEntry`
225
224
226
225
- When using the `navigate` event to [convert a cross-document replace navigation into a same-document navigation](#navigation-monitoring-and-interception).
227
226
228
-
In both cases, for same-document navigations a `currentchange` event will fire on `appHistory`:
// appHistory.current has changed: either to a completely new entry (with new key),
233
-
// or it has been replaced (keeping the same key).
234
-
});
235
-
```
236
-
237
227
### Inspection of the app history list
238
228
239
229
In addition to the current entry, the entire list of app history entries can be inspected, using `appHistory.entries()`, which returns an array of `AppHistoryEntry` instances. (Recall that all app history entries are same-origin contiguous entries for the current frame, so this is not a security issue.)
@@ -500,7 +490,7 @@ This does not yet solve all accessibility problems with single-page navigations.
500
490
501
491
Continuing with the theme of `respondWith()` giving ecosystem benefits beyond just web developer convenience, telling the browser about the start time, duration, end time, and success/failure if a single-page app navigation has benefits for metrics gathering.
502
492
503
-
In particular, analytics frameworks would be able to consume this information from the browser in a way that works across all applications using the app history API. See the example in the [Current entry change monitoring](#current-entry-change-monitoring) section for one way this could look; other possibilities include integrating into the existing [performance APIs](https://w3c.github.io/performance-timeline/).
493
+
In particular, analytics frameworks would be able to consume this information from the browser in a way that works across all applications using the app history API. See the discussion on [performance timeline API integration](#performance-timeline-api-integration) for what we are proposing there.
504
494
505
495
This standardized notion of single-page navigations also gives a hook for other useful metrics to build off of. For example, you could imagine variants of the `"first-paint"` and `"first-contentful-paint"` APIs which are collected after the `navigate` event is fired. Or, you could imagine vendor-specific or application-specific measurements like [Cumulative Layout Shift](https://web.dev/cls/) or React hydration time being reset after such navigations begin.
This can be useful for cleaning up any information in secondary stores, such as `sessionStorage` or caches, when we're guaranteed to never reach those particular history entries again.
877
867
878
-
### Current entry change monitoring
868
+
### Performance timeline API integration
879
869
880
-
**Although the basic idea of an event for when `appHistory.current` changes will probably survive, much of this section needs revamping. See the several discussions linked below.**
870
+
The [performance timeline API](https://w3c.github.io/performance-timeline/) provides a generic framework for the browser to signal about interesting events, their durations, and their associated data via `PerformanceEntry` objects. For example, cross-document navigations are done with the [navigation timing API](https://w3c.github.io/navigation-timing/), which uses a subclass of `PerformanceEntry` called `PerformanceNavigationTiming`.
881
871
882
-
The `window.appHistory` object has an event, `currentchange`, which allows the application to react to any updates to the `appHistory.current` property. This includes both navigations that change its value, and calls to `appHistory.navigate()` that change its state or URL. This cannot be intercepted or canceled, as it occurs after the navigation has already happened; it's just an after-the-fact notification.
872
+
Until now, it has not been possible to measure such data for same-document navigations. This is somewhat understandable, as such navigations have always been "zero duration": they occur instantaneously when the application calls `history.pushState()` or `history.replaceState()`. So measuring them isn't that interesting. Butwith the app history API, [browsers know about the start time, end time, and duration ofthe navigation](#measuring-standardized-single-page-navigations), so we can give useful performance entries.
883
873
884
-
This event has one special property, `event.startTime`, whichfor[same-document](#appendix-types-of-navigations) navigations gives the value of`performance.now()` when the navigation was initiated. This includes for navigations that were originally [cross-document](#appendix-types-of-navigations), like the user clicking on `<a href="https://example.com/another-page">`, but were transformed into same-document navigations by [navigation interception](#navigation-monitoring-and-interception). For completely cross-document navigations, `startTime` will be `null`.
874
+
The `PerformanceEntry` instancesforsuch same-document navigations are instances ofa newsubclass, `SameDocumentNavigationEntry`, with the following properties:
885
875
886
-
"Initiated" means either when the corresponding API was called (like `location.href` or `appHistory.navigate()`), or when the user activated the corresponding `<a>` element, or submitted the corresponding `<form>`. This allows it to be used for determining the overall time from navigation initiation to navigation completion, including the time it took for a promise passed to `e.respondWith()` to settle:
876
+
-`name`:the URL being navigated to. (The use of`name` instead of`url` is strange, but matches all the other `PerformanceEntry`s on the platform.)
887
877
888
-
```js
889
-
appHistory.addEventListener("currentchange", e => {
document.querySelector("#status-bar").textContent = `Welcome to thisdocument!`;
897
-
}
898
-
});
899
-
```
880
+
-`startTime`: the time at which the navigation was initiated, i.e. when the corresponding API was called (like `location.href` or `appHistory.navigate()`), or when the user activated the corresponding `<a>` element, or submitted the corresponding `<form>`.
881
+
882
+
-`duration`: the duration of the navigation, which is either `0`for`history.pushState()`/`history.replaceState()`, or is the duration it takes the promise passed to `event.respondWith()` to settle, for navigations intercepted by a `navigate`event handler.
900
883
901
-
_TODO: reconsider cross-documentnavigations. There will only be one (the initial load of the page); should we even fire thiseventin that case? (That's [#31](https://github.com/WICG/app-history/issues/31).) Could we give `startTime` a useful value there, if we do?_
884
+
-`success`:`false`ifthe promise passed to `event.respondWith()` rejected; `true`otherwise (including for`history.pushState()`/`history.replaceState()`).
902
885
903
-
_TODO: this property-on-the-event design is not good and does not work, per [#59](https://github.com/WICG/app-history/issues/59). We should probably integrate with the performance timeline APIs instead? Discuss in [#33](https://github.com/WICG/app-history/issues/33)._
886
+
To record single-page navigations using [`PerformanceObserver`](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver), web developers could then use code such as the following:
904
887
905
-
_TODO: Add a non-analytics examples, similar to how people use `popstate` today. [#14](https://github.com/WICG/app-history/issues/14)_
888
+
```js
889
+
const observer = new PerformanceObserver(list => {
@@ -915,7 +905,6 @@ Between the per-`AppHistoryEntry` events and the `window.appHistory` events, as
915
905
1.`appHistory.current` fires `navigatefrom`.
916
906
1.`location.href` updates.
917
907
1.`appHistory.current` updates. `appHistory.transition` is created.
918
-
1. `currentchange` fires on `window.appHistory`.
919
908
1.`appHistory.current` fires `navigateto`.
920
909
1. Any now-unreachable `AppHistoryEntry` instances fire `dispose`.
921
910
1. The URL bar updates.
@@ -927,20 +916,23 @@ Between the per-`AppHistoryEntry` events and the `window.appHistory` events, as
927
916
1. If the process was initiated by a call to an `appHistory` API that returns a promise, then that promise gets fulfilled.
928
917
1. `appHistory.transition.finished` fulfills with undefined.
929
918
1. `appHistory.transition` becomes null.
919
+
1. Queue a new `SameDocumentNavigationEntry` indicating success.
930
920
1. Alternately, if the promise passed to `event.respondWith()` rejects:
931
921
1. `appHistory.current` fires `finish`.
932
922
1. `navigateerror` fires on `window.appHistory` with the rejection reason as its `error` property.
933
923
1. Any loading spinner UI stops.
934
924
1. If the process was initiated by a call to an `appHistory` API that returns a promise, then that promise gets rejected with the same rejection reason.
935
925
1. `appHistory.transition.finished` rejects with the same rejection reason.
936
926
1. `appHistory.transition` becomes null.
927
+
1. Queue a new `SameDocumentNavigationEntry` indicating failure.
937
928
1. Alternately, if the navigation gets [aborted](#aborted-navigations) before either of those two things occur:
938
929
1. (`appHistory.current` never fires the `finish` event.)
939
930
1. `navigateerror` fires on `window.appHistory` with an `"AbortError"` `DOMException` as its `error` property.
940
931
1. Any loading spinner UI stops. (But potentially restarts, or maybe doesn't stop at all, if the navigation was aborted due to a second navigation starting.)
941
932
1. If the process was initiated by a call to an `appHistory`API that returns a promise, then that promise gets rejected with the same `"AbortError"``DOMException`.
942
933
1.`appHistory.transition.finished` rejects with the same `"AbortError"``DOMException`.
943
934
1.`appHistory.transition` becomes null.
935
+
1. Queue a new`SameDocumentNavigationEntry` indicating failure.
944
936
945
937
For more detailed analysis, including specific code examples, see [this dedicated document](./interception-details.md).
946
938
@@ -1094,7 +1086,7 @@ The app history API provides several replacements that subsume these events:
1094
1086
1095
1087
- To react to and potentially intercept navigations before they complete, use the `navigate` event on `appHistory`. See the [Navigation monitoring and interception](#navigation-monitoring-and-interception) section for more details, including how the event object provides useful information that can be used to distinguish different types of navigations.
1096
1088
1097
-
- To react to navigations that have completed, use the `currentchange`eventon `appHistory`. See the [Current entry change monitoring](#current-entry-change-monitoring) section for more details, including an example of how to use it to determine how long a same-document navigation took.
1089
+
- To react to navigations that have completed, use the `navigatesuccess` or `navigateerror` events on `appHistory`. Note that these will only be fired asynchronously, after any handlers passed to the `navigate` event's `event.respondWith()` method have completed.
1098
1090
1099
1091
- To watch a particular entry to see when it's navigated to, navigated from, or becomes unreachable, use that `AppHistoryEntry`'s `navigateto`, `navigatefrom`, and `dispose` events. See the [Per-entry events](#per-entry-events) section for more details.
0 commit comments