chrome-devtools-frontend 1.0.1521880 → 1.0.1522585

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 (59) hide show
  1. package/docs/ui_engineering.md +76 -0
  2. package/front_end/core/host/GdpClient.ts +116 -66
  3. package/front_end/core/root/Runtime.ts +1 -0
  4. package/front_end/core/sdk/EnhancedTracesParser.ts +13 -6
  5. package/front_end/entrypoints/inspector_main/InspectorMain.ts +82 -32
  6. package/front_end/entrypoints/inspector_main/inspector_main-meta.ts +1 -1
  7. package/front_end/entrypoints/main/MainImpl.ts +7 -1
  8. package/front_end/generated/Deprecation.ts +4 -4
  9. package/front_end/models/ai_assistance/agents/NetworkAgent.ts +10 -6
  10. package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +559 -0
  11. package/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts +42 -4
  12. package/front_end/models/badges/UserBadges.ts +14 -16
  13. package/front_end/models/javascript_metadata/NativeFunctions.js +1 -1
  14. package/front_end/models/trace/LanternComputationData.ts +1 -0
  15. package/front_end/models/trace/handlers/NetworkRequestsHandler.ts +10 -0
  16. package/front_end/models/trace/insights/DocumentLatency.ts +9 -10
  17. package/front_end/models/trace/types/TraceEvents.ts +6 -5
  18. package/front_end/panels/ai_assistance/components/UserActionRow.ts +1 -2
  19. package/front_end/panels/application/IndexedDBViews.ts +1 -0
  20. package/front_end/panels/application/ReportingApiTreeElement.ts +1 -2
  21. package/front_end/panels/application/ReportingApiView.ts +18 -20
  22. package/front_end/panels/application/ServiceWorkerCacheViews.ts +3 -0
  23. package/front_end/panels/application/components/EndpointsGrid.ts +51 -59
  24. package/front_end/panels/application/components/ReportsGrid.ts +86 -107
  25. package/front_end/panels/application/components/StorageMetadataView.ts +30 -4
  26. package/front_end/panels/application/components/endpointsGrid.css +30 -0
  27. package/front_end/panels/application/components/reportsGrid.css +34 -0
  28. package/front_end/panels/application/components/storageMetadataView.css +9 -0
  29. package/front_end/panels/browser_debugger/CategorizedBreakpointsSidebarPane.ts +19 -27
  30. package/front_end/panels/common/BadgeNotification.ts +10 -3
  31. package/front_end/panels/network/NetworkPanel.ts +1 -1
  32. package/front_end/panels/protocol_monitor/ProtocolMonitor.ts +31 -32
  33. package/front_end/panels/search/SearchResultsPane.ts +14 -13
  34. package/front_end/panels/search/SearchView.ts +3 -20
  35. package/front_end/panels/settings/components/SyncSection.ts +8 -6
  36. package/front_end/panels/sources/SearchSourcesView.ts +1 -1
  37. package/front_end/panels/timeline/TimelineFlameChartView.ts +17 -0
  38. package/front_end/panels/timeline/TimelinePanel.ts +5 -0
  39. package/front_end/panels/timeline/TimelineUIUtils.ts +12 -3
  40. package/front_end/panels/timeline/components/ExportTraceOptions.ts +21 -9
  41. package/front_end/panels/timeline/timelineDetailsView.css +5 -0
  42. package/front_end/panels/whats_new/ReleaseNoteText.ts +15 -11
  43. package/front_end/panels/whats_new/resources/WNDT.md +9 -6
  44. package/front_end/third_party/chromium/README.chromium +1 -1
  45. package/front_end/third_party/diff/README.chromium +0 -1
  46. package/front_end/ui/components/tooltips/Tooltip.ts +13 -4
  47. package/front_end/ui/legacy/Treeoutline.ts +6 -9
  48. package/front_end/ui/legacy/UIUtils.ts +4 -17
  49. package/front_end/ui/legacy/Widget.ts +0 -5
  50. package/front_end/ui/legacy/XElement.ts +0 -33
  51. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +3 -3
  52. package/front_end/ui/legacy/components/perf_ui/FilmStripView.ts +38 -21
  53. package/front_end/ui/legacy/components/perf_ui/filmStripView.css +29 -0
  54. package/front_end/ui/legacy/components/source_frame/XMLView.ts +3 -2
  55. package/front_end/ui/legacy/legacy.ts +0 -2
  56. package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
  57. package/package.json +1 -1
  58. package/front_end/panels/application/components/reportingApiGrid.css +0 -31
  59. package/front_end/ui/legacy/XWidget.ts +0 -133
@@ -1,23 +1,17 @@
1
1
  // Copyright 2021 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-lit-render-outside-of-view */
5
4
 
