chrome-devtools-frontend 1.0.1652307 → 1.0.1654411

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/.agents/skills/fixing-skipped-tests/SKILL.md +32 -0
  2. package/.agents/skills/migrate-chromium-test/SKILL.md +144 -0
  3. package/docs/get_the_code.md +1 -2
  4. package/docs/ui_engineering.md +3 -1
  5. package/eslint.config.mjs +0 -5
  6. package/front_end/core/sdk/CPUThrottlingManager.ts +13 -8
  7. package/front_end/core/sdk/CSSMetadata.ts +23 -0
  8. package/front_end/core/sdk/CompilerSourceMappingContentProvider.ts +8 -4
  9. package/front_end/core/sdk/DOMModel.ts +4 -0
  10. package/front_end/core/sdk/EmulationModel.ts +4 -0
  11. package/front_end/core/sdk/ResourceTreeModel.ts +3 -2
  12. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +459 -74
  13. package/front_end/entrypoints/main/MainImpl.ts +0 -6
  14. package/front_end/foundation/Universe.ts +18 -0
  15. package/front_end/generated/InspectorBackendCommands.ts +6 -5
  16. package/front_end/generated/SupportedCSSProperties.js +122 -28
  17. package/front_end/generated/protocol.ts +58 -0
  18. package/front_end/models/ai_assistance/AiConversation.ts +2 -1
  19. package/front_end/models/ai_assistance/README.md +4 -3
  20. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +1 -1
  21. package/front_end/models/ai_assistance/agents/PerformanceAgent.snapshot.txt +2 -2
  22. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +11 -164
  23. package/front_end/models/ai_assistance/ai_assistance.ts +6 -2
  24. package/front_end/models/ai_assistance/contexts/PerformanceTraceContext.ts +295 -0
  25. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +10 -10
  26. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +12 -11
  27. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +6 -6
  28. package/front_end/models/ai_assistance/skills/accessibility.md +4 -2
  29. package/front_end/models/ai_assistance/tools/GetElementAccessibilityDetails.ts +142 -0
  30. package/front_end/models/ai_assistance/tools/GetStyles.ts +1 -1
  31. package/front_end/models/ai_assistance/tools/README.md +21 -1
  32. package/front_end/models/ai_assistance/tools/{ResolveLighthousePath.ts → ResolveDevtoolsNodePath.ts} +18 -17
  33. package/front_end/models/ai_assistance/tools/Tool.ts +6 -1
  34. package/front_end/models/ai_assistance/tools/ToolRegistry.ts +4 -2
  35. package/front_end/models/autofill_manager/AutofillManager.ts +7 -15
  36. package/front_end/models/bindings/NetworkProject.ts +4 -8
  37. package/front_end/models/breakpoints/BreakpointManager.ts +9 -9
  38. package/front_end/models/emulation/DeviceModeModel.ts +24 -0
  39. package/front_end/models/emulation/EmulatedDevices.ts +108 -0
  40. package/front_end/models/heap_snapshot/HeapSnapshotModel.ts +36 -0
  41. package/front_end/models/heap_snapshot/HeapSnapshotProxy.ts +8 -0
  42. package/front_end/models/issues_manager/IssuesManager.ts +10 -6
  43. package/front_end/models/javascript_metadata/JavaScriptMetadata.ts +4 -5
  44. package/front_end/models/live-metrics/LiveMetrics.ts +6 -6
  45. package/front_end/models/live-metrics/web-vitals-injected/spec/spec.ts +5 -5
  46. package/front_end/models/live-metrics/web-vitals-injected/web-vitals-injected.ts +3 -3
  47. package/front_end/models/logs/LogManager.ts +25 -10
  48. package/front_end/models/logs/NetworkLog.ts +24 -19
  49. package/front_end/models/persistence/AutomaticFileSystemWorkspaceBinding.ts +14 -5
  50. package/front_end/models/persistence/IsolatedFileSystem.ts +9 -11
  51. package/front_end/models/persistence/IsolatedFileSystemManager.ts +5 -3
  52. package/front_end/models/persistence/NetworkPersistenceManager.ts +73 -44
  53. package/front_end/models/persistence/PersistenceImpl.ts +4 -5
  54. package/front_end/models/trace/EntityMapper.ts +1 -1
  55. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +9 -9
  56. package/front_end/panels/ai_assistance/ExportConversation.ts +4 -1
  57. package/front_end/panels/ai_assistance/components/ChatInput.ts +2 -2
  58. package/front_end/panels/application/IndexedDBViews.ts +38 -36
  59. package/front_end/panels/application/components/AdsView.ts +4 -0
  60. package/front_end/panels/autofill/AutofillView.ts +1 -1
  61. package/front_end/panels/autofill/autofill-meta.ts +3 -2
  62. package/front_end/panels/elements/AccessibilityTreeUtils.ts +26 -15
  63. package/front_end/panels/elements/AccessibilityTreeView.ts +12 -9
  64. package/front_end/panels/elements/ElementsTreeOutline.ts +5 -0
  65. package/front_end/panels/layer_viewer/LayerTreeOutline.ts +118 -68
  66. package/front_end/panels/layer_viewer/Layers3DView.ts +1 -1
  67. package/front_end/panels/layer_viewer/layerTreeOutline.css +20 -13
  68. package/front_end/panels/layers/LayersPanel.ts +1 -1
  69. package/front_end/panels/mobile_throttling/ThrottlingSettingsTab.ts +64 -17
  70. package/front_end/panels/mobile_throttling/throttlingSettingsTab.css +5 -0
  71. package/front_end/panels/network/LinkPreloadGenerator.ts +101 -0
  72. package/front_end/panels/network/NetworkLogView.ts +28 -0
  73. package/front_end/panels/network/network.ts +2 -0
  74. package/front_end/panels/profiler/HeapSnapshotView.ts +121 -33
  75. package/front_end/panels/profiler/ModuleUIStrings.ts +4 -0
  76. package/front_end/panels/timeline/TimelineHistoryManager.ts +38 -37
  77. package/front_end/panels/timeline/TimelinePanel.ts +32 -8
  78. package/front_end/panels/timeline/components/CPUThrottlingSelector.ts +7 -6
  79. package/front_end/panels/timeline/components/LiveMetricsView.ts +38 -38
  80. package/front_end/panels/timeline/components/MetricCard.ts +18 -18
  81. package/front_end/panels/timeline/components/liveMetricsView.css +6 -6
  82. package/front_end/panels/timeline/components/metricCard.css +4 -4
  83. package/front_end/panels/whats_new/ReleaseNoteText.ts +3 -3
  84. package/front_end/panels/whats_new/resources/WNDT.md +1 -1
  85. package/front_end/third_party/chromium/README.chromium +1 -1
  86. package/front_end/ui/legacy/InspectorDrawerView.ts +7 -5
  87. package/front_end/ui/legacy/ListWidget.ts +12 -0
  88. package/front_end/ui/legacy/SplitWidget.ts +12 -11
  89. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +23 -19
  90. package/front_end/ui/legacy/components/object_ui/CustomPreviewComponent.ts +8 -1
  91. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +110 -17
  92. package/front_end/ui/legacy/treeoutline.css +1 -1
  93. package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
  94. package/package.json +4 -3
