chrome-devtools-frontend 1.0.1521746 → 1.0.1522145

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 (47) hide show
  1. package/front_end/core/host/GdpClient.ts +116 -66
  2. package/front_end/core/root/Runtime.ts +1 -0
  3. package/front_end/entrypoints/inspector_main/InspectorMain.ts +82 -32
  4. package/front_end/entrypoints/inspector_main/inspector_main-meta.ts +1 -1
  5. package/front_end/entrypoints/main/MainImpl.ts +7 -1
  6. package/front_end/generated/InspectorBackendCommands.js +3 -2
  7. package/front_end/generated/protocol-mapping.d.ts +9 -0
  8. package/front_end/generated/protocol-proxy-api.d.ts +8 -0
  9. package/front_end/generated/protocol.ts +12 -0
  10. package/front_end/models/ai_assistance/agents/NetworkAgent.ts +10 -6
  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/panels/ai_assistance/components/UserActionRow.ts +1 -2
  14. package/front_end/panels/application/IndexedDBViews.ts +1 -0
  15. package/front_end/panels/application/ReportingApiTreeElement.ts +1 -2
  16. package/front_end/panels/application/ReportingApiView.ts +18 -20
  17. package/front_end/panels/application/ServiceWorkerCacheViews.ts +3 -0
  18. package/front_end/panels/application/components/EndpointsGrid.ts +51 -59
  19. package/front_end/panels/application/components/ReportsGrid.ts +86 -107
  20. package/front_end/panels/application/components/StorageMetadataView.ts +30 -4
  21. package/front_end/panels/application/components/endpointsGrid.css +30 -0
  22. package/front_end/panels/application/components/reportsGrid.css +34 -0
  23. package/front_end/panels/application/components/storageMetadataView.css +9 -0
  24. package/front_end/panels/browser_debugger/CategorizedBreakpointsSidebarPane.ts +19 -27
  25. package/front_end/panels/common/BadgeNotification.ts +10 -3
  26. package/front_end/panels/network/NetworkPanel.ts +1 -1
  27. package/front_end/panels/search/SearchResultsPane.ts +14 -13
  28. package/front_end/panels/search/SearchView.ts +3 -20
  29. package/front_end/panels/settings/components/SyncSection.ts +8 -6
  30. package/front_end/panels/sources/SearchSourcesView.ts +1 -1
  31. package/front_end/panels/whats_new/ReleaseNoteText.ts +15 -11
  32. package/front_end/panels/whats_new/resources/WNDT.md +9 -6
  33. package/front_end/third_party/chromium/README.chromium +1 -1
  34. package/front_end/third_party/diff/README.chromium +0 -1
  35. package/front_end/ui/legacy/Treeoutline.ts +6 -9
  36. package/front_end/ui/legacy/UIUtils.ts +4 -17
  37. package/front_end/ui/legacy/Widget.ts +0 -5
  38. package/front_end/ui/legacy/XElement.ts +0 -33
  39. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +3 -3
  40. package/front_end/ui/legacy/components/perf_ui/FilmStripView.ts +38 -21
  41. package/front_end/ui/legacy/components/perf_ui/filmStripView.css +29 -0
  42. package/front_end/ui/legacy/components/source_frame/XMLView.ts +3 -2
  43. package/front_end/ui/legacy/legacy.ts +0 -2
  44. package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
  45. package/package.json +1 -1
  46. package/front_end/panels/application/components/reportingApiGrid.css +0 -31
  47. package/front_end/ui/legacy/XWidget.ts +0 -133
@@ -5,10 +5,12 @@
5
5
  import type * as SDK from '../../../core/sdk/sdk.js';
6
6
  import * as Logs from '../../logs/logs.js';
7
7
  import * as NetworkTimeCalculator from '../../network_time_calculator/network_time_calculator.js';
8
+ import * as TextUtils from '../../text_utils/text_utils.js';
8
9
 
9
10
  import {seconds} from './UnitFormatters.js';
10
11
 
11
12
  const MAX_HEADERS_SIZE = 1000;
