chrome-devtools-frontend 1.0.1558690 → 1.0.1561080

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 (167) hide show
  1. package/front_end/Images/src/container.svg +4 -0
  2. package/front_end/core/common/Gzip.ts +15 -0
  3. package/front_end/core/host/InspectorFrontendHostStub.ts +0 -3
  4. package/front_end/core/platform/ArrayUtilities.ts +13 -0
  5. package/front_end/core/root/Runtime.ts +0 -5
  6. package/front_end/core/sdk/CSSMetadata.ts +6 -6
  7. package/front_end/core/sdk/CSSModel.ts +2 -2
  8. package/front_end/core/sdk/DOMModel.ts +15 -3
  9. package/front_end/core/sdk/NetworkManager.ts +4 -0
  10. package/front_end/core/sdk/NetworkRequest.ts +9 -0
  11. package/front_end/core/sdk/OverlayModel.ts +20 -9
  12. package/front_end/entrypoints/main/MainImpl.ts +2 -1
  13. package/front_end/generated/InspectorBackendCommands.ts +6 -3
  14. package/front_end/generated/SupportedCSSProperties.js +64 -32
  15. package/front_end/generated/protocol-mapping.d.ts +16 -0
  16. package/front_end/generated/protocol-proxy-api.d.ts +12 -0
  17. package/front_end/generated/protocol.ts +38 -1
  18. package/front_end/models/ai_assistance/agents/StylingAgent.ts +1 -1
  19. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +11 -7
  20. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +23 -22
  21. package/front_end/models/badges/UserBadges.ts +48 -16
  22. package/front_end/models/greendev/Prototypes.ts +6 -1
  23. package/front_end/models/trace/LanternComputationData.ts +4 -3
  24. package/front_end/models/trace/Processor.ts +6 -5
  25. package/front_end/models/trace/Styles.ts +10 -1
  26. package/front_end/models/trace/extras/TraceTree.ts +1 -1
  27. package/front_end/models/trace/handlers/LargestImagePaintHandler.ts +2 -2
  28. package/front_end/models/trace/handlers/MetaHandler.ts +14 -0
  29. package/front_end/models/trace/handlers/PageLoadMetricsHandler.ts +59 -34
  30. package/front_end/models/trace/helpers/Timing.ts +8 -1
  31. package/front_end/models/trace/insights/Common.ts +1 -1
  32. package/front_end/models/trace/insights/LCPBreakdown.ts +4 -4
  33. package/front_end/models/trace/insights/LCPDiscovery.ts +3 -3
  34. package/front_end/models/trace/insights/RenderBlocking.ts +1 -1
  35. package/front_end/models/trace/insights/types.ts +1 -1
  36. package/front_end/models/trace/types/TraceEvents.ts +62 -10
  37. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +11 -142
  38. package/front_end/panels/ai_assistance/PatchWidget.ts +90 -72
  39. package/front_end/panels/ai_assistance/ai_assistance.ts +1 -0
  40. package/front_end/panels/ai_assistance/components/ChatInput.ts +701 -0
  41. package/front_end/panels/ai_assistance/components/ChatView.ts +71 -1268
  42. package/front_end/panels/ai_assistance/components/UserActionRow.ts +514 -31
  43. package/front_end/panels/ai_assistance/components/chatInput.css +387 -0
  44. package/front_end/panels/ai_assistance/components/chatView.css +38 -599
  45. package/front_end/panels/ai_assistance/components/userActionRow.css +230 -0
  46. package/front_end/panels/autofill/AutofillView.ts +2 -2
  47. package/front_end/panels/changes/ChangesView.ts +15 -1
  48. package/front_end/panels/changes/changesView.css +6 -0
  49. package/front_end/panels/common/AiCodeGenerationTeaser.ts +48 -12
  50. package/front_end/panels/common/BadgeNotification.ts +44 -58
  51. package/front_end/panels/common/CopyChangesToPrompt.ts +233 -0
  52. package/front_end/panels/common/aiCodeGenerationTeaser.css +14 -0
  53. package/front_end/panels/common/common.ts +2 -1
  54. package/front_end/panels/console/consoleView.css +1 -1
  55. package/front_end/panels/elements/CSSRuleValidator.ts +38 -0
  56. package/front_end/panels/elements/ElementsTreeElement.ts +222 -377
  57. package/front_end/panels/elements/ElementsTreeOutline.ts +0 -23
  58. package/front_end/panels/elements/ShortcutTreeElement.ts +57 -50
  59. package/front_end/panels/elements/StylePropertiesSection.ts +1 -3
  60. package/front_end/panels/elements/StylesSidebarPane.ts +15 -4
  61. package/front_end/panels/elements/components/AdornerManager.ts +5 -149
  62. package/front_end/panels/issues/HiddenIssuesRow.ts +1 -2
  63. package/front_end/panels/issues/IssueKindView.ts +2 -4
  64. package/front_end/panels/issues/IssueView.ts +2 -4
  65. package/front_end/panels/network/NetworkDataGridNode.ts +65 -1
  66. package/front_end/panels/network/NetworkLogView.ts +2 -4
  67. package/front_end/panels/network/NetworkLogViewColumns.ts +9 -0
  68. package/front_end/panels/screencast/ScreencastApp.ts +1 -0
  69. package/front_end/panels/settings/SettingsScreen.ts +3 -2
  70. package/front_end/panels/timeline/CompatibilityTracksAppender.ts +14 -1
  71. package/front_end/panels/timeline/StatusDialog.ts +4 -3
  72. package/front_end/panels/timeline/ThirdPartyTreeView.ts +1 -4
  73. package/front_end/panels/timeline/TimelineController.ts +185 -3
  74. package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +52 -25
  75. package/front_end/panels/timeline/TimelineFlameChartNetworkDataProvider.ts +3 -16
  76. package/front_end/panels/timeline/TimelineFlameChartView.ts +65 -21
  77. package/front_end/panels/timeline/TimelinePanel.ts +86 -126
  78. package/front_end/panels/timeline/TimelineTreeView.ts +1 -0
  79. package/front_end/panels/timeline/TimelineUIUtils.ts +28 -2
  80. package/front_end/panels/timeline/TimingsTrackAppender.ts +3 -1
  81. package/front_end/panels/timeline/components/SidebarSingleInsightSet.ts +1 -1
  82. package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +2 -2
  83. package/front_end/panels/timeline/components/insights/RenderBlocking.ts +6 -4
  84. package/front_end/panels/timeline/components/insights/Table.ts +3 -3
  85. package/front_end/panels/timeline/overlays/OverlaysImpl.ts +4 -0
  86. package/front_end/panels/timeline/timelinePanel.css +8 -1
  87. package/front_end/panels/timeline/utils/EntryNodes.ts +2 -1
  88. package/front_end/panels/whats_new/ReleaseNoteText.ts +15 -9
  89. package/front_end/panels/whats_new/resources/WNDT.md +6 -6
  90. package/front_end/third_party/chromium/README.chromium +1 -1
  91. package/front_end/third_party/codemirror.next/rebuild.sh +1 -1
  92. package/front_end/third_party/lit/rebuild.sh +1 -1
  93. package/front_end/third_party/puppeteer/README.chromium +2 -2
  94. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.d.ts +2 -3
  95. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.d.ts.map +1 -1
  96. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.js.map +1 -1
  97. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPRequest.d.ts.map +1 -1
  98. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPRequest.js +9 -0
  99. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPRequest.js.map +1 -1
  100. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPResponse.d.ts +3 -0
  101. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPResponse.d.ts.map +1 -1
  102. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPResponse.js +9 -0
  103. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPResponse.js.map +1 -1
  104. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Request.d.ts +3 -0
  105. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Request.d.ts.map +1 -1
  106. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Request.js +10 -0
  107. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Request.js.map +1 -1
  108. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.d.ts.map +1 -1
  109. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.js +8 -4
  110. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.js.map +1 -1
  111. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.d.ts +1 -1
  112. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.d.ts.map +1 -1
  113. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.js +1 -1
  114. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.js.map +1 -1
  115. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
  116. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +3 -3
  117. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +3 -3
  118. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js.map +1 -1
  119. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  120. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.d.ts +1 -1
  121. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.js +1 -1
  122. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +10 -1
  123. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +13 -7
  124. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.d.ts +2 -3
  125. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.d.ts.map +1 -1
  126. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.js.map +1 -1
  127. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPRequest.d.ts.map +1 -1
  128. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPRequest.js +9 -0
  129. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPRequest.js.map +1 -1
  130. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPResponse.d.ts +3 -0
  131. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPResponse.d.ts.map +1 -1
  132. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPResponse.js +9 -0
  133. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPResponse.js.map +1 -1
  134. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Request.d.ts +3 -0
  135. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Request.d.ts.map +1 -1
  136. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Request.js +10 -0
  137. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Request.js.map +1 -1
  138. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.d.ts.map +1 -1
  139. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.js +8 -4
  140. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.js.map +1 -1
  141. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.d.ts +1 -1
  142. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.d.ts.map +1 -1
  143. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.js +1 -1
  144. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.js.map +1 -1
  145. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +3 -3
  146. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +3 -3
  147. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js.map +1 -1
  148. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.d.ts +1 -1
  149. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.js +1 -1
  150. package/front_end/third_party/puppeteer/package/lib/types.d.ts +10 -1
  151. package/front_end/third_party/puppeteer/package/package.json +3 -3
  152. package/front_end/third_party/puppeteer/package/src/api/Page.ts +2 -3
  153. package/front_end/third_party/puppeteer/package/src/bidi/HTTPRequest.ts +13 -0
  154. package/front_end/third_party/puppeteer/package/src/bidi/HTTPResponse.ts +10 -0
  155. package/front_end/third_party/puppeteer/package/src/bidi/core/Request.ts +15 -0
  156. package/front_end/third_party/puppeteer/package/src/cdp/Browser.ts +9 -4
  157. package/front_end/third_party/puppeteer/package/src/generated/injected.ts +1 -1
  158. package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
  159. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  160. package/front_end/ui/components/adorners/Adorner.ts +8 -68
  161. package/front_end/ui/components/text_editor/AiCodeGenerationProvider.ts +70 -28
  162. package/front_end/ui/legacy/SearchableView.ts +11 -5
  163. package/front_end/ui/legacy/SplitWidget.ts +1 -1
  164. package/front_end/ui/legacy/TabbedPane.ts +1 -1
  165. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +43 -9
  166. package/front_end/ui/visual_logging/KnownContextValues.ts +16 -0
  167. package/package.json +2 -1
