chrome-devtools-frontend 1.0.1519267 → 1.0.1520139

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 (90) hide show
  1. package/config/owner/COMMON_OWNERS +1 -2
  2. package/config/typescript/tsconfig.eslint.json +12 -1
  3. package/docs/ui_engineering.md +1011 -0
  4. package/front_end/core/host/GdpClient.ts +12 -3
  5. package/front_end/core/sdk/NetworkManager.ts +1 -0
  6. package/front_end/core/sdk/NetworkRequest.ts +10 -0
  7. package/front_end/entrypoints/main/MainImpl.ts +6 -1
  8. package/front_end/entrypoints/main/main-meta.ts +3 -3
  9. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +60 -34
  10. package/front_end/models/ai_assistance/agents/PerformanceAnnotationsAgent.ts +4 -4
  11. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +127 -29
  12. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +100 -55
  13. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +317 -640
  14. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +23 -19
  15. package/front_end/models/ai_assistance/performance/AICallTree.snapshot.txt +75 -0
  16. package/front_end/models/ai_assistance/performance/AICallTree.ts +14 -6
  17. package/front_end/models/ai_assistance/performance/AIContext.ts +62 -7
  18. package/front_end/models/ai_code_completion/AiCodeCompletion.ts +5 -5
  19. package/front_end/models/badges/Badge.ts +6 -1
  20. package/front_end/models/badges/StarterBadge.ts +5 -0
  21. package/front_end/models/javascript_metadata/NativeFunctions.js +5 -1
  22. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +14 -7
  23. package/front_end/panels/ai_assistance/PatchWidget.ts +17 -55
  24. package/front_end/panels/ai_assistance/components/ChatView.ts +44 -68
  25. package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +47 -1
  26. package/front_end/panels/ai_assistance/components/chatView.css +12 -0
  27. package/front_end/panels/animation/AnimationTimeline.ts +1 -1
  28. package/front_end/panels/animation/animationTimeline.css +4 -0
  29. package/front_end/panels/application/preloading/components/PreloadingString.ts +2 -5
  30. package/front_end/panels/common/AiCodeCompletionTeaser.ts +5 -0
  31. package/front_end/panels/common/aiCodeCompletionTeaser.css +6 -1
  32. package/front_end/panels/console/ConsolePrompt.ts +6 -0
  33. package/front_end/panels/console/ConsoleView.ts +4 -2
  34. package/front_end/panels/coverage/CoverageListView.ts +133 -158
  35. package/front_end/panels/coverage/CoverageView.ts +39 -16
  36. package/front_end/panels/mobile_throttling/NetworkThrottlingSelector.ts +2 -0
  37. package/front_end/panels/network/NetworkDataGridNode.ts +22 -0
  38. package/front_end/panels/network/NetworkLogViewColumns.ts +9 -0
  39. package/front_end/panels/recorder/components/CreateRecordingView.ts +2 -0
  40. package/front_end/panels/search/SearchResultsPane.ts +48 -15
  41. package/front_end/panels/search/SearchView.ts +33 -30
  42. package/front_end/panels/search/searchView.css +0 -2
  43. package/front_end/panels/settings/components/SyncSection.ts +3 -3
  44. package/front_end/panels/sources/AiCodeCompletionPlugin.ts +1 -4
  45. package/front_end/panels/sources/DebuggerPlugin.ts +4 -0
  46. package/front_end/panels/timeline/ThirdPartyTreeView.ts +1 -1
  47. package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +0 -8
  48. package/front_end/panels/timeline/TimelineFlameChartView.ts +5 -5
  49. package/front_end/panels/timeline/TimelinePanel.ts +2 -0
  50. package/front_end/panels/timeline/TimelineTreeView.ts +1 -1
  51. package/front_end/third_party/chromium/README.chromium +1 -1
  52. package/front_end/third_party/puppeteer/README.chromium +2 -2
  53. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Realm.d.ts +2 -2
  54. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.d.ts +1 -1
  55. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.js +1 -1
  56. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
  57. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/BrowserLauncher.js +1 -1
  58. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/BrowserLauncher.js.map +1 -1
  59. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/PipeTransport.d.ts.map +1 -1
  60. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/PipeTransport.js +15 -16
  61. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/PipeTransport.js.map +1 -1
  62. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +2 -2
  63. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +2 -2
  64. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Function.js +1 -1
  65. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Function.js.map +1 -1
  66. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  67. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +4 -4
  68. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.d.ts +1 -1
  69. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.js +1 -1
  70. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/BrowserLauncher.js +1 -1
  71. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/BrowserLauncher.js.map +1 -1
  72. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/PipeTransport.d.ts.map +1 -1
  73. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/PipeTransport.js +15 -16
  74. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/PipeTransport.js.map +1 -1
  75. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +2 -2
  76. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +2 -2
  77. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/Function.js +1 -1
  78. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/Function.js.map +1 -1
  79. package/front_end/third_party/puppeteer/package/package.json +3 -2
  80. package/front_end/third_party/puppeteer/package/src/generated/version.ts +1 -1
  81. package/front_end/third_party/puppeteer/package/src/node/BrowserLauncher.ts +1 -1
  82. package/front_end/third_party/puppeteer/package/src/node/PipeTransport.ts +15 -17
  83. package/front_end/third_party/puppeteer/package/src/revisions.ts +2 -2
  84. package/front_end/third_party/puppeteer/package/src/util/Function.ts +1 -1
  85. package/front_end/tsconfig.json +12 -1
  86. package/front_end/ui/legacy/InspectorView.ts +86 -13
  87. package/front_end/ui/legacy/TabbedPane.ts +2 -1
  88. package/front_end/ui/visual_logging/KnownContextValues.ts +6 -0
  89. package/front_end/ui/visual_logging/LoggingEvents.ts +1 -1
  90. package/package.json +1 -1
@@ -68,6 +68,11 @@ export interface Profile {
68
68
  };
69
69
  }
70
70
 
