chrome-devtools-frontend 1.0.1519267 → 1.0.1520535

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 (94) 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 +26 -5
  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 +50 -48
  10. package/front_end/models/ai_assistance/agents/PerformanceAnnotationsAgent.ts +4 -4
  11. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +128 -30
  12. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +98 -63
  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 +36 -21
  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/badges/UserBadges.ts +5 -4
  22. package/front_end/models/javascript_metadata/NativeFunctions.js +5 -1
  23. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +14 -7
  24. package/front_end/panels/ai_assistance/PatchWidget.ts +17 -55
  25. package/front_end/panels/ai_assistance/components/ChatView.ts +45 -69
  26. package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +47 -1
  27. package/front_end/panels/ai_assistance/components/chatView.css +13 -1
  28. package/front_end/panels/animation/AnimationTimeline.ts +1 -1
  29. package/front_end/panels/animation/animationTimeline.css +4 -0
  30. package/front_end/panels/application/preloading/components/PreloadingString.ts +2 -5
  31. package/front_end/panels/common/AiCodeCompletionTeaser.ts +5 -0
  32. package/front_end/panels/common/aiCodeCompletionTeaser.css +6 -1
  33. package/front_end/panels/console/ConsolePrompt.ts +6 -0
  34. package/front_end/panels/console/ConsoleView.ts +4 -2
  35. package/front_end/panels/coverage/CoverageListView.ts +146 -198
  36. package/front_end/panels/coverage/CoverageView.ts +48 -18
  37. package/front_end/panels/mobile_throttling/NetworkThrottlingSelector.ts +2 -0
  38. package/front_end/panels/network/NetworkDataGridNode.ts +22 -0
  39. package/front_end/panels/network/NetworkLogViewColumns.ts +9 -0
  40. package/front_end/panels/recorder/components/CreateRecordingView.ts +2 -0
  41. package/front_end/panels/search/SearchResultsPane.ts +48 -15
  42. package/front_end/panels/search/SearchView.ts +33 -30
  43. package/front_end/panels/search/searchView.css +0 -2
  44. package/front_end/panels/settings/components/SyncSection.ts +4 -4
  45. package/front_end/panels/sources/AiCodeCompletionPlugin.ts +1 -4
  46. package/front_end/panels/sources/DebuggerPlugin.ts +4 -0
  47. package/front_end/panels/timeline/ThirdPartyTreeView.ts +1 -1
  48. package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +0 -8
  49. package/front_end/panels/timeline/TimelineFlameChartView.ts +5 -5
  50. package/front_end/panels/timeline/TimelinePanel.ts +2 -0
  51. package/front_end/panels/timeline/TimelineTreeView.ts +1 -1
  52. package/front_end/panels/timeline/components/ExportTraceOptions.ts +56 -4
  53. package/front_end/panels/timeline/components/exportTraceOptions.css +5 -0
  54. package/front_end/third_party/chromium/README.chromium +1 -1
  55. package/front_end/third_party/lighthouse/README.chromium +8 -1
  56. package/front_end/third_party/puppeteer/README.chromium +2 -2
  57. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Realm.d.ts +2 -2
  58. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.d.ts +1 -1
  59. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.js +1 -1
  60. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
  61. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/BrowserLauncher.js +1 -1
  62. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/BrowserLauncher.js.map +1 -1
  63. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/PipeTransport.d.ts.map +1 -1
  64. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/PipeTransport.js +15 -16
  65. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/PipeTransport.js.map +1 -1
  66. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +2 -2
  67. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +2 -2
  68. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Function.js +1 -1
  69. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Function.js.map +1 -1
  70. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  71. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +4 -4
  72. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.d.ts +1 -1
  73. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.js +1 -1
  74. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/BrowserLauncher.js +1 -1
  75. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/BrowserLauncher.js.map +1 -1
  76. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/PipeTransport.d.ts.map +1 -1
  77. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/PipeTransport.js +15 -16
  78. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/PipeTransport.js.map +1 -1
  79. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +2 -2
  80. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +2 -2
  81. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/Function.js +1 -1
  82. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/Function.js.map +1 -1
  83. package/front_end/third_party/puppeteer/package/package.json +3 -2
  84. package/front_end/third_party/puppeteer/package/src/generated/version.ts +1 -1
  85. package/front_end/third_party/puppeteer/package/src/node/BrowserLauncher.ts +1 -1
  86. package/front_end/third_party/puppeteer/package/src/node/PipeTransport.ts +15 -17
  87. package/front_end/third_party/puppeteer/package/src/revisions.ts +2 -2
  88. package/front_end/third_party/puppeteer/package/src/util/Function.ts +1 -1
  89. package/front_end/tsconfig.json +12 -1
  90. package/front_end/ui/legacy/InspectorView.ts +86 -13
  91. package/front_end/ui/legacy/TabbedPane.ts +2 -1
  92. package/front_end/ui/visual_logging/KnownContextValues.ts +6 -0
  93. package/front_end/ui/visual_logging/LoggingEvents.ts +1 -1
  94. package/package.json +1 -1
