chrome-devtools-frontend 1.0.1625079 → 1.0.1626437

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 (190) hide show
  1. package/front_end/core/common/VersionController.ts +9 -7
  2. package/front_end/core/common/common.ts +0 -4
  3. package/front_end/core/host/AidaClient.ts +1 -1
  4. package/front_end/core/host/AidaClientTypes.ts +2 -0
  5. package/front_end/core/host/AidaGcaTranslation.ts +2 -2
  6. package/front_end/core/host/Platform.ts +0 -9
  7. package/front_end/core/host/UserMetrics.ts +0 -55
  8. package/front_end/core/root/ExperimentNames.ts +0 -1
  9. package/front_end/core/sdk/CPUThrottlingManager.ts +1 -1
  10. package/front_end/core/sdk/CSSMetadata.ts +60 -63
  11. package/front_end/core/sdk/CSSProperty.ts +1 -5
  12. package/front_end/core/sdk/CSSPropertyParserMatchers.ts +11 -32
  13. package/front_end/{panels/application → core/sdk}/DOMStorageModel.ts +12 -10
  14. package/front_end/core/sdk/sdk.ts +2 -0
  15. package/front_end/entrypoints/main/MainImpl.ts +12 -8
  16. package/front_end/entrypoints/main/SimpleApp.ts +3 -4
  17. package/front_end/entrypoints/main/main-meta.ts +1 -1
  18. package/front_end/generated/InspectorBackendCommands.ts +3 -3
  19. package/front_end/generated/SupportedCSSProperties.js +21 -21
  20. package/front_end/generated/protocol.ts +4 -1
  21. package/front_end/models/ai_assistance/StorageItem.ts +16 -0
  22. package/front_end/models/ai_assistance/agents/StorageAgent.ts +82 -0
  23. package/front_end/models/ai_assistance/ai_assistance.ts +4 -0
  24. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +9 -37
  25. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +26 -3
  26. package/front_end/models/ai_code_completion/AiCodeCompletion.ts +10 -2
  27. package/front_end/models/ai_code_generation/AiCodeGeneration.ts +5 -1
  28. package/front_end/models/issues_manager/CookieIssue.ts +0 -4
  29. package/front_end/models/issues_manager/RelatedIssue.ts +0 -14
  30. package/front_end/models/javascript_metadata/NativeFunctions.js +0 -4
  31. package/front_end/models/web_mcp/WebMCPModel.ts +11 -2
  32. package/front_end/models/workspace/IgnoreListManager.ts +1 -1
  33. package/front_end/models/workspace/UISourceCode.ts +10 -5
  34. package/front_end/panels/ai_assistance/ai_assistance-meta.ts +6 -1
  35. package/front_end/panels/ai_assistance/components/ChatMessage.ts +26 -9
  36. package/front_end/panels/application/ApplicationPanelSidebar.ts +32 -42
  37. package/front_end/panels/application/BackForwardCacheTreeElement.ts +2 -2
  38. package/front_end/panels/application/BounceTrackingMitigationsTreeElement.ts +2 -2
  39. package/front_end/panels/application/DOMStorageItemsView.ts +18 -13
  40. package/front_end/panels/application/InterestGroupTreeElement.ts +2 -2
  41. package/front_end/panels/application/ReportingApiTreeElement.ts +3 -3
  42. package/front_end/panels/application/ResourcesPanel.ts +1 -2
  43. package/front_end/panels/application/ServiceWorkerCacheTreeElement.ts +1 -2
  44. package/front_end/panels/application/StorageView.ts +1 -2
  45. package/front_end/panels/application/TrustTokensTreeElement.ts +2 -2
  46. package/front_end/panels/application/WebMCPTreeElement.ts +2 -2
  47. package/front_end/panels/application/WebMCPView.ts +74 -16
  48. package/front_end/panels/application/application.ts +0 -2
  49. package/front_end/panels/application/webMCPView.css +13 -2
  50. package/front_end/panels/console/ConsolePrompt.ts +1 -1
  51. package/front_end/panels/console/ConsoleView.ts +3 -4
  52. package/front_end/panels/elements/ColorSwatchPopoverIcon.ts +0 -152
  53. package/front_end/panels/elements/ElementsTreeElement.ts +4 -9
  54. package/front_end/panels/elements/StandaloneStylesContainer.ts +3 -0
  55. package/front_end/panels/elements/StylePropertiesSection.ts +0 -61
  56. package/front_end/panels/elements/StylePropertyTreeElement.ts +36 -23
  57. package/front_end/panels/elements/StylesAiCodeCompletionProvider.ts +6 -4
  58. package/front_end/panels/elements/StylesContainer.ts +1 -0
  59. package/front_end/panels/elements/StylesSidebarPane.ts +12 -7
  60. package/front_end/panels/elements/stylesSidebarPane.css +0 -8
  61. package/front_end/panels/emulation/AdvancedApp.ts +3 -3
  62. package/front_end/panels/emulation/DeviceModeToolbar.ts +207 -377
  63. package/front_end/panels/emulation/emulation-meta.ts +1 -1
  64. package/front_end/panels/network/NetworkPanel.ts +1 -1
  65. package/front_end/panels/profiler/HeapProfilerPanel.ts +1 -2
  66. package/front_end/panels/screencast/ScreencastApp.ts +3 -3
  67. package/front_end/panels/screencast/screencast-meta.ts +1 -2
  68. package/front_end/panels/settings/SettingsScreen.ts +2 -2
  69. package/front_end/panels/sources/AiCodeCompletionPlugin.ts +2 -4
  70. package/front_end/panels/sources/ScopeChainSidebarPane.ts +5 -1
  71. package/front_end/panels/sources/SourcesNavigator.ts +1 -1
  72. package/front_end/panels/sources/UISourceCodeFrame.ts +1 -3
  73. package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +179 -145
  74. package/front_end/panels/timeline/TimelinePanel.ts +2 -2
  75. package/front_end/panels/timeline/timelineTreeView.css +2 -2
  76. package/front_end/third_party/chromium/README.chromium +1 -1
  77. package/front_end/third_party/puppeteer/README.chromium +2 -2
  78. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.d.ts +2 -2
  79. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/locators/locators.d.ts +3 -2
  80. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/locators/locators.d.ts.map +1 -1
  81. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/locators/locators.js +53 -23
  82. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/locators/locators.js.map +1 -1
  83. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.d.ts +3 -2
  84. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.d.ts.map +1 -1
  85. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.js +18 -8
  86. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.js.map +1 -1
  87. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/BrowserConnector.d.ts.map +1 -1
  88. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/BrowserConnector.js +2 -2
  89. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/BrowserConnector.js.map +1 -1
  90. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.d.ts.map +1 -1
  91. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.js +4 -0
  92. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.js.map +1 -1
  93. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/TargetManager.d.ts +1 -1
  94. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/TargetManager.d.ts.map +1 -1
  95. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/TargetManager.js +54 -19
  96. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/TargetManager.js.map +1 -1
  97. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/WebMCP.d.ts +5 -0
  98. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/WebMCP.d.ts.map +1 -1
  99. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/WebMCP.js +2 -9
  100. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/WebMCP.js.map +1 -1
  101. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/common/BrowserConnector.d.ts.map +1 -1
  102. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/common/BrowserConnector.js +3 -0
  103. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/common/BrowserConnector.js.map +1 -1
  104. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/common/ConnectOptions.d.ts +37 -1
  105. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/common/ConnectOptions.d.ts.map +1 -1
  106. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
  107. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/BrowserLauncher.d.ts.map +1 -1
  108. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/BrowserLauncher.js +5 -2
  109. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/BrowserLauncher.js.map +1 -1
  110. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
  111. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js +1 -0
  112. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js.map +1 -1
  113. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +3 -3
  114. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +3 -3
  115. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  116. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.d.ts +1 -1
  117. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.js +1 -1
  118. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +47 -65
  119. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +143 -55
  120. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.d.ts +2 -2
  121. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/locators/locators.d.ts +3 -2
  122. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/locators/locators.d.ts.map +1 -1
  123. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/locators/locators.js +53 -23
  124. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/locators/locators.js.map +1 -1
  125. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.d.ts +3 -2
  126. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.d.ts.map +1 -1
  127. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.js +18 -8
  128. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.js.map +1 -1
  129. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/BrowserConnector.d.ts.map +1 -1
  130. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/BrowserConnector.js +2 -2
  131. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/BrowserConnector.js.map +1 -1
  132. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.d.ts.map +1 -1
  133. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.js +4 -0
  134. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.js.map +1 -1
  135. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/TargetManager.d.ts +1 -1
  136. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/TargetManager.d.ts.map +1 -1
  137. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/TargetManager.js +54 -19
  138. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/TargetManager.js.map +1 -1
  139. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/WebMCP.d.ts +5 -0
  140. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/WebMCP.d.ts.map +1 -1
  141. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/WebMCP.js +2 -9
  142. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/WebMCP.js.map +1 -1
  143. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/common/BrowserConnector.d.ts.map +1 -1
  144. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/common/BrowserConnector.js +3 -0
  145. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/common/BrowserConnector.js.map +1 -1
  146. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/common/ConnectOptions.d.ts +37 -1
  147. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/common/ConnectOptions.d.ts.map +1 -1
  148. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/BrowserLauncher.d.ts.map +1 -1
  149. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/BrowserLauncher.js +5 -2
  150. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/BrowserLauncher.js.map +1 -1
  151. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
  152. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js +1 -0
  153. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js.map +1 -1
  154. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +3 -3
  155. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +3 -3
  156. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.d.ts +1 -1
  157. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.js +1 -1
  158. package/front_end/third_party/puppeteer/package/lib/types.d.ts +47 -65
  159. package/front_end/third_party/puppeteer/package/package.json +6 -6
  160. package/front_end/third_party/puppeteer/package/src/api/Page.ts +2 -2
  161. package/front_end/third_party/puppeteer/package/src/api/locators/locators.ts +69 -27
  162. package/front_end/third_party/puppeteer/package/src/cdp/Browser.ts +27 -8
  163. package/front_end/third_party/puppeteer/package/src/cdp/BrowserConnector.ts +4 -2
  164. package/front_end/third_party/puppeteer/package/src/cdp/Page.ts +6 -0
  165. package/front_end/third_party/puppeteer/package/src/cdp/TargetManager.ts +60 -18
  166. package/front_end/third_party/puppeteer/package/src/cdp/WebMCP.ts +7 -9
  167. package/front_end/third_party/puppeteer/package/src/common/BrowserConnector.ts +3 -0
  168. package/front_end/third_party/puppeteer/package/src/common/ConnectOptions.ts +37 -1
  169. package/front_end/third_party/puppeteer/package/src/node/BrowserLauncher.ts +7 -2
  170. package/front_end/third_party/puppeteer/package/src/node/ChromeLauncher.ts +1 -0
  171. package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
  172. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  173. package/front_end/ui/components/text_editor/AiCodeCompletionProvider.ts +19 -14
  174. package/front_end/ui/components/text_editor/AiCodeGenerationProvider.ts +8 -7
  175. package/front_end/ui/components/tooltips/Tooltip.ts +3 -3
  176. package/front_end/{core/common → ui/legacy}/AppProvider.ts +1 -1
  177. package/front_end/ui/legacy/InspectorView.ts +4 -3
  178. package/front_end/ui/legacy/TabbedPane.ts +1 -1
  179. package/front_end/ui/legacy/UIUserMetrics.ts +70 -0
  180. package/front_end/ui/legacy/components/cookie_table/CookiesTable.ts +1 -8
  181. package/front_end/ui/legacy/components/inline_editor/inline_editor.ts +0 -6
  182. package/front_end/ui/legacy/legacy.ts +6 -0
  183. package/front_end/ui/visual_logging/KnownContextValues.ts +2 -0
  184. package/package.json +1 -1
  185. package/front_end/Images/src/custom-typography.svg +0 -3
  186. package/front_end/ui/legacy/components/inline_editor/FontEditor.ts +0 -798
  187. package/front_end/ui/legacy/components/inline_editor/FontEditorUnitConverter.ts +0 -250
  188. package/front_end/ui/legacy/components/inline_editor/FontEditorUtils.ts +0 -204
  189. package/front_end/ui/legacy/components/inline_editor/fontEditor.css +0 -185
  190. /package/front_end/{core/common → ui/legacy}/App.ts +0 -0
