chrome-devtools-frontend 1.0.1559913 → 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 (130) hide show
  1. package/front_end/core/host/InspectorFrontendHostStub.ts +0 -3
  2. package/front_end/core/platform/ArrayUtilities.ts +13 -0
  3. package/front_end/core/root/Runtime.ts +0 -5
  4. package/front_end/core/sdk/DOMModel.ts +8 -0
  5. package/front_end/core/sdk/NetworkManager.ts +4 -0
  6. package/front_end/core/sdk/NetworkRequest.ts +9 -0
  7. package/front_end/core/sdk/OverlayModel.ts +20 -9
  8. package/front_end/entrypoints/main/MainImpl.ts +2 -1
  9. package/front_end/generated/InspectorBackendCommands.ts +4 -2
  10. package/front_end/generated/protocol-mapping.d.ts +7 -0
  11. package/front_end/generated/protocol-proxy-api.d.ts +5 -0
  12. package/front_end/generated/protocol.ts +24 -0
  13. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +23 -22
  14. package/front_end/models/badges/UserBadges.ts +48 -16
  15. package/front_end/models/greendev/Prototypes.ts +6 -1
  16. package/front_end/models/trace/extras/TraceTree.ts +1 -1
  17. package/front_end/models/trace/handlers/PageLoadMetricsHandler.ts +8 -3
  18. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +11 -142
  19. package/front_end/panels/ai_assistance/PatchWidget.ts +90 -72
  20. package/front_end/panels/ai_assistance/ai_assistance.ts +1 -0
  21. package/front_end/panels/ai_assistance/components/ChatInput.ts +701 -0
  22. package/front_end/panels/ai_assistance/components/ChatView.ts +71 -1268
  23. package/front_end/panels/ai_assistance/components/UserActionRow.ts +514 -31
  24. package/front_end/panels/ai_assistance/components/chatInput.css +387 -0
  25. package/front_end/panels/ai_assistance/components/chatView.css +38 -599
  26. package/front_end/panels/ai_assistance/components/userActionRow.css +230 -0
  27. package/front_end/panels/autofill/AutofillView.ts +2 -2
  28. package/front_end/panels/changes/ChangesView.ts +15 -1
  29. package/front_end/panels/changes/changesView.css +6 -0
  30. package/front_end/panels/common/BadgeNotification.ts +44 -58
  31. package/front_end/panels/common/CopyChangesToPrompt.ts +233 -0
  32. package/front_end/panels/common/common.ts +1 -0
  33. package/front_end/panels/elements/ElementsTreeElement.ts +183 -359
  34. package/front_end/panels/elements/ElementsTreeOutline.ts +0 -6
  35. package/front_end/panels/elements/ShortcutTreeElement.ts +57 -50
  36. package/front_end/panels/elements/StylePropertiesSection.ts +1 -3
  37. package/front_end/panels/elements/components/AdornerManager.ts +5 -149
  38. package/front_end/panels/issues/HiddenIssuesRow.ts +1 -2
  39. package/front_end/panels/issues/IssueKindView.ts +2 -4
  40. package/front_end/panels/issues/IssueView.ts +2 -4
  41. package/front_end/panels/network/NetworkDataGridNode.ts +65 -1
  42. package/front_end/panels/network/NetworkLogView.ts +2 -4
  43. package/front_end/panels/network/NetworkLogViewColumns.ts +9 -0
  44. package/front_end/panels/screencast/ScreencastApp.ts +1 -0
  45. package/front_end/panels/settings/SettingsScreen.ts +3 -2
  46. package/front_end/panels/timeline/CompatibilityTracksAppender.ts +14 -1
  47. package/front_end/panels/timeline/ThirdPartyTreeView.ts +1 -4
  48. package/front_end/panels/timeline/TimelineController.ts +185 -3
  49. package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +52 -25
  50. package/front_end/panels/timeline/TimelineFlameChartView.ts +1 -0
  51. package/front_end/panels/timeline/TimelinePanel.ts +17 -104
  52. package/front_end/panels/timeline/TimelineTreeView.ts +1 -0
  53. package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +2 -2
  54. package/front_end/panels/timeline/components/insights/Table.ts +3 -3
  55. package/front_end/panels/whats_new/ReleaseNoteText.ts +15 -9
  56. package/front_end/panels/whats_new/resources/WNDT.md +6 -6
  57. package/front_end/third_party/chromium/README.chromium +1 -1
  58. package/front_end/third_party/codemirror.next/rebuild.sh +1 -1
  59. package/front_end/third_party/lit/rebuild.sh +1 -1
  60. package/front_end/third_party/puppeteer/README.chromium +2 -2
  61. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.d.ts +2 -3
  62. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.d.ts.map +1 -1
  63. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.js.map +1 -1
  64. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPRequest.d.ts.map +1 -1
  65. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPRequest.js +9 -0
  66. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPRequest.js.map +1 -1
  67. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPResponse.d.ts +3 -0
  68. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPResponse.d.ts.map +1 -1
  69. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPResponse.js +9 -0
  70. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPResponse.js.map +1 -1
  71. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Request.d.ts +3 -0
  72. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Request.d.ts.map +1 -1
  73. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Request.js +10 -0
  74. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Request.js.map +1 -1
  75. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.d.ts.map +1 -1
  76. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.js +8 -4
  77. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.js.map +1 -1
  78. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.d.ts +1 -1
  79. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.d.ts.map +1 -1
  80. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.js +1 -1
  81. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.js.map +1 -1
  82. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
  83. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +3 -3
  84. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +3 -3
  85. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js.map +1 -1
  86. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  87. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.d.ts +1 -1
  88. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.js +1 -1
  89. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +10 -1
  90. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +13 -7
  91. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.d.ts +2 -3
  92. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.d.ts.map +1 -1
  93. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.js.map +1 -1
  94. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPRequest.d.ts.map +1 -1
  95. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPRequest.js +9 -0
  96. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPRequest.js.map +1 -1
  97. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPResponse.d.ts +3 -0
  98. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPResponse.d.ts.map +1 -1
  99. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPResponse.js +9 -0
  100. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPResponse.js.map +1 -1
  101. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Request.d.ts +3 -0
  102. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Request.d.ts.map +1 -1
  103. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Request.js +10 -0
  104. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Request.js.map +1 -1
  105. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.d.ts.map +1 -1
  106. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.js +8 -4
  107. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.js.map +1 -1
  108. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.d.ts +1 -1
  109. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.d.ts.map +1 -1
  110. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.js +1 -1
  111. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.js.map +1 -1
  112. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +3 -3
  113. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +3 -3
  114. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js.map +1 -1
  115. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.d.ts +1 -1
  116. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.js +1 -1
  117. package/front_end/third_party/puppeteer/package/lib/types.d.ts +10 -1
  118. package/front_end/third_party/puppeteer/package/package.json +3 -3
  119. package/front_end/third_party/puppeteer/package/src/api/Page.ts +2 -3
  120. package/front_end/third_party/puppeteer/package/src/bidi/HTTPRequest.ts +13 -0
  121. package/front_end/third_party/puppeteer/package/src/bidi/HTTPResponse.ts +10 -0
  122. package/front_end/third_party/puppeteer/package/src/bidi/core/Request.ts +15 -0
  123. package/front_end/third_party/puppeteer/package/src/cdp/Browser.ts +9 -4
  124. package/front_end/third_party/puppeteer/package/src/generated/injected.ts +1 -1
  125. package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
  126. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  127. package/front_end/ui/components/adorners/Adorner.ts +8 -68
  128. package/front_end/ui/legacy/TabbedPane.ts +1 -1
  129. package/front_end/ui/visual_logging/KnownContextValues.ts +3 -0
  130. package/package.json +2 -1
