navigation-timing icon indicating copy to clipboard operation
navigation-timing copied to clipboard

Extending the NavigationTimingType

Open tunetheweb opened this issue 3 years ago • 5 comments

Related to the discussion on #179

There are a few uses cases where the NavigationTimingType does not cover the type:

  • Restores - can be detected with document.wasDiscarded. Should we add a restore navigation type rather than reuse the original navigation type?
  • BFcache usage - it's arguable whether this is a "navigation", but given pages that are not in bfcache will reload, and that counts as back_forward navigation, I argue BFcache restores should be a navigation. Currently this can be detected with the persisted flag on the pageshow event. Should we add a back_forward_cache navigation type, and treat this is as a true navigation, to explicitly make it easier to identify these?
  • Prerender - this is explicitly noted in the spec as for the Resource Hint, but is currently being reimplemented in Chrome using the Speculation rules API, and the omnibox which does not fit this definition. Should the wording be relaxed to include all prerenders?
  • Additionally the prerender option is not listed in the interace, when it should be shouldn't it?

These types, can have very different performance measurements compared to other navigations and it is often recommended to measure them separately, but at the moment, this results in lots of extra code for (for example) and understanding of all these nuances to enable this.

There's a lot of different issues in this one, so happy to split this out, or continue some of the discussion in #179 but thought I'd start with the one issue for now in case we wanted to tackle together.

tunetheweb avatar Nov 08 '22 10:11 tunetheweb

Should we add a back_forward_cache navigation type, and treat this is as a true navigation, to explicitly make it easier to identify these?

I'm in favour 👍

verlok-cn avatar Dec 08 '22 11:12 verlok-cn

Should we add a back_forward_cache navigation type, and treat this is as a true navigation, to explicitly make it easier to identify these?

A possible reason against, might be that it could be confusing (and possibly break compat) if we retroactively change the value of performance.navigation.type, performance.timing, and performance.getEntriesByType('navigation') during a bfcache restore that includes the original JS heap as these would already have been consumed.

I notice that in Firefox today, it already does this. The original data in performance.timing is preserved as-is with navigationStart holding the timestamp of when the original navigation started, but performance.navigation.type changes during the bfcache restore from TYPE_NAVIGATE to TYPE_BACK_FORWARD. I realize that I had incorrectly assumed that this navigation type would only be set when bfcache isn't leveraged, since no navigation got rendered. It's restored instantly from memory with no new navigation timing data being provided. I guess it doesn't cause double-counting of the original navigation, since the already-executed JS code isn't forced to consume it a second time, but it also makes it unclear what the intended use case is for seeing a new navigation type without seeing new navigation timing data.

Krinkle avatar Dec 08 '22 16:12 Krinkle

RE BFCache specifically, there's a proposal for dedicated entries for it, to avoid the issues @Krinkle pointed out. It was discussed a while back (recording)

/cc @clelland

yoavweiss avatar Dec 09 '22 04:12 yoavweiss

I guess it doesn't cause double-counting of the original navigation, since the already-executed JS code isn't forced to consume it a second time but it also makes it unclear what the intended use case is for seeing a new navigation type without seeing new navigation timing data.

In part it's to avoid me (and others!) having to write code like this:

https://github.com/GoogleChrome/web-vitals/blob/39f178242afbb96dca3d48b216d60e7cd4cfa633/src/lib/initMetric.ts#L26-L34

I feel it would make it more obvious to anyone firing events (e.g. LCP), without having to be NavigationTimingType experts and know ALL the edge case you need to consider (3 currently!).

For restores and prerender it does show new navigational timing data, so this is more about categorising that data appriopriately.

For bfcache, I agree it does not fire new data. There's a question if it should? Which seems to be discussed prior to me joining this WG as per Yoav's comment. Seems like from a review of that, that there was some agreement, but needs the work to be done.

Maybe we drop bfcache for now, and limit scope of my ask here to restores and prerender, since they are simpler?

