chrome-devtools-frontend 1.0.1518653 → 1.0.1520139

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 (143) hide show
  1. package/config/owner/COMMON_OWNERS +1 -2
  2. package/config/typescript/tsconfig.eslint.json +12 -1
  3. package/docs/ui_engineering.md +1011 -0
  4. package/eslint.config.mjs +1 -0
  5. package/front_end/core/host/GdpClient.ts +12 -3
  6. package/front_end/core/sdk/EnhancedTracesParser.ts +5 -5
  7. package/front_end/core/sdk/NetworkManager.ts +1 -0
  8. package/front_end/core/sdk/NetworkRequest.ts +10 -0
  9. package/front_end/core/sdk/RehydratingConnection.snapshot.txt +211 -0
  10. package/front_end/core/sdk/TargetManager.ts +4 -0
  11. package/front_end/entrypoints/main/MainImpl.ts +6 -1
  12. package/front_end/entrypoints/main/main-meta.ts +3 -3
  13. package/front_end/generated/SupportedCSSProperties.js +19 -4
  14. package/front_end/models/ai_assistance/agents/AiAgent.ts +57 -10
  15. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +64 -87
  16. package/front_end/models/ai_assistance/agents/PerformanceAnnotationsAgent.ts +4 -4
  17. package/front_end/models/ai_assistance/agents/StylingAgent.ts +0 -31
  18. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +127 -29
  19. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +106 -55
  20. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +317 -640
  21. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +23 -19
  22. package/front_end/models/ai_assistance/performance/AICallTree.snapshot.txt +75 -0
  23. package/front_end/models/ai_assistance/performance/AICallTree.ts +14 -6
  24. package/front_end/models/ai_assistance/performance/AIContext.ts +63 -8
  25. package/front_end/models/ai_code_completion/AiCodeCompletion.ts +5 -0
  26. package/front_end/models/badges/AiExplorerBadge.ts +19 -3
  27. package/front_end/models/badges/Badge.ts +8 -1
  28. package/front_end/models/badges/CodeWhispererBadge.ts +1 -0
  29. package/front_end/models/badges/DOMDetectiveBadge.ts +1 -0
  30. package/front_end/models/badges/SpeedsterBadge.ts +1 -0
  31. package/front_end/models/badges/StarterBadge.ts +6 -0
  32. package/front_end/models/badges/badges.ts +1 -0
  33. package/front_end/models/javascript_metadata/NativeFunctions.js +4 -0
  34. package/front_end/models/trace/EventsSerializer.ts +4 -3
  35. package/front_end/models/trace/handlers/UserInteractionsHandler.ts +101 -73
  36. package/front_end/models/trace/helpers/Timing.ts +1 -1
  37. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +18 -8
  38. package/front_end/panels/ai_assistance/PatchWidget.ts +17 -55
  39. package/front_end/panels/ai_assistance/components/ChatView.ts +44 -68
  40. package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +63 -15
  41. package/front_end/panels/ai_assistance/components/chatView.css +12 -0
  42. package/front_end/panels/animation/AnimationTimeline.ts +1 -1
  43. package/front_end/panels/animation/animationTimeline.css +4 -0
  44. package/front_end/panels/application/components/BounceTrackingMitigationsView.ts +2 -2
  45. package/front_end/panels/application/preloading/components/PreloadingString.ts +2 -5
  46. package/front_end/panels/common/AiCodeCompletionTeaser.ts +5 -0
  47. package/front_end/panels/common/BadgeNotification.ts +3 -3
  48. package/front_end/panels/common/GdpSignUpDialog.ts +3 -4
  49. package/front_end/panels/common/aiCodeCompletionTeaser.css +6 -1
  50. package/front_end/panels/console/ConsolePrompt.ts +6 -0
  51. package/front_end/panels/console/ConsoleView.ts +4 -2
  52. package/front_end/panels/coverage/CoverageListView.ts +133 -158
  53. package/front_end/panels/coverage/CoverageView.ts +39 -16
  54. package/front_end/panels/css_overview/CSSOverviewCompletedView.ts +5 -5
  55. package/front_end/panels/mobile_throttling/NetworkThrottlingSelector.ts +2 -0
  56. package/front_end/panels/network/NetworkDataGridNode.ts +22 -0
  57. package/front_end/panels/network/NetworkLogViewColumns.ts +9 -0
  58. package/front_end/panels/recorder/components/CreateRecordingView.ts +2 -0
  59. package/front_end/panels/recorder/components/RecordingView.ts +2 -2
  60. package/front_end/panels/search/SearchResultsPane.ts +186 -134
  61. package/front_end/panels/search/SearchView.ts +42 -36
  62. package/front_end/panels/search/searchResultsPane.css +9 -0
  63. package/front_end/panels/search/searchView.css +0 -2
  64. package/front_end/panels/security/CookieControlsView.ts +2 -1
  65. package/front_end/panels/settings/AISettingsTab.ts +6 -3
  66. package/front_end/panels/settings/components/SyncSection.ts +26 -12
  67. package/front_end/panels/settings/emulation/components/UserAgentClientHintsForm.ts +1 -1
  68. package/front_end/panels/sources/AiCodeCompletionPlugin.ts +4 -4
  69. package/front_end/panels/sources/DebuggerPlugin.ts +4 -0
  70. package/front_end/panels/sources/SourcesPanel.ts +1 -1
  71. package/front_end/panels/sources/sourcesView.css +6 -1
  72. package/front_end/panels/timeline/ThirdPartyTreeView.ts +1 -1
  73. package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +0 -8
  74. package/front_end/panels/timeline/TimelineFlameChartView.ts +5 -5
  75. package/front_end/panels/timeline/TimelinePanel.ts +2 -0
  76. package/front_end/panels/timeline/TimelineTreeView.ts +1 -1
  77. package/front_end/panels/timeline/components/LayoutShiftDetails.ts +1 -1
  78. package/front_end/panels/timeline/components/NetworkRequestDetails.ts +1 -1
  79. package/front_end/panels/timeline/components/RelatedInsightChips.ts +1 -1
  80. package/front_end/panels/timeline/components/SidebarSingleInsightSet.ts +1 -1
  81. package/front_end/third_party/chromium/README.chromium +1 -1
  82. package/front_end/third_party/puppeteer/README.chromium +2 -2
  83. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Realm.d.ts +2 -2
  84. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.d.ts +1 -1
  85. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.js +1 -1
  86. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.d.ts +1 -1
  87. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.js +1 -1
  88. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
  89. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/BrowserLauncher.js +1 -1
  90. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/BrowserLauncher.js.map +1 -1
  91. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
  92. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js +1 -0
  93. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js.map +1 -1
  94. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/PipeTransport.d.ts.map +1 -1
  95. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/PipeTransport.js +15 -16
  96. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/PipeTransport.js.map +1 -1
  97. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +3 -3
  98. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +3 -3
  99. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js.map +1 -1
  100. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Function.d.ts.map +1 -1
  101. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Function.js +16 -25
  102. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Function.js.map +1 -1
  103. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  104. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +19 -28
  105. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.d.ts +1 -1
  106. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.js +1 -1
  107. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.d.ts +1 -1
  108. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.js +1 -1
  109. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/BrowserLauncher.js +1 -1
  110. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/BrowserLauncher.js.map +1 -1
  111. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
  112. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js +1 -0
  113. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js.map +1 -1
  114. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/PipeTransport.d.ts.map +1 -1
  115. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/PipeTransport.js +15 -16
  116. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/PipeTransport.js.map +1 -1
  117. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +3 -3
  118. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +3 -3
  119. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js.map +1 -1
  120. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/Function.d.ts.map +1 -1
  121. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/Function.js +16 -25
  122. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/Function.js.map +1 -1
  123. package/front_end/third_party/puppeteer/package/package.json +12 -4
  124. package/front_end/third_party/puppeteer/package/src/generated/injected.ts +1 -1
  125. package/front_end/third_party/puppeteer/package/src/generated/version.ts +1 -1
  126. package/front_end/third_party/puppeteer/package/src/node/BrowserLauncher.ts +1 -1
  127. package/front_end/third_party/puppeteer/package/src/node/ChromeLauncher.ts +1 -0
  128. package/front_end/third_party/puppeteer/package/src/node/PipeTransport.ts +15 -17
  129. package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
  130. package/front_end/third_party/puppeteer/package/src/util/Function.ts +22 -30
  131. package/front_end/tsconfig.json +12 -1
  132. package/front_end/ui/components/dialogs/Dialog.ts +1 -1
  133. package/front_end/ui/components/markdown_view/MarkdownImage.ts +4 -5
  134. package/front_end/ui/components/switch/SwitchImpl.ts +12 -1
  135. package/front_end/ui/components/text_editor/config.ts +16 -2
  136. package/front_end/ui/legacy/InspectorView.ts +86 -13
  137. package/front_end/ui/legacy/TabbedPane.ts +2 -1
  138. package/front_end/ui/legacy/Treeoutline.ts +3 -1
  139. package/front_end/ui/legacy/components/source_frame/XMLView.ts +12 -11
  140. package/front_end/ui/lit/i18n-template.ts +5 -2
  141. package/front_end/ui/visual_logging/KnownContextValues.ts +15 -5
  142. package/front_end/ui/visual_logging/LoggingEvents.ts +1 -1
  143. package/package.json +1 -1
@@ -771,6 +771,24 @@ export class NetworkRequestNode extends NetworkNode {
771
771
  return aScore - bScore || aRequest.identityCompare(bRequest);
772
772
  }
773
773
 
774
+ static IsAdRelatedComparator(a: NetworkNode, b: NetworkNode): number {
775
+ // TODO(allada) Handle this properly for group nodes.
776
+ const aRequest = a.requestOrFirstKnownChildRequest();
777
+ const bRequest = b.requestOrFirstKnownChildRequest();
778
+ if (!aRequest || !bRequest) {
779
+ return !aRequest ? -1 : 1;
780
+ }
781
+ const aIsAdRelated = aRequest.isAdRelated();
782
+ const bIsAdRelated = bRequest.isAdRelated();
783
+ if (aIsAdRelated > bIsAdRelated) {
784
+ return 1;
785
+ }
786
+ if (bIsAdRelated > aIsAdRelated) {
787
+ return -1;
788
+ }
789
+ return aRequest.identityCompare(bRequest);
790
+ }
791
+
774
792
  static RequestPropertyComparator(propertyName: string, a: NetworkNode, b: NetworkNode): number {
775
793
  // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
776
794
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -1038,6 +1056,10 @@ export class NetworkRequestNode extends NetworkNode {
1038
1056
  this.renderAddressSpaceCell(cell, this.requestInternal.remoteAddressSpace());
1039
1057
  break;
1040
1058
  }
1059
+ case 'is-ad-related': {
1060
+ this.setTextAndTitle(cell, this.requestInternal.isAdRelated().toLocaleString());
1061
+ break;
1062
+ }
1041
1063
  case 'cookies': {
1042
1064
  this.setTextAndTitle(cell, this.arrayLength(this.requestInternal.includedRequestCookies()));
1043
1065
  break;
@@ -148,6 +148,10 @@ const UIStrings = {
148
148
  * @description Text in Network Log View Columns of the Network panel
149
149
  */
150
150
  remoteAddressSpace: 'Remote Address Space',
151
+ /**
152
+ * @description Text to show whether a request is ad-related
153
+ */
154
+ isAdRelated: 'Is Ad-Related',
151
155
  } as const;
152
156
  const str_ = i18n.i18n.registerUIStrings('panels/network/NetworkLogViewColumns.ts', UIStrings);
153
157
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -1183,6 +1187,11 @@ const DEFAULT_COLUMNS = [
1183
1187
  title: i18n.i18n.lockedLazyString('User-Agent'),
1184
1188
  sortingFunction: NetworkRequestNode.RequestHeaderStringComparator.bind(null, 'user-agent'),
1185
1189
  },
1190
+ {
1191
+ id: 'is-ad-related',
1192
+ title: i18nLazyString(UIStrings.isAdRelated),
1193
+ sortingFunction: NetworkRequestNode.IsAdRelatedComparator,
1194
+ },
1186
1195
  // This header is a placeholder to let datagrid know that it can be sorted by this column, but never shown.
1187
1196
  {
1188
1197
  id: 'waterfall',
@@ -8,6 +8,7 @@ import '../../../ui/components/icon_button/icon_button.js';
8
8
  import './ControlButton.js';
9
9
 
10
10
  import * as i18n from '../../../core/i18n/i18n.js';
11
+ import * as Badges from '../../../models/badges/badges.js';
11
12
  import * as Buttons from '../../../ui/components/buttons/buttons.js';
12
13
  import * as Input from '../../../ui/components/input/input.js';
13
14
  import * as Lit from '../../../ui/lit/lit.js';
@@ -231,6 +232,7 @@ export class CreateRecordingView extends HTMLElement {
231
232
  selectorAttribute,
232
233
  ),
233
234
  );
235
+ Badges.UserBadges.instance().recordAction(Badges.BadgeAction.RECORDER_RECORDING_STARTED);
234
236
  }
235
237
 
236
238
  #dispatchRecordingCancelled(): 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';
@@ -41,143 +38,185 @@ interface SearchMatch {
41
38
  resultLabel: string|number;
42
39
  }
43
40
 
44
- export class SearchResultsPane extends UI.Widget.VBox {
45
- private readonly searchConfig: Workspace.SearchConfig.SearchConfig;
46
- private readonly searchResults = new Map<SearchResult, UI.TreeOutline.TreeElement>();
47
- private expandedResults = new WeakSet<SearchResult>();
48
- private readonly searchMatches = new WeakMap<SearchResult, SearchMatch[]>();
49
- private treeOutline: UI.TreeOutline.TreeOutlineInShadow;
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);
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
+ }
49
+
50
+ export type View = (input: ViewInput, output: unknown, target: HTMLElement) => void;
51
+
52
+ export const DEFAULT_VIEW: View = (input, _output, target) => {
53
+ const {results, matches, expandedResults, onSelectMatch, onExpandSearchResult, onShowMoreMatches} = input;
54
+
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);
59
63
  }
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();
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
+ };
84
+
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
+ };
98
+
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
+ };
137
+
138
+ export class SearchResultsPane extends UI.Widget.VBox {
139
+ #searchConfig: Workspace.SearchConfig.SearchConfig|null = null;
140
+ #searchResults: SearchResult[] = [];
141
+ #resultsUpdated = false;
142
+ #expandedResults = new WeakSet<SearchResult>();
143
+ readonly #searchMatches = new WeakMap<SearchResult, SearchMatch[]>();
144
+ #view: View;
145
+
146
+ constructor(element: HTMLElement|undefined, view: View = DEFAULT_VIEW) {
147
+ super(element, {useShadowDom: true});
148
+ this.#view = view;
74
149
  }
75
150
 
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) {
151
+ get searchResults(): SearchResult[] {
152
+ return this.#searchResults;
153
+ }
154
+
155
+ set searchResults(searchResults: SearchResult[]) {
156
+ if (this.#searchResults === searchResults) {
80
157
  return;
81
158
  }
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));
159
+ if (this.#searchResults.length !== searchResults.length) {
160
+ this.#resultsUpdated = true;
161
+ } else if (this.#searchResults.length === searchResults.length) {
162
+ for (let i = 0; i < this.#searchResults.length; ++i) {
163
+ if (this.#searchResults[i] === searchResults[i]) {
164
+ continue;
165
+ }
166
+ this.#resultsUpdated = true;
167
+ break;
107
168
  }
108
- });
109
- searchMatchElement.tooltip = lineContent;
169
+ }
170
+ if (!this.#resultsUpdated) {
171
+ return;
172
+ }
173
+ this.#searchResults = searchResults;
174
+ this.requestUpdate();
110
175
  }
111
176
 
112
- constructor(searchConfig: Workspace.SearchConfig.SearchConfig) {
113
- super({useShadowDom: true});
114
- this.searchConfig = searchConfig;
115
- this.treeOutline = new UI.TreeOutline.TreeOutlineInShadow();
116
- this.treeOutline.registerRequiredCSS(searchResultsPaneStyles);
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
- });
127
-
128
- this.contentElement.appendChild(this.treeOutline.element);
129
-
130
- this.matchesExpandedCount = 0;
177
+ get searchConfig(): Workspace.SearchConfig.SearchConfig|null {
178
+ return this.#searchConfig;
131
179
  }
132
180
 
133
- addSearchResult(searchResult: SearchResult): void {
134
- this.addTreeElement(searchResult);
135
- // Expand until at least a certain number of matches is expanded.
136
- if (this.matchesExpandedCount < matchesExpandedByDefault) {
137
- this.searchResults.get(searchResult)?.expand();
138
- }
181
+ set searchConfig(searchConfig: Workspace.SearchConfig.SearchConfig|null) {
182
+ this.#searchConfig = searchConfig;
183
+ this.requestUpdate();
139
184
  }
140
185
 
141
186
  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());
187
+ for (const searchResult of this.#searchResults) {
188
+ const startMatchIndex = this.#searchMatches.get(searchResult)?.length ?? 0;
189
+ this.#appendSearchMatches(searchResult, startMatchIndex, searchResult.matchesCount());
190
+ this.#expandedResults.add(searchResult);
145
191
  }
192
+ this.requestUpdate();
146
193
  }
147
194
 
148
195
  collapseAllResults(): void {
149
- for (const treeElement of this.searchResults.values()) {
150
- treeElement.collapse();
151
- }
196
+ this.#expandedResults = new WeakSet<SearchResult>();
197
+ this.requestUpdate();
152
198
  }
153
199
 
154
- private expandSearchResult(searchResult: SearchResult): void {
200
+ #onExpandSearchResult(searchResult: SearchResult): void {
155
201
  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);
159
- } else {
160
- this.appendSearchMatches(searchResult, 0, toIndex);
161
- }
162
- this.matchesExpandedCount += toIndex;
202
+ this.#appendSearchMatches(searchResult, 0, toIndex);
163
203
  this.requestUpdate();
164
204
  }
165
205
 
166
- private collapseSearchResult(searchResult: SearchResult): void {
167
- this.searchResults.get(searchResult)?.collapse();
168
- }
169
-
170
- private appendSearchMatches(searchResult: SearchResult, fromIndex: number, toIndex: number): void {
171
- const queries = this.searchConfig.queries();
206
+ #appendSearchMatches(searchResult: SearchResult, fromIndex: number, toIndex: number): void {
207
+ if (!this.#searchConfig) {
208
+ return;
209
+ }
210
+ const queries = this.#searchConfig.queries();
172
211
  const regexes = [];
173
212
  for (let i = 0; i < queries.length; ++i) {
174
213
  regexes.push(Platform.StringUtilities.createSearchRegex(
175
- queries[i], !this.searchConfig.ignoreCase(), this.searchConfig.isRegex()));
214
+ queries[i], !this.#searchConfig.ignoreCase(), this.#searchConfig.isRegex()));
176
215
  }
177
216
 
178
- const searchMatches = this.searchMatches.get(searchResult) ?? [];
179
- this.searchMatches.set(searchResult, searchMatches);
180
- if (searchMatches.length > toIndex) {
217
+ const searchMatches = this.#searchMatches.get(searchResult) ?? [];
218
+ this.#searchMatches.set(searchResult, searchMatches);
219
+ if (searchMatches.length >= toIndex) {
181
220
  return;
182
221
  }
183
222
 
@@ -197,32 +236,48 @@ export class SearchResultsPane extends UI.Widget.VBox {
197
236
  } else {
198
237
  lineContent = lineContent.trim();
199
238
  for (let j = 0; j < regexes.length; ++j) {
200
- matchRanges = matchRanges.concat(this.regexMatchRanges(lineContent, regexes[j]));
239
+ matchRanges = matchRanges.concat(this.#regexMatchRanges(lineContent, regexes[j]));
201
240
  }
202
241
  ({lineSegment: lineContent, matchRanges} = lineSegmentForMultipleMatches(lineContent, matchRanges));
203
242
  }
204
243
 
205
244
  const resultLabel = searchResult.matchLabel(i);
206
245
  searchMatches.push({lineContent, matchRanges, resultLabel});
207
- this.appendSearchMatchElement(searchResult, searchMatches[i], i);
208
246
  }
209
247
  }
210
248
 
211
- private appendShowMoreMatchesElement(searchResult: SearchResult, startMatchIndex: number): void {
212
- const element = this.searchResults.get(searchResult);
213
- if (!element) {
214
- return;
249
+ override performUpdate(): void {
250
+ if (this.#resultsUpdated) {
251
+ let matchesExpandedCount = 0;
252
+ for (const searchResult of this.#searchResults) {
253
+ if (this.#expandedResults.has(searchResult)) {
254
+ matchesExpandedCount += this.#searchMatches.get(searchResult)?.length ?? 0;
255
+ }
256
+ }
257
+ for (const searchResult of this.#searchResults) {
258
+ if (matchesExpandedCount < matchesExpandedByDefault && !this.#expandedResults.has(searchResult)) {
259
+ this.#expandedResults.add(searchResult);
260
+ this.#onExpandSearchResult(searchResult);
261
+ matchesExpandedCount += this.#searchMatches.get(searchResult)?.length ?? 0;
262
+ }
263
+ }
264
+ this.#resultsUpdated = false;
215
265
  }
216
- const matchesLeftCount = searchResult.matchesCount() - startMatchIndex;
217
- const showMoreMatchesText = i18nString(UIStrings.showDMore, {PH1: matchesLeftCount});
218
- const showMoreMatchesTreeElement = new UI.TreeOutline.TreeElement(showMoreMatchesText);
219
- element.appendChild(showMoreMatchesTreeElement);
220
- showMoreMatchesTreeElement.listItemElement.classList.add('show-more-matches');
221
- showMoreMatchesTreeElement.onselect =
222
- this.showMoreMatchesElementSelected.bind(this, searchResult, showMoreMatchesTreeElement, startMatchIndex);
266
+ this.#view(
267
+ {
268
+ results: this.#searchResults,
269
+ matches: this.#searchMatches,
270
+ expandedResults: this.#expandedResults,
271
+ onSelectMatch: (searchResult, matchIndex) => {
272
+ void Common.Revealer.reveal(searchResult.matchRevealable(matchIndex));
273
+ },
274
+ onExpandSearchResult: this.#onExpandSearchResult.bind(this),
275
+ onShowMoreMatches: this.#onShowMoreMatches.bind(this),
276
+ },
277
+ {}, this.contentElement);
223
278
  }
224
279
 
225
- private regexMatchRanges(lineContent: string, regex: RegExp): TextUtils.TextRange.SourceRange[] {
280
+ #regexMatchRanges(lineContent: string, regex: RegExp): TextUtils.TextRange.SourceRange[] {
226
281
  regex.lastIndex = 0;
227
282
  let match;
228
283
  const matchRanges = [];
@@ -233,13 +288,10 @@ export class SearchResultsPane extends UI.Widget.VBox {
233
288
  return matchRanges;
234
289
  }
235
290
 
236
- private showMoreMatchesElementSelected(
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;
291
+ #onShowMoreMatches(searchResult: SearchResult): void {
292
+ const startMatchIndex = this.#searchMatches.get(searchResult)?.length ?? 0;
293
+ this.#appendSearchMatches(searchResult, startMatchIndex, searchResult.matchesCount());
294
+ this.requestUpdate();
243
295
  }
244
296
  }
245
297