chrome-devtools-frontend 1.0.1602348 → 1.0.1603822

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 (105) hide show
  1. package/.agents/skills/version-control/SKILL.md +17 -2
  2. package/AUTHORS +1 -0
  3. package/front_end/core/common/Settings.ts +1 -871
  4. package/front_end/core/common/VersionController.ts +880 -0
  5. package/front_end/core/common/common.ts +2 -0
  6. package/front_end/core/platform/HostRuntime.ts +9 -3
  7. package/front_end/core/protocol_client/InspectorBackend.ts +8 -0
  8. package/front_end/core/root/Runtime.ts +5 -0
  9. package/front_end/core/sdk/OverlayModel.ts +13 -13
  10. package/front_end/core/sdk/OverlayPersistentHighlighter.ts +13 -10
  11. package/front_end/core/sdk/RuntimeModel.ts +5 -5
  12. package/front_end/core/sdk/ServiceWorkerManager.ts +4 -2
  13. package/front_end/core/sdk/Target.ts +5 -0
  14. package/front_end/core/sdk/TargetManager.ts +5 -0
  15. package/front_end/core/sdk/WebMCPModel.ts +104 -0
  16. package/front_end/core/sdk/sdk.ts +2 -0
  17. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +13 -19
  18. package/front_end/generated/InspectorBackendCommands.ts +3 -0
  19. package/front_end/generated/protocol-mapping.d.ts +8 -0
  20. package/front_end/generated/protocol-proxy-api.d.ts +10 -0
  21. package/front_end/generated/protocol.ts +57 -0
  22. package/front_end/models/ai_assistance/AiConversation.ts +3 -3
  23. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +50 -0
  24. package/front_end/models/ai_assistance/agents/AiAgent.ts +14 -1
  25. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +19 -4
  26. package/front_end/models/ai_assistance/agents/NetworkAgent.ts +7 -1
  27. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +30 -7
  28. package/front_end/models/ai_assistance/agents/StylingAgent.ts +0 -23
  29. package/front_end/models/ai_assistance/data_formatters/LighthouseFormatter.snapshot.txt +60 -9
  30. package/front_end/models/ai_assistance/data_formatters/LighthouseFormatter.ts +71 -15
  31. package/front_end/models/heap_snapshot_model/HeapSnapshotModel.ts +7 -7
  32. package/front_end/models/javascript_metadata/NativeFunctions.js +6 -2
  33. package/front_end/models/lighthouse/LighthouseReporterTypes.ts +10 -8
  34. package/front_end/models/lighthouse/RunTypes.ts +3 -1
  35. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +13 -5
  36. package/front_end/panels/ai_assistance/components/ChatMessage.ts +143 -18
  37. package/front_end/panels/ai_assistance/components/WalkthroughView.ts +30 -10
  38. package/front_end/panels/ai_assistance/components/chatMessage.css +58 -3
  39. package/front_end/panels/ai_assistance/components/walkthroughView.css +35 -5
  40. package/front_end/panels/application/AppManifestView.ts +2 -2
  41. package/front_end/panels/application/BackgroundServiceView.ts +9 -9
  42. package/front_end/panels/application/FrameDetailsView.ts +4 -13
  43. package/front_end/panels/application/IndexedDBViews.ts +1 -1
  44. package/front_end/panels/application/ServiceWorkerCacheViews.ts +3 -3
  45. package/front_end/panels/application/components/PermissionsPolicySection.ts +1 -1
  46. package/front_end/panels/application/components/permissionsPolicySection.css +54 -52
  47. package/front_end/panels/application/frameDetailsReportView.css +13 -1
  48. package/front_end/panels/common/ThrottlingUtils.ts +46 -0
  49. package/front_end/panels/common/common.ts +1 -0
  50. package/front_end/panels/elements/CSSValueTraceView.ts +9 -1
  51. package/front_end/panels/elements/ComputedStyleWidget.ts +11 -1
  52. package/front_end/panels/elements/PropertiesWidget.ts +14 -13
  53. package/front_end/panels/elements/PropertyRenderer.ts +10 -9
  54. package/front_end/panels/elements/StandaloneStylesContainer.ts +15 -5
  55. package/front_end/panels/elements/StylePropertiesSection.ts +0 -49
  56. package/front_end/panels/elements/StylePropertyTreeElement.ts +20 -0
  57. package/front_end/panels/elements/StylesContainer.ts +0 -1
  58. package/front_end/panels/elements/StylesSidebarPane.ts +65 -31
  59. package/front_end/panels/elements/stylePropertiesTreeOutline.css +5 -0
  60. package/front_end/panels/issues/AffectedSelectivePermissionsInterventionView.ts +2 -2
  61. package/front_end/panels/lighthouse/LighthouseController.ts +4 -1
  62. package/front_end/panels/media/EventDisplayTable.ts +3 -2
  63. package/front_end/panels/mobile_throttling/NetworkThrottlingSelector.ts +14 -0
  64. package/front_end/panels/network/EventSourceMessagesView.ts +2 -2
  65. package/front_end/panels/network/ResourceChunkView.ts +1 -1
  66. package/front_end/panels/profiler/HeapDetachedElementsView.ts +9 -4
  67. package/front_end/panels/profiler/HeapProfileView.ts +8 -4
  68. package/front_end/panels/profiler/HeapSnapshotDataGrids.ts +40 -33
  69. package/front_end/panels/profiler/HeapSnapshotGridNodes.ts +1 -1
  70. package/front_end/panels/profiler/HeapSnapshotProxy.ts +1 -1
  71. package/front_end/panels/profiler/HeapSnapshotView.ts +12 -18
  72. package/front_end/panels/profiler/LiveHeapProfileView.ts +3 -4
  73. package/front_end/panels/profiler/ProfileHeader.ts +5 -11
  74. package/front_end/panels/profiler/ProfileView.ts +1 -10
  75. package/front_end/panels/sensors/SensorsView.ts +24 -43
  76. package/front_end/panels/settings/keybindsSettingsTab.css +25 -31
  77. package/front_end/panels/timeline/EventsTimelineTreeView.ts +2 -2
  78. package/front_end/panels/timeline/ThirdPartyTreeView.ts +8 -0
  79. package/front_end/panels/timeline/TimelinePanel.ts +14 -2
  80. package/front_end/panels/timeline/TimelineTreeView.ts +19 -17
  81. package/front_end/panels/timeline/components/FieldSettingsDialog.ts +5 -9
  82. package/front_end/panels/timeline/components/LiveMetricsView.ts +1 -2
  83. package/front_end/panels/timeline/components/OriginMap.ts +176 -159
  84. package/front_end/panels/timeline/components/originMap.css +4 -51
  85. package/front_end/panels/timeline/components/timelineRangeSummaryView.css +3 -1
  86. package/front_end/panels/timeline/thirdPartyTreeView.css +6 -0
  87. package/front_end/panels/timeline/timeline-meta.ts +11 -0
  88. package/front_end/panels/timeline/utils/Helpers.ts +5 -25
  89. package/front_end/panels/webauthn/WebauthnPane.ts +52 -0
  90. package/front_end/third_party/acorn/README.chromium +1 -0
  91. package/front_end/third_party/chromium/README.chromium +1 -1
  92. package/front_end/ui/components/{list → lists}/List.ts +1 -1
  93. package/front_end/ui/components/{list → lists}/list.css +0 -1
  94. package/front_end/ui/legacy/Treeoutline.ts +7 -3
  95. package/front_end/ui/legacy/UIUtils.ts +13 -11
  96. package/front_end/ui/legacy/Widget.ts +5 -0
  97. package/front_end/ui/legacy/components/data_grid/DataGrid.ts +1 -1
  98. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +3 -3
  99. package/front_end/ui/legacy/components/data_grid/SortableDataGrid.ts +2 -2
  100. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +39 -23
  101. package/front_end/ui/legacy/components/perf_ui/GCActionDelegate.ts +20 -4
  102. package/front_end/ui/legacy/treeoutline.css +4 -0
  103. package/front_end/ui/visual_logging/KnownContextValues.ts +2 -0
  104. package/package.json +1 -1
  105. /package/front_end/ui/components/{list → lists}/lists.ts +0 -0
