chrome-devtools-frontend 1.0.1595925 → 1.0.1596535

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 (129) hide show
  1. package/.stylelintrc.json +3 -1
  2. package/docs/ui_engineering.md +0 -36
  3. package/front_end/core/common/Console.ts +6 -6
  4. package/front_end/core/common/Settings.ts +12 -8
  5. package/front_end/core/host/UserMetrics.ts +0 -1
  6. package/front_end/core/root/DevToolsContext.ts +13 -7
  7. package/front_end/core/root/ExperimentNames.ts +0 -1
  8. package/front_end/core/sdk/CPUThrottlingManager.ts +12 -9
  9. package/front_end/core/sdk/DOMModel.ts +206 -0
  10. package/front_end/core/sdk/FrameManager.ts +7 -8
  11. package/front_end/core/sdk/SourceMapScopesInfo.ts +1 -1
  12. package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +1 -1
  13. package/front_end/entrypoints/greendev_floaty/floaty.css +2 -2
  14. package/front_end/entrypoints/main/MainImpl.ts +1 -7
  15. package/front_end/foundation/Universe.ts +22 -11
  16. package/front_end/generated/InspectorBackendCommands.ts +7 -0
  17. package/front_end/generated/protocol-mapping.d.ts +16 -0
  18. package/front_end/generated/protocol-proxy-api.d.ts +25 -0
  19. package/front_end/generated/protocol.ts +71 -0
  20. package/front_end/models/ai_assistance/AiConversation.ts +6 -95
  21. package/front_end/models/ai_assistance/ConversationHandler.ts +1 -1
  22. package/front_end/models/ai_assistance/agents/AiAgent.ts +11 -1
  23. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +14 -0
  24. package/front_end/models/ai_code_completion/AiCodeCompletion.ts +1 -1
  25. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +3 -0
  26. package/front_end/panels/ai_assistance/components/ChatMessage.ts +73 -35
  27. package/front_end/panels/ai_assistance/components/WalkthroughView.ts +30 -6
  28. package/front_end/panels/ai_assistance/components/chatMessage.css +4 -2
  29. package/front_end/panels/application/IndexedDBModel.ts +2 -4
  30. package/front_end/panels/application/ServiceWorkersView.ts +3 -11
  31. package/front_end/panels/developer_resources/DeveloperResourcesListView.ts +1 -1
  32. package/front_end/panels/elements/ComputedStyleWidget.ts +25 -16
  33. package/front_end/panels/elements/ElementsPanel.ts +0 -1
  34. package/front_end/panels/elements/StandaloneStylesContainer.ts +48 -12
  35. package/front_end/panels/elements/StylePropertiesSection.ts +97 -1
  36. package/front_end/panels/elements/StylePropertyTreeElement.ts +27 -5
  37. package/front_end/panels/elements/StylesContainer.ts +1 -0
  38. package/front_end/panels/elements/StylesSidebarPane.ts +48 -32
  39. package/front_end/panels/elements/nodeStackTraceWidget.css +1 -1
  40. package/front_end/panels/elements/stylePropertiesTreeOutline.css +1 -1
  41. package/front_end/panels/emulation/DeviceModeToolbar.ts +171 -89
  42. package/front_end/panels/greendev/GreenDevPanel.css +2 -2
  43. package/front_end/panels/issues/AffectedPermissionElementsView.ts +9 -6
  44. package/front_end/panels/lighthouse/LighthouseController.ts +13 -5
  45. package/front_end/panels/lighthouse/LighthousePanel.ts +22 -5
  46. package/front_end/panels/profiler/HeapSnapshotGridNodes.ts +11 -3
  47. package/front_end/panels/recorder/components/StepView.ts +0 -2
  48. package/front_end/panels/recorder/components/stepView.css +13 -13
  49. package/front_end/panels/recorder/components/timelineSection.css +6 -7
  50. package/front_end/panels/settings/components/SyncSection.ts +4 -13
  51. package/front_end/panels/timeline/CompatibilityTracksAppender.ts +2 -15
  52. package/front_end/panels/timeline/RecordingMetadata.ts +1 -1
  53. package/front_end/panels/timeline/TimelineDetailsView.ts +2 -2
  54. package/front_end/panels/timeline/TimelinePanel.ts +18 -0
  55. package/front_end/panels/timeline/TimelineUIUtils.ts +7 -4
  56. package/front_end/panels/timeline/components/CWVMetrics.ts +339 -0
  57. package/front_end/panels/timeline/components/Sidebar.ts +17 -0
  58. package/front_end/panels/timeline/components/SidebarInsightsTab.ts +10 -0
  59. package/front_end/panels/timeline/components/SidebarSingleInsightSet.ts +25 -267
  60. package/front_end/panels/timeline/components/Utils.ts +2 -2
  61. package/front_end/panels/timeline/components/components.ts +2 -0
  62. package/front_end/panels/timeline/components/cwvMetrics.css +107 -0
  63. package/front_end/panels/timeline/components/sidebarSingleInsightSet.css +0 -102
  64. package/front_end/panels/timeline/timeline-meta.ts +11 -0
  65. package/front_end/panels/timeline/utils/Helpers.ts +5 -1
  66. package/front_end/panels/webauthn/webauthnPane.css +1 -1
  67. package/front_end/third_party/chromium/README.chromium +1 -1
  68. package/front_end/third_party/puppeteer/README.chromium +2 -2
  69. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.d.ts +7 -0
  70. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.d.ts.map +1 -1
  71. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.js.map +1 -1
  72. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Page.d.ts +1 -0
  73. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Page.d.ts.map +1 -1
  74. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Page.js +3 -0
  75. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Page.js.map +1 -1
  76. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.d.ts +1 -0
  77. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.d.ts.map +1 -1
  78. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.js +6 -0
  79. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.js.map +1 -1
  80. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.d.ts +1 -0
  81. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.d.ts.map +1 -1
  82. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.js +5 -0
  83. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.js.map +1 -1
  84. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
  85. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +2 -2
  86. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +2 -2
  87. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  88. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.d.ts +1 -1
  89. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.js +1 -1
  90. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +7 -0
  91. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +14 -3
  92. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.d.ts +7 -0
  93. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.d.ts.map +1 -1
  94. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.js.map +1 -1
  95. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Page.d.ts +1 -0
  96. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Page.d.ts.map +1 -1
  97. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Page.js +3 -0
  98. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Page.js.map +1 -1
  99. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.d.ts +1 -0
  100. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.d.ts.map +1 -1
  101. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.js +6 -0
  102. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.js.map +1 -1
  103. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.d.ts +1 -0
  104. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.d.ts.map +1 -1
  105. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.js +5 -0
  106. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.js.map +1 -1
  107. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +2 -2
  108. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +2 -2
  109. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.d.ts +1 -1
  110. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.js +1 -1
  111. package/front_end/third_party/puppeteer/package/lib/types.d.ts +7 -0
  112. package/front_end/third_party/puppeteer/package/package.json +1 -1
  113. package/front_end/third_party/puppeteer/package/src/api/Page.ts +8 -0
  114. package/front_end/third_party/puppeteer/package/src/bidi/Page.ts +4 -0
  115. package/front_end/third_party/puppeteer/package/src/cdp/Browser.ts +7 -0
  116. package/front_end/third_party/puppeteer/package/src/cdp/Page.ts +6 -0
  117. package/front_end/third_party/puppeteer/package/src/revisions.ts +2 -2
  118. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  119. package/front_end/ui/components/text_editor/AutocompleteHistory.ts +89 -5
  120. package/front_end/ui/components/tree_outline/TreeOutline.ts +1 -1
  121. package/front_end/ui/legacy/ProgressIndicator.ts +1 -1
  122. package/front_end/ui/legacy/Toolbar.ts +1 -1
  123. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +14 -7
  124. package/front_end/ui/legacy/legacy.ts +0 -2
  125. package/front_end/ui/visual_logging/KnownContextValues.ts +5 -0
  126. package/inspector_overlay/loadCSS.rollup.js +2 -2
  127. package/inspector_overlay/tool_source_order.css +1 -0
  128. package/package.json +1 -1
  129. package/front_end/ui/legacy/Fragment.ts +0 -234
