chrome-devtools-frontend 1.0.1516909 → 1.0.1518653

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/docs/checklist/README.md +2 -2
  2. package/docs/checklist/javascript.md +1 -1
  3. package/docs/contributing/README.md +1 -1
  4. package/docs/contributing/settings-experiments-features.md +9 -8
  5. package/docs/cookbook/devtools_on_devtools.md +2 -2
  6. package/docs/cookbook/localization.md +10 -10
  7. package/docs/devtools-protocol.md +9 -8
  8. package/docs/ecosystem/automatic_workspace_folders.md +3 -3
  9. package/docs/get_the_code.md +0 -2
  10. package/docs/styleguide/ux/components.md +166 -85
  11. package/docs/styleguide/ux/numbers.md +3 -4
  12. package/front_end/core/common/README.md +13 -12
  13. package/front_end/core/host/GdpClient.ts +16 -1
  14. package/front_end/core/host/UserMetrics.ts +4 -2
  15. package/front_end/core/root/Runtime.ts +13 -0
  16. package/front_end/core/sdk/CSSMatchedStyles.ts +5 -1
  17. package/front_end/entrypoints/main/MainImpl.ts +6 -3
  18. package/front_end/generated/InspectorBackendCommands.js +10 -7
  19. package/front_end/generated/SupportedCSSProperties.js +21 -7
  20. package/front_end/generated/protocol-mapping.d.ts +16 -1
  21. package/front_end/generated/protocol-proxy-api.d.ts +13 -1
  22. package/front_end/generated/protocol.ts +95 -0
  23. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +166 -49
  24. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +14 -181
  25. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +13 -315
  26. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +224 -50
  27. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +310 -11
  28. package/front_end/models/ai_assistance/performance/AIContext.ts +15 -2
  29. package/front_end/models/ai_code_completion/AiCodeCompletion.ts +17 -11
  30. package/front_end/models/badges/Badge.ts +8 -3
  31. package/front_end/models/badges/CodeWhispererBadge.ts +2 -4
  32. package/front_end/models/badges/StarterBadge.ts +2 -2
  33. package/front_end/models/badges/UserBadges.ts +21 -3
  34. package/front_end/models/javascript_metadata/NativeFunctions.js +1 -1
  35. package/front_end/models/trace/README.md +28 -1
  36. package/front_end/models/trace/handlers/UserTimingsHandler.ts +1 -1
  37. package/front_end/models/trace/helpers/Trace.ts +99 -43
  38. package/front_end/models/trace/types/TraceEvents.ts +9 -0
  39. package/front_end/panels/accessibility/ARIAAttributesView.ts +113 -191
  40. package/front_end/panels/accessibility/AccessibilityNodeView.ts +9 -9
  41. package/front_end/panels/accessibility/AccessibilitySubPane.ts +6 -4
  42. package/front_end/panels/accessibility/accessibilityProperties.css +2 -0
  43. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +16 -2
  44. package/front_end/panels/ai_assistance/components/ChatView.ts +9 -10
  45. package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +42 -0
  46. package/front_end/panels/common/AiCodeCompletionDisclaimer.ts +32 -9
  47. package/front_end/panels/common/AiCodeCompletionSummaryToolbar.ts +7 -1
  48. package/front_end/panels/common/BadgeNotification.ts +21 -5
  49. package/front_end/panels/common/GdpSignUpDialog.ts +18 -9
  50. package/front_end/panels/console/ConsolePrompt.ts +1 -1
  51. package/front_end/panels/console/ConsoleView.ts +6 -2
  52. package/front_end/panels/elements/ElementsPanel.ts +4 -0
  53. package/front_end/panels/elements/ElementsTreeElement.ts +18 -0
  54. package/front_end/panels/elements/ElementsTreeOutline.ts +13 -0
  55. package/front_end/panels/elements/StylePropertyTreeElement.ts +21 -6
  56. package/front_end/panels/media/TickingFlameChart.ts +1 -1
  57. package/front_end/panels/profiler/HeapSnapshotView.ts +34 -19
  58. package/front_end/panels/search/SearchResultsPane.ts +124 -128
  59. package/front_end/panels/search/SearchView.ts +24 -17
  60. package/front_end/panels/settings/components/SyncSection.ts +16 -8
  61. package/front_end/panels/sources/AiCodeCompletionPlugin.ts +6 -1
  62. package/front_end/panels/sources/SourcesPanel.ts +3 -0
  63. package/front_end/panels/timeline/AppenderUtils.ts +2 -2
  64. package/front_end/panels/timeline/ExtensionTrackAppender.ts +13 -4
  65. package/front_end/panels/timeline/GPUTrackAppender.ts +2 -1
  66. package/front_end/panels/timeline/InteractionsTrackAppender.ts +5 -1
  67. package/front_end/panels/timeline/LayoutShiftsTrackAppender.ts +2 -1
  68. package/front_end/panels/timeline/ThreadAppender.ts +12 -3
  69. package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +9 -4
  70. package/front_end/panels/timeline/TimelinePanel.ts +3 -2
  71. package/front_end/panels/timeline/TimelineUIUtils.ts +5 -4
  72. package/front_end/panels/timeline/TimingsTrackAppender.ts +6 -1
  73. package/front_end/panels/timeline/components/CPUThrottlingSelector.ts +95 -82
  74. package/front_end/panels/timeline/components/LiveMetricsView.ts +2 -2
  75. package/front_end/panels/timeline/components/cpuThrottlingSelector.css +17 -15
  76. package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +3 -0
  77. package/front_end/third_party/chromium/README.chromium +1 -1
  78. package/front_end/third_party/codemirror.next/chunk/codemirror.js +1 -1
  79. package/front_end/third_party/codemirror.next/chunk/codemirror.js.map +1 -1
  80. package/front_end/third_party/codemirror.next/codemirror.next.d.ts +6 -9
  81. package/front_end/third_party/codemirror.next/package.json +2 -1
  82. package/front_end/third_party/diff/README.chromium +1 -0
  83. package/front_end/ui/components/text_editor/config.ts +6 -7
  84. package/front_end/ui/components/tooltips/Tooltip.ts +70 -31
  85. package/front_end/ui/legacy/README.md +33 -24
  86. package/front_end/ui/legacy/SearchableView.ts +19 -26
  87. package/front_end/ui/legacy/TextPrompt.ts +166 -1
  88. package/front_end/ui/legacy/Treeoutline.ts +16 -2
  89. package/front_end/ui/legacy/UIUtils.ts +15 -2
  90. package/front_end/ui/legacy/XElement.ts +0 -43
  91. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +20 -4
  92. package/front_end/ui/visual_logging/KnownContextValues.ts +19 -6
  93. package/front_end/ui/visual_logging/README.md +43 -27
  94. package/package.json +1 -1