@@ -0,0 +1,880 @@
1
+ // Copyright 2026 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import * as Platform from '../platform/platform.js';
6
+
7
+ /* eslint @devtools/enforce-version-controller-methods: "error" */
8
+
9
+ import {type Setting, Settings, type SettingsStorage, SettingStorageType} from './Settings.js';
10
+
11
+ // The VersionController does a lot of mapping and restructuring which often need
12
+ // typecasting to any, allow it in there
13
+ /* eslint-disable @typescript-eslint/no-explicit-any */
14
+ export class VersionController {
15
+ static readonly GLOBAL_VERSION_SETTING_NAME = 'inspectorVersion';
16
+ static readonly SYNCED_VERSION_SETTING_NAME = 'syncedInspectorVersion';
17
+ static readonly LOCAL_VERSION_SETTING_NAME = 'localInspectorVersion';
18
+
19
+ static readonly CURRENT_VERSION = 42;
20
+
21
+ readonly #settings: Settings;
22
+ readonly #globalVersionSetting: Setting<number>;
23
+ readonly #syncedVersionSetting: Setting<number>;
24
+ readonly #localVersionSetting: Setting<number>;
25
+
26
+ constructor(settings: Settings) {
27
+ this.#settings = settings;
28
+ // If no version setting is found, we initialize with the current version and don't do anything.
29
+ this.#globalVersionSetting = this.#settings.createSetting(
30
+ VersionController.GLOBAL_VERSION_SETTING_NAME, VersionController.CURRENT_VERSION, SettingStorageType.GLOBAL);
31
+ this.#syncedVersionSetting = this.#settings.createSetting(
32
+ VersionController.SYNCED_VERSION_SETTING_NAME, VersionController.CURRENT_VERSION, SettingStorageType.SYNCED);
33
+ this.#localVersionSetting = this.#settings.createSetting(
34
+ VersionController.LOCAL_VERSION_SETTING_NAME, VersionController.CURRENT_VERSION, SettingStorageType.LOCAL);
35
+ }
36
+
37
+ /**
38
+ * Force re-sets all version number settings to the current version without
39
+ * running any migrations.
40
+ */
41
+ resetToCurrent(): void {
42
+ this.#globalVersionSetting.set(VersionController.CURRENT_VERSION);
43
+ this.#syncedVersionSetting.set(VersionController.CURRENT_VERSION);
44
+ this.#localVersionSetting.set(VersionController.CURRENT_VERSION);
45
+ }
46
+
47
+ #removeSetting(setting: {name: string, storage: SettingsStorage}): void {
48
+ const name = setting.name;
49
+
50
+ this.#settings.getRegistry().delete(name);
51
+ this.#settings.moduleSettings.delete(name);
52
+
53
+ setting.storage.remove(name);
54
+ }
55
+
56
+ /**
57
+ * Runs the appropriate migrations and updates the version settings accordingly.
58
+ *
59
+ * To determine what migrations to run we take the minimum of all version number settings.
60
+ *
61
+ * IMPORTANT: All migrations must be idempotent since they might be applied multiple times.
62
+ */
63
+ updateVersion(): void {
64
+ const currentVersion = VersionController.CURRENT_VERSION;
65
+ const minimumVersion =
66
+ Math.min(this.#globalVersionSetting.get(), this.#syncedVersionSetting.get(), this.#localVersionSetting.get());
67
+ const methodsToRun = this.methodsToRunToUpdateVersion(minimumVersion, currentVersion);
68
+ console.assert(
69
+ // @ts-expect-error
70
+ this[`updateVersionFrom${currentVersion}To${currentVersion + 1}`] === undefined,
71
+ 'Unexpected migration method found. Increment CURRENT_VERSION or remove the method.');
72
+ for (const method of methodsToRun) {
73
+ // @ts-expect-error Special version method matching
74
+ this[method].call(this);
75
+ }
76
+ this.resetToCurrent();
77
+ }
78
+
79
+ private methodsToRunToUpdateVersion(oldVersion: number, currentVersion: number): string[] {
80
+ const result = [];
81
+ for (let i = oldVersion; i < currentVersion; ++i) {
82
+ result.push('updateVersionFrom' + i + 'To' + (i + 1));
83
+ }
84
+ return result;
85
+ }
86
+
87
+ updateVersionFrom0To1(): void {
88
+ this.clearBreakpointsWhenTooMany(this.#settings.createLocalSetting('breakpoints', []), 500000);
89
+ }
90
+
91
+ updateVersionFrom1To2(): void {
92
+ this.#settings.createSetting('previouslyViewedFiles', []).set([]);
93
+ }
94
+
95
+ updateVersionFrom2To3(): void {
96
+ this.#settings.createSetting('fileSystemMapping', {}).set({});
97
+ this.#removeSetting(this.#settings.createSetting('fileMappingEntries', []));
98
+ }
99
+
100
+ updateVersionFrom3To4(): void {
101
+ const advancedMode = this.#settings.createSetting('showHeaSnapshotObjectsHiddenProperties', false);
102
+ this.#settings.moduleSetting('showAdvancedHeapSnapshotProperties').set(advancedMode.get());
103
+ this.#removeSetting(advancedMode);
104
+ }
105
+
106
+ updateVersionFrom4To5(): void {
107
+ const settingNames: Record<string, string> = {
108
+ FileSystemViewSidebarWidth: 'fileSystemViewSplitViewState',
109
+ elementsSidebarWidth: 'elementsPanelSplitViewState',
110
+ StylesPaneSplitRatio: 'stylesPaneSplitViewState',
111
+ heapSnapshotRetainersViewSize: 'heapSnapshotSplitViewState',
112
+ 'InspectorView.splitView': 'InspectorView.splitViewState',
113
+ 'InspectorView.screencastSplitView': 'InspectorView.screencastSplitViewState',
114
+ 'Inspector.drawerSplitView': 'Inspector.drawerSplitViewState',
115
+ layerDetailsSplitView: 'layerDetailsSplitViewState',
116
+ networkSidebarWidth: 'networkPanelSplitViewState',
117
+ sourcesSidebarWidth: 'sourcesPanelSplitViewState',
118
+ scriptsPanelNavigatorSidebarWidth: 'sourcesPanelNavigatorSplitViewState',
119
+ sourcesPanelSplitSidebarRatio: 'sourcesPanelDebuggerSidebarSplitViewState',
120
+ 'timeline-details': 'timelinePanelDetailsSplitViewState',
121
+ 'timeline-split': 'timelinePanelRecorsSplitViewState',
122
+ 'timeline-view': 'timelinePanelTimelineStackSplitViewState',
123
+ auditsSidebarWidth: 'auditsPanelSplitViewState',
124
+ layersSidebarWidth: 'layersPanelSplitViewState',
125
+ profilesSidebarWidth: 'profilesPanelSplitViewState',
126
+ resourcesSidebarWidth: 'resourcesPanelSplitViewState',
127
+ };
128
+ const empty = {};
129
+ for (const oldName in settingNames) {
130
+ const newName = settingNames[oldName];
131
+ const oldNameH = oldName + 'H';
132
+
133
+ let newValue: object|null = null;
134
+ const oldSetting = this.#settings.createSetting(oldName, empty);
135
+ if (oldSetting.get() !== empty) {
136
+ newValue = newValue || {};
137
+ // @ts-expect-error
138
+ newValue.vertical = {};
139
+ // @ts-expect-error
140
+ newValue.vertical.size = oldSetting.get();
141
+ this.#removeSetting(oldSetting);
142
+ }
143
+ const oldSettingH = this.#settings.createSetting(oldNameH, empty);
144
+ if (oldSettingH.get() !== empty) {
145
+ newValue = newValue || {};
146
+ // @ts-expect-error
147
+ newValue.horizontal = {};
148
+ // @ts-expect-error
149
+ newValue.horizontal.size = oldSettingH.get();
150
+ this.#removeSetting(oldSettingH);
151
+ }
152
+ if (newValue) {
153
+ this.#settings.createSetting(newName, {}).set(newValue);
154
+ }
155
+ }
156
+ }
157
+
158
+ updateVersionFrom5To6(): void {
159
+ const settingNames: Record<string, string> = {
160
+ debuggerSidebarHidden: 'sourcesPanelSplitViewState',
161
+ navigatorHidden: 'sourcesPanelNavigatorSplitViewState',
162
+ 'WebInspector.Drawer.showOnLoad': 'Inspector.drawerSplitViewState',
163
+ };
164
+
165
+ for (const oldName in settingNames) {
166
+ const oldSetting = this.#settings.createSetting(oldName, null);
167
+ if (oldSetting.get() === null) {
168
+ this.#removeSetting(oldSetting);
169
+ continue;
170
+ }
171
+
172
+ const newName = settingNames[oldName];
173
+ const invert = oldName === 'WebInspector.Drawer.showOnLoad';
174
+ const hidden = oldSetting.get() !== invert;
175
+ this.#removeSetting(oldSetting);
176
+ const showMode = hidden ? 'OnlyMain' : 'Both';
177
+
178
+ const newSetting = this.#settings.createSetting(newName, {});
179
+ const newValue = newSetting.get() || {};
180
+
181
+ // @ts-expect-error
182
+ newValue.vertical = newValue.vertical || {};
183
+
184
+ // @ts-expect-error
185
+ newValue.vertical.showMode = showMode;
186
+
187
+ // @ts-expect-error
188
+ newValue.horizontal = newValue.horizontal || {};
189
+
190
+ // @ts-expect-error
191
+ newValue.horizontal.showMode = showMode;
192
+ newSetting.set(newValue);
193
+ }
194
+ }
195
+
196
+ updateVersionFrom6To7(): void {
197
+ const settingNames = {
198
+ sourcesPanelNavigatorSplitViewState: 'sourcesPanelNavigatorSplitViewState',
199
+ elementsPanelSplitViewState: 'elementsPanelSplitViewState',
200
+ stylesPaneSplitViewState: 'stylesPaneSplitViewState',
201
+ sourcesPanelDebuggerSidebarSplitViewState: 'sourcesPanelDebuggerSidebarSplitViewState',
202
+ };
203
+
204
+ const empty = {};
205
+ for (const name in settingNames) {
206
+ const setting =
207
+ this.#settings.createSetting<{vertical?: {size?: number}, horizontal?: {size?: number}}>(name, empty);
208
+ const value = setting.get();
209
+ if (value === empty) {
210
+ continue;
211
+ }
212
+ // Zero out saved percentage sizes, and they will be restored to defaults.
213
+ if (value.vertical?.size && value.vertical.size < 1) {
214
+ value.vertical.size = 0;
215
+ }
216
+ if (value.horizontal?.size && value.horizontal.size < 1) {
217
+ value.horizontal.size = 0;
218
+ }
219
+ setting.set(value);
220
+ }
221
+ }
222
+
223
+ updateVersionFrom7To8(): void {
224
+ }
225
+
226
+ updateVersionFrom8To9(): void {
227
+ const settingNames = ['skipStackFramesPattern', 'workspaceFolderExcludePattern'];
228
+
229
+ for (let i = 0; i < settingNames.length; ++i) {
230
+ const setting = this.#settings.createSetting<string|unknown[]>(settingNames[i], '');
231
+ let value = setting.get();
232
+ if (!value) {
233
+ return;
234
+ }
235
+ if (typeof value === 'string') {
236
+ value = [value];
237
+ }
238
+ for (let j = 0; j < value.length; ++j) {
239
+ if (typeof value[j] === 'string') {
240
+ value[j] = {pattern: value[j]};
241
+ }
242
+ }
243
+ setting.set(value);
244
+ }
245
+ }
246
+
247
+ updateVersionFrom9To10(): void {
248
+ // This one is localStorage specific, which is fine.
249
+ if (!window.localStorage) {
250
+ return;
251
+ }
252
+ for (const key in window.localStorage) {
253
+ if (key.startsWith('revision-history')) {
254
+ window.localStorage.removeItem(key);
255
+ }
256
+ }
257
+ }
258
+
259
+ updateVersionFrom10To11(): void {
260
+ const oldSettingName = 'customDevicePresets';
261
+ const newSettingName = 'customEmulatedDeviceList';
262
+ const oldSetting = this.#settings.createSetting<unknown>(oldSettingName, undefined);
263
+ const list = oldSetting.get();
264
+ if (!Array.isArray(list)) {
265
+ return;
266
+ }
267
+ const newList = [];
268
+ for (let i = 0; i < list.length; ++i) {
269
+ const value = list[i];
270
+
271
+ const device: Record<string, any> = {};
272
+ device['title'] = value['title'];
273
+ device['type'] = 'unknown';
274
+ device['user-agent'] = value['userAgent'];
275
+ device['capabilities'] = [];
276
+ if (value['touch']) {
277
+ device['capabilities'].push('touch');
278
+ }
279
+ if (value['mobile']) {
280
+ device['capabilities'].push('mobile');
281
+ }
282
+ device['screen'] = {};
283
+ device['screen']['vertical'] = {width: value['width'], height: value['height']};
284
+ device['screen']['horizontal'] = {width: value['height'], height: value['width']};
285
+ device['screen']['device-pixel-ratio'] = value['deviceScaleFactor'];
286
+ device['modes'] = [];
287
+ device['show-by-default'] = true;
288
+ device['show'] = 'Default';
289
+ newList.push(device);
290
+ }
291
+ if (newList.length) {
292
+ this.#settings.createSetting<unknown[]>(newSettingName, []).set(newList);
293
+ }
294
+ this.#removeSetting(oldSetting);
295
+ }
296
+
297
+ updateVersionFrom11To12(): void {
298
+ this.migrateSettingsFromLocalStorage();
299
+ }
300
+
301
+ updateVersionFrom12To13(): void {
302
+ this.migrateSettingsFromLocalStorage();
303
+ this.#removeSetting(this.#settings.createSetting('timelineOverviewMode', ''));
304
+ }
305
+
306
+ updateVersionFrom13To14(): void {
307
+ const defaultValue = {throughput: -1, latency: 0};
308
+ this.#settings.createSetting('networkConditions', defaultValue).set(defaultValue);
309
+ }
310
+
311
+ updateVersionFrom14To15(): void {
312
+ const setting = this.#settings.createLocalSetting<any>('workspaceExcludedFolders', {});
313
+ const oldValue = setting.get();
314
+ const newValue: Record<string, string[]> = {};
315
+ for (const fileSystemPath in oldValue) {
316
+ newValue[fileSystemPath] = [];
317
+ for (const entry of oldValue[fileSystemPath]) {
318
+ newValue[fileSystemPath].push(entry.path);
319
+ }
320
+ }
321
+ setting.set(newValue);
322
+ }
323
+
324
+ updateVersionFrom15To16(): void {
325
+ const setting = this.#settings.createSetting<any>('InspectorView.panelOrder', {});
326
+ const tabOrders = setting.get();
327
+ for (const key of Object.keys(tabOrders)) {
328
+ tabOrders[key] = (tabOrders[key] + 1) * 10;
329
+ }
330
+ setting.set(tabOrders);
331
+ }
332
+
333
+ updateVersionFrom16To17(): void {
334
+ const setting = this.#settings.createSetting<any>('networkConditionsCustomProfiles', []);
335
+ const oldValue = setting.get();
336
+ const newValue = [];
337
+ if (Array.isArray(oldValue)) {
338
+ for (const preset of oldValue) {
339
+ if (typeof preset.title === 'string' && typeof preset.value === 'object' &&
340
+ typeof preset.value.throughput === 'number' && typeof preset.value.latency === 'number') {
341
+ newValue.push({
342
+ title: preset.title,
343
+ value: {download: preset.value.throughput, upload: preset.value.throughput, latency: preset.value.latency},
344
+ });
345
+ }
346
+ }
347
+ }
348
+ setting.set(newValue);
349
+ }
350
+
351
+ updateVersionFrom17To18(): void {
352
+ const setting = this.#settings.createLocalSetting<any>('workspaceExcludedFolders', {});
353
+ const oldValue = setting.get();
354
+ const newValue: Record<string, string> = {};
355
+ for (const oldKey in oldValue) {
356
+ let newKey = oldKey.replace(/\\/g, '/');
357
+ if (!newKey.startsWith('file://')) {
358
+ if (newKey.startsWith('/')) {
359
+ newKey = 'file://' + newKey;
360
+ } else {
361
+ newKey = 'file:///' + newKey;
362
+ }
363
+ }
364
+ newValue[newKey] = oldValue[oldKey];
365
+ }
366
+ setting.set(newValue);
367
+ }
368
+
369
+ updateVersionFrom18To19(): void {
370
+ const defaultColumns = {status: true, type: true, initiator: true, size: true, time: true};
371
+ const visibleColumnSettings = this.#settings.createSetting<any>('networkLogColumnsVisibility', defaultColumns);
372
+ const visibleColumns = visibleColumnSettings.get();
373
+ visibleColumns.name = true;
374
+ visibleColumns.timeline = true;
375
+
376
+ const configs: Record<string, {
377
+ visible: number,
378
+ }> = {};
379
+ for (const columnId in visibleColumns) {
380
+ if (!visibleColumns.hasOwnProperty(columnId)) {
381
+ continue;
382
+ }
383
+ configs[columnId.toLowerCase()] = {visible: visibleColumns[columnId]};
384
+ }
385
+ const newSetting = this.#settings.createSetting('networkLogColumns', {});
386
+ newSetting.set(configs);
387
+ this.#removeSetting(visibleColumnSettings);
388
+ }
389
+
390
+ updateVersionFrom19To20(): void {
391
+ const oldSetting = this.#settings.createSetting('InspectorView.panelOrder', {});
392
+ const newSetting = this.#settings.createSetting('panel-tabOrder', {});
393
+ newSetting.set(oldSetting.get());
394
+ this.#removeSetting(oldSetting);
395
+ }
396
+
397
+ updateVersionFrom20To21(): void {
398
+ const networkColumns = this.#settings.createSetting('networkLogColumns', {});
399
+ const columns = (networkColumns.get() as Record<string, string>);
400
+ delete columns['timeline'];
401
+ delete columns['waterfall'];
402
+ networkColumns.set(columns);
403
+ }
404
+
405
+ updateVersionFrom21To22(): void {
406
+ const breakpointsSetting = this.#settings.createLocalSetting<any>('breakpoints', []);
407
+ const breakpoints = breakpointsSetting.get();
408
+ for (const breakpoint of breakpoints) {
409
+ breakpoint['url'] = breakpoint['sourceFileId'];
410
+ delete breakpoint['sourceFileId'];
411
+ }
412
+ breakpointsSetting.set(breakpoints);
413
+ }
414
+
415
+ updateVersionFrom22To23(): void {
416
+ // This update is no-op.
417
+ }
418
+
419
+ updateVersionFrom23To24(): void {
420
+ const oldSetting = this.#settings.createSetting('searchInContentScripts', false);
421
+ const newSetting = this.#settings.createSetting('searchInAnonymousAndContentScripts', false);
422
+ newSetting.set(oldSetting.get());
423
+ this.#removeSetting(oldSetting);
424
+ }
425
+
426
+ updateVersionFrom24To25(): void {
427
+ const defaultColumns = {status: true, type: true, initiator: true, size: true, time: true};
428
+
429
+ const networkLogColumnsSetting = this.#settings.createSetting<any>('networkLogColumns', defaultColumns);
430
+ const columns = networkLogColumnsSetting.get();
431
+ delete columns.product;
432
+ networkLogColumnsSetting.set(columns);
433
+ }
434
+
435
+ updateVersionFrom25To26(): void {
436
+ const oldSetting = this.#settings.createSetting('messageURLFilters', {});
437
+ const urls = Object.keys(oldSetting.get());
438
+ const textFilter = urls.map(url => `-url:${url}`).join(' ');
439
+ if (textFilter) {
440
+ const textFilterSetting = this.#settings.createSetting<any>('console.textFilter', '');
441
+ const suffix = textFilterSetting.get() ? ` ${textFilterSetting.get()}` : '';
442
+ textFilterSetting.set(`${textFilter}${suffix}`);
443
+ }
444
+ this.#removeSetting(oldSetting);
445
+ }
446
+
447
+ updateVersionFrom26To27(): void {
448
+ const settings = this.#settings;
449
+ function renameKeyInObjectSetting(settingName: string, from: string, to: string): void {
450
+ const setting = settings.createSetting<any>(settingName, {});
451
+ const value = setting.get();
452
+ if (from in value) {
453
+ value[to] = value[from];
454
+ delete value[from];
455
+ setting.set(value);
456
+ }
457
+ }
458
+
459
+ function renameInStringSetting(settingName: string, from: string, to: string): void {
460
+ const setting = settings.createSetting(settingName, '');
461
+ const value = setting.get();
462
+ if (value === from) {
463
+ setting.set(to);
464
+ }
465
+ }
466
+
467
+ renameKeyInObjectSetting('panel-tabOrder', 'audits2', 'audits');
468
+ renameKeyInObjectSetting('panel-closeableTabs', 'audits2', 'audits');
469
+ renameInStringSetting('panel-selectedTab', 'audits2', 'audits');
470
+ }
471
+
472
+ updateVersionFrom27To28(): void {
473
+ const setting = this.#settings.createSetting('uiTheme', 'systemPreferred');
474
+ if (setting.get() === 'default') {
475
+ setting.set('systemPreferred');
476
+ }
477
+ }
478
+
479
+ updateVersionFrom28To29(): void {
480
+ const settings = this.#settings;
481
+ function renameKeyInObjectSetting(settingName: string, from: string, to: string): void {
482
+ const setting = settings.createSetting<any>(settingName, {});
483
+ const value = setting.get();
484
+ if (from in value) {
485
+ value[to] = value[from];
486
+ delete value[from];
487
+ setting.set(value);
488
+ }
489
+ }
490
+
491
+ function renameInStringSetting(settingName: string, from: string, to: string): void {
492
+ const setting = settings.createSetting(settingName, '');
493
+ const value = setting.get();
494
+ if (value === from) {
495
+ setting.set(to);
496
+ }
497
+ }
498
+
499
+ renameKeyInObjectSetting('panel-tabOrder', 'audits', 'lighthouse');
500
+ renameKeyInObjectSetting('panel-closeableTabs', 'audits', 'lighthouse');
501
+ renameInStringSetting('panel-selectedTab', 'audits', 'lighthouse');
502
+ }
503
+
504
+ updateVersionFrom29To30(): void {
505
+ // Create new location agnostic setting
506
+ const closeableTabSetting = this.#settings.createSetting('closeableTabs', {});
507
+
508
+ // Read current settings
509
+ const panelCloseableTabSetting = this.#settings.createSetting('panel-closeableTabs', {});
510
+ const drawerCloseableTabSetting = this.#settings.createSetting('drawer-view-closeableTabs', {});
511
+ const openTabsInPanel = panelCloseableTabSetting.get();
512
+ const openTabsInDrawer = panelCloseableTabSetting.get();
513
+
514
+ // Set #value of new setting
515
+ const newValue = Object.assign(openTabsInDrawer, openTabsInPanel);
516
+ closeableTabSetting.set(newValue);
517
+
518
+ // Remove old settings
519
+ this.#removeSetting(panelCloseableTabSetting);
520
+ this.#removeSetting(drawerCloseableTabSetting);
521
+ }
522
+
523
+ updateVersionFrom30To31(): void {
524
+ // Remove recorder_recordings setting that was used for storing recordings
525
+ // by an old recorder experiment.
526
+ const recordingsSetting = this.#settings.createSetting('recorder_recordings', []);
527
+ this.#removeSetting(recordingsSetting);
528
+ }
529
+
530
+ updateVersionFrom31To32(): void {
531
+ // Introduce the new 'resourceTypeName' property on stored breakpoints. Prior to
532
+ // this change we synchronized the breakpoint only by URL, but since we don't
533
+ // know on which resource type the given breakpoint was set, we just assume
534
+ // 'script' here to keep things simple.
535
+
536
+ const breakpointsSetting = this.#settings.createLocalSetting<any>('breakpoints', []);
537
+ const breakpoints = breakpointsSetting.get();
538
+ for (const breakpoint of breakpoints) {
539
+ breakpoint['resourceTypeName'] = 'script';
540
+ }
541
+ breakpointsSetting.set(breakpoints);
542
+ }
543
+
544
+ updateVersionFrom32To33(): void {
545
+ const previouslyViewedFilesSetting = this.#settings.createLocalSetting<any>('previouslyViewedFiles', []);
546
+ let previouslyViewedFiles = previouslyViewedFilesSetting.get();
547
+
548
+ // Discard old 'previouslyViewedFiles' items that don't have a 'url' property.
549
+
550
+ previouslyViewedFiles = previouslyViewedFiles.filter((previouslyViewedFile: any) => 'url' in previouslyViewedFile);
551
+
552
+ // Introduce the new 'resourceTypeName' property on previously viewed files.
553
+ // Prior to this change we only keyed them based on the URL, but since we
554
+ // don't know which resource type the given file had, we just assume 'script'
555
+ // here to keep things simple.
556
+ for (const previouslyViewedFile of previouslyViewedFiles) {
557
+ previouslyViewedFile['resourceTypeName'] = 'script';
558
+ }
559
+
560
+ previouslyViewedFilesSetting.set(previouslyViewedFiles);
561
+ }
562
+
563
+ updateVersionFrom33To34(): void {
564
+ // Introduces the 'isLogpoint' property on stored breakpoints. This information was
565
+ // previously encoded in the 'condition' itself. This migration leaves the condition
566
+ // alone but ensures that 'isLogpoint' is accurate for already stored breakpoints.
567
+ // This enables us to use the 'isLogpoint' property in code.
568
+ // A separate migration will remove the special encoding from the condition itself
569
+ // once all refactorings are done.
570
+
571
+ // The prefix/suffix are hardcoded here, since these constants will be removed in
572
+ // the future.
573
+ const logpointPrefix = '/** DEVTOOLS_LOGPOINT */ console.log(';
574
+ const logpointSuffix = ')';
575
+
576
+ const breakpointsSetting = this.#settings.createLocalSetting<any>('breakpoints', []);
577
+ const breakpoints = breakpointsSetting.get();
578
+ for (const breakpoint of breakpoints) {
579
+ const isLogpoint =
580
+ breakpoint.condition.startsWith(logpointPrefix) && breakpoint.condition.endsWith(logpointSuffix);
581
+ breakpoint['isLogpoint'] = isLogpoint;
582
+ }
583
+ breakpointsSetting.set(breakpoints);
584
+ }
585
+
586
+ updateVersionFrom34To35(): void {
587
+ // Uses the 'isLogpoint' property on stored breakpoints to remove the prefix/suffix
588
+ // from logpoints. This way, we store the entered log point condition as the user
589
+ // entered it.
590
+
591
+ // The prefix/suffix are hardcoded here, since these constants will be removed in
592
+ // the future.
593
+ const logpointPrefix = '/** DEVTOOLS_LOGPOINT */ console.log(';
594
+ const logpointSuffix = ')';
595
+
596
+ const breakpointsSetting = this.#settings.createLocalSetting<any>('breakpoints', []);
597
+ const breakpoints = breakpointsSetting.get();
598
+ for (const breakpoint of breakpoints) {
599
+ const {condition, isLogpoint} = breakpoint;
600
+ if (isLogpoint) {
601
+ breakpoint.condition = condition.slice(logpointPrefix.length, condition.length - logpointSuffix.length);
602
+ }
603
+ }
604
+ breakpointsSetting.set(breakpoints);
605
+ }
606
+
607
+ updateVersionFrom35To36(): void {
608
+ // We have changed the default from 'false' to 'true' and this updates the existing setting just for once.
609
+ this.#settings.createSetting('showThirdPartyIssues', true).set(true);
610
+ }
611
+
612
+ updateVersionFrom36To37(): void {
613
+ const updateStorage = (storage: SettingsStorage): void => {
614
+ for (const key of storage.keys()) {
615
+ const normalizedKey = Settings.normalizeSettingName(key);
616
+ if (normalizedKey !== key) {
617
+ const value = storage.get(key);
618
+ this.#removeSetting({name: key, storage});
619
+ storage.set(normalizedKey, value);
620
+ }
621
+ }
622
+ };
623
+ updateStorage(this.#settings.globalStorage);
624
+ updateStorage(this.#settings.syncedStorage);
625
+ updateStorage(this.#settings.localStorage);
626
+
627
+ for (const key of this.#settings.globalStorage.keys()) {
628
+ if ((key.startsWith('data-grid-') && key.endsWith('-column-weights')) || key.endsWith('-tab-order') ||
629
+ key === 'views-location-override' || key === 'closeable-tabs') {
630
+ const setting = this.#settings.createSetting(key, {});
631
+ setting.set(Platform.StringUtilities.toKebabCaseKeys(setting.get()));
632
+ }
633
+ if (key.endsWith('-selected-tab')) {
634
+ const setting = this.#settings.createSetting(key, '');
635
+ setting.set(Platform.StringUtilities.toKebabCase(setting.get()));
636
+ }
637
+ }
638
+ }
639
+
640
+ updateVersionFrom37To38(): void {
641
+ const getConsoleInsightsEnabledSetting = (): Setting<boolean>|undefined => {
642
+ try {
643
+ return this.#settings.moduleSetting('console-insights-enabled') as Setting<boolean>;
644
+ } catch {
645
+ return;
646
+ }
647
+ };
648
+
649
+ const consoleInsightsEnabled = getConsoleInsightsEnabledSetting();
650
+ const onboardingFinished = this.#settings.createLocalSetting('console-insights-onboarding-finished', false);
651
+
652
+ if (consoleInsightsEnabled && consoleInsightsEnabled.get() === true && onboardingFinished.get() === false) {
653
+ consoleInsightsEnabled.set(false);
654
+ }
655
+ if (consoleInsightsEnabled && consoleInsightsEnabled.get() === false) {
656
+ onboardingFinished.set(false);
657
+ }
658
+ }
659
+
660
+ updateVersionFrom38To39(): void {
661
+ const PREFERRED_NETWORK_COND = 'preferred-network-condition';
662
+ // crrev.com/c/5582013 renamed "Slow 3G" to "3G" and "Fast 3G" => "Slow 4G".
663
+ // Any users with the old values need to have them moved to avoid breaking DevTools.
664
+ // Note: we load the raw value via the globalStorage here because
665
+ // `createSetting` creates if it is not present, and we do not want that;
666
+ // we only want to update existing, old values.
667
+ const setting = this.#settings.globalStorage.get(PREFERRED_NETWORK_COND);
668
+ if (!setting) {
669
+ return;
670
+ }
671
+ try {
672
+ const networkSetting = JSON.parse(setting) as unknown as {
673
+ // Can't use SDK type here as it creates a common<>sdk circular
674
+ // dep. This type is not exhaustive but contains the fields we
675
+ // need.
676
+ title: string,
677
+ i18nTitleKey?: string,
678
+ };
679
+ if (networkSetting.title === 'Slow 3G') {
680
+ networkSetting.title = '3G';
681
+ networkSetting.i18nTitleKey = '3G';
682
+ this.#settings.globalStorage.set(PREFERRED_NETWORK_COND, JSON.stringify(networkSetting));
683
+ } else if (networkSetting.title === 'Fast 3G') {
684
+ networkSetting.title = 'Slow 4G';
685
+ networkSetting.i18nTitleKey = 'Slow 4G';
686
+ this.#settings.globalStorage.set(PREFERRED_NETWORK_COND, JSON.stringify(networkSetting));
687
+ }
688
+ } catch {
689
+ // If parsing the setting threw, it's in some invalid state, so remove it.
690
+ this.#settings.globalStorage.remove(PREFERRED_NETWORK_COND);
691
+ }
692
+ }
693
+
694
+ /**
695
+ * There are two related migrations here for handling network throttling persistence:
696
+ * 1. Go through all user custom throttling conditions and add a `key` property.
697
+ * 2. If the user has a 'preferred-network-condition' setting, take the value
698
+ * of that and set the right key for the new 'active-network-condition-key'
699
+ * setting. Then, remove the now-obsolete 'preferred-network-condition'
700
+ * setting.
701
+ */
702
+ updateVersionFrom39To40(): void {
703
+ const hasCustomNetworkConditionsSetting = (): boolean => {
704
+ try {
705
+ // this will error if it does not exist
706
+ this.#settings.moduleSetting('custom-network-conditions');
707
+ return true;
708
+ } catch {
709
+ return false;
710
+ }
711
+ };
712
+ if (hasCustomNetworkConditionsSetting()) {
713
+ /**
714
+ * We added keys to custom network throttling conditions in M140, so we
715
+ * need to go through any existing profiles the user has and add the key to
716
+ * them.
717
+ * We can't use the SDK.NetworkManager.Condition here as it would be a
718
+ * circular dependency. All that matters is that these conditions are
719
+ * objects, and we need to set the right key on each one. The actual keys &
720
+ * values in the object are not important.
721
+ */
722
+ const conditionsSetting =
723
+ this.#settings.moduleSetting('custom-network-conditions') as Setting<Array<{key?: string}>>;
724
+ const customConditions = conditionsSetting.get();
725
+ if (customConditions?.length > 0) {
726
+ customConditions.forEach((condition, i) => {
727
+ // This could be run multiple times, make sure that we don't override any
728
+ // existing keys.
729
+ if (condition.key) {
730
+ return;
731
+ }
732
+ // The format of this key is important: see
733
+ // SDK.NetworkManager.UserDefinedThrottlingConditionKey
734
+ condition.key = `USER_CUSTOM_SETTING_${i + 1}`;
735
+ });
736
+ conditionsSetting.set(customConditions);
737
+ }
738
+ }
739
+
740
+ // Additionally, we need to make sure we persist the right throttling for
741
+ // users who have a preferred-network-condition set.
742
+ const PREFERRED_NETWORK_COND_SETTING = 'preferred-network-condition';
743
+ // We shipped a change to how we persist network throttling conditions and
744
+ // added a `key` property rather than rely on any user visible string which
745
+ // is more likely to change. This migration step tries to update the
746
+ // setting for users, or removes it if we fail, so they start fresh next
747
+ // time they load DevTools.
748
+ const setting = this.#settings.globalStorage.get(PREFERRED_NETWORK_COND_SETTING);
749
+ if (!setting) {
750
+ return;
751
+ }
752
+ // The keys here are the UI Strings as of July 2025 (shipped in M139).
753
+ // This migration shipped in M140. The values are the values of the
754
+ // `PredefinedThrottlingConditionKey` in SDK.NetworkManager.
755
+ const UI_STRING_TO_NEW_KEY = {
756
+ 'Fast 4G': 'SPEED_FAST_4G',
757
+ 'Slow 4G': 'SPEED_SLOW_4G',
758
+ '3G': 'SPEED_3G',
759
+ 'No throttling': 'NO_THROTTLING',
760
+ Offline: 'OFFLINE'
761
+ };
762
+ try {
763
+ const networkSetting = JSON.parse(setting) as unknown as {
764
+ // Can't use SDK type here as it creates a common<>sdk circular
765
+ // dep. We only rely on the i18nTitleKey.
766
+ i18nTitleKey?: string,
767
+ };
768
+ if (networkSetting.i18nTitleKey && UI_STRING_TO_NEW_KEY.hasOwnProperty(networkSetting.i18nTitleKey)) {
769
+ const key = UI_STRING_TO_NEW_KEY[networkSetting.i18nTitleKey as keyof typeof UI_STRING_TO_NEW_KEY];
770
+
771
+ // The second argument is the default value, so it's important that we
772
+ // set this to the default, and then update it to the new key.
773
+ const newSetting = this.#settings.createSetting('active-network-condition-key', 'NO_THROTTLING');
774
+ newSetting.set(key);
775
+ }
776
+ } finally {
777
+ // This setting is now not used, so we can remove it.
778
+ this.#settings.globalStorage.remove(PREFERRED_NETWORK_COND_SETTING);
779
+ }
780
+ }
781
+
782
+ // This migration handles two setting renames that requires inverted logic
783
+ // (from "Hide X" to "X") and flipped the default values to true.
784
+ updateVersionFrom40To41(): void {
785
+ // 1. Rename 'Hide network messages' to 'Network messages'
786
+ if (this.#settings.syncedStorage.has('hide-network-messages')) {
787
+ const oldNetworkSetting = this.#settings.createSetting('hide-network-messages', false, SettingStorageType.SYNCED);
788
+ if (!this.#settings.syncedStorage.has('network-messages')) {
789
+ const newNetworkSetting = this.#settings.createSetting('network-messages', true, SettingStorageType.SYNCED);
790
+ // If the user had a saved preference for the old setting, migrate it by
791
+ // inverting the value to match the new logic.
792
+ newNetworkSetting.set(!oldNetworkSetting.get());
793
+ }
794
+ this.#removeSetting(oldNetworkSetting);
795
+ }
796
+
797
+ // 2. Rename 'Hide `chrome` frame in Layers view' to 'Chrome frame in Layers view'
798
+ if (this.#settings.syncedStorage.has('frame-viewer-hide-chrome-window')) {
799
+ const oldChromeFrameSetting =
800
+ this.#settings.createSetting('frame-viewer-hide-chrome-window', false, SettingStorageType.SYNCED);
801
+ if (!this.#settings.syncedStorage.has('frame-viewer-chrome-window')) {
802
+ const newChromeFrameSetting =
803
+ this.#settings.createSetting('frame-viewer-chrome-window', true, SettingStorageType.SYNCED);
804
+ // Similar to above, move the preference and invert the boolean.
805
+ newChromeFrameSetting.set(!oldChromeFrameSetting.get());
806
+ }
807
+ this.#removeSetting(oldChromeFrameSetting);
808
+ }
809
+ }
810
+
811
+ /**
812
+ * The recording in recorder panel may have unreasonably long titles
813
+ * or a lot of steps which can cause renderer crashes.
814
+ * Similar to https://crbug.com/40918380
815
+ */
816
+ updateVersionFrom41To42(): void {
817
+ const recordingsSetting = this.#settings.createSetting<Array<{flow: {steps: unknown[], title: string}}>>(
818
+ 'recorder-recordings-ng',
819
+ [],
820
+ );
821
+ const recordings = recordingsSetting.get();
822
+ if (recordings.length === 0) {
823
+ return;
824
+ }
825
+
826
+ for (const recording of recordings) {
827
+ recording.flow.title = Platform.StringUtilities.trimEndWithMaxLength(recording.flow.title, 300);
828
+ recording.flow.steps = recording.flow.steps.slice(0, 4096);
829
+ }
830
+
831
+ recordingsSetting.set(recordings);
832
+ }
833
+
834
+ /*
835
+ * Any new migration should be added before this comment.
836
+ *
837
+ * IMPORTANT: Migrations must be idempotent, since they may be applied
838
+ * multiple times! E.g. when renaming a setting one has to check that the
839
+ * a setting with the new name does not yet exist.
840
+ * ----------------------------------------------------------------------- */
841
+
842
+ private migrateSettingsFromLocalStorage(): void {
843
+ // This step migrates all the settings except for the ones below into the browser profile.
844
+ const localSettings = new Set<string>([
845
+ 'advancedSearchConfig',
846
+ 'breakpoints',
847
+ 'consoleHistory',
848
+ 'domBreakpoints',
849
+ 'eventListenerBreakpoints',
850
+ 'fileSystemMapping',
851
+ 'lastSelectedSourcesSidebarPaneTab',
852
+ 'previouslyViewedFiles',
853
+ 'savedURLs',
854
+ 'watchExpressions',
855
+ 'workspaceExcludedFolders',
856
+ 'xhrBreakpoints',
857
+ ]);
858
+ if (!window.localStorage) {
859
+ return;
860
+ }
861
+
862
+ for (const key in window.localStorage) {
863
+ if (localSettings.has(key)) {
864
+ continue;
865
+ }
866
+ const value = window.localStorage[key];
867
+ window.localStorage.removeItem(key);
868
+ this.#settings.globalStorage.set(key, value);
869
+ }
870
+ }
871
+
872
+ private clearBreakpointsWhenTooMany(breakpointsSetting: Setting<unknown[]>, maxBreakpointsCount: number): void {
873
+ // If there are too many breakpoints in a storage, it is likely due to a recent bug that caused
874
+ // periodical breakpoints duplication leading to inspector slowness.
875
+ if (breakpointsSetting.get().length > maxBreakpointsCount) {
876
+ breakpointsSetting.set([]);
877
+ }
878
+ }
879
+ }
880
+ /* eslint-enable @typescript-eslint/no-explicit-any */