chrome-devtools-frontend 1.0.1642845 → 1.0.1642899

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 (51) hide show
  1. package/SECURITY.md +1 -0
  2. package/front_end/core/host/UserMetrics.ts +2 -1
  3. package/front_end/core/sdk/CSSMatchedStyles.ts +55 -26
  4. package/front_end/core/sdk/CSSRule.ts +1 -0
  5. package/front_end/core/sdk/DebuggerModel.ts +5 -0
  6. package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +4 -3
  7. package/front_end/entrypoints/greendev_floaty/greendev_floaty.ts +4 -3
  8. package/front_end/models/ai_assistance/AiAgent2.ts +80 -16
  9. package/front_end/models/ai_assistance/AiConversation.ts +3 -2
  10. package/front_end/models/ai_assistance/README.md +8 -0
  11. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +50 -35
  12. package/front_end/models/ai_assistance/agents/AiAgent.ts +16 -0
  13. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +2 -2
  14. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +195 -147
  15. package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +0 -25
  16. package/front_end/models/ai_assistance/agents/StylingAgent.ts +24 -305
  17. package/front_end/models/ai_assistance/ai_assistance.ts +8 -0
  18. package/front_end/models/ai_assistance/contexts/DOMNodeContext.snapshot.txt +51 -0
  19. package/front_end/models/ai_assistance/contexts/DOMNodeContext.ts +200 -0
  20. package/front_end/models/ai_assistance/skills/styling.md +36 -2
  21. package/front_end/models/ai_assistance/tools/GetStyles.ts +137 -0
  22. package/front_end/models/ai_assistance/tools/Tool.ts +55 -0
  23. package/front_end/models/ai_assistance/tools/ToolRegistry.ts +34 -0
  24. package/front_end/models/lighthouse/LighthouseReporterTypes.ts +5 -0
  25. package/front_end/models/live-metrics/LiveMetrics.ts +24 -13
  26. package/front_end/models/stack_trace/DetailedErrorStackParser.ts +2 -2
  27. package/front_end/models/stack_trace/StackTrace.ts +4 -1
  28. package/front_end/models/stack_trace/StackTraceImpl.ts +9 -2
  29. package/front_end/models/stack_trace/StackTraceModel.ts +17 -4
  30. package/front_end/models/stack_trace/Trie.ts +1 -1
  31. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +19 -15
  32. package/front_end/panels/ai_assistance/ai_assistance-meta.ts +16 -0
  33. package/front_end/panels/ai_assistance/components/ChatInput.ts +2 -2
  34. package/front_end/panels/application/DOMStorageItemsView.ts +4 -0
  35. package/front_end/panels/application/KeyValueStorageItemsView.ts +39 -7
  36. package/front_end/panels/common/ExtensionServer.ts +26 -15
  37. package/front_end/panels/elements/StandaloneStylesContainer.ts +1 -1
  38. package/front_end/panels/elements/StylePropertiesSection.ts +8 -0
  39. package/front_end/panels/elements/StylePropertyHighlighter.ts +4 -2
  40. package/front_end/panels/elements/StylePropertyTreeElement.ts +6 -5
  41. package/front_end/panels/elements/StylesContainer.ts +1 -1
  42. package/front_end/panels/elements/StylesSidebarPane.ts +4 -4
  43. package/front_end/panels/layer_viewer/PaintProfilerView.ts +106 -132
  44. package/front_end/panels/lighthouse/LighthousePanel.ts +4 -3
  45. package/front_end/panels/network/NetworkLogView.ts +3 -0
  46. package/front_end/panels/network/networkLogView.css +0 -15
  47. package/front_end/ui/legacy/components/cookie_table/CookiesTable.ts +36 -3
  48. package/front_end/ui/legacy/components/data_grid/dataGridAiButton.css +20 -0
  49. package/front_end/ui/legacy/components/utils/Linkifier.ts +19 -4
  50. package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
  51. package/package.json +1 -1
@@ -28,8 +28,13 @@
28
28
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
29
  */
30
30
  /* eslint no-return-assign: "off" */
31
+ import '../../ui/components/buttons/buttons.js';
32
+
31
33
  import * as i18n from '../../core/i18n/i18n.js';
34
+ import * as AIAssistance from '../../models/ai_assistance/ai_assistance.js';
32
35
  import * as Geometry from '../../models/geometry/geometry.js';
