chrome-devtools-frontend 1.0.1624583 → 1.0.1625854

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