chrome-devtools-frontend 1.0.1669021 → 1.0.1672557

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 (302) hide show
  1. package/.agents/skills/fixing-skipped-tests/SKILL.md +6 -6
  2. package/.agents/skills/gerrit-cli/SKILL.md +4 -10
  3. package/.agents/skills/gerrit-cli/references/commands.md +45 -2
  4. package/.agents/skills/gerrit-cli/references/workflows.md +7 -0
  5. package/.agents/skills/gerrit-cli/scripts/import_upstream.py +34 -152
  6. package/config/typescript/tsconfig.eslint.json +1 -1
  7. package/docs/contributing/settings-experiments-features.md +2 -8
  8. package/docs/ecosystem/automatic_workspace_folders.md +0 -19
  9. package/eslint.config.mjs +5 -5
  10. package/front_end/core/common/Lazy.ts +3 -3
  11. package/front_end/core/common/SettingRegistration.ts +0 -7
  12. package/front_end/core/common/Settings.ts +0 -38
  13. package/front_end/core/host/UserMetrics.ts +187 -953
  14. package/front_end/core/host/UserMetricsEnums.ts +949 -0
  15. package/front_end/core/root/Runtime.ts +21 -138
  16. package/front_end/core/sdk/CSSMetadata.ts +70 -65
  17. package/front_end/core/sdk/CategorizedBreakpoint.ts +0 -1
  18. package/front_end/core/sdk/ChildTargetManager.ts +0 -2
  19. package/front_end/core/sdk/EmulationModel.ts +0 -2
  20. package/front_end/core/sdk/EventBreakpointsModel.ts +0 -4
  21. package/front_end/core/sdk/NetworkManager.ts +0 -3
  22. package/front_end/core/sdk/PageResourceLoader.ts +9 -9
  23. package/front_end/core/sdk/RehydratingConnection.ts +52 -4
  24. package/front_end/core/sdk/Target.ts +0 -4
  25. package/front_end/devtools_compatibility.js +886 -0
  26. package/front_end/entrypoints/inspector_main/RenderingOptions.ts +7 -6
  27. package/front_end/entrypoints/main/ExecutionContextSelector.ts +0 -1
  28. package/front_end/entrypoints/main/MainImpl.ts +18 -22
  29. package/front_end/foundation/Universe.ts +7 -2
  30. package/front_end/generated/InspectorBackendCommands.ts +3 -10
  31. package/front_end/generated/{SupportedCSSProperties.js → SupportedCSSProperties.ts} +16 -0
  32. package/front_end/generated/protocol-mapping.d.ts +0 -42
  33. package/front_end/generated/protocol-proxy-api.d.ts +0 -38
  34. package/front_end/generated/protocol.ts +9 -169
  35. package/front_end/models/ai_assistance/AiAgent2.ts +11 -3
  36. package/front_end/models/ai_assistance/AiConversation.ts +1 -0
  37. package/front_end/models/ai_assistance/AiHistoryStorage.ts +8 -5
  38. package/front_end/models/ai_assistance/AiSetting.ts +108 -0
  39. package/front_end/models/ai_assistance/AiUtils.ts +47 -0
  40. package/front_end/models/ai_assistance/ChangeManager.ts +1 -0
  41. package/front_end/models/ai_assistance/ExtensionScope.ts +3 -12
  42. package/front_end/models/ai_assistance/agents/AiAgent.ts +11 -18
  43. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +3 -0
  44. package/front_end/models/ai_assistance/agents/ExecuteJavascript.ts +42 -17
  45. package/front_end/models/ai_assistance/agents/PerformanceAgent.snapshot.txt +1 -1
  46. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +3 -2
  47. package/front_end/models/ai_assistance/agents/StorageAgent.ts +253 -155
  48. package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +2 -2
  49. package/front_end/models/ai_assistance/ai_assistance.ts +4 -0
  50. package/front_end/models/ai_assistance/contexts/PerformanceTraceContext.ts +12 -6
  51. package/front_end/models/ai_assistance/data_formatters/FileFormatter.ts +3 -1
  52. package/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts +1 -0
  53. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +4 -3
  54. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +1 -1
  55. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +1 -0
  56. package/front_end/models/ai_assistance/injected.ts +1 -0
  57. package/front_end/models/ai_assistance/performance/AICallTree.ts +1 -0
  58. package/front_end/models/ai_assistance/skills/Skill.ts +1 -1
  59. package/front_end/models/ai_assistance/skills/SkillRegistry.ts +2 -0
  60. package/front_end/models/ai_assistance/skills/performance.md +10 -0
  61. package/front_end/models/ai_assistance/tools/ExecuteJavaScript.ts +52 -5
  62. package/front_end/models/ai_assistance/tools/GetElementAccessibilityDetails.ts +4 -4
  63. package/front_end/models/ai_assistance/tools/GetLighthouseAudits.ts +4 -4
  64. package/front_end/models/ai_assistance/tools/GetNetworkRequestDetails.ts +5 -4
  65. package/front_end/models/ai_assistance/tools/GetStyles.ts +5 -4
  66. package/front_end/models/ai_assistance/tools/ListNetworkRequests.ts +5 -4
  67. package/front_end/models/ai_assistance/tools/README.md +1 -1
  68. package/front_end/models/ai_assistance/tools/RecordPerformanceTrace.ts +63 -0
  69. package/front_end/models/ai_assistance/tools/ResolveDevtoolsNodePath.ts +5 -5
  70. package/front_end/models/ai_assistance/tools/Tool.ts +118 -24
  71. package/front_end/models/ai_assistance/tools/ToolRegistry.ts +2 -0
  72. package/front_end/models/badges/UserBadges.ts +2 -0
  73. package/front_end/models/bindings/NetworkProject.ts +5 -0
  74. package/front_end/models/bindings/ResourceScriptMapping.ts +2 -0
  75. package/front_end/models/bindings/ResourceUtils.ts +3 -0
  76. package/front_end/models/crux-manager/CrUXManager.ts +1 -0
  77. package/front_end/models/emulation/DeviceModeModel.ts +140 -7
  78. package/front_end/models/emulation/EmulatedDevices.ts +246 -793
  79. package/front_end/models/issues_manager/IssuesManager.ts +10 -2
  80. package/front_end/models/live-metrics/LiveMetrics.ts +41 -9
  81. package/front_end/models/logs/LogManager.ts +8 -5
  82. package/front_end/models/logs/NetworkLog.ts +1 -0
  83. package/front_end/models/persistence/IsolatedFileSystemManager.ts +21 -3
  84. package/front_end/models/persistence/NetworkPersistenceManager.ts +6 -0
  85. package/front_end/models/project_settings/ProjectSettingsModel.ts +8 -12
  86. package/front_end/models/trace/Processor.ts +42 -10
  87. package/front_end/models/trace/handlers/LayoutShiftsHandler.ts +12 -19
  88. package/front_end/models/trace/handlers/MetaHandler.ts +38 -5
  89. package/front_end/models/trace/insights/CLSCulprits.ts +1 -2
  90. package/front_end/models/trace/insights/CharacterSet.ts +1 -1
  91. package/front_end/models/trace/insights/Common.ts +21 -2
  92. package/front_end/models/trace/insights/DocumentLatency.ts +1 -1
  93. package/front_end/models/trace/insights/LCPBreakdown.ts +7 -6
  94. package/front_end/models/trace/insights/LCPDiscovery.ts +1 -1
  95. package/front_end/models/trace/insights/ModernHTTP.ts +7 -3
  96. package/front_end/models/trace/insights/NetworkDependencyTree.ts +1 -1
  97. package/front_end/models/trace/insights/RenderBlocking.ts +1 -1
  98. package/front_end/models/trace/insights/ThirdParties.ts +5 -2
  99. package/front_end/models/trace/insights/types.ts +11 -6
  100. package/front_end/models/trace/types/Configuration.ts +5 -0
  101. package/front_end/models/trace_source_maps_resolver/SourceMapsResolver.ts +3 -1
  102. package/front_end/models/workspace/IgnoreListManager.ts +2 -0
  103. package/front_end/models/workspace/UISourceCode.ts +2 -0
  104. package/front_end/models/workspace_diff/WorkspaceDiff.ts +4 -0
  105. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +22 -22
  106. package/front_end/panels/ai_assistance/ai_assistance-meta.ts +0 -7
  107. package/front_end/panels/ai_assistance/components/ChatInput.ts +4 -0
  108. package/front_end/panels/ai_assistance/components/ChatMessage.ts +79 -34
  109. package/front_end/panels/ai_assistance/components/WalkthroughView.ts +1 -2
  110. package/front_end/panels/ai_assistance/components/walkthroughView.css +1 -1
  111. package/front_end/panels/application/ApplicationPanelSidebar.ts +9 -106
  112. package/front_end/panels/application/ApplicationPanelTreeElement.ts +1 -1
  113. package/front_end/panels/application/application.ts +0 -10
  114. package/front_end/panels/application/components/components.ts +0 -4
  115. package/front_end/panels/browser_debugger/CategorizedBreakpointsSidebarPane.ts +0 -5
  116. package/front_end/panels/browser_debugger/EventListenerBreakpointsSidebarPane.ts +6 -12
  117. package/front_end/panels/browser_debugger/browser_debugger-meta.ts +5 -3
  118. package/front_end/panels/console/ConsoleFormat.ts +2 -62
  119. package/front_end/panels/console/ConsoleViewMessage.ts +14 -7
  120. package/front_end/panels/elements/ElementsTreeElement.ts +1 -2
  121. package/front_end/panels/emulation/AdvancedApp.ts +1 -2
  122. package/front_end/panels/emulation/DeviceModeToolbar.ts +83 -69
  123. package/front_end/panels/emulation/DeviceModeView.ts +4 -134
  124. package/front_end/panels/emulation/DeviceModeWrapper.ts +52 -69
  125. package/front_end/panels/emulation/MediaQueryInspector.ts +2 -2
  126. package/front_end/panels/emulation/emulation-meta.ts +12 -12
  127. package/front_end/panels/issues/IssuesPane.ts +3 -1
  128. package/front_end/panels/lighthouse/LighthouseController.ts +6 -0
  129. package/front_end/panels/linear_memory_inspector/components/LinearMemoryInspector.ts +17 -21
  130. package/front_end/panels/linear_memory_inspector/components/LinearMemoryNavigator.ts +206 -126
  131. package/front_end/panels/linear_memory_inspector/components/linearMemoryInspector.css +1 -1
  132. package/front_end/panels/mobile_throttling/CalibrationController.ts +1 -1
  133. package/front_end/panels/mobile_throttling/MobileThrottlingSelector.ts +3 -3
  134. package/front_end/panels/mobile_throttling/NetworkPanelIndicator.ts +6 -6
  135. package/front_end/panels/mobile_throttling/NetworkThrottlingSelector.ts +14 -12
  136. package/front_end/panels/mobile_throttling/ThrottlingManager.ts +13 -13
  137. package/front_end/panels/mobile_throttling/ThrottlingPresets.ts +10 -10
  138. package/front_end/panels/mobile_throttling/ThrottlingSettingsTab.ts +28 -28
  139. package/front_end/panels/mobile_throttling/mobile_throttling-meta.ts +6 -6
  140. package/front_end/panels/network/RequestHeadersView.ts +1 -1
  141. package/front_end/panels/network/ResourceChunkView.ts +243 -144
  142. package/front_end/panels/network/ResourceDirectSocketChunkView.ts +52 -29
  143. package/front_end/panels/network/ResourceWebSocketFrameView.ts +21 -35
  144. package/front_end/panels/network/resourceChunkView.css +41 -36
  145. package/front_end/panels/profiler/HeapDetachedElementsDataGrid.ts +21 -12
  146. package/front_end/panels/profiler/profiler.ts +4 -0
  147. package/front_end/panels/recorder/RecorderPanel.ts +6 -14
  148. package/front_end/panels/sensors/LocationsSettingsTab.ts +7 -7
  149. package/front_end/panels/settings/AISettingsTab.ts +75 -31
  150. package/front_end/panels/settings/SettingsScreen.ts +8 -13
  151. package/front_end/panels/settings/components/SyncSection.ts +3 -4
  152. package/front_end/panels/settings/emulation/DevicesSettingsTab.ts +650 -18
  153. package/front_end/panels/settings/emulation/devicesSettingsTab.css +18 -0
  154. package/front_end/panels/settings/settings-meta.ts +0 -1
  155. package/front_end/panels/sources/CategorizedBreakpointL10n.ts +0 -2
  156. package/front_end/panels/sources/DebuggerPausedMessage.ts +11 -5
  157. package/front_end/panels/sources/DebuggerPlugin.ts +1 -1
  158. package/front_end/panels/sources/EditingLocationHistoryManager.ts +12 -9
  159. package/front_end/panels/sources/SourcesPanel.ts +19 -17
  160. package/front_end/panels/sources/SourcesView.ts +191 -96
  161. package/front_end/panels/sources/TabbedEditorContainer.ts +3 -4
  162. package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +2 -5
  163. package/front_end/panels/timeline/TimelinePanel.ts +2 -0
  164. package/front_end/panels/timeline/TimelineUIUtils.ts +15 -0
  165. package/front_end/panels/timeline/components/CWVMetrics.ts +16 -3
  166. package/front_end/panels/timeline/components/cwvMetrics.css +4 -0
  167. package/front_end/panels/timeline/components/liveMetricsView.css +0 -17
  168. package/front_end/panels/timeline/components/metricValueStyles.css +17 -0
  169. package/front_end/panels/timeline/timeline-meta.ts +3 -5
  170. package/front_end/third_party/chromium/README.chromium +1 -1
  171. package/front_end/third_party/codemirror.next/chunk/angular.js +1 -1
  172. package/front_end/third_party/codemirror.next/chunk/codemirror.js +1 -1
  173. package/front_end/third_party/codemirror.next/chunk/codemirror.js.map +1 -1
  174. package/front_end/third_party/codemirror.next/chunk/cpp.js +1 -1
  175. package/front_end/third_party/codemirror.next/chunk/cpp.js.map +1 -1
  176. package/front_end/third_party/codemirror.next/chunk/java.js +1 -1
  177. package/front_end/third_party/codemirror.next/chunk/java.js.map +1 -1
  178. package/front_end/third_party/codemirror.next/chunk/legacy.js +1 -1
  179. package/front_end/third_party/codemirror.next/chunk/less.js +1 -1
  180. package/front_end/third_party/codemirror.next/chunk/less.js.map +1 -1
  181. package/front_end/third_party/codemirror.next/chunk/markdown.js +1 -1
  182. package/front_end/third_party/codemirror.next/chunk/markdown.js.map +1 -1
  183. package/front_end/third_party/codemirror.next/chunk/php.js +1 -1
  184. package/front_end/third_party/codemirror.next/chunk/php.js.map +1 -1
  185. package/front_end/third_party/codemirror.next/chunk/python.js +1 -1
  186. package/front_end/third_party/codemirror.next/chunk/python.js.map +1 -1
  187. package/front_end/third_party/codemirror.next/chunk/sass.js +1 -1
  188. package/front_end/third_party/codemirror.next/chunk/sass.js.map +1 -1
  189. package/front_end/third_party/codemirror.next/chunk/svelte.js +1 -1
  190. package/front_end/third_party/codemirror.next/chunk/svelte.js.map +1 -1
  191. package/front_end/third_party/codemirror.next/chunk/vue.js +1 -1
  192. package/front_end/third_party/codemirror.next/chunk/wast.js +1 -1
  193. package/front_end/third_party/codemirror.next/chunk/wast.js.map +1 -1
  194. package/front_end/third_party/codemirror.next/chunk/xml.js +1 -1
  195. package/front_end/third_party/codemirror.next/chunk/xml.js.map +1 -1
  196. package/front_end/third_party/codemirror.next/codemirror.next.d.ts +1332 -193
  197. package/front_end/third_party/codemirror.next/codemirror.next.js +1 -1
  198. package/front_end/third_party/codemirror.next/rebuild.sh +1 -1
  199. package/front_end/third_party/puppeteer/README.chromium +2 -2
  200. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +64 -15
  201. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +65 -16
  202. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Page.d.ts +50 -7
  203. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Page.d.ts.map +1 -1
  204. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Page.js.map +1 -1
  205. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/BidiOverCdp.d.ts +2 -1
  206. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/BidiOverCdp.d.ts.map +1 -1
  207. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/BidiOverCdp.js +5 -5
  208. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/BidiOverCdp.js.map +1 -1
  209. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/Connection.d.ts +2 -1
  210. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/Connection.d.ts.map +1 -1
  211. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/Connection.js +8 -6
  212. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/Connection.js.map +1 -1
  213. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/Dialog.d.ts.map +1 -1
  214. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/Dialog.js +3 -0
  215. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/Dialog.js.map +1 -1
  216. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Browser.d.ts.map +1 -1
  217. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Browser.js +16 -1
  218. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Browser.js.map +1 -1
  219. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Connection.d.ts +5 -1
  220. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Connection.d.ts.map +1 -1
  221. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Connection.js +11 -6
  222. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Connection.js.map +1 -1
  223. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Dialog.d.ts.map +1 -1
  224. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Dialog.js +5 -0
  225. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Dialog.js.map +1 -1
  226. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/NetworkManager.d.ts.map +1 -1
  227. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/NetworkManager.js +10 -0
  228. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/NetworkManager.js.map +1 -1
  229. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/BrowserConnector.js +4 -3
  230. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/BrowserConnector.js.map +1 -1
  231. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.d.ts +27 -2
  232. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.d.ts.map +1 -1
  233. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.js +12 -1
  234. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.js.map +1 -1
  235. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/util.d.ts +2 -2
  236. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/util.d.ts.map +1 -1
  237. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/util.js +2 -2
  238. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/util.js.map +1 -1
  239. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
  240. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/ChromeLauncher.js +2 -0
  241. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/ChromeLauncher.js.map +1 -1
  242. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/ScreenRecorder.d.ts +2 -1
  243. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/ScreenRecorder.d.ts.map +1 -1
  244. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/ScreenRecorder.js +5 -4
  245. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/ScreenRecorder.js.map +1 -1
  246. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.d.ts +3 -3
  247. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.js +3 -3
  248. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.d.ts +1 -1
  249. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.js +1 -1
  250. package/front_end/third_party/puppeteer/package/lib/types.d.ts +64 -15
  251. package/front_end/third_party/puppeteer/package/package.json +2 -2
  252. package/front_end/third_party/puppeteer/package/src/api/Page.ts +50 -7
  253. package/front_end/third_party/puppeteer/package/src/bidi/BidiOverCdp.ts +9 -7
  254. package/front_end/third_party/puppeteer/package/src/bidi/Connection.ts +9 -6
  255. package/front_end/third_party/puppeteer/package/src/bidi/Dialog.ts +4 -0
  256. package/front_end/third_party/puppeteer/package/src/cdp/Browser.ts +24 -1
  257. package/front_end/third_party/puppeteer/package/src/cdp/Connection.ts +11 -6
  258. package/front_end/third_party/puppeteer/package/src/cdp/Dialog.ts +7 -0
  259. package/front_end/third_party/puppeteer/package/src/cdp/NetworkManager.ts +11 -0
  260. package/front_end/third_party/puppeteer/package/src/common/BrowserConnector.ts +7 -3
  261. package/front_end/third_party/puppeteer/package/src/common/Debug.ts +30 -5
  262. package/front_end/third_party/puppeteer/package/src/common/util.ts +2 -2
  263. package/front_end/third_party/puppeteer/package/src/node/ChromeLauncher.ts +2 -0
  264. package/front_end/third_party/puppeteer/package/src/node/ScreenRecorder.ts +6 -4
  265. package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
  266. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  267. package/front_end/tsconfig.compatibility.json +2 -1
  268. package/front_end/tsconfig.json +1 -1
  269. package/front_end/ui/components/icon_button/IconButton.ts +1 -1
  270. package/front_end/ui/components/issue_counter/IssueCounter.ts +1 -1
  271. package/front_end/ui/components/settings/SettingCheckbox.ts +4 -7
  272. package/front_end/ui/components/settings/settings.ts +1 -2
  273. package/front_end/ui/legacy/Treeoutline.ts +52 -30
  274. package/front_end/ui/legacy/components/object_ui/CSSStyleSanitizer.ts +74 -0
  275. package/front_end/ui/legacy/components/object_ui/CustomPreviewComponent.ts +6 -1
  276. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +47 -54
  277. package/front_end/ui/legacy/components/object_ui/object_ui.ts +2 -0
  278. package/front_end/ui/legacy/components/quick_open/CommandMenu.ts +5 -27
  279. package/front_end/ui/legacy/components/settings_ui/SettingsUI.ts +10 -12
  280. package/front_end/ui/settings/SettingUIRegistration.ts +0 -8
  281. package/front_end/ui/visual_logging/KnownContextValues.ts +37 -0
  282. package/inspector_overlay/tsconfig.json +8 -2
  283. package/package.json +3 -2
  284. package/.agents/skills/gerrit-cli/scripts/gerrit_client_wrapper.py +0 -47
  285. package/front_end/core/sdk/CSSModel.test.api.ts +0 -102
  286. package/front_end/core/sdk/DOMModel.test.api.ts +0 -104
  287. package/front_end/foundation/Universe.test.api.ts +0 -73
  288. package/front_end/panels/application/SharedStorageEventsView.ts +0 -128
  289. package/front_end/panels/application/SharedStorageItemsView.ts +0 -179
  290. package/front_end/panels/application/SharedStorageListTreeElement.ts +0 -65
  291. package/front_end/panels/application/SharedStorageModel.ts +0 -249
  292. package/front_end/panels/application/SharedStorageTreeElement.ts +0 -40
  293. package/front_end/panels/application/components/SharedStorageAccessGrid.ts +0 -191
  294. package/front_end/panels/application/components/SharedStorageMetadataView.ts +0 -133
  295. package/front_end/panels/application/components/sharedStorageAccessGrid.css +0 -29
  296. package/front_end/panels/application/components/sharedStorageMetadataView.css +0 -29
  297. package/front_end/panels/application/sharedStorageEventsView.css +0 -9
  298. package/front_end/panels/network/OriginalResourceChunkView.ts +0 -340
  299. package/front_end/panels/network/originalResourceChunkView.css +0 -49
  300. package/front_end/ui/components/settings/SettingDeprecationWarning.ts +0 -47
  301. package/front_end/ui/components/settings/settingDeprecationWarning.css +0 -14
  302. /package/front_end/generated/{ARIAProperties.js → ARIAProperties.ts} +0 -0
