chrome-devtools-frontend 1.0.1595925 → 1.0.1596260

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/.stylelintrc.json +3 -1
  2. package/docs/ui_engineering.md +0 -36
  3. package/front_end/core/host/UserMetrics.ts +0 -1
  4. package/front_end/core/root/ExperimentNames.ts +0 -1
  5. package/front_end/core/sdk/DOMModel.ts +206 -0
  6. package/front_end/core/sdk/SourceMapScopesInfo.ts +1 -1
  7. package/front_end/entrypoints/greendev_floaty/floaty.css +2 -2
  8. package/front_end/entrypoints/main/MainImpl.ts +0 -5
  9. package/front_end/models/ai_assistance/ConversationHandler.ts +1 -1
  10. package/front_end/models/ai_code_completion/AiCodeCompletion.ts +1 -1
  11. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +2 -0
  12. package/front_end/panels/ai_assistance/components/ChatMessage.ts +48 -31
  13. package/front_end/panels/ai_assistance/components/WalkthroughView.ts +28 -4
  14. package/front_end/panels/ai_assistance/components/chatMessage.css +4 -2
  15. package/front_end/panels/application/IndexedDBModel.ts +2 -4
  16. package/front_end/panels/application/ServiceWorkersView.ts +3 -11
  17. package/front_end/panels/developer_resources/DeveloperResourcesListView.ts +1 -1
  18. package/front_end/panels/elements/ComputedStyleWidget.ts +25 -16
  19. package/front_end/panels/elements/ElementsPanel.ts +0 -1
  20. package/front_end/panels/elements/StylePropertyTreeElement.ts +11 -5
  21. package/front_end/panels/elements/nodeStackTraceWidget.css +1 -1
  22. package/front_end/panels/emulation/DeviceModeToolbar.ts +171 -89
  23. package/front_end/panels/greendev/GreenDevPanel.css +2 -2
  24. package/front_end/panels/issues/AffectedPermissionElementsView.ts +9 -6
  25. package/front_end/panels/profiler/HeapSnapshotGridNodes.ts +11 -3
  26. package/front_end/panels/settings/components/SyncSection.ts +4 -13
  27. package/front_end/panels/timeline/CompatibilityTracksAppender.ts +2 -15
  28. package/front_end/panels/timeline/RecordingMetadata.ts +1 -1
  29. package/front_end/panels/timeline/TimelineDetailsView.ts +2 -2
  30. package/front_end/panels/timeline/TimelineUIUtils.ts +7 -4
  31. package/front_end/panels/timeline/components/Utils.ts +2 -2
  32. package/front_end/panels/webauthn/webauthnPane.css +1 -1
  33. package/front_end/ui/components/tree_outline/TreeOutline.ts +1 -1
  34. package/front_end/ui/legacy/ProgressIndicator.ts +1 -1
  35. package/front_end/ui/legacy/Toolbar.ts +1 -1
  36. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +14 -7
  37. package/front_end/ui/legacy/legacy.ts +0 -2
  38. package/front_end/ui/visual_logging/KnownContextValues.ts +4 -0
  39. package/inspector_overlay/loadCSS.rollup.js +2 -2
  40. package/inspector_overlay/tool_source_order.css +1 -0
  41. package/package.json +1 -1
  42. package/front_end/ui/legacy/Fragment.ts +0 -234
@@ -10,6 +10,7 @@ import * as SDK from '../../core/sdk/sdk.js';
10
10
  import type * as Protocol from '../../generated/protocol.js';
11
11
  import * as NetworkForward from '../../panels/network/forward/forward.js';
12
12
  import * as Buttons from '../../ui/components/buttons/buttons.js';
13
+ import {Link} from '../../ui/kit/kit.js';
13
14
  import * as Components from '../../ui/legacy/components/utils/utils.js';
14
15
  import * as UI from '../../ui/legacy/legacy.js';
15
16
  import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
@@ -227,17 +228,8 @@ export class ServiceWorkersView extends UI.Widget.VBox implements
227
228
  othersView.show(othersDiv);
228
229
  const othersSection = othersView.appendSection(i18nString(UIStrings.serviceWorkersFromOtherOrigins));