@@ -52,7 +52,7 @@ import {
52
52
  StylesSidebarPane,
53
53
  } from './StylesSidebarPane.js';
54
54
 
55
- const {html, nothing, render, Directives: {classMap, ifDefined}} = Lit;
55
+ const {html, nothing, render, Directives: {classMap}} = Lit;
56
56
  const ASTUtils = SDK.CSSPropertyParser.ASTUtils;
57
57
  const FlexboxEditor = ElementsComponents.StylePropertyEditor.FlexboxEditor;
58
58
  const GridEditor = ElementsComponents.StylePropertyEditor.GridEditor;
@@ -446,19 +446,25 @@ export class AttributeRenderer extends rendererBase(SDK.CSSPropertyParserMatcher
446
446
  const attrCall =
447
447
  this.#treeElement?.getTracingTooltip('attr', match.node, this.#matchedStyles, this.#computedStyles, context);
448
448
  const tooltipId = attributeMissing ? undefined : this.#treeElement?.getTooltipId('custom-attribute');
449
+ const tooltip = tooltipId ? {tooltipId} : undefined;
449
450
  // clang-format off
450
451
  render(html`
451
452
  <span data-title=${computedValue || ''}
452
453
  jslog=${VisualLogging.link('css-variable').track({click: true, hover: true})}
453
- >${attrCall ?? 'attr'}(<span class=${attributeClass} aria-details=${ifDefined(tooltipId)}>${match.name}</span>${
454
- match.type ? html` <span class=${typeClass}>${match.type}</span>` : nothing
455
- }${renderedFallback ? html`, <span class=${fallbackClass}>${renderedFallback.nodes}</span>` : nothing
456
- })</span>${tooltipId ? html`
454
+ >${attrCall ?? 'attr'}(<devtools-link-swatch class=${attributeClass} .data=${{
455
+ tooltip,
456
+ text: match.name,
457
+ isDefined: true,
458
+ onLinkActivate: () => this.#handleAttributeActivate(this.#matchedStyles.originatingNodeForStyle(match.style), match.name),
459
+ }}></devtools-link-swatch>${tooltipId ? html`
457
460
  <devtools-tooltip
458
461
  id=${tooltipId}
459
462
  variant=rich
460
463
  jslogContext=elements.css-var
461
- >${JSON.stringify(rawValue)}</devtools-tooltip>` : ''}`, varSwatch);
464
+ >${JSON.stringify(rawValue)}</devtools-tooltip>` : nothing}${
465
+ match.type ? html` <span class=${typeClass}>${match.type}</span>` : nothing
466
+ }${renderedFallback ? html`, <span class=${fallbackClass}>${renderedFallback.nodes}</span>` : nothing
467
+ })</span>`, varSwatch);
462
468
  // clang-format on
463
469
 
464
470
  const color = computedValue && Common.Color.parse(computedValue);
@@ -478,6 +484,15 @@ export class AttributeRenderer extends rendererBase(SDK.CSSPropertyParserMatcher
478
484
 
479
485
  return [colorSwatch, varSwatch];
480
486
  }
487
+
488
+ #handleAttributeActivate(node: SDK.DOMModel.DOMNode|null, attribute: string): void {
489
+ if (!node) {
490
+ return;
491
+ }
492
+ Host.userMetrics.actionTaken(Host.UserMetrics.Action.AttributeLinkClicked);
493
+ Host.userMetrics.swatchActivated(Host.UserMetrics.SwatchType.ATTR_LINK);
494
+ ElementsPanel.instance().highlightNodeAttribute(node, attribute);
495
+ }
481
496
  }
482
497
 
483
498
  // clang-format off
@@ -19,7 +19,7 @@ function getGroupDefaultTextColor(): string {
19
19
  const DefaultStyle: () => PerfUI.FlameChart.GroupStyle = () => ({
20
20
  height: 20,
21
21
  padding: 2,
22
- collapsible: false,
22
+ collapsible: PerfUI.FlameChart.GroupCollapsibleState.NEVER,
23
23
  font: defaultFont,
24
24
  color: getGroupDefaultTextColor(),
25
25
  backgroundColor: 'rgba(100 0 0 / 10%)',
@@ -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> {
@@ -35,22 +35,95 @@ const UIStrings = {
35
35
  const str_ = i18n.i18n.registerUIStrings('panels/search/SearchResultsPane.ts', UIStrings);
36
36
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
37
37
 
38
+ interface SearchMatch {
39
+ lineContent: string;
40
+ matchRanges: TextUtils.TextRange.SourceRange[];
41
+ resultLabel: string|number;
42
+ }
43
+
38
44
  export class SearchResultsPane extends UI.Widget.VBox {
39
45
  private readonly searchConfig: Workspace.SearchConfig.SearchConfig;
40
- private readonly searchResults: SearchResult[];
41
- private readonly treeElements: SearchResultsTreeElement[];
46
+ private readonly searchResults = new Map<SearchResult, UI.TreeOutline.TreeElement>();
47
+ private expandedResults = new WeakSet<SearchResult>();
48
+ private readonly searchMatches = new WeakMap<SearchResult, SearchMatch[]>();
42
49
  private treeOutline: UI.TreeOutline.TreeOutlineInShadow;
43
- private matchesExpandedCount: number;
50
+ private matchesExpandedCount = 0;
51
+
52
+ private addTreeElement(searchResult: SearchResult): void {
53
+ const treeElement = new UI.TreeOutline.TreeElement('', true);
54
+ treeElement.toggleOnClick = true;
55
+ this.searchResults.set(searchResult, treeElement);
56
+ this.treeOutline.appendChild(treeElement);
57
+ if (!this.treeOutline.selectedTreeElement) {
58
+ treeElement.select(/* omitFocus */ true, /* selectedByUser */ true);
59
+ }
60
+ treeElement.listItemElement.classList.add('search-result');
61
+ // clang-format off
62
+ render(html`
63
+ <span class="search-result-file-name">${searchResult.label()}
64
+ <span class="search-result-dash">${'\u2014'}</span>
65
+ <span class="search-result-qualifier">${searchResult.description()}</span>
66
+ </span>
67
+ <span class="search-result-matches-count"
68
+ aria-label=${i18nString(UIStrings.matchesCountS, {PH1: searchResult.matchesCount()})}>
69
+ ${searchResult.matchesCount()}
70
+ </span>`,
71
+ treeElement.listItemElement);
72
+ // clang-format on
73
+ treeElement.tooltip = searchResult.description();
74
+ }
75
+
76
+ private appendSearchMatchElement(searchResult: SearchResult, match: SearchMatch, i: number): void {
77
+ const {lineContent, matchRanges, resultLabel} = match;
78
+ const element = this.searchResults.get(searchResult);
79
+ if (!element) {
80
+ return;
81
+ }
82
+ const searchMatchElement = new UI.TreeOutline.TreeElement();
83
+ element.appendChild(searchMatchElement);
84
+ // clang-format off
85
+ render(html`
86
+ <button class="devtools-link text-button link-style search-match-link"
87
+ jslog="Link; context: search-match; track: click" role="link" tabindex="0"
88
+ @click=${() => void Common.Revealer.reveal(searchResult.matchRevealable(i))}>
89
+ <span class="search-match-line-number"
90
+ aria-label=${typeof resultLabel === 'number' && !isNaN(resultLabel)
91
+ ? i18nString(UIStrings.lineS, {PH1: resultLabel}) : resultLabel}>
92
+ ${resultLabel}
93
+ </span>
94
+ <span class="search-match-content" aria-label="${lineContent} line">
95
+ ${lineContent}
96
+ </span>
97
+ </button>`,
98
+ searchMatchElement.listItemElement);
99
+ // clang-format on
100
+ const contentSpan = searchMatchElement.listItemElement.querySelector('.search-match-content') as HTMLElement;
101
+ UI.UIUtils.highlightRangesWithStyleClass(contentSpan, matchRanges, 'highlighted-search-result');
102
+ searchMatchElement.listItemElement.className = 'search-match';
103
+ searchMatchElement.listItemElement.addEventListener('keydown', event => {
104
+ if (event.key === 'Enter') {
105
+ event.consume(true);
106
+ void Common.Revealer.reveal(searchResult.matchRevealable(i));
107
+ }
108
+ });
109
+ searchMatchElement.tooltip = lineContent;
110
+ }
44
111
 
45
112
  constructor(searchConfig: Workspace.SearchConfig.SearchConfig) {
46
113
  super({useShadowDom: true});
47
114
  this.searchConfig = searchConfig;
48
-
49
- this.searchResults = [];
50
- this.treeElements = [];
51
115
  this.treeOutline = new UI.TreeOutline.TreeOutlineInShadow();
52
116
  this.treeOutline.registerRequiredCSS(searchResultsPaneStyles);
53
117
  this.treeOutline.setHideOverflow(true);
118
+ this.treeOutline.addEventListener(
119
+ UI.TreeOutline.Events.ElementExpanded,
120
+ (event: Common.EventTarget.EventTargetEvent<UI.TreeOutline.TreeElement>) => {
121
+ const searchResult = this.searchResults.entries().find(entry => entry[1] === event.data)?.[0];
122
+ if (!searchResult) {
123
+ return;
124
+ }
125
+ this.expandSearchResult(searchResult);
126
+ });
54
127
 
55
128
  this.contentElement.appendChild(this.treeOutline.element);
56
129
 
@@ -58,108 +131,43 @@ export class SearchResultsPane extends UI.Widget.VBox {
58
131
  }
59
132
 
60
133
  addSearchResult(searchResult: SearchResult): void {
61
- this.searchResults.push(searchResult);
62
134
  this.addTreeElement(searchResult);
63
- }
64
-
65
- showAllMatches(): void {
66
- this.treeElements.forEach(treeElement => {
67
- treeElement.expand();
68
- treeElement.showAllMatches();
69
- });
70
- }
71
-
72
- collapseAllResults(): void {
73
- this.treeElements.forEach(treeElement => {
74
- treeElement.collapse();
75
- });
76
- }
77
-
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
135
  // Expand until at least a certain number of matches is expanded.
85
136
  if (this.matchesExpandedCount < matchesExpandedByDefault) {
86
- treeElement.expand();
137
+ this.searchResults.get(searchResult)?.expand();
87
138
  }
88
- this.matchesExpandedCount += searchResult.matchesCount();
89
- this.treeElements.push(treeElement);
90
- }
91
- }
92
-
93
- export const matchesExpandedByDefault = 200;
94
- export const matchesShownAtOnce = 20;
95
-
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;
108
139
  }
109
140
 
110
- override onexpand(): void {
111
- if (this.initialized) {
112
- return;
141
+ showAllMatches(): void {
142
+ for (const searchResult of this.searchResults.keys()) {
143
+ const startMatchIndex = this.searchMatches.get(searchResult)?.length ?? 0;
144
+ this.appendSearchMatches(searchResult, startMatchIndex, searchResult.matchesCount());
113
145
  }
114
-
115
- this.updateMatchesUI();
116
- this.initialized = true;
117
146
  }
118
147
 
119
- showAllMatches(): void {
120
- this.removeChildren();
121
- this.appendSearchMatches(0, this.searchResult.matchesCount());
148
+ collapseAllResults(): void {
149
+ for (const treeElement of this.searchResults.values()) {
150
+ treeElement.collapse();
151
+ }
122
152
  }
123
153
 
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);
154
+ private expandSearchResult(searchResult: SearchResult): void {
155
+ const toIndex = Math.min(searchResult.matchesCount(), matchesShownAtOnce);
156
+ if (toIndex < searchResult.matchesCount()) {
157
+ this.appendSearchMatches(searchResult, 0, toIndex - 1);
158
+ this.appendShowMoreMatchesElement(searchResult, toIndex - 1);
130
159
  } else {
131
- this.appendSearchMatches(0, toIndex);
160
+ this.appendSearchMatches(searchResult, 0, toIndex);
132
161
  }
162
+ this.matchesExpandedCount += toIndex;
163
+ this.requestUpdate();
133
164
  }
134
165
 
135
- override onattach(): void {
136
- this.updateSearchMatches();
166
+ private collapseSearchResult(searchResult: SearchResult): void {
167
+ this.searchResults.get(searchResult)?.collapse();
137
168
  }
138
169
 
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
- }
158
- }
159
-
160
- private appendSearchMatches(fromIndex: number, toIndex: number): void {
161
- const searchResult = this.searchResult;
162
-
170
+ private appendSearchMatches(searchResult: SearchResult, fromIndex: number, toIndex: number): void {
163
171
  const queries = this.searchConfig.queries();
164
172
  const regexes = [];
165
173
  for (let i = 0; i < queries.length; ++i) {
@@ -167,6 +175,12 @@ export class SearchResultsTreeElement extends UI.TreeOutline.TreeElement {
167
175
  queries[i], !this.searchConfig.ignoreCase(), this.searchConfig.isRegex()));
168
176
  }
169
177
 
178
+ const searchMatches = this.searchMatches.get(searchResult) ?? [];
179
+ this.searchMatches.set(searchResult, searchMatches);
180
+ if (searchMatches.length > toIndex) {
181
+ return;
182
+ }
183
+
170
184
  for (let i = fromIndex; i < toIndex; ++i) {
171
185
  let lineContent = searchResult.matchLineContent(i);
172
186
  let matchRanges: TextUtils.TextRange.SourceRange[] = [];
@@ -189,46 +203,23 @@ export class SearchResultsTreeElement extends UI.TreeOutline.TreeElement {
189
203
  }
190
204
 
191
205
  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;
206
+ searchMatches.push({lineContent, matchRanges, resultLabel});
207
+ this.appendSearchMatchElement(searchResult, searchMatches[i], i);
221
208
  }
222
209
  }
223
210
 
224
- private appendShowMoreMatchesElement(startMatchIndex: number): void {
225
- const matchesLeftCount = this.searchResult.matchesCount() - startMatchIndex;
211
+ private appendShowMoreMatchesElement(searchResult: SearchResult, startMatchIndex: number): void {
212
+ const element = this.searchResults.get(searchResult);
213
+ if (!element) {
214
+ return;
215
+ }
216
+ const matchesLeftCount = searchResult.matchesCount() - startMatchIndex;
226
217
  const showMoreMatchesText = i18nString(UIStrings.showDMore, {PH1: matchesLeftCount});
227
218
  const showMoreMatchesTreeElement = new UI.TreeOutline.TreeElement(showMoreMatchesText);
228
- this.appendChild(showMoreMatchesTreeElement);
219
+ element.appendChild(showMoreMatchesTreeElement);
229
220
  showMoreMatchesTreeElement.listItemElement.classList.add('show-more-matches');
230
221
  showMoreMatchesTreeElement.onselect =
231
- this.showMoreMatchesElementSelected.bind(this, showMoreMatchesTreeElement, startMatchIndex);
222
+ this.showMoreMatchesElementSelected.bind(this, searchResult, showMoreMatchesTreeElement, startMatchIndex);
232
223
  }
233
224
 
234
225
  private regexMatchRanges(lineContent: string, regex: RegExp): TextUtils.TextRange.SourceRange[] {
@@ -243,13 +234,18 @@ export class SearchResultsTreeElement extends UI.TreeOutline.TreeElement {
243
234
  }
244
235
 
245
236
  private showMoreMatchesElementSelected(
246
- showMoreMatchesTreeElement: UI.TreeOutline.TreeElement, startMatchIndex: number): boolean {
247
- this.removeChild(showMoreMatchesTreeElement);
248
- this.appendSearchMatches(startMatchIndex, this.searchResult.matchesCount());
249
- return false;
237
+ searchResult: SearchResult, showMoreMatchesTreeElement: UI.TreeOutline.TreeElement,
238
+ startMatchIndex: number): boolean {
239
+ const parentElement = showMoreMatchesTreeElement.parent;
240
+ parentElement?.removeChild(showMoreMatchesTreeElement);
241
+ this.appendSearchMatches(searchResult, startMatchIndex, searchResult.matchesCount());
242
+ return true;
250
243
  }
251
244
  }
252
245
 
246
+ export const matchesExpandedByDefault = 200;
247
+ export const matchesShownAtOnce = 20;
248
+
253
249
  const DEFAULT_OPTS = {
254
250
  prefixLength: 25,
255
251
  maxLength: 1000,
@@ -106,12 +106,11 @@ const UIStrings = {
106
106
  } as const;
107
107
  const str_ = i18n.i18n.registerUIStrings('panels/search/SearchView.ts', UIStrings);
108
108
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
109
- const {ref} = Directives;
109
+ const {ref, live} = Directives;
110
110
  const {widgetConfig} = UI.Widget;
111
111
 
112
112
  interface SearchViewInput {
113
113
  query: string;
114
- focusSearchInput: boolean;
115
114
  matchCase: boolean;
116
115
  isRegex: boolean;
117
116
  searchMessage: string;
@@ -128,12 +127,15 @@ interface SearchViewInput {
128
127
  onClearSearch: () => void;
129
128
  }
130
129
 
131
- type View = (input: SearchViewInput, output: object, target: HTMLElement) => void;
130
+ interface SearchViewOutput {
131
+ focusSearchInput: () => void;
132
+ }
133
+
134
+ type View = (input: SearchViewInput, output: SearchViewOutput, target: HTMLElement) => void;
132
135
 
133
136
  export const DEFAULT_VIEW: View = (input, output, target) => {
134
137
  const {
135
138
  query,
136
- focusSearchInput,
137
139
  matchCase,
138
140
  isRegex,
139
141
  searchMessage,
@@ -175,14 +177,16 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
175
177
  change: true, keydown: 'ArrowUp|ArrowDown|Enter'})}
176
178
  aria-label=${i18nString(UIStrings.find)}
177
179
  size="100" results="0"
178
- .value=${query}
180
+ .value=${live(query)}
179
181
  @keydown=${onQueryKeyDown}
180
182
  @input=${(e: Event) => onQueryChange((e.target as HTMLInputElement).value)}
181
183
  ${ref(e => {
182
- if (e instanceof HTMLInputElement && focusSearchInput) {
183
- e.focus();
184
- e.select();
185
- }
184
+ output.focusSearchInput = () => {
185
+ if (e instanceof HTMLInputElement) {
186
+ e.focus();
187
+ e.select();
188
+ }
189
+ };
186
190
  })}>
187
191
  <devtools-button class="clear-button" tabindex="-1"
188
192
  @click=${onClearSearchInput}
@@ -258,7 +262,7 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
258
262
 
259
263
  export class SearchView extends UI.Widget.VBox {
260
264
  readonly #view: View;
261
- #focusSearchInput: boolean;
265
+ #focusSearchInput = (): void => {};
262
266
  #isIndexing: boolean;
263
267
  #searchId: number;
264
268
  #searchMatchesCount: number;
@@ -294,7 +298,6 @@ export class SearchView extends UI.Widget.VBox {
294
298
  this.#view = view;
295
299
  this.setMinimumSize(0, 40);
296
300
 
297
- this.#focusSearchInput = false;
298
301
  this.#isIndexing = false;
299
302
  this.#searchId = 1;
300
303
  this.#query = '';
@@ -320,7 +323,6 @@ export class SearchView extends UI.Widget.VBox {
320
323
  override performUpdate(): void {
321
324
  const input: SearchViewInput = {
322
325
  query: this.#query,
323
- focusSearchInput: this.#focusSearchInput,
324
326
  matchCase: this.#matchCase,
325
327
  isRegex: this.#isRegex,
326
328
  searchMessage: this.#searchMessage,
@@ -338,9 +340,13 @@ export class SearchView extends UI.Widget.VBox {
338
340
  onRefresh: this.#onRefresh.bind(this),
339
341
  onClearSearch: this.#onClearSearch.bind(this),
340
342
  };
341
- const output = {};
343
+ const that = this;
344
+ const output: SearchViewOutput = {
345
+ set focusSearchInput(value: () => void) {
346
+ that.#focusSearchInput = value;
347
+ }
348
+ };
342
349
  this.#view(input, output, this.contentElement);
343
- this.#focusSearchInput = false;
344
350
  }
345
351
 
346
352
  #onToggleRegex(): void {
@@ -360,7 +366,9 @@ export class SearchView extends UI.Widget.VBox {
360
366
  toggle(queryCandidate: string, searchImmediately?: boolean): void {
361
367
  this.#query = queryCandidate;
362
368
  this.requestUpdate();
363
- this.focus();
369
+ void this.updateComplete.then(() => {
370
+ this.focus();
371
+ });
364
372
 
365
373
  this.#initScope();
366
374
  if (searchImmediately) {
@@ -537,8 +545,7 @@ export class SearchView extends UI.Widget.VBox {
537
545
  }
538
546
 
539
547
  override focus(): void {
540
- this.#focusSearchInput = true;
541
- this.requestUpdate();
548
+ this.#focusSearchInput();
542
549
  }
543
550
 
544
551
  override willHide(): void {