chrome-devtools-frontend 1.0.1662965 → 1.0.1664496

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 (273) hide show
  1. package/.agents/skills/{creating-a-model → devtools-model-management}/SKILL.md +0 -1
  2. package/.agents/skills/devtools-ux-writing-refactor/SKILL.md +2 -1
  3. package/.agents/skills/fixing-skipped-tests/SKILL.md +3 -3
  4. package/.agents/skills/merging-devtools-module/SKILL.md +1 -1
  5. package/.agents/skills/migrate-chromium-test/SKILL.md +3 -2
  6. package/.agents/skills/repro-flaky-tests/SKILL.md +71 -31
  7. package/.agents/skills/ui-eng-vision-widget-promoter/SKILL.md +1 -0
  8. package/docs/ui_engineering.md +2 -0
  9. package/eslint.config.mjs +1 -0
  10. package/front_end/core/common/Settings.ts +75 -0
  11. package/front_end/core/protocol_client/PuppeteerDevToolsConnection.ts +112 -0
  12. package/front_end/core/sdk/CPUProfilerModel.ts +21 -13
  13. package/front_end/core/sdk/CSSContainerQuery.ts +1 -1
  14. package/front_end/core/sdk/CSSLayer.ts +1 -1
  15. package/front_end/core/sdk/CSSMedia.ts +1 -1
  16. package/front_end/core/sdk/CSSModel.ts +1 -1
  17. package/front_end/core/sdk/CSSNavigation.ts +1 -1
  18. package/front_end/core/sdk/CSSProperty.ts +1 -1
  19. package/front_end/core/sdk/CSSQuery.ts +1 -1
  20. package/front_end/core/sdk/CSSRule.ts +1 -1
  21. package/front_end/core/sdk/CSSScope.ts +1 -1
  22. package/front_end/core/sdk/CSSStartingStyle.ts +1 -1
  23. package/front_end/core/sdk/CSSStyleDeclaration.ts +1 -1
  24. package/front_end/core/sdk/CSSStyleSheetHeader.ts +1 -1
  25. package/front_end/core/sdk/CSSSupports.ts +1 -1
  26. package/front_end/core/sdk/CompilerSourceMappingContentProvider.ts +1 -1
  27. package/front_end/core/sdk/NetworkManager.ts +1 -1
  28. package/front_end/core/sdk/NetworkRequest.ts +7 -7
  29. package/front_end/core/sdk/Resource.ts +1 -1
  30. package/front_end/core/sdk/ScopeTreeCache.ts +1 -1
  31. package/front_end/core/sdk/Script.ts +1 -1
  32. package/front_end/core/sdk/ServerSentEvents.ts +1 -1
  33. package/front_end/core/sdk/SourceMap.ts +1 -1
  34. package/front_end/core/sdk/SourceMapScopesInfo.ts +1 -1
  35. package/front_end/core/sdk/sdk-meta.ts +3 -3
  36. package/front_end/{models → core}/text_utils/ContentProvider.ts +4 -6
  37. package/front_end/{models → core}/text_utils/StaticContentProvider.ts +4 -6
  38. package/front_end/{models → core}/text_utils/StreamingContentData.ts +1 -2
  39. package/front_end/{models → core}/text_utils/TextRange.ts +6 -3
  40. package/front_end/{models → core}/text_utils/TextUtils.ts +7 -7
  41. package/front_end/{models → core}/text_utils/WasmDisassembly.ts +2 -2
  42. package/front_end/core/text_utils/text_utils.ts +27 -0
  43. package/front_end/entrypoints/formatter_worker/AcornTokenizer.ts +1 -1
  44. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshotLoader.ts +1 -1
  45. package/front_end/entrypoints/main/MainImpl.ts +14 -1
  46. package/front_end/foundation/Universe.ts +1 -1
  47. package/front_end/models/ai_assistance/AgentProject.ts +1 -1
  48. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +3 -6
  49. package/front_end/models/ai_assistance/agents/AiAgent.ts +1 -1
  50. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +19 -11
  51. package/front_end/models/ai_assistance/agents/PerformanceAgent.snapshot.txt +2 -2
  52. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +23 -10
  53. package/front_end/models/ai_assistance/agents/StorageAgent.ts +19 -18
  54. package/front_end/models/ai_assistance/agents/StylingAgent.ts +2 -3
  55. package/front_end/models/ai_assistance/contexts/FileContext.ts +7 -3
  56. package/front_end/models/ai_assistance/contexts/PerformanceTraceContext.ts +39 -11
  57. package/front_end/models/ai_assistance/data_formatters/FileFormatter.ts +6 -3
  58. package/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts +1 -1
  59. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +4 -4
  60. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +8 -8
  61. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +76 -3
  62. package/front_end/models/ai_assistance/tools/ListNetworkRequests.ts +11 -1
  63. package/front_end/models/badges/AiExplorerBadge.ts +8 -3
  64. package/front_end/models/badges/Badge.ts +1 -0
  65. package/front_end/models/badges/UserBadges.ts +11 -5
  66. package/front_end/models/bindings/CompilerScriptMapping.ts +1 -1
  67. package/front_end/models/bindings/ContentProviderBasedProject.ts +1 -1
  68. package/front_end/models/bindings/DebuggerLanguagePlugins.ts +2 -2
  69. package/front_end/models/bindings/DebuggerWorkspaceBinding.ts +1 -1
  70. package/front_end/models/bindings/DefaultScriptMapping.ts +1 -1
  71. package/front_end/models/bindings/FileUtils.ts +1 -1
  72. package/front_end/models/bindings/PresentationConsoleMessageHelper.ts +7 -10
  73. package/front_end/models/bindings/ResourceMapping.ts +1 -1
  74. package/front_end/models/bindings/ResourceScriptMapping.ts +1 -1
  75. package/front_end/models/bindings/SASSSourceMapping.ts +1 -1
  76. package/front_end/models/bindings/StylesSourceMapping.ts +1 -1
  77. package/front_end/models/breakpoints/BreakpointManager.ts +1 -1
  78. package/front_end/models/crux-manager/CrUXManager.ts +1 -1
  79. package/front_end/models/emulation/EmulatedDevices.ts +3 -2
  80. package/front_end/models/har/Importer.ts +1 -1
  81. package/front_end/models/har/Writer.ts +1 -1
  82. package/front_end/models/issues_manager/IssueAggregator.ts +9 -0
  83. package/front_end/models/issues_manager/IssuesManager.ts +5 -0
  84. package/front_end/models/issues_manager/LazyLoadImageIssue.ts +76 -0
  85. package/front_end/models/issues_manager/descriptions/lazyLoadImageZeroSize.md +7 -0
  86. package/front_end/models/issues_manager/issues_manager.ts +2 -0
  87. package/front_end/models/javascript_metadata/NativeFunctions.js +9 -10
  88. package/front_end/models/live-metrics/LiveMetrics.ts +2 -3
  89. package/front_end/models/logs/NetworkLog.ts +4 -3
  90. package/front_end/models/logs/logs-meta.ts +3 -3
  91. package/front_end/models/network_time_calculator/NetworkTimeCalculator.ts +4 -4
  92. package/front_end/models/persistence/Automapping.ts +1 -1
  93. package/front_end/models/persistence/AutomaticFileSystemWorkspaceBinding.ts +2 -2
  94. package/front_end/models/persistence/FileSystemWorkspaceBinding.ts +1 -1
  95. package/front_end/models/persistence/IsolatedFileSystem.ts +2 -2
  96. package/front_end/models/persistence/NetworkPersistenceManager.ts +1 -1
  97. package/front_end/models/persistence/PersistenceImpl.ts +1 -1
  98. package/front_end/models/persistence/PlatformFileSystem.ts +1 -1
  99. package/front_end/models/source_map_scopes/FunctionCodeResolver.ts +1 -1
  100. package/front_end/models/source_map_scopes/NamesResolver.ts +1 -1
  101. package/front_end/models/text_utils/text_utils.ts +2 -24
  102. package/front_end/models/trace/insights/ImageDelivery.ts +2 -2
  103. package/front_end/models/trace/insights/LegacyJavaScript.ts +1 -1
  104. package/front_end/models/trace/insights/RenderBlocking.ts +1 -1
  105. package/front_end/models/trace/insights/ThirdParties.ts +1 -1
  106. package/front_end/models/workspace/FileManager.ts +1 -1
  107. package/front_end/models/workspace/UISourceCode.ts +1 -1
  108. package/front_end/models/workspace/WorkspaceImpl.ts +1 -1
  109. package/front_end/models/workspace_diff/WorkspaceDiff.ts +1 -1
  110. package/front_end/panels/accessibility/AccessibilityNodeView.ts +1 -1
  111. package/front_end/panels/accessibility/AccessibilityStrings.ts +2 -2
  112. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +1 -1
  113. package/front_end/panels/ai_assistance/ExportConversation.ts +1 -1
  114. package/front_end/panels/ai_assistance/ai_assistance-meta.ts +1 -1
  115. package/front_end/panels/ai_assistance/components/ChatMessage.ts +1 -1
  116. package/front_end/panels/application/AppManifestView.ts +4 -4
  117. package/front_end/panels/application/ApplicationPanelSidebar.ts +1 -2
  118. package/front_end/panels/application/CrashReportContextView.ts +1 -1
  119. package/front_end/panels/application/DOMStorageItemsView.ts +1 -1
  120. package/front_end/panels/application/DeviceBoundSessionsView.ts +4 -4
  121. package/front_end/panels/application/ExtensionStorageItemsView.ts +1 -1
  122. package/front_end/panels/application/FrameDetailsView.ts +1 -1
  123. package/front_end/panels/application/ServiceWorkerCacheTreeElement.ts +12 -2
  124. package/front_end/panels/application/ServiceWorkerCacheViews.ts +1 -1
  125. package/front_end/panels/application/application.ts +2 -0
  126. package/front_end/panels/application/components/BackForwardCacheView.ts +21 -5
  127. package/front_end/panels/application/components/CrashReportContextGrid.ts +1 -1
  128. package/front_end/panels/application/components/TrustTokensView.ts +2 -2
  129. package/front_end/panels/application/preloading/PreloadingView.ts +1 -1
  130. package/front_end/panels/application/preloading/components/PreloadingDisabledInfobar.ts +2 -2
  131. package/front_end/panels/application/preloading/components/PreloadingString.ts +2 -2
  132. package/front_end/panels/application/preloading/components/UsedPreloadingView.ts +4 -4
  133. package/front_end/panels/autofill/AutofillView.ts +1 -1
  134. package/front_end/panels/common/AiCodeCompletionTeaser.ts +1 -1
  135. package/front_end/panels/common/ExtensionServer.ts +1 -1
  136. package/front_end/panels/console/ConsoleFilter.ts +1 -1
  137. package/front_end/panels/console/ConsoleSidebar.ts +1 -1
  138. package/front_end/panels/console/ConsoleView.ts +1 -1
  139. package/front_end/panels/console/ConsoleViewMessage.ts +1 -1
  140. package/front_end/panels/console/PromptBuilder.ts +1 -1
  141. package/front_end/panels/coverage/CoverageDecorationManager.ts +9 -1
  142. package/front_end/panels/coverage/CoverageModel.ts +1 -1
  143. package/front_end/panels/css_overview/CSSOverviewCompletedView.ts +1 -1
  144. package/front_end/panels/developer_resources/DeveloperResourcesListView.ts +1 -1
  145. package/front_end/panels/developer_resources/DeveloperResourcesView.ts +1 -1
  146. package/front_end/panels/elements/AccessibilityTreeView.ts +38 -3
  147. package/front_end/panels/elements/AdoptedStyleSheetTreeElement.ts +1 -1
  148. package/front_end/panels/elements/ElementIssueUtils.ts +2 -2
  149. package/front_end/panels/elements/ElementsTreeElement.ts +10 -3
  150. package/front_end/panels/elements/StandaloneStylesContainer.ts +1 -1
  151. package/front_end/panels/elements/StylePropertiesSection.ts +1 -1
  152. package/front_end/panels/elements/StylePropertyTreeElement.ts +1 -1
  153. package/front_end/panels/elements/StylesAiCodeCompletionProvider.ts +1 -1
  154. package/front_end/panels/elements/StylesContainer.ts +1 -1
  155. package/front_end/panels/elements/StylesSidebarPane.ts +1 -1
  156. package/front_end/panels/elements/components/CSSPropertyDocsView.ts +1 -1
  157. package/front_end/panels/explain/components/ConsoleInsight.ts +2 -2
  158. package/front_end/panels/issues/AffectedLazyLoadImagesView.ts +76 -0
  159. package/front_end/panels/issues/AffectedResourcesView.ts +1 -1
  160. package/front_end/panels/issues/CorsIssueDetailsView.ts +1 -1
  161. package/front_end/panels/issues/IssueView.ts +2 -0
  162. package/front_end/panels/issues/issues.ts +2 -0
  163. package/front_end/panels/layer_viewer/Layers3DView.ts +1 -1
  164. package/front_end/panels/lighthouse/LighthouseController.ts +5 -0
  165. package/front_end/panels/lighthouse/LighthouseReportRenderer.ts +1 -1
  166. package/front_end/panels/network/BinaryResourceView.ts +1 -1
  167. package/front_end/panels/network/NetworkDataGridNode.ts +29 -7
  168. package/front_end/panels/network/NetworkLogView.ts +1 -1
  169. package/front_end/panels/network/NetworkLogViewColumns.ts +6 -6
  170. package/front_end/panels/network/NetworkPanel.ts +4 -4
  171. package/front_end/panels/network/NetworkSearchScope.ts +1 -1
  172. package/front_end/panels/network/RequestConditionsDrawer.ts +1 -1
  173. package/front_end/panels/network/RequestHTMLView.ts +1 -1
  174. package/front_end/panels/network/RequestPayloadView.ts +1 -1
  175. package/front_end/panels/network/RequestPreviewView.ts +1 -1
  176. package/front_end/panels/network/RequestResponseView.ts +1 -1
  177. package/front_end/panels/network/ResourceChunkView.ts +1 -1
  178. package/front_end/panels/network/ResourceDirectSocketChunkView.ts +1 -1
  179. package/front_end/panels/network/ResourceWebSocketFrameView.ts +1 -1
  180. package/front_end/panels/network/components/ResponseHeaderSection.ts +1 -1
  181. package/front_end/panels/network/forward/UIRequestLocation.ts +1 -1
  182. package/front_end/panels/network/network-meta.ts +3 -3
  183. package/front_end/panels/profiler/HeapProfileView.ts +6 -6
  184. package/front_end/panels/profiler/HeapSnapshotView.ts +1 -1
  185. package/front_end/panels/profiler/ProfileDataGrid.ts +12 -8
  186. package/front_end/panels/protocol_monitor/ProtocolMonitor.ts +1 -1
  187. package/front_end/panels/recorder/RecorderPanel.ts +493 -381
  188. package/front_end/panels/recorder/RecordingListView.ts +8 -37
  189. package/front_end/panels/recorder/RecordingView.ts +54 -27
  190. package/front_end/panels/recorder/SelectorPicker.ts +0 -13
  191. package/front_end/panels/recorder/StepEditor.ts +6 -14
  192. package/front_end/panels/recorder/StepView.ts +36 -105
  193. package/front_end/panels/search/SearchResultsPane.ts +1 -1
  194. package/front_end/panels/security/SecurityPanel.ts +3 -3
  195. package/front_end/panels/settings/AISettingsTab.ts +4 -4
  196. package/front_end/panels/settings/components/SyncSection.ts +4 -4
  197. package/front_end/panels/settings/emulation/components/UserAgentClientHintsForm.ts +3 -3
  198. package/front_end/panels/snippets/ScriptSnippetFileSystem.ts +1 -1
  199. package/front_end/panels/sources/BreakpointsView.ts +1 -1
  200. package/front_end/panels/sources/CoveragePlugin.ts +1 -1
  201. package/front_end/panels/sources/DebuggerPlugin.ts +4 -4
  202. package/front_end/panels/sources/FilteredUISourceCodeListProvider.ts +27 -3
  203. package/front_end/panels/sources/NavigatorView.ts +1 -1
  204. package/front_end/panels/sources/PersistenceActions.ts +1 -1
  205. package/front_end/panels/sources/SourcesNavigator.ts +1 -1
  206. package/front_end/panels/sources/SourcesSearchScope.ts +1 -1
  207. package/front_end/panels/sources/TabbedEditorContainer.ts +3 -3
  208. package/front_end/panels/sources/UISourceCodeFrame.ts +1 -1
  209. package/front_end/panels/sources/components/HeadersView.ts +1 -1
  210. package/front_end/panels/sources/filteredUISourceCodeListProvider.css +12 -1
  211. package/front_end/panels/sources/sources-meta.ts +1 -1
  212. package/front_end/panels/timeline/StatusDialog.ts +1 -1
  213. package/front_end/panels/timeline/TimelineController.ts +24 -3
  214. package/front_end/panels/timeline/TimelinePanel.ts +116 -25
  215. package/front_end/panels/timeline/TimelineSelectorStatsView.ts +2 -2
  216. package/front_end/panels/timeline/TimelineUIUtils.ts +1 -1
  217. package/front_end/panels/timeline/components/CWVMetrics.ts +1 -1
  218. package/front_end/panels/timeline/components/IgnoreListSetting.ts +1 -1
  219. package/front_end/panels/timeline/overlays/components/EntryLabelOverlay.ts +2 -2
  220. package/front_end/panels/timeline/timeline-meta.ts +13 -0
  221. package/front_end/services/tracing/TracingManager.ts +27 -2
  222. package/front_end/third_party/chromium/README.chromium +1 -1
  223. package/front_end/third_party/lighthouse/README.chromium +2 -2
  224. package/front_end/third_party/lighthouse/lighthouse-dt-bundle.js +1418 -1413
  225. package/front_end/third_party/lighthouse/locales/en-US.json +12 -6
  226. package/front_end/third_party/lighthouse/locales/en-XL.json +12 -6
  227. package/front_end/third_party/lighthouse/report-assets/report-generator.mjs +1 -1
  228. package/front_end/third_party/puppeteer/puppeteer.ts +20 -1
  229. package/front_end/ui/components/highlighting/HighlightElement.ts +4 -1
  230. package/front_end/ui/components/highlighting/HighlightManager.ts +1 -1
  231. package/front_end/ui/components/highlighting/MarkupHighlight.ts +1 -1
  232. package/front_end/ui/components/markdown_view/MarkdownView.ts +46 -2
  233. package/front_end/ui/components/markdown_view/markdownView.css +23 -0
  234. package/front_end/ui/components/text_editor/config.ts +1 -1
  235. package/front_end/ui/components/tree_outline/TreeOutline.ts +7 -1
  236. package/front_end/ui/legacy/Infobar.ts +1 -1
  237. package/front_end/ui/legacy/InspectorView.ts +12 -7
  238. package/front_end/ui/legacy/SuggestBox.ts +1 -1
  239. package/front_end/ui/legacy/TextPrompt.ts +1 -1
  240. package/front_end/ui/legacy/Toolbar.ts +5 -2
  241. package/front_end/ui/legacy/Treeoutline.ts +26 -7
  242. package/front_end/ui/legacy/components/color_picker/Spectrum.ts +1 -1
  243. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +1 -1
  244. package/front_end/ui/legacy/components/data_grid/ViewportDataGrid.ts +1 -1
  245. package/front_end/ui/legacy/components/data_grid/dataGrid.css +1 -1
  246. package/front_end/ui/legacy/components/inline_editor/AnimationTimingUI.ts +27 -13
  247. package/front_end/ui/legacy/components/inline_editor/BezierEditor.ts +5 -2
  248. package/front_end/ui/legacy/components/inline_editor/CSSLinearEasingModel.ts +23 -0
  249. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +161 -54
  250. package/front_end/ui/legacy/components/quick_open/FilteredListWidget.ts +1 -1
  251. package/front_end/ui/legacy/components/source_frame/BinaryResourceViewFactory.ts +1 -1
  252. package/front_end/ui/legacy/components/source_frame/FontView.ts +1 -1
  253. package/front_end/ui/legacy/components/source_frame/ImageView.ts +1 -1
  254. package/front_end/ui/legacy/components/source_frame/JSONView.ts +105 -115
  255. package/front_end/ui/legacy/components/source_frame/PreviewFactory.ts +1 -1
  256. package/front_end/ui/legacy/components/source_frame/ResourceSourceFrame.ts +1 -1
  257. package/front_end/ui/legacy/components/source_frame/SourceFrame.ts +3 -3
  258. package/front_end/ui/legacy/components/source_frame/StreamingContentHexView.ts +1 -1
  259. package/front_end/ui/legacy/components/source_frame/XMLView.ts +5 -1
  260. package/front_end/ui/legacy/components/source_frame/jsonView.css +4 -0
  261. package/front_end/ui/legacy/components/utils/Linkifier.ts +1 -1
  262. package/front_end/ui/legacy/inspectorCommon.css +6 -2
  263. package/front_end/ui/settings/SettingUIRegistration.ts +105 -0
  264. package/front_end/ui/settings/settings.ts +9 -0
  265. package/front_end/ui/visual_logging/KnownContextValues.ts +13 -0
  266. package/mcp/mcp.ts +1 -0
  267. package/package.json +1 -1
  268. /package/.agents/skills/{verification → devtools-verification}/SKILL.md +0 -0
  269. /package/front_end/{models → core}/text_utils/CodeMirrorUtils.ts +0 -0
  270. /package/front_end/{models → core}/text_utils/ContentData.ts +0 -0
  271. /package/front_end/{models → core}/text_utils/Text.ts +0 -0
  272. /package/front_end/{models → core}/text_utils/TextCursor.ts +0 -0
  273. /package/front_end/{models → core}/text_utils/TextUtils.snapshot.txt +0 -0