71
+ interface InitializeResult {
72
+ hasProfile: boolean;
73
+ isEligible: boolean;
74
+ }
75
+
71
76
  // The `batchGet` awards endpoint returns badge names with an
72
77
  // obfuscated user ID (e.g., `profiles/12345/awards/badge-name`).
73
78
  // This function normalizes them to use `me` instead of the ID
@@ -114,9 +119,13 @@ export class GdpClient {
114
119
  return gdpClientInstance;
115
120
  }
116
121
 
117
- async initialize(): Promise<void> {
118
- void this.getProfile();
119
- void this.checkEligibility();
122
+ async initialize(): Promise<InitializeResult> {
123
+ return await Promise.all([this.getProfile(), this.checkEligibility()]).then(([profile, eligibilityResponse]) => {
124
+ return {
125
+ hasProfile: Boolean(profile),
126
+ isEligible: eligibilityResponse?.createProfile === EligibilityStatus.ELIGIBLE
127
+ };
128
+ });
120
129
  }
121
130
 
122
131
  async getProfile(): Promise<Profile|null> {
@@ -597,6 +597,7 @@ export class NetworkDispatcher implements ProtocolProxyApi.NetworkDispatcher {
597
597
  networkRequest.mixedContentType = request.mixedContentType || Protocol.Security.MixedContentType.None;
598
598
  networkRequest.setReferrerPolicy(request.referrerPolicy);
599
599
  networkRequest.setIsSameSite(request.isSameSite || false);
600
+ networkRequest.setIsAdRelated(request.isAdRelated || false);
600
601
  }
601
602
 
602
603
  private updateNetworkRequestWithResponse(networkRequest: NetworkRequest, response: Protocol.Network.Response): void {
@@ -320,6 +320,7 @@ export class NetworkRequest extends Common.ObjectWrapper.ObjectWrapper<EventType
320
320
  directSocketInfo?: DirectSocketInfo;
321
321
  readonly #directSocketChunks: DirectSocketChunk[] = [];
322
322
  #isIpProtectionUsed: boolean;
323
+ #isAdRelated: boolean;
323
324
 
324
325
  constructor(
325
326
  requestId: string,
@@ -342,6 +343,7 @@ export class NetworkRequest extends Common.ObjectWrapper.ObjectWrapper<EventType
342
343
  this.#initiator = initiator;
343
344
  this.#hasUserGesture = hasUserGesture;
344
345
  this.#isIpProtectionUsed = false;
346
+ this.#isAdRelated = false;
345
347
  }
346
348
 
347
349
  static create(
@@ -1850,6 +1852,14 @@ export class NetworkRequest extends Common.ObjectWrapper.ObjectWrapper<EventType
1850
1852
  return this.#isIpProtectionUsed;
1851
1853
  }
1852
1854
 
1855
+ setIsAdRelated(isAdRelated: boolean): void {
1856
+ this.#isAdRelated = isAdRelated;
1857
+ }
1858
+
1859
+ isAdRelated(): boolean {
1860
+ return this.#isAdRelated;
1861
+ }
1862
+
1853
1863
  getAssociatedData(key: string): object|null {
1854
1864
  return this.#associatedData.get(key) || null;
1855
1865
  }
@@ -519,7 +519,12 @@ export class MainImpl {
519
519
 
520
520
  // Initialize `GDPClient` and `UserBadges` for Google Developer Program integration
521
521
  if (Host.GdpClient.isGdpProfilesAvailable()) {
522
- void Host.GdpClient.GdpClient.instance().initialize();
522
+ void Host.GdpClient.GdpClient.instance().initialize().then(({hasProfile, isEligible}) => {
523
+ const contextString = hasProfile ? 'has-profile' :
524
+ isEligible ? 'no-profile-and-eligible' :
525
+ 'no-profile-and-not-eligible';
526
+ void VisualLogging.logFunctionCall('gdp-client-initialize', contextString);
527
+ });
523
528
  void Badges.UserBadges.instance().initialize();
524
529
  Badges.UserBadges.instance().addEventListener(Badges.Events.BADGE_TRIGGERED, async ev => {
525
530
  loadedPanelCommonModule ??= await import('../../panels/common/common.js') as typeof PanelCommon;
@@ -188,9 +188,9 @@ const UIStrings = {
188
188
  browserLanguage: 'Browser UI language',
189
189
  /**
190
190
  * @description Label for a checkbox in the settings UI. Allows developers to opt-in/opt-out
191
- * of syncing DevTools settings via Chrome Sync.
191
+ * of saving settings to their Google account.
192
192
  */
193
- enableSync: 'Enable settings sync',
193
+ saveSettings: 'Save `DevTools` settings to your `Google` account',
194
194
  /**
195
195
  * @description Label for a checkbox in the settings UI. Allows developers to opt-in/opt-out
196
196
  * of receiving Google Developer Program (GDP) badges based on their activity in Chrome DevTools.
@@ -789,7 +789,7 @@ Common.Settings.registerSettingExtension({
789
789
  // This name must be kept in sync with DevToolsSettings::kSyncDevToolsPreferencesFrontendName.
790
790
  settingName: 'sync-preferences',
791
791
  settingType: Common.Settings.SettingType.BOOLEAN,
792
- title: i18nLazyString(UIStrings.enableSync),
792
+ title: i18nLazyString(UIStrings.saveSettings),
793
793
  defaultValue: false,
794
794
  reloadRequired: true,
795
795
  });
@@ -136,13 +136,23 @@ When referring to a trace event that has a corresponding \`eventKey\`, annotate
136
136
  When asking the user to make a choice between multiple options, output a list of choices at the end of your text response. The format is \`SUGGESTIONS: ["suggestion1", "suggestion2", "suggestion3"]\`. This MUST start on a newline, and be a single line.
137
137
  `;
138
138
 
139
+ const extraPreambleWhenFreshTrace = `Additional notes:
140
+
141
+ When referring to an element for which you know the nodeId, annotate your output using markdown link syntax:
142
+ - For example, if nodeId is 23: [LCP element](#node-23)
143
+ - This link will reveal the element in the Elements panel
144
+ - Never mention node or nodeId when referring to the element, and especially not in the link text.
145
+ - When referring to the LCP, it's useful to also mention what the LCP element is via its nodeId. Use the markdown link syntax to do so.
146
+ `;
147
+
139
148
  const callFrameDataFormatDescription = `Each call frame is presented in the following format:
140
149
 
141
- 'id;name;duration;selfTime;urlIndex;childRange;[S]'
150
+ 'id;eventKey;name;duration;selfTime;urlIndex;childRange;[S]'
142
151
 
143
152
  Key definitions:
144
153
 
145
154
  * id: A unique numerical identifier for the call frame. Never mention this id in the output to the user.
155
+ * eventKey: String that uniquely identifies this event in the flame chart.
146
156
  * name: A concise string describing the call frame (e.g., 'Evaluate Script', 'render', 'fetchData').
147
157
  * duration: The total execution time of the call frame, including its children.
148
158
  * selfTime: The time spent directly within the call frame, excluding its children's execution.
@@ -152,11 +162,11 @@ Key definitions:
152
162
 
153
163
  Example Call Tree:
154
164
 
155
- 1;main;500;100;;
156
- 2;update;200;50;;3
157
- 3;animate;150;20;0;4-5;S
158
- 4;calculatePosition;80;80;;
159
- 5;applyStyles;50;50;;
165
+ 1;r-123;main;500;100;;
166
+ 2;r-124;update;200;50;;3
167
+ 3;p-49575-15428179-2834-374;animate;150;20;0;4-5;S
168
+ 4;p-49575-15428179-3505-1162;calculatePosition;80;80;;
169
+ 5;p-49575-15428179-5391-2767;applyStyles;50;50;;
160
170
  `;
161
171
 
162
172
  enum ScorePriority {
@@ -188,7 +198,7 @@ export class PerformanceTraceContext extends ConversationContext<AgentFocus> {
188
198
  }
189
199
 
190
200
  override getOrigin(): string {
191
- const {min, max} = this.#focus.data.parsedTrace.data.Meta.traceBounds;
201
+ const {min, max} = this.#focus.parsedTrace.data.Meta.traceBounds;
192
202
  return `trace-${min}-${max}`;
193
203
  }
194
204
 
@@ -197,7 +207,7 @@ export class PerformanceTraceContext extends ConversationContext<AgentFocus> {
197
207
  }
198
208
 
199
209
  override getTitle(): string {
200
- const focus = this.#focus.data;
210
+ const focus = this.#focus;
201
211
 
202
212
  let url = focus.insightSet?.url;
203
213
  if (!url) {
@@ -208,6 +218,9 @@ export class PerformanceTraceContext extends ConversationContext<AgentFocus> {
208
218
  if (focus.insight) {
209
219
  parts.push(focus.insight.title);
210
220
  }
221
+ if (focus.event) {
222
+ parts.push(Trace.Name.forEntry(focus.event));
223
+ }
211
224
  if (focus.callTree) {
212
225
  const node = focus.callTree.selectedNode ?? focus.callTree.rootNode;
213
226
  parts.push(Trace.Name.forEntry(node.event));
@@ -220,9 +233,9 @@ export class PerformanceTraceContext extends ConversationContext<AgentFocus> {
220
233
  * "Ask AI".
221
234
  */
222
235
  override async getSuggestions(): Promise<ConversationSuggestions|undefined> {
223
- const data = this.#focus.data;
236
+ const focus = this.#focus;
224
237
 
225
- if (data.callTree) {
238
+ if (focus.callTree) {
226
239
  return [
227
240
  {title: 'What\'s the purpose of this work?', jslogContext: 'performance-default'},
228
241
  {title: 'Where is time being spent?', jslogContext: 'performance-default'},
@@ -230,17 +243,17 @@ export class PerformanceTraceContext extends ConversationContext<AgentFocus> {
230
243
  ];
231
244
  }
232
245
 
233
- if (data.insight) {
234
- return new PerformanceInsightFormatter(this.#focus, data.insight).getSuggestions();
246
+ if (focus.insight) {
247
+ return new PerformanceInsightFormatter(focus, focus.insight).getSuggestions();
235
248
  }
236
249
 
237
250
  const suggestions: ConversationSuggestions =
238
251
  [{title: 'What performance issues exist with my page?', jslogContext: 'performance-default'}];
239
252
 
240
- if (data.insightSet) {
241
- const lcp = data.insightSet ? Trace.Insights.Common.getLCP(data.insightSet) : null;
242
- const cls = data.insightSet ? Trace.Insights.Common.getCLS(data.insightSet) : null;
243
- const inp = data.insightSet ? Trace.Insights.Common.getINP(data.insightSet) : null;
253
+ if (focus.insightSet) {
254
+ const lcp = focus.insightSet ? Trace.Insights.Common.getLCP(focus.insightSet) : null;
255
+ const cls = focus.insightSet ? Trace.Insights.Common.getCLS(focus.insightSet) : null;
256
+ const inp = focus.insightSet ? Trace.Insights.Common.getINP(focus.insightSet) : null;
244
257
 
245
258
  const ModelHandlers = Trace.Handlers.ModelHandlers;
246
259
  const GOOD = Trace.Handlers.ModelHandlers.PageLoadMetrics.ScoreClassification.GOOD;
@@ -257,9 +270,9 @@ export class PerformanceTraceContext extends ConversationContext<AgentFocus> {
257
270
 
258
271
  // Add up to 3 suggestions from the top failing insights.
259
272
  const top3FailingInsightSuggestions =
260
- Object.values(data.insightSet.model)
273
+ Object.values(focus.insightSet.model)
261
274
  .filter(model => model.state !== 'pass')
262
- .map(model => new PerformanceInsightFormatter(this.#focus, model).getSuggestions().at(-1))
275
+ .map(model => new PerformanceInsightFormatter(focus, model).getSuggestions().at(-1))
263
276
  .filter(suggestion => !!suggestion)
264
277
  .slice(0, 3);
265
278
  suggestions.push(...top3FailingInsightSuggestions);
@@ -278,6 +291,7 @@ const MAX_FUNCTION_RESULT_BYTE_LENGTH = 16384 * 4;
278
291
  */
279
292
  export class PerformanceAgent extends AiAgent<AgentFocus> {
280
293
  #formatter: PerformanceTraceFormatter|null = null;
294
+ #lastEventForEnhancedQuery: Trace.Types.Events.Event|undefined;
281
295
  #lastInsightForEnhancedQuery: Trace.Insights.Types.InsightModel|undefined;
282
296
  #hasShownAnalyzeTraceContext = false;
283
297
 
@@ -298,6 +312,10 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
298
312
  text: extraPreambleWhenNotExternal,
299
313
  metadata: {source: 'devtools', score: ScorePriority.CRITICAL}
300
314
  };
315
+ #freshTraceExtraPreambleFact: Host.AidaClient.RequestFact = {
316
+ text: extraPreambleWhenFreshTrace,
317
+ metadata: {source: 'devtools', score: ScorePriority.CRITICAL}
318
+ };
301
319
  #networkDataDescriptionFact: Host.AidaClient.RequestFact = {
302
320
  text: PerformanceTraceFormatter.networkDataFormatDescription,
303
321
  metadata: {source: 'devtools', score: ScorePriority.CRITICAL}
@@ -371,10 +389,6 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
371
389
  * 1. go to paulirish.com, record a trace
372
390
  * 2. say "What performance issues exist with my page?"
373
391
  * 3. then say "images"
374
- *
375
- * TODO(cjamcl): reduce the reliance on this by making sure all URL references
376
- * (such as the insight formatters) add the "eventKey" as a suffix, just like all
377
- * other events.
378
392
  */
379
393
  #parseForKnownUrls(response: string): string {
380
394
  const focus = this.context?.getItem();
@@ -403,8 +417,7 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
403
417
  return match;
404
418
  }
405
419
 
406
- const request =
407
- focus.data.parsedTrace.data.NetworkRequests.byTime.find(request => request.args.data.url === urlText);
420
+ const request = focus.parsedTrace.data.NetworkRequests.byTime.find(request => request.args.data.url === urlText);
408
421
  if (!request) {
409
422
  return match;
410
423
  }
@@ -451,13 +464,21 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
451
464
  const focus = context.getItem();
452
465
  const selected: string[] = [];
453
466
 
454
- if (focus.data.callTree) {
467
+ if (focus.event) {
468
+ const includeEventInfo = focus.event !== this.#lastEventForEnhancedQuery;
469
+ this.#lastEventForEnhancedQuery = focus.event;
470
+ if (includeEventInfo) {
471
+ selected.push(`User selected an event ${this.#formatter?.serializeEvent(focus.event)}.\n\n`);
472
+ }
473
+ }
474
+
475
+ if (focus.callTree) {
455
476
  // If this is a followup chat about the same call tree, don't include the call tree serialization again.
456
477
  // We don't need to repeat it and we'd rather have more the context window space.
457
478
  let contextString = '';
458
- if (!this.#callTreeContextSet.has(focus.data.callTree)) {
459
- contextString = focus.data.callTree.serialize();
460
- this.#callTreeContextSet.add(focus.data.callTree);
479
+ if (!this.#callTreeContextSet.has(focus.callTree)) {
480
+ contextString = focus.callTree.serialize();
481
+ this.#callTreeContextSet.add(focus.callTree);
461
482
  }
462
483
 
463
484
  if (contextString) {
@@ -465,17 +486,17 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
465
486
  }
466
487
  }
467
488
 
468
- if (focus.data.insight) {
489
+ if (focus.insight) {
469
490
  // We only need to add Insight info to a prompt when the context changes. For example:
470
491
  // User clicks Insight A. We need to send info on Insight A with the prompt.
471
492
  // User asks follow up question. We do not need to resend Insight A with the prompt.
472
493
  // User clicks Insight B. We now need to send info on Insight B with the prompt.
473
494
  // User clicks Insight A. We should resend the Insight info with the prompt.
474
- const includeInsightInfo = focus.data.insight !== this.#lastInsightForEnhancedQuery;
475
- this.#lastInsightForEnhancedQuery = focus.data.insight;
495
+ const includeInsightInfo = focus.insight !== this.#lastInsightForEnhancedQuery;
496
+ this.#lastInsightForEnhancedQuery = focus.insight;
476
497
 
477
498
  if (includeInsightInfo) {
478
- selected.push(`User selected the ${focus.data.insight.insightKey} insight.\n\n`);
499
+ selected.push(`User selected the ${focus.insight.insightKey} insight.\n\n`);
479
500
  }
480
501
  }
481
502
 
@@ -587,11 +608,16 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
587
608
  this.addFact(this.#notExternalExtraPreambleFact);
588
609
  }
589
610
 
611
+ const isFresh = Tracing.FreshRecording.Tracker.instance().recordingIsFresh(focus.parsedTrace);
612
+ if (isFresh) {
613
+ this.addFact(this.#freshTraceExtraPreambleFact);
614
+ }
615
+
590
616
  this.addFact(this.#callFrameDataDescriptionFact);
591
617
  this.addFact(this.#networkDataDescriptionFact);
592
618
 
593
619
  if (!this.#traceFacts.length) {
594
- this.#formatter = new PerformanceTraceFormatter(focus);
620
+ this.#formatter = new PerformanceTraceFormatter(focus, PerformanceInsightFormatter.create);
595
621
  this.#createFactForTraceSummary();
596
622
  this.#createFactForCriticalRequests();
597
623
  this.#createFactForMainThreadBottomUpSummary();
@@ -623,7 +649,7 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
623
649
 
624
650
  #declareFunctions(context: PerformanceTraceContext): void {
625
651
  const focus = context.getItem();
626
- const {parsedTrace, insightSet} = focus.data;
652
+ const {parsedTrace, insightSet} = focus;
627
653
 
628
654
  this.declareFunction<{insightName: string}, {details: string}>('getInsightDetails', {
629
655
  description:
@@ -116,11 +116,11 @@ export class PerformanceAnnotationsAgent extends AiAgent<AgentFocus> {
116
116
  }
117
117
 
118
118
  const focus = context.getItem();
119
- if (!focus.data.callTree) {
119
+ if (!focus.callTree) {
120
120
  throw new Error('unexpected context');
121
121
  }
122
122
 
123
- const callTree = focus.data.callTree;
123
+ const callTree = focus.callTree;
124
124
 
125
125
  yield {
126
126
  type: ResponseType.CONTEXT,
@@ -140,11 +140,11 @@ export class PerformanceAnnotationsAgent extends AiAgent<AgentFocus> {
140
140
  }
141
141
 
142
142
  const focus = context.getItem();
143
- if (!focus.data.callTree) {
143
+ if (!focus.callTree) {
144
144
  throw new Error('unexpected context');
145
145
  }
146
146
 
147
- const callTree = focus.data.callTree;
147
+ const callTree = focus.callTree;
148
148
  const contextString = callTree.serialize();
149
149
  return `${contextString}\n\n# User request\n\n${query}`;
150
150
  }
@@ -7,7 +7,7 @@ This insight is used to analyze the time spent that contributed to the final LCP
7
7
 
8
8
  ## Detailed analysis:
9
9
  The Largest Contentful Paint (LCP) time for this navigation was 240 ms.
10
- The LCP element (IMG) is an image fetched from `https://creativetouchrotherham.co.uk/images/creative-touch-home/creative_touch_rotherham_homehero/creative_touch_rotherham_homehero_700.webp`.
10
+ The LCP element (IMG, nodeId: 23) is an image fetched from https://creativetouchrotherham.co.uk/images/creative-touch-home/creative_touch_rotherham_homehero/creative_touch_rotherham_homehero_700.webp (eventKey: s-1418).
11
11
  ## LCP resource network request: https://creativetouchrotherham.co.uk/images/creative-touch-home/creative_touch_rotherham_homehero/creative_touch_rotherham_homehero_700.webp
12
12
  eventKey: s-1418
13
13
  Timings:
@@ -68,7 +68,7 @@ This insight is used to analyze the time spent that contributed to the final LCP
68
68
 
69
69
  ## Detailed analysis:
70
70
  The Largest Contentful Paint (LCP) time for this navigation was 129 ms.
71
- The LCP element is an image fetched from `https://web-dev.imgix.net/image/kheDArv5csY6rvQUJDbWRscckLr1/4i7JstVZvgTFk9dxCe4a.svg`.
71
+ The LCP element is an image fetched from https://web-dev.imgix.net/image/kheDArv5csY6rvQUJDbWRscckLr1/4i7JstVZvgTFk9dxCe4a.svg (eventKey: s-1314).
72
72
  ## LCP resource network request: https://web-dev.imgix.net/image/kheDArv5csY6rvQUJDbWRscckLr1/4i7JstVZvgTFk9dxCe4a.svg
73
73
  eventKey: s-1314
74
74
  Timings:
@@ -186,7 +186,7 @@ It is important that all of these checks pass to minimize the delay between the
186
186
 
187
187
  ## Detailed analysis:
188
188
  The Largest Contentful Paint (LCP) time for this navigation was 1,077 ms.
189
- The LCP element is an image fetched from `http://localhost:8787/lcp-discovery-delay/lcp-image.jpg`.
189
+ The LCP element is an image fetched from http://localhost:8787/lcp-discovery-delay/lcp-image.jpg (eventKey: s-25281).
190
190
  ## LCP resource network request: http://localhost:8787/lcp-discovery-delay/lcp-image.jpg
191
191
  eventKey: s-25281
192
192
  Timings:
@@ -310,7 +310,7 @@ Layout shifts in this cluster:
310
310
  - Start time: 472 ms
311
311
  - Score: 0.0003
312
312
  - Potential root causes:
313
- - A font that was loaded over the network (https://fonts.gstatic.com/s/specialgothicexpandedone/v2/IurO6Zxk74-YaYk1r3HOet4g75ENmBxUmOK61tA0Iu5QmJF_.woff2).
313
+ - A font that was loaded over the network: https://fonts.gstatic.com/s/specialgothicexpandedone/v2/IurO6Zxk74-YaYk1r3HOet4g75ENmBxUmOK61tA0Iu5QmJF_.woff2 (eventKey: s-1158).
314
314
  ### Layout shift 2:
315
315
  - Start time: 857 ms
316
316
  - Score: 0.0844
@@ -320,12 +320,12 @@ Layout shifts in this cluster:
320
320
  - Start time: 1,352 ms
321
321
  - Score: 0.0068
322
322
  - Potential root causes:
323
- - An unsized image (IMG) (url: http://localhost:8000/unsized-image.png).
323
+ - An unsized image (IMG) (url: http://localhost:8000/unsized-image.png (eventKey: s-4487)).
324
324
  ### Layout shift 4:
325
325
  - Start time: 1,537 ms
326
326
  - Score: 0.3344
327
327
  - Potential root causes:
328
- - An unsized image (IMG) (url: http://localhost:8000/unsized-image.png).
328
+ - An unsized image (IMG) (url: http://localhost:8000/unsized-image.png (eventKey: s-4487)).
329
329
  ### Layout shift 5:
330
330
  - Start time: 2,343 ms
331
331
  - Score: 0.3396
@@ -338,6 +338,104 @@ Layout shifts in this cluster:
338
338
  - https://web.dev/articles/optimize-cls
339
339
  === end content
340
340
 
341
+ Title: PerformanceInsightFormatter CLS serializes correctly when there are no layout shifts
342
+ Content:
343
+ ## Insight Title: Layout shift culprits
344
+
345
+ ## Insight Summary:
346
+ Cumulative Layout Shifts (CLS) is a measure of the largest burst of layout shifts for every unexpected layout shift that occurs during the lifecycle of a page. This is a Core Web Vital and the thresholds for categorizing a score are:
347
+ - Good: 0.1 or less
348
+ - Needs improvement: more than 0.1 and less than or equal to 0.25
349
+ - Bad: over 0.25
350
+
351
+ ## Detailed analysis:
352
+ No layout shifts were found.
353
+
354
+ ## Estimated savings: none
355
+
356
+ ## External resources:
357
+ - https://wdeb.dev/articles/cls
358
+ - https://web.dev/articles/optimize-cls
359
+ === end content
360
+
361
+ Title: PerformanceInsightFormatter CLS outputs information on non-composited animations
362
+ Content:
363
+ ## Insight Title: Layout shift culprits
364
+
365
+ ## Insight Summary:
366
+ Cumulative Layout Shifts (CLS) is a measure of the largest burst of layout shifts for every unexpected layout shift that occurs during the lifecycle of a page. This is a Core Web Vital and the thresholds for categorizing a score are:
367
+ - Good: 0.1 or less
368
+ - Needs improvement: more than 0.1 and less than or equal to 0.25
369
+ - Bad: over 0.25
370
+
371
+ ## Detailed analysis:
372
+ The worst layout shift cluster was the cluster that started at 60 ms and ended at 1,243 ms, with a duration of 1,183 ms.
373
+ The score for this cluster is 0.0140.
374
+
375
+ Layout shifts in this cluster:
376
+ ### Layout shift 1:
377
+ - Start time: 60 ms
378
+ - Score: 0.0012
379
+ - No potential root causes identified
380
+ ### Layout shift 2:
381
+ - Start time: 76 ms
382
+ - Score: 0.0012
383
+ - No potential root causes identified
384
+ ### Layout shift 3:
385
+ - Start time: 93 ms
386
+ - Score: 0.0012
387
+ - No potential root causes identified
388
+ ### Layout shift 4:
389
+ - Start time: 110 ms
390
+ - Score: 0.0012
391
+ - No potential root causes identified
392
+ ### Layout shift 5:
393
+ - Start time: 126 ms
394
+ - Score: 0.0012
395
+ - No potential root causes identified
396
+ ### Layout shift 6:
397
+ - Start time: 143 ms
398
+ - Score: 0.0012
399
+ - No potential root causes identified
400
+ ### Layout shift 7:
401
+ - Start time: 160 ms
402
+ - Score: 0.0012
403
+ - No potential root causes identified
404
+ ### Layout shift 8:
405
+ - Start time: 176 ms
406
+ - Score: 0.0012
407
+ - No potential root causes identified
408
+ ### Layout shift 9:
409
+ - Start time: 193 ms
410
+ - Score: 0.0012
411
+ - No potential root causes identified
412
+ ### Layout shift 10:
413
+ - Start time: 210 ms
414
+ - Score: 0.0012
415
+ - No potential root causes identified
416
+ ### Layout shift 11:
417
+ - Start time: 226 ms
418
+ - Score: 0.0012
419
+ - No potential root causes identified
420
+ ### Layout shift 12:
421
+ - Start time: 243 ms
422
+ - Score: 0.0012
423
+ - Potential root causes:
424
+ - A non-composited animation:
425
+ - non-composited animation: `change-height`
426
+ Animation name: change-height
427
+ Unsupported CSS properties:
428
+ - height
429
+ Failure reasons:
430
+ - TARGET_HAS_INVALID_COMPOSITING_STATE, UNSUPPORTED_CSS_PROPERTY
431
+
432
+ ## Estimated savings: none
433
+
434
+ ## External resources:
435
+ - https://wdeb.dev/articles/cls
436
+ - https://web.dev/articles/optimize-cls
437
+ === end content
438
+
341
439
  Title: PerformanceInsightFormatter INP breakdown serializes the correct details
342
440
  Content:
343
441
  ## Insight Title: INP breakdown
@@ -589,16 +687,16 @@ Total legacy JavaScript: 8 files.
589
687
 
590
688
  Legacy JavaScript by file:
591
689
 
592
- - Script: https://s.yimg.com/aaq/benji/benji-2.2.99.js - Wasted bytes: 37204 bytes
690
+ - Script: https://s.yimg.com/aaq/benji/benji-2.2.99.js (eventKey: s-3387) - Wasted bytes: 37204 bytes
593
691
  Matches:
594
692
  Line: 0, Column: 133, Name: Promise.allSettled
595
693
 
596
- - Script: https://s.yimg.com/aaq/c/25fa214.caas-news_web.min.js - Wasted bytes: 36084 bytes
694
+ - Script: https://s.yimg.com/aaq/c/25fa214.caas-news_web.min.js (eventKey: s-3412) - Wasted bytes: 36084 bytes
597
695
  Matches:
598
696
  Line: 0, Column: 13310, Name: Array.from
599
697
  Line: 0, Column: 14623, Name: Object.assign
600
698
 
601
- - Script: https://s.yimg.com/du/ay/wnsrvbjmeprtfrnfx.js - Wasted bytes: 12850 bytes
699
+ - Script: https://s.yimg.com/du/ay/wnsrvbjmeprtfrnfx.js (eventKey: s-6273) - Wasted bytes: 12850 bytes
602
700
  Matches:
603
701
  Line: 111, Column: 7829, Name: @babel/plugin-transform-spread
604
702
  Line: 111, Column: 1794, Name: Array.prototype.find
@@ -607,21 +705,21 @@ Line: 111, Column: 2748, Name: Object.values
607
705
  Line: 111, Column: 2473, Name: String.prototype.includes
608
706
  Line: 111, Column: 2627, Name: String.prototype.startsWith
609
707
 
610
- - Script: https://static.criteo.net/js/ld/publishertag.prebid.144.js - Wasted bytes: 10751 bytes
708
+ - Script: https://static.criteo.net/js/ld/publishertag.prebid.144.js (eventKey: s-257378) - Wasted bytes: 10751 bytes
611
709
  Matches:
612
710
  Line: 1, Column: 74871, Name: Array.isArray
613
711
  Line: 1, Column: 75344, Name: Array.prototype.filter
614
712
  Line: 1, Column: 75013, Name: Array.prototype.indexOf
615
713
 
616
- - Script: https://s.yimg.com/oa/consent.js - Wasted bytes: 8157 bytes
714
+ - Script: https://s.yimg.com/oa/consent.js (eventKey: s-3384) - Wasted bytes: 8157 bytes
617
715
  Matches:
618
716
  Line: 1, Column: 132267, Name: Array.prototype.includes
619
717
 
620
- - Script: https://pm-widget.taboola.com/yahooweb-network/pmk-20220605.1.js - Wasted bytes: 7625 bytes
718
+ - Script: https://pm-widget.taboola.com/yahooweb-network/pmk-20220605.1.js (eventKey: s-52229) - Wasted bytes: 7625 bytes
621
719
  Matches:
622
720
  Line: 181, Column: 26, Name: Object.keys
623
721
 
624
- - Script: https://news.yahoo.com/ - Wasted bytes: 7141 bytes
722
+ - Script: https://news.yahoo.com/ (eventKey: s-2116) - Wasted bytes: 7141 bytes
625
723
  Matches:
626
724
  Line: 0, Column: 8382, Name: @babel/plugin-transform-classes
627
725
  Line: 0, Column: 107712, Name: Array.prototype.filter
@@ -629,7 +727,7 @@ Line: 0, Column: 107393, Name: Array.prototype.forEach
629
727
  Line: 0, Column: 108005, Name: Array.prototype.map
630
728
  Line: 0, Column: 108358, Name: String.prototype.includes
631
729
 
632
- - Script: https://cdn.taboola.com/libtrc/yahooweb-network/loader.js - Wasted bytes: 7061 bytes
730
+ - Script: https://cdn.taboola.com/libtrc/yahooweb-network/loader.js (eventKey: s-6352) - Wasted bytes: 7061 bytes
633
731
  Matches:
634
732
  Line: 0, Column: 390544, Name: Object.entries
635
733
  Line: 0, Column: 390688, Name: Object.values
@@ -669,12 +767,12 @@ This insight identifies font issues when a webpage uses custom fonts, for exampl
669
767
  ## Detailed analysis:
670
768
  The following font display issues were found:
671
769
 
672
- - Font name: jizaRExUiTo99u79D0KExcOPIDU.woff2, URL: https://fonts.gstatic.com/s/ptsans/v17/jizaRExUiTo99u79D0KExcOPIDU.woff2, Property 'font-display' set to: 'auto', Wasted time: 20 ms.
673
- - Font name: SlGVmQWMvZQIdix7AFxXkHNSbRYXags.woff2, URL: https://fonts.gstatic.com/s/droidsans/v18/SlGVmQWMvZQIdix7AFxXkHNSbRYXags.woff2, Property 'font-display' set to: 'auto', Wasted time: 15 ms.
674
- - Font name: jizfRExUiTo99u79B_mh0O6tLR8a8zI.woff2, URL: https://fonts.gstatic.com/s/ptsans/v17/jizfRExUiTo99u79B_mh0O6tLR8a8zI.woff2, Property 'font-display' set to: 'auto', Wasted time: 15 ms.
675
- - Font name: SlGWmQWMvZQIdix7AFxXmMh3eDs1ZyHKpWg.woff2, URL: https://fonts.gstatic.com/s/droidsans/v18/SlGWmQWMvZQIdix7AFxXmMh3eDs1ZyHKpWg.woff2, Property 'font-display' set to: 'auto', Wasted time: 15 ms.
676
- - Font name: EJRVQgYoZZY2vCFuvAFWzr-_dSb_.woff2, URL: https://fonts.gstatic.com/s/ptserif/v18/EJRVQgYoZZY2vCFuvAFWzr-_dSb_.woff2, Property 'font-display' set to: 'auto', Wasted time: 15 ms.
677
- - Font name: S6u9w4BMUTPHh6UVSwiPGQ3q5d0.woff2, URL: https://fonts.gstatic.com/s/lato/v24/S6u9w4BMUTPHh6UVSwiPGQ3q5d0.woff2, Property 'font-display' set to: 'auto', Wasted time: 10 ms.
770
+ - Font name: jizaRExUiTo99u79D0KExcOPIDU.woff2, URL: https://fonts.gstatic.com/s/ptsans/v17/jizaRExUiTo99u79D0KExcOPIDU.woff2 (eventKey: s-5246), Property 'font-display' set to: 'auto', Wasted time: 20 ms.
771
+ - Font name: SlGVmQWMvZQIdix7AFxXkHNSbRYXags.woff2, URL: https://fonts.gstatic.com/s/droidsans/v18/SlGVmQWMvZQIdix7AFxXkHNSbRYXags.woff2 (eventKey: s-5232), Property 'font-display' set to: 'auto', Wasted time: 15 ms.
772
+ - Font name: jizfRExUiTo99u79B_mh0O6tLR8a8zI.woff2, URL: https://fonts.gstatic.com/s/ptsans/v17/jizfRExUiTo99u79B_mh0O6tLR8a8zI.woff2 (eventKey: s-5259), Property 'font-display' set to: 'auto', Wasted time: 15 ms.
773
+ - Font name: SlGWmQWMvZQIdix7AFxXmMh3eDs1ZyHKpWg.woff2, URL: https://fonts.gstatic.com/s/droidsans/v18/SlGWmQWMvZQIdix7AFxXmMh3eDs1ZyHKpWg.woff2 (eventKey: s-5269), Property 'font-display' set to: 'auto', Wasted time: 15 ms.
774
+ - Font name: EJRVQgYoZZY2vCFuvAFWzr-_dSb_.woff2, URL: https://fonts.gstatic.com/s/ptserif/v18/EJRVQgYoZZY2vCFuvAFWzr-_dSb_.woff2 (eventKey: s-5325), Property 'font-display' set to: 'auto', Wasted time: 15 ms.
775
+ - Font name: S6u9w4BMUTPHh6UVSwiPGQ3q5d0.woff2, URL: https://fonts.gstatic.com/s/lato/v24/S6u9w4BMUTPHh6UVSwiPGQ3q5d0.woff2 (eventKey: s-5238), Property 'font-display' set to: 'auto', Wasted time: 10 ms.
678
776
 
679
777
  Consider setting [`font-display`](https://developer.chrome.com/blog/font-display) to `swap` or `optional` to ensure text is consistently visible. `swap` can be further optimized to mitigate layout shifts with [font metric overrides](https://developer.chrome.com/blog/font-fallbacks).
680
778
 
@@ -714,28 +812,28 @@ Total potential savings: 2 MB
714
812
 
715
813
  The following images could be optimized:
716
814
 
717
- ### https://images.ctfassets.net/u275ja1nivmq/6T6z40ay5GFCUtwV7DONgh/0e23606ed1692d9721ab0f39a8d8a99e/yeti_cover.jpg
815
+ ### https://images.ctfassets.net/u275ja1nivmq/6T6z40ay5GFCUtwV7DONgh/0e23606ed1692d9721ab0f39a8d8a99e/yeti_cover.jpg (eventKey: s-1212)
718
816
  - Potential savings: 1.1 MB
719
817
  - Optimizations:
720
818
  Using a modern image format (WebP, AVIF) or increasing the image compression could improve this image's download size. (Est 1.1 MB)
721
819
 
722
- ### https://raw.githubusercontent.com/GoogleChrome/lighthouse/refs/heads/main/cli/test/fixtures/dobetterweb/lighthouse-rotating.gif
820
+ ### https://raw.githubusercontent.com/GoogleChrome/lighthouse/refs/heads/main/cli/test/fixtures/dobetterweb/lighthouse-rotating.gif (eventKey: s-1216)
723
821
  - Potential savings: 682 kB
724
822
  - Optimizations:
725
823
  Using video formats instead of GIFs can improve the download size of animated content. (Est 682 kB)
726
824
 
727
- ### https://onlinepngtools.com/images/examples-onlinepngtools/elephant-hd-quality.png
825
+ ### https://onlinepngtools.com/images/examples-onlinepngtools/elephant-hd-quality.png (eventKey: s-1228)
728
826
  - Potential savings: 176 kB
729
827
  - Optimizations:
730
828
  Using a modern image format (WebP, AVIF) or increasing the image compression could improve this image's download size. (Est 134.1 kB)
731
829
  This image file is larger than it needs to be (640x436) for its displayed dimensions (200x136). Use responsive images to reduce the image download size. (Est 162.9 kB)
732
830
 
733
- ### https://images.ctfassets.net/u275ja1nivmq/6T6z40ay5GFCUtwV7DONgh/0e23606ed1692d9721ab0f39a8d8a99e/yeti_cover.jpg?fm=webp
831
+ ### https://images.ctfassets.net/u275ja1nivmq/6T6z40ay5GFCUtwV7DONgh/0e23606ed1692d9721ab0f39a8d8a99e/yeti_cover.jpg?fm=webp (eventKey: s-1224)
734
832
  - Potential savings: 49.8 kB
735
833
  - Optimizations:
736
834
  Increasing the image compression factor could improve this image's download size. (Est 49.8 kB)
737
835
 
738
- ### https://raw.githubusercontent.com/GoogleChrome/lighthouse/refs/heads/main/cli/test/fixtures/byte-efficiency/lighthouse-2048x1356.webp
836
+ ### https://raw.githubusercontent.com/GoogleChrome/lighthouse/refs/heads/main/cli/test/fixtures/byte-efficiency/lighthouse-2048x1356.webp (eventKey: s-1226)
739
837
  - Potential savings: 41.4 kB
740
838
  - Optimizations:
741
839
  This image file is larger than it needs to be (2048x1356) for its displayed dimensions (200x132). Use responsive images to reduce the image download size. (Est 41.4 kB)
@@ -852,9 +950,9 @@ The network dependency tree checks found one or more problems.
852
950
  Max critical path latency is 5,015 ms
853
951
 
854
952
  The following is the critical request chain:
855
- - http://localhost:8787/lcp-iframes/index.html (5,006 ms) (longest chain)
856
- - http://localhost:8787/lcp-iframes/app.js (5,015 ms) (longest chain)
857
- - http://localhost:8787/lcp-iframes/app.css (5,010 ms)
953
+ - http://localhost:8787/lcp-iframes/index.html (eventKey: s-7103) (5,006 ms) (longest chain)
954
+ - http://localhost:8787/lcp-iframes/app.js (eventKey: s-7225) (5,015 ms) (longest chain)
955
+ - http://localhost:8787/lcp-iframes/app.css (eventKey: s-7213) (5,010 ms)
858
956
 
859
957
  no origins were preconnected.
860
958
 
@@ -979,7 +1077,7 @@ This insight identifies static resources that are not cached effectively by the
979
1077
  ## Detailed analysis:
980
1078
  The following resources were associated with ineffficient cache policies:
981
1079
 
982
- - https://chromedevtools.github.io/performance-stories/lcp-large-image/app.css
1080
+ - https://chromedevtools.github.io/performance-stories/lcp-large-image/app.css (eventKey: s-1106)
983
1081
  - Cache Time to Live (TTL): 600 seconds
984
1082
  - Wasted bytes: 0 B
985
1083