chrome-devtools-frontend 1.0.1516909 → 1.0.1519267

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 (162) hide show
  1. package/config/owner/COMMON_OWNERS +2 -2
  2. package/docs/checklist/README.md +2 -2
  3. package/docs/checklist/javascript.md +1 -1
  4. package/docs/contributing/README.md +1 -1
  5. package/docs/contributing/settings-experiments-features.md +9 -8
  6. package/docs/cookbook/devtools_on_devtools.md +2 -2
  7. package/docs/cookbook/localization.md +10 -10
  8. package/docs/devtools-protocol.md +9 -8
  9. package/docs/ecosystem/automatic_workspace_folders.md +3 -3
  10. package/docs/get_the_code.md +0 -2
  11. package/docs/styleguide/ux/components.md +166 -85
  12. package/docs/styleguide/ux/numbers.md +3 -4
  13. package/eslint.config.mjs +1 -0
  14. package/front_end/core/common/README.md +13 -12
  15. package/front_end/core/host/GdpClient.ts +16 -1
  16. package/front_end/core/host/UserMetrics.ts +4 -2
  17. package/front_end/core/root/Runtime.ts +13 -0
  18. package/front_end/core/sdk/CSSMatchedStyles.ts +5 -1
  19. package/front_end/core/sdk/EnhancedTracesParser.ts +5 -5
  20. package/front_end/core/sdk/RehydratingConnection.snapshot.txt +211 -0
  21. package/front_end/core/sdk/TargetManager.ts +4 -0
  22. package/front_end/entrypoints/main/MainImpl.ts +6 -3
  23. package/front_end/generated/InspectorBackendCommands.js +10 -7
  24. package/front_end/generated/SupportedCSSProperties.js +40 -11
  25. package/front_end/generated/protocol-mapping.d.ts +16 -1
  26. package/front_end/generated/protocol-proxy-api.d.ts +13 -1
  27. package/front_end/generated/protocol.ts +95 -0
  28. package/front_end/models/ai_assistance/agents/AiAgent.ts +57 -10
  29. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +119 -51
  30. package/front_end/models/ai_assistance/agents/StylingAgent.ts +0 -31
  31. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +14 -181
  32. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +19 -315
  33. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +224 -50
  34. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +310 -11
  35. package/front_end/models/ai_assistance/performance/AIContext.ts +15 -2
  36. package/front_end/models/ai_code_completion/AiCodeCompletion.ts +22 -11
  37. package/front_end/models/badges/AiExplorerBadge.ts +19 -3
  38. package/front_end/models/badges/Badge.ts +10 -3
  39. package/front_end/models/badges/CodeWhispererBadge.ts +3 -4
  40. package/front_end/models/badges/DOMDetectiveBadge.ts +1 -0
  41. package/front_end/models/badges/SpeedsterBadge.ts +1 -0
  42. package/front_end/models/badges/StarterBadge.ts +3 -2
  43. package/front_end/models/badges/UserBadges.ts +21 -3
  44. package/front_end/models/badges/badges.ts +1 -0
  45. package/front_end/models/javascript_metadata/NativeFunctions.js +2 -2
  46. package/front_end/models/trace/EventsSerializer.ts +4 -3
  47. package/front_end/models/trace/README.md +28 -1
  48. package/front_end/models/trace/handlers/UserInteractionsHandler.ts +101 -73
  49. package/front_end/models/trace/handlers/UserTimingsHandler.ts +1 -1
  50. package/front_end/models/trace/helpers/Timing.ts +1 -1
  51. package/front_end/models/trace/helpers/Trace.ts +99 -43
  52. package/front_end/models/trace/types/TraceEvents.ts +9 -0
  53. package/front_end/panels/accessibility/ARIAAttributesView.ts +113 -191
  54. package/front_end/panels/accessibility/AccessibilityNodeView.ts +9 -9
  55. package/front_end/panels/accessibility/AccessibilitySubPane.ts +6 -4
  56. package/front_end/panels/accessibility/accessibilityProperties.css +2 -0
  57. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +20 -3
  58. package/front_end/panels/ai_assistance/components/ChatView.ts +9 -10
  59. package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +44 -0
  60. package/front_end/panels/application/components/BounceTrackingMitigationsView.ts +2 -2
  61. package/front_end/panels/common/AiCodeCompletionDisclaimer.ts +32 -9
  62. package/front_end/panels/common/AiCodeCompletionSummaryToolbar.ts +7 -1
  63. package/front_end/panels/common/BadgeNotification.ts +21 -5
  64. package/front_end/panels/common/GdpSignUpDialog.ts +20 -12
  65. package/front_end/panels/console/ConsolePrompt.ts +1 -1
  66. package/front_end/panels/console/ConsoleView.ts +6 -2
  67. package/front_end/panels/css_overview/CSSOverviewCompletedView.ts +5 -5
  68. package/front_end/panels/elements/ElementsPanel.ts +4 -0
  69. package/front_end/panels/elements/ElementsTreeElement.ts +18 -0
  70. package/front_end/panels/elements/ElementsTreeOutline.ts +13 -0
  71. package/front_end/panels/elements/StylePropertyTreeElement.ts +21 -6
  72. package/front_end/panels/media/TickingFlameChart.ts +1 -1
  73. package/front_end/panels/profiler/HeapSnapshotView.ts +34 -19
  74. package/front_end/panels/recorder/components/RecordingView.ts +2 -2
  75. package/front_end/panels/search/SearchResultsPane.ts +167 -152
  76. package/front_end/panels/search/SearchView.ts +36 -26
  77. package/front_end/panels/search/searchResultsPane.css +9 -0
  78. package/front_end/panels/security/CookieControlsView.ts +2 -1
  79. package/front_end/panels/settings/AISettingsTab.ts +6 -3
  80. package/front_end/panels/settings/components/SyncSection.ts +39 -17
  81. package/front_end/panels/settings/emulation/components/UserAgentClientHintsForm.ts +1 -1
  82. package/front_end/panels/sources/AiCodeCompletionPlugin.ts +9 -1
  83. package/front_end/panels/sources/SourcesPanel.ts +4 -1
  84. package/front_end/panels/sources/sourcesView.css +6 -1
  85. package/front_end/panels/timeline/AppenderUtils.ts +2 -2
  86. package/front_end/panels/timeline/ExtensionTrackAppender.ts +13 -4
  87. package/front_end/panels/timeline/GPUTrackAppender.ts +2 -1
  88. package/front_end/panels/timeline/InteractionsTrackAppender.ts +5 -1
  89. package/front_end/panels/timeline/LayoutShiftsTrackAppender.ts +2 -1
  90. package/front_end/panels/timeline/ThreadAppender.ts +12 -3
  91. package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +9 -4
  92. package/front_end/panels/timeline/TimelinePanel.ts +3 -2
  93. package/front_end/panels/timeline/TimelineUIUtils.ts +5 -4
  94. package/front_end/panels/timeline/TimingsTrackAppender.ts +6 -1
  95. package/front_end/panels/timeline/components/CPUThrottlingSelector.ts +95 -82
  96. package/front_end/panels/timeline/components/LayoutShiftDetails.ts +1 -1
  97. package/front_end/panels/timeline/components/LiveMetricsView.ts +2 -2
  98. package/front_end/panels/timeline/components/NetworkRequestDetails.ts +1 -1
  99. package/front_end/panels/timeline/components/RelatedInsightChips.ts +1 -1
  100. package/front_end/panels/timeline/components/SidebarSingleInsightSet.ts +1 -1
  101. package/front_end/panels/timeline/components/cpuThrottlingSelector.css +17 -15
  102. package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +3 -0
  103. package/front_end/third_party/chromium/README.chromium +1 -1
  104. package/front_end/third_party/codemirror.next/chunk/codemirror.js +1 -1
  105. package/front_end/third_party/codemirror.next/chunk/codemirror.js.map +1 -1
  106. package/front_end/third_party/codemirror.next/codemirror.next.d.ts +6 -9
  107. package/front_end/third_party/codemirror.next/package.json +2 -1
  108. package/front_end/third_party/diff/README.chromium +1 -0
  109. package/front_end/third_party/puppeteer/README.chromium +2 -2
  110. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Realm.d.ts +2 -2
  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.js +1 -1
  113. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.d.ts +1 -1
  114. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.js +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/node/ChromeLauncher.d.ts.map +1 -1
  117. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js +1 -0
  118. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js.map +1 -1
  119. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +3 -3
  120. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +3 -3
  121. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js.map +1 -1
  122. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Function.d.ts.map +1 -1
  123. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Function.js +16 -25
  124. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Function.js.map +1 -1
  125. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  126. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +19 -28
  127. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.d.ts +1 -1
  128. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.js +1 -1
  129. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.d.ts +1 -1
  130. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.js +1 -1
  131. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
  132. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js +1 -0
  133. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js.map +1 -1
  134. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +3 -3
  135. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +3 -3
  136. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js.map +1 -1
  137. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/Function.d.ts.map +1 -1
  138. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/Function.js +16 -25
  139. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/Function.js.map +1 -1
  140. package/front_end/third_party/puppeteer/package/package.json +10 -3
  141. package/front_end/third_party/puppeteer/package/src/generated/injected.ts +1 -1
  142. package/front_end/third_party/puppeteer/package/src/generated/version.ts +1 -1
  143. package/front_end/third_party/puppeteer/package/src/node/ChromeLauncher.ts +1 -0
  144. package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
  145. package/front_end/third_party/puppeteer/package/src/util/Function.ts +22 -30
  146. package/front_end/ui/components/dialogs/Dialog.ts +1 -1
  147. package/front_end/ui/components/markdown_view/MarkdownImage.ts +4 -5
  148. package/front_end/ui/components/switch/SwitchImpl.ts +12 -1
  149. package/front_end/ui/components/text_editor/config.ts +22 -9
  150. package/front_end/ui/components/tooltips/Tooltip.ts +70 -31
  151. package/front_end/ui/legacy/README.md +33 -24
  152. package/front_end/ui/legacy/SearchableView.ts +19 -26
  153. package/front_end/ui/legacy/TextPrompt.ts +166 -1
  154. package/front_end/ui/legacy/Treeoutline.ts +19 -3
  155. package/front_end/ui/legacy/UIUtils.ts +15 -2
  156. package/front_end/ui/legacy/XElement.ts +0 -43
  157. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +20 -4
  158. package/front_end/ui/legacy/components/source_frame/XMLView.ts +12 -11
  159. package/front_end/ui/lit/i18n-template.ts +5 -2
  160. package/front_end/ui/visual_logging/KnownContextValues.ts +23 -6
  161. package/front_end/ui/visual_logging/README.md +43 -27
  162. package/package.json +1 -1