@@ -188,25 +188,10 @@ const UIStrings = {
188
188
  } as const;
189
189
  const str_ = i18n.i18n.registerUIStrings('panels/emulation/DeviceModeToolbar.ts', UIStrings);
190
190
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
191
- const {ref, classMap} = Directives;
191
+ const {ifDefined, styleMap} = Directives;
192
192
  const {widget} = UI.Widget;
193
193
  const {bindToSetting} = UI.UIUtils;
194
194
 
195
- /**
196
- * Even though the emulation panel uses all UI elements, the tooltips are not supported.
197
- * That's because the emulation elements are rendered around the page context, rather
198
- * than in the DevTools panel itself. Therefore, we need to fall back to using the
199
- * built-in tooltip by setting the title attribute on the button's element.
200
- */
201
- function setTitleForButton(button: Buttons.Button.Button|UI.Toolbar.ToolbarMenuButton, title: string): void {
202
- if (button instanceof UI.Toolbar.ToolbarMenuButton) {
203
- button.setTitle(title);
204
- button.element.title = title;
205
- } else {
206
- button.title = title;
207
- }
208
- }
209
-
210
195
  export class DeviceModeToolbar {
211
196
  private model: EmulationModel.DeviceModeModel.DeviceModeModel;
212
197
  private readonly showMediaInspectorSetting: Common.Settings.Setting<boolean>;
@@ -219,23 +204,10 @@ export class DeviceModeToolbar {
219
204
  readonly #element: HTMLDivElement;
220
205
  private readonly emulatedDevicesList: EmulationModel.EmulatedDevices.EmulatedDevicesList;
221
206
  private readonly persistenceSetting: Common.Settings.Setting<{device: string, orientation: string, mode: string}>;
222
- private spanButton!: Buttons.Button.Button;
223
- private postureItem!: HTMLSelectElement;
224
- private modeButton!: Buttons.Button.Button;
225
- private widthInput!: HTMLInputElement;
226
- private heightInput!: HTMLInputElement;
227
- private deviceScaleItem!: HTMLSelectElement;
228
- private deviceScaleItems: HTMLElement[] = [];
229
- private deviceSelectItem!: HTMLSelectElement;
230
- private scaleItem!: HTMLSelectElement;
231
- private uaItem!: HTMLSelectElement;
232
- private cachedDeviceScale!: number|null;
233
- private cachedUaType!: string|null;
234
- private xItem?: HTMLElement;
235
- private cachedModelType?: EmulationModel.DeviceModeModel.Type;
236
- private cachedScale?: number;
237
- private cachedModelDevice?: EmulationModel.EmulatedDevices.EmulatedDevice|null;
238
- private cachedModelMode?: EmulationModel.EmulatedDevices.Mode|null;
207
+ private mainToolbar: UI.Toolbar.Toolbar;
208
+ private optionsToolbar: UI.Toolbar.Toolbar;
209
+ readonly #itemWidthCache = new Map<string, string>();
210
+ #measuringElement: HTMLSelectElement|null = null;
239
211
 
240
212
  constructor(
241
213
  model: EmulationModel.DeviceModeModel.DeviceModeModel,
@@ -248,11 +220,11 @@ export class DeviceModeToolbar {
248
220
  this.deviceOutlineSetting = this.model.deviceOutlineSetting();
249
221
  this.showDeviceScaleFactorSetting =
250
222
  Common.Settings.Settings.instance().createSetting('emulation.show-device-scale-factor', false);
251
- this.showDeviceScaleFactorSetting.addChangeListener(this.updateDeviceScaleFactorVisibility, this);
223
+ this.showDeviceScaleFactorSetting.addChangeListener(this.update, this);
252
224
 
253
225
  this.showUserAgentTypeSetting =
254
226
  Common.Settings.Settings.instance().createSetting('emulation.show-user-agent-type', false);
255
- this.showUserAgentTypeSetting.addChangeListener(this.updateUserAgentTypeVisibility, this);
227
+ this.showUserAgentTypeSetting.addChangeListener(this.update, this);
256
228
 
257
229
  this.autoAdjustScaleSetting =
258
230
  Common.Settings.Settings.instance().createSetting('emulation.auto-adjust-scale', true);
@@ -263,8 +235,8 @@ export class DeviceModeToolbar {
263
235
  this.#element.classList.add('device-mode-toolbar');
264
236
  this.#element.setAttribute('jslog', `${VisualLogging.toolbar('device-mode').track({resize: true})}`);
265
237
 
266
- const mainToolbar = this.createMainToolbar();
267
- const optionsToolbar = this.createOptionsToolbar();
238
+ this.mainToolbar = this.createMainToolbar();
239
+ this.optionsToolbar = this.createOptionsToolbar();
268
240
 
269
241
  this.emulatedDevicesList = EmulationModel.EmulatedDevices.EmulatedDevicesList.instance();
270
242
  this.emulatedDevicesList.addEventListener(
@@ -275,28 +247,13 @@ export class DeviceModeToolbar {
275
247
  this.persistenceSetting = Common.Settings.Settings.instance().createSetting(
276
248
  'emulation.device-mode-value', {device: '', orientation: '', mode: ''});
277
249
 
278
- this.model.toolbarControlsEnabledSetting().addChangeListener(updateToolbarsEnabled);
279
- updateToolbarsEnabled();
280
-
281
- this.updateDeviceMenuItems();
282
- this.updateScaleMenuItems();
283
- this.updateDeviceScaleMenuItems();
284
- this.updateUserAgentMenuItems();
285
- this.updateDevicePostureItems();
286
-
287
- function updateToolbarsEnabled(): void {
288
- const enabled = model.toolbarControlsEnabledSetting().get();
289
- mainToolbar.setEnabled(enabled);
290
- optionsToolbar.setEnabled(enabled);
291
- }
292
- }
250
+ this.model.toolbarControlsEnabledSetting().addChangeListener(this.update, this);
251
+ this.model.scaleSetting().addChangeListener(this.update, this);
252
+ this.model.uaSetting().addChangeListener(this.update, this);
253
+ this.model.deviceScaleFactorSetting().addChangeListener(this.update, this);
254
+ this.model.addEventListener(EmulationModel.DeviceModeModel.Events.UPDATED, this.update, this);
293
255
 
294
- private appendOption(parentElement: HTMLElement, title: string, value: string, jslogContext: string): void {
295
- const option = document.createElement('option');
296
- option.text = title;
297
- option.value = value;
298
- option.setAttribute('jslog', `${VisualLogging.item(jslogContext).track({click: true})}`);
299
- parentElement.appendChild(option);
256
+ this.update();
300
257
  }
301
258
 
302
259
  private createEmptyToolbarElement(): HTMLDivElement {
@@ -307,7 +264,7 @@ export class DeviceModeToolbar {
307
264
 
308
265
  private createSizeInput(
309
266
  title: string, jslogContext: string, value: string|undefined, disabled: boolean|undefined,
310
- refCallback: (el: Element|undefined) => void, onChange: () => void): LitTemplate {
267
+ placeholder: string|undefined, onChange: (event: Event) => void): LitTemplate {
311
268
  return html`
312
269
  <input type="number"
313
270
  max=${EmulationModel.DeviceModeModel.MaxDeviceSize}
@@ -317,8 +274,8 @@ export class DeviceModeToolbar {
317
274
  .disabled=${disabled ?? false}
318
275
  jslog=${VisualLogging.textField().track({change: true}).context(jslogContext)}
319
276
  .value=${value ?? ''}
277
+ placeholder=${ifDefined(placeholder)}
320
278
  @change=${onChange}
321
- ${ref(refCallback)}
322
279
  @keydown=${(event: Event): void => {
323
280
  const input = event.target as HTMLInputElement;
324
281
  let modifiedValue = UI.UIUtils.modifiedFloatNumber(Number(input.value), event);
@@ -336,50 +293,120 @@ export class DeviceModeToolbar {
336
293
 
337
294
  private createMainToolbar(): UI.Toolbar.Toolbar {
338
295
  const mainToolbar = this.#element.createChild('devtools-toolbar', 'main-toolbar');
296
+ return mainToolbar as UI.Toolbar.Toolbar;
297
+ }
298
+
299
+ private renderMainToolbar(): void {
300
+ const isResponsive = this.model.type() === EmulationModel.DeviceModeModel.Type.Responsive;
301
+ const isFullHeight = isResponsive && this.model.isFullHeight();
302
+ const size = this.model.appliedDeviceSize();
303
+ const widthValue = String(size.width);
304
+ const heightValue = isFullHeight ? '' : String(size.height);
305
+ const heightPlaceholder = String(size.height);
306
+
307
+ const device = this.model.device();
308
+ let modeButtonTitle = i18nString(UIStrings.rotate);
309
+ let modeButtonDisabled = false;
310
+ if (this.model.isScreenOrientationLocked()) {
311
+ modeButtonDisabled = true;
312
+ modeButtonTitle = i18nString(UIStrings.screenOrientationLocked);
313
+ } else if (device) {
314
+ const modeCount = device.modes.length;
315
+ modeButtonDisabled = modeCount < 2;
316
+ modeButtonTitle = modeCount === 2 ? i18nString(UIStrings.rotate) : i18nString(UIStrings.screenOrientationOptions);
317
+ } else if (isResponsive) {
318
+ modeButtonDisabled = false;
319
+ modeButtonTitle = i18nString(UIStrings.rotate);
320
+ } else {
321
+ modeButtonDisabled = true;
322
+ }
323
+
324
+ const showSpanButton = Boolean(device?.isDualScreen);
325
+ const showPostureItem = Boolean(!device?.isDualScreen && device?.isFoldableScreen);
326
+
327
+ const deviceModeOptions = this.getDeviceModeOptions();
328
+ const scaleOptions = this.getScaleOptions();
329
+ const dprOptions = this.getDeviceScaleFactorOptions();
330
+ const uaOptions = this.getUserAgentOptions();
331
+ const postureOptions = this.getDevicePostureOptions();
332
+
333
+ const selectedDeviceOption = [
334
+ deviceModeOptions.responsive,
335
+ ...deviceModeOptions.standard,
336
+ ...deviceModeOptions.custom,
337
+ ].find(o => o.selected);
338
+ const deviceText = selectedDeviceOption ? selectedDeviceOption.title : deviceModeOptions.responsive.title;
339
+
340
+ let scaleText = scaleOptions.find(o => o.selected)?.title || '';
341
+ if (scaleText.includes(' (')) {
342
+ scaleText = scaleText.substring(0, scaleText.indexOf(' ('));
343
+ }
344
+
345
+ let dprText = dprOptions.find(o => o.selected)?.title || '';
346
+ if (dprText.includes(' (')) {
347
+ dprText = dprText.substring(0, dprText.indexOf(' ('));
348
+ }
349
+
350
+ const uaText = uaOptions.find(o => o.selected)?.title || '';
351
+ const postureText = postureOptions.find(o => o.selected)?.title || '';
352
+
339
353
  // clang-format off
340
354
  render(html`
341
355
  <div class="device-mode-empty-toolbar-element"></div>
342
356
  ${i18nTemplate(str_, UIStrings.dimensions, {PH1: html`
343
357
  <select class="dark-text toolbar-has-dropdown-shrinkable"
358
+ style=${styleMap({width: this.calculateItemWidth(deviceText)})}
344
359
  title=${i18nString(UIStrings.deviceType)}
345
360
  aria-label=${i18nString(UIStrings.deviceType)}
346
361
  @change=${this.onDeviceChange.bind(this)}
347
- jslog=${VisualLogging.dropDown().track({change: true}).context('device')}
348
- ${ref(el => { this.deviceSelectItem = el as HTMLSelectElement; })}>
362
+ .value=${selectedDeviceOption === deviceModeOptions.responsive ? 'Responsive' : (selectedDeviceOption?.title || 'Responsive')}
363
+ jslog=${VisualLogging.dropDown().track({change: true}).context('device')}>
364
+ <option value="Responsive" ?selected=${deviceModeOptions.responsive.selected} jslog=${VisualLogging.item(deviceModeOptions.responsive.jslogContext).track({click: true})}>
365
+ ${deviceModeOptions.responsive.title}
366
+ </option>
367
+ ${deviceModeOptions.standard.length > 0 ? html`
368
+ <optgroup label="Standard">
369
+ ${deviceModeOptions.standard.map(o => html`<option value=${o.title} ?selected=${o.selected} jslog=${VisualLogging.item(o.jslogContext).track({click: true})}>${o.title}</option>`)}
370
+ </optgroup>
371
+ ` : ''}
372
+ ${deviceModeOptions.custom.length > 0 ? html`
373
+ <optgroup label="Custom">
374
+ ${deviceModeOptions.custom.map(o => html`<option value=${o.title} ?selected=${o.selected} jslog=${VisualLogging.item(o.jslogContext).track({click: true})}>${o.title}</option>`)}
375
+ </optgroup>
376
+ ` : ''}
377
+ <option value="Edit" jslog=${VisualLogging.item(deviceModeOptions.edit.jslogContext).track({click: true})}>
378
+ ${deviceModeOptions.edit.title}
379
+ </option>
349
380
  </select>`})}
350
381
 
351
- ${this.createSizeInput(
352
- i18nString(UIStrings.width), 'width', this.widthInput?.value, this.widthInput?.disabled,
353
- el => { this.widthInput = el as HTMLInputElement; },
354
- () => {
355
- const width = Number(this.widthInput.value);
356
- if (this.autoAdjustScaleSetting.get()) {
357
- this.model.setWidthAndScaleToFit(width);
358
- } else {
359
- this.model.setWidth(width);
360
- }
361
- })}
362
-
363
- <div class="device-mode-x" ${ref(el => { this.xItem = el as HTMLElement; })}>×</div>
364
- ${this.createSizeInput(
365
- i18nString(UIStrings.heightLeaveEmptyForFull), 'height', this.heightInput?.value, this.heightInput?.disabled,
366
- el => { this.heightInput = el as HTMLInputElement; },
367
- () => {
368
- const height = Number(this.heightInput.value);
369
- if (this.autoAdjustScaleSetting.get()) {
370
- this.model.setHeightAndScaleToFit(height);
371
- } else {
372
- this.model.setHeight(height);
373
- }
374
- })}
382
+ ${this.createSizeInput(i18nString(UIStrings.width), 'width', widthValue, !isResponsive, '', (event: Event) => {
383
+ const width = Number((event.target as HTMLInputElement).value);
384
+ if (this.autoAdjustScaleSetting.get()) {
385
+ this.model.setWidthAndScaleToFit(width);
386
+ } else {
387
+ this.model.setWidth(width);
388
+ }
389
+ })}
390
+
391
+ <div class="device-mode-x">×</div>
392
+ ${this.createSizeInput(i18nString(UIStrings.heightLeaveEmptyForFull), 'height', heightValue, !isResponsive, heightPlaceholder, (event: Event) => {
393
+ const height = Number((event.target as HTMLInputElement).value);
394
+ if (this.autoAdjustScaleSetting.get()) {
395
+ this.model.setHeightAndScaleToFit(height);
396
+ } else {
397
+ this.model.setHeight(height);
398
+ }
399
+ })}
375
400
 
376
401
  <div class="device-mode-empty-toolbar-element"></div>
377
402
  <select class="dark-text toolbar-has-dropdown-shrinkable"
403
+ style=${styleMap({width: this.calculateItemWidth(scaleText)})}
378
404
  title=${i18nString(UIStrings.zoom)}
379
405
  aria-label=${i18nString(UIStrings.zoom)}
380
406
  @change=${this.onScaleChange.bind(this)}
381
- jslog=${VisualLogging.dropDown().track({change: true}).context('scale')}
382
- ${ref((el: Element|undefined) => { this.scaleItem = el as HTMLSelectElement; })}>
407
+ .value=${String(scaleOptions.find(o => o.selected)?.value || '')}
408
+ jslog=${VisualLogging.dropDown().track({change: true}).context('scale')}>
409
+ ${scaleOptions.map(o => html`<option value=${o.value} ?selected=${o.selected} jslog=${VisualLogging.item(o.jslogContext).track({click: true})}>${o.title}</option>`)}
383
410
  </select>
384
411
 
385
412
  <devtools-button .data=${{variant: Buttons.Button.Variant.TOOLBAR, iconName: 'center-focus-weak',
@@ -390,28 +417,33 @@ export class DeviceModeToolbar {
390
417
 
391
418
  <div class="device-mode-empty-toolbar-element"></div>
392
419
 
393
- <span>
420
+ ${this.showDeviceScaleFactorSetting.get() ? html`
394
421
  ${i18nTemplate(str_, UIStrings.dpr, {
395
422
  PH1: html`
396
423
  <select class="dark-text toolbar-has-dropdown-shrinkable"
424
+ style=${styleMap({width: this.calculateItemWidth(dprText)})}
397
425
  title=${i18nString(UIStrings.devicePixelRatio)}
398
426
  aria-label=${i18nString(UIStrings.devicePixelRatio)}
399
427
  @change=${this.onDeviceScaleChange.bind(this)}
428
+ .value=${String(dprOptions.find(o => o.selected)?.value || '')}
400
429
  jslog=${VisualLogging.dropDown().track({change: true}).context('device-pixel-ratio')}
401
- ${ref(el => { this.deviceScaleItem = el as HTMLSelectElement; })}>
430
+ ?disabled=${!isResponsive}>
431
+ ${dprOptions.map(o => html`<option value=${o.value} ?selected=${o.selected} jslog=${VisualLogging.item(o.jslogContext).track({click: true})}>${o.title}</option>`)}
402
432
  </select>`
403
- })}
404
- </span>
433
+ })}` : ''}
405
434
 
406
435
  <div class="device-mode-empty-toolbar-element"></div>
407
- <select class="dark-text toolbar-has-dropdown-shrinkable
408
- ${classMap({hidden: !this.showUserAgentTypeSetting.get()})}"
409
- title=${i18nString(UIStrings.deviceType)}
410
- aria-label=${i18nString(UIStrings.deviceType)}
411
- @change=${this.onUAChange.bind(this)}
412
- jslog=${VisualLogging.dropDown().track({change: true}).context('device-type')}
413
- ${ref(el => { this.uaItem = el as HTMLSelectElement; })}>
414
- </select>
436
+ ${this.showUserAgentTypeSetting.get() ? html`
437
+ <select class="dark-text toolbar-has-dropdown-shrinkable"
438
+ style=${styleMap({width: this.calculateItemWidth(uaText)})}
439
+ title=${i18nString(UIStrings.deviceType)}
440
+ aria-label=${i18nString(UIStrings.deviceType)}
441
+ @change=${this.onUAChange.bind(this)}
442
+ .value=${uaOptions.find(o => o.selected)?.value || ''}
443
+ jslog=${VisualLogging.dropDown().track({change: true}).context('device-type')}
444
+ ?disabled=${!isResponsive}>
445
+ ${uaOptions.map(o => html`<option value=${o.value} ?selected=${o.selected} jslog=${VisualLogging.item(o.jslogContext).track({click: true})}>${o.title}</option>`)}
446
+ </select>` : ''}
415
447
  <select class="dark-text" ${widget(MobileThrottling.NetworkThrottlingSelector.NetworkThrottlingSelect, {
416
448
  title: i18nString(UIStrings.throttling),
417
449
  bindToGlobalConditions: true,
@@ -421,45 +453,35 @@ export class DeviceModeToolbar {
421
453
 
422
454
  <div class="device-mode-empty-toolbar-element"></div>
423
455
  <devtools-button class="toolbar-button"
424
- .data=${{variant: Buttons.Button.Variant.ICON, iconName: 'screen-rotation'} as Buttons.Button.ButtonData}
456
+ .data=${{variant: Buttons.Button.Variant.TOOLBAR, iconName: 'screen-rotation',
457
+ disabled: modeButtonDisabled} as Buttons.Button.ButtonData}
425
458
  jslog=${VisualLogging.action('screen-rotation').track({click: true})}
426
459
  @click=${this.modeMenuClicked.bind(this)}
427
- ${ref(el => { this.modeButton = el as Buttons.Button.Button; })}>
460
+ .title=${modeButtonTitle}>
428
461
  </devtools-button>
429
462
 
430
463
  <!-- Show dual screen toolbar -->
431
- <devtools-button class="toolbar-button"
432
- .data=${{variant: Buttons.Button.Variant.ICON, iconName: 'device-fold'} as Buttons.Button.ButtonData}
433
- jslog=${VisualLogging.action('device-fold').track({click: true})}
434
- @click=${this.spanClicked.bind(this)}
435
- ${ref(el => { this.spanButton = el as Buttons.Button.Button; })}>
436
- </devtools-button>
464
+ ${showSpanButton ? html`
465
+ <devtools-button class="toolbar-button"
466
+ .data=${{variant: Buttons.Button.Variant.TOOLBAR, iconName: 'device-fold'} as Buttons.Button.ButtonData}
467
+ jslog=${VisualLogging.action('device-fold').track({click: true})}
468
+ .title=${i18nString(UIStrings.toggleDualscreenMode)}
469
+ @click=${this.spanClicked.bind(this)}>
470
+ </devtools-button>` : ''}
437
471
 
438
472
  <!-- Show posture toolbar menu for foldable devices. -->
439
473
  <div class="device-mode-empty-toolbar-element"></div>
440
- <select class="dark-text toolbar-has-dropdown-shrinkable"
441
- title=${i18nString(UIStrings.devicePosture)}
442
- aria-label=${i18nString(UIStrings.devicePosture)}
443
- @change=${this.onPostureChange.bind(this)}
444
- jslog=${VisualLogging.dropDown().track({change: true}).context('device-posture')}
445
- ${ref(el => { this.postureItem = el as HTMLSelectElement; })}>
446
- </select>`,
447
- mainToolbar);
474
+ ${showPostureItem ? html`
475
+ <select class="dark-text toolbar-has-dropdown-shrinkable"
476
+ style=${styleMap({width: this.calculateItemWidth(postureText)})}
477
+ title=${i18nString(UIStrings.devicePosture)}
478
+ aria-label=${i18nString(UIStrings.devicePosture)}
479
+ @change=${this.onPostureChange.bind(this)}
480
+ .value=${postureOptions.find(o => o.selected)?.value || ''}
481
+ jslog=${VisualLogging.dropDown().track({change: true}).context('device-posture')}>
482
+ ${postureOptions.map(o => html`<option value=${o.value} ?selected=${o.selected} jslog=${VisualLogging.item(o.value.toLowerCase()).track({click: true})}>${o.title}</option>`)}
483
+ </select>` : ''}`, this.mainToolbar, {host: this});
448
484
  // clang-format on
449
- const deviceScaleSpan = this.deviceScaleItem.parentElement as HTMLElement;
450
- for (const node of Array.from(deviceScaleSpan.childNodes)) {
451
- if (node instanceof HTMLElement) {
452
- this.deviceScaleItems.push(node);
453
- } else if (node instanceof Text) {
454
- const span = document.createElement('span');
455
- span.classList.add('toolbar-text');
456
- span.appendChild(node);
457
- this.deviceScaleItems.push(span);
458
- }
459
- }
460
- deviceScaleSpan.replaceWith(...this.deviceScaleItems);
461
- this.updateDeviceScaleFactorVisibility();
462
- return mainToolbar;
463
485
  }
464
486
 
465
487
  private createOptionsToolbar(): UI.Toolbar.Toolbar {
@@ -478,20 +500,8 @@ export class DeviceModeToolbar {
478
500
  return ['Continuous', 'Folded'].map(title => ({title, value: title, selected: currentPosture === title}));
479
501
  }
480
502
 
481
- private updateDevicePostureItems(): void {
482
- this.postureItem.replaceChildren();
483
- for (const option of this.getDevicePostureOptions()) {
484
- this.appendOption(this.postureItem, option.title, option.value, option.title.toLowerCase());
485
- }
486
- const currentPosture = this.currentDevicePosture();
487
- if (this.postureItem.value !== currentPosture) {
488
- this.postureItem.value = currentPosture;
489
- }
490
- this.resizeItem(this.postureItem);
491
- }
492
-
493
- private onPostureChange(): void {
494
- const value = this.postureItem.value;
503
+ private onPostureChange(event: Event): void {
504
+ const value = (event.target as HTMLSelectElement).value;
495
505
  if (value !== this.currentDevicePosture()) {
496
506
  this.spanClicked();
497
507
  }
@@ -539,21 +549,8 @@ export class DeviceModeToolbar {
539
549
  });
540
550
  }
541
551
 
542
- private updateScaleMenuItems(): void {
543
- this.scaleItem.replaceChildren();
544
- const options = this.getScaleOptions();
545
- for (const option of options) {
546
- this.appendOption(this.scaleItem, option.title, String(option.value), option.jslogContext);
547
- }
548
- const selected = options.find(o => o.selected);
549
- if (selected) {
550
- this.scaleItem.value = String(selected.value);
551
- }
552
- this.resizeItem(this.scaleItem, true);
553
- }
554
-
555
- private onScaleChange(): void {
556
- const value = Number(this.scaleItem.value);
552
+ private onScaleChange(event: Event): void {
553
+ const value = Number((event.target as HTMLSelectElement).value);
557
554
  this.model.scaleSetting().set(value);
558
555
  }
559
556
 
@@ -587,21 +584,8 @@ export class DeviceModeToolbar {
587
584
  });
588
585
  }
589
586
 
590
- private updateDeviceScaleMenuItems(): void {
591
- this.deviceScaleItem.replaceChildren();
592
- const options = this.getDeviceScaleFactorOptions();
593
- for (const option of options) {
594
- this.appendOption(this.deviceScaleItem, option.title, String(option.value), option.jslogContext);
595
- }
596
- const selected = options.find(o => o.selected);
597
- if (selected) {
598
- this.deviceScaleItem.value = String(selected.value);
599
- }
600
- this.resizeItem(this.deviceScaleItem, true);
601
- }
602
-
603
- private onDeviceScaleChange(): void {
604
- const value = Number(this.deviceScaleItem.value);
587
+ private onDeviceScaleChange(event: Event): void {
588
+ const value = Number((event.target as HTMLSelectElement).value);
605
589
  this.model.deviceScaleFactorSetting().set(value);
606
590
  }
607
591
 
@@ -622,23 +606,9 @@ export class DeviceModeToolbar {
622
606
  }));
623
607
  }
624
608
 
625
- private updateUserAgentMenuItems(): void {
626
- this.uaItem.replaceChildren();
627
- const options = this.getUserAgentOptions();
628
- for (const option of options) {
629
- this.appendOption(this.uaItem, option.title, option.value, option.jslogContext);
630
- }
631
- const selected = options.find(o => o.selected);
632
- if (selected) {
633
- this.uaItem.value = selected.value;
634
- }
635
- this.resizeItem(this.uaItem);
636
- }
637
-
638
- private onUAChange(): void {
639
- const value = this.uaItem.value as EmulationModel.DeviceModeModel.UA;
609
+ private onUAChange(event: Event): void {
610
+ const value = (event.target as HTMLSelectElement).value as EmulationModel.DeviceModeModel.UA;
640
611
  this.model.uaSetting().set(value);
641
- this.resizeItem(this.uaItem);
642
612
  }
643
613
 
644
614
  private appendOptionsMenuItems(contextMenu: UI.ContextMenu.ContextMenu): void {
@@ -763,48 +733,11 @@ export class DeviceModeToolbar {
763
733
  };
764
734
  }
765
735
 
766
- private updateDeviceMenuItems(): void {
767
- this.deviceSelectItem.replaceChildren();
768
- const options = this.getDeviceModeOptions();
769
-
770
- this.appendOption(this.deviceSelectItem, options.responsive.title, 'Responsive', options.responsive.jslogContext);
771
-
772
- appendGroup.call(this, options.standard, 'Standard');
773
- appendGroup.call(this, options.custom, 'Custom');
774
-
775
- this.appendOption(this.deviceSelectItem, options.edit.title, 'Edit', options.edit.jslogContext);
776
-
777
- this.updateDeviceSelection();
778
-
779
- function appendGroup(
780
- this: DeviceModeToolbar, group: Array<{title: string, jslogContext: string}>, label: string): void {
781
- if (!group.length) {
782
- return;
783
- }
784
- const optGroup = document.createElement('optgroup');
785
- optGroup.label = label;
786
- for (const item of group) {
787
- this.appendOption(optGroup, item.title, item.title, item.jslogContext);
788
- }
789
- this.deviceSelectItem.appendChild(optGroup);
790
- }
791
- }
792
-
793
- private updateDeviceSelection(): void {
794
- const device = this.model.device();
795
- if (this.model.type() === EmulationModel.DeviceModeModel.Type.Responsive) {
796
- this.deviceSelectItem.value = 'Responsive';
797
- } else if (device) {
798
- this.deviceSelectItem.value = device.title;
799
- }
800
- this.resizeItem(this.deviceSelectItem);
801
- }
802
-
803
- private onDeviceChange(): void {
804
- const value = this.deviceSelectItem.value;
736
+ private onDeviceChange(event: Event): void {
737
+ const value = (event.target as HTMLSelectElement).value;
805
738
  if (value === 'Edit') {
806
739
  this.emulatedDevicesList.revealCustomSetting();
807
- this.updateDeviceSelection();
740
+ this.renderMainToolbar();
808
741
  } else if (value === 'Responsive') {
809
742
  this.switchToResponsive();
810
743
  } else {
@@ -818,7 +751,7 @@ export class DeviceModeToolbar {
818
751
  }
819
752
 
820
753
  private deviceListChanged(): void {
821
- this.updateDeviceMenuItems();
754
+ this.renderMainToolbar();
822
755
  const device = this.model.device();
823
756
  if (!device) {
824
757
  return;
@@ -836,26 +769,6 @@ export class DeviceModeToolbar {
836
769
  }
837
770
  }
838
771
 
839
- private updateDeviceScaleFactorVisibility(): void {
840
- if (this.deviceScaleItem) {
841
- const visible = this.showDeviceScaleFactorSetting.get();
842
- this.deviceScaleItem.classList.toggle('hidden', !visible);
843
- for (const item of this.deviceScaleItems) {
844
- item.classList.toggle('hidden', !visible);
845
- }
846
- }
847
- }
848
-
849
- private updateUserAgentTypeVisibility(): void {
850
- if (this.uaItem) {
851
- const visible = this.showUserAgentTypeSetting.get();
852
- this.uaItem.classList.toggle('hidden', !visible);
853
- if (visible) {
854
- this.resizeItem(this.uaItem);
855
- }
856
- }
857
- }
858
-
859
772
  private spanClicked(): void {
860
773
  const device = this.model.device();
861
774
 
@@ -916,13 +829,10 @@ export class DeviceModeToolbar {
916
829
  return;
917
830
  }
918
831
 
919
- if (!this.modeButton) {
920
- return;
921
- }
922
-
832
+ const modeButton = event.target as HTMLElement;
923
833
  const contextMenu = new UI.ContextMenu.ContextMenu(event, {
924
- x: this.modeButton.getBoundingClientRect().left,
925
- y: this.modeButton.getBoundingClientRect().top + this.modeButton.offsetHeight,
834
+ x: modeButton.getBoundingClientRect().left,
835
+ y: modeButton.getBoundingClientRect().top + modeButton.offsetHeight,
926
836
  });
927
837
  addOrientation(EmulationModel.EmulatedDevices.Vertical, i18nString(UIStrings.portrait));
928
838
  addOrientation(EmulationModel.EmulatedDevices.Horizontal, i18nString(UIStrings.landscape));
@@ -970,77 +880,11 @@ export class DeviceModeToolbar {
970
880
  }
971
881
 
972
882
  update(): void {
973
- if (this.model.type() !== this.cachedModelType) {
974
- this.cachedModelType = this.model.type();
975
- this.widthInput.disabled = this.model.type() !== EmulationModel.DeviceModeModel.Type.Responsive;
976
-
977
- this.heightInput.disabled = this.model.type() !== EmulationModel.DeviceModeModel.Type.Responsive;
978
- this.deviceScaleItem.disabled = this.model.type() !== EmulationModel.DeviceModeModel.Type.Responsive;
979
- this.uaItem.disabled = this.model.type() !== EmulationModel.DeviceModeModel.Type.Responsive;
980
-
981
- if (this.model.type() === EmulationModel.DeviceModeModel.Type.Responsive) {
982
- this.modeButton.disabled = false;
983
- setTitleForButton(this.modeButton, i18nString(UIStrings.rotate));
984
- } else {
985
- this.modeButton.disabled = true;
986
- }
987
- }
988
-
989
- const size = this.model.appliedDeviceSize();
990
- this.widthInput.value = String(size.width);
991
- this.heightInput.value =
992
- this.model.type() === EmulationModel.DeviceModeModel.Type.Responsive && this.model.isFullHeight() ?
993
- '' :
994
- String(size.height);
995
- this.heightInput.placeholder = String(size.height);
996
-
997
- if (this.model.scale() !== this.cachedScale) {
998
- this.updateScaleMenuItems();
999
- this.cachedScale = this.model.scale();
1000
- }
1001
-
1002
- const deviceScale = this.model.appliedDeviceScaleFactor();
1003
- if (deviceScale !== this.cachedDeviceScale) {
1004
- this.deviceScaleItem.value = String(deviceScale);
1005
- this.cachedDeviceScale = deviceScale;
1006
- this.resizeItem(this.deviceScaleItem, true);
1007
- }
1008
-
1009
- const uaType = this.model.appliedUserAgentType();
1010
- if (uaType !== this.cachedUaType) {
1011
- this.uaItem.value = uaType;
1012
- this.cachedUaType = uaType;
1013
- this.updateDeviceScaleMenuItems();
1014
- this.resizeItem(this.uaItem);
1015
- }
883
+ const enabled = this.model.toolbarControlsEnabledSetting().get();
884
+ this.mainToolbar.setEnabled(enabled);
885
+ this.optionsToolbar.setEnabled(enabled);
1016
886
 
1017
887
  const device = this.model.device();
1018
- this.updateDeviceSelection();
1019
-
1020
- if (this.model.device() !== this.cachedModelDevice) {
1021
- const device = this.model.device();
1022
- if (device) {
1023
- const modeCount = device ? device.modes.length : 0;
1024
- this.modeButton.disabled = modeCount < 2;
1025
- setTitleForButton(
1026
- this.modeButton,
1027
- modeCount === 2 ? i18nString(UIStrings.rotate) : i18nString(UIStrings.screenOrientationOptions));
1028
- }
1029
- this.cachedModelDevice = device;
1030
- }
1031
-
1032
- if (device?.isDualScreen) {
1033
- this.spanButton.classList.toggle('hidden', false);
1034
- this.postureItem.classList.toggle('hidden', true);
1035
- } else if (device?.isFoldableScreen) {
1036
- this.spanButton.classList.toggle('hidden', true);
1037
- this.postureItem.classList.toggle('hidden', false);
1038
- this.updateDevicePostureItems();
1039
- } else {
1040
- this.spanButton.classList.toggle('hidden', true);
1041
- this.postureItem.classList.toggle('hidden', true);
1042
- }
1043
- setTitleForButton(this.spanButton, i18nString(UIStrings.toggleDualscreenMode));
1044
888
 
1045
889
  if (this.model.type() === EmulationModel.DeviceModeModel.Type.Device) {
1046
890
  this.lastMode.set(
@@ -1048,39 +892,20 @@ export class DeviceModeToolbar {
1048
892
  (this.model.mode() as EmulationModel.EmulatedDevices.Mode));
1049
893
  }
1050
894
 
1051
- if (this.model.mode() !== this.cachedModelMode && this.model.type() !== EmulationModel.DeviceModeModel.Type.None) {
1052
- this.cachedModelMode = this.model.mode();
1053
- const value = this.persistenceSetting.get();
1054
- const device = this.model.device();
1055
- if (device) {
1056
- value.device = device.title;
1057
- const mode = this.model.mode();
1058
- value.orientation = mode ? mode.orientation : '';
1059
- value.mode = mode ? mode.title : '';
1060
- } else {
1061
- value.device = '';
1062
- value.orientation = '';
1063
- value.mode = '';
1064
- }
1065
- this.persistenceSetting.set(value);
895
+ const value = this.persistenceSetting.get();
896
+ const currentMode = this.model.mode();
897
+ if (device) {
898
+ value.device = device.title;
899
+ value.orientation = currentMode ? currentMode.orientation : '';
900
+ value.mode = currentMode ? currentMode.title : '';
901
+ } else {
902
+ value.device = '';
903
+ value.orientation = '';
904
+ value.mode = '';
1066
905
  }
906
+ this.persistenceSetting.set(value);
1067
907
 
1068
- // When screen orientation is locked by the page (via screen.orientation.lock()),
1069
- // disable the rotate button to prevent user-initiated rotation.
1070
- // When unlocked, restore the button to its normal state.
1071
- if (this.model.isScreenOrientationLocked()) {
1072
- this.modeButton.disabled = true;
1073
- setTitleForButton(this.modeButton, i18nString(UIStrings.screenOrientationLocked));
1074
- } else if (this.cachedModelDevice) {
1075
- const modeCount = this.cachedModelDevice.modes.length;
1076
- this.modeButton.disabled = modeCount < 2;
1077
- setTitleForButton(
1078
- this.modeButton,
1079
- modeCount === 2 ? i18nString(UIStrings.rotate) : i18nString(UIStrings.screenOrientationOptions));
1080
- } else if (this.model.type() === EmulationModel.DeviceModeModel.Type.Responsive) {
1081
- this.modeButton.disabled = false;
1082
- setTitleForButton(this.modeButton, i18nString(UIStrings.rotate));
1083
- }
908
+ this.renderMainToolbar();
1084
909
  }
1085
910
 
1086
911
  restore(): void {
@@ -1100,30 +925,35 @@ export class DeviceModeToolbar {
1100
925
  this.model.emulate(EmulationModel.DeviceModeModel.Type.Responsive, null, null);
1101
926
  }
1102
927
 
1103
- private resizeItem(item: HTMLSelectElement, stripParens = false): void {
1104
- const selectedOption = item.options[item.selectedIndex];
1105
- if (!selectedOption) {
1106
- return;
928
+ private calculateItemWidth(text: string): string {
929
+ if (!text) {
930
+ return '';
931
+ }
932
+ if (this.#itemWidthCache.has(text)) {
933
+ return this.#itemWidthCache.get(text) as string;
1107
934
  }
1108
935
 
1109
- const dummySelect = item.cloneNode(false) as HTMLSelectElement;
1110
- const dummyOption = selectedOption.cloneNode(true) as HTMLOptionElement;
1111
-
1112
- if (stripParens) {
1113
- const parensIndex = dummyOption.text.indexOf(' (');
1114
- if (parensIndex >= 0) {
1115
- dummyOption.text = dummyOption.text.substring(0, parensIndex);
1116
- }
936
+ if (!this.#measuringElement) {
937
+ this.#measuringElement = document.createElement('select');
938
+ this.#measuringElement.className = 'dark-text toolbar-has-dropdown-shrinkable';
939
+ this.#measuringElement.style.width = 'fit-content';
940
+ this.#measuringElement.style.position = 'absolute';
941
+ this.#measuringElement.style.visibility = 'hidden';
942
+ this.#measuringElement.style.pointerEvents = 'none';
943
+ const dummyOption = document.createElement('option');
944
+ this.#measuringElement.appendChild(dummyOption);
945
+ this.#element.appendChild(this.#measuringElement);
1117
946
  }
1118
947
 
1119
- dummySelect.appendChild(dummyOption);
1120
- dummySelect.style.width = 'fit-content';
1121
- dummySelect.style.position = 'absolute';
1122
- dummySelect.style.visibility = 'hidden';
1123
- dummySelect.style.pointerEvents = 'none';
948
+ const dummyOption = this.#measuringElement.options[0];
949
+ dummyOption.textContent = text;
950
+
951
+ const width = this.#measuringElement.offsetWidth;
1124
952
 
1125
- item.parentElement?.appendChild(dummySelect);
1126
- item.style.width = dummySelect.offsetWidth + 'px';
1127
- dummySelect.remove();
953
+ const widthPx = width ? `${width}px` : '';
954
+ if (width > 0) {
955
+ this.#itemWidthCache.set(text, widthPx);
956
+ }
957
+ return widthPx;
1128
958
  }
1129
959
  }