chrome-devtools-frontend 1.0.1724140 → 1.0.1726686

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/.agents/skills/review-ui-widgets/SKILL.md +7 -7
  2. package/.agents/skills/ui-eng-vision-local-lit-renderer/SKILL.md +12 -0
  3. package/.agents/skills/ui-eng-vision-widget-promoter/SKILL.md +3 -3
  4. package/.agents/skills/ui-widgets/SKILL.md +2 -1
  5. package/docs/cookbook/dependencies.md +6 -4
  6. package/front_end/core/i18n/i18nImpl.ts +1 -1
  7. package/front_end/core/root/Runtime.ts +1 -1
  8. package/front_end/core/sdk/ResourceTreeModel.ts +16 -0
  9. package/front_end/entrypoints/js_app/js_app.ts +2 -5
  10. package/front_end/entrypoints/node_app/node_app.ts +2 -3
  11. package/front_end/generated/InspectorBackendCommands.ts +1 -1
  12. package/front_end/generated/protocol.ts +1 -0
  13. package/front_end/models/ai_assistance/skills/lighthouse.md +2 -1
  14. package/front_end/models/ai_assistance/skills/network.md +1 -0
  15. package/front_end/models/ai_assistance/tools/ExecuteJavaScript.ts +26 -2
  16. package/front_end/models/ai_assistance/tools/GetLighthouseAudits.ts +1 -1
  17. package/front_end/models/ai_assistance/tools/ListNetworkRequests.ts +2 -7
  18. package/front_end/models/ai_assistance/tools/RunLighthouse.ts +2 -2
  19. package/front_end/models/comment_manager/CommentManager.ts +0 -6
  20. package/front_end/models/comment_manager/CommentThread.ts +23 -19
  21. package/front_end/models/elements/StyleChanges.ts +66 -0
  22. package/front_end/models/trace/EventsSerializer.ts +2 -7
  23. package/front_end/models/trace/ModelImpl.ts +0 -2
  24. package/front_end/models/trace/handlers/FlowsHandler.ts +37 -29
  25. package/front_end/models/trace/helpers/SyntheticEvents.ts +10 -37
  26. package/front_end/models/trace/insights/RenderBlocking.ts +3 -1
  27. package/front_end/models/trace/lantern/simulation/Simulator.ts +4 -0
  28. package/front_end/models/trace/lantern/types/Lantern.ts +1 -0
  29. package/front_end/panels/accessibility/SourceOrderView.ts +1 -1
  30. package/front_end/panels/application/ApplicationPanelSidebar.ts +90 -0
  31. package/front_end/panels/application/StorageView.ts +196 -174
  32. package/front_end/panels/application/SubAppsView.ts +2 -2
  33. package/front_end/panels/application/preloading/PreloadingView.ts +360 -336
  34. package/front_end/panels/application/preloading/components/PreloadingDisabledInfobar.ts +2 -2
  35. package/front_end/panels/application/preloading/components/PreloadingGrid.ts +6 -3
  36. package/front_end/panels/application/preloading/components/RuleSetGrid.ts +17 -17
  37. package/front_end/panels/application/preloading/components/UsedPreloadingView.ts +2 -2
  38. package/front_end/panels/application/preloading/preloadingView.css +12 -4
  39. package/front_end/panels/application/preloading/preloadingViewDropDown.css +1 -1
  40. package/front_end/panels/application/storageView.css +16 -21
  41. package/front_end/panels/browser_debugger/CategorizedBreakpointsSidebarPane.ts +2 -2
  42. package/front_end/panels/browser_debugger/browser_debugger-meta.ts +6 -8
  43. package/front_end/panels/comments/CommentsPane.ts +26 -9
  44. package/front_end/panels/comments/commentsPane.css +25 -6
  45. package/front_end/panels/common/CommentThreadWidget.ts +2 -2
  46. package/front_end/panels/common/CommentsOverlayWidget.ts +94 -35
  47. package/front_end/panels/common/commentThreadWidget.css +17 -0
  48. package/front_end/panels/common/commentsOverlay.css +5 -10
  49. package/front_end/panels/common/common-meta.ts +11 -1
  50. package/front_end/panels/elements/ClassesPaneWidget.ts +130 -88
  51. package/front_end/panels/elements/ColorSwatchPopoverIcon.ts +7 -33
  52. package/front_end/panels/elements/DOMTreeWidget.ts +7 -3
  53. package/front_end/panels/elements/ElementStatePaneWidget.ts +11 -1
  54. package/front_end/panels/elements/PropertyRenderer.ts +3 -2
  55. package/front_end/panels/elements/StyleEditorWidget.ts +60 -41
  56. package/front_end/panels/elements/StylePropertiesSection.ts +32 -5
  57. package/front_end/panels/elements/StylePropertyTreeElement.ts +227 -162
  58. package/front_end/panels/event_listeners/EventListenersView.ts +11 -8
  59. package/front_end/panels/linear_memory_inspector/components/ValueInterpreterSettings.ts +1 -1
  60. package/front_end/panels/network/NetworkDataGridNode.ts +3 -1
  61. package/front_end/panels/network/RequestConditionsDrawer.ts +1 -1
  62. package/front_end/panels/network/RequestPayloadView.ts +12 -5
  63. package/front_end/panels/profiler/HeapProfileView.ts +28 -6
  64. package/front_end/panels/profiler/HeapSnapshotDataGrids.ts +7 -3
  65. package/front_end/panels/protocol_monitor/JSONEditor.ts +0 -1
  66. package/front_end/panels/recorder/RecorderPanel.ts +2 -2
  67. package/front_end/panels/settings/SettingsScreen.ts +1 -1
  68. package/front_end/panels/sources/NavigatorView.ts +105 -45
  69. package/front_end/panels/sources/ScopeChainSidebarPane.ts +8 -5
  70. package/front_end/panels/sources/SourcesNavigator.ts +421 -213
  71. package/front_end/panels/sources/SourcesPanel.ts +6 -4
  72. package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +10 -5
  73. package/front_end/panels/sources/sources-meta.ts +4 -15
  74. package/front_end/panels/sources/sourcesNavigator.css +5 -0
  75. package/front_end/panels/timeline/ModificationsManager.ts +0 -2
  76. package/front_end/panels/timeline/README.md +2 -0
  77. package/front_end/panels/timeline/TimelineCommentController.ts +132 -30
  78. package/front_end/panels/timeline/TimelinePanel.ts +4 -0
  79. package/front_end/panels/timeline/components/IgnoreListSetting.ts +1 -1
  80. package/front_end/panels/timeline/utils/Treemap.ts +8 -0
  81. package/front_end/third_party/chromium/README.chromium +1 -1
  82. package/front_end/ui/comments/CommentAnchorResolver.ts +3 -0
  83. package/front_end/ui/comments/CommentOverlayManager.ts +2 -4
  84. package/front_end/ui/components/lists/List.ts +14 -1
  85. package/front_end/ui/components/panel_feedback/PreviewToggle.ts +1 -1
  86. package/front_end/ui/components/text_editor/AiCodeCompletionProvider.ts +23 -14
  87. package/front_end/ui/components/text_editor/AiCodeGenerationProvider.ts +3 -0
  88. package/front_end/ui/legacy/Toolbar.ts +0 -1
  89. package/front_end/ui/legacy/Treeoutline.ts +7 -2
  90. package/front_end/ui/legacy/UIUtils.ts +4 -1
  91. package/front_end/ui/legacy/components/inline_editor/Swatches.ts +19 -6
  92. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +20 -11
  93. package/front_end/ui/legacy/components/source_frame/XMLView.ts +27 -7
  94. package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
  95. package/package.json +1 -1