@@ -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 {
@@ -125,6 +125,7 @@ export class TimelineFlameChartView extends Common.ObjectWrapper.eventMixin<Even
125
125
  private readonly onMainEntrySelected: (event: Common.EventTarget.EventTargetEvent<number>) => void;
126
126
  private readonly onNetworkEntrySelected: (event: Common.EventTarget.EventTargetEvent<number>) => void;
127
127
  readonly #boundRefreshAfterIgnoreList: () => void;
128
+ /** This is sorted by ts. */
128
129
  #selectedEvents: Trace.Types.Events.Event[]|null;
129
130
  // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
130
131
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -228,10 +228,6 @@ const UIStrings = {
228
228
  * @description Text in Timeline Panel of the Performance panel
229
229
  */
230
230
  processingTrace: 'Processing trace…',
231
- /**
232
- * @description Text in Timeline Panel of the Performance panel
233
- */
234
- initializingTracing: 'Initializing tracing…',
235
231
  /**
236
232
  * @description Text in Timeline Panel of the Performance panel. Shown to the user after they request to download the trace.
237
233
  */
@@ -345,7 +341,6 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
345
341
  private readonly recordingOptionUIControls: UI.Toolbar.ToolbarItem[];
346
342
  private state: State;
347
343
  private recordingPageReload: boolean;
348
- private readonly millisecondsToRecordAfterLoadEvent: number;
349
344
  private readonly toggleRecordAction: UI.ActionRegistration.Action;
350
345
  private readonly recordReloadAction: UI.ActionRegistration.Action;
351
346
  readonly #historyManager: TimelineHistoryManager;
@@ -459,10 +454,8 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
459
454
  ">💫</div>`;
460
455
  const adorner = new Adorners.Adorner.Adorner();
461
456
  adorner.classList.add('fix-perf-icon');
462
- adorner.data = {
463
- name: i18nString(UIStrings.fixMe),
464
- content: adornerContent,
465
- };
457
+ adorner.name = i18nString(UIStrings.fixMe);
458
+ adorner.append(adornerContent);
466
459
  this.#traceEngineModel = traceModel || this.#instantiateNewModel();
467
460
 
468
461
  this.element.addEventListener('contextmenu', this.contextMenu.bind(this), false);
@@ -473,7 +466,6 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
473
466
  this.recordingOptionUIControls = [];
474
467
  this.state = State.IDLE;
475
468
  this.recordingPageReload = false;
476
- this.millisecondsToRecordAfterLoadEvent = 5000;
477
469
  this.toggleRecordAction = UI.ActionRegistry.ActionRegistry.instance().getAction('timeline.toggle-recording');
478
470
  this.recordReloadAction = UI.ActionRegistry.ActionRegistry.instance().getAction('timeline.record-reload');
479
471
 
@@ -538,9 +530,6 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
538
530
 
539
531
  this.createFileSelector();
540
532
 
541
- SDK.TargetManager.TargetManager.instance().addModelListener(
542
- SDK.ResourceTreeModel.ResourceTreeModel, SDK.ResourceTreeModel.Events.Load, this.loadEventFired, this);
543
-
544
533
  this.flameChart = new TimelineFlameChartView(this);
545
534
  this.element.addEventListener(
546
535
  'toggle-popover', event => this.flameChart.togglePopover((event as CustomEvent).detail));
@@ -1858,41 +1847,6 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
1858
1847
  return navigationEntry.url as Platform.DevToolsPath.UrlString;
1859
1848
  }
1860
1849
 
1861
- async #navigateToAboutBlank(): Promise<void> {
1862
- const aboutBlankNavigationComplete = new Promise<void>(async (resolve, reject) => {
1863
- if (!this.controller) {
1864
- reject('Could not find TimelineController');
1865
- return;
1866
- }
1867
- const target = this.controller.primaryPageTarget;
1868
- const resourceModel = target.model(SDK.ResourceTreeModel.ResourceTreeModel);
1869
- if (!resourceModel) {
1870
- reject('Could not load resourceModel');
1871
- return;
1872
- }
1873
-
1874
- /**
1875
- * To clear out the page and any state from prior test runs, we
1876
- * navigate to about:blank before initiating the trace recording.
1877
- * Once we have navigated to about:blank, we start recording and
1878
- * then navigate to the original page URL, to ensure we profile the
1879
- * page load.
1880
- **/
1881
- function waitForAboutBlank(event: Common.EventTarget.EventTargetEvent<SDK.ResourceTreeModel.ResourceTreeFrame>):
1882
- void {
1883
- if (event.data.url === 'about:blank') {
1884
- resolve();
1885
- } else {
1886
- reject(`Unexpected navigation to ${event.data.url}`);
1887
- }
1888
- resourceModel?.removeEventListener(SDK.ResourceTreeModel.Events.FrameNavigated, waitForAboutBlank);
1889
- }
1890
- resourceModel.addEventListener(SDK.ResourceTreeModel.Events.FrameNavigated, waitForAboutBlank);
1891
- await resourceModel.navigate('about:blank' as Platform.DevToolsPath.UrlString);
1892
- });
1893
- await aboutBlankNavigationComplete;
1894
- }
1895
-
1896
1850
  async #startCPUProfilingRecording(): Promise<void> {
