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.
- package/front_end/Images/src/dots-circle.svg +10 -0
- package/front_end/core/host/UserMetrics.ts +0 -1
- package/front_end/core/platform/api/HostRuntime.ts +9 -6
- package/front_end/core/platform/browser/HostRuntime.ts +2 -2
- package/front_end/core/platform/node/HostRuntime.ts +7 -7
- package/front_end/core/protocol_client/InspectorBackend.ts +4 -0
- package/front_end/core/root/ExperimentNames.ts +0 -1
- package/front_end/core/sdk/CrashReportContextModel.ts +28 -0
- package/front_end/core/sdk/sdk.ts +2 -2
- package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshotWorkerDispatcher.ts +3 -3
- package/front_end/entrypoints/heap_snapshot_worker/heap_snapshot_worker-entrypoint.ts +5 -4
- package/front_end/entrypoints/main/MainImpl.ts +0 -101
- package/front_end/models/ai_assistance/ChangeManager.ts +6 -6
- package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +112 -5
- package/front_end/models/ai_assistance/agents/AiAgent.ts +9 -25
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +2 -2
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +6 -3
- package/front_end/models/ai_assistance/agents/ConversationSummaryAgent.ts +10 -9
- package/front_end/models/ai_assistance/agents/ExecuteJavascript.ts +313 -0
- package/front_end/models/ai_assistance/agents/FileAgent.ts +15 -1
- package/front_end/models/ai_assistance/agents/NetworkAgent.ts +15 -1
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +19 -8
- package/front_end/models/ai_assistance/agents/StylingAgent.ts +35 -278
- package/front_end/models/ai_assistance/ai_assistance.ts +0 -2
- package/front_end/models/javascript_metadata/NativeFunctions.js +1 -5
- package/front_end/models/web_mcp/WebMCPModel.ts +187 -0
- package/front_end/models/web_mcp/web_mcp.ts +9 -0
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +8 -1
- package/front_end/panels/ai_assistance/ai_assistance.ts +1 -0
- package/front_end/panels/ai_assistance/components/AccessibilityAgentMarkdownRenderer.ts +88 -0
- package/front_end/panels/ai_assistance/components/ChatMessage.ts +149 -49
- package/front_end/panels/ai_assistance/components/ChatView.ts +11 -15
- package/front_end/panels/ai_assistance/components/ExportForAgentsDialog.ts +13 -4
- package/front_end/panels/ai_assistance/components/chatMessage.css +53 -1
- package/front_end/panels/ai_assistance/components/chatView.css +0 -10
- package/front_end/panels/ai_assistance/components/exportForAgentsDialog.css +13 -0
- package/front_end/panels/application/WebMCPView.ts +471 -25
- package/front_end/panels/application/webMCPView.css +53 -2
- package/front_end/panels/elements/ElementsTreeElement.ts +1 -1
- package/front_end/panels/elements/ElementsTreeOutline.ts +87 -0
- package/front_end/panels/elements/StylePropertiesSection.ts +0 -4
- package/front_end/panels/elements/elements.ts +3 -0
- package/front_end/panels/elements/elementsTreeOutline.css +21 -0
- package/front_end/panels/elements/stylePropertiesTreeOutline.css +7 -0
- package/front_end/panels/lighthouse/LighthousePanel.ts +7 -1
- package/front_end/panels/lighthouse/LighthouseProtocolService.ts +25 -2
- package/front_end/panels/profiler/HeapDetachedElementsView.ts +0 -4
- package/front_end/panels/profiler/HeapProfileView.ts +0 -4
- package/front_end/panels/profiler/HeapProfilerPanel.ts +4 -13
- package/front_end/panels/profiler/HeapSnapshotProxy.ts +2 -6
- package/front_end/panels/profiler/HeapSnapshotView.ts +19 -32
- package/front_end/panels/profiler/ProfileHeader.ts +1 -15
- package/front_end/panels/profiler/ProfileTypeRegistry.ts +4 -12
- package/front_end/panels/profiler/ProfileView.ts +1 -6
- package/front_end/panels/profiler/ProfilesPanel.ts +60 -7
- package/front_end/panels/protocol_monitor/ProtocolMonitor.ts +27 -22
- package/front_end/panels/timeline/EventsTimelineTreeView.ts +1 -1
- package/front_end/panels/timeline/ThirdPartyTreeView.ts +2 -2
- package/front_end/panels/timeline/TimelinePanel.ts +11 -153
- package/front_end/panels/timeline/TimelineTreeView.ts +147 -48
- package/front_end/panels/timeline/TimelineUIUtils.ts +2 -1
- package/front_end/panels/timeline/components/CWVMetrics.ts +18 -2
- package/front_end/panels/timeline/timeline-meta.ts +11 -0
- package/front_end/panels/timeline/utils/Helpers.ts +5 -0
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/ui/components/markdown_view/CodeBlock.ts +47 -1
- package/front_end/ui/components/markdown_view/codeBlock.css +8 -0
- package/front_end/ui/legacy/FilterBar.ts +2 -0
- package/front_end/ui/legacy/SplitWidget.ts +33 -27
- package/front_end/ui/legacy/TabbedPane.ts +123 -3
- package/front_end/ui/legacy/Widget.ts +95 -48
- package/front_end/ui/legacy/components/source_frame/JSONView.ts +1 -1
- package/front_end/ui/legacy/components/utils/jsUtils.css +2 -0
- package/front_end/ui/legacy/infobar.css +1 -0
- package/front_end/ui/visual_logging/KnownContextValues.ts +6 -1
- package/package.json +1 -1
- package/front_end/core/sdk/WebMCPModel.ts +0 -104
- 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
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
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,
|
|
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:
|
|
57
|
-
type WidgetProducer<WidgetT extends Widget> = (element:
|
|
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<
|
|
157
|
+
const widgetConfigs = new WeakMap<HTMLElement, WidgetConfig<any>>();
|
|
158
158
|
|
|
159
|
-
export
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
181
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
206
|
-
|
|
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
|
-
|
|
214
|
-
|
|
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 (
|
|
243
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
495
|
-
|
|
540
|
+
let config = widgetConfigs.get(element as WidgetElement<Widget>);
|
|
541
|
+
if (!config) {
|
|
542
|
+
config = widgetConfig(element => new Widget(element));
|
|
496
543
|
}
|
|
497
|
-
return
|
|
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;
|
|
@@ -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
|
@@ -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});
|