chrome-devtools-frontend 1.0.1605219 → 1.0.1606789

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 (78) hide show
  1. package/front_end/Images/src/dots-circle.svg +10 -0
  2. package/front_end/core/host/UserMetrics.ts +0 -1
  3. package/front_end/core/platform/api/HostRuntime.ts +9 -6
  4. package/front_end/core/platform/browser/HostRuntime.ts +2 -2
  5. package/front_end/core/platform/node/HostRuntime.ts +7 -7
  6. package/front_end/core/protocol_client/InspectorBackend.ts +4 -0
  7. package/front_end/core/root/ExperimentNames.ts +0 -1
  8. package/front_end/core/sdk/CrashReportContextModel.ts +28 -0
  9. package/front_end/core/sdk/sdk.ts +2 -2
  10. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshotWorkerDispatcher.ts +3 -3
  11. package/front_end/entrypoints/heap_snapshot_worker/heap_snapshot_worker-entrypoint.ts +5 -4
  12. package/front_end/entrypoints/main/MainImpl.ts +0 -101
  13. package/front_end/models/ai_assistance/ChangeManager.ts +6 -6
  14. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +112 -5
  15. package/front_end/models/ai_assistance/agents/AiAgent.ts +9 -25
  16. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +2 -2
  17. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +6 -3
  18. package/front_end/models/ai_assistance/agents/ConversationSummaryAgent.ts +10 -9
  19. package/front_end/models/ai_assistance/agents/ExecuteJavascript.ts +313 -0
  20. package/front_end/models/ai_assistance/agents/FileAgent.ts +15 -1
  21. package/front_end/models/ai_assistance/agents/NetworkAgent.ts +15 -1
  22. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +19 -8
  23. package/front_end/models/ai_assistance/agents/StylingAgent.ts +35 -278
  24. package/front_end/models/ai_assistance/ai_assistance.ts +0 -2
  25. package/front_end/models/javascript_metadata/NativeFunctions.js +1 -5
  26. package/front_end/models/web_mcp/WebMCPModel.ts +187 -0
  27. package/front_end/models/web_mcp/web_mcp.ts +9 -0
  28. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +8 -1
  29. package/front_end/panels/ai_assistance/ai_assistance.ts +1 -0
  30. package/front_end/panels/ai_assistance/components/AccessibilityAgentMarkdownRenderer.ts +88 -0
  31. package/front_end/panels/ai_assistance/components/ChatMessage.ts +149 -49
  32. package/front_end/panels/ai_assistance/components/ChatView.ts +11 -15
  33. package/front_end/panels/ai_assistance/components/ExportForAgentsDialog.ts +13 -4
  34. package/front_end/panels/ai_assistance/components/chatMessage.css +53 -1
  35. package/front_end/panels/ai_assistance/components/chatView.css +0 -10
  36. package/front_end/panels/ai_assistance/components/exportForAgentsDialog.css +13 -0
  37. package/front_end/panels/application/WebMCPView.ts +471 -25
  38. package/front_end/panels/application/webMCPView.css +53 -2
  39. package/front_end/panels/elements/ElementsTreeElement.ts +1 -1
  40. package/front_end/panels/elements/ElementsTreeOutline.ts +87 -0
  41. package/front_end/panels/elements/StylePropertiesSection.ts +0 -4
  42. package/front_end/panels/elements/elements.ts +3 -0
  43. package/front_end/panels/elements/elementsTreeOutline.css +21 -0
  44. package/front_end/panels/elements/stylePropertiesTreeOutline.css +7 -0
  45. package/front_end/panels/lighthouse/LighthousePanel.ts +7 -1
  46. package/front_end/panels/lighthouse/LighthouseProtocolService.ts +25 -2
  47. package/front_end/panels/profiler/HeapDetachedElementsView.ts +0 -4
  48. package/front_end/panels/profiler/HeapProfileView.ts +0 -4
  49. package/front_end/panels/profiler/HeapProfilerPanel.ts +4 -13
  50. package/front_end/panels/profiler/HeapSnapshotProxy.ts +2 -6
  51. package/front_end/panels/profiler/HeapSnapshotView.ts +19 -32
  52. package/front_end/panels/profiler/ProfileHeader.ts +1 -15
  53. package/front_end/panels/profiler/ProfileTypeRegistry.ts +4 -12
  54. package/front_end/panels/profiler/ProfileView.ts +1 -6
  55. package/front_end/panels/profiler/ProfilesPanel.ts +60 -7
  56. package/front_end/panels/protocol_monitor/ProtocolMonitor.ts +27 -22
  57. package/front_end/panels/timeline/EventsTimelineTreeView.ts +1 -1
  58. package/front_end/panels/timeline/ThirdPartyTreeView.ts +2 -2
  59. package/front_end/panels/timeline/TimelinePanel.ts +11 -153
  60. package/front_end/panels/timeline/TimelineTreeView.ts +147 -48
  61. package/front_end/panels/timeline/TimelineUIUtils.ts +2 -1
  62. package/front_end/panels/timeline/components/CWVMetrics.ts +18 -2
  63. package/front_end/panels/timeline/timeline-meta.ts +11 -0
  64. package/front_end/panels/timeline/utils/Helpers.ts +5 -0
  65. package/front_end/third_party/chromium/README.chromium +1 -1
  66. package/front_end/ui/components/markdown_view/CodeBlock.ts +47 -1
  67. package/front_end/ui/components/markdown_view/codeBlock.css +8 -0
  68. package/front_end/ui/legacy/FilterBar.ts +2 -0
  69. package/front_end/ui/legacy/SplitWidget.ts +33 -27
  70. package/front_end/ui/legacy/TabbedPane.ts +123 -3
  71. package/front_end/ui/legacy/Widget.ts +95 -48
  72. package/front_end/ui/legacy/components/source_frame/JSONView.ts +1 -1
  73. package/front_end/ui/legacy/components/utils/jsUtils.css +2 -0
  74. package/front_end/ui/legacy/infobar.css +1 -0
  75. package/front_end/ui/visual_logging/KnownContextValues.ts +6 -1
  76. package/package.json +1 -1
  77. package/front_end/core/sdk/WebMCPModel.ts +0 -104
  78. package/front_end/models/ai_assistance/ConversationHandler.ts +0 -325
