chrome-devtools-frontend 1.0.1558690 → 1.0.1559913

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/front_end/Images/src/container.svg +4 -0
  2. package/front_end/core/common/Gzip.ts +15 -0
  3. package/front_end/core/sdk/CSSMetadata.ts +6 -6
  4. package/front_end/core/sdk/CSSModel.ts +2 -2
  5. package/front_end/core/sdk/DOMModel.ts +7 -3
  6. package/front_end/generated/InspectorBackendCommands.ts +2 -1
  7. package/front_end/generated/SupportedCSSProperties.js +64 -32
  8. package/front_end/generated/protocol-mapping.d.ts +9 -0
  9. package/front_end/generated/protocol-proxy-api.d.ts +7 -0
  10. package/front_end/generated/protocol.ts +14 -1
  11. package/front_end/models/ai_assistance/agents/StylingAgent.ts +1 -1
  12. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +11 -7
  13. package/front_end/models/trace/LanternComputationData.ts +4 -3
  14. package/front_end/models/trace/Processor.ts +6 -5
  15. package/front_end/models/trace/Styles.ts +10 -1
  16. package/front_end/models/trace/handlers/LargestImagePaintHandler.ts +2 -2
  17. package/front_end/models/trace/handlers/MetaHandler.ts +14 -0
  18. package/front_end/models/trace/handlers/PageLoadMetricsHandler.ts +54 -34
  19. package/front_end/models/trace/helpers/Timing.ts +8 -1
  20. package/front_end/models/trace/insights/Common.ts +1 -1
  21. package/front_end/models/trace/insights/LCPBreakdown.ts +4 -4
  22. package/front_end/models/trace/insights/LCPDiscovery.ts +3 -3
  23. package/front_end/models/trace/insights/RenderBlocking.ts +1 -1
  24. package/front_end/models/trace/insights/types.ts +1 -1
  25. package/front_end/models/trace/types/TraceEvents.ts +62 -10
  26. package/front_end/panels/common/AiCodeGenerationTeaser.ts +48 -12
  27. package/front_end/panels/common/aiCodeGenerationTeaser.css +14 -0
  28. package/front_end/panels/common/common.ts +1 -1
  29. package/front_end/panels/console/consoleView.css +1 -1
  30. package/front_end/panels/elements/CSSRuleValidator.ts +38 -0
  31. package/front_end/panels/elements/ElementsTreeElement.ts +79 -58
  32. package/front_end/panels/elements/ElementsTreeOutline.ts +0 -17
  33. package/front_end/panels/elements/StylesSidebarPane.ts +15 -4
  34. package/front_end/panels/timeline/StatusDialog.ts +4 -3
  35. package/front_end/panels/timeline/TimelineFlameChartNetworkDataProvider.ts +3 -16
  36. package/front_end/panels/timeline/TimelineFlameChartView.ts +64 -21
  37. package/front_end/panels/timeline/TimelinePanel.ts +71 -24
  38. package/front_end/panels/timeline/TimelineUIUtils.ts +28 -2
  39. package/front_end/panels/timeline/TimingsTrackAppender.ts +3 -1
  40. package/front_end/panels/timeline/components/SidebarSingleInsightSet.ts +1 -1
  41. package/front_end/panels/timeline/components/insights/RenderBlocking.ts +6 -4
  42. package/front_end/panels/timeline/overlays/OverlaysImpl.ts +4 -0
  43. package/front_end/panels/timeline/timelinePanel.css +8 -1
  44. package/front_end/panels/timeline/utils/EntryNodes.ts +2 -1
  45. package/front_end/third_party/chromium/README.chromium +1 -1
  46. package/front_end/ui/components/text_editor/AiCodeGenerationProvider.ts +70 -28
  47. package/front_end/ui/legacy/SearchableView.ts +11 -5
  48. package/front_end/ui/legacy/SplitWidget.ts +1 -1
  49. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +43 -9
  50. package/front_end/ui/visual_logging/KnownContextValues.ts +13 -0
  51. package/package.json +1 -1
@@ -59,6 +59,7 @@ let traceBounds: Types.Timing.TraceWindowMicro = makeNewTraceBounds();
59
59
  */
60
60
  let navigationsByFrameId = new Map<string, Types.Events.NavigationStart[]>();
61
61
  let navigationsByNavigationId = new Map<string, Types.Events.NavigationStart>();
62
+ let softNavigationsById = new Map<number, Types.Events.SoftNavigationStart>();
62
63
  let finalDisplayUrlByNavigationId = new Map<string, string>();
63
64
  let mainFrameNavigations: Types.Events.NavigationStart[] = [];
64
65
 
