chrome-ai-bridge 1.0.2 → 1.0.4

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 (69) 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/bindings/CompilerScriptMapping.js +5 -3
  38. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/DebuggerWorkspaceBinding.js +7 -3
  39. package/build/node_modules/chrome-devtools-frontend/front_end/models/emulation/DeviceModeModel.js +1 -1
  40. package/build/node_modules/chrome-devtools-frontend/front_end/models/emulation/EmulatedDevices.js +14 -0
  41. package/build/node_modules/chrome-devtools-frontend/front_end/models/formatter/FormatterWorkerPool.js +8 -5
  42. package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/StackTraceImpl.js +70 -1
  43. package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/StackTraceModel.js +82 -30
  44. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/EventsSerializer.js +7 -2
  45. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/LanternComputationData.js +2 -2
  46. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/Processor.js +18 -19
  47. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/Styles.js +12 -4
  48. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/extras/Initiators.js +46 -0
  49. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/extras/TraceTree.js +4 -3
  50. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/extras/extras.js +1 -0
  51. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/LargestImagePaintHandler.js +2 -2
  52. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/LayoutShiftsHandler.js +1 -1
  53. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/MetaHandler.js +6 -0
  54. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/NetworkRequestsHandler.js +10 -1
  55. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/PageLoadMetricsHandler.js +44 -27
  56. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/helpers/Timing.js +9 -2
  57. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/Common.js +1 -6
  58. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LCPBreakdown.js +2 -2
  59. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LCPDiscovery.js +2 -4
  60. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/NetworkDependencyTree.js +3 -2
  61. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/RenderBlocking.js +1 -1
  62. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/types/TraceEvents.js +30 -11
  63. package/build/node_modules/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.js +28 -13
  64. package/build/node_modules/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/encode/encoder.js +1 -1
  65. package/build/node_modules/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/scopes.js +4 -0
  66. package/build/src/tools/chatgpt-web.js +102 -64
  67. package/build/src/tools/gemini-web.js +43 -16
  68. package/build/src/tools/pages.js +0 -1
  69. 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();
@@ -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
  }