6
5
  import '../../../ui/legacy/components/data_grid/data_grid.js';
7
- import '../../../ui/components/icon_button/icon_button.js';
8
- import '../../../ui/legacy/legacy.js';
9
6
 
10
7
  import * as i18n from '../../../core/i18n/i18n.js';
11
8
  import * as Root from '../../../core/root/root.js';
12
9
  import type * as Protocol from '../../../generated/protocol.js';
13
- // inspectorCommonStyles is imported for the empty state styling that is used for the start view
14
- // eslint-disable-next-line rulesdir/es-modules-import
15
- import inspectorCommonStyles from '../../../ui/legacy/inspectorCommon.css.js';
16
10
  import * as UI from '../../../ui/legacy/legacy.js';
17
11
  import * as Lit from '../../../ui/lit/lit.js';
18
12
  import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
19
13
 
20
- import reportingApiGridStyles from './reportingApiGrid.css.js';
14
+ import reportsGridStyles from './reportsGrid.css.js';
21
15
 
22
16
  const UIStrings = {
23
17
  /**
@@ -48,7 +42,7 @@ const UIStrings = {
48
42
  * @description Column header for a table displaying Reporting API reports.
49
43
  *The column contains the timestamp of when a report was generated.
50
44
  */
51
- generatedAt: 'Generated at'
45
+ generatedAt: 'Generated at',
52
46
  } as const;
53
47
  const str_ = i18n.i18n.registerUIStrings('panels/application/components/ReportsGrid.ts', UIStrings);
54
48
  export const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -57,115 +51,100 @@ const {render, html} = Lit;
57
51
 
58
52
  const REPORTING_API_EXPLANATION_URL = 'https://developer.chrome.com/docs/capabilities/web-apis/reporting-api';
59
53
 
60
- export class ReportsGridStatusHeader extends HTMLElement {
61
- readonly #shadow = this.attachShadow({mode: 'open'});
62
-
63
- connectedCallback(): void {
64
- this.#render();
65
- }
66
-
67
- #render(): void {
68
- // Disabled until https://crbug.com/1079231 is fixed.
69
- // clang-format off
70
- render(html`
71
- <style>${reportingApiGridStyles}</style>
72
- <span class="status-header">${i18nString(UIStrings.status)}</span>
73
- <x-link href="https://web.dev/reporting-api/#report-status"
74
- jslog=${VisualLogging.link('report-status').track({click: true})}>
75
- <devtools-icon class="inline-icon medium" name="help" style="color: var(--icon-link);"></devtools-icon>
76
- </x-link>
77
- `, this.#shadow, {host: this});
78
- // clang-format on
79
- }
80
- }
81
-
82
54
  export interface ReportsGridData {
83
55
  reports: Protocol.Network.ReportingApiReport[];
84
56
  }
85
57
 