@@ -92,6 +93,7 @@ const CHROME_WEB_TRACE_EVENTS = new Set([
92
93
  export function reset(): void {
93
94
  navigationsByFrameId = new Map();
94
95
  navigationsByNavigationId = new Map();
96
+ softNavigationsById = new Map();
95
97
  finalDisplayUrlByNavigationId = new Map();
96
98
  processNames = new Map();
97
99
  mainFrameNavigations = [];
@@ -324,6 +326,10 @@ export function handleEvent(event: Types.Events.Event): void {
324
326
  return;
325
327
  }
326
328
 
329
+ if (Types.Events.isSoftNavigationStart(event)) {
330
+ softNavigationsById.set(event.args.context.performanceTimelineNavigationId, event);
331
+ }
332
+
327
333
  // Update `finalDisplayUrlByNavigationId` to reflect the latest redirect for each navigation.
328
334
  if (Types.Events.isResourceSendRequest(event)) {
329
335
  if (event.args.data.resourceType !== 'Document') {
@@ -445,7 +451,14 @@ export interface MetaHandlerData {
445
451
  browserThreadId: Types.Events.ThreadID;
446
452
  gpuProcessId: Types.Events.ProcessID;
447
453
  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
+ */
448
460
  navigationsByNavigationId: Map<string, Types.Events.NavigationStart>;
461
+ softNavigationsById: Map<number, Types.Events.SoftNavigationStart>;
449
462
  /**
450
463
  * The user-visible URL displayed to users in the address bar.
451
464
  * This captures:
@@ -514,6 +527,7 @@ export function data(): MetaHandlerData {
514
527
  mainFrameURL,
515
528
  navigationsByFrameId,
516
529
  navigationsByNavigationId,
530
+ softNavigationsById,
517
531
  finalDisplayUrlByNavigationId,
518
532
  threadsInProcess,
519
533
  rendererProcessesByFrame: rendererProcessesByFrameId,
@@ -5,7 +5,9 @@
5
5
  /**
6
6
  * This handler stores page load metrics, including web vitals,
7
7
  * and exports them in the shape of a map with the following shape:
8
- * Map(FrameId -> Map(navigationID -> metrics) )
8
+ * Map(FrameId -> Map(navigation -> metrics) )
9
+ *
10
+ * Includes soft navigations.
9
11
  *
10
12
  * It also exports all markers in a trace in an array.
11
13
  *
@@ -22,14 +24,15 @@ import type {HandlerName} from './types.js';
22
24
 
23
25
  // Small helpers to make the below type easier to read.
24
26
  type FrameId = string;
25
- type NavigationId = string;
27
+ type AnyNavigationStart = Types.Events.NavigationStart|Types.Events.SoftNavigationStart;
28
+
26
29
  /**
27
30
  * This represents the metric scores for all navigations, for all frames in a trace.
28
31
  * Given a frame id, the map points to another map from navigation id to metric scores.
29
32
  * The metric scores include the event related to the metric as well as the data regarding
30
33
  * the score itself.
31
34
  */
32
- let metricScoresByFrameId = new Map<FrameId, Map<NavigationId, Map<MetricName, MetricScore>>>();
35
+ let metricScoresByFrameId = new Map<FrameId, Map<AnyNavigationStart, Map<MetricName, MetricScore>>>();
33
36
 
34
37
  /**
35
38
  * Page load events with no associated duration that happened in the
@@ -54,7 +57,7 @@ let pageLoadEventsArray: Types.Events.PageLoadEvent[] = [];
54
57
  // trace, we store that and delete the prior event. When we've parsed the
55
58
  // entire trace this set will contain all the LCP events that were used - e.g.
56
59
  // the candidates that were the actual LCP events.
57
- let selectedLCPCandidateEvents = new Set<Types.Events.LargestContentfulPaintCandidate>();
60
+ let selectedLCPCandidateEvents = new Set<Types.Events.AnyLargestContentfulPaintCandidate>();
58
61
 
59
62
  export function handleEvent(event: Types.Events.Event): void {
60
63
  if (!Types.Events.eventIsPageLoadEvent(event)) {
@@ -64,11 +67,7 @@ export function handleEvent(event: Types.Events.Event): void {
64
67
  }
65
68
 
66
69
  function storePageLoadMetricAgainstNavigationId(
67
- navigation: Types.Events.NavigationStart, event: Types.Events.PageLoadEvent): void {
68
- const navigationId = navigation.args.data?.navigationId;
69
- if (!navigationId) {
70
- throw new Error('Navigation event unexpectedly had no navigation ID.');
71
- }
70
+ navigation: AnyNavigationStart, event: Types.Events.PageLoadEvent): void {
72
71
  const frameId = getFrameIdForPageLoadEvent(event);
73
72
  const {rendererProcessesByFrame} = metaHandlerData();
74
73
 
@@ -95,7 +94,7 @@ function storePageLoadMetricAgainstNavigationId(
95
94
  const fcpTime = Types.Timing.Micro(event.ts - navigation.ts);
96
95
  const classification = scoreClassificationForFirstContentfulPaint(fcpTime);
97
96
  const metricScore = {event, metricName: MetricName.FCP, classification, navigation, timing: fcpTime};
98
- storeMetricScore(frameId, navigationId, metricScore);
97
+ storeMetricScore(frameId, navigation, metricScore);
99
98
  return;
100
99
  }
101
100
 
@@ -103,7 +102,7 @@ function storePageLoadMetricAgainstNavigationId(
103
102
  const paintTime = Types.Timing.Micro(event.ts - navigation.ts);
104
103
  const classification = ScoreClassification.UNCLASSIFIED;
105
104
  const metricScore = {event, metricName: MetricName.FP, classification, navigation, timing: paintTime};
106
- storeMetricScore(frameId, navigationId, metricScore);
105
+ storeMetricScore(frameId, navigation, metricScore);
107
106
  return;
108
107
  }
109
108
 
@@ -116,7 +115,7 @@ function storePageLoadMetricAgainstNavigationId(
116
115
  navigation,
117
116
  timing: dclTime,
118
117
  };
119
- storeMetricScore(frameId, navigationId, metricScore);
118
+ storeMetricScore(frameId, navigation, metricScore);
120
119
  return;
121
120
  }
122
121
 
@@ -129,7 +128,7 @@ function storePageLoadMetricAgainstNavigationId(
129
128
  navigation,
130
129
  timing: ttiValue,
131
130
  };
132
- storeMetricScore(frameId, navigationId, tti);
131
+ storeMetricScore(frameId, navigation, tti);
133
132
 
134
133
  const tbtValue = Helpers.Timing.milliToMicro(Types.Timing.Milli(event.args.args.total_blocking_time_ms));
135
134
  const tbt = {
@@ -139,7 +138,7 @@ function storePageLoadMetricAgainstNavigationId(
139
138
  navigation,
140
139
  timing: tbtValue,
141
140
  };
142
- storeMetricScore(frameId, navigationId, tbt);
141
+ storeMetricScore(frameId, navigation, tbt);
143
142
  return;
144
143
  }
145
144
 
@@ -152,11 +151,11 @@ function storePageLoadMetricAgainstNavigationId(
152
151
  navigation,
153
152
  timing: loadTime,
154
153
  };
155
- storeMetricScore(frameId, navigationId, metricScore);
154
+ storeMetricScore(frameId, navigation, metricScore);
156
155
  return;
157
156
  }
158
157
 
159
- if (Types.Events.isLargestContentfulPaintCandidate(event)) {
158
+ if (Types.Events.isAnyLargestContentfulPaintCandidate(event)) {
160
159
  const candidateIndex = event.args.data?.candidateIndex;
161
160
  if (!candidateIndex) {
162
161
  throw new Error('Largest Contentful Paint unexpectedly had no candidateIndex.');
@@ -170,16 +169,16 @@ function storePageLoadMetricAgainstNavigationId(
170
169
  timing: lcpTime,
171
170
  };
172
171
  const metricsByNavigation = Platform.MapUtilities.getWithDefault(metricScoresByFrameId, frameId, () => new Map());
173
- const metrics = Platform.MapUtilities.getWithDefault(metricsByNavigation, navigationId, () => new Map());
172
+ const metrics = Platform.MapUtilities.getWithDefault(metricsByNavigation, navigation, () => new Map());
174
173
  const lastLCPCandidate = metrics.get(MetricName.LCP);
175
174
  if (lastLCPCandidate === undefined) {
176
175
  selectedLCPCandidateEvents.add(lcp.event);
177
- storeMetricScore(frameId, navigationId, lcp);
176
+ storeMetricScore(frameId, navigation, lcp);
178
177
  return;
179
178
  }
180
179
  const lastLCPCandidateEvent = lastLCPCandidate.event;
181
180
 
182
- if (!Types.Events.isLargestContentfulPaintCandidate(lastLCPCandidateEvent)) {
181
+ if (!Types.Events.isAnyLargestContentfulPaintCandidate(lastLCPCandidateEvent)) {
183
182
  return;
184
183
  }
185
184
  const lastCandidateIndex = lastLCPCandidateEvent.args.data?.candidateIndex;
@@ -192,19 +191,22 @@ function storePageLoadMetricAgainstNavigationId(
192
191
  if (lastCandidateIndex < candidateIndex) {
193
192
  selectedLCPCandidateEvents.delete(lastLCPCandidateEvent);
194
193
  selectedLCPCandidateEvents.add(lcp.event);
195
- storeMetricScore(frameId, navigationId, lcp);
194
+ storeMetricScore(frameId, navigation, lcp);
196
195
  }
197
196
  return;
198
197
  }
199
198
  if (Types.Events.isLayoutShift(event)) {
200
199
  return;
201
200
  }
201
+ if (Types.Events.isSoftNavigationStart(event)) {
202
+ return;
203
+ }
202
204
  return Platform.assertNever(event, `Unexpected event type: ${event}`);
203
205
  }
204
206
 
205
- function storeMetricScore(frameId: string, navigationId: string, metricScore: MetricScore): void {
207
+ function storeMetricScore(frameId: string, navigation: AnyNavigationStart, metricScore: MetricScore): void {
206
208
  const metricsByNavigation = Platform.MapUtilities.getWithDefault(metricScoresByFrameId, frameId, () => new Map());
207
- const metrics = Platform.MapUtilities.getWithDefault(metricsByNavigation, navigationId, () => new Map());
209
+ const metrics = Platform.MapUtilities.getWithDefault(metricsByNavigation, navigation, () => new Map());
208
210
  // If an entry with that metric name is present, delete it so that the new entry that
209
211
  // will replace it is added at the end of the map. This way we guarantee the map entries
210
212
  // are ordered in ASC manner by timestamp.
@@ -214,8 +216,9 @@ function storeMetricScore(frameId: string, navigationId: string, metricScore: Me
214
216
 
215
217
  export function getFrameIdForPageLoadEvent(event: Types.Events.PageLoadEvent): string {
216
218
  if (Types.Events.isFirstContentfulPaint(event) || Types.Events.isInteractiveTime(event) ||
217
- Types.Events.isLargestContentfulPaintCandidate(event) || Types.Events.isNavigationStart(event) ||
218
- Types.Events.isLayoutShift(event) || Types.Events.isFirstPaint(event)) {
219
+ Types.Events.isAnyLargestContentfulPaintCandidate(event) || Types.Events.isNavigationStart(event) ||
220
+ Types.Events.isSoftNavigationStart(event) || Types.Events.isLayoutShift(event) ||
221
+ Types.Events.isFirstPaint(event)) {
219
222
  return event.args.frame;
220
223
  }
221
224
  if (Types.Events.isMarkDOMContent(event) || Types.Events.isMarkLoad(event)) {
@@ -228,16 +231,23 @@ export function getFrameIdForPageLoadEvent(event: Types.Events.PageLoadEvent): s
228
231
  Platform.assertNever(event, `Unexpected event type: ${event}`);
229
232
  }
230
233
 
231
- function getNavigationForPageLoadEvent(event: Types.Events.PageLoadEvent): Types.Events.NavigationStart|null {
232
- if (Types.Events.isFirstContentfulPaint(event) || Types.Events.isLargestContentfulPaintCandidate(event) ||
234
+ function getNavigationForPageLoadEvent(event: Types.Events.PageLoadEvent): AnyNavigationStart|null {
235
+ if (Types.Events.isFirstContentfulPaint(event) || Types.Events.isAnyLargestContentfulPaintCandidate(event) ||
233
236
  Types.Events.isFirstPaint(event)) {
234
- const navigationId = event.args.data?.navigationId;
235
- if (!navigationId) {
236
- throw new Error('Trace event unexpectedly had no navigation ID.');
237
+ const {navigationsByNavigationId, softNavigationsById} = metaHandlerData();
238
+ let navigation;
239
+ if (event.name === Types.Events.Name.MARK_LCP_CANDIDATE_FOR_SOFT_NAVIGATION &&
240
+ event.args.data?.performanceTimelineNavigationId) {
241
+ navigation = softNavigationsById.get(event.args.data.performanceTimelineNavigationId);
237
242
  }
238
- const {navigationsByNavigationId} = metaHandlerData();
239
- const navigation = navigationsByNavigationId.get(navigationId);
243
+ if (!navigation) {
244
+ const navigationId = event.args.data?.navigationId;
245
+ if (!navigationId) {
246
+ throw new Error('Trace event unexpectedly had no navigation ID.');
247
+ }
240
248
 
249
+ navigation = navigationsByNavigationId.get(navigationId);
250
+ }
241
251
  if (!navigation) {
242
252
  // This event's navigation has been filtered out by the meta handler as a noise event.
243
253
  return null;
@@ -245,6 +255,11 @@ function getNavigationForPageLoadEvent(event: Types.Events.PageLoadEvent): Types
245
255
  return navigation;
246
256
  }
247
257
 
258
+ if (Types.Events.isSoftNavigationStart(event)) {
259
+ const {softNavigationsById} = metaHandlerData();
260
+ return softNavigationsById.get(event.args.context.performanceTimelineNavigationId) ?? null;
261
+ }
262
+
248
263
  if (Types.Events.isMarkDOMContent(event) || Types.Events.isInteractiveTime(event) ||
249
264
  Types.Events.isLayoutShift(event) || Types.Events.isMarkLoad(event)) {
250
265
  const frameId = getFrameIdForPageLoadEvent(event);
@@ -378,7 +393,8 @@ export async function finalize(): Promise<void> {
378
393
  const allFinalLCPEvents = gatherFinalLCPEvents();
379
394
  const mainFrame = metaHandlerData().mainFrameId;
380
395
  // Filter out LCP candidates to use only definitive LCP values
381
- const allEventsButLCP = pageLoadEventsArray.filter(event => !Types.Events.isLargestContentfulPaintCandidate(event));
396
+ const allEventsButLCP =
397
+ pageLoadEventsArray.filter(event => !Types.Events.isAnyLargestContentfulPaintCandidate(event));
382
398
  const markerEvents = [...allFinalLCPEvents, ...allEventsButLCP].filter(Types.Events.isMarkerEvent);
383
399
  // Filter by main frame and sort.
384
400
  allMarkerEvents =
@@ -391,8 +407,10 @@ export interface PageLoadMetricsData {
391
407
  * Given a frame id, the map points to another map from navigation id to metric scores.
392
408
  * The metric scores include the event related to the metric as well as the data regarding
393
409
  * the score itself.
410
+ *
411
+ * Includes soft navigations.
394
412
  */
395
- metricScoresByFrameId: Map<string, Map<string, Map<MetricName, MetricScore>>>;
413
+ metricScoresByFrameId: Map<string, Map<AnyNavigationStart, Map<MetricName, MetricScore>>>;
396
414
  /**
397
415
  * Page load events with no associated duration that happened in the
398
416
  * main frame.
@@ -437,6 +455,8 @@ export const enum MetricName {
437
455
  CLS = 'CLS',
438
456
  // Navigation
439
457
  NAV = 'Nav',
458
+ // Soft Navigation (just "Nav" b/c space is limited in flame chart)
459
+ SOFT_NAV = 'Nav',
440
460
  // Note: INP is handled in UserInteractionsHandler
441
461
  }
442
462
 
@@ -445,7 +465,7 @@ export interface MetricScore {
445
465
  classification: ScoreClassification;
446
466
  event?: Types.Events.PageLoadEvent;
447
467
  // The last navigation that occurred before this metric score.
448
- navigation?: Types.Events.NavigationStart;
468
+ navigation?: AnyNavigationStart;
449
469
  estimated?: boolean;
450
470
  timing: Types.Timing.Micro;
451
471
  }
@@ -22,10 +22,17 @@ export function timeStampForEventAdjustedByClosestNavigation(
22
22
  event: Types.Events.Event,
23
23
  traceBounds: Types.Timing.TraceWindowMicro,
24
24
  navigationsByNavigationId: Map<string, Types.Events.NavigationStart>,
25
+ softNavigationsById: Map<number, Types.Events.SoftNavigationStart>,
25
26
  navigationsByFrameId: Map<string, Types.Events.NavigationStart[]>,
26
27
  ): Types.Timing.Micro {
27
28
  let eventTimeStamp = event.ts - traceBounds.min;
28
- if (event.args?.data?.navigationId) {
29
+ if (event.name === Types.Events.Name.MARK_LCP_CANDIDATE_FOR_SOFT_NAVIGATION &&
30
+ event.args?.data?.performanceTimelineNavigationId) {
31
+ const navigationForEvent = softNavigationsById.get(event.args.data.performanceTimelineNavigationId);
32
+ if (navigationForEvent) {
33
+ eventTimeStamp = event.ts - navigationForEvent.ts;
34
+ }
35
+ } else if (event.args?.data?.navigationId) {
29
36
  const navigationForEvent = navigationsByNavigationId.get(event.args.data.navigationId);
30
37
  if (navigationForEvent) {
31
38
  eventTimeStamp = event.ts - navigationForEvent.ts;
@@ -27,7 +27,7 @@ export function getInsight<InsightName extends keyof InsightModels>(
27
27
  }
28
28
 
29
29
  export function getLCP(insightSet: InsightSet):
30
- {value: Types.Timing.Micro, event: Types.Events.LargestContentfulPaintCandidate}|null {
30
+ {value: Types.Timing.Micro, event: Types.Events.AnyLargestContentfulPaintCandidate}|null {
31
31
  const insight = getInsight(InsightKeys.LCP_BREAKDOWN, insightSet);
32
32
  if (!insight || !insight.lcpMs || !insight.lcpEvent) {
33
33
  return null;
@@ -95,7 +95,7 @@ export function isLCPBreakdownInsight(model: InsightModel): model is LCPBreakdow
95
95
  export type LCPBreakdownInsightModel = InsightModel<typeof UIStrings, {
96
96
  lcpMs?: Types.Timing.Milli,
97
97
  lcpTs?: Types.Timing.Milli,
98
- lcpEvent?: Types.Events.LargestContentfulPaintCandidate,
98
+ lcpEvent?: Types.Events.AnyLargestContentfulPaintCandidate,
99
99
  /** The network request for the LCP image, if there was one. */
100
100
  lcpRequest?: Types.Events.SyntheticNetworkRequest,
101
101
  subparts?: LCPSubparts,
@@ -112,7 +112,7 @@ function anyValuesNaN(...values: number[]): boolean {
112
112
  */
113
113
  function determineSubparts(
114
114
  nav: Types.Events.NavigationStart, docRequest: Types.Events.SyntheticNetworkRequest,
115
- lcpEvent: Types.Events.LargestContentfulPaintCandidate,
115
+ lcpEvent: Types.Events.AnyLargestContentfulPaintCandidate,
116
116
  lcpRequest: Types.Events.SyntheticNetworkRequest|undefined): LCPSubparts|null {
117
117
  const firstDocByteTs = calculateDocFirstByteTs(docRequest);
118
118
  if (firstDocByteTs === null) {
@@ -218,13 +218,13 @@ export function generateInsight(
218
218
  throw new Error('no frame metrics');
219
219
  }
220
220
 
221
- const navMetrics = frameMetrics.get(context.navigationId);
221
+ const navMetrics = frameMetrics.get(context.navigation);
222
222
  if (!navMetrics) {
223
223
  throw new Error('no navigation metrics');
224
224
  }
225
225
  const metricScore = navMetrics.get(Handlers.ModelHandlers.PageLoadMetrics.MetricName.LCP);
226
226
  const lcpEvent = metricScore?.event;
227
- if (!lcpEvent || !Types.Events.isLargestContentfulPaintCandidate(lcpEvent)) {
227
+ if (!lcpEvent || !Types.Events.isAnyLargestContentfulPaintCandidate(lcpEvent)) {
228
228
  return finalize({warnings: [InsightWarning.NO_LCP]});
229
229
  }
230
230
 
@@ -66,7 +66,7 @@ export function isLCPDiscoveryInsight(model: InsightModel): model is LCPDiscover
66
66
  return model.insightKey === 'LCPDiscovery';
67
67
  }
68
68
  export type LCPDiscoveryInsightModel = InsightModel<typeof UIStrings, {
69
- lcpEvent?: Types.Events.LargestContentfulPaintCandidate,
69
+ lcpEvent?: Types.Events.AnyLargestContentfulPaintCandidate,
70
70
  /** The network request for the LCP image, if there was one. */
71
71
  lcpRequest?: Types.Events.SyntheticNetworkRequest,
72
72
  earliestDiscoveryTimeTs?: Types.Timing.Micro,
@@ -108,13 +108,13 @@ export function generateInsight(
108
108
  throw new Error('no frame metrics');
109
109
  }
110
110
 
111
- const navMetrics = frameMetrics.get(context.navigationId);
111
+ const navMetrics = frameMetrics.get(context.navigation);
112
112
  if (!navMetrics) {
113
113
  throw new Error('no navigation metrics');
114
114
  }
115
115
  const metricScore = navMetrics.get(Handlers.ModelHandlers.PageLoadMetrics.MetricName.LCP);
116
116
  const lcpEvent = metricScore?.event;
117
- if (!lcpEvent || !Types.Events.isLargestContentfulPaintCandidate(lcpEvent)) {
117
+ if (!lcpEvent || !Types.Events.isAnyLargestContentfulPaintCandidate(lcpEvent)) {
118
118
  return finalize({warnings: [InsightWarning.NO_LCP]});
119
119
  }
120
120
 
@@ -185,7 +185,7 @@ export function generateInsight(
185
185
  }
186
186
 
187
187
  const firstPaintTs = data.PageLoadMetrics.metricScoresByFrameId.get(context.frameId)
188
- ?.get(context.navigationId)
188
+ ?.get(context.navigation)
189
189
  ?.get(Handlers.ModelHandlers.PageLoadMetrics.MetricName.FP)
190
190
  ?.event?.ts;
191
191
  if (!firstPaintTs) {
@@ -96,7 +96,7 @@ export type InsightModel<UIStrings extends Record<string, string> = Record<strin
96
96
  /**
97
97
  * If this insight is attached to a navigation, this stores its ID.
98
98
  */
99
- navigationId?: string,
99
+ navigation?: Types.Events.NavigationStart,
100
100
  /** This is lazily-generated because some insights may create many overlays. */
101
101
  createOverlays?: () => Types.Overlays.Overlay[],
102
102
  };
@@ -100,6 +100,8 @@ export interface ArgsData {
100
100
  sampleTraceId?: number;
101
101
  url?: string;
102
102
  navigationId?: string;
103
+ /** For soft navs. */
104
+ performanceTimelineNavigationId?: number;
103
105
  frame?: string;
104
106
  }
105
107
 
@@ -702,6 +704,29 @@ export interface NavigationStart extends Mark {
702
704
  };
703
705
  }
704
706
 
707
+ export interface SoftNavigationStart extends Event {
708
+ name: Name.SOFT_NAVIGATION_START;
709
+ ph: Phase.ASYNC_NESTABLE_INSTANT;
710
+ args: Args&{
711
+ frame: string,
712
+ context: {
713
+ softNavContextId: number,
714
+ // eslint-disable-next-line @typescript-eslint/naming-convention
715
+ URL: string,
716
+ timeOrigin: number,
717
+ domModifications: number,
718
+ firstContentfulPaint: number,
719
+ paintedArea: number,
720
+ performanceTimelineNavigationId: number,
721
+ repaintedArea: number,
722
+ },
723
+ };
724
+ }
725
+
726
+ export function isSoftNavigationStart(event: Event): event is SoftNavigationStart {
727
+ return event.name === Name.SOFT_NAVIGATION_START;
728
+ }
729
+
705
730
  export interface FirstContentfulPaint extends Mark {
706
731
  name: Name.MARK_FCP;
707
732
  args: Args&{
@@ -722,27 +747,30 @@ export interface FirstPaint extends Mark {
722
747
  };
723
748
  }
724
749
 
725
- export type PageLoadEvent = FirstContentfulPaint|MarkDOMContent|InteractiveTime|LargestContentfulPaintCandidate|
726
- LayoutShift|FirstPaint|MarkLoad|NavigationStart;
750
+ export type PageLoadEvent = FirstContentfulPaint|MarkDOMContent|InteractiveTime|AnyLargestContentfulPaintCandidate|
751
+ LayoutShift|FirstPaint|MarkLoad|NavigationStart|SoftNavigationStart;
727
752
 
728
753
  const markerTypeGuards = [
729
754
  isMarkDOMContent,
730
755
  isMarkLoad,
731
756
  isFirstPaint,
732
757
  isFirstContentfulPaint,
733
- isLargestContentfulPaintCandidate,
758
+ isAnyLargestContentfulPaintCandidate,
734
759
  isNavigationStart,
760
+ isSoftNavigationStart,
735
761
  ];
736
762
 
737
- export const MarkerName =
738
- ['MarkDOMContent', 'MarkLoad', 'firstPaint', 'firstContentfulPaint', 'largestContentfulPaint::Candidate'] as const;
763
+ export const MarkerName = [
764
+ 'MarkDOMContent', 'MarkLoad', 'firstPaint', 'firstContentfulPaint', 'largestContentfulPaint::Candidate',
765
+ 'largestContentfulPaint::CandidateForSoftNavigation'
766
+ ] as const;
739
767
 
740
768
  export interface MarkerEvent extends Event {
741
769
  name: typeof MarkerName[number];
742
770
  }
743
771
 
744
772
  export function isMarkerEvent(event: Event): event is MarkerEvent {
745
- if (event.ph === Phase.INSTANT || event.ph === Phase.MARK) {
773
+ if (event.ph === Phase.INSTANT || Phase.ASYNC_NESTABLE_INSTANT || event.ph === Phase.MARK) {
746
774
  return markerTypeGuards.some(fn => fn(event));
747
775
  }
748
776
  return false;
@@ -754,7 +782,7 @@ const pageLoadEventTypeGuards = [
754
782
  ];
755
783
 
756
784
  export function eventIsPageLoadEvent(event: Event): event is PageLoadEvent {
757
- if (event.ph === Phase.INSTANT || event.ph === Phase.MARK) {
785
+ if (event.ph === Phase.INSTANT || Phase.ASYNC_NESTABLE_INSTANT || event.ph === Phase.MARK) {
758
786
  return pageLoadEventTypeGuards.some(fn => fn(event));
759
787
  }
760
788
  return false;
@@ -777,6 +805,28 @@ export interface LargestContentfulPaintCandidate extends Mark {
777
805
  },
778
806
  };
779
807
  }
808
+
809
+ export interface LargestContentfulPaintCandidateForSoftNavigation extends Mark {
810
+ name: Name.MARK_LCP_CANDIDATE_FOR_SOFT_NAVIGATION;
811
+ args: Args&{
812
+ frame: string,
813
+ data?: ArgsData&{
814
+ candidateIndex: number,
815
+ isOutermostMainFrame: boolean,
816
+ isMainFrame: boolean,
817
+ nodeId: Protocol.DOM.BackendNodeId,
818
+ loadingAttr: string,
819
+ performanceTimelineNavigationId: number,
820
+ type?: string,
821
+ // Landed in Chrome M140: crrev.com/c/6702010
822
+ nodeName?: string,
823
+ },
824
+ };
825
+ }
826
+
827
+ export type AnyLargestContentfulPaintCandidate =
828
+ LargestContentfulPaintCandidate|LargestContentfulPaintCandidateForSoftNavigation;
829
+
780
830
  export interface LargestImagePaintCandidate extends Mark {
781
831
  name: 'LargestImagePaint::Candidate';
782
832
  args: Args&{
@@ -2209,8 +2259,8 @@ export function isFirstContentfulPaint(event: Event): event is FirstContentfulPa
2209
2259
  return event.name === 'firstContentfulPaint';
2210
2260
  }
2211
2261
 
2212
- export function isLargestContentfulPaintCandidate(event: Event): event is LargestContentfulPaintCandidate {
2213
- return event.name === Name.MARK_LCP_CANDIDATE;
2262
+ export function isAnyLargestContentfulPaintCandidate(event: Event): event is AnyLargestContentfulPaintCandidate {
2263
+ return event.name === Name.MARK_LCP_CANDIDATE || event.name === Name.MARK_LCP_CANDIDATE_FOR_SOFT_NAVIGATION;
2214
2264
  }
2215
2265
  export function isLargestImagePaintCandidate(event: Event): event is LargestImagePaintCandidate {
2216
2266
  return event.name === 'LargestImagePaint::Candidate';
@@ -2336,7 +2386,7 @@ export function isPrePaint(
2336
2386
 
2337
2387
  /** A VALID navigation start (as it has a populated documentLoaderURL) */
2338
2388
  export function isNavigationStart(event: Event): event is NavigationStart {
2339
- return event.name === 'navigationStart' && (event as NavigationStart).args?.data?.documentLoaderURL !== '';
2389
+ return event.name === Name.NAVIGATION_START && (event as NavigationStart).args?.data?.documentLoaderURL !== '';
2340
2390
  }
2341
2391
 
2342
2392
  export interface DidCommitSameDocumentNavigation extends Complete {
@@ -3089,8 +3139,10 @@ export const enum Name {
3089
3139
  MARK_FIRST_PAINT = 'firstPaint',
3090
3140
  MARK_FCP = 'firstContentfulPaint',
3091
3141
  MARK_LCP_CANDIDATE = 'largestContentfulPaint::Candidate',
3142
+ MARK_LCP_CANDIDATE_FOR_SOFT_NAVIGATION = 'largestContentfulPaint::CandidateForSoftNavigation',
3092
3143
  MARK_LCP_INVALIDATE = 'largestContentfulPaint::Invalidate',
3093
3144
  NAVIGATION_START = 'navigationStart',
3145
+ SOFT_NAVIGATION_START = 'SoftNavigationStart',
3094
3146
  CONSOLE_TIME = 'ConsoleTime',
3095
3147
  USER_TIMING = 'UserTiming',
3096
3148
  INTERACTIVE_TIME = 'InteractiveTime',
@@ -5,7 +5,9 @@
5
5
  import * as Host from '../../core/host/host.js';
6
6
  import * as i18n from '../../core/i18n/i18n.js';
7
7
  import * as UI from '../../ui/legacy/legacy.js';
8
- import {html, render} from '../../ui/lit/lit.js';
8
+ import {html, nothing, render} from '../../ui/lit/lit.js';
9
+
10
+ import styles from './aiCodeGenerationTeaser.css.js';
9
11
 
10
12
  const UIStringsNotTranslate = {
11
13
  /**
@@ -20,23 +22,56 @@ const UIStringsNotTranslate = {
20
22
  * Text for teaser when generating suggestion.
21
23
  */
22
24
  generating: 'Generating... (esc to cancel)',
25
+ /**
26
+ * Text for teaser for discoverability.
27
+ */
28
+ writeACommentToGenerateCode: 'Write a comment to generate code',
23
29
  } as const;
24
30
 
25
31
  const lockedString = i18n.i18n.lockedString;
32
+ const PROMOTION_ID = 'ai-code-generation';
33
+
34
+ export enum AiCodeGenerationTeaserDisplayState {
35
+ TRIGGER = 'trigger',
36
+ DISCOVERY = 'discovery',
37
+ LOADING = 'loading',
38
+ }
26
39
 
27
40
  export interface ViewInput {
28
- loading: boolean;
41
+ displayState: AiCodeGenerationTeaserDisplayState;
29
42
  }
30
43
 
31
44
  export type View = (input: ViewInput, output: object, target: HTMLElement) => void;
32
45
 
33
46
  export const DEFAULT_VIEW: View = (input, _output, target) => {
34
- const toGenerateCode = Host.Platform.isMac() ? lockedString(UIStringsNotTranslate.cmdItoGenerateCode) :
35
- lockedString(UIStringsNotTranslate.ctrlItoGenerateCode);
36
- const teaserLabel = input.loading ? lockedString(UIStringsNotTranslate.generating) : toGenerateCode;
47
+ let teaserLabel;
48
+ switch (input.displayState) {
49
+ case AiCodeGenerationTeaserDisplayState.DISCOVERY: {
50
+ const newBadge = UI.UIUtils.maybeCreateNewBadge(PROMOTION_ID);
51
+ teaserLabel = newBadge ?
52
+ html`${lockedString(UIStringsNotTranslate.writeACommentToGenerateCode)}&nbsp;${newBadge}` :
53
+ nothing;
54
+ break;
55
+ }
56
+
57
+ case AiCodeGenerationTeaserDisplayState.LOADING: {
58
+ teaserLabel = html`${lockedString(UIStringsNotTranslate.generating)}`;
59
+ break;
60
+ }
61
+
62
+ case AiCodeGenerationTeaserDisplayState.TRIGGER: {
63
+ const toGenerateCode = Host.Platform.isMac() ? lockedString(UIStringsNotTranslate.cmdItoGenerateCode) :
64
+ lockedString(UIStringsNotTranslate.ctrlItoGenerateCode);
65
+ teaserLabel = html`${toGenerateCode}`;
66
+ break;
67
+ }
68
+ }
69
+
37
70
  // clang-format off
38
71
  render(
39
72
  html`
73
+ <style>${styles}</style>
74
+ <style>@scope to (devtools-widget > *) { ${UI.inspectorCommonStyles} }</style>
40
75
  <div class="ai-code-generation-teaser">
41
76
  &nbsp;${teaserLabel}
42
77
  </div>
@@ -45,10 +80,11 @@ export const DEFAULT_VIEW: View = (input, _output, target) => {
45
80
  // clang-format on
46
81
  };
47
82
 
83
+ // TODO(b/448063927): Add "Dont show again" for discovery teaser.
48
84
  export class AiCodeGenerationTeaser extends UI.Widget.Widget {
49
85
  readonly #view: View;
50
86
 
51
- #loading = false;
87
+ #displayState = AiCodeGenerationTeaserDisplayState.TRIGGER;
52
88
 
53
89
  constructor(view?: View) {
54
90
  super();
@@ -61,20 +97,20 @@ export class AiCodeGenerationTeaser extends UI.Widget.Widget {
61
97
  const output = {};
62
98
  this.#view(
63
99
  {
64
- loading: this.#loading,
100
+ displayState: this.#displayState,
65
101
  },
66
102
  output, this.contentElement);
67
103
  }
68
104
 
69
- get loading(): boolean {
70
- return this.#loading;
105
+ get displayState(): AiCodeGenerationTeaserDisplayState {
106
+ return this.#displayState;
71
107
  }
72
108
 
73
- set loading(loading: boolean) {
74
- if (loading === this.#loading) {
109
+ set displayState(displayState: AiCodeGenerationTeaserDisplayState) {
110
+ if (displayState === this.#displayState) {
75
111
  return;
76
112
  }
77
- this.#loading = loading;
113
+ this.#displayState = displayState;
78
114
  this.requestUpdate();
79
115
  }
80
116
  }