@@ -74,17 +74,188 @@ const UIStrings = {
74
74
  * @description Error message in the Devices settings tab shown when the user agent string is empty.
75
75
  */
76
76
  userAgentStringCannotBeEmpty: 'User agent string can’t be empty.',
77
+ /**
78
+ * @description Label for portrait safe-area values on a custom device.
79
+ */
80
+ portraitSafeArea: 'Portrait safe area',
81
+ /**
82
+ * @description Label for landscape safe-area values on a custom device.
83
+ */
84
+ landscapeSafeArea: 'Landscape safe area',
85
+ /**
86
+ * @description Placeholder text for a custom device safe-area left inset field.
87
+ */
88
+ safeAreaLeft: 'Left inset',
89
+ /**
90
+ * @description Placeholder text for a custom device safe-area top inset field.
91
+ */
92
+ safeAreaTop: 'Top inset',
93
+ /**
94
+ * @description Placeholder text for a custom device safe-area right inset field.
95
+ */
96
+ safeAreaRight: 'Right inset',
97
+ /**
98
+ * @description Placeholder text for a custom device safe-area bottom inset field.
99
+ */
100
+ safeAreaBottom: 'Bottom inset',
101
+ /**
102
+ * @description Error message shown when a custom device safe-area value is invalid.
103
+ * @example {Portrait safe area} PH1
104
+ * @example {Top inset} PH2
105
+ * @example {9999} PH3
106
+ */
107
+ safeAreaValueMustBeInRange: '{PH1}: {PH2} must be an integer from 0 to {PH3}.',
108
+ /**
109
+ * @description Error message shown when custom device safe-area left and right insets are too large.
110
+ * @example {Portrait safe area} PH1
111
+ */
112
+ safeAreaHorizontalInsetsExceedWidth: '{PH1}: Left and right insets must not exceed the device width.',
113
+ /**
114
+ * @description Error message shown when custom device safe-area top and bottom insets are too large.
115
+ * @example {Landscape safe area} PH1
116
+ */
117
+ safeAreaVerticalInsetsExceedHeight: '{PH1}: Top and bottom insets must not exceed the device height.',
118
+ /**
119
+ * @description Label for display cutout values on a custom device.
120
+ */
121
+ displayCutout: 'Display cutout',
122
+ /**
123
+ * @description Option shown when a custom device has no display cutout.
124
+ */
125
+ noDisplayCutout: 'No cutout',
126
+ /**
127
+ * @description Option shown for pill-shaped display cutouts such as Dynamic Island.
128
+ */
129
+ pillDisplayCutout: 'Pill',
130
+ /**
131
+ * @description Option shown for classic notch display cutouts.
132
+ */
133
+ notchDisplayCutout: 'Notch',
134
+ /**
135
+ * @description Option shown for circular display cutouts such as hole-punch cameras.
136
+ */
137
+ circleDisplayCutout: 'Circle',
138
+ /**
139
+ * @description Option shown for rectangular display cutouts.
140
+ */
141
+ rectangleDisplayCutout: 'Rectangle',
142
+ /**
143
+ * @description Placeholder text for a custom device display cutout x coordinate field.
144
+ */
145
+ cutoutX: 'Cutout x',
146
+ /**
147
+ * @description Placeholder text for a custom device display cutout y coordinate field.
148
+ */
149
+ cutoutY: 'Cutout y',
150
+ /**
151
+ * @description Placeholder text for a custom device display cutout width field.
152
+ */
153
+ cutoutWidth: 'Cutout width',
154
+ /**
155
+ * @description Placeholder text for a custom device display cutout height field.
156
+ */
157
+ cutoutHeight: 'Cutout height',
158
+ /**
159
+ * @description Placeholder text for a custom device pill-shaped display cutout radius field.
160
+ */
161
+ cutoutBorderRadius: 'Pill radius',
162
+ /**
163
+ * @description Placeholder text for a custom device notch upper radius field.
164
+ */
165
+ cutoutUpperRadius: 'Upper radius',
166
+ /**
167
+ * @description Placeholder text for a custom device notch lower radius field.
168
+ */
169
+ cutoutLowerRadius: 'Lower radius',
170
+ /**
171
+ * @description Placeholder text for a custom device circular display cutout center x coordinate field.
172
+ */
173
+ cutoutCenterX: 'Center x',
174
+ /**
175
+ * @description Placeholder text for a custom device circular display cutout center y coordinate field.
176
+ */
177
+ cutoutCenterY: 'Center y',
178
+ /**
179
+ * @description Placeholder text for a custom device circular display cutout radius field.
180
+ */
181
+ cutoutRadius: 'Radius',
182
+ /**
183
+ * @description Error message shown when a custom display cutout field is required.
184
+ * @example {Cutout width} PH1
185
+ */
186
+ cutoutFieldRequired: '{PH1} is required when display cutout is enabled.',
187
+ /**
188
+ * @description Error message shown when a custom display cutout field is outside its supported integer range.
189
+ * @example {Cutout x} PH1
190
+ * @example {9999} PH2
191
+ */
192
+ cutoutValueMustBeInRange: '{PH1} must be an integer from 0 to {PH2}.',
193
+ /**
194
+ * @description Error message shown when a custom display cutout field must be positive.
195
+ * @example {Cutout width} PH1
196
+ */
197
+ cutoutValueMustBePositiveInteger: '{PH1} must be a positive integer.',
198
+ /**
199
+ * @description Error message shown when a custom display cutout's x coordinate plus its width exceeds the device width.
200
+ */
201
+ cutoutXAndWidthExceedDeviceWidth: 'Cutout x plus width must not exceed the device width.',
202
+ /**
203
+ * @description Error message shown when a custom display cutout's y coordinate plus its height exceeds the device height.
204
+ */
205
+ cutoutYAndHeightExceedDeviceHeight: 'Cutout y plus height must not exceed the device height.',
206
+ /**
207
+ * @description Error message shown when a circular display cutout is outside its cutout bounds.
208
+ */
209
+ circleMustFitCutoutBounds: 'Circle must fit within the cutout bounds.',
77
210
  } as const;
