chrome-devtools-frontend 1.0.1606789 → 1.0.1608868

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 (75) hide show
  1. package/docs/ui_engineering.md +2 -0
  2. package/eslint.config.mjs +2 -0
  3. package/front_end/core/common/VersionController.ts +18 -1
  4. package/front_end/core/host/UserMetrics.ts +0 -2
  5. package/front_end/core/platform/node/HostRuntime.ts +1 -1
  6. package/front_end/core/root/ExperimentNames.ts +0 -2
  7. package/front_end/core/sdk/AccessibilityModel.ts +13 -1
  8. package/front_end/core/sdk/ServiceWorkerCacheModel.ts +0 -13
  9. package/front_end/core/sdk/StorageBucketsModel.ts +0 -12
  10. package/front_end/entrypoints/main/MainImpl.ts +2 -11
  11. package/front_end/generated/InspectorBackendCommands.ts +3 -33
  12. package/front_end/generated/SupportedCSSProperties.js +8 -4
  13. package/front_end/generated/protocol-mapping.d.ts +7 -26
  14. package/front_end/generated/protocol-proxy-api.d.ts +5 -24
  15. package/front_end/generated/protocol.ts +2 -296
  16. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +2 -2
  17. package/front_end/models/ai_assistance/agents/BreakpointDebuggerAgent.ts +1 -1
  18. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +6 -6
  19. package/front_end/models/ai_assistance/agents/ConversationSummaryAgent.ts +2 -1
  20. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +46 -21
  21. package/front_end/{panels/profiler → models/heap_snapshot_model}/ChildrenProvider.ts +3 -4
  22. package/front_end/{panels/profiler → models/heap_snapshot_model}/HeapSnapshotProxy.ts +19 -36
  23. package/front_end/models/heap_snapshot_model/heap_snapshot_model.ts +4 -0
  24. package/front_end/models/javascript_metadata/NativeFunctions.js +0 -4
  25. package/front_end/panels/accessibility/AccessibilitySidebarView.ts +53 -10
  26. package/front_end/panels/accessibility/accessibility.ts +0 -2
  27. package/front_end/panels/accessibility/accessibilitySidebarView.css +10 -0
  28. package/front_end/panels/ai_assistance/components/ChatMessage.ts +6 -5
  29. package/front_end/panels/ai_assistance/components/WalkthroughView.ts +36 -17
  30. package/front_end/panels/ai_assistance/components/chatMessage.css +7 -0
  31. package/front_end/panels/ai_assistance/components/walkthroughView.css +39 -11
  32. package/front_end/panels/application/CrashReportContextView.ts +237 -0
  33. package/front_end/panels/application/IndexedDBModel.ts +0 -10
  34. package/front_end/panels/application/InterestGroupStorageModel.ts +0 -11
  35. package/front_end/panels/application/ReportingApiTreeElement.ts +40 -1
  36. package/front_end/panels/application/SharedStorageModel.ts +0 -11
  37. package/front_end/panels/application/WebMCPView.ts +389 -52
  38. package/front_end/panels/application/application.ts +4 -0
  39. package/front_end/panels/application/components/CrashReportContextGrid.ts +154 -0
  40. package/front_end/panels/application/components/components.ts +2 -0
  41. package/front_end/panels/application/webMCPView.css +107 -10
  42. package/front_end/panels/common/AiCodeCompletionDisclaimer.ts +12 -2
  43. package/front_end/panels/elements/AccessibilityTreeView.ts +10 -6
  44. package/front_end/panels/elements/ElementsPanel.ts +9 -50
  45. package/front_end/panels/elements/StylePropertiesSection.ts +1 -1
  46. package/front_end/panels/elements/elements-meta.ts +1 -0
  47. package/front_end/panels/elements/elementsPanel.css +4 -14
  48. package/front_end/panels/profiler/HeapSnapshotDataGrids.ts +23 -13
  49. package/front_end/panels/profiler/HeapSnapshotGridNodes.ts +30 -30
  50. package/front_end/panels/profiler/HeapSnapshotView.ts +16 -13
  51. package/front_end/panels/profiler/profiler.ts +0 -4
  52. package/front_end/panels/snippets/SnippetsQuickOpen.ts +5 -5
  53. package/front_end/panels/sources/FilteredUISourceCodeListProvider.ts +2 -2
  54. package/front_end/panels/sources/sources-meta.ts +7 -7
  55. package/front_end/panels/timeline/CompatibilityTracksAppender.ts +1 -3
  56. package/front_end/panels/timeline/ThirdPartyTreeView.ts +6 -1
  57. package/front_end/panels/timeline/ThreadAppender.ts +1 -3
  58. package/front_end/panels/timeline/TimelineController.ts +2 -2
  59. package/front_end/panels/timeline/TimelinePanel.ts +2 -4
  60. package/front_end/panels/timeline/TimelineTreeView.ts +32 -2
  61. package/front_end/panels/timeline/components/LiveMetricsView.ts +75 -54
  62. package/front_end/panels/timeline/components/timelineRangeSummaryView.css +8 -0
  63. package/front_end/panels/timeline/thirdPartyTreeView.css +0 -1
  64. package/front_end/panels/timeline/timeline-meta.ts +13 -0
  65. package/front_end/third_party/chromium/README.chromium +1 -1
  66. package/front_end/ui/legacy/TabbedPane.ts +3 -1
  67. package/front_end/ui/legacy/components/quick_open/CommandMenu.ts +3 -3
  68. package/front_end/ui/legacy/components/quick_open/FilteredListWidget.ts +2 -5
  69. package/front_end/ui/legacy/components/quick_open/HelpQuickOpen.ts +4 -4
  70. package/front_end/ui/legacy/components/quick_open/QuickOpen.ts +5 -1
  71. package/front_end/ui/legacy/components/utils/Linkifier.ts +2 -1
  72. package/front_end/ui/legacy/components/utils/imagePreview.css +1 -1
  73. package/front_end/ui/visual_logging/KnownContextValues.ts +2 -3
  74. package/package.json +1 -1
  75. package/front_end/panels/accessibility/AXBreadcrumbsPane.ts +0 -653
@@ -4,22 +4,30 @@
4
4
 
5
5
  import '../../ui/components/icon_button/icon_button.js';
6
6
  import '../../ui/components/lists/lists.js';
7
+ import '../../ui/components/node_text/node_text.js';
7
8
  import '../../ui/legacy/components/data_grid/data_grid.js';
8
9
  import '../../ui/legacy/legacy.js';
9
10
 
11
+ import * as Common from '../../core/common/common.js';
10
12
  import * as i18n from '../../core/i18n/i18n.js';
11
13
  import * as Platform from '../../core/platform/platform.js';
12
14
  import * as SDK from '../../core/sdk/sdk.js';
13
15
  import * as Protocol from '../../generated/protocol.js';
16
+ import type * as StackTrace from '../../models/stack_trace/stack_trace.js';
14
17
  import * as WebMCP from '../../models/web_mcp/web_mcp.js';
15
18
  import * as Adorners from '../../ui/components/adorners/adorners.js';
16
19
  import * as Buttons from '../../ui/components/buttons/buttons.js';
17
20
  import type * as IconButton from '../../ui/components/icon_button/icon_button.js';
21
+ import type * as NodeText from '../../ui/components/node_text/node_text.js';
22
+ import * as ObjectUI from '../../ui/legacy/components/object_ui/object_ui.js';
23
+ import * as Components from '../../ui/legacy/components/utils/utils.js';
18
24
  import * as UI from '../../ui/legacy/legacy.js';
19
25
  import {
20
26
  Directives,
21
27
  html,
28
+ nothing,
22
29
  render,
30
+ type TemplateResult,
23
31
  } from '../../ui/lit/lit.js';
24
32
  import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
25
33
 
@@ -47,6 +55,18 @@ const UIStrings = {
47
55
  * @description Text to display when no calls have been made
48
56
  */
49
57
  noCallsPlaceholder: 'Start interacting with your `WebMCP` agent to see real-time tool calls and executions here.',
58
+ /**
59
+ * @description Text for the header of the tool details section
60
+ */
61
+ toolDetails: 'Details',
62
+ /**
63
+ * @description Text for the link to reveal the tool's DOM node in the Elements panel
64
+ */
65
+ viewInElementsPanel: 'View in Elements panel',
66
+ /**
67
+ * @description Text for the frame of a tool
68
+ */
69
+ frame: 'Frame',
50
70
  /**
51
71
  * @description Text for the name of a tool call
52
72
  */
@@ -71,6 +91,10 @@ const UIStrings = {
71
91
  * @description Tooltip for the clear log button
72
92
  */
73
93
  clearLog: 'Clear log',
94
+ /**
95
+ * @description Text to close something
96
+ */
97
+ close: 'Close',
74
98
  /**
75
99
  * @description Placeholder for the filter input
76
100
  */
@@ -134,6 +158,7 @@ const UIStrings = {
134
158
  } as const;
135
159
  const str_ = i18n.i18n.registerUIStrings('panels/application/WebMCPView.ts', UIStrings);
136
160
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
161
+ const {widget} = UI.Widget;
137
162
 
138
163
  export interface FilterState {
139
164
  text: string;
@@ -159,6 +184,10 @@ export interface FilterMenuButtons {
159
184
  }
160
185
  export interface ViewInput {
161
186
  tools: WebMCP.WebMCPModel.Tool[];
187
+ selectedTool: WebMCP.WebMCPModel.Tool|null;
188
+ onToolSelect: (tool: WebMCP.WebMCPModel.Tool|null) => void;
189
+ selectedCall: WebMCP.WebMCPModel.Call|null;
190
+ onCallSelect: (call: WebMCP.WebMCPModel.Call|null) => void;
162
191
  filters: FilterState;
163
192
  filterButtons: FilterMenuButtons;
164
193
  onClearLogClick: () => void;
@@ -271,6 +300,24 @@ function getIconGroupsFromStats(toolStats: ReturnType<typeof calculateToolStats>
271
300
  }
272
301
  return groups;
273
302
  }
303
+
304
+ export function parsePayload(payload?: unknown): {
305
+ valueObject: unknown,
306
+ valueString: string|undefined,
307
+ } {
308
+ if (payload === undefined) {
309
+ return {valueObject: undefined, valueString: undefined};
310
+ }
311
+ if (typeof payload === 'string') {
312
+ try {
313
+ return {valueObject: JSON.parse(payload), valueString: undefined};
314
+ } catch {
315
+ return {valueObject: undefined, valueString: payload};
316
+ }
317
+ }
318
+ return {valueObject: payload, valueString: undefined};
319
+ }
320
+
274
321
  export const DEFAULT_VIEW: View = (input, output, target) => {
275
322
  const tools = input.tools;
276
323
  const stats = calculateToolStats(input.toolCalls);
@@ -332,36 +379,77 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
332
379
  </devtools-toolbar>
333
380
  </div>
334
381
  ${input.toolCalls.length > 0 ? html`
335
- <devtools-data-grid striped>
336
- <table>
337
- <tr>
338
- <th id="name" weight="20">
339
- ${i18nString(UIStrings.name)}
340
- </th>
341
- <th id="status" weight="20">${i18nString(UIStrings.status)}</th>
342
- <th id="input" weight="30">${i18nString(UIStrings.input)}</th>
343
- <th id="output" weight="30">${i18nString(UIStrings.output)}</th>
344
- </tr>
345
- ${Directives.repeat(input.toolCalls, call => call.invocationId + '-' + (call.result?.status ?? ''), call => html`
346
- <tr class=${
347
- call.result?.status === Protocol.WebMCP.InvocationStatus.Error ? 'status-error' :
348
- call.result?.status === Protocol.WebMCP.InvocationStatus.Canceled ? 'status-cancelled' : ''
349
- }>
382
+ <devtools-split-view name="webmcp-call-split-view"
383
+ direction="column"
384
+ sidebar-position="second"
385
+ sidebar-visibility=${input.selectedCall ? 'show' : 'hidden'}>
386
+ <div slot="main" style="display: flex; flex-direction: column; overflow: hidden; height: 100%;">
387
+ <devtools-data-grid striped .template=${html`
388
+ <table>
350
389
  <style>${webMCPViewStyles}</style>
351
- <td>${call.tool.name}</td>
352
- <td>
353
- <div class="status-cell">
354
- ${iconName(call) ? html`<devtools-icon class="small" name=${iconName(call)}></devtools-icon>`
355
- : ''}
356
- <span>${statusString(call)}</span>
357
- </div>
358
- </td>
359
- <td>${call.input}</td>
360
- <td>${call.result?.output ? JSON.stringify(call.result.output) : call.result?.errorText ?? ''}</td>
361
- </tr>
362
- `)}
363
- </table>
364
- </devtools-data-grid>
390
+ <tr>
391
+ <th id="name" weight="20">
392
+ ${i18nString(UIStrings.name)}
393
+ </th>
394
+ <th id="status" weight="20">${i18nString(UIStrings.status)}</th>
395
+ ${!input.selectedCall ? html`
396
+ <th id="input" weight="30">${i18nString(UIStrings.input)}</th>
397
+ <th id="output" weight="30">${i18nString(UIStrings.output)}</th>
398
+ ` : nothing}
399
+ </tr>
400
+ ${Directives.repeat(input.toolCalls, call => call.invocationId + '-' + (call.result?.status ?? ''),
401
+ call => html`
402
+ <tr class=${Directives.classMap({
403
+ 'status-error': call.result?.status === Protocol.WebMCP.InvocationStatus.Error,
404
+ 'status-cancelled': call.result?.status === Protocol.WebMCP.InvocationStatus.Canceled,
405
+ selected: call === input.selectedCall,
406
+ })} @click=${() => input.onCallSelect(call)}>
407
+ <td>${call.tool.name}</td>
408
+ <td>
409
+ <div class="status-cell">
410
+ ${iconName(call) ? html`<devtools-icon class="small" name=${iconName(call)}></devtools-icon>`
411
+ : ''}
412
+ <span>${statusString(call)}</span>
413
+ </div>
414
+ </td>
415
+ ${!input.selectedCall ? html`
416
+ <td>${call.input}</td>
417
+ <td>${call.result?.output ? JSON.stringify(call.result.output)
418
+ : call.result?.errorText ?? ''}</td>
419
+ ` : nothing}
420
+ </tr>
421
+ `)}
422
+ </table>`}>
423
+ </devtools-data-grid>
424
+ </div>
425
+ <div slot="sidebar" style="height: 100%; display: flex; flex-direction: column; overflow: hidden;">
426
+ <devtools-tabbed-pane class="call-details-tabbed-pane">
427
+ <devtools-button
428
+ slot="left"
429
+ .iconName=${'cross'}
430
+ .size=${Buttons.Button.Size.SMALL}
431
+ .variant=${Buttons.Button.Variant.ICON}
432
+ title=${i18nString(UIStrings.close)}
433
+ @click=${() => input.onCallSelect(null)}
434
+ ></devtools-button>
435
+ <devtools-widget
436
+ id="details"
437
+ title=${i18nString(UIStrings.toolDetails)}
438
+ ${widget(ToolDetailsWidget, {tool: input.selectedCall?.tool})}>
439
+ </devtools-widget>
440
+ <devtools-widget
441
+ id="inputs"
442
+ title=${i18nString(UIStrings.input)}
443
+ ${widget(PayloadWidget, parsePayload(input.selectedCall?.input))}>
444
+ </devtools-widget>
445
+ <devtools-widget
446
+ id="outputs"
447
+ title=${i18nString(UIStrings.output)}
448
+ ${widget(PayloadWidget, parsePayload(input.selectedCall?.result?.output))}>
449
+ </devtools-widget>
450
+ </devtools-tabbed-pane>
451
+ </div>
452
+ </devtools-split-view>
365
453
  <div class="webmcp-toolbar-container" role="toolbar">
366
454
  <devtools-toolbar class="webmcp-toolbar" role="presentation" wrappable>
367
455
  <span class="toolbar-text">${i18nString(UIStrings.totalCalls, {PH1: stats.total})}</span>
@@ -379,30 +467,50 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
379
467
  text: i18nString(UIStrings.noCallsPlaceholder)})}
380
468
  `}
381
469
  </div>
382
- <div slot="sidebar" class="tool-list">
383
- <div class="section-title">${i18nString(UIStrings.toolRegistry)}</div>
384
- ${tools.length === 0 ? html`
385
- ${UI.Widget.widget(UI.EmptyWidget.EmptyWidget, {header: i18nString(UIStrings.noToolsPlaceholderTitle),
386
- text: i18nString(UIStrings.noToolsPlaceholder)})}
387
- ` : html`
388
- <devtools-list>
389
- ${tools.map(tool => {
390
- const toolStats = calculateToolStats(input.toolCalls.filter(c => c.tool === tool));
391
- const groups = getIconGroupsFromStats(toolStats);
392
- return html`
393
- <div class="tool-item">
394
- <div class="tool-name-container">
395
- <div class="tool-name source-code">${tool.name}</div>
396
- ${groups.length > 0 ? html`<icon-button .data=${
397
- {groups, compact: false} as IconButton.IconButton.IconButtonData}></icon-button>` : ''}
470
+ <devtools-split-view slot="sidebar"
471
+ direction="column"
472
+ sidebar-position="second"
473
+ name="webmcp-details-split-view"
474
+ sidebar-visibility=${input.selectedTool ? 'show' : 'hidden'}>
475
+ <div slot="main" class="tool-list">
476
+ <div class="section-title">${i18nString(UIStrings.toolRegistry)}</div>
477
+ ${tools.length === 0 ? html`
478
+ ${UI.Widget.widget(UI.EmptyWidget.EmptyWidget, {header: i18nString(UIStrings.noToolsPlaceholderTitle),
479
+ text: i18nString(UIStrings.noToolsPlaceholder)})}
480
+ ` : html`
481
+ <devtools-list>
482
+ ${tools.map(tool => {
483
+ const toolStats = calculateToolStats(input.toolCalls.filter(c => c.tool === tool));
484
+ const groups = getIconGroupsFromStats(toolStats);
485
+ return html`
486
+ <div class=${Directives.classMap({'tool-item': true, selected: tool === input.selectedTool})}
487
+ @click=${() => input.onToolSelect(tool)}>
488
+ <div class="tool-name-container">
489
+ <div class="tool-name source-code">${tool.name}</div>
490
+ ${groups.length > 0 ? html`<icon-button .data=${
491
+ {groups, compact: false} as IconButton.IconButton.IconButtonData}></icon-button>` : ''}
492
+ </div>
493
+ <div class="tool-description">${tool.description}</div>
398
494
  </div>
399
- <div class="tool-description">${tool.description}</div>
400
- </div>
401
- `;
402
- })}
403
- </devtools-list>
404
- `}
405
- </div>
495
+ `;
496
+ })}
497
+ </devtools-list>
498
+ `}
499
+ </div>
500
+ <div slot="sidebar" class="tool-details">
501
+ <div class="section-title">
502
+ <devtools-button
503
+ .iconName=${'cross'}
504
+ .size=${Buttons.Button.Size.SMALL}
505
+ .variant=${Buttons.Button.Variant.ICON}
506
+ title=${i18nString(UIStrings.close)}
507
+ @click=${() => input.onToolSelect(null)}
508
+ ></devtools-button>
509
+ <span>${i18nString(UIStrings.toolDetails)}</span>
510
+ </div>
511
+ ${widget(ToolDetailsWidget, {tool: input.selectedTool})}
512
+ </div>
513
+ </devtools-split-view>
406
514
  </devtools-split-view>
407
515
  `, target);
408
516
  // clang-format on
@@ -410,6 +518,8 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
410
518
 
411
519
  export class WebMCPView extends UI.Widget.VBox {
412
520
  readonly #view: View;
521
+ #selectedTool: WebMCP.WebMCPModel.Tool|null = null;
522
+ #selectedCall: WebMCP.WebMCPModel.Call|null = null;
413
523
 
414
524
  #filterState: FilterState = {
415
525
  text: '',
@@ -553,6 +663,16 @@ export class WebMCPView extends UI.Widget.VBox {
553
663
  const filteredCalls = filterToolCalls(toolCalls, this.#filterState);
554
664
  const input: ViewInput = {
555
665
  tools: this.#getTools(),
666
+ selectedTool: this.#selectedTool,
667
+ onToolSelect: tool => {
668
+ this.#selectedTool = tool;
669
+ this.requestUpdate();
670
+ },
671
+ selectedCall: this.#selectedCall,
672
+ onCallSelect: call => {
673
+ this.#selectedCall = call;
674
+ this.requestUpdate();
675
+ },
556
676
  toolCalls: filteredCalls,
557
677
  filters: this.#filterState,
558
678
  filterButtons: this.#filterButtons,
@@ -562,3 +682,220 @@ export class WebMCPView extends UI.Widget.VBox {
562
682
  this.#view(input, {}, this.contentElement);
563
683
  }
564
684
  }
685
+ export interface PayloadViewInput {
686
+ valueObject?: unknown;
687
+ valueString?: string;
688
+ }
689
+
690
+ export const PAYLOAD_DEFAULT_VIEW = (input: PayloadViewInput, output: object, target: HTMLElement): void => {
691
+ if (input.valueObject === undefined && input.valueString === undefined) {
692
+ render(nothing, target);
693
+ return;
694
+ }
695
+ const isParsable = input.valueObject !== undefined;
696
+
697
+ const createPayload = (parsedInput: unknown): TemplateResult => {
698
+ const object = new SDK.RemoteObject.LocalJSONObject(parsedInput);
699
+ const section =
700
+ new ObjectUI.ObjectPropertiesSection.RootElement(new ObjectUI.ObjectPropertiesSection.ObjectTree(object, {
701
+ readOnly: true,
702
+ propertiesMode: ObjectUI.ObjectPropertiesSection.ObjectPropertiesMode.OWN_AND_INTERNAL_AND_INHERITED,
703
+ }));
704
+ section.title = document.createTextNode(object.description);
705
+ section.listItemElement.classList.add('source-code', 'object-properties-section');
706
+ section.childrenListElement.classList.add('source-code', 'object-properties-section');
707
+ section.expand();
708
+ return html`<devtools-tree .template=${html`
709
+ <style>${ObjectUI.ObjectPropertiesSection.objectValueStyles}</style>
710
+ <style>${ObjectUI.ObjectPropertiesSection.objectPropertiesSectionStyles}</style>
711
+ <ul role="tree">
712
+ <devtools-tree-wrapper .treeElement=${section}></devtools-tree-wrapper>
713
+ </ul>
714
+ `}></devtools-tree>`;
715
+ };
716
+
717
+ const createSourceText = (text: string): TemplateResult => html`<div class="payload-value source-code">${text}</div>`;
718
+
719
+ render(
720
+ html`
721
+ <style>${webMCPViewStyles}</style>
722
+ <div class="call-payload-view">
723
+ <div class="call-payload-content">
724
+ ${
725
+ isParsable ? createPayload(input.valueObject) :
726
+ (input.valueString !== undefined ? createSourceText(input.valueString) : nothing)}
727
+ </div>
728
+ </div>
729
+ `,
730
+ target);
731
+ };
732
+
733
+ export class PayloadWidget extends UI.Widget.Widget {
734
+ #valueObject?: unknown;
735
+ #valueString?: string;
736
+ #view: typeof PAYLOAD_DEFAULT_VIEW;
737
+
738
+ constructor(element?: HTMLElement, view = PAYLOAD_DEFAULT_VIEW) {
739
+ super(element);
740
+ this.#view = view;
741
+ }
742
+
743
+ set valueObject(valueObject: unknown) {
744
+ this.#valueObject = valueObject;
745
+ this.requestUpdate();
746
+ }
747
+
748
+ get valueObject(): unknown {
749
+ return this.#valueObject;
750
+ }
751
+
752
+ set valueString(valueString: string|undefined) {
753
+ this.#valueString = valueString;
754
+ this.requestUpdate();
755
+ }
756
+
757
+ get valueString(): string|undefined {
758
+ return this.#valueString;
759
+ }
760
+
761
+ override wasShown(): void {
762
+ super.wasShown();
763
+ this.requestUpdate();
764
+ }
765
+
766
+ override performUpdate(): void {
767
+ const input: PayloadViewInput = {
768
+ valueObject: this.#valueObject,
769
+ valueString: this.#valueString,
770
+ };
771
+ this.#view(input, {}, this.contentElement);
772
+ }
773
+ }
774
+
775
+ export interface ToolDetailsViewInput {
776
+ tool: WebMCP.WebMCPModel.Tool|null|undefined;
777
+ origin: SDK.DOMModel.DOMNode|StackTrace.StackTrace.StackTrace|undefined;
778
+ highlightNode: (node: SDK.DOMModel.DOMNode) => void;
779
+ clearHighlight: () => void;
780
+ revealNode: (node: SDK.DOMModel.DOMNode) => void;
781
+ }
782
+
783
+ // clang-format off
784
+ const TOOL_DETAILS_VIEW = (input: ToolDetailsViewInput, output: undefined, target: HTMLElement): void => {
785
+ if (!input.tool) {
786
+ render(nothing, target);
787
+ return;
788
+ }
789
+ const tool = input.tool;
790
+ const origin = input.origin;
791
+ render(html`
792
+ <style>${webMCPViewStyles}</style>
793
+ <div class="tool-details-grid">
794
+ <div class="label">Name</div>
795
+ <div class="value source-code">${tool.name}</div>
796
+ <div class="label">Description</div>
797
+ <div class="value">${tool.description}</div>
798
+ ${tool.frame ? html`
799
+ <div class="label">${i18nString(UIStrings.frame)}</div>
800
+ <div class="value">${Components.Linkifier.Linkifier.linkifyRevealable(tool.frame, tool.frame.displayName())}</div>
801
+ ` : nothing}
802
+ ${origin instanceof SDK.DOMModel.DOMNode ? html`
803
+ <div class="label">Origin</div>
804
+ <div class="value tool-origin-container">
805
+ <span
806
+ class="node-text-container source-code tool-origin-node"
807
+ data-label="true"
808
+ @mouseenter=${() => input.highlightNode(origin)}
809
+ @mouseleave=${input.clearHighlight}>
810
+ <devtools-node-text .data=${{
811
+ nodeId: origin.getAttribute('id') || undefined,
812
+ nodeTitle: origin.nodeNameInCorrectCase(),
813
+ nodeClasses: origin.getAttribute('class')?.split(/\s+/).filter(s => Boolean(s))
814
+ } as NodeText.NodeText.NodeTextData}>
815
+ </devtools-node-text>
816
+ </span>
817
+ <devtools-button class="show-element"
818
+ .title=${i18nString(UIStrings.viewInElementsPanel)}
819
+ aria-label=${i18nString(UIStrings.viewInElementsPanel)}
820
+ .iconName=${'select-element'}
821
+ .jslogContext=${'elements.select-element'}
822
+ .size=${Buttons.Button.Size.SMALL}
823
+ .variant=${Buttons.Button.Variant.ICON}
824
+ @click=${() => input.revealNode(origin)}
825
+ ></devtools-button>
826
+ </div>` : origin ? html`
827
+ <div class="label">Origin</div>
828
+ <div class="value">
829
+ ${widget(Components.JSPresentationUtils.StackTracePreviewContent,
830
+ {stackTrace: origin, options: { expandable: true}})}
831
+ </div>` : nothing}
832
+ </div>
833
+ `, target);
834
+ };
835
+ // clang-format on
836
+
837
+ export class ToolDetailsWidget extends UI.Widget.Widget {
838
+ #tool: WebMCP.WebMCPModel.Tool|null|undefined = null;
839
+ #origin: SDK.DOMModel.DOMNode|StackTrace.StackTrace.StackTrace|undefined;
840
+
841
+ #view: typeof TOOL_DETAILS_VIEW;
842
+
843
+ constructor(element?: HTMLElement, view: typeof TOOL_DETAILS_VIEW = TOOL_DETAILS_VIEW) {
844
+ super(element);
845
+ this.#view = view;
846
+ }
847
+
848
+ set tool(tool: WebMCP.WebMCPModel.Tool|null|undefined) {
849
+ if (this.#tool === tool) {
850
+ return;
851
+ }
852
+ this.#tool = tool;
853
+ this.#origin = undefined;
854
+
855
+ if (this.#tool) {
856
+ void this.#setToolOrigin(this.#tool);
857
+ }
858
+ this.requestUpdate();
859
+ }
860
+
861
+ async #setToolOrigin(tool: WebMCP.WebMCPModel.Tool): Promise<void> {
862
+ const origin = await (tool.node ? tool.node.resolvePromise() : tool.stackTrace);
863
+ if (this.#tool === tool && origin) {
864
+ this.#origin = origin;
865
+ this.requestUpdate();
866
+ }
867
+ }
868
+
869
+ get tool(): WebMCP.WebMCPModel.Tool|null|undefined {
870
+ return this.#tool;
871
+ }
872
+
873
+ #highlightNode = (node: SDK.DOMModel.DOMNode): void => {
874
+ node.highlight();
875
+ };
876
+
877
+ #clearHighlight = (): void => {
878
+ SDK.OverlayModel.OverlayModel.hideDOMNodeHighlight();
879
+ };
880
+
881
+ #revealNode = (node: SDK.DOMModel.DOMNode): void => {
882
+ void Common.Revealer.reveal(node);
883
+ void node.scrollIntoView();
884
+ };
885
+
886
+ override performUpdate(): void {
887
+ const viewInput = {
888
+ tool: this.#tool,
889
+ origin: this.#origin,
890
+ highlightNode: this.#highlightNode,
891
+ clearHighlight: this.#clearHighlight,
892
+ revealNode: this.#revealNode,
893
+ };
894
+ this.#view(viewInput, undefined, this.contentElement);
895
+ }
896
+
897
+ override wasShown(): void {
898
+ super.wasShown();
899
+ this.requestUpdate();
900
+ }
901
+ }
@@ -8,6 +8,7 @@ import * as BackgroundServiceModel from './BackgroundServiceModel.js';
8
8
  import * as BackgroundServiceView from './BackgroundServiceView.js';
9
9
  import * as BounceTrackingMitigationsTreeElement from './BounceTrackingMitigationsTreeElement.js';
10
10
  import * as CookieItemsView from './CookieItemsView.js';
11
+ import * as CrashReportContextView from './CrashReportContextView.js';
11
12
  import * as DeviceBoundSessionsModel from './DeviceBoundSessionsModel.js';
12
13
  import * as DeviceBoundSessionsTreeElement from './DeviceBoundSessionsTreeElement.js';
13
14
  import * as DeviceBoundSessionsView from './DeviceBoundSessionsView.js';
@@ -26,6 +27,7 @@ import * as OpenedWindowDetailsView from './OpenedWindowDetailsView.js';
26
27
  import * as OriginTrialTreeView from './OriginTrialTreeView.js';
27
28
  import * as PreloadingView from './preloading/PreloadingView.js';
28
29
  import * as PreloadingTreeElement from './PreloadingTreeElement.js';
30
+ import * as ReportingApiTreeElement from './ReportingApiTreeElement.js';
29
31
  import * as ReportingApiView from './ReportingApiView.js';
30
32
  import * as ResourcesPanel from './ResourcesPanel.js';
31
33
  import * as ServiceWorkerCacheViews from './ServiceWorkerCacheViews.js';
@@ -50,6 +52,7 @@ export {
50
52
  BackgroundServiceView,
51
53
  BounceTrackingMitigationsTreeElement,
52
54
  CookieItemsView,
55
+ CrashReportContextView,
53
56
  DeviceBoundSessionsModel,
54
57
  DeviceBoundSessionsTreeElement,
55
58
  DeviceBoundSessionsView,
@@ -68,6 +71,7 @@ export {
68
71
  OriginTrialTreeView,
69
72
  PreloadingTreeElement,
70
73
  PreloadingView,
74
+ ReportingApiTreeElement,
71
75
  ReportingApiView,
72
76
  ResourcesPanel,
73
77
  ServiceWorkerCacheViews,