chrome-devtools-frontend 1.0.1593518 → 1.0.1595090

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 (111) hide show
  1. package/docs/contributing/settings-experiments-features.md +93 -98
  2. package/front_end/core/common/README.md +126 -0
  3. package/front_end/core/host/UserMetrics.ts +1 -2
  4. package/front_end/core/protocol_client/InspectorBackend.ts +4 -0
  5. package/front_end/core/root/ExperimentNames.ts +0 -1
  6. package/front_end/core/sdk/CSSModel.ts +22 -0
  7. package/front_end/core/sdk/CSSNavigation.ts +33 -0
  8. package/front_end/core/sdk/CSSRule.ts +12 -2
  9. package/front_end/core/sdk/EmulationModel.ts +41 -1
  10. package/front_end/core/sdk/sdk-meta.ts +22 -18
  11. package/front_end/core/sdk/sdk.ts +2 -0
  12. package/front_end/design_system_tokens.css +538 -259
  13. package/front_end/entrypoints/main/MainImpl.ts +0 -6
  14. package/front_end/entrypoints/main/main-meta.ts +2 -2
  15. package/front_end/generated/InspectorBackendCommands.ts +6 -4
  16. package/front_end/generated/SupportedCSSProperties.js +12 -0
  17. package/front_end/generated/protocol-mapping.d.ts +7 -0
  18. package/front_end/generated/protocol-proxy-api.d.ts +5 -0
  19. package/front_end/generated/protocol.ts +47 -0
  20. package/front_end/models/ai_code_completion/AiCodeCompletion.ts +1 -0
  21. package/front_end/models/emulation/DeviceModeModel.ts +47 -0
  22. package/front_end/models/issues_manager/Issue.ts +1 -0
  23. package/front_end/models/issues_manager/IssueAggregator.ts +9 -9
  24. package/front_end/models/issues_manager/IssuesManager.ts +5 -5
  25. package/front_end/models/issues_manager/SelectivePermissionsInterventionIssue.ts +65 -0
  26. package/front_end/models/issues_manager/descriptions/selectivePermissionsIntervention.md +7 -0
  27. package/front_end/models/issues_manager/issues_manager.ts +2 -4
  28. package/front_end/models/javascript_metadata/NativeFunctions.js +48 -9
  29. package/front_end/models/lighthouse/lighthouse.ts +9 -0
  30. package/front_end/models/persistence/persistence-meta.ts +4 -4
  31. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +12 -1
  32. package/front_end/panels/ai_assistance/components/ChatInput.ts +37 -30
  33. package/front_end/panels/ai_assistance/components/ChatView.ts +4 -2
  34. package/front_end/panels/ai_assistance/components/chatInput.css +26 -1
  35. package/front_end/panels/application/StorageView.ts +8 -2
  36. package/front_end/panels/application/preloading/PreloadingView.ts +105 -7
  37. package/front_end/panels/application/preloading/preloadingView.css +4 -0
  38. package/front_end/panels/common/AiCodeCompletionDisclaimer.ts +3 -0
  39. package/front_end/panels/common/AiCodeGenerationTeaser.ts +3 -0
  40. package/front_end/panels/console/ConsoleView.ts +2 -2
  41. package/front_end/panels/console/console-meta.ts +18 -14
  42. package/front_end/panels/elements/ComputedStyleWidget.ts +12 -7
  43. package/front_end/panels/elements/ElementsPanel.ts +1 -1
  44. package/front_end/panels/elements/ElementsTreeOutline.ts +4 -6
  45. package/front_end/panels/elements/ImagePreviewPopover.ts +6 -9
  46. package/front_end/panels/elements/StylePropertiesSection.ts +30 -0
  47. package/front_end/panels/elements/StylesAiCodeCompletionProvider.ts +1 -6
  48. package/front_end/panels/elements/StylesSidebarPane.ts +92 -7
  49. package/front_end/panels/elements/elements-meta.ts +12 -8
  50. package/front_end/panels/emulation/DeviceModeToolbar.ts +206 -66
  51. package/front_end/panels/issues/AffectedSelectivePermissionsInterventionView.ts +120 -0
  52. package/front_end/panels/issues/HiddenIssuesRow.ts +1 -1
  53. package/front_end/panels/issues/IssueKindView.ts +2 -1
  54. package/front_end/panels/issues/IssueView.ts +4 -4
  55. package/front_end/panels/issues/IssuesPane.ts +8 -2
  56. package/front_end/panels/issues/issues.ts +2 -0
  57. package/front_end/panels/lighthouse/LighthouseController.ts +3 -3
  58. package/front_end/panels/lighthouse/LighthousePanel.ts +9 -5
  59. package/front_end/panels/lighthouse/LighthouseProtocolService.ts +5 -5
  60. package/front_end/panels/lighthouse/LighthouseReportRenderer.ts +6 -7
  61. package/front_end/panels/lighthouse/LighthouseReportSelector.ts +4 -3
  62. package/front_end/panels/lighthouse/LighthouseStartView.ts +99 -38
  63. package/front_end/panels/lighthouse/LighthouseTimespanView.ts +53 -14
  64. package/front_end/panels/lighthouse/RadioSetting.ts +33 -19
  65. package/front_end/panels/lighthouse/lighthouse.ts +2 -2
  66. package/front_end/panels/media/PlayerMessagesView.ts +62 -49
  67. package/front_end/panels/media/media.ts +2 -0
  68. package/front_end/panels/media/playerMessagesView.css +1 -1
  69. package/front_end/panels/profiler/HeapSnapshotGridNodes.ts +49 -24
  70. package/front_end/panels/settings/FrameworkIgnoreListSettingsTab.ts +2 -2
  71. package/front_end/panels/sources/sources-meta.ts +8 -4
  72. package/front_end/panels/utils/utils.ts +11 -5
  73. package/front_end/third_party/chromium/README.chromium +1 -1
  74. package/front_end/third_party/lit/lib/async-directive.d.ts +465 -0
  75. package/front_end/third_party/lit/lib/async-directive.js +23 -0
  76. package/front_end/third_party/lit/lib/async-directive.js.map +1 -0
  77. package/front_end/third_party/lit/lib/decorators.d.ts +7 -1
  78. package/front_end/third_party/lit/lib/decorators.js +2 -2
  79. package/front_end/third_party/lit/lib/decorators.js.map +1 -1
  80. package/front_end/third_party/lit/lib/directive.js.map +1 -1
  81. package/front_end/third_party/lit/lib/directives.d.ts +27 -8
  82. package/front_end/third_party/lit/lib/directives.js +8 -8
  83. package/front_end/third_party/lit/lib/directives.js.map +1 -1
  84. package/front_end/third_party/lit/lib/lit.d.ts +15 -5
  85. package/front_end/third_party/lit/lib/lit.js +4 -4
  86. package/front_end/third_party/lit/lib/lit.js.map +1 -1
  87. package/front_end/third_party/lit/lib/static-html.js +2 -2
  88. package/front_end/third_party/lit/lib/static-html.js.map +1 -1
  89. package/front_end/third_party/lit/lit.ts +2 -1
  90. package/front_end/third_party/lit/rollup.config.mjs +1 -1
  91. package/front_end/third_party/source-map-scopes-codec/package/deno.json +1 -1
  92. package/front_end/third_party/source-map-scopes-codec/package/package.json +1 -1
  93. package/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.js +1 -1
  94. package/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.js.map +1 -1
  95. package/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.ts +1 -1
  96. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encoder.js +1 -1
  97. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encoder.js.map +1 -1
  98. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encoder.ts +2 -2
  99. package/front_end/ui/components/markdown_view/MarkdownLinksMap.ts +5 -0
  100. package/front_end/ui/legacy/SuggestBox.ts +4 -0
  101. package/front_end/ui/legacy/TextPrompt.ts +1 -1
  102. package/front_end/ui/legacy/components/utils/ImagePreview.ts +27 -23
  103. package/front_end/ui/lit/lit.ts +1 -0
  104. package/front_end/ui/visual_logging/Debugging.ts +1 -1
  105. package/front_end/ui/visual_logging/KnownContextValues.ts +19 -0
  106. package/inspector_overlay/highlight_grid_common.ts +11 -8
  107. package/package.json +1 -1
  108. package/front_end/models/issues_manager/ContrastCheckTrigger.ts +0 -78
  109. package/front_end/models/issues_manager/LowTextContrastIssue.ts +0 -63
  110. package/front_end/panels/issues/AffectedElementsWithLowContrastView.ts +0 -91
  111. /package/front_end/{panels → models}/lighthouse/LighthouseReporterTypes.ts +0 -0
