chrome-devtools-frontend 1.0.1518653 → 1.0.1520139

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) 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/eslint.config.mjs +1 -0
  5. package/front_end/core/host/GdpClient.ts +12 -3
  6. package/front_end/core/sdk/EnhancedTracesParser.ts +5 -5
  7. package/front_end/core/sdk/NetworkManager.ts +1 -0
  8. package/front_end/core/sdk/NetworkRequest.ts +10 -0
  9. package/front_end/core/sdk/RehydratingConnection.snapshot.txt +211 -0
  10. package/front_end/core/sdk/TargetManager.ts +4 -0
  11. package/front_end/entrypoints/main/MainImpl.ts +6 -1
  12. package/front_end/entrypoints/main/main-meta.ts +3 -3
  13. package/front_end/generated/SupportedCSSProperties.js +19 -4
  14. package/front_end/models/ai_assistance/agents/AiAgent.ts +57 -10
  15. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +64 -87
  16. package/front_end/models/ai_assistance/agents/PerformanceAnnotationsAgent.ts +4 -4
  17. package/front_end/models/ai_assistance/agents/StylingAgent.ts +0 -31
  18. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +127 -29
  19. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +106 -55
  20. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +317 -640
  21. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +23 -19
  22. package/front_end/models/ai_assistance/performance/AICallTree.snapshot.txt +75 -0
  23. package/front_end/models/ai_assistance/performance/AICallTree.ts +14 -6
  24. package/front_end/models/ai_assistance/performance/AIContext.ts +63 -8
  25. package/front_end/models/ai_code_completion/AiCodeCompletion.ts +5 -0
  26. package/front_end/models/badges/AiExplorerBadge.ts +19 -3
  27. package/front_end/models/badges/Badge.ts +8 -1
  28. package/front_end/models/badges/CodeWhispererBadge.ts +1 -0
  29. package/front_end/models/badges/DOMDetectiveBadge.ts +1 -0
  30. package/front_end/models/badges/SpeedsterBadge.ts +1 -0
  31. package/front_end/models/badges/StarterBadge.ts +6 -0
  32. package/front_end/models/badges/badges.ts +1 -0
  33. package/front_end/models/javascript_metadata/NativeFunctions.js +4 -0
  34. package/front_end/models/trace/EventsSerializer.ts +4 -3
  35. package/front_end/models/trace/handlers/UserInteractionsHandler.ts +101 -73
  36. package/front_end/models/trace/helpers/Timing.ts +1 -1
  37. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +18 -8
  38. package/front_end/panels/ai_assistance/PatchWidget.ts +17 -55
  39. package/front_end/panels/ai_assistance/components/ChatView.ts +44 -68
  40. package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +63 -15
  41. package/front_end/panels/ai_assistance/components/chatView.css +12 -0
  42. package/front_end/panels/animation/AnimationTimeline.ts +1 -1
  43. package/front_end/panels/animation/animationTimeline.css +4 -0
  44. package/front_end/panels/application/components/BounceTrackingMitigationsView.ts +2 -2
  45. package/front_end/panels/application/preloading/components/PreloadingString.ts +2 -5
  46. package/front_end/panels/common/AiCodeCompletionTeaser.ts +5 -0
  47. package/front_end/panels/common/BadgeNotification.ts +3 -3
  48. package/front_end/panels/common/GdpSignUpDialog.ts +3 -4
  49. package/front_end/panels/common/aiCodeCompletionTeaser.css +6 -1
  50. package/front_end/panels/console/ConsolePrompt.ts +6 -0
  51. package/front_end/panels/console/ConsoleView.ts +4 -2
  52. package/front_end/panels/coverage/CoverageListView.ts +133 -158
  53. package/front_end/panels/coverage/CoverageView.ts +39 -16
  54. package/front_end/panels/css_overview/CSSOverviewCompletedView.ts +5 -5
  55. package/front_end/panels/mobile_throttling/NetworkThrottlingSelector.ts +2 -0
  56. package/front_end/panels/network/NetworkDataGridNode.ts +22 -0
  57. package/front_end/panels/network/NetworkLogViewColumns.ts +9 -0
  58. package/front_end/panels/recorder/components/CreateRecordingView.ts +2 -0
  59. package/front_end/panels/recorder/components/RecordingView.ts +2 -2
  60. package/front_end/panels/search/SearchResultsPane.ts +186 -134
  61. package/front_end/panels/search/SearchView.ts +42 -36
  62. package/front_end/panels/search/searchResultsPane.css +9 -0
  63. package/front_end/panels/search/searchView.css +0 -2
  64. package/front_end/panels/security/CookieControlsView.ts +2 -1
  65. package/front_end/panels/settings/AISettingsTab.ts +6 -3
  66. package/front_end/panels/settings/components/SyncSection.ts +26 -12
  67. package/front_end/panels/settings/emulation/components/UserAgentClientHintsForm.ts +1 -1
  68. package/front_end/panels/sources/AiCodeCompletionPlugin.ts +4 -4
  69. package/front_end/panels/sources/DebuggerPlugin.ts +4 -0
  70. package/front_end/panels/sources/SourcesPanel.ts +1 -1
  71. package/front_end/panels/sources/sourcesView.css +6 -1
  72. package/front_end/panels/timeline/ThirdPartyTreeView.ts +1 -1
  73. package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +0 -8
  74. package/front_end/panels/timeline/TimelineFlameChartView.ts +5 -5
  75. package/front_end/panels/timeline/TimelinePanel.ts +2 -0
  76. package/front_end/panels/timeline/TimelineTreeView.ts +1 -1
  77. package/front_end/panels/timeline/components/LayoutShiftDetails.ts +1 -1
  78. package/front_end/panels/timeline/components/NetworkRequestDetails.ts +1 -1
  79. package/front_end/panels/timeline/components/RelatedInsightChips.ts +1 -1
  80. package/front_end/panels/timeline/components/SidebarSingleInsightSet.ts +1 -1
  81. package/front_end/third_party/chromium/README.chromium +1 -1
  82. package/front_end/third_party/puppeteer/README.chromium +2 -2
  83. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Realm.d.ts +2 -2
  84. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.d.ts +1 -1
  85. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.js +1 -1
  86. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.d.ts +1 -1
  87. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.js +1 -1
  88. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
  89. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/BrowserLauncher.js +1 -1
  90. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/BrowserLauncher.js.map +1 -1
  91. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
  92. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js +1 -0
  93. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js.map +1 -1
  94. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/PipeTransport.d.ts.map +1 -1
  95. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/PipeTransport.js +15 -16
  96. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/PipeTransport.js.map +1 -1
  97. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +3 -3
  98. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +3 -3
  99. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js.map +1 -1
  100. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Function.d.ts.map +1 -1
  101. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Function.js +16 -25
  102. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Function.js.map +1 -1
  103. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  104. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +19 -28
  105. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.d.ts +1 -1
  106. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.js +1 -1
  107. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.d.ts +1 -1
  108. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.js +1 -1
  109. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/BrowserLauncher.js +1 -1
  110. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/BrowserLauncher.js.map +1 -1
  111. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
  112. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js +1 -0
  113. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js.map +1 -1
  114. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/PipeTransport.d.ts.map +1 -1
  115. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/PipeTransport.js +15 -16
  116. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/PipeTransport.js.map +1 -1
  117. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +3 -3
  118. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +3 -3
  119. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js.map +1 -1
  120. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/Function.d.ts.map +1 -1
  121. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/Function.js +16 -25
  122. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/Function.js.map +1 -1
  123. package/front_end/third_party/puppeteer/package/package.json +12 -4
  124. package/front_end/third_party/puppeteer/package/src/generated/injected.ts +1 -1
  125. package/front_end/third_party/puppeteer/package/src/generated/version.ts +1 -1
  126. package/front_end/third_party/puppeteer/package/src/node/BrowserLauncher.ts +1 -1
  127. package/front_end/third_party/puppeteer/package/src/node/ChromeLauncher.ts +1 -0
  128. package/front_end/third_party/puppeteer/package/src/node/PipeTransport.ts +15 -17
  129. package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
  130. package/front_end/third_party/puppeteer/package/src/util/Function.ts +22 -30
  131. package/front_end/tsconfig.json +12 -1
  132. package/front_end/ui/components/dialogs/Dialog.ts +1 -1
  133. package/front_end/ui/components/markdown_view/MarkdownImage.ts +4 -5
  134. package/front_end/ui/components/switch/SwitchImpl.ts +12 -1
  135. package/front_end/ui/components/text_editor/config.ts +16 -2
  136. package/front_end/ui/legacy/InspectorView.ts +86 -13
  137. package/front_end/ui/legacy/TabbedPane.ts +2 -1
  138. package/front_end/ui/legacy/Treeoutline.ts +3 -1
  139. package/front_end/ui/legacy/components/source_frame/XMLView.ts +12 -11
  140. package/front_end/ui/lit/i18n-template.ts +5 -2
  141. package/front_end/ui/visual_logging/KnownContextValues.ts +15 -5
  142. package/front_end/ui/visual_logging/LoggingEvents.ts +1 -1
  143. package/package.json +1 -1