@@ -21,17 +21,17 @@ export class PerformanceTraceFormatter {
21
21
  #focus: AgentFocus;
22
22
  #parsedTrace: Trace.TraceModel.ParsedTrace;
23
23
  #insightSet: Trace.Insights.Types.InsightSet|null;
24
- protected eventsSerializer: Trace.EventsSerializer.EventsSerializer;
24
+ #eventsSerializer: Trace.EventsSerializer.EventsSerializer;
25
25
 
26
26
  constructor(focus: AgentFocus) {
27
27
  this.#focus = focus;
28
- this.#parsedTrace = focus.data.parsedTrace;
29
- this.#insightSet = focus.data.insightSet;
30
- this.eventsSerializer = focus.eventsSerializer;
28
+ this.#parsedTrace = focus.parsedTrace;
29
+ this.#insightSet = focus.insightSet;
30
+ this.#eventsSerializer = focus.eventsSerializer;
31
31
  }
32
32
 
33
33
  serializeEvent(event: Trace.Types.Events.Event): string {
34
- const key = this.eventsSerializer.keyForEvent(event);
34
+ const key = this.#eventsSerializer.keyForEvent(event);
35
35
  return `(eventKey: ${key}, ts: ${event.ts})`;
36
36
  }
37
37
 
@@ -133,7 +133,10 @@ export class PerformanceTraceFormatter {
133
133
  if (lcp || cls || inp) {
134
134
  parts.push('Metrics (lab / observed):');
135
135
  if (lcp) {
136
- parts.push(` - LCP: ${Math.round(lcp.value / 1000)} ms, event: ${this.serializeEvent(lcp.event)}`);
136
+ const nodeId = insightSet?.model.LCPBreakdown.lcpEvent?.args.data?.nodeId;
137
+ const nodeIdText = nodeId !== undefined ? `, nodeId: ${nodeId}` : '';
138
+ parts.push(
139
+ ` - LCP: ${Math.round(lcp.value / 1000)} ms, event: ${this.serializeEvent(lcp.event)}${nodeIdText}`);
137
140
  const subparts = insightSet?.model.LCPBreakdown.subparts;
138
141
  if (subparts) {
139
142
  const serializeSubpart = (subpart: Trace.Insights.Models.LCPBreakdown.Subpart): string => {
@@ -442,19 +445,7 @@ export class PerformanceTraceFormatter {
442
445
  }
443
446
 
444
447
  formatCallTree(tree: AICallTree, headerLevel = 1): string {
445
- const results = [tree.serialize(headerLevel), ''];
446
-
447
- // TODO(b/425270067): add eventKey to tree.serialize, but need to wait for other
448
- // performance agent to be consolidated.
449
- results.push('#'.repeat(headerLevel) + ' Node id to eventKey\n');
450
- results.push('These node ids correspond to the call tree nodes listed in the above section.\n');
451
- tree.breadthFirstWalk(tree.rootNode.children().values(), (node, nodeId) => {
452
- results.push(`${nodeId}: ${this.eventsSerializer.keyForEvent(node.event)}`);
453
- });
454
-
455
- results.push('\nIMPORTANT: Never show eventKey to the user.');
456
-
457
- return results.join('\n');
448
+ return `${tree.serialize(headerLevel)}\n\nIMPORTANT: Never show eventKey to the user.`;
458
449
  }
459
450
 
460
451
  formatNetworkRequests(
@@ -580,7 +571,7 @@ export class PerformanceTraceFormatter {
580
571
  const initiators = this.#getInitiatorChain(parsedTrace, request);
581
572
  const initiatorUrls = initiators.map(initiator => initiator.args.data.url);
582
573
 
583
- const eventKey = this.eventsSerializer.keyForEvent(request);
574
+ const eventKey = this.#eventsSerializer.keyForEvent(request);
584
575
  const eventKeyLine = eventKey ? `eventKey: ${eventKey}\n` : '';
585
576
 
586
577
  return `${titlePrefix}: ${url}
@@ -635,6 +626,30 @@ Network requests data:
635
626
  return networkDataString + '\n\n' + urlsMapString + '\n\n' + allRequestsText;
636
627
  }
637
628
 
629
+ static callFrameDataFormatDescription = `Each call frame is presented in the following format:
630
+
631
+ 'id;eventKey;name;duration;selfTime;urlIndex;childRange;[S]'
632
+
633
+ Key definitions:
634
+
635
+ * id: A unique numerical identifier for the call frame. Never mention this id in the output to the user.
636
+ * eventKey: String that uniquely identifies this event in the flame chart.
637
+ * name: A concise string describing the call frame (e.g., 'Evaluate Script', 'render', 'fetchData').
638
+ * duration: The total execution time of the call frame, including its children.
639
+ * selfTime: The time spent directly within the call frame, excluding its children's execution.
640
+ * urlIndex: Index referencing the "All URLs" list. Empty if no specific script URL is associated.
641
+ * childRange: Specifies the direct children of this node using their IDs. If empty ('' or 'S' at the end), the node has no children. If a single number (e.g., '4'), the node has one child with that ID. If in the format 'firstId-lastId' (e.g., '4-5'), it indicates a consecutive range of child IDs from 'firstId' to 'lastId', inclusive.
642
+ * S: _Optional_. The letter 'S' terminates the line if that call frame was selected by the user.
643
+
644
+ Example Call Tree:
645
+
646
+ 1;r-123;main;500;100;;
647
+ 2;r-124;update;200;50;;3
648
+ 3;p-49575-15428179-2834-374;animate;150;20;0;4-5;S
649
+ 4;p-49575-15428179-3505-1162;calculatePosition;80;80;;
650
+ 5;p-49575-15428179-5391-2767;applyStyles;50;50;;
651
+ `;
652
+
638
653
  /**
639
654
  * Network requests format description that is sent to the model as a fact.
640
655
  */
@@ -727,7 +742,7 @@ The order of headers corresponds to an internal fixed list. If a header is not p
727
742
 
728
743
  const parts = [
729
744
  urlIndex,
730
- this.eventsSerializer.keyForEvent(request) ?? '',
745
+ this.#eventsSerializer.keyForEvent(request) ?? '',
731
746
  queuedTime,
732
747
  requestSentTime,
733
748
  downloadCompleteTime,
@@ -0,0 +1,75 @@
1
+ Title: AICallTree supports NodeJS traces that do not have a "main thread"
2
+ Content:
3
+ # All URLs:
4
+
5
+ * 0: node:internal/main/run_main_module
6
+ * 1: node:internal/modules/run_main
7
+ * 2: node:internal/modules/cjs/loader
8
+ * 3: file:///Users/andoli/Desktop/mocks/fixnodeinspector/app.js
9
+
10
+ # Call tree:
11
+
12
+ 1;p-1-1-0-2;(anonymous);2370;;0;2
13
+ 2;p-1-1-0-3;executeUserEntryPoint;2370;;1;3
14
+ 3;p-1-1-0-4;Module._load;2370;;2;4
15
+ 4;p-1-1-0-5;Module.load;2370;;2;5
16
+ 5;p-1-1-0-6;Module._extensions..js;2370;;2;6
17
+ 6;p-1-1-0-7;Module._compile;2370;;2;7
18
+ 7;p-1-1-0-8;callAndPauseOnStart;2370;;;8;S
19
+ 8;p-1-1-0-9;(anonymous);2370;2370;3;
20
+ === end content
21
+
22
+ Title: AICallTree serializes a simple tree
23
+ Content:
24
+ # All URLs:
25
+
26
+ * 0: https://www.gstatic.com/devrel-devsite/prod/vafe2e13ca17bb026e70df42a2ead1c8192750e86a12923a88eda839025dabf95/js/devsite_app_module.js
27
+
28
+ # Call tree:
29
+
30
+ 1;r-36071;Task;0.2;;;2
31
+ 2;r-36072;Timer fired;0.2;;;3
32
+ 3;r-36076;Function call;0.2;;0;4
33
+ 4;p-74406-259-16342-528;_ds.q.ns;0.2;;0;5;S
34
+ 5;p-74406-259-16342-529;clearTimeout;0.2;0;;6
35
+ 6;r-36082;Recalculate style;0.2;0.2;;
36
+ === end content
37
+
38
+ Title: AICallTree serializes a simple tree in a concise format
39
+ Content:
40
+ # All URLs:
41
+
42
+ * 0: https://www.gstatic.com/devrel-devsite/prod/vafe2e13ca17bb026e70df42a2ead1c8192750e86a12923a88eda839025dabf95/js/devsite_app_module.js
43
+
44
+ # Call tree:
45
+
46
+ 1;r-36071;Task;0.2;;;2
47
+ 2;r-36072;Timer fired;0.2;;;3
48
+ 3;r-36076;Function call;0.2;;0;4
49
+ 4;p-74406-259-16342-528;_ds.q.ns;0.2;;0;5;S
50
+ 5;p-74406-259-16342-529;clearTimeout;0.2;0;;6
51
+ 6;r-36082;Recalculate style;0.2;0.2;;
52
+ === end content
53
+
54
+ Title: AICallTree serializes a tree in a concise format
55
+ Content:
56
+ # All URLs:
57
+
58
+ * 0: https://www.gstatic.com/firebasejs/6.6.1/firebase-performance.js
59
+
60
+ # Call tree:
61
+
62
+ 1;r-5764;Task;0.9;0;;2;S
63
+ 2;r-5765;Timer fired;0.9;0;;3
64
+ 3;r-5766;Function call;0.9;0.1;0;4
65
+ 4;p-73704-775-2873-705;(anonymous);0.8;;0;5
66
+ 5;p-73704-775-2873-706;(anonymous);0.8;;0;6-8
67
+ 6;p-73704-775-2873-707;Ot.getEntriesByType;0.1;;0;8
68
+ 7;p-73704-775-2874-709;le.createOobTrace;0.6;0.2;0;9-11
69
+ 8;p-73704-775-2873-708;getEntriesByType;0.1;0.1;;
70
+ 9;p-73704-775-2875-710;le;0.1;0.1;0;
71
+ 10;p-73704-775-2877-711;ie;0.2;;0;11-13
72
+ 11;p-73704-775-2877-712;Ot.requiredApisAvailable;0.2;0.2;0;
73
+ 12;p-73704-775-2879-713;oe;0;;0;13
74
+ 13;p-73704-775-2879-714;setTimeout;0;0;;
75
+ === end content
@@ -24,6 +24,10 @@ export interface FromTimeOnThreadOptions {
24
24
  }
25
25
 
26
26
  export class AICallTree {
27
+ // Note: ideally this is passed in (or lived on ParsedTrace), but this class is
28
+ // stateless (mostly, there's a cache for some stuff) so it doesn't match much.
29
+ #eventsSerializer = new Trace.EventsSerializer.EventsSerializer();
30
+
27
31
  constructor(
28
32
  public selectedNode: Trace.Extras.TraceTree.Node|null,
29
33
  public rootNode: Trace.Extras.TraceTree.TopDownRootNode,
@@ -307,7 +311,10 @@ export class AICallTree {
307
311
  // 1. ID
308
312
  const idStr = String(nodeId);
309
313
 
310
- // 2. Name
314
+ // 2. eventKey
315
+ const eventKey = this.#eventsSerializer.keyForEvent(node.event);
316
+
317
+ // 3. Name
311
318
  const name = Trace.Name.forEntry(event, parsedTrace);
312
319
 
313
320
  // Round milliseconds to one decimal place, return empty string if zero/undefined
@@ -318,13 +325,13 @@ export class AICallTree {
318
325
  return String(Math.round(num * 10) / 10);
319
326
  };
320
327
 
321
- // 3. Duration
328
+ // 4. Duration
322
329
  const durationStr = roundToTenths(node.totalTime);
323
330
 
324
- // 4. Self Time
331
+ // 5. Self Time
325
332
  const selfTimeStr = roundToTenths(node.selfTime);
326
333
 
327
- // 5. URL Index
334
+ // 6. URL Index
328
335
  const url = SourceMapsResolver.SourceMapsResolver.resolvedURLForEntry(parsedTrace, event);
329
336
  let urlIndexStr = '';
330
337
  if (url) {
@@ -336,7 +343,7 @@ export class AICallTree {
336
343
  }
337
344
  }
338
345
 
339
- // 6. Child Range
346
+ // 7. Child Range
340
347
  const children = Array.from(node.children().values());
341
348
  let childRangeStr = '';
342
349
  if (childStartingNodeIndex) {
@@ -344,11 +351,12 @@ export class AICallTree {
344
351
  `${childStartingNodeIndex}-${childStartingNodeIndex + children.length}`;
345
352
  }
346
353
 
347
- // 7. Selected Marker
354
+ // 8. Selected Marker
348
355
  const selectedMarker = selectedNode?.event === node.event ? 'S' : '';
349
356
 
350
357
  // Combine fields
351
358
  let line = idStr;
359
+ line += ';' + eventKey;
352
360
  line += ';' + name;
353
361
  line += ';' + durationStr;
354
362
  line += ';' + selfTimeStr;
@@ -4,11 +4,14 @@
4
4
 
5
5
  import * as Trace from '../../../models/trace/trace.js';
6
6
 
7
- import type {AICallTree} from './AICallTree.js';
7
+ import {AICallTree} from './AICallTree.js';
8
8
 
9
- export interface AgentFocusData {
9
+ interface AgentFocusData {
10
10
  parsedTrace: Trace.TraceModel.ParsedTrace;
11
11
  insightSet: Trace.Insights.Types.InsightSet|null;
12
+ /** Note: at most one of event or callTree is non-null. */
13
+ event: Trace.Types.Events.Event|null;
14
+ /** Note: at most one of event or callTree is non-null. */
12
15
  callTree: AICallTree|null;
13
16
  insight: Trace.Insights.Types.InsightModel|null;
14
17
  }
@@ -30,6 +33,7 @@ export class AgentFocus {
30
33
  return new AgentFocus({
31
34
  parsedTrace,
32
35
  insightSet,
36
+ event: null,
33
37
  callTree: null,
34
38
  insight: null,
35
39
  });
@@ -45,11 +49,22 @@ export class AgentFocus {
45
49
  return new AgentFocus({
46
50
  parsedTrace,
47
51
  insightSet,
52
+ event: null,
48
53
  callTree: null,
49
54
  insight,
50
55
  });
51
56
  }
52
57
 
58
+ static fromEvent(parsedTrace: Trace.TraceModel.ParsedTrace, event: Trace.Types.Events.Event): AgentFocus {
59
+ if (!parsedTrace.insights) {
60
+ throw new Error('missing insights');
61
+ }
62
+
63
+ const insightSet = getFirstInsightSet(parsedTrace.insights);
64
+ const result = AgentFocus.#getCallTreeOrEvent(parsedTrace, event);
65
+ return new AgentFocus({parsedTrace, insightSet, event: result.event, callTree: result.callTree, insight: null});
66
+ }
67
+
53
68
  static fromCallTree(callTree: AICallTree): AgentFocus {
54
69
  const insights = callTree.parsedTrace.insights;
55
70
 
@@ -65,7 +80,7 @@ export class AgentFocus {
65
80
  getFirstInsightSet(insights);
66
81
  }
67
82
 
68
- return new AgentFocus({parsedTrace: callTree.parsedTrace, insightSet, callTree, insight: null});
83
+ return new AgentFocus({parsedTrace: callTree.parsedTrace, insightSet, event: null, callTree, insight: null});
69
84
  }
70
85
 
71
86
  #data: AgentFocusData;
@@ -75,8 +90,26 @@ export class AgentFocus {
75
90
  this.#data = data;
76
91
  }
77
92
 
78
- get data(): AgentFocusData {
79
- return this.#data;
93
+ get parsedTrace(): Trace.TraceModel.ParsedTrace {
94
+ return this.#data.parsedTrace;
95
+ }
96
+
97
+ get insightSet(): Trace.Insights.Types.InsightSet|null {
98
+ return this.#data.insightSet;
99
+ }
100
+
101
+ /** Note: at most one of event or callTree is non-null. */
102
+ get event(): Trace.Types.Events.Event|null {
103
+ return this.#data.event;
104
+ }
105
+
106
+ /** Note: at most one of event or callTree is non-null. */
107
+ get callTree(): AICallTree|null {
108
+ return this.#data.callTree;
109
+ }
110
+
111
+ get insight(): Trace.Insights.Types.InsightModel|null {
112
+ return this.#data.insight;
80
113
  }
81
114
 
82
115
  withInsight(insight: Trace.Insights.Types.InsightModel|null): AgentFocus {
@@ -85,9 +118,11 @@ export class AgentFocus {
85
118
  return focus;
86
119
  }
87
120
 
88
- withCallTree(callTree: AICallTree|null): AgentFocus {
121
+ withEvent(event: Trace.Types.Events.Event|null): AgentFocus {
89
122
  const focus = new AgentFocus(this.#data);
90
- focus.#data.callTree = callTree;
123
+ const result = AgentFocus.#getCallTreeOrEvent(this.#data.parsedTrace, event);
124
+ focus.#data.callTree = result.callTree;
125
+ focus.#data.event = result.event;
91
126
  return focus;
92
127
  }
93
128
 
@@ -102,6 +137,26 @@ export class AgentFocus {
102
137
  throw err;
103
138
  }
104
139
  }
140
+
141
+ /**
142
+ * If an event is a call tree, this returns that call tree and a null event.
143
+ * If not a call tree, this only returns a non-null event if the event is a network
144
+ * request.
145
+ * This is an arbitrary limitation – it should be removed, but first we need to
146
+ * improve the agent's knowledge of events that are not main-thread or network
147
+ * events.
148
+ */
149
+ static #getCallTreeOrEvent(parsedTrace: Trace.TraceModel.ParsedTrace, event: Trace.Types.Events.Event|null):
150
+ {callTree: AICallTree|null, event: Trace.Types.Events.Event|null} {
151
+ const callTree = event && AICallTree.fromEvent(event, parsedTrace);
152
+ if (callTree) {
153
+ return {callTree, event: null};
154
+ }
155
+ if (event && Trace.Types.Events.isSyntheticNetworkRequest(event)) {
156
+ return {callTree: null, event};
157
+ }
158
+ return {callTree: null, event: null};
159
+ }
105
160
  }
106
161
 
107
162
  export function getPerformanceAgentFocusFromModel(model: Trace.TraceModel.Model): AgentFocus|null {
@@ -332,7 +332,7 @@ export class AiCodeCompletion extends Common.ObjectWrapper.ObjectWrapper<EventTy
332
332
  sampleId,
333
333
  startTime,
334
334
  onImpression: this.#registerUserImpression.bind(this),
335
- clearCachedRequest: this.#clearCachedRequest.bind(this),
335
+ clearCachedRequest: this.clearCachedRequest.bind(this),
336
336
  })
337
337
  });
338
338
 
@@ -415,10 +415,6 @@ export class AiCodeCompletion extends Common.ObjectWrapper.ObjectWrapper<EventTy
415
415
  this.#aidaRequestCache = {request, response};
416
416
  }
417
417
 
418
- #clearCachedRequest(): void {
419
- this.#aidaRequestCache = undefined;
420
- }
421
-
422
418
  #registerUserImpression(rpcGlobalId: Host.AidaClient.RpcGlobalId, sampleId: number, latency: number): void {
423
419
  const seconds = Math.floor(latency / 1_000);
424
420
  const remainingMs = latency % 1_000;
@@ -461,6 +457,10 @@ export class AiCodeCompletion extends Common.ObjectWrapper.ObjectWrapper<EventTy
461
457
  Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiCodeCompletionSuggestionAccepted);
462
458
  }
463
459
 
460
+ clearCachedRequest(): void {
461
+ this.#aidaRequestCache = undefined;
462
+ }
463
+
464
464
  onTextChanged(
465
465
  prefix: string, suffix: string, cursorPositionAtRequest: number,
466
466
  inferenceLanguage?: Host.AidaClient.AidaInferenceLanguage): void {
@@ -13,7 +13,12 @@ export enum BadgeAction {
13
13
  STARTED_AI_CONVERSATION = 'started-ai-conversation',
14
14
  // TODO(ergunsh): Instrument performance insight clicks.
15
15
  PERFORMANCE_INSIGHT_CLICKED = 'performance-insight-clicked',
16
- DEBUGGER_PAUSED = 'debugger-paused'
16
+ DEBUGGER_PAUSED = 'debugger-paused',
17
+ BREAKPOINT_ADDED = 'breakpoint-added',
18
+ CONSOLE_PROMPT_EXECUTED = 'console-prompt-executed',
19
+ PERFORMANCE_RECORDING_STARTED = 'performance-recording-started',
20
+ NETWORK_SPEED_THROTTLED = 'network-speed-throttled',
21
+ RECORDER_RECORDING_STARTED = 'recorder-recording-started',
17
22
  }
18
23
 
19
24
  export type BadgeActionEvents = Record<BadgeAction, void>;
@@ -19,6 +19,11 @@ export class StarterBadge extends Badge {
19
19
  BadgeAction.RECEIVE_BADGES_SETTING_ENABLED,
20
20
  BadgeAction.CSS_RULE_MODIFIED,
21
21
  BadgeAction.DOM_ELEMENT_OR_ATTRIBUTE_EDITED,
22
+ BadgeAction.BREAKPOINT_ADDED,
23
+ BadgeAction.CONSOLE_PROMPT_EXECUTED,
24
+ BadgeAction.PERFORMANCE_RECORDING_STARTED,
25
+ BadgeAction.NETWORK_SPEED_THROTTLED,
26
+ BadgeAction.RECORDER_RECORDING_STARTED,
22
27
  ] as const;
23
28
 
24
29
  handleAction(action: BadgeAction): void {
@@ -163,10 +163,11 @@ export class UserBadges extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
163
163
  return;
164
164
  }
165
165
 
166
- const [gdpProfile, isEligibleToCreateProfile] = await Promise.all([
167
- Host.GdpClient.GdpClient.instance().getProfile(),
168
- Host.GdpClient.GdpClient.instance().isEligibleToCreateProfile(),
169
- ]);
166
+ const gdpProfile = await Host.GdpClient.GdpClient.instance().getProfile();
167
+ let isEligibleToCreateProfile = Boolean(gdpProfile);
168
+ if (!gdpProfile) {
169
+ isEligibleToCreateProfile = await Host.GdpClient.GdpClient.instance().isEligibleToCreateProfile();
170
+ }
170
171
 
171
172
  // User does not have a GDP profile & not eligible to create one.
172
173
  // So, we don't activate any badges for them.
@@ -7277,6 +7277,10 @@ export const NativeFunctions = [
7277
7277
  name: "unregisterTool",
7278
7278
  signatures: [["tool_name"]]
7279
7279
  },
7280
+ {
7281
+ name: "provideContext",
7282
+ signatures: [["params"]]
7283
+ },
7280
7284
  {
7281
7285
  name: "SnapEvent",
7282
7286
  signatures: [["type","?eventInitDict"]]
@@ -7978,7 +7982,7 @@ export const NativeFunctions = [
7978
7982
  },
7979
7983
  {
7980
7984
  name: "constant",
7981
- signatures: [["tensor"],["desc","buffer"],["type","value"]]
7985
+ signatures: [["tensor"],["desc","buffer"]]
7982
7986
  },
7983
7987
  {
7984
7988
  name: "argMin",
@@ -266,11 +266,18 @@ async function getEmptyStateSuggestions(
266
266
  }
267
267
  }
268
268
 
269
- function getMarkdownRenderer(context: AiAssistanceModel.ConversationContext<unknown>|null):
270
- MarkdownRendererWithCodeBlock {
271
- if (context instanceof AiAssistanceModel.PerformanceTraceContext && !context.external) {
272
- const focus = context.getItem();
273
- return new PerformanceAgentMarkdownRenderer(focus.lookupEvent.bind(focus));
269
+ function getMarkdownRenderer(
270
+ context: AiAssistanceModel.ConversationContext<unknown>|null,
271
+ conversation?: AiAssistanceModel.Conversation): MarkdownRendererWithCodeBlock {
272
+ if (context instanceof AiAssistanceModel.PerformanceTraceContext) {
273
+ if (!context.external) {
274
+ const focus = context.getItem();
275
+ return new PerformanceAgentMarkdownRenderer(
276
+ focus.parsedTrace.data.Meta.mainFrameId, focus.lookupEvent.bind(focus));
277
+ }
278
+ } else if (conversation?.type === AiAssistanceModel.ConversationType.PERFORMANCE) {
279
+ // Handle historical conversations (can't linkify anything).
280
+ return new PerformanceAgentMarkdownRenderer();
274
281
  }
275
282
 
276
283
  return new MarkdownRendererWithCodeBlock();
@@ -850,7 +857,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
850
857
 
851
858
  override async performUpdate(): Promise<void> {
852
859
  const emptyStateSuggestions = await getEmptyStateSuggestions(this.#selectedContext, this.#conversation);
853
- const markdownRenderer = getMarkdownRenderer(this.#selectedContext);
860
+ const markdownRenderer = getMarkdownRenderer(this.#selectedContext, this.#conversation);
854
861
 
855
862
  this.view(
856
863
  {
@@ -1066,7 +1073,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1066
1073
  return Common.Revealer.reveal(context.getItem().uiLocation(0, 0));
1067
1074
  }
1068
1075
  if (context instanceof AiAssistanceModel.PerformanceTraceContext) {
1069
- const focus = context.getItem().data;
1076
+ const focus = context.getItem();
1070
1077
  if (focus.callTree) {
1071
1078
  const event = focus.callTree.selectedNode?.event ?? focus.callTree.rootNode.event;
1072
1079
  const revealable = new SDK.TraceObject.RevealableEvent(event);
@@ -182,7 +182,6 @@ export interface ViewInput {
182
182
  }
183
183
 
184
184
  export interface ViewOutput {
185
- tooltipRef?: Directives.Ref<HTMLElement>;
186
185
  changeRef?: Directives.Ref<HTMLElement>;
187
186
  summaryRef?: Directives.Ref<HTMLElement>;
188
187
  }
@@ -211,7 +210,6 @@ export class PatchWidget extends UI.Widget.Widget {
211
210
  #automaticFileSystem =
212
211
  Persistence.AutomaticFileSystemManager.AutomaticFileSystemManager.instance().automaticFileSystem;
213
212
  #applyToDisconnectedAutomaticWorkspace = false;
214
- #popoverHelper: UI.PopoverHelper.PopoverHelper|null = null;
215
213
  // `rpcId` from the `applyPatch` request
216
214
  #rpcId: Host.AidaClient.RpcGlobalId|null = null;
217
215
 
@@ -228,7 +226,7 @@ export class PatchWidget extends UI.Widget.Widget {
228
226
  if (!input.changeSummary && input.patchSuggestionState === PatchSuggestionState.INITIAL) {
229
227
  return;
230
228
  }
231
- output.tooltipRef = output.tooltipRef ?? Directives.createRef<HTMLElement>();
229
+
232
230
  output.changeRef = output.changeRef ?? Directives.createRef<HTMLElement>();
233
231
  output.summaryRef = output.summaryRef ?? Directives.createRef<HTMLElement>();
234
232
 
@@ -390,8 +388,23 @@ export class PatchWidget extends UI.Widget.Widget {
390
388
  .jslogContext=${'patch-widget.info-tooltip-trigger'}
391
389
  .iconName=${'info'}
392
390
  .variant=${Buttons.Button.Variant.ICON}
393
- .title=${input.applyToWorkspaceTooltipText}
394
391
  ></devtools-button>
392
+ <devtools-tooltip
393
+ id="info-tooltip"
394
+ variant=${'rich'}
395
+ >
396
+ <div class="info-tooltip-container">
397
+ ${input.applyToWorkspaceTooltipText}
398
+ <button
399
+ class="link tooltip-link"
400
+ role="link"
401
+ jslog=${VisualLogging.link('open-ai-settings').track({
402
+ click: true,
403
+ })}
404
+ @click=${input.onLearnMoreTooltipClick}
405
+ >${lockedString(UIStringsNotTranslate.learnMore)}</button>
406
+ </div>
407
+ </devtools-tooltip>
395
408
  </div>
396
409
  </div>`;
397
410
  }
@@ -417,62 +430,11 @@ export class PatchWidget extends UI.Widget.Widget {
417
430
 
418
431
  render(template, target, {host: target});
419
432
  });
420
- // We're using PopoverHelper as a workaround instead of using <devtools-tooltip>. See the bug for more details.
421
- // TODO: Update here when b/409965560 is fixed.
422
- this.#popoverHelper = new UI.PopoverHelper.PopoverHelper(this.contentElement, event => {
423
- // There are two ways this event is received for showing a popover case:
424
- // * The latest element on the composed path is `<devtools-button>`
425
- // * The 2nd element on the composed path is `<devtools-button>` (the last element is the `<button>` inside it.)
426
- const hoveredNode = event.composedPath()[0];
427
- const maybeDevToolsButton = event.composedPath()[2];
428
-
429
- const popoverShownNode = hoveredNode instanceof HTMLElement && hoveredNode.getAttribute('aria-details') === 'info-tooltip' ? hoveredNode
430
- : maybeDevToolsButton instanceof HTMLElement && maybeDevToolsButton.getAttribute('aria-details') === 'info-tooltip' ? maybeDevToolsButton
431
- : null;
432
- if (!popoverShownNode) {
433
- return null;
434
- }
435
- return {
436
- box: popoverShownNode.boxInWindow(),
437
- show: async (popover: UI.GlassPane.GlassPane) => {
438
- // clang-format off
439
- render(html`
440
- <style>
441
- .info-tooltip-container {
442
- max-width: var(--sys-size-28);
443
- padding: var(--sys-size-4) var(--sys-size-5);
444
-
445
- .tooltip-link {
446
- display: block;
447
- margin-top: var(--sys-size-4);
448
- color: var(--sys-color-primary);
449
- padding-left: 0;
450
- }
451
- }
452
- </style>
453
- <div class="info-tooltip-container">
454
- ${UIStringsNotTranslate.applyToWorkspaceTooltip}
455
- <button
456
- class="link tooltip-link"
457
- role="link"
458
- jslog=${VisualLogging.link('open-ai-settings').track({
459
- click: true,
460
- })}
461
- @click=${this.#onLearnMoreTooltipClick}
462
- >${lockedString(UIStringsNotTranslate.learnMore)}</button>
463
- </div>`, popover.contentElement, {host: this});
464
- // clang-forat on
465
- return true;
466
- },
467
- };
468
- }, 'patch-widget.info-tooltip');
469
- this.#popoverHelper.setTimeout(0);
470
433
  // clang-format on
471
434
  this.requestUpdate();
472
435
  }
473
436
 
474
437
  #onLearnMoreTooltipClick(): void {
475
- this.#viewOutput.tooltipRef?.value?.hidePopover();
476
438
  void UI.ViewManager.ViewManager.instance().showView('chrome-ai');
477
439
  }
478
440