chrome-ai-bridge 1.0.3 → 1.0.5

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 (74) hide show
  1. package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Base64.js +20 -2
  2. package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Gzip.js +11 -0
  3. package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Object.js +6 -1
  4. package/build/node_modules/chrome-devtools-frontend/front_end/core/common/ParsedURL.js +3 -0
  5. package/build/node_modules/chrome-devtools-frontend/front_end/core/common/ResourceType.js +6 -0
  6. package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Settings.js +18 -8
  7. package/build/node_modules/chrome-devtools-frontend/front_end/core/host/InspectorFrontendHostStub.js +3 -3
  8. package/build/node_modules/chrome-devtools-frontend/front_end/core/host/ResourceLoader.js +1 -1
  9. package/build/node_modules/chrome-devtools-frontend/front_end/core/host/UserMetrics.js +17 -1
  10. package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/ArrayUtilities.js +10 -0
  11. package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/StringUtilities.js +63 -12
  12. package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/CDPConnection.js +1 -0
  13. package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/InspectorBackend.js +4 -1
  14. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSMatchedStyles.js +44 -9
  15. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSMetadata.js +6 -6
  16. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSModel.js +1 -1
  17. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/DOMModel.js +169 -12
  18. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/DebuggerModel.js +2 -1
  19. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/IsolateManager.js +6 -0
  20. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/NetworkManager.js +18 -4
  21. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/NetworkRequest.js +7 -21
  22. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/OverlayModel.js +17 -5
  23. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/RehydratingConnection.js +5 -1
  24. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ResourceTreeModel.js +8 -5
  25. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMap.js +14 -2
  26. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMapManager.js +1 -1
  27. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMapScopesInfo.js +11 -4
  28. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/Target.js +3 -1
  29. package/build/node_modules/chrome-devtools-frontend/front_end/generated/ARIAProperties.js +1 -1
  30. package/build/node_modules/chrome-devtools-frontend/front_end/generated/Deprecation.js +1 -16
  31. package/build/node_modules/chrome-devtools-frontend/front_end/generated/InspectorBackendCommands.js +35 -14
  32. package/build/node_modules/chrome-devtools-frontend/front_end/generated/SupportedCSSProperties.js +197 -101
  33. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.js +2 -1
  34. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.js +10 -16
  35. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.js +97 -26
  36. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AICallTree.js +35 -0
  37. package/build/node_modules/chrome-devtools-frontend/front_end/models/annotations/AnnotationRepository.js +163 -0
  38. package/build/node_modules/chrome-devtools-frontend/front_end/models/annotations/AnnotationType.js +10 -0
  39. package/build/node_modules/chrome-devtools-frontend/front_end/models/annotations/annotations.js +5 -0
  40. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/CompilerScriptMapping.js +5 -3
  41. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/DebuggerWorkspaceBinding.js +7 -3
  42. package/build/node_modules/chrome-devtools-frontend/front_end/models/emulation/DeviceModeModel.js +1 -1
  43. package/build/node_modules/chrome-devtools-frontend/front_end/models/emulation/EmulatedDevices.js +14 -0
  44. package/build/node_modules/chrome-devtools-frontend/front_end/models/formatter/FormatterWorkerPool.js +8 -5
  45. package/build/node_modules/chrome-devtools-frontend/front_end/models/greendev/Prototypes.js +33 -0
  46. package/build/node_modules/chrome-devtools-frontend/front_end/models/greendev/greendev.js +4 -0
  47. package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/StackTraceImpl.js +70 -1
  48. package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/StackTraceModel.js +82 -30
  49. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/EventsSerializer.js +7 -2
  50. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/LanternComputationData.js +2 -2
  51. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/Processor.js +18 -19
  52. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/Styles.js +12 -4
  53. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/extras/Initiators.js +46 -0
  54. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/extras/TraceTree.js +4 -3
  55. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/extras/extras.js +1 -0
  56. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/LargestImagePaintHandler.js +2 -2
  57. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/LayoutShiftsHandler.js +1 -1
  58. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/MetaHandler.js +6 -0
  59. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/NetworkRequestsHandler.js +10 -1
  60. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/PageLoadMetricsHandler.js +44 -27
  61. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/helpers/Timing.js +9 -2
  62. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/Common.js +1 -6
  63. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LCPBreakdown.js +2 -2
  64. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LCPDiscovery.js +2 -4
  65. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/NetworkDependencyTree.js +3 -2
  66. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/RenderBlocking.js +1 -1
  67. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/types/TraceEvents.js +30 -11
  68. package/build/node_modules/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.js +28 -13
  69. package/build/node_modules/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/encode/encoder.js +1 -1
  70. package/build/node_modules/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/scopes.js +4 -0
  71. package/build/src/tools/chatgpt-web.js +68 -49
  72. package/build/src/tools/gemini-web.js +66 -22
  73. package/build/src/tools/pages.js +0 -1
  74. package/package.json +1 -1
@@ -8,8 +8,8 @@ import { bytes, millis } from './UnitFormatters.js';
8
8
  /**
9
9
  * For a given frame ID and navigation ID, returns the LCP Event and the LCP Request, if the resource was an image.
10
10
  */
