chrome-devtools-frontend 1.0.1593959 → 1.0.1595090

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 (73) hide show
  1. package/docs/contributing/settings-experiments-features.md +25 -5
  2. package/front_end/core/common/README.md +126 -0
  3. package/front_end/core/sdk/CSSModel.ts +22 -0
  4. package/front_end/core/sdk/CSSNavigation.ts +33 -0
  5. package/front_end/core/sdk/CSSRule.ts +12 -2
  6. package/front_end/core/sdk/sdk-meta.ts +22 -18
  7. package/front_end/core/sdk/sdk.ts +2 -0
  8. package/front_end/design_system_tokens.css +538 -259
  9. package/front_end/entrypoints/main/main-meta.ts +2 -2
  10. package/front_end/generated/SupportedCSSProperties.js +12 -0
  11. package/front_end/models/issues_manager/Issue.ts +1 -0
  12. package/front_end/models/issues_manager/IssueAggregator.ts +9 -0
  13. package/front_end/models/issues_manager/IssuesManager.ts +5 -0
  14. package/front_end/models/issues_manager/SelectivePermissionsInterventionIssue.ts +65 -0
  15. package/front_end/models/issues_manager/descriptions/selectivePermissionsIntervention.md +7 -0
  16. package/front_end/models/issues_manager/issues_manager.ts +2 -0
  17. package/front_end/models/javascript_metadata/NativeFunctions.js +44 -9
  18. package/front_end/models/persistence/persistence-meta.ts +4 -4
  19. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +12 -1
  20. package/front_end/panels/ai_assistance/components/ChatInput.ts +37 -30
  21. package/front_end/panels/ai_assistance/components/ChatView.ts +4 -2
  22. package/front_end/panels/ai_assistance/components/chatInput.css +26 -1
  23. package/front_end/panels/application/StorageView.ts +8 -2
  24. package/front_end/panels/application/preloading/PreloadingView.ts +105 -7
  25. package/front_end/panels/application/preloading/preloadingView.css +4 -0
  26. package/front_end/panels/console/ConsoleView.ts +2 -2
  27. package/front_end/panels/console/console-meta.ts +18 -14
  28. package/front_end/panels/elements/ComputedStyleWidget.ts +12 -7
  29. package/front_end/panels/elements/ElementsPanel.ts +1 -1
  30. package/front_end/panels/elements/ElementsTreeOutline.ts +4 -6
  31. package/front_end/panels/elements/ImagePreviewPopover.ts +6 -9
  32. package/front_end/panels/elements/StylePropertiesSection.ts +30 -0
  33. package/front_end/panels/elements/StylesSidebarPane.ts +13 -7
  34. package/front_end/panels/elements/elements-meta.ts +12 -8
  35. package/front_end/panels/issues/AffectedSelectivePermissionsInterventionView.ts +120 -0
  36. package/front_end/panels/issues/IssueView.ts +2 -0
  37. package/front_end/panels/issues/IssuesPane.ts +6 -0
  38. package/front_end/panels/issues/issues.ts +2 -0
  39. package/front_end/panels/lighthouse/LighthouseStartView.ts +99 -38
  40. package/front_end/panels/lighthouse/LighthouseTimespanView.ts +53 -14
  41. package/front_end/panels/lighthouse/RadioSetting.ts +33 -19
  42. package/front_end/panels/lighthouse/lighthouse.ts +2 -0
  43. package/front_end/panels/media/PlayerMessagesView.ts +62 -49
  44. package/front_end/panels/media/media.ts +2 -0
  45. package/front_end/panels/media/playerMessagesView.css +1 -1
  46. package/front_end/panels/profiler/HeapSnapshotGridNodes.ts +49 -24
  47. package/front_end/panels/settings/FrameworkIgnoreListSettingsTab.ts +2 -2
  48. package/front_end/panels/sources/sources-meta.ts +8 -4
  49. package/front_end/panels/utils/utils.ts +11 -5
  50. package/front_end/third_party/chromium/README.chromium +1 -1
  51. package/front_end/third_party/lit/lib/async-directive.d.ts +465 -0
  52. package/front_end/third_party/lit/lib/async-directive.js +23 -0
  53. package/front_end/third_party/lit/lib/async-directive.js.map +1 -0
  54. package/front_end/third_party/lit/lib/decorators.d.ts +7 -1
  55. package/front_end/third_party/lit/lib/decorators.js +2 -2
  56. package/front_end/third_party/lit/lib/decorators.js.map +1 -1
  57. package/front_end/third_party/lit/lib/directive.js.map +1 -1
  58. package/front_end/third_party/lit/lib/directives.d.ts +27 -8
  59. package/front_end/third_party/lit/lib/directives.js +8 -8
  60. package/front_end/third_party/lit/lib/directives.js.map +1 -1
  61. package/front_end/third_party/lit/lib/lit.d.ts +15 -5
  62. package/front_end/third_party/lit/lib/lit.js +4 -4
  63. package/front_end/third_party/lit/lib/lit.js.map +1 -1
  64. package/front_end/third_party/lit/lib/static-html.js +2 -2
  65. package/front_end/third_party/lit/lib/static-html.js.map +1 -1
  66. package/front_end/third_party/lit/lit.ts +2 -1
  67. package/front_end/third_party/lit/rollup.config.mjs +1 -1
  68. package/front_end/ui/components/markdown_view/MarkdownLinksMap.ts +5 -0
  69. package/front_end/ui/legacy/components/utils/ImagePreview.ts +27 -23
  70. package/front_end/ui/lit/lit.ts +1 -0
  71. package/front_end/ui/visual_logging/Debugging.ts +1 -1
  72. package/front_end/ui/visual_logging/KnownContextValues.ts +4 -0
  73. package/package.json +1 -1
