chrome-devtools-frontend 1.0.1521880 → 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 (42) 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/models/ai_assistance/agents/NetworkAgent.ts +10 -6
  7. package/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts +42 -4
  8. package/front_end/models/badges/UserBadges.ts +14 -16
  9. package/front_end/panels/ai_assistance/components/UserActionRow.ts +1 -2
  10. package/front_end/panels/application/IndexedDBViews.ts +1 -0
  11. package/front_end/panels/application/ReportingApiTreeElement.ts +1 -2
  12. package/front_end/panels/application/ReportingApiView.ts +18 -20
  13. package/front_end/panels/application/ServiceWorkerCacheViews.ts +3 -0
  14. package/front_end/panels/application/components/EndpointsGrid.ts +51 -59
  15. package/front_end/panels/application/components/ReportsGrid.ts +86 -107
  16. package/front_end/panels/application/components/StorageMetadataView.ts +30 -4
  17. package/front_end/panels/application/components/endpointsGrid.css +30 -0
  18. package/front_end/panels/application/components/reportsGrid.css +34 -0
  19. package/front_end/panels/application/components/storageMetadataView.css +9 -0
  20. package/front_end/panels/browser_debugger/CategorizedBreakpointsSidebarPane.ts +19 -27
  21. package/front_end/panels/common/BadgeNotification.ts +10 -3
  22. package/front_end/panels/network/NetworkPanel.ts +1 -1
  23. package/front_end/panels/search/SearchResultsPane.ts +14 -13
  24. package/front_end/panels/search/SearchView.ts +3 -20
  25. package/front_end/panels/settings/components/SyncSection.ts +8 -6
  26. package/front_end/panels/sources/SearchSourcesView.ts +1 -1
  27. package/front_end/panels/whats_new/ReleaseNoteText.ts +15 -11
  28. package/front_end/panels/whats_new/resources/WNDT.md +9 -6
  29. package/front_end/third_party/diff/README.chromium +0 -1
  30. package/front_end/ui/legacy/Treeoutline.ts +6 -9
  31. package/front_end/ui/legacy/UIUtils.ts +4 -17
  32. package/front_end/ui/legacy/Widget.ts +0 -5
  33. package/front_end/ui/legacy/XElement.ts +0 -33
  34. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +3 -3
  35. package/front_end/ui/legacy/components/perf_ui/FilmStripView.ts +38 -21
  36. package/front_end/ui/legacy/components/perf_ui/filmStripView.css +29 -0
  37. package/front_end/ui/legacy/components/source_frame/XMLView.ts +3 -2
  38. package/front_end/ui/legacy/legacy.ts +0 -2
  39. package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
  40. package/package.json +1 -1
  41. package/front_end/panels/application/components/reportingApiGrid.css +0 -31
  42. package/front_end/ui/legacy/XWidget.ts +0 -133
@@ -68,11 +68,22 @@ export interface Profile {
68
68
  };
69
69
  }
70
70
 