@@ -161,11 +161,6 @@ const UIStrings = {
161
161
  * @description Title of button in the Device Mode Toolbar which rotates the device 90 degrees.
162
162
  */
163
163
  rotate: 'Rotate',
164
- /**
165
- * @description Fallback/default text used for the name of a custom device when no name has been
166
- * provided by the user.
167
- */
168
- none: 'None',
169
164
  /**
170
165
  * @description Tooltip of the rotate/screen orientation button.
171
166
  */
@@ -185,6 +180,10 @@ const UIStrings = {
185
180
  * posture e.g. Continuous, Folded.
186
181
  */
187
182
  devicePosture: 'Device posture',
183
+ /**
184
+ * @description Title of the network throttling selection in the Device Mode Toolbar.
185
+ */
186
+ throttling: 'Throttling',
188
187
  } as const;
189
188
  const str_ = i18n.i18n.registerUIStrings('panels/emulation/DeviceModeToolbar.ts', UIStrings);
190
189
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -213,18 +212,17 @@ export class DeviceModeToolbar {
213
212
  private readonly emulatedDevicesList: EmulationModel.EmulatedDevices.EmulatedDevicesList;
214
213
  private readonly persistenceSetting: Common.Settings.Setting<{device: string, orientation: string, mode: string}>;
215
214
  private spanButton!: UI.Toolbar.ToolbarButton;
216
- private postureItem!: UI.Toolbar.ToolbarMenuButton;
215
+ private postureItem!: UI.Toolbar.ToolbarComboBox;
217
216
  private modeButton!: UI.Toolbar.ToolbarButton;
218
217
  private widthInput: EmulationComponents.DeviceSizeInputElement.SizeInputElement;
219
218
  private heightInput: EmulationComponents.DeviceSizeInputElement.SizeInputElement;
220
- private deviceScaleItem!: UI.Toolbar.ToolbarMenuButton;
221
- private deviceSelectItem!: UI.Toolbar.ToolbarMenuButton;
222
- private scaleItem!: UI.Toolbar.ToolbarMenuButton;
223
- private uaItem!: UI.Toolbar.ToolbarMenuButton;
219
+ private deviceScaleItem!: UI.Toolbar.ToolbarComboBox;
220
+ private deviceSelectItem!: UI.Toolbar.ToolbarComboBox;
221
+ private scaleItem!: UI.Toolbar.ToolbarComboBox;
222
+ private uaItem!: UI.Toolbar.ToolbarComboBox;
224
223
  private cachedDeviceScale!: number|null;
225
224
  private cachedUaType!: string|null;
226
225
  private xItem?: UI.Toolbar.ToolbarItem;
227
- private throttlingConditionsItem?: UI.Toolbar.ToolbarMenuButton;
228
226
  private cachedModelType?: EmulationModel.DeviceModeModel.Type;
229
227
  private cachedScale?: number;
230
228
  private cachedModelDevice?: EmulationModel.EmulatedDevices.EmulatedDevice|null;
@@ -275,6 +273,12 @@ export class DeviceModeToolbar {
275
273
  this.model.toolbarControlsEnabledSetting().addChangeListener(updateToolbarsEnabled);
276
274
  updateToolbarsEnabled();
277
275
 
276
+ this.updateDeviceMenuItems();
277
+ this.updateScaleMenuItems();
278
+ this.updateDeviceScaleMenuItems();
279
+ this.updateUserAgentMenuItems();
280
+ this.updateDevicePostureItems();
281
+
278
282
  function updateToolbarsEnabled(): void {
279
283
  const enabled = model.toolbarControlsEnabledSetting().get();
280
284
  mainToolbar.setEnabled(enabled);
@@ -292,10 +296,9 @@ export class DeviceModeToolbar {
292
296
  const mainToolbar = this.#element.createChild('devtools-toolbar', 'main-toolbar');
293
297
 
294
298
  mainToolbar.appendToolbarItem(new UI.Toolbar.ToolbarItem(this.createEmptyToolbarElement()));
295
- this.deviceSelectItem =
296
- new UI.Toolbar.ToolbarMenuButton(this.appendDeviceMenuItems.bind(this), undefined, undefined, 'device');
299
+ this.deviceSelectItem = new UI.Toolbar.ToolbarComboBox(
300
+ this.onDeviceChange.bind(this), i18nString(UIStrings.deviceType), 'dark-text', 'device');
297
301
  this.deviceSelectItem.turnShrinkable();
298
- this.deviceSelectItem.setDarkText();
299
302
  const dimensionsSpan =
300
303
  uiI18n.getFormatLocalizedString(str_, UIStrings.dimensions, {PH1: this.deviceSelectItem.element});
301
304
  mainToolbar.append(...dimensionsSpan.childNodes);
@@ -325,10 +328,8 @@ export class DeviceModeToolbar {
325
328
 
326
329
  mainToolbar.appendToolbarItem(new UI.Toolbar.ToolbarItem(this.createEmptyToolbarElement()));
327
330
  this.scaleItem =
328
- new UI.Toolbar.ToolbarMenuButton(this.appendScaleMenuItems.bind(this), undefined, undefined, 'scale');
329
- setTitleForButton(this.scaleItem, i18nString(UIStrings.zoom));
331
+ new UI.Toolbar.ToolbarComboBox(this.onScaleChange.bind(this), i18nString(UIStrings.zoom), 'dark-text', 'scale');
330
332
  this.scaleItem.turnShrinkable();
331
- this.scaleItem.setDarkText();
332
333
  mainToolbar.appendToolbarItem(this.scaleItem);
333
334
 
334
335
  const autoAdjustScaleButton = new UI.Toolbar.ToolbarSettingToggle(
@@ -337,28 +338,22 @@ export class DeviceModeToolbar {
337
338
 
338
339
  mainToolbar.appendToolbarItem(new UI.Toolbar.ToolbarItem(this.createEmptyToolbarElement()));
339
340
 
340
- this.deviceScaleItem = new UI.Toolbar.ToolbarMenuButton(
341
- this.appendDeviceScaleMenuItems.bind(this), undefined, undefined, 'device-pixel-ratio');
341
+ this.deviceScaleItem = new UI.Toolbar.ToolbarComboBox(
342
+ this.onDeviceScaleChange.bind(this), i18nString(UIStrings.devicePixelRatio), 'dark-text', 'device-pixel-ratio');
342
343
  this.deviceScaleItem.turnShrinkable();
343
344
  this.deviceScaleItem.setVisible(this.showDeviceScaleFactorSetting.get());
344
- setTitleForButton(this.deviceScaleItem, i18nString(UIStrings.devicePixelRatio));
345
- this.deviceScaleItem.setDarkText();
346
345
  const deviceScaleSpan = uiI18n.getFormatLocalizedString(str_, UIStrings.dpr, {PH1: this.deviceScaleItem.element});
347
346
  mainToolbar.append(...deviceScaleSpan.childNodes);
348
347
  mainToolbar.appendToolbarItem(this.deviceScaleItem);
349
348
  mainToolbar.appendToolbarItem(new UI.Toolbar.ToolbarItem(this.createEmptyToolbarElement()));
350
- this.uaItem =
351
- new UI.Toolbar.ToolbarMenuButton(this.appendUserAgentMenuItems.bind(this), undefined, undefined, 'device-type');
349
+ this.uaItem = new UI.Toolbar.ToolbarComboBox(
350
+ this.onUAChange.bind(this), i18nString(UIStrings.deviceType), 'dark-text', 'device-type');
352
351
  this.uaItem.turnShrinkable();
353
352
  this.uaItem.setVisible(this.showUserAgentTypeSetting.get());
354
- setTitleForButton(this.uaItem, i18nString(UIStrings.deviceType));
355
- this.uaItem.setDarkText();
356
353
  mainToolbar.appendToolbarItem(this.uaItem);
357
354
 
358
- this.throttlingConditionsItem =
359
- MobileThrottling.ThrottlingManager.throttlingManager().createMobileThrottlingButton();
360
- this.throttlingConditionsItem.turnShrinkable();
361
- mainToolbar.appendToolbarItem(this.throttlingConditionsItem);
355
+ MobileThrottling.NetworkThrottlingSelector.NetworkThrottlingSelect.createForGlobalConditions(
356
+ mainToolbar, i18nString(UIStrings.throttling));
362
357
  const saveDataItem = MobileThrottling.ThrottlingManager.throttlingManager().createSaveDataOverrideSelector();
363
358
  saveDataItem.turnShrinkable();
364
359
  mainToolbar.appendToolbarItem(saveDataItem);
@@ -375,11 +370,9 @@ export class DeviceModeToolbar {
375
370
 
376
371
  // Show posture toolbar menu for foldable devices.
377
372
  mainToolbar.appendToolbarItem(new UI.Toolbar.ToolbarItem(this.createEmptyToolbarElement()));
378
- this.postureItem = new UI.Toolbar.ToolbarMenuButton(
379
- this.appendDevicePostureItems.bind(this), undefined, undefined, 'device-posture');
373
+ this.postureItem = new UI.Toolbar.ToolbarComboBox(
374
+ this.onPostureChange.bind(this), i18nString(UIStrings.devicePosture), 'dark-text', 'device-posture');
380
375
  this.postureItem.turnShrinkable();
381
- this.postureItem.setDarkText();
382
- setTitleForButton(this.postureItem, i18nString(UIStrings.devicePosture));
383
376
  mainToolbar.appendToolbarItem(this.postureItem);
384
377
 
385
378
  return mainToolbar;
@@ -401,11 +394,22 @@ export class DeviceModeToolbar {
401
394
  return ['Continuous', 'Folded'].map(title => ({title, value: title, selected: currentPosture === title}));
402
395
  }
403
396
 
404
- private appendDevicePostureItems(contextMenu: UI.ContextMenu.ContextMenu): void {
397
+ private updateDevicePostureItems(): void {
398
+ this.postureItem.removeOptions();
405
399
  for (const option of this.getDevicePostureOptions()) {
406
- contextMenu.defaultSection().appendCheckboxItem(
407
- option.title, this.spanClicked.bind(this),
408
- {checked: option.selected, jslogContext: option.title.toLowerCase()});
400
+ this.postureItem.createOption(option.title, option.value, option.title.toLowerCase());
401
+ }
402
+ const currentPosture = this.currentDevicePosture();
403
+ if (this.postureItem.element.value !== currentPosture) {
404
+ this.postureItem.element.value = currentPosture;
405
+ }
406
+ this.resizeItem(this.postureItem);
407
+ }
408
+
409
+ private onPostureChange(): void {
410
+ const value = this.postureItem.element.value;
411
+ if (value !== this.currentDevicePosture()) {
412
+ this.spanClicked();
409
413
  }
410
414
  }
411
415
 
@@ -451,15 +455,21 @@ export class DeviceModeToolbar {
451
455
  });
452
456
  }
453
457
 
454
- private appendScaleMenuItems(contextMenu: UI.ContextMenu.ContextMenu): void {
455
- for (const option of this.getScaleOptions()) {
456
- contextMenu.defaultSection().appendCheckboxItem(
457
- option.title, this.onScaleMenuChanged.bind(this, option.value),
458
- {checked: option.selected, jslogContext: option.jslogContext});
458
+ private updateScaleMenuItems(): void {
459
+ this.scaleItem.removeOptions();
460
+ const options = this.getScaleOptions();
461
+ for (const option of options) {
462
+ this.scaleItem.createOption(option.title, String(option.value), option.jslogContext);
463
+ }
464
+ const selected = options.find(o => o.selected);
465
+ if (selected) {
466
+ this.scaleItem.element.value = String(selected.value);
459
467
  }
468
+ this.resizeItem(this.scaleItem, true);
460
469
  }
461
470
 
462
- private onScaleMenuChanged(value: number): void {
471
+ private onScaleChange(): void {
472
+ const value = Number(this.scaleItem.selectedOption()?.value);
463
473
  this.model.scaleSetting().set(value);
464
474
  }
465
475
 
@@ -493,13 +503,22 @@ export class DeviceModeToolbar {
493
503
  });
494
504
  }
495
505
 
496
- private appendDeviceScaleMenuItems(contextMenu: UI.ContextMenu.ContextMenu): void {
497
- const deviceScaleFactorSetting = this.model.deviceScaleFactorSetting();
498
- for (const option of this.getDeviceScaleFactorOptions()) {
499
- contextMenu.defaultSection().appendCheckboxItem(
500
- option.title, deviceScaleFactorSetting.set.bind(deviceScaleFactorSetting, option.value),
501
- {checked: option.selected, jslogContext: option.jslogContext});
506
+ private updateDeviceScaleMenuItems(): void {
507
+ this.deviceScaleItem.removeOptions();
508
+ const options = this.getDeviceScaleFactorOptions();
509
+ for (const option of options) {
510
+ this.deviceScaleItem.createOption(option.title, String(option.value), option.jslogContext);
502
511
  }
512
+ const selected = options.find(o => o.selected);
513
+ if (selected) {
514
+ this.deviceScaleItem.element.value = String(selected.value);
515
+ }
516
+ this.resizeItem(this.deviceScaleItem, true);
517
+ }
518
+
519
+ private onDeviceScaleChange(): void {
520
+ const value = Number(this.deviceScaleItem.selectedOption()?.value);
521
+ this.model.deviceScaleFactorSetting().set(value);
503
522
  }
504
523
 
505
524
  private getUserAgentOptions():
@@ -519,13 +538,23 @@ export class DeviceModeToolbar {
519
538
  }));
520
539
  }
521
540
 
522
- private appendUserAgentMenuItems(contextMenu: UI.ContextMenu.ContextMenu): void {
523
- const uaSetting = this.model.uaSetting();
524
- for (const option of this.getUserAgentOptions()) {
525
- contextMenu.defaultSection().appendCheckboxItem(
526
- option.title, uaSetting.set.bind(uaSetting, option.value),
527
- {checked: option.selected, jslogContext: option.jslogContext});
541
+ private updateUserAgentMenuItems(): void {
542
+ this.uaItem.removeOptions();
543
+ const options = this.getUserAgentOptions();
544
+ for (const option of options) {
545
+ this.uaItem.createOption(option.title, option.value, option.jslogContext);
528
546
  }
547
+ const selected = options.find(o => o.selected);
548
+ if (selected) {
549
+ this.uaItem.element.value = selected.value;
550
+ }
551
+ this.resizeItem(this.uaItem);
552
+ }
553
+
554
+ private onUAChange(): void {
555
+ const value = this.uaItem.selectedOption()?.value as EmulationModel.DeviceModeModel.UA;
556
+ this.model.uaSetting().set(value);
557
+ this.resizeItem(this.uaItem);
529
558
  }
530
559
 
531
560
  private appendOptionsMenuItems(contextMenu: UI.ContextMenu.ContextMenu): void {
@@ -650,40 +679,66 @@ export class DeviceModeToolbar {
650
679
  };
651
680
  }
652
681
 
653
- private appendDeviceMenuItems(contextMenu: UI.ContextMenu.ContextMenu): void {
682
+ private updateDeviceMenuItems(): void {
683
+ this.deviceSelectItem.removeOptions();
654
684
  const options = this.getDeviceModeOptions();
655
685
 
656
- contextMenu.headerSection().appendCheckboxItem(
657
- options.responsive.title, this.switchToResponsive.bind(this),
658
- {checked: options.responsive.selected, jslogContext: options.responsive.jslogContext});
686
+ this.deviceSelectItem.createOption(options.responsive.title, 'Responsive', options.responsive.jslogContext);
659
687
 
660
- appendGroup.call(this, options.standard);
661
- appendGroup.call(this, options.custom);
688
+ appendGroup.call(this, options.standard, 'Standard');
689
+ appendGroup.call(this, options.custom, 'Custom');
662
690
 
663
- contextMenu.footerSection().appendItem(
664
- options.edit.title, this.emulatedDevicesList.revealCustomSetting.bind(this.emulatedDevicesList),
665
- {jslogContext: options.edit.jslogContext});
666
-
667
- function appendGroup(this: DeviceModeToolbar, group: Array<{
668
- device: EmulationModel.EmulatedDevices.EmulatedDevice,
669
- title: string,
670
- selected: boolean,
671
- jslogContext: string,
672
- }>): void {
691
+ this.deviceSelectItem.createOption(options.edit.title, 'Edit', options.edit.jslogContext);
692
+
693
+ this.updateDeviceSelection();
694
+
695
+ function appendGroup(
696
+ this: DeviceModeToolbar, group: Array<{title: string, jslogContext: string}>, label: string): void {
673
697
  if (!group.length) {
674
698
  return;
675
699
  }
676
- const section = contextMenu.section();
700
+ const optGroup = document.createElement('optgroup');
701
+ optGroup.label = label;
677
702
  for (const item of group) {
678
- section.appendCheckboxItem(item.title, this.emulateDevice.bind(this, item.device), {
679
- checked: item.selected,
680
- jslogContext: item.jslogContext,
681
- });
703
+ const option = document.createElement('option');
704
+ option.value = item.title;
705
+ option.text = item.title;
706
+ option.setAttribute('jslog', `${VisualLogging.item(item.jslogContext).track({click: true})}`);
707
+ optGroup.appendChild(option);
708
+ }
709
+ this.deviceSelectItem.addOption(optGroup);
710
+ }
711
+ }
712
+
713
+ private updateDeviceSelection(): void {
714
+ const device = this.model.device();
715
+ if (this.model.type() === EmulationModel.DeviceModeModel.Type.Responsive) {
716
+ this.deviceSelectItem.element.value = 'Responsive';
717
+ } else if (device) {
718
+ this.deviceSelectItem.element.value = device.title;
719
+ }
720
+ this.resizeItem(this.deviceSelectItem);
721
+ }
722
+
723
+ private onDeviceChange(): void {
724
+ const value = this.deviceSelectItem.selectedOption()?.value;
725
+ if (value === 'Edit') {
726
+ this.emulatedDevicesList.revealCustomSetting();
727
+ this.updateDeviceSelection();
728
+ } else if (value === 'Responsive') {
729
+ this.switchToResponsive();
730
+ } else {
731
+ for (const device of this.allDevices()) {
732
+ if (device.title === value) {
733
+ this.emulateDevice(device);
734
+ break;
735
+ }
682
736
  }
683
737
  }
684
738
  }
685
739
 
686
740
  private deviceListChanged(): void {
741
+ this.updateDeviceMenuItems();
687
742
  const device = this.model.device();
688
743
  if (!device) {
689
744
  return;
@@ -709,7 +764,11 @@ export class DeviceModeToolbar {
709
764
 
710
765
  private updateUserAgentTypeVisibility(): void {
711
766
  if (this.uaItem) {
712
- this.uaItem.setVisible(this.showUserAgentTypeSetting.get());
767
+ const visible = this.showUserAgentTypeSetting.get();
768
+ this.uaItem.setVisible(visible);
769
+ if (visible) {
770
+ this.resizeItem(this.uaItem);
771
+ }
713
772
  }
714
773
  }
715
774
 
@@ -854,31 +913,27 @@ export class DeviceModeToolbar {
854
913
  this.heightInput.placeholder = String(size.height);
855
914
 
856
915
  if (this.model.scale() !== this.cachedScale) {
857
- this.scaleItem.setText(`${this.getPrettyZoomPercentage()}%`);
916
+ this.updateScaleMenuItems();
858
917
  this.cachedScale = this.model.scale();
859
918
  }
860
919
 
861
920
  const deviceScale = this.model.appliedDeviceScaleFactor();
862
921
  if (deviceScale !== this.cachedDeviceScale) {
863
- this.deviceScaleItem.setText(`${deviceScale.toFixed(1)}`);
922
+ this.deviceScaleItem.element.value = String(deviceScale);
864
923
  this.cachedDeviceScale = deviceScale;
924
+ this.resizeItem(this.deviceScaleItem, true);
865
925
  }
866
926
 
867
927
  const uaType = this.model.appliedUserAgentType();
868
928
  if (uaType !== this.cachedUaType) {
869
- this.uaItem.setText(uaType);
929
+ this.uaItem.element.value = uaType;
870
930
  this.cachedUaType = uaType;
931
+ this.updateDeviceScaleMenuItems();
932
+ this.resizeItem(this.uaItem);
871
933
  }
872
934
 
873
- let deviceItemTitle: string = i18nString(UIStrings.none);
874
- if (this.model.type() === EmulationModel.DeviceModeModel.Type.Responsive) {
875
- deviceItemTitle = i18nString(UIStrings.responsive);
876
- }
877
935
  const device = this.model.device();
878
- if (this.model.type() === EmulationModel.DeviceModeModel.Type.Device && device) {
879
- deviceItemTitle = device.title;
880
- }
881
- this.deviceSelectItem.setText(deviceItemTitle);
936
+ this.updateDeviceSelection();
882
937
 
883
938
  if (this.model.device() !== this.cachedModelDevice) {
884
939
  const device = this.model.device();
@@ -898,7 +953,7 @@ export class DeviceModeToolbar {
898
953
  } else if (device?.isFoldableScreen) {
899
954
  this.spanButton.setVisible(false);
900
955
  this.postureItem.setVisible(true);
901
- this.postureItem.setText(this.currentDevicePosture());
956
+ this.updateDevicePostureItems();
902
957
  } else {
903
958
  this.spanButton.setVisible(false);
904
959
  this.postureItem.setVisible(false);
@@ -962,4 +1017,31 @@ export class DeviceModeToolbar {
962
1017
 
963
1018
  this.model.emulate(EmulationModel.DeviceModeModel.Type.Responsive, null, null);
964
1019
  }
1020
+
1021
+ private resizeItem(item: UI.Toolbar.ToolbarComboBox, stripParens = false): void {
1022
+ const selectedOption = item.selectedOption();
1023
+ if (!selectedOption) {
1024
+ return;
1025
+ }
1026
+
1027
+ const dummySelect = item.element.cloneNode(false) as HTMLSelectElement;
1028
+ const dummyOption = selectedOption.cloneNode(true) as HTMLOptionElement;
1029
+
1030
+ if (stripParens) {
1031
+ const parensIndex = dummyOption.text.indexOf(' (');
1032
+ if (parensIndex >= 0) {
1033
+ dummyOption.text = dummyOption.text.substring(0, parensIndex);
1034
+ }
1035
+ }
1036
+
1037
+ dummySelect.appendChild(dummyOption);
1038
+ dummySelect.style.width = 'fit-content';
1039
+ dummySelect.style.position = 'absolute';
1040
+ dummySelect.style.visibility = 'hidden';
1041
+ dummySelect.style.pointerEvents = 'none';
1042
+
1043
+ item.element.parentElement?.appendChild(dummySelect);
1044
+ item.element.style.width = dummySelect.offsetWidth + 'px';
1045
+ dummySelect.remove();
1046
+ }
965
1047
  }
@@ -117,7 +117,7 @@
117
117
  margin-top: 8px;
118
118
  padding-top: 8px;
119
119
  border-top: 1px solid #eee;
120
- font-family: monospace;
120
+ font-family: var(--monospace-font-family);
121
121
  }
122
122
 
123
123
  .thought, .action {
@@ -169,7 +169,7 @@
169
169
 
170
170
  .green-dev-floaty-dialog-node-description {
171
171
  font-size: 11px;
172
- font-family: monospace;
172
+ font-family: var(--monospace-font-family);
173
173
  color: #0b57d0;
174
174
  background-color: #d3e3fd;
175
175
  padding: 2px 8px;
@@ -5,7 +5,7 @@
5
5
  import * as i18n from '../../core/i18n/i18n.js';
6
6
  import type * as Platform from '../../core/platform/platform.js';
7
7
  import type * as IssuesManager from '../../models/issues_manager/issues_manager.js';
8
- import * as UI from '../../ui/legacy/legacy.js';
8
+ import {html, render} from '../../ui/lit/lit.js';
9
9
 
10
10
  import {AffectedElementsView} from './AffectedElementsView.js';
11
11
 
@@ -31,16 +31,19 @@ export class AffectedPermissionElementsView extends AffectedElementsView {
31
31
  async #appendAffectedElements(issues: Iterable<IssuesManager.PermissionElementIssue.PermissionElementIssue>):
32
32
  Promise<void> {
33
33
  let count = 0;
34
+ // clang-format off
35
+ const templates = [];
34
36
  for (const issue of issues) {
35
37
  for (const element of issue.elements()) {
36
- const rowElement = UI.Fragment.html`
37
- <tr>
38
- ${await this.createElementCell(element, this.issue.getCategory())}
39
- </tr>`;
40
- this.affectedResources.appendChild(rowElement);
41
38
  count++;
39
+ templates.push(html`<tr>
40
+ ${await this.createElementCell(element, this.issue.getCategory())}
41
+ </tr>`);
42
42
  }
43
43
  }
44
+ // clang-format on
45
+ // eslint-disable-next-line @devtools/no-lit-render-outside-of-view
46
+ render(html`${templates}`, this.affectedResources);
44
47
  this.updateAffectedResourceCount(count);
45
48
  }
46
49
  }
@@ -12,6 +12,7 @@ import * as EmulationModel from '../../models/emulation/emulation.js';
12
12
  import type * as LighthouseModel from '../../models/lighthouse/lighthouse.js';
13
13
  import * as Emulation from '../emulation/emulation.js';
14
14
 
15
+ import type {RunOverrides} from './LighthousePanel.js';
15
16
  import type {LighthouseRun as LighthouseRunType, ProtocolService} from './LighthouseProtocolService.js';
16
17
 
17
18
  const UIStrings = {
@@ -567,9 +568,9 @@ export class LighthouseController extends Common.ObjectWrapper.ObjectWrapper<Eve
567
568
  };
568
569
  }
569
570
 
570
- getCategoryIDs(): string[] {
571
+ getCategoryIDs(): CategoryId[] {
571
572
  const {mode} = this.getFlags();
572
- const categoryIDs = [];
573
+ const categoryIDs: CategoryId[] = [];
573
574
  for (const preset of Presets) {
574
575
  if (mode && !preset.supportedModes.includes(mode)) {
575
576
  continue;
@@ -639,7 +640,12 @@ export class LighthouseController extends Common.ObjectWrapper.ObjectWrapper<Eve
639
640
  }
640
641
  }
641
642
 
642
- async startLighthouse(): Promise<void> {
643
+ /**
644
+ * Starts a LH run. By default it will use the categories based on what the
645
+ * user has selected in the UI, but these can be overridden by passing in the
646
+ * category IDs, in which case these take priority.
647
+ */
648
+ async startLighthouse(overrides?: RunOverrides): Promise<void> {
643
649
  if (this.lastAction) {
644
650
  await this.lastAction;
645
651
  }
@@ -651,7 +657,7 @@ export class LighthouseController extends Common.ObjectWrapper.ObjectWrapper<Eve
651
657
  }
652
658
 
653
659
  const inspectedURL = await this.getInspectedURL({force: true});
654
- const categoryIDs = this.getCategoryIDs();
660
+ const categoryIDs = overrides?.categoryIds ?? this.getCategoryIDs();
655
661
  const flags = this.getFlags();
656
662
 
657
663
  this.recordMetrics(flags, categoryIDs);
@@ -861,9 +867,11 @@ export interface EventTypes {
861
867
  [Events.AuditProgressChanged]: AuditProgressChangedEvent;
862
868
  }
863
869
 
870
+ export type CategoryId = 'performance'|'accessibility'|'best-practices'|'seo';
871
+
864
872
  export interface Preset {
865
873
  setting: Common.Settings.Setting<boolean>;
866
- configID: string;
874
+ configID: CategoryId;
867
875
  title: () => Common.UIString.LocalizedString;
868
876
  description: () => Common.UIString.LocalizedString;
869
877
  supportedModes: string[];
@@ -13,10 +13,11 @@ import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
13
13
 
14
14
  import {
15
15
  type AuditProgressChangedEvent,
16
+ type CategoryId,
16
17
  Events,
17
18
  LighthouseController,
18
19
  type PageAuditabilityChangedEvent,
19
- type PageWarningsChangedEvent,
20
+ type PageWarningsChangedEvent
20
21
  } from './LighthouseController.js';
21
22
  import lighthousePanelStyles from './lighthousePanel.css.js';
22
23
  import {ProtocolService} from './LighthouseProtocolService.js';
@@ -70,7 +71,6 @@ export class LighthousePanel extends UI.Panel.Panel {
70
71
  private warningText: Nullable<string>;
71
72
  private unauditableExplanation: Nullable<string>;
72
73
  private readonly cachedRenderedReports: Map<LighthouseModel.ReporterTypes.ReportJSON, HTMLElement>;
73
- private readonly dropTarget: UI.DropTarget.DropTarget;
74
74
  private readonly auditResultsElement: HTMLElement;
75
75
  private clearButton!: UI.Toolbar.ToolbarButton;
76
76
  private newButton!: UI.Toolbar.ToolbarButton;
@@ -94,7 +94,7 @@ export class LighthousePanel extends UI.Panel.Panel {
94
94
  this.unauditableExplanation = null;
95
95
  this.cachedRenderedReports = new Map();
96
96
 
97
- this.dropTarget = new UI.DropTarget.DropTarget(
97
+ new UI.DropTarget.DropTarget(
98
98
  this.contentElement, [UI.DropTarget.Type.File], i18nString(UIStrings.dropLighthouseJsonHere),
99
99
  this.handleDrop.bind(this));
100
100
 
@@ -147,14 +147,17 @@ export class LighthousePanel extends UI.Panel.Panel {
147
147
  }
148
148
  }
149
149
 
150
- async handleCompleteRun(): Promise<void> {
150
+ async handleCompleteRun(overrides?: RunOverrides):
151
+ Promise<{report: LighthouseModel.ReporterTypes.ReportJSON | null}> {
151
152
  try {
152
- await this.controller.startLighthouse();
153
+ await this.controller.startLighthouse(overrides);
153
154
  this.renderStatusView();
154
155
  const {lhr, artifacts} = await this.controller.collectLighthouseResults();
155
156
  this.buildReportUI(lhr, artifacts);
157
+ return {report: lhr};
156
158
  } catch (err) {
157
159
  this.handleError(err);
160
+ return {report: null};
158
161
  }
159
162
  }
160
163
 
@@ -379,4 +382,18 @@ export class LighthousePanel extends UI.Panel.Panel {
379
382
  event.handled = true;
380
383
  }
381
384
  }
385
+
386
+ static async executeLighthouseRecording(
387
+ overrides?: RunOverrides,
388
+ ): Promise<LighthouseModel.ReporterTypes.ReportJSON|null> {
389
+ const panel = LighthousePanel.instance();
390
+ await UI.ViewManager.ViewManager.instance().showView('lighthouse');
391
+
392
+ const {report} = await panel.handleCompleteRun(overrides);
393
+ return report;
394
+ }
395
+ }
396
+
397
+ export interface RunOverrides {
398
+ categoryIds: CategoryId[];
382
399
  }
@@ -906,8 +906,15 @@ export class HeapSnapshotObjectNode extends HeapSnapshotGenericObjectNode {
906
906
  if (this.cycledWithAncestorGridNode) {
907
907
  div.classList.add('cycled-ancestor-node');
908
908
  }
909
- div.prepend(UI.Fragment.html`<span class="property-name ${nameClass}">${name}</span>
910
- <span class="grayed">${this.edgeNodeSeparator()}</span>`);
909
+ const property = document.createElement('span');
910
+ property.classList.add('property-name', nameClass);
911
+ property.textContent = name;
912
+
913
+ const separator = document.createElement('span');
914
+ separator.classList.add('grayed');
915
+ separator.textContent = this.edgeNodeSeparator();
916
+
917
+ div.prepend(property, separator);
911
918
  }
912
919
 
913
920
  edgeNodeSeparator(): string {
@@ -1170,7 +1177,8 @@ export class HeapSnapshotConstructorNode extends HeapSnapshotGridNode {
1170
1177
  override createCell(columnId: string): HTMLElement {
1171
1178
  const cell = columnId === 'object' ? super.createCell(columnId) : this.createValueCell(columnId);
1172
1179
  if (columnId === 'object' && this.count > 1) {
1173
- cell.appendChild(UI.Fragment.html`<span class="objects-count">×${this.data.count}</span>`);
1180
+ const template = html`<span class="objects-count">×${this.data.count}</span>`;
1181
+ render(template, cell);
1174
1182
  }
1175
1183
  return cell;
1176
1184
  }
@@ -362,7 +362,6 @@ function renderStepActions(input: ViewInput): Lit.TemplateResult|null {
362
362
  }}
363
363
  jslog=${VisualLogging.dropDown('step-actions').track({click: true})}
364
364
  .iconName=${'dots-vertical'}
365
- }
366
365
  ></devtools-menu-button>
367
366
  `;
368
367
  // clang-format on
@@ -459,7 +458,6 @@ export const DEFAULT_VIEW = (input: ViewInput, _output: ViewOutput, target: HTML
459
458
  <div class="subtitle" title=${subtitle}>${subtitle}</div>
460
459
  </div>
461
460
  </div>
462
- <div class="filler"></div>
463
461
  ${renderStepActions(input)}
464
462
  </div>
465
463
  <div class="details">