36
+ // eslint-disable-next-line @devtools/es-modules-import
37
+ import dataGridAiButtonStyles from '../../ui/legacy/components/data_grid/dataGridAiButton.css.js';
33
38
  import * as UI from '../../ui/legacy/legacy.js';
34
39
  import {Directives as LitDirectives, html, nothing, render} from '../../ui/lit/lit.js';
35
40
  import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
@@ -37,11 +42,13 @@ import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
37
42
  import * as ApplicationComponents from './components/components.js';
38
43
  import {StorageItemsToolbar} from './StorageItemsToolbar.js';
39
44
 
45
+ const STORAGE_FLOATING_BUTTON_ACTION_ID = 'ai-assistance.storage-floating-button';
46
+
40
47
  const {ARIAUtils} = UI;
41
48
  const {EmptyWidget} = UI.EmptyWidget;
42
49
  const {VBox, widget} = UI.Widget;
43
50
  const {Size} = Geometry;
44
- const {repeat} = LitDirectives;
51
+ const {repeat, ifDefined} = LitDirectives;
45
52
 
46
53
  type Widget = UI.Widget.Widget;
47
54
  type VBox = UI.Widget.VBox;
@@ -87,6 +94,9 @@ export interface ViewInput {
87
94
  onDeleteAll: () => void;
88
95
  jslog?: string;
89
96
  classes?: string[];
97
+ aiButtonTitle?: string;
98
+ showAiButton?: boolean;
99
+ onAiButtonClick?: (item: {key: string, value: string}, event: Event) => void;
90
100
  }
91
101
 
