chrome-devtools-frontend 1.0.1534717 → 1.0.1536371

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 (95) hide show
  1. package/docs/contributing/images/issues-nearestslo.png +0 -0
  2. package/docs/contributing/issues.md +32 -58
  3. package/eslint.config.mjs +1 -0
  4. package/front_end/core/common/Console.ts +1 -8
  5. package/front_end/core/common/ParsedURL.ts +10 -20
  6. package/front_end/core/common/SegmentedRange.ts +1 -2
  7. package/front_end/core/common/StringOutputStream.ts +1 -4
  8. package/front_end/core/host/InspectorFrontendHost.ts +6 -0
  9. package/front_end/core/host/UserMetrics.ts +5 -1
  10. package/front_end/core/i18n/i18nImpl.ts +0 -24
  11. package/front_end/core/protocol_client/CDPConnection.ts +53 -5
  12. package/front_end/core/protocol_client/protocol_client.ts +2 -0
  13. package/front_end/core/sdk/AnimationModel.ts +1 -2
  14. package/front_end/core/sdk/CSSMatchedStyles.ts +2 -2
  15. package/front_end/core/sdk/CSSModel.ts +1 -1
  16. package/front_end/core/sdk/CSSProperty.ts +3 -6
  17. package/front_end/core/sdk/CSSStyleDeclaration.ts +4 -4
  18. package/front_end/core/sdk/DebuggerModel.ts +1 -2
  19. package/front_end/core/sdk/EnhancedTracesParser.ts +4 -0
  20. package/front_end/core/sdk/SourceMap.ts +2 -3
  21. package/front_end/devtools_compatibility.js +32 -24
  22. package/front_end/entrypoints/node_app/NodeConnectionsPanel.ts +2 -1
  23. package/front_end/generated/InspectorBackendCommands.js +1 -2
  24. package/front_end/generated/SupportedCSSProperties.js +57 -0
  25. package/front_end/generated/protocol.ts +0 -27
  26. package/front_end/panels/accessibility/AccessibilityNodeView.ts +18 -17
  27. package/front_end/panels/accessibility/AccessibilitySidebarView.ts +9 -12
  28. package/front_end/panels/ai_assistance/PatchWidget.ts +39 -40
  29. package/front_end/panels/ai_assistance/components/ChatView.ts +5 -4
  30. package/front_end/panels/ai_assistance/components/ExploreWidget.ts +0 -2
  31. package/front_end/panels/application/AppManifestView.ts +7 -6
  32. package/front_end/panels/application/ApplicationPanelSidebar.ts +4 -4
  33. package/front_end/panels/application/OpenedWindowDetailsView.ts +6 -6
  34. package/front_end/panels/application/StorageView.ts +9 -8
  35. package/front_end/panels/application/components/BackForwardCacheView.ts +333 -314
  36. package/front_end/panels/application/components/ProtocolHandlersView.ts +3 -2
  37. package/front_end/panels/application/preloading/components/PreloadingDisabledInfobar.ts +2 -1
  38. package/front_end/panels/autofill/AutofillView.ts +2 -3
  39. package/front_end/panels/browser_debugger/ObjectEventListenersSidebarPane.ts +8 -8
  40. package/front_end/panels/changes/CombinedDiffView.ts +13 -14
  41. package/front_end/panels/common/BadgeNotification.ts +2 -1
  42. package/front_end/panels/common/GdpSignUpDialog.ts +2 -1
  43. package/front_end/panels/console/ConsoleInsightTeaser.ts +13 -2
  44. package/front_end/panels/console/ConsolePinPane.ts +12 -7
  45. package/front_end/panels/console/ConsoleView.ts +1 -0
  46. package/front_end/panels/console/consoleView.css +0 -1
  47. package/front_end/panels/developer_resources/DeveloperResourcesView.ts +9 -9
  48. package/front_end/panels/elements/ComputedStyleWidget.ts +7 -7
  49. package/front_end/panels/elements/ElementsTreeOutline.ts +1 -1
  50. package/front_end/panels/elements/EventListenersWidget.ts +9 -9
  51. package/front_end/panels/elements/NodeStackTraceWidget.ts +6 -6
  52. package/front_end/panels/elements/PlatformFontsWidget.ts +5 -5
  53. package/front_end/panels/elements/PropertiesWidget.ts +8 -8
  54. package/front_end/panels/layer_viewer/Layers3DView.ts +2 -1
  55. package/front_end/panels/layer_viewer/PaintProfilerView.ts +3 -3
  56. package/front_end/panels/network/RequestCookiesView.ts +2 -1
  57. package/front_end/panels/network/RequestTimingView.ts +2 -1
  58. package/front_end/panels/network/components/DirectSocketConnectionView.ts +4 -6
  59. package/front_end/panels/recorder/RecorderController.ts +34 -23
  60. package/front_end/panels/recorder/components/CreateRecordingView.ts +249 -240
  61. package/front_end/panels/security/CookieControlsView.ts +74 -67
  62. package/front_end/panels/security/CookieReportView.ts +18 -16
  63. package/front_end/panels/security/IPProtectionView.ts +1 -2
  64. package/front_end/panels/security/SecurityPanel.ts +19 -19
  65. package/front_end/panels/settings/AISettingsTab.ts +2 -1
  66. package/front_end/panels/settings/KeybindsSettingsTab.ts +6 -0
  67. package/front_end/panels/settings/components/SyncSection.ts +2 -1
  68. package/front_end/panels/sources/DebuggerPausedMessage.ts +4 -3
  69. package/front_end/panels/sources/ResourceOriginPlugin.ts +3 -2
  70. package/front_end/panels/sources/SourcesNavigator.ts +2 -1
  71. package/front_end/panels/sources/TabbedEditorContainer.ts +3 -2
  72. package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +9 -9
  73. package/front_end/panels/timeline/TimelineSelectorStatsView.ts +36 -36
  74. package/front_end/panels/timeline/TimelineUIUtils.ts +3 -2
  75. package/front_end/panels/timeline/components/DetailsView.ts +5 -4
  76. package/front_end/panels/timeline/components/FieldSettingsDialog.ts +2 -1
  77. package/front_end/panels/timeline/components/LiveMetricsView.ts +5 -4
  78. package/front_end/panels/timeline/components/MetricCompareStrings.ts +25 -24
  79. package/front_end/panels/timeline/components/SidebarAnnotationsTab.ts +1 -2
  80. package/front_end/panels/timeline/components/insights/LCPDiscovery.ts +2 -1
  81. package/front_end/third_party/chromium/README.chromium +1 -1
  82. package/front_end/ui/components/docs/tooltip/basic.ts +1 -1
  83. package/front_end/ui/components/tooltips/Tooltip.ts +32 -17
  84. package/front_end/ui/i18n/i18n.ts +31 -0
  85. package/front_end/ui/legacy/SoftDropDown.ts +1 -12
  86. package/front_end/ui/legacy/ViewManager.ts +2 -4
  87. package/front_end/ui/legacy/Widget.ts +33 -17
  88. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +2 -1
  89. package/front_end/ui/legacy/legacy.ts +0 -2
  90. package/front_end/ui/visual_logging/KnownContextValues.ts +5 -0
  91. package/mcp/mcp.ts +1 -0
  92. package/package.json +1 -1
  93. package/front_end/ui/components/docs/recorder_create_recording_view/basic.html +0 -20
  94. package/front_end/ui/components/docs/recorder_create_recording_view/basic.ts +0 -27
  95. package/front_end/ui/legacy/ThrottledWidget.ts +0 -48
@@ -1,9 +1,7 @@
1
1
  // Copyright 2023 The Chromium Authors
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
- /* eslint-disable @devtools/no-lit-render-outside-of-view */
5
4
 
6
- import '../../../ui/legacy/legacy.js';
7
5
  import '../../../ui/components/icon_button/icon_button.js';
8
6
  import './ControlButton.js';
9
7
 
@@ -11,6 +9,7 @@ import * as i18n from '../../../core/i18n/i18n.js';
11
9
  import * as Badges from '../../../models/badges/badges.js';
12
10
  import * as Buttons from '../../../ui/components/buttons/buttons.js';
13
11
  import * as Input from '../../../ui/components/input/input.js';
12
+ import * as UI from '../../../ui/legacy/legacy.js';
14
13
  import * as Lit from '../../../ui/lit/lit.js';
15
14
  import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
16
15
  import * as Models from '../models/models.js';
@@ -18,7 +17,7 @@ import * as Actions from '../recorder-actions/recorder-actions.js';
18
17
 
19
18
  import createRecordingViewStyles from './createRecordingView.css.js';
20
19
 
21
- const {html, Directives: {ifDefined}} = Lit;
20
+ const {html, Directives: {ifDefined, ref, createRef}} = Lit;
22
21
 
23
22
  const UIStrings = {
24
23
  /**
@@ -103,272 +102,282 @@ const str_ = i18n.i18n.registerUIStrings(
103
102
  );
104
103
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
105
104
 
106
- declare global {
107
- interface HTMLElementTagNameMap {
108
- 'devtools-create-recording-view': CreateRecordingView;
109
- }
110
- interface HTMLElementEventMap {
111
- recordingstarted: RecordingStartedEvent;
112
- recordingcancelled: RecordingCancelledEvent;
113
- }
105
+ export interface ViewInput {
106
+ defaultRecordingName: string;
107
+ recorderSettings?: Models.RecorderSettings.RecorderSettings;
108
+ error?: Error;
109
+ startRecording: (name: string, selectorTypes: Models.Schema.SelectorType[], selectorAttribute?: string) => void;
110
+ onRecordingCancelled: () => void;
111
+ resetError: () => void;
114
112
  }
115
113
 
116
- export class RecordingStartedEvent extends Event {
117
- static readonly eventName = 'recordingstarted';
118
- name: string;
119
- selectorAttribute?: string;
120
- selectorTypesToRecord: Models.Schema.SelectorType[];
121
-
122
- constructor(
123
- name: string,
124
- selectorTypesToRecord: Models.Schema.SelectorType[],
125
- selectorAttribute?: string,
126
- ) {
127
- super(RecordingStartedEvent.eventName, {});
128
- this.name = name;
129
- this.selectorAttribute = selectorAttribute || undefined;
130
- this.selectorTypesToRecord = selectorTypesToRecord;
131
- }
114
+ export interface ViewOutput {
115
+ focusInput?: () => void;
116
+ triggerFormSubmission?: () => void;
132
117
  }
133
118
 
134
- export class RecordingCancelledEvent extends Event {
135
- static readonly eventName = 'recordingcancelled';
136
- constructor() {
137
- super(RecordingCancelledEvent.eventName);
138
- }
139
- }
119
+ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLElement): void => {
120
+ const {defaultRecordingName, recorderSettings, error, startRecording, onRecordingCancelled, resetError} = input;
140
121
 
141
- export interface CreateRecordingViewData {
142
- recorderSettings: Models.RecorderSettings.RecorderSettings;
143
- }
122
+ // TODO(crbug.com/455531160): move state from input elements into the presenter widget.
123
+ const selectorTypeToLabel = new Map([
124
+ [Models.Schema.SelectorType.ARIA, i18nString(UIStrings.selectorTypeARIA)],
125
+ [Models.Schema.SelectorType.CSS, i18nString(UIStrings.selectorTypeCSS)],
126
+ [Models.Schema.SelectorType.Text, i18nString(UIStrings.selectorTypeText)],
127
+ [
128
+ Models.Schema.SelectorType.XPath,
129
+ i18nString(UIStrings.selectorTypeXPath),
130
+ ],
131
+ [
132
+ Models.Schema.SelectorType.Pierce,
133
+ i18nString(UIStrings.selectorTypePierce),
134
+ ],
135
+ ]);
136
+ function getSelectorTypes(): Models.Schema.SelectorType[] {
137
+ const selectorTypeElements = target.querySelectorAll(
138
+ '.selector-type input[type=checkbox]',
139
+ );
140
+ const selectorTypesToRecord: Models.Schema.SelectorType[] = [];
141
+ for (const selectorType of selectorTypeElements) {
142
+ const checkbox = selectorType as HTMLInputElement;
143
+ const checkboxValue = checkbox.value as Models.Schema.SelectorType;
144
+ if (checkbox.checked) {
145
+ selectorTypesToRecord.push(checkboxValue);
146
+ }
147
+ }
148
+ return selectorTypesToRecord;
149
+ }
144
150
 
145
- export class CreateRecordingView extends HTMLElement {
146
- readonly #shadow = this.attachShadow({mode: 'open'});
147
- #defaultRecordingName = '';
148
- #error?: Error;
149
- #recorderSettings?: Models.RecorderSettings.RecorderSettings;
151
+ const selectorAttributeInputRef = createRef<HTMLInputElement>();
150
152
 
151
- constructor() {
152
- super();
153
- this.setAttribute('jslog', `${VisualLogging.section('create-recording-view')}`);
153
+ function getSelectorAttribute(): string|undefined {
154
+ const selectorAttribute = selectorAttributeInputRef.value?.value.trim();
155
+ if (!selectorAttribute) {
156
+ return undefined;
157
+ }
158
+ return selectorAttribute;
154
159
  }
155
160
 
156
- connectedCallback(): void {
157
- this.#render();
158
- this.#shadow.querySelector('input')?.focus();
159
- }
161
+ const nameInputRef = createRef<HTMLInputElement>();
160
162
 
161
- set data(data: CreateRecordingViewData) {
162
- this.#recorderSettings = data.recorderSettings;
163
- this.#defaultRecordingName = this.#recorderSettings.defaultTitle;
163
+ function handleStartRecording(): void {
164
+ startRecording(nameInputRef.value?.value ?? '', getSelectorTypes(), getSelectorAttribute());
164
165
  }
165
166
 
166
- #onKeyDown(event: KeyboardEvent): void {
167
- if (this.#error) {
168
- this.#error = undefined;
169
- this.#render();
167
+ const onKeyDown = (event: KeyboardEvent): void => {
168
+ if (error) {
169
+ resetError();
170
170
  }
171
171
 
172
172
  const keyboardEvent = event;
173
173
  if (keyboardEvent.key === 'Enter') {
174
- this.startRecording();
174
+ handleStartRecording();
175
175
  event.stopPropagation();
176
176
  event.preventDefault();
177
177
  }
178
- }
178
+ };
179
179
 
180
- startRecording(): void {
181
- const nameInput = this.#shadow.querySelector<HTMLInputElement>('#user-flow-name');
182
- if (!nameInput) {
183
- throw new Error('input#user-flow-name not found');
184
- }
185
- if (!this.#recorderSettings) {
186
- throw new Error('settings not set');
187
- }
180
+ const onInputFocus = (): void => {
181
+ nameInputRef.value?.select();
182
+ };
188
183
 
189
- if (!nameInput.value.trim()) {
190
- this.#error = new Error(i18nString(UIStrings.recordingNameIsRequired));
191
- this.#render();
192
- return;
193
- }
184
+ output.focusInput = () => {
185
+ nameInputRef.value?.focus();
186
+ };
194
187
 
195
- const selectorTypeElements = this.#shadow.querySelectorAll(
196
- '.selector-type input[type=checkbox]',
197
- );
198
- const selectorTypesToRecord: Models.Schema.SelectorType[] = [];
199
- for (const selectorType of selectorTypeElements) {
200
- const checkbox = selectorType as HTMLInputElement;
201
- const checkboxValue = checkbox.value as Models.Schema.SelectorType;
202
- if (checkbox.checked) {
203
- selectorTypesToRecord.push(checkboxValue);
204
- }
205
- }
188
+ output.triggerFormSubmission = () => {
189
+ handleStartRecording();
190
+ };
206
191
 
207
- if (!selectorTypesToRecord.includes(Models.Schema.SelectorType.CSS) &&
208
- !selectorTypesToRecord.includes(Models.Schema.SelectorType.XPath) &&
209
- !selectorTypesToRecord.includes(Models.Schema.SelectorType.Pierce)) {
210
- this.#error = new Error(i18nString(UIStrings.includeNecessarySelectors));
211
- this.#render();
212
- return;
213
- }
192
+ // clang-format off
193
+ Lit.render(
194
+ html`
195
+ <style>${createRecordingViewStyles}</style>
196
+ <style>${Input.textInputStyles}</style>
197
+ <style>${Input.checkboxStyles}</style>
198
+ <div class="wrapper" jslog=${VisualLogging.section('create-recording-view')}>
199
+ <div class="header-wrapper">
200
+ <h1>${i18nString(UIStrings.createRecording)}</h1>
201
+ <devtools-button
202
+ title=${i18nString(UIStrings.cancelRecording)}
203
+ jslog=${VisualLogging.close().track({click: true})}
204
+ .data=${
205
+ {
206
+ variant: Buttons.Button.Variant.ICON,
207
+ size: Buttons.Button.Size.SMALL,
208
+ iconName: 'cross',
209
+ } as Buttons.Button.ButtonData
210
+ }
211
+ @click=${onRecordingCancelled}
212
+ ></devtools-button>
213
+ </div>
214
+ <label class="row-label" for="user-flow-name">${i18nString(
215
+ UIStrings.recordingName,
216
+ )}</label>
217
+ <input
218
+ value=${defaultRecordingName}
219
+ @focus=${onInputFocus}
220
+ @keydown=${onKeyDown}
221
+ jslog=${VisualLogging.textField('user-flow-name').track({change: true})}
222
+ class="devtools-text-input"
223
+ id="user-flow-name"
224
+ ${ref(nameInputRef)}
225
+ />
226
+ <label class="row-label" for="selector-attribute">
227
+ <span>${i18nString(UIStrings.selectorAttribute)}</span>
228
+ <x-link
229
+ class="link" href="https://g.co/devtools/recorder#selector"
230
+ title=${i18nString(UIStrings.learnMore)}
231
+ jslog=${VisualLogging.link('recorder-selector-help').track({click: true})}>
232
+ <devtools-icon name="help">
233
+ </devtools-icon>
234
+ </x-link>
235
+ </label>
236
+ <input
237
+ value=${ifDefined(recorderSettings?.selectorAttribute)}
238
+ placeholder="data-testid"
239
+ @keydown=${onKeyDown}
240
+ jslog=${VisualLogging.textField('selector-attribute').track({change: true})}
241
+ class="devtools-text-input"
242
+ id="selector-attribute"
243
+ ${ref(selectorAttributeInputRef)}
244
+ />
245
+ <label class="row-label">
246
+ <span>${i18nString(UIStrings.selectorTypes)}</span>
247
+ <x-link
248
+ class="link" href="https://g.co/devtools/recorder#selector"
249
+ title=${i18nString(UIStrings.learnMore)}
250
+ jslog=${VisualLogging.link('recorder-selector-help').track({click: true})}>
251
+ <devtools-icon name="help">
252
+ </devtools-icon>
253
+ </x-link>
254
+ </label>
255
+ <div class="checkbox-container">
256
+ ${Object.values(Models.Schema.SelectorType).map(selectorType => {
257
+ const checked =
258
+ recorderSettings?.getSelectorByType(selectorType);
259
+ return html`
260
+ <label class="checkbox-label selector-type">
261
+ <input
262
+ @keydown=${onKeyDown}
263
+ .value=${selectorType}
264
+ jslog=${VisualLogging.toggle().track({click: true}).context(`selector-${selectorType}`)}
265
+ ?checked=${checked}
266
+ type="checkbox"
267
+ />
268
+ ${selectorTypeToLabel.get(selectorType) || selectorType}
269
+ </label>
270
+ `;
271
+ })}
272
+ </div>
214
273
 
215
- for (const selectorType of Object.values(Models.Schema.SelectorType)) {
216
- this.#recorderSettings.setSelectorByType(
217
- selectorType,
218
- selectorTypesToRecord.includes(selectorType),
219
- );
220
- }
274
+ ${
275
+ error &&
276
+ html`
277
+ <div class="error" role="alert">
278
+ ${error.message}
279
+ </div>
280
+ `
281
+ }
282
+ </div>
283
+ <div class="footer">
284
+ <div class="controls">
285
+ <devtools-control-button
286
+ @click=${handleStartRecording}
287
+ .label=${i18nString(UIStrings.startRecording)}
288
+ .shape=${'circle'}
289
+ jslog=${VisualLogging.action(Actions.RecorderActions.START_RECORDING).track({click: true})}
290
+ title=${Models.Tooltip.getTooltipForActions(
291
+ i18nString(UIStrings.startRecording),
292
+ Actions.RecorderActions.START_RECORDING,
293
+ )}
294
+ ></devtools-control-button>
295
+ </div>
296
+ </div>
297
+ `,
298
+ target,
299
+ );
300
+ // clang-format on
301
+ };
221
302
 
222
- const selectorAttributeEl = this.#shadow.querySelector(
223
- '#selector-attribute',
224
- ) as HTMLInputElement;
225
- const selectorAttribute = selectorAttributeEl.value.trim();
226
- this.#recorderSettings.selectorAttribute = selectorAttribute;
227
-
228
- this.dispatchEvent(
229
- new RecordingStartedEvent(
230
- nameInput.value.trim(),
231
- selectorTypesToRecord,
232
- selectorAttribute,
233
- ),
234
- );
235
- Badges.UserBadges.instance().recordAction(Badges.BadgeAction.RECORDER_RECORDING_STARTED);
303
+ export class CreateRecordingView extends UI.Widget.Widget {
304
+ #error?: Error;
305
+ #view: typeof DEFAULT_VIEW;
306
+ #output: ViewOutput = {};
307
+
308
+ onRecordingStarted:
309
+ (data: {name: string, selectorTypesToRecord: Models.Schema.SelectorType[], selectorAttribute?: string}) => void =
310
+ () => {};
311
+ onRecordingCancelled = (): void => {};
312
+ recorderSettings?: Models.RecorderSettings.RecorderSettings;
313
+ defaultRecordingName = '';
314
+
315
+ constructor(element?: HTMLElement, view?: typeof DEFAULT_VIEW) {
316
+ super(element, {useShadowDom: true});
317
+ this.#view = view || DEFAULT_VIEW;
236
318
  }
237
319
 
238
- #dispatchRecordingCancelled(): void {
239
- this.dispatchEvent(new RecordingCancelledEvent());
320
+ override wasShown(): void {
321
+ super.wasShown();
322
+ this.requestUpdate();
323
+ void this.updateComplete.then(() => this.#output.focusInput?.());
240
324
  }
241
325
 
242
- #onInputFocus = (): void => {
243
- (this.#shadow.querySelector('#user-flow-name') as HTMLInputElement)?.select();
244
- };
326
+ triggerFormSubmission(): void {
327
+ this.#output.triggerFormSubmission?.();
328
+ }
245
329
 
246
- #render(): void {
247
- const selectorTypeToLabel = new Map([
248
- [Models.Schema.SelectorType.ARIA, i18nString(UIStrings.selectorTypeARIA)],
249
- [Models.Schema.SelectorType.CSS, i18nString(UIStrings.selectorTypeCSS)],
250
- [Models.Schema.SelectorType.Text, i18nString(UIStrings.selectorTypeText)],
251
- [
252
- Models.Schema.SelectorType.XPath,
253
- i18nString(UIStrings.selectorTypeXPath),
254
- ],
255
- [
256
- Models.Schema.SelectorType.Pierce,
257
- i18nString(UIStrings.selectorTypePierce),
258
- ],
259
- ]);
260
- // clang-format off
261
- Lit.render(
262
- html`
263
- <style>${createRecordingViewStyles}</style>
264
- <style>${Input.textInputStyles}</style>
265
- <style>${Input.checkboxStyles}</style>
266
- <div class="wrapper">
267
- <div class="header-wrapper">
268
- <h1>${i18nString(UIStrings.createRecording)}</h1>
269
- <devtools-button
270
- title=${i18nString(UIStrings.cancelRecording)}
271
- jslog=${VisualLogging.close().track({click: true})}
272
- .data=${
273
- {
274
- variant: Buttons.Button.Variant.ICON,
275
- size: Buttons.Button.Size.SMALL,
276
- iconName: 'cross',
277
- } as Buttons.Button.ButtonData
278
- }
279
- @click=${this.#dispatchRecordingCancelled}
280
- ></devtools-button>
281
- </div>
282
- <label class="row-label" for="user-flow-name">${i18nString(
283
- UIStrings.recordingName,
284
- )}</label>
285
- <input
286
- value=${this.#defaultRecordingName}
287
- @focus=${this.#onInputFocus}
288
- @keydown=${this.#onKeyDown}
289
- jslog=${VisualLogging.textField('user-flow-name').track({change: true})}
290
- class="devtools-text-input"
291
- id="user-flow-name"
292
- />
293
- <label class="row-label" for="selector-attribute">
294
- <span>${i18nString(UIStrings.selectorAttribute)}</span>
295
- <x-link
296
- class="link" href="https://g.co/devtools/recorder#selector"
297
- title=${i18nString(UIStrings.learnMore)}
298
- jslog=${VisualLogging.link('recorder-selector-help').track({click: true})}>
299
- <devtools-icon name="help">
300
- </devtools-icon>
301
- </x-link>
302
- </label>
303
- <input
304
- value=${ifDefined(this.#recorderSettings?.selectorAttribute)}
305
- placeholder="data-testid"
306
- @keydown=${this.#onKeyDown}
307
- jslog=${VisualLogging.textField('selector-attribute').track({change: true})}
308
- class="devtools-text-input"
309
- id="selector-attribute"
310
- />
311
- <label class="row-label">
312
- <span>${i18nString(UIStrings.selectorTypes)}</span>
313
- <x-link
314
- class="link" href="https://g.co/devtools/recorder#selector"
315
- title=${i18nString(UIStrings.learnMore)}
316
- jslog=${VisualLogging.link('recorder-selector-help').track({click: true})}>
317
- <devtools-icon name="help">
318
- </devtools-icon>
319
- </x-link>
320
- </label>
321
- <div class="checkbox-container">
322
- ${Object.values(Models.Schema.SelectorType).map(selectorType => {
323
- const checked =
324
- this.#recorderSettings?.getSelectorByType(selectorType);
325
- return html`
326
- <label class="checkbox-label selector-type">
327
- <input
328
- @keydown=${this.#onKeyDown}
329
- .value=${selectorType}
330
- jslog=${VisualLogging.toggle().track({click: true}).context(`selector-${selectorType}`)}
331
- ?checked=${checked}
332
- type="checkbox"
333
- />
334
- ${selectorTypeToLabel.get(selectorType) || selectorType}
335
- </label>
336
- `;
337
- })}
338
- </div>
339
-
340
- ${
341
- this.#error &&
342
- html`
343
- <div class="error" role="alert">
344
- ${this.#error.message}
345
- </div>
346
- `
347
- }
348
- </div>
349
- <div class="footer">
350
- <div class="controls">
351
- <devtools-control-button
352
- @click=${this.startRecording}
353
- .label=${i18nString(UIStrings.startRecording)}
354
- .shape=${'circle'}
355
- jslog=${VisualLogging.action(Actions.RecorderActions.START_RECORDING).track({click: true})}
356
- title=${Models.Tooltip.getTooltipForActions(
357
- i18nString(UIStrings.startRecording),
358
- Actions.RecorderActions.START_RECORDING,
359
- )}
360
- ></devtools-control-button>
361
- </div>
362
- </div>
363
- `,
364
- this.#shadow,
365
- { host: this },
366
- );
367
- // clang-format on
330
+ override performUpdate(): void {
331
+ this.#view(
332
+ {
333
+ defaultRecordingName: this.defaultRecordingName,
334
+ recorderSettings: this.recorderSettings,
335
+ error: this.#error,
336
+ onRecordingCancelled: this.onRecordingCancelled,
337
+ startRecording:
338
+ (name: string, selectorTypesToRecord: Models.Schema.SelectorType[], selectorAttribute?: string): void => {
339
+ if (!this.recorderSettings) {
340
+ throw new Error('settings not set');
341
+ }
342
+
343
+ if (!name.trim()) {
344
+ this.#error = new Error(i18nString(UIStrings.recordingNameIsRequired));
345
+ this.requestUpdate();
346
+ return;
347
+ }
348
+
349
+ if (!selectorTypesToRecord.includes(Models.Schema.SelectorType.CSS) &&
350
+ !selectorTypesToRecord.includes(Models.Schema.SelectorType.XPath) &&
351
+ !selectorTypesToRecord.includes(Models.Schema.SelectorType.Pierce)) {
352
+ this.#error = new Error(i18nString(UIStrings.includeNecessarySelectors));
353
+ this.requestUpdate();
354
+ return;
355
+ }
356
+
357
+ for (const selectorType of Object.values(Models.Schema.SelectorType)) {
358
+ this.recorderSettings.setSelectorByType(
359
+ selectorType,
360
+ selectorTypesToRecord.includes(selectorType),
361
+ );
362
+ }
363
+
364
+ if (selectorAttribute) {
365
+ this.recorderSettings.selectorAttribute = selectorAttribute;
366
+ }
367
+
368
+ this.onRecordingStarted({
369
+ name,
370
+ selectorTypesToRecord,
371
+ selectorAttribute,
372
+ });
373
+
374
+ Badges.UserBadges.instance().recordAction(Badges.BadgeAction.RECORDER_RECORDING_STARTED);
375
+ },
376
+ resetError: () => {
377
+ this.#error = undefined;
378
+ this.requestUpdate();
379
+ },
380
+ },
381
+ this.#output, this.contentElement);
368
382
  }
369
383
  }
370
-
371
- customElements.define(
372
- 'devtools-create-recording-view',
373
- CreateRecordingView,
374
- );