WICG / soft-navigations

Heuristics to detect Single Page Apps soft navigations
https://wicg.github.io/soft-navigations/
Other
46 stars 6 forks source link

How best to deal with buffered entries? #10

Closed tunetheweb closed 1 year ago

tunetheweb commented 1 year ago

I'm trying to implemented support of this into web-vital.js.

For future events this works well. For example, for LCP events I can do the following:

I presume this will work as the events should be queued in order, so a SoftNav should always be emitted before it's first LCP entry and testing appears to show that is the case. So my first question is: Is that a valid assumption?

However, I'm running into problems when processing buffered events that happened before the library was initiated. The flow is basically the following:

In effect, I need to synchronise the results of two different PerformanceObservers and ensure that no entries get dropped, and that no entries outstay their welcome. It's possible, but proving a bit tricky...

I thought of setting one PerformanceObserver with both LCP and SoftNav entries to allow buffered ones to be replayed in order, using entryTypes but that doesn't support the buffered flag otherwise that could work quite well and only require processing one LCP at a time (as we do currently). But, given the lack of buffered support that is not an option. 😔

While we're on the subject, I have an additional problem that we don't "finalize" an LCP event, until a SoftNav occurs, at which point the URL has changed. As users will want to associate the LCP event with the previous URL to send to their analytics provider, that needs to be available too. We do need to get the SoftNav entry anyway to calculate the correct LCP start time so could store the URL form that and add the URL to the metric emitted by the library, but for the initial navigation and initial LCP we don't have that URL so need to set up ANOTHER PerformanceObserver to get the initial navigation event (which is PerformanceNavigationTiming rather than a SoftNavigationEntry so again slightly different). Which makes the problem even trickier as now dealing with three PerformanceObservers... Multiple that up by the 6 metrics that we track (all of which are basically currently handled independently at the moment) and it gets even more complicated.

This all would be considerably simplified, if the LCP event included the Soft Navigation startTime (or the LCP time was based on this rather than on navigationStart) and also if it included the navigation URL it was the LCP for. Or, alternatively, if it included a reference to the navigation entry (full or soft as appropriate) that the LCP was emitted for, so I can get those two bits of information more easily. But, again, that's not currently possible AFAIK.

I'm sure I've over complicated this, so any thoughts on a better way of doing this?

mmocny commented 1 year ago

I thought of setting one PerformanceObserver with both LCP and SoftNav entries

FYI you can call .observe() multiple times on a single PO and achieve this using buffered flag:

let entries = [];
let obs = new PerformanceObserver(list => entries.push(...list.getEntries()));
['paint','event','largest-contentful-paint','element','resource','navigation'].forEach(type => obs.observe({ type, buffered:true, durationThreshold: 0 }));

I also checked and it looks like entries are sorted so I think you'll get them in expected order-- though I'm not sure if thats guarenteed.

tunetheweb commented 1 year ago

There's an easier solution. You CAN look up the navigation entries directly with:

So that massively reduces the complexity here. Completely forgot about that API.

Not everything is in that API, but the navigation ones are, and those are the only ones I need.