@@ -13,7 +13,7 @@ import * as ARIAUtils from './ARIAUtils.js';
13
13
  import {Events as ResizerWidgetEvents, type ResizeUpdatePositionEvent, SimpleResizerWidget} from './ResizerWidget.js';
14
14
  import splitWidgetStyles from './splitWidget.css.js';
15
15
  import {ToolbarButton} from './Toolbar.js';
16
- import {Widget, WidgetElement} from './Widget.js';
16
+ import {registerWidgetConfig, Widget, widgetConfig, WidgetElement} from './Widget.js';
17
17
  import {Events as ZoomManagerEvents, ZoomManager} from './ZoomManager.js';
18
18
 
19
19
  export class SplitWidget extends Common.ObjectWrapper.eventMixin<EventTypes, typeof Widget>(Widget) {
@@ -882,32 +882,38 @@ export class SplitWidget extends Common.ObjectWrapper.eventMixin<EventTypes, typ
882
882
  export class SplitWidgetElement extends WidgetElement<SplitWidget> {
883
883
  static readonly observedAttributes = ['direction', 'sidebar-position', 'sidebar-initial-size', 'sidebar-visibility'];
884
884
 
885
- override createWidget(): SplitWidget {
886
- const vertical = this.getAttribute('direction') === 'column';
887
- const autoAdjustOrientation = this.getAttribute('direction') === 'auto';
888
- const secondIsSidebar = this.getAttribute('sidebar-position') === 'second';
889
- const settingName = this.getAttribute('name') ?? undefined;
890
- const sidebarSize = parseInt(this.getAttribute('sidebar-initial-size') || '', 10);
891
- const defaultSidebarWidth = !isNaN(sidebarSize) ? sidebarSize : undefined;
892
- const defaultSidebarHeight = !isNaN(sidebarSize) ? sidebarSize : undefined;
893
- const widget = new SplitWidget(
894
- vertical, secondIsSidebar, settingName, defaultSidebarWidth, defaultSidebarHeight,
895
- /* constraintsInDip=*/ false, this);
896
- if (this.getAttribute('sidebar-initial-size') === 'minimized') {
897
- widget.setSidebarMinimized(true);
898
- }
899
- if (autoAdjustOrientation) {
900
- widget.setAutoAdjustOrientation(true);
901
- }
902
- const sidebarHidden = this.getAttribute('sidebar-visibility') === 'hidden';
903
- if (sidebarHidden) {
904
- widget.hideSidebar();
905
- }
906
- widget.addEventListener(Events.SHOW_MODE_CHANGED, () => {
907
- this.dispatchEvent(new CustomEvent('change', {detail: widget.showMode()}));
908
- });
909
-
910
- return widget;
885
+ constructor() {
886
+ super();
887
+
888
+ registerWidgetConfig(this, widgetConfig(element => {
889
+ const vertical = element.getAttribute('direction') === 'column';
890
+ const autoAdjustOrientation = element.getAttribute('direction') === 'auto';
891
+ const secondIsSidebar = element.getAttribute('sidebar-position') === 'second';
892
+ const settingName = element.getAttribute('name') ?? undefined;
893
+ const sidebarSize = parseInt(element.getAttribute('sidebar-initial-size') || '', 10);
894
+ const defaultSidebarWidth = !isNaN(sidebarSize) ? sidebarSize : undefined;
895
+ const defaultSidebarHeight = !isNaN(sidebarSize) ? sidebarSize : undefined;
896
+
897
+ const widget = new SplitWidget(
898
+ vertical, secondIsSidebar, settingName, defaultSidebarWidth, defaultSidebarHeight,
899
+ /* constraintsInDip=*/ false, element as SplitWidgetElement);
900
+
901
+ if (element.getAttribute('sidebar-initial-size') === 'minimized') {
902
+ widget.setSidebarMinimized(true);
903
+ }
904
+ if (autoAdjustOrientation) {
905
+ widget.setAutoAdjustOrientation(true);
906
+ }
907
+ const sidebarHidden = element.getAttribute('sidebar-visibility') === 'hidden';
908
+ if (sidebarHidden) {
909
+ widget.hideSidebar();
910
+ }
911
+ widget.addEventListener(Events.SHOW_MODE_CHANGED, () => {
912
+ element.dispatchEvent(new CustomEvent('change', {detail: widget.showMode()}));
913
+ });
914
+
915
+ return widget;
916
+ }));
911
917
  }
912
918
 
913
919
  attributeChangedCallback(name: string, _oldValue: string, newValue: string): void {
@@ -4,8 +4,6 @@
4
4
 
5
5
  /* eslint-disable @devtools/no-imperative-dom-api, @devtools/no-lit-render-outside-of-view */
6
6
 
7
- import './Toolbar.js';
8
-
9
7
  import * as Common from '../../core/common/common.js';
10
8
  import * as i18n from '../../core/i18n/i18n.js';
11
9
  import * as Platform from '../../core/platform/platform.js';
@@ -22,7 +20,7 @@ import tabbedPaneStyles from './tabbedPane.css.js';
22
20
  import type {Toolbar} from './Toolbar.js';
23
21
  import {Tooltip} from './Tooltip.js';
24
22
  import {installDragHandle} from './UIUtils.js';
25
- import {VBox, type Widget} from './Widget.js';
23
+ import {registerWidgetConfig, VBox, Widget, widgetConfig, WidgetElement} from './Widget.js';
26
24
  import {Events as ZoomManagerEvents, ZoomManager} from './ZoomManager.js';
27
25
 
28
26
  const UIStrings = {
@@ -123,7 +121,26 @@ export class TabbedPane extends Common.ObjectWrapper.eventMixin<EventTypes, type
123
121
  this.contentElement.tabIndex = -1;
124
122
  this.setDefaultFocusedElement(this.contentElement);
125
123
  this.#headerElement = this.contentElement.createChild('div', 'tabbed-pane-header');
124
+ const leftSlot = document.createElement('slot');
125
+ leftSlot.name = 'left';
126
+ leftSlot.classList.add('tabbed-pane-left-toolbar');
127
+ this.#headerElement.appendChild(leftSlot);
128
+ leftSlot.addEventListener('slotchange', () => {
129
+ this.#leftToolbar = leftSlot.assignedElements()[0] as Toolbar | undefined;
130
+ this.requestUpdate();
131
+ });
132
+
126
133
  this.headerContentsElement = this.#headerElement.createChild('div', 'tabbed-pane-header-contents');
134
+
135
+ const rightSlot = document.createElement('slot');
136
+ rightSlot.name = 'right';
137
+ rightSlot.classList.add('tabbed-pane-right-toolbar');
138
+ this.#headerElement.appendChild(rightSlot);
139
+ rightSlot.addEventListener('slotchange', () => {
140
+ this.#rightToolbar = rightSlot.assignedElements()[0] as Toolbar | undefined;
141
+ this.requestUpdate();
142
+ });
143
+
127
144
  this.tabSlider = document.createElement('div');
128
145
  this.tabSlider.classList.add('tabbed-pane-tab-slider');
129
146
  this.tabsElement = this.headerContentsElement.createChild('div', 'tabbed-pane-header-tabs');
@@ -547,6 +564,9 @@ export class TabbedPane extends Common.ObjectWrapper.eventMixin<EventTypes, type
547
564
  if (existingTab) {
548
565
  this.changeTabView(tab.id, tab.view);
549
566
  this.changeTabTitle(tab.id, tab.title, tab.tabTooltip);
567
+ if (tab.jslogContext !== undefined) {
568
+ existingTab.jslogContext = tab.jslogContext;
569
+ }
550
570
  if (tab.isCloseable !== undefined) {
551
571
  existingTab.closeable = tab.isCloseable;
552
572
  }
@@ -1031,6 +1051,10 @@ export class TabbedPane extends Common.ObjectWrapper.eventMixin<EventTypes, type
1031
1051
  }
1032
1052
 
1033
1053
  leftToolbar(): Toolbar {
1054
+ if (!this.#leftToolbar) {
1055
+ const leftSlot = this.#headerElement.querySelector('slot[name="left"]') as HTMLSlotElement | null;
1056
+ this.#leftToolbar = leftSlot?.assignedElements()[0] as Toolbar | undefined;
1057
+ }
1034
1058
  if (!this.#leftToolbar) {
1035
1059
  this.#leftToolbar = document.createElement('devtools-toolbar');
1036
1060
  this.#leftToolbar.classList.add('tabbed-pane-left-toolbar');
@@ -1040,6 +1064,10 @@ export class TabbedPane extends Common.ObjectWrapper.eventMixin<EventTypes, type
1040
1064
  }
1041
1065
 
1042
1066
  rightToolbar(): Toolbar {
1067
+ if (!this.#rightToolbar) {
1068
+ const rightSlot = this.#headerElement.querySelector('slot[name="right"]') as HTMLSlotElement | null;
1069
+ this.#rightToolbar = rightSlot?.assignedElements()[0] as Toolbar | undefined;
1070
+ }
1043
1071
  if (!this.#rightToolbar) {
1044
1072
  this.#rightToolbar = document.createElement('devtools-toolbar');
1045
1073
  this.#rightToolbar.classList.add('tabbed-pane-right-toolbar');
@@ -1191,6 +1219,10 @@ export class TabbedPaneTab {
1191
1219
  return this.#jslogContext ?? (this.#id === 'console-view' ? 'console' : this.#id);
1192
1220
  }
1193
1221
 
1222
+ set jslogContext(jslogContext: string|undefined) {
1223
+ this.#jslogContext = jslogContext;
1224
+ }
1225
+
1194
1226
  get tabAnnotationIcon(): boolean {
1195
1227
  return this.#tabAnnotationIcon;
1196
1228
  }
@@ -1614,3 +1646,91 @@ export interface TabbedPaneTabDelegate {
1614
1646
  closeTabs(tabbedPane: TabbedPane, ids: string[]): void;
1615
1647
  onContextMenu(tabId: string, contextMenu: ContextMenu): void;
1616
1648
  }
1649
+
1650
+ export class TabbedPaneElement extends WidgetElement<TabbedPane> {
1651
+ readonly #tabObserver = new MutationObserver(() => this.#updateTabs());
1652
+
1653
+ constructor() {
1654
+ super();
1655
+
1656
+ registerWidgetConfig(this, widgetConfig(element => {
1657
+ const widget = new TabbedPane(element as TabbedPaneElement);
1658
+ const slot = widget.contentElement.querySelector('slot:not([name])');
1659
+ if (slot) {
1660
+ slot.addEventListener('slotchange', () => this.#syncTabs());
1661
+ }
1662
+ widget.addEventListener(Events.TabSelected, () => {
1663
+ const slot =
1664
+ widget.contentElement.querySelector('slot:not([name])') as HTMLSlotElement | null;
1665
+ const nodes = slot ? slot.assignedElements() : [];
1666
+ for (const child of nodes) {
1667
+ if (child.id === widget.selectedTabId) {
1668
+ child.setAttribute('selected', '');
1669
+ } else {
1670
+ child.removeAttribute('selected');
1671
+ }
1672
+ }
1673
+ });
1674
+ this.#syncTabs(widget);
1675
+ return widget;
1676
+ }));
1677
+ }
1678
+
1679
+ override disconnectedCallback(): void {
1680
+ super.disconnectedCallback();
1681
+ this.#tabObserver.disconnect();
1682
+ }
1683
+
1684
+ #syncTabs(widget = this.getWidget()): void {
1685
+ if (!widget) {
1686
+ return;
1687
+ }
1688
+ this.#updateObserver(widget);
1689
+ this.#updateTabs(widget);
1690
+ }
1691
+
1692
+ #updateObserver(widget: TabbedPane): void {
1693
+ this.#tabObserver.disconnect();
1694
+ const slot = widget.contentElement.querySelector('slot:not([name])') as HTMLSlotElement | null;
1695
+ const nodes = slot ? slot.assignedElements() : [];
1696
+ for (const child of nodes) {
1697
+ this.#tabObserver.observe(
1698
+ child, {attributes: true, attributeFilter: ['title', 'jslogcontext', 'selected', 'disabled']});
1699
+ }
1700
+ }
1701
+
1702
+ #updateTabs(widget = this.getWidget()): void {
1703
+ if (!widget) {
1704
+ return;
1705
+ }
1706
+ const tabs: TabInfo[] = [];
1707
+ const slot = widget.contentElement.querySelector('slot:not([name])') as HTMLSlotElement | null;
1708
+ const nodes = slot ? slot.assignedElements() : [];
1709
+ for (const child of nodes) {
1710
+ const id = child.id;
1711
+ const title = child.getAttribute('title') || '';
1712
+ const jslogContext = child.getAttribute('jslogcontext') || undefined;
1713
+ const selected = child.hasAttribute('selected');
1714
+ const enabled = !child.hasAttribute('disabled');
1715
+ const view = Widget.getOrCreateWidget(child as HTMLElement);
1716
+ view.setHideOnDetach();
1717
+ if (widget.selectedTabId !== id) {
1718
+ view.hideWidget();
1719
+ } else {
1720
+ view.showWidget();
1721
+ }
1722
+ tabs.push({
1723
+ id,
1724
+ title,
1725
+ view,
1726
+ jslogContext,
1727
+ selected,
1728
+ enabled,
1729
+ });
1730
+ }
1731
+
1732
+ widget.tabs = tabs;
1733
+ }
1734
+ }
1735
+
1736
+ customElements.define('devtools-tabbed-pane', TabbedPaneElement);
@@ -53,8 +53,8 @@ function assert(condition: unknown, message: string): void {
53
53
  }
54
54
  }
55
55
 
56
- type WidgetConstructor<WidgetT extends Widget> = new (element: WidgetElement<WidgetT>) => WidgetT;
57
- type WidgetProducer<WidgetT extends Widget> = (element: WidgetElement<WidgetT>) => WidgetT;
56
+ type WidgetConstructor<WidgetT extends Widget> = new (element: HTMLElement) => WidgetT;
57
+ type WidgetProducer<WidgetT extends Widget> = (element: HTMLElement) => WidgetT;
58
58
  type WidgetFactory<WidgetT extends Widget> = WidgetConstructor<WidgetT>|WidgetProducer<WidgetT>;
59
59
  type InferWidgetTFromFactory<F> = F extends WidgetFactory<infer WidgetT>? WidgetT : never;
60
60
 
@@ -154,64 +154,113 @@ function runNextUpdate(): void {
154
154
  }
155
155
 
156
156
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
157
- const widgetConfigs = new WeakMap<WidgetElement<any>, WidgetConfig<any>>();
157
+ const widgetConfigs = new WeakMap<HTMLElement, WidgetConfig<any>>();
158
158
 
159
- export class WidgetElement<WidgetT extends Widget> extends HTMLElement {
160
- createWidget(): WidgetT {
161
- const config = widgetConfigs.get(this) as WidgetConfig<WidgetT>;
162
- const widget = this.#instantiateWidget(config.widgetClass);
163
- if (config.widgetParams) {
164
- Object.assign(widget, config.widgetParams);
165
- }
166
- widget.requestUpdate();
167
- return widget;
159
+ export function registerWidgetConfig<WidgetT extends Widget>(
160
+ element: HTMLElement, config: WidgetConfig<WidgetT>): void {
161
+ if (!widgetConfigs.has(element)) {
162
+ setUpLifecycleTracking(element);
168
163
  }
164
+ widgetConfigs.set(element, config);
165
+ }
169
166
 
170
- #instantiateWidget(widgetClass: WidgetFactory<WidgetT>): WidgetT {
171
- if (!widgetClass) {
172
- throw new Error('No widgetClass defined');
173
- }
167
+ function instantiateWidget<WidgetT extends Widget>(element: HTMLElement, widgetConfig: WidgetConfig<WidgetT>): WidgetT {
168
+ if (!widgetConfig.widgetClass) {
169
+ throw new Error('No widgetClass defined');
170
+ }
174
171
 
175
- if (Widget.isPrototypeOf(widgetClass)) {
176
- const ctor = widgetClass as WidgetConstructor<WidgetT>;
177
- return new ctor(this);
178
- }
172
+ let newWidget: WidgetT;
173
+ if (Widget.isPrototypeOf(widgetConfig.widgetClass)) {
174
+ const ctor = widgetConfig.widgetClass as WidgetConstructor<WidgetT>;
175
+ newWidget = new ctor(element);
176
+ } else {
177
+ const factory = widgetConfig.widgetClass as WidgetProducer<WidgetT>;
178
+ newWidget = factory(element);
179
+ }
180
+
181
+ if (widgetConfig.widgetParams) {
182
+ Object.assign(newWidget, widgetConfig.widgetParams);
183
+ }
184
+ newWidget.requestUpdate();
185
+ return newWidget;
186
+ }
179
187
 
180
- const factory = widgetClass as WidgetProducer<WidgetT>;
181
- return factory(this);
188
+ function setUpLifecycleTracking<WidgetT extends Widget>(element: HTMLElement): void {
189
+ let tracker: WidgetElement<WidgetT>;
190
+ if (element instanceof WidgetElement) {
191
+ tracker = element as WidgetElement<WidgetT>;
192
+ } else {
193
+ tracker = document.createElement('devtools-widget') as WidgetElement<WidgetT>;
194
+ tracker.style.display = 'none';
195
+ element.appendChild(tracker);
182
196
  }
183
197
 
198
+ tracker.onDisconnect = () => {
199
+ const widget = Widget.get(element);
200
+ if (widget) {
201
+ widget.setHideOnDetach();
202
+ widget.detach();
203
+ }
204
+ };
205
+ tracker.onConnect = () => {
206
+ let widget = Widget.get(element) as WidgetT;
207
+ if (!widget) {
208
+ const config = widgetConfigs.get(element);
209
+ if (!config) {
210
+ throw new Error('No widgetConfig defined');
211
+ }
212
+ widget = instantiateWidget(element, config);
213
+ }
214
+ const parent = element.parentElementOrShadowHost() as HTMLElement | null;
215
+ if (!parent) {
216
+ widget.markAsRoot();
217
+ }
218
+ widget.show(parent as HTMLElement, undefined, /* suppressOrphanWidgetError= */ true);
219
+ };
220
+ }
221
+
222
+ export class WidgetElement<WidgetT extends Widget> extends HTMLElement {
223
+ onDisconnect?: () => void;
224
+ onConnect?: () => void;
225
+ #disconnectTimeout?: ReturnType<typeof setTimeout>;
226
+
184
227
  getWidget(): WidgetT|undefined {
185
228
  return Widget.get(this) as WidgetT | undefined;
186
229
  }
187
230
 
188
231
  connectedCallback(): void {
189
- const widget = Widget.getOrCreateWidget(this);
190
- if (!widget.element.parentElement) {
191
- widget.markAsRoot();
232
+ if (this.#disconnectTimeout) {
233
+ clearTimeout(this.#disconnectTimeout);
234
+ this.#disconnectTimeout = undefined;
235
+ }
236
+ if (this.onConnect) {
237
+ this.onConnect();
238
+ return;
192
239
  }
193
- widget.show(this.parentElement as HTMLElement, undefined, /* suppressOrphanWidgetError= */ true);
194
240
  }
195
241
 
196
242
  disconnectedCallback(): void {
197
- const widget = Widget.get(this);
198
- if (widget) {
199
- widget.setHideOnDetach();
200
- widget.detach();
243
+ if (this.onDisconnect) {
244
+ this.#disconnectTimeout = setTimeout(() => {
245
+ this.onDisconnect?.();
246
+ }, 0);
247
+ return;
201
248
  }
202
249
  }
203
250
 
204
251
  override appendChild<T extends Node>(child: T): T {
205
- if (child instanceof HTMLElement && child.tagName !== 'STYLE') {
206
- Widget.getOrCreateWidget(child).show(this);
252
+ const widget = child instanceof HTMLElement ? Widget.get(child) : null;
253
+ if (widget) {
254
+ widget.show(this, undefined, /* suppressOrphanWidgetError= */ true);
207
255
  return child;
208
256
  }
209
257
  return super.appendChild(child);
210
258
  }
211
259
 
212
260
  override insertBefore<T extends Node>(child: T, referenceChild: Node): T {
213
- if (child instanceof HTMLElement && child.tagName !== 'STYLE') {
214
- Widget.getOrCreateWidget(child).show(this, referenceChild, true);
261
+ const widget = child instanceof HTMLElement ? Widget.get(child) : null;
262
+ if (widget) {
263
+ widget.show(this, referenceChild, /* suppressOrphanWidgetError= */ true);
215
264
  return child;
216
265
  }
217
266
  return super.insertBefore(child, referenceChild);
@@ -220,7 +269,7 @@ export class WidgetElement<WidgetT extends Widget> extends HTMLElement {
220
269
  override removeChild<T extends Node>(child: T): T {
221
270
  const childWidget = Widget.get(child);
222
271
  if (childWidget) {
223
- childWidget.detach();
272
+ childWidget.detach(/* overrideHideOnDetach= */ true);
224
273
  return child;
225
274
  }
226
275
  return super.removeChild(child);
@@ -230,7 +279,7 @@ export class WidgetElement<WidgetT extends Widget> extends HTMLElement {
230
279
  for (const child of this.children) {
231
280
  const childWidget = Widget.get(child);
232
281
  if (childWidget) {
233
- childWidget.detach();
282
+ childWidget.detach(/* overrideHideOnDetach= */ true);
234
283
  }
235
284
  }
236
285
  super.removeChildren();
@@ -239,10 +288,9 @@ export class WidgetElement<WidgetT extends Widget> extends HTMLElement {
239
288
  override cloneNode(deep: boolean): Node {
240
289
  const clone = cloneCustomElement(this, deep) as WidgetElement<WidgetT>;
241
290
  const config = widgetConfigs.get(this);
242
- if (!config?.widgetClass) {
243
- throw new Error('No widgetClass defined');
291
+ if (config) {
292
+ registerWidgetConfig(clone, config);
244
293
  }
245
- widgetConfigs.set(clone, config);
246
294
  return clone;
247
295
  }
248
296
 
@@ -269,10 +317,8 @@ export class WidgetDirective extends Lit.Directive.Directive {
269
317
 
270
318
  override update(part: Lit.Directive.Part, [widgetClass, widgetParams]: Parameters<this['render']>): unknown {
271
319
  if (this.#partType === Lit.Directive.PartType.ELEMENT) {
272
- const element = (part as Lit.Directive.ElementPart).element;
273
- if (!(element instanceof WidgetElement)) {
274
- throw new Error('Widget directive must be used on a devtools-widget element.');
275
- }
320
+ const element = (part as Lit.Directive.ElementPart).element as HTMLElement;
321
+
276
322
  const config = widgetConfig(widgetClass, widgetParams);
277
323
  const oldConfig = widgetConfigs.get(element);
278
324
  const widget = Widget.get(element);
@@ -290,7 +336,7 @@ export class WidgetDirective extends Lit.Directive.Directive {
290
336
  widget.requestUpdate();
291
337
  }
292
338
  }
293
- widgetConfigs.set(element, config);
339
+ registerWidgetConfig(element, config);
294
340
  return Lit.nothing;
295
341
  }
296
342
  return this.render(widgetClass, widgetParams);
@@ -329,7 +375,7 @@ export function widgetRef<T extends Widget, Args extends unknown[]>(
329
375
  if (!(widget instanceof type)) {
330
376
  throw new Error(`Expected an element with a widget of type ${type.name} but got ${e?.constructor?.name}`);
331
377
  }
332
- callback(widget);
378
+ callback(widget as T);
333
379
  });
334
380
  }
335
381
 
@@ -491,10 +537,11 @@ export class Widget {
491
537
  if (widget) {
492
538
  return widget;
493
539
  }
494
- if (element instanceof WidgetElement) {
495
- return element.createWidget();
540
+ let config = widgetConfigs.get(element as WidgetElement<Widget>);
541
+ if (!config) {
542
+ config = widgetConfig(element => new Widget(element));
496
543
  }
497
- return new Widget(element);
544
+ return instantiateWidget(element as WidgetElement<Widget>, config);
498
545
  }
499
546
 
500
547
  markAsRoot(): void {
@@ -300,7 +300,7 @@ export class SearchableJsonView extends UI.SearchableView.SearchableView {
300
300
  jsonView.element.tabIndex = 0;
301
301
  }
302
302
 
303
- set jsonObject(obj: Object) {
303
+ set jsonObject(obj: Object|null|undefined) {
304
304
  const jsonView = new JSONView(new ParsedJSON(obj, '', ''));
305
305
  this.#jsonView.detach();
306
306
  this.#jsonView = jsonView;
@@ -13,6 +13,8 @@
13
13
  --display-ignored-row: none;
14
14
  --display-toggle-link: var(--override-display-stack-preview-toggle-link, none);
15
15
 
16
+ user-select: text;
17
+
16
18
  & > tfoot > tr {
17
19
  font-style: italic;
18
20
  display: var(--display-toggle-link);
@@ -125,6 +125,7 @@
125
125
 
126
126
  .infobar-info-actions {
127
127
  display: flex;
128
+ flex-wrap: wrap;
128
129
  gap: var(--sys-size-5);
129
130
  }
130
131
 
@@ -3584,6 +3584,7 @@ export const knownContextValues = new Set([
3584
3584
  'show-ad-highlights-true',
3585
3585
  'show-adorner-settings',
3586
3586
  'show-all',
3587
+ 'show-all-nodes',
3587
3588
  'show-all-properties',
3588
3589
  'show-as-javascript-object',
3589
3590
  'show-console-insight-teasers',
@@ -3622,7 +3623,6 @@ export const knownContextValues = new Set([
3622
3623
  'show-minimal-safe-area-for-maskable-icons',
3623
3624
  'show-more',
3624
3625
  'show-network-requests',
3625
- 'show-option-to-expose-internals-in-heap-snapshot',
3626
3626
  'show-overrides',
3627
3627
  'show-paint-rects',
3628
3628
  'show-paint-rects-true',
@@ -4332,6 +4332,11 @@ export const knownContextValues = new Set([
4332
4332
  'webauthn.remove-credential',
4333
4333
  'webgl-error-fired',
4334
4334
  'webgl-warning-fired',
4335
+ 'webmcp.declarative',
4336
+ 'webmcp.error',
4337
+ 'webmcp.imperative',
4338
+ 'webmcp.pending',
4339
+ 'webmcp.success',
4335
4340
  'webp-format-disabled',
4336
4341
  'webp-format-disabled-true',
4337
4342
  'websocket',
package/package.json CHANGED
@@ -105,5 +105,5 @@
105
105
  "flat-cache": "6.1.12"
106
106
  }
107
107
  },
108
- "version": "1.0.1605219"
108
+ "version": "1.0.1606789"
109
109
  }
@@ -1,104 +0,0 @@
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 type * as ProtocolProxyApi from '../../generated/protocol-proxy-api.js';
6
- import type * as Protocol from '../../generated/protocol.js';
7
- import type * as Common from '../common/common.js';
8
-
9
- import {Events as RuntimeModelEvents, type ExecutionContext, RuntimeModel} from './RuntimeModel.js';
10
- import {SDKModel} from './SDKModel.js';
11
- import {Capability, type Target} from './Target.js';
12
-
13
- export const enum Events {
14
- TOOLS_ADDED = 'ToolsAdded',
15
- TOOLS_REMOVED = 'ToolsRemoved',
16
- }
17
-
18
- export interface EventTypes {
19
- [Events.TOOLS_ADDED]: ReadonlyArray<Readonly<Protocol.WebMCP.Tool>>;
20
- [Events.TOOLS_REMOVED]: ReadonlyArray<Readonly<Protocol.WebMCP.Tool>>;
21
- }
22
-
23
- export class WebMCPModel extends SDKModel<EventTypes> {
24
- readonly #tools = new Map<Protocol.Page.FrameId, Map<string, Protocol.WebMCP.Tool>>();
25
- readonly agent: ProtocolProxyApi.WebMCPApi;
26
- #enabled = false;
27
-
28
- constructor(target: Target) {
29
- super(target);
30
- this.agent = target.webMCPAgent();
31
- target.registerWebMCPDispatcher(new WebMCPDispatcher(this));
32
-
33
- const runtimeModel = target.model(RuntimeModel);
34
- if (runtimeModel) {
35
- runtimeModel.addEventListener(
36
- RuntimeModelEvents.ExecutionContextDestroyed, this.#executionContextDestroyed, this);
37
- }
38
-
39
- void this.enable();
40
- }
41
-
42
- get tools(): IteratorObject<Protocol.WebMCP.Tool> {
43
- return this.#tools.values().flatMap(toolMap => toolMap.values());
44
- }
45
-
46
- async enable(): Promise<void> {
47
- if (this.#enabled) {
48
- return;
49
- }
50
- await this.agent.invoke_enable();
51
- this.#enabled = true;
52
- }
53
-
54
- #executionContextDestroyed(event: Common.EventTarget.EventTargetEvent<ExecutionContext>): void {
55
- const executionContext = event.data;
56
- if (executionContext.isDefault && executionContext.frameId) {
57
- const frameTools = this.#tools.get(executionContext.frameId);
58
- if (frameTools) {
59
- const toolsToRemove = [...frameTools.values()];
60
- this.#tools.delete(executionContext.frameId);
61
- this.dispatchEventToListeners(Events.TOOLS_REMOVED, toolsToRemove);
62
- }
63
- }
64
- }
65
-
66
- onToolsRemoved(tools: Protocol.WebMCP.Tool[]): void {
67
- const deletedTools = tools.filter(tool => this.#tools.get(tool.frameId)?.delete(tool.name));
68
- this.dispatchEventToListeners(Events.TOOLS_REMOVED, deletedTools);
69
- }
70
-
71
- onToolsAdded(tools: Protocol.WebMCP.Tool[]): void {
72
- for (const tool of tools) {
73
- const frameTools = this.#tools.get(tool.frameId) ?? new Map();
74
- if (!this.#tools.has(tool.frameId)) {
75
- this.#tools.set(tool.frameId, frameTools);
76
- }
77
- frameTools.set(tool.name, tool);
78
- }
79
- this.dispatchEventToListeners(Events.TOOLS_ADDED, tools);
80
- }
81
- }
82
-
83
- class WebMCPDispatcher implements ProtocolProxyApi.WebMCPDispatcher {
84
- readonly #model: WebMCPModel;
85
- constructor(model: WebMCPModel) {
86
- this.#model = model;
87
- }
88
-
89
- toolsAdded(params: Protocol.WebMCP.ToolsAddedEvent): void {
90
- this.#model.onToolsAdded(params.tools);
91
- }
92
-
93
- toolsRemoved(params: Protocol.WebMCP.ToolsRemovedEvent): void {
94
- this.#model.onToolsRemoved(params.tools);
95
- }
96
-
97
- toolInvoked(): void {
98
- }
99
-
100
- toolResponded(): void {
101
- }
102
- }
103
-
104
- SDKModel.register(WebMCPModel, {capabilities: Capability.WEB_MCP, autostart: true});