chrome-devtools-frontend 1.0.1672557 → 1.0.1675430

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 (177) hide show
  1. package/.agents/skills/devtools-ux-writing-refactor/SKILL.md +26 -9
  2. package/front_end/core/common/ParsedURL.ts +5 -1
  3. package/front_end/core/common/SettingRegistration.ts +30 -34
  4. package/front_end/core/common/Settings.ts +4 -64
  5. package/front_end/core/host/UserMetrics.ts +127 -117
  6. package/front_end/core/protocol_client/PuppeteerDevToolsConnection.ts +42 -9
  7. package/front_end/core/root/Runtime.ts +14 -0
  8. package/front_end/core/sdk/NetworkManager.ts +0 -35
  9. package/front_end/core/sdk/SourceMap.ts +1 -19
  10. package/front_end/core/sdk/SourceMapManager.ts +75 -48
  11. package/front_end/core/sdk/SourceMapScopesInfo.ts +0 -62
  12. package/front_end/core/sdk/TargetManager.ts +18 -0
  13. package/front_end/devtools_compatibility.js +1 -1
  14. package/front_end/entrypoints/main/MainImpl.ts +1 -0
  15. package/front_end/foundation/Universe.ts +44 -29
  16. package/front_end/generated/InspectorBackendCommands.ts +4 -3
  17. package/front_end/generated/SupportedCSSProperties.ts +16 -1
  18. package/front_end/generated/protocol-mapping.d.ts +8 -0
  19. package/front_end/generated/protocol-proxy-api.d.ts +6 -0
  20. package/front_end/generated/protocol.ts +16 -2
  21. package/front_end/models/ai_assistance/AiSetting.ts +66 -3
  22. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +15 -5
  23. package/front_end/models/bindings/CompilerScriptMapping.ts +1 -1
  24. package/front_end/models/emulation/DeviceModeModel.ts +82 -102
  25. package/front_end/models/emulation/EmulatedDevices.ts +18 -66
  26. package/front_end/models/javascript_metadata/NativeFunctions.js +4 -4
  27. package/front_end/models/persistence/AutomaticFileSystemManager.ts +2 -2
  28. package/front_end/models/persistence/IsolatedFileSystemManager.ts +20 -29
  29. package/front_end/models/trace/ModelImpl.ts +5 -0
  30. package/front_end/models/trace/Processor.ts +5 -0
  31. package/front_end/models/trace/handlers/LayoutShiftsHandler.ts +7 -1
  32. package/front_end/models/trace/handlers/MetaHandler.ts +8 -4
  33. package/front_end/models/trace/handlers/PageLoadMetricsHandler.ts +11 -0
  34. package/front_end/models/workspace/FileManager.ts +13 -6
  35. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +11 -10
  36. package/front_end/panels/ai_assistance/ai_assistance-meta.ts +3 -45
  37. package/front_end/panels/application/BackgroundServiceView.ts +42 -40
  38. package/front_end/panels/application/KeyValueStorageItemsView.ts +1 -0
  39. package/front_end/panels/application/WebMCPView.ts +13 -5
  40. package/front_end/panels/application/components/AdsView.ts +24 -1
  41. package/front_end/panels/application/components/adsView.css +27 -21
  42. package/front_end/panels/autofill/AutofillView.ts +12 -12
  43. package/front_end/panels/autofill/autofill-meta.ts +2 -2
  44. package/front_end/panels/common/ExtensionServer.ts +7 -1
  45. package/front_end/panels/common/common.ts +0 -1
  46. package/front_end/panels/console/ConsoleInsightTeaser.ts +7 -6
  47. package/front_end/panels/console/ConsoleViewMessage.ts +1 -1
  48. package/front_end/panels/elements/ClassesPaneWidget.ts +2 -2
  49. package/front_end/panels/elements/ElementsTreeElement.ts +602 -327
  50. package/front_end/panels/elements/ElementsTreeOutline.ts +1 -1
  51. package/front_end/panels/elements/StylesSidebarPane.ts +1 -1
  52. package/front_end/panels/elements/components/AdornerManager.ts +1 -0
  53. package/front_end/panels/emulation/AdvancedApp.ts +3 -3
  54. package/front_end/panels/emulation/DeviceModeToolbar.ts +2 -11
  55. package/front_end/panels/emulation/DeviceModeView.ts +234 -75
  56. package/front_end/panels/emulation/InspectedPagePlaceholder.ts +1 -1
  57. package/front_end/panels/emulation/deviceModeView.css +4 -21
  58. package/front_end/panels/emulation/emulation-meta.ts +4 -30
  59. package/front_end/panels/emulation/emulation.ts +0 -3
  60. package/front_end/panels/explain/components/ConsoleInsight.ts +11 -7
  61. package/front_end/panels/js_timeline/js_timeline-meta.ts +8 -8
  62. package/front_end/panels/layer_viewer/LayerDetailsView.ts +30 -30
  63. package/front_end/panels/layer_viewer/LayerTreeOutline.ts +6 -6
  64. package/front_end/panels/layer_viewer/LayerViewHost.ts +1 -1
  65. package/front_end/panels/layer_viewer/Layers3DView.ts +11 -11
  66. package/front_end/panels/layer_viewer/PaintProfilerView.ts +8 -8
  67. package/front_end/panels/layer_viewer/TransformController.ts +3 -3
  68. package/front_end/panels/layer_viewer/layer_viewer-meta.ts +11 -11
  69. package/front_end/panels/layers/LayersPanel.ts +2 -2
  70. package/front_end/panels/layers/layers-meta.ts +2 -2
  71. package/front_end/panels/lighthouse/LighthouseController.ts +3 -7
  72. package/front_end/panels/mobile_throttling/CPUThrottlingSelector.ts +313 -0
  73. package/front_end/panels/mobile_throttling/NetworkPanelIndicator.ts +0 -10
  74. package/front_end/panels/mobile_throttling/NetworkThrottlingSelector.ts +21 -10
  75. package/front_end/panels/mobile_throttling/ThrottlingManager.ts +1 -58
  76. package/front_end/panels/mobile_throttling/mobile_throttling.ts +2 -9
  77. package/front_end/panels/network/NetworkConfigView.ts +1 -71
  78. package/front_end/panels/network/networkConfigView.css +1 -2
  79. package/front_end/panels/profiler/HeapDetachedElementsDataGrid.ts +3 -3
  80. package/front_end/panels/profiler/HeapDetachedElementsView.ts +5 -5
  81. package/front_end/panels/profiler/HeapProfileView.ts +32 -32
  82. package/front_end/panels/profiler/HeapProfilerPanel.ts +2 -2
  83. package/front_end/panels/profiler/HeapSnapshotDataGrids.ts +33 -37
  84. package/front_end/panels/profiler/HeapSnapshotGridNodes.ts +26 -27
  85. package/front_end/panels/profiler/HeapSnapshotView.ts +48 -48
  86. package/front_end/panels/profiler/IsolateSelector.ts +10 -10
  87. package/front_end/panels/profiler/ModuleUIStrings.ts +25 -25
  88. package/front_end/panels/profiler/ProfileLauncherView.ts +6 -6
  89. package/front_end/panels/profiler/ProfilesPanel.ts +6 -6
  90. package/front_end/panels/profiler/WritableProfileHeader.ts +6 -6
  91. package/front_end/panels/profiler/profiler-meta.ts +7 -7
  92. package/front_end/panels/protocol_monitor/ProtocolMonitor.ts +7 -0
  93. package/front_end/panels/recorder/RecorderPanel.ts +1 -1
  94. package/front_end/panels/settings/AISettingsTab.ts +27 -23
  95. package/front_end/panels/settings/emulation/DevicesSettingsTab.ts +1 -2
  96. package/front_end/panels/timeline/TimelineFlameChartNetworkDataProvider.ts +12 -17
  97. package/front_end/panels/timeline/TimelinePanel.ts +20 -35
  98. package/front_end/panels/timeline/components/LiveMetricsView.ts +27 -6
  99. package/front_end/panels/timeline/components/NetworkTrackWidget.ts +1 -1
  100. package/front_end/panels/timeline/components/components.ts +0 -4
  101. package/front_end/panels/timeline/components/liveMetricsView.css +7 -0
  102. package/front_end/third_party/chromium/README.chromium +1 -1
  103. package/front_end/third_party/source-map-scopes-codec/package/CONTRIBUTING.md +0 -0
  104. package/front_end/third_party/source-map-scopes-codec/package/LICENSE +0 -0
  105. package/front_end/third_party/source-map-scopes-codec/package/README.md +0 -0
  106. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/builder/builder.d.ts +0 -0
  107. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/builder/builder.d.ts.map +1 -1
  108. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/builder/safe_builder.d.ts +0 -0
  109. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/builder/safe_builder.d.ts.map +0 -0
  110. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/decode/decode.d.ts +0 -0
  111. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/decode/decode.d.ts.map +0 -0
  112. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/encode/encode.d.ts +0 -0
  113. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/encode/encode.d.ts.map +0 -0
  114. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/mod.d.ts +0 -0
  115. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/mod.d.ts.map +0 -0
  116. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/scopes.d.ts +0 -0
  117. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/scopes.d.ts.map +0 -0
  118. package/front_end/third_party/source-map-scopes-codec/package/deno.json +1 -1
  119. package/front_end/third_party/source-map-scopes-codec/package/package.json +1 -1
  120. package/front_end/third_party/source-map-scopes-codec/package/src/builder/builder.js +2 -0
  121. package/front_end/third_party/source-map-scopes-codec/package/src/builder/builder.js.map +1 -1
  122. package/front_end/third_party/source-map-scopes-codec/package/src/builder/builder.ts +2 -0
  123. package/front_end/third_party/source-map-scopes-codec/package/src/builder/safe_builder.js +0 -0
  124. package/front_end/third_party/source-map-scopes-codec/package/src/builder/safe_builder.js.map +0 -0
  125. package/front_end/third_party/source-map-scopes-codec/package/src/builder/safe_builder.ts +0 -0
  126. package/front_end/third_party/source-map-scopes-codec/package/src/codec.js +0 -0
  127. package/front_end/third_party/source-map-scopes-codec/package/src/codec.js.map +0 -0
  128. package/front_end/third_party/source-map-scopes-codec/package/src/codec.ts +0 -0
  129. package/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.js +0 -0
  130. package/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.js.map +0 -0
  131. package/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.ts +0 -0
  132. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encode.js +0 -0
  133. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encode.js.map +0 -0
  134. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encode.ts +0 -0
  135. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encoder.js +0 -0
  136. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encoder.js.map +0 -0
  137. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encoder.ts +0 -0
  138. package/front_end/third_party/source-map-scopes-codec/package/src/mod.js +0 -0
  139. package/front_end/third_party/source-map-scopes-codec/package/src/mod.js.map +0 -0
  140. package/front_end/third_party/source-map-scopes-codec/package/src/mod.ts +0 -0
  141. package/front_end/third_party/source-map-scopes-codec/package/src/scopes.js +0 -0
  142. package/front_end/third_party/source-map-scopes-codec/package/src/scopes.js.map +0 -0
  143. package/front_end/third_party/source-map-scopes-codec/package/src/scopes.ts +0 -0
  144. package/front_end/third_party/source-map-scopes-codec/package/src/util.js +0 -0
  145. package/front_end/third_party/source-map-scopes-codec/package/src/util.js.map +0 -0
  146. package/front_end/third_party/source-map-scopes-codec/package/src/util.ts +0 -0
  147. package/front_end/third_party/source-map-scopes-codec/package/src/vlq.js +0 -0
  148. package/front_end/third_party/source-map-scopes-codec/package/src/vlq.js.map +0 -0
  149. package/front_end/third_party/source-map-scopes-codec/package/src/vlq.ts +0 -0
  150. package/front_end/ui/comments/CommentAnchorResolver.ts +132 -0
  151. package/front_end/ui/comments/comments.ts +9 -0
  152. package/front_end/ui/components/dialogs/ShortcutDialog.ts +2 -0
  153. package/front_end/ui/components/settings/SettingCheckbox.ts +4 -12
  154. package/front_end/ui/components/settings/settingCheckbox.css +0 -7
  155. package/front_end/ui/legacy/Treeoutline.ts +14 -14
  156. package/front_end/ui/legacy/UIUtils.ts +2 -47
  157. package/front_end/ui/legacy/Widget.ts +10 -2
  158. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +40 -0
  159. package/front_end/ui/legacy/components/settings_ui/SettingsUI.ts +1 -1
  160. package/front_end/ui/legacy/components/utils/Linkifier.ts +1 -0
  161. package/front_end/ui/lit/Directives.ts +80 -0
  162. package/front_end/ui/lit/lit.ts +1 -0
  163. package/front_end/ui/lit/render.ts +8 -0
  164. package/front_end/ui/settings/SettingUIRegistration.ts +4 -0
  165. package/front_end/ui/visual_logging/Debugging.ts +1 -5
  166. package/front_end/ui/visual_logging/KnownContextValues.ts +3 -0
  167. package/front_end/ui/visual_logging/LoggingConfig.ts +21 -0
  168. package/front_end/ui/visual_logging/visual_logging.ts +9 -0
  169. package/mcp/mcp.ts +1 -0
  170. package/package.json +1 -1
  171. package/front_end/panels/common/ThrottlingUtils.ts +0 -51
  172. package/front_end/panels/emulation/DeviceModeWrapper.ts +0 -248
  173. package/front_end/panels/mobile_throttling/MobileThrottlingSelector.ts +0 -92
  174. package/front_end/panels/timeline/components/CPUThrottlingSelector.ts +0 -235
  175. package/front_end/panels/timeline/components/NetworkThrottlingSelector.ts +0 -245
  176. package/front_end/panels/timeline/components/cpuThrottlingSelector.css +0 -30
  177. package/front_end/panels/timeline/components/networkThrottlingSelector.css +0 -24