Bfcache could be revisited later (perhaps with soft nav as mentioned in the discussions Yoav's linked?)

tunetheweb avatar Dec 09 '22 17:12 tunetheweb

Tab duplication/cloning has the same issues. Maybe RESTORE can be extended to cover both (maybe with a name change) or we would have a new entry for it.

fergald avatar Oct 20 '23 05:10 fergald

We've received feedback from multiple developers recently tackling BFCache and having an hard time to easily distinguish back_forward_cache vs a back_forward navigation type and most of them were surprised back_forward_cache is not treated as a Navigation Type itself considering the different nature of what's going on in that case.

Similarly for Prerender navigations, using the prerenderingchange callback in combination with the change is pretty cumbersome and prone to error.

I believe there is a good appetite to include these Navigation Types in these (back_forward_cache and prerender) to help developers not only measure the metrics itself but allow them to better measure the business impact of these different navigaiton types.

Using the pageshow event.persisted event handler case in the runtime is not great considering that the navigationType can be a much more elegant way to distinguish this different experience.

gilbertococchi avatar Sep 17 '25 10:09 gilbertococchi

Added this to the TPAC agenda. Let's discuss it further then.

yoavweiss avatar Sep 18 '25 05:09 yoavweiss

My two cents (since I won't be at TPAC):

It's important for the web platform to have appropriate orthogonal primitives that are correct. For example, currently we have NavigationType, which is separate from prerendering state (since you can prerender several different types of NavigationTypes, so it's best modeled as a layer on top of those), and is separate from bfcache state (since bfcache is a layer on top of the back_forward NavigationType). We should preserve this for people who need access to the underlying ground truth.

If web or library developers find this underlying ground truth unwieldy or less useful for their purposes, that's good feedback, but it shouldn't cause us to muddle NavigationType.

  • It's a good reason for creating high-level wrapper libraries that produce more useful, albeit less informational abstractions. (E.g., a library which surfaces prerender as a "navigation category", dropping the information about whether the prerender was for a push/replace navigation, a reload, or a back/forward traversal.)

  • And, if we're really quite certain that the burden of maintaining or finding those libraries is prohibitive for web developers, and that such libraries have found the perfect high-level patterns, then we could consider shifting the cost of maintenance to browsers, by having them all ship a new spec for some new high-level "navigation category" property.

domenic avatar Sep 18 '25 07:09 domenic

Having back_foward_cache as a NavigationType would mean that we would have to update the navigation type. Currently it is fixed from document creation.

For prerendering, imagine if we predicted that you would click the back button and started to prerender that page. What should be the navigation type, back_forward or prerender?

I agree it's confusing. Some natural questions are

  • What user action brought them to the current page?
  • Was the current page served from BFCache?
  • Was this page prerendering?
  • Is this page currently prerendering?

and I think only the last none of these questions can be answered by just looking at a single value in an API.

fergald avatar Sep 18 '25 08:09 fergald

I tend to agree that we don't want to actually change the navigation timing type to "back_forward_cache", as there isn't actually a navigation.

I suspect a lot of the confusion comes from the "BACK_FORWARD" value. Maybe we can rename it?? Also, maybe we need a more explicit API indicating that the document was restored from the BFCache.

yoavweiss avatar Sep 18 '25 09:09 yoavweiss

While I agree there is overlap in some of these types, I'm not sure how much value there is in knowing this a prerendered page is from a back_forward navigation or not. In my opinion most people will want to know one value, and therefore speccing that order of preference is important to avoid RUM providers A reporting as prerender and B as back_forward (unless a clear reasons exists to know both).

To me the value of this API is two fold:

  1. In order to understand performance characteristics (e.g. that page view was faster because it was a bfcache or prerender)
  2. In order to optimise for faster navigation types (e.g. aim to transfer more back_forward types to bfcache, or navigation types to prerender).

In neither case is knowing the explicit "type" and separate "category" useful and I'd much rather know one value, even if that value is based on an opinionated order of preference.

Certainly that is the approach we took when adding navigation types to CrUX and similarly when adding navigationType to web-vitals.

Having back_foward_cache as a NavigationType would mean that we would have to update the navigation type. Currently it is fixed from document creation.

I tend to agree that we don't want to actually change the navigation timing type to "back_forward_cache", as there isn't actually a navigation.

There is precedent in the NotRestoredReasons API updating the PerformanceNavigationTiming entry after the fact.

Maybe changing NavigationType is too much of a breaking change (and maybe there ARE reasons to know the "type" that I'm not thinking of) but a new entry maybe could still update on bfcache restores?

tunetheweb avatar Sep 18 '25 09:09 tunetheweb

BTW, on a related note, I've opened #207 to remove prerender which is a half specced type (it's in some placed, but not in the enum) that is no longer used. Let me know on that PR if there's any objections to removing that.

Even if we bring that back later based on this discussion, I think the spec should be updated now to reflect the current reality.

Any work that comes from this will need a bigger review anyway (e.g. to add new types it to the enum).

tunetheweb avatar Sep 18 '25 09:09 tunetheweb

+1 about what Barry said, I don't believe there is appetite or a real need to detect whether a back_forward_cache navigation was prerendered even thought there might be overlaps as discussed.

Developers can definitively benefit more of distinguish back_forward vs back_forward_cache while avoiding to have to custom listen for pageshow event.persisted=true to finally tell whether a back_forward navigationType was of type back_forward_cache.

There isn't a perfect type to flag these navigation type but there are opportunities to make it simpler for developers to understand the differences, currently I believe there is confusion and developers may likely not be in the situation they can measure the impact of these types well enough.

gilbertococchi avatar Sep 18 '25 10:09 gilbertococchi

FYI: some developers asked assistance as they struggle to collectively identify different Navigation Types across different standards options.

This is a small example about what developers have been asked to know to be able to distinguish navigations across multiple types.

https://gist.github.com/gilbertococchi/a321cd7f47061427f93ec3b6fd142a22

I honestly doubt that developers are able to navigated these difference alone and they may just have an hard time distinguish navigation types effectively.

I suspect that the majority of them stopped at navigationTimingEntry.type options and that's it, loosing the opportunity to measure what back_forward_cache, prerender, navigational-prefetch and cache that for some reason they are defined into another field called deliveryType.

gilbertococchi avatar Sep 23 '25 12:09 gilbertococchi

There is precedent in the NotRestoredReasons API updating the PerformanceNavigationTiming entry after the fact.

I'm not sure what you mean here. performance.getEntriesByType("navigation")[0].notRestoredReasons does not change throughout the lifetime of the document. So e.g.

  • take a web lock with navigator.locks.request("fergal", async () => {await new Promise(r => {})})
  • navigate away
  • navigate back
  • performance.getEntriesByType("navigation")[0].notRestoredReasons shows "lock"
  • navigate away
  • navigate back
  • even though BFCache was used, notRestoredReasons still shows "lock"

fergald avatar Sep 24 '25 08:09 fergald

Oh that’s interesting. And also confusing! I presumed it reset in such scenarios to show there were no bfcache blockers.

So you should only look at this field if pageshow event says this wasn’t a bfcache restore? Which I guess works but is also a bit of an oddity to know this value is now stale.

Sigh… every time I look into this I find more nuance and complexity.

If values can’t change, then maybe we should have a getLatestNavType function exposed somewhere that encapsulates all this complexity?

tunetheweb avatar Sep 24 '25 08:09 tunetheweb

while avoiding to have to custom listen for pageshow event.persisted=true

No matter what, you have to install a custom listener for pageshow because otherwise no code will run when the page comes out of BFCache. The only way we could make it easier would be a specifically named BFCache event, so you could avoid needed to check for event.persisted=true but adding a new event would be a lot of work for not much reward.

While https://gist.github.com/gilbertococchi/a321cd7f47061427f93ec3b6fd142a22 contains a fair bit of complexity, I'm not sure it's realistic. I.e. I don't know when anyone would want a function that will potentially wait until pageshow and return that information. pageshow requires all images, scripts etc to be finished loading, waiting for that before logging the navigation is going to make your logging unreliable.

That gist essentially has 2 branches and there is no way to unify them. There's no way to change the API so that you can avoid writing 2 code-paths.

  • A BFCache-load must be logged from some event handler.
  • A non-BFCache load must be logged from the page's initial script unless you want to lose logs.

Your code should look like

<script>
  logNavigation(getNonBFCacheNavigationType());
  window.onpageshow = e => {
    if (e.persisted) {
      logNavigation("bfcache");
    }
  };
</script>

I don't think any new API or API change can get around that and so there is no need for a function or API that can answer "what was my last navigation?".

fergald avatar Sep 24 '25 08:09 fergald

Thanks Fergal, I agree with you, at first I wanted to help with a simple one helper but considering that on a restored page via BFCache code would run only via pageshow event.persisted=true that may be acceptable.

What about a prerender navigation and cached scenarios?

Any ideas about how we can avoid requesting developers to implement these logic checks?

gilbertococchi avatar Sep 24 '25 09:09 gilbertococchi

prerender seems othogonal to the what kind of navigation it was. I could imagine exposing something to say "this document was prerendered at some point" but I guess that hides a lot and might not be that useful. How far did it get before activation is an important question and the current API allows devs to tell what was doing while prerendering and what was done after. So another API that's less useful is unlikely to added just because it's a bit more convenient.

Not sure what you mean by cached.

I don't think there is any way to avoid this logic. It's fundamental. As @yoavweiss points out, maybe the naming would make it less confusing.

fergald avatar Sep 24 '25 09:09 fergald

Thanks Fergal, I think I would need a table to better map all the cases overall, happy to jump on a chromium doc when possible.

Currently I believe navigate can be of at least three sub-types:

  • navigate: navigation coming from Network (no cache) and not falling on the sub-types below.
  • navigate (deliveryType=navigational-prefetch): Cache from Speculation Rules Prefetch
  • navigate (deliveryType=cache): Cache from Disk/Mem Cache (not Speculation Rules Prefetch).
  • navigate (activationStart > 0): Prerender from Speculation Rules Prerender

I wonder if this was the desired scenario and whether this sub composition is also optimal.

About BFCache I have a last consideration to share, although it's acceptable that 1P developers can rely on pageshow event.persisted=true to detect a "back_forward_cache" navigation I think it would still be desirable that performance.getEntriesByType("navigation")[0].type would update to "back_forward_cache" when the Browser would trigger pageshow event.persisted=true. The reason why I am saying that is that other Scripts on the page like 3Ps may be in the position to understand what kind of Navigation Scenario a request may be triggered if something wasn't triggered by the 3P itself.

I can think about Analytics and Ads providers, where developers trigger the logic with pageshow event.persisted but it's unlikely the 3P provider would delay their request beacon system to listen async to pageshow event again.

gilbertococchi avatar Sep 24 '25 15:09 gilbertococchi

I can think about Analytics and Ads providers, where developers trigger the logic with pageshow event.persisted but it's unlikely the 3P provider would delay their request beacon system to listen async to pageshow event again.

Nobody should delay their beacon until pageshow. Everyone should log the initial non-bfcache navigation using performance.getEntriesByType("navigation")[0].type as soon as they run any script. Everyone should also install a pageshow handler or they are not going to log anything at all when the page is restored from BFCache.

Everyone must do both if they want to log all navigations without delays and risk of loss. Everyone's code is going to look like the code I wrote above and updating .type does nothing to make that code simpler.

If you disagree, please assume that .type updates to "back_forward_cache"after persisted pageshow and post code for how you would use that.

fergald avatar Sep 25 '25 01:09 fergald

Hi Fergal, thanks for your answer.

Can you clarify what you mean with "please assume that .type updates to "back_forward_cache"after persisted pageshow and post code for how you would use that."?

That developers should update their own .type reference to "back"forward_cache" internally?

Because performance.getEntriesByType('navigation')[0].type does not update to "back_forward_cache" after persisted pageshow, I would love browsers to do that if possible!

Please let me explain the situation and why reporting BFCache is difficult for some connections between 1P and 3Ps.

Example 1: Analytics

Developer using Google Analytics may want to report pageView on BFCache restored page, they may implement persisted pageshow as GA is not doing anything on BFCache. As a result GA from the 3P POV would not be able to distinguish the beacon coming from different navigation types without the developer sending this info because the pageshow event handler was triggered by the 1P developer. If GA would like to distinguish the pageView Navigation Type they would have to listen to pageshow again which is cumbersome. Having performance.getEntriesByType('navigation')[0].type to update after persisted pageshow would solve this problem.

Example 2: Ads

Developer using Ads via some Ad Server (example Google Publisher Tag) may decide to refresh their Ads when page is restored form BFCache.

Again, GPT is not triggering any ad request, it's the 1P developer that implements persisted pageshow listener and triggers the Ad Request if desired. Also in this case GTP from the 3P POV would have not be able to distinguish whether the beacon was triggered from a BFCache restored page without neec to listen themselves again to pageshow.

gilbertococchi avatar Oct 01 '25 10:10 gilbertococchi

Can you clarify what you mean with "please assume that .type updates to "back_forward_cache"after persisted pageshow and post code for how you would use that."?

I mean show me the code that devs would write if the API did what you wanted it to do.

fergald avatar Oct 02 '25 00:10 fergald

Discussed at TPAC 2025

  • There is some support for adding new navigation types:
    • prerender, or perhaps prerender_navigation, prerender_back_forward if that separation is useful.
    • Maybe restore?
  • deliveryType likely should stay as separate bit. But since it's on the same PerformanceNavigationTiming entry, it's at least close to the navigation type, rather than being in a different api.
  • bfcache shouldn't updated the immutable PerformanceNavigationTiming entry (bug raised with Mozilla for Firefox current implementation) so new performance timeline entry (bfcache_restore?) seems the best path forward — with it's own timing, and potentially PaintTimingMixin to allow measurement of FCP (currently web-vitals.js does a double rAF to guesstimate this time and Boomerang does similar).
  • Potentially other "navigation"-akin entries (e.g. prerenderingChange event) should also be reported to performance timeline.

tunetheweb avatar Nov 11 '25 06:11 tunetheweb

Note the current spec says type (and redirectType) is set "in the current browsing context". This was discussed in https://github.com/w3c/navigation-timing/issues/114 and updated (in HTML spec) "so the navigation type is set as part of the navigation, here, and not for a browsing context." but looks like spec wasn't updated to reflect that.

tunetheweb avatar Nov 11 '25 07:11 tunetheweb

Note the current spec says type (and redirectType) is set "in the current browsing context". This was discussed in https://github.com/w3c/navigation-timing/issues/114 and updated (in HTML spec) "so the navigation type is set as part of the navigation, here, and not for a browsing context." but looks like spec wasn't updated to reflect that.

Fixed in #209

tunetheweb avatar Nov 12 '25 03:11 tunetheweb

Thanks for the update everyone, having another field to tell 1P or 3P scripts that their JS code is running within a BFCache restored pages, like bfcache_restore property would be a good path torwards.

Is it worth opening a new issue to track this?

gilbertococchi avatar Nov 13 '25 07:11 gilbertococchi