@@ -2,6 +2,7 @@
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
4
 
5
+ import * as Platform from '../../../core/platform/platform.js';
5
6
  import * as Helpers from '../helpers/helpers.js';
6
7
  import * as Types from '../types/types.js';
7
8
 
@@ -56,15 +57,15 @@ let longestInteractionEvent: Types.Events.SyntheticInteractionPair|null = null;
56
57
 
57
58
  let interactionEvents: Types.Events.SyntheticInteractionPair[] = [];
58
59
  let interactionEventsWithNoNesting: Types.Events.SyntheticInteractionPair[] = [];
59
- let eventTimingEndEventsById = new Map<string, Types.Events.EventTimingEnd>();
60
60
  let eventTimingStartEventsForInteractions: Types.Events.EventTimingBegin[] = [];
61
+ let eventTimingEndEventsForInteractions: Types.Events.EventTimingEnd[] = [];
61
62
 
62
63
  export function reset(): void {
63
64
  beginCommitCompositorFrameEvents = [];
64
65
  parseMetaViewportEvents = [];
65
66
  interactionEvents = [];
66
67
  eventTimingStartEventsForInteractions = [];
67
- eventTimingEndEventsById = new Map();
68
+ eventTimingEndEventsForInteractions = [];
68
69
  interactionEventsWithNoNesting = [];
69
70
  longestInteractionEvent = null;
70
71
  }
