chrome-devtools-frontend 1.0.1574367 → 1.0.1575174
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/AUTHORS +1 -0
- package/docs/contributing/settings-experiments-features.md +5 -2
- package/front_end/core/common/Settings.ts +1 -1
- package/front_end/core/host/InspectorFrontendHostAPI.ts +5 -1
- package/front_end/core/host/InspectorFrontendHostStub.ts +10 -0
- package/front_end/core/host/UserMetrics.ts +15 -0
- package/front_end/core/root/Runtime.ts +119 -39
- package/front_end/entrypoints/main/MainImpl.ts +13 -1
- package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +3 -3
- package/front_end/models/ai_assistance/agents/StylingAgent.ts +5 -4
- package/front_end/models/issues_manager/CorsIssue.ts +0 -3
- package/front_end/models/stack_trace/StackTrace.ts +40 -0
- package/front_end/panels/ai_assistance/components/ChatInput.ts +24 -2
- package/front_end/panels/common/AiCodeGenerationUpgradeDialog.ts +32 -16
- package/front_end/panels/console/ConsoleFormat.ts +31 -2
- package/front_end/panels/console/ConsoleInsightTeaser.ts +6 -1
- package/front_end/panels/elements/ElementsTreeElement.ts +44 -0
- package/front_end/panels/elements/ElementsTreeOutline.ts +1 -1
- package/front_end/panels/settings/SettingsScreen.ts +12 -6
- package/front_end/panels/settings/settings-meta.ts +1 -0
- package/front_end/panels/whats_new/ReleaseNoteText.ts +11 -11
- package/front_end/panels/whats_new/resources/WNDT.md +6 -6
- package/mcp/HostBindings.ts +6 -0
- package/package.json +1 -1
package/AUTHORS
CHANGED
|
@@ -64,6 +64,7 @@ Krishnal Ciccolella <ciccolella.krishnal@gmail.com>
|
|
|
64
64
|
Liam DeBeasi <ldebeasi@gmail.com>
|
|
65
65
|
Luke Swiderski <luke.swiderski@gmail.com>
|
|
66
66
|
Luke Warlow <luke@warlow.dev>
|
|
67
|
+
Lyra Rebane <rebane2001@gmail.com>
|
|
67
68
|
Marijn Haverbeke <marijnh@gmail.com>
|
|
68
69
|
Max 😎 Coplan <mchcopl@gmail.com>
|
|
69
70
|
Michael Brüning <michael.bruning@qt.io>
|
|
@@ -12,6 +12,9 @@ Adding new DevTools experiments is deprecated, the preferred way for adding new
|
|
|
12
12
|
features / exposing experimental features is via `base::Feature`s. These are
|
|
13
13
|
controllable via Chromium command line parameters or optionally via `chrome://flags`.
|
|
14
14
|
|
|
15
|
+
Note: We are currently in the process of migrating away from DevTools experiments,
|
|
16
|
+
this documentation is partly outdated and will be updated ASAP.
|
|
17
|
+
|
|
15
18
|
|
|
16
19
|
[TOC]
|
|
17
20
|
|
|
@@ -76,9 +79,9 @@ This step is optional. If you want the `base::Feature` to be controllable via th
|
|
|
76
79
|
|
|
77
80
|
Please refer to this [example CL](https://crrev.com/c/5626314).
|
|
78
81
|
|
|
79
|
-
## How to add experiments
|
|
82
|
+
## DEPRECATED:How to add experiments
|
|
80
83
|
|
|
81
|
-
Note:
|
|
84
|
+
Note: We are currently in the process of migrating away from DevTools experiments, please use a `base::Feature` instead.
|
|
82
85
|
|
|
83
86
|
If you want to launch a new feature in DevTools behind an experiment flag, you
|
|
84
87
|
will need to do two things:
|
|
@@ -343,7 +343,7 @@ export class SettingsStorage {
|
|
|
343
343
|
export class Deprecation {
|
|
344
344
|
readonly disabled: boolean;
|
|
345
345
|
readonly warning: Platform.UIString.LocalizedString;
|
|
346
|
-
readonly experiment?: Root.Runtime.Experiment;
|
|
346
|
+
readonly experiment?: Root.Runtime.Experiment|Root.Runtime.HostExperiment;
|
|
347
347
|
|
|
348
348
|
constructor({deprecationNotice}: SettingRegistration) {
|
|
349
349
|
if (!deprecationNotice) {
|
|
@@ -389,6 +389,8 @@ export interface InspectorFrontendHostAPI {
|
|
|
389
389
|
|
|
390
390
|
recordPerformanceHistogram(histogramName: string, duration: number): void;
|
|
391
391
|
|
|
392
|
+
recordPerformanceHistogramMedium(histogramName: string, duration: number): void;
|
|
393
|
+
|
|
392
394
|
recordUserMetricsAction(umaName: string): void;
|
|
393
395
|
|
|
394
396
|
recordNewBadgeUsage(featureName: string): void;
|
|
@@ -449,6 +451,8 @@ export interface InspectorFrontendHostAPI {
|
|
|
449
451
|
recordKeyDown(event: KeyDownEvent): void;
|
|
450
452
|
recordSettingAccess(event: SettingAccessEvent): void;
|
|
451
453
|
recordFunctionCall(event: FunctionCallEvent): void;
|
|
454
|
+
|
|
455
|
+
setChromeFlag(flagName: string, value: boolean): void;
|
|
452
456
|
}
|
|
453
457
|
|
|
454
458
|
export interface AcceleratorDescriptor {
|
|
@@ -512,7 +516,7 @@ export interface SyncInformation {
|
|
|
512
516
|
}
|
|
513
517
|
|
|
514
518
|
/**
|
|
515
|
-
* Enum for
|
|
519
|
+
* Enum for recordEnumeratedHistogram
|
|
516
520
|
* Warning: There is another definition of this enum in the DevTools code
|
|
517
521
|
* base, keep them in sync:
|
|
518
522
|
* front_end/devtools_compatibility.js
|
|
@@ -255,6 +255,13 @@ export class InspectorFrontendHostStub implements InspectorFrontendHostAPI {
|
|
|
255
255
|
this.recordedPerformanceHistograms.push({histogramName, duration});
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
+
recordPerformanceHistogramMedium(histogramName: string, duration: number): void {
|
|
259
|
+
if (this.recordedPerformanceHistograms.length >= MAX_RECORDED_HISTOGRAMS_SIZE) {
|
|
260
|
+
this.recordedPerformanceHistograms.shift();
|
|
261
|
+
}
|
|
262
|
+
this.recordedPerformanceHistograms.push({histogramName, duration});
|
|
263
|
+
}
|
|
264
|
+
|
|
258
265
|
recordUserMetricsAction(_umaName: string): void {
|
|
259
266
|
}
|
|
260
267
|
|
|
@@ -555,4 +562,7 @@ export class InspectorFrontendHostStub implements InspectorFrontendHostAPI {
|
|
|
555
562
|
}
|
|
556
563
|
recordFunctionCall(_event: FunctionCallEvent): void {
|
|
557
564
|
}
|
|
565
|
+
|
|
566
|
+
setChromeFlag(_flagName: string, _value: boolean): void {
|
|
567
|
+
}
|
|
558
568
|
}
|
|
@@ -283,11 +283,26 @@ export class UserMetrics {
|
|
|
283
283
|
'DevTools.Insights.TeaserGenerationTime', timeInMilliseconds);
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
+
consoleInsightTeaserGeneratedMedium(timeInMilliseconds: number): void {
|
|
287
|
+
InspectorFrontendHostInstance.recordPerformanceHistogramMedium(
|
|
288
|
+
'DevTools.Insights.TeaserGenerationTimeMedium', timeInMilliseconds);
|
|
289
|
+
}
|
|
290
|
+
|
|
286
291
|
consoleInsightTeaserFirstChunkGenerated(timeInMilliseconds: number): void {
|
|
287
292
|
InspectorFrontendHostInstance.recordPerformanceHistogram(
|
|
288
293
|
'DevTools.Insights.TeaserFirstChunkGenerationTime', timeInMilliseconds);
|
|
289
294
|
}
|
|
290
295
|
|
|
296
|
+
consoleInsightTeaserFirstChunkGeneratedMedium(timeInMilliseconds: number): void {
|
|
297
|
+
InspectorFrontendHostInstance.recordPerformanceHistogramMedium(
|
|
298
|
+
'DevTools.Insights.TeaserFirstChunkGenerationTimeMedium', timeInMilliseconds);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
consoleInsightTeaserChunkToEndMedium(timeInMilliseconds: number): void {
|
|
302
|
+
InspectorFrontendHostInstance.recordPerformanceHistogramMedium(
|
|
303
|
+
'DevTools.Insights.TeaserChunkToEndMedium', timeInMilliseconds);
|
|
304
|
+
}
|
|
305
|
+
|
|
291
306
|
consoleInsightTeaserAbortedAfterFirstCharacter(timeInMilliseconds: number): void {
|
|
292
307
|
InspectorFrontendHostInstance.recordPerformanceHistogram(
|
|
293
308
|
'DevTools.Insights.TeaserAfterFirstCharacterAbortionTime', timeInMilliseconds);
|
|
@@ -159,24 +159,35 @@ export interface Option {
|
|
|
159
159
|
|
|
160
160
|
export class ExperimentsSupport {
|
|
161
161
|
#experiments: Experiment[] = [];
|
|
162
|
+
#hostExperiments = new Map<ExperimentName, HostExperiment>();
|
|
162
163
|
readonly #experimentNames = new Set<ExperimentName>();
|
|
163
|
-
readonly #
|
|
164
|
+
readonly #enabledForTests = new Set<ExperimentName>();
|
|
164
165
|
readonly #enabledByDefault = new Set<ExperimentName>();
|
|
165
166
|
readonly #serverEnabled = new Set<ExperimentName>();
|
|
166
167
|
readonly #storage = new ExperimentStorage();
|
|
167
168
|
|
|
168
|
-
allConfigurableExperiments(): Experiment
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
169
|
+
allConfigurableExperiments(): Array<Experiment|HostExperiment> {
|
|
170
|
+
return [...this.#experiments, ...this.#hostExperiments.values()];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
registerHostExperiment(params: {
|
|
174
|
+
name: ExperimentName,
|
|
175
|
+
title: string,
|
|
176
|
+
aboutFlag: string,
|
|
177
|
+
isEnabled: boolean,
|
|
178
|
+
docLink?: Platform.DevToolsPath.UrlString,
|
|
179
|
+
readonly feedbackLink?: Platform.DevToolsPath.UrlString,
|
|
180
|
+
}): HostExperiment {
|
|
181
|
+
if (this.#isHostExperiment(params.name) || this.#isExperiment(params.name)) {
|
|
182
|
+
throw new Error(`Duplicate registration of experiment '${params.name}'`);
|
|
174
183
|
}
|
|
175
|
-
|
|
184
|
+
const hostExperiment = new HostExperiment({...params, experiments: this});
|
|
185
|
+
this.#hostExperiments.set(params.name, hostExperiment);
|
|
186
|
+
return hostExperiment;
|
|
176
187
|
}
|
|
177
188
|
|
|
178
189
|
register(experimentName: ExperimentName, experimentTitle: string, docLink?: string, feedbackLink?: string): void {
|
|
179
|
-
if (this.#
|
|
190
|
+
if (this.#isHostExperiment(experimentName) || this.#isExperiment(experimentName)) {
|
|
180
191
|
throw new Error(`Duplicate registration of experiment '${experimentName}'`);
|
|
181
192
|
}
|
|
182
193
|
this.#experimentNames.add(experimentName);
|
|
@@ -187,63 +198,87 @@ export class ExperimentsSupport {
|
|
|
187
198
|
}
|
|
188
199
|
|
|
189
200
|
isEnabled(experimentName: ExperimentName): boolean {
|
|
190
|
-
this
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if (this.#storage.get(experimentName) === false) {
|
|
194
|
-
return false;
|
|
201
|
+
if (this.#isHostExperiment(experimentName)) {
|
|
202
|
+
return this.#enabledForTests.has(experimentName) ||
|
|
203
|
+
(this.#hostExperiments.get(experimentName)?.isEnabled() ?? false);
|
|
195
204
|
}
|
|
196
|
-
if (this.#
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
205
|
+
if (this.#isExperiment(experimentName)) {
|
|
206
|
+
// Check for explicitly disabled #experiments first - the code could call setEnable(false)
|
|
207
|
+
// on the experiment enabled by default and we should respect that.
|
|
208
|
+
if (this.#storage.get(experimentName) === false) {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
if (this.#enabledForTests.has(experimentName) || this.#enabledByDefault.has(experimentName)) {
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
if (this.#serverEnabled.has(experimentName)) {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
return Boolean(this.#storage.get(experimentName));
|
|
201
218
|
}
|
|
202
|
-
|
|
203
|
-
return Boolean(this.#storage.get(experimentName));
|
|
219
|
+
throw new Error(`Unknown experiment '${experimentName}'`);
|
|
204
220
|
}
|
|
205
221
|
|
|
206
|
-
|
|
207
|
-
this.
|
|
208
|
-
this.#storage.set(experimentName, enabled);
|
|
222
|
+
getValueFromStorage(experimentName: ExperimentName): boolean|undefined {
|
|
223
|
+
return this.#storage.get(experimentName);
|
|
209
224
|
}
|
|
210
225
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
this.
|
|
214
|
-
|
|
226
|
+
setEnabled(experimentName: ExperimentName, enabled: boolean): void {
|
|
227
|
+
if (this.#isHostExperiment(experimentName)) {
|
|
228
|
+
this.#hostExperiments.get(experimentName)?.setEnabled(enabled);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (this.#isExperiment(experimentName)) {
|
|
232
|
+
this.#storage.set(experimentName, enabled);
|
|
233
|
+
return;
|
|
215
234
|
}
|
|
235
|
+
throw new Error(`Unknown experiment '${experimentName}'`);
|
|
216
236
|
}
|
|
217
237
|
|
|
238
|
+
// Only applicable to legacy experiments.
|
|
218
239
|
enableExperimentsByDefault(experimentNames: ExperimentName[]): void {
|
|
219
240
|
for (const experimentName of experimentNames) {
|
|
220
|
-
this
|
|
241
|
+
if (!this.#isExperiment(experimentName)) {
|
|
242
|
+
throw new Error(`Unknown (legacy) experiment '${experimentName}'`);
|
|
243
|
+
}
|
|
221
244
|
this.#enabledByDefault.add(experimentName);
|
|
222
245
|
}
|
|
223
246
|
}
|
|
224
247
|
|
|
248
|
+
// Only applicable to legacy experiments.
|
|
225
249
|
setServerEnabledExperiments(experiments: string[]): void {
|
|
226
250
|
for (const experiment of experiments) {
|
|
227
251
|
const experimentName = experiment as ExperimentName;
|
|
228
|
-
this
|
|
252
|
+
if (!this.#isExperiment(experimentName)) {
|
|
253
|
+
throw new Error(`Unknown (legacy) experiment '${experimentName}'`);
|
|
254
|
+
}
|
|
229
255
|
this.#serverEnabled.add(experimentName);
|
|
230
256
|
}
|
|
231
257
|
}
|
|
232
258
|
|
|
233
259
|
enableForTest(experimentName: ExperimentName): void {
|
|
234
|
-
this
|
|
235
|
-
|
|
260
|
+
if (!this.#isHostExperiment(experimentName) && !this.#isExperiment(experimentName)) {
|
|
261
|
+
throw new Error(`Unknown experiment '${experimentName}'`);
|
|
262
|
+
}
|
|
263
|
+
this.#enabledForTests.add(experimentName);
|
|
236
264
|
}
|
|
237
265
|
|
|
238
266
|
disableForTest(experimentName: ExperimentName): void {
|
|
239
|
-
this
|
|
240
|
-
|
|
267
|
+
if (!this.#isHostExperiment(experimentName) && !this.#isExperiment(experimentName)) {
|
|
268
|
+
throw new Error(`Unknown experiment '${experimentName}'`);
|
|
269
|
+
}
|
|
270
|
+
this.#enabledForTests.delete(experimentName);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
isEnabledForTest(experimentName: ExperimentName): boolean {
|
|
274
|
+
return this.#enabledForTests.has(experimentName);
|
|
241
275
|
}
|
|
242
276
|
|
|
243
277
|
clearForTest(): void {
|
|
244
278
|
this.#experiments = [];
|
|
279
|
+
this.#hostExperiments.clear();
|
|
245
280
|
this.#experimentNames.clear();
|
|
246
|
-
this.#
|
|
281
|
+
this.#enabledForTests.clear();
|
|
247
282
|
this.#enabledByDefault.clear();
|
|
248
283
|
this.#serverEnabled.clear();
|
|
249
284
|
}
|
|
@@ -252,10 +287,12 @@ export class ExperimentsSupport {
|
|
|
252
287
|
this.#storage.cleanUpStaleExperiments(this.#experimentNames);
|
|
253
288
|
}
|
|
254
289
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
290
|
+
#isHostExperiment(experimentName: ExperimentName): boolean {
|
|
291
|
+
return this.#hostExperiments.has(experimentName);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
#isExperiment(experimentName: ExperimentName): boolean {
|
|
295
|
+
return this.#experimentNames.has(experimentName);
|
|
259
296
|
}
|
|
260
297
|
}
|
|
261
298
|
|
|
@@ -332,6 +369,44 @@ export class Experiment {
|
|
|
332
369
|
}
|
|
333
370
|
}
|
|
334
371
|
|
|
372
|
+
export class HostExperiment {
|
|
373
|
+
name: ExperimentName;
|
|
374
|
+
title: string;
|
|
375
|
+
readonly #experiments: ExperimentsSupport;
|
|
376
|
+
// This is the name of the corresponding Chromium flag (in chrome/browser/about_flags.cc).
|
|
377
|
+
// It is NOT the the name of the corresponding Chromium `base::Feature`.
|
|
378
|
+
aboutFlag: string;
|
|
379
|
+
#isEnabled: boolean;
|
|
380
|
+
docLink?: Platform.DevToolsPath.UrlString;
|
|
381
|
+
readonly feedbackLink?: Platform.DevToolsPath.UrlString;
|
|
382
|
+
|
|
383
|
+
constructor(params: {
|
|
384
|
+
name: ExperimentName,
|
|
385
|
+
title: string,
|
|
386
|
+
experiments: ExperimentsSupport,
|
|
387
|
+
aboutFlag: string,
|
|
388
|
+
isEnabled: boolean,
|
|
389
|
+
docLink?: Platform.DevToolsPath.UrlString,
|
|
390
|
+
feedbackLink?: Platform.DevToolsPath.UrlString,
|
|
391
|
+
}) {
|
|
392
|
+
this.name = params.name;
|
|
393
|
+
this.title = params.title;
|
|
394
|
+
this.#experiments = params.experiments;
|
|
395
|
+
this.aboutFlag = params.aboutFlag;
|
|
396
|
+
this.#isEnabled = params.isEnabled;
|
|
397
|
+
this.docLink = params.docLink;
|
|
398
|
+
this.feedbackLink = params.feedbackLink;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
isEnabled(): boolean {
|
|
402
|
+
return this.#experiments.isEnabledForTest(this.name) || this.#isEnabled;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
setEnabled(enabled: boolean): void {
|
|
406
|
+
this.#isEnabled = enabled;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
335
410
|
/** This must be constructed after the query parameters have been parsed. **/
|
|
336
411
|
export const experiments = new ExperimentsSupport();
|
|
337
412
|
|
|
@@ -524,6 +599,10 @@ interface ConsoleInsightsTeasers {
|
|
|
524
599
|
allowWithoutGpu: boolean;
|
|
525
600
|
}
|
|
526
601
|
|
|
602
|
+
interface DevToolsProtocolMonitor {
|
|
603
|
+
enabled: boolean;
|
|
604
|
+
}
|
|
605
|
+
|
|
527
606
|
/**
|
|
528
607
|
* The host configuration that we expect from the DevTools back-end.
|
|
529
608
|
*
|
|
@@ -575,6 +654,7 @@ export type HostConfig = Platform.TypeScriptUtilities.RecursivePartial<{
|
|
|
575
654
|
devToolsAiAssistanceContextSelectionAgent: HostConfigAiAssistanceContextSelectionAgent,
|
|
576
655
|
devToolsConsoleInsightsTeasers: ConsoleInsightsTeasers,
|
|
577
656
|
devToolsGeminiRebranding: HostConfigGeminiRebranding,
|
|
657
|
+
devToolsProtocolMonitor: DevToolsProtocolMonitor,
|
|
578
658
|
}>;
|
|
579
659
|
|
|
580
660
|
/**
|
|
@@ -328,6 +328,19 @@ export class MainImpl {
|
|
|
328
328
|
return {syncedStorage, globalStorage, localStorage};
|
|
329
329
|
}
|
|
330
330
|
|
|
331
|
+
// eslint-disable-next-line no-unused-private-class-members
|
|
332
|
+
#migrateValueFromLegacyToHostExperiment(
|
|
333
|
+
legacyExperimentName: Root.ExperimentNames.ExperimentName, hostExperiment: Root.Runtime.HostExperiment): void {
|
|
334
|
+
const value = Root.Runtime.experiments.getValueFromStorage(legacyExperimentName);
|
|
335
|
+
if (value !== undefined && hostExperiment.aboutFlag) {
|
|
336
|
+
// Set the host experiment to the same value as the legacy experiment.
|
|
337
|
+
hostExperiment.setEnabled(value);
|
|
338
|
+
// Set the chrome flag to the same value as the legacy experiment.
|
|
339
|
+
Host.InspectorFrontendHost.InspectorFrontendHostInstance.setChromeFlag(hostExperiment.aboutFlag, value);
|
|
340
|
+
// The legacy experiment will be cleaned up by `cleanUpStaleExperiments`.
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
331
344
|
#initializeExperiments(): void {
|
|
332
345
|
Root.Runtime.experiments.register(
|
|
333
346
|
Root.ExperimentNames.ExperimentName.CAPTURE_NODE_CREATION_STACKS, 'Capture node creation stacks');
|
|
@@ -413,7 +426,6 @@ export class MainImpl {
|
|
|
413
426
|
if (enabledExperiments) {
|
|
414
427
|
Root.Runtime.experiments.setServerEnabledExperiments(enabledExperiments.split(';'));
|
|
415
428
|
}
|
|
416
|
-
Root.Runtime.experiments.enableExperimentsTransiently([]);
|
|
417
429
|
|
|
418
430
|
if (Host.InspectorFrontendHost.isUnderTest()) {
|
|
419
431
|
const testParam = Root.Runtime.Runtime.queryParam('test');
|
|
@@ -57,7 +57,7 @@ Content:
|
|
|
57
57
|
"function_declarations": [
|
|
58
58
|
{
|
|
59
59
|
"name": "getStyles",
|
|
60
|
-
"description": "Get computed and source styles for one or multiple elements on the inspected page for multiple elements at once by uid.\n\n**CRITICAL** Use selectors to refer to elements in the text output. Do not use uids.\n**CRITICAL** Always provide the explanation argument to explain what and why you query.",
|
|
60
|
+
"description": "Get computed and source styles for one or multiple elements on the inspected page for multiple elements at once by uid.\n\n**CRITICAL** An element uid is a number, not a selector.\n**CRITICAL** Use selectors to refer to elements in the text output. Do not use uids.\n**CRITICAL** Always provide the explanation argument to explain what and why you query.",
|
|
61
61
|
"parameters": {
|
|
62
62
|
"type": 6,
|
|
63
63
|
"description": "",
|
|
@@ -70,9 +70,9 @@ Content:
|
|
|
70
70
|
},
|
|
71
71
|
"elements": {
|
|
72
72
|
"type": 5,
|
|
73
|
-
"description": "A list of element uids to get data for",
|
|
73
|
+
"description": "A list of element uids to get data for. These are numbers, not selectors.",
|
|
74
74
|
"items": {
|
|
75
|
-
"type":
|
|
75
|
+
"type": 3,
|
|
76
76
|
"description": "An element uid."
|
|
77
77
|
},
|
|
78
78
|
"nullable": false
|
|
@@ -279,13 +279,14 @@ export class StylingAgent extends AiAgent<SDK.DOMModel.DOMNode> {
|
|
|
279
279
|
});
|
|
280
280
|
|
|
281
281
|
this.declareFunction<{
|
|
282
|
-
elements:
|
|
282
|
+
elements: number[],
|
|
283
283
|
styleProperties: string[],
|
|
284
284
|
explanation: string,
|
|
285
285
|
}>('getStyles', {
|
|
286
286
|
description:
|
|
287
287
|
`Get computed and source styles for one or multiple elements on the inspected page for multiple elements at once by uid.
|
|
288
288
|
|
|
289
|
+
**CRITICAL** An element uid is a number, not a selector.
|
|
289
290
|
**CRITICAL** Use selectors to refer to elements in the text output. Do not use uids.
|
|
290
291
|
**CRITICAL** Always provide the explanation argument to explain what and why you query.`,
|
|
291
292
|
parameters: {
|
|
@@ -300,8 +301,8 @@ export class StylingAgent extends AiAgent<SDK.DOMModel.DOMNode> {
|
|
|
300
301
|
},
|
|
301
302
|
elements: {
|
|
302
303
|
type: Host.AidaClient.ParametersTypes.ARRAY,
|
|
303
|
-
description: 'A list of element uids to get data for',
|
|
304
|
-
items: {type: Host.AidaClient.ParametersTypes.
|
|
304
|
+
description: 'A list of element uids to get data for. These are numbers, not selectors.',
|
|
305
|
+
items: {type: Host.AidaClient.ParametersTypes.INTEGER, description: `An element uid.`},
|
|
305
306
|
nullable: false,
|
|
306
307
|
},
|
|
307
308
|
styleProperties: {
|
|
@@ -605,7 +606,7 @@ const data = {
|
|
|
605
606
|
return this.context?.getItem() ?? null;
|
|
606
607
|
}
|
|
607
608
|
|
|
608
|
-
async getStyles(elements:
|
|
609
|
+
async getStyles(elements: number[], properties: string[]): Promise<FunctionCallHandlerResult<unknown>> {
|
|
609
610
|
const result:
|
|
610
611
|
Record<string, {computed: Record<string, string|undefined>, authored: Record<string, string|undefined>}> = {};
|
|
611
612
|
for (const uid of elements) {
|
|
@@ -94,9 +94,6 @@ function getIssueCode(details: Protocol.Audits.CorsIssueDetails): IssueCode {
|
|
|
94
94
|
case Protocol.Network.CorsError.LocalNetworkAccessPermissionDenied:
|
|
95
95
|
return IssueCode.LOCAL_NETWORK_ACCESS_PERMISSION_DENIED;
|
|
96
96
|
}
|
|
97
|
-
// TODO(b/394636065): Remove this once browser protocol has rolled, as we
|
|
98
|
-
// will never hit this case.
|
|
99
|
-
return null as unknown as IssueCode;
|
|
100
97
|
}
|
|
101
98
|
|
|
102
99
|
export class CorsIssue extends Issue<Protocol.Audits.CorsIssueDetails, IssueCode> {
|
|
@@ -62,3 +62,43 @@ export const enum Events {
|
|
|
62
62
|
export interface EventTypes {
|
|
63
63
|
[Events.UPDATED]: void;
|
|
64
64
|
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* A small wrapper around a DebuggableFrame usable as a UI.Context flavor.
|
|
68
|
+
* This is necessary as Frame and DebuggableFrame are updated in place, but
|
|
69
|
+
* for UI.Context we need a new instance.
|
|
70
|
+
*/
|
|
71
|
+
export class DebuggableFrameFlavor implements DebuggableFrame {
|
|
72
|
+
static #last?: DebuggableFrameFlavor;
|
|
73
|
+
|
|
74
|
+
readonly url?: string;
|
|
75
|
+
readonly uiSourceCode?: Workspace.UISourceCode.UISourceCode;
|
|
76
|
+
readonly name?: string;
|
|
77
|
+
readonly line: number;
|
|
78
|
+
readonly column: number;
|
|
79
|
+
readonly missingDebugInfo?: MissingDebugInfo;
|
|
80
|
+
readonly sdkFrame: SDK.DebuggerModel.CallFrame;
|
|
81
|
+
|
|
82
|
+
private constructor(frame: DebuggableFrame) {
|
|
83
|
+
this.url = frame.url;
|
|
84
|
+
this.uiSourceCode = frame.uiSourceCode;
|
|
85
|
+
this.name = frame.name;
|
|
86
|
+
this.line = frame.line;
|
|
87
|
+
this.column = frame.column;
|
|
88
|
+
this.missingDebugInfo = frame.missingDebugInfo;
|
|
89
|
+
this.sdkFrame = frame.sdkFrame;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** @returns the same instance of DebuggableFrameFlavor for repeated calls with the same (i.e. deep equal) DebuggableFrame */
|
|
93
|
+
static for(frame: DebuggableFrame): DebuggableFrameFlavor {
|
|
94
|
+
function equals(a: DebuggableFrame, b: DebuggableFrame): boolean {
|
|
95
|
+
return a.url === b.url && a.uiSourceCode === b.uiSourceCode && a.name === b.name && a.line === b.line &&
|
|
96
|
+
a.column === b.column && a.sdkFrame === b.sdkFrame;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!DebuggableFrameFlavor.#last || !equals(DebuggableFrameFlavor.#last, frame)) {
|
|
100
|
+
DebuggableFrameFlavor.#last = new DebuggableFrameFlavor(frame);
|
|
101
|
+
}
|
|
102
|
+
return DebuggableFrameFlavor.#last;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -135,6 +135,8 @@ export interface ViewInput {
|
|
|
135
135
|
onRemoveImageInput: () => void;
|
|
136
136
|
onImageUpload: (ev: Event) => void;
|
|
137
137
|
onImagePaste: (event: ClipboardEvent) => void;
|
|
138
|
+
onImageDragOver: (event: DragEvent) => void;
|
|
139
|
+
onImageDrop: (event: DragEvent) => void;
|
|
138
140
|
}
|
|
139
141
|
|
|
140
142
|
export type ViewOutput = undefined;
|
|
@@ -289,6 +291,8 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
|
|
|
289
291
|
maxlength="10000"
|
|
290
292
|
@keydown=${input.onTextAreaKeyDown}
|
|
291
293
|
@paste=${input.onImagePaste}
|
|
294
|
+
@dragover=${input.onImageDragOver}
|
|
295
|
+
@drop=${input.onImageDrop}
|
|
292
296
|
@input=${(event: KeyboardEvent) => {
|
|
293
297
|
input.onTextInputChange((event.target as HTMLInputElement).value);
|
|
294
298
|
}}
|
|
@@ -550,12 +554,12 @@ export class ChatInput extends UI.Widget.Widget implements SDK.TargetManager.Obs
|
|
|
550
554
|
});
|
|
551
555
|
}
|
|
552
556
|
|
|
553
|
-
#
|
|
557
|
+
#handleImageDataTransferEvent(dataTransfer: DataTransfer|null, event: Event): void {
|
|
554
558
|
if (this.conversationType !== AiAssistanceModel.AiHistoryStorage.ConversationType.STYLING) {
|
|
555
559
|
return;
|
|
556
560
|
}
|
|
557
561
|
|
|
558
|
-
const files =
|
|
562
|
+
const files = dataTransfer?.files;
|
|
559
563
|
if (!files || files.length === 0) {
|
|
560
564
|
return;
|
|
561
565
|
}
|
|
@@ -567,6 +571,22 @@ export class ChatInput extends UI.Widget.Widget implements SDK.TargetManager.Obs
|
|
|
567
571
|
|
|
568
572
|
event.preventDefault();
|
|
569
573
|
void this.#handleLoadImage(imageFile);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
#handleImagePaste = (event: ClipboardEvent): void => {
|
|
577
|
+
this.#handleImageDataTransferEvent(event.clipboardData, event);
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
#handleImageDragOver = (event: DragEvent): void => {
|
|
581
|
+
if (this.conversationType !== AiAssistanceModel.AiHistoryStorage.ConversationType.STYLING) {
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
event.preventDefault();
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
#handleImageDrop = (event: DragEvent): void => {
|
|
589
|
+
this.#handleImageDataTransferEvent(event.dataTransfer, event);
|
|
570
590
|
};
|
|
571
591
|
|
|
572
592
|
async #handleLoadImage(file: File): Promise<void> {
|
|
@@ -663,6 +683,8 @@ export class ChatInput extends UI.Widget.Widget implements SDK.TargetManager.Obs
|
|
|
663
683
|
onTextAreaKeyDown: this.onTextAreaKeyDown,
|
|
664
684
|
onCancel: this.onCancel,
|
|
665
685
|
onImageUpload: this.onImageUpload,
|
|
686
|
+
onImageDragOver: this.#handleImageDragOver,
|
|
687
|
+
onImageDrop: this.#handleImageDrop,
|
|
666
688
|
},
|
|
667
689
|
undefined, this.contentElement);
|
|
668
690
|
}
|
|
@@ -19,33 +19,43 @@ const UIStringsNotTranslate = {
|
|
|
19
19
|
*/
|
|
20
20
|
codeCompletionJustGotBetter: 'Code completion just got better',
|
|
21
21
|
/**
|
|
22
|
-
* @description First item in the description
|
|
23
|
-
*/
|
|
24
|
-
asYouType: 'As you type, DevTools generates code suggestions to help you code faster.',
|
|
25
|
-
/**
|
|
26
|
-
* @description Second item in the description
|
|
22
|
+
* @description First item in the description.
|
|
27
23
|
*/
|
|
28
24
|
describeCodeInComment:
|
|
29
|
-
'
|
|
25
|
+
'Pressing Ctrl+I on a comment in the Console and Sources panels now generates entire code blocks based on the instructions in the comment.',
|
|
30
26
|
/**
|
|
31
|
-
* @description
|
|
27
|
+
* @description First item in the description.
|
|
32
28
|
*/
|
|
33
29
|
describeCodeInCommentForMacOs:
|
|
34
|
-
'
|
|
30
|
+
'Pressing Cmd+I on a comment in the Console and Sources panels now generates entire code blocks based on the instructions in the comment.',
|
|
31
|
+
/**
|
|
32
|
+
* @description Second item in the description.
|
|
33
|
+
*/
|
|
34
|
+
asYouType: 'You will still receive the real-time, as-you-type suggestions to help you code faster.',
|
|
35
|
+
/**
|
|
36
|
+
* @description Third item in the description.
|
|
37
|
+
*/
|
|
38
|
+
disclaimerTextPrivacy:
|
|
39
|
+
'To generate code suggestions, your console input, the history of your current console session, the currently inspected CSS, and the contents of the currently open file are shared with Google. This data may be seen by human reviewers to improve this feature.',
|
|
40
|
+
/**
|
|
41
|
+
* @description Third item in the description.
|
|
42
|
+
*/
|
|
43
|
+
disclaimerTextPrivacyNoLogging:
|
|
44
|
+
'To generate code suggestions, your console input, the history of your current console session, the currently inspected CSS, and the contents of the currently open file are shared with Google. This data will not be used to improve Google’s AI models. Your organization may change these settings at any time.',
|
|
35
45
|
/**
|
|
36
46
|
* @description Text for the manage in settings button in the upgrade notice dialog.
|
|
37
47
|
*/
|
|
38
48
|
manageInSettings: 'Manage in settings',
|
|
39
49
|
/**
|
|
40
|
-
* @description Text for the
|
|
50
|
+
* @description Text for the generate code button in the upgrade notice dialog.
|
|
41
51
|
*/
|
|
42
|
-
|
|
52
|
+
generateCode: 'Generate code',
|
|
43
53
|
} as const;
|
|
44
54
|
|
|
45
55
|
const lockedString = i18n.i18n.lockedString;
|
|
46
56
|
|
|
47
57
|
export class AiCodeGenerationUpgradeDialog {
|
|
48
|
-
static show(): void {
|
|
58
|
+
static show({noLogging}: {noLogging: boolean}): void {
|
|
49
59
|
const dialog = new UI.Dialog.Dialog();
|
|
50
60
|
dialog.setAriaLabel(lockedString(UIStringsNotTranslate.codeCompletionJustGotBetter));
|
|
51
61
|
// clang-format off
|
|
@@ -63,10 +73,6 @@ export class AiCodeGenerationUpgradeDialog {
|
|
|
63
73
|
</h2>
|
|
64
74
|
</header>
|
|
65
75
|
<main class="reminder-container">
|
|
66
|
-
<div class="reminder-item">
|
|
67
|
-
<devtools-icon class="reminder-icon" name="code"></devtools-icon>
|
|
68
|
-
<span>${lockedString(UIStringsNotTranslate.asYouType)}</span>
|
|
69
|
-
</div>
|
|
70
76
|
<div class="reminder-item">
|
|
71
77
|
<devtools-icon class="reminder-icon" name="text-analysis"></devtools-icon>
|
|
72
78
|
<span>
|
|
@@ -75,6 +81,16 @@ export class AiCodeGenerationUpgradeDialog {
|
|
|
75
81
|
lockedString(UIStringsNotTranslate.describeCodeInComment)}
|
|
76
82
|
</span>
|
|
77
83
|
</div>
|
|
84
|
+
<div class="reminder-item">
|
|
85
|
+
<devtools-icon class="reminder-icon" name="code"></devtools-icon>
|
|
86
|
+
<span>${lockedString(UIStringsNotTranslate.asYouType)}</span>
|
|
87
|
+
</div>
|
|
88
|
+
<div class="reminder-item">
|
|
89
|
+
<devtools-icon class="reminder-icon" name="google"></devtools-icon>
|
|
90
|
+
<span>${noLogging ? lockedString(UIStringsNotTranslate.disclaimerTextPrivacyNoLogging) :
|
|
91
|
+
lockedString(UIStringsNotTranslate.disclaimerTextPrivacy)}
|
|
92
|
+
</span>
|
|
93
|
+
</div>
|
|
78
94
|
</main>
|
|
79
95
|
<footer>
|
|
80
96
|
<div class="right-buttons">
|
|
@@ -93,7 +109,7 @@ export class AiCodeGenerationUpgradeDialog {
|
|
|
93
109
|
}}
|
|
94
110
|
jslogcontext="ai-code-generation-upgrade-dialog.continue"
|
|
95
111
|
.variant=${Buttons.Button.Variant.PRIMARY}>
|
|
96
|
-
${lockedString(UIStringsNotTranslate.
|
|
112
|
+
${lockedString(UIStringsNotTranslate.generateCode)}
|
|
97
113
|
</devtools-button>
|
|
98
114
|
</div>
|
|
99
115
|
</footer>
|
|
@@ -199,11 +199,34 @@ export const format = (fmt: string, args: SDK.RemoteObject.RemoteObject[]): {
|
|
|
199
199
|
return {tokens, args: args.slice(argIndex)};
|
|
200
200
|
};
|
|
201
201
|
|
|
202
|
+
/**
|
|
203
|
+
* This function converts a string into a partial regex string that
|
|
204
|
+
* case-insensitively matches it in CSS, even if CSS escapes are used.
|
|
205
|
+
*
|
|
206
|
+
* @param cssString the target string.
|
|
207
|
+
* @returns a partial regex matching the string in CSS.
|
|
208
|
+
*/
|
|
209
|
+
const cssEscapeRegex = (cssString: string): string => {
|
|
210
|
+
return [...cssString]
|
|
211
|
+
.map(char => {
|
|
212
|
+
const charCodes = new Set([char.toLowerCase(), char.toUpperCase()].map(c => c.charCodeAt(0).toString(16)));
|
|
213
|
+
const charCodeRegex =
|
|
214
|
+
[...charCodes].map(charCode => `\\\\0{0,${6 - charCode.length}}${charCode}[ \\n\\t]?`).join('|');
|
|
215
|
+
return `\\\\?(?:${charCodeRegex}|${char})`;
|
|
216
|
+
})
|
|
217
|
+
.join('');
|
|
218
|
+
};
|
|
219
|
+
|
|
202
220
|
export const updateStyle = (currentStyle: Map<string, {value: string, priority: string}>, styleToAdd: string): void => {
|
|
203
221
|
const ALLOWED_PROPERTY_PREFIXES = ['background', 'border', 'color', 'font', 'line', 'margin', 'padding', 'text'];
|
|
204
222
|
// We only allow data URLs with the `url()` CSS function.
|
|
205
223
|
// The capture group is not intended to grab the whole URL exactly, just enough so we can check the scheme.
|
|
206
|
-
|
|
224
|
+
// The regex also covers CSS hex-escaped variations of `url()`.
|
|
225
|
+
const URL_REGEX = new RegExp(`(?=${cssEscapeRegex('url')}\\(['"]?([^\\)]*))`, 'gi');
|
|
226
|
+
// We greedily capture all `image-set()`s to make sure that all of
|
|
227
|
+
// them properly use `url()`s to enforce the data URL check later.
|
|
228
|
+
const IMAGESET_REGEX = new RegExp(`(?=(${cssEscapeRegex('image-set')}\\(.*))`, 'gi');
|
|
229
|
+
const GOOD_IMAGESET_REGEX = /^image-set\((?:(?:(?:url|type)\("[^\\"]*"\)|[\d.]+(?:x|dpi|dpcm|dppx)),?\s*)+\)/i;
|
|
207
230
|
|
|
208
231
|
currentStyle.clear();
|
|
209
232
|
/* eslint-disable-next-line @devtools/no-imperative-dom-api --
|
|
@@ -218,9 +241,15 @@ export const updateStyle = (currentStyle: Map<string, {value: string, priority:
|
|
|
218
241
|
continue;
|
|
219
242
|
}
|
|
220
243
|
|
|
244
|
+
const value = buffer.style.getPropertyValue(property);
|
|
245
|
+
// We make sure every `image-set()` only uses `url()`s for its images.
|
|
246
|
+
// If any of them seem malformed, we skip the whole property.
|
|
247
|
+
const imageSets = [...value.matchAll(IMAGESET_REGEX)];
|
|
248
|
+
if (imageSets.some(match => !GOOD_IMAGESET_REGEX.test(match[1]))) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
221
251
|
// There could be multiple `url()` functions, so we check them all.
|
|
222
252
|
// If any of them is not a `data` URL, we skip the whole property.
|
|
223
|
-
const value = buffer.style.getPropertyValue(property);
|
|
224
253
|
const potentialUrls = [...value.matchAll(URL_REGEX)].map(match => match[1]);
|
|
225
254
|
if (potentialUrls.some(
|
|
226
255
|
potentialUrl => !Common.ParsedURL.schemeIs(potentialUrl as Platform.DevToolsPath.UrlString, 'data:'))) {
|
|
@@ -626,6 +626,7 @@ export class ConsoleInsightTeaser extends UI.Widget.Widget {
|
|
|
626
626
|
this.#startTime = performance.now();
|
|
627
627
|
let teaserText = '';
|
|
628
628
|
let firstChunkReceived = false;
|
|
629
|
+
let firstChunkTime = 0;
|
|
629
630
|
try {
|
|
630
631
|
for await (const chunk of this.#getOnDeviceInsight()) {
|
|
631
632
|
teaserText += chunk;
|
|
@@ -634,7 +635,9 @@ export class ConsoleInsightTeaser extends UI.Widget.Widget {
|
|
|
634
635
|
this.requestUpdate();
|
|
635
636
|
if (!firstChunkReceived) {
|
|
636
637
|
firstChunkReceived = true;
|
|
637
|
-
|
|
638
|
+
firstChunkTime = performance.now();
|
|
639
|
+
Host.userMetrics.consoleInsightTeaserFirstChunkGenerated(firstChunkTime - this.#startTime);
|
|
640
|
+
Host.userMetrics.consoleInsightTeaserFirstChunkGeneratedMedium(firstChunkTime - this.#startTime);
|
|
638
641
|
}
|
|
639
642
|
}
|
|
640
643
|
} catch (err) {
|
|
@@ -654,6 +657,8 @@ export class ConsoleInsightTeaser extends UI.Widget.Widget {
|
|
|
654
657
|
clearTimeout(this.#timeoutId);
|
|
655
658
|
const duration = performance.now() - this.#startTime;
|
|
656
659
|
Host.userMetrics.consoleInsightTeaserGenerated(duration);
|
|
660
|
+
Host.userMetrics.consoleInsightTeaserGeneratedMedium(duration);
|
|
661
|
+
Host.userMetrics.consoleInsightTeaserChunkToEndMedium(performance.now() - firstChunkTime);
|
|
657
662
|
if (teaserText.length > 300) {
|
|
658
663
|
Host.userMetrics.consoleInsightLongTeaserGenerated(duration);
|
|
659
664
|
} else {
|
|
@@ -1092,6 +1092,7 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
|
|
|
1092
1092
|
}
|
|
1093
1093
|
|
|
1094
1094
|
override onbind(): void {
|
|
1095
|
+
this.performUpdate();
|
|
1095
1096
|
if (this.treeOutline && !this.isClosingTag()) {
|
|
1096
1097
|
this.treeOutline.treeElementByNode.set(this.nodeInternal, this);
|
|
1097
1098
|
this.nodeInternal.addEventListener(SDK.DOMModel.DOMNodeEvents.TOP_LAYER_INDEX_CHANGED, this.performUpdate, this);
|
|
@@ -1115,6 +1116,49 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
|
|
|
1115
1116
|
if (this.editing) {
|
|
1116
1117
|
this.editing.cancel();
|
|
1117
1118
|
}
|
|
1119
|
+
// Update the element to clean up adorner registrations with the
|
|
1120
|
+
// ElementsPanel.
|
|
1121
|
+
// We do not change the ElementsTreeElement state in case the
|
|
1122
|
+
// element is bound again.
|
|
1123
|
+
DEFAULT_VIEW(
|
|
1124
|
+
{
|
|
1125
|
+
containerAdornerActive: false,
|
|
1126
|
+
showAdAdorner: false,
|
|
1127
|
+
showContainerAdorner: false,
|
|
1128
|
+
containerType: this.#layout?.containerType,
|
|
1129
|
+
showFlexAdorner: false,
|
|
1130
|
+
flexAdornerActive: false,
|
|
1131
|
+
showGridAdorner: false,
|
|
1132
|
+
showGridLanesAdorner: false,
|
|
1133
|
+
showMediaAdorner: false,
|
|
1134
|
+
showPopoverAdorner: false,
|
|
1135
|
+
showTopLayerAdorner: false,
|
|
1136
|
+
gridAdornerActive: false,
|
|
1137
|
+
popoverAdornerActive: false,
|
|
1138
|
+
isSubgrid: false,
|
|
1139
|
+
showViewSourceAdorner: false,
|
|
1140
|
+
showScrollAdorner: false,
|
|
1141
|
+
showScrollSnapAdorner: false,
|
|
1142
|
+
scrollSnapAdornerActive: false,
|
|
1143
|
+
showSlotAdorner: false,
|
|
1144
|
+
showStartingStyleAdorner: false,
|
|
1145
|
+
startingStyleAdornerActive: false,
|
|
1146
|
+
nodeInfo: this.#nodeInfo,
|
|
1147
|
+
onStartingStyleAdornerClick: () => {},
|
|
1148
|
+
onSlotAdornerClick: () => {},
|
|
1149
|
+
topLayerIndex: -1,
|
|
1150
|
+
onViewSourceAdornerClick: () => {},
|
|
1151
|
+
onGutterClick: () => {},
|
|
1152
|
+
onContainerAdornerClick: () => {},
|
|
1153
|
+
onFlexAdornerClick: () => {},
|
|
1154
|
+
onGridAdornerClick: () => {},
|
|
1155
|
+
onMediaAdornerClick: () => {},
|
|
1156
|
+
onPopoverAdornerClick: () => {},
|
|
1157
|
+
onScrollSnapAdornerClick: () => {},
|
|
1158
|
+
onTopLayerAdornerClick: () => {},
|
|
1159
|
+
},
|
|
1160
|
+
this, this.listItemElement);
|
|
1161
|
+
|
|
1118
1162
|
if (this.treeOutline && this.treeOutline.treeElementByNode.get(this.nodeInternal) === this) {
|
|
1119
1163
|
this.treeOutline.treeElementByNode.delete(this.nodeInternal);
|
|
1120
1164
|
}
|
|
@@ -80,7 +80,7 @@ const str_ = i18n.i18n.registerUIStrings('panels/elements/ElementsTreeOutline.ts
|
|
|
80
80
|
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
|
81
81
|
const elementsTreeOutlineByDOMModel = new WeakMap<SDK.DOMModel.DOMModel, ElementsTreeOutline>();
|
|
82
82
|
|
|
83
|
-
const populatedTreeElements = new
|
|
83
|
+
const populatedTreeElements = new WeakSet<ElementsTreeElement>();
|
|
84
84
|
|
|
85
85
|
export type View = typeof DEFAULT_VIEW;
|
|
86
86
|
|
|
@@ -383,7 +383,7 @@ export class GenericSettingsTab extends UI.Widget.VBox implements SettingsTab {
|
|
|
383
383
|
|
|
384
384
|
export class ExperimentsSettingsTab extends UI.Widget.VBox implements SettingsTab {
|
|
385
385
|
#experimentsSection: Card|undefined;
|
|
386
|
-
private readonly experimentToControl = new Map<Root.Runtime.Experiment, HTMLElement>();
|
|
386
|
+
private readonly experimentToControl = new Map<Root.Runtime.Experiment|Root.Runtime.HostExperiment, HTMLElement>();
|
|
387
387
|
private readonly containerElement: HTMLElement;
|
|
388
388
|
|
|
389
389
|
constructor() {
|
|
@@ -452,12 +452,16 @@ export class ExperimentsSettingsTab extends UI.Widget.VBox implements SettingsTa
|
|
|
452
452
|
return subsection;
|
|
453
453
|
}
|
|
454
454
|
|
|
455
|
-
private createExperimentCheckbox(experiment: Root.Runtime.Experiment):
|
|
455
|
+
private createExperimentCheckbox(experiment: Root.Runtime.Experiment|Root.Runtime.HostExperiment):
|
|
456
|
+
HTMLParagraphElement {
|
|
456
457
|
const checkbox =
|
|
457
458
|
UI.UIUtils.CheckboxLabel.createWithStringLiteral(experiment.title, experiment.isEnabled(), experiment.name);
|
|
458
459
|
checkbox.classList.add('experiment-label');
|
|
459
460
|
checkbox.name = experiment.name;
|
|
460
461
|
function listener(): void {
|
|
462
|
+
if (experiment instanceof Root.Runtime.HostExperiment) {
|
|
463
|
+
Host.InspectorFrontendHost.InspectorFrontendHostInstance.setChromeFlag(experiment.aboutFlag, checkbox.checked);
|
|
464
|
+
}
|
|
461
465
|
experiment.setEnabled(checkbox.checked);
|
|
462
466
|
Host.userMetrics.experimentChanged(experiment.name, experiment.isEnabled());
|
|
463
467
|
UI.InspectorView.InspectorView.instance().displayReloadRequiredWarning(
|
|
@@ -499,7 +503,7 @@ export class ExperimentsSettingsTab extends UI.Widget.VBox implements SettingsTa
|
|
|
499
503
|
}
|
|
500
504
|
|
|
501
505
|
highlightObject(experiment: Object): void {
|
|
502
|
-
if (experiment instanceof Root.Runtime.Experiment) {
|
|
506
|
+
if (experiment instanceof Root.Runtime.Experiment || experiment instanceof Root.Runtime.HostExperiment) {
|
|
503
507
|
const element = this.experimentToControl.get(experiment);
|
|
504
508
|
if (element) {
|
|
505
509
|
PanelUtils.highlightElement(element);
|
|
@@ -534,10 +538,12 @@ export class ActionDelegate implements UI.ActionRegistration.ActionDelegate {
|
|
|
534
538
|
return false;
|
|
535
539
|
}
|
|
536
540
|
}
|
|
537
|
-
export class Revealer implements
|
|
538
|
-
|
|
541
|
+
export class Revealer implements
|
|
542
|
+
Common.Revealer.Revealer<Root.Runtime.Experiment|Root.Runtime.HostExperiment|Common.Settings.Setting<unknown>> {
|
|
543
|
+
async reveal(object: Root.Runtime.Experiment|Root.Runtime.HostExperiment|Common.Settings.Setting<unknown>):
|
|
544
|
+
Promise<void> {
|
|
539
545
|
const context = UI.Context.Context.instance();
|
|
540
|
-
if (object instanceof Root.Runtime.Experiment) {
|
|
546
|
+
if (object instanceof Root.Runtime.Experiment || object instanceof Root.Runtime.HostExperiment) {
|
|
541
547
|
Host.InspectorFrontendHost.InspectorFrontendHostInstance.bringToFront();
|
|
542
548
|
await SettingsScreen.showSettingsScreen({name: 'experiments'});
|
|
543
549
|
const experimentsSettingsTab = context.flavor(ExperimentsSettingsTab);
|
|
@@ -40,28 +40,28 @@ export function getReleaseNote(): ReleaseNote {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
let releaseNote: ReleaseNote = {
|
|
43
|
-
version:
|
|
44
|
-
header: 'What\'s new in DevTools
|
|
43
|
+
version: 145,
|
|
44
|
+
header: 'What\'s new in DevTools 145',
|
|
45
45
|
markdownLinks: [
|
|
46
46
|
{
|
|
47
|
-
key: '
|
|
48
|
-
link: 'https://developer.chrome.com/blog/new-in-devtools-
|
|
47
|
+
key: 'soft-navigations',
|
|
48
|
+
link: 'https://developer.chrome.com/blog/new-in-devtools-145/#soft-navigations',
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
|
-
key: '
|
|
52
|
-
link: 'https://developer.chrome.com/blog/new-in-devtools-
|
|
51
|
+
key: 'render-blocking',
|
|
52
|
+
link: 'https://developer.chrome.com/blog/new-in-devtools-145/#render-blocking',
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
|
-
key: '
|
|
56
|
-
link: 'https://developer.chrome.com/blog/new-in-devtools-
|
|
55
|
+
key: 'mcp-server',
|
|
56
|
+
link: 'https://developer.chrome.com/blog/new-in-devtools-145/#mcp-server',
|
|
57
57
|
},
|
|
58
58
|
],
|
|
59
59
|
videoLinks: [
|
|
60
60
|
{
|
|
61
|
-
description: 'See past highlights from Chrome 144',
|
|
62
|
-
link: 'https://
|
|
61
|
+
description: 'See past highlights from Chrome 142-144',
|
|
62
|
+
link: 'https://www.youtube.com/watch?v=2rOeZ98AOb8' as Platform.DevToolsPath.UrlString,
|
|
63
63
|
type: VideoType.WHATS_NEW,
|
|
64
64
|
},
|
|
65
65
|
],
|
|
66
|
-
link: 'https://developer.chrome.com/blog/new-in-devtools-
|
|
66
|
+
link: 'https://developer.chrome.com/blog/new-in-devtools-145/',
|
|
67
67
|
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
### [
|
|
1
|
+
### [Soft navigations in performance traces](soft-navigations)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Soft navigation and soft LCP markers are now visible in performance traces for single-page applications.
|
|
4
4
|
|
|
5
|
-
### [
|
|
5
|
+
### [Identify render blocking requests](render-blocking)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Enable the Render blocking column in the Network panel to spot scripts or stylesheets that are blocking the rendering process.
|
|
8
8
|
|
|
9
|
-
### [
|
|
9
|
+
### [MCP server](mcp-server)
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Simulate device viewports and user agents, work with extensions, and open pages in the background.
|
package/mcp/HostBindings.ts
CHANGED
|
@@ -180,6 +180,9 @@ export class McpHostBindings implements Host.InspectorFrontendHostAPI.InspectorF
|
|
|
180
180
|
recordPerformanceHistogram(): void {
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
+
recordPerformanceHistogramMedium(): void {
|
|
184
|
+
}
|
|
185
|
+
|
|
183
186
|
recordUserMetricsAction(): void {
|
|
184
187
|
}
|
|
185
188
|
|
|
@@ -307,4 +310,7 @@ export class McpHostBindings implements Host.InspectorFrontendHostAPI.InspectorF
|
|
|
307
310
|
|
|
308
311
|
recordFunctionCall(): void {
|
|
309
312
|
}
|
|
313
|
+
|
|
314
|
+
setChromeFlag(): void {
|
|
315
|
+
}
|
|
310
316
|
}
|
package/package.json
CHANGED