@@ -0,0 +1,120 @@
1
+ // Copyright 2026 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import * as i18n from '../../core/i18n/i18n.js';
6
+ import type * as Platform from '../../core/platform/platform.js';
7
+ import type * as SDK from '../../core/sdk/sdk.js';
8
+ import type * as Protocol from '../../generated/protocol.js';
9
+ import * as Bindings from '../../models/bindings/bindings.js';
10
+ import type * as IssuesManager from '../../models/issues_manager/issues_manager.js';
11
+ import * as Components from '../../ui/legacy/components/utils/utils.js';
12
+ import * as UI from '../../ui/legacy/legacy.js';
13
+ import {Directives, html, type LitTemplate, nothing, render} from '../../ui/lit/lit.js';
14
+
15
+ import {AffectedResourcesView} from './AffectedResourcesView.js';
16
+
17
+ const UIStrings = {
18
+ /**
19
+ * @description Label for number of affected resources indication in issue view
20
+ */
21
+ nViolations: '{n, plural, =1 {# violation} other {# violations}}',
22
+ /**
23
+ * @description Title for the API column in the Selective Permissions Intervention affected resources list
24
+ */
25
+ api: 'API',
26
+ /**
27
+ * @description Title for the Script column in the Selective Permissions Intervention affected resources list
28
+ */
29
+ script: 'Script',
30
+ /**
31
+ * @description Title for the Ad Ancestry column in the Selective Permissions Intervention affected resources list
32
+ */
33
+ adAncestry: 'Ad Ancestry',
34
+ /**
35
+ * @description Text for unknown value
36
+ */
37
+ unknown: 'unknown',
38
+ /**
39
+ * @description Text for loading state
40
+ */
41
+ loading: 'loading…',
42
+ } as const;
43
+ const str_ = i18n.i18n.registerUIStrings('panels/issues/AffectedSelectivePermissionsInterventionView.ts', UIStrings);
44
+ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
45
+
46
+ export class AffectedSelectivePermissionsInterventionView extends AffectedResourcesView {
47
+ readonly #linkifier = new Components.Linkifier.Linkifier();
48
+
49
+ protected getResourceNameWithCount(count: number): string {
50
+ return i18nString(UIStrings.nViolations, {n: count});
51
+ }
52
+
53
+ #render(): void {
54
+ const issues = Array.from(this.issue.getSelectivePermissionsInterventionIssues());
55
+ // eslint-disable-next-line @devtools/no-lit-render-outside-of-view
56
+ render(
57
+ html`
58
+ <tr>
59
+ <td class="affected-resource-header">${i18nString(UIStrings.api)}</td>
60
+ <td class="affected-resource-header">${i18nString(UIStrings.script)}</td>
61
+ <td class="affected-resource-header">${i18nString(UIStrings.adAncestry)}</td>
62
+ </tr>
63
+ ${issues.map(issue => this.#renderDetail(issue))}
64
+ `,
65
+ this.affectedResources, {host: this});
66
+ this.updateAffectedResourceCount(issues.length);
67
+ }
68
+
69
+ #renderDetail(issue: IssuesManager.SelectivePermissionsInterventionIssue.SelectivePermissionsInterventionIssue):
70
+ LitTemplate {
71
+ const details = issue.details();
72
+ const issuesModel = issue.model();
73
+
74
+ const stackTracePromise = (details.stackTrace && issuesModel) ?
75
+ this.#resolveStackTrace(details.stackTrace, issuesModel) :
76
+ Promise.resolve(html`<span>${i18nString(UIStrings.unknown)}</span>`);
77
+
78
+ const target = issuesModel ? issuesModel.target() : null;
79
+
80
+ return html`
81
+ <tr class="affected-resource-directive">
82
+ <td>${details.apiName}</td>
83
+ <td>${Directives.until(stackTracePromise, html`<span>${i18nString(UIStrings.loading)}</span>`)}</td>
84
+ <td class="affected-resource-cell">
85
+ <div class="ad-ancestry-list">
86
+ ${(details.adAncestry?.adAncestryChain || []).map(script => {
87
+ const link = this.#linkifier.linkifyScriptLocation(
88
+ target, script.scriptId, script.name as Platform.DevToolsPath.UrlString, 0);
89
+ return html`<div>${link}</div>`;
90
+ })}
91
+ ${
92
+ details.adAncestry?.rootScriptFilterlistRule ?
93
+ html`<div>Rule: ${details.adAncestry.rootScriptFilterlistRule}</div>` :
94
+ nothing}
95
+ </div>
96
+ </td>
97
+ </tr>
98
+ `;
99
+ }
100
+
101
+ async #resolveStackTrace(stackTrace: Protocol.Runtime.StackTrace, issuesModel: SDK.IssuesModel.IssuesModel):
102
+ Promise<LitTemplate> {
103
+ const debuggerWorkspaceBinding = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance();
104
+ const stackTraceTranslated =
105
+ await debuggerWorkspaceBinding.createStackTraceFromProtocolRuntime(stackTrace, issuesModel.target());
106
+ return html`
107
+ <devtools-widget .widgetConfig=${UI.Widget.widgetConfig(Components.JSPresentationUtils.StackTracePreviewContent, {
108
+ stackTrace: stackTraceTranslated,
109
+ options: {expandable: true},
110
+ })}>
111
+ </devtools-widget>
112
+ `;
113
+ }
114
+
115
+ update(): void {
116
+ this.requestResolver.clear();
117
+ this.#linkifier.reset();
118
+ this.#render();
119
+ }
120
+ }
@@ -29,6 +29,7 @@ import {AffectedMetadataAllowedSitesView} from './AffectedMetadataAllowedSitesVi
29
29
  import {AffectedPartitioningBlobURLView} from './AffectedPartitioningBlobURLView.js';