@@ -1188,7 +1188,7 @@ export class ElementsTreeOutline extends
1188
1188
  if (!treeElement) {
1189
1189
  return false;
1190
1190
  }
1191
- return treeElement.isEditing() || false;
1191
+ return treeElement.isEditing || false;
1192
1192
  }
1193
1193
 
1194
1194
  update(): void {
@@ -1668,7 +1668,7 @@ export class StylesSidebarPane extends Common.ObjectWrapper.eventMixin<EventType
1668
1668
  this.#elementsForSyncViewportCheck = [];
1669
1669
  return;
1670
1670
  }
1671
- const scrollContainer = this.contentElement.parentElement;
1671
+ const scrollContainer = this.element.parentElement;
1672
1672
  if (!scrollContainer) {
1673
1673
  this.#elementsForSyncViewportCheck = [];
1674
1674
  return;
@@ -16,6 +16,7 @@ export enum RegisteredAdorners {
16
16
  FLEX = 'flex',
17
17
  GRID = 'grid',
18
18
  GRID_LANES = 'grid-lanes',
19
+ INTEREST = 'interest',
19
20
  MEDIA = 'media',
20
21
  POPOVER = 'popover',
21
22
  REVEAL = 'reveal',
@@ -9,14 +9,14 @@ import type * as Foundation from '../../foundation/foundation.js';
9
9
  import * as UI from '../../ui/legacy/legacy.js';
10
10
  import * as ThemeSupport from '../../ui/legacy/theme_support/theme_support.js';
11
11
 
12
- import {DeviceModeWrapper} from './DeviceModeWrapper.js';
12
+ import {DeviceModeView} from './DeviceModeView.js';
13
13
  import {type Bounds, Events, InspectedPagePlaceholder} from './InspectedPagePlaceholder.js';
14
14
 
15
15
  let appInstance: AdvancedApp|null = null;
16
16
 
17
17
  export class AdvancedApp implements UI.App.App {
18
18
  private rootSplitWidget!: UI.SplitWidget.SplitWidget;
19
- private deviceModeView!: DeviceModeWrapper;
19
+ private deviceModeView!: DeviceModeView;
20
20
  private inspectedPagePlaceholder!: InspectedPagePlaceholder;
21
21
  private toolboxWindow?: Window|null;
22
22
  private toolboxRootView?: UI.RootView.RootView;
@@ -58,7 +58,7 @@ export class AdvancedApp implements UI.App.App {
58
58
 
59
59
  this.inspectedPagePlaceholder = InspectedPagePlaceholder.instance();
60
60
  this.inspectedPagePlaceholder.addEventListener(Events.UPDATE, this.onSetInspectedPageBounds.bind(this), this);
61
- this.deviceModeView = new DeviceModeWrapper();
61
+ this.deviceModeView = new DeviceModeView();
62
62
 
63
63
  UI.DockController.DockController.instance().addEventListener(
64
64
  UI.DockController.Events.BEFORE_DOCK_SIDE_CHANGED, this.onBeforeDockSideChange, this);
@@ -117,14 +117,7 @@ const UIStrings = {
117
117
  * @example {4.3} PH1
118
118
  */
119
119
  defaultF: '{PH1} (default)',
120
- /**
121
- * @description Command to hide the frame around the mobile device screen.
122
- */
123
- hideDeviceFrame: 'Hide device frame',
124
- /**
125
- * @description Command to show the frame around the mobile device screen.
126
- */
127
- showDeviceFrame: 'Show device frame',
120
+
128
121
  /**
129
122
  * @description Command to hide a display in the device mode toolbar that shows the different media
130
123
  * queries for the device, above the device screen.
@@ -801,8 +794,7 @@ export class DeviceModeToolbar extends UI.Widget.Widget {
801
794
  return;
802
795
  }
803
796
  const model = this.model;
804
- appendToggleItem(contextMenu.headerSection(), model.deviceOutlineSetting(), i18nString(UIStrings.hideDeviceFrame),
805
- i18nString(UIStrings.showDeviceFrame), !model.canShowDeviceFrame(), 'device-frame');
797
+
806
798
  appendToggleItem(
807
799
  contextMenu.headerSection(), this.showMediaInspectorSetting, i18nString(UIStrings.hideMediaQueries),
808
800
  i18nString(UIStrings.showMediaQueries), undefined, 'media-queries');
@@ -839,7 +831,6 @@ export class DeviceModeToolbar extends UI.Widget.Widget {
839
831
  }
840
832
 
841
833
  private reset(): void {
842
- this.model?.deviceOutlineSetting().set(false);
843
834
  this.showDeviceScaleFactorSetting.set(false);
844
835
  this.showUserAgentTypeSetting.set(false);
845
836
  this.showMediaInspectorSetting.set(false);
@@ -5,6 +5,9 @@
5
5
  import * as Common from '../../core/common/common.js';
6
6
  import * as Host from '../../core/host/host.js';
7
7
  import * as i18n from '../../core/i18n/i18n.js';
8
+ import * as Root from '../../core/root/root.js';
9
+ import * as SDK from '../../core/sdk/sdk.js';
10
+ import type * as Protocol from '../../generated/protocol.js';
8
11
  import * as EmulationModel from '../../models/emulation/emulation.js';
9
12
  import * as Geometry from '../../models/geometry/geometry.js';
10
13
  import * as UI from '../../ui/legacy/legacy.js';
@@ -13,6 +16,7 @@ import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
13
16
 
14
17
  import {DeviceModeToolbar} from './DeviceModeToolbar.js';
15
18
  import deviceModeViewStyles from './deviceModeView.css.js';
19
+ import {InspectedPagePlaceholder} from './InspectedPagePlaceholder.js';
16
20
  import {MediaQueryInspector} from './MediaQueryInspector.js';
17
21
 
18
22
  const {classMap, ref, styleMap} = Directives;
@@ -59,19 +63,15 @@ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
59
63
 
60
64
  export interface DeviceModeViewInput {
61
65
  model: EmulationModel.DeviceModeModel.DeviceModeModel;
66
+ showDeviceMode: boolean;
62
67
  showMediaInspectorSetting: Common.Settings.Setting<boolean>;
63
68
  showRulersSetting: Common.Settings.Setting<boolean>;
64
- outlineImage: string;
65
- outlineImageLoaded: boolean;
66
- screenImage: string;
67
- screenImageLoaded: boolean;
68
69
  resizable: boolean;
69
70
  showRulers: boolean;
70
71
  showMediaInspector: boolean;
71
72
  scale: number;
72
73
  cachedCssScreenRect?: EmulationModel.DeviceModeModel.Rect;
73
74
  cachedCssVisiblePageRect?: EmulationModel.DeviceModeModel.Rect;
74
- cachedOutlineRect?: EmulationModel.DeviceModeModel.Rect;
75
75
  onApplyPresetSize: (size: number, e: Event) => void;
76
76
  bottomRightResizer: UI.ResizerWidget.ResizerWidget;
77
77
  bottomLeftResizer: UI.ResizerWidget.ResizerWidget;
@@ -84,8 +84,6 @@ export interface DeviceModeViewInput {
84
84
  leftResizerRef: (el?: Element) => void;
85
85
  bottomResizerRef: (el?: Element) => void;
86
86
  onDoubleclickBottomResizer: () => void;
87
- onOutlineImageLoaded: (success: boolean) => void;
88
- onScreenImageLoaded: (success: boolean) => void;
89
87
  }
90
88
 
91
89
  export type DeviceModeViewView = (
@@ -133,12 +131,11 @@ export const DEFAULT_DEVICE_MODE_VIEW: DeviceModeViewView = (
133
131
  '4K',
134
132
  ];
135
133
  // clang-format off
136
- render(html`
137
- ${UI.Widget.widget(DeviceModeToolbar, {model: input.model})}
134
+ render(input.showDeviceMode
135
+ ? html`${UI.Widget.widget(DeviceModeToolbar, {model: input.model})}
138
136
  <div class=${classMap({
139
137
  'device-mode-content-clip': true,
140
138
  vbox: true,
141
- 'device-mode-outline-visible': Boolean(input.outlineImage),
142
139
  'device-mode-rulers-visible': input.showRulers,
143
140
  })}>
144
141
  <div class="device-mode-presets-container" jslog=${VisualLogging.responsivePresets()}>
@@ -163,17 +160,6 @@ export const DEFAULT_DEVICE_MODE_VIEW: DeviceModeViewView = (
163
160
  }) : nothing}
164
161
  </div>
165
162
  <div class="device-mode-content-area">
166
- <img class="device-mode-outline-image fill"
167
- ?hidden=${!input.outlineImage || !input.outlineImageLoaded}
168
- style=${styleMap(input.cachedOutlineRect ? {
169
- left: `${input.cachedOutlineRect.left}px`,
170
- top: `${input.cachedOutlineRect.top}px`,
171
- width: `${input.cachedOutlineRect.width}px`,
172
- height: `${input.cachedOutlineRect.height}px`,
173
- } : {})}
174
- srcset=${input.outlineImage || nothing}
175
- @load=${(): void => input.onOutlineImageLoaded(true)}
176
- @error=${(): void => input.onOutlineImageLoaded(false)}>
177
163
  <div class="device-mode-screen-area"
178
164
  style=${styleMap(input.cachedCssScreenRect ? {
179
165
  left: `${input.cachedCssScreenRect.left}px`,
@@ -181,11 +167,6 @@ export const DEFAULT_DEVICE_MODE_VIEW: DeviceModeViewView = (
181
167
  width: `${input.cachedCssScreenRect.width}px`,
182
168
  height: `${input.cachedCssScreenRect.height}px`,
183
169
  } : {})}>
184
- <img class="device-mode-screen-image"
185
- ?hidden=${!input.screenImage || !input.screenImageLoaded}
186
- srcset=${input.screenImage || nothing}
187
- @load=${(): void => input.onScreenImageLoaded(true)}
188
- @error=${(): void => input.onScreenImageLoaded(false)}>
189
170
  <div class="device-mode-resizer device-mode-bottom-right-resizer"
190
171
  ?hidden=${!input.resizable}
191
172
  jslog=${VisualLogging.slider('device-mode-resizer').track({drag: true})}
@@ -224,7 +205,9 @@ export const DEFAULT_DEVICE_MODE_VIEW: DeviceModeViewView = (
224
205
  top: `${input.cachedCssVisiblePageRect.top}px`,
225
206
  width: `${input.cachedCssVisiblePageRect.width}px`,
226
207
  height: `${input.cachedCssVisiblePageRect.height}px`,
227
- } : {})}><slot></slot></div>
208
+ } : {})}>
209
+ ${widget(() => InspectedPagePlaceholder.instance(), {minimumSize: new Geometry.Size(1, 1)})}
210
+ </div>
228
211
  </div>
229
212
  ${input.showRulers ? html`
230
213
  <devtools-widget class="device-mode-ruler-top device-mode-ruler"
@@ -246,7 +229,9 @@ export const DEFAULT_DEVICE_MODE_VIEW: DeviceModeViewView = (
246
229
  ` : nothing}
247
230
  </div>
248
231
  </div>
249
- `, target, {
232
+ `
233
+ : widget(() => InspectedPagePlaceholder.instance(), {minimumSize: new Geometry.Size(150, 150)}),
234
+ target, {
250
235
  container: {
251
236
  classes: ['device-mode-view'],
252
237
  },
@@ -276,14 +261,11 @@ export class DeviceModeView extends UI.Widget.VBox {
276
261
  private resizeStart?: Geometry.Size;
277
262
  private cachedCssScreenRect?: EmulationModel.DeviceModeModel.Rect;
278
263
  private cachedCssVisiblePageRect?: EmulationModel.DeviceModeModel.Rect;
279
- private cachedOutlineRect?: EmulationModel.DeviceModeModel.Rect;
280
264
  private cachedMediaInspectorVisible?: boolean;
281
265
  private cachedShowRulers?: boolean;
282
266
  private cachedScale?: number;
283
- #outlineImageLoaded = false;
284
- #lastOutlineImageSrc?: string;
285
- #screenImageLoaded = false;
286
- #lastScreenImageSrc?: string;
267
+ readonly #toggleDeviceModeAction: UI.ActionRegistration.Action;
268
+ readonly #showDeviceModeSetting: Common.Settings.Setting<boolean>;
287
269
  #view: DeviceModeViewView;
288
270
 
289
271
  constructor(element?: HTMLElement, view: DeviceModeViewView = DEFAULT_DEVICE_MODE_VIEW) {
@@ -300,27 +282,27 @@ export class DeviceModeView extends UI.Widget.VBox {
300
282
  this.showRulersSetting = Common.Settings.Settings.instance().moduleSetting('emulation.show-rulers');
301
283
  this.showRulersSetting.addChangeListener(this.updateUI, this);
302
284
 
285
+ this.#toggleDeviceModeAction =
286
+ UI.ActionRegistry.ActionRegistry.instance().getAction('emulation.toggle-device-mode');
287
+ const model = EmulationModel.DeviceModeModel.DeviceModeModel.instance();
288
+ this.#showDeviceModeSetting = model.enabledSetting();
289
+ this.#showDeviceModeSetting.setRequiresUserAction(Boolean(Root.Runtime.Runtime.queryParam('hasOtherClients')));
290
+ this.#showDeviceModeSetting.addChangeListener(this.requestUpdate.bind(this));
291
+ SDK.TargetManager.TargetManager.instance().addModelListener(SDK.OverlayModel.OverlayModel,
292
+ SDK.OverlayModel.Events.SCREENSHOT_REQUESTED,
293
+ this.screenshotRequestedFromOverlay, this);
294
+
303
295
  this.performUpdate();
304
296
  UI.ZoomManager.ZoomManager.instance().addEventListener(UI.ZoomManager.Events.ZOOM_CHANGED, this.zoomChanged, this);
305
297
  }
306
298
 
307
299
  override performUpdate(): void {
308
- if (this.#lastOutlineImageSrc !== this.model.outlineImage()) {
309
- this.#lastOutlineImageSrc = this.model.outlineImage();
310
- this.#outlineImageLoaded = false;
311
- }
312
- if (this.#lastScreenImageSrc !== this.model.screenImage()) {
313
- this.#lastScreenImageSrc = this.model.screenImage();
314
- this.#screenImageLoaded = false;
315
- }
300
+ this.#toggleDeviceModeAction.setToggled(this.#showDeviceModeSetting.get());
316
301
  const input: DeviceModeViewInput = {
317
302
  model: this.model,
303
+ showDeviceMode: this.#showDeviceModeSetting.get(),
318
304
  showMediaInspectorSetting: this.showMediaInspectorSetting,
319
305
  showRulersSetting: this.showRulersSetting,
320
- outlineImage: this.model.outlineImage(),
321
- outlineImageLoaded: this.#outlineImageLoaded,
322
- screenImage: this.model.screenImage(),
323
- screenImageLoaded: this.#screenImageLoaded,
324
306
  resizable: this.model.type() === EmulationModel.DeviceModeModel.Type.Responsive,
325
307
  showRulers: this.showRulersSetting.get() && this.model.type() !== EmulationModel.DeviceModeModel.Type.None,
326
308
  showMediaInspector:
@@ -328,7 +310,6 @@ export class DeviceModeView extends UI.Widget.VBox {
328
310
  scale: this.model.scale(),
329
311
  cachedCssScreenRect: this.cachedCssScreenRect,
330
312
  cachedCssVisiblePageRect: this.cachedCssVisiblePageRect,
331
- cachedOutlineRect: this.cachedOutlineRect,
332
313
  onApplyPresetSize: (width: number, e: Event): void => {
333
314
  this.model.emulate(EmulationModel.DeviceModeModel.Type.Responsive, null, null);
334
315
  this.model.setWidthAndScaleToFit(width);
@@ -345,24 +326,40 @@ export class DeviceModeView extends UI.Widget.VBox {
345
326
  leftResizerRef: this.leftResizerRef,
346
327
  bottomResizerRef: this.bottomResizerRef,
347
328
  onDoubleclickBottomResizer: (): void => this.model.setHeight(0),
348
- onOutlineImageLoaded: (success: boolean): void => this.onOutlineImageLoaded(success),
349
- onScreenImageLoaded: (success: boolean): void => this.onScreenImageLoaded(success),
350
329
  };
351
330
  this.#view(input, undefined, this.contentElement);
352
331
  }
353
332
 
354
- private onOutlineImageLoaded(success: boolean): void {
355
- if (this.#outlineImageLoaded !== success) {
356
- this.#outlineImageLoaded = success;
357
- this.requestUpdate();
333
+ static #setNonEmulatedAvailableSize(): void {
334
+ const model = EmulationModel.DeviceModeModel.DeviceModeModel.instance();
335
+ if (model.type() !== EmulationModel.DeviceModeModel.Type.None) {
336
+ return;
358
337
  }
338
+
339
+ const zoomFactor = UI.ZoomManager.ZoomManager.instance().zoomFactor();
340
+ const rect = InspectedPagePlaceholder.instance().element.getBoundingClientRect();
341
+ const availableSize =
342
+ new Geometry.Size(Math.max(rect.width * zoomFactor, 1), Math.max(rect.height * zoomFactor, 1));
343
+ model.setAvailableSize(availableSize, availableSize);
359
344
  }
360
345
 
361
- private onScreenImageLoaded(success: boolean): void {
362
- if (this.#screenImageLoaded !== success) {
363
- this.#screenImageLoaded = success;
364
- this.requestUpdate();
346
+ static captureScreenshot(fullSize?: boolean, clip?: Protocol.Page.Viewport): boolean {
347
+ const model = EmulationModel.DeviceModeModel.DeviceModeModel.instance();
348
+
349
+ this.#setNonEmulatedAvailableSize();
350
+ if (fullSize) {
351
+ void model.captureFullSizeScreenshot();
352
+ } else if (clip) {
353
+ void model.captureAreaScreenshot(clip);
354
+ } else {
355
+ void model.captureScreenshot();
365
356
  }
357
+ return true;
358
+ }
359
+
360
+ private screenshotRequestedFromOverlay(event: Common.EventTarget.EventTargetEvent<Protocol.Page.Viewport>): void {
361
+ const clip = event.data;
362
+ DeviceModeView.captureScreenshot(false, clip);
366
363
  }
367
364
 
368
365
  private createResizer(widthFactor: number, heightFactor: number): UI.ResizerWidget.ResizerWidget {
@@ -375,10 +372,20 @@ export class DeviceModeView extends UI.Widget.VBox {
375
372
  cursor = 'nesw-resize';
376
373
  }
377
374
  resizer.setCursor(cursor);
378
- resizer.addEventListener(UI.ResizerWidget.Events.RESIZE_START, this.onResizeStart, this);
379
375
  resizer.addEventListener(
380
- UI.ResizerWidget.Events.RESIZE_UPDATE_XY, this.onResizeUpdate.bind(this, widthFactor, heightFactor));
381
- resizer.addEventListener(UI.ResizerWidget.Events.RESIZE_END, this.onResizeEnd, this);
376
+ UI.ResizerWidget.Events.RESIZE_START,
377
+ this.onResizeStart,
378
+ this,
379
+ );
380
+ resizer.addEventListener(
381
+ UI.ResizerWidget.Events.RESIZE_UPDATE_XY,
382
+ this.onResizeUpdate.bind(this, widthFactor, heightFactor),
383
+ );
384
+ resizer.addEventListener(
385
+ UI.ResizerWidget.Events.RESIZE_END,
386
+ this.onResizeEnd,
387
+ this,
388
+ );
382
389
  return resizer;
383
390
  }
384
391
 
@@ -456,15 +463,6 @@ export class DeviceModeView extends UI.Widget.VBox {
456
463
  this.cachedCssVisiblePageRect = cssVisiblePageRect;
457
464
  }
458
465
 
459
- const outlineRectFromModel = this.model.outlineRect();
460
- if (outlineRectFromModel) {
461
- const outlineRect = outlineRectFromModel.scale(1 / zoomFactor);
462
- if (!this.cachedOutlineRect || !outlineRect.isEqual(this.cachedOutlineRect)) {
463
- callDoResize = true;
464
- this.cachedOutlineRect = outlineRect;
465
- }
466
- }
467
-
468
466
  const mediaInspectorVisible =
469
467
  this.showMediaInspectorSetting.get() && this.model.type() !== EmulationModel.DeviceModeModel.Type.None;
470
468
  if (mediaInspectorVisible !== this.cachedMediaInspectorVisible) {
@@ -485,12 +483,14 @@ export class DeviceModeView extends UI.Widget.VBox {
485
483
  }
486
484
 
487
485
  this.requestUpdate();
488
- if (callDoResize) {
489
- this.doResize();
490
- }
491
- if (contentAreaResized) {
492
- this.contentAreaResized();
493
- }
486
+ void this.updateComplete.then(() => {
487
+ if (callDoResize) {
488
+ this.doResize();
489
+ }
490
+ if (contentAreaResized) {
491
+ this.contentAreaResized();
492
+ }
493
+ });
494
494
  }
495
495
 
496
496
  private contentAreaResized(): void {
@@ -498,8 +498,8 @@ export class DeviceModeView extends UI.Widget.VBox {
498
498
  if (!contentArea) {
499
499
  return;
500
500
  }
501
- const zoomFactor = UI.ZoomManager.ZoomManager.instance().zoomFactor();
502
501
  const rect = contentArea.getBoundingClientRect();
502
+ const zoomFactor = UI.ZoomManager.ZoomManager.instance().zoomFactor();
503
503
  const handleWidth = this.contentElement.querySelector<HTMLElement>('.device-mode-right-resizer')?.offsetWidth || 20;
504
504
  const handleHeight =
505
505
  this.contentElement.querySelector<HTMLElement>('.device-mode-bottom-resizer')?.offsetHeight || 20;
@@ -673,3 +673,162 @@ export class Ruler extends Common.ObjectWrapper.eventMixin<RulerEventTypes, type
673
673
  this.#view(viewInput, undefined, this.contentElement);
674
674
  }
675
675
  }
676
+
677
+ export class ActionDelegate implements UI.ActionRegistration.ActionDelegate {
678
+ handleAction(context: UI.Context.Context, actionId: string): boolean {
679
+ switch (actionId) {
680
+ case 'emulation.capture-screenshot':
681
+ return DeviceModeView.captureScreenshot();
682
+
683
+ case 'emulation.capture-node-screenshot': {
684
+ const node = context.flavor(SDK.DOMModel.DOMNode);
685
+ if (!node) {
686
+ return true;
687
+ }
688
+ async function captureClip(): Promise<void> {
689
+ if (!node) {
690
+ return;
691
+ }
692
+
693
+ // Resolve to a remote object to ensure the node is alive in the context.
694
+ const object = await node.resolveToObject();
695
+ if (!object) {
696
+ return;
697
+ }
698
+
699
+ // Get the Box Model via CDP.
700
+ // This returns the quads relative to the target's viewport.
701
+ // We use the 'border' quad to include the border and padding in the screenshot,
702
+ // matching the 'width' and 'height' properties which are also Border Box dimensions.
703
+ const nodeBoxModel = await node.boxModel();
704
+ if (!nodeBoxModel) {
705
+ throw new Error(`Unable to get box model of the node: ${new Error().stack}`);
706
+ }
707
+ const nodeBorderQuad = nodeBoxModel.border;
708
+ // Calculate the rendered bounding box from the border quad to account for CSS scaling and transforms (e.g. zoom or transform: scale).
709
+ const {minX, maxX, minY, maxY} = getQuadBoundingBox(nodeBorderQuad);
710
+
711
+ // Get Layout Metrics to account for the Visual Viewport scroll and zoom.
712
+ const metrics = await node.domModel().target().pageAgent().invoke_getLayoutMetrics();
713
+ if (metrics.getError()) {
714
+ throw new Error(`Unable to get metrics: ${new Error().stack}`);
715
+ }
716
+
717
+ const scrollX = metrics.cssVisualViewport.pageX;
718
+ const scrollY = metrics.cssVisualViewport.pageY;
719
+
720
+ // Calculate the global offset for OOPiFs (Out-of-Process iframes).
721
+ // This accounts for the position of the target's frame within the main page.
722
+ const {x: oopifOffsetX, y: oopifOffsetY} = await getOopifOffset(node.domModel().target());
723
+
724
+ // Assemble the final Clip.
725
+ // The absolute coordinates are: Global (OOPiF) + Viewport Scroll + Local Node Position (Border Box).
726
+ const clip = {
727
+ x: oopifOffsetX + scrollX + minX,
728
+ y: oopifOffsetY + scrollY + minY,
729
+ width: maxX - minX,
730
+ height: maxY - minY,
731
+ scale: 1,
732
+ };
733
+
734
+ // Apply Zoom factor.
735
+ const zoom = metrics.cssVisualViewport.zoom ?? 1;
736
+ clip.x *= zoom;
737
+ clip.y *= zoom;
738
+ clip.width *= zoom;
739
+ clip.height *= zoom;
740
+ DeviceModeView.captureScreenshot(false, clip);
741
+ }
742
+ void captureClip();
743
+ return true;
744
+ }
745
+
746
+ case 'emulation.capture-full-height-screenshot':
747
+ return DeviceModeView.captureScreenshot(true);
748
+
749
+ case 'emulation.toggle-device-mode': {
750
+ const model = EmulationModel.DeviceModeModel.DeviceModeModel.instance();
751
+ model.toggleDeviceMode();
752
+ return true;
753
+ }
754
+ }
755
+ return false;
756
+ }
757
+ }
758
+
759
+ /**
760
+ * Calculate the offset of the "Local Root" frame relative to the "Global Root" (the main frame).
761
+ * This involves traversing the CDP Targets for OOPiFs.
762
+ */
763
+ async function getOopifOffset(target: SDK.Target.Target|null): Promise<{x: number, y: number}> {
764
+ if (!target) {
765
+ return {x: 0, y: 0};
766
+ }
767
+
768
+ // Get the parent target. If there's no parent (we are at root) or it's not a frame, we are done.
769
+ const parentTarget = target.parentTarget();
770
+ if (!parentTarget || parentTarget.type() !== SDK.Target.Type.FRAME) {
771
+ return {x: 0, y: 0};
772
+ }
773
+
774
+ // Identify the current frame's ID to find its owner in the parent.
775
+ const frameId = target.model(SDK.ResourceTreeModel.ResourceTreeModel)?.mainFrame?.id;
776
+ if (!frameId) {
777
+ return {x: 0, y: 0};
778
+ }
779
+
780
+ // Get the DOMModel of the parent to query the frame owner element.
781
+ const parentDOMModel = parentTarget.model(SDK.DOMModel.DOMModel);
782
+ if (!parentDOMModel) {
783
+ return {x: 0, y: 0};
784
+ }
785
+
786
+ // Retrieve the frame owner node (e.g. the <iframe> element) in the parent's document.
787
+ const frameOwnerDeferred = await parentDOMModel.getOwnerNodeForFrame(frameId);
788
+ const frameOwner = await frameOwnerDeferred?.resolvePromise();
789
+ if (!frameOwner) {
790
+ return {x: 0, y: 0};
791
+ }
792
+
793
+ // Get the content box of the iframe element.
794
+ // This is relative to the parent target's viewport.
795
+ const boxModel = await frameOwner.boxModel();
796
+ if (!boxModel) {
797
+ return {x: 0, y: 0};
798
+ }
799
+
800
+ // content is a Quad [x1, y1, x2, y2, x3, y3, x4, y4]
801
+ const contentQuad = boxModel.content;
802
+ const iframeContentX = contentQuad[0];
803
+ const iframeContentY = contentQuad[1];
804
+
805
+ // Get the scroll position of the parent target to convert viewport-relative coordinates
806
+ // to document-relative coordinates.
807
+ const parentMetrics = await parentTarget.pageAgent().invoke_getLayoutMetrics();
808
+ if (parentMetrics.getError()) {
809
+ return {x: 0, y: 0};
810
+ }
811
+
812
+ const scrollX = parentMetrics.cssVisualViewport.pageX;
813
+ const scrollY = parentMetrics.cssVisualViewport.pageY;
814
+
815
+ // Recursively add the offset of the parent target itself (if it is also an OOPiF).
816
+ const parentOffset = await getOopifOffset(parentTarget);
817
+
818
+ return {
819
+ x: iframeContentX + scrollX + parentOffset.x,
820
+ y: iframeContentY + scrollY + parentOffset.y,
821
+ };
822
+ }
823
+
824
+ /**
825
+ * Calculate the axis-aligned bounding box for a Quad [x1, y1, x2, y2, x3, y3, x4, y4].
826
+ * This accounts for CSS scaling and transforms (e.g. zoom, transform: scale).
827
+ */
828
+ function getQuadBoundingBox(quad: Protocol.DOM.Quad): {minX: number, maxX: number, minY: number, maxY: number} {
829
+ const minX = Math.min(quad[0], quad[2], quad[4], quad[6]);
830
+ const maxX = Math.max(quad[0], quad[2], quad[4], quad[6]);
831
+ const minY = Math.min(quad[1], quad[3], quad[5], quad[7]);
832
+ const maxY = Math.max(quad[1], quad[3], quad[5], quad[7]);
833
+ return {minX, maxX, minY, maxY};
834
+ }
@@ -20,7 +20,7 @@ export class InspectedPagePlaceholder extends Common.ObjectWrapper.eventMixin<Ev
20
20
  }
21
21
 
22
22
  static instance(opts: {
23
- forceNew: null,
23
+ forceNew: boolean|null,
24
24
  } = {forceNew: null}): InspectedPagePlaceholder {
25
25
  const {forceNew} = opts;
26
26
  if (!inspectedPagePlaceholderInstance || forceNew) {
@@ -83,7 +83,7 @@ devtools-toolbar.device-mode-toolbar-options {
83
83
  box-shadow: inset 0 -1px var(--sys-color-divider);
84
84
  }
85
85
 
86
- .device-mode-content-clip:not(.device-mode-outline-visible) .device-mode-media-container {
86
+ .device-mode-content-clip .device-mode-media-container {
87
87
  margin-bottom: 20px;
88
88
  }
89
89
 
@@ -157,25 +157,17 @@ devtools-toolbar.device-mode-toolbar-options {
157
157
  background-color: var(--sys-color-inverse-surface);
158
158
  }
159
159
 
160
- .device-mode-content-clip:not(.device-mode-outline-visible) .device-mode-screen-area {
160
+ .device-mode-content-clip .device-mode-screen-area {
161
161
  --override-screen-area-box-shadow: hsl(240deg 3% 84%) 0 0 0 0.5px, hsl(0deg 0% 80% / 40%) 0 0 20px;
162
162
 
163
163
  box-shadow: var(--override-screen-area-box-shadow);
164
164
  }
165
165
 
166
- .theme-with-dark-background .device-mode-content-clip:not(.device-mode-outline-visible) .device-mode-screen-area,
167
- :host-context(.theme-with-dark-background) .device-mode-content-clip:not(.device-mode-outline-visible) .device-mode-screen-area {
166
+ .theme-with-dark-background .device-mode-content-clip .device-mode-screen-area,
167
+ :host-context(.theme-with-dark-background) .device-mode-content-clip .device-mode-screen-area {
168
168
  --override-screen-area-box-shadow: rgb(40 40 42) 0 0 0 0.5px, rgb(51 51 51 / 40%) 0 0 20px;
169
169
  }
170
170
 
171
- .device-mode-screen-image {
172
- position: absolute;
173
- left: 0;
174
- top: 0;
175
- width: 100%;
176
- height: 100%;
177
- }
178
-
179
171
  .device-mode-resizer {
180
172
  position: absolute;
181
173
  display: flex;
@@ -316,15 +308,6 @@ devtools-toolbar.device-mode-toolbar-options {
316
308
  bottom: 0;
317
309
  }
318
310
 
319
- .device-mode-content-clip.device-mode-outline-visible .device-mode-ruler-top .device-mode-ruler-content {
320
- border-top: 1px solid var(--sys-color-token-subtle);
321
- }
322
-
323
- .device-mode-content-clip.device-mode-outline-visible .device-mode-ruler-left .device-mode-ruler-content {
324
- border-left: 1px solid var(--sys-color-token-subtle);
325
- border-top: 1px solid var(--sys-color-token-subtle);
326
- }
327
-
328
311
  .device-mode-ruler-inner {
329
312
  position: absolute;
330
313
  }