@@ -19,7 +19,7 @@ import * as Buttons from '../../ui/components/buttons/buttons.js';
19
19
  import type * as Dialogs from '../../ui/components/dialogs/dialogs.js';
20
20
  import type * as Menus from '../../ui/components/menus/menus.js';
21
21
  import * as UI from '../../ui/legacy/legacy.js';
22
- import * as Lit from '../../ui/lit/lit.js';
22
+ import {Directives, html, type LitTemplate, render} from '../../ui/lit/lit.js';
23
23
  import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
24
24
 
25
25
  import * as Converters from './converters/converters.js';
@@ -29,29 +29,18 @@ import * as Models from './models/models.js';
29
29
  import * as Actions from './recorder-actions/recorder-actions.js';
30
30
  import * as Events from './RecorderEvents.js';
31
31
  import recorderPanelStyles from './recorderPanel.css.js';
32
- import {
33
- DeleteRecordingEvent,
34
- OpenRecordingEvent,
35
- PlayRecordingEvent as ListViewPlayRecordingEvent,
36
- RecordingListView
37
- } from './RecordingListView.js';
32
+ import {RecordingListView} from './RecordingListView.js';
38
33
  import {
39
34
  type PlayRecordingEvent as ViewPlayRecordingEvent,
40
35
  RecordingView,
41
36
  type ReplayState,
42
37
  TargetPanel
43
38
  } from './RecordingView.js';