@@ -646,7 +646,7 @@ const GREENDEV_VIEW: View = (input, _output, target) => {
646
646
  <span>${i18nString(UIStrings.greenDevUnstable)}</span>
647
647
  </div>
648
648
  <div class="settings-experiments-block">
649
- ${renderPrototypeCheckboxes(input.settings, ['aiAnnotations', 'inDevToolsFloaty'])}
649
+ ${renderPrototypeCheckboxes(input.settings, ['aiAnnotations', 'inDevToolsFloaty', 'copyToGemini'])}
650
650
  </div>
651
651
  </devtools-card>
652
652
 
@@ -668,7 +668,8 @@ const GREENDEV_PROTOTYPE_NAMES: Record<keyof GreenDev.GreenDevSettings, string>
668
668
  inDevToolsFloaty: 'In DevTools context picker',
669
669
  aiAnnotations: 'AI auto-annotations',
670
670
  inlineWidgets: 'Inline widgets in AI Assistance',
671
- artifactViewer: 'Widgets in the Artifact viewer'
671
+ artifactViewer: 'Widgets in the Artifact viewer',
672
+ copyToGemini: 'Copy changes to AI Prompt'
672
673
  };
673
674
 
674
675
  function renderWidgetOptions(settings: GreenDev.GreenDevSettings): TemplateResult {
@@ -4,6 +4,7 @@
4
4
  /* eslint-disable @devtools/no-imperative-dom-api */
5
5
 
6
6
  import * as Common from '../../core/common/common.js';
7
+ import * as Host from '../../core/host/host.js';
7
8
  import * as Platform from '../../core/platform/platform.js';
8
9
  import * as Root from '../../core/root/root.js';
9
10
  import * as Trace from '../../models/trace/trace.js';
@@ -409,6 +410,7 @@ export class CompatibilityTracksAppender {
409
410
  if (trackStartLevel === null || trackEndLevel === null) {
410
411
  throw new Error(`Could not find events for track: ${trackAppender}`);
411
412
  }
413
+
412
414
  const entryLevels = this.#flameChartData.entryLevels;
413
415
  const events = [];
414
416
  for (let i = 0; i < entryLevels.length; i++) {
@@ -416,7 +418,13 @@ export class CompatibilityTracksAppender {
416
418
  events.push(this.#entryData[i]);
417
419
  }
418
420
  }
419
- events.sort((a, b) => a.ts - b.ts); // TODO(paulirish): Remove as I'm 90% it's already sorted.
421
+
422
+ // TODO(crbug.com/457866795): callers expect this to be sorted, but #entryData
423
+ // currently isn't guaranteed to be sorted because of appendEventsAtLevel and
424
+ // appendEventAtLevel. Also, see
425
+ // TimelineFlameChartDataProvider#insertEventToEntryData. This method is cached
426
+ // in eventsForTreeView, so it doesn't impact performance much.
427
+ events.sort((a, b) => a.ts - b.ts);
420
428
 
421
429
  this.#eventsForTrack.set(trackAppender, events);
422
430
  return events;
@@ -541,6 +549,11 @@ export class CompatibilityTracksAppender {
541
549
  appendEventsAtLevel<T extends Trace.Types.Events.Event>(
542
550
  events: readonly T[], trackStartLevel: number, appender: TrackAppender,
543
551
  eventAppendedCallback?: (event: T, index: number) => void): number {
552
+ // Usage of getEventLevel below requires `events` to be sorted.
553
+ if (Host.InspectorFrontendHost.isUnderTest()) {
554
+ Platform.ArrayUtilities.assertArrayIsSorted(events, (a, b) => a.ts - b.ts);
555
+ }
556
+
544
557
  const lastTimestampByLevel: LastTimestampByLevel = [];
545
558
  for (let i = 0; i < events.length; ++i) {
546
559
  const event = events[i];
@@ -145,14 +145,15 @@ export class StatusDialog extends UI.Widget.VBox {
145
145
  }
146
146
 
147
147
  remove(): void {
148
- (this.element.parentNode as HTMLElement)?.classList.remove('tinted');
148
+ (this.element.parentNode as HTMLElement)?.classList.remove('opaque', 'tinted');
149
149
  this.stopTimer();
150
150
  this.element.remove();
151
151
  }
152
152
 
153
- showPane(parent: Element): void {
153
+ showPane(parent: Element, mode: 'tinted'|'opaque' = 'opaque'): void {
154
154
  this.show(parent);
155
- parent.classList.add('tinted');
155
+ parent.classList.toggle('tinted', mode === 'tinted');
156
+ parent.classList.toggle('opaque', mode === 'opaque');
156
157
  }
157
158
 
158
159
  enableAndFocusButton(): void {
@@ -80,9 +80,6 @@ export class ThirdPartyTreeViewWidget extends TimelineTreeView.TimelineTreeView
80
80
  });
81
81
  }
82
82
 
83
- // const events = this.#thirdPartySummaries.entityByEvent.keys();
84
- const relatedEvents = this.selectedEvents().sort(Trace.Helpers.Trace.eventTimeComparator);
85
-
86
83
  // The filters for this view are slightly different; we want to use the set
87
84
  // of visible event types, but also include network events, which by
88
85
  // default are not in the set of visible entries (as they are not shown on
@@ -90,7 +87,7 @@ export class ThirdPartyTreeViewWidget extends TimelineTreeView.TimelineTreeView
90
87
  const filter = new Trace.Extras.TraceFilter.VisibleEventsFilter(
91
88
  Trace.Styles.visibleTypes().concat([Trace.Types.Events.Name.SYNTHETIC_NETWORK_REQUEST]));
92
89
 
93
- const node = new Trace.Extras.TraceTree.BottomUpRootNode(relatedEvents, {
90
+ const node = new Trace.Extras.TraceTree.BottomUpRootNode(this.selectedEvents(), {
94
91
  textFilter: this.textFilter(),
95
92
  filters: [filter],
96
93
  startTime: this.startTime,
@@ -2,7 +2,9 @@
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 type * as Common from '../../core/common/common.js';
5
6
  import * as i18n from '../../core/i18n/i18n.js';
7
+ import type * as Platform from '../../core/platform/platform.js';
6
8
  import * as Root from '../../core/root/root.js';
7
9
  import * as SDK from '../../core/sdk/sdk.js';
8
10
  import type * as Protocol from '../../generated/protocol.js';
@@ -15,15 +17,82 @@ import * as Tracing from '../../services/tracing/tracing.js';
15
17
  import * as RecordingMetadata from './RecordingMetadata.js';
16
18
 
17
19
  const UIStrings = {
20
+ /**
21
+ * @description Text in Timeline Panel of the Performance panel
22
+ */
23
+ initializingTracing: 'Initializing tracing…',
18
24
  /**
19
25
  * @description Text in Timeline Controller of the Performance panel indicating that the Performance Panel cannot
20
26
  * record a performance trace because the type of target (where possible types are page, service worker and shared
21
27
  * worker) doesn't support it.
22
28
  */
23
29
  tracingNotSupported: 'Performance trace recording not supported for this type of target',
30
+ /**
31
+ * @description Text in a status dialog shown during a performance trace of a web page. It indicates to the user what the tracing is currently waiting on.
32
+ */
33
+ waitingForLoadEvent: 'Waiting for load event…',
34
+ /**
35
+ * @description Text in a status dialog shown during a performance trace of a web page. It indicates to the user what the tracing is currently waiting on.
36
+ */
37
+ waitingForLoadEventPlus5Seconds: 'Waiting for load event (+5s)…',
24
38
  } as const;
25
39
  const str_ = i18n.i18n.registerUIStrings('panels/timeline/TimelineController.ts', UIStrings);
26
40
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
41
+
42
+ type StatusUpdate = string|null;
43
+ type Listener = (status: StatusUpdate) => void;
44
+
45
+ /**
46
+ * Accepts promises with a text label, and reports to a listener as promises resolve.
47
+ * Only returns the label of the first incomplete promise. When no more promises
48
+ * remain, the updated status is null.
49
+ */
50
+ class StatusChecker {
51
+ #checkers: Array<{title: string, complete: boolean}> = [];
52
+ #listener: Listener|null = null;
53
+ #currentStatus: StatusUpdate = null;
54
+
55
+ add(title: string, promise: Promise<unknown>): void {
56
+ const item = {title, complete: false};
57
+ this.#checkers.push(item);
58
+
59
+ void promise.finally(() => {
60
+ item.complete = true;
61
+ this.#evaluate();
62
+ });
63
+ }
64
+
65
+ setListener(listener: Listener): void {
66
+ this.#listener = null;
67
+ this.#evaluate();
68
+ this.#listener = listener;
69
+ listener(this.#currentStatus);
70
+ }
71
+
72
+ removeListener(): void {
73
+ this.#listener = null;
74
+ }
75
+
76
+ #evaluate(): void {
77
+ let nextStatus: StatusUpdate = null;
78
+
79
+ // Only report the status of the first incomplete checker.
80
+ for (const checker of this.#checkers) {
81
+ if (!checker.complete) {
82
+ nextStatus = checker.title;
83
+ break;
84
+ }
85
+ }
86
+
87
+ if (nextStatus !== this.#currentStatus) {
88
+ this.#currentStatus = nextStatus;
89
+ if (this.#listener) {
90
+ this.#listener(nextStatus);
91
+ }
92
+ }
93
+ }
94
+ }
95
+
27
96
  export class TimelineController implements Tracing.TracingManager.TracingManagerClient {
28
97
  readonly primaryPageTarget: SDK.Target.Target;
29
98
  readonly rootTarget: SDK.Target.Target;
@@ -35,6 +104,10 @@ export class TimelineController implements Tracing.TracingManager.TracingManager
35
104
  private readonly client: Client;
36
105
  private tracingCompletePromise: PromiseWithResolvers<void>|null = null;
37
106
 
107
+ // These properties are only used for "Reload and record".
108
+ #statusChecker: StatusChecker|null = null;
109
+ #loadEventFiredCb: (() => void)|null = null;
110
+
38
111
  /**
39
112
  * We always need to profile against the DevTools root target, which is
40
113
  * the target that DevTools is attached to.
@@ -76,11 +149,72 @@ export class TimelineController implements Tracing.TracingManager.TracingManager
76
149
  }
77
150
  }
78
151
 
79
- async startRecording(options: RecordingOptions): Promise<Protocol.ProtocolResponseWithError> {
152
+ async #navigateToAboutBlank(): Promise<void> {
153
+ const aboutBlankNavigationComplete = new Promise<void>(async (resolve, reject) => {
154
+ const target = this.primaryPageTarget;
155
+ const resourceModel = target.model(SDK.ResourceTreeModel.ResourceTreeModel);
156
+ if (!resourceModel) {
157
+ reject('Could not load resourceModel');
158
+ return;
159
+ }
160
+
161
+ /**
162
+ * To clear out the page and any state from prior test runs, we
163
+ * navigate to about:blank before initiating the trace recording.
164
+ * Once we have navigated to about:blank, we start recording and
165
+ * then navigate to the original page URL, to ensure we profile the
166
+ * page load.
167
+ **/
168
+ function waitForAboutBlank(event: Common.EventTarget.EventTargetEvent<SDK.ResourceTreeModel.ResourceTreeFrame>):
169
+ void {
170
+ if (event.data.url === 'about:blank') {
171
+ resolve();
172
+ } else {
173
+ reject(`Unexpected navigation to ${event.data.url}`);
174
+ }
175
+ resourceModel?.removeEventListener(SDK.ResourceTreeModel.Events.FrameNavigated, waitForAboutBlank);
176
+ }
177
+ resourceModel.addEventListener(SDK.ResourceTreeModel.Events.FrameNavigated, waitForAboutBlank);
178
+ await resourceModel.navigate('about:blank' as Platform.DevToolsPath.UrlString);
179
+ });
180
+
181
+ await aboutBlankNavigationComplete;
182
+ }
183
+
184
+ async #navigateWithSDK(url: Platform.DevToolsPath.UrlString): Promise<void> {
185
+ const resourceModel = this.primaryPageTarget.model(SDK.ResourceTreeModel.ResourceTreeModel);
186
+ if (!resourceModel) {
187
+ throw new Error('expected to find ResourceTreeModel');
188
+ }
189
+
190
+ const loadPromiseWithResolvers = Promise.withResolvers<void>();
191
+ this.#loadEventFiredCb = loadPromiseWithResolvers.resolve;
192
+ SDK.TargetManager.TargetManager.instance().addModelListener(
193
+ SDK.ResourceTreeModel.ResourceTreeModel, SDK.ResourceTreeModel.Events.Load, this.#onLoadEventFired, this);
194
+
195
+ // We don't need to await this because we are purposefully showing UI
196
+ // progress as the page loads & tracing is underway.
197
+ void resourceModel.navigate(url);
198
+
199
+ await loadPromiseWithResolvers.promise;
200
+ }
201
+
202
+ async startRecording(options: RecordingOptions): Promise<void> {
80
203
  function disabledByDefault(category: string): string {
81
204
  return 'disabled-by-default-' + category;
82
205
  }
83
206
 
207
+ this.client.recordingStatus(i18nString(UIStrings.initializingTracing));
208
+
209
+ // If we are doing "Reload & record", we first navigate the page to
210
+ // about:blank. This is to ensure any data on the timeline from any
211
+ // previous performance recording is lost, avoiding the problem where a
212
+ // timeline will show data & screenshots from a previous page load that
213
+ // was not relevant.
214
+ if (options.navigateToUrl) {
215
+ await this.#navigateToAboutBlank();
216
+ }
217
+
84
218
  // The following categories are also used in other tools, but this panel
85
219
  // offers the possibility of turning them off (see below).
86
220
  // 'disabled-by-default-devtools.screenshot'
@@ -144,11 +278,40 @@ export class TimelineController implements Tracing.TracingManager.TracingManager
144
278
  this.#navigationUrls = [];
145
279
  this.#fieldData = null;
146
280
  this.#recordingStartTime = Date.now();
281
+
147
282
  const response = await this.startRecordingWithCategories(categoriesArray.join(','));
148
283
  if (response.getError()) {
149
284
  await SDK.TargetManager.TargetManager.instance().resumeAllTargets();
285
+ throw new Error(response.getError());
150
286
  }
151
- return response;
287
+
288
+ if (!options.navigateToUrl) {
289
+ return;
290
+ }
291
+
292
+ // If the user hit "Reload & record", by this point we have:
293
+ // 1. Navigated to about:blank
294
+ // 2. Initiated tracing.
295
+ // We therefore now should navigate back to the original URL that the user wants to profile.
296
+
297
+ // Setup a status checker so we can wait long enough for the page to settle,
298
+ // and to let users know what is going on.
299
+ this.#statusChecker?.removeListener();
300
+ this.#statusChecker = new StatusChecker();
301
+
302
+ const loadEvent = this.#navigateWithSDK(options.navigateToUrl);
303
+ this.#statusChecker.add(i18nString(UIStrings.waitingForLoadEvent), loadEvent);
304
+ this.#statusChecker.add(
305
+ i18nString(UIStrings.waitingForLoadEventPlus5Seconds),
306
+ loadEvent.then(() => new Promise(resolve => setTimeout(resolve, 5000))));
307
+
308
+ this.#statusChecker.setListener(status => {
309
+ if (status === null) {
310
+ void this.stopRecording();
311
+ } else {
312
+ this.client.recordingStatus(status);
313
+ }
314
+ });
152
315
  }
153
316
 
154
317
  async #onFrameNavigated(event: {data: SDK.ResourceTreeModel.ResourceTreeFrame}): Promise<void> {
@@ -159,7 +322,22 @@ export class TimelineController implements Tracing.TracingManager.TracingManager
159
322
  this.#navigationUrls.push(event.data.url);
160
323
  }
161
324
 
325
+ async #onLoadEventFired(
326
+ event: Common.EventTarget
327
+ .EventTargetEvent<{resourceTreeModel: SDK.ResourceTreeModel.ResourceTreeModel, loadTime: number}>):
328
+ Promise<void> {
329
+ if (!event.data.resourceTreeModel.mainFrame?.isPrimaryFrame()) {
330
+ return;
331
+ }
332
+
333
+ this.#loadEventFiredCb?.();
334
+ }
335
+
162
336
  async stopRecording(): Promise<void> {
337
+ this.#statusChecker?.removeListener();
338
+ this.#statusChecker = null;
339
+ this.#loadEventFiredCb = null;
340
+
163
341
  if (this.tracingManager) {
164
342
  this.tracingManager.stop();
165
343
  }
@@ -167,6 +345,8 @@ export class TimelineController implements Tracing.TracingManager.TracingManager
167
345
  SDK.TargetManager.TargetManager.instance().removeModelListener(
168
346
  SDK.ResourceTreeModel.ResourceTreeModel, SDK.ResourceTreeModel.Events.FrameNavigated, this.#onFrameNavigated,
169
347
  this);
348
+ SDK.TargetManager.TargetManager.instance().removeModelListener(
349
+ SDK.ResourceTreeModel.ResourceTreeModel, SDK.ResourceTreeModel.Events.Load, this.#onLoadEventFired, this);
170
350
 
171
351
  // When throttling is applied to the main renderer, it can slow down the
172
352
  // collection of trace events once tracing has completed. Therefore we
@@ -286,7 +466,8 @@ export class TimelineController implements Tracing.TracingManager.TracingManager
286
466
  }
287
467
 
288
468
  export interface Client {
289
- recordingProgress(usage: number): void;
469
+ recordingProgress(bufferUsage: number): void;
470
+ recordingStatus(status: string): void;
290
471
  loadingStarted(): void;
291
472
  processingStarted(): void;
292
473
  loadingProgress(progress?: number): void;
@@ -300,4 +481,5 @@ export interface RecordingOptions {
300
481
  capturePictures?: boolean;
301
482
  captureFilmStrip?: boolean;
302
483
  captureSelectorStats?: boolean;
484
+ navigateToUrl?: Platform.DevToolsPath.UrlString;
303
485
  }
@@ -453,6 +453,20 @@ export class TimelineFlameChartDataProvider extends Common.ObjectWrapper.ObjectW
453
453
  return this.compatibilityTracksAppender;
454
454
  }
455
455
 
456
+ #insertEventToEntryData(event: Trace.Types.Events.Event): number {
457
+ // TODO(crbug.com/457866795): We don't actually need to keep this sorted yet, because we sort
458
+ // in CompatibilityTracksAppender.eventsInTrack. But if we ever wanted to remove that sort,
459
+ // the following code will be needed.
460
+
461
+ // const index = Platform.ArrayUtilities.lowerBound(this.entryData, event, (a, b) => a.ts - b.ts);
462
+ // this.entryData.splice(index, 0, event);
463
+ // return index;
464
+
465
+ // For now, just keep it simple and slightly faster.
466
+ this.entryData.push(event);
467
+ return this.entryData.length - 1;
468
+ }
469
+
456
470
  /**
457
471
  * Returns the instance of the timeline flame chart data, without
458
472
  * adding data to it. In case the timeline data hasn't been instanced
@@ -546,7 +560,7 @@ export class TimelineFlameChartDataProvider extends Common.ObjectWrapper.ObjectW
546
560
  this.entryData = [];
547
561
  this.entryTypeByLevel = [];
548
562
  this.entryIndexToTitle = [];
549
- this.#eventIndexByEvent = new Map();
563
+ this.#eventIndexByEvent = new WeakMap();
550
564
 
551
565
  if (this.#timelineData) {
552
566
  this.compatibilityTracksAppender?.setFlameChartDataAndEntryData(
@@ -568,7 +582,7 @@ export class TimelineFlameChartDataProvider extends Common.ObjectWrapper.ObjectW
568
582
  this.entryData = [];
569
583
  this.entryTypeByLevel = [];
570
584
  this.entryIndexToTitle = [];
571
- this.#eventIndexByEvent = new Map();
585
+ this.#eventIndexByEvent = new WeakMap();
572
586
  this.#minimumBoundary = 0;
573
587
  this.timeSpan = 0;
574
588
 
@@ -750,9 +764,14 @@ export class TimelineFlameChartDataProvider extends Common.ObjectWrapper.ObjectW
750
764
  * because then when it comes to drawing we can decorate them differently.
751
765
  **/
752
766
  #appendFramesAndScreenshotsTrack(): void {
767
+ if (this.entryData.length) {
768
+ throw new Error('expected this.entryData to be empty');
769
+ }
770
+
753
771
  if (!this.parsedTrace) {
754
772
  return;
755
773
  }
774
+
756
775
  const filmStrip = Trace.Extras.FilmStrip.fromHandlerData(this.parsedTrace.data);
757
776
  const hasScreenshots = filmStrip.frames.length > 0;
758
777
  const hasFrames = this.parsedTrace.data.Frames.frames.length > 0;
@@ -782,27 +801,27 @@ export class TimelineFlameChartDataProvider extends Common.ObjectWrapper.ObjectW
782
801
  if (!this.#timelineData || !this.parsedTrace) {
783
802
  return;
784
803
  }
804
+
785
805
  this.appendHeader('', this.screenshotsGroupStyle, false /* selectable */);
786
806
  this.entryTypeByLevel[this.currentLevel] = EntryType.SCREENSHOT;
787
- let prevTimestamp: Trace.Types.Timing.Milli|undefined = undefined;
788
-
789
- for (const filmStripFrame of filmStrip.frames) {
790
- const screenshotTimeInMilliSeconds = Trace.Helpers.Timing.microToMilli(filmStripFrame.screenshotEvent.ts);
791
- this.entryData.push(filmStripFrame.screenshotEvent);
792
- (this.#timelineData.entryLevels as number[]).push(this.currentLevel);
793
- (this.#timelineData.entryStartTimes as number[]).push(screenshotTimeInMilliSeconds);
794
- if (prevTimestamp) {
795
- (this.#timelineData.entryTotalTimes as number[]).push(screenshotTimeInMilliSeconds - prevTimestamp);
796
- }
797
- prevTimestamp = screenshotTimeInMilliSeconds;
798
- }
799
- if (filmStrip.frames.length && prevTimestamp !== undefined) {
800
- const maxRecordTimeMillis =
801
- Trace.Helpers.Timing.traceWindowMilliSeconds(this.parsedTrace.data.Meta.traceBounds).max;
802
807
 
803
- // Set the total time of the final screenshot so it takes up the remainder of the trace.
804
- (this.#timelineData.entryTotalTimes as number[]).push(maxRecordTimeMillis - prevTimestamp);
808
+ const traceEnd = Trace.Helpers.Timing.traceWindowMilliSeconds(this.parsedTrace.data.Meta.traceBounds).max;
809
+
810
+ for (let i = 0; i < filmStrip.frames.length; ++i) {
811
+ const currentFrame = filmStrip.frames[i];
812
+ const nextFrame = filmStrip.frames[i + 1];
813
+ const startTimeMillis = Trace.Helpers.Timing.microToMilli(currentFrame.screenshotEvent.ts);
814
+ // If there is no next frame, use the end of the trace.
815
+ const endTimeMillis = nextFrame ? Trace.Helpers.Timing.microToMilli(nextFrame.screenshotEvent.ts) : traceEnd;
816
+ const durationMillis = endTimeMillis - startTimeMillis;
817
+
818
+ const index = this.#insertEventToEntryData(currentFrame.screenshotEvent);
819
+ (this.#timelineData.entryLevels as number[]).splice(index, 0, this.currentLevel);
820
+ (this.#timelineData.entryStartTimes as number[]).splice(index, 0, startTimeMillis);
821
+ (this.#timelineData.entryTotalTimes as number[]).splice(index, 0, durationMillis);
822
+ this.entryIndexToTitle.splice(index, 0, '');
805
823
  }
824
+
806
825
  ++this.currentLevel;
807
826
  }
808
827
 
@@ -1175,16 +1194,24 @@ export class TimelineFlameChartDataProvider extends Common.ObjectWrapper.ObjectW
1175
1194
  }
1176
1195
 
1177
1196
  #appendFrame(frame: Trace.Types.Events.LegacyTimelineFrame): void {
1178
- const index = this.entryData.length;
1179
- this.entryData.push(frame);
1197
+ const index = this.#insertEventToEntryData(frame);
1180
1198
  const durationMilliseconds = Trace.Helpers.Timing.microToMilli(frame.duration);
1181
- this.entryIndexToTitle[index] = i18n.TimeUtilities.millisToString(durationMilliseconds, true);
1199
+ this.entryIndexToTitle.splice(index, 0, i18n.TimeUtilities.millisToString(durationMilliseconds, true));
1200
+
1182
1201
  if (!this.#timelineData) {
1183
1202
  return;
1184
1203
  }
1185
- this.#timelineData.entryLevels[index] = this.currentLevel;
1186
- this.#timelineData.entryTotalTimes[index] = durationMilliseconds;
1187
- this.#timelineData.entryStartTimes[index] = Trace.Helpers.Timing.microToMilli(frame.startTime);
1204
+
1205
+ if (Array.isArray(this.#timelineData.entryLevels) && Array.isArray(this.#timelineData.entryTotalTimes) &&
1206
+ Array.isArray(this.#timelineData.entryStartTimes)) {
1207
+ this.#timelineData.entryLevels.splice(index, 0, this.currentLevel);
1208
+ this.#timelineData.entryTotalTimes.splice(index, 0, durationMilliseconds);
1209
+ this.#timelineData.entryStartTimes.splice(index, 0, Trace.Helpers.Timing.microToMilli(frame.startTime));
1210
+ } else {
1211
+ this.#timelineData.entryLevels[index] = this.currentLevel;
1212
+ this.#timelineData.entryTotalTimes[index] = durationMilliseconds;
1213
+ this.#timelineData.entryStartTimes[index] = Trace.Helpers.Timing.microToMilli(frame.startTime);
1214
+ }
1188
1215
  }
1189
1216
 
1190
1217
  createSelection(entryIndex: number): TimelineSelection|null {
@@ -423,28 +423,15 @@ export class TimelineFlameChartNetworkDataProvider implements PerfUI.FlameChart.
423
423
 
424
424
  /**
425
425
  * When users zoom in the flamechart, we only want to show them the network
426
- * requests between startTime and endTime. This function will call the
427
- * trackAppender to update the timeline data, and then force to create a new
428
- * PerfUI.FlameChart.FlameChartTimelineData instance to force the flamechart
429
- * to re-render.
426
+ * requests between startTime and endTime.
430
427
  */
431
428
  #updateTimelineData(startTime: Trace.Types.Timing.Milli, endTime: Trace.Types.Timing.Milli): void {
432
429
  if (!this.#networkTrackAppender || !this.#timelineData) {
433
430
  return;
434
431
  }
432
+ // This also has the side-effect of updating this.#timelineData with new
433
+ // information.
435
434
  this.#maxLevel = this.#networkTrackAppender.relayoutEntriesWithinBounds(this.#events, startTime, endTime);
436
-
437
- // TODO(crbug.com/1459225): Remove this recreating code.
438
- // Force to create a new PerfUI.FlameChart.FlameChartTimelineData instance
439
- // to force the flamechart to re-render. This also causes crbug.com/1459225.
440
- this.#timelineData = PerfUI.FlameChart.FlameChartTimelineData.create({
441
- entryLevels: this.#timelineData?.entryLevels,
442
- entryTotalTimes: this.#timelineData?.entryTotalTimes,
443
- entryStartTimes: this.#timelineData?.entryStartTimes,
444
- groups: this.#timelineData?.groups,
445
- initiatorsData: this.#timelineData.initiatorsData,
446
- entryDecorations: this.#timelineData.entryDecorations,
447
- });
448
435
  }
449
436
 
450
437
  /**
@@ -66,10 +66,12 @@ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
66
66
  */
67
67
  export const SORT_ORDER_PAGE_LOAD_MARKERS: Readonly<Record<string, number>> = {
68
68
  [Trace.Types.Events.Name.NAVIGATION_START]: 0,
69
- [Trace.Types.Events.Name.MARK_LOAD]: 1,
70
- [Trace.Types.Events.Name.MARK_FCP]: 2,
71
- [Trace.Types.Events.Name.MARK_DOM_CONTENT]: 3,
72
- [Trace.Types.Events.Name.MARK_LCP_CANDIDATE]: 4,
69
+ [Trace.Types.Events.Name.SOFT_NAVIGATION_START]: 1,
70
+ [Trace.Types.Events.Name.MARK_LOAD]: 2,
71
+ [Trace.Types.Events.Name.MARK_FCP]: 3,
72
+ [Trace.Types.Events.Name.MARK_DOM_CONTENT]: 4,
73
+ [Trace.Types.Events.Name.MARK_LCP_CANDIDATE]: 5,
74
+ [Trace.Types.Events.Name.MARK_LCP_CANDIDATE_FOR_SOFT_NAVIGATION]: 6,
73
75
  };
74
76
 
75
77
  // Threshold to match up overlay markers that are off by a tiny amount so they aren't rendered
@@ -123,6 +125,7 @@ export class TimelineFlameChartView extends Common.ObjectWrapper.eventMixin<Even
123
125
  private readonly onMainEntrySelected: (event: Common.EventTarget.EventTargetEvent<number>) => void;
124
126
  private readonly onNetworkEntrySelected: (event: Common.EventTarget.EventTargetEvent<number>) => void;
125
127
  readonly #boundRefreshAfterIgnoreList: () => void;
128
+ /** This is sorted by ts. */
126
129
  #selectedEvents: Trace.Types.Events.Event[]|null;
127
130
  // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
128
131
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -648,6 +651,7 @@ export class TimelineFlameChartView extends Common.ObjectWrapper.eventMixin<Even
648
651
  fieldMetricResult = fieldMetricResults.fcp;
649
652
  } else if (event.name === Trace.Types.Events.Name.MARK_LCP_CANDIDATE) {
650
653
  fieldMetricResult = fieldMetricResults.lcp;
654
+ // Ignoring soft-nav LCP on purpose.
651
655
  }
652
656
 
653
657
  if (!fieldMetricResult) {
@@ -669,7 +673,9 @@ export class TimelineFlameChartView extends Common.ObjectWrapper.eventMixin<Even
669
673
  // Set markers for Navigations, LCP, FCP, DCL, L.
670
674
  const markers = markerEvents.filter(
671
675
  event => event.name === Trace.Types.Events.Name.NAVIGATION_START ||
676
+ event.name === Trace.Types.Events.Name.SOFT_NAVIGATION_START ||
672
677
  event.name === Trace.Types.Events.Name.MARK_LCP_CANDIDATE ||
678
+ event.name === Trace.Types.Events.Name.MARK_LCP_CANDIDATE_FOR_SOFT_NAVIGATION ||
673
679
  event.name === Trace.Types.Events.Name.MARK_FCP ||
674
680
  event.name === Trace.Types.Events.Name.MARK_DOM_CONTENT ||
675
681
  event.name === Trace.Types.Events.Name.MARK_LOAD);
@@ -681,6 +687,7 @@ export class TimelineFlameChartView extends Common.ObjectWrapper.eventMixin<Even
681
687
  marker,
682
688
  parsedTrace.data.Meta.traceBounds,
683
689
  parsedTrace.data.Meta.navigationsByNavigationId,
690
+ parsedTrace.data.Meta.softNavigationsById,
684
691
  parsedTrace.data.Meta.navigationsByFrameId,
685
692
  );
686
693
  // If any of the markers overlap in timing, lets put them on the same marker.
@@ -732,24 +739,11 @@ export class TimelineFlameChartView extends Common.ObjectWrapper.eventMixin<Even
732
739
  entries.push(...Overlays.Overlays.entriesForOverlay(overlay));
733
740
  }
734
741
 
735
- // The insight's `relatedEvents` property likely already includes the events associated with
736
- // an overlay, but just in case not, include both arrays. Duplicates are fine.
737
- let relatedEventsList = this.#activeInsight?.model.relatedEvents;
738
- if (!relatedEventsList) {
739
- relatedEventsList = [];
740
- } else if (relatedEventsList instanceof Map) {
741
- relatedEventsList = Array.from(relatedEventsList.keys());
742
- }
743
- this.#dimInsightRelatedEvents([...entries, ...relatedEventsList]);
744
-
745
742
  if (options.updateTraceWindow) {
746
- // We should only expand the entry track when we are updating the trace window
747
- // (eg. when insight cards are initially opened).
743
+ // We should only expand the entry track when we are updating the trace window (eg. when insight cards are initially opened).
748
744
  // Otherwise the track will open when not intending to.
749
- for (const entry of entries) {
750
- // Ensure that the track for the entries are open.
751
- this.#expandEntryTrack(entry);
752
- }
745
+ this.#bulkExpandGroupsForEntries(entries);
746
+
753
747
  const overlaysBounds = Overlays.Overlays.traceWindowContainingOverlays(this.#currentInsightOverlays);
754
748
  if (overlaysBounds) {
755
749
  // Trace window covering all overlays expanded by 50% so that the overlays cover 2/3 (100/150) of the visible window. (Or use provided override)
@@ -767,11 +761,26 @@ export class TimelineFlameChartView extends Common.ObjectWrapper.eventMixin<Even
767
761
  }
768
762
  }
769
763
 
764
+ // The insight's `relatedEvents` property likely already includes the events associated with
765
+ // an overlay, but just in case not, include both arrays. Duplicates are fine.
766
+ let relatedEventsList = this.#activeInsight?.model.relatedEvents;
767
+ if (!relatedEventsList) {
768
+ relatedEventsList = [];
769
+ } else if (relatedEventsList instanceof Map) {
770
+ relatedEventsList = Array.from(relatedEventsList.keys());
771
+ }
772
+ this.#dimInsightRelatedEvents([...entries, ...relatedEventsList]);
773
+
770
774
  // Reveal entry if we have one.
775
+ // This is wrapped in a rAF to make sure the FlameChart draw from the
776
+ // expansion of any groups is complete - we need all the update() handlers
777
+ // to have run so the FlameChart has been drawn correctly at the right height.
771
778
  if (entries.length !== 0) {
772
779
  const earliestEntry =
773
780
  entries.reduce((earliest, current) => (earliest.ts < current.ts ? earliest : current), entries[0]);
774
- this.revealEventVertically(earliestEntry);
781
+ requestAnimationFrame(() => {
782
+ this.revealEventVertically(earliestEntry);
783
+ });
775
784
  }
776
785
  }
777
786
 
@@ -823,6 +832,41 @@ export class TimelineFlameChartView extends Common.ObjectWrapper.eventMixin<Even
823
832
  }
824
833
  }
825
834
 
835
+ /**
836
+ * Bulk expands the tracks (e.g. groups) that the given entries belong to.
837
+ * Will update them all at once and then do a redraw.
838
+ */
839
+ #bulkExpandGroupsForEntries(entries: Trace.Types.Events.Event[]): void {
840
+ const networkGroupIndexes = new Set<number>();
841
+ const mainGroupIndexes = new Set<number>();
842
+
843
+ for (const entry of entries) {
844
+ const chartName = Overlays.Overlays.chartForEntry(entry);
845
+ const provider = chartName === 'main' ? this.mainDataProvider : this.networkDataProvider;
846
+ const entryIndex = provider.indexForEvent?.(entry) ?? null;
847
+ if (entryIndex === null) {
848
+ continue;
849
+ }
850
+
851
+ const group = provider.groupForEvent?.(entryIndex) ?? null;
852
+ if (!group) {
853
+ continue;
854
+ }
855
+ if (group.expanded) {
856
+ continue;
857
+ }
858
+ const groupIndex = provider.timelineData().groups.indexOf(group);
859
+ if (chartName === 'main') {
860
+ mainGroupIndexes.add(groupIndex);
861
+ } else {
862
+ networkGroupIndexes.add(groupIndex);
863
+ }
864
+ }
865
+
866
+ this.mainFlameChart.bulkExpandGroups([...mainGroupIndexes]);
867
+ this.networkFlameChart.bulkExpandGroups([...networkGroupIndexes]);
868
+ }
869
+
826
870
  /**
827
871
  * Expands the track / group that the given entry is in.
828
872
  */