1897
1851
  try {
1898
1852
  this.cpuProfiler = UI.Context.Context.instance().flavor(SDK.CPUProfilerModel.CPUProfilerModel);
@@ -1953,30 +1907,18 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
1953
1907
  }
1954
1908
 
1955
1909
  const urlToTrace = await this.#evaluateInspectedURL();
1956
- // If we are doing "Reload & record", we first navigate the page to
1957
- // about:blank. This is to ensure any data on the timeline from any
1958
- // previous performance recording is lost, avoiding the problem where a
1959
- // timeline will show data & screenshots from a previous page load that
1960
- // was not relevant.
1961
- if (this.recordingPageReload) {
1962
- await this.#navigateToAboutBlank();
1963
- }
1964
- const recordingOptions = {
1910
+
1911
+ // Order is important here: we tell the controller to start recording, which enables tracing.
1912
+ await this.controller.startRecording({
1965
1913
  enableJSSampling: !this.disableCaptureJSProfileSetting.get(),
1966
1914
  capturePictures: this.captureLayersAndPicturesSetting.get(),
1967
1915
  captureFilmStrip: this.showScreenshotsSetting.get(),
1968
1916
  captureSelectorStats: this.captureSelectorStatsSetting.get(),
1969
- };
1970
- // Order is important here: we tell the controller to start recording, which enables tracing.
1971
- const response = await this.controller.startRecording(recordingOptions);
1972
- if (response.getError()) {
1973
- throw new Error(response.getError());
1974
- }
1917
+ navigateToUrl: this.recordingPageReload ? urlToTrace : undefined,
1918
+ });
1919
+
1975
1920
  // Once we get here, we know tracing is active.
