“Soft navigations” are JS-driven same-document navigations that are using the history API or the new Navigation API, triggered by a user gesture and modifies the DOM, modifying the previous content, as well as the URL displayed to the user.
PerformanceTimeline#168 outlines the desire to be able to better report performance metrics on soft navigation. Heuristics for detecting soft navigations can ensure that developers can measure their SPA’s performance metrics, and optimize them to benefit their users.
Why would we want to web-expose soft navigation at all, you ask?
Well, a few reasons:
From a user's perspective, while they don't necessarily care about the architecture of the site they're visiting, they likely care about it being fast. This specification would enable alignment of the measurements with the user experience, improving the chances of SPA sites being perceived as fast by their users.
traverseTo()
.history.pushState()
or history.replaceState()
calls, or a change to the document’s location.The above heuristics rely on the ability to keep track of tasks and their provenance. We need to be able to tell that a certain task was posted by another, and be able to create a causality chain between DOM dirtying and URL modifications to the event handler that triggered the soft navigation.
SoftNavigationEntry : PerformanceEntry {
}
The inheritance from PerformanceEntry
means that the entry will have startTime
, name
, entryType
and duration
:
startTime
is defined as the time in which the user's interaction event processing ended, or the time in which the "navigate" event processing ended, whichever's first.name
is the URL of the history entry representing the soft navigation.entryType
is "soft-navigation".duration
is the time difference between the point in which a soft navigation is detected and the startTime
.That's all neat, but how would developers use the above? Great question!
If developers want to augment their current reporting with soft navigations, they'd need to do something like the following:
const soft_navs = await new Promise(resolve => {
(new PerformanceObserver( list => resolve(list.getEntries()))).observe(
{type: 'soft-navigation', buffered: true});
});
Or by using getEntriesByType
:
const soft_navs = performance.getEntriesByType('soft-navigation');
That would give them a list of past and future soft navigations they can send to their server for processing.
They would be able to also get soft navigations as they come (similar to other performance entries):
const soft_navs = [];
(new PerformanceObserver( list => soft_navs.push(...list.getEntries()))).observe(
{type: 'soft-navigation'});
Or to include past soft navigations:
const soft_navs = [];
(new PerformanceObserver( list => soft_navs.push(...list.getEntries()))).observe(
{type: 'soft-navigation', buffered: true});
For that developers would need to collect soft_navs
into an array as above.
Then they can, for each entry (which can be LCP, FCP, or any other entry type), find its corresponding duration as following:
const lcp_entries = [];
(new PerformanceObserver( list => lcp_entries.push(...list.getEntries()))).observe(
{type: 'largest-contentful-paint', includeSoftNavigationObservations: true});
for (entry of lcp_entries) {
const id = entry.navigationId;
const nav = soft_navs.filter(entry => entry.navigationId == id)[0];
entry.lcp_duration = entry.startTime - nav.startTime;
}
PerformanceObserverInit
option named "includeSoftNavigationObservations", that will indicate that post-soft-navigation FP, FCP and LCP entries should be observed.This API exposes a few novel paint timestamps that are not available today, after a soft navigation is detected: the first paint, the first contentful paint and the largest contentful paint.
It is already possible to get some of that data through Element Timing and requestAnimationFrame, but this proposal will expose that data without the need to craft specific elements with the elementtiming
attribute.
Given the above mitigations, attacks such as history sniffing attacks are not feasible, given that :visited
information is not exposed.
:visited
only modifications are not counting as DOM modifications, so soft navigations are not detected. Whenever other DOM modifications are included alongside visited changes, the next paint would include both modifications, and hence won't expose visited state.
Furthermore, cross-origin imformation about images or font resources is not exposed by Soft Navigation LCP, similarly to regular LCP.
<your questions here>
A few notes regarding heuristic alternatives:
I like how you're thinking!
You can do that by:
SoftNavigationEntry
entriesThe Chrome team have published an article about its implementation, and how developers can use this to try out the proposed API to see how it fits your needs.
And remember, if you find bugs, https://crbug.com is the best way to get them fixed!
If this effort were to rely on the Navigation API, that would mean that it can only cover future web apps, or require web apps to completely rewrite their routing libraries in order to take advantage of Soft Navigation measurement. That would go against the goal of being able to measure such navigations at scale.
On top of that, the Navigation API does not make any distinction between "real" navigations and interactions, so even if we were to rely on the Navigation API, extra heuristics would still be needed.
With that said, this effort works great with the Navigation API, as well as with the older history API.