71
- interface InitializeResult {
72
- hasProfile: boolean;
71
+ export interface GetProfileResponse {
72
+ profile: Profile|null;
73
73
  isEligible: boolean;
74
74
  }
75
75
 
76
+ export enum GdpErrorType {
77
+ HTTP_RESPONSE_UNAVAILABLE = 'HTTP_RESPONSE_UNAVAILABLE',
78
+ NOT_FOUND = 'NOT_FOUND',
79
+ }
80
+
81
+ class GdpError extends Error {
82
+ constructor(readonly type: GdpErrorType, options?: ErrorOptions) {
83
+ super(undefined, options);
84
+ }
85
+ }
86
+
76
87
  // The `batchGet` awards endpoint returns badge names with an
77
88
  // obfuscated user ID (e.g., `profiles/12345/awards/badge-name`).
78
89
  // This function normalizes them to use `me` instead of the ID
@@ -84,9 +95,9 @@ function normalizeBadgeName(name: string): string {
84
95
 
85
96
  export const GOOGLE_DEVELOPER_PROGRAM_PROFILE_LINK = 'https://developers.google.com/profile/u/me';
86
97
 
87
- async function makeHttpRequest<R extends object>(request: DispatchHttpRequestRequest): Promise<R|null> {
98
+ async function makeHttpRequest<R>(request: DispatchHttpRequestRequest): Promise<R> {
88
99
  if (!isGdpProfilesAvailable()) {
89
- return null;
100
+ throw new GdpError(GdpErrorType.HTTP_RESPONSE_UNAVAILABLE);
90
101
  }
91
102
 
92
103
  const response = await new Promise<DispatchHttpRequestResult>(resolve => {
@@ -94,18 +105,26 @@ async function makeHttpRequest<R extends object>(request: DispatchHttpRequestReq
94
105
  });
95
106
 
96
107
  debugLog({request, response});
108
+ if (response.statusCode === 404) {
109
+ throw new GdpError(GdpErrorType.NOT_FOUND);
110
+ }
111
+
97
112
  if ('response' in response && response.statusCode === 200) {
98
- return JSON.parse(response.response) as R;
113
+ try {
114
+ return JSON.parse(response.response) as R;
115
+ } catch (err) {
116
+ throw new GdpError(GdpErrorType.HTTP_RESPONSE_UNAVAILABLE, {cause: err});
117
+ }
99
118
  }
100
119
 
101
- return null;
120
+ throw new GdpError(GdpErrorType.HTTP_RESPONSE_UNAVAILABLE);
102
121
  }
103
122
 
104
123
  const SERVICE_NAME = 'gdpService';
105
124
  let gdpClientInstance: GdpClient|null = null;
106
125
  export class GdpClient {
107
- #cachedProfilePromise?: Promise<Profile|null>;
108
- #cachedEligibilityPromise?: Promise<CheckElibigilityResponse|null>;
126
+ #cachedProfilePromise?: Promise<Profile>;
127
+ #cachedEligibilityPromise?: Promise<CheckElibigilityResponse>;
109
128
 
110
129
  private constructor() {
111
130
  }
@@ -119,41 +138,58 @@ export class GdpClient {
119
138
  return gdpClientInstance;
120
139
  }
121
140
 
122
- async initialize(): Promise<InitializeResult> {
123
- const profile = await this.getProfile();
124
- if (profile) {
141
+ /**
142
+ * Fetches the user's GDP profile and eligibility status.
143
+ *
144
+ * It first attempts to fetch the profile. If the profile is not found
145
+ * (a `NOT_FOUND` error), this is handled gracefully by treating the profile
146
+ * as `null` and then proceeding to check for eligibility.
147
+ *
148
+ * @returns A promise that resolves with an object containing the `profile`
149
+ * and `isEligible` status, or `null` if an unexpected error occurs.
150
+ */
151
+ async getProfile(): Promise<GetProfileResponse|null> {
152
+ try {
153
+ const profile = await this.#getProfile();
125
154
  return {
126
- hasProfile: true,
155
+ profile,
127
156
  isEligible: true,
128
157
  };
158
+ } catch (err: unknown) {
159
+ if (err instanceof GdpError && err.type === GdpErrorType.HTTP_RESPONSE_UNAVAILABLE) {
160
+ return null;
161
+ }
129
162
  }
130
163
 
131
- const isEligible = await this.isEligibleToCreateProfile();
132
- return {
133
- hasProfile: false,
134
- isEligible,
135
- };
164
+ try {
165
+ const checkEligibilityResponse = await this.#checkEligibility();
166
+ return {
167
+ profile: null,
168
+ isEligible: checkEligibilityResponse.createProfile === EligibilityStatus.ELIGIBLE,
169
+ };
170
+ } catch {
171
+ return null;
172
+ }
136
173
  }
137
174
 
138
- async getProfile(): Promise<Profile|null> {
175
+ async #getProfile(): Promise<Profile> {
139
176
  if (this.#cachedProfilePromise) {
140
177
  return await this.#cachedProfilePromise;
141
178
  }
142
179
 
143
- this.#cachedProfilePromise = makeHttpRequest({
144
- service: SERVICE_NAME,
145
- path: '/v1beta1/profile:get',
146
- method: 'GET',
180
+ this.#cachedProfilePromise = makeHttpRequest<Profile>({
181
+ service: SERVICE_NAME,
182
+ path: '/v1beta1/profile:get',
183
+ method: 'GET',
184
+ }).then(profile => {
185
+ this.#cachedEligibilityPromise = Promise.resolve({createProfile: EligibilityStatus.ELIGIBLE});
186
+ return profile;
147
187
  });
148
188
 
149
- const profile = await this.#cachedProfilePromise;
150
- if (profile) {
151
- this.#cachedEligibilityPromise = Promise.resolve({createProfile: EligibilityStatus.ELIGIBLE});
152
- }
153
- return profile;
189
+ return await this.#cachedProfilePromise;
154
190
  }
155
191
 
156
- async checkEligibility(): Promise<CheckElibigilityResponse|null> {
192
+ async #checkEligibility(): Promise<CheckElibigilityResponse> {
157
193
  if (this.#cachedEligibilityPromise) {
158
194
  return await this.#cachedEligibilityPromise;
159
195
  }
@@ -168,42 +204,40 @@ export class GdpClient {
168
204
  * @returns null if the request fails, the awarded badge names otherwise.
169
205
  */
170
206
  async getAwardedBadgeNames({names}: {names: string[]}): Promise<Set<string>|null> {
171
- const result = await makeHttpRequest<BatchGetAwardsResponse>({
172
- service: SERVICE_NAME,
173
- path: '/v1beta1/profiles/me/awards:batchGet',
174
- method: 'GET',
175
- queryParams: {
176
- allowMissing: 'true',
177
- names,
178
- }
179
- });
180
-
181
- if (!result) {
207
+ try {
208
+ const response = await makeHttpRequest<BatchGetAwardsResponse>({
209
+ service: SERVICE_NAME,
210
+ path: '/v1beta1/profiles/me/awards:batchGet',
211
+ method: 'GET',
212
+ queryParams: {
213
+ allowMissing: 'true',
214
+ names,
215
+ }
216
+ });
217
+
218
+ return new Set(response.awards?.map(award => normalizeBadgeName(award.name)) ?? []);
219
+ } catch {
182
220
  return null;
183
221
  }
184
-
185
- return new Set(result.awards?.map(award => normalizeBadgeName(award.name)) ?? []);
186
- }
187
-
188
- async isEligibleToCreateProfile(): Promise<boolean> {
189
- return (await this.checkEligibility())?.createProfile === EligibilityStatus.ELIGIBLE;
190
222
  }
191
223
 
192
224
  async createProfile({user, emailPreference}: {user: string, emailPreference: EmailPreference}):
193
225
  Promise<Profile|null> {
194
- const result = await makeHttpRequest<Profile>({
195
- service: SERVICE_NAME,
196
- path: '/v1beta1/profiles',
197
- method: 'POST',
198
- body: JSON.stringify({
199
- user,
200
- newsletter_email: emailPreference,
201
- }),
202
- });
203
- if (result) {
226
+ try {
227
+ const response = await makeHttpRequest<Profile>({
228
+ service: SERVICE_NAME,
229
+ path: '/v1beta1/profiles',
230
+ method: 'POST',
231
+ body: JSON.stringify({
232
+ user,
233
+ newsletter_email: emailPreference,
234
+ }),
235
+ });
204
236
  this.#clearCache();
237
+ return response;
238
+ } catch {
239
+ return null;
205
240
  }
206
- return result;
207
241
  }
208
242
 
209
243
  #clearCache(): void {
@@ -211,16 +245,21 @@ export class GdpClient {
211
245
  this.#cachedEligibilityPromise = undefined;
212
246
  }
213
247
 
214
- createAward({name}: {name: string}): Promise<Award|null> {
215
- return makeHttpRequest({
216
- service: SERVICE_NAME,
217
- path: '/v1beta1/profiles/me/awards',
218
- method: 'POST',
219
- body: JSON.stringify({
220
- awardingUri: 'devtools://devtools',
221
- name,
222
- })
223
- });
248
+ async createAward({name}: {name: string}): Promise<Award|null> {
249
+ try {
250
+ const response = await makeHttpRequest<Award>({
251
+ service: SERVICE_NAME,
252
+ path: '/v1beta1/profiles/me/awards',
253
+ method: 'POST',
254
+ body: JSON.stringify({
255
+ awardingUri: 'devtools://devtools',
256
+ name,
257
+ })
258
+ });
259
+ return response;
260
+ } catch {
261
+ return null;
262
+ }
224
263
  }
225
264
  }
226
265
 
@@ -260,5 +299,16 @@ export function getGdpProfilesEnterprisePolicy(): Root.Runtime.GdpProfilesEnterp
260
299
  Root.Runtime.GdpProfilesEnterprisePolicyValue.DISABLED);
261
300
  }
262
301
 
302
+ export function isBadgesEnabled(): boolean {
303
+ const isBadgesEnabledByEnterprisePolicy =
304
+ getGdpProfilesEnterprisePolicy() === Root.Runtime.GdpProfilesEnterprisePolicyValue.ENABLED;
305
+ const isBadgesEnabledByFeatureFlag = Boolean(Root.Runtime.hostConfig.devToolsGdpProfiles?.badgesEnabled);
306
+ return isBadgesEnabledByEnterprisePolicy && isBadgesEnabledByFeatureFlag;
307
+ }
308
+
309
+ export function isStarterBadgeEnabled(): boolean {
310
+ return Boolean(Root.Runtime.hostConfig.devToolsGdpProfiles?.starterBadgeEnabled);
311
+ }
312
+
263
313
  // @ts-expect-error
264
314
  globalThis.setDebugGdpIntegrationEnabled = setDebugGdpIntegrationEnabled;
@@ -476,6 +476,7 @@ interface GlobalAiButton {
476
476
 
477
477
  interface GdpProfiles {
478
478
  enabled: boolean;
479
+ badgesEnabled: boolean;
479
480
  starterBadgeEnabled: boolean;
480
481
  }
481
482
 
@@ -1,7 +1,6 @@
1
1
  // Copyright 2018 The Chromium Authors
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
- /* eslint-disable rulesdir/no-imperative-dom-api */
5
4
 
6
5
  import * as Common from '../../core/common/common.js';
7
6
  import * as Host from '../../core/host/host.js';
@@ -13,9 +12,12 @@ import * as MobileThrottling from '../../panels/mobile_throttling/mobile_throttl
13
12
  import * as Security from '../../panels/security/security.js';
14
13
  import * as Components from '../../ui/legacy/components/utils/utils.js';
15
14
  import * as UI from '../../ui/legacy/legacy.js';
15
+ import * as Lit from '../../ui/lit/lit.js';
16
16
 
17
17
  import nodeIconStyles from './nodeIcon.css.js';
18
18
 
19
+ const {html} = Lit;
20
+
19
21
  const UIStrings = {
20
22
  /**
21
23
  * @description Text that refers to the main target. The main target is the primary webpage that
@@ -185,50 +187,98 @@ export class FocusDebuggeeActionDelegate implements UI.ActionRegistration.Action
185
187
  }
186
188
  }
187
189
 
188
- let nodeIndicatorInstance: NodeIndicator;
190
+ interface ViewInput {
191
+ nodeProcessRunning: Boolean;
192
+ }
189
193
 
190
- export class NodeIndicator implements UI.Toolbar.Provider {
191
- readonly #element: Element;
192
- readonly #button: UI.Toolbar.ToolbarItem;
193
- private constructor() {
194
- const element = document.createElement('div');
195
- const shadowRoot = UI.UIUtils.createShadowRootWithCoreStyles(element, {cssFile: nodeIconStyles});
196
- this.#element = shadowRoot.createChild('div', 'node-icon');
197
- element.addEventListener(
198
- 'click', () => Host.InspectorFrontendHost.InspectorFrontendHostInstance.openNodeFrontend(), false);
199
- this.#button = new UI.Toolbar.ToolbarItem(element);
200
- this.#button.setTitle(i18nString(UIStrings.openDedicatedTools));
201
- SDK.TargetManager.TargetManager.instance().addEventListener(
202
- SDK.TargetManager.Events.AVAILABLE_TARGETS_CHANGED, event => this.#update(event.data));
203
- this.#button.setVisible(false);
204
- this.#update([]);
205
- }
206
- static instance(opts: {
207
- forceNew: boolean|null,
208
- } = {forceNew: null}): NodeIndicator {
209
- const {forceNew} = opts;
210
- if (!nodeIndicatorInstance || forceNew) {
211
- nodeIndicatorInstance = new NodeIndicator();
212
- }
194
+ type View = (input: ViewInput, _output: object, target: HTMLElement) => void;
195
+
196
+ const isNodeProcessRunning = (targetInfos: Protocol.Target.TargetInfo[]): Boolean => {
197
+ return Boolean(targetInfos.find(target => target.type === 'node' && !target.attached));
198
+ };
199
+
200
+ export const DEFAULT_VIEW: View = (input, output, target) => {
201
+ const {
202
+ nodeProcessRunning,
203
+ } = input;
204
+
205
+ // clang-format off
206
+ Lit.render(html`
207
+ <style>${nodeIconStyles}</style>
208
+ <div
209
+ class="node-icon ${!nodeProcessRunning ? 'inactive' : ''}"
210
+ title=${i18nString(UIStrings.openDedicatedTools)}
211
+ @click=${
212
+ () => Host.InspectorFrontendHost.InspectorFrontendHostInstance.openNodeFrontend()}>
213
+ </div>
214
+ `, target);
215
+ // clang-format on
216
+ };
217
+
218
+ export class NodeIndicator extends UI.Widget.Widget {
219
+ readonly #view: View;
220
+ #targetInfos: Protocol.Target.TargetInfo[] = [];
221
+ #wasShown = false;
222
+
223
+ constructor(element?: HTMLElement, view = DEFAULT_VIEW) {
224
+ super(element, {useShadowDom: true});
225
+ this.#view = view;
213
226
 
214
- return nodeIndicatorInstance;
227
+ SDK.TargetManager.TargetManager.instance().addEventListener(
228
+ SDK.TargetManager.Events.AVAILABLE_TARGETS_CHANGED, event => {
229
+ this.#targetInfos = event.data;
230
+ this.requestUpdate();
231
+ });
215
232
  }
216
233
 
217
- #update(targetInfos: Protocol.Target.TargetInfo[]): void {
234
+ override performUpdate(): void {
218
235
  // Disable when we are testing, as debugging e2e
219
236
  // attaches a debug process and this changes some view sizes
220
237
  if (Host.InspectorFrontendHost.isUnderTest()) {
221
238
  return;
222
239
  }
223
- const hasNode = Boolean(targetInfos.find(target => target.type === 'node' && !target.attached));
224
- this.#element.classList.toggle('inactive', !hasNode);
225
- if (hasNode) {
226
- this.#button.setVisible(true);
240
+
241
+ const nodeProcessRunning: Boolean = isNodeProcessRunning(this.#targetInfos);
242
+
243
+ if (!this.#wasShown && !nodeProcessRunning) {
244
+ // This widget is designed to be hidden until the first debuggable Node process is detected. Therefore
245
+ // we don't construct the view if there's no data. After we've shown it once, it remains on-sreen and
246
+ // indicates via its disabled state whether Node debugging is available.
247
+ return;
227
248
  }
249
+ this.#wasShown = true;
250
+
251
+ const input: ViewInput = {
252
+ nodeProcessRunning,
253
+ };
254
+ this.#view(input, {}, this.contentElement);
255
+ }
256
+ }
257
+
258
+ let nodeIndicatorProviderInstance: NodeIndicatorProvider;
259
+ export class NodeIndicatorProvider implements UI.Toolbar.Provider {
260
+ #toolbarItem: UI.Toolbar.ToolbarItem;
261
+ #widgetElement: UI.Widget.WidgetElement<NodeIndicator>;
262
+
263
+ private constructor() {
264
+ this.#widgetElement = document.createElement('devtools-widget') as UI.Widget.WidgetElement<NodeIndicator>;
265
+ this.#widgetElement.widgetConfig = UI.Widget.widgetConfig(NodeIndicator);
266
+
267
+ this.#toolbarItem = new UI.Toolbar.ToolbarItem(this.#widgetElement);
268
+ this.#toolbarItem.setVisible(false);
228
269
  }
229
270
 
230
271
  item(): UI.Toolbar.ToolbarItem|null {
231
- return this.#button;
272
+ return this.#toolbarItem;
273
+ }
274
+
275
+ static instance(opts: {forceNew: boolean|null} = {forceNew: null}): NodeIndicatorProvider {
276
+ const {forceNew} = opts;
277
+ if (!nodeIndicatorProviderInstance || forceNew) {
278
+ nodeIndicatorProviderInstance = new NodeIndicatorProvider();
279
+ }
280
+
281
+ return nodeIndicatorProviderInstance;
232
282
  }
233
283
  }
234
284
 
@@ -257,7 +257,7 @@ Common.Settings.registerSettingExtension({
257
257
  UI.Toolbar.registerToolbarItem({
258
258
  async loadItem() {
259
259
  const InspectorMain = await loadInspectorMainModule();
260
- return InspectorMain.InspectorMain.NodeIndicator.instance();
260
+ return InspectorMain.InspectorMain.NodeIndicatorProvider.instance();
261
261
  },
262
262
  order: 2,
263
263
  location: UI.Toolbar.ToolbarItemLocation.MAIN_TOOLBAR_LEFT,
@@ -519,7 +519,13 @@ export class MainImpl {
519
519
 
520
520
  // Initialize `GDPClient` and `UserBadges` for Google Developer Program integration
521
521
  if (Host.GdpClient.isGdpProfilesAvailable()) {
522
- void Host.GdpClient.GdpClient.instance().initialize().then(({hasProfile, isEligible}) => {
522
+ void Host.GdpClient.GdpClient.instance().getProfile().then(getProfileResponse => {
523
+ if (!getProfileResponse) {
524
+ return;
525
+ }
526
+
527
+ const {profile, isEligible} = getProfileResponse;
528
+ const hasProfile = Boolean(profile);
523
529
  const contextString = hasProfile ? 'has-profile' :
524
530
  isEligible ? 'no-profile-and-eligible' :
525
531
  'no-profile-and-not-eligible';
@@ -149,23 +149,23 @@ export class NetworkAgent extends AiAgent<SDK.NetworkRequest.NetworkRequest> {
149
149
  yield {
150
150
  type: ResponseType.CONTEXT,
151
151
  title: lockedString(UIStringsNotTranslate.analyzingNetworkData),
152
- details: createContextDetailsForNetworkAgent(selectedNetworkRequest),
152
+ details: await createContextDetailsForNetworkAgent(selectedNetworkRequest),
153
153
  };
154
154
  }
155
155
 
156
156
  override async enhanceQuery(query: string, selectedNetworkRequest: RequestContext|null): Promise<string> {
157
157
  const networkEnchantmentQuery = selectedNetworkRequest ?
158
158
  `# Selected network request \n${
159
- new NetworkRequestFormatter(selectedNetworkRequest.getItem(), selectedNetworkRequest.calculator)
160
- .formatNetworkRequest()}\n\n# User request\n\n` :
159
+ await (new NetworkRequestFormatter(selectedNetworkRequest.getItem(), selectedNetworkRequest.calculator)
160
+ .formatNetworkRequest())}\n\n# User request\n\n` :
161
161
  '';
162
162
  return `${networkEnchantmentQuery}${query}`;
163
163
  }
164
164
  }
165
165
 
166
- function createContextDetailsForNetworkAgent(
166
+ async function createContextDetailsForNetworkAgent(
167
167
  selectedNetworkRequest: RequestContext,
168
- ): [ContextDetail, ...ContextDetail[]] {
168
+ ): Promise<[ContextDetail, ...ContextDetail[]]> {
169
169
  const request = selectedNetworkRequest.getItem();
170
170
  const formatter = new NetworkRequestFormatter(request, selectedNetworkRequest.calculator);
171
171
  const requestContextDetail: ContextDetail = {
@@ -173,10 +173,13 @@ function createContextDetailsForNetworkAgent(
173
173
  text: lockedString(UIStringsNotTranslate.requestUrl) + ': ' + request.url() + '\n\n' +
174
174
  formatter.formatRequestHeaders(),
175
175
  };
176
+ const responseBody = await formatter.formatResponseBody();
177
+ const responseBodyString = responseBody ? `\n\n${responseBody}` : '';
178
+
176
179
  const responseContextDetail: ContextDetail = {
177
180
  title: lockedString(UIStringsNotTranslate.response),
178
181
  text: lockedString(UIStringsNotTranslate.responseStatus) + ': ' + request.statusCode + ' ' + request.statusText +
179
- '\n\n' + formatter.formatResponseHeaders(),
182
+ `\n\n${formatter.formatResponseHeaders()}` + responseBodyString,
180
183
  };
181
184
  const timingContextDetail: ContextDetail = {
182
185
  title: lockedString(UIStringsNotTranslate.timing),
@@ -186,6 +189,7 @@ function createContextDetailsForNetworkAgent(
186
189
  title: lockedString(UIStringsNotTranslate.requestInitiatorChain),
187
190
  text: formatter.formatRequestInitiatorChain(),
188
191
  };
192
+
189
193
  return [
190
194
  requestContextDetail,
191
195
  responseContextDetail,
@@ -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 {