Skip to content

Commit 073fe02

Browse files
Connor ClarkDevtools-frontend LUCI CQ
authored andcommitted
[RPP] Add marker for soft navigations
Bug: 450673886 Change-Id: I978f4093126bd9e045be7cc931c04a2ea054f747 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7214928 Reviewed-by: Paul Irish <paulirish@chromium.org> Commit-Queue: Connor Clark <cjamcl@chromium.org> Auto-Submit: Connor Clark <cjamcl@chromium.org>
1 parent 9cb1a58 commit 073fe02

11 files changed

Lines changed: 116 additions & 14 deletions

File tree

front_end/models/trace/Styles.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,6 +1126,10 @@ export function markerDetailsForEvent(event: Types.Events.Event): {
11261126
color = 'var(--color-text-primary)';
11271127
title = Handlers.ModelHandlers.PageLoadMetrics.MetricName.NAV;
11281128
}
1129+
if (Types.Events.isSoftNavigationStart(event)) {
1130+
color = 'var(--sys-color-blue)';
1131+
title = Handlers.ModelHandlers.PageLoadMetrics.MetricName.SOFT_NAV;
1132+
}
11291133
if (Types.Events.isMarkDOMContent(event)) {
11301134
color = 'var(--color-text-disabled)';
11311135
title = Handlers.ModelHandlers.PageLoadMetrics.MetricName.DCL;

front_end/models/trace/handlers/MetaHandler.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ let traceBounds: Types.Timing.TraceWindowMicro = makeNewTraceBounds();
5959
*/
6060
let navigationsByFrameId = new Map<string, Types.Events.NavigationStart[]>();
6161
let navigationsByNavigationId = new Map<string, Types.Events.NavigationStart>();
62+
let softNavigationsById = new Map<number, Types.Events.SoftNavigationStart>();
6263
let finalDisplayUrlByNavigationId = new Map<string, string>();
6364
let mainFrameNavigations: Types.Events.NavigationStart[] = [];
6465

@@ -92,6 +93,7 @@ const CHROME_WEB_TRACE_EVENTS = new Set([
9293
export function reset(): void {
9394
navigationsByFrameId = new Map();
9495
navigationsByNavigationId = new Map();
96+
softNavigationsById = new Map();
9597
finalDisplayUrlByNavigationId = new Map();
9698
processNames = new Map();
9799
mainFrameNavigations = [];
@@ -324,6 +326,10 @@ export function handleEvent(event: Types.Events.Event): void {
324326
return;
325327
}
326328

329+
if (Types.Events.isSoftNavigationStart(event)) {
330+
softNavigationsById.set(event.args.context.performanceTimelineNavigationId, event);
331+
}
332+
327333
// Update `finalDisplayUrlByNavigationId` to reflect the latest redirect for each navigation.
328334
if (Types.Events.isResourceSendRequest(event)) {
329335
if (event.args.data.resourceType !== 'Document') {
@@ -445,7 +451,14 @@ export interface MetaHandlerData {
445451
browserThreadId: Types.Events.ThreadID;
446452
gpuProcessId: Types.Events.ProcessID;
447453
navigationsByFrameId: Map<string, Types.Events.NavigationStart[]>;
454+
/**
455+
* This does not include soft navigations.
456+
*
457+
* TODO(crbug.com/414468047): include soft navs here, so that
458+
* PageLoadMetricsHandler and insights can use this map for all navigation types.
459+
*/
448460
navigationsByNavigationId: Map<string, Types.Events.NavigationStart>;
461+
softNavigationsById: Map<number, Types.Events.SoftNavigationStart>;
449462
/**
450463
* The user-visible URL displayed to users in the address bar.
451464
* This captures:
@@ -514,6 +527,7 @@ export function data(): MetaHandlerData {
514527
mainFrameURL,
515528
navigationsByFrameId,
516529
navigationsByNavigationId,
530+
softNavigationsById,
517531
finalDisplayUrlByNavigationId,
518532
threadsInProcess,
519533
rendererProcessesByFrame: rendererProcessesByFrameId,

front_end/models/trace/handlers/PageLoadMetricsHandler.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,12 +241,14 @@ describeWithEnvironment('PageLoadMetricsHandler', function() {
241241
describe('Marker events', () => {
242242
let mainFrameId: string;
243243
let allMarkerEvents: Trace.Types.Events.PageLoadEvent[];
244+
244245
beforeEach(async function() {
245246
const {data} = await TraceLoader.traceEngine(this, 'multiple-navigations-with-iframes.json.gz');
246247
const {PageLoadMetrics, Meta} = data;
247248
mainFrameId = Meta.mainFrameId;
248249
allMarkerEvents = PageLoadMetrics.allMarkerEvents;
249250
});
251+
250252
it('extracts all marker events from a trace correctly', () => {
251253
for (const metricName of Trace.Types.Events.MarkerName) {
252254
const markerEventsOfThisType = allMarkerEvents.filter(event => event.name === metricName);
@@ -256,6 +258,7 @@ describeWithEnvironment('PageLoadMetricsHandler', function() {
256258
marker => Trace.Handlers.ModelHandlers.PageLoadMetrics.getFrameIdForPageLoadEvent(marker) === mainFrameId));
257259
}
258260
});
261+
259262
it('only marker events are exported in allMarkerEvents', () => {
260263
for (const marker of allMarkerEvents) {
261264
assert.isTrue(Trace.Types.Events.isMarkerEvent(marker));
@@ -271,4 +274,16 @@ describeWithEnvironment('PageLoadMetricsHandler', function() {
271274
assert.strictEqual(largestContentfulPaints[0].args.data?.candidateIndex, 2);
272275
});
273276
});
277+
278+
describe('soft navs', () => {
279+
it('detects SoftNavigationStart', async function() {
280+
const {data} = await TraceLoader.traceEngine(this, 'soft-navs.json.gz');
281+
const {PageLoadMetrics} = data;
282+
assert.deepEqual(PageLoadMetrics.allMarkerEvents.map(e => e.name), [
283+
'SoftNavigationStart',
284+
'SoftNavigationStart',
285+
'SoftNavigationStart',
286+
]);
287+
});
288+
});
274289
});

front_end/models/trace/handlers/PageLoadMetricsHandler.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import type {HandlerName} from './types.js';
2323
// Small helpers to make the below type easier to read.
2424
type FrameId = string;
2525
type NavigationId = string;
26+
type AnyNavigationStart = Types.Events.NavigationStart|Types.Events.SoftNavigationStart;
2627
/**
2728
* This represents the metric scores for all navigations, for all frames in a trace.
2829
* Given a frame id, the map points to another map from navigation id to metric scores.
@@ -64,8 +65,10 @@ export function handleEvent(event: Types.Events.Event): void {
6465
}
6566

6667
function storePageLoadMetricAgainstNavigationId(
67-
navigation: Types.Events.NavigationStart, event: Types.Events.PageLoadEvent): void {
68-
const navigationId = navigation.args.data?.navigationId;
68+
navigation: AnyNavigationStart, event: Types.Events.PageLoadEvent): void {
69+
const navigationId = Types.Events.isSoftNavigationStart(navigation) ?
70+
`softnav-${navigation.args.context.performanceTimelineNavigationId}` :
71+
navigation.args.data?.navigationId;
6972
if (!navigationId) {
7073
throw new Error('Navigation event unexpectedly had no navigation ID.');
7174
}
@@ -199,6 +202,9 @@ function storePageLoadMetricAgainstNavigationId(
199202
if (Types.Events.isLayoutShift(event)) {
200203
return;
201204
}
205+
if (Types.Events.isSoftNavigationStart(event)) {
206+
return;
207+
}
202208
return Platform.assertNever(event, `Unexpected event type: ${event}`);
203209
}
204210

@@ -215,7 +221,8 @@ function storeMetricScore(frameId: string, navigationId: string, metricScore: Me
215221
export function getFrameIdForPageLoadEvent(event: Types.Events.PageLoadEvent): string {
216222
if (Types.Events.isFirstContentfulPaint(event) || Types.Events.isInteractiveTime(event) ||
217223
Types.Events.isLargestContentfulPaintCandidate(event) || Types.Events.isNavigationStart(event) ||
218-
Types.Events.isLayoutShift(event) || Types.Events.isFirstPaint(event)) {
224+
Types.Events.isSoftNavigationStart(event) || Types.Events.isLayoutShift(event) ||
225+
Types.Events.isFirstPaint(event)) {
219226
return event.args.frame;
220227
}
221228
if (Types.Events.isMarkDOMContent(event) || Types.Events.isMarkLoad(event)) {
@@ -228,7 +235,7 @@ export function getFrameIdForPageLoadEvent(event: Types.Events.PageLoadEvent): s
228235
Platform.assertNever(event, `Unexpected event type: ${event}`);
229236
}
230237

231-
function getNavigationForPageLoadEvent(event: Types.Events.PageLoadEvent): Types.Events.NavigationStart|null {
238+
function getNavigationForPageLoadEvent(event: Types.Events.PageLoadEvent): AnyNavigationStart|null {
232239
if (Types.Events.isFirstContentfulPaint(event) || Types.Events.isLargestContentfulPaintCandidate(event) ||
233240
Types.Events.isFirstPaint(event)) {
234241
const navigationId = event.args.data?.navigationId;
@@ -245,6 +252,11 @@ function getNavigationForPageLoadEvent(event: Types.Events.PageLoadEvent): Types
245252
return navigation;
246253
}
247254

255+
if (Types.Events.isSoftNavigationStart(event)) {
256+
const {softNavigationsById} = metaHandlerData();
257+
return softNavigationsById.get(event.args.context.performanceTimelineNavigationId) ?? null;
258+
}
259+
248260
if (Types.Events.isMarkDOMContent(event) || Types.Events.isInteractiveTime(event) ||
249261
Types.Events.isLayoutShift(event) || Types.Events.isMarkLoad(event)) {
250262
const frameId = getFrameIdForPageLoadEvent(event);
@@ -437,6 +449,8 @@ export const enum MetricName {
437449
CLS = 'CLS',
438450
// Navigation
439451
NAV = 'Nav',
452+
// Soft Navigation (just "Nav" b/c space is limited in flame chart)
453+
SOFT_NAV = 'Nav',
440454
// Note: INP is handled in UserInteractionsHandler
441455
}
442456

@@ -445,7 +459,7 @@ export interface MetricScore {
445459
classification: ScoreClassification;
446460
event?: Types.Events.PageLoadEvent;
447461
// The last navigation that occurred before this metric score.
448-
navigation?: Types.Events.NavigationStart;
462+
navigation?: AnyNavigationStart;
449463
estimated?: boolean;
450464
timing: Types.Timing.Micro;
451465
}

front_end/models/trace/types/TraceEvents.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,29 @@ export interface NavigationStart extends Mark {
702702
};
703703
}
704704

705+
export interface SoftNavigationStart extends Event {
706+
name: Name.SOFT_NAVIGATION_START;
707+
ph: Phase.ASYNC_NESTABLE_INSTANT;
708+
args: Args&{
709+
frame: string,
710+
context: {
711+
softNavContextId: number,
712+
// eslint-disable-next-line @typescript-eslint/naming-convention
713+
URL: string,
714+
timeOrigin: number,
715+
domModifications: number,
716+
firstContentfulPaint: number,
717+
paintedArea: number,
718+
performanceTimelineNavigationId: number,
719+
repaintedArea: number,
720+
},
721+
};
722+
}
723+
724+
export function isSoftNavigationStart(event: Event): event is SoftNavigationStart {
725+
return event.name === Name.SOFT_NAVIGATION_START;
726+
}
727+
705728
export interface FirstContentfulPaint extends Mark {
706729
name: Name.MARK_FCP;
707730
args: Args&{
@@ -723,7 +746,7 @@ export interface FirstPaint extends Mark {
723746
}
724747

725748
export type PageLoadEvent = FirstContentfulPaint|MarkDOMContent|InteractiveTime|LargestContentfulPaintCandidate|
726-
LayoutShift|FirstPaint|MarkLoad|NavigationStart;
749+
LayoutShift|FirstPaint|MarkLoad|NavigationStart|SoftNavigationStart;
727750

728751
const markerTypeGuards = [
729752
isMarkDOMContent,
@@ -732,6 +755,7 @@ const markerTypeGuards = [
732755
isFirstContentfulPaint,
733756
isLargestContentfulPaintCandidate,
734757
isNavigationStart,
758+
isSoftNavigationStart,
735759
];
736760

737761
export const MarkerName =
@@ -742,7 +766,7 @@ export interface MarkerEvent extends Event {
742766
}
743767

744768
export function isMarkerEvent(event: Event): event is MarkerEvent {
745-
if (event.ph === Phase.INSTANT || event.ph === Phase.MARK) {
769+
if (event.ph === Phase.INSTANT || Phase.ASYNC_NESTABLE_INSTANT || event.ph === Phase.MARK) {
746770
return markerTypeGuards.some(fn => fn(event));
747771
}
748772
return false;
@@ -754,7 +778,7 @@ const pageLoadEventTypeGuards = [
754778
];
755779

756780
export function eventIsPageLoadEvent(event: Event): event is PageLoadEvent {
757-
if (event.ph === Phase.INSTANT || event.ph === Phase.MARK) {
781+
if (event.ph === Phase.INSTANT || Phase.ASYNC_NESTABLE_INSTANT || event.ph === Phase.MARK) {
758782
return pageLoadEventTypeGuards.some(fn => fn(event));
759783
}
760784
return false;
@@ -2336,7 +2360,7 @@ export function isPrePaint(
23362360

23372361
/** A VALID navigation start (as it has a populated documentLoaderURL) */
23382362
export function isNavigationStart(event: Event): event is NavigationStart {
2339-
return event.name === 'navigationStart' && (event as NavigationStart).args?.data?.documentLoaderURL !== '';
2363+
return event.name === Name.NAVIGATION_START && (event as NavigationStart).args?.data?.documentLoaderURL !== '';
23402364
}
23412365

23422366
export interface DidCommitSameDocumentNavigation extends Complete {
@@ -3091,6 +3115,7 @@ export const enum Name {
30913115
MARK_LCP_CANDIDATE = 'largestContentfulPaint::Candidate',
30923116
MARK_LCP_INVALIDATE = 'largestContentfulPaint::Invalidate',
30933117
NAVIGATION_START = 'navigationStart',
3118+
SOFT_NAVIGATION_START = 'SoftNavigationStart',
30943119
CONSOLE_TIME = 'ConsoleTime',
30953120
USER_TIMING = 'UserTiming',
30963121
INTERACTIVE_TIME = 'InteractiveTime',

front_end/panels/timeline/TimelineFlameChartView.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,11 @@ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
6666
*/
6767
export const SORT_ORDER_PAGE_LOAD_MARKERS: Readonly<Record<string, number>> = {
6868
[Trace.Types.Events.Name.NAVIGATION_START]: 0,
69-
[Trace.Types.Events.Name.MARK_LOAD]: 1,
70-
[Trace.Types.Events.Name.MARK_FCP]: 2,
71-
[Trace.Types.Events.Name.MARK_DOM_CONTENT]: 3,
72-
[Trace.Types.Events.Name.MARK_LCP_CANDIDATE]: 4,
69+
[Trace.Types.Events.Name.SOFT_NAVIGATION_START]: 1,
70+
[Trace.Types.Events.Name.MARK_LOAD]: 2,
71+
[Trace.Types.Events.Name.MARK_FCP]: 3,
72+
[Trace.Types.Events.Name.MARK_DOM_CONTENT]: 4,
73+
[Trace.Types.Events.Name.MARK_LCP_CANDIDATE]: 5,
7374
};
7475

7576
// Threshold to match up overlay markers that are off by a tiny amount so they aren't rendered
@@ -669,6 +670,7 @@ export class TimelineFlameChartView extends Common.ObjectWrapper.eventMixin<Even
669670
// Set markers for Navigations, LCP, FCP, DCL, L.
670671
const markers = markerEvents.filter(
671672
event => event.name === Trace.Types.Events.Name.NAVIGATION_START ||
673+
event.name === Trace.Types.Events.Name.SOFT_NAVIGATION_START ||
672674
event.name === Trace.Types.Events.Name.MARK_LCP_CANDIDATE ||
673675
event.name === Trace.Types.Events.Name.MARK_FCP ||
674676
event.name === Trace.Types.Events.Name.MARK_DOM_CONTENT ||

front_end/panels/timeline/TimelineUIUtils.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1001,6 +1001,20 @@ export class TimelineUIUtils {
10011001
return contentHelper.fragment;
10021002
}
10031003

1004+
if (Trace.Types.Events.isNavigationStart(event)) {
1005+
url = (event.args.data?.documentLoaderURL ?? event.args.data?.url) as Platform.DevToolsPath.UrlString;
1006+
if (url) {
1007+
contentHelper.appendElementRow(i18nString(UIStrings.url), LegacyComponents.Linkifier.Linkifier.linkifyURL(url));
1008+
}
1009+
}
1010+
1011+
if (Trace.Types.Events.isSoftNavigationStart(event)) {
1012+
url = event.args.context.URL as Platform.DevToolsPath.UrlString;
1013+
if (url) {
1014+
contentHelper.appendElementRow(i18nString(UIStrings.url), LegacyComponents.Linkifier.Linkifier.linkifyURL(url));
1015+
}
1016+
}
1017+
10041018
if (Trace.Types.Events.isV8Compile(event)) {
10051019
url = event.args.data?.url as Platform.DevToolsPath.UrlString;
10061020
if (url) {
@@ -2244,6 +2258,10 @@ export class TimelineUIUtils {
22442258
color = 'var(--color-text-primary)';
22452259
tall = true;
22462260
break;
2261+
case Trace.Types.Events.Name.SOFT_NAVIGATION_START:
2262+
color = 'var(--sys-color-blue)';
2263+
tall = true;
2264+
break;
22472265
case Trace.Types.Events.Name.FRAME_STARTED_LOADING:
22482266
color = 'green';
22492267
tall = true;
@@ -2523,7 +2541,8 @@ export function timeStampForEventAdjustedForClosestNavigationIfPossible(
25232541
export function isMarkerEvent(parsedTrace: Trace.TraceModel.ParsedTrace, event: Trace.Types.Events.Event): boolean {
25242542
const {Name} = Trace.Types.Events;
25252543

2526-
if (event.name === Name.TIME_STAMP || event.name === Name.NAVIGATION_START) {
2544+
if (event.name === Name.TIME_STAMP || event.name === Name.NAVIGATION_START ||
2545+
event.name === Name.SOFT_NAVIGATION_START) {
25272546
return true;
25282547
}
25292548

front_end/panels/timeline/fixtures/traces/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ copy_to_gen("traces") {
128128
"simple-js-program.json.gz",
129129
"slow-interaction-button-click.json.gz",
130130
"slow-interaction-keydown.json.gz",
131+
"soft-navs.json.gz",
131132
"style-invalidation-change-attribute.json.gz",
132133
"style-invalidation-change-id.json.gz",
133134
"sync-like-timings.json.gz",

front_end/panels/timeline/fixtures/traces/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,3 +369,7 @@ A trace of https://astro-news-1026410574114.us-central1.run.app/ used to test CL
369369
### render-blocking-preload
370370

371371
A trace of https://andydavies.github.io/agent-tests/render-blocking/css-preload.html that highlighted a bug where we do not update render blocking status based on PreloadRenderBlockingStatus events (crbug.com/457323832).
372+
373+
### soft-navs
374+
375+
A trace of https://developer.chrome.com/docs/web-platform/soft-navigations-experiment, navigating to many other pages on the same domain. All are soft navigations.
5.35 MB
Binary file not shown.

0 commit comments

Comments
 (0)