@@ -151,6 +151,32 @@ const UIStrings = {
151
151
  * objects retained by event handlers
152
152
  */
153
153
  objectsRetainedByEventHandlers: 'Objects retained by Event Handlers',
154
+ /**
155
+ * @description An option which will filter the heap snapshot to show only
156
+ * objects attributed to a specific native context (roughly, a JavaScript
157
+ * realm such as a frame). PH1 is a name identifying the context
158
+ * (often a URL) which may be empty, PH2 is the id of the native context
159
+ * object, and PH3 is the context's attributed size.
160
+ * @example {https://example.com/ } PH1
161
+ * @example {1234} PH2
162
+ * @example {1.2 MB} PH3
163
+ */
164
+ objectsAttributedToNativeContextS: 'Native context {PH1}@{PH2} ({PH3})',
165
+ /**
166
+ * @description An option which will filter the heap snapshot to show only
167
+ * objects which are shared between multiple native contexts (roughly,
168
+ * JavaScript realms such as frames). PH1 is their total size.
169
+ * @example {1.2 MB} PH1
170
+ */
171
+ objectsSharedBetweenNativeContextsS: 'Objects shared between native contexts ({PH1})',
172
+ /**
173
+ * @description An option which will filter the heap snapshot to show only
174
+ * objects which could not be attributed to any single native context
175
+ * (roughly, a JavaScript realm such as a frame). PH1 is their
176
+ * total size.
177
+ * @example {1.2 MB} PH1
178
+ */
179
+ objectsNotAttributedToNativeContextS: 'Objects not attributed to a native context ({PH1})',
154
180
  /**
155
181
  * @description Text for the summary view
156
182
  */
@@ -266,6 +292,19 @@ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
266
292
  // eslint-disable-next-line @typescript-eslint/naming-convention
267
293
  const moduleUIstr_ = i18n.i18n.registerUIStrings('panels/profiler/ModuleUIStrings.ts', ModuleUIStrings.UIStrings);
268
294
  const moduleI18nString = i18n.i18n.getLocalizedString.bind(undefined, moduleUIstr_);
295
+
296
+ interface NamedFilter {
297
+ uiName: string;
298
+ filterName: string;
299
+ }
300
+
301
+ interface FilterOption {
302
+ uiName: string;
303
+ profileIndex: number;
304
+ filterName?: string;
305
+ disabled?: boolean;
306
+ }
307
+
269
308
  export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayDelegate, UI.SearchableView.Searchable {
270
309
  searchResults: number[] = [];
271
310
  profile: HeapProfileHeader;
@@ -293,6 +332,8 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
293
332
  readonly perspectiveSelect: UI.Toolbar.ToolbarComboBox;
294
333
  baseSelect: UI.Toolbar.ToolbarComboBox;
295
334
  readonly filterSelect: UI.Toolbar.ToolbarComboBox;
335
+ #filterOptions: FilterOption[] = [];
336
+ #nativeContextFilters: NamedFilter[] = [];
296
337
  readonly classNameFilter: UI.Toolbar.ToolbarInput;
297
338
  readonly selectedSizeText: UI.Toolbar.ToolbarText;
298
339
  readonly resetRetainersButton: UI.Toolbar.ToolbarButton;
@@ -556,6 +597,7 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
556
597
  const heapSnapshotProxy = await this.profile.loadPromise;
557
598
 
558
599
  void this.retrieveStatistics(heapSnapshotProxy);
600
+ void this.updateNativeContextFilters(heapSnapshotProxy);
559
601
  if (this.dataGrid) {
560
602
  void this.dataGrid.setDataSource(heapSnapshotProxy, 0);
561
603
  }
@@ -611,6 +653,52 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
611
653
  return statistics;
612
654
  }
613
655
 
656
+ async updateNativeContextFilters(heapSnapshotProxy: HeapSnapshotModel.HeapSnapshotProxy.HeapSnapshotProxy):
657
+ Promise<void> {
658
+ const sizes = await heapSnapshotProxy.getNativeContextSizes();
659
+ const filters: NamedFilter[] = [];
660
+
661
+ // List the individual native contexts first, sorted by attributed size
662
+ // (largest first), then the shared and unattributed buckets.
663
+ const nativeContexts = sizes.nativeContexts.toSorted((a, b) => b.attributedSize - a.attributedSize);
664
+ for (const nativeContext of nativeContexts) {
665
+ // Drop the "system / NativeContext" boilerplate (and any "Detached "
666
+ // marker) so the label shows just the distinguishing part of the name
667
+ // (e.g. the URL). The remaining name (if any) is followed by the native
668
+ // context object id, e.g. "Native context https://example.com @1234".
669
+ let name = nativeContext.nodeName;
670
+ if (name.startsWith('Detached ')) {
671
+ name = name.substring('Detached '.length);
672
+ }
673
+ if (name.startsWith('system / NativeContext / ')) {
674
+ name = name.substring('system / NativeContext / '.length);
675
+ } else if (name.startsWith('system / NativeContext')) {
676
+ name = name.substring('system / NativeContext'.length);
677
+ }
678
+ filters.push({
679
+ uiName: i18nString(UIStrings.objectsAttributedToNativeContextS, {
680
+ PH1: name ? `${name} ` : '',
681
+ PH2: nativeContext.nodeId,
682
+ PH3: i18n.ByteUtilities.bytesToString(nativeContext.attributedSize),
683
+ }),
684
+ filterName: `nativeContext_${nativeContext.nodeIndex}`,
685
+ });
686
+ }
687
+ filters.push({
688
+ uiName: i18nString(UIStrings.objectsSharedBetweenNativeContextsS,
689
+ {PH1: i18n.ByteUtilities.bytesToString(sizes.sharedSize)}),
690
+ filterName: 'sharedNativeContext',
691
+ });
692
+ filters.push({
693
+ uiName: i18nString(UIStrings.objectsNotAttributedToNativeContextS,
694
+ {PH1: i18n.ByteUtilities.bytesToString(sizes.noAttributionSize)}),
695
+ filterName: 'noNativeContext',
696
+ });
697
+
698
+ this.#nativeContextFilters = filters;
699
+ this.updateFilterOptions();
700
+ }
701
+
614
702
  onIdsRangeChanged(event: Common.EventTarget.EventTargetEvent<IdsRangeChangedEvent>): void {
615
703
  const {minId, maxId} = event.data;
616
704
  this.selectedSizeText.setText(
@@ -775,7 +863,7 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
775
863
  this.performSearch(this.currentSearch, false);
776
864
  }
777
865
 
778
- static readonly ALWAYS_AVAILABLE_FILTERS: ReadonlyArray<{uiName: string, filterName: string}> = [
866
+ static readonly ALWAYS_AVAILABLE_FILTERS: readonly NamedFilter[] = [
779
867
  {uiName: i18nString(UIStrings.duplicatedStrings), filterName: 'duplicatedStrings'},
780
868
  {uiName: i18nString(UIStrings.objectsRetainedByDetachedDomNodes), filterName: 'objectsRetainedByDetachedDomNodes'},
781
869
  {uiName: i18nString(UIStrings.objectsRetainedByContexts), filterName: 'objectsRetainedByContexts'},
@@ -784,16 +872,9 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
784
872
  ];
785
873
 
786
874
  changeFilter(): void {
787
- let selectedIndex = this.filterSelect.selectedIndex();
788
- let filterName = undefined;
789
- const indexOfFirstAlwaysAvailableFilter =
790
- this.filterSelect.size() - HeapSnapshotView.ALWAYS_AVAILABLE_FILTERS.length;
791
- if (selectedIndex >= indexOfFirstAlwaysAvailableFilter) {
792
- filterName =
793
- HeapSnapshotView.ALWAYS_AVAILABLE_FILTERS[selectedIndex - indexOfFirstAlwaysAvailableFilter].filterName;
794
- selectedIndex = 0;
795
- }
796
- const profileIndex = selectedIndex - 1;
875
+ const selectedOption = this.#filterOptions[this.filterSelect.selectedIndex()];
876
+ const profileIndex = selectedOption?.profileIndex ?? -1;
877
+ const filterName = selectedOption?.filterName;
797
878
  if (!this.dataGrid) {
798
879
  return;
799
880
  }
@@ -1024,10 +1105,20 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
1024
1105
  updateFilterOptions(): void {
1025
1106
  const list = this.profiles();
1026
1107
  const selectedIndex = this.filterSelect.selectedIndex();
1027
- const originalSize = this.filterSelect.size();
1108
+ const selectedOption = this.#filterOptions[selectedIndex];
1109
+ const filterOptions: FilterOption[] = [];
1110
+ const createOption = (filterOption: FilterOption): HTMLOptionElement => {
1111
+ filterOptions.push(filterOption);
1112
+ const option = this.filterSelect.createOption(filterOption.uiName);
1113
+ option.disabled = Boolean(filterOption.disabled);
1114
+ return option;
1115
+ };
1116
+ const createSeparator = (): HTMLOptionElement => {
1117
+ return createOption({uiName: '\u2014'.repeat(18), profileIndex: -1, disabled: true});
1118
+ };
1028
1119
 
1029
1120
  this.filterSelect.removeOptions();
1030
- this.filterSelect.createOption(i18nString(UIStrings.allObjects));
1121
+ createOption({uiName: i18nString(UIStrings.allObjects), profileIndex: -1});
1031
1122
  for (let i = 0; i < list.length; ++i) {
1032
1123
  let title;
1033
1124
  if (!i) {
@@ -1035,34 +1126,31 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
1035
1126
  } else {
1036
1127
  title = i18nString(UIStrings.objectsAllocatedBetweenSAndS, {PH1: list[i - 1].title, PH2: list[i].title});
1037
1128
  }
1038
- this.filterSelect.createOption(title);
1129
+ createOption({uiName: title, profileIndex: i});
1039
1130
  }
1040
1131
 
1041
- // Create a dividing line using em dashes.
1042
- const dividerIndex = this.filterSelect.size();
1043
- const divider = this.filterSelect.createOption('\u2014'.repeat(18));
1044
- (divider).disabled = true;
1132
+ createSeparator();
1045
1133
 
1046
1134
  for (const filter of HeapSnapshotView.ALWAYS_AVAILABLE_FILTERS) {
1047
- this.filterSelect.createOption(filter.uiName);
1135
+ createOption({uiName: filter.uiName, profileIndex: -1, filterName: filter.filterName});
1048
1136
  }
1049
1137
 
1050
- const newSize = this.filterSelect.size();
1051
-
1052
- if (selectedIndex > -1) {
1053
- const distanceFromEnd = originalSize - selectedIndex;
1054
- if (distanceFromEnd <= HeapSnapshotView.ALWAYS_AVAILABLE_FILTERS.length) {
1055
- // If one of the always-available filters was selected, then select the
1056
- // same filter again even though its index may have changed.
1057
- this.filterSelect.setSelectedIndex(newSize - distanceFromEnd);
1058
- } else if (selectedIndex >= dividerIndex) {
1059
- // If the select list is now shorter than it was, such that we can't
1060
- // keep the index unchanged, set it to -1, which causes it to be blank.
1061
- this.filterSelect.setSelectedIndex(-1);
1062
- } else {
1063
- this.filterSelect.setSelectedIndex(selectedIndex);
1138
+ if (this.#nativeContextFilters.length > 0) {
1139
+ createSeparator();
1140
+ for (const filter of this.#nativeContextFilters) {
1141
+ createOption({uiName: filter.uiName, profileIndex: -1, filterName: filter.filterName});
1064
1142
  }
1065
1143
  }
1144
+
1145
+ this.#filterOptions = filterOptions;
1146
+
1147
+ if (selectedOption) {
1148
+ const newSelectedIndex = this.#filterOptions.findIndex(option => {
1149
+ return !option.disabled && option.profileIndex === selectedOption.profileIndex &&
1150
+ option.filterName === selectedOption.filterName;
1151
+ });
1152
+ this.filterSelect.setSelectedIndex(newSelectedIndex);
1153
+ }
1066
1154
  }
1067
1155
 
1068
1156
  updateControls(): void {
@@ -53,6 +53,10 @@ export const UIStrings = {
53
53
  * @description Text to indicate the status of a heap snapshot in the Performance Pane
54
54
  */
55
55
  buildingLocations: 'Building locations…',
56
+ /**
57
+ * @description Text to indicate the status of a heap snapshot in the Performance Pane
58
+ */
59
+ calculatingNativeContextAttribution: 'Calculating native context attribution…',
56
60
  /**
57
61
  * @description Text to indicate the status of a heap snapshot in the Performance Pane
58
62
  */
@@ -7,8 +7,8 @@ import * as Common from '../../core/common/common.js';
7
7
  import * as i18n from '../../core/i18n/i18n.js';
8
8
  import * as Platform from '../../core/platform/platform.js';
9
9
  import * as Trace from '../../models/trace/trace.js';
10
- import {createIcon} from '../../ui/kit/kit.js';
11
10
  import * as UI from '../../ui/legacy/legacy.js';
11
+ import {html, type LitTemplate, render} from '../../ui/lit/lit.js';
12
12
  import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
13
13
 
14
14
  import {
@@ -361,39 +361,43 @@ export class TimelineHistoryManager {
361
361
  };
362
362
  parsedTraceIndexToPerformancePreviewData.set(parsedTraceIndex, data);
363
363
 
364
- preview.appendChild(this.#buildTextDetails(parsedTrace.metadata, domain));
365
- const screenshotAndOverview = preview.createChild('div', 'hbox');
366
- screenshotAndOverview.appendChild(this.#buildScreenshotThumbnail(filmStrip));
367
- screenshotAndOverview.appendChild(this.#buildOverview(parsedTrace));
364
+ // eslint-disable-next-line @devtools/no-lit-render-outside-of-view
365
+ render(html`
366
+ ${this.#renderTextDetails(parsedTrace.metadata, domain)}
367
+ <div class="hbox">
368
+ ${this.#renderScreenshotThumbnail(filmStrip)}
369
+ ${this.#buildOverview(parsedTrace)}
370
+ </div>
371
+ `,
372
+ preview, {host: this});
373
+
368
374
  return data.preview;
369
375
  }
370
376
 
371
- #buildTextDetails(metadata: Trace.Types.File.MetaData|null, title: string): Element {
372
- const container = document.createElement('div');
373
- container.classList.add('text-details');
374
- container.classList.add('hbox');
375
- const nameSpan = container.createChild('span', 'name');
376
- nameSpan.textContent = title;
377
- UI.ARIAUtils.setLabel(nameSpan, title);
378
-
377
+ #renderTextDetails(metadata: Trace.Types.File.MetaData|null, title: string): LitTemplate {
378
+ let metadataText = '';
379
379
  if (metadata) {
380
380
  const parts = [
381
381
  metadata.emulatedDeviceTitle,
382
382
  metadata.cpuThrottling ? i18nString(UIStrings.dSlowdown, {PH1: metadata.cpuThrottling}) : undefined,
383
383
  metadata.networkThrottling,
384
384
  ].filter(Boolean);
385
- container.createChild('span', 'metadata').textContent = listFormatter.format(parts as string[]);
385
+ metadataText = listFormatter.format(parts as string[]);
386
386
  }
387
387
 
388
- return container;
388
+ return html`
389
+ <div class="text-details hbox">
390
+ <span class="name">${title}</span>
391
+ ${metadataText ? html`<span class="metadata">${metadataText}</span>` : ''}
392
+ </div>
393
+ `;
389
394
  }
390
395
 
391
- #buildScreenshotThumbnail(filmStrip: Trace.Extras.FilmStrip.Data|null): Element {
392
- const container = document.createElement('div');
393
- container.classList.add('screenshot-thumb');
396
+ #renderScreenshotThumbnail(filmStrip: Trace.Extras.FilmStrip.Data|null): LitTemplate {
394
397
  const thumbnailAspectRatio = 3 / 2;
395
- container.style.width = this.totalHeight * thumbnailAspectRatio + 'px';
396
- container.style.height = this.totalHeight + 'px';
398
+ const width = this.totalHeight * thumbnailAspectRatio + 'px';
399
+ const height = this.totalHeight + 'px';
400
+ const container = html`<div class="screenshot-thumb" style="width: ${width}; height: ${height}"></div>`;
397
401
  if (!filmStrip) {
398
402
  return container;
399
403
  }
@@ -403,12 +407,11 @@ export class TimelineHistoryManager {
403
407
  }
404
408
  // TODO(paulirish): Adopt Util.ImageCache
405
409
  const uri = Trace.Handlers.ModelHandlers.Screenshots.screenshotImageDataUri(lastFrame.screenshotEvent);
406
- void UI.UIUtils.loadImage(uri).then(img => {
407
- if (img) {
408
- container.appendChild(img);
409
- }
410
- });
411
- return container;
410
+ return html`
411
+ <div class="screenshot-thumb" style="width: ${width}; height: ${height}">
412
+ <img src=${uri}>
413
+ </div>
414
+ `;
412
415
  }
413
416
 
414
417
  #buildOverview(parsedTrace: Trace.TraceModel.ParsedTrace): Element {
@@ -582,14 +585,14 @@ export class DropDown implements UI.ListControl.ListDelegate<number> {
582
585
 
583
586
  div.style.width = `${previewWidth}px`;
584
587
 
585
- const icon = createIcon('arrow-back');
586
- icon.title = i18nString(UIStrings.backButtonTooltip);
587
- icon.classList.add('back-arrow');
588
- div.appendChild(icon);
588
+ // eslint-disable-next-line @devtools/no-lit-render-outside-of-view
589
+ render(html`
590
+ <devtools-icon class="back-arrow" title=${
591
+ i18nString(UIStrings.backButtonTooltip)} name="arrow-back"></devtools-icon>
592
+ <span>${this.#landingPageTitle}</span>
593
+ `,
594
+ div, {host: this});
589
595
 
590
- const text = document.createElement('span');
591
- text.innerText = this.#landingPageTitle;
592
- div.appendChild(text);
593
596
  return div;
594
597
  }
595
598
 
@@ -619,14 +622,11 @@ export class DropDown implements UI.ListControl.ListDelegate<number> {
619
622
  }
620
623
 
621
624
  export class ToolbarButton extends UI.Toolbar.ToolbarItem {
622
- private contentElement: HTMLElement;
623
-
624
625
  constructor(action: UI.ActionRegistration.Action) {
625
626
  const element = document.createElement('button');
626
627
  element.classList.add('history-dropdown-button');
627
628
  element.setAttribute('jslog', `${VisualLogging.dropDown('history')}`);
628
629
  super(element);
629
- this.contentElement = this.element.createChild('span', 'content');
630
630
  this.element.addEventListener('click', () => void action.execute(), false);
631
631
  this.setEnabled(action.enabled());
632
632
  action.addEventListener(UI.ActionRegistration.Events.ENABLED, event => this.setEnabled(event.data));
@@ -634,6 +634,7 @@ export class ToolbarButton extends UI.Toolbar.ToolbarItem {
634
634
  }
635
635
 
636
636
  setText(text: string): void {
637
- this.contentElement.textContent = text;
637
+ // eslint-disable-next-line @devtools/no-lit-render-outside-of-view
638
+ render(html`<span class="content">${text}</span>`, this.element, {host: this});
638
639
  }
639
640
  }
@@ -2149,14 +2149,21 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
2149
2149
  // Annotation Overlays.
2150
2150
  currentManager?.addEventListener(AnnotationModifiedEvent.eventName, this.#onAnnotationModifiedEventBound);
2151
2151
 
2152
- // To calculate the activity we might want to zoom in, we use the top-most main-thread track
2153
- const topMostMainThreadAppender =
2154
- this.flameChart.getMainDataProvider().compatibilityTracksAppenderInstance().threadAppenders().at(0);
2155
- if (topMostMainThreadAppender) {
2156
- const zoomedInBounds = Trace.Extras.MainThreadActivity.calculateWindow(
2157
- parsedTrace.data.Meta.traceBounds, topMostMainThreadAppender.getEntries());
2158
-
2159
- TraceBounds.TraceBounds.BoundsManager.instance().setTimelineVisibleWindow(zoomedInBounds);
2152
+ // To calculate the activity we might want to zoom in, we use the top-most main-thread track.
2153
+ // If the trace has an active breadcrumb that is not the initial full trace breadcrumb, we do
2154
+ // not zoom in, but keep the window at the active breadcrumb bounds.
2155
+ const breadcrumbs = currentManager?.getTimelineBreadcrumbs();
2156
+ const hasActiveBreadcrumb = breadcrumbs ? breadcrumbs.activeBreadcrumb !== breadcrumbs.initialBreadcrumb : false;
2157
+ if (!hasActiveBreadcrumb) {
2158
+ const topMostMainThreadAppender =
2159
+ this.flameChart.getMainDataProvider().compatibilityTracksAppenderInstance().threadAppenders().at(0);
2160
+ const zoomWindow = calculateAutoZoomWindow(
2161
+ parsedTrace.data.Meta.traceBounds,
2162
+ topMostMainThreadAppender?.getEntries(),
2163
+ );
2164
+ if (zoomWindow) {
2165
+ TraceBounds.TraceBounds.BoundsManager.instance().setTimelineVisibleWindow(zoomWindow);
2166
+ }
2160
2167
  }
2161
2168
 
2162
2169
  // Add overlays for annotations loaded from the trace file
@@ -3206,3 +3213,20 @@ export interface EventTypes {
3206
3213
  [Events.IS_VIEWING_TRACE]: boolean;
3207
3214
  [Events.RECORDING_COMPLETED]: {traceIndex: number}|{errorText: string};
3208
3215
  }
3216
+
3217
+ /**
3218
+ * Calculates the window to auto-zoom into when a trace is loaded.
3219
+ * We only auto-zoom to the main thread activity if there is no active breadcrumb,
3220
+ * as we want to preserve the active breadcrumb's window if one exists.
3221
+ *
3222
+ * @returns the window to zoom into, or null if no auto-zoom should be applied.
3223
+ */
3224
+ export function calculateAutoZoomWindow(
3225
+ traceBounds: Trace.Types.Timing.TraceWindowMicro,
3226
+ topMostMainThreadAppenderEntries: readonly Trace.Types.Events.Event[]|undefined,
3227
+ ): Trace.Types.Timing.TraceWindowMicro|null {
3228
+ if (!topMostMainThreadAppenderEntries || topMostMainThreadAppenderEntries.length === 0) {
3229
+ return null;
3230
+ }
3231
+ return Trace.Extras.MainThreadActivity.calculateWindow(traceBounds, topMostMainThreadAppenderEntries);
3232
+ }
@@ -139,10 +139,11 @@ export class CPUThrottlingSelector extends UI.Widget.Widget {
139
139
  #groups: CPUThrottlingGroup[] = [];
140
140
  #calibratedThrottlingSetting: Common.Settings.Setting<SDK.CPUThrottlingManager.CalibratedCPUThrottling>;
141
141
  readonly #view: View;
142
+ readonly #cpuThrottlingManager = SDK.CPUThrottlingManager.CPUThrottlingManager.instance();
142
143
 
143
144
  constructor(element?: HTMLElement, view: View = DEFAULT_VIEW) {
144
145
  super(element);
145
- this.#currentOption = SDK.CPUThrottlingManager.CPUThrottlingManager.instance().cpuThrottlingOption();
146
+ this.#currentOption = this.#cpuThrottlingManager.cpuThrottlingOption();
146
147
  this.#calibratedThrottlingSetting =
147
148
  Common.Settings.Settings.instance().createSetting<SDK.CPUThrottlingManager.CalibratedCPUThrottling>(
148
149
  'calibrated-cpu-throttling', {}, Common.Settings.SettingStorageType.GLOBAL);
@@ -157,8 +158,8 @@ export class CPUThrottlingSelector extends UI.Widget.Widget {
157
158
 
158
159
  override wasShown(): void {
159
160
  super.wasShown();
160
- SDK.CPUThrottlingManager.CPUThrottlingManager.instance().addEventListener(
161
- SDK.CPUThrottlingManager.Events.RATE_CHANGED, this.#onOptionChange, this);
161
+ this.#cpuThrottlingManager.addEventListener(SDK.CPUThrottlingManager.Events.RATE_CHANGED, this.#onOptionChange,
162
+ this);
162
163
  this.#calibratedThrottlingSetting.addChangeListener(this.#onCalibratedSettingChanged, this);
163
164
  this.#onOptionChange();
164
165
  }
@@ -166,12 +167,12 @@ export class CPUThrottlingSelector extends UI.Widget.Widget {
166
167
  override willHide(): void {
167
168
  super.willHide();
168
169
  this.#calibratedThrottlingSetting.removeChangeListener(this.#onCalibratedSettingChanged, this);
169
- SDK.CPUThrottlingManager.CPUThrottlingManager.instance().removeEventListener(
170
- SDK.CPUThrottlingManager.Events.RATE_CHANGED, this.#onOptionChange, this);
170
+ this.#cpuThrottlingManager.removeEventListener(SDK.CPUThrottlingManager.Events.RATE_CHANGED, this.#onOptionChange,
171
+ this);
171
172
  }
172
173
 
173
174
  #onOptionChange(): void {
174
- this.#currentOption = SDK.CPUThrottlingManager.CPUThrottlingManager.instance().cpuThrottlingOption();
175
+ this.#currentOption = this.#cpuThrottlingManager.cpuThrottlingOption();
175
176
 
176
177
  this.requestUpdate();
177
178
  }
@@ -236,31 +236,31 @@ const UIStrings = {
236
236
  */
237
237
  clearCurrentLog: 'Clear the current log',
238
238
  /**
239
- * @description Title for a page load phase that measures the time between when the page load starts and the time when the first byte of the initial document is downloaded.
239
+ * @description Title for a page load subpart that measures the time between when the page load starts and the time when the first byte of the initial document is downloaded.
240
240
  */
241
241
  timeToFirstByte: 'Time to first byte',
242
242
  /**
243
- * @description Title for a page load phase that measures the time between when the first byte of the initial document is downloaded and when the request for the largest image content starts.
243
+ * @description Title for a page load subpart that measures the time between when the first byte of the initial document is downloaded and when the request for the largest image content starts.
244
244
  */
245
245
  resourceLoadDelay: 'Resource load delay',
246
246
  /**
247
- * @description Title for a page load phase that measures the time between when the request for the largest image content starts and when it finishes.
247
+ * @description Title for a page load subpart that measures the time between when the request for the largest image content starts and when it finishes.
248
248
  */
249
249
  resourceLoadDuration: 'Resource load duration',
250
250
  /**
251
- * @description Title for a page load phase that measures the time between when the request for the largest image content finishes and when the largest image element is rendered on the page.
251
+ * @description Title for a page load subpart that measures the time between when the request for the largest image content finishes and when the largest image element is rendered on the page.
252
252
  */
253
253
  elementRenderDelay: 'Element render delay',
254
254
  /**
255
- * @description Title for a phase during a user interaction that measures the time between when the interaction starts and when the browser starts running interaction handlers.
255
+ * @description Title for a subpart during a user interaction that measures the time between when the interaction starts and when the browser starts running interaction handlers.
256
256
  */
257
257
  inputDelay: 'Input delay',
258
258
  /**
259
- * @description Title for a phase during a user interaction that measures the time between when the browser starts running interaction handlers and when the browser finishes running interaction handlers.
259
+ * @description Title for a subpart during a user interaction that measures the time between when the browser starts running interaction handlers and when the browser finishes running interaction handlers.
260
260
  */
261
261
  processingDuration: 'Processing duration',
262
262
  /**
263
- * @description Title for a phase during a user interaction that measures the time between when the browser finishes running interaction handlers and when the browser renders the next visual frame that shows the result of the interaction.
263
+ * @description Title for a subpart during a user interaction that measures the time between when the browser finishes running interaction handlers and when the browser renders the next visual frame that shows the result of the interaction.
264
264
  */
265
265
  presentationDelay: 'Presentation delay',
266
266
  /**
@@ -276,11 +276,11 @@ const UIStrings = {
276
276
  */
277
277
  showClsCluster: 'Go to worst layout shift cluster.',
278
278
  /**
279
- * @description Column header for table cell values representing the phase/component/stage/section of a larger duration.
279
+ * @description Column header for table cell values representing the subpart/component/stage/section of a larger duration.
280
280
  */
281
- phase: 'Phase',
281
+ subpart: 'Subpart',
282
282
  /**
283
- * @description Column header for table cell values representing a phase duration (in milliseconds) that was measured in the developers local environment.
283
+ * @description Column header for table cell values representing a subpart duration (in milliseconds) that was measured in the developers local environment.
284
284
  */
285
285
  duration: 'Local duration (ms)',
286
286
  /**
@@ -328,7 +328,7 @@ export interface ViewOutput {
328
328
 
329
329
  export type View = (input: ViewInput, output: ViewOutput, target: HTMLElement|DocumentFragment) => void;
330
330
 
331
- function getLcpFieldPhases(cruxManager: CrUXManager.CrUXManager): LiveMetrics.LcpValue['phases']|null {
331
+ function getLcpFieldSubparts(cruxManager: CrUXManager.CrUXManager): LiveMetrics.LcpValue['subparts']|null {
332
332
  const ttfb =
333
333
  cruxManager.getSelectedFieldMetricData('largest_contentful_paint_image_time_to_first_byte')?.percentiles?.p75;
334
334
  const loadDelay =
@@ -484,9 +484,9 @@ function renderLcpCard(input: ViewInput): Lit.LitTemplate {
484
484
  const fieldData = input.cruxManager.getSelectedFieldMetricData('largest_contentful_paint');
485
485
  const nodeLink =
486
486
  input.lcpValue?.nodeRef && PanelsCommon.DOMLinkifier.Linkifier.instance().linkify(input.lcpValue?.nodeRef);
487
- const phases = input.lcpValue?.phases;
487
+ const subparts = input.lcpValue?.subparts;
488
488
 
489
- const fieldPhases = getLcpFieldPhases(input.cruxManager);
489
+ const fieldSubparts = getLcpFieldSubparts(input.cruxManager);
490
490
 
491
491
  // clang-format off
492
492
  return html`
@@ -496,11 +496,11 @@ function renderLcpCard(input: ViewInput): Lit.LitTemplate {
496
496
  fieldValue: fieldData?.percentiles?.p75,
497
497
  histogram: fieldData?.histogram,
498
498
  warnings: input.lcpValue?.warnings,
499
- phases: phases && [
500
- [i18nString(UIStrings.timeToFirstByte), phases.timeToFirstByte, fieldPhases?.timeToFirstByte],
501
- [i18nString(UIStrings.resourceLoadDelay), phases.resourceLoadDelay, fieldPhases?.resourceLoadDelay],
502
- [i18nString(UIStrings.resourceLoadDuration), phases.resourceLoadTime, fieldPhases?.resourceLoadTime],
503
- [i18nString(UIStrings.elementRenderDelay), phases.elementRenderDelay, fieldPhases?.elementRenderDelay],
499
+ subparts: subparts && [
500
+ [i18nString(UIStrings.timeToFirstByte), subparts.timeToFirstByte, fieldSubparts?.timeToFirstByte],
501
+ [i18nString(UIStrings.resourceLoadDelay), subparts.resourceLoadDelay, fieldSubparts?.resourceLoadDelay],
502
+ [i18nString(UIStrings.resourceLoadDuration), subparts.resourceLoadTime, fieldSubparts?.resourceLoadTime],
503
+ [i18nString(UIStrings.elementRenderDelay), subparts.elementRenderDelay, fieldSubparts?.elementRenderDelay],
504
504
  ],
505
505
  })}>
506
506
  ${nodeLink ? html`
@@ -551,7 +551,7 @@ function renderClsCard(input: ViewInput): Lit.LitTemplate {
551
551
 
552
552
  function renderInpCard(input: ViewInput): Lit.LitTemplate {
553
553
  const fieldData = input.cruxManager.getSelectedFieldMetricData('interaction_to_next_paint');
554
- const phases = input.inpValue?.phases;
554
+ const subparts = input.inpValue?.subparts;
555
555
  const interaction = input.inpValue && input.interactions.get(input.inpValue.interactionId);
556
556
 
557
557
  // clang-format off
@@ -562,10 +562,10 @@ function renderInpCard(input: ViewInput): Lit.LitTemplate {
562
562
  fieldValue: fieldData?.percentiles?.p75,
563
563
  histogram: fieldData?.histogram,
564
564
  warnings: input.inpValue?.warnings,
565
- phases: phases && [
566
- [i18nString(UIStrings.inputDelay), phases.inputDelay],
567
- [i18nString(UIStrings.processingDuration), phases.processingDuration],
568
- [i18nString(UIStrings.presentationDelay), phases.presentationDelay],
565
+ subparts: subparts && [
566
+ [i18nString(UIStrings.inputDelay), subparts.inputDelay],
567
+ [i18nString(UIStrings.processingDuration), subparts.processingDuration],
568
+ [i18nString(UIStrings.presentationDelay), subparts.presentationDelay],
569
569
  ],
570
570
  })}>
571
571
  ${interaction ? html`
@@ -873,30 +873,30 @@ function renderInteractionsLog(input: ViewInput, output: ViewOutput): Lit.LitTem
873
873
  ></devtools-icon>` : nothing}
874
874
  <span class="interaction-duration">${metricValue}</span>
875
875
  </summary>
876
- <div class="phase-table" role="table">
877
- <div class="phase-table-row phase-table-header-row" role="row">
878
- <div role="columnheader">${i18nString(UIStrings.phase)}</div>
876
+ <div class="subpart-table" role="table">
877
+ <div class="subpart-table-row subpart-table-header-row" role="row">
878
+ <div role="columnheader">${i18nString(UIStrings.subpart)}</div>
879
879
  <div role="columnheader">
880
880
  ${interaction.longAnimationFrameTimings.length ? html`
881
- <button
882
- class="log-extra-details-button"
883
- title=${i18nString(UIStrings.logToConsole)}
884
- @click=${() => input.logExtraInteractionDetails(interaction)}
885
- >${i18nString(UIStrings.duration)}</button>
886
- ` : i18nString(UIStrings.duration)}
881
+ <button
882
+ class="log-extra-details-button"
883
+ title=${i18nString(UIStrings.logToConsole)}
884
+ @click=${() => input.logExtraInteractionDetails(interaction)}
885
+ >${i18nString(UIStrings.duration)}</button>
886
+ ` : i18nString(UIStrings.duration)}
887
887
  </div>
888
888
  </div>
889
- <div class="phase-table-row" role="row">
889
+ <div class="subpart-table-row" role="row">
890
890
  <div role="cell">${i18nString(UIStrings.inputDelay)}</div>
891
- <div role="cell">${Math.round(interaction.phases.inputDelay)}</div>
891
+ <div role="cell">${Math.round(interaction.subparts.inputDelay)}</div>
892
892
  </div>
893
- <div class="phase-table-row" role="row">
893
+ <div class="subpart-table-row" role="row">
894
894
  <div role="cell">${i18nString(UIStrings.processingDuration)}</div>
895
- <div role="cell">${Math.round(interaction.phases.processingDuration)}</div>
895
+ <div role="cell">${Math.round(interaction.subparts.processingDuration)}</div>
896
896
  </div>
897
- <div class="phase-table-row" role="row">
897
+ <div class="subpart-table-row" role="row">
898
898
  <div role="cell">${i18nString(UIStrings.presentationDelay)}</div>
899
- <div role="cell">${Math.round(interaction.phases.presentationDelay)}</div>
899
+ <div role="cell">${Math.round(interaction.subparts.presentationDelay)}</div>
900
900
  </div>
901
901
  </div>
902
902
  </details>