13
+ const MAX_BODY_SIZE = 10000;
12
14
 
13
15
  /**
14
16
  * Sanitizes the set of headers, removing values that are not on the allow-list and replacing them with '<redacted>'.
@@ -24,6 +26,8 @@ function sanitizeHeaders(headers: Array<{name: string, value: string}>): Array<{
24
26
 
25
27
  export class NetworkRequestFormatter {
26
28
  #calculator: NetworkTimeCalculator.NetworkTransferTimeCalculator;
29
+ #request: SDK.NetworkRequest.NetworkRequest;
30
+
27
31
  static allowHeader(headerName: string): boolean {
28
32
  return allowedHeaders.has(headerName.toLowerCase().trim());
29
33
  }
@@ -37,6 +41,31 @@ export class NetworkRequestFormatter {
37
41
  MAX_HEADERS_SIZE);
38
42
  }
39
43
 
44
+ static async formatBody(title: string, request: SDK.NetworkRequest.NetworkRequest, maxBodySize: number):
45
+ Promise<string> {
46
+ const data = await request.requestContentData();
47
+
48
+ if (TextUtils.ContentData.ContentData.isError(data)) {
49
+ return '';
50
+ }
51
+
52
+ if (data.isEmpty) {
53
+ return `${title}\n<empty response>`;
54
+ }
55
+
56
+ if (data.isTextContent) {
57
+ const dataAsText = data.text;
58
+
59
+ if (dataAsText.length > maxBodySize) {
60
+ return `${title}\n${dataAsText.substring(0, maxBodySize) + '... <truncated>'}`;
61
+ }
62
+
63
+ return `${title}\n${dataAsText}`;
64
+ }
65
+
66
+ return `${title}\n<binary data>`;
67
+ }
68
+
40
69
  static formatInitiatorUrl(initiatorUrl: string, allowedOrigin: string): string {
41
70
  const initiatorOrigin = new URL(initiatorUrl).origin;
42
71
  if (initiatorOrigin === allowedOrigin) {
@@ -45,8 +74,6 @@ export class NetworkRequestFormatter {
45
74
  return '<redacted cross-origin initiator URL>';
46
75
  }
47
76
 
48
- #request: SDK.NetworkRequest.NetworkRequest;
49
-
50
77
  constructor(
51
78
  request: SDK.NetworkRequest.NetworkRequest, calculator: NetworkTimeCalculator.NetworkTransferTimeCalculator) {
52
79
  this.#request = request;
@@ -61,16 +88,27 @@ export class NetworkRequestFormatter {
61
88
  return NetworkRequestFormatter.formatHeaders('Response headers:', this.#request.responseHeaders);
62
89
  }
63
90
 
91
+ async formatResponseBody(): Promise<string> {
92
+ return await NetworkRequestFormatter.formatBody('Response body:', this.#request, MAX_BODY_SIZE);
93
+ }
94
+
64
95
  /**
65
96
  * Note: nothing here should include information from origins other than
66
97
  * the request's origin.
67
98
  */