@@ -1,8 +1,6 @@
1
1
  // Copyright 2022 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-imperative-dom-api */
5
- /* eslint-disable @devtools/no-lit-render-outside-of-view */
6
4
 
7
5
  import '../../../ui/kit/kit.js';
8
6
  import '../../../ui/legacy/legacy.js';
@@ -26,8 +24,8 @@ import * as PreloadingHelper from './helper/helper.js';
26
24
  import preloadingViewStyles from './preloadingView.css.js';
27
25
  import preloadingViewDropDownStyles from './preloadingViewDropDown.css.js';
28
26
 
29
- const {createRef, ref} = Directives;
30
- const {widget} = UI.Widget;
27
+ const {live} = Directives;
28
+ const {widget, widgetRef} = UI.Widget;
31
29
 
32
30
  const UIStrings = {
33
31
  /**
@@ -102,6 +100,10 @@ const UIStrings = {
102
100
  * @description Link text to learn more about speculation rules.
103
101
  */
104
102
  learnMore: 'Learn more',
103
+ /**
104
+ * @description Tooltip text for the button to clear speculative loads.
105
+ */
106
+ clearSpeculativeLoads: 'Clear speculative loads',
105
107
  } as const;
106
108
  const str_ = i18n.i18n.registerUIStrings('panels/application/preloading/PreloadingView.ts', UIStrings);
107
109
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -189,69 +191,29 @@ class PreloadingUIUtils {
189
191
  }
190
192
 
191
193
  function pageURL(): Platform.DevToolsPath.UrlString {
192
- return SDK.TargetManager.TargetManager.instance().scopeTarget()?.inspectedURL() ||
193
- ('' as Platform.DevToolsPath.UrlString);
194
+ return SDK.TargetManager.TargetManager.instance().scopeTarget()?.inspectedURL() ??
195
+ Platform.DevToolsPath.EmptyUrlString;
194
196
  }
195
197
 