86
- export class ReportsGrid extends HTMLElement {
87
- readonly #shadow = this.attachShadow({mode: 'open'});
88
- #reports: Protocol.Network.ReportingApiReport[] = [];
89
- #protocolMonitorExperimentEnabled = false;
58
+ export interface ViewInput {
59
+ reports: Protocol.Network.ReportingApiReport[];
60
+ protocolMonitorExperimentEnabled: boolean;
61
+ onSelect: (e: CustomEvent<HTMLElement|null>) => void;
62
+ }
90
63
 
91
- connectedCallback(): void {
92
- this.#protocolMonitorExperimentEnabled = Root.Runtime.experiments.isEnabled('protocol-monitor');
93
- this.#render();
94
- }
64
+ export const DEFAULT_VIEW = (input: ViewInput, output: undefined, target: HTMLElement): void => {
65
+ // clang-format off
66
+ render(html`
67
+ <style>${reportsGridStyles}</style>
68
+ <style>${UI.inspectorCommonStyles}</style>
69
+ <div class="reporting-container" jslog=${VisualLogging.section('reports')}>
70
+ <div class="reporting-header">${i18n.i18n.lockedString('Reports')}</div>
71
+ ${input.reports.length > 0 ? html`
72
+ <devtools-data-grid striped @select=${input.onSelect}>
73
+ <table>
74
+ <tr>
75
+ ${input.protocolMonitorExperimentEnabled ? html`
76
+ <th id="id" weight="30">${i18n.i18n.lockedString('ID')}</th>
77
+ ` : ''}
78
+ <th id="url" weight="30">${i18n.i18n.lockedString('URL')}</th>
79
+ <th id="type" weight="20">${i18n.i18n.lockedString('Type')}</th>
80
+ <th id="status" weight="20">
81
+ <style>${reportsGridStyles}</style>
82
+ <span class="status-header">${i18nString(UIStrings.status)}</span>
83
+ <x-link href="https://web.dev/reporting-api/#report-status"
84
+ jslog=${VisualLogging.link('report-status').track({click: true})}>
85
+ <devtools-icon class="inline-icon medium" name="help" style="color: var(--icon-link);"
86
+ ></devtools-icon>
87
+ </x-link>
88
+ </th>
89
+ <th id="destination" weight="20">${i18nString(UIStrings.destination)}</th>
90
+ <th id="timestamp" weight="20">${i18nString(UIStrings.generatedAt)}</th>
91
+ <th id="body" weight="20">${i18n.i18n.lockedString('Body')}</th>
92
+ </tr>
93
+ ${input.reports.map(report => html`
94
+ <tr data-id=${report.id}>
95
+ ${input.protocolMonitorExperimentEnabled ? html`<td>${report.id}</td>` : ''}
96
+ <td>${report.initiatorUrl}</td>
97
+ <td>${report.type}</td>
98
+ <td>${report.status}</td>
99
+ <td>${report.destination}</td>
100
+ <td>${new Date(report.timestamp * 1000).toLocaleString()}</td>
101
+ <td>${JSON.stringify(report.body)}</td>
102
+ </tr>
103
+ `)}
104
+ </table>
105
+ </devtools-data-grid>
106
+ ` : html`
107
+ <div class="empty-state">
108
+ <span class="empty-state-header">${i18nString(UIStrings.noReportsToDisplay)}</span>
109
+ <div class="empty-state-description">
110
+ <span>${i18nString(UIStrings.reportingApiDescription)}</span>
111
+ ${UI.XLink.XLink.create(REPORTING_API_EXPLANATION_URL, i18nString(UIStrings.learnMore), undefined,
112
+ undefined, 'learn-more')}
113
+ </div>
114
+ </div>
115
+ `}
116
+ </div>
117
+ `, target);
118
+ // clang-format on
119
+ };
95
120
 
96
- set data(data: ReportsGridData) {
97
- this.#reports = data.reports;
98
- this.#render();
99
- }
121
+ type View = typeof DEFAULT_VIEW;
100
122
 
101
- get data(): ReportsGridData {
102
- return {reports: this.#reports};
103
- }
123
+ export class ReportsGrid extends UI.Widget.Widget {
124
+ reports: Protocol.Network.ReportingApiReport[] = [];
125
+ #protocolMonitorExperimentEnabled = false;
126
+ #view: View;
127
+ onReportSelected: (id: string) => void = () => {};
104
128
 
105
- #render(): void {
106
- // Disabled until https://crbug.com/1079231 is fixed.
107
- // clang-format off
108
- render(html`
109
- <style>${reportingApiGridStyles}</style>
110
- <style>${inspectorCommonStyles}</style>
111
- <div class="reporting-container" jslog=${VisualLogging.section('reports')}>
112
- <div class="reporting-header">${i18n.i18n.lockedString('Reports')}</div>
113
- ${this.#reports.length > 0 ? html`
114
- <devtools-data-grid striped @select=${this.#onSelect}>
115
- <table>
116
- <tr>
117
- ${this.#protocolMonitorExperimentEnabled ? html`
118
- <th id="id" weight="30">${i18n.i18n.lockedString('ID')}</th>
119
- ` : ''}
120
- <th id="url" weight="30">${i18n.i18n.lockedString('URL')}</th>
121
- <th id="type" weight="20">${i18n.i18n.lockedString('Type')}</th>
122
- <th id="status" weight="20">
123
- <devtools-resources-reports-grid-status-header></devtools-resources-reports-grid-status-header>
124
- </th>
125
- <th id="destination" weight="20">${i18nString(UIStrings.destination)}</th>
126
- <th id="timestamp" weight="20">${i18nString(UIStrings.generatedAt)}</th>
127
- <th id="body" weight="20">${i18n.i18n.lockedString('Body')}</th>
128
- </tr>
129
- ${this.#reports.map(report => html`
130
- <tr data-id=${report.id}>
131
- ${this.#protocolMonitorExperimentEnabled ? html`<td>${report.id}</td>` : ''}
132
- <td>${report.initiatorUrl}</td>
133
- <td>${report.type}</td>
134
- <td>${report.status}</td>
135
- <td>${report.destination}</td>
136
- <td>${new Date(report.timestamp * 1000).toLocaleString()}</td>
137
- <td>${JSON.stringify(report.body)}</td>
138
- </tr>
139
- `)}
140
- </table>
141
- </devtools-data-grid>
142
- ` : html`
143
- <div class="empty-state">
144
- <span class="empty-state-header">${i18nString(UIStrings.noReportsToDisplay)}</span>
145
- <div class="empty-state-description">
146
- <span>${i18nString(UIStrings.reportingApiDescription)}</span>
147
- ${UI.XLink.XLink.create(REPORTING_API_EXPLANATION_URL, i18nString(UIStrings.learnMore), undefined, undefined, 'learn-more')}
148
- </div>
149
- </div>
150
- `}
151
- </div>
152
- `, this.#shadow, {host: this});
153
- // clang-format on
129
+ constructor(element?: HTMLElement, view: View = DEFAULT_VIEW) {
130
+ super(element);
131
+ this.#view = view;
132
+ this.#protocolMonitorExperimentEnabled = Root.Runtime.experiments.isEnabled('protocol-monitor');
133
+ this.requestUpdate();
154
134
  }
155
135
 
156
- #onSelect(e: CustomEvent<HTMLElement|null>): void {
157
- if (e.detail) {
158
- this.dispatchEvent(new CustomEvent('select', {detail: e.detail.dataset.id}));
136
+ #onSelect = (e: CustomEvent<HTMLElement|null>): void => {
137
+ if (e.detail?.dataset.id) {
138
+ this.onReportSelected(e.detail.dataset.id);
159
139
  }
160
- }
161
- }
162
-
163
- customElements.define('devtools-resources-reports-grid-status-header', ReportsGridStatusHeader);
164
- customElements.define('devtools-resources-reports-grid', ReportsGrid);
165
-
166
- declare global {
167
- interface HTMLElementTagNameMap {
168
- 'devtools-resources-reports-grid-status-header': ReportsGridStatusHeader;
169
- 'devtools-resources-reports-grid': ReportsGrid;
140
+ };
141
+
142
+ override performUpdate(): void {
143
+ const viewInput = {
144
+ reports: this.reports,
145
+ protocolMonitorExperimentEnabled: this.#protocolMonitorExperimentEnabled,
146
+ onSelect: this.#onSelect,
147
+ };
148
+ this.#view(viewInput, undefined, this.contentElement);
170
149
  }
171
150
  }
@@ -14,6 +14,8 @@ import * as RenderCoordinator from '../../../ui/components/render_coordinator/re
14
14
  import * as UI from '../../../ui/legacy/legacy.js';
15
15
  import * as Lit from '../../../ui/lit/lit.js';
16
16
 
17
+ import storageMetadataViewStyle from './storageMetadataView.css.js';
18
+
17
19
  const {html} = Lit;
18
20
 
19
21
  const UIStrings = {
@@ -21,7 +23,7 @@ const UIStrings = {
21
23
  * @description The origin of a URL (https://web.dev/same-site-same-origin/#origin).
22
24
  *(for a lot of languages this does not need to be translated, please translate only where necessary)
23
25
  */
24
- origin: 'Origin',
26
+ origin: 'Frame origin',
25
27
  /**
26
28
  * @description Site (https://web.dev/same-site-same-origin/#site) for the URL the user sees in the omnibox.
27
29
  */
@@ -118,6 +120,7 @@ export class StorageMetadataView extends LegacyWrapper.LegacyWrapper.WrappableCo
118
120
  #storageBucketsModel?: SDK.StorageBucketsModel.StorageBucketsModel;
119
121
  #storageKey: SDK.StorageKeyManager.StorageKey|null = null;
120
122
  #storageBucket: Protocol.Storage.StorageBucketInfo|null = null;
123
+ #showOnlyBucket = true;
121
124
 
122
125
  setStorageKey(storageKey: string): void {
123
126
  this.#storageKey = SDK.StorageKeyManager.parseStorageKey(storageKey);
@@ -129,6 +132,10 @@ export class StorageMetadataView extends LegacyWrapper.LegacyWrapper.WrappableCo
129
132
  this.setStorageKey(storageBucket.bucket.storageKey);
130
133
  }
131
134
 
135
+ setShowOnlyBucket(show: boolean): void {
136
+ this.#showOnlyBucket = show;
137
+ }
138
+
132
139
  enableStorageBucketControls(model: SDK.StorageBucketsModel.StorageBucketsModel): void {
133
140
  this.#storageBucketsModel = model;
134
141
  if (this.#storageKey) {
@@ -141,6 +148,9 @@ export class StorageMetadataView extends LegacyWrapper.LegacyWrapper.WrappableCo
141
148
  // Disabled until https://crbug.com/1079231 is fixed.
142
149
  // clang-format off
143
150
  Lit.render(html`
151
+ <style>
152
+ ${storageMetadataViewStyle}
153
+ </style>
144
154
  <devtools-report .data=${{reportTitle: this.getTitle() ?? i18nString(UIStrings.loading)}}>
145
155
  ${await this.renderReportContent()}
146
156
  </devtools-report>`, this.#shadow, {host: this});
@@ -181,11 +191,16 @@ export class StorageMetadataView extends LegacyWrapper.LegacyWrapper.WrappableCo
181
191
  topLevelSiteIsOpaque ? i18nString(UIStrings.yesBecauseTopLevelIsOpaque) :
182
192
  (topLevelSite && origin !== topLevelSite) ? i18nString(UIStrings.yesBecauseOriginNotInTopLevelSite) :
183
193
  null;
194
+
195
+ const isIframeOrEmbedded = topLevelSite && origin !== topLevelSite;
196
+
184
197
  // Disabled until https://crbug.com/1079231 is fixed.
185
198
  // clang-format off
186
199
  return html`
187
- ${this.key(i18nString(UIStrings.origin))}
188
- ${this.value(html`<div class="text-ellipsis" title=${origin}>${origin}</div>`)}
200
+ ${(isIframeOrEmbedded) ?
201
+ html`${this.key(i18nString(UIStrings.origin))}
202
+ ${this.value(html`<div class="text-ellipsis" title=${origin}>${origin}</div>`)}`
203
+ : Lit.nothing}
189
204
  ${(topLevelSite || topLevelSiteIsOpaque) ? this.key(i18nString(UIStrings.topLevelSite)) : Lit.nothing}
190
205
  ${topLevelSite ? this.value(topLevelSite) : Lit.nothing}
191
206
  ${topLevelSiteIsOpaque ? this.value(i18nString(UIStrings.opaque)) : Lit.nothing}
@@ -205,11 +220,22 @@ export class StorageMetadataView extends LegacyWrapper.LegacyWrapper.WrappableCo
205
220
  throw new Error('Should not call #renderStorageBucketInfo if #bucket is null.');
206
221
  }
207
222
  const {bucket: {name}, persistent, durability, quota} = this.#storageBucket;
223
+ const isDefault = !name;
208
224
 
225
+ if (!this.#showOnlyBucket) {
226
+ if (isDefault) {
227
+ return html`
228
+ ${this.key(i18nString(UIStrings.bucketName))}
229
+ ${this.value(html`<span class="default-bucket">default</span>`)}`;
230
+ }
231
+ return html`
232
+ ${this.key(i18nString(UIStrings.bucketName))}
233
+ ${this.value(name)}`;
234
+ }
209
235
  // clang-format off
210
236
  return html`
211
237
  ${this.key(i18nString(UIStrings.bucketName))}
212
- ${this.value(name || 'default')}
238
+ ${this.value(name || html`<span class="default-bucket">default</span>`)}
213
239
  ${this.key(i18nString(UIStrings.persistent))}
214
240
  ${this.value(persistent ? i18nString(UIStrings.yes) : i18nString(UIStrings.no))}
215
241
  ${this.key(i18nString(UIStrings.durability))}
@@ -0,0 +1,30 @@
1
+ /*
2
+ * Copyright 2025 The Chromium Authors
3
+ * Use of this source code is governed by a BSD-style license that can be
4
+ * found in the LICENSE file.
5
+ */
6
+
7
+ @scope to (devtools-widget > *) {
8
+ :scope {
9
+ overflow: auto;
10
+ height: 100%;
11
+ }
12
+
13
+ .endpoints-container {
14
+ height: 100%;
15
+ display: flex;
16
+ flex-direction: column;
17
+ width: 100%;
18
+ }
19
+
20
+ .endpoints-header {
21
+ font-size: 15px;
22
+ background-color: var(--sys-color-surface2);
23
+ padding: 1px 4px;
24
+ flex-shrink: 0;
25
+ }
26
+
27
+ devtools-data-grid {
28
+ flex: auto;
29
+ }
30
+ }
@@ -0,0 +1,34 @@
1
+ /*
2
+ * Copyright 2025 The Chromium Authors
3
+ * Use of this source code is governed by a BSD-style license that can be
4
+ * found in the LICENSE file.
5
+ */
6
+
7
+ @scope to (devtools-widget > *) {
8
+ :scope {
9
+ overflow: auto;
10
+ height: 100%;
11
+ }
12
+
13
+ .reporting-container {
14
+ height: 100%;
15
+ display: flex;
16
+ flex-direction: column;
17
+ width: 100%;
18
+ }
19
+
20
+ .reporting-header {
21
+ font-size: 15px;
22
+ background-color: var(--sys-color-surface2);
23
+ padding: 1px 4px;
24
+ flex-shrink: 0;
25
+ }
26
+
27
+ devtools-data-grid {
28
+ flex: auto;
29
+ }
30
+
31
+ .inline-icon {
32
+ vertical-align: text-bottom;
33
+ }
34
+ }
@@ -0,0 +1,9 @@
1
+ /*
2
+ * Copyright 2025 The Chromium Authors. All rights reserved.
3
+ * Use of this source code is governed by a BSD-style license that can be
4
+ * found in the LICENSE file.
5
+ */
6
+
7
+ .default-bucket {
8
+ font-style: italic;
9
+ }
@@ -183,29 +183,23 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
183
183
  'source-code': true,
184
184
  'breakpoint-hit': input.highlightedItem === breakpoint,
185
185
  });
186
- const categoryConfigElements = new WeakMap<HTMLLIElement, SDK.CategorizedBreakpoint.Category>();
187
- const trackCategoryConfigElement = (category: SDK.CategorizedBreakpoint.Category): ReturnType<typeof ref> =>
188
- ref((e: Element|undefined) => {
189
- if (e instanceof HTMLLIElement) {
190
- categoryConfigElements.set(e, category);
191
- }
192
- });
193
- const onExpand = ({detail: {expanded, target}}: UI.TreeOutline.TreeViewElement.ExpandEvent): void => {
194
- const category = categoryConfigElements.get(target);
195
- const breakpoints = category && input.categories.get(category);
196
- if (!breakpoints) {
197
- return;
198
- }
199
- if (shouldExpandCategory(breakpoints)) {
200
- // Basically ignore expand/collapse when the category is expanded by default.
201
- return;
202
- }
203
- if (expanded) {
204
- output.userExpandedCategories.add(category);
205
- } else {
206
- output.userExpandedCategories.delete(category);
207
- }
208
- };
186
+ const onExpand =
187
+ (category: SDK.CategorizedBreakpoint.Category, {detail: {expanded}}: UI.TreeOutline.TreeViewElement.ExpandEvent):
188
+ void => {
189
+ const breakpoints = category && input.categories.get(category);
190
+ if (!breakpoints) {
191
+ return;
192
+ }
193
+ if (shouldExpandCategory(breakpoints)) {
194
+ // Basically ignore expand/collapse when the category is expanded by default.
195
+ return;
196
+ }
197
+ if (expanded) {
198
+ output.userExpandedCategories.add(category);
199
+ } else {
200
+ output.userExpandedCategories.delete(category);
201
+ }
202
+ };
209
203
 
210
204
  render(
211
205
  // clang-format off
@@ -219,17 +213,15 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
219
213
  </devtools-toolbar>
220
214
  <devtools-tree
221
215
  ${ref(e => { output.defaultFocus = e; })}
222
- @expand=${onExpand}
223
216
  .template=${html`
224
217
  <ul role="tree">
225
218
  ${filteredCategories.map(([category, breakpoints]) => html`
226
- <li
219
+ <li @expand=${(e: UI.TreeOutline.TreeViewElement.ExpandEvent) => onExpand(category, e)}
227
220
  role="treeitem"
228
221
  jslog-context=${category}
229
222
  aria-checked=${breakpoints.some(breakpoint => breakpoint.enabled())
230
223
  ? breakpoints.some(breakpoint => !breakpoint.enabled()) ? 'mixed' : true
231
- : false}
232
- ${trackCategoryConfigElement(category)}>
224
+ : false}>
233
225
  <style>${categorizedBreakpointsSidebarPaneStyles}</style>
234
226
  <devtools-checkbox
235
227
  class="small"
@@ -192,7 +192,14 @@ export class BadgeNotification extends UI.Widget.Widget {
192
192
  }
193
193
 
194
194
  async #presentStarterBadge(badge: Badges.Badge): Promise<void> {
195
- const gdpProfile = await Host.GdpClient.GdpClient.instance().getProfile();
195
+ const getProfileResponse = await Host.GdpClient.GdpClient.instance().getProfile();
196
+ // The `getProfile` call failed and returned a `null`.
197
+ // For that case, we don't show anything.
198
+ if (!getProfileResponse) {
199
+ return;
200
+ }
201
+
202
+ const hasGdpProfile = Boolean(getProfileResponse.profile);
196
203
  const receiveBadgesSettingEnabled = Badges.UserBadges.instance().isReceiveBadgesSettingEnabled();
197
204
  const googleDeveloperProgramLink = UI.XLink.XLink.create(
198
205
  'https://developers.google.com/program', lockedString('Google Developer Program'), 'badge-link', undefined,
@@ -200,14 +207,14 @@ export class BadgeNotification extends UI.Widget.Widget {
200
207
 
201
208
  // If the user already has a GDP profile and the receive badges setting enabled,
202
209
  // starter badge behaves as if it's an activity based badge.
203
- if (gdpProfile && receiveBadgesSettingEnabled) {
210
+ if (hasGdpProfile && receiveBadgesSettingEnabled) {
204
211
  this.#presentActivityBasedBadge(badge);
205
212
  return;
206
213
  }
207
214
 
208
215
  // If the user already has a GDP profile and the receive badges setting disabled,
209
216
  // starter badge behaves as a nudge for opting into receiving badges.
210
- if (gdpProfile && !receiveBadgesSettingEnabled) {
217
+ if (hasGdpProfile && !receiveBadgesSettingEnabled) {
211
218
  this.#show({
212
219
  message: i18nFormatString(
213
220
  UIStrings.starterBadgeAwardMessageSettingDisabled, {PH1: badge.title, PH2: googleDeveloperProgramLink}),
@@ -1050,7 +1050,7 @@ let searchNetworkViewInstance: SearchNetworkView;
1050
1050
 
1051
1051
  export class SearchNetworkView extends Search.SearchView.SearchView {
1052
1052
  private constructor() {
1053
- super('network', new Common.Throttler.Throttler(/* timeoutMs */ 200));
1053
+ super('network');
1054
1054
  }
1055
1055
 
1056
1056
  static instance(opts: {
@@ -173,17 +173,17 @@ export interface ViewInput {
173
173
  filterKeys: string[];
174
174
  filter: string;
175
175
  parseFilter: (filter: string) => TextUtils.TextUtils.ParsedFilter[];
176
- onRecord: (e: Event) => void;
176
+ onRecord: (record: boolean) => void;
177
177
  onClear: () => void;
178
178
  onSave: () => void;
179
- onSplitChange: (e: CustomEvent<string>) => void;
179
+ onSplitChange: (onlyMain: boolean) => void;
180
180
  onSelect: (e: CustomEvent<HTMLElement|null>) => void;
181
181
  onContextMenu: (e: CustomEvent<{menu: UI.ContextMenu.ContextMenu, element: HTMLElement}>) => void;
182
- onFilterChanged: (e: CustomEvent<string>) => void;
183
- onCommandChange: (e: CustomEvent<string>) => void;
184
- onCommandSubmitted: (e: CustomEvent<string>) => void;
185
- onTargetChange: (e: Event) => void;
186
- onToggleSidebar: (e: Event) => void;
182
+ onFilterChanged: (filter: string) => void;
183
+ onCommandChange: (command: string) => void;
184
+ onCommandSubmitted: (input: string) => void;
185
+ onTargetChange: (targetId: string) => void;
186
+ onToggleSidebar: () => void;
187
187
  targets: SDK.Target.Target[];
188
188
  selectedTargetId: string;
189
189
  }
@@ -203,7 +203,7 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
203
203
  direction="column"
204
204
  sidebar-initial-size="400"
205
205
  sidebar-visibility=${input.sidebarVisible ? 'visible' : 'hidden'}
206
- @change=${input.onSplitChange}>
206
+ @change=${(e: CustomEvent<string>) => input.onSplitChange(e.detail === 'OnlyMain')}>
207
207
  <div slot="main" class="vbox protocol-monitor-main">
208
208
  <devtools-toolbar class="protocol-monitor-toolbar"
209
209
  jslog=${VisualLogging.toolbar('top')}>
@@ -214,22 +214,23 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
214
214
  .variant=${Buttons.Button.Variant.ICON_TOGGLE}
215
215
  .toggleType=${Buttons.Button.ToggleType.RED}
216
216
  .toggled=${true}
217
- @click=${input.onRecord}></devtools-button>
217
+ @click=${(e: Event) => input.onRecord((e.target as Buttons.Button.Button).toggled)}>
218
+ </devtools-button>
218
219
  <devtools-button title=${i18nString(UIStrings.clearAll)}
219
220
  .iconName=${'clear'}
220
221
  .variant=${Buttons.Button.Variant.TOOLBAR}
221
222
  .jslogContext=${'protocol-monitor.clear-all'}
222
- @click=${input.onClear}></devtools-button>
223
+ @click=${() => input.onClear()}></devtools-button>
223
224
  <devtools-button title=${i18nString(UIStrings.save)}
224
225
  .iconName=${'download'}
225
226
  .variant=${Buttons.Button.Variant.TOOLBAR}
226
227
  .jslogContext=${'protocol-monitor.save'}
227
- @click=${input.onSave}></devtools-button>
228
+ @click=${() => input.onSave()}></devtools-button>
228
229
  <devtools-toolbar-input type="filter"
229
230
  list="filter-suggestions"
230
231
  style="flex-grow: 1"
231
232
  value=${input.filter}
232
- @change=${input.onFilterChanged}>
233
+ @change=${(e: Event) => input.onFilterChanged((e.target as HTMLInputElement).value)}>
233
234
  <datalist id="filter-suggestions">
234
235
  ${input.filterKeys.map(key => html`
235
236
  <option value=${key + ':'}></option>
@@ -320,7 +321,7 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
320
321
  .iconName=${input.sidebarVisible ? 'left-panel-close' : 'left-panel-open'}
321
322
  .variant=${Buttons.Button.Variant.TOOLBAR}
322
323
  .jslogContext=${'protocol-monitor.toggle-command-editor'}
323
- @click=${input.onToggleSidebar}></devtools-button>
324
+ @click=${() => input.onToggleSidebar()}></devtools-button>
324
325
  </devtools-button>
325
326
  <devtools-toolbar-input id="command-input"
326
327
  style=${styleMap({
@@ -330,8 +331,8 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
330
331
  list="command-input-suggestions"
331
332
  placeholder=${i18nString(UIStrings.sendRawCDPCommand)}
332
333
  title=${i18nString(UIStrings.sendRawCDPCommandExplanation)}
333
- @change=${input.onCommandChange}
334
- @submit=${input.onCommandSubmitted}>
334
+ @change=${(e: Event) => input.onCommandChange((e.target as HTMLInputElement).value)}
335
+ @submit=${(e: Event) => input.onCommandSubmitted((e.target as HTMLInputElement).value)}>
335
336
  <datalist id="command-input-suggestions">
336
337
  ${input.commandSuggestions.map(c => html`<option value=${c}></option>`)}
337
338
  </datalist>
@@ -340,7 +341,7 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
340
341
  title=${i18nString(UIStrings.selectTarget)}
341
342
  style=${styleMap({display: input.sidebarVisible ? 'none' : 'flex'})}
342
343
  jslog=${VisualLogging.dropDown('target-selector').track({change: true})}
343
- @change=${input.onTargetChange}>
344
+ @change=${(e: Event) => input.onTargetChange((e.target as HTMLSelectElement).value)}>
344
345
  ${input.targets.map(target => html`
345
346
  <option jslog=${VisualLogging.item('target').track({click: true})}
346
347
  value=${target.id()} ?selected=${target.id() === input.selectedTargetId}>
@@ -416,8 +417,8 @@ export class ProtocolMonitorImpl extends UI.Panel.Panel {
416
417
  filterKeys: this.#filterKeys,
417
418
  filter: this.#filter,
418
419
  parseFilter: this.filterParser.parse.bind(this.filterParser),
419
- onSplitChange: (e: CustomEvent<string>) => {
420
- if (e.detail === 'OnlyMain') {
420
+ onSplitChange: (onlyMain: boolean) => {
421
+ if (onlyMain) {
421
422
  this.#populateToolbarInput();
422
423
  this.#sidebarVisible = false;
423
424
  } else {
@@ -427,8 +428,8 @@ export class ProtocolMonitorImpl extends UI.Panel.Panel {
427
428
  }
428
429
  this.requestUpdate();
429
430
  },
430
- onRecord: (e: Event) => {
431
- this.setRecording((e.target as Buttons.Button.Button).toggled);
431
+ onRecord: (recording: boolean) => {
432
+ this.setRecording(recording);
432
433
  },
433
434
  onClear: () => {
434
435
  this.#messages = [];
@@ -449,24 +450,22 @@ export class ProtocolMonitorImpl extends UI.Panel.Panel {
449
450
  this.#populateContextMenu(e.detail.menu, message);
450
451
  }
451
452
  },
452
- onCommandChange: (e: CustomEvent<string>) => {
453
- this.#command = e.detail;
453
+ onCommandChange: (command: string) => {
454
+ this.#command = command;
454
455
  },
455
- onCommandSubmitted: (e: CustomEvent<string>) => {
456
- this.#commandAutocompleteSuggestionProvider.addEntry(e.detail);
457
- const {command, parameters} = parseCommandInput(e.detail);
456
+ onCommandSubmitted: (input: string) => {
457
+ this.#commandAutocompleteSuggestionProvider.addEntry(input);
458
+ const {command, parameters} = parseCommandInput(input);
458
459
  this.onCommandSend(command, parameters, this.#selectedTargetId);
459
460
  },
460
- onFilterChanged: (e: CustomEvent<string>) => {
461
- this.#filter = e.detail;
461
+ onFilterChanged: (filter: string) => {
462
+ this.#filter = filter;
462
463
  this.requestUpdate();
463
464
  },
464
- onTargetChange: (e: Event) => {
465
- if (e.target instanceof HTMLSelectElement) {
466
- this.#selectedTargetId = e.target.value;
467
- }
465
+ onTargetChange: (targetId: string) => {
466
+ this.#selectedTargetId = targetId;
468
467
  },
469
- onToggleSidebar: (_e: Event) => {
468
+ onToggleSidebar: () => {
470
469
  this.#sidebarVisible = !this.#sidebarVisible;
471
470
  this.requestUpdate();
472
471
  },