11
- function getLCPData(parsedTrace, frameId, navigationId) {
12
- const navMetrics = parsedTrace.data.PageLoadMetrics.metricScoresByFrameId.get(frameId)?.get(navigationId);
11
+ function getLCPData(parsedTrace, frameId, navigation) {
12
+ const navMetrics = parsedTrace.data.PageLoadMetrics.metricScoresByFrameId.get(frameId)?.get(navigation);
13
13
  if (!navMetrics) {
14
14
  return null;
15
15
  }
@@ -18,12 +18,14 @@ function getLCPData(parsedTrace, frameId, navigationId) {
18
18
  return null;
19
19
  }
20
20
  const lcpEvent = metric?.event;
21
- if (!lcpEvent || !Trace.Types.Events.isLargestContentfulPaintCandidate(lcpEvent)) {
21
+ if (!lcpEvent || !Trace.Types.Events.isAnyLargestContentfulPaintCandidate(lcpEvent)) {
22
22
  return null;
23
23
  }
24
+ const navigationId = navigation.args.data?.navigationId;
24
25
  return {
25
26
  lcpEvent,
26
- lcpRequest: parsedTrace.data.LargestImagePaint.lcpRequestByNavigationId.get(navigationId),
27
+ lcpRequest: navigationId ? parsedTrace.data.LargestImagePaint.lcpRequestByNavigationId.get(navigationId) :
28
+ undefined,
27
29
  metricScore: metric,
28
30
  };
29
31
  }
@@ -68,14 +70,14 @@ export class PerformanceInsightFormatter {
68
70
  * Information about LCP which we pass to the LLM for all insights that relate to LCP.
69
71
  */
70
72
  #lcpMetricSharedContext() {
71
- if (!this.#insight.navigationId) {
73
+ if (!this.#insight.navigation) {
72
74
  // No navigation ID = no LCP.
73
75
  return '';
74
76
  }
75
- if (!this.#insight.frameId || !this.#insight.navigationId) {
77
+ if (!this.#insight.frameId || !this.#insight.navigation) {
76
78
  return '';
77
79
  }
78
- const data = getLCPData(this.#parsedTrace, this.#insight.frameId, this.#insight.navigationId);
80
+ const data = getLCPData(this.#parsedTrace, this.#insight.frameId, this.#insight.navigation);
79
81
  if (!data) {
80
82
  return '';
81
83
  }
@@ -97,12 +99,6 @@ export class PerformanceInsightFormatter {
97
99
  return parts.join('\n');
98
100
  }
99
101
  insightIsSupported() {
100
- // Although our types don't show it, Insights can end up as Errors if there
101
- // is an issue in the processing stage. In this case we should gracefully
102
- // ignore this error.
103
- if (this.#insight instanceof Error) {
104
- return false;
105
- }
106
102
  return this.#description().length > 0;
107
103
  }
108
104
  getSuggestions() {
@@ -179,7 +175,7 @@ export class PerformanceInsightFormatter {
179
175
  { title: 'How can I reduce the amount of legacy JavaScript on my page?' },
180
176
  ];
181
177
  default:
182
- throw new Error('Unknown insight key');
178
+ throw new Error(`Unknown insight key '${this.#insight.insightKey}'`);
183
179
  }
184
180
  }
185
181
  /**
@@ -228,8 +224,6 @@ export class PerformanceInsightFormatter {
228
224
  potentialRootCauses.push(animationInfoOutput.map(l => ' '.repeat(4) + l).join('\n'));
229
225
  });
230
226
  rootCauses.unsizedImages.forEach(img => {
231
- // TODO(b/413284569): if we store a nice human readable name for this
232
- // image in the trace metadata, we can do something much nicer here.
233
227
  const url = img.paintImageEvent.args.data.url;
234
228
  const nodeName = img.paintImageEvent.args.data.nodeName;
235
229
  const extraText = url ? `url: ${this.#formatUrl(url)}` : `id: ${img.backendNodeId}`;
@@ -1,6 +1,7 @@
1
1
  // Copyright 2025 The Chromium Authors
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
+ import * as Annotations from '../../annotations/annotations.js';
4
5
  import * as CrUXManager from '../../crux-manager/crux-manager.js';
5
6
  import * as Trace from '../../trace/trace.js';
6
7
  import { AIQueries } from '../performance/AIQueries.js';
@@ -12,6 +13,8 @@ export class PerformanceTraceFormatter {
12
13
  #parsedTrace;
13
14
  #insightSet;
14
15
  #eventsSerializer;
16
+ #formattedFunctionCodes = new Set();
17
+ resolveFunctionCode;
15
18
  constructor(focus) {
16
19
  this.#focus = focus;
17
20
  this.#parsedTrace = focus.parsedTrace;
@@ -101,19 +104,19 @@ export class PerformanceTraceFormatter {
101
104
  parts.push('\n# Available insight sets\n');
102
105
  parts.push('The following is a list of insight sets. An insight set covers a specific part of the trace, split by navigations. The insights within each insight set are specific to that part of the trace. Be sure to consider the insight set id and bounds when calling functions. If no specific insight set or navigation is mentioned, assume the user is referring to the first one.');
103
106
  for (const insightSet of parsedTrace.insights?.values() ?? []) {
104
- const lcp = insightSet ? Trace.Insights.Common.getLCP(insightSet) : null;
105
- const cls = insightSet ? Trace.Insights.Common.getCLS(insightSet) : null;
106
- const inp = insightSet ? Trace.Insights.Common.getINP(insightSet) : null;
107
+ const lcp = Trace.Insights.Common.getLCP(insightSet);
108
+ const cls = Trace.Insights.Common.getCLS(insightSet);
109
+ const inp = Trace.Insights.Common.getINP(insightSet);
107
110
  parts.push(`\n## insight set id: ${insightSet.id}\n`);
108
111
  parts.push(`URL: ${insightSet.url}`);
109
112
  parts.push(`Bounds: ${this.serializeBounds(insightSet.bounds)}`);
110
113
  if (lcp || cls || inp) {
111
114
  parts.push('Metrics (lab / observed):');
112
115
  if (lcp) {
113
- const nodeId = insightSet?.model.LCPBreakdown.lcpEvent?.args.data?.nodeId;
116
+ const nodeId = insightSet.model.LCPBreakdown?.lcpEvent?.args.data?.nodeId;
114
117
  const nodeIdText = nodeId !== undefined ? `, nodeId: ${nodeId}` : '';
115
118
  parts.push(` - LCP: ${Math.round(lcp.value / 1000)} ms, event: ${this.serializeEvent(lcp.event)}${nodeIdText}`);
116
- const subparts = insightSet?.model.LCPBreakdown.subparts;
119
+ const subparts = insightSet.model.LCPBreakdown?.subparts;
117
120
  if (subparts) {
118
121
  const serializeSubpart = (subpart) => {
119
122
  return `${micros(subpart.range)}, bounds: ${this.serializeBounds(subpart)}`;
@@ -135,6 +138,13 @@ export class PerformanceTraceFormatter {
135
138
  if (cls) {
136
139
  const eventText = cls.worstClusterEvent ? `, event: ${this.serializeEvent(cls.worstClusterEvent)}` : '';
137
140
  parts.push(` - CLS: ${cls.value.toFixed(2)}${eventText}`);
141
+ if (Annotations.AnnotationRepository.annotationsEnabled()) {
142
+ const worstClusterEvent = cls.worstClusterEvent;
143
+ const layoutShiftData = worstClusterEvent?.worstShiftEvent?.args?.data;
144
+ if (layoutShiftData?.impacted_nodes && layoutShiftData.impacted_nodes?.length > 0) {
145
+ Annotations.AnnotationRepository.instance().addElementsAnnotation('This element is impacted by a layout shift', layoutShiftData.impacted_nodes[0].node_id.toString());
146
+ }
147
+ }
138
148
  }
139
149
  }
140
150
  else {
@@ -178,7 +188,7 @@ export class PerformanceTraceFormatter {
178
188
  }
179
189
  return parts.join('\n');
180
190
  }
181
- #formatFactByInsightSet(options) {
191
+ async #formatFactByInsightSet(options) {
182
192
  const { insights, title, description, empty, cb } = options;
183
193
  const lines = [`# ${title}\n`];
184
194
  if (description) {
@@ -190,7 +200,7 @@ export class PerformanceTraceFormatter {
190
200
  if (multipleInsightSets) {
191
201
  lines.push(`## insight set id: ${insightSet.id}\n`);
192
202
  }
193
- lines.push((cb(insightSet) ?? empty) + '\n');
203
+ lines.push((await cb(insightSet) ?? empty) + '\n');
194
204
  }
195
205
  }
196
206
  else {
@@ -204,18 +214,18 @@ export class PerformanceTraceFormatter {
204
214
  insights: parsedTrace.insights,
205
215
  title: 'Critical network requests',
206
216
  empty: 'none',
207
- cb: insightSet => {
217
+ cb: async (insightSet) => {
208
218
  const criticalRequests = [];
209
219
  const walkRequest = (node) => {
210
220
  criticalRequests.push(node.request);
211
221
  node.children.forEach(walkRequest);
212
222
  };
213
- insightSet.model.NetworkDependencyTree.rootNodes.forEach(walkRequest);
223
+ insightSet.model.NetworkDependencyTree?.rootNodes.forEach(walkRequest);
214
224
  return criticalRequests.length ? this.formatNetworkRequests(criticalRequests, { verbose: false }) : null;
215
225
  },
216
226
  });
217
227
  }
218
- #serializeBottomUpRootNode(rootNode, limit) {
228
+ async #serializeBottomUpRootNode(rootNode, limit) {
219
229
  // Sorted by selfTime.
220
230
  // No nodes less than 1 ms.
221
231
  // Limit.
@@ -223,15 +233,20 @@ export class PerformanceTraceFormatter {
223
233
  .filter(n => n.totalTime >= 1)
224
234
  .sort((a, b) => b.selfTime - a.selfTime)
225
235
  .slice(0, limit);
236
+ const callFrames = [];
226
237
  function nodeToText(node) {
227
238
  const event = node.event;
228
239
  let frame;
229
240
  if (Trace.Types.Events.isProfileCall(event)) {
230
241
  frame = event.callFrame;
242
+ if (node.selfTime >= 100 && callFrames.length < 3) {
243
+ callFrames.push(frame);
244
+ }
231
245
  }
232
246
  else {
233
247
  frame = Trace.Helpers.Trace.getStackTraceTopCallFrameInEventPayload(event);
234
248
  }
249
+ // TODO(crbug.com/452333154): this is not source mapped.
235
250
  let source = Trace.Name.forEntry(event);
236
251
  if (frame?.url) {
237
252
  source += ` (url: ${frame.url}`;
@@ -245,7 +260,8 @@ export class PerformanceTraceFormatter {
245
260
  }
246
261
  return `- self: ${millis(node.selfTime)}, total: ${millis(node.totalTime)}, source: ${source}`;
247
262
  }
248
- return topNodes.map(node => nodeToText.call(this, node)).join('\n');
263
+ return topNodes.map(node => nodeToText.call(this, node)).join('\n') +
264
+ await this.#serializeRelevantFunctions(callFrames);
249
265
  }
250
266
  #getSerializeBottomUpRootNodeFormat(limit) {
251
267
  return `This is the bottom-up summary for the entire trace. Only the top ${limit} activities (sorted by self time) are shown. An activity is all the aggregated time spent on the same type of work. For example, it can be all the time spent in a specific JavaScript function, or all the time spent in a specific browser rendering stage (like layout, v8 compile, parsing html). "Self time" represents the aggregated time spent directly in an activity, across all occurrences. "Total time" represents the aggregated time spent in an activity or any of its children.`;
@@ -258,9 +274,9 @@ export class PerformanceTraceFormatter {
258
274
  title: 'Main thread bottom-up summary',
259
275
  description: this.#getSerializeBottomUpRootNodeFormat(limit),
260
276
  empty: 'no activity',
261
- cb: insightSet => {
277
+ cb: async (insightSet) => {
262
278
  const rootNode = AIQueries.mainThreadActivityBottomUpSingleNavigation(insightSet.navigation?.args.data?.navigationId, insightSet.bounds, parsedTrace);
263
- return rootNode ? this.#serializeBottomUpRootNode(rootNode, limit) : null;
279
+ return rootNode ? await this.#serializeBottomUpRootNode(rootNode, limit) : null;
264
280
  },
265
281
  });
266
282
  }
@@ -283,7 +299,7 @@ export class PerformanceTraceFormatter {
283
299
  insights: parsedTrace.insights,
284
300
  title: '3rd party summary',
285
301
  empty: 'no 3rd parties',
286
- cb: insightSet => {
302
+ cb: async (insightSet) => {
287
303
  const thirdPartySummaries = Trace.Extras.ThirdParties.summarizeByThirdParty(parsedTrace.data, insightSet.bounds);
288
304
  return thirdPartySummaries.length ? this.#formatThirdPartyEntitySummaries(thirdPartySummaries) : null;
289
305
  },
@@ -295,7 +311,7 @@ export class PerformanceTraceFormatter {
295
311
  insights: parsedTrace.insights,
296
312
  title: 'Longest tasks',
297
313
  empty: 'none',
298
- cb: insightSet => {
314
+ cb: async (insightSet) => {
299
315
  const longestTaskTrees = AIQueries.longestTasks(insightSet.navigation?.args.data?.navigationId, insightSet.bounds, parsedTrace, 3);
300
316
  if (!longestTaskTrees?.length) {
301
317
  return null;
@@ -342,7 +358,7 @@ export class PerformanceTraceFormatter {
342
358
  }
343
359
  return results.join('\n');
344
360
  }
345
- formatMainThreadTrackSummary(bounds) {
361
+ async formatMainThreadTrackSummary(bounds) {
346
362
  if (!this.#parsedTrace.insights) {
347
363
  return 'No main thread activity found';
348
364
  }
@@ -351,14 +367,14 @@ export class PerformanceTraceFormatter {
351
367
  const topDownTree = AIQueries.mainThreadActivityTopDown(insightSet?.navigation?.args.data?.navigationId, bounds, this.#parsedTrace);
352
368
  if (topDownTree) {
353
369
  results.push('# Top-down main thread summary');
354
- results.push(this.formatCallTree(topDownTree, 2 /* headerLevel */));
370
+ results.push(await this.formatCallTree(topDownTree, 2 /* headerLevel */));
355
371
  }
356
372
  const bottomUpRootNode = AIQueries.mainThreadActivityBottomUp(bounds, this.#parsedTrace);
357
373
  if (bottomUpRootNode) {
358
374
  results.push('# Bottom-up main thread summary');
359
375
  const limit = 20;
360
376
  results.push(this.#getSerializeBottomUpRootNodeFormat(limit));
361
- results.push(this.#serializeBottomUpRootNode(bottomUpRootNode, limit));
377
+ results.push(await this.#serializeBottomUpRootNode(bottomUpRootNode, limit));
362
378
  }
363
379
  const thirdPartySummaries = Trace.Extras.ThirdParties.summarizeByThirdParty(this.#parsedTrace.data, bounds);
364
380
  if (thirdPartySummaries.length) {
@@ -390,8 +406,19 @@ export class PerformanceTraceFormatter {
390
406
  }
391
407
  return results.join('\n\n');
392
408
  }
393
- formatCallTree(tree, headerLevel = 1) {
394
- return `${tree.serialize(headerLevel)}\n\nIMPORTANT: Never show eventKey to the user.`;
409
+ async formatCallTree(tree, headerLevel = 1) {
410
+ let result = `${tree.serialize(headerLevel)}\n\nIMPORTANT: Never show eventKey to the user.\n`;
411
+ const relevantCallFrames = [];
412
+ if (tree.selectedNode && Trace.Types.Events.isProfileCall(tree.selectedNode.event)) {
413
+ relevantCallFrames.push(tree.selectedNode.event.callFrame);
414
+ }
415
+ const topCallFrameByTotalTime = tree.topCallFrameByTotalTime();
416
+ if (topCallFrameByTotalTime) {
417
+ relevantCallFrames.push(topCallFrameByTotalTime);
418
+ }
419
+ relevantCallFrames.push(...tree.topCallFramesBySelfTime(3));
420
+ result += await this.#serializeRelevantFunctions(relevantCallFrames);
421
+ return result;
395
422
  }
396
423
  formatNetworkRequests(requests, options) {
397
424
  if (requests.length === 0) {
@@ -424,7 +451,7 @@ export class PerformanceTraceFormatter {
424
451
  const initiators = [];
425
452
  let cur = request;
426
453
  while (cur) {
427
- const initiator = parsedTrace.data.NetworkRequests.eventToInitiator.get(cur);
454
+ const initiator = Trace.Extras.Initiators.getNetworkInitiator(parsedTrace.data, cur);
428
455
  if (initiator) {
429
456
  // Should never happen, but if it did that would be an infinite loop.
430
457
  if (initiators.includes(initiator)) {
@@ -445,7 +472,7 @@ export class PerformanceTraceFormatter {
445
472
  * talk to jacktfranklin@.
446
473
  */
447
474
  #networkRequestVerbosely(request, options) {
448
- const { url, statusCode, initialPriority, priority, fromServiceWorker, mimeType, responseHeaders, syntheticData, protocol } = request.args.data;
475
+ const { url, requestId, statusCode, initialPriority, priority, fromServiceWorker, mimeType, responseHeaders, syntheticData, protocol } = request.args.data;
449
476
  const parsedTrace = this.#parsedTrace;
450
477
  const titlePrefix = `## ${options?.customTitle ?? 'Network request'}`;
451
478
  // Note: unlike other agents, we do have the ability to include
@@ -464,7 +491,7 @@ export class PerformanceTraceFormatter {
464
491
  const mainThreadProcessingDuration = startTimesForLifecycle.processingCompletedAt - startTimesForLifecycle.downloadCompletedAt;
465
492
  const downloadTime = syntheticData.finishTime - syntheticData.downloadStart;
466
493
  const renderBlocking = Trace.Helpers.Network.isSyntheticNetworkRequestEventRenderBlocking(request);
467
- const initiator = parsedTrace.data.NetworkRequests.eventToInitiator.get(request);
494
+ const initiator = Trace.Extras.Initiators.getNetworkInitiator(parsedTrace.data, request);
468
495
  const priorityLines = [];
469
496
  if (initialPriority === priority) {
470
497
  priorityLines.push(`Priority: ${priority}`);
@@ -483,7 +510,7 @@ export class PerformanceTraceFormatter {
483
510
  const initiatorUrls = initiators.map(initiator => initiator.args.data.url);
484
511
  const eventKey = this.#eventsSerializer.keyForEvent(request);
485
512
  const eventKeyLine = eventKey ? `eventKey: ${eventKey}\n` : '';
486
- return `${titlePrefix}: ${url}
513
+ return `${titlePrefix}: ${url}${Annotations.AnnotationRepository.annotationsEnabled() ? `\nrequestId: ${requestId}` : ''}
487
514
  ${eventKeyLine}Timings:
488
515
  - Queued at: ${micros(startTimesForLifecycle.queuedAt)}
489
516
  - Request sent at: ${micros(startTimesForLifecycle.requestSentAt)}
@@ -648,17 +675,61 @@ The order of headers corresponds to an internal fixed list. If a header is not p
648
675
  ];
649
676
  return parts.join(';');
650
677
  }
678
+ resolveFunctionCodeAtLocation(url, line, column) {
679
+ if (!this.resolveFunctionCode) {
680
+ throw new Error('missing resolveFunctionCode');
681
+ }
682
+ return this.resolveFunctionCode(url, line, column);
683
+ }
651
684
  formatFunctionCode(code) {
685
+ return this.#getFormattedFunctionCodeExplainer() + '\n\n' + this.#formatFunctionCode(code);
686
+ }
687
+ #getFormattedFunctionCodeExplainer() {
688
+ return 'The following are markdown block(s) of code that ran in the page, each representing a separate function. <FUNCTION_START> and <FUNCTION_END> marks the exact function declaration, and everything outside that is provided for additional context. Comments at the end of each line indicate the runtime performance cost of that code. Do not show the user the function markers or the additional context.';
689
+ }
690
+ #functionCodeToKey(code) {
691
+ return code.functionBounds.uiSourceCode.url() + ':' + code.functionBounds.range.toString();
692
+ }
693
+ #hasFormattedFunctionCode(code) {
694
+ return this.#formattedFunctionCodes.has(this.#functionCodeToKey(code));
695
+ }
696
+ #formatFunctionCode(code) {
697
+ this.#formattedFunctionCodes.add(this.#functionCodeToKey(code));
652
698
  const { startLine, startColumn } = code.range;
653
699
  const { startLine: contextStartLine, startColumn: contextStartColumn, endLine: contextEndLine, endColumn: contextEndColumn } = code.rangeWithContext;
654
- const name = code.functionBounds.name;
700
+ const name = code.functionBounds.name || '(anonymous)';
655
701
  const url = code.functionBounds.uiSourceCode.url();
656
702
  const parts = [];
657
703
  parts.push(`${name} @ ${url}:${startLine}:${startColumn}. With added context, chunk is from ${contextStartLine}:${contextStartColumn} to ${contextEndLine}:${contextEndColumn}`);
658
- parts.push('\nThe following is a markdown block of JavaScript. <FUNCTION_START> and <FUNCTION_END> marks the exact function declaration, and everything outside that is provided for additional context. Comments at the end of each line indicate the runtime performance cost of that code. Do not show the user the function markers or the additional context.\n');
659
704
  parts.push('```');
660
705
  parts.push(code.codeWithContext);
661
706
  parts.push('```');
662
707
  return parts.join('\n');
663
708
  }
709
+ /**
710
+ * Appends the code of each call frame's function, but only if the function was not
711
+ * serialized previously.
712
+ */
713
+ async #serializeRelevantFunctions(callFrames) {
714
+ const resolveFunctionCode = this.resolveFunctionCode;
715
+ if (!resolveFunctionCode) {
716
+ return '';
717
+ }
718
+ const functionCodeStrings = [];
719
+ const functionCodes = await Promise.all(callFrames.map(frame => resolveFunctionCode(frame.url, frame.lineNumber, frame.columnNumber)));
720
+ for (const code of functionCodes) {
721
+ if (code && !this.#hasFormattedFunctionCode(code)) {
722
+ functionCodeStrings.push(this.#formatFunctionCode(code));
723
+ }
724
+ }
725
+ if (!functionCodeStrings.length) {
726
+ return '';
727
+ }
728
+ return '\n' + [
729
+ this.#getFormattedFunctionCodeExplainer(),
730
+ functionCodeStrings.length > 1 ? `Here are ${functionCodeStrings.length} relevant functions:` :
731
+ `Here is a relevant function:`,
732
+ ...functionCodeStrings,
733
+ ].join('\n\n');
734
+ }
664
735
  }
@@ -319,6 +319,41 @@ export class AICallTree {
319
319
  }
320
320
  return line;
321
321
  }
322
+ topCallFramesBySelfTime(limit) {
323
+ const functionNodesByCallFrame = new Map();
324
+ this.breadthFirstWalk(this.rootNode.children().values(), node => {
325
+ if (Trace.Types.Events.isProfileCall(node.event)) {
326
+ const callFrame = node.event.callFrame;
327
+ const callFrameKey = `${callFrame.scriptId}:${callFrame.lineNumber}:${callFrame.columnNumber}`;
328
+ const array = functionNodesByCallFrame.get(callFrameKey) ?? [];
329
+ array.push(node);
330
+ functionNodesByCallFrame.set(callFrameKey, array);
331
+ }
332
+ });
333
+ return [...functionNodesByCallFrame.values()]
334
+ .map(nodes => {
335
+ return {
336
+ callFrame: nodes[0].event.callFrame,
337
+ selfTime: nodes.reduce((total, cur) => total + cur.selfTime, 0),
338
+ };
339
+ })
340
+ .sort((a, b) => b.selfTime - a.selfTime)
341
+ .slice(0, limit)
342
+ .map(({ callFrame }) => callFrame);
343
+ }
344
+ topCallFrameByTotalTime() {
345
+ let topChild = null;
346
+ let topProfileCallEvent = null;
347
+ for (const child of this.rootNode.children().values()) {
348
+ if (Trace.Types.Events.isProfileCall(child.event)) {
349
+ if (!topChild || child.totalTime > topChild.totalTime) {
350
+ topChild = child;
351
+ topProfileCallEvent = child.event;
352
+ }
353
+ }
354
+ }
355
+ return topProfileCallEvent?.callFrame ?? null;
356
+ }
322
357
  // Only used for debugging.
323
358
  logDebug() {
324
359
  const str = this.serialize();
@@ -0,0 +1,163 @@
1
+ // Copyright 2025 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+ import * as Common from '../../core/common/common.js';
5
+ import * as GreenDev from '../greendev/greendev.js';
6
+ import { AnnotationType } from './AnnotationType.js';
7
+ export class AnnotationRepository {
8
+ static #instance = null;
9
+ static #hasRepliedGreenDevDisabled = false;
10
+ static #hasShownFlagWarning = false;
11
+ #events = new Common.ObjectWrapper.ObjectWrapper();
12
+ #annotationData = [];
13
+ #nextId = 0;
14
+ static instance() {
15
+ if (!AnnotationRepository.#instance) {
16
+ AnnotationRepository.#instance = new AnnotationRepository();
17
+ }
18
+ return AnnotationRepository.#instance;
19
+ }
20
+ static annotationsEnabled() {
21
+ const enabled = GreenDev.Prototypes.instance().isEnabled('aiAnnotations');
22
+ // TODO(finnur): Fix race when Repository is created before feature flags have been set properly.
23
+ if (!enabled) {
24
+ this.#hasRepliedGreenDevDisabled = true;
25
+ }
26
+ else if (this.#hasRepliedGreenDevDisabled && !this.#hasShownFlagWarning) {
27
+ console.warn('Flag controlling GreenDev has flipped from false to true. ' +
28
+ 'Only some callers will expect GreenDev to be enabled, which can lead to unexpected results.');
29
+ this.#hasShownFlagWarning = true;
30
+ }
31
+ return Boolean(enabled);
32
+ }
33
+ addEventListener(eventType, listener, thisObject) {
34
+ if (!AnnotationRepository.annotationsEnabled()) {
35
+ console.warn('Received request to add event listener with annotations disabled');
36
+ }
37
+ return this.#events.addEventListener(eventType, listener, thisObject);
38
+ }
39
+ getAnnotationDataByType(type) {
40
+ if (!AnnotationRepository.annotationsEnabled()) {
41
+ console.warn('Received query for annotation types with annotations disabled');
42
+ return [];
43
+ }
44
+ const annotations = this.#annotationData.filter(annotation => annotation.type === type);
45
+ return annotations;
46
+ }
47
+ getAnnotationDataById(id) {
48
+ if (!AnnotationRepository.annotationsEnabled()) {
49
+ console.warn('Received query for annotation type with annotations disabled');
50
+ return undefined;
51
+ }
52
+ return this.#annotationData.find(annotation => annotation.id === id);
53
+ }
54
+ #getExistingAnnotation(type, anchor) {
55
+ const annotations = this.getAnnotationDataByType(type);
56
+ const annotation = annotations.find(annotation => {
57
+ if (typeof anchor === 'string') {
58
+ return annotation.lookupId === anchor;
59
+ }
60
+ switch (type) {
61
+ case AnnotationType.ELEMENT_NODE: {
62
+ const elementAnnotation = annotation;
63
+ return elementAnnotation.anchor === anchor;
64
+ }
65
+ case AnnotationType.NETWORK_REQUEST_SUBPANEL_HEADERS: {
66
+ const networkRequestDetailsAnnotation = annotation;
67
+ return networkRequestDetailsAnnotation.anchor === anchor;
68
+ }
69
+ default:
70
+ console.warn('[AnnotationRepository] Unknown AnnotationType', type);
71
+ return false;
72
+ }
73
+ });
74
+ return annotation;
75
+ }
76
+ #updateExistingAnnotationLabel(label, type, anchor) {
77
+ const annotation = this.#getExistingAnnotation(type, anchor);
78
+ if (annotation) {
79
+ // TODO(finnur): This should work for annotations that have not been displayed yet,
80
+ // but we need to also notify the AnnotationManager for those that have been shown.
81
+ annotation.message = label;
82
+ return true;
83
+ }
84
+ return false;
85
+ }
86
+ addElementsAnnotation(label, anchor, anchorToString) {
87
+ if (!AnnotationRepository.annotationsEnabled()) {
88
+ console.warn('Received annotation registration with annotations disabled');
89
+ return;
90
+ }
91
+ if (this.#updateExistingAnnotationLabel(label, AnnotationType.ELEMENT_NODE, anchor)) {
92
+ return;
93
+ }
94
+ const annotationData = {
95
+ id: this.#nextId++,
96
+ type: AnnotationType.ELEMENT_NODE,
97
+ message: label,
98
+ lookupId: typeof anchor === 'string' ? anchor : '',
99
+ anchor: typeof anchor !== 'string' ? anchor : undefined,
100
+ anchorToString,
101
+ };
102
+ this.#annotationData.push(annotationData);
103
+ // eslint-disable-next-line no-console
104
+ console.log('[AnnotationRepository] Added element annotation:', label, {
105
+ annotationData,
106
+ annotations: this.#annotationData.length,
107
+ });
108
+ this.#events.dispatchEventToListeners("AnnotationAdded" /* Events.ANNOTATION_ADDED */, annotationData);
109
+ }
110
+ addNetworkRequestAnnotation(label, anchor, anchorToString) {
111
+ if (!AnnotationRepository.annotationsEnabled()) {
112
+ console.warn('Received annotation registration with annotations disabled');
113
+ return;
114
+ }
115
+ // We only need to update the NETWORK_REQUEST_SUBPANEL_HEADERS because the
116
+ // NETWORK_REQUEST Annotation has no meaningful label.
117
+ if (this.#updateExistingAnnotationLabel(label, AnnotationType.NETWORK_REQUEST_SUBPANEL_HEADERS, anchor)) {
118
+ return;
119
+ }
120
+ const annotationData = {
121
+ id: this.#nextId++,
122
+ type: AnnotationType.NETWORK_REQUEST,
123
+ message: '',
124
+ lookupId: typeof anchor === 'string' ? anchor : '',
125
+ anchor: typeof anchor !== 'string' ? anchor : undefined,
126
+ anchorToString,
127
+ };
128
+ this.#annotationData.push(annotationData);
129
+ // eslint-disable-next-line no-console
130
+ console.log('[AnnotationRepository] Added annotation:', label, {
131
+ annotationData,
132
+ annotations: this.#annotationData.length,
133
+ });
134
+ this.#events.dispatchEventToListeners("AnnotationAdded" /* Events.ANNOTATION_ADDED */, annotationData);
135
+ const annotationDetailsData = {
136
+ id: this.#nextId++,
137
+ type: AnnotationType.NETWORK_REQUEST_SUBPANEL_HEADERS,
138
+ message: label,
139
+ lookupId: typeof anchor === 'string' ? anchor : '',
140
+ anchor: typeof anchor !== 'string' ? anchor : undefined,
141
+ anchorToString,
142
+ };
143
+ this.#annotationData.push(annotationDetailsData);
144
+ this.#events.dispatchEventToListeners("AnnotationAdded" /* Events.ANNOTATION_ADDED */, annotationDetailsData);
145
+ }
146
+ deleteAllAnnotations() {
147
+ this.#annotationData = [];
148
+ this.#events.dispatchEventToListeners("AllAnnotationsDeleted" /* Events.ALL_ANNOTATIONS_DELETED */);
149
+ // eslint-disable-next-line no-console
150
+ console.log('[AnnotationRepository] Deleting all annotations');
151
+ }
152
+ deleteAnnotation(id) {
153
+ const index = this.#annotationData.findIndex(annotation => annotation.id === id);
154
+ if (index === -1) {
155
+ console.warn(`[AnnotationRepository] Could not find annotation with id ${id}`);
156
+ return;
157
+ }
158
+ this.#annotationData.splice(index, 1);
159
+ this.#events.dispatchEventToListeners("AnnotationDeleted" /* Events.ANNOTATION_DELETED */, { id });
160
+ // eslint-disable-next-line no-console
161
+ console.log(`[AnnotationRepository] Deleted annotation with id ${id}`);
162
+ }
163
+ }
@@ -0,0 +1,10 @@
1
+ // Copyright 2025 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+ export var AnnotationType;
5
+ (function (AnnotationType) {
6
+ AnnotationType[AnnotationType["ELEMENT_NODE"] = 0] = "ELEMENT_NODE";
7
+ AnnotationType[AnnotationType["STYLE_RULE"] = 1] = "STYLE_RULE";
8
+ AnnotationType[AnnotationType["NETWORK_REQUEST"] = 2] = "NETWORK_REQUEST";
9
+ AnnotationType[AnnotationType["NETWORK_REQUEST_SUBPANEL_HEADERS"] = 3] = "NETWORK_REQUEST_SUBPANEL_HEADERS";
10
+ })(AnnotationType || (AnnotationType = {}));
@@ -0,0 +1,5 @@
1
+ // Copyright 2025 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+ export * from './AnnotationRepository.js';
5
+ export * from './AnnotationType.js';
@@ -229,6 +229,7 @@ export class CompilerScriptMapping {
229
229
  if (!sourceMap) {
230
230
  return null;
231
231
  }
232
+ await sourceMap.waitForScopeInfo();
232
233
  const { lineNumber, columnNumber } = script.rawLocationToRelativeLocation(rawLocation);
233
234
  const { url, scope } = sourceMap.findOriginalFunctionScope({ line: lineNumber, column: columnNumber }) ?? {};
234
235
  if (!scope || !url) {
@@ -254,21 +255,22 @@ export class CompilerScriptMapping {
254
255
  const range = new TextUtils.TextRange.TextRange(scope.start.line, scope.start.column, scope.end.line, scope.end.column);
255
256
  return new Workspace.UISourceCode.UIFunctionBounds(uiSourceCode, range, name);
256
257
  }
257
- translateRawFramesStep(rawFrames, translatedFrames) {
258
+ async translateRawFramesStep(rawFrames, translatedFrames) {
258
259
  const frame = rawFrames[0];
259
260
  if (StackTraceImpl.Trie.isBuiltinFrame(frame)) {
260
261
  return false;
261
262
  }
262
- const sourceMapWithScopeInfoForFrame = (rawFrame) => {
263
+ const sourceMapWithScopeInfoForFrame = async (rawFrame) => {
263
264
  const script = this.#debuggerModel.scriptForId(rawFrame.scriptId ?? '');
264
265
  if (!script || this.#stubUISourceCodes.has(script)) {
265
266
  // Use fallback while source map is being loaded.
266
267
  return null;
267
268
  }
268
269
  const sourceMap = script.sourceMap();
270
+ await sourceMap?.waitForScopeInfo();
269
271
  return sourceMap?.hasScopeInfo() ? { sourceMap, script } : null;
270
272
  };
271
- const sourceMapAndScript = sourceMapWithScopeInfoForFrame(frame);
273
+ const sourceMapAndScript = await sourceMapWithScopeInfoForFrame(frame);
272
274
  if (!sourceMapAndScript) {
273
275
  return false;
274
276
  }