78
211
  const str_ = i18n.i18n.registerUIStrings('panels/settings/emulation/DevicesSettingsTab.ts', UIStrings);
79
212
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
80
213
 
214
+ function parseOptionalNonNegativeInteger(value: string): number|null {
215
+ const trimmedValue = value.trim();
216
+ if (!trimmedValue) {
217
+ return 0;
218
+ }
219
+ if (!/^\d+$/.test(trimmedValue)) {
220
+ return null;
221
+ }
222
+ const parsedValue = Number(trimmedValue);
223
+ if (!Number.isSafeInteger(parsedValue) || parsedValue < 0 ||
224
+ parsedValue > EmulationModel.DeviceModeModel.MaxDeviceSize) {
225
+ return null;
226
+ }
227
+ return parsedValue;
228
+ }
229
+
230
+ const NO_CUSTOM_CUTOUT = 'none';
231
+
232
+ type DeviceEditorValidator = (item: EmulationModel.EmulatedDevices.EmulatedDevice, index: number,
233
+ input: UI.ListWidget.EditorControl) => UI.ListWidget.ValidatorResult;
234
+
235
+ interface CutoutFieldValidators {
236
+ shape: DeviceEditorValidator;
237
+ x: DeviceEditorValidator;
238
+ y: DeviceEditorValidator;
239
+ width: DeviceEditorValidator;
240
+ height: DeviceEditorValidator;
241
+ pillRadius: DeviceEditorValidator;
242
+ notchUpperRadius: DeviceEditorValidator;
243
+ notchLowerRadius: DeviceEditorValidator;
244
+ circleCenterX: DeviceEditorValidator;
245
+ circleCenterY: DeviceEditorValidator;
246
+ circleRadius: DeviceEditorValidator;
247
+ }
248
+
81
249
  export class DevicesSettingsTab extends UI.Widget.VBox implements