@@ -141,6 +141,10 @@ export class DebuggerWorkspaceBinding {
141
141
  const model = target.model(StackTraceImpl.StackTraceModel.StackTraceModel);
142
142
  return await model.createFromProtocolRuntime(stackTrace, this.#translateRawFrames.bind(this));
143
143
  }
144
+ async createStackTraceFromDebuggerPaused(pausedDetails, target) {
145
+ const model = target.model(StackTraceImpl.StackTraceModel.StackTraceModel);
146
+ return await model.createFromDebuggerPaused(pausedDetails, this.#translateRawFrames.bind(this));
147
+ }
144
148
  async createLiveLocation(rawLocation, updateDelegate, locationPool) {
145
149
  const modelData = this.#debuggerModelToData.get(rawLocation.debuggerModel);
146
150
  if (!modelData) {
@@ -382,7 +386,7 @@ export class DebuggerWorkspaceBinding {
382
386
  }
383
387
  const modelData = this.#debuggerModelToData.get(target.model(SDK.DebuggerModel.DebuggerModel));
384
388
  if (modelData) {
385
- modelData.translateRawFramesStep(rawFrames, translatedFrames);
389
+ await modelData.translateRawFramesStep(rawFrames, translatedFrames);
386
390
  return;
387
391
  }
388
392
  const frame = rawFrames.shift();
@@ -473,8 +477,8 @@ class ModelData {
473
477
  scope = scope || await this.#resourceMapping.functionBoundsAtRawLocation(rawLocation);
474
478
  return scope;
475
479
  }
476
- translateRawFramesStep(rawFrames, translatedFrames) {
477
- if (!this.compilerMapping.translateRawFramesStep(rawFrames, translatedFrames)) {
480
+ async translateRawFramesStep(rawFrames, translatedFrames) {
481
+ if (!await this.compilerMapping.translateRawFramesStep(rawFrames, translatedFrames)) {
478
482
  this.#defaultTranslateRawFramesStep(rawFrames, translatedFrames);
479
483
  }
480
484
  }
@@ -107,7 +107,7 @@ export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper {
107
107
  this.#preferredSize = new Geometry.Size(1, 1);
108
108
  this.#initialized = false;
109
109
  this.#appliedDeviceSize = new Geometry.Size(1, 1);
110
- this.#appliedDeviceScaleFactor = window.devicePixelRatio;
110
+ this.#appliedDeviceScaleFactor = globalThis.devicePixelRatio;
111
111
  this.#appliedUserAgentType = "Desktop" /* UA.DESKTOP */;
112
112
  this.#scaleSetting = Common.Settings.Settings.instance().createSetting('emulation.device-scale', 1);
113
113
  // We've used to allow zero before.
@@ -550,6 +550,7 @@ const emulatedDevices = [
550
550
  },
551
551
  'capabilities': ['touch', 'mobile'],
552
552
  'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1',
553
+ 'user-agent-metadata': { 'platform': 'iOS', 'platformVersion': '18.5', 'architecture': '', 'model': 'iPhone', 'mobile': true },
553
554
  'type': 'phone',
554
555
  },
555
556
  {
@@ -569,6 +570,7 @@ const emulatedDevices = [
569
570
  },
570
571
  'capabilities': ['touch', 'mobile'],
571
572
  'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1',
573
+ 'user-agent-metadata': { 'platform': 'iOS', 'platformVersion': '18.5', 'architecture': '', 'model': 'iPhone', 'mobile': true },
572
574
  'type': 'phone',
573
575
  },
574
576
  {
@@ -588,6 +590,7 @@ const emulatedDevices = [
588
590
  },
589
591
  'capabilities': ['touch', 'mobile'],
590
592
  'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1',
593
+ 'user-agent-metadata': { 'platform': 'iOS', 'platformVersion': '18.5', 'architecture': '', 'model': 'iPhone', 'mobile': true },
591
594
  'type': 'phone',
592
595
  },
593
596
  {
@@ -607,6 +610,7 @@ const emulatedDevices = [
607
610
  },
608
611
  'capabilities': ['touch', 'mobile'],
609
612
  'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1',
613
+ 'user-agent-metadata': { 'platform': 'iOS', 'platformVersion': '18.5', 'architecture': '', 'model': 'iPhone', 'mobile': true },
610
614
  'type': 'phone',
611
615
  },
612
616
  {
@@ -706,6 +710,7 @@ const emulatedDevices = [
706
710
  },
707
711
  'capabilities': ['touch', 'mobile'],
708
712
  'user-agent': 'Mozilla/5.0 (iPad; CPU OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1',
713
+ 'user-agent-metadata': { 'platform': 'iOS', 'platformVersion': '18.5', 'architecture': '', 'model': 'iPad', 'mobile': true },
709
714
  'type': 'tablet',
710
715
  },
711
716
  {
@@ -725,6 +730,7 @@ const emulatedDevices = [
725
730
  },
726
731
  'capabilities': ['touch', 'mobile'],
727
732
  'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Safari/605.1.15',
733
+ 'user-agent-metadata': { 'platform': 'iOS', 'platformVersion': '18.5', 'architecture': '', 'model': 'iPad', 'mobile': true },
728
734
  'type': 'tablet',
729
735
  },
730
736
  {
@@ -744,6 +750,7 @@ const emulatedDevices = [
744
750
  },
745
751
  'capabilities': ['touch', 'mobile'],
746
752
  'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Safari/605.1.15',
753
+ 'user-agent-metadata': { 'platform': 'iOS', 'platformVersion': '18.5', 'architecture': '', 'model': 'iPad', 'mobile': true },
747
754
  'type': 'tablet',
748
755
  },
749
756
  {
@@ -982,6 +989,7 @@ const emulatedDevices = [
982
989
  },
983
990
  'capabilities': ['touch', 'mobile'],
984
991
  'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53',
992
+ 'user-agent-metadata': { 'platform': 'iOS', 'platformVersion': '7.1.2', 'architecture': '', 'model': 'iPhone', 'mobile': true },
985
993
  'type': 'phone',
986
994
  },
987
995
  {
@@ -1009,6 +1017,7 @@ const emulatedDevices = [
1009
1017
  },
1010
1018
  'capabilities': ['touch', 'mobile'],
1011
1019
  'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1',
1020
+ 'user-agent-metadata': { 'platform': 'iOS', 'platformVersion': '10.3.1', 'architecture': '', 'model': 'iPhone', 'mobile': true },
1012
1021
  'type': 'phone',
1013
1022
  },
1014
1023
  {
@@ -1036,6 +1045,7 @@ const emulatedDevices = [
1036
1045
  },
1037
1046
  'capabilities': ['touch', 'mobile'],
1038
1047
  'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',
1048
+ 'user-agent-metadata': { 'platform': 'iOS', 'platformVersion': '13.2.3', 'architecture': '', 'model': 'iPhone', 'mobile': true },
1039
1049
  'type': 'phone',
1040
1050
  },
1041
1051
  {
@@ -1063,6 +1073,7 @@ const emulatedDevices = [
1063
1073
  },
1064
1074
  'capabilities': ['touch', 'mobile'],
1065
1075
  'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',
1076
+ 'user-agent-metadata': { 'platform': 'iOS', 'platformVersion': '13.2.3', 'architecture': '', 'model': 'iPhone', 'mobile': true },
1066
1077
  'type': 'phone',
1067
1078
  },
1068
1079
  {
@@ -1076,6 +1087,7 @@ const emulatedDevices = [
1076
1087
  },
1077
1088
  'capabilities': ['touch', 'mobile'],
1078
1089
  'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',
1090
+ 'user-agent-metadata': { 'platform': 'iOS', 'platformVersion': '13.2.3', 'architecture': '', 'model': 'iPhone', 'mobile': true },
1079
1091
  'type': 'phone',
1080
1092
  },
1081
1093
  {
@@ -1499,6 +1511,7 @@ const emulatedDevices = [
1499
1511
  },
1500
1512
  'capabilities': ['touch', 'mobile'],
1501
1513
  'user-agent': 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',
1514
+ 'user-agent-metadata': { 'platform': 'iOS', 'platformVersion': '11.0', 'architecture': '', 'model': 'iPad', 'mobile': true },
1502
1515
  'type': 'tablet',
1503
1516
  },
1504
1517
  {
@@ -1512,6 +1525,7 @@ const emulatedDevices = [
1512
1525
  },
1513
1526
  'capabilities': ['touch', 'mobile'],
1514
1527
  'user-agent': 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',
1528
+ 'user-agent-metadata': { 'platform': 'iOS', 'platformVersion': '11.0', 'architecture': '', 'model': 'iPad', 'mobile': true },
1515
1529
  'type': 'tablet',
1516
1530
  },
1517
1531
  {
@@ -6,13 +6,16 @@ let formatterWorkerPoolInstance;
6
6
  export class FormatterWorkerPool {
7
7
  taskQueue;
8
8
  workerTasks;
9
- constructor() {
9
+ entrypointURL;
10
+ constructor(entrypointURL) {
10
11
  this.taskQueue = [];
11
12
  this.workerTasks = new Map();
13
+ this.entrypointURL =
14
+ entrypointURL ?? import.meta.resolve('../../entrypoints/formatter_worker/formatter_worker-entrypoint.js');
12
15
  }
13
- static instance() {
14
- if (!formatterWorkerPoolInstance) {
15
- formatterWorkerPoolInstance = new FormatterWorkerPool();
16
+ static instance(opts) {
17
+ if (!formatterWorkerPoolInstance || opts?.forceNew) {
18
+ formatterWorkerPoolInstance = new FormatterWorkerPool(opts?.entrypointURL);
16
19
  }
17
20
  return formatterWorkerPoolInstance;
18
21
  }
@@ -31,7 +34,7 @@ export class FormatterWorkerPool {
31
34
  formatterWorkerPoolInstance = undefined;
32
35
  }
33
36
  createWorker() {
34
- const worker = Platform.HostRuntime.HOST_RUNTIME.createWorker(new URL('../../entrypoints/formatter_worker/formatter_worker-entrypoint.js', import.meta.url).toString());
37
+ const worker = Platform.HostRuntime.HOST_RUNTIME.createWorker(this.entrypointURL);
35
38
  worker.onmessage = this.onWorkerMessage.bind(this, worker);
36
39
  worker.onerror = this.onWorkerError.bind(this, worker);
37
40
  return worker;