92
102
  interface ViewOutput {
@@ -150,6 +160,7 @@ export abstract class KeyValueStorageItemsView extends UI.Widget.VBox {
150
160
  @deselect=${() => input.onSelect(null)}
151
161
  >
152
162
  <table>
163
+ ${input.showAiButton ? html`<style>${dataGridAiButtonStyles}</style>`: nothing}
153
164
  <tr>
154
165
  <th id="key" sortable ?editable=${input.editable}>
155
166
  ${i18nString(UIStrings.key)}
@@ -165,7 +176,15 @@ export abstract class KeyValueStorageItemsView extends UI.Widget.VBox {
165
176
  input.onEdit(item.key, item.value, e.detail.columnId, e.detail.valueBeforeEditing, e.detail.newText)}
166
177
  @delete=${() => input.onDelete(item.key)}
167
178
  selected=${(input.selectedKey === item.key) || nothing}>
168
- <td>${item.key}</td>
179
+ <td>${input.showAiButton ? html`
180
+ <span class="ai-button-container">
181
+ <devtools-floating-button
182
+ icon-name=${AIAssistance.AiUtils.getIconName()}
183
+ title=${ifDefined(input.aiButtonTitle)}
184
+ @click=${(e: Event) => input.onAiButtonClick?.(item, e)}
185
+ ></devtools-floating-button>
186
+ </span>
187
+ ` : nothing}${item.key}</td>
169
188
  <td>${item.value.substr(0, MAX_VALUE_LENGTH)}</td>
170
189
  </tr>`)}
171
190
  <tr placeholder></tr>
@@ -217,15 +236,24 @@ export abstract class KeyValueStorageItemsView extends UI.Widget.VBox {
217
236
  preview: this.#preview,
218
237
  jslog: this.#jslog,
219
238
  classes: this.#classes,
239
+ showAiButton: this.isAiButtonEnabled(),
240
+ aiButtonTitle: this.isAiButtonEnabled() &&
241
+ UI.ActionRegistry.ActionRegistry.instance().hasAction(STORAGE_FLOATING_BUTTON_ACTION_ID) ?
242
+ UI.ActionRegistry.ActionRegistry.instance().getAction(STORAGE_FLOATING_BUTTON_ACTION_ID).title() :
243
+ undefined,
220
244
  onSelect: (item: {key: string, value: string}|null) => {
221
245
  this.#toolbar?.setCanDeleteSelected(Boolean(item));
222
- if (!item) {
223
- void this.#previewEntry(null);
224
- } else {
225
- void this.#previewEntry(item);
226
- }
246
+ void this.#previewEntry(item);
227
247
  this.selectedItemChanged(item);
228
248
  },
249
+ onAiButtonClick: (item: {key: string, value: string}, event: Event) => {
250
+ event.stopPropagation();
251
+ viewInput.onSelect(item);
252
+ const actionRegistry = UI.ActionRegistry.ActionRegistry.instance();
253
+ if (actionRegistry.hasAction(STORAGE_FLOATING_BUTTON_ACTION_ID)) {
254
+ void actionRegistry.getAction(STORAGE_FLOATING_BUTTON_ACTION_ID).execute();
255
+ }
256
+ },
229
257
 
230
258
  onSort: (ascending: boolean) => {
231
259
  this.#isSortOrderAscending = ascending;
@@ -252,6 +280,10 @@ export abstract class KeyValueStorageItemsView extends UI.Widget.VBox {
252
280
  this.#view(viewInput, viewOutput, this.contentElement);
253
281
  }
254
282
 
283
+ protected isAiButtonEnabled(): boolean {
284
+ return false;
285
+ }
286
+
255
287
  protected get toolbar(): StorageItemsToolbar|undefined {
256
288
  return this.#toolbar;
257
289
  }
@@ -27,7 +27,13 @@ import * as ThemeSupport from '../../ui/legacy/theme_support/theme_support.js';
27
27
  import {ExtensionButton, ExtensionPanel, ExtensionSidebarPane} from './ExtensionPanel.js';
28
28
 
29
29
  const extensionOrigins = new WeakMap<MessagePort, Platform.DevToolsPath.UrlString>();
30
- const kPermittedSchemes = ['http:', 'https:', 'file:', 'data:', 'chrome-extension:', 'about:'];
30
+ const kForbiddenSchemes = [
31
+ 'chrome:',
32
+ 'chrome-untrusted:',
33
+ 'chrome-error:',
34
+ 'chrome-search:',
35
+ 'devtools:',
36
+ ];
31
37
 
32
38
  declare global {
33
39
  interface Window {
@@ -78,7 +84,6 @@ export class HostsPolicy {
78
84
  }
79
85
 
80
86
  class RegisteredExtension {
81
- openResourceScheme: null|string = null;
82
87
  constructor(readonly name: string, readonly hostsPolicy: HostsPolicy, readonly allowFileAccess: boolean) {
83
88
  }
84
89
 
@@ -91,8 +96,11 @@ class RegisteredExtension {
91
96
  return false;
92
97
  }
93
98
 
94
- if (this.openResourceScheme && inspectedURL.startsWith(this.openResourceScheme)) {
95
- return true;
99
+ let parsedURL;
100
+ try {
101
+ parsedURL = new URL(inspectedURL);
102
+ } catch {
103
+ return false;
96
104
  }
97
105
 
98
106
  if (!ExtensionServer.canInspectURL(inspectedURL)) {
@@ -104,12 +112,6 @@ class RegisteredExtension {
104
112
  }
105
113
 
106
114
  if (!this.allowFileAccess) {
107
- let parsedURL;
108
- try {
109
- parsedURL = new URL(inspectedURL);
110
- } catch {
111
- return false;
112
- }
113
115
  return parsedURL.protocol !== 'file:';
114
116
  }
115
117
 
@@ -863,18 +865,27 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
863
865
  if (!extension) {
864
866
  throw new Error('Received a message from an unregistered extension');
865
867
  }
866
- if (message.urlScheme) {
867
- extension.openResourceScheme = message.urlScheme;
868
+ let validScheme = message.urlScheme;
869
+ if (validScheme) {
870
+ try {
871
+ const urlToParse = validScheme.replace(/:?(\/\/)?$/, '') + '://test';
872
+ validScheme = new URL(urlToParse).protocol;
873
+ } catch {
874
+ return this.status.E_BADARG('urlScheme', 'Invalid scheme');
875
+ }
876
+ if (kForbiddenSchemes.includes(validScheme) || validScheme === 'file:') {
877
+ return this.status.E_BADARG('urlScheme', 'Scheme is forbidden');
878
+ }
868
879
  }
869
880
  const extensionOrigin = this.getExtensionOrigin(port);
870
881
  const {name} = extension;
871
882
  const registration = {
872
883
  title: name,
873
884
  origin: extensionOrigin,
874
- scheme: message.urlScheme,
885
+ scheme: validScheme,
875
886
  handler: this.handleOpenURL.bind(this, port),
876
887
  shouldHandleOpenResource: (url: Platform.DevToolsPath.UrlString, schemes: Set<string>) =>
877
- Components.Linkifier.Linkifier.shouldHandleOpenResource(extension.openResourceScheme, url, schemes),
888
+ Components.Linkifier.Linkifier.shouldHandleOpenResource(validScheme || null, url, schemes),
878
889
  };
879
890
  if (message.handlerPresent) {
880
891
  Components.Linkifier.Linkifier.registerLinkHandler(registration);
@@ -1645,7 +1656,7 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
1645
1656
  return false;
1646
1657
  }
1647
1658
 
1648
- if (!kPermittedSchemes.includes(parsedURL.protocol)) {
1659
+ if (kForbiddenSchemes.includes(parsedURL.protocol)) {
1649
1660
  return false;
1650
1661
  }
1651
1662
 
@@ -276,7 +276,7 @@ export class StandaloneStylesContainer extends Common.ObjectWrapper.eventMixin<E
276
276
  return null;
277
277
  }
278
278
 
279
- jumpToFunctionDefinition(_functionName: string): void {
279
+ jumpToFunctionDefinition(_functionName: string, _treeScopeDistance: number): void {
280
280
  }
281
281
 
282
282
  continueEditingElement(_sectionIndex: number, _propertyIndex: number): void {
@@ -379,6 +379,14 @@ export class StylePropertiesSection {
379
379
  return this.sectionIdx;
380
380
  }
381
381
 
382
+ treeScopeDistance(): number {
383
+ const treeScope = this.styleInternal.parentRule?.treeScope;
384
+ if (!treeScope) {
385
+ return -1;
386
+ }
387
+ return SDK.CSSMatchedStyles.distanceToTreeScope(this.matchedStyles.node(), treeScope);
388
+ }
389
+
382
390
  static createRuleOriginNode(
383
391
  matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles, linkifier: Components.Linkifier.Linkifier,
384
392
  rule: SDK.CSSRule.CSSRule|null): LitTemplate {
@@ -51,9 +51,11 @@ export class StylePropertyHighlighter {
51
51
  PanelUtils.highlightElement(block.titleElement() as HTMLElement);
52
52
  }
53
53
 
54
- findAndHighlightSection(sectionName: string, blockName: string): void {
54
+ findAndHighlightSection(sectionName: string, blockName: string, treeScopeDistance = -1): void {
55
55
  const block = this.styleSidebarPane.getSectionBlockByName(blockName);
56
- const section = block?.sections.find(section => section.headerText() === sectionName);
56
+ const section = block?.sections.find(
57
+ section => section.headerText() === sectionName &&
58
+ (treeScopeDistance === -1 || section.treeScopeDistance() === treeScopeDistance));
57
59
  if (!section || !block) {
58
60
  return;
59
61
  }
@@ -2661,10 +2661,11 @@ export class StylePropertyTreeElement extends UI.TreeOutline.TreeElement {
2661
2661
  return container;
2662
2662
  }
2663
2663
 
2664
- #getLinkableFunction(functionName: string, matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles):
2665
- InlineEditor.LinkSwatch.LinkSwatch {
2664
+ #getLinkableFunction(
2665
+ functionName: string, matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles,
2666
+ property: SDK.CSSProperty.CSSProperty): InlineEditor.LinkSwatch.LinkSwatch {
2666
2667
  const swatch = new InlineEditor.LinkSwatch.LinkSwatch();
2667
- const registeredFunction = matchedStyles.getRegisteredFunction(functionName);
2668
+ const {registeredFunction, treeScopeDistance} = matchedStyles.getRegisteredFunction(functionName, property);
2668
2669
  const isDefined = Boolean(registeredFunction);
2669
2670
  swatch.data = {
2670
2671
  jslogContext: 'css-function',
@@ -2675,7 +2676,7 @@ export class StylePropertyTreeElement extends UI.TreeOutline.TreeElement {
2675
2676
  if (!registeredFunction) {
2676
2677
  return;
2677
2678
  }
2678
- this.#stylesContainer.jumpToFunctionDefinition(registeredFunction);
2679
+ this.#stylesContainer.jumpToFunctionDefinition(registeredFunction, treeScopeDistance);
2679
2680
  },
2680
2681
  };
2681
2682
  return swatch;
@@ -2696,7 +2697,7 @@ export class StylePropertyTreeElement extends UI.TreeOutline.TreeElement {
2696
2697
  const tooltipId = this.getTooltipId(`${functionName}-trace`);
2697
2698
  // clang-format off
2698
2699
  return html`
2699
- <span tabIndex=-1 class=tracing-anchor aria-details=${tooltipId}>${functionName.startsWith('--') ? this.#getLinkableFunction(functionName, matchedStyles) : functionName}</span>
2700
+ <span tabIndex=-1 class=tracing-anchor aria-details=${tooltipId}>${functionName.startsWith('--') ? this.#getLinkableFunction(functionName, matchedStyles, context.property) : functionName}</span>
2700
2701
  <devtools-tooltip
2701
2702
  id=${tooltipId}
2702
2703
  use-hotkey
@@ -37,7 +37,7 @@ export interface StylesContainer {
37
37
  computedValue: string|null): ElementsComponents.CSSVariableValueView.CSSVariableValueView;
38
38
  getVariableParserError(matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles, variableName: string):
39
39
  ElementsComponents.CSSVariableValueView.CSSVariableParserError|null;
40
- jumpToFunctionDefinition(functionName: string): void;
40
+ jumpToFunctionDefinition(functionName: string, treeScopeDistance: number): void;
41
41
  continueEditingElement(sectionIndex: number, propertyIndex: number): void;
42
42
  revealProperty(cssProperty: SDK.CSSProperty.CSSProperty): void;
43
43
  resetFocus(): void;
@@ -344,16 +344,16 @@ export class StylesSidebarPane extends Common.ObjectWrapper.eventMixin<EventType
344
344
  }
345
345
  }
346
346
 
347
- jumpToSection(sectionName: string, blockName: string): void {
348
- this.decorator.findAndHighlightSection(sectionName, blockName);
347
+ jumpToSection(sectionName: string, blockName: string, treeScopeDistance?: number): void {
348
+ this.decorator.findAndHighlightSection(sectionName, blockName, treeScopeDistance);
349
349
  }
350
350
 
351
351
  jumpToSectionBlock(section: string): void {
352
352
  this.decorator.findAndHighlightSectionBlock(section);
353
353
  }
354
354
 
355
- jumpToFunctionDefinition(functionName: string): void {
356
- this.jumpToSection(functionName, FUNCTION_SECTION_NAME);
355
+ jumpToFunctionDefinition(functionName: string, treeScopeDistance: number): void {
356
+ this.jumpToSection(functionName, FUNCTION_SECTION_NAME, treeScopeDistance);
357
357
  }
358
358
 
359
359
  jumpToFontPaletteDefinition(paletteName: string): void {
@@ -11,9 +11,13 @@ import type * as SDK from '../../core/sdk/sdk.js';
11
11
  import type * as Protocol from '../../generated/protocol.js';
12
12
  import * as PerfUI from '../../ui/legacy/components/perf_ui/perf_ui.js';
13
13
  import * as UI from '../../ui/legacy/legacy.js';
14
+ import * as Lit from '../../ui/lit/lit.js';
14
15
 
15
16
  import paintProfilerStyles from './paintProfiler.css.js';
16
17
 
18
+ const {html, render, nothing} = Lit;
19
+ const {repeat} = Lit.Directives;
20
+
17
21
  const UIStrings = {
18
22
  /**
19
23
  * @description Text to indicate the progress of a profile
@@ -397,161 +401,131 @@ export interface EventTypes {
397
401
  [Events.WINDOW_CHANGED]: void;
398
402
  }
399
403
 
400
- export class PaintProfilerCommandLogView extends UI.Widget.VBox {
401
- private readonly treeOutline: UI.TreeOutline.TreeOutlineInShadow;
402
- private log: SDK.PaintProfiler.PaintProfilerLogItem[];
403
- private readonly treeItemCache: Map<SDK.PaintProfiler.PaintProfilerLogItem, LogTreeElement>;
404
- private selectionWindow?: {left: number, right: number}|null;
405
- constructor() {
406
- super();
407
- this.setMinimumSize(100, 25);
408
- this.element.classList.add('overflow-auto');
409
-
410
- this.treeOutline = new UI.TreeOutline.TreeOutlineInShadow();
411
- UI.ARIAUtils.setLabel(this.treeOutline.contentElement, i18nString(UIStrings.commandLog));
412
- this.element.appendChild(this.treeOutline.element);
413
- this.setDefaultFocusedElement(this.treeOutline.contentElement);
414
-
415
- this.log = [];
416
- this.treeItemCache = new Map();
417
- }
418
-
419
- setCommandLog(log: SDK.PaintProfiler.PaintProfilerLogItem[]): void {
420
- this.log = log;
404
+ export interface CommandLogViewInput {
405
+ visibleLogItems: SDK.PaintProfiler.PaintProfilerLogItem[];
406
+ }
421
407
 
422
- this.updateWindow({left: 0, right: this.log.length});
408
+ function paramToString(param: SDK.PaintProfiler.RawPaintProfilerLogItemParamValue, name: string): string {
409
+ if (typeof param !== 'object') {
410
+ return typeof param === 'string' && param.length > 100 ? name : JSON.stringify(param);
423
411
  }
424
-
425
- private appendLogItem(logItem: SDK.PaintProfiler.PaintProfilerLogItem): void {
426
- let treeElement = this.treeItemCache.get(logItem);
427
- if (!treeElement) {
428
- treeElement = new LogTreeElement(logItem);
429
- this.treeItemCache.set(logItem, treeElement);
430
- } else if (treeElement.parent) {
431
- return;
412
+ let str = '';
413
+ let keyCount = 0;
414
+ for (const key in param) {
415
+ const paramKey = param[key];
416
+ if (++keyCount > 4 || typeof paramKey === 'object' || (typeof paramKey === 'string' && paramKey.length > 100)) {
417
+ return name;
418
+ }
419
+ if (str) {
420
+ str += ', ';
432
421
  }
433
- this.treeOutline.appendChild(treeElement);
422
+ str += paramKey;
434
423
  }
424
+ return str;
425
+ }
435
426
 
436
- updateWindow(selectionWindow: {left: number, right: number}|null): void {
437
- this.selectionWindow = selectionWindow;
438
- this.requestUpdate();
427
+ function paramsToString(params: SDK.PaintProfiler.RawPaintProfilerLogItemParams|null): string {
428
+ let str = '';
429
+ for (const key in params) {
430
+ if (str) {
431
+ str += ', ';
432
+ }
433
+ str += paramToString(params[key], key);
439
434
  }
435
+ return str;
436
+ }
440
437
 
441
- override performUpdate(): Promise<void> {
442
- if (!this.selectionWindow || !this.log.length) {
443
- this.treeOutline.removeChildren();
444
- return Promise.resolve();
445
- }
446
- const root = this.treeOutline.rootElement();
447
- for (;;) {
448
- const child = root.firstChild() as LogTreeElement;
449
- if (!child || child.logItem.commandIndex >= this.selectionWindow.left) {
450
- break;
438
+ function renderProperty(name: string, value: SDK.PaintProfiler.RawPaintProfilerLogItemParamValue): Lit.LitTemplate {
439
+ const isObject = value !== null && typeof value === 'object';
440
+
441
+ // clang-format off
442
+ return html`
443
+ <li role="treeitem">
444
+ <span>${name}: </span>${
445
+ isObject ? html`
446
+ <ul role="group">
447
+ ${Object.entries(value).map(([key, val]) => renderProperty(key, val))}
448
+ </ul>` : html`
449
+ <span>${JSON.stringify(value)}</span>`
451
450
  }
452
- root.removeChildAtIndex(0);
453
- }
454
- for (;;) {
455
- const child = root.lastChild() as LogTreeElement;
456
- if (!child || child.logItem.commandIndex < this.selectionWindow.right) {
457
- break;
458
- }
459
- root.removeChildAtIndex(root.children().length - 1);
460
- }
461
- for (let i = this.selectionWindow.left, right = this.selectionWindow.right; i < right; ++i) {
462
- this.appendLogItem(this.log[i]);
463
- }
464
- return Promise.resolve();
465
- }
451
+ </li>
452
+ `;
453
+ // clang-format on
466
454
  }
467
455
 
468
- export class LogTreeElement extends UI.TreeOutline.TreeElement {
469
- readonly logItem: SDK.PaintProfiler.PaintProfilerLogItem;
456
+ function renderLogItem(logItem: SDK.PaintProfiler.PaintProfilerLogItem): Lit.LitTemplate {
457
+ const hasParams = Boolean(logItem.params && Object.keys(logItem.params).length > 0);
458
+ const titleText = logItem.method + '(' + paramsToString(logItem.params) + ')';
459
+
460
+ // clang-format off
461
+ return html`
462
+ <li role="treeitem">
463
+ ${titleText}
464
+ ${hasParams ? html`
465
+ <ul role="group">
466
+ ${Object.entries(logItem.params || {}).map(([key, val]) => renderProperty(key, val))}
467
+ </ul>` : nothing}
468
+ </li>
469
+ `;
470
+ // clang-format on
471
+ }
470
472
 
471
- constructor(logItem: SDK.PaintProfiler.PaintProfilerLogItem) {
472
- super('', Boolean(logItem.params));
473
- this.logItem = logItem;
474
- }
473
+ // clang-format off
474
+ export const COMMAND_LOG_DEFAULT_VIEW = (input: CommandLogViewInput, _output: undefined, target: HTMLElement): void => {
475
+ render(html`
476
+ <div class="overflow-auto flex-auto vbox">
477
+ <devtools-tree
478
+ autofocus
479
+ aria-label=${i18nString(UIStrings.commandLog)}
480
+ .template=${html`
481
+ <ul role="tree">
482
+ ${repeat(input.visibleLogItems,
483
+ item => item.commandIndex,
484
+ item => renderLogItem(item))}
485
+ </ul>`}>
486
+ </devtools-tree>
487
+ </div>`,
488
+ target);
489
+ };
490
+ // clang-format on
491
+
492
+ type CommandLogView = typeof COMMAND_LOG_DEFAULT_VIEW;
475
493
 
476
- override onattach(): void {
477
- this.update();
478
- }
494
+ export class PaintProfilerCommandLogView extends UI.Widget.VBox {
495
+ private log: SDK.PaintProfiler.PaintProfilerLogItem[];
496
+ private selectionWindow?: {left: number, right: number}|null;
497
+ readonly #view: CommandLogView;
479
498
 
480
- override async onpopulate(): Promise<void> {
481
- for (const param in this.logItem.params) {
482
- LogPropertyTreeElement.appendLogPropertyItem(this, param, this.logItem.params[param]);
483
- }
484
- }
499
+ constructor(element?: HTMLElement, view: CommandLogView = COMMAND_LOG_DEFAULT_VIEW) {
500
+ super(element);
501
+ this.#view = view;
502
+ this.setMinimumSize(100, 25);
485
503
 
486
- private paramToString(param: SDK.PaintProfiler.RawPaintProfilerLogItemParamValue, name: string): string {
487
- if (typeof param !== 'object') {
488
- return typeof param === 'string' && param.length > 100 ? name : JSON.stringify(param);
489
- }
490
- let str = '';
491
- let keyCount = 0;
492
- for (const key in param) {
493
- const paramKey = param[key];
494
- if (++keyCount > 4 || paramKey === 'object' || (paramKey === 'string' && paramKey.length > 100)) {
495
- return name;
496
- }
497
- if (str) {
498
- str += ', ';
499
- }
500
- str += paramKey;
501
- }
502
- return str;
504
+ this.log = [];
503
505
  }
504
506
 
505
- private paramsToString(params: SDK.PaintProfiler.RawPaintProfilerLogItemParams|null): string {
506
- let str = '';
507
- for (const key in params) {
508
- if (str) {
509
- str += ', ';
510
- }
511
- str += this.paramToString(params[key], key);
512
- }
513
- return str;
507
+ override wasShown(): void {
508
+ super.wasShown();
509
+ this.requestUpdate();
514
510
  }
515
511
 
516
- private update(): void {
517
- const title = document.createDocumentFragment();
518
- UI.UIUtils.createTextChild(title, this.logItem.method + '(' + this.paramsToString(this.logItem.params) + ')');
519
- this.title = title;
512
+ setCommandLog(log: SDK.PaintProfiler.PaintProfilerLogItem[]): void {
513
+ this.log = log;
514
+ this.updateWindow({left: 0, right: this.log.length});
520
515
  }
521
- }
522
-
523
- export class LogPropertyTreeElement extends UI.TreeOutline.TreeElement {
524
- private property: {name: string, value: SDK.PaintProfiler.RawPaintProfilerLogItemParamValue};
525
516
 
526
- constructor(property: {name: string, value: SDK.PaintProfiler.RawPaintProfilerLogItemParamValue}) {
527
- super();
528
- this.property = property;
517
+ updateWindow(selectionWindow: {left: number, right: number}|null): void {
518
+ this.selectionWindow = selectionWindow;
519
+ this.requestUpdate();
529
520
  }
530
521
 
531
- static appendLogPropertyItem(
532
- element: UI.TreeOutline.TreeElement, name: string,
533
- value: SDK.PaintProfiler.RawPaintProfilerLogItemParamValue): void {
534
- const treeElement = new LogPropertyTreeElement({name, value});
535
- element.appendChild(treeElement);
536
- if (value && typeof value === 'object') {
537
- for (const property in value) {
538
- LogPropertyTreeElement.appendLogPropertyItem(treeElement, property, value[property]);
539
- }
540
- }
541
- }
522
+ override performUpdate(): Promise<void> {
523
+ const visibleLogItems = this.selectionWindow && this.log.length ?
524
+ this.log.slice(this.selectionWindow.left, this.selectionWindow.right) :
525
+ [];
542
526
 
543
- override onattach(): void {
544
- const title = document.createDocumentFragment();
545
- const nameElement = title.createChild('span', 'name');
546
- nameElement.textContent = this.property.name;
547
- const separatorElement = title.createChild('span', 'separator');
548
- separatorElement.textContent = ': ';
549
- if (this.property.value === null || typeof this.property.value !== 'object') {
550
- const valueElement = title.createChild('span', 'value');
551
- valueElement.textContent = JSON.stringify(this.property.value);
552
- valueElement.classList.add('cm-js-' + (this.property.value === null ? 'null' : typeof this.property.value));
553
- }
554
- this.title = title;
527
+ this.#view({visibleLogItems}, undefined, this.contentElement);
528
+ return Promise.resolve();
555
529
  }
556
530
  }
557
531
 
@@ -374,11 +374,12 @@ export class LighthousePanel extends UI.Panel.Panel {
374
374
  }
375
375
 
376
376
  private loadedFromFile(report: string): void {
377
- const data = JSON.parse(report);
378
- if (!data['lighthouseVersion']) {
377
+ const data = JSON.parse(report) as LighthouseModel.ReporterTypes.ReportJSON;
378
+ if (!data.lighthouseVersion) {
379
379
  return;
380
380
  }
381
- this.buildReportUI(data as LighthouseModel.ReporterTypes.ReportJSON);
381
+ data.isImported = true;
382
+ this.buildReportUI(data);
382
383
  }
383
384
 
384
385
  override elementsToRestoreScrollPositionsFor(): Element[] {
@@ -54,6 +54,8 @@ import * as Adorners from '../../ui/components/adorners/adorners.js';
54
54
  import * as Buttons from '../../ui/components/buttons/buttons.js';
55
55
  import * as RenderCoordinator from '../../ui/components/render_coordinator/render_coordinator.js';
56
56
  import * as DataGrid from '../../ui/legacy/components/data_grid/data_grid.js';
57
+ // eslint-disable-next-line @devtools/es-modules-import
58
+ import dataGridAiButtonStyles from '../../ui/legacy/components/data_grid/dataGridAiButton.css.js';
57
59
  import * as PerfUI from '../../ui/legacy/components/perf_ui/perf_ui.js';
58
60
  import * as Components from '../../ui/legacy/components/utils/utils.js';
59
61
  import * as UI from '../../ui/legacy/legacy.js';
@@ -560,6 +562,7 @@ export class NetworkLogView extends Common.ObjectWrapper.eventMixin<EventTypes,
560
562
  networkLogLargeRowsSetting: Common.Settings.Setting<boolean>) {
561
563
  super();
562
564
  this.registerRequiredCSS(networkLogViewStyles);
565
+ this.registerRequiredCSS(dataGridAiButtonStyles);
563
566
  this.setMinimumSize(50, 64);
564
567
 
565
568
  this.element.id = 'network-container';
@@ -268,21 +268,6 @@ td.time-column {
268
268
  vertical-align: sub;
269
269
  }
270
270
 
271
- .data-grid-data-grid-node .ai-button-container {
272
- display: none;
273
- float: right;
274
-
275
- devtools-floating-button {
276
- position: absolute;
277
- z-index: 999;
278
- margin-left: -17px;
279
- }
280
- }
281
-
282
- .data-grid-data-grid-node:hover .ai-button-container {
283
- display: inline-flex;
284
- }
285
-
286
271
  .image-network-icon-preview {
287
272
  inset: 0;
288
273
  margin: auto;