@@ -86,7 +87,7 @@ export function handleEvent(event: Types.Events.Event): void {
86
87
 
87
88
  if (Types.Events.isEventTimingEnd(event)) {
88
89
  // Store the end event; for each start event that is an interaction, we need the matching end event to calculate the duration correctly.
89
- eventTimingEndEventsById.set(event.id, event);
90
+ eventTimingEndEventsForInteractions.push(event);
90
91
  }
91
92
 
92
93
  // From this point on we want to find events that represent interactions.
@@ -167,9 +168,18 @@ export function categoryOfInteraction(interaction: Types.Events.SyntheticInterac
167
168
  * =======B=[keyup]=====
168
169
  * ====C=[pointerdown]=
169
170
  * =D=[pointerup]=
171
+ *
172
+ * Additionally, this method will also maximise the processing duration of the
173
+ * events that we keep as non-nested. We want to make sure we give an accurate
174
+ * representation of main thread activity, so if we keep an event + hide its
175
+ * nested children, we set the top level event's processing start &
176
+ * processing end to be the earliest processing start & the latest processing
177
+ * end of its children. This ensures we report a more accurate main thread
178
+ * activity time which is important as we want developers to focus on fixing
179
+ * this.
170
180
  **/
171
- export function removeNestedInteractions(interactions: readonly Types.Events.SyntheticInteractionPair[]):
172
- readonly Types.Events.SyntheticInteractionPair[] {
181
+ export function removeNestedInteractionsAndSetProcessingTime(
182
+ interactions: readonly Types.Events.SyntheticInteractionPair[]): readonly Types.Events.SyntheticInteractionPair[] {
173
183
  /**
174
184
  * Because we nest events only that are in the same category, we store the
175
185
  * longest event for a given end time by category.
@@ -257,77 +267,95 @@ function writeSyntheticTimespans(event: Types.Events.SyntheticInteractionPair):
257
267
  export async function finalize(): Promise<void> {
258
268
  const {navigationsByFrameId} = metaHandlerData();
259
269
 
260
- // For each interaction start event, find the async end event by the ID, and then create the Synthetic Interaction event.
261
- for (const interactionStartEvent of eventTimingStartEventsForInteractions) {
262
- const endEvent = eventTimingEndEventsById.get(interactionStartEvent.id);
263
- if (!endEvent) {
264
- // If we cannot find an end event, bail and drop this event.
265
- continue;
266
- }
267
- const {type, interactionId, timeStamp, processingStart, processingEnd} = interactionStartEvent.args.data;
268
- if (!type || !interactionId || !timeStamp || !processingStart || !processingEnd) {
269
- // A valid interaction event that we care about has to have a type (e.g. pointerdown, keyup).
270
- // We also need to ensure it has an interactionId and various timings. There are edge cases where these aren't included in the trace event.
271
- continue;
272
- }
273
-
274
- // In the future we will add microsecond timestamps to the trace events…
275
- // (See https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/timing/window_performance.cc;l=900-901;drc=b503c262e425eae59ced4a80d59d176ed07152c7 )
276
- // …but until then we can use the millisecond precision values that are in
277
- // the trace event. To adjust them to be relative to the event.ts and the
278
- // trace timestamps, for both processingStart and processingEnd we subtract
279
- // the event timestamp (NOT event.ts, but the timeStamp millisecond value
280
- // emitted in args.data), and then add that value to the event.ts. This
281
- // will give us a processingStart and processingEnd time in microseconds
282
- // that is relative to event.ts, and can be used when drawing boxes.
283
- // There is some inaccuracy here as we are converting milliseconds to microseconds, but it is good enough until the backend emits more accurate numbers.
284
- const processingStartRelativeToTraceTime = Types.Timing.Micro(
285
- Helpers.Timing.milliToMicro(processingStart) - Helpers.Timing.milliToMicro(timeStamp) +
286
- interactionStartEvent.ts,
287
- );
288
-
289
- const processingEndRelativeToTraceTime = Types.Timing.Micro(
290
- (Helpers.Timing.milliToMicro(processingEnd) - Helpers.Timing.milliToMicro(timeStamp)) +
291
- interactionStartEvent.ts);
292
-
293
- // Ultimate frameId fallback only needed for TSC, see comments in the type.
294
- const frameId = interactionStartEvent.args.frame ?? interactionStartEvent.args.data.frame ?? '';
295
- const navigation = Helpers.Trace.getNavigationForTraceEvent(interactionStartEvent, frameId, navigationsByFrameId);
296
- const navigationId = navigation?.args.data?.navigationId;
297
- const interactionEvent =
298
- Helpers.SyntheticEvents.SyntheticEventsManager.registerSyntheticEvent<Types.Events.SyntheticInteractionPair>({
299
- // Use the start event to define the common fields.
300
- rawSourceEvent: interactionStartEvent,
301
- cat: interactionStartEvent.cat,
302
- name: interactionStartEvent.name,
303
- pid: interactionStartEvent.pid,
304
- tid: interactionStartEvent.tid,
305
- ph: interactionStartEvent.ph,
306
- processingStart: processingStartRelativeToTraceTime,
307
- processingEnd: processingEndRelativeToTraceTime,
308
- // These will be set in writeSyntheticTimespans()
309
- inputDelay: Types.Timing.Micro(-1),
310
- mainThreadHandling: Types.Timing.Micro(-1),
311
- presentationDelay: Types.Timing.Micro(-1),
312
- args: {
313
- data: {
314
- beginEvent: interactionStartEvent,
315
- endEvent,
316
- frame: frameId,
317
- navigationId,
270
+ const beginAndEndEvents = Platform.ArrayUtilities.mergeOrdered(
271
+ eventTimingStartEventsForInteractions, eventTimingEndEventsForInteractions,
272
+ Helpers.Trace.eventTimeComparator) as Types.Events.EventTimingBeginOrEnd[];
273
+
274
+ // Pair up the begin & end events and create synthetic user timing events.
275
+ const beginEventById = new Map<string, Types.Events.EventTimingBegin[]>();
276
+ for (const event of beginAndEndEvents) {
277
+ if (Types.Events.isEventTimingStart(event)) {
278
+ const forId = beginEventById.get(event.id) ?? [];
279
+ forId.push(event);
280
+ beginEventById.set(event.id, forId);
281
+ } else if (Types.Events.isEventTimingEnd(event)) {
282
+ const beginEvents = beginEventById.get(event.id) ?? [];
283
+ const beginEvent = beginEvents.pop();
284
+ if (!beginEvent) {
285
+ continue;
286
+ }
287
+ const {type, interactionId, timeStamp, processingStart, processingEnd} = beginEvent.args.data;
288
+ if (!type || !interactionId || !timeStamp || !processingStart || !processingEnd) {
289
+ // A valid interaction event that we care about has to have a type (e.g. pointerdown, keyup).
290
+ // We also need to ensure it has an interactionId and various timings. There are edge cases where these aren't included in the trace event.
291
+ continue;
292
+ }
293
+ // In the future we will add microsecond timestamps to the trace events…
294
+ // (See https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/timing/window_performance.cc;l=900-901;drc=b503c262e425eae59ced4a80d59d176ed07152c7 )
295
+ // …but until then we can use the millisecond precision values that are in
296
+ // the trace event. To adjust them to be relative to the event.ts and the
297
+ // trace timestamps, for both processingStart and processingEnd we subtract
298
+ // the event timestamp (NOT event.ts, but the timeStamp millisecond value
299
+ // emitted in args.data), and then add that value to the event.ts. This
300
+ // will give us a processingStart and processingEnd time in microseconds
301
+ // that is relative to event.ts, and can be used when drawing boxes.
302
+ // There is some inaccuracy here as we are converting milliseconds to
303
+ // microseconds, but it is good enough until the backend emits more
304
+ // accurate numbers.
305
+ const processingStartRelativeToTraceTime = Types.Timing.Micro(
306
+ Helpers.Timing.milliToMicro(processingStart) - Helpers.Timing.milliToMicro(timeStamp) + beginEvent.ts,
307
+ );
308
+
309
+ const processingEndRelativeToTraceTime = Types.Timing.Micro(
310
+ (Helpers.Timing.milliToMicro(processingEnd) - Helpers.Timing.milliToMicro(timeStamp)) + beginEvent.ts);
311
+
312
+ // Ultimate frameId fallback only needed for TSC, see comments in the type.
313
+ const frameId = beginEvent.args.frame ?? beginEvent.args.data.frame ?? '';
314
+ const navigation = Helpers.Trace.getNavigationForTraceEvent(beginEvent, frameId, navigationsByFrameId);
315
+ const navigationId = navigation?.args.data?.navigationId;
316
+ const interactionEvent =
317
+ Helpers.SyntheticEvents.SyntheticEventsManager.registerSyntheticEvent<Types.Events.SyntheticInteractionPair>({
318
+ // Use the start event to define the common fields.
319
+ rawSourceEvent: beginEvent,
320
+ cat: beginEvent.cat,
321
+ name: beginEvent.name,
322
+ pid: beginEvent.pid,
323
+ tid: beginEvent.tid,
324
+ ph: beginEvent.ph,
325
+ processingStart: processingStartRelativeToTraceTime,
326
+ processingEnd: processingEndRelativeToTraceTime,
327
+ // These will be set in writeSyntheticTimespans()
328
+ inputDelay: Types.Timing.Micro(-1),
329
+ mainThreadHandling: Types.Timing.Micro(-1),
330
+ presentationDelay: Types.Timing.Micro(-1),
331
+ args: {
332
+ data: {
333
+ beginEvent,
334
+ endEvent: event,
335
+ frame: frameId,
336
+ navigationId,
337
+ },
318
338
  },
319
- },
320
- ts: interactionStartEvent.ts,
321
- dur: Types.Timing.Micro(endEvent.ts - interactionStartEvent.ts),
322
- type: interactionStartEvent.args.data.type,
323
- interactionId: interactionStartEvent.args.data.interactionId,
324
- });
325
- writeSyntheticTimespans(interactionEvent);
326
-
327
- interactionEvents.push(interactionEvent);
339
+ ts: beginEvent.ts,
340
+ dur: Types.Timing.Micro(event.ts - beginEvent.ts),
341
+ type: beginEvent.args.data.type,
342
+ interactionId: beginEvent.args.data.interactionId,
343
+ });
344
+ writeSyntheticTimespans(interactionEvent);
345
+ interactionEvents.push(interactionEvent);
346
+ }
328
347
  }
329
348
 
330
- interactionEventsWithNoNesting.push(...removeNestedInteractions(interactionEvents));
349
+ // Once we gather up all the interactions, we want to remove nested
350
+ // interactions. Interactions can be nested because one user action (e.g. a
351
+ // click) will cause a pointerdown, pointerup and click. But we don't want to
352
+ // fill the interactions track with lots of noise. To fix this, we go through
353
+ // all the events and remove any nested ones so on the timeline we focus the
354
+ // user on the most important events, which we define as the longest one. But
355
+ // this algorithm assumes the events are in ASC order, so we first sort the
356
+ // set of interactions.
357
+ Helpers.Trace.sortTraceEventsInPlace(interactionEvents);
358
+ interactionEventsWithNoNesting.push(...removeNestedInteractionsAndSetProcessingTime(interactionEvents));
331
359
 
332
360
  // Pick the longest interactions from the set that were not nested, as we
333
361
  // know those are the set of the largest interactions.
@@ -221,7 +221,7 @@ export function boundsIncludeTimeRange(data: BoundsIncludeTimeRange): boolean {
221
221
  /** Checks to see if the event is within or overlaps the bounds */
222
222
  export function eventIsInBounds(event: Types.Events.Event, bounds: Types.Timing.TraceWindowMicro): boolean {
223
223
  const startTime = event.ts;
224
- return startTime <= bounds.max && bounds.min <= (startTime + (event.dur ?? 0));
224
+ return startTime <= bounds.max && bounds.min < (startTime + (event.dur ?? 0));
225
225
  }
226
226
 
227
227
  export function timestampIsInBounds(bounds: Types.Timing.TraceWindowMicro, timestamp: Types.Timing.Micro): boolean {
@@ -12,6 +12,7 @@ import * as Root from '../../core/root/root.js';
12
12
  import * as SDK from '../../core/sdk/sdk.js';
13
13
  import * as Protocol from '../../generated/protocol.js';
14
14
  import * as AiAssistanceModel from '../../models/ai_assistance/ai_assistance.js';
15
+ import * as Badges from '../../models/badges/badges.js';
15
16
  import * as TextUtils from '../../models/text_utils/text_utils.js';
16
17
  import * as Workspace from '../../models/workspace/workspace.js';
17
18
  import * as Buttons from '../../ui/components/buttons/buttons.js';
@@ -265,11 +266,18 @@ async function getEmptyStateSuggestions(
265
266
  }
266
267
  }
267
268
 
268
- function getMarkdownRenderer(context: AiAssistanceModel.ConversationContext<unknown>|null):
269
- MarkdownRendererWithCodeBlock {
270
- if (context instanceof AiAssistanceModel.PerformanceTraceContext && !context.external) {
271
- const focus = context.getItem();
272
- 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();
273
281
  }
274
282
 
275
283
  return new MarkdownRendererWithCodeBlock();
@@ -849,7 +857,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
849
857
 
850
858
  override async performUpdate(): Promise<void> {
851
859
  const emptyStateSuggestions = await getEmptyStateSuggestions(this.#selectedContext, this.#conversation);
852
- const markdownRenderer = getMarkdownRenderer(this.#selectedContext);
860
+ const markdownRenderer = getMarkdownRenderer(this.#selectedContext, this.#conversation);
853
861
 
854
862
  this.view(
855
863
  {
@@ -1065,7 +1073,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1065
1073
  return Common.Revealer.reveal(context.getItem().uiLocation(0, 0));
1066
1074
  }
1067
1075
  if (context instanceof AiAssistanceModel.PerformanceTraceContext) {
1068
- const focus = context.getItem().data;
1076
+ const focus = context.getItem();
1069
1077
  if (focus.callTree) {
1070
1078
  const event = focus.callTree.selectedNode?.event ?? focus.callTree.rootNode.event;
1071
1079
  const revealable = new SDK.TraceObject.RevealableEvent(event);
@@ -1409,7 +1417,9 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1409
1417
  // invariants do not hold anymore.
1410
1418
  throw new Error('cross-origin context data should not be included');
1411
1419
  }
1412
-
1420
+ if (this.#conversation?.isEmpty) {
1421
+ Badges.UserBadges.instance().recordAction(Badges.BadgeAction.STARTED_AI_CONVERSATION);
1422
+ }
1413
1423
  const image = isAiAssistanceMultimodalInputEnabled() ? imageInput : undefined;
1414
1424
  const imageId = image ? crypto.randomUUID() : undefined;
1415
1425
  const multimodalInput = image && imageId && multimodalInputType ? {
@@ -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
 
@@ -214,8 +214,8 @@ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
214
214
  const lockedString = i18n.i18n.lockedString;
215
215
 
216
216
  const SCROLL_ROUNDING_OFFSET = 1;
217
- const TOOLTIP_POPOVER_OFFSET = 4;
218
- const RELEVANT_DATA_LINK_ID = 'relevant-data-link';
217
+ const RELEVANT_DATA_LINK_FOOTER_ID = 'relevant-data-link-footer';
218
+ const RELEVANT_DATA_LINK_CHAT_ID = 'relevant-data-link-chat';
219
219
 
220
220
  export interface Step {
221
221
  isLoading: boolean;
@@ -313,7 +313,6 @@ export class ChatView extends HTMLElement {
313
313
  #messagesContainerElement?: Element;
314
314
  #mainElementRef?: Lit.Directives.Ref<Element> = Lit.Directives.createRef();
315
315
  #messagesContainerResizeObserver = new ResizeObserver(() => this.#handleMessagesContainerResize());
316
- #popoverHelper: UI.PopoverHelper.PopoverHelper|null = null;
317
316
  /**
318
317
  * Indicates whether the chat scroll position should be pinned to the bottom.
319
318
  *
@@ -391,63 +390,6 @@ export class ChatView extends HTMLElement {
391
390
  this.#setMainElementScrollTop(this.#mainElementRef.value.scrollHeight);
392
391
  }
393
392
 
394
- #handleChatUiRef(el: Element|undefined): void {
395
- if (!el || this.#popoverHelper) {
396
- return;
397
- }
398
-
399
- // TODO: Update here when b/409965560 is fixed.
400
- this.#popoverHelper = new UI.PopoverHelper.PopoverHelper((el as HTMLElement), event => {
401
- const popoverShownNode =
402
- event.target instanceof HTMLElement && event.target.id === RELEVANT_DATA_LINK_ID ? event.target : null;
403
- if (!popoverShownNode) {
404
- return null;
405
- }
406
-
407
- // We move the glass pane to be a bit lower so
408
- // that it does not disappear when moving the cursor
409
- // over to link.
410
- const nodeBox = popoverShownNode.boxInWindow();
411
- nodeBox.y = nodeBox.y + TOOLTIP_POPOVER_OFFSET;
412
- return {
413
- box: nodeBox,
414
- show: async (popover: UI.GlassPane.GlassPane) => {
415
- // clang-format off
416
- Lit.render(html`
417
- <style>
418
- .info-tooltip-container {
419
- max-width: var(--sys-size-28);
420
- padding: var(--sys-size-4) var(--sys-size-5);
421
-
422
- .tooltip-link {
423
- display: block;
424
- margin-top: var(--sys-size-4);
425
- color: var(--sys-color-primary);
426
- padding-left: 0;
427
- }
428
- }
429
- </style>
430
- <div class="info-tooltip-container">
431
- ${this.#props.disclaimerText}
432
- <button
433
- class="link tooltip-link"
434
- role="link"
435
- jslog=${VisualLogging.link('open-ai-settings').track({
436
- click: true,
437
- })}
438
- @click=${() => {
439
- void UI.ViewManager.ViewManager.instance().showView('chrome-ai');
440
- }}
441
- >${i18nString(UIStrings.learnAbout)}</button>
442
- </div>`, popover.contentElement, {host: this});
443
- // clang-format on
444
- return true;
445
- },
446
- };
447
- });
448
- this.#popoverHelper.setTimeout(0);
449
- }
450
-
451
393
  #handleMessagesContainerResize(): void {
452
394
  if (!this.#pinScrollToBottom) {
453
395
  return;
@@ -583,9 +525,11 @@ export class ChatView extends HTMLElement {
583
525
  // clang-format off
584
526
  const footerContents = this.#props.conversationType
585
527
  ? renderRelevantDataDisclaimer({
586
- isLoading: this.#props.isLoading,
587
- blockedByCrossOrigin: this.#props.blockedByCrossOrigin,
588
- })
528
+ isLoading: this.#props.isLoading,
529
+ blockedByCrossOrigin: this.#props.blockedByCrossOrigin,
530
+ tooltipId: RELEVANT_DATA_LINK_FOOTER_ID,
531
+ disclaimerText: this.#props.disclaimerText,
532
+ })
589
533
  : html`<p>
590
534
  ${lockedString(UIStringsNotTranslate.inputDisclaimerForEmptyState)}
591
535
  <button
@@ -611,7 +555,7 @@ export class ChatView extends HTMLElement {
611
555
  // clang-format off
612
556
  Lit.render(html`
613
557
  <style>${chatViewStyles}</style>
614
- <div class="chat-ui" ${Lit.Directives.ref(this.#handleChatUiRef)}>
558
+ <div class="chat-ui">
615
559
  <main @scroll=${this.#handleScroll} ${ref(this.#mainElementRef)}>
616
560
  ${renderMainContents({
617
561
  state: this.#props.state,
@@ -643,6 +587,7 @@ export class ChatView extends HTMLElement {
643
587
  isTextInputDisabled: this.#props.isTextInputDisabled,
644
588
  inputPlaceholder: this.#props.inputPlaceholder,
645
589
  state: this.#props.state,
590
+ disclaimerText: this.#props.disclaimerText,
646
591
  selectedContext: this.#props.selectedContext,
647
592
  inspectElementToggled: this.#props.inspectElementToggled,
648
593
  multimodalInputEnabled: this.#props.multimodalInputEnabled,
@@ -1433,8 +1378,12 @@ function renderImageInput({
1433
1378
  // clang-format on
1434
1379
  }
1435
1380
 
1436
- function renderRelevantDataDisclaimer(
1437
- {isLoading, blockedByCrossOrigin}: {isLoading: boolean, blockedByCrossOrigin: boolean}): Lit.LitTemplate {
1381
+ function renderRelevantDataDisclaimer({isLoading, blockedByCrossOrigin, tooltipId, disclaimerText}: {
1382
+ isLoading: boolean,
1383
+ blockedByCrossOrigin: boolean,
1384
+ tooltipId: string,
1385
+ disclaimerText: string,
1386
+ }): Lit.LitTemplate {
1438
1387
  const classes =
1439
1388
  Lit.Directives.classMap({'chat-input-disclaimer': true, 'hide-divider': !isLoading && blockedByCrossOrigin});
1440
1389
  // clang-format off
@@ -1443,7 +1392,7 @@ function renderRelevantDataDisclaimer(
1443
1392
  <button
1444
1393
  class="link"
1445
1394
  role="link"
1446
- id=${RELEVANT_DATA_LINK_ID}
1395
+ aria-details=${tooltipId}
1447
1396
  jslog=${VisualLogging.link('open-ai-settings').track({
1448
1397
  click: true,
1449
1398
  })}
@@ -1451,6 +1400,7 @@ function renderRelevantDataDisclaimer(
1451
1400
  void UI.ViewManager.ViewManager.instance().showView('chrome-ai');
1452
1401
  }}
1453
1402
  >${lockedString('Relevant data')}</button>&nbsp;${lockedString('is sent to Google')}
1403
+ ${renderDisclamerTooltip(tooltipId, disclaimerText)}
1454
1404
  </p>
1455
1405
  `;
1456
1406
  // clang-format on
@@ -1470,6 +1420,7 @@ function renderChatInput({
1470
1420
  isTextInputEmpty,
1471
1421
  uploadImageInputEnabled,
1472
1422
  aidaAvailability,
1423
+ disclaimerText,
1473
1424
  onContextClick,
1474
1425
  onInspectElementClick,
1475
1426
  onSubmit,
@@ -1490,6 +1441,7 @@ function renderChatInput({
1490
1441
  inspectElementToggled: boolean,
1491
1442
  isTextInputEmpty: boolean,
1492
1443
  aidaAvailability: Host.AidaClient.AidaAccessPreconditions,
1444
+ disclaimerText: string,
1493
1445
  onContextClick: () => void,
1494
1446
  onInspectElementClick: () => void,
1495
1447
  onSubmit: (ev: SubmitEvent) => void,
@@ -1547,7 +1499,7 @@ function renderChatInput({
1547
1499
  </div>
1548
1500
  <div class="chat-input-actions-right">
1549
1501
  <div class="chat-input-disclaimer-container">
1550
- ${renderRelevantDataDisclaimer({isLoading, blockedByCrossOrigin})}
1502
+ ${renderRelevantDataDisclaimer({ isLoading, blockedByCrossOrigin, tooltipId: RELEVANT_DATA_LINK_CHAT_ID, disclaimerText})}
1551
1503
  </div>
1552
1504
  ${renderMultimodalInputButtons({
1553
1505
  multimodalInputEnabled, blockedByCrossOrigin, isTextInputDisabled, imageInput, uploadImageInputEnabled, onTakeScreenshot, onImageUpload
@@ -1697,6 +1649,30 @@ function renderMainContents({
1697
1649
  return renderEmptyState({isTextInputDisabled, suggestions, onSuggestionClick});
1698
1650
  }
1699
1651
 
1652
+ function renderDisclamerTooltip(id: string, disclaimerText: string): Lit.TemplateResult {
1653
+ // clang-format off
1654
+ return html`
1655
+ <devtools-tooltip
1656
+ id=${id}
1657
+ variant=${'rich'}
1658
+ >
1659
+ <div class="info-tooltip-container">
1660
+ ${disclaimerText}
1661
+ <button
1662
+ class="link tooltip-link"
1663
+ role="link"
1664
+ jslog=${VisualLogging.link('open-ai-settings').track({
1665
+ click: true,
1666
+ })}
1667
+ @click=${() => {
1668
+ void UI.ViewManager.ViewManager.instance().showView('chrome-ai');
1669
+ }}>${i18nString(UIStrings.learnAbout)}
1670
+ </button>
1671
+ </div>
1672
+ </devtools-tooltip>`;
1673
+ // clang-format on
1674
+ }
1675
+
1700
1676
  declare global {
1701
1677
  interface HTMLElementTagNameMap {
1702
1678
  'devtools-ai-chat-view': ChatView;