229
230
  const othersSectionRow = othersSection.appendRow();
230
- const seeOthers =
231
- UI.Fragment
232
- .html`<a class="devtools-link" role="link" tabindex="0" href="chrome://serviceworker-internals" target="_blank" style="display: inline; cursor: pointer;">${
233
- i18nString(UIStrings.seeAllRegistrations)}</a>`;
234
- seeOthers.setAttribute('jslog', `${VisualLogging.link('view-all').track({click: true})}`);
235
- self.onInvokeElement(seeOthers, event => {
236
- const rootTarget = SDK.TargetManager.TargetManager.instance().rootTarget();
237
- rootTarget &&
238
- void rootTarget.targetAgent().invoke_createTarget({url: 'chrome://serviceworker-internals?devtools'});
239
- event.consume(true);
240
- });
231
+ const seeOthers = Link.create(
232
+ 'chrome://serviceworker-internals', i18nString(UIStrings.seeAllRegistrations), undefined, 'view-all');
241
233
  othersSectionRow.appendChild(seeOthers);
242
234
 
243
235
  this.toolbar.appendToolbarItem(
@@ -104,7 +104,7 @@ const DEFAULT_VIEW: View = (input, _output, target) => {
104
104
  if (!filter?.regex) {
105
105
  return '';
106
106
  }
107
- const matches = filter.regex.exec(textContent ?? '');
107
+ const matches = filter.regex.exec(textContent);
108
108
  if (!matches?.length) {
109
109
  return '';
110
110
  }
@@ -294,7 +294,14 @@ export const DEFAULT_VIEW: View = (input, _output, target) => {
294
294
  };
295
295
 
296
296
  export class ComputedStyleWidget extends UI.Widget.VBox {
297
- #computedStyleModel?: ComputedStyleModule.ComputedStyleModel.ComputedStyleModel;
297
+ /**
298
+ * We store these because they are used when calculating the dimensions for the image preview.
299
+ * When we need to get those dimensions, we try to resolve them against the
300
+ * node fresh (in case the dimensions have changed), but if that doesn't work,
301
+ * we fallback to the precomputed ones, which helps to deal with situations
302
+ * where the node might have been removed from the DOM.
303
+ */
304
+ #storedNodeFeatures: Components.ImagePreview.PrecomputedFeatures|null = null;
298
305
  #nodeStyle: ComputedStyleModule.ComputedStyleModel.ComputedStyle|null = null;
299
306
  #matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles|null = null;
300
307
  #propertyTraces: Map<string, SDK.CSSProperty.CSSProperty[]>|null = null;
@@ -363,7 +370,8 @@ export class ComputedStyleWidget extends UI.Widget.VBox {
363
370
  return null;
364
371
  },
365
372
  async () => {
366
- return await Components.ImagePreview.loadPrecomputedFeatures(this.#computedStyleModel?.node);
373
+ const liveFeatures = await Components.ImagePreview.loadPrecomputedFeatures(this.#nodeStyle?.node);
374
+ return liveFeatures ?? this.#storedNodeFeatures ?? undefined;
367
375
  });
368
376
 
369
377
  this.#updateView({hasMatches: true});
@@ -430,7 +438,13 @@ export class ComputedStyleWidget extends UI.Widget.VBox {
430
438
 
431
439
  set nodeStyle(nodeStyle: ComputedStyleModule.ComputedStyleModel.ComputedStyle|null) {
432
440
  this.#nodeStyle = nodeStyle;
433
- this.requestUpdate();
441
+ if (nodeStyle) {
442
+ // Make sure we get the node features before we request an update so we
443
+ // don't run the update before we have the features fetched.
444
+ void this.#storeNodeFeatures(nodeStyle.node).then(() => this.requestUpdate());
445
+ } else {
446
+ this.requestUpdate();
447
+ }
434
448
  }
435
449
 
436
450
  get matchedStyles(): SDK.CSSMatchedStyles.CSSMatchedStyles|null {
@@ -447,13 +461,13 @@ export class ComputedStyleWidget extends UI.Widget.VBox {
447
461
  this.requestUpdate();
448
462
  }
449
463
 
450
- get computedStyleModel(): ComputedStyleModule.ComputedStyleModel.ComputedStyleModel|undefined {
451
- return this.#computedStyleModel;
452
- }
453
-
454
- set computedStyleModel(computedStyleModel: ComputedStyleModule.ComputedStyleModel.ComputedStyleModel) {
455
- this.#computedStyleModel = computedStyleModel;
456
- this.requestUpdate();
464
+ async #storeNodeFeatures(node: SDK.DOMModel.DOMNode|null): Promise<void> {
465
+ if (node) {
466
+ const features = await Components.ImagePreview.loadPrecomputedFeatures(node);
467
+ this.#storedNodeFeatures = features ?? null;
468
+ } else {
469
+ this.#storedNodeFeatures = null;
470
+ }
457
471
  }
458
472
 
459
473
  #shouldGroupStyles(): boolean {
@@ -483,10 +497,6 @@ export class ComputedStyleWidget extends UI.Widget.VBox {
483
497
  matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles): Promise<void> {
484
498
  this.imagePreviewPopover.hide();
485
499
  this.linkifier.reset();
486
- const cssModel = this.#computedStyleModel?.cssModel();
487
- if (!cssModel) {
488
- return;
489
- }
490
500
 
491
501
  const uniqueProperties = [...nodeStyle.computedStyle.keys()];
492
502
  uniqueProperties.sort(propertySorter);
@@ -526,8 +536,7 @@ export class ComputedStyleWidget extends UI.Widget.VBox {
526
536
  matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles|null): Promise<void> {
527
537
  this.imagePreviewPopover.hide();
528
538
  this.linkifier.reset();
529
- const cssModel = this.#computedStyleModel?.cssModel();
530
- if (!nodeStyle || !matchedStyles || !cssModel) {
539
+ if (!nodeStyle || !matchedStyles) {
531
540
  this.#updateView({hasMatches: false});
532
541
  return;
533
542
  }
@@ -316,7 +316,6 @@ export class ElementsPanel extends UI.Panel.Panel implements UI.SearchableView.S
316
316
 
317
317
  this.stylesWidget = new StylesSidebarPane(this.#computedStyleModel);
318
318
  this.#computedStyleWidget = new ComputedStyleWidget();
319
- this.#computedStyleWidget.computedStyleModel = this.#computedStyleModel;
320
319
  this.#computedStyleModel.addEventListener(
321
320
  ComputedStyle.ComputedStyleModel.Events.COMPUTED_STYLE_CHANGED, this.#updateComputedStyles, this);
322
321
  this.#computedStyleModel.addEventListener(
@@ -1575,11 +1575,17 @@ export class GridTemplateRenderer extends rendererBase(SDK.CSSPropertyParserMatc
1575
1575
 
1576
1576
  const indent = Common.Settings.Settings.instance().moduleSetting('text-editor-indent').get();
1577
1577
  const container = document.createDocumentFragment();
1578
- for (const line of match.lines) {
1579
- const value = Renderer.render(line, context);
1580
- const lineBreak = UI.Fragment.html`<br /><span class='styles-clipboard-only'>${indent.repeat(2)}</span>`;
1581
- container.append(lineBreak, ...value.nodes);
1582
- }
1578
+
1579
+ const template = html`
1580
+ ${match.lines.map(line => {
1581
+ const lines = Renderer.render(line, context).nodes;
1582
+ return html`
1583
+ <span class='styles-clipboard-only'>${indent.repeat(2)}</span>
1584
+ ${lines}`;
1585
+ })}
1586
+ `;
1587
+
1588
+ render(template, container);
1583
1589
  return [container];
1584
1590
  }
1585
1591
  }
@@ -6,6 +6,6 @@
6
6
  @scope to (devtools-widget > *) {
7
7
  .stack-trace {
8
8
  font-size: 11px !important; /* stylelint-disable-line declaration-no-important */
9
- font-family: Menlo, monospace;
9
+ font-family: Menlo, var(--monospace-font-family);
10
10
  }
11
11
  }
@@ -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
  }