68
- formatNetworkRequest(): string {
99
+ async formatNetworkRequest(): Promise<string> {
100
+ let responseBody = await this.formatResponseBody();
101
+
102
+ if (responseBody) {
103
+ // if we have a response then we add 2 new line to follow same structure of the context
104
+ responseBody = `\n\n${responseBody}`;
105
+ }
106
+
69
107
  return `Request: ${this.#request.url()}
70
108
 
71
109
  ${this.formatRequestHeaders()}
72
110
 
73
- ${this.formatResponseHeaders()}
111
+ ${this.formatResponseHeaders()}${responseBody}
74
112
 
75
113
  Response status: ${this.#request.statusCode} ${this.#request.statusText}
76
114
 
@@ -4,7 +4,6 @@
4
4
 
5
5
  import * as Common from '../../core/common/common.js';
6
6
  import * as Host from '../../core/host/host.js';
7
- import * as Root from '../../core/root/root.js';
8
7
 
9
8
  import {AiExplorerBadge} from './AiExplorerBadge.js';
10
9
  import type {Badge, BadgeAction, BadgeActionEvents, BadgeContext, TriggerOptions} from './Badge.js';
@@ -50,8 +49,7 @@ export class UserBadges extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
50
49
  super();
51
50
 
52
51
  this.#receiveBadgesSetting = Common.Settings.Settings.instance().moduleSetting('receive-gdp-badges');
53
- if (Host.GdpClient.getGdpProfilesEnterprisePolicy() ===
54
- Root.Runtime.GdpProfilesEnterprisePolicyValue.ENABLED_WITHOUT_BADGES) {
52
+ if (!Host.GdpClient.isBadgesEnabled()) {
55
53
  this.#receiveBadgesSetting.set(false);
56
54
  }
57
55
  this.#receiveBadgesSetting.addChangeListener(this.#reconcileBadges, this);
@@ -106,10 +104,10 @@ export class UserBadges extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
106
104
  if (!badge.isStarterBadge) {
107
105
  shouldAwardBadge = true;
108
106
  } else {
109
- const gdpProfile = await Host.GdpClient.GdpClient.instance().getProfile();
107
+ const getProfileResponse = await Host.GdpClient.GdpClient.instance().getProfile();
110
108
  const receiveBadgesSettingEnabled = Boolean(this.#receiveBadgesSetting.get());
111
109
  // If there is a GDP profile and the user has enabled receiving badges, we award the starter badge as well.
112
- if (gdpProfile && receiveBadgesSettingEnabled && !this.#isStarterBadgeDismissed() &&
110
+ if (getProfileResponse?.profile && receiveBadgesSettingEnabled && !this.#isStarterBadgeDismissed() &&
113
111
  !this.#isStarterBadgeSnoozed()) {
114
112
  shouldAwardBadge = true;
115
113
  }
@@ -157,27 +155,28 @@ export class UserBadges extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
157
155
  return;
158
156
  }
159
157
 
160
- if (!Host.GdpClient.isGdpProfilesAvailable() ||
161
- Host.GdpClient.getGdpProfilesEnterprisePolicy() !== Root.Runtime.GdpProfilesEnterprisePolicyValue.ENABLED) {
158
+ if (!Host.GdpClient.isGdpProfilesAvailable() || !Host.GdpClient.isBadgesEnabled()) {
162
159
  this.#deactivateAllBadges();
163
160
  return;
164
161
  }
165
162
 
166
- const gdpProfile = await Host.GdpClient.GdpClient.instance().getProfile();
167
- let isEligibleToCreateProfile = Boolean(gdpProfile);
168
- if (!gdpProfile) {
169
- isEligibleToCreateProfile = await Host.GdpClient.GdpClient.instance().isEligibleToCreateProfile();
163
+ const getProfileResponse = await Host.GdpClient.GdpClient.instance().getProfile();
164
+ if (!getProfileResponse) {
165
+ this.#deactivateAllBadges();
166
+ return;
170
167
  }
171
168
 
169
+ const hasGdpProfile = Boolean(getProfileResponse.profile);
170
+ const isEligibleToCreateProfile = getProfileResponse.isEligible;
172
171
  // User does not have a GDP profile & not eligible to create one.
173
172
  // So, we don't activate any badges for them.
174
- if (!gdpProfile && !isEligibleToCreateProfile) {
173
+ if (!hasGdpProfile && !isEligibleToCreateProfile) {
175
174
  this.#deactivateAllBadges();
176
175
  return;
177
176
  }
178
177
 
179
178
  let awardedBadgeNames: Set<string>|null = null;
180
- if (gdpProfile) {
179
+ if (hasGdpProfile) {
181
180
  awardedBadgeNames = await Host.GdpClient.GdpClient.instance().getAwardedBadgeNames(
182
181
  {names: this.#allBadges.map(badge => badge.name)});
183
182
  // This is a conservative approach. We bail out if `awardedBadgeNames` is null
@@ -202,9 +201,8 @@ export class UserBadges extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
202
201
  }
203
202
 
204
203
  const shouldActivateStarterBadge = badge.isStarterBadge && isEligibleToCreateProfile &&
205
- !this.#isStarterBadgeDismissed() && !this.#isStarterBadgeSnoozed();
206
- const shouldActivateActivityBasedBadge =
207
- !badge.isStarterBadge && Boolean(gdpProfile) && receiveBadgesSettingEnabled;
204
+ Host.GdpClient.isStarterBadgeEnabled() && !this.#isStarterBadgeDismissed() && !this.#isStarterBadgeSnoozed();
205
+ const shouldActivateActivityBasedBadge = !badge.isStarterBadge && hasGdpProfile && receiveBadgesSettingEnabled;
208
206
  if (shouldActivateStarterBadge || shouldActivateActivityBasedBadge) {
209
207
  badge.activate();
210
208
  } else {
@@ -1,7 +1,6 @@
1
1
  // Copyright 2024 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 * as Common from '../../../core/common/common.js';
7
6
  import * as Host from '../../../core/host/host.js';
@@ -273,7 +272,7 @@ export const DEFAULT_VIEW = (input: UserActionRowViewInput, output: ViewOutput,
273
272
  </div>
274
273
  </form>
275
274
  ` : Lit.nothing}
276
- `, target, {host: target});
275
+ `, target);
277
276
  // clang-format on