@@ -303,7 +303,17 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
303
303
  baseProfile!: HeapProfileHeader|null;
304
304
  trackingOverviewGrid?: HeapTimelineOverview;
305
305
  currentSearchResultIndex = -1;
306
- currentQuery?: HeapSnapshotModel.HeapSnapshotModel.SearchConfig;
306
+ currentSearch?: HeapSnapshotModel.HeapSnapshotModel.SearchConfig;
307
+
308
+ get currentQuery(): string|undefined {
309
+ return this.currentSearch?.query;
310
+ }
311
+ set currentQuery(value: string) {
312
+ if (this.currentSearch) {
313
+ this.currentSearch.query = value;
314
+ }
315
+ }
316
+
307
317
  constructor(dataDisplayDelegate: DataDisplayDelegate, profile: HeapProfileHeader) {
308
318
  super({
309
319
  title: i18nString(UIStrings.heapSnapshot),
@@ -643,8 +653,13 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
643
653
 
644
654
  performSearch(searchConfig: UI.SearchableView.SearchConfig, shouldJump: boolean, jumpBackwards?: boolean): void {
645
655
  const nextQuery = new HeapSnapshotModel.HeapSnapshotModel.SearchConfig(
646
- searchConfig.query.trim(), searchConfig.caseSensitive, searchConfig.wholeWord, searchConfig.isRegex, shouldJump,
647
- jumpBackwards || false);
656
+ searchConfig.query.trim(),
657
+ searchConfig.caseSensitive,
658
+ searchConfig.wholeWord,
659
+ searchConfig.isRegex,
660
+ shouldJump,
661
+ jumpBackwards || false,
662
+ );
648
663
 
649
664
  void this.searchThrottler.schedule(this.performSearchInternal.bind(this, nextQuery));
650
665
  }
@@ -657,7 +672,7 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
657
672
  return;
658
673
  }
659
674
 
660
- this.currentQuery = nextQuery;
675
+ this.currentSearch = nextQuery;
661
676
  const query = nextQuery.query.trim();
662
677
 
663
678
  if (!query) {
@@ -682,7 +697,7 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
682
697
  }
683
698
 
684
699
  const filter = this.dataGrid.nodeFilter();
685
- this.searchResults = filter ? await this.profile.snapshotProxy.search(this.currentQuery, filter) : [];
700
+ this.searchResults = filter ? await this.profile.snapshotProxy.search(this.currentSearch, filter) : [];
686
701
 
687
702
  this.searchableViewInternal.updateSearchMatchesCount(this.searchResults.length);
688
703
  if (this.searchResults.length) {
@@ -724,10 +739,10 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
724
739
  if (!this.dataGrid) {
725
740
  return;
726
741
  }
727
- let child: (HeapSnapshotGridNode|null) = (this.dataGrid.rootNode().children[0] as HeapSnapshotGridNode | null);
742
+ let child: DataGrid.DataGrid.DataGridNode<HeapSnapshotGridNode>|null = this.dataGrid.rootNode().children[0];
728
743
  while (child) {
729
744
  child.refresh();
730
- child = (child.traverseNextNode(false, null, true) as HeapSnapshotGridNode | null);
745
+ child = child.traverseNextNode(false, null, true);
731
746
  }
732
747
  }
733
748
 
@@ -735,28 +750,28 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
735
750
  if (this.baseProfile === this.profiles()[this.baseSelect.selectedIndex()]) {
736
751
  return;
737
752
  }
738
- this.baseProfile = (this.profiles()[this.baseSelect.selectedIndex()] as HeapProfileHeader);
753
+ this.baseProfile = this.profiles()[this.baseSelect.selectedIndex()];
739
754
  const dataGrid = (this.dataGrid as HeapSnapshotDiffDataGrid);
740
755
  // Change set base data source only if main data source is already set.
741
756
  if (dataGrid.snapshot) {
742
757
  void this.baseProfile.loadPromise.then(dataGrid.setBaseDataSource.bind(dataGrid));
743
758
  }
744
759
 
745
- if (!this.currentQuery || !this.searchResults) {
760
+ if (!this.currentSearch || !this.searchResults) {
746
761
  return;
747
762
  }
748
763
 
749
764
  // The current search needs to be performed again. First negate out previous match
750
765
  // count by calling the search finished callback with a negative number of matches.
751
766
  // Then perform the search again with the same query and callback.
752
- this.performSearch(this.currentQuery, false);
767
+ this.performSearch(this.currentSearch, false);
753
768
  }
754
769
 
755
- static readonly ALWAYS_AVAILABLE_FILTERS = [
770
+ static readonly ALWAYS_AVAILABLE_FILTERS: ReadonlyArray<{uiName: string, filterName: string}> = [
756
771
  {uiName: i18nString(UIStrings.duplicatedStrings), filterName: 'duplicatedStrings'},
757
772
  {uiName: i18nString(UIStrings.objectsRetainedByDetachedDomNodes), filterName: 'objectsRetainedByDetachedDomNodes'},
758
773
  {uiName: i18nString(UIStrings.objectsRetainedByConsole), filterName: 'objectsRetainedByConsole'},
759
- ] as ReadonlyArray<{uiName: string, filterName: string}>;
774
+ ];
760
775
 
761
776
  changeFilter(): void {
762
777
  let selectedIndex = this.filterSelect.selectedIndex();
@@ -773,19 +788,19 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
773
788
  return;
774
789
  }
775
790
  (this.dataGrid as HeapSnapshotConstructorsDataGrid)
776
- .filterSelectIndexChanged((this.profiles() as HeapProfileHeader[]), profileIndex, filterName);
791
+ .filterSelectIndexChanged(this.profiles(), profileIndex, filterName);
777
792
 
778
- if (!this.currentQuery || !this.searchResults) {
793
+ if (!this.currentSearch || !this.searchResults) {
779
794
  return;
780
795
  }
781
796
 
782
797
  // The current search needs to be performed again. First negate out previous match
783
798
  // count by calling the search finished callback with a negative number of matches.
784
799
  // Then perform the search again with the same query and callback.
785
- this.performSearch(this.currentQuery, false);
800
+ this.performSearch(this.currentSearch, false);
786
801
  }
787
802
 
788
- profiles(): ProfileHeader[] {
803
+ profiles(): HeapProfileHeader[] {
789
804
  return this.profile.profileType().getProfiles();
790
805
  }
791
806
 
@@ -866,7 +881,7 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
866
881
  return;
867
882
  }
868
883
  if (!this.baseProfile) {
869
- this.baseProfile = (this.profiles()[this.baseSelect.selectedIndex()] as HeapProfileHeader);
884
+ this.baseProfile = this.profiles()[this.baseSelect.selectedIndex()];
870
885
  }
871
886
 
872
887
  const baseSnapshotProxy = await this.baseProfile.loadPromise;
@@ -900,14 +915,14 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
900
915
 
901
916
  void this.updateDataSourceAndView();
902
917
 
903
- if (!this.currentQuery || !this.searchResults) {
918
+ if (!this.currentSearch || !this.searchResults) {
904
919
  return;
905
920
  }
906
921
 
907
922
  // The current search needs to be performed again. First negate out previous match
908
923
  // count by calling the search finished callback with a negative number of matches.
909
924
  // Then perform the search again the with same query and callback.
910
- this.performSearch(this.currentQuery, false);
925
+ this.performSearch(this.currentSearch, false);
911
926
  }
912
927
 
913
928
  async selectLiveObject(perspectiveName: string, snapshotObjectId: string): Promise<void> {
@@ -220,9 +220,9 @@ function renderSettings({
220
220
  replayState,
221
221
  onReplaySettingsKeydown,
222
222
  onToggleReplaySettings
223
- }: ViewInput): Lit.TemplateResult {
223
+ }: ViewInput): Lit.LitTemplate {
224
224
  if (!settings) {
225
- return html``;
225
+ return Lit.nothing;
226
226
  }
227
227
  const environmentFragments = [];
228
228
  if (settings.viewportSettings) {
@@ -1,16 +1,13 @@
1
1
  // Copyright 2014 The Chromium Authors
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
- /* eslint-disable rulesdir/no-imperative-dom-api */
5
- /* eslint-disable rulesdir/no-lit-render-outside-of-view */
6
-
7
4
  import * as Common from '../../core/common/common.js';
8
5
  import * as i18n from '../../core/i18n/i18n.js';
9
6
  import * as Platform from '../../core/platform/platform.js';
10
7
  import * as TextUtils from '../../models/text_utils/text_utils.js';
11
8
  import type * as Workspace from '../../models/workspace/workspace.js';
12
9
  import * as UI from '../../ui/legacy/legacy.js';
13
- import {html, render} from '../../ui/lit/lit.js';
10
+ import {html, render, type TemplateResult} from '../../ui/lit/lit.js';
14
11
 
15
12
  import searchResultsPaneStyles from './searchResultsPane.css.js';
16
13
  import type {SearchResult} from './SearchScope.js';
@@ -35,136 +32,175 @@ const UIStrings = {
35
32
  const str_ = i18n.i18n.registerUIStrings('panels/search/SearchResultsPane.ts', UIStrings);
36
33
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
37
34
 
38
- export class SearchResultsPane extends UI.Widget.VBox {
39
- private readonly searchConfig: Workspace.SearchConfig.SearchConfig;
40
- private readonly searchResults: SearchResult[];
41
- private readonly treeElements: SearchResultsTreeElement[];
42
- private treeOutline: UI.TreeOutline.TreeOutlineInShadow;
43
- private matchesExpandedCount: number;
35
+ interface SearchMatch {
36
+ lineContent: string;
37
+ matchRanges: TextUtils.TextRange.SourceRange[];
38
+ resultLabel: string|number;
39
+ }
44
40
 
45
- constructor(searchConfig: Workspace.SearchConfig.SearchConfig) {
46
- super({useShadowDom: true});
47
- this.searchConfig = searchConfig;
41
+ interface ViewInput {
42
+ results: SearchResult[];
43
+ matches: WeakMap<SearchResult, SearchMatch[]>;
44
+ expandedResults: WeakSet<SearchResult>;
45
+ onSelectMatch: (searchResult: SearchResult, matchIndex: number) => void;
46
+ onExpandSearchResult: (searchResult: SearchResult) => void;
47
+ onShowMoreMatches: (searchResult: SearchResult) => void;
48
+ }
48
49
 
49
- this.searchResults = [];
50
- this.treeElements = [];
51
- this.treeOutline = new UI.TreeOutline.TreeOutlineInShadow();
52
- this.treeOutline.registerRequiredCSS(searchResultsPaneStyles);
53
- this.treeOutline.setHideOverflow(true);
50
+ export type View = (input: ViewInput, output: unknown, target: HTMLElement) => void;
54
51
 
55
- this.contentElement.appendChild(this.treeOutline.element);
52
+ export const DEFAULT_VIEW: View = (input, _output, target) => {
53
+ const {results, matches, expandedResults, onSelectMatch, onExpandSearchResult, onShowMoreMatches} = input;
56
54
 
57
- this.matchesExpandedCount = 0;
58
- }
55
+ const onExpand = ({detail: {expanded, target}}: UI.TreeOutline.TreeViewElement.ExpandEvent): void => {
56
+ const searchResultIndex = Number(target.dataset.searchResultIndex);
57
+ const searchResult = results[searchResultIndex];
58
+ if (expanded) {
59
+ expandedResults.add(searchResult);
60
+ onExpandSearchResult(searchResult);
61
+ } else {
62
+ expandedResults.delete(searchResult);
63
+ }
64
+ };
65
+
66
+ // clang-format off
67
+ render(html`
68
+ <devtools-tree hide-overflow @expand=${onExpand} .template=${html`
69
+ <ul role="tree">
70
+ ${results.map((searchResult, i) => html`
71
+ <li role="treeitem" data-search-result-index=${i} class="search-result">
72
+ <style>${searchResultsPaneStyles}</style>
73
+ ${renderSearchResult(searchResult)}
74
+ <ul role="group" ?hidden=${!expandedResults.has(searchResult)}>
75
+ ${renderSearchMatches(searchResult, matches, onSelectMatch, onShowMoreMatches)}
76
+ </ul>
77
+ </li>`)}
78
+ </ul>
79
+ `}></devtools-tree>`,
80
+ target,
81
+ );
82
+ // clang-format on
83
+ };
59
84
 
60
- addSearchResult(searchResult: SearchResult): void {
61
- this.searchResults.push(searchResult);
62
- this.addTreeElement(searchResult);
63
- }
85
+ const renderSearchResult = (searchResult: SearchResult): TemplateResult => {
86
+ // clang-format off
87
+ return html`
88
+ <span class="search-result-file-name">${searchResult.label()}
89
+ <span class="search-result-dash">${'\u2014'}</span>
90
+ <span class="search-result-qualifier">${searchResult.description()}</span>
91
+ </span>
92
+ <span class="search-result-matches-count"
93
+ aria-label=${i18nString(UIStrings.matchesCountS, {PH1: searchResult.matchesCount()})}>
94
+ ${searchResult.matchesCount()}
95
+ </span>`;
96
+ // clang-format on
97
+ };
64
98
 
65
- showAllMatches(): void {
66
- this.treeElements.forEach(treeElement => {
67
- treeElement.expand();
68
- treeElement.showAllMatches();
69
- });
70
- }
99
+ const renderSearchMatches =
100
+ (searchResult: SearchResult, matches: WeakMap<SearchResult, SearchMatch[]>,
101
+ onSelectMatch: (searchResult: SearchResult, matchIndex: number) => void,
102
+ onShowMoreMatches: (searchResult: SearchResult) => void): TemplateResult => {
103
+ const visibleMatches = matches.get(searchResult) ?? [];
104
+ const matchesLeftCount = searchResult.matchesCount() - visibleMatches.length;
105
+ // clang-format off
106
+ return html`
107
+ ${visibleMatches.map(({lineContent, matchRanges, resultLabel}, i) => html`
108
+ <li role="treeitem" class="search-match" @click=${() => onSelectMatch(searchResult, i)}
109
+ ${UI.TreeOutline.TreeSearch.highlight(matchRanges.map(range =>
110
+ ({offset: range.offset + `${resultLabel}`.length, length: range.length})), undefined)}
111
+ @keydown=${(event: KeyboardEvent) => {
112
+ if (event.key === 'Enter') {
113
+ onSelectMatch(searchResult, i);
114
+ }
115
+ }}
116
+ >
117
+ <button class="devtools-link text-button link-style search-match-link"
118
+ jslog="Link; context: search-match; track: click" role="link" tabindex="0"
119
+ @click=${() => void Common.Revealer.reveal(searchResult.matchRevealable(i))}>
120
+ <span class="search-match-line-number"
121
+ aria-label=${typeof resultLabel === 'number' && !isNaN(resultLabel)
122
+ ? i18nString(UIStrings.lineS, {PH1: resultLabel}) : resultLabel}>
123
+ ${resultLabel}
124
+ </span>
125
+ <span class="search-match-content" aria-label="${lineContent} line">
126
+ ${lineContent}
127
+ </span>
128
+ </button>
129
+ </li>`)}
130
+ ${
131
+ matchesLeftCount > 0 ? html`
132
+ <li role="treeitem" class="show-more-matches" @click=${() => onShowMoreMatches(searchResult)}>
133
+ ${i18nString(UIStrings.showDMore, { PH1: matchesLeftCount })}
134
+ </li>` : ''}`;
135
+ // clang-format on
136
+ };
71
137
 
72
- collapseAllResults(): void {
73
- this.treeElements.forEach(treeElement => {
74
- treeElement.collapse();
75
- });
76
- }
138
+ export class SearchResultsPane extends UI.Widget.VBox {
139
+ readonly #searchConfig: Workspace.SearchConfig.SearchConfig;
140
+ #searchResults: SearchResult[] = [];
141
+ #expandedResults = new WeakSet<SearchResult>();
142
+ readonly #searchMatches = new WeakMap<SearchResult, SearchMatch[]>();
143
+ #view: View;
77
144
 
78
- private addTreeElement(searchResult: SearchResult): void {
79
- const treeElement = new SearchResultsTreeElement(this.searchConfig, searchResult);
80
- this.treeOutline.appendChild(treeElement);
81
- if (!this.treeOutline.selectedTreeElement) {
82
- treeElement.select(/* omitFocus */ true, /* selectedByUser */ true);
83
- }
84
- // Expand until at least a certain number of matches is expanded.
85
- if (this.matchesExpandedCount < matchesExpandedByDefault) {
86
- treeElement.expand();
87
- }
88
- this.matchesExpandedCount += searchResult.matchesCount();
89
- this.treeElements.push(treeElement);
145
+ constructor(searchConfig: Workspace.SearchConfig.SearchConfig, view: View = DEFAULT_VIEW) {
146
+ super({useShadowDom: true});
147
+ this.#view = view;
148
+ this.#searchConfig = searchConfig;
90
149
  }
91
- }
92
-
93
- export const matchesExpandedByDefault = 200;
94
- export const matchesShownAtOnce = 20;
95
150
 
96
- export class SearchResultsTreeElement extends UI.TreeOutline.TreeElement {
97
- private searchConfig: Workspace.SearchConfig.SearchConfig;
98
- private searchResult: SearchResult;
99
- private initialized: boolean;
100
- override toggleOnClick: boolean;
101
-
102
- constructor(searchConfig: Workspace.SearchConfig.SearchConfig, searchResult: SearchResult) {
103
- super('', true);
104
- this.searchConfig = searchConfig;
105
- this.searchResult = searchResult;
106
- this.initialized = false;
107
- this.toggleOnClick = true;
151
+ get searchResults(): SearchResult[] {
152
+ return this.#searchResults;
108
153
  }
109
154
 
110
- override onexpand(): void {
111
- if (this.initialized) {
112
- return;
155
+ set searchResults(searchResults: SearchResult[]) {
156
+ this.#searchResults = searchResults;
157
+ let matchesExpandedCount = 0;
158
+ for (const searchResult of searchResults) {
159
+ if (this.#expandedResults.has(searchResult)) {
160
+ matchesExpandedCount += this.#searchMatches.get(searchResult)?.length ?? 0;
161
+ }
113
162
  }
114
-
115
- this.updateMatchesUI();
116
- this.initialized = true;
163
+ for (const searchResult of searchResults) {
164
+ if (matchesExpandedCount < matchesExpandedByDefault && !this.#expandedResults.has(searchResult)) {
165
+ this.#expandedResults.add(searchResult);
166
+ this.#onExpandSearchResult(searchResult);
167
+ matchesExpandedCount += this.#searchMatches.get(searchResult)?.length ?? 0;
168
+ }
169
+ }
170
+ this.requestUpdate();
117
171
  }
118
172
 
119
173
  showAllMatches(): void {
120
- this.removeChildren();
121
- this.appendSearchMatches(0, this.searchResult.matchesCount());
122
- }
123
-
124
- private updateMatchesUI(): void {
125
- this.removeChildren();
126
- const toIndex = Math.min(this.searchResult.matchesCount(), matchesShownAtOnce);
127
- if (toIndex < this.searchResult.matchesCount()) {
128
- this.appendSearchMatches(0, toIndex - 1);
129
- this.appendShowMoreMatchesElement(toIndex - 1);
130
- } else {
131
- this.appendSearchMatches(0, toIndex);
174
+ for (const searchResult of this.#searchResults) {
175
+ const startMatchIndex = this.#searchMatches.get(searchResult)?.length ?? 0;
176
+ this.#appendSearchMatches(searchResult, startMatchIndex, searchResult.matchesCount());
132
177
  }
178
+ this.requestUpdate();
133
179
  }
134
180
 
135
- override onattach(): void {
136
- this.updateSearchMatches();
181
+ collapseAllResults(): void {
182
+ this.#expandedResults = new WeakSet<SearchResult>();
183
+ this.requestUpdate();
137
184
  }
138
185
 
139
- private updateSearchMatches(): void {
140
- this.listItemElement.classList.add('search-result');
141
- // clang-format off
142
- render(html`
143
- <span class="search-result-file-name">${this.searchResult.label()}
144
- <span class="search-result-dash">${'\u2014'}</span>
145
- <span class="search-result-qualifier">${this.searchResult.description()}</span>
146
- </span>
147
- <span class="search-result-matches-count"
148
- aria-label=${i18nString(UIStrings.matchesCountS, {PH1: this.searchResult.matchesCount()})}>
149
- ${this.searchResult.matchesCount()}
150
- </span>`,
151
- this.listItemElement);
152
- // clang-format on
153
-
154
- this.tooltip = this.searchResult.description();
155
- if (this.expanded) {
156
- this.updateMatchesUI();
157
- }
186
+ #onExpandSearchResult(searchResult: SearchResult): void {
187
+ const toIndex = Math.min(searchResult.matchesCount(), matchesShownAtOnce);
188
+ this.#appendSearchMatches(searchResult, 0, toIndex);
189
+ this.requestUpdate();
158
190
  }
159
191
 
160
- private appendSearchMatches(fromIndex: number, toIndex: number): void {
161
- const searchResult = this.searchResult;
162
-
163
- const queries = this.searchConfig.queries();
192
+ #appendSearchMatches(searchResult: SearchResult, fromIndex: number, toIndex: number): void {
193
+ const queries = this.#searchConfig.queries();
164
194
  const regexes = [];
165
195
  for (let i = 0; i < queries.length; ++i) {
166
196
  regexes.push(Platform.StringUtilities.createSearchRegex(
167
- queries[i], !this.searchConfig.ignoreCase(), this.searchConfig.isRegex()));
197
+ queries[i], !this.#searchConfig.ignoreCase(), this.#searchConfig.isRegex()));
198
+ }
199
+
200
+ const searchMatches = this.#searchMatches.get(searchResult) ?? [];
201
+ this.#searchMatches.set(searchResult, searchMatches);
202
+ if (searchMatches.length >= toIndex) {
203
+ return;
168
204
  }
169
205
 
170
206
  for (let i = fromIndex; i < toIndex; ++i) {
@@ -183,55 +219,32 @@ export class SearchResultsTreeElement extends UI.TreeOutline.TreeElement {
183
219
  } else {
184
220
  lineContent = lineContent.trim();
185
221
  for (let j = 0; j < regexes.length; ++j) {
186
- matchRanges = matchRanges.concat(this.regexMatchRanges(lineContent, regexes[j]));
222
+ matchRanges = matchRanges.concat(this.#regexMatchRanges(lineContent, regexes[j]));
187
223
  }
188
224
  ({lineSegment: lineContent, matchRanges} = lineSegmentForMultipleMatches(lineContent, matchRanges));
189
225
  }
190
226
 
191
227
  const resultLabel = searchResult.matchLabel(i);
192
-
193
- const searchMatchElement = new UI.TreeOutline.TreeElement();
194
- this.appendChild(searchMatchElement);
195
- // clang-format off
196
- render(html`
197
- <button class="devtools-link text-button link-style search-match-link"
198
- jslog="Link; context: search-match; track: click" role="link" tabindex="0"
199
- @click=${() => void Common.Revealer.reveal(searchResult.matchRevealable(i))}>
200
- <span class="search-match-line-number"
201
- aria-label=${typeof resultLabel === 'number' && !isNaN(resultLabel)
202
- ? i18nString(UIStrings.lineS, {PH1: resultLabel}) : resultLabel}>
203
- ${resultLabel}
204
- </span>
205
- <span class="search-match-content" aria-label="${lineContent} line">
206
- ${lineContent}
207
- </span>
208
- </button>`,
209
- searchMatchElement.listItemElement);
210
- // clang-format on
211
- const contentSpan = searchMatchElement.listItemElement.querySelector('.search-match-content') as HTMLElement;
212
- UI.UIUtils.highlightRangesWithStyleClass(contentSpan, matchRanges, 'highlighted-search-result');
213
- searchMatchElement.listItemElement.className = 'search-match';
214
- searchMatchElement.listItemElement.addEventListener('keydown', event => {
215
- if (event.key === 'Enter') {
216
- event.consume(true);
217
- void Common.Revealer.reveal(searchResult.matchRevealable(i));
218
- }
219
- });
220
- searchMatchElement.tooltip = lineContent;
228
+ searchMatches.push({lineContent, matchRanges, resultLabel});
221
229
  }
222
230
  }
223
231
 
224
- private appendShowMoreMatchesElement(startMatchIndex: number): void {
225
- const matchesLeftCount = this.searchResult.matchesCount() - startMatchIndex;
226
- const showMoreMatchesText = i18nString(UIStrings.showDMore, {PH1: matchesLeftCount});
227
- const showMoreMatchesTreeElement = new UI.TreeOutline.TreeElement(showMoreMatchesText);
228
- this.appendChild(showMoreMatchesTreeElement);
229
- showMoreMatchesTreeElement.listItemElement.classList.add('show-more-matches');
230
- showMoreMatchesTreeElement.onselect =
231
- this.showMoreMatchesElementSelected.bind(this, showMoreMatchesTreeElement, startMatchIndex);
232
+ override performUpdate(): void {
233
+ this.#view(
234
+ {
235
+ results: this.#searchResults,
236
+ matches: this.#searchMatches,
237
+ expandedResults: this.#expandedResults,
238
+ onSelectMatch: (searchResult, matchIndex) => {
239
+ void Common.Revealer.reveal(searchResult.matchRevealable(matchIndex));
240
+ },
241
+ onExpandSearchResult: this.#onExpandSearchResult.bind(this),
242
+ onShowMoreMatches: this.#onShowMoreMatches.bind(this),
243
+ },
244
+ {}, this.contentElement);
232
245
  }
233
246
 
234
- private regexMatchRanges(lineContent: string, regex: RegExp): TextUtils.TextRange.SourceRange[] {
247
+ #regexMatchRanges(lineContent: string, regex: RegExp): TextUtils.TextRange.SourceRange[] {
235
248
  regex.lastIndex = 0;
236
249
  let match;
237
250
  const matchRanges = [];
@@ -242,14 +255,16 @@ export class SearchResultsTreeElement extends UI.TreeOutline.TreeElement {
242
255
  return matchRanges;
243
256
  }
244
257
 
245
- private showMoreMatchesElementSelected(
246
- showMoreMatchesTreeElement: UI.TreeOutline.TreeElement, startMatchIndex: number): boolean {
247
- this.removeChild(showMoreMatchesTreeElement);
248
- this.appendSearchMatches(startMatchIndex, this.searchResult.matchesCount());
249
- return false;
258
+ #onShowMoreMatches(searchResult: SearchResult): void {
259
+ const startMatchIndex = this.#searchMatches.get(searchResult)?.length ?? 0;
260
+ this.#appendSearchMatches(searchResult, startMatchIndex, searchResult.matchesCount());
261
+ this.requestUpdate();
250
262
  }
251
263
  }
252
264
 
265
+ export const matchesExpandedByDefault = 200;
266
+ export const matchesShownAtOnce = 20;
267
+
253
268
  const DEFAULT_OPTS = {
254
269
  prefixLength: 25,
255
270
  maxLength: 1000,