1976
- // This is when, if the user has hit "Reload & Record" that we now need to navigate to the original URL.
1977
- // If the user has just hit "record", we don't do any navigating.
1978
- const recordingConfig = this.recordingPageReload ? {navigateToUrl: urlToTrace} : undefined;
1979
- this.recordingStarted(recordingConfig);
1921
+ this.recordingStarted();
1980
1922
  } catch (e) {
1981
1923
  await this.recordingFailed(e.message);
1982
1924
  }
@@ -2428,28 +2370,11 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
2428
2370
  return ThemeSupport.ThemeSupport.instance().getComputedValue('--app-color-system');
2429
2371
  }
2430
2372
 
2431
- private recordingStarted(config?: {navigateToUrl: Platform.DevToolsPath.UrlString}): void {
2432
- if (config && this.recordingPageReload && this.controller) {
2433
- // If the user hit "Reload & record", by this point we have:
2434
- // 1. Navigated to about:blank
2435
- // 2. Initiated tracing.
2436
- // We therefore now should navigate back to the original URL that the user wants to profile.
2437
- const resourceModel = this.controller?.primaryPageTarget.model(SDK.ResourceTreeModel.ResourceTreeModel);
2438
- if (!resourceModel) {
2439
- void this.recordingFailed('Could not navigate to original URL');
2440
- return;
2441
- }
2442
- // We don't need to await this because we are purposefully showing UI
2443
- // progress as the page loads & tracing is underway.
2444
- void resourceModel.navigate(config.navigateToUrl);
2445
- }
2446
-
2373
+ private recordingStarted(): void {
2447
2374
  this.#changeView({mode: 'STATUS_PANE_OVERLAY'});
2448
2375
  this.setState(State.RECORDING);
2449
- this.showRecordingStarted();
2450
2376
  if (this.statusDialog) {
2451
2377
  this.statusDialog.enableAndFocusButton();
2452
- this.statusDialog.updateStatus(i18nString(UIStrings.tracing));
2453
2378
  this.statusDialog.updateProgressBar(i18nString(UIStrings.bufferUsage), 0);
2454
2379
  this.statusDialog.startTimer();
2455
2380
  }
@@ -2461,6 +2386,12 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
2461
2386
  }
2462
2387
  }
2463
2388
 