82
250
  UI.ListWidget.Delegate<EmulationModel.EmulatedDevices.EmulatedDevice> {
83
251
  containerElement: HTMLElement;
84
252
  private readonly addCustomButton: Buttons.Button.Button;
85
253
  private readonly ariaSuccessMessageElement: HTMLElement;
86
254
  readonly #customDeviceList: UI.ListWidget.ListWidget<EmulationModel.EmulatedDevices.EmulatedDevice>;
87
- readonly #defaultDeviceList: UI.ListWidget.ListWidget<EmulationModel.EmulatedDevices.EmulatedDevice>;
255
+ readonly #defaultDeviceLists = new Map<EmulationModel.EmulatedDevices.Category, {
256
+ container: HTMLElement,
257
+ list: UI.ListWidget.ListWidget<EmulationModel.EmulatedDevices.EmulatedDevice>,
258
+ }>();
88
259
  private muteUpdate: boolean;
89
260
  private emulatedDevicesList: EmulationModel.EmulatedDevices.EmulatedDevicesList;
90
261
  private editor?: UI.ListWidget.Editor<EmulationModel.EmulatedDevices.EmulatedDevice>;
@@ -97,10 +268,6 @@ export class DevicesSettingsTab extends UI.Widget.VBox implements
97
268
  this.contentElement.createChild('div', 'settings-card-container-wrapper').createChild('div');
98
269
  this.containerElement.classList.add('settings-card-container', 'ignore-list-settings');
99
270
 
100
- this.#defaultDeviceList = new UI.ListWidget.ListWidget(this, false /* delegatesFocus */);
101
- this.#defaultDeviceList.registerRequiredCSS(devicesSettingsTabStyles);
102
- this.#defaultDeviceList.element.classList.add('devices-list', 'device-card-content');
103
-
104
271
  this.muteUpdate = false;
105
272
  this.emulatedDevicesList = EmulationModel.EmulatedDevices.EmulatedDevicesList.instance();
106
273
  this.emulatedDevicesList.addEventListener(
@@ -132,7 +299,23 @@ export class DevicesSettingsTab extends UI.Widget.VBox implements
132
299
 
133
300
  const defaultDevicesCard = this.containerElement.createChild('devtools-card');
134
301
  defaultDevicesCard.heading = i18nString(UIStrings.defaultDevices);
135
- defaultDevicesCard.append(this.#defaultDeviceList.element);
302
+
303
+ for (const category of EmulationModel.EmulatedDevices.CATEGORY_ORDER) {
304
+ const groupContainer = document.createElement('div');
305
+ groupContainer.classList.add('device-group-container');
306
+
307
+ const groupTitle = groupContainer.createChild('div', 'device-group-title');
308
+ groupTitle.textContent = EmulationModel.EmulatedDevices.getCategoryTitle(category);
309
+
310
+ defaultDevicesCard.append(groupContainer);
311
+
312
+ const listWidget = new UI.ListWidget.ListWidget(this, false /* delegatesFocus */);
313
+ listWidget.registerRequiredCSS(devicesSettingsTabStyles);
314
+ listWidget.element.classList.add('devices-list', 'device-card-content');
315
+ listWidget.show(groupContainer);
316
+
317
+ this.#defaultDeviceLists.set(category, {container: groupContainer, list: listWidget});
318
+ }
136
319
  }
137
320
 
138
321
  override wasShown(): void {
@@ -145,18 +328,32 @@ export class DevicesSettingsTab extends UI.Widget.VBox implements
145
328
  return;
146
329
  }
147
330
 
148
- this.#defaultDeviceList.clear();
331
+ for (const {list} of this.#defaultDeviceLists.values()) {
332
+ list.clear();
333
+ }
149
334
  this.#customDeviceList.clear();
150
335
 
151
- let devices = this.emulatedDevicesList.custom().slice();
152
- for (let i = 0; i < devices.length; ++i) {
153
- this.#customDeviceList.appendItem(devices[i], true);
336
+ const customDevices = this.emulatedDevicesList.custom().slice();
337
+ for (let i = 0; i < customDevices.length; ++i) {
338
+ this.#customDeviceList.appendItem(customDevices[i], true);
339
+ }
340
+
341
+ const standardDevices = this.emulatedDevicesList.standard().slice();
342
+ standardDevices.sort(EmulationModel.EmulatedDevices.EmulatedDevice.deviceComparator);
343
+
344
+ const categoryItemCounts = new Map<EmulationModel.EmulatedDevices.Category, number>();
345
+ for (const device of standardDevices) {
346
+ const cat = EmulationModel.EmulatedDevices.deviceCategory(device);
347
+ const group = this.#defaultDeviceLists.get(cat);
348
+ if (group) {
349
+ group.list.appendItem(device, false);
350
+ categoryItemCounts.set(cat, (categoryItemCounts.get(cat) || 0) + 1);
351
+ }
154
352
  }
155
353
 
156
- devices = this.emulatedDevicesList.standard().slice();
157
- devices.sort(EmulationModel.EmulatedDevices.EmulatedDevice.deviceComparator);
158
- for (let i = 0; i < devices.length; ++i) {
159
- this.#defaultDeviceList.appendItem(devices[i], false);
354
+ for (const [cat, group] of this.#defaultDeviceLists.entries()) {
355
+ const count = categoryItemCounts.get(cat) || 0;
356
+ group.container.style.display = count > 0 ? '' : 'none';
160
357
  }
161
358
  }
162
359
 
@@ -185,6 +382,21 @@ export class DevicesSettingsTab extends UI.Widget.VBox implements
185
382
  return value ? String(value) : '';
186
383
  }
187
384
 
385
+ private verticalMode(device: EmulationModel.EmulatedDevices.EmulatedDevice): EmulationModel.EmulatedDevices.Mode
386
+ |null {
387
+ return device.modes.find(mode => mode.orientation === EmulationModel.EmulatedDevices.Vertical) || null;
388
+ }
389
+
390
+ private horizontalMode(device: EmulationModel.EmulatedDevices.EmulatedDevice): EmulationModel.EmulatedDevices.Mode
391
+ |null {
392
+ return device.modes.find(mode => mode.orientation === EmulationModel.EmulatedDevices.Horizontal) || null;
393
+ }
394
+
395
+ private editorIntegerValue(editor: UI.ListWidget.Editor<EmulationModel.EmulatedDevices.EmulatedDevice>,
396
+ controlName: string): number {
397
+ return parseOptionalNonNegativeInteger(editor.control(controlName).value) ?? 0;
398
+ }
399
+
188
400
  renderItem(device: EmulationModel.EmulatedDevices.EmulatedDevice, editable: boolean): Element {
189
401
  const label = document.createElement('label');
190
402
  label.classList.add('devices-list-item');
@@ -222,18 +434,32 @@ export class DevicesSettingsTab extends UI.Widget.VBox implements
222
434
  device.deviceScaleFactor = editor.control('scale').value ? parseFloat(editor.control('scale').value) : 0;
223
435
  device.userAgent = editor.control('user-agent').value;
224
436
  device.modes = [];
225
- device.modes.push({
437
+ const verticalMode: EmulationModel.EmulatedDevices.Mode = {
226
438
  title: '',
227
439
  orientation: EmulationModel.EmulatedDevices.Vertical,
228
440
  insets: new EmulationModel.DeviceModeModel.Insets(0, 0, 0, 0),
229
441
  image: null,
230
- });
231
- device.modes.push({
442
+ };
443
+ const safeAreaInsets = this.safeAreaInsetsFromEditor(editor);
444
+ if (safeAreaInsets) {
445
+ verticalMode.safeAreaInsets = safeAreaInsets;
446
+ }
447
+ const cutout = this.cutoutFromEditor(editor);
448
+ if (cutout) {
449
+ verticalMode.cutout = cutout;
450
+ }
451
+ device.modes.push(verticalMode);
452
+ const horizontalMode: EmulationModel.EmulatedDevices.Mode = {
232
453
  title: '',
233
454
  orientation: EmulationModel.EmulatedDevices.Horizontal,
234
455
  insets: new EmulationModel.DeviceModeModel.Insets(0, 0, 0, 0),
235
456
  image: null,
236
- });
457
+ };
458
+ const landscapeSafeAreaInsets = this.safeAreaInsetsFromEditor(editor, 'landscape-');
459
+ if (landscapeSafeAreaInsets) {
460
+ horizontalMode.safeAreaInsets = landscapeSafeAreaInsets;
461
+ }
462
+ device.modes.push(horizontalMode);
237
463
  device.capabilities = [];
238
464
  const uaType = editor.control('ua-type').value;
239
465
  if (uaType === EmulationModel.DeviceModeModel.UA.MOBILE ||
@@ -277,6 +503,8 @@ export class DevicesSettingsTab extends UI.Widget.VBox implements
277
503
  editor.control('height').value = this.toNumericInputValue(device.vertical.height);
278
504
  editor.control('scale').value = this.toNumericInputValue(device.deviceScaleFactor);
279
505
  editor.control('user-agent').value = device.userAgent;
506
+ this.populateSafeAreaEditor(editor, device);
507
+ this.populateCutoutEditor(editor, device);
280
508
  let uaType;
281
509
  if (device.mobile()) {
282
510
  uaType =
@@ -292,6 +520,118 @@ export class DevicesSettingsTab extends UI.Widget.VBox implements
292
520
  return editor;
293
521
  }
294
522
 
523
+ private safeAreaInsetsFromEditor(editor: UI.ListWidget.Editor<EmulationModel.EmulatedDevices.EmulatedDevice>,
524
+ controlPrefix = ''): EmulationModel.DeviceModeModel.Insets|null {
525
+ const left = this.editorIntegerValue(editor, `${controlPrefix}safe-area-left`);
526
+ const top = this.editorIntegerValue(editor, `${controlPrefix}safe-area-top`);
527
+ const right = this.editorIntegerValue(editor, `${controlPrefix}safe-area-right`);
528
+ const bottom = this.editorIntegerValue(editor, `${controlPrefix}safe-area-bottom`);
529
+ if (!left && !top && !right && !bottom) {
530
+ return null;
531
+ }
532
+ return new EmulationModel.DeviceModeModel.Insets(left, top, right, bottom);
533
+ }
534
+
535
+ private cutoutFromEditor(editor: UI.ListWidget.Editor<EmulationModel.EmulatedDevices.EmulatedDevice>):
536
+ EmulationModel.EmulatedDevices.Cutout|null {
537
+ const shape = editor.control('cutout-shape').value;
538
+ const baseCutout = {
539
+ x: this.editorIntegerValue(editor, 'cutout-x'),
540
+ y: this.editorIntegerValue(editor, 'cutout-y'),
541
+ width: this.editorIntegerValue(editor, 'cutout-width'),
542
+ height: this.editorIntegerValue(editor, 'cutout-height'),
543
+ };
544
+ switch (shape) {
545
+ case EmulationModel.EmulatedDevices.CutoutShape.PILL:
546
+ return {
547
+ shape,
548
+ ...baseCutout,
549
+ borderRadius: this.editorIntegerValue(editor, 'cutout-border-radius'),
550
+ };
551
+ case EmulationModel.EmulatedDevices.CutoutShape.NOTCH:
552
+ return {
553
+ shape,
554
+ ...baseCutout,
555
+ upperRadius: this.editorIntegerValue(editor, 'cutout-upper-radius'),
556
+ lowerRadius: this.editorIntegerValue(editor, 'cutout-lower-radius'),
557
+ };
558
+ case EmulationModel.EmulatedDevices.CutoutShape.CIRCLE:
559
+ return {
560
+ shape,
561
+ ...baseCutout,
562
+ cx: this.editorIntegerValue(editor, 'cutout-cx'),
563
+ cy: this.editorIntegerValue(editor, 'cutout-cy'),
564
+ radius: this.editorIntegerValue(editor, 'cutout-radius'),
565
+ };
566
+ case EmulationModel.EmulatedDevices.CutoutShape.RECTANGLE:
567
+ return {shape, ...baseCutout};
568
+ default:
569
+ return null;
570
+ }
571
+ }
572
+
573
+ private populateSafeAreaEditor(editor: UI.ListWidget.Editor<EmulationModel.EmulatedDevices.EmulatedDevice>,
574
+ device: EmulationModel.EmulatedDevices.EmulatedDevice): void {
575
+ const safeAreaInsets = this.verticalMode(device)?.safeAreaInsets;
576
+ editor.control('safe-area-left').value = this.toNumericInputValue(safeAreaInsets?.left || 0);
577
+ editor.control('safe-area-top').value = this.toNumericInputValue(safeAreaInsets?.top || 0);
578
+ editor.control('safe-area-right').value = this.toNumericInputValue(safeAreaInsets?.right || 0);
579
+ editor.control('safe-area-bottom').value = this.toNumericInputValue(safeAreaInsets?.bottom || 0);
580
+
581
+ const landscapeSafeAreaInsets = this.horizontalMode(device)?.safeAreaInsets;
582
+ editor.control('landscape-safe-area-left').value = this.toNumericInputValue(landscapeSafeAreaInsets?.left || 0);
583
+ editor.control('landscape-safe-area-top').value = this.toNumericInputValue(landscapeSafeAreaInsets?.top || 0);
584
+ editor.control('landscape-safe-area-right').value = this.toNumericInputValue(landscapeSafeAreaInsets?.right || 0);
585
+ editor.control('landscape-safe-area-bottom').value = this.toNumericInputValue(landscapeSafeAreaInsets?.bottom || 0);
586
+ }
587
+
588
+ private populateCutoutEditor(editor: UI.ListWidget.Editor<EmulationModel.EmulatedDevices.EmulatedDevice>,
589
+ device: EmulationModel.EmulatedDevices.EmulatedDevice): void {
590
+ const cutout = this.verticalMode(device)?.cutout;
591
+ editor.control('cutout-shape').value = cutout?.shape || NO_CUSTOM_CUTOUT;
592
+ editor.control('cutout-x').value = String(cutout?.x ?? '');
593
+ editor.control('cutout-y').value = String(cutout?.y ?? '');
594
+ editor.control('cutout-width').value = String(cutout?.width ?? '');
595
+ editor.control('cutout-height').value = String(cutout?.height ?? '');
596
+ editor.control('cutout-border-radius').value =
597
+ String(cutout?.shape === EmulationModel.EmulatedDevices.CutoutShape.PILL ? cutout.borderRadius : '');
598
+ editor.control('cutout-upper-radius').value =
599
+ String(cutout?.shape === EmulationModel.EmulatedDevices.CutoutShape.NOTCH ? cutout.upperRadius : '');
600
+ editor.control('cutout-lower-radius').value =
601
+ String(cutout?.shape === EmulationModel.EmulatedDevices.CutoutShape.NOTCH ? cutout.lowerRadius : '');
602
+ editor.control('cutout-cx').value =
603
+ String(cutout?.shape === EmulationModel.EmulatedDevices.CutoutShape.CIRCLE ? cutout.cx : '');
604
+ editor.control('cutout-cy').value =
605
+ String(cutout?.shape === EmulationModel.EmulatedDevices.CutoutShape.CIRCLE ? cutout.cy : '');
606
+ editor.control('cutout-radius').value =
607
+ String(cutout?.shape === EmulationModel.EmulatedDevices.CutoutShape.CIRCLE ? cutout.radius : '');
608
+ this.updateCutoutFieldsVisibility(editor);
609
+ }
610
+
611
+ private updateCutoutFieldsVisibility(editor: UI.ListWidget.Editor<EmulationModel.EmulatedDevices.EmulatedDevice>):
612
+ void {
613
+ const shape = editor.control('cutout-shape').value;
614
+ const noCutout = shape === NO_CUSTOM_CUTOUT;
615
+ const isPill = shape === EmulationModel.EmulatedDevices.CutoutShape.PILL;
616
+ const isNotch = shape === EmulationModel.EmulatedDevices.CutoutShape.NOTCH;
617
+ const isCircle = shape === EmulationModel.EmulatedDevices.CutoutShape.CIRCLE;
618
+ const content = editor.contentElement();
619
+ const rectRow = content.querySelector<HTMLElement>('.devices-edit-cutout-rect-row');
620
+ const radiusRow = content.querySelector<HTMLElement>('.devices-edit-cutout-radius-row');
621
+ if (rectRow) {
622
+ rectRow.hidden = noCutout;
623
+ }
624
+ if (radiusRow) {
625
+ radiusRow.hidden = noCutout || !(isPill || isNotch || isCircle);
626
+ }
627
+ (editor.control('cutout-border-radius') as HTMLElement).hidden = !isPill;
628
+ (editor.control('cutout-upper-radius') as HTMLElement).hidden = !isNotch;
629
+ (editor.control('cutout-lower-radius') as HTMLElement).hidden = !isNotch;
630
+ (editor.control('cutout-cx') as HTMLElement).hidden = !isCircle;
631
+ (editor.control('cutout-cy') as HTMLElement).hidden = !isCircle;
632
+ (editor.control('cutout-radius') as HTMLElement).hidden = !isCircle;
633
+ }
634
+
295
635
  private createEditor(): UI.ListWidget.Editor<EmulationModel.EmulatedDevices.EmulatedDevice> {
296
636
  if (this.editor) {
297
637
  return this.editor;
@@ -313,6 +653,24 @@ export class DevicesSettingsTab extends UI.Widget.VBox implements
313
653
  dpr.classList.add('device-edit-fixed');
314
654
  screen.appendChild(dpr);
315
655
 
656
+ this.appendSafeAreaFields(editor, deviceFields, i18nString(UIStrings.portraitSafeArea), '',
657
+ portraitSafeAreaValidator);
658
+ this.appendSafeAreaFields(editor, deviceFields, i18nString(UIStrings.landscapeSafeArea), 'landscape-',
659
+ landscapeSafeAreaValidator);
660
+ this.appendCutoutFields(editor, content, {
661
+ shape: cutoutShapeValidator,
662
+ x: cutoutXValidator,
663
+ y: cutoutYValidator,
664
+ width: cutoutWidthValidator,
665
+ height: cutoutHeightValidator,
666
+ pillRadius: cutoutPillRadiusValidator,
667
+ notchUpperRadius: cutoutNotchUpperRadiusValidator,
668
+ notchLowerRadius: cutoutNotchLowerRadiusValidator,
669
+ circleCenterX: cutoutCircleCenterXValidator,
670
+ circleCenterY: cutoutCircleCenterYValidator,
671
+ circleRadius: cutoutCircleRadiusValidator,
672
+ });
673
+
316
674
  const uaStringFields = content.createChild('div', 'devices-edit-fields');
317
675
  UI.UIUtils.createTextChild(uaStringFields.createChild('b'), i18nString(UIStrings.userAgentString));
318
676
 
@@ -389,5 +747,279 @@ export class DevicesSettingsTab extends UI.Widget.VBox implements
389
747
  input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult {
390
748
  return EmulationModel.DeviceModeModel.DeviceModeModel.scaleValidator(input.value);
391
749
  }
750
+
751
+ function nonNegativeIntegerValue(controlName: string): number|null {
752
+ return parseOptionalNonNegativeInteger(editor.control(controlName).value);
753
+ }
754
+
755
+ function positiveIntegerValue(controlName: string): number|null {
756
+ const value = nonNegativeIntegerValue(controlName);
757
+ return value && value > 0 ? value : null;
758
+ }
759
+
760
+ function requiredIntegerValue(controlName: string): number|null {
761
+ const value = editor.control(controlName).value.trim();
762
+ return value ? parseOptionalNonNegativeInteger(value) : null;
763
+ }
764
+
765
+ function controlLabel(input: UI.ListWidget.EditorControl): string {
766
+ return input.getAttribute('aria-label') || input.getAttribute('placeholder') || '';
767
+ }
768
+
769
+ function safeAreaValidator(controlPrefix: string, orientationLabel: string, widthControlName: string,
770
+ heightControlName: string,
771
+ input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult {
772
+ const ownValue = parseOptionalNonNegativeInteger(input.value);
773
+ if (ownValue === null) {
774
+ return {
775
+ valid: false,
776
+ errorMessage: i18nString(UIStrings.safeAreaValueMustBeInRange, {
777
+ PH1: orientationLabel,
778
+ PH2: input.getAttribute('aria-label') || input.getAttribute('placeholder') || '',
779
+ PH3: EmulationModel.DeviceModeModel.MaxDeviceSize,
780
+ }),
781
+ };
782
+ }
783
+
784
+ const left = nonNegativeIntegerValue(`${controlPrefix}safe-area-left`);
785
+ const top = nonNegativeIntegerValue(`${controlPrefix}safe-area-top`);
786
+ const right = nonNegativeIntegerValue(`${controlPrefix}safe-area-right`);
787
+ const bottom = nonNegativeIntegerValue(`${controlPrefix}safe-area-bottom`);
788
+ const width = positiveIntegerValue(widthControlName);
789
+ const height = positiveIntegerValue(heightControlName);
790
+ if (input === editor.control(`${controlPrefix}safe-area-right`) && left !== null && right !== null &&
791
+ width !== null && left + right > width) {
792
+ return {
793
+ valid: false,
794
+ errorMessage: i18nString(UIStrings.safeAreaHorizontalInsetsExceedWidth, {PH1: orientationLabel}),
795
+ };
796
+ }
797
+ if (input === editor.control(`${controlPrefix}safe-area-bottom`) && top !== null && bottom !== null &&
798
+ height !== null && top + bottom > height) {
799
+ return {
800
+ valid: false,
801
+ errorMessage: i18nString(UIStrings.safeAreaVerticalInsetsExceedHeight, {PH1: orientationLabel}),
802
+ };
803
+ }
804
+ return {valid: true};
805
+ }
806
+
807
+ function portraitSafeAreaValidator(_item: EmulationModel.EmulatedDevices.EmulatedDevice, _index: number,
808
+ input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult {
809
+ return safeAreaValidator('', i18nString(UIStrings.portraitSafeArea), 'width', 'height', input);
810
+ }
811
+
812
+ function landscapeSafeAreaValidator(_item: EmulationModel.EmulatedDevices.EmulatedDevice, _index: number,
813
+ input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult {
814
+ return safeAreaValidator('landscape-', i18nString(UIStrings.landscapeSafeArea), 'height', 'width', input);
815
+ }
816
+
817
+ function cutoutShapeValidator(): UI.ListWidget.ValidatorResult {
818
+ return {valid: true};
819
+ }
820
+
821
+ function cutoutValueValidator(input: UI.ListWidget.EditorControl,
822
+ mustBePositive = false): UI.ListWidget.ValidatorResult {
823
+ const label = controlLabel(input);
824
+ const value = input.value.trim();
825
+ if (!value) {
826
+ return {valid: false, errorMessage: i18nString(UIStrings.cutoutFieldRequired, {PH1: label})};
827
+ }
828
+ const numericValue = parseOptionalNonNegativeInteger(value);
829
+ if (numericValue === null) {
830
+ return {
831
+ valid: false,
832
+ errorMessage: i18nString(UIStrings.cutoutValueMustBeInRange, {
833
+ PH1: label,
834
+ PH2: EmulationModel.DeviceModeModel.MaxDeviceSize,
835
+ }),
836
+ };
837
+ }
838
+ if (mustBePositive && numericValue === 0) {
839
+ return {valid: false, errorMessage: i18nString(UIStrings.cutoutValueMustBePositiveInteger, {PH1: label})};
840
+ }
841
+ return {valid: true};
842
+ }
843
+
844
+ function isCutoutFieldActive(shape?: EmulationModel.EmulatedDevices.CutoutShape): boolean {
845
+ const selectedShape = editor.control('cutout-shape').value;
846
+ return selectedShape !== NO_CUSTOM_CUTOUT && (shape === undefined || selectedShape === shape);
847
+ }
848
+
849
+ function cutoutXValidator(_item: EmulationModel.EmulatedDevices.EmulatedDevice, _index: number,
850
+ input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult {
851
+ return isCutoutFieldActive() ? cutoutValueValidator(input) : {valid: true};
852
+ }
853
+
854
+ function cutoutYValidator(_item: EmulationModel.EmulatedDevices.EmulatedDevice, _index: number,
855
+ input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult {
856
+ return isCutoutFieldActive() ? cutoutValueValidator(input) : {valid: true};
857
+ }
858
+
859
+ function cutoutWidthValidator(_item: EmulationModel.EmulatedDevices.EmulatedDevice, _index: number,
860
+ input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult {
861
+ if (!isCutoutFieldActive()) {
862
+ return {valid: true};
863
+ }
864
+ const validation = cutoutValueValidator(input, true);
865
+ if (!validation.valid) {
866
+ return validation;
867
+ }
868
+
869
+ const cutoutX = requiredIntegerValue('cutout-x');
870
+ const cutoutWidth = positiveIntegerValue('cutout-width');
871
+ const width = positiveIntegerValue('width');
872
+ if (width !== null && cutoutX !== null && cutoutWidth !== null && cutoutX + cutoutWidth > width) {
873
+ return {valid: false, errorMessage: i18nString(UIStrings.cutoutXAndWidthExceedDeviceWidth)};
874
+ }
875
+ return {valid: true};
876
+ }
877
+
878
+ function cutoutHeightValidator(_item: EmulationModel.EmulatedDevices.EmulatedDevice, _index: number,
879
+ input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult {
880
+ if (!isCutoutFieldActive()) {
881
+ return {valid: true};
882
+ }
883
+ const validation = cutoutValueValidator(input, true);
884
+ if (!validation.valid) {
885
+ return validation;
886
+ }
887
+
888
+ const cutoutY = requiredIntegerValue('cutout-y');
889
+ const cutoutHeight = positiveIntegerValue('cutout-height');
890
+ const height = positiveIntegerValue('height');
891
+ if (height !== null && cutoutY !== null && cutoutHeight !== null && cutoutY + cutoutHeight > height) {
892
+ return {valid: false, errorMessage: i18nString(UIStrings.cutoutYAndHeightExceedDeviceHeight)};
893
+ }
894
+ return {valid: true};
895
+ }
896
+
897
+ function cutoutPillRadiusValidator(_item: EmulationModel.EmulatedDevices.EmulatedDevice, _index: number,
898
+ input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult {
899
+ return isCutoutFieldActive(EmulationModel.EmulatedDevices.CutoutShape.PILL) ? cutoutValueValidator(input) :
900
+ {valid: true};
901
+ }
902
+
903
+ function cutoutNotchUpperRadiusValidator(_item: EmulationModel.EmulatedDevices.EmulatedDevice, _index: number,
904
+ input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult {
905
+ return isCutoutFieldActive(EmulationModel.EmulatedDevices.CutoutShape.NOTCH) ? cutoutValueValidator(input) :
906
+ {valid: true};
907
+ }
908
+
909
+ function cutoutNotchLowerRadiusValidator(_item: EmulationModel.EmulatedDevices.EmulatedDevice, _index: number,
910
+ input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult {
911
+ return isCutoutFieldActive(EmulationModel.EmulatedDevices.CutoutShape.NOTCH) ? cutoutValueValidator(input) :
912
+ {valid: true};
913
+ }
914
+
915
+ function cutoutCircleCenterXValidator(_item: EmulationModel.EmulatedDevices.EmulatedDevice, _index: number,
916
+ input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult {
917
+ return isCutoutFieldActive(EmulationModel.EmulatedDevices.CutoutShape.CIRCLE) ? cutoutValueValidator(input) :
918
+ {valid: true};
919
+ }
920
+
921
+ function cutoutCircleCenterYValidator(_item: EmulationModel.EmulatedDevices.EmulatedDevice, _index: number,
922
+ input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult {
923
+ return isCutoutFieldActive(EmulationModel.EmulatedDevices.CutoutShape.CIRCLE) ? cutoutValueValidator(input) :
924
+ {valid: true};
925
+ }
926
+
927
+ function cutoutCircleRadiusValidator(_item: EmulationModel.EmulatedDevices.EmulatedDevice, _index: number,
928
+ input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult {
929
+ if (!isCutoutFieldActive(EmulationModel.EmulatedDevices.CutoutShape.CIRCLE)) {
930
+ return {valid: true};
931
+ }
932
+ const validation = cutoutValueValidator(input, true);
933
+ if (!validation.valid) {
934
+ return validation;
935
+ }
936
+
937
+ const cutoutX = requiredIntegerValue('cutout-x');
938
+ const cutoutY = requiredIntegerValue('cutout-y');
939
+ const cutoutWidth = positiveIntegerValue('cutout-width');
940
+ const cutoutHeight = positiveIntegerValue('cutout-height');
941
+ const centerX = requiredIntegerValue('cutout-cx');
942
+ const centerY = requiredIntegerValue('cutout-cy');
943
+ const radius = positiveIntegerValue('cutout-radius');
944
+ if (cutoutX !== null && cutoutY !== null && cutoutWidth !== null && cutoutHeight !== null && centerX !== null &&
945
+ centerY !== null && radius !== null &&
946
+ (centerX - radius < cutoutX || centerX + radius > cutoutX + cutoutWidth || centerY - radius < cutoutY ||
947
+ centerY + radius > cutoutY + cutoutHeight)) {
948
+ return {valid: false, errorMessage: i18nString(UIStrings.circleMustFitCutoutBounds)};
949
+ }
950
+ return {valid: true};
951
+ }
952
+ }
953
+
954
+ private appendSafeAreaFields(editor: UI.ListWidget.Editor<EmulationModel.EmulatedDevices.EmulatedDevice>,
955
+ deviceFields: HTMLElement, title: string, controlPrefix: string,
956
+ safeAreaValidator:
957
+ (item: EmulationModel.EmulatedDevices.EmulatedDevice, index: number,
958
+ input: UI.ListWidget.EditorControl) => UI.ListWidget.ValidatorResult): void {
959
+ const safeAreaGroup = deviceFields.createChild('div', 'devices-edit-safe-area-group');
960
+ UI.ARIAUtils.markAsGroup(safeAreaGroup);
961
+ const heading = safeAreaGroup.createChild('b');
962
+ heading.id = UI.ARIAUtils.nextId('safe-area-heading-');
963
+ UI.UIUtils.createTextChild(heading, title);
964
+ safeAreaGroup.setAttribute('aria-labelledby', heading.id);
965
+ const safeAreaRow = safeAreaGroup.createChild('div', 'hbox');
966
+ safeAreaRow.appendChild(editor.createInput(`${controlPrefix}safe-area-left`, 'text',
967
+ i18nString(UIStrings.safeAreaLeft), safeAreaValidator));
968
+ safeAreaRow.appendChild(editor.createInput(`${controlPrefix}safe-area-top`, 'text',
969
+ i18nString(UIStrings.safeAreaTop), safeAreaValidator));
970
+ safeAreaRow.appendChild(editor.createInput(`${controlPrefix}safe-area-right`, 'text',
971
+ i18nString(UIStrings.safeAreaRight), safeAreaValidator));
972
+ safeAreaRow.appendChild(editor.createInput(`${controlPrefix}safe-area-bottom`, 'text',
973
+ i18nString(UIStrings.safeAreaBottom), safeAreaValidator));
974
+ }
975
+
976
+ private appendCutoutFields(editor: UI.ListWidget.Editor<EmulationModel.EmulatedDevices.EmulatedDevice>,
977
+ content: Element, validators: CutoutFieldValidators): void {
978
+ const cutoutFields = content.createChild('div', 'devices-edit-fields');
979
+ cutoutFields.classList.add('devices-edit-cutout-fields');
980
+ UI.ARIAUtils.markAsGroup(cutoutFields);
981
+ const heading = cutoutFields.createChild('b');
982
+ heading.id = UI.ARIAUtils.nextId('cutout-heading-');
983
+ UI.UIUtils.createTextChild(heading, i18nString(UIStrings.displayCutout));
984
+ cutoutFields.setAttribute('aria-labelledby', heading.id);
985
+ const shapeOptions = [
986
+ NO_CUSTOM_CUTOUT,
987
+ EmulationModel.EmulatedDevices.CutoutShape.PILL,
988
+ EmulationModel.EmulatedDevices.CutoutShape.NOTCH,
989
+ EmulationModel.EmulatedDevices.CutoutShape.CIRCLE,
990
+ EmulationModel.EmulatedDevices.CutoutShape.RECTANGLE,
991
+ ];
992
+ const shapeControl =
993
+ editor.createSelect('cutout-shape', shapeOptions, validators.shape, i18nString(UIStrings.displayCutout));
994
+ shapeControl.options[0].textContent = i18nString(UIStrings.noDisplayCutout);
995
+ shapeControl.options[1].textContent = i18nString(UIStrings.pillDisplayCutout);
996
+ shapeControl.options[2].textContent = i18nString(UIStrings.notchDisplayCutout);
997
+ shapeControl.options[3].textContent = i18nString(UIStrings.circleDisplayCutout);
998
+ shapeControl.options[4].textContent = i18nString(UIStrings.rectangleDisplayCutout);
999
+ cutoutFields.createChild('div', 'hbox').appendChild(shapeControl);
1000
+ shapeControl.addEventListener('input', () => this.updateCutoutFieldsVisibility(editor), false);
1001
+
1002
+ const rectRow = cutoutFields.createChild('div', 'hbox devices-edit-cutout-rect-row');
1003
+ rectRow.appendChild(editor.createInput('cutout-x', 'text', i18nString(UIStrings.cutoutX), validators.x));
1004
+ rectRow.appendChild(editor.createInput('cutout-y', 'text', i18nString(UIStrings.cutoutY), validators.y));
1005
+ rectRow.appendChild(
1006
+ editor.createInput('cutout-width', 'text', i18nString(UIStrings.cutoutWidth), validators.width));
1007
+ rectRow.appendChild(
1008
+ editor.createInput('cutout-height', 'text', i18nString(UIStrings.cutoutHeight), validators.height));
1009
+
1010
+ const radiusRow = cutoutFields.createChild('div', 'hbox devices-edit-cutout-radius-row');
1011
+ radiusRow.appendChild(editor.createInput('cutout-border-radius', 'text', i18nString(UIStrings.cutoutBorderRadius),
1012
+ validators.pillRadius));
1013
+ radiusRow.appendChild(editor.createInput('cutout-upper-radius', 'text', i18nString(UIStrings.cutoutUpperRadius),
1014
+ validators.notchUpperRadius));
1015
+ radiusRow.appendChild(editor.createInput('cutout-lower-radius', 'text', i18nString(UIStrings.cutoutLowerRadius),
1016
+ validators.notchLowerRadius));
1017
+ radiusRow.appendChild(
1018
+ editor.createInput('cutout-cx', 'text', i18nString(UIStrings.cutoutCenterX), validators.circleCenterX));
1019
+ radiusRow.appendChild(
1020
+ editor.createInput('cutout-cy', 'text', i18nString(UIStrings.cutoutCenterY), validators.circleCenterY));
1021
+ radiusRow.appendChild(
1022
+ editor.createInput('cutout-radius', 'text', i18nString(UIStrings.cutoutRadius), validators.circleRadius));
1023
+ this.updateCutoutFieldsVisibility(editor);
392
1024
  }
393
1025
  }