30
30
  import {AffectedPermissionElementsView} from './AffectedPermissionElementsView.js';
31
31
  import {AffectedItem, AffectedResourcesView, extractShortPath} from './AffectedResourcesView.js';
32
+ import {AffectedSelectivePermissionsInterventionView} from './AffectedSelectivePermissionsInterventionView.js';
32
33
  import {AffectedSharedArrayBufferIssueDetailsView} from './AffectedSharedArrayBufferIssueDetailsView.js';
33
34
  import {AffectedSourcesView} from './AffectedSourcesView.js';
34
35
  import {AffectedTrackingSitesView} from './AffectedTrackingSitesView.js';
@@ -260,6 +261,7 @@ export class IssueView extends UI.TreeOutline.TreeElement {
260
261
  new AffectedDescendantsWithinSelectElementView(this, this.#issue, 'disallowed-select-descendants-details'),
261
262
  new AffectedPartitioningBlobURLView(this, this.#issue, 'partitioning-blob-url-details'),
262
263
  new AffectedPermissionElementsView(this, this.#issue, 'permission-element-elements'),
264
+ new AffectedSelectivePermissionsInterventionView(this, this.#issue, 'selective-permissions-intervention-details'),
263
265
  ];
264
266
  this.#hiddenIssuesMenu = new Components.HideIssuesMenu.HideIssuesMenu();
265
267
  this.#aggregatedIssuesCount = null;
@@ -110,6 +110,10 @@ const UIStrings = {
110
110
  * @description Category title for a group of permission element issues
111
111
  */
112
112
  permissionElement: 'PEPC Element',
113
+ /**
114
+ * @description Category title for the different 'Selective Permissions Intervention' issues.
115
+ */
116
+ selectivePermissionsIntervention: 'Selective Permissions Intervention',
113
117
  } as const;
114
118
  const str_ = i18n.i18n.registerUIStrings('panels/issues/IssuesPane.ts', UIStrings);
115
119
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -153,6 +157,8 @@ class IssueCategoryView extends UI.TreeOutline.TreeElement {
153
157
  return i18nString(UIStrings.generic);
154
158
  case IssuesManager.Issue.IssueCategory.PERMISSION_ELEMENT:
155
159
  return i18nString(UIStrings.permissionElement);
160
+ case IssuesManager.Issue.IssueCategory.SELECTIVE_PERMISSIONS_INTERVENTION:
161
+ return i18nString(UIStrings.selectivePermissionsIntervention);
156
162
  case IssuesManager.Issue.IssueCategory.OTHER:
157
163
  return i18nString(UIStrings.other);
158
164
  }
@@ -2,11 +2,13 @@
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
4
 
5
+ import * as AffectedSelectivePermissionsInterventionView from './AffectedSelectivePermissionsInterventionView.js';
5
6
  import * as IssueRevealer from './IssueRevealer.js';
6
7
  import * as IssuesPane from './IssuesPane.js';
7
8
  import * as IssueView from './IssueView.js';
8
9
 
9
10
  export {
11
+ AffectedSelectivePermissionsInterventionView,
10
12
  IssueRevealer,
11
13
  IssuesPane,
12
14
  IssueView,
@@ -11,6 +11,7 @@ import type * as Platform from '../../core/platform/platform.js';
11
11
  import * as Buttons from '../../ui/components/buttons/buttons.js';
12
12
  import {Link} from '../../ui/kit/kit.js';
13
13
  import * as UI from '../../ui/legacy/legacy.js';
14
+ import {Directives, html, render} from '../../ui/lit/lit.js';
14
15
 
15
16
  import {type LighthouseController, type Preset, Presets, RuntimeSettings} from './LighthouseController.js';
16
17
  import type {LighthousePanel} from './LighthousePanel.js';
@@ -55,6 +56,78 @@ const UIStrings = {
55
56
  const str_ = i18n.i18n.registerUIStrings('panels/lighthouse/LighthouseStartView.ts', UIStrings);
56
57
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
57
58
 
59
+ const renderStartView = (
60
+ _input: Record<string, never>,
61
+ output: {
62
+ helpText?: HTMLElement,
63
+ warningText?: HTMLElement,
64
+ modeFormElements?: HTMLElement,
65
+ deviceTypeFormElements?: HTMLElement,
66
+ categoriesFormElements?: HTMLElement,
67
+ },
68
+ target: HTMLElement,
69
+ ): void => {
70
+ // clang-format off
71
+ render(
72
+ html`
73
+ <form class="lighthouse-start-view">
74
+ <header class="hbox">
75
+ <div class="lighthouse-logo"></div>
76
+ <div class="lighthouse-title">
77
+ ${i18nString(UIStrings.generateLighthouseReport)}
78
+ </div>
79
+ <div class="lighthouse-start-button-container"></div>
80
+ </header>
81
+ <div
82
+ ${Directives.ref(e => {
83
+ output.helpText = e as HTMLElement;
84
+ })}
85
+ class="lighthouse-help-text hidden"
86
+ ></div>
87
+ <div class="lighthouse-options hbox">
88
+ <div class="lighthouse-form-section">
89
+ <div
90
+ class="lighthouse-form-elements"
91
+ ${Directives.ref(e => {
92
+ output.modeFormElements = e as HTMLElement;
93
+ })}
94
+ ></div>
95
+ </div>
96
+ <div class="lighthouse-form-section">
97
+ <div
98
+ class="lighthouse-form-elements"
99
+ ${Directives.ref(e => {
100
+ output.deviceTypeFormElements = e as HTMLElement;
101
+ })}
102
+ ></div>
103
+ </div>
104
+ <div class="lighthouse-form-categories">
105
+ <fieldset class="lighthouse-form-section lighthouse-form-categories-fieldset">
106
+ <legend class="lighthouse-form-section-label">
107
+ ${i18nString(UIStrings.categories)}
108
+ </legend>
109
+ <div
110
+ class="lighthouse-form-elements"
111
+ ${Directives.ref(e => {
112
+ output.categoriesFormElements = e as HTMLElement;
113
+ })}
114
+ ></div>
115
+ </fieldset>
116
+ </div>
117
+ </div>
118
+ <div
119
+ ${Directives.ref(e => {
120
+ output.warningText = e as HTMLElement;
121
+ })}
122
+ class="lighthouse-warning-text hidden"
123
+ ></div>
124
+ </form>
125
+ `,
126
+ target,
127
+ );
128
+ // clang-format on
129
+ };
130
+
58
131
  export class StartView extends UI.Widget.Widget {
59
132
  private controller: LighthouseController;
60
133
  private panel: LighthousePanel;
@@ -145,13 +218,11 @@ export class StartView extends UI.Widget.Widget {
145
218
  }
146
219
  }
147
220
 
148
- private populateFormControls(fragment: UI.Fragment.Fragment, mode?: string): void {
221
+ private populateFormControls(deviceTypeFormElements: Element, categoryFormElements: Element, mode?: string): void {
149
222
  // Populate the device type
150
- const deviceTypeFormElements = fragment.$('device-type-form-elements');
151
223
  this.populateRuntimeSettingAsRadio('lighthouse.device-type', i18nString(UIStrings.device), deviceTypeFormElements);
152
224
 
153
225
  // Populate the categories
154
- const categoryFormElements = fragment.$('categories-form-elements') as HTMLElement;
155
226
 
156
227
  this.checkboxes = [];
157
228
  for (const preset of Presets) {
@@ -174,44 +245,34 @@ export class StartView extends UI.Widget.Widget {
174
245
  this.populateRuntimeSettingAsToolbarDropdown('lighthouse.throttling', this.#settingsToolbar);
175
246
 
176
247
  const {mode} = this.controller.getFlags();
177
- this.populateStartButton(mode);
178
248
 
179
- const fragment = UI.Fragment.Fragment.build`
180
- <form class="lighthouse-start-view">
181
- <header class="hbox">
182
- <div class="lighthouse-logo"></div>
183
- <h1 class="lighthouse-title">${i18nString(UIStrings.generateLighthouseReport)}</h1>
184
- <div class="lighthouse-start-button-container" $="start-button-container">${this.startButton}</div>
185
- </header>
186
- <div $="help-text" class="lighthouse-help-text hidden"></div>
187
- <div class="lighthouse-options hbox">
188
- <div class="lighthouse-form-section">
189
- <div class="lighthouse-form-elements" $="mode-form-elements"></div>
190
- </div>
191
- <div class="lighthouse-form-section">
192
- <div class="lighthouse-form-elements" $="device-type-form-elements"></div>
193
- </div>
194
- <div class="lighthouse-form-categories">
195
- <fieldset class="lighthouse-form-section lighthouse-form-categories-fieldset">
196
- <legend class="lighthouse-form-section-label">${i18nString(UIStrings.categories)}</legend>
197
- <div class="lighthouse-form-elements" $="categories-form-elements"></div>
198
- </fieldset>
199
- </div>
200
- </div>
201
- <div $="warning-text" class="lighthouse-warning-text hidden"></div>
202
- </form>
203
- `;
204
-
205
- this.helpText = fragment.$('help-text');
206
- this.warningText = fragment.$('warning-text');
207
-
208
- const modeFormElements = fragment.$('mode-form-elements');
209
- this.populateRuntimeSettingAsRadio('lighthouse.mode', i18nString(UIStrings.mode), modeFormElements);
249
+ const output = {
250
+ helpText: undefined as HTMLElement | undefined,
251
+ warningText: undefined as HTMLElement | undefined,
252
+ modeFormElements: undefined as HTMLElement | undefined,
253
+ deviceTypeFormElements: undefined as HTMLElement | undefined,
254
+ categoriesFormElements: undefined as HTMLElement | undefined,
255
+ };
256
+
257
+ renderStartView(
258
+ {},
259
+ output,
260
+ this.contentElement,
261
+ );
210
262
 
211
- this.populateFormControls(fragment, mode);
263
+ this.helpText = output.helpText;
264
+ this.warningText = output.warningText;
212
265
 
213
- this.contentElement.textContent = '';
214
- this.contentElement.append(fragment.element());
266
+ const modeFormElements = output.modeFormElements;
267
+ const deviceTypeFormElements = output.deviceTypeFormElements;
268
+ const categoriesFormElements = output.categoriesFormElements;
269
+
270
+ if (!modeFormElements || !deviceTypeFormElements || !categoriesFormElements) {
271
+ throw new Error('Required elements not found in template');
272
+ }
273
+
274
+ this.populateRuntimeSettingAsRadio('lighthouse.mode', i18nString(UIStrings.mode), modeFormElements);
275
+ this.populateFormControls(deviceTypeFormElements, categoriesFormElements, mode);
215
276
 
216
277
  this.refresh();
217
278
  }
@@ -7,6 +7,7 @@ import * as i18n from '../../core/i18n/i18n.js';
7
7
  import * as Geometry from '../../models/geometry/geometry.js';
8
8
  import * as Buttons from '../../ui/components/buttons/buttons.js';
9
9
  import * as UI from '../../ui/legacy/legacy.js';
10
+ import {Directives, html, render} from '../../ui/lit/lit.js';
10
11
 
11
12
  import lighthouseDialogStyles from './lighthouseDialog.css.js';
12
13
  import type {LighthousePanel} from './LighthousePanel.js';
@@ -37,6 +38,43 @@ const UIStrings = {
37
38
  const str_ = i18n.i18n.registerUIStrings('panels/lighthouse/LighthouseTimespanView.ts', UIStrings);
38
39
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
39
40
 
41
+ const renderTimespanView = (
42
+ input: {
43
+ cancelButton: Element,
44
+ endButton: Element,
45
+ },
46
+ output: {
47
+ statusHeader?: HTMLElement,
48
+ contentContainer?: HTMLElement,
49
+ },
50
+ target: ShadowRoot,
51
+ ): void => {
52
+ // clang-format off
53
+ render(
54
+ html`
55
+ <div class="lighthouse-view vbox">
56
+ <span
57
+ ${Directives.ref(e => {
58
+ output.statusHeader = e as HTMLElement;
59
+ })}
60
+ class="header"
61
+ ></span>
62
+ <span
63
+ ${Directives.ref(e => {
64
+ output.contentContainer = e as HTMLElement;
65
+ })}
66
+ class="lighthouse-dialog-text"
67
+ ></span>
68
+ <div class="lighthouse-action-buttons hbox">
69
+ ${input.cancelButton} ${input.endButton}
70
+ </div>
71
+ </div>
72
+ `,
73
+ target,
74
+ );
75
+ // clang-format on
76
+ };
77
+
40
78
  export class TimespanView extends UI.Dialog.Dialog {
41
79
  private panel: LighthousePanel;
42
80
  private statusHeader: Element|null;
@@ -90,20 +128,21 @@ export class TimespanView extends UI.Dialog.Dialog {
90
128
  className: 'cancel',
91
129
  jslogContext: 'lighthouse.cancel',
92
130
  });
93
- const fragment = UI.Fragment.Fragment.build`
94
- <div class="lighthouse-view vbox">
95
- <span $="status-header" class="header"></span>
96
- <span $="call-to-action" class="lighthouse-dialog-text"></span>
97
- <div class="lighthouse-action-buttons hbox">
98
- ${cancelButton}
99
- ${this.endButton}
100
- </div>
101
- </div>
102
- `;
103
-
104
- this.statusHeader = fragment.$('status-header');
105
- this.contentContainer = fragment.$('call-to-action');
106
- dialogRoot.appendChild(fragment.element());
131
+
132
+ const output = {
133
+ statusHeader: undefined as HTMLElement | undefined,
134
+ contentContainer: undefined as HTMLElement | undefined,
135
+ };
136
+
137
+ renderTimespanView(
138
+ {
139
+ cancelButton,
140
+ endButton: this.endButton,
141
+ },
142
+ output, dialogRoot);
143
+
144
+ this.statusHeader = output.statusHeader ?? null;
145
+ this.contentContainer = output.contentContainer ?? null;
107
146
 
108
147
  this.setSizeBehavior(UI.GlassPane.SizeBehavior.SET_EXACT_WIDTH_MAX_HEIGHT);
109
148
  this.setMaxContentSize(new Geometry.Size(500, 400));
@@ -2,10 +2,13 @@
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
4
  /* eslint-disable @devtools/no-imperative-dom-api */
5
+ /* eslint-disable @devtools/no-lit-render-outside-of-view */
5
6
 
6
7
  import type * as Common from '../../core/common/common.js';
7
8
  import * as UI from '../../ui/legacy/legacy.js';
9
+ import {Directives, html, render} from '../../ui/lit/lit.js';
8
10
 
11
+ const {ifDefined} = Directives;
9
12
  interface RadioOption {
10
13
  value: string;
11
14
  label: () => Common.UIString.LocalizedString;
@@ -28,25 +31,36 @@ export class RadioSetting {
28
31
  UI.ARIAUtils.markAsRadioGroup(this.element);
29
32
 
30
33
  this.radioElements = [];
31
- for (const option of this.options) {
32
- const fragment = UI.Fragment.Fragment.build`
33
- <label $="label" class="lighthouse-radio">
34
- <input $="input" type="radio" value=${option.value} name=${setting.name}>
35
- <span $="span" class="lighthouse-radio-text">${option.label()}</span>
36
- </label>
37
- `;
38
-
39
- this.element.appendChild(fragment.element());
40
-
41
- const tooltip = option.tooltip?.() || description;
42
- if (description) {
43
- UI.Tooltip.Tooltip.install(fragment.$('input') as HTMLElement, tooltip);
44
- UI.Tooltip.Tooltip.install(fragment.$('span') as HTMLElement, tooltip);
45
- }
46
- const radioElement = fragment.$('input') as HTMLInputElement;
47
- radioElement.addEventListener('change', this.valueChanged.bind(this));
48
- this.radioElements.push(radioElement);
49
- }
34
+ // clang-format off
35
+ render(
36
+ html`
37
+ ${this.options.map(option => {
38
+ const tooltip = option.tooltip?.() || description;
39
+ return html`
40
+ <label class="lighthouse-radio">
41
+ <input
42
+ type="radio"
43
+ value=${option.value}
44
+ name=${setting.name}
45
+ @change=${this.valueChanged.bind(this)}
46
+ title=${ifDefined(description ? tooltip : undefined)}
47
+ ${Directives.ref(el => {
48
+ this.radioElements.push(el as HTMLInputElement);
49
+ }
50
+ )}
51
+ />
52
+ <span
53
+ class="lighthouse-radio-text"
54
+ title=${ifDefined(description ? tooltip : undefined)}
55
+ >${option.label()}</span
56
+ >
57
+ </label>
58
+ `;
59
+ })}
60
+ `,
61
+ this.element,
62
+ );
63
+ // clang-format on
50
64
 
51
65
  this.ignoreChangeEvents = false;
52
66
  this.selectedIndex = -1;
@@ -11,6 +11,7 @@ import * as LighthouseReportRenderer from './LighthouseReportRenderer.js';
11
11
  import * as LighthouseReportSelector from './LighthouseReportSelector.js';
12
12
  import * as LighthouseStartView from './LighthouseStartView.js';
13
13
  import * as LighthouseStatusView from './LighthouseStatusView.js';
14
+ import * as LighthouseTimespanView from './LighthouseTimespanView.js';
14
15
  import * as RadioSetting from './RadioSetting.js';
15
16
 
16
17
  export {
@@ -21,5 +22,6 @@ export {
21
22
  LighthouseReportSelector,
22
23
  LighthouseStartView,
23
24
  LighthouseStatusView,
25
+ LighthouseTimespanView,
24
26
  RadioSetting,
25
27
  };
@@ -2,6 +2,7 @@
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
4
  /* eslint-disable @devtools/no-imperative-dom-api */
5
+ /* eslint-disable @devtools/no-lit-render-outside-of-view */
5
6
 
6
7
  import '../../ui/legacy/legacy.js';
7
8
 
@@ -9,6 +10,7 @@ import type * as Common from '../../core/common/common.js';
9
10
  import * as i18n from '../../core/i18n/i18n.js';
10
11
  import type * as Protocol from '../../generated/protocol.js';
11
12
  import * as UI from '../../ui/legacy/legacy.js';
13
+ import {html, type LitTemplate, nothing, render} from '../../ui/lit/lit.js';
12
14
  import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
13
15
 
14
16
  import playerMessagesViewStyles from './playerMessagesView.css.js';
@@ -339,59 +341,70 @@ export class PlayerMessagesView extends UI.Widget.VBox {
339
341
  UI.UIUtils.createTextChild(container, message.message);
340
342
  }
341
343
 
342
- private errorToDiv(error: Protocol.Media.PlayerError): Element {
343
- const entry = UI.Fragment.Fragment.build`
344
- <div class="status-error-box">
345
- <div class="status-error-field-labeled">
346
- <span class="status-error-field-label" $="status-error-group"></span>
347
- <span>${error.errorType}</span>
348
- </div>
349
- <div class="status-error-field-labeled">
350
- <span class="status-error-field-label" $="status-error-code"></span>
351
- <span>${error.code}</span>
352
- </div>
353
- <div class="status-error-field-labeled" $="status-error-data">
354
- </div>
355
- <div class="status-error-field-labeled" $="status-error-stack">
356
- </div>
357
- <div class="status-error-field-labeled" $="status-error-cause">
358
- </div>
344
+ private renderError(error: Protocol.Media.PlayerError): LitTemplate {
345
+ // clang-format off
346
+ return html`
347
+ <div class="status-error-box">
348
+ <div class="status-error-field-labeled">
349
+ <span class="status-error-field-label"
350
+ >${i18nString(UIStrings.errorGroupLabel)}</span
351
+ >
352
+ <span>${error.errorType}</span>
353
+ </div>
354
+ <div class="status-error-field-labeled">
355
+ <span class="status-error-field-label"
356
+ >${i18nString(UIStrings.errorCodeLabel)}</span
357
+ >
358
+ <span>${error.code}</span>
359
+ </div>
360
+ <div class="status-error-field-labeled">
361
+ ${
362
+ Object.keys(error.data).length !== 0
363
+ ? html`<span class="status-error-field-label"
364
+ >${i18nString(UIStrings.errorDataLabel)}</span
365
+ >
366
+ <div>
367
+ ${Object.entries(error.data).map(
368
+ ([key, value]) => html`<div>${key}: ${value}</div>`,
369
+ )}
370
+ </div>`
371
+ : nothing
372
+ }
373
+ </div>
374
+ <div class="status-error-field-labeled">
375
+ ${
376
+ error.stack.length !== 0
377
+ ? html`<span class="status-error-field-label"
378
+ >${i18nString(UIStrings.errorStackLabel)}</span
379
+ >
380
+ <div>
381
+ ${error.stack.map(
382
+ stackEntry =>
383
+ html`<div>${stackEntry.file}:${stackEntry.line}</div>`,
384
+ )}
385
+ </div>`
386
+ : nothing
387
+ }
388
+ </div>
389
+ <div class="status-error-field-labeled">
390
+ ${
391
+ error.cause.length !== 0
392
+ ? html`
393
+ <span class="status-error-field-label"
394
+ >${i18nString(UIStrings.errorCauseLabel)}</span
395
+ >
396
+ ${this.renderError(error.cause[0])}
397
+ `
398
+ : nothing
399
+ }
400
+ </div>
401
+ </div>
359
402
  `;
360
-
361
- entry.$('status-error-group').textContent = i18nString(UIStrings.errorGroupLabel);
362
- entry.$('status-error-code').textContent = i18nString(UIStrings.errorCodeLabel);
363
-
364
- if (Object.keys(error.data).length !== 0) {
365
- const label = entry.$('status-error-data').createChild('span', 'status-error-field-label');
366
- UI.UIUtils.createTextChild(label, i18nString(UIStrings.errorDataLabel));
367
- const dataContent = entry.$('status-error-data').createChild('div');
368
- for (const [key, value] of Object.entries(error.data)) {
369
- const datumContent = dataContent.createChild('div');
370
- UI.UIUtils.createTextChild(datumContent, `${key}: ${value}`);
371
- }
372
- }
373
-
374
- if (error.stack.length !== 0) {
375
- const label = entry.$('status-error-stack').createChild('span', 'status-error-field-label');
376
- UI.UIUtils.createTextChild(label, i18nString(UIStrings.errorStackLabel));
377
- const stackContent = entry.$('status-error-stack').createChild('div');
378
- for (const stackEntry of error.stack) {
379
- const frameBox = stackContent.createChild('div');
380
- UI.UIUtils.createTextChild(frameBox, `${stackEntry.file}:${stackEntry.line}`);
381
- }
382
- }
383
-
384
- if (error.cause.length !== 0) {
385
- const label = entry.$('status-error-cause').createChild('span', 'status-error-field-label');
386
- UI.UIUtils.createTextChild(label, i18nString(UIStrings.errorCauseLabel));
387
- entry.$('status-error-cause').appendChild(this.errorToDiv(error.cause[0]));
388
- }
389
-
390
- return entry.element();
403
+ // clang-format on
391
404
  }
392
405
 
393
406
  addError(error: Protocol.Media.PlayerError): void {
394
407
  const container = this.bodyPanel.createChild('div', 'media-messages-message-container media-message-error');
395
- container.appendChild(this.errorToDiv(error));
408
+ render(this.renderError(error), container);
396
409
  }
397
410
  }