@@ -10,6 +10,7 @@ import * as Host from '../../core/host/host.js';
10
10
  import * as i18n from '../../core/i18n/i18n.js';
11
11
  import * as Platform from '../../core/platform/platform.js';
12
12
  import * as EmulationModel from '../../models/emulation/emulation.js';
13
+ import * as uiI18n from '../../ui/i18n/i18n.js';
13
14
  import * as UI from '../../ui/legacy/legacy.js';
14
15
  import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
15
16
  import * as MobileThrottling from '../mobile_throttling/mobile_throttling.js';
@@ -19,8 +20,14 @@ const UIStrings = {
19
20
  /**
20
21
  * @description Title of the device dimensions selection item in the Device Mode Toolbar.
21
22
  * webpage in pixels.
23
+ * @example {Responsive} PH1
22
24
  */
23
- dimensions: 'Dimensions',
25
+ dimensions: 'Dimensions: {PH1}',
26
+ /**
27
+ * @description Title of the device pixel ratio selection item in the Device Mode Toolbar.
28
+ * @example {2.0} PH1
29
+ */
30
+ dpr: 'DPR: {PH1}',
24
31
  /**
25
32
  * @description Title of the width input textbox in the Device Mode Toolbar, for the width of the
26
33
  * webpage in pixels.
@@ -53,12 +60,12 @@ const UIStrings = {
53
60
  */
54
61
  moreOptions: 'More options',
55
62
  /**
56
- * @description A context menu item in the Device Mode Toolbar. This is a command to resize the
57
- * webpage preview to fit the current window. The placeholder is the percentage of full-size that
58
- * will be displayed after fitting.
63
+ * @description A menu item in the drop-down box that allows the user to select the zoom level.
64
+ * Labels the value which corresponds to the 'fit to window' zoom level, represented by the
65
+ * placeholder, which is a number. In the Device Mode Toolbar.
59
66
  * @example {30.0} PH1
60
67
  */
61
- fitToWindowF: 'Fit to window ({PH1}%)',
68
+ fitToWindowPercentage: '{PH1}% (fit to window)',
62
69
  /**
63
70
  * @description A checkbox setting that appears in the context menu for the zoom level, in the
64
71
  * Device Mode Toolbar.
@@ -70,7 +77,7 @@ const UIStrings = {
70
77
  * placeholder, which is a number. In the Device Mode Toolbar.
71
78
  * @example {4.3} PH1
72
79
  */
73
- defaultF: 'Default: {PH1}',
80
+ defaultF: '{PH1} (default)',
74
81
  /**
75
82
  * @description Command to hide the frame (like a picture frame) around the mobile device screen.
76
83
  */
@@ -163,6 +170,11 @@ const UIStrings = {
163
170
  * @description Tooltip of the rotate/screen orientation button.
164
171
  */
165
172
  screenOrientationOptions: 'Screen orientation options',
173
+ /**
174
+ * @description Tooltip shown on the rotate button when screen orientation is locked by the page
175
+ * via screen.orientation.lock().
176
+ */
177
+ screenOrientationLocked: 'Screen orientation is locked by the page',
166
178
  /**
167
179
  * @description Tooltip for a button which turns on/off dual-screen mode, which emulates devices
168
180
  * like tablets which have two screens.
@@ -284,6 +296,9 @@ export class DeviceModeToolbar {
284
296
  new UI.Toolbar.ToolbarMenuButton(this.appendDeviceMenuItems.bind(this), undefined, undefined, 'device');
285
297
  this.deviceSelectItem.turnShrinkable();
286
298
  this.deviceSelectItem.setDarkText();
299
+ const dimensionsSpan =
300
+ uiI18n.getFormatLocalizedString(str_, UIStrings.dimensions, {PH1: this.deviceSelectItem.element});
301
+ mainToolbar.append(...dimensionsSpan.childNodes);
287
302
  mainToolbar.appendToolbarItem(this.deviceSelectItem);
288
303
 
289
304
  this.widthInput.addEventListener('sizechanged', ({size: width}) => {
@@ -315,6 +330,11 @@ export class DeviceModeToolbar {
315
330
  this.scaleItem.turnShrinkable();
316
331
  this.scaleItem.setDarkText();
317
332
  mainToolbar.appendToolbarItem(this.scaleItem);
333
+
334
+ const autoAdjustScaleButton = new UI.Toolbar.ToolbarSettingToggle(
335
+ this.autoAdjustScaleSetting, 'center-focus-weak', i18nString(UIStrings.autoadjustZoom));
336
+ mainToolbar.appendToolbarItem(autoAdjustScaleButton);
337
+
318
338
  mainToolbar.appendToolbarItem(new UI.Toolbar.ToolbarItem(this.createEmptyToolbarElement()));
319
339
 
320
340
  this.deviceScaleItem = new UI.Toolbar.ToolbarMenuButton(
@@ -323,6 +343,8 @@ export class DeviceModeToolbar {
323
343
  this.deviceScaleItem.setVisible(this.showDeviceScaleFactorSetting.get());
324
344
  setTitleForButton(this.deviceScaleItem, i18nString(UIStrings.devicePixelRatio));
325
345
  this.deviceScaleItem.setDarkText();
346
+ const deviceScaleSpan = uiI18n.getFormatLocalizedString(str_, UIStrings.dpr, {PH1: this.deviceScaleItem.element});
347
+ mainToolbar.append(...deviceScaleSpan.childNodes);
326
348
  mainToolbar.appendToolbarItem(this.deviceScaleItem);
327
349
  mainToolbar.appendToolbarItem(new UI.Toolbar.ToolbarItem(this.createEmptyToolbarElement()));
328
350
  this.uaItem =
@@ -374,11 +396,16 @@ export class DeviceModeToolbar {
374
396
  return optionsToolbar;
375
397
  }
376
398
 
399
+ private getDevicePostureOptions(): Array<{title: string, value: string, selected: boolean}> {
400
+ const currentPosture = this.currentDevicePosture();
401
+ return ['Continuous', 'Folded'].map(title => ({title, value: title, selected: currentPosture === title}));
402
+ }
403
+
377
404
  private appendDevicePostureItems(contextMenu: UI.ContextMenu.ContextMenu): void {
378
- for (const title of ['Continuous', 'Folded']) {
405
+ for (const option of this.getDevicePostureOptions()) {
379
406
  contextMenu.defaultSection().appendCheckboxItem(
380
- title, this.spanClicked.bind(this),
381
- {checked: title === this.currentDevicePosture(), jslogContext: title.toLowerCase()});
407
+ option.title, this.spanClicked.bind(this),
408
+ {checked: option.selected, jslogContext: option.title.toLowerCase()});
382
409
  }
383
410
  }
384
411
 
@@ -392,27 +419,43 @@ export class DeviceModeToolbar {
392
419
  return 'Continuous';
393
420
  }
394
421
 
395
- private appendScaleMenuItems(contextMenu: UI.ContextMenu.ContextMenu): void {
422
+ private getScaleOptions(): Array<{title: string, value: number, selected: boolean, jslogContext: string}> {
423
+ const values = [0.5, 0.75, 1, 1.25, 1.5, 2];
424
+ let fitValue: number|null = null;
396
425
  if (this.model.type() === EmulationModel.DeviceModeModel.Type.Device) {
397
- contextMenu.footerSection().appendItem(
398
- i18nString(UIStrings.fitToWindowF, {PH1: this.getPrettyFitZoomPercentage()}),
399
- this.onScaleMenuChanged.bind(this, this.model.fitScale()), {jslogContext: 'fit-to-window'});
400
- }
401
- contextMenu.footerSection().appendCheckboxItem(
402
- i18nString(UIStrings.autoadjustZoom), this.onAutoAdjustScaleChanged.bind(this),
403
- {checked: this.autoAdjustScaleSetting.get(), jslogContext: 'auto-adjust-zoom'});
404
- const boundAppendScaleItem = appendScaleItem.bind(this);
405
- boundAppendScaleItem('50%', 0.5);
406
- boundAppendScaleItem('75%', 0.75);
407
- boundAppendScaleItem('100%', 1);
408
- boundAppendScaleItem('125%', 1.25);
409
- boundAppendScaleItem('150%', 1.5);
410
- boundAppendScaleItem('200%', 2);
411
-
412
- function appendScaleItem(this: DeviceModeToolbar, title: string, value: number): void {
426
+ fitValue = this.model.fitScale();
427
+ const fitValuePct = (fitValue * 100).toFixed(0);
428
+ let found = false;
429
+ for (let i = 0; i < values.length; ++i) {
430
+ if ((values[i] * 100).toFixed(0) === fitValuePct) {
431
+ found = true;
432
+ values[i] = fitValue;
433
+ break;
434
+ }
435
+ }
436
+ if (!found) {
437
+ values.push(fitValue);
438
+ values.sort((a, b) => a - b);
439
+ }
440
+ }
441
+
442
+ const currentScale = this.model.scaleSetting().get();
443
+ return values.map(value => {
444
+ let title = (value * 100).toFixed(0) + '%';
445
+ let jslogContext = title;
446
+ if (value === fitValue) {
447
+ title = i18nString(UIStrings.fitToWindowPercentage, {PH1: (value * 100).toFixed(0)});
448
+ jslogContext = 'fit-to-window';
449
+ }
450
+ return {title, value, selected: currentScale === value, jslogContext};
451
+ });
452
+ }
453
+
454
+ private appendScaleMenuItems(contextMenu: UI.ContextMenu.ContextMenu): void {
455
+ for (const option of this.getScaleOptions()) {
413
456
  contextMenu.defaultSection().appendCheckboxItem(
414
- title, this.onScaleMenuChanged.bind(this, value),
415
- {checked: this.model.scaleSetting().get() === value, jslogContext: title});
457
+ option.title, this.onScaleMenuChanged.bind(this, option.value),
458
+ {checked: option.selected, jslogContext: option.jslogContext});
416
459
  }
417
460
  }
418
461
 
@@ -420,41 +463,68 @@ export class DeviceModeToolbar {
420
463
  this.model.scaleSetting().set(value);
421
464
  }
422
465
 
423
- private onAutoAdjustScaleChanged(): void {
424
- this.autoAdjustScaleSetting.set(!this.autoAdjustScaleSetting.get());
425
- }
426
-
427
- private appendDeviceScaleMenuItems(contextMenu: UI.ContextMenu.ContextMenu): void {
466
+ private getDeviceScaleFactorOptions():
467
+ Array<{title: string, value: number, selected: boolean, jslogContext: string}> {
428
468
  const deviceScaleFactorSetting = this.model.deviceScaleFactorSetting();
429
469
  const defaultValue = this.model.uaSetting().get() === EmulationModel.DeviceModeModel.UA.MOBILE ||
430
470
  this.model.uaSetting().get() === EmulationModel.DeviceModeModel.UA.MOBILE_NO_TOUCH ?
431
471
  EmulationModel.DeviceModeModel.defaultMobileScaleFactor :
432
472
  window.devicePixelRatio;
433
- appendDeviceScaleFactorItem(
434
- contextMenu.headerSection(), i18nString(UIStrings.defaultF, {PH1: defaultValue}), 0, 'dpr-default');
435
- appendDeviceScaleFactorItem(contextMenu.defaultSection(), '1', 1, 'dpr-1');
436
- appendDeviceScaleFactorItem(contextMenu.defaultSection(), '2', 2, 'dpr-2');
437
- appendDeviceScaleFactorItem(contextMenu.defaultSection(), '3', 3, 'dpr-3');
473
+ const values = [1, 2, 3];
474
+ if (!values.includes(defaultValue)) {
475
+ values.push(defaultValue);
476
+ values.sort((a, b) => a - b);
477
+ }
478
+
479
+ const currentDPR = deviceScaleFactorSetting.get();
480
+ return values.map(value => {
481
+ let title = String(value);
482
+ let jslogContext = `dpr-${value}`;
483
+ if (value === defaultValue) {
484
+ title = i18nString(UIStrings.defaultF, {PH1: value});
485
+ jslogContext = 'dpr-default';
486
+ }
487
+ return {
488
+ title,
489
+ value: value === defaultValue ? 0 : value,
490
+ selected: currentDPR === value || (value === defaultValue && currentDPR === 0),
491
+ jslogContext
492
+ };
493
+ });
494
+ }
438
495
 
439
- function appendDeviceScaleFactorItem(
440
- section: UI.ContextMenu.Section, title: string, value: number, jslogContext: string): void {
441
- section.appendCheckboxItem(
442
- title, deviceScaleFactorSetting.set.bind(deviceScaleFactorSetting, value),
443
- {checked: deviceScaleFactorSetting.get() === value, jslogContext});
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});
444
502
  }
445
503
  }
446
504
 
447
- private appendUserAgentMenuItems(contextMenu: UI.ContextMenu.ContextMenu): void {
505
+ private getUserAgentOptions():
506
+ Array<{title: string, value: EmulationModel.DeviceModeModel.UA, selected: boolean, jslogContext: string}> {
448
507
  const uaSetting = this.model.uaSetting();
449
- appendUAItem(EmulationModel.DeviceModeModel.UA.MOBILE, EmulationModel.DeviceModeModel.UA.MOBILE);
450
- appendUAItem(EmulationModel.DeviceModeModel.UA.MOBILE_NO_TOUCH, EmulationModel.DeviceModeModel.UA.MOBILE_NO_TOUCH);
451
- appendUAItem(EmulationModel.DeviceModeModel.UA.DESKTOP, EmulationModel.DeviceModeModel.UA.DESKTOP);
452
- appendUAItem(EmulationModel.DeviceModeModel.UA.DESKTOP_TOUCH, EmulationModel.DeviceModeModel.UA.DESKTOP_TOUCH);
508
+ const currentUserAgent = uaSetting.get();
509
+ return [
510
+ EmulationModel.DeviceModeModel.UA.MOBILE,
511
+ EmulationModel.DeviceModeModel.UA.MOBILE_NO_TOUCH,
512
+ EmulationModel.DeviceModeModel.UA.DESKTOP,
513
+ EmulationModel.DeviceModeModel.UA.DESKTOP_TOUCH,
514
+ ].map(value => ({
515
+ title: value,
516
+ value,
517
+ selected: currentUserAgent === value,
518
+ jslogContext: Platform.StringUtilities.toKebabCase(value)
519
+ }));
520
+ }
453
521
 
454
- function appendUAItem(title: string, value: EmulationModel.DeviceModeModel.UA): void {
522
+ private appendUserAgentMenuItems(contextMenu: UI.ContextMenu.ContextMenu): void {
523
+ const uaSetting = this.model.uaSetting();
524
+ for (const option of this.getUserAgentOptions()) {
455
525
  contextMenu.defaultSection().appendCheckboxItem(
456
- title, uaSetting.set.bind(uaSetting, value),
457
- {checked: uaSetting.get() === value, jslogContext: Platform.StringUtilities.toKebabCase(value)});
526
+ option.title, uaSetting.set.bind(uaSetting, option.value),
527
+ {checked: option.selected, jslogContext: option.jslogContext});
458
528
  }
459
529
  }
460
530
 
@@ -539,25 +609,75 @@ export class DeviceModeToolbar {
539
609
  return this.standardDevices().concat(this.customDevices());
540
610
  }
541
611
 
612
+ private getDeviceModeOptions(): {
613
+ responsive: {title: string, selected: boolean, jslogContext: string},
614
+ standard: Array<{
615
+ device: EmulationModel.EmulatedDevices.EmulatedDevice,
616
+ title: string,
617
+ selected: boolean,
618
+ jslogContext: string,
619
+ }>,
620
+ custom: Array<{
621
+ device: EmulationModel.EmulatedDevices.EmulatedDevice,
622
+ title: string,
623
+ selected: boolean,
624
+ jslogContext: string,
625
+ }>,
626
+ edit: {title: string, jslogContext: string},
627
+ } {
628
+ return {
629
+ responsive: {
630
+ title: i18nString(UIStrings.responsive),
631
+ selected: this.model.type() === EmulationModel.DeviceModeModel.Type.Responsive,
632
+ jslogContext: 'responsive'
633
+ },
634
+ standard: this.standardDevices().map(device => ({
635
+ device,
636
+ title: device.title,
637
+ selected: this.model.device() === device,
638
+ jslogContext: Platform.StringUtilities.toKebabCase(device.title)
639
+ })),
640
+ custom: this.customDevices().map(device => ({
641
+ device,
642
+ title: device.title,
643
+ selected: this.model.device() === device,
644
+ jslogContext: Platform.StringUtilities.toKebabCase(device.title)
645
+ })),
646
+ edit: {
647
+ title: i18nString(UIStrings.edit),
648
+ jslogContext: 'edit',
649
+ }
650
+ };
651
+ }
652
+
542
653
  private appendDeviceMenuItems(contextMenu: UI.ContextMenu.ContextMenu): void {
654
+ const options = this.getDeviceModeOptions();
655
+
543
656
  contextMenu.headerSection().appendCheckboxItem(
544
- i18nString(UIStrings.responsive), this.switchToResponsive.bind(this),
545
- {checked: this.model.type() === EmulationModel.DeviceModeModel.Type.Responsive, jslogContext: 'responsive'});
546
- appendGroup.call(this, this.standardDevices());
547
- appendGroup.call(this, this.customDevices());
548
- contextMenu.footerSection().appendItem(
549
- i18nString(UIStrings.edit), this.emulatedDevicesList.revealCustomSetting.bind(this.emulatedDevicesList),
550
- {jslogContext: 'edit'});
657
+ options.responsive.title, this.switchToResponsive.bind(this),
658
+ {checked: options.responsive.selected, jslogContext: options.responsive.jslogContext});
659
+
660
+ appendGroup.call(this, options.standard);
661
+ appendGroup.call(this, options.custom);
551
662
 
552
- function appendGroup(this: DeviceModeToolbar, devices: EmulationModel.EmulatedDevices.EmulatedDevice[]): void {
553
- if (!devices.length) {
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 {
673
+ if (!group.length) {
554
674
  return;
555
675
  }
556
676
  const section = contextMenu.section();
557
- for (const device of devices) {
558
- section.appendCheckboxItem(device.title, this.emulateDevice.bind(this, device), {
559
- checked: this.model.device() === device,
560
- jslogContext: Platform.StringUtilities.toKebabCase(device.title),
677
+ for (const item of group) {
678
+ section.appendCheckboxItem(item.title, this.emulateDevice.bind(this, item.device), {
679
+ checked: item.selected,
680
+ jslogContext: item.jslogContext,
561
681
  });
562
682
  }
563
683
  }
@@ -617,6 +737,9 @@ export class DeviceModeToolbar {
617
737
  private modeMenuClicked(event: {
618
738
  data: Event,
619
739
  }): void {
740
+ if (this.model.isScreenOrientationLocked()) {
741
+ return;
742
+ }
620
743
  const device = this.model.device();
621
744
  const model = this.model;
622
745
  const autoAdjustScaleSetting = this.autoAdjustScaleSetting;
@@ -737,7 +860,7 @@ export class DeviceModeToolbar {
737
860
 
738
861
  const deviceScale = this.model.appliedDeviceScaleFactor();
739
862
  if (deviceScale !== this.cachedDeviceScale) {
740
- this.deviceScaleItem.setText(`DPR: ${deviceScale.toFixed(1)}`);
863
+ this.deviceScaleItem.setText(`${deviceScale.toFixed(1)}`);
741
864
  this.cachedDeviceScale = deviceScale;
742
865
  }
743
866
 
@@ -755,7 +878,7 @@ export class DeviceModeToolbar {
755
878
  if (this.model.type() === EmulationModel.DeviceModeModel.Type.Device && device) {
756
879
  deviceItemTitle = device.title;
757
880
  }
758
- this.deviceSelectItem.setText(`${i18nString(UIStrings.dimensions)}: ${deviceItemTitle}`);
881
+ this.deviceSelectItem.setText(deviceItemTitle);
759
882
 
760
883
  if (this.model.device() !== this.cachedModelDevice) {
761
884
  const device = this.model.device();
@@ -804,6 +927,23 @@ export class DeviceModeToolbar {
804
927
  }
805
928
  this.persistenceSetting.set(value);
806
929
  }
930
+
931
+ // When screen orientation is locked by the page (via screen.orientation.lock()),
932
+ // disable the rotate button to prevent user-initiated rotation.
933
+ // When unlocked, restore the button to its normal state.
934
+ if (this.model.isScreenOrientationLocked()) {
935
+ this.modeButton.setEnabled(false);
936
+ setTitleForButton(this.modeButton, i18nString(UIStrings.screenOrientationLocked));
937
+ } else if (this.cachedModelDevice) {
938
+ const modeCount = this.cachedModelDevice.modes.length;
939
+ this.modeButton.setEnabled(modeCount >= 2);
940
+ setTitleForButton(
941
+ this.modeButton,
942
+ modeCount === 2 ? i18nString(UIStrings.rotate) : i18nString(UIStrings.screenOrientationOptions));
943
+ } else if (this.model.type() === EmulationModel.DeviceModeModel.Type.Responsive) {
944
+ this.modeButton.setEnabled(true);
945
+ setTitleForButton(this.modeButton, i18nString(UIStrings.rotate));
946
+ }
807
947
  }
808
948
 
809
949
  restore(): void {
@@ -0,0 +1,120 @@
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 i18n from '../../core/i18n/i18n.js';
6
+ import type * as Platform from '../../core/platform/platform.js';
7
+ import type * as SDK from '../../core/sdk/sdk.js';
8
+ import type * as Protocol from '../../generated/protocol.js';
9
+ import * as Bindings from '../../models/bindings/bindings.js';
10
+ import type * as IssuesManager from '../../models/issues_manager/issues_manager.js';
11
+ import * as Components from '../../ui/legacy/components/utils/utils.js';
12
+ import * as UI from '../../ui/legacy/legacy.js';
13
+ import {Directives, html, type LitTemplate, nothing, render} from '../../ui/lit/lit.js';
14
+
15
+ import {AffectedResourcesView} from './AffectedResourcesView.js';
16
+
17
+ const UIStrings = {
18
+ /**
19
+ * @description Label for number of affected resources indication in issue view
20
+ */
21
+ nViolations: '{n, plural, =1 {# violation} other {# violations}}',
22
+ /**
23
+ * @description Title for the API column in the Selective Permissions Intervention affected resources list
24
+ */
25
+ api: 'API',
26
+ /**
27
+ * @description Title for the Script column in the Selective Permissions Intervention affected resources list
28
+ */
29
+ script: 'Script',
30
+ /**
31
+ * @description Title for the Ad Ancestry column in the Selective Permissions Intervention affected resources list
32
+ */
33
+ adAncestry: 'Ad Ancestry',
34
+ /**
35
+ * @description Text for unknown value
36
+ */
37
+ unknown: 'unknown',
38
+ /**
39
+ * @description Text for loading state
40
+ */
41
+ loading: 'loading…',
42
+ } as const;
43
+ const str_ = i18n.i18n.registerUIStrings('panels/issues/AffectedSelectivePermissionsInterventionView.ts', UIStrings);
44
+ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
45
+
46
+ export class AffectedSelectivePermissionsInterventionView extends AffectedResourcesView {
47
+ readonly #linkifier = new Components.Linkifier.Linkifier();
48
+
49
+ protected getResourceNameWithCount(count: number): string {
50
+ return i18nString(UIStrings.nViolations, {n: count});
51
+ }
52
+
53
+ #render(): void {
54
+ const issues = Array.from(this.issue.getSelectivePermissionsInterventionIssues());
55
+ // eslint-disable-next-line @devtools/no-lit-render-outside-of-view
56
+ render(
57
+ html`
58
+ <tr>
59
+ <td class="affected-resource-header">${i18nString(UIStrings.api)}</td>
60
+ <td class="affected-resource-header">${i18nString(UIStrings.script)}</td>
61
+ <td class="affected-resource-header">${i18nString(UIStrings.adAncestry)}</td>
62
+ </tr>
63
+ ${issues.map(issue => this.#renderDetail(issue))}
64
+ `,
65
+ this.affectedResources, {host: this});
66
+ this.updateAffectedResourceCount(issues.length);
67
+ }
68
+
69
+ #renderDetail(issue: IssuesManager.SelectivePermissionsInterventionIssue.SelectivePermissionsInterventionIssue):
70
+ LitTemplate {
71
+ const details = issue.details();
72
+ const issuesModel = issue.model();
73
+
74
+ const stackTracePromise = (details.stackTrace && issuesModel) ?
75
+ this.#resolveStackTrace(details.stackTrace, issuesModel) :
76
+ Promise.resolve(html`<span>${i18nString(UIStrings.unknown)}</span>`);
77
+
78
+ const target = issuesModel ? issuesModel.target() : null;
79
+
80
+ return html`
81
+ <tr class="affected-resource-directive">
82
+ <td>${details.apiName}</td>
83
+ <td>${Directives.until(stackTracePromise, html`<span>${i18nString(UIStrings.loading)}</span>`)}</td>
84
+ <td class="affected-resource-cell">
85
+ <div class="ad-ancestry-list">
86
+ ${(details.adAncestry?.adAncestryChain || []).map(script => {
87
+ const link = this.#linkifier.linkifyScriptLocation(
88
+ target, script.scriptId, script.name as Platform.DevToolsPath.UrlString, 0);
89
+ return html`<div>${link}</div>`;
90
+ })}
91
+ ${
92
+ details.adAncestry?.rootScriptFilterlistRule ?
93
+ html`<div>Rule: ${details.adAncestry.rootScriptFilterlistRule}</div>` :
94
+ nothing}
95
+ </div>
96
+ </td>
97
+ </tr>
98
+ `;
99
+ }
100
+
101
+ async #resolveStackTrace(stackTrace: Protocol.Runtime.StackTrace, issuesModel: SDK.IssuesModel.IssuesModel):
102
+ Promise<LitTemplate> {
103
+ const debuggerWorkspaceBinding = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance();
104
+ const stackTraceTranslated =
105
+ await debuggerWorkspaceBinding.createStackTraceFromProtocolRuntime(stackTrace, issuesModel.target());
106
+ return html`
107
+ <devtools-widget .widgetConfig=${UI.Widget.widgetConfig(Components.JSPresentationUtils.StackTracePreviewContent, {
108
+ stackTrace: stackTraceTranslated,
109
+ options: {expandable: true},
110
+ })}>
111
+ </devtools-widget>
112
+ `;
113
+ }
114
+
115
+ update(): void {
116
+ this.requestResolver.clear();
117
+ this.#linkifier.reset();
118
+ this.#render();
119
+ }
120
+ }
@@ -66,7 +66,7 @@ export class HiddenIssuesRow extends UI.TreeOutline.TreeElement {
66
66
  #view: View;
67
67
 
68
68
  constructor(view: View = DEFAULT_VIEW) {
69
- super(undefined, true);
69
+ super(undefined, true, 'hidden-issues');
70
70
  this.#view = view;
71
71
  this.toggleOnClick = true;
72
72
  this.listItemElement.classList.add('issue-category', 'hidden-issues');
@@ -5,6 +5,7 @@
5
5
 
6
6
  import * as Common from '../../core/common/common.js';
7
7
  import * as i18n from '../../core/i18n/i18n.js';
8
+ import * as Platform from '../../core/platform/platform.js';
8
9
  import * as IssuesManager from '../../models/issues_manager/issues_manager.js';
9
10
  import * as Adorners from '../../ui/components/adorners/adorners.js';
10
11
  import * as IssueCounter from '../../ui/components/issue_counter/issue_counter.js';
@@ -64,7 +65,7 @@ export class IssueKindView extends UI.TreeOutline.TreeElement {
64
65
  #issueCount: HTMLElement;
65
66
 
66
67
  constructor(kind: IssuesManager.Issue.IssueKind) {
67
- super(undefined, true);
68
+ super(undefined, true, Platform.StringUtilities.toKebabCase(kind));
68
69
  this.#kind = kind;
69
70
  this.#issueCount = document.createElement('span');
70
71
 
@@ -6,7 +6,7 @@
6
6
  import * as Common from '../../core/common/common.js';
7
7
  import * as Host from '../../core/host/host.js';
8
8
  import * as i18n from '../../core/i18n/i18n.js';
9
- import type * as Platform from '../../core/platform/platform.js';
9
+ import * as Platform from '../../core/platform/platform.js';
10
10
  import * as Protocol from '../../generated/protocol.js';
11
11
  import type {AggregatedIssue} from '../../models/issues_manager/IssueAggregator.js';
12
12
  import * as IssuesManager from '../../models/issues_manager/issues_manager.js';
@@ -24,12 +24,12 @@ import {AffectedDescendantsWithinSelectElementView} from './AffectedDescendantsW
24
24
  import {AffectedDirectivesView} from './AffectedDirectivesView.js';
25
25
  import {AffectedDocumentsInQuirksModeView} from './AffectedDocumentsInQuirksModeView.js';
26
26
  import {AffectedElementsView} from './AffectedElementsView.js';
27
- import {AffectedElementsWithLowContrastView} from './AffectedElementsWithLowContrastView.js';
28
27
  import {AffectedHeavyAdView} from './AffectedHeavyAdView.js';
29
28
  import {AffectedMetadataAllowedSitesView} from './AffectedMetadataAllowedSitesView.js';
30
29
  import {AffectedPartitioningBlobURLView} from './AffectedPartitioningBlobURLView.js';
31
30
  import {AffectedPermissionElementsView} from './AffectedPermissionElementsView.js';
32
31
  import {AffectedItem, AffectedResourcesView, extractShortPath} from './AffectedResourcesView.js';
32
+ import {AffectedSelectivePermissionsInterventionView} from './AffectedSelectivePermissionsInterventionView.js';
33
33
  import {AffectedSharedArrayBufferIssueDetailsView} from './AffectedSharedArrayBufferIssueDetailsView.js';
34
34
  import {AffectedSourcesView} from './AffectedSourcesView.js';
35
35
  import {AffectedTrackingSitesView} from './AffectedTrackingSitesView.js';
@@ -230,7 +230,7 @@ export class IssueView extends UI.TreeOutline.TreeElement {
230
230
  #contentCreated = false;
231
231
 
232
232
  constructor(issue: AggregatedIssue, description: IssuesManager.MarkdownIssueDescription.IssueDescription) {
233
- super();
233
+ super(undefined, undefined, Platform.StringUtilities.toKebabCase(issue.getCategory()));
234
234
  this.#issue = issue;
235
235
  this.#description = description;
236
236
  this.#throttle = new Common.Throttler.Throttler(250);
@@ -251,7 +251,6 @@ export class IssueView extends UI.TreeOutline.TreeElement {
251
251
  new AffectedDirectivesView(this, this.#issue, 'directives-details'),
252
252
  new AffectedBlockedByResponseView(this, this.#issue, 'blocked-by-response-details'),
253
253
  new AffectedSharedArrayBufferIssueDetailsView(this, this.#issue, 'sab-details'),
254
- new AffectedElementsWithLowContrastView(this, this.#issue, 'low-contrast-details'),
255
254
  new CorsIssueDetailsView(this, this.#issue, 'cors-details'),
256
255
  new GenericIssueDetailsView(this, this.#issue, 'generic-details'),
257
256
  new AffectedDocumentsInQuirksModeView(this, this.#issue, 'affected-documents'),
@@ -262,6 +261,7 @@ export class IssueView extends UI.TreeOutline.TreeElement {
262
261
  new AffectedDescendantsWithinSelectElementView(this, this.#issue, 'disallowed-select-descendants-details'),
263
262
  new AffectedPartitioningBlobURLView(this, this.#issue, 'partitioning-blob-url-details'),
264
263
  new AffectedPermissionElementsView(this, this.#issue, 'permission-element-elements'),
264
+ new AffectedSelectivePermissionsInterventionView(this, this.#issue, 'selective-permissions-intervention-details'),
265
265
  ];
266
266
  this.#hiddenIssuesMenu = new Components.HideIssuesMenu.HideIssuesMenu();
267
267
  this.#aggregatedIssuesCount = null;
@@ -7,7 +7,7 @@ import '../../ui/legacy/legacy.js';
7
7
 
8
8
  import * as Common from '../../core/common/common.js';
9
9
  import * as i18n from '../../core/i18n/i18n.js';
10
- import type * as Platform from '../../core/platform/platform.js';
10
+ import * as Platform from '../../core/platform/platform.js';
11
11
  import * as IssuesManager from '../../models/issues_manager/issues_manager.js';
12
12
  import * as IssueCounter from '../../ui/components/issue_counter/issue_counter.js';
13
13
  import * as UI from '../../ui/legacy/legacy.js';
@@ -110,6 +110,10 @@ const UIStrings = {
110
110
  * @description Category title for a group of permission element issues
111
111
  */
112
112
  permissionElement: 'PEPC Element',
113
+ /**
114
+ * @description Category title for the different 'Selective Permissions Intervention' issues.
115
+ */
116
+ selectivePermissionsIntervention: 'Selective Permissions Intervention',
113
117
  } as const;
114
118
  const str_ = i18n.i18n.registerUIStrings('panels/issues/IssuesPane.ts', UIStrings);
115
119
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -121,7 +125,7 @@ class IssueCategoryView extends UI.TreeOutline.TreeElement {
121
125
  #category: IssuesManager.Issue.IssueCategory;
122
126
 
123
127
  constructor(category: IssuesManager.Issue.IssueCategory) {
124
- super();
128
+ super(undefined, undefined, Platform.StringUtilities.toKebabCase(category));
125
129
  this.#category = category;
126
130
 
127
131
  this.toggleOnClick = true;
@@ -153,6 +157,8 @@ class IssueCategoryView extends UI.TreeOutline.TreeElement {
153
157
  return i18nString(UIStrings.generic);
154
158
  case IssuesManager.Issue.IssueCategory.PERMISSION_ELEMENT:
155
159
  return i18nString(UIStrings.permissionElement);
160
+ case IssuesManager.Issue.IssueCategory.SELECTIVE_PERMISSIONS_INTERVENTION:
161
+ return i18nString(UIStrings.selectivePermissionsIntervention);
156
162
  case IssuesManager.Issue.IssueCategory.OTHER:
157
163
  return i18nString(UIStrings.other);
158
164
  }
@@ -2,11 +2,13 @@
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
4
 
5
+ import * as AffectedSelectivePermissionsInterventionView from './AffectedSelectivePermissionsInterventionView.js';
5
6
  import * as IssueRevealer from './IssueRevealer.js';
6
7
  import * as IssuesPane from './IssuesPane.js';
7
8
  import * as IssueView from './IssueView.js';
8
9
 
9
10
  export {
11
+ AffectedSelectivePermissionsInterventionView,
10
12
  IssueRevealer,
11
13
  IssuesPane,
12
14
  IssueView,