2389
+ recordingStatus(status: string): void {
2390
+ if (this.statusDialog) {
2391
+ this.statusDialog.updateStatus(status);
2392
+ }
2393
+ }
2394
+
2464
2395
  /**
2465
2396
  * Hide the sidebar, but persist the user's state, because when they import a
2466
2397
  * trace we want to revert the sidebar back to what it was.
@@ -2902,7 +2833,7 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
2902
2833
  },
2903
2834
  () => this.stopRecording());
2904
2835
  this.statusDialog.showPane(this.statusPaneContainer);
2905
- this.statusDialog.updateStatus(i18nString(UIStrings.initializingTracing));
2836
+ this.statusDialog.updateStatus(i18nString(UIStrings.tracing));
2906
2837
  this.statusDialog.updateProgressBar(i18nString(UIStrings.bufferUsage), 0);
2907
2838
  }
2908
2839
 
@@ -2912,24 +2843,6 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
2912
2843
  }
2913
2844
  }
2914
2845
 
2915
- private async loadEventFired(
2916
- event: Common.EventTarget
2917
- .EventTargetEvent<{resourceTreeModel: SDK.ResourceTreeModel.ResourceTreeModel, loadTime: number}>):
2918
- Promise<void> {
2919
- if (this.state !== State.RECORDING || !this.recordingPageReload ||
2920
- this.controller?.primaryPageTarget !== event.data.resourceTreeModel.target()) {
2921
- return;
2922
- }
2923
- const controller = this.controller;
2924
- await new Promise(r => window.setTimeout(r, this.millisecondsToRecordAfterLoadEvent));
2925
-
2926
- // Check if we're still in the same recording session.
2927
- if (controller !== this.controller || this.state !== State.RECORDING) {
2928
- return;
2929
- }
2930
- void this.stopRecording();
2931
- }
2932
-
2933
2846
  private frameForSelection(selection: TimelineSelection): Trace.Types.Events.LegacyTimelineFrame|null {
2934
2847
  if (this.#viewMode.mode !== 'VIEWING_TRACE') {
2935
2848
  return null;
@@ -171,6 +171,7 @@ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
171
171
  export class TimelineTreeView extends
172
172
  Common.ObjectWrapper.eventMixin<TimelineTreeView.EventTypes, typeof UI.Widget.VBox>(UI.Widget.VBox)
173
173
  implements UI.SearchableView.Searchable {
174
+ /** This is sorted by ts. */
174
175
  #selectedEvents: Trace.Types.Events.Event[]|null;
175
176
  private searchResults: Trace.Extras.TraceTree.Node[];
176
177
  linkifier!: Components.Linkifier.Linkifier;
@@ -75,7 +75,7 @@ interface ViewInput {
75
75
  estimatedSavingsAriaLabel: string|null;
76
76
  renderContent: () => Lit.LitTemplate;
77
77
  dispatchInsightToggle: () => void;
78
- onHeaderKeyDown: () => void;
78
+ onHeaderKeyDown: (event: KeyboardEvent) => void;
79
79
  onAskAIButtonClick: () => void;
80
80
  }
81
81
 
@@ -412,7 +412,7 @@ export abstract class BaseInsightComponent<T extends InsightModel> extends UI.Wi
412
412
  showAskAI: this.#canShowAskAI(),
413
413
  dispatchInsightToggle: () => this.#dispatchInsightToggle(),
414
414
  renderContent: () => this.renderContent(),
415
- onHeaderKeyDown: () => this.#onHeaderKeyDown,
415
+ onHeaderKeyDown: this.#onHeaderKeyDown.bind(this),
416
416
  onAskAIButtonClick: () => this.#onAskAIButtonClick(),
417
417
  };
418
418
  this.#view(input, undefined, this.contentElement);
@@ -239,11 +239,11 @@ export class Table extends UI.Widget.Widget {
239
239
  }
240
240
 
241
241
  #onHoverRow(row: TableDataRow, rowEl: HTMLElement): void {
242
- if (row === this.#currentHoverRow) {
242
+ if (row === this.#currentHoverRow || !this.element.shadowRoot) {
243
243
  return;
244
244
  }
245
245
 
246
- for (const el of this.element.querySelectorAll('.hover')) {
246
+ for (const el of this.element.shadowRoot.querySelectorAll('.hover')) {
247
247
  el.classList.remove('hover');
248
248
  }
249
249
 
@@ -251,7 +251,7 @@ export class Table extends UI.Widget.Widget {
251
251
  let curRow: TableDataRow|undefined = this.#rowToParentRow.get(row);
252
252
  while (curRow) {
253
253
  rowEl.classList.add('hover');
254
- curRow = this.#rowToParentRow.get(row);
254
+ curRow = this.#rowToParentRow.get(curRow);
255
255
  }
256
256
 
257
257
  this.#currentHoverRow = row;