278
277
  };
279
278
 
@@ -146,6 +146,7 @@ export class IDBDatabaseView extends ApplicationComponents.StorageMetadataView.S
146
146
  super();
147
147
 
148
148
  this.model = model;
149
+ this.setShowOnlyBucket(false);
149
150
  if (database) {
150
151
  this.update(database);
151
152
  }
@@ -8,7 +8,6 @@ import type * as Platform from '../../core/platform/platform.js';
8
8
  import * as IconButton from '../../ui/components/icon_button/icon_button.js';
9
9
 
10
10
  import {ApplicationPanelTreeElement} from './ApplicationPanelTreeElement.js';
11
- import * as ApplicationComponents from './components/components.js';
12
11
  import {ReportingApiView} from './ReportingApiView.js';
13
12
  import type {ResourcesPanel} from './ResourcesPanel.js';
14
13
 
@@ -37,7 +36,7 @@ export class ReportingApiTreeElement extends ApplicationPanelTreeElement {
37
36
  override onselect(selectedByUser?: boolean): boolean {
38
37
  super.onselect(selectedByUser);
39
38
  if (!this.view) {
40
- this.view = new ReportingApiView(new ApplicationComponents.EndpointsGrid.EndpointsGrid());
39
+ this.view = new ReportingApiView();
41
40
  }
42
41
  this.showView(this.view);
43
42
  Host.userMetrics.panelShown('reporting-api');
@@ -50,11 +50,10 @@ const REPORTING_API_EXPLANATION_URL =
50
50
  interface ViewInput {
51
51
  hasReports: boolean;
52
52
  hasEndpoints: boolean;
53
- // TODO (crbug.com/407940329): port EndpointsGrid to a UI Widget and instantiate it in the view
54
- endpointsGrid: ApplicationComponents.EndpointsGrid.EndpointsGrid;
55
- // TODO (crbug.com/407940381): port ReportsGrid to a UI Widget and instantiate it in the view
56
- reportsGrid: ApplicationComponents.ReportsGrid.ReportsGrid;
53
+ endpoints: Map<string, Protocol.Network.ReportingApiEndpoint[]>;
54
+ reports: Protocol.Network.ReportingApiReport[];
57
55
  focusedReport?: Protocol.Network.ReportingApiReport;
56
+ onReportSelected: (id: string) => void;
58
57
  }
59
58
 
60
59
  type View = (input: ViewInput, output: object, target: HTMLElement) => void;
@@ -68,7 +67,9 @@ export const DEFAULT_VIEW: View = (input, _output, target) => {
68
67
  ${input.hasReports ? html`
69
68
  <devtools-split-view slot="main" sidebar-position="second" sidebar-initial-size="150">
70
69
  <div slot="main">
71
- ${input.reportsGrid}
70
+ <devtools-widget .widgetConfig=${widgetConfig(ApplicationComponents.ReportsGrid.ReportsGrid, {
71
+ reports: input.reports, onReportSelected: input.onReportSelected,
72
+ })}></devtools-widget>
72
73
  </div>
73
74
  <div slot="sidebar" class="vbox" jslog=${VisualLogging.pane('preview').track({resize: true})}>
74
75
  ${input.focusedReport ? html`
@@ -85,11 +86,15 @@ export const DEFAULT_VIEW: View = (input, _output, target) => {
85
86
  </devtools-split-view>
86
87
  ` : html`
87
88
  <div slot="main">
88
- ${input.reportsGrid}
89
+ <devtools-widget .widgetConfig=${widgetConfig(ApplicationComponents.ReportsGrid.ReportsGrid, {
90
+ reports: input.reports, onReportSelected: input.onReportSelected,
91
+ })}></devtools-widget>
89
92
  </div>
90
93
  `}
91
94
  <div slot="sidebar">
92
- ${input.endpointsGrid}
95
+ <devtools-widget .widgetConfig=${widgetConfig(ApplicationComponents.EndpointsGrid.EndpointsGrid, {
96
+ endpoints: input.endpoints,
97
+ })}></devtools-widget>
93
98
  </div>
94
99
  </devtools-split-view>
95
100
  `, target);
@@ -109,20 +114,16 @@ export const DEFAULT_VIEW: View = (input, _output, target) => {
109
114
 
110
115
  export class ReportingApiView extends UI.Widget.VBox implements
111
116
  SDK.TargetManager.SDKModelObserver<SDK.NetworkManager.NetworkManager> {
112
- readonly #endpointsGrid: ApplicationComponents.EndpointsGrid.EndpointsGrid;
113
117
  #endpoints: Map<string, Protocol.Network.ReportingApiEndpoint[]>;
114
118
  #view: View;
115
119
  #networkManager?: SDK.NetworkManager.NetworkManager;
116
- #reportsGrid = new ApplicationComponents.ReportsGrid.ReportsGrid();
117
120
  #reports: Protocol.Network.ReportingApiReport[] = [];
118
121
  #focusedReport?: Protocol.Network.ReportingApiReport;
119
122
 
120
- constructor(endpointsGrid: ApplicationComponents.EndpointsGrid.EndpointsGrid, view = DEFAULT_VIEW) {
123
+ constructor(view = DEFAULT_VIEW) {
121
124
  super();
122
125
  this.#view = view;
123
- this.#endpointsGrid = endpointsGrid;
124
126
  this.#endpoints = new Map();
125
- this.#reportsGrid.addEventListener('select', this.#onFocus.bind(this));
126
127
  SDK.TargetManager.TargetManager.instance().observeModels(SDK.NetworkManager.NetworkManager, this);
127
128
  this.requestUpdate();
128
129
  }
@@ -158,35 +159,32 @@ export class ReportingApiView extends UI.Widget.VBox implements
158
159
  const viewInput = {
159
160
  hasReports: this.#reports.length > 0,
160
161
  hasEndpoints: this.#endpoints.size > 0,
161
- endpointsGrid: this.#endpointsGrid,
162
- reportsGrid: this.#reportsGrid,
162
+ endpoints: this.#endpoints,
163
+ reports: this.#reports,
163
164
  focusedReport: this.#focusedReport,
165
+ onReportSelected: this.#onReportSelected.bind(this),
164
166
  };
165
167
  this.#view(viewInput, {}, this.element);
166
168
  }
167
169
 
168
170
  #onEndpointsChangedForOrigin({data}: {data: Protocol.Network.ReportingApiEndpointsChangedForOriginEvent}): void {
169
171
  this.#endpoints.set(data.origin, data.endpoints);
170
- this.#endpointsGrid.data = {endpoints: this.#endpoints};
171
172
  this.requestUpdate();
172
173
  }
173
174
 
174
175
  #onReportAdded({data: report}: {data: Protocol.Network.ReportingApiReport}): void {
175
176
  this.#reports.push(report);
176
- this.#reportsGrid.data = {reports: this.#reports};
177
177
  this.requestUpdate();
178
178
  }
179
179
 
180
180
  #onReportUpdated({data: report}: {data: Protocol.Network.ReportingApiReport}): void {
181
181
  const index = this.#reports.findIndex(oldReport => oldReport.id === report.id);
182
182
  this.#reports[index] = report;
183
- this.#reportsGrid.data = {reports: this.#reports};
184
183
  this.requestUpdate();
185
184
  }
186
185
 
187
- async #onFocus(event: Event): Promise<void> {
188
- const selectEvent = event as CustomEvent<string>;
189
- const report = this.#reports.find(report => report.id === selectEvent.detail);
186
+ #onReportSelected(id: string): void {
187
+ const report = this.#reports.find(report => report.id === id);
190
188
  if (report) {
191
189
  this.#focusedReport = report;
192
190
  this.requestUpdate();
@@ -134,6 +134,9 @@ export class ServiceWorkerCacheView extends UI.View.SimpleView {
134
134
  const bucketInfo = this.model.target()
135
135
  .model(SDK.StorageBucketsModel.StorageBucketsModel)
136
136
  ?.getBucketByName(cache.storageBucket.storageKey, cache.storageBucket.name);
137
+
138
+ this.metadataView.setShowOnlyBucket(false);
139
+
137
140
  if (bucketInfo) {
138
141
  this.metadataView.setStorageBucket(bucketInfo);
139
142
  } else if (cache.storageKey) {
@@ -1,7 +1,6 @@
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
6
 
@@ -11,7 +10,7 @@ import * as UI from '../../../ui/legacy/legacy.js';
11
10
  import * as Lit from '../../../ui/lit/lit.js';
12
11
  import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
13
12
 
14
- import reportingApiGridStyles from './reportingApiGrid.css.js';
13
+ import endpointsGridStyles from './endpointsGrid.css.js';
15
14
 
16
15
  const UIStrings = {
17
16
  /**
@@ -23,76 +22,69 @@ const UIStrings = {
23
22
  * @description Placeholder text when there are no Reporting API endpoints.
24
23
  *(https://developers.google.com/web/updates/2018/09/reportingapi#tldr)
25
24
  */
26
- endpointsDescription: 'Here you will find the list of endpoints that receive the reports'
25
+ endpointsDescription: 'Here you will find the list of endpoints that receive the reports',
27
26
  } as const;
28
27
  const str_ = i18n.i18n.registerUIStrings('panels/application/components/EndpointsGrid.ts', UIStrings);
29
28
  export const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
30
29
 
31
30
  const {render, html} = Lit;
32
31
 
33
- export interface EndpointsGridData {
32
+ export interface ViewInput {
34
33
  endpoints: Map<string, Protocol.Network.ReportingApiEndpoint[]>;
35
34
  }
36
35
 
37
- export class EndpointsGrid extends HTMLElement {
36
+ export const DEFAULT_VIEW = (input: ViewInput, output: undefined, target: HTMLElement): void => {
37
+ // clang-format off
38
+ render(html`
39
+ <style>${endpointsGridStyles}</style>
40
+ <style>${UI.inspectorCommonStyles}</style>
41
+ <div class="endpoints-container" jslog=${VisualLogging.section('endpoints')}>
42
+ <div class="endpoints-header">${i18n.i18n.lockedString('Endpoints')}</div>
43
+ ${input.endpoints.size > 0 ? html`
44
+ <devtools-data-grid striped>
45
+ <table>
46
+ <tr>
47
+ <th id="origin" weight="30">${i18n.i18n.lockedString('Origin')}</th>
48
+ <th id="name" weight="20">${i18n.i18n.lockedString('Name')}</th>
49
+ <th id="url" weight="30">${i18n.i18n.lockedString('URL')}</th>
50
+ </tr>
51
+ ${Array.from(input.endpoints).map(([origin, endpointArray]) =>
52
+ endpointArray.map(endpoint => html`<tr>
53
+ <td>${origin}</td>
54
+ <td>${endpoint.groupName}</td>
55
+ <td>${endpoint.url}</td>
56
+ </tr>`))
57
+ .flat()}
58
+ </table>
59
+ </devtools-data-grid>
60
+ ` : html`
61
+ <div class="empty-state">
62
+ <span class="empty-state-header">${i18nString(UIStrings.noEndpointsToDisplay)}</span>
63
+ <span class="empty-state-description">${i18nString(UIStrings.endpointsDescription)}</span>
64
+ </div>
65
+ `}
66
+ </div>
67
+ `, target);
68
+ // clang-format on
69
+ };
38
70
 
39
- readonly #shadow = this.attachShadow({mode: 'open'});
40
- #endpoints = new Map<string, Protocol.Network.ReportingApiEndpoint[]>();
71
+ type View = typeof DEFAULT_VIEW;
41
72
 
42
- connectedCallback(): void {
43
- this.#render();
44
- }
45
-
46
- set data(data: EndpointsGridData) {
47
- this.#endpoints = data.endpoints;
48
- this.#render();
49
- }
73
+ export class EndpointsGrid extends UI.Widget.Widget {
74
+ endpoints = new Map<string, Protocol.Network.ReportingApiEndpoint[]>();
75
+ #view: View;
50
76
 
51
- get data(): EndpointsGridData {
52
- return {endpoints: this.#endpoints};
77
+ constructor(element?: HTMLElement, view: View = DEFAULT_VIEW) {
78
+ super(element);
79
+ this.#view = view;
80
+ this.requestUpdate();
53
81
  }
54
82
 
55
- #render(): void {
56
- // Disabled until https://crbug.com/1079231 is fixed.
57
- // clang-format off
58
- render(html`
59
- <style>${reportingApiGridStyles}</style>
60
- <style>${UI.inspectorCommonStyles}</style>
61
- <div class="reporting-container" jslog=${VisualLogging.section('endpoints')}>
62
- <div class="reporting-header">${i18n.i18n.lockedString('Endpoints')}</div>
63
- ${this.#endpoints.size > 0 ? html`
64
- <devtools-data-grid striped>
65
- <table>
66
- <tr>
67
- <th id="origin" weight="30">${i18n.i18n.lockedString('Origin')}</th>
68
- <th id="name" weight="20">${i18n.i18n.lockedString('Name')}</th>
69
- <th id="url" weight="30">${i18n.i18n.lockedString('URL')}</th>
70
- </tr>
71
- ${Array.from(this.#endpoints).map(([origin, endpointArray]) =>
72
- endpointArray.map(endpoint => html`<tr>
73
- <td>${origin}</td>
74
- <td>${endpoint.groupName}</td>
75
- <td>${endpoint.url}</td>
76
- </tr>`))
77
- .flat()}
78
- </table>
79
- </devtools-data-grid>
80
- ` : html`
81
- <div class="empty-state">
82
- <span class="empty-state-header">${i18nString(UIStrings.noEndpointsToDisplay)}</span>
83
- <span class="empty-state-description">${i18nString(UIStrings.endpointsDescription)}</span>
84
- </div>
85
- `}
86
- </div>
87
- `, this.#shadow, {host: this});
88
- // clang-format on
89
- }
90
- }
91
-
92
- customElements.define('devtools-resources-endpoints-grid', EndpointsGrid);
93
-
94
- declare global {
95
- interface HTMLElementTagNameMap {
96
- 'devtools-resources-endpoints-grid': EndpointsGrid;
83
+ override performUpdate(): void {
84
+ this.#view(
85
+ {
86
+ endpoints: this.endpoints,
87
+ },
88
+ undefined, this.contentElement);
97
89
  }
98
90
  }