44
- import type {RequestSelectorAttributeEvent} from './SelectorPicker.js';
45
39
  import {
46
- type AddBreakpointEvent,
47
- type AddStep,
48
40
  AddStepPosition,
49
- type RemoveBreakpointEvent,
50
- type RemoveStep,
51
- type StepChanged
52
41
  } from './StepView.js';
53
42
 
54
- const {html, Directives: {ref}} = Lit;
43
+ const {ref, repeat} = Directives;
55
44
 
56
45
  let recorderPanelInstance: RecorderPanel;
57
46
 
@@ -178,7 +167,7 @@ const GET_EXTENSIONS_URL = 'https://goo.gle/recorder-extension-list' as Platform
178
167
  const RECORDER_EXPLANATION_URL = 'https://developer.chrome.com/docs/devtools/recorder';
179
168
  const FEEDBACK_URL = 'https://goo.gle/recorder-feedback' as Platform.DevToolsPath.UrlString;
180
169
 
181
- interface StoredRecording {
170
+ export interface StoredRecording {
182
171
  storageName: string;
183
172
  flow: Models.Schema.UserFlow;
184
173
  }
@@ -220,6 +209,391 @@ function verifyFlowSize(flow: Models.Schema.UserFlow): void {
220
209
  }
221
210
  }
222
211
 
212
+ export interface ViewInput {
213
+ recordings: StoredRecording[];
214
+ currentRecording?: StoredRecording;
215
+ currentPage: Pages;
216
+ isRecording: boolean;
217
+ isToggling: boolean;
218
+ importError?: Error;
219
+ recordingError?: Error;
220
+ sections: Models.Section.Section[];
221
+ settings?: Models.RecordingSettings.RecordingSettings;
222
+ recorderSettings: Models.RecorderSettings.RecorderSettings;
223
+ lastReplayResult?: Models.RecordingPlayer.ReplayResult;
224
+ replayAllowed: boolean;
225
+ breakpointIndexes: Set<number>;
226
+ builtInConverters: readonly Converters.Converter.Converter[];
227
+ extensionConverters: Converters.Converter.Converter[];
228
+ replayExtensions: Extensions.ExtensionManager.Extension[];
229
+ extensionDescriptor?: PublicExtensions.RecorderPluginManager.ViewDescriptor;
230
+ exportMenuExpanded: boolean;
231
+ replayState: ReplayState;
232
+ shortcutsInfo: Dialogs.ShortcutDialog.Shortcut[];
233
+ currentStep?: Models.Schema.Step;
234
+
235
+ onCreateNewRecording: (event?: Event) => void;
236
+ onImportRecording: (event: Event) => void;
237
+ onExportRecording: (event: Event) => void;
238
+ onDeleteRecording: (event: Event|string) => void;
239
+ onRecordingSelected: (event: Event|string) => void;
240
+ onPlayRecordingByName: (storageName: string) => void;
241
+ onPlayRecording: (event: ViewPlayRecordingEvent) => void;
242
+ onAbortReplay: () => void;
243
+ onNetworkConditionsChanged: (data?: SDK.NetworkManager.Conditions) => void;
244
+ onTimeoutChanged: (timeout?: number) => void;
245
+ handleRecordingTitleChanged: (title: string) => void;
246
+ handleRecordingChanged: (currentStep: Models.Schema.Step, newStep: Models.Schema.Step) => void;
247
+ handleStepAdded: (stepOrSection: Models.Schema.Step|Models.Section.Section, position: AddStepPosition) => void;
248
+ handleStepRemoved: (step: Models.Schema.Step) => void;
249
+ onAddBreakpoint: (index: number) => void;
250
+ onRemoveBreakpoint: (index: number) => void;
251
+ onExtensionViewClosed: () => void;
252
+ onExportMenuClosed: () => void;
253
+ onExportOptionSelected: (event: Menus.SelectMenu.SelectMenuItemSelectedEvent) => void;
254
+ onRecordingFinished: () => void;
255
+ handleAddAssertionEvent: () => void;
256
+ onSetRecording: (event: Event) => void;
257
+ onContinueReplay: () => void;
258
+ onStepOverReplay: () => void;
259
+
260
+ getExportMenuButton: () => Buttons.Button.Button;
261
+
262
+ onRecordingStarted:
263
+ (data: {name: string, selectorTypesToRecord: Models.Schema.SelectorType[], selectorAttribute?: string}) => void;
264
+ onRecordingCancelled: () => void;
265
+ }
266
+
267
+ export interface ViewOutput {
268
+ exportMenuButton: Buttons.Button.Button;
269
+ recordingView: RecordingView;
270
+ createRecordingView: CreateRecordingView;
271
+ }
272
+
273
+ export type View = (input: ViewInput, output: ViewOutput, target: DocumentFragment) => void;
274
+
275
+ export const DEFAULT_VIEW: View = (input, output, target): void => {
276
+ function renderCurrentPage(): LitTemplate {
277
+ switch (input.currentPage) {
278
+ case Pages.START_PAGE:
279
+ return renderStartPage();
280
+ case Pages.ALL_RECORDINGS_PAGE:
281
+ return renderAllRecordingsPage();
282
+ case Pages.RECORDING_PAGE:
283
+ return renderRecordingPage();
284
+ case Pages.CREATE_RECORDING_PAGE:
285
+ return renderCreateRecordingPage();
286
+ }
287
+ }
288
+
289
+ function renderAllRecordingsPage(): LitTemplate {
290
+ // clang-format off
291
+ return html`
292
+ <devtools-widget
293
+ ${widget(RecordingListView, {
294
+ recordings: input.recordings.map(recording => ({
295
+ storageName: recording.storageName,
296
+ name: recording.flow.title,
297
+ })),
298
+ replayAllowed: input.replayAllowed,
299
+ onCreateRecording: input.onCreateNewRecording,
300
+ onDeleteRecording: input.onDeleteRecording,
301
+ onOpenRecording: input.onRecordingSelected,
302
+ onPlayRecording: input.onPlayRecordingByName,
303
+ })}
304
+ >
305
+ </devtools-widget>
306
+ `;
307
+ // clang-format on
308
+ }
309
+
310
+ function renderStartPage(): LitTemplate {
311
+ // clang-format off
312
+ return html`
313
+ <div class="empty-state" jslog=${VisualLogging.section().context('start-view')}>
314
+ <div class="empty-state-header">${i18nString(UIStrings.header)}</div>
315
+ <div class="empty-state-description">
316
+ <span>${i18nString(UIStrings.recordingDescription)}</span>
317
+ <devtools-link
318
+ class="devtools-link"
319
+ href=${RECORDER_EXPLANATION_URL}
320
+ jslogcontext="learn-more"
321
+ >${i18nString(UIStrings.learnMore)}</devtools-link>
322
+ </div>
323
+ <devtools-button .variant=${Buttons.Button.Variant.TONAL} jslogContext=${Actions.RecorderActions.CREATE_RECORDING} @click=${input.onCreateNewRecording}>${i18nString(UIStrings.createRecording)}</devtools-button>
324
+ </div>
325
+ `;
326
+ // clang-format on
327
+ }
328
+
329
+ function renderRecordingPage(): LitTemplate {
330
+ // clang-format off
331
+ return html`
332
+ <devtools-widget
333
+ class="recording-view"
334
+ ${widget(RecordingView, {
335
+ recording: input.currentRecording?.flow ?? {title: '', steps: []},
336
+ replayState: input.replayState,
337
+ isRecording: input.isRecording,
338
+ recordingTogglingInProgress: input.isToggling,
339
+ currentStep: input.currentStep,
340
+ currentError: input.recordingError,
341
+ sections: input.sections ?? [],
342
+ settings: input.settings,
343
+ recorderSettings: input.recorderSettings,
344
+ lastReplayResult: input.lastReplayResult,
345
+ replayAllowed: input.replayAllowed,
346
+ breakpointIndexes: input.breakpointIndexes,
347
+ builtInConverters: input.builtInConverters,
348
+ extensionConverters: input.extensionConverters,
349
+ replayExtensions: input.replayExtensions,
350
+ extensionDescriptor: input.extensionDescriptor,
351
+ onRecordingFinished: input.onRecordingFinished,
352
+ onAddAssertion: input.handleAddAssertionEvent,
353
+ onAbortReplay: input.onAbortReplay,
354
+ onPlayRecording: input.onPlayRecording,
355
+ onNetworkConditionsChanged: input.onNetworkConditionsChanged,
356
+ onTimeoutChanged: input.onTimeoutChanged,
357
+ onTitleChanged: input.handleRecordingTitleChanged,
358
+ onStepChanged: input.handleRecordingChanged,
359
+ onAddStep: input.handleStepAdded,
360
+ onRemoveStep: input.handleStepRemoved,
361
+ onAddBreakpoint: input.onAddBreakpoint,
362
+ onRemoveBreakpoint: input.onRemoveBreakpoint,
363
+ onAttributeRequested: (send: (attribute?: string) => void) => {
364
+ send(input.currentRecording?.flow.selectorAttribute);
365
+ },
366
+ })}
367
+ @recorderextensionviewclosed=${input.onExtensionViewClosed}
368
+ ${UI.Widget.widgetRef(RecordingView, widget => {output.recordingView = widget; })}
369
+ ></devtools-widget>
370
+ `;
371
+ // clang-format on
372
+ }
373
+
374
+ function renderCreateRecordingPage(): LitTemplate {
375
+ // clang-format off
376
+ return html`
377
+ <devtools-widget
378
+ class="recording-view"
379
+ ${widget(CreateRecordingView, {
380
+ recorderSettings: input.recorderSettings,
381
+ onRecordingStarted: input.onRecordingStarted,
382
+ onRecordingCancelled: input.onRecordingCancelled,
383
+ })}
384
+ ${UI.Widget.widgetRef(
385
+ CreateRecordingView,
386
+ widget => {
387
+ output.createRecordingView = widget;
388
+ },
389
+ )}
390
+ ></devtools-widget>
391
+ `;
392
+ // clang-format on
393
+ }
394
+
395
+ const selectValue: string = input.currentRecording ? input.currentRecording.storageName : input.currentPage;
396
+ // clang-format off
397
+ const values = [
398
+ input.recordings.length === 0
399
+ ? {
400
+ value: Pages.START_PAGE,
401
+ name: i18nString(UIStrings.noRecordings),
402
+ selected: selectValue === Pages.START_PAGE,
403
+ }
404
+ : {
405
+ value: Pages.ALL_RECORDINGS_PAGE,
406
+ name: `${input.recordings.length} ${i18nString(UIStrings.numberOfRecordings)}`,
407
+ selected: selectValue === Pages.ALL_RECORDINGS_PAGE,
408
+ },
409
+ ...input.recordings.map(recording => ({
410
+ value: recording.storageName,
411
+ name: recording.flow.title,
412
+ selected: selectValue === recording.storageName,
413
+ })),
414
+ ];
415
+
416
+ render(html`
417
+ <style>${UI.inspectorCommonStyles}</style>
418
+ <style>${recorderPanelStyles}</style>
419
+ <div class="wrapper">
420
+ <div class="header" jslog=${VisualLogging.toolbar()}>
421
+ <devtools-button
422
+ @click=${input.onCreateNewRecording}
423
+ .data=${
424
+ {
425
+ variant: Buttons.Button.Variant.TOOLBAR,
426
+ iconName: 'plus',
427
+ disabled:
428
+ input.replayState.isPlaying ||
429
+ input.isRecording ||
430
+ input.isToggling,
431
+ title: Models.Tooltip.getTooltipForActions(
432
+ i18nString(UIStrings.createRecording),
433
+ Actions.RecorderActions.CREATE_RECORDING,
434
+ ),
435
+ jslogContext: Actions.RecorderActions.CREATE_RECORDING,
436
+ } as Buttons.Button.ButtonData
437
+ }
438
+ ></devtools-button>
439
+ <div class="separator"></div>
440
+ <select
441
+ .disabled=${
442
+ input.recordings.length === 0 ||
443
+ input.replayState.isPlaying ||
444
+ input.isRecording ||
445
+ input.isToggling
446
+ }
447
+ @click=${(e: Event) => e.stopPropagation()}
448
+ @change=${input.onRecordingSelected}
449
+ jslog=${VisualLogging.dropDown('recordings').track({change: true})}
450
+ >
451
+ ${repeat(
452
+ values,
453
+ item => item.value,
454
+ item => {
455
+ return html`<option .selected=${item.selected} value=${item.value}>${item.name}</option>`;
456
+ },
457
+ )}
458
+ </select>
459
+ <div class="separator"></div>
460
+ <devtools-button
461
+ @click=${input.onImportRecording}
462
+ .data=${
463
+ {
464
+ variant: Buttons.Button.Variant.TOOLBAR,
465
+ iconName: 'import',
466
+ title: i18nString(UIStrings.importRecording),
467
+ jslogContext: 'import-recording',
468
+ } as Buttons.Button.ButtonData
469
+ }
470
+ ></devtools-button>
471
+ <devtools-button
472
+ id='origin'
473
+ @click=${input.onExportRecording}
474
+ ${ref(el => {
475
+ if (el instanceof HTMLElement) {
476
+ output.exportMenuButton = el as Buttons.Button.Button;
477
+ }
478
+ })}
479
+ .data=${
480
+ {
481
+ variant: Buttons.Button.Variant.TOOLBAR,
482
+ iconName: 'download',
483
+ title: i18nString(UIStrings.exportRecording),
484
+ disabled: !input.currentRecording,
485
+ } as Buttons.Button.ButtonData
486
+ }
487
+ jslog=${VisualLogging.dropDown('export-recording').track({click: true})}
488
+ ></devtools-button>
489
+ <devtools-menu
490
+ @menucloserequest=${input.onExportMenuClosed}
491
+ @menuitemselected=${input.onExportOptionSelected}
492
+ .origin=${input.getExportMenuButton}
493
+ .showDivider=${false}
494
+ .showSelectedItem=${false}
495
+ .open=${input.exportMenuExpanded}
496
+ >
497
+ <devtools-menu-group .name=${i18nString(UIStrings.export)}>
498
+ ${repeat(
499
+ input.builtInConverters,
500
+ converter => {
501
+ return html`
502
+ <devtools-menu-item
503
+ .value=${converter.getId()}
504
+ jslog=${VisualLogging.item(`converter-${Platform.StringUtilities.toKebabCase(converter.getId())}`).track({click: true})}>
505
+ ${converter.getFormatName()}
506
+ </devtools-menu-item>
507
+ `;
508
+ },
509
+ )}
510
+ </devtools-menu-group>
511
+ <devtools-menu-group .name=${i18nString(UIStrings.exportViaExtensions)}>
512
+ ${repeat(
513
+ input.extensionConverters,
514
+ converter => {
515
+ return html`
516
+ <devtools-menu-item
517
+ .value=${converter.getId()}
518
+ jslog=${VisualLogging.item('converter-extension').track({click: true})}>
519
+ ${converter.getFormatName()}
520
+ </devtools-menu-item>
521
+ `;
522
+ },
523
+ )}
524
+ <devtools-menu-item .value=${GET_EXTENSIONS_MENU_ITEM}>
525
+ ${i18nString(UIStrings.getExtensions)}
526
+ </devtools-menu-item>
527
+ </devtools-menu-group>
528
+ </devtools-menu>
529
+ <devtools-button
530
+ @click=${input.onDeleteRecording}
531
+ .data=${
532
+ {
533
+ variant: Buttons.Button.Variant.TOOLBAR,
534
+ iconName: 'bin',
535
+ disabled:
536
+ !input.currentRecording ||
537
+ input.replayState.isPlaying ||
538
+ input.isRecording ||
539
+ input.isToggling,
540
+ title: i18nString(UIStrings.deleteRecording),
541
+ jslogContext: 'delete-recording',
542
+ } as Buttons.Button.ButtonData
543
+ }
544
+ ></devtools-button>
545
+ <div class="separator"></div>
546
+ <devtools-button
547
+ @click=${input.onContinueReplay}
548
+ .data=${
549
+ {
550
+ variant: Buttons.Button.Variant.PRIMARY_TOOLBAR,
551
+ iconName: 'resume',
552
+ disabled: !input.replayState.isPausedOnBreakpoint,
553
+ title: i18nString(UIStrings.continueReplay),
554
+ jslogContext: 'continue-replay',
555
+ } as Buttons.Button.ButtonData
556
+ }
557
+ ></devtools-button>
558
+ <devtools-button
559
+ @click=${input.onStepOverReplay}
560
+ .data=${
561
+ {
562
+ variant: Buttons.Button.Variant.TOOLBAR,
563
+ iconName: 'step-over',
564
+ disabled: !input.replayState.isPausedOnBreakpoint,
565
+ title: i18nString(UIStrings.stepOverReplay),
566
+ jslogContext: 'step-over',
567
+ } as Buttons.Button.ButtonData
568
+ }
569
+ ></devtools-button>
570
+ <div class="feedback">
571
+ <devtools-link class="devtools-link" title=${i18nString(UIStrings.sendFeedback)} href=${
572
+ FEEDBACK_URL
573
+ } jslogcontext="feedback">${i18nString(UIStrings.sendFeedback)}</devtools-link>
574
+ </div>
575
+ <div class="separator"></div>
576
+ <devtools-shortcut-dialog
577
+ .data=${
578
+ {
579
+ shortcuts: input.shortcutsInfo,
580
+ } as Dialogs.ShortcutDialog.ShortcutDialogData
581
+ } jslog=${VisualLogging.action('show-shortcuts').track({click: true})}
582
+ ></devtools-shortcut-dialog>
583
+ </div>
584
+ ${
585
+ input.importError
586
+ ? html`<div class='error'>Import error: ${
587
+ input.importError.message
588
+ }</div>`
589
+ : ''
590
+ }
591
+ ${renderCurrentPage()}
592
+ </div>
593
+ `, target, { container: { listeners: {setrecording: input.onSetRecording}}});
594
+ // clang-format on
595
+ };
596
+
223
597
  export class RecorderPanel extends UI.Widget.VBox<DocumentFragment> {
224
598
  static panelName = 'chrome-recorder';
225
599
 
@@ -449,9 +823,12 @@ export class RecorderPanel extends UI.Widget.VBox<DocumentFragment> {
449
823
  #recordingView?: RecordingView;
450
824
  #createRecordingView?: CreateRecordingView;
451
825
 
452
- constructor(element?: HTMLElement) {
826
+ #view: typeof DEFAULT_VIEW;
827
+
828
+ constructor(element?: HTMLElement, view?: typeof DEFAULT_VIEW) {
453
829
  const el = element || document.createElement('devtools-recorder-panel');
454
830
  super(el, {useShadowDom: 'pure'});
831
+ this.#view = view || DEFAULT_VIEW;
455
832
 
456
833
  this.setHideOnDetach();
457
834
 
@@ -901,7 +1278,7 @@ export class RecorderPanel extends UI.Widget.VBox<DocumentFragment> {
901
1278
  return this.currentRecording?.flow;
902
1279
  }
903
1280
 
904
- async #handleRecordingChanged(event: StepChanged): Promise<void> {
1281
+ async #handleRecordingChanged(currentStep: Models.Schema.Step, newStep: Models.Schema.Step): Promise<void> {
905
1282
  if (!this.currentRecording) {
906
1283
  throw new Error('Current recording expected to be defined.');
907
1284
  }
@@ -909,7 +1286,7 @@ export class RecorderPanel extends UI.Widget.VBox<DocumentFragment> {
909
1286
  ...this.currentRecording,
910
1287
  flow: {
911
1288
  ...this.currentRecording.flow,
912
- steps: this.currentRecording.flow.steps.map(step => step === event.currentStep ? event.newStep : step),
1289
+ steps: this.currentRecording.flow.steps.map(step => step === currentStep ? newStep : step),
913
1290
  },
914
1291
  };
915
1292
  this.#setCurrentRecording(await this.#storage.upsertRecording(
@@ -919,26 +1296,26 @@ export class RecorderPanel extends UI.Widget.VBox<DocumentFragment> {
919
1296
  {keepBreakpoints: true, updateSession: true});
920
1297
  }
921
1298
 
922
- async #handleStepAdded(event: AddStep): Promise<void> {
1299
+ async #handleStepAdded(stepOrSection: Models.Schema.Step|Models.Section.Section,
1300
+ position: AddStepPosition): Promise<void> {
923
1301
  if (!this.currentRecording) {
924
1302
  throw new Error('Current recording expected to be defined.');
925
1303
  }
926
- const stepOrSection = event.stepOrSection;
927
1304
  let step;
928
- let position = event.position;
1305
+ let actualPosition = position;
929
1306
  if ('steps' in stepOrSection) {
930
1307
  // section
931
1308
  const sectionIdx = this.sections?.indexOf(stepOrSection);
932
1309
  if (sectionIdx === undefined || sectionIdx === -1) {
933
1310
  throw new Error('There is no section to add a step to');
934
1311
  }
935
- if (event.position === AddStepPosition.AFTER) {
1312
+ if (position === AddStepPosition.AFTER) {
936
1313
  if (this.sections?.[sectionIdx].steps.length) {
937
1314
  step = this.sections?.[sectionIdx].steps[0];
938
- position = AddStepPosition.BEFORE;
1315
+ actualPosition = AddStepPosition.BEFORE;
939
1316
  } else {
940
1317
  step = this.sections?.[sectionIdx].causingStep;
941
- position = AddStepPosition.AFTER;
1318
+ actualPosition = AddStepPosition.AFTER;
942
1319
  }
943
1320
  } else {
944
1321
  if (sectionIdx <= 0) {
@@ -946,7 +1323,7 @@ export class RecorderPanel extends UI.Widget.VBox<DocumentFragment> {
946
1323
  }
947
1324
  const prevSection = this.sections?.[sectionIdx - 1];
948
1325
  step = prevSection?.steps[prevSection.steps.length - 1];
949
- position = AddStepPosition.AFTER;
1326
+ actualPosition = AddStepPosition.AFTER;
950
1327
  }
951
1328
  } else {
952
1329
  // step
@@ -957,7 +1334,7 @@ export class RecorderPanel extends UI.Widget.VBox<DocumentFragment> {
957
1334
  }
958
1335
  const steps = this.currentRecording.flow.steps;
959
1336
  const currentIndex = steps.indexOf(step);
960
- const indexToInsertAt = currentIndex + (position === AddStepPosition.BEFORE ? 0 : 1);
1337
+ const indexToInsertAt = currentIndex + (actualPosition === AddStepPosition.BEFORE ? 0 : 1);
961
1338
  steps.splice(indexToInsertAt, 0, {type: Models.Schema.StepType.WaitForElement, selectors: ['body']});
962
1339
  const recording = {...this.currentRecording, flow: {...this.currentRecording.flow, steps}};
963
1340
  this.#stepBreakpointIndexes = new Set([...this.#stepBreakpointIndexes.values()].map(breakpointIndex => {
@@ -986,13 +1363,13 @@ export class RecorderPanel extends UI.Widget.VBox<DocumentFragment> {
986
1363
  ));
987
1364
  }
988
1365
 
989
- async #handleStepRemoved(event: RemoveStep): Promise<void> {
1366
+ async #handleStepRemoved(step: Models.Schema.Step): Promise<void> {
990
1367
  if (!this.currentRecording) {
991
1368
  throw new Error('Current recording expected to be defined.');
992
1369
  }
993
1370
 
994
1371
  const steps = this.currentRecording.flow.steps;
995
- const currentIndex = steps.indexOf(event.step);
1372
+ const currentIndex = steps.indexOf(step);
996
1373
  steps.splice(currentIndex, 1);
997
1374
  const flow = {...this.currentRecording.flow, steps};
998
1375
  this.#stepBreakpointIndexes = new Set([...this.#stepBreakpointIndexes.values()]
@@ -1065,19 +1442,22 @@ export class RecorderPanel extends UI.Widget.VBox<DocumentFragment> {
1065
1442
  ));
1066
1443
  }
1067
1444
 
1068
- async #onDeleteRecording(event: Event): Promise<void> {
1069
- event.stopPropagation();
1070
- if (event instanceof DeleteRecordingEvent) {
1071
- await this.#storage.deleteRecording(event.storageName);
1072
- this.#screenshotStorage.deleteScreenshotsForRecording(event.storageName);
1073
- this.requestUpdate();
1445
+ async #onDeleteRecording(storageNameOrEvent: string|Event): Promise<void> {
1446
+ let storageName: string;
1447
+ if (typeof storageNameOrEvent === 'string') {
1448
+ storageName = storageNameOrEvent;
1074
1449
  } else {
1450
+ storageNameOrEvent.stopPropagation();
1075
1451
  if (!this.currentRecording) {
1076
1452
  return;
1077
1453
  }
1078
- await this.#storage.deleteRecording(this.currentRecording.storageName);
1079
- this.#screenshotStorage.deleteScreenshotsForRecording(this.currentRecording.storageName);
1454
+ storageName = this.currentRecording.storageName;
1080
1455
  }
1456
+
1457
+ await this.#storage.deleteRecording(storageName);
1458
+ this.#screenshotStorage.deleteScreenshotsForRecording(storageName);
1459
+ this.requestUpdate();
1460
+
1081
1461
  UI.ARIAUtils.LiveAnnouncer.alert(i18nString(UIStrings.recordingDeleted));
1082
1462
  if ((await this.#storage.getRecordings()).length) {
1083
1463
  this.#setCurrentPage(Pages.ALL_RECORDINGS_PAGE);
@@ -1205,10 +1585,13 @@ export class RecorderPanel extends UI.Widget.VBox<DocumentFragment> {
1205
1585
  }
1206
1586
  }
1207
1587
 
1208
- async #onRecordingSelected(event: Event): Promise<void> {
1209
- const storageName = event instanceof OpenRecordingEvent || event instanceof ListViewPlayRecordingEvent ?
1210
- event.storageName :
1211
- ((event as InputEvent).target as HTMLSelectElement)?.value;
1588
+ async #onRecordingSelected(storageNameOrEvent: string|Event): Promise<void> {
1589
+ let storageName: string;
1590
+ if (typeof storageNameOrEvent === 'string') {
1591
+ storageName = storageNameOrEvent;
1592
+ } else {
1593
+ storageName = ((storageNameOrEvent as InputEvent).target as HTMLSelectElement)?.value;
1594
+ }
1212
1595
  this.#setCurrentRecording(await this.#storage.getRecording(storageName));
1213
1596
  if (this.currentRecording) {
1214
1597
  this.#setCurrentPage(Pages.RECORDING_PAGE);
@@ -1322,21 +1705,21 @@ export class RecorderPanel extends UI.Widget.VBox<DocumentFragment> {
1322
1705
  }
1323
1706
  }
1324
1707
 
1325
- async #onPlayRecordingByName(event: ListViewPlayRecordingEvent): Promise<void> {
1326
- await this.#onRecordingSelected(event);
1708
+ async #onPlayRecordingByName(storageName: string): Promise<void> {
1709
+ await this.#onRecordingSelected(storageName);
1327
1710
  await this.#onPlayRecording({targetPanel: TargetPanel.DEFAULT, speed: this.#recorderSettings.speed});
1328
1711
  }
1329
1712
 
1330
- #onAddBreakpoint = (event: AddBreakpointEvent): void => {
1713
+ #onAddBreakpoint = (index: number): void => {
1331
1714
  this.#stepBreakpointIndexes = structuredClone(this.#stepBreakpointIndexes);
1332
- this.#stepBreakpointIndexes.add(event.index);
1715
+ this.#stepBreakpointIndexes.add(index);
1333
1716
  this.recordingPlayer?.updateBreakpointIndexes(this.#stepBreakpointIndexes);
1334
1717
  this.requestUpdate();
1335
1718
  };
1336
1719
 
1337
- #onRemoveBreakpoint = (event: RemoveBreakpointEvent): void => {
1720
+ #onRemoveBreakpoint = (index: number): void => {
1338
1721
  this.#stepBreakpointIndexes = structuredClone(this.#stepBreakpointIndexes);
1339
- this.#stepBreakpointIndexes.delete(event.index);
1722
+ this.#stepBreakpointIndexes.delete(index);
1340
1723
  this.recordingPlayer?.updateBreakpointIndexes(this.#stepBreakpointIndexes);
1341
1724
  this.requestUpdate();
1342
1725
  };
@@ -1431,128 +1814,6 @@ export class RecorderPanel extends UI.Widget.VBox<DocumentFragment> {
1431
1814
  ];
1432
1815
  }
1433
1816
 
1434
- #renderCurrentPage(): Lit.TemplateResult {
1435
- switch (this.currentPage) {
1436
- case Pages.START_PAGE:
1437
- return this.#renderStartPage();
1438
- case Pages.ALL_RECORDINGS_PAGE:
1439
- return this.#renderAllRecordingsPage();
1440
- case Pages.RECORDING_PAGE:
1441
- return this.#renderRecordingPage();
1442
- case Pages.CREATE_RECORDING_PAGE:
1443
- return this.#renderCreateRecordingPage();
1444
- }
1445
- }
1446
-
1447
- #renderAllRecordingsPage(): Lit.TemplateResult {
1448
- const recordings = this.#storage.getRecordings();
1449
- // clang-format off
1450
- return html`
1451
- <devtools-widget
1452
- ${widget(RecordingListView, {
1453
- recordings: recordings.map(recording => ({
1454
- storageName: recording.storageName,
1455
- name: recording.flow.title,
1456
- })),
1457
- replayAllowed: this.#replayAllowed,
1458
- })}
1459
- @createrecording=${this.#onCreateNewRecording.bind(this)}
1460
- @deleterecording=${this.#onDeleteRecording.bind(this)}
1461
- @openrecording=${this.#onRecordingSelected.bind(this)}
1462
- @playrecording=${this.#onPlayRecordingByName.bind(this)}
1463
- >
1464
- </devtools-widget>
1465
- `;
1466
- // clang-format on
1467
- }
1468
-
1469
- #renderStartPage(): Lit.TemplateResult {
1470
- // clang-format off
1471
- return html`
1472
- <div class="empty-state" jslog=${VisualLogging.section().context('start-view')}>
1473
- <div class="empty-state-header">${i18nString(UIStrings.header)}</div>
1474
- <div class="empty-state-description">
1475
- <span>${i18nString(UIStrings.recordingDescription)}</span>
1476
- <devtools-link
1477
- class="devtools-link"
1478
- href=${RECORDER_EXPLANATION_URL}
1479
- jslogcontext="learn-more"
1480
- >${i18nString(UIStrings.learnMore)}</devtools-link>
1481
- </div>
1482
- <devtools-button .variant=${Buttons.Button.Variant.TONAL} jslogContext=${Actions.RecorderActions.CREATE_RECORDING} @click=${this.#onCreateNewRecording.bind(this)}>${i18nString(UIStrings.createRecording)}</devtools-button>
1483
- </div>
1484
- `;
1485
- // clang-format on
1486
- }
1487
-
1488
- #renderRecordingPage(): Lit.TemplateResult {
1489
- // clang-format off
1490
- return html`
1491
- <devtools-widget
1492
- class="recording-view"
1493
- ${widget(RecordingView, {
1494
- recording: this.currentRecording?.flow ?? {title: '', steps: []},
1495
- replayState: this.#replayState,
1496
- isRecording: this.isRecording,
1497
- recordingTogglingInProgress: this.isToggling,
1498
- currentStep: this.currentStep,
1499
- currentError: this.recordingError,
1500
- sections: this.sections ?? [],
1501
- settings: this.settings,
1502
- recorderSettings: this.#recorderSettings,
1503
- lastReplayResult: this.lastReplayResult,
1504
- replayAllowed: this.#replayAllowed,
1505
- breakpointIndexes: this.#stepBreakpointIndexes,
1506
- builtInConverters: this.#builtInConverters,
1507
- extensionConverters: this.extensionConverters,
1508
- replayExtensions: this.replayExtensions,
1509
- extensionDescriptor: this.viewDescriptor,
1510
- recordingFinished: this.#onRecordingFinished.bind(this),
1511
- addAssertion: this.#handleAddAssertionEvent.bind(this),
1512
- abortReplay: this.#onAbortReplay.bind(this),
1513
- playRecording: this.#onPlayRecording.bind(this),
1514
- networkConditionsChanged: this.#onNetworkConditionsChanged.bind(this),
1515
- timeoutChanged: this.#onTimeoutChanged.bind(this),
1516
- titleChanged: this.#handleRecordingTitleChanged.bind(this),
1517
- })}
1518
- @requestselectorattribute=${(
1519
- event: RequestSelectorAttributeEvent,
1520
- ) => {
1521
- event.send(this.currentRecording?.flow.selectorAttribute);
1522
- }}
1523
- @stepchanged=${this.#handleRecordingChanged.bind(this)}
1524
- @addstep=${this.#handleStepAdded.bind(this)}
1525
- @removestep=${this.#handleStepRemoved.bind(this)}
1526
- @addbreakpoint=${this.#onAddBreakpoint.bind(this)}
1527
- @removebreakpoint=${this.#onRemoveBreakpoint.bind(this)}
1528
- @recorderextensionviewclosed=${this.#onExtensionViewClosed.bind(this)}
1529
- ${UI.Widget.widgetRef(RecordingView, widget => {this.#recordingView = widget;})}
1530
- ></devtools-widget>
1531
- `;
1532
- // clang-format on
1533
- }
1534
-
1535
- #renderCreateRecordingPage(): Lit.TemplateResult {
1536
- // clang-format off
1537
- return html`
1538
- <devtools-widget
1539
- class="recording-view"
1540
- ${widget(CreateRecordingView, {
1541
- recorderSettings: this.#recorderSettings,
1542
- onRecordingStarted: this.#onRecordingStarted.bind(this),
1543
- onRecordingCancelled: this.onRecordingCancelled.bind(this),
1544
- })}
1545
- ${UI.Widget.widgetRef(
1546
- CreateRecordingView,
1547
- widget => {
1548
- this.#createRecordingView = widget;
1549
- },
1550
- )}
1551
- ></devtools-widget>
1552
- `;
1553
- // clang-format on
1554
- }
1555
-
1556
1817
  #getExportMenuButton = (): Buttons.Button.Button => {
1557
1818
  if (!this.#exportMenuButton) {
1558
1819
  throw new Error('#exportMenuButton not found');
@@ -1571,220 +1832,71 @@ export class RecorderPanel extends UI.Widget.VBox<DocumentFragment> {
1571
1832
  }
1572
1833
 
1573
1834
  override performUpdate(): void {
1574
- // eslint-disable-next-line @devtools/no-lit-render-outside-of-view
1575
- Lit.render(this.render(), this.contentElement, {
1576
- container: {
1577
- listeners: {setrecording: this.#onSetRecording.bind(this)},
1578
- }
1579
- });
1580
- }
1581
-
1582
- protected render(): Lit.TemplateResult {
1583
1835
  const recordings = this.#storage.getRecordings();
1584
- const selectValue: string = this.currentRecording ? this.currentRecording.storageName : this.currentPage;
1585
- // clang-format off
1586
- const values = [
1587
- recordings.length === 0
1588
- ? {
1589
- value: Pages.START_PAGE,
1590
- name: i18nString(UIStrings.noRecordings),
1591
- selected: selectValue === Pages.START_PAGE,
1592
- }
1593
- : {
1594
- value: Pages.ALL_RECORDINGS_PAGE,
1595
- name: `${recordings.length} ${i18nString(UIStrings.numberOfRecordings)}`,
1596
- selected: selectValue === Pages.ALL_RECORDINGS_PAGE,
1597
- },
1598
- ...recordings.map(recording => ({
1599
- value: recording.storageName,
1600
- name: recording.flow.title,
1601
- selected: selectValue === recording.storageName,
1602
- })),
1603
- ];
1604
-
1605
- return html`
1606
- <style>${UI.inspectorCommonStyles}</style>
1607
- <style>${recorderPanelStyles}</style>
1608
- <div class="wrapper">
1609
- <div class="header" jslog=${VisualLogging.toolbar()}>
1610
- <devtools-button
1611
- @click=${this.#onCreateNewRecording.bind(this)}
1612
- .data=${
1613
- {
1614
- variant: Buttons.Button.Variant.TOOLBAR,
1615
- iconName: 'plus',
1616
- disabled:
1617
- this.#replayState.isPlaying ||
1618
- this.isRecording ||
1619
- this.isToggling,
1620
- title: Models.Tooltip.getTooltipForActions(
1621
- i18nString(UIStrings.createRecording),
1622
- Actions.RecorderActions.CREATE_RECORDING,
1623
- ),
1624
- jslogContext: Actions.RecorderActions.CREATE_RECORDING,
1625
- } as Buttons.Button.ButtonData
1626
- }
1627
- ></devtools-button>
1628
- <div class="separator"></div>
1629
- <select
1630
- .disabled=${
1631
- recordings.length === 0 ||
1632
- this.#replayState.isPlaying ||
1633
- this.isRecording ||
1634
- this.isToggling
1635
- }
1636
- @click=${(e: Event) => e.stopPropagation()}
1637
- @change=${this.#onRecordingSelected.bind(this)}
1638
- jslog=${VisualLogging.dropDown('recordings').track({change: true})}
1639
- >
1640
- ${Lit.Directives.repeat(
1641
- values,
1642
- item => item.value,
1643
- item => {
1644
- return html`<option .selected=${item.selected} value=${item.value}>${item.name}</option>`;
1645
- },
1646
- )}
1647
- </select>
1648
- <div class="separator"></div>
1649
- <devtools-button
1650
- @click=${this.#onImportRecording.bind(this)}
1651
- .data=${
1652
- {
1653
- variant: Buttons.Button.Variant.TOOLBAR,
1654
- iconName: 'import',
1655
- title: i18nString(UIStrings.importRecording),
1656
- jslogContext: 'import-recording',
1657
- } as Buttons.Button.ButtonData
1658
- }
1659
- ></devtools-button>
1660
- <devtools-button
1661
- id='origin'
1662
- @click=${this.#onExportRecording.bind(this)}
1663
- ${ref(el => {
1664
- if (el instanceof HTMLElement) {
1665
- this.#exportMenuButton = el as Buttons.Button.Button;
1666
- }
1667
- })}
1668
- .data=${
1669
- {
1670
- variant: Buttons.Button.Variant.TOOLBAR,
1671
- iconName: 'download',
1672
- title: i18nString(UIStrings.exportRecording),
1673
- disabled: !this.currentRecording,
1674
- } as Buttons.Button.ButtonData
1675
- }
1676
- jslog=${VisualLogging.dropDown('export-recording').track({click: true})}
1677
- ></devtools-button>
1678
- <devtools-menu
1679
- @menucloserequest=${this.#onExportMenuClosed.bind(this)}
1680
- @menuitemselected=${this.#onExportOptionSelected.bind(this)}
1681
- .origin=${this.#getExportMenuButton}
1682
- .showDivider=${false}
1683
- .showSelectedItem=${false}
1684
- .open=${this.exportMenuExpanded}
1685
- >
1686
- <devtools-menu-group .name=${i18nString(UIStrings.export)}>
1687
- ${Lit.Directives.repeat(
1688
- this.#builtInConverters,
1689
- converter => {
1690
- return html`
1691
- <devtools-menu-item
1692
- .value=${converter.getId()}
1693
- jslog=${VisualLogging.item(`converter-${Platform.StringUtilities.toKebabCase(converter.getId())}`).track({click: true})}>
1694
- ${converter.getFormatName()}
1695
- </devtools-menu-item>
1696
- `;
1697
- },
1698
- )}
1699
- </devtools-menu-group>
1700
- <devtools-menu-group .name=${i18nString(UIStrings.exportViaExtensions)}>
1701
- ${Lit.Directives.repeat(
1702
- this.extensionConverters,
1703
- converter => {
1704
- return html`
1705
- <devtools-menu-item
1706
- .value=${converter.getId()}
1707
- jslog=${VisualLogging.item('converter-extension').track({click: true})}>
1708
- ${converter.getFormatName()}
1709
- </devtools-menu-item>
1710
- `;
1711
- },
1712
- )}
1713
- <devtools-menu-item .value=${GET_EXTENSIONS_MENU_ITEM}>
1714
- ${i18nString(UIStrings.getExtensions)}
1715
- </devtools-menu-item>
1716
- </devtools-menu-group>
1717
- </devtools-menu>
1718
- <devtools-button
1719
- @click=${this.#onDeleteRecording.bind(this)}
1720
- .data=${
1721
- {
1722
- variant: Buttons.Button.Variant.TOOLBAR,
1723
- iconName: 'bin',
1724
- disabled:
1725
- !this.currentRecording ||
1726
- this.#replayState.isPlaying ||
1727
- this.isRecording ||
1728
- this.isToggling,
1729
- title: i18nString(UIStrings.deleteRecording),
1730
- jslogContext: 'delete-recording',
1731
- } as Buttons.Button.ButtonData
1732
- }
1733
- ></devtools-button>
1734
- <div class="separator"></div>
1735
- <devtools-button
1736
- @click=${() => this.recordingPlayer?.continue()}
1737
- .data=${
1738
- {
1739
- variant: Buttons.Button.Variant.PRIMARY_TOOLBAR,
1740
- iconName: 'resume',
1741
- disabled:
1742
- !this.recordingPlayer ||
1743
- !this.#replayState.isPausedOnBreakpoint,
1744
- title: i18nString(UIStrings.continueReplay),
1745
- jslogContext: 'continue-replay',
1746
- } as Buttons.Button.ButtonData
1747
- }
1748
- ></devtools-button>
1749
- <devtools-button
1750
- @click=${() => this.recordingPlayer?.stepOver()}
1751
- .data=${
1752
- {
1753
- variant: Buttons.Button.Variant.TOOLBAR,
1754
- iconName: 'step-over',
1755
- disabled:
1756
- !this.recordingPlayer ||
1757
- !this.#replayState.isPausedOnBreakpoint,
1758
- title: i18nString(UIStrings.stepOverReplay),
1759
- jslogContext: 'step-over',
1760
- } as Buttons.Button.ButtonData
1761
- }
1762
- ></devtools-button>
1763
- <div class="feedback">
1764
- <devtools-link class="devtools-link" title=${i18nString(UIStrings.sendFeedback)} href=${
1765
- FEEDBACK_URL
1766
- } jslogcontext="feedback">${i18nString(UIStrings.sendFeedback)}</devtools-link>
1767
- </div>
1768
- <div class="separator"></div>
1769
- <devtools-shortcut-dialog
1770
- .data=${
1771
- {
1772
- shortcuts: this.#getShortcutsInfo(),
1773
- } as Dialogs.ShortcutDialog.ShortcutDialogData
1774
- } jslog=${VisualLogging.action('show-shortcuts').track({click: true})}
1775
- ></devtools-shortcut-dialog>
1776
- </div>
1777
- ${
1778
- this.importError
1779
- ? html`<div class='error'>Import error: ${
1780
- this.importError.message
1781
- }</div>`
1782
- : ''
1783
- }
1784
- ${this.#renderCurrentPage()}
1785
- </div>
1786
- `;
1787
- // clang-format on
1836
+ const that = this;
1837
+ const output = {
1838
+ set exportMenuButton(el: Buttons.Button.Button) {
1839
+ that.#exportMenuButton = el;
1840
+ },
1841
+ set recordingView(widget: RecordingView) {
1842
+ that.#recordingView = widget;
1843
+ },
1844
+ set createRecordingView(widget: CreateRecordingView) {
1845
+ that.#createRecordingView = widget;
1846
+ }
1847
+ };
1848
+ this.#view({
1849
+ recordings,
1850
+ currentRecording: this.currentRecording,
1851
+ currentPage: this.currentPage,
1852
+ isRecording: this.isRecording,
1853
+ isToggling: this.isToggling,
1854
+ importError: this.importError,
1855
+ recordingError: this.recordingError,
1856
+ sections: this.sections ?? [],
1857
+ settings: this.settings,
1858
+ recorderSettings: this.#recorderSettings,
1859
+ lastReplayResult: this.lastReplayResult,
1860
+ replayAllowed: this.#replayAllowed,
1861
+ breakpointIndexes: this.#stepBreakpointIndexes,
1862
+ builtInConverters: this.#builtInConverters,
1863
+ extensionConverters: this.extensionConverters,
1864
+ replayExtensions: this.replayExtensions,
1865
+ extensionDescriptor: this.viewDescriptor,
1866
+ exportMenuExpanded: this.exportMenuExpanded,
1867
+ replayState: this.#replayState,
1868
+ shortcutsInfo: this.#getShortcutsInfo(),
1869
+ currentStep: this.currentStep,
1870
+
1871
+ onCreateNewRecording: this.#onCreateNewRecording.bind(this),
1872
+ onImportRecording: this.#onImportRecording.bind(this),
1873
+ onExportRecording: this.#onExportRecording.bind(this),
1874
+ onDeleteRecording: this.#onDeleteRecording.bind(this),
1875
+ onRecordingSelected: this.#onRecordingSelected.bind(this),
1876
+ onPlayRecordingByName: this.#onPlayRecordingByName.bind(this),
1877
+ onPlayRecording: this.#onPlayRecording.bind(this),
1878
+ onAbortReplay: this.#onAbortReplay.bind(this),
1879
+ onNetworkConditionsChanged: this.#onNetworkConditionsChanged.bind(this),
1880
+ onTimeoutChanged: this.#onTimeoutChanged.bind(this),
1881
+ handleRecordingTitleChanged: this.#handleRecordingTitleChanged.bind(this),
1882
+ handleRecordingChanged: this.#handleRecordingChanged.bind(this),
1883
+ handleStepAdded: this.#handleStepAdded.bind(this),
1884
+ handleStepRemoved: this.#handleStepRemoved.bind(this),
1885
+ onAddBreakpoint: this.#onAddBreakpoint.bind(this),
1886
+ onRemoveBreakpoint: this.#onRemoveBreakpoint.bind(this),
1887
+ onExtensionViewClosed: this.#onExtensionViewClosed.bind(this),
1888
+ onExportMenuClosed: this.#onExportMenuClosed.bind(this),
1889
+ onExportOptionSelected: this.#onExportOptionSelected.bind(this),
1890
+ onRecordingFinished: this.#onRecordingFinished.bind(this),
1891
+ handleAddAssertionEvent: this.#handleAddAssertionEvent.bind(this),
1892
+ onSetRecording: this.#onSetRecording.bind(this),
1893
+ onContinueReplay: () => this.recordingPlayer?.continue(),
1894
+ onStepOverReplay: () => this.recordingPlayer?.stepOver(),
1895
+ getExportMenuButton: this.#getExportMenuButton.bind(this),
1896
+ onRecordingStarted: this.#onRecordingStarted.bind(this),
1897
+ onRecordingCancelled: this.onRecordingCancelled.bind(this),
1898
+ },
1899
+ output, this.contentElement);
1788
1900
  }
1789
1901
  }
1790
1902