196
- export class PreloadingRuleSetView extends UI.Widget.VBox {
197
- private model: SDK.PreloadingModel.PreloadingModel;
198
- private focusedRuleSetId: Protocol.Preload.RuleSetId|null = null;
199
-
200
- private readonly warningsContainer: HTMLDivElement;
201
- private readonly warningsView = new PreloadingComponents.PreloadingDisabledInfobar.PreloadingDisabledInfobar();
202
- private readonly hsplit: HTMLElement;
203
- private readonly ruleSetGrid = new PreloadingComponents.RuleSetGrid.RuleSetGrid();
204
- private readonly ruleSetGridContainerRef = createRef<HTMLDivElement>();
205
- private readonly ruleSetDetailsRef:
206
- Directives.Ref<UI.Widget.WidgetElement<PreloadingComponents.RuleSetDetailsView.RuleSetDetailsView>>;
207
-
208
- private shouldPrettyPrint = Common.Settings.Settings.instance()
209
- .resolve(SettingsUI.SourcesSettings.autoPrettyPrintMinifiedSettingDescriptor)
210
- .get();
211
-
212
- constructor(model: SDK.PreloadingModel.PreloadingModel) {
213
- super({useShadowDom: true});
214
- this.registerRequiredCSS(emptyWidgetStyles, preloadingViewStyles);
198
+ interface RuleSetViewInput {
199
+ warnings?: Protocol.Preload.PreloadEnabledStateUpdatedEvent;
200
+ rows: PreloadingComponents.RuleSetGrid.RuleSetGridRow[];
201
+ pageURL: Platform.DevToolsPath.UrlString;
202
+ ruleSet: Protocol.Preload.RuleSet|null;
203
+ shouldPrettyPrint: boolean;
204
+ onRuleSetSelected: (id: Protocol.Preload.RuleSetId) => void;
205
+ onPrettyPrintToggle: () => void;
206
+ }
215
207
 
216
- this.model = model;
217
- SDK.TargetManager.TargetManager.instance().addScopeChangeListener(this.onScopeChange.bind(this));
218
- SDK.TargetManager.TargetManager.instance().addModelListener(
219
- SDK.PreloadingModel.PreloadingModel, SDK.PreloadingModel.Events.MODEL_UPDATED, this.render, this,
220
- {scoped: true});
221
- SDK.TargetManager.TargetManager.instance().addModelListener(
222
- SDK.PreloadingModel.PreloadingModel, SDK.PreloadingModel.Events.WARNINGS_UPDATED, e => {
223
- Object.assign(this.warningsView, e.data);
224
- }, this, {scoped: true});
225
-
226
- // this (VBox)
227
- // +- warningsContainer
228
- // +- PreloadingWarningsView
229
- // +- hsplit
230
- // +- leftContainer
231
- // +- RuleSetGrid
232
- // +- rightContainer
233
- // +- RuleSetDetailsView
234
- //
235
- // - If an row of RuleSetGrid selected, RuleSetDetailsView shows details of it.
236
- // - If not, RuleSetDetailsView hides.
237
-
238
- this.warningsContainer = document.createElement('div');
239
- this.warningsContainer.classList.add('flex-none');
240
- this.contentElement.insertBefore(this.warningsContainer, this.contentElement.firstChild);
241
- this.warningsView.show(this.warningsContainer);
242
-
243
- this.ruleSetGrid.addEventListener(
244
- PreloadingComponents.RuleSetGrid.Events.SELECT, this.onRuleSetsGridCellFocused, this);
245
- this.ruleSetDetailsRef =
246
- createRef<UI.Widget.WidgetElement<PreloadingComponents.RuleSetDetailsView.RuleSetDetailsView>>();
247
- const onPrettyPrintToggle = (): void => {
248
- this.shouldPrettyPrint = !this.shouldPrettyPrint;
249
- this.updateRuleSetDetails();
250
- };
208
+ type RuleSetView = (input: RuleSetViewInput, output: undefined, target: HTMLElement) => void;
251
209
 
252
- // clang-format off
253
- render(
254
- html`
210
+ const RULE_SET_DEFAULT_VIEW: RuleSetView = (input, _output, target) => {
211
+ // clang-format off
212
+ render(
213
+ html`
214
+ <style>${emptyWidgetStyles}</style>
215
+ <style>${preloadingViewStyles}</style>
216
+ ${widget(PreloadingComponents.PreloadingDisabledInfobar.PreloadingDisabledInfobar, input.warnings)}
255
217
  <div class="empty-state">
256
218
  <span class="empty-state-header">${i18nString(UIStrings.noRulesDetected)}</span>
257
219
  <div class="empty-state-description">
@@ -263,66 +225,89 @@ export class PreloadingRuleSetView extends UI.Widget.VBox {
263
225
  >${i18nString(UIStrings.learnMore)}</devtools-link>
264
226
  </div>
265
227
  </div>
266
- <devtools-split-view sidebar-position="second">
267
- <div slot="main" ${ref(this.ruleSetGridContainerRef)}>
228
+ <devtools-split-view sidebar-position="second" show=${input.ruleSet === null ? 'main' : 'both'}>
229
+ <div slot="main">
230
+ ${widget(PreloadingComponents.RuleSetGrid.RuleSetGrid, {
231
+ data: {rows: input.rows, pageURL: input.pageURL},
232
+ onSelect: input.onRuleSetSelected,
233
+ })}
268
234
  </div>
269
235
  <div slot="sidebar" jslog=${VisualLogging.section('rule-set-details')}>
270
- <devtools-widget ${widget(PreloadingComponents.RuleSetDetailsView.RuleSetDetailsView, {
271
- ruleSet: this.getRuleSet(),
272
- shouldPrettyPrint: this.shouldPrettyPrint,
273
- })} ${ref(this.ruleSetDetailsRef)}></devtools-widget>
236
+ ${widget(PreloadingComponents.RuleSetDetailsView.RuleSetDetailsView, {
237
+ ruleSet: input.ruleSet,
238
+ shouldPrettyPrint: input.shouldPrettyPrint,
239
+ })}
274
240
  </div>
275
241
  </devtools-split-view>
276
- <div class="pretty-print-button" style="border-top: 1px solid var(--sys-color-divider)">
277
- <devtools-button
278
- .iconName=${'brackets'}
279
- .toggledIconName=${'brackets'}
280
- .toggled=${this.shouldPrettyPrint}
281
- .toggleType=${Buttons.Button.ToggleType.PRIMARY}
282
- .title=${i18nString(UIStrings.prettyPrint)}
283
- .variant=${Buttons.Button.Variant.ICON_TOGGLE}
284
- .size=${Buttons.Button.Size.REGULAR}
285
- @click=${onPrettyPrintToggle}
286
- jslog=${VisualLogging.action().track({click: true}).context('preloading-status-panel-pretty-print')}></devtools-button>
287
- </div>`,
288
- this.contentElement, {host: this});
289
- // clang-format on
290
- this.hsplit = this.contentElement.querySelector('devtools-split-view') as HTMLElement;
242
+ <div class="pretty-print-button">
243
+ <devtools-button
244
+ .iconName=${'brackets'}
245
+ .toggledIconName=${'brackets'}
246
+ .toggled=${input.shouldPrettyPrint}
247
+ .toggleType=${Buttons.Button.ToggleType.PRIMARY}
248
+ .title=${i18nString(UIStrings.prettyPrint)}
249
+ .variant=${Buttons.Button.Variant.ICON_TOGGLE}
250
+ .size=${Buttons.Button.Size.REGULAR}
251
+ .jslogContext=${'preloading-status-panel-pretty-print'}
252
+ @click=${input.onPrettyPrintToggle}>
253
+ </devtools-button>
254
+ </div>
255
+ `,
256
+ target, {container: {classes: input.rows.length === 0 ? ['empty'] : []}});
257
+ // clang-format on
258
+ };
259
+
260
+ export class PreloadingRuleSetView extends UI.Widget.VBox {
261
+ private model: SDK.PreloadingModel.PreloadingModel;
262
+ private focusedRuleSetId: Protocol.Preload.RuleSetId|null = null;
263
+ readonly #view: RuleSetView;
264
+ #warnings?: Protocol.Preload.PreloadEnabledStateUpdatedEvent;
265
+
266
+ private shouldPrettyPrint = Common.Settings.Settings.instance()
267
+ .resolve(SettingsUI.SourcesSettings.autoPrettyPrintMinifiedSettingDescriptor)
268
+ .get();
269
+
270
+ readonly #onRuleSetSelected = (id: Protocol.Preload.RuleSetId): void => {
271
+ this.focusedRuleSetId = id;
272
+ this.requestUpdate();
273
+ };
274
+
275
+ readonly #onPrettyPrintToggle = (): void => {
276
+ this.shouldPrettyPrint = !this.shouldPrettyPrint;
277
+ this.requestUpdate();
278
+ };
279
+
280
+ constructor(model: SDK.PreloadingModel.PreloadingModel, view: RuleSetView = RULE_SET_DEFAULT_VIEW) {
281
+ super({useShadowDom: true});
282
+ this.#view = view;
283
+
284
+ this.model = model;
285
+ SDK.TargetManager.TargetManager.instance().addScopeChangeListener(this.onScopeChange.bind(this));
286
+ SDK.TargetManager.TargetManager.instance().addModelListener(SDK.PreloadingModel.PreloadingModel,
287
+ SDK.PreloadingModel.Events.MODEL_UPDATED,
288
+ this.requestUpdate, this, {scoped: true});
289
+ SDK.TargetManager.TargetManager.instance().addModelListener(SDK.PreloadingModel.PreloadingModel,
290
+ SDK.PreloadingModel.Events.WARNINGS_UPDATED, e => {
291
+ this.#warnings = e.data;
292
+ this.requestUpdate();
293
+ }, this, {scoped: true});
291
294
  }
292
295
 
293
296
  override wasShown(): void {
294
297
  super.wasShown();
295
-
296
- this.warningsView.wasShown();
297
-
298
- this.render();
298
+ this.requestUpdate();
299
299
  }
300
300
 
301
301
  onScopeChange(): void {
302
302
  const model = SDK.TargetManager.TargetManager.instance().scopeTarget()?.model(SDK.PreloadingModel.PreloadingModel);
303
303
  assertNotNullOrUndefined(model);
304
304
  this.model = model;
305
- this.render();
305
+ this.requestUpdate();
306
306
  }
307
307
 
308
308
  revealRuleSet(revealInfo: PreloadingHelper.PreloadingForward.RuleSetView): void {
309
309
  this.focusedRuleSetId = revealInfo.ruleSetId;
310
- this.render();
311
- }
312
-
313
- private updateRuleSetDetails(): void {
314
- const ruleSet = this.getRuleSet();
315
- const widget = this.ruleSetDetailsRef.value?.getWidget();
316
- if (widget) {
317
- widget.shouldPrettyPrint = this.shouldPrettyPrint;
318
- widget.ruleSet = ruleSet;
319
- }
320
-
321
- if (ruleSet === null) {
322
- this.hsplit.setAttribute('show', 'main');
323
- } else {
324
- this.hsplit.setAttribute('show', 'both');
325
- }
310
+ this.requestUpdate();
326
311
  }
327
312
 
328
313
  private getRuleSet(): Protocol.Preload.RuleSet|null {
@@ -330,37 +315,25 @@ export class PreloadingRuleSetView extends UI.Widget.VBox {
330
315
  return id === null ? null : this.model.getRuleSetById(id);
331
316
  }
332
317
 
333
- render(): void {
334
- // Update rule sets grid
318
+ override performUpdate(): void {
335
319
  const countsByRuleSetId = this.model.getPreloadCountsByRuleSetId();
336
- const ruleSetRows = this.model.getAllRuleSets().map(({id, value}) => {
320
+ const rows = this.model.getAllRuleSets().map(({id, value}) => {
337
321
  const countsByStatus = countsByRuleSetId.get(id) || new Map<SDK.PreloadingModel.PreloadingStatus, number>();
338
322
  return {
339
323
  ruleSet: value,
340
324
  preloadsStatusSummary: PreloadingUIUtils.preloadsStatusSummary(countsByStatus),
341
325
  };
342
326
  });
343
- this.ruleSetGrid.data = {rows: ruleSetRows, pageURL: pageURL()};
344
- this.contentElement.classList.toggle('empty', ruleSetRows.length === 0);
345
- this.updateRuleSetDetails();
346
-
347
- const container = this.ruleSetGridContainerRef.value;
348
- if (container && this.ruleSetGrid.element.parentElement !== container) {
349
- this.ruleSetGrid.show(container);
350
- }
351
- }
352
-
353
- private onRuleSetsGridCellFocused(event: Common.EventTarget.EventTargetEvent<Protocol.Preload.RuleSetId>): void {
354
- this.focusedRuleSetId = event.data;
355
- this.render();
356
- }
357
-
358
- getInfobarContainerForTest(): HTMLElement {
359
- return this.warningsView.contentElement;
360
- }
361
-
362
- getRuleSetGridForTest(): PreloadingComponents.RuleSetGrid.RuleSetGrid {
363
- return this.ruleSetGrid;
327
+ const input: RuleSetViewInput = {
328
+ warnings: this.#warnings,
329
+ rows,
330
+ pageURL: pageURL(),
331
+ ruleSet: this.getRuleSet(),
332
+ shouldPrettyPrint: this.shouldPrettyPrint,
333
+ onRuleSetSelected: this.#onRuleSetSelected,
334
+ onPrettyPrintToggle: this.#onPrettyPrintToggle,
335
+ };
336
+ this.#view(input, /* output= */ undefined, this.contentElement);
364
337
  }
365
338
  }
366
339
 
@@ -440,118 +413,175 @@ export function applyFilterText(filterText: string, rows: PreloadingComponents.P
440
413
  });
441
414
  }
442
415
 
416
+ interface PreloadingWarnings {
417
+ disabledByPreference: boolean;
418
+ disabledByDataSaver: boolean;
419
+ disabledByBatterySaver: boolean;
420
+ disabledByHoldbackPrefetchSpeculationRules: boolean;
421
+ disabledByHoldbackPrerenderSpeculationRules: boolean;
422
+ }
423
+
424
+ interface AttemptViewInput {
425
+ warnings?: PreloadingWarnings;
426
+ rows: PreloadingComponents.PreloadingGrid.PreloadingGridRow[];
427
+ isEmpty: boolean;
428
+ pageURL: Platform.DevToolsPath.UrlString;
429
+ details: PreloadingComponents.PreloadingDetailsReportView.PreloadingDetailsReportViewData;
430
+ ruleSetSelectorToolbarItem: UI.Toolbar.ToolbarItem;
431
+ filterText: string;
432
+ onFilterChanged: (filterText: string) => void;
433
+ onClear: () => void;
434
+ onAttemptSelected: ({rowId}: {rowId: string}) => void;
435
+ }
436
+
437
+ interface AttemptViewOutput {
438
+ doSplitWidgetLayout?: () => void;
439
+ }
440
+
441
+ type AttemptView = (input: AttemptViewInput, output: AttemptViewOutput, target: HTMLElement) => void;
442
+
443
+ const ATTEMPT_DEFAULT_VIEW: AttemptView = (input, output, target) => {
444
+ // clang-format off
445
+ render(
446
+ html`
447
+ <style>${emptyWidgetStyles}</style>
448
+ <style>${preloadingViewStyles}</style>
449
+ ${widget(PreloadingComponents.PreloadingDisabledInfobar.PreloadingDisabledInfobar, input.warnings)}
450
+ <devtools-toolbar class="preloading-toolbar" jslog=${VisualLogging.toolbar()}>
451
+ ${input.ruleSetSelectorToolbarItem.element}
452
+ <devtools-toolbar-input
453
+ type="filter"
454
+ .value=${live(input.filterText)}
455
+ @change=${(e: CustomEvent<string>) => input.onFilterChanged(e.detail)}>
456
+ </devtools-toolbar-input>
457
+ <div class="toolbar-divider"></div>
458
+ <devtools-button
459
+ title=${i18nString(UIStrings.clearSpeculativeLoads)}
460
+ aria-label=${i18nString(UIStrings.clearSpeculativeLoads)}
461
+ .iconName=${'clear'}
462
+ .variant=${Buttons.Button.Variant.TOOLBAR}
463
+ .jslogContext=${'clear-speculative-loads'}
464
+ @click=${input.onClear}>
465
+ </devtools-button>
466
+ </devtools-toolbar>
467
+ <div class="empty-state">
468
+ <span class="empty-state-header">${i18nString(UIStrings.noPrefetchAttempts)}</span>
469
+ <div class="empty-state-description">
470
+ <span>${i18nString(UIStrings.prefetchDescription)}</span>
471
+ <devtools-link
472
+ class="devtools-link"
473
+ href=${SPECULATION_EXPLANATION_URL}
474
+ jslogcontext="learn-more"
475
+ >${i18nString(UIStrings.learnMore)}</devtools-link>
476
+ </div>
477
+ </div>
478
+ <devtools-split-view sidebar-position="second" ${
479
+ widgetRef(
480
+ UI.SplitWidget.SplitWidget,
481
+ w => {
482
+ output.doSplitWidgetLayout = () => w.doLayout();
483
+ })}>
484
+ <div slot="main" class="overflow-auto">
485
+ ${widget(PreloadingComponents.PreloadingGrid.PreloadingGrid, {
486
+ rows: input.rows,
487
+ pageURL: input.pageURL,
488
+ onSelect: input.onAttemptSelected,
489
+ })}
490
+ </div>
491
+ <div slot="sidebar" class="overflow-auto">
492
+ ${widget(PreloadingComponents.PreloadingDetailsReportView.PreloadingDetailsReportView, {
493
+ data: input.details,
494
+ })}
495
+ </div>
496
+ </devtools-split-view>
497
+ `,
498
+ target, {container: {classes: input.isEmpty ? ['empty'] : []}});
499
+ // clang-format on
500
+ };
501
+
443
502
  export class PreloadingAttemptView extends UI.Widget.VBox {
444
503
  private model: SDK.PreloadingModel.PreloadingModel;
445
504
  // Note that we use id of (representative) preloading attempt while we show pipelines in grid.
446
505
  // This is because `NOT_TRIGGERED` preloading attempts don't have pipeline id and we can use it.
447
506
  private focusedPreloadingAttemptId: SDK.PreloadingModel.PreloadingAttemptId|null = null;
507
+ readonly #view: AttemptView;
508
+ readonly #viewOutput: AttemptViewOutput = {};
509
+ readonly #ruleSetSelector: PreloadingRuleSetSelector;
510
+ #warnings?: PreloadingWarnings;
511
+ #filterText = '';
512
+
513
+ readonly #onFilterChanged = (filterText: string): void => {
514
+ this.#filterText = filterText;
515
+ this.requestUpdate();
516
+ };
517
+
518
+ readonly #onClear = (): void => {
519
+ const model = SDK.TargetManager.TargetManager.instance().scopeTarget()?.model(SDK.PreloadingModel.PreloadingModel);
520
+ if (!model) {
521
+ return;
522
+ }
523
+
524
+ model.reset();
525
+ this.#filterText = '';
526
+ this.#ruleSetSelector.select(null);
527
+ this.requestUpdate();
528
+ };
529
+
530
+ readonly #onAttemptSelected = ({rowId}: {rowId: string}): void => {
531
+ this.focusedPreloadingAttemptId = rowId;
532
+ this.requestUpdate();
533
+ };
534
+
535
+ readonly #onWarningsUpdated =
536
+ (event: Common.EventTarget.EventTargetEvent<Protocol.Preload.PreloadEnabledStateUpdatedEvent>): void => {
537
+ this.#warnings = {
538
+ disabledByPreference: event.data.disabledByPreference,
539
+ disabledByDataSaver: event.data.disabledByDataSaver,
540
+ disabledByBatterySaver: event.data.disabledByBatterySaver,
541
+ disabledByHoldbackPrefetchSpeculationRules: event.data.disabledByHoldbackPrefetchSpeculationRules,
542
+ disabledByHoldbackPrerenderSpeculationRules: event.data.disabledByHoldbackPrerenderSpeculationRules,
543
+ };
544
+ this.requestUpdate();
545
+ };
448
546
 
449
- private readonly warningsContainer: HTMLDivElement;
450
- private readonly warningsView = new PreloadingComponents.PreloadingDisabledInfobar.PreloadingDisabledInfobar();
451
- private readonly preloadingGrid = new PreloadingComponents.PreloadingGrid.PreloadingGrid();
452
- private readonly preloadingGridContainer: HTMLDivElement;
453
- private readonly renderContainer: HTMLElement;
454
- private readonly ruleSetSelector: PreloadingRuleSetSelector;
455
- private readonly textFilterUI: UI.Toolbar.ToolbarFilter;
456
- private hsplit?: UI.SplitWidget.SplitWidget;
457
- private clearButton: UI.Toolbar.ToolbarButton;
458
-
459
- constructor(model: SDK.PreloadingModel.PreloadingModel) {
547
+ constructor(model: SDK.PreloadingModel.PreloadingModel, view: AttemptView = ATTEMPT_DEFAULT_VIEW) {
460
548
  super({
461
549
  jslog: `${VisualLogging.pane('preloading-speculations')}`,
462
550
  useShadowDom: true,
463
551
  });
464
- this.registerRequiredCSS(emptyWidgetStyles, preloadingViewStyles);
552
+ this.#view = view;
465
553
 
466
554
  this.model = model;
467
555
  SDK.TargetManager.TargetManager.instance().addScopeChangeListener(this.onScopeChange.bind(this));
468
- SDK.TargetManager.TargetManager.instance().addModelListener(
469
- SDK.PreloadingModel.PreloadingModel, SDK.PreloadingModel.Events.MODEL_UPDATED, this.render, this,
470
- {scoped: true});
471
- SDK.TargetManager.TargetManager.instance().addModelListener(
472
- SDK.PreloadingModel.PreloadingModel, SDK.PreloadingModel.Events.WARNINGS_UPDATED, e => {
473
- Object.assign(this.warningsView, e.data);
474
- }, this, {scoped: true});
475
-
476
- // this (VBox)
477
- // +- warningsContainer
478
- // +- PreloadingWarningsView
479
- // +- VBox
480
- // +- toolbar (filtering)
481
- // +- hsplit
482
- // +- leftContainer
483
- // +- PreloadingGrid
484
- // +- rightContainer
485
- // +- PreloadingDetailsReportView
486
- //
487
- // - If an row of PreloadingGrid selected, PreloadingDetailsReportView shows details of it.
488
- // - If not, PreloadingDetailsReportView shows some messages.
489
-
490
- this.warningsContainer = document.createElement('div');
491
- this.warningsContainer.classList.add('flex-none');
492
- this.contentElement.insertBefore(this.warningsContainer, this.contentElement.firstChild);
493
- this.warningsView.show(this.warningsContainer);
494
-
495
- const vbox = new UI.Widget.VBox();
496
-
497
- const toolbar = vbox.contentElement.createChild('devtools-toolbar', 'preloading-toolbar');
498
- toolbar.setAttribute('jslog', `${VisualLogging.toolbar()}`);
499
-
500
- // Rule set dropdown first
501
- this.ruleSetSelector = new PreloadingRuleSetSelector(() => this.render());
502
- toolbar.appendToolbarItem(this.ruleSetSelector.item());
503
-
504
- // Text filter second
505
- this.textFilterUI = new UI.Toolbar.ToolbarFilter(undefined, 1, 1);
506
- this.textFilterUI.addEventListener(UI.Toolbar.ToolbarInput.Event.TEXT_CHANGED, this.onTextFilterChanged, this);
507
- toolbar.appendToolbarItem(this.textFilterUI);
508
-
509
- // Separator between text filter and clear button
510
- toolbar.appendToolbarItem(new UI.Toolbar.ToolbarSeparator());
511
-
512
- // Clear button last (rightmost)
513
- this.clearButton =
514
- new UI.Toolbar.ToolbarButton('Clear speculative loads', 'clear', undefined, 'clear-speculative-loads');
515
- this.clearButton.addEventListener(UI.Toolbar.ToolbarButton.Events.CLICK, () => {
516
- const model =
517
- SDK.TargetManager.TargetManager.instance().scopeTarget()?.model(SDK.PreloadingModel.PreloadingModel);
518
- if (!model) {
519
- return;
520
- }
521
-
522
- model.reset();
523
- // Reset UI state
524
- this.textFilterUI.setValue('');
525
- this.ruleSetSelector.select(null);
526
- this.render();
527
- });
528
- toolbar.appendToolbarItem(this.clearButton);
529
-
530
- this.preloadingGrid.onSelect = this.onPreloadingGridCellFocused.bind(this);
531
556
 
532
- this.preloadingGridContainer = document.createElement('div');
533
- this.preloadingGridContainer.className = 'preloading-grid-widget-container';
534
- this.preloadingGridContainer.style.height = '100%';
535
- this.preloadingGrid.show(this.preloadingGridContainer, null, true);
536
-
537
- this.renderContainer = vbox.contentElement;
538
-
539
- vbox.show(this.contentElement);
557
+ this.#ruleSetSelector = new PreloadingRuleSetSelector(() => this.requestUpdate());
540
558
  }
541
559
 
542
560
  override wasShown(): void {
543
561
  super.wasShown();
562
+ SDK.TargetManager.TargetManager.instance().addModelListener(SDK.PreloadingModel.PreloadingModel,
563
+ SDK.PreloadingModel.Events.MODEL_UPDATED,
564
+ this.requestUpdate, this, {scoped: true});
565
+ SDK.TargetManager.TargetManager.instance().addModelListener(SDK.PreloadingModel.PreloadingModel,
566
+ SDK.PreloadingModel.Events.WARNINGS_UPDATED,
567
+ this.#onWarningsUpdated, this, {scoped: true});
568
+ this.requestUpdate();
569
+ }
544
570
 
545
- this.warningsView.wasShown();
546
-
547
- this.render();
571
+ override willHide(): void {
572
+ super.willHide();
573
+ SDK.TargetManager.TargetManager.instance().removeModelListener(
574
+ SDK.PreloadingModel.PreloadingModel, SDK.PreloadingModel.Events.MODEL_UPDATED, this.requestUpdate, this);
575
+ SDK.TargetManager.TargetManager.instance().removeModelListener(SDK.PreloadingModel.PreloadingModel,
576
+ SDK.PreloadingModel.Events.WARNINGS_UPDATED,
577
+ this.#onWarningsUpdated, this);
548
578
  }
549
579
 
550
580
  onScopeChange(): void {
551
581
  const model = SDK.TargetManager.TargetManager.instance().scopeTarget()?.model(SDK.PreloadingModel.PreloadingModel);
552
582
  assertNotNullOrUndefined(model);
553
583
  this.model = model;
554
- this.render();
584
+ this.requestUpdate();
555
585
  }
556
586
 
557
587
  setFilter(filter: PreloadingHelper.PreloadingForward.AttemptViewWithFilter): void {
@@ -560,15 +590,11 @@ export class PreloadingAttemptView extends UI.Widget.VBox {
560
590
  id = null;
561
591
  }
562
592
 
563
- this.ruleSetSelector.select(id);
593
+ this.#ruleSetSelector.select(id);
564
594
 
565
595
  // Reset text filter when navigating from Rules view to clear any existing filter
566
- this.textFilterUI.setValue('');
567
- this.render();
568
- }
569
-
570
- private onTextFilterChanged(): void {
571
- this.render();
596
+ this.#filterText = '';
597
+ this.requestUpdate();
572
598
  }
573
599
 
574
600
  private getPreloadingDetailsData(): PreloadingComponents.PreloadingDetailsReportView.PreloadingDetailsReportViewData {
@@ -587,9 +613,9 @@ export class PreloadingAttemptView extends UI.Widget.VBox {
587
613
  };
588
614
  }
589
615
 
590
- render(): void {
616
+ override performUpdate(): void {
591
617
  // Update preloading grid
592
- const filteringRuleSetId = this.ruleSetSelector.getSelected();
618
+ const filteringRuleSetId = this.#ruleSetSelector.getSelected();
593
619
  const rows = this.model.getRepresentativePreloadingAttempts(filteringRuleSetId).map(({id, value}) => {
594
620
  const attempt = value;
595
621
  const pipeline = this.model.getPipeline(attempt);
@@ -609,139 +635,103 @@ export class PreloadingAttemptView extends UI.Widget.VBox {
609
635
  };
610
636
  });
611
637
 
612
- // Apply text filter
613
- const filteredRows = applyFilterText(this.textFilterUI.valueWithoutSuggestion(), rows);
614
-
615
- this.preloadingGrid.rows = filteredRows;
616
- this.preloadingGrid.pageURL = pageURL();
617
638
  // Only show empty state when there are truly no speculations (not when filter has no matches)
618
- const wasEmpty = this.contentElement.classList.contains('empty');
619
639
  const isEmpty = rows.length === 0;
620
- this.contentElement.classList.toggle('empty', isEmpty);
640
+
641
+ const input: AttemptViewInput = {
642
+ warnings: this.#warnings,
643
+ rows: applyFilterText(this.#filterText, rows),
644
+ isEmpty,
645
+ pageURL: pageURL(),
646
+ details: this.getPreloadingDetailsData(),
647
+ ruleSetSelectorToolbarItem: this.#ruleSetSelector.item(),
648
+ filterText: this.#filterText,
649
+ onFilterChanged: this.#onFilterChanged,
650
+ onClear: this.#onClear,
651
+ onAttemptSelected: this.#onAttemptSelected,
652
+ };
653
+ this.#view(input, this.#viewOutput, this.contentElement);
621
654
 
622
655
  // When this view starts empty, the split view gets laid out while hidden and collapses
623
656
  // its sidebar to 0. Re-layout once it becomes visible.
624
- if (wasEmpty && !isEmpty) {
625
- this.hsplit?.doLayout();
657
+ if (!isEmpty) {
658
+ this.#viewOutput.doSplitWidgetLayout?.();
626
659
  }
627
-
628
- // clang-format off
629
- render(html`
630
- <div class="empty-state">
631
- <span class="empty-state-header">${i18nString(UIStrings.noPrefetchAttempts)}</span>
632
- <div class="empty-state-description">
633
- <span>${i18nString(UIStrings.prefetchDescription)}</span>
634
- <devtools-link
635
- class="devtools-link"
636
- href=${SPECULATION_EXPLANATION_URL}
637
- jslogcontext="learn-more"
638
- >${i18nString(UIStrings.learnMore)}</devtools-link>
639
- </div>
640
- </div>
641
- <devtools-split-view sidebar-position="second" ${
642
- UI.Widget.widgetRef(
643
- UI.SplitWidget.SplitWidget,
644
- w => {
645
- this.hsplit = w;
646
- })}>
647
- <div slot="main" class="overflow-auto" style="height: 100%">
648
- ${this.preloadingGridContainer}
649
- </div>
650
- <div slot="sidebar" class="overflow-auto" style="height: 100%">
651
- ${widget(PreloadingComponents.PreloadingDetailsReportView.PreloadingDetailsReportView, {
652
- data: this.getPreloadingDetailsData(),
653
- })}
654
- </div>
655
- </devtools-split-view>
656
- `, this.renderContainer, {host: this});
657
- // clang-format on
658
- }
659
-
660
- private onPreloadingGridCellFocused({rowId}: {rowId: string}): void {
661
- this.focusedPreloadingAttemptId = rowId;
662
- this.render();
663
660
  }
664
661
 
665
662
  getRuleSetSelectorToolbarItemForTest(): UI.Toolbar.ToolbarItem {
666
- return this.ruleSetSelector.item();
667
- }
668
-
669
- getPreloadingGridForTest(): PreloadingComponents.PreloadingGrid.PreloadingGrid {
670
- return this.preloadingGrid;
671
- }
672
-
673
- getPreloadingDetailsForTest(): PreloadingComponents.PreloadingDetailsReportView.PreloadingDetailsReportView {
674
- const widgetElement = this.renderContainer.querySelector('div[slot="sidebar"] devtools-widget') as
675
- UI.Widget.WidgetElement<PreloadingComponents.PreloadingDetailsReportView.PreloadingDetailsReportView>|
676
- null;
677
- const widget = widgetElement?.getWidget();
678
- assertNotNullOrUndefined(widget);
679
- return widget;
663
+ return this.#ruleSetSelector.item();
680
664
  }
681
665
 
682
666
  selectRuleSetOnFilterForTest(id: Protocol.Preload.RuleSetId|null): void {
683
- this.ruleSetSelector.select(id);
667
+ this.#ruleSetSelector.select(id);
684
668
  }
685
669
  }
686
670
 
671
+ interface SummaryViewInput {
672
+ warnings?: Protocol.Preload.PreloadEnabledStateUpdatedEvent;
673
+ usedPreloading: PreloadingComponents.UsedPreloadingView.UsedPreloadingViewData;
674
+ }
675
+
676
+ type SummaryView = (input: SummaryViewInput, output: undefined, target: HTMLElement) => void;
677
+
678
+ const SUMMARY_DEFAULT_VIEW: SummaryView = (input, _output, target) => {
679
+ // clang-format off
680
+ render(
681
+ html`
682
+ ${widget(PreloadingComponents.PreloadingDisabledInfobar.PreloadingDisabledInfobar, input.warnings)}
683
+ ${widget(PreloadingComponents.UsedPreloadingView.UsedPreloadingView, {data: input.usedPreloading})}
684
+ `,
685
+ target);
686
+ // clang-format on
687
+ };
688
+
687
689
  export class PreloadingSummaryView extends UI.Widget.VBox {
688
690
  private model: SDK.PreloadingModel.PreloadingModel;
691
+ readonly #view: SummaryView;
692
+ #warnings?: Protocol.Preload.PreloadEnabledStateUpdatedEvent;
689
693
 
690
- private readonly warningsContainer: HTMLDivElement;
691
- private readonly warningsView = new PreloadingComponents.PreloadingDisabledInfobar.PreloadingDisabledInfobar();
692
- private readonly usedPreloading = new PreloadingComponents.UsedPreloadingView.UsedPreloadingView();
693
-
694
- constructor(model: SDK.PreloadingModel.PreloadingModel) {
694
+ constructor(model: SDK.PreloadingModel.PreloadingModel, view: SummaryView = SUMMARY_DEFAULT_VIEW) {
695
695
  super({
696
696
  jslog: `${VisualLogging.pane('speculative-loads')}`,
697
697
  useShadowDom: true,
698
698
  });
699
- this.registerRequiredCSS(emptyWidgetStyles, preloadingViewStyles);
699
+ this.#view = view;
700
700
 
701
701
  this.model = model;
702
702
  SDK.TargetManager.TargetManager.instance().addScopeChangeListener(this.onScopeChange.bind(this));
703
- SDK.TargetManager.TargetManager.instance().addModelListener(
704
- SDK.PreloadingModel.PreloadingModel, SDK.PreloadingModel.Events.MODEL_UPDATED, this.render, this,
705
- {scoped: true});
706
- SDK.TargetManager.TargetManager.instance().addModelListener(
707
- SDK.PreloadingModel.PreloadingModel, SDK.PreloadingModel.Events.WARNINGS_UPDATED, e => {
708
- Object.assign(this.warningsView, e.data);
709
- }, this, {scoped: true});
710
-
711
- this.warningsContainer = document.createElement('div');
712
- this.warningsContainer.classList.add('flex-none');
713
- this.contentElement.insertBefore(this.warningsContainer, this.contentElement.firstChild);
714
- this.warningsView.show(this.warningsContainer);
715
-
716
- this.usedPreloading.show(this.contentElement);
703
+ SDK.TargetManager.TargetManager.instance().addModelListener(SDK.PreloadingModel.PreloadingModel,
704
+ SDK.PreloadingModel.Events.MODEL_UPDATED,
705
+ this.requestUpdate, this, {scoped: true});
706
+ SDK.TargetManager.TargetManager.instance().addModelListener(SDK.PreloadingModel.PreloadingModel,
707
+ SDK.PreloadingModel.Events.WARNINGS_UPDATED, e => {
708
+ this.#warnings = e.data;
709
+ this.requestUpdate();
710
+ }, this, {scoped: true});
717
711
  }
718
712
 
719
713
  override wasShown(): void {
720
714
  super.wasShown();
721
-
722
- this.warningsView.wasShown();
723
-
724
- this.render();
715
+ this.requestUpdate();
725
716
  }
726
717
 
727
718
  onScopeChange(): void {
728
719
  const model = SDK.TargetManager.TargetManager.instance().scopeTarget()?.model(SDK.PreloadingModel.PreloadingModel);
729
720
  assertNotNullOrUndefined(model);
730
721
  this.model = model;
731
- this.render();
722
+ this.requestUpdate();
732
723
  }
733
724
 
734
- render(): void {
735
- this.usedPreloading.data = {
736
- pageURL: SDK.TargetManager.TargetManager.instance().scopeTarget()?.inspectedURL() ||
737
- ('' as Platform.DevToolsPath.UrlString),
738
- previousAttempts: this.model.getRepresentativePreloadingAttemptsOfPreviousPage().map(({value}) => value),
739
- currentAttempts: this.model.getRepresentativePreloadingAttempts(null).map(({value}) => value),
725
+ override performUpdate(): void {
726
+ const input: SummaryViewInput = {
727
+ warnings: this.#warnings,
728
+ usedPreloading: {
729
+ pageURL: pageURL(),
730
+ previousAttempts: this.model.getRepresentativePreloadingAttemptsOfPreviousPage().map(({value}) => value),
731
+ currentAttempts: this.model.getRepresentativePreloadingAttempts(null).map(({value}) => value),
732
+ },
740
733
  };
741
- }
742
-
743
- getUsedPreloadingForTest(): PreloadingComponents.UsedPreloadingView.UsedPreloadingView {
744
- return this.usedPreloading;
734
+ this.#view(input, /* output= */ undefined, this.contentElement);
745
735
  }
746
736
  }
747
737
 
@@ -760,9 +750,9 @@ class PreloadingRuleSetSelector implements
760
750
  assertNotNullOrUndefined(model);
761
751
  this.model = model;
762
752
  SDK.TargetManager.TargetManager.instance().addScopeChangeListener(this.onScopeChange.bind(this));
763
- SDK.TargetManager.TargetManager.instance().addModelListener(
764
- SDK.PreloadingModel.PreloadingModel, SDK.PreloadingModel.Events.MODEL_UPDATED, this.onModelUpdated, this,
765
- {scoped: true});
753
+ SDK.TargetManager.TargetManager.instance().addModelListener(SDK.PreloadingModel.PreloadingModel,
754
+ SDK.PreloadingModel.Events.MODEL_UPDATED,
755
+ this.onModelUpdated, this, {scoped: true});
766
756
 
767
757
  this.listModel = new UI.ListModel.ListModel();
768
758
 
@@ -773,8 +763,8 @@ class PreloadingRuleSetSelector implements
773
763
  this.toolbarItem = new UI.Toolbar.ToolbarItem(this.dropDown.element);
774
764
  this.toolbarItem.setTitle(i18nString(UIStrings.filterFilterByRuleSet));
775
765
  this.toolbarItem.element.classList.add('toolbar-has-dropdown');
776
- this.toolbarItem.element.setAttribute(
777
- 'jslog', `${VisualLogging.action('filter-by-rule-set').track({click: true})}`);
766
+ this.toolbarItem.element.setAttribute('jslog',
767
+ `${VisualLogging.action('filter-by-rule-set').track({click: true})}`);
778
768
 
779
769
  // Initializes `listModel` and `dropDown` using data of the model.
780
770
  this.onModelUpdated();
@@ -864,13 +854,8 @@ class PreloadingRuleSetSelector implements
864
854
 
865
855
  // Method for UI.SoftDropDown.Delegate<Protocol.Preload.RuleSetId|typeof AllRuleSetRootId>
866
856
  createElementForItem(id: Protocol.Preload.RuleSetId|typeof AllRuleSetRootId): Element {
867
- const element = document.createElement('div');
868
- const shadowRoot = UI.UIUtils.createShadowRootWithCoreStyles(element, {cssFile: preloadingViewDropDownStyles});
869
- const title = shadowRoot.createChild('div', 'title');
870
- UI.UIUtils.createTextChild(title, Platform.StringUtilities.trimEndWithMaxLength(this.titleFor(id), 100));
871
- const subTitle = shadowRoot.createChild('div', 'subtitle');
872
- UI.UIUtils.createTextChild(subTitle, this.subtitleFor(id));
873
- return element;
857
+ const selectorElement = new PreloadingRuleSetSelectorElement(this.titleFor(id), this.subtitleFor(id));
858
+ return selectorElement.contentElement;
874
859
  }
875
860
 
876
861
  // Method for UI.SoftDropDown.Delegate<Protocol.Preload.RuleSetId|typeof AllRuleSetRootId>
@@ -884,9 +869,48 @@ class PreloadingRuleSetSelector implements
884
869
  }
885
870
 
886
871
  // Method for UI.SoftDropDown.Delegate<Protocol.Preload.RuleSetId|typeof AllRuleSetRootId>
887
- highlightedItemChanged(
888
- _from: Protocol.Preload.RuleSetId|typeof AllRuleSetRootId,
889
- _to: Protocol.Preload.RuleSetId|typeof AllRuleSetRootId, _fromElement: Element|null,
890
- _toElement: Element|null): void {
872
+ highlightedItemChanged(_from: Protocol.Preload.RuleSetId|typeof AllRuleSetRootId,
873
+ _to: Protocol.Preload.RuleSetId|typeof AllRuleSetRootId, _fromElement: Element|null,
874
+ _toElement: Element|null): void {
875
+ }
876
+ }
877
+
878
+ interface RuleSetSelectorElementViewInput {
879
+ title: string;
880
+ subtitle: string;
881
+ }
882
+
883
+ type RuleSetSelectorElementView = (input: RuleSetSelectorElementViewInput, output: undefined, target: HTMLElement) =>
884
+ void;
885
+
886
+ const RULE_SET_SELECTOR_ELEMENT_DEFAULT_VIEW: RuleSetSelectorElementView = (input, _output, target) => {
887
+ // clang-format off
888
+ render(
889
+ html`
890
+ <style>${preloadingViewDropDownStyles}</style>
891
+ <div class="title">${Platform.StringUtilities.trimEndWithMaxLength(input.title, 100)}</div>
892
+ <div class="subtitle">${input.subtitle}</div>
893
+ `,
894
+ target);
895
+ // clang-format on
896
+ };
897
+
898
+ class PreloadingRuleSetSelectorElement extends UI.Widget.Widget {
899
+ readonly #title: string;
900
+ readonly #subtitle: string;
901
+ readonly #view: RuleSetSelectorElementView;
902
+
903
+ constructor(title: string, subtitle: string,
904
+ view: RuleSetSelectorElementView = RULE_SET_SELECTOR_ELEMENT_DEFAULT_VIEW) {
905
+ super({useShadowDom: true});
906
+ this.markAsExternallyManaged();
907
+ this.#title = title;
908
+ this.#subtitle = subtitle;
909
+ this.#view = view;
910
+ this.requestUpdate();
911
+ }
912
+
913
+ override performUpdate(): void {
914
+ this.#view({title: this.#title, subtitle: this.#subtitle}, /* output= */ undefined, this.contentElement);
891
915
  }
892
916
  }