chrome-devtools-frontend 1.0.1535712 → 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 (77) hide show
  1. package/docs/contributing/images/issues-nearestslo.png +0 -0
  2. package/docs/contributing/issues.md +17 -21
  3. package/front_end/core/common/Console.ts +1 -8
  4. package/front_end/core/common/ParsedURL.ts +10 -20
  5. package/front_end/core/common/SegmentedRange.ts +1 -2
  6. package/front_end/core/common/StringOutputStream.ts +1 -4
  7. package/front_end/core/i18n/i18nImpl.ts +0 -24
  8. package/front_end/core/sdk/AnimationModel.ts +1 -2
  9. package/front_end/core/sdk/CSSMatchedStyles.ts +2 -2
  10. package/front_end/core/sdk/CSSModel.ts +1 -1
  11. package/front_end/core/sdk/CSSProperty.ts +3 -6
  12. package/front_end/core/sdk/CSSStyleDeclaration.ts +4 -4
  13. package/front_end/core/sdk/DebuggerModel.ts +1 -2
  14. package/front_end/core/sdk/EnhancedTracesParser.ts +4 -0
  15. package/front_end/core/sdk/SourceMap.ts +2 -3
  16. package/front_end/entrypoints/node_app/NodeConnectionsPanel.ts +2 -1
  17. package/front_end/generated/InspectorBackendCommands.js +1 -2
  18. package/front_end/generated/SupportedCSSProperties.js +19 -0
  19. package/front_end/generated/protocol.ts +0 -27
  20. package/front_end/panels/accessibility/AccessibilityNodeView.ts +18 -17
  21. package/front_end/panels/accessibility/AccessibilitySidebarView.ts +9 -12
  22. package/front_end/panels/ai_assistance/components/ChatView.ts +5 -4
  23. package/front_end/panels/application/AppManifestView.ts +7 -6
  24. package/front_end/panels/application/ApplicationPanelSidebar.ts +4 -4
  25. package/front_end/panels/application/OpenedWindowDetailsView.ts +6 -6
  26. package/front_end/panels/application/StorageView.ts +9 -8
  27. package/front_end/panels/application/components/BackForwardCacheView.ts +333 -314
  28. package/front_end/panels/application/components/ProtocolHandlersView.ts +3 -2
  29. package/front_end/panels/application/preloading/components/PreloadingDisabledInfobar.ts +2 -1
  30. package/front_end/panels/browser_debugger/ObjectEventListenersSidebarPane.ts +8 -8
  31. package/front_end/panels/common/BadgeNotification.ts +2 -1
  32. package/front_end/panels/common/GdpSignUpDialog.ts +2 -1
  33. package/front_end/panels/console/ConsoleInsightTeaser.ts +8 -2
  34. package/front_end/panels/console/ConsolePinPane.ts +12 -7
  35. package/front_end/panels/developer_resources/DeveloperResourcesView.ts +9 -9
  36. package/front_end/panels/elements/ComputedStyleWidget.ts +7 -7
  37. package/front_end/panels/elements/EventListenersWidget.ts +9 -9
  38. package/front_end/panels/elements/NodeStackTraceWidget.ts +6 -6
  39. package/front_end/panels/elements/PlatformFontsWidget.ts +5 -5
  40. package/front_end/panels/elements/PropertiesWidget.ts +8 -8
  41. package/front_end/panels/layer_viewer/Layers3DView.ts +2 -1
  42. package/front_end/panels/layer_viewer/PaintProfilerView.ts +3 -3
  43. package/front_end/panels/network/RequestCookiesView.ts +2 -1
  44. package/front_end/panels/network/RequestTimingView.ts +2 -1
  45. package/front_end/panels/recorder/RecorderController.ts +34 -23
  46. package/front_end/panels/recorder/components/CreateRecordingView.ts +249 -240
  47. package/front_end/panels/security/CookieControlsView.ts +2 -1
  48. package/front_end/panels/security/CookieReportView.ts +3 -2
  49. package/front_end/panels/settings/AISettingsTab.ts +2 -1
  50. package/front_end/panels/settings/KeybindsSettingsTab.ts +6 -0
  51. package/front_end/panels/settings/components/SyncSection.ts +2 -1
  52. package/front_end/panels/sources/DebuggerPausedMessage.ts +4 -3
  53. package/front_end/panels/sources/ResourceOriginPlugin.ts +3 -2
  54. package/front_end/panels/sources/SourcesNavigator.ts +2 -1
  55. package/front_end/panels/sources/TabbedEditorContainer.ts +3 -2
  56. package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +9 -9
  57. package/front_end/panels/timeline/TimelineUIUtils.ts +3 -2
  58. package/front_end/panels/timeline/components/DetailsView.ts +5 -4
  59. package/front_end/panels/timeline/components/FieldSettingsDialog.ts +2 -1
  60. package/front_end/panels/timeline/components/LiveMetricsView.ts +5 -4
  61. package/front_end/panels/timeline/components/MetricCompareStrings.ts +25 -24
  62. package/front_end/panels/timeline/components/insights/LCPDiscovery.ts +2 -1
  63. package/front_end/third_party/chromium/README.chromium +1 -1
  64. package/front_end/ui/components/docs/tooltip/basic.ts +1 -1
  65. package/front_end/ui/components/tooltips/Tooltip.ts +32 -17
  66. package/front_end/ui/i18n/i18n.ts +31 -0
  67. package/front_end/ui/legacy/SoftDropDown.ts +1 -12
  68. package/front_end/ui/legacy/ViewManager.ts +2 -4
  69. package/front_end/ui/legacy/Widget.ts +33 -17
  70. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +2 -1
  71. package/front_end/ui/legacy/legacy.ts +0 -2
  72. package/front_end/ui/visual_logging/KnownContextValues.ts +3 -0
  73. package/mcp/mcp.ts +1 -0
  74. package/package.json +1 -1
  75. package/front_end/ui/components/docs/recorder_create_recording_view/basic.html +0 -20
  76. package/front_end/ui/components/docs/recorder_create_recording_view/basic.ts +0 -27
  77. package/front_end/ui/legacy/ThrottledWidget.ts +0 -48
@@ -233,6 +233,7 @@ export class RecorderController extends LitElement {
233
233
  'disable-self-xss-warning', false, Common.Settings.SettingStorageType.SYNCED);
234
234
 
235
235
  #recordingView?: Components.RecordingView.RecordingView;
236
+ #createRecordingView?: Components.CreateRecordingView.CreateRecordingView;
236
237
 
237
238
  constructor() {
238
239
  super();
@@ -804,7 +805,9 @@ export class RecorderController extends LitElement {
804
805
  this.#clearError();
805
806
  }
806
807
 
807
- async #onRecordingStarted(event: Components.CreateRecordingView.RecordingStartedEvent): Promise<void> {
808
+ async #onRecordingStarted(
809
+ data: {name: string, selectorTypesToRecord: Models.Schema.SelectorType[], selectorAttribute?: string}):
810
+ Promise<void> {
808
811
  // Recording is not available in device mode.
809
812
  await this.#disableDeviceModeIfEnabled();
810
813
 
@@ -815,10 +818,10 @@ export class RecorderController extends LitElement {
815
818
  // -- Recording logic starts here --
816
819
  Host.userMetrics.recordingToggled(Host.UserMetrics.RecordingToggled.RECORDING_STARTED);
817
820
  this.currentRecordingSession = new Models.RecordingSession.RecordingSession(this.#getMainTarget(), {
818
- title: event.name,
819
- selectorAttribute: event.selectorAttribute,
820
- selectorTypesToRecord: event.selectorTypesToRecord.length ? event.selectorTypesToRecord :
821
- Object.values(Models.Schema.SelectorType),
821
+ title: data.name,
822
+ selectorAttribute: data.selectorAttribute,
823
+ selectorTypesToRecord: data.selectorTypesToRecord.length ? data.selectorTypesToRecord :
824
+ Object.values(Models.Schema.SelectorType),
822
825
  });
823
826
  this.#setCurrentRecording(await this.#storage.saveRecording(this.currentRecordingSession.cloneUserFlow()));
824
827
 
@@ -901,7 +904,7 @@ export class RecorderController extends LitElement {
901
904
  this.dispatchEvent(new Events.RecordingStateChangedEvent(this.currentRecording.flow));
902
905
  }
903
906
 
904
- async #onRecordingCancelled(): Promise<void> {
907
+ async onRecordingCancelled(): Promise<void> {
905
908
  if (this.previousPage) {
906
909
  this.#setCurrentPage(this.previousPage);
907
910
  }
@@ -1060,15 +1063,17 @@ export class RecorderController extends LitElement {
1060
1063
 
1061
1064
  case Actions.RecorderActions.START_RECORDING:
1062
1065
  if (this.currentPage !== Pages.CREATE_RECORDING_PAGE && !this.isRecording) {
1063
- this.#shortcutHelper.handleShortcut(this.#onRecordingStarted.bind(
1064
- this,
1065
- new Components.CreateRecordingView.RecordingStartedEvent(
1066
- this.#recorderSettings.defaultTitle, this.#recorderSettings.defaultSelectors,
1067
- this.#recorderSettings.selectorAttribute)));
1066
+ this.#shortcutHelper.handleShortcut(this.#onRecordingStarted.bind(this, {
1067
+ name: this.#recorderSettings.defaultTitle,
1068
+ selectorTypesToRecord: this.#recorderSettings.defaultSelectors,
1069
+ selectorAttribute: this.#recorderSettings.selectorAttribute ? this.#recorderSettings.selectorAttribute :
1070
+ undefined,
1071
+ }));
1068
1072
  } else if (this.currentPage === Pages.CREATE_RECORDING_PAGE) {
1069
- const view = this.renderRoot.querySelector('devtools-create-recording-view');
1070
- if (view) {
1071
- this.#shortcutHelper.handleShortcut(view.startRecording.bind(view));
1073
+ if (this.#createRecordingView) {
1074
+ this.#shortcutHelper.handleShortcut(() => {
1075
+ this.#createRecordingView?.triggerFormSubmission();
1076
+ });
1072
1077
  }
1073
1078
  } else if (this.isRecording) {
1074
1079
  void this.#onRecordingFinished();
@@ -1233,15 +1238,21 @@ export class RecorderController extends LitElement {
1233
1238
  #renderCreateRecordingPage(): Lit.TemplateResult {
1234
1239
  // clang-format off
1235
1240
  return html`
1236
- <devtools-create-recording-view
1237
- .data=${
1238
- {
1239
- recorderSettings: this.#recorderSettings,
1240
- } as Components.CreateRecordingView.CreateRecordingViewData
1241
- }
1242
- @recordingstarted=${this.#onRecordingStarted}
1243
- @recordingcancelled=${this.#onRecordingCancelled}
1244
- ></devtools-create-recording-view>
1241
+ <devtools-widget
1242
+ class="recording-view"
1243
+ .widgetConfig=${UI.Widget.widgetConfig(Components.CreateRecordingView.CreateRecordingView, {
1244
+ recorderSettings: this.#recorderSettings,
1245
+ defaultRecordingName: this.#recorderSettings.defaultTitle,
1246
+ onRecordingStarted: this.#onRecordingStarted.bind(this),
1247
+ onRecordingCancelled: this.onRecordingCancelled.bind(this),
1248
+ })}
1249
+ ${UI.Widget.widgetRef(
1250
+ Components.CreateRecordingView.CreateRecordingView,
1251
+ widget => {
1252
+ this.#createRecordingView = widget;
1253
+ },
1254
+ )}
1255
+ ></devtools-widget>
1245
1256
  `;
1246
1257
  // clang-format on
1247
1258
  }
@@ -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
- );
@@ -14,6 +14,7 @@ import * as Root from '../../core/root/root.js';
14
14
  import * as SDK from '../../core/sdk/sdk.js';
15
15
  import * as Buttons from '../../ui/components/buttons/buttons.js';
16
16
  import * as Input from '../../ui/components/input/input.js';
17
+ import * as uiI18n from '../../ui/i18n/i18n.js';
17
18
  import * as UI from '../../ui/legacy/legacy.js';
18
19
  import {Directives, html, nothing, render, type TemplateResult} from '../../ui/lit/lit.js';
19
20
  import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
@@ -113,7 +114,7 @@ const UIStrings = {
113
114
 
114
115
  const str_ = i18n.i18n.registerUIStrings('panels/security/CookieControlsView.ts', UIStrings);
115
116
  export const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
116
- export const i18nFormatString = i18n.i18n.getFormatLocalizedString.bind(undefined, str_);
117
+ export const i18nFormatString = uiI18n.getFormatLocalizedString.bind(undefined, str_);
117
118
 
118
119
  export interface ViewInput {
119
120
  thirdPartyControlsDict: Root.Runtime.HostConfig['thirdPartyCookieControls'];