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.
- package/front_end/core/host/GdpClient.ts +116 -66
- package/front_end/core/root/Runtime.ts +1 -0
- package/front_end/entrypoints/inspector_main/InspectorMain.ts +82 -32
- package/front_end/entrypoints/inspector_main/inspector_main-meta.ts +1 -1
- package/front_end/entrypoints/main/MainImpl.ts +7 -1
- package/front_end/models/ai_assistance/agents/NetworkAgent.ts +10 -6
- package/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts +42 -4
- package/front_end/models/badges/UserBadges.ts +14 -16
- package/front_end/panels/ai_assistance/components/UserActionRow.ts +1 -2
- package/front_end/panels/application/IndexedDBViews.ts +1 -0
- package/front_end/panels/application/ReportingApiTreeElement.ts +1 -2
- package/front_end/panels/application/ReportingApiView.ts +18 -20
- package/front_end/panels/application/ServiceWorkerCacheViews.ts +3 -0
- package/front_end/panels/application/components/EndpointsGrid.ts +51 -59
- package/front_end/panels/application/components/ReportsGrid.ts +86 -107
- package/front_end/panels/application/components/StorageMetadataView.ts +30 -4
- package/front_end/panels/application/components/endpointsGrid.css +30 -0
- package/front_end/panels/application/components/reportsGrid.css +34 -0
- package/front_end/panels/application/components/storageMetadataView.css +9 -0
- package/front_end/panels/browser_debugger/CategorizedBreakpointsSidebarPane.ts +19 -27
- package/front_end/panels/common/BadgeNotification.ts +10 -3
- package/front_end/panels/network/NetworkPanel.ts +1 -1
- package/front_end/panels/search/SearchResultsPane.ts +14 -13
- package/front_end/panels/search/SearchView.ts +3 -20
- package/front_end/panels/settings/components/SyncSection.ts +8 -6
- package/front_end/panels/sources/SearchSourcesView.ts +1 -1
- package/front_end/panels/whats_new/ReleaseNoteText.ts +15 -11
- package/front_end/panels/whats_new/resources/WNDT.md +9 -6
- package/front_end/third_party/diff/README.chromium +0 -1
- package/front_end/ui/legacy/Treeoutline.ts +6 -9
- package/front_end/ui/legacy/UIUtils.ts +4 -17
- package/front_end/ui/legacy/Widget.ts +0 -5
- package/front_end/ui/legacy/XElement.ts +0 -33
- package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +3 -3
- package/front_end/ui/legacy/components/perf_ui/FilmStripView.ts +38 -21
- package/front_end/ui/legacy/components/perf_ui/filmStripView.css +29 -0
- package/front_end/ui/legacy/components/source_frame/XMLView.ts +3 -2
- package/front_end/ui/legacy/legacy.ts +0 -2
- package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
- package/package.json +1 -1
- package/front_end/panels/application/components/reportingApiGrid.css +0 -31
- package/front_end/ui/legacy/XWidget.ts +0 -133
@@ -68,11 +68,22 @@ export interface Profile {
|
|
68
68
|
};
|
69
69
|
}
|
70
70
|
|
71
|
-
interface
|
72
|
-
|
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
|
98
|
+
async function makeHttpRequest<R>(request: DispatchHttpRequestRequest): Promise<R> {
|
88
99
|
if (!isGdpProfilesAvailable()) {
|
89
|
-
|
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
|
-
|
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
|
-
|
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
|
108
|
-
#cachedEligibilityPromise?: Promise<CheckElibigilityResponse
|
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
|
-
|
123
|
-
|
124
|
-
|
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
|
-
|
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
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
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
|
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
|
-
|
145
|
-
|
146
|
-
|
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
|
-
|
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
|
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
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
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
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
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
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
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;
|
@@ -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
|
-
|
190
|
+
interface ViewInput {
|
191
|
+
nodeProcessRunning: Boolean;
|
192
|
+
}
|
189
193
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
224
|
-
this.#
|
225
|
-
|
226
|
-
|
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.#
|
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.
|
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().
|
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
|
-
|
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
|
-
|
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.
|
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
|
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 (
|
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
|
167
|
-
|
168
|
-
|
169
|
-
|
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 (!
|
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 (
|
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 {
|