chrome-devtools-frontend 1.0.1519267 → 1.0.1520535

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/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/front_end/core/host/GdpClient.ts +26 -5
  5. package/front_end/core/sdk/NetworkManager.ts +1 -0
  6. package/front_end/core/sdk/NetworkRequest.ts +10 -0
  7. package/front_end/entrypoints/main/MainImpl.ts +6 -1
  8. package/front_end/entrypoints/main/main-meta.ts +3 -3
  9. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +50 -48
  10. package/front_end/models/ai_assistance/agents/PerformanceAnnotationsAgent.ts +4 -4
  11. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +128 -30
  12. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +98 -63
  13. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +317 -640
  14. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +36 -21
  15. package/front_end/models/ai_assistance/performance/AICallTree.snapshot.txt +75 -0
  16. package/front_end/models/ai_assistance/performance/AICallTree.ts +14 -6
  17. package/front_end/models/ai_assistance/performance/AIContext.ts +62 -7
  18. package/front_end/models/ai_code_completion/AiCodeCompletion.ts +5 -5
  19. package/front_end/models/badges/Badge.ts +6 -1
  20. package/front_end/models/badges/StarterBadge.ts +5 -0
  21. package/front_end/models/badges/UserBadges.ts +5 -4
  22. package/front_end/models/javascript_metadata/NativeFunctions.js +5 -1
  23. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +14 -7
  24. package/front_end/panels/ai_assistance/PatchWidget.ts +17 -55
  25. package/front_end/panels/ai_assistance/components/ChatView.ts +45 -69
  26. package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +47 -1
  27. package/front_end/panels/ai_assistance/components/chatView.css +13 -1
  28. package/front_end/panels/animation/AnimationTimeline.ts +1 -1
  29. package/front_end/panels/animation/animationTimeline.css +4 -0
  30. package/front_end/panels/application/preloading/components/PreloadingString.ts +2 -5
  31. package/front_end/panels/common/AiCodeCompletionTeaser.ts +5 -0
  32. package/front_end/panels/common/aiCodeCompletionTeaser.css +6 -1
  33. package/front_end/panels/console/ConsolePrompt.ts +6 -0
  34. package/front_end/panels/console/ConsoleView.ts +4 -2
  35. package/front_end/panels/coverage/CoverageListView.ts +146 -198
  36. package/front_end/panels/coverage/CoverageView.ts +48 -18
  37. package/front_end/panels/mobile_throttling/NetworkThrottlingSelector.ts +2 -0
  38. package/front_end/panels/network/NetworkDataGridNode.ts +22 -0
  39. package/front_end/panels/network/NetworkLogViewColumns.ts +9 -0
  40. package/front_end/panels/recorder/components/CreateRecordingView.ts +2 -0
  41. package/front_end/panels/search/SearchResultsPane.ts +48 -15
  42. package/front_end/panels/search/SearchView.ts +33 -30
  43. package/front_end/panels/search/searchView.css +0 -2
  44. package/front_end/panels/settings/components/SyncSection.ts +4 -4
  45. package/front_end/panels/sources/AiCodeCompletionPlugin.ts +1 -4
  46. package/front_end/panels/sources/DebuggerPlugin.ts +4 -0
  47. package/front_end/panels/timeline/ThirdPartyTreeView.ts +1 -1
  48. package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +0 -8
  49. package/front_end/panels/timeline/TimelineFlameChartView.ts +5 -5
  50. package/front_end/panels/timeline/TimelinePanel.ts +2 -0
  51. package/front_end/panels/timeline/TimelineTreeView.ts +1 -1
  52. package/front_end/panels/timeline/components/ExportTraceOptions.ts +56 -4
  53. package/front_end/panels/timeline/components/exportTraceOptions.css +5 -0
  54. package/front_end/third_party/chromium/README.chromium +1 -1
  55. package/front_end/third_party/lighthouse/README.chromium +8 -1
  56. package/front_end/third_party/puppeteer/README.chromium +2 -2
  57. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Realm.d.ts +2 -2
  58. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.d.ts +1 -1
  59. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.js +1 -1
  60. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
  61. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/BrowserLauncher.js +1 -1
  62. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/BrowserLauncher.js.map +1 -1
  63. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/PipeTransport.d.ts.map +1 -1
  64. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/PipeTransport.js +15 -16
  65. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/PipeTransport.js.map +1 -1
  66. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +2 -2
  67. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +2 -2
  68. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Function.js +1 -1
  69. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Function.js.map +1 -1
  70. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  71. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +4 -4
  72. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.d.ts +1 -1
  73. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.js +1 -1
  74. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/BrowserLauncher.js +1 -1
  75. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/BrowserLauncher.js.map +1 -1
  76. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/PipeTransport.d.ts.map +1 -1
  77. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/PipeTransport.js +15 -16
  78. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/PipeTransport.js.map +1 -1
  79. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +2 -2
  80. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +2 -2
  81. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/Function.js +1 -1
  82. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/Function.js.map +1 -1
  83. package/front_end/third_party/puppeteer/package/package.json +3 -2
  84. package/front_end/third_party/puppeteer/package/src/generated/version.ts +1 -1
  85. package/front_end/third_party/puppeteer/package/src/node/BrowserLauncher.ts +1 -1
  86. package/front_end/third_party/puppeteer/package/src/node/PipeTransport.ts +15 -17
  87. package/front_end/third_party/puppeteer/package/src/revisions.ts +2 -2
  88. package/front_end/third_party/puppeteer/package/src/util/Function.ts +1 -1
  89. package/front_end/tsconfig.json +12 -1
  90. package/front_end/ui/legacy/InspectorView.ts +86 -13
  91. package/front_end/ui/legacy/TabbedPane.ts +2 -1
  92. package/front_end/ui/visual_logging/KnownContextValues.ts +6 -0
  93. package/front_end/ui/visual_logging/LoggingEvents.ts +1 -1
  94. 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 {
@@ -136,16 +136,16 @@ const renderSearchMatches =
136
136
  };
137
137
 
138
138
  export class SearchResultsPane extends UI.Widget.VBox {
139
- readonly #searchConfig: Workspace.SearchConfig.SearchConfig;
139
+ #searchConfig: Workspace.SearchConfig.SearchConfig|null = null;
140
140
  #searchResults: SearchResult[] = [];
141
+ #resultsUpdated = false;
141
142
  #expandedResults = new WeakSet<SearchResult>();
142
143
  readonly #searchMatches = new WeakMap<SearchResult, SearchMatch[]>();
143
144
  #view: View;
144
145
 
145
- constructor(searchConfig: Workspace.SearchConfig.SearchConfig, view: View = DEFAULT_VIEW) {
146
- super({useShadowDom: true});
146
+ constructor(element: HTMLElement|undefined, view: View = DEFAULT_VIEW) {
147
+ super(element, {useShadowDom: true});
147
148
  this.#view = view;
148
- this.#searchConfig = searchConfig;
149
149
  }
150
150
 
151
151
  get searchResults(): SearchResult[] {
@@ -153,20 +153,33 @@ export class SearchResultsPane extends UI.Widget.VBox {
153
153
  }
154
154
 
155
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
- }
156
+ if (this.#searchResults === searchResults) {
157
+ return;
162
158
  }
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;
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;
168
168
  }
169
169
  }
170
+ if (!this.#resultsUpdated) {
171
+ return;
172
+ }
173
+ this.#searchResults = searchResults;
174
+ this.requestUpdate();
175
+ }
176
+
177
+ get searchConfig(): Workspace.SearchConfig.SearchConfig|null {
178
+ return this.#searchConfig;
179
+ }
180
+
181
+ set searchConfig(searchConfig: Workspace.SearchConfig.SearchConfig|null) {
182
+ this.#searchConfig = searchConfig;
170
183
  this.requestUpdate();
171
184
  }
172
185
 
@@ -174,6 +187,7 @@ export class SearchResultsPane extends UI.Widget.VBox {
174
187
  for (const searchResult of this.#searchResults) {
175
188
  const startMatchIndex = this.#searchMatches.get(searchResult)?.length ?? 0;
176
189
  this.#appendSearchMatches(searchResult, startMatchIndex, searchResult.matchesCount());
190
+ this.#expandedResults.add(searchResult);
177
191
  }
178
192
  this.requestUpdate();
179
193
  }
@@ -190,6 +204,9 @@ export class SearchResultsPane extends UI.Widget.VBox {
190
204
  }
191
205
 
192
206
  #appendSearchMatches(searchResult: SearchResult, fromIndex: number, toIndex: number): void {
207
+ if (!this.#searchConfig) {
208
+ return;
209
+ }
193
210
  const queries = this.#searchConfig.queries();
194
211
  const regexes = [];
195
212
  for (let i = 0; i < queries.length; ++i) {
@@ -230,6 +247,22 @@ export class SearchResultsPane extends UI.Widget.VBox {
230
247
  }
231
248
 
232
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;
265
+ }
233
266
  this.#view(
234
267
  {
235
268
  results: this.#searchResults,
@@ -107,15 +107,16 @@ const UIStrings = {
107
107
  const str_ = i18n.i18n.registerUIStrings('panels/search/SearchView.ts', UIStrings);
108
108
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
109
109
  const {ref, live} = Directives;
110
- const {widgetConfig} = UI.Widget;
110
+ const {widgetConfig, widgetRef} = UI.Widget;
111
111
 
112
- interface SearchViewInput {
112
+ export interface SearchViewInput {
113
113
  query: string;
114
114
  matchCase: boolean;
115
115
  isRegex: boolean;
116
+ searchConfig: Workspace.SearchConfig.SearchConfig|null;
116
117
  searchMessage: string;
117
118
  searchResultsMessage: string;
118
- searchResultsPane: SearchResultsPane|null;
119
+ searchResults: SearchResult[];
119
120
  progress: Common.Progress.Progress|null;
120
121
  onQueryChange: (query: string) => void;
121
122
  onQueryKeyDown: (evt: KeyboardEvent) => void;
@@ -127,19 +128,22 @@ interface SearchViewInput {
127
128
  onClearSearch: () => void;
128
129
  }
129
130
 
130
- interface SearchViewOutput {
131
+ export interface SearchViewOutput {
131
132
  focusSearchInput: () => void;
133
+ showAllMatches: () => void;
134
+ collapseAllResults: () => void;
132
135
  }
133
136
 
134
- type View = (input: SearchViewInput, output: SearchViewOutput, target: HTMLElement) => void;
137
+ export type View = (input: SearchViewInput, output: SearchViewOutput, target: HTMLElement) => void;
135
138
 
136
139
  export const DEFAULT_VIEW: View = (input, output, target) => {
137
140
  const {
138
141
  query,
139
142
  matchCase,
140
143
  isRegex,
144
+ searchConfig,
141
145
  searchMessage,
142
- searchResultsPane,
146
+ searchResults,
143
147
  searchResultsMessage,
144
148
  progress,
145
149
  onQueryChange,
@@ -159,12 +163,13 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
159
163
  {PH1: UI.KeyboardShortcut.KeyboardShortcut.shortcutToString(UI.KeyboardShortcut.Keys.Enter)});
160
164
  } else if (progress) {
161
165
  header = i18nString(UIStrings.searching);
162
- } else if (!searchResultsPane) {
166
+ } else if (!searchResults.length) {
163
167
  header = i18nString(UIStrings.noMatchesFound);
164
168
  text = i18nString(UIStrings.nothingMatchedTheQuery);
165
169
  }
166
170
  // clang-format off
167
171
  render(html`
172
+ <style>${UI.inspectorCommonStyles}</style>
168
173
  <style>${searchViewStyles}</style>
169
174
  <div class="search-drawer-header" @keydown=${onPanelKeyDown}>
170
175
  <div class="search-container">
@@ -240,9 +245,11 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
240
245
  </devtools-toolbar>
241
246
  </div>
242
247
  <div class="search-results" @keydown=${onPanelKeyDown}>
243
- ${searchResultsPane
244
- ? html`<devtools-widget .widgetConfig=${widgetConfig(UI.Widget.VBox)}>
245
- ${searchResultsPane.element}
248
+ ${searchResults.length
249
+ ? html`<devtools-widget .widgetConfig=${widgetConfig(SearchResultsPane, {searchResults, searchConfig})}
250
+ ${widgetRef(SearchResultsPane, w => {
251
+ output.showAllMatches = () => void w.showAllMatches();
252
+ output.collapseAllResults = () => void w.collapseAllResults(); })}>
246
253
  </devtools-widget>`
247
254
  : html`<devtools-widget .widgetConfig=${widgetConfig(UI.EmptyWidget.EmptyWidget, {header, text})}>
248
255
  </devtools-widget>`}
@@ -263,6 +270,8 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
263
270
  export class SearchView extends UI.Widget.VBox {
264
271
  readonly #view: View;
265
272
  #focusSearchInput = (): void => {};
273
+ #showAllMatches = (): void => {};
274
+ #collapseAllResults = (): void => {};
266
275
  #isIndexing: boolean;
267
276
  #searchId: number;
268
277
  #searchMatchesCount: number;
@@ -271,7 +280,6 @@ export class SearchView extends UI.Widget.VBox {
271
280
  #searchingView: UI.Widget.Widget|null;
272
281
  #searchConfig: Workspace.SearchConfig.SearchConfig|null;
273
282
  #pendingSearchConfig: Workspace.SearchConfig.SearchConfig|null;
274
- #searchResultsPane: SearchResultsPane|null;
275
283
  #progress: Common.Progress.Progress|null;
276
284
  #query: string;
277
285
  #matchCase = false;
@@ -307,7 +315,6 @@ export class SearchView extends UI.Widget.VBox {
307
315
  this.#searchingView = null;
308
316
  this.#searchConfig = null;
309
317
  this.#pendingSearchConfig = null;
310
- this.#searchResultsPane = null;
311
318
  this.#progress = null;
312
319
  this.#throttler = throttler;
313
320
 
@@ -325,8 +332,9 @@ export class SearchView extends UI.Widget.VBox {
325
332
  query: this.#query,
326
333
  matchCase: this.#matchCase,
327
334
  isRegex: this.#isRegex,
335
+ searchConfig: this.#searchConfig,
328
336
  searchMessage: this.#searchMessage,
329
- searchResultsPane: this.#searchResultsPane,
337
+ searchResults: this.#searchResults.filter(searchResult => searchResult.matchesCount()),
330
338
  searchResultsMessage: this.#searchResultsMessage,
331
339
  progress: this.#progress,
332
340
  onQueryChange: (query: string) => {
@@ -344,6 +352,12 @@ export class SearchView extends UI.Widget.VBox {
344
352
  const output: SearchViewOutput = {
345
353
  set focusSearchInput(value: () => void) {
346
354
  that.#focusSearchInput = value;
355
+ },
356
+ set showAllMatches(value: () => void) {
357
+ that.#showAllMatches = value;
358
+ },
359
+ set collapseAllResults(value: () => void) {
360
+ that.#collapseAllResults = value;
347
361
  }
348
362
  };
349
363
  this.#view(input, output, this.contentElement);
@@ -436,17 +450,10 @@ export class SearchView extends UI.Widget.VBox {
436
450
  this.#onIndexingFinished();
437
451
  return;
438
452
  }
439
- if (!this.#searchResultsPane) {
440
- this.#searchResultsPane = this.createSearchResultsPane();
441
- }
442
453
  this.#searchResults.push(searchResult);
443
454
  void this.#throttler.schedule(async () => this.#setSearchResults());
444
455
  }
445
456
 
446
- protected createSearchResultsPane(): SearchResultsPane {
447
- return new SearchResultsPane((this.#searchConfig as Workspace.SearchConfig.SearchConfig));
448
- }
449
-
450
457
  #setSearchResults(): void {
451
458
  this.#searchMatchesCount = 0;
452
459
  this.#searchResultsCount = 0;
@@ -454,9 +461,7 @@ export class SearchView extends UI.Widget.VBox {
454
461
  for (const searchResult of this.#searchResults) {
455
462
  this.#addSearchResult(searchResult);
456
463
  }
457
- if (this.#searchResultsPane) {
458
- this.#searchResultsPane.searchResults = this.#searchResults.filter(searchResult => searchResult.matchesCount());
459
- }
464
+ this.performUpdate();
460
465
  }
461
466
 
462
467
  #onSearchFinished(searchId: number, finished: boolean): void {
@@ -465,7 +470,6 @@ export class SearchView extends UI.Widget.VBox {
465
470
  }
466
471
  this.#progress = null;
467
472
  this.#searchFinished(finished);
468
- this.#searchConfig = null;
469
473
  UI.ARIAUtils.LiveAnnouncer.alert(this.#searchMessage + ' ' + this.#searchResultsMessage);
470
474
  }
471
475
 
@@ -486,7 +490,7 @@ export class SearchView extends UI.Widget.VBox {
486
490
 
487
491
  #resetSearch(): void {
488
492
  this.#stopSearch();
489
- this.#searchResultsPane = null;
493
+ this.#searchResults = [];
490
494
  this.#searchMessage = '';
491
495
  this.#searchResultsMessage = '';
492
496
  this.performUpdate();
@@ -499,7 +503,6 @@ export class SearchView extends UI.Widget.VBox {
499
503
  if (this.#searchScope) {
500
504
  this.#searchScope.stopSearch();
501
505
  }
502
- this.#searchConfig = null;
503
506
  }
504
507
 
505
508
  #searchStarted(): void {
@@ -544,7 +547,7 @@ export class SearchView extends UI.Widget.VBox {
544
547
 
545
548
  #searchFinished(finished: boolean): void {
546
549
  this.#searchMessage = finished ? i18nString(UIStrings.searchFinished) : i18nString(UIStrings.searchInterrupted);
547
- this.performUpdate();
550
+ this.requestUpdate();
548
551
  }
549
552
 
550
553
  override focus(): void {
@@ -593,15 +596,15 @@ export class SearchView extends UI.Widget.VBox {
593
596
  // "Command + Alt + [" for Mac
594
597
  const shouldCollapseAllForMac =
595
598
  isMac && event.metaKey && !event.ctrlKey && event.altKey && event.code === 'BracketLeft';
596
- // "Command + Alt + {" for other platforms
599
+ // "Ctrl + Alt + {" for other platforms
597
600
  const shouldCollapseAllForOtherPlatforms =
598
601
  !isMac && event.ctrlKey && !event.metaKey && event.shiftKey && event.code === 'BracketLeft';
599
602
 
600
603
  if (shouldShowAllForMac || shouldShowAllForOtherPlatforms) {
601
- this.#searchResultsPane?.showAllMatches();
604
+ this.#showAllMatches();
602
605
  void VisualLogging.logKeyDown(event.currentTarget, event, 'show-all-matches');
603
606
  } else if (shouldCollapseAllForMac || shouldCollapseAllForOtherPlatforms) {
604
- this.#searchResultsPane?.collapseAllResults();
607
+ this.#collapseAllResults();
605
608
  void VisualLogging.logKeyDown(event.currentTarget, event, 'collapse-all-results');
606
609
  }
607
610
  }
@@ -5,7 +5,6 @@
5
5
  */
6
6
 
7
7
  .search-drawer-header {
8
- align-items: center;
9
8
  flex-shrink: 0;
10
9
  overflow: hidden;
11
10
  display: inline-flex;
@@ -14,7 +13,6 @@
14
13
  .search-container {
15
14
  border-bottom: 1px solid var(--sys-color-divider);
16
15
  display: flex;
17
- height: 100%;
18
16
  align-items: center;
19
17
  flex-grow: 1;
20
18
  }
@@ -31,10 +31,10 @@ const UIStrings = {
31
31
  */
32
32
  syncDisabled: 'To turn this setting on, you must enable Chrome sync.',
33
33
  /**
34
- * @description Text shown to the user in the Settings UI. 'This setting' refers
35
- * to a checkbox that is disabled.
34
+ * @description Text shown to the user in the Settings UI. Explains why the checkbox
35
+ * for saving DevTools settings to the user's Google account is inactive.
36
36
  */
37
- preferencesSyncDisabled: 'To turn this setting on, you must first enable settings sync in Chrome.',
37
+ preferencesSyncDisabled: 'You need to first enable saving `Chrome` settings in your `Google` account.',
38
38
  /**
39
39
  * @description Label for the account email address. Shown in the DevTools Settings UI in
40
40
  * front of the email address currently used for Chrome Sync.
@@ -270,7 +270,7 @@ function renderWarningIfNeeded(syncInfo: Host.InspectorFrontendHostAPI.SyncInfor
270
270
  return html`
271
271
  <devtools-button
272
272
  aria-describedby=settings-sync-info
273
- aria-label=${warningText}
273
+ .title=${warningText}
274
274
  .iconName=${'info'}
275
275
  .variant=${Buttons.Button.Variant.ICON}
276
276
  .size=${Buttons.Button.Size.SMALL}
@@ -103,6 +103,7 @@ export class AiCodeCompletionPlugin extends Plugin {
103
103
  if (this.#teaser) {
104
104
  if (update.docChanged) {
105
105
  update.view.dispatch({effects: this.#teaserCompartment.reconfigure([])});
106
+ window.clearTimeout(this.#teaserDisplayTimeout);
106
107
  this.#addTeaserPluginToCompartment(update.view);
107
108
  } else if (update.selectionSet && update.state.doc.length > 0) {
108
109
  update.view.dispatch({effects: this.#teaserCompartment.reconfigure([])});
@@ -187,10 +188,6 @@ export class AiCodeCompletionPlugin extends Plugin {
187
188
  }
188
189
 
189
190
  #addTeaserPluginToCompartment = Common.Debouncer.debounce((view: CodeMirror.EditorView) => {
190
- if (this.#teaserDisplayTimeout) {
191
- window.clearTimeout(this.#teaserDisplayTimeout);
192
- this.#teaserDisplayTimeout = undefined;
193
- }
194
191
  this.#teaserDisplayTimeout = window.setTimeout(() => {
195
192
  this.#addTeaserPluginToCompartmentImmediate(view);
196
193
  }, AiCodeCompletion.AiCodeCompletion.DELAY_BEFORE_SHOWING_RESPONSE_MS);
@@ -10,6 +10,7 @@ import * as i18n from '../../core/i18n/i18n.js';
10
10
  import * as Platform from '../../core/platform/platform.js';
11
11
  import * as SDK from '../../core/sdk/sdk.js';
12
12
  import * as Protocol from '../../generated/protocol.js';
13
+ import * as Badges from '../../models/badges/badges.js';
13
14
  import * as Bindings from '../../models/bindings/bindings.js';
14
15
  import * as Breakpoints from '../../models/breakpoints/breakpoints.js';
15
16
  import * as Formatter from '../../models/formatter/formatter.js';
@@ -1628,6 +1629,9 @@ export class DebuggerPlugin extends Plugin {
1628
1629
  this.uiSourceCode, lineNumber, columnNumber, condition, enabled, isLogpoint,
1629
1630
  Breakpoints.BreakpointManager.BreakpointOrigin.USER_ACTION);
1630
1631
  this.breakpointWasSetForTest(lineNumber, columnNumber, condition, enabled);
1632
+ if (bp) {
1633
+ Badges.UserBadges.instance().recordAction(Badges.BadgeAction.BREAKPOINT_ADDED);
1634
+ }
1631
1635
  return bp;
1632
1636
  }
1633
1637
 
@@ -226,7 +226,7 @@ export class ThirdPartyTreeViewWidget extends TimelineTreeView.TimelineTreeView
226
226
  const unattributed = i18nString(UIStrings.unattributed);
227
227
  const id = typeof node.id === 'symbol' ? undefined : node.id;
228
228
  // This `undefined` is [unattributed]
229
- // TODO(paulirish,aixba): Improve attribution to reduce amount of items in [unattributed].
229
+ // TODO(paulirish): Improve attribution to reduce amount of items in [unattributed].
230
230
  const domainName = id ? this.entityMapper()?.entityForEvent(node.event)?.name || id : undefined;
231
231
  return {name: domainName || unattributed, color, icon: undefined};
232
232
  }
@@ -254,14 +254,6 @@ export class TimelineFlameChartDataProvider extends Common.ObjectWrapper.ObjectW
254
254
  if (perfAIEntryPointEnabled && this.parsedTrace) {
255
255
  const callTree = AIAssistance.AICallTree.fromEvent(entry, this.parsedTrace);
256
256
  if (callTree) {
257
- let focus = UI.Context.Context.instance().flavor(AIAssistance.AgentFocus);
258
- if (focus) {
259
- focus = focus.withCallTree(callTree);
260
- } else {
261
- focus = AIAssistance.AgentFocus.fromCallTree(callTree);
262
- }
263
- UI.Context.Context.instance().setFlavor(AIAssistance.AgentFocus, focus);
264
-
265
257
  const action = UI.ActionRegistry.ActionRegistry.instance().getAction(PERF_AI_ACTION_ID);
266
258
 
267
259
  if (Root.Runtime.hostConfig.devToolsAiSubmenuPrompts?.enabled) {
@@ -1516,13 +1516,13 @@ export class TimelineFlameChartView extends Common.ObjectWrapper.eventMixin<Even
1516
1516
  return;
1517
1517
  }
1518
1518
 
1519
- const callTree =
1520
- selectionIsEvent(selection) ? AIAssistance.AICallTree.fromEvent(selection.event, this.#parsedTrace) : null;
1519
+ const event = selectionIsEvent(selection) ? selection.event : null;
1520
+
1521
1521
  let focus = UI.Context.Context.instance().flavor(AIAssistance.AgentFocus);
1522
1522
  if (focus) {
1523
- focus = focus.withCallTree(callTree);
1524
- } else if (callTree) {
1525
- focus = AIAssistance.AgentFocus.fromCallTree(callTree);
1523
+ focus = focus.withEvent(event);
1524
+ } else if (event) {
1525
+ focus = AIAssistance.AgentFocus.fromEvent(this.#parsedTrace, event);
1526
1526
  } else {
1527
1527
  focus = null;
1528
1528
  }
@@ -44,6 +44,7 @@ import * as Root from '../../core/root/root.js';
44
44
  import * as SDK from '../../core/sdk/sdk.js';
45
45
  import type * as Protocol from '../../generated/protocol.js';
46
46
  import * as AiAssistanceModel from '../../models/ai_assistance/ai_assistance.js';
47
+ import * as Badges from '../../models/badges/badges.js';
47
48
  import * as CrUXManager from '../../models/crux-manager/crux-manager.js';
48
49
  import * as TextUtils from '../../models/text_utils/text_utils.js';
49
50
  import * as Trace from '../../models/trace/trace.js';
@@ -1924,6 +1925,7 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
1924
1925
  } else {
1925
1926
  await this.#startTraceRecording();
1926
1927
  }
1928
+ Badges.UserBadges.instance().recordAction(Badges.BadgeAction.PERFORMANCE_RECORDING_STARTED);
1927
1929
  }
1928
1930
 
1929
1931
  private async stopRecording(): Promise<void> {
@@ -1005,7 +1005,7 @@ export class AggregatedTimelineTreeView extends TimelineTreeView {
1005
1005
  case AggregatedTimelineTreeView.GroupBy.Subdomain:
1006
1006
  case AggregatedTimelineTreeView.GroupBy.ThirdParties: {
1007
1007
  // This `undefined` is [unattributed]
1008
- // TODO(paulirish,aixba): Improve attribution to reduce amount of items in [unattributed].
1008
+ // TODO(paulirish): Improve attribution to reduce amount of items in [unattributed].
1009
1009
  const domainName = id ? this.beautifyDomainName(id, node) : undefined;
1010
1010
  return {name: domainName || unattributed, color, icon: undefined};
1011
1011
  }
@@ -3,6 +3,9 @@
3
3
  // found in the LICENSE file.
4
4
  /* eslint-disable rulesdir/no-lit-render-outside-of-view */
5
5
 
6
+ import '../../../ui/components/tooltips/tooltips.js';
7
+ import '../../../ui/components/buttons/buttons.js';
8
+
6
9
  import * as Common from '../../../core/common/common.js';
7
10
  import * as Host from '../../../core/host/host.js';
8
11
  import * as i18n from '../../../core/i18n/i18n.js';
@@ -46,6 +49,18 @@ const UIStrings = {
46
49
  * @description Text for the save trace button
47
50
  */
48
51
  saveButtonTitle: 'Save',
52
+ /**
53
+ * @description Title for the information icon showing more information about an option
54
+ */
55
+ moreInfoTitle: 'More information',
56
+ /**
57
+ * @description Text shown in the information pop-up next to the "Include script content" option.
58
+ */
59
+ scriptContentPrivacyInfo: 'Includes the full content of all loaded scripts (except extensions).',
60
+ /**
61
+ * @description Text shown in the information pop-up next to the "Include script sourcemaps" option.
62
+ */
63
+ sourceMapsContentPrivacyInfo: 'Includes available source maps, which may expose authored code.'
49
64
  } as const;
50
65
 
51
66
  const str_ = i18n.i18n.registerUIStrings('panels/timeline/components/ExportTraceOptions.ts', UIStrings);
@@ -74,6 +89,9 @@ export interface ExportTraceOptionsState {
74
89
  displaySourceMapsCheckbox?: boolean;
75
90
  }
76
91
 
92
+ type CheckboxId = 'annotations'|'script-content'|'script-source-maps'|'compress-with-gzip';
93
+ const checkboxesWithInfoDialog = new Set<CheckboxId>(['script-content', 'script-source-maps']);
94
+
77
95
  export class ExportTraceOptions extends HTMLElement {
78
96
  readonly #shadow = this.attachShadow({mode: 'open'});
79
97
  #data: ExportTraceOptionsData|null = null;
@@ -187,7 +205,7 @@ export class ExportTraceOptions extends HTMLElement {
187
205
  }
188
206
 
189
207
  #renderCheckbox(
190
- checkboxWithLabel: UI.UIUtils.CheckboxLabel, title: Common.UIString.LocalizedString,
208
+ checkboxId: CheckboxId, checkboxWithLabel: UI.UIUtils.CheckboxLabel, title: Common.UIString.LocalizedString,
191
209
  checked: boolean): Lit.TemplateResult {
192
210
  UI.Tooltip.Tooltip.install(checkboxWithLabel, title);
193
211
  checkboxWithLabel.ariaLabel = title;
@@ -202,11 +220,40 @@ export class ExportTraceOptions extends HTMLElement {
202
220
  return html`
203
221
  <div class='export-trace-options-row'>
204
222
  ${checkboxWithLabel}
223
+
224
+ ${checkboxesWithInfoDialog.has(checkboxId) ? html`
225
+ <devtools-button
226
+ aria-details=${`export-trace-tooltip-${checkboxId}`}
227
+ class="pen-icon"
228
+ .title=${UIStrings.moreInfoTitle}
229
+ .iconName=${'info'}
230
+ .variant=${Buttons.Button.Variant.ICON}
231
+ ></devtools-button>
232
+ ` : Lit.nothing}
205
233
  </div>
206
234
  `;
207
235
  // clang-format on
208
236
  }
209
237
 
238
+ #renderInfoTooltip(checkboxId: CheckboxId): Lit.LitTemplate {
239
+ if (!checkboxesWithInfoDialog.has(checkboxId)) {
240
+ return Lit.nothing;
241
+ }
242
+
243
+ return html`
244
+ <devtools-tooltip
245
+ variant="rich"
246
+ id=${`export-trace-tooltip-${checkboxId}`}
247
+ >
248
+ <div class="info-tooltip-container">
249
+ <p>
250
+ ${checkboxId === 'script-content' ? i18nString(UIStrings.scriptContentPrivacyInfo) : Lit.nothing}
251
+ ${checkboxId === 'script-source-maps' ? i18nString(UIStrings.sourceMapsContentPrivacyInfo) : Lit.nothing}
252
+ </p>
253
+ </div>
254
+ </devtools-tooltip>`;
255
+ }
256
+
210
257
  #render(): void {
211
258
  if (!ComponentHelpers.ScheduledRender.isScheduledRender(this)) {
212
259
  throw new Error('Export trace options dialog render was not scheduled');
@@ -230,16 +277,18 @@ export class ExportTraceOptions extends HTMLElement {
230
277
  state: this.#state.dialogState,
231
278
  } as Dialogs.ButtonDialog.ButtonDialogData}>
232
279
  <div class='export-trace-options-content'>
233
- ${this.#state.displayAnnotationsCheckbox ? this.#renderCheckbox(this.#includeAnnotationsCheckbox,
280
+ ${this.#state.displayAnnotationsCheckbox ? this.#renderCheckbox('annotations', this.#includeAnnotationsCheckbox,
234
281
  i18nString(UIStrings.includeAnnotations),
235
282
  this.#state.includeAnnotations): ''}
236
- ${this.#state.displayScriptContentCheckbox ? this.#renderCheckbox(this.#includeScriptContentCheckbox,
283
+ ${this.#state.displayScriptContentCheckbox ? this.#renderCheckbox('script-content', this.#includeScriptContentCheckbox,
237
284
  i18nString(UIStrings.includeScriptContent), this.#state.includeScriptContent): ''}
238
285
  ${this.#state.displayScriptContentCheckbox && this.#state.displaySourceMapsCheckbox ? this.#renderCheckbox(
286
+ 'script-source-maps',
239
287
  this.#includeSourceMapsCheckbox, i18nString(UIStrings.includeSourcemap), this.#state.includeSourceMaps): ''}
240
- ${this.#renderCheckbox(this.#shouldCompressCheckbox, i18nString(UIStrings.shouldCompress), this.#state.shouldCompress)}
288
+ ${this.#renderCheckbox('compress-with-gzip', this.#shouldCompressCheckbox, i18nString(UIStrings.shouldCompress), this.#state.shouldCompress)}
241
289
  <div class='export-trace-options-row'><div class='export-trace-blank'></div><devtools-button
242
290
  class="setup-button"
291
+ data-export-button
243
292
  @click=${this.#onExportClick.bind(this)}
244
293
  .data=${{
245
294
  variant: Buttons.Button.Variant.PRIMARY,
@@ -249,6 +298,9 @@ export class ExportTraceOptions extends HTMLElement {
249
298
  </div>
250
299
  </div>
251
300
  </devtools-button-dialog>
301
+
302
+ ${this.#state.displayScriptContentCheckbox ? this.#renderInfoTooltip('script-content') : Lit.nothing}
303
+ ${this.#state.displayScriptContentCheckbox && this.#state.displaySourceMapsCheckbox ? this.#renderInfoTooltip('script-source-maps') : Lit.nothing}
252
304
  `;
253
305
  // clang-format on
254
306
  Lit.render(output, this.#shadow, {host: this});
@@ -24,3 +24,8 @@
24
24
  min-width: var(--sys-size-25)
25
25
  }
26
26
  }
27
+
28
+ .info-tooltip-container {
29
+ max-width: var(--sys-size-28);
30
+ white-space: normal;
31
+ }