chrome-devtools-frontend 1.0.1621064 → 1.0.1622369
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/.agents/skills/foundation-test-migration/SKILL.md +171 -0
- package/.agents/skills/verification/SKILL.md +2 -11
- package/front_end/core/common/Base64.ts +12 -2
- package/front_end/core/i18n/i18nImpl.ts +8 -4
- package/front_end/core/root/Runtime.ts +28 -9
- package/front_end/entrypoints/device_mode_emulation_frame/device_mode_emulation_frame.ts +1 -1
- package/front_end/entrypoints/shell/shell.ts +1 -1
- package/front_end/generated/InspectorBackendCommands.ts +1 -1
- package/front_end/generated/protocol.ts +1 -1
- package/front_end/models/ai_assistance/agents/AiAgent.ts +1 -1
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +32 -20
- package/front_end/models/bindings/DebuggerWorkspaceBinding.ts +5 -1
- package/front_end/models/bindings/SymbolizedError.ts +18 -1
- package/front_end/models/issues_manager/GenericIssue.ts +50 -1
- package/front_end/models/issues_manager/descriptions/genericFormModelContextMissingToolDescription.md +5 -0
- package/front_end/models/issues_manager/descriptions/genericFormModelContextMissingToolName.md +5 -0
- package/front_end/models/issues_manager/descriptions/genericFormModelContextParameterMissingName.md +5 -0
- package/front_end/models/issues_manager/descriptions/genericFormModelContextParameterMissingTitleAndDescription.md +5 -0
- package/front_end/models/issues_manager/descriptions/genericFormModelContextRequiredParameterMissingName.md +5 -0
- package/front_end/models/javascript_metadata/NativeFunctions.js +8 -0
- package/front_end/models/trace/insights/Common.ts +4 -0
- package/front_end/models/trace/insights/types.ts +1 -1
- package/front_end/models/web_mcp/WebMCPModel.ts +11 -1
- package/front_end/panels/ai_assistance/components/ChatMessage.ts +94 -23
- package/front_end/panels/application/WebMCPView.ts +16 -7
- package/front_end/panels/console/SymbolizedErrorWidget.ts +69 -0
- package/front_end/panels/console/console.ts +3 -0
- package/front_end/panels/elements/AdoptedStyleSheetTreeElement.ts +0 -1
- package/front_end/panels/elements/ElementsTreeElement.ts +53 -61
- package/front_end/panels/elements/PropertiesWidget.ts +1 -1
- package/front_end/panels/emulation/DeviceModeToolbar.ts +152 -119
- package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +12 -4
- package/front_end/panels/timeline/components/liveMetricsView.css +2 -2
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/ui/components/text_editor/TextEditor.ts +1 -3
- package/front_end/{core → ui}/dom_extension/DOMExtension.ts +1 -1
- package/front_end/ui/legacy/Treeoutline.ts +5 -1
- package/front_end/ui/legacy/Widget.ts +1 -1
- package/front_end/ui/visual_logging/KnownContextValues.ts +4 -0
- package/package.json +1 -1
- /package/front_end/{core → ui}/dom_extension/dom_extension.ts +0 -0
|
@@ -7639,6 +7639,14 @@ export const NativeFunctions = [
|
|
|
7639
7639
|
name: "Sanitizer",
|
|
7640
7640
|
signatures: [["?configuration"]]
|
|
7641
7641
|
},
|
|
7642
|
+
{
|
|
7643
|
+
name: "allowProcessingInstruction",
|
|
7644
|
+
signatures: [["pi"]]
|
|
7645
|
+
},
|
|
7646
|
+
{
|
|
7647
|
+
name: "removeProcessingInstruction",
|
|
7648
|
+
signatures: [["pi"]]
|
|
7649
|
+
},
|
|
7642
7650
|
{
|
|
7643
7651
|
name: "TaskController",
|
|
7644
7652
|
signatures: [["?init"]]
|
|
@@ -26,6 +26,10 @@ export function getInsight<InsightName extends keyof InsightModels>(
|
|
|
26
26
|
return insightSet.model[insightName];
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
export function isInsightKey(key: string): key is InsightKeys {
|
|
30
|
+
return Object.values(InsightKeys).includes(key as InsightKeys);
|
|
31
|
+
}
|
|
32
|
+
|
|
29
33
|
export function getLCP(insightSet: InsightSet):
|
|
30
34
|
{value: Types.Timing.Micro, event: Types.Events.AnyLargestContentfulPaintCandidate}|null {
|
|
31
35
|
const insight = getInsight(InsightKeys.LCP_BREAKDOWN, insightSet);
|
|
@@ -145,7 +145,7 @@ export type InsightModelErrors = {
|
|
|
145
145
|
*/
|
|
146
146
|
export type TraceInsightSets = Map<Types.Events.NavigationId, InsightSet>;
|
|
147
147
|
|
|
148
|
-
export
|
|
148
|
+
export enum InsightKeys {
|
|
149
149
|
LCP_BREAKDOWN = 'LCPBreakdown',
|
|
150
150
|
INP_BREAKDOWN = 'INPBreakdown',
|
|
151
151
|
CLS_CULPRITS = 'CLSCulprits',
|
|
@@ -90,6 +90,7 @@ export interface Call {
|
|
|
90
90
|
tool: Tool;
|
|
91
91
|
input: string;
|
|
92
92
|
result?: Result;
|
|
93
|
+
cancel: () => void;
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
export class Tool {
|
|
@@ -244,7 +245,16 @@ export class WebMCPModel extends SDK.SDKModel.SDKModel<EventTypes> implements Pr
|
|
|
244
245
|
if (!tool) {
|
|
245
246
|
return;
|
|
246
247
|
}
|
|
247
|
-
const call: Call = {
|
|
248
|
+
const call: Call = {
|
|
249
|
+
tool,
|
|
250
|
+
input: params.input,
|
|
251
|
+
invocationId: params.invocationId,
|
|
252
|
+
cancel: () => {
|
|
253
|
+
if (call.result === undefined) {
|
|
254
|
+
void this.agent.invoke_cancelInvocation({invocationId: params.invocationId});
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
};
|
|
248
258
|
this.#calls.set(params.invocationId, call);
|
|
249
259
|
this.dispatchEventToListeners(Events.TOOL_INVOKED, call);
|
|
250
260
|
}
|
|
@@ -20,6 +20,7 @@ import * as AiAssistanceModel from '../../../models/ai_assistance/ai_assistance.
|
|
|
20
20
|
import * as ComputedStyle from '../../../models/computed_style/computed_style.js';
|
|
21
21
|
import * as Trace from '../../../models/trace/trace.js';
|
|
22
22
|
import * as PanelsCommon from '../../../panels/common/common.js';
|
|
23
|
+
import * as TraceBounds from '../../../services/trace_bounds/trace_bounds.js';
|
|
23
24
|
import * as Marked from '../../../third_party/marked/marked.js';
|
|
24
25
|
import * as Buttons from '../../../ui/components/buttons/buttons.js';
|
|
25
26
|
import * as Input from '../../../ui/components/input/input.js';
|
|
@@ -31,6 +32,7 @@ import * as Lit from '../../../ui/lit/lit.js';
|
|
|
31
32
|
import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
|
|
32
33
|
import * as Elements from '../../elements/elements.js';
|
|
33
34
|
import * as TimelineComponents from '../../timeline/components/components.js';
|
|
35
|
+
import type {BaseInsightComponent} from '../../timeline/components/insights/BaseInsightComponent.js';
|
|
34
36
|
import * as TimelineInsights from '../../timeline/components/insights/insights.js';
|
|
35
37
|
import * as Timeline from '../../timeline/timeline.js';
|
|
36
38
|
import * as TimelineUtils from '../../timeline/utils/utils.js';
|
|
@@ -201,6 +203,18 @@ const UIStringsNotTranslate = {
|
|
|
201
203
|
* @description Accessible label for the reveal button in the LCP breakdown widget.
|
|
202
204
|
*/
|
|
203
205
|
revealLcpBreakdown: 'Reveal LCP breakdown',
|
|
206
|
+
/**
|
|
207
|
+
* @description Accessible label for the reveal button in the LCP discovery widget.
|
|
208
|
+
*/
|
|
209
|
+
revealLcpDiscovery: 'Reveal LCP discovery',
|
|
210
|
+
/**
|
|
211
|
+
* @description Accessible label for the reveal button in the layout shift culprits widget.
|
|
212
|
+
*/
|
|
213
|
+
revealClsCulprits: 'Reveal layout shift culprits',
|
|
214
|
+
/**
|
|
215
|
+
* @description Accessible label for the reveal button in the render-blocking requests widget.
|
|
216
|
+
*/
|
|
217
|
+
revealRenderBlockingBreakdown: 'Reveal render-blocking requests',
|
|
204
218
|
/**
|
|
205
219
|
* @description Accessible label for the reveal button in the LCP element widget.
|
|
206
220
|
*/
|
|
@@ -221,6 +235,18 @@ const UIStringsNotTranslate = {
|
|
|
221
235
|
* @description Title for the LCP breakdown widget.
|
|
222
236
|
*/
|
|
223
237
|
lcpBreakdown: 'LCP breakdown',
|
|
238
|
+
/**
|
|
239
|
+
* @description Title for the LCP discovery widget.
|
|
240
|
+
*/
|
|
241
|
+
lcpDiscovery: 'LCP discovery',
|
|
242
|
+
/**
|
|
243
|
+
* @description Title for the layout shift culprits widget.
|
|
244
|
+
*/
|
|
245
|
+
clsCulprits: 'Layout shift culprits',
|
|
246
|
+
/**
|
|
247
|
+
* @description Title for the render-blocking requests widget.
|
|
248
|
+
*/
|
|
249
|
+
renderBlockingBreakdown: 'Render-blocking requests',
|
|
224
250
|
/**
|
|
225
251
|
* @description Title for the LCP element widget.
|
|
226
252
|
*/
|
|
@@ -914,33 +940,78 @@ async function makeStylePropertiesWidget(widgetData: StylePropertiesAiWidget): P
|
|
|
914
940
|
};
|
|
915
941
|
}
|
|
916
942
|
|
|
943
|
+
const INSIGHT_METADATA: Record<string, {
|
|
944
|
+
component: typeof TimelineInsights.LCPBreakdown.LCPBreakdown | typeof TimelineInsights.RenderBlocking.RenderBlocking |
|
|
945
|
+
typeof TimelineInsights.LCPDiscovery.LCPDiscovery | typeof TimelineInsights.CLSCulprits.CLSCulprits,
|
|
946
|
+
accessibleLabel: string,
|
|
947
|
+
title: string,
|
|
948
|
+
jslog: string,
|
|
949
|
+
}> = {
|
|
950
|
+
[Trace.Insights.Types.InsightKeys.LCP_BREAKDOWN]: {
|
|
951
|
+
component: TimelineInsights.LCPBreakdown.LCPBreakdown,
|
|
952
|
+
accessibleLabel: UIStringsNotTranslate.revealLcpBreakdown,
|
|
953
|
+
title: UIStringsNotTranslate.lcpBreakdown,
|
|
954
|
+
jslog: 'lcp-breakdown-widget',
|
|
955
|
+
},
|
|
956
|
+
[Trace.Insights.Types.InsightKeys.RENDER_BLOCKING]: {
|
|
957
|
+
component: TimelineInsights.RenderBlocking.RenderBlocking,
|
|
958
|
+
accessibleLabel: UIStringsNotTranslate.revealRenderBlockingBreakdown,
|
|
959
|
+
title: UIStringsNotTranslate.renderBlockingBreakdown,
|
|
960
|
+
jslog: 'render-blocking-widget',
|
|
961
|
+
},
|
|
962
|
+
[Trace.Insights.Types.InsightKeys.LCP_DISCOVERY]: {
|
|
963
|
+
component: TimelineInsights.LCPDiscovery.LCPDiscovery,
|
|
964
|
+
accessibleLabel: UIStringsNotTranslate.revealLcpDiscovery,
|
|
965
|
+
title: UIStringsNotTranslate.lcpDiscovery,
|
|
966
|
+
jslog: 'lcp-discovery-widget',
|
|
967
|
+
},
|
|
968
|
+
[Trace.Insights.Types.InsightKeys.CLS_CULPRITS]: {
|
|
969
|
+
component: TimelineInsights.CLSCulprits.CLSCulprits,
|
|
970
|
+
accessibleLabel: UIStringsNotTranslate.revealClsCulprits,
|
|
971
|
+
title: UIStringsNotTranslate.clsCulprits,
|
|
972
|
+
jslog: 'cls-culprits-widget',
|
|
973
|
+
},
|
|
974
|
+
};
|
|
975
|
+
|
|
976
|
+
function renderInsightWidget<T extends Trace.Insights.Types.InsightModel>(
|
|
977
|
+
component: new () => BaseInsightComponent<T>, insight: T, jslog: string, accessibleLabel: string, title: string,
|
|
978
|
+
bounds?: Trace.Types.Timing.TraceWindowMicro): WidgetMakerResponse {
|
|
979
|
+
const renderedWidget = html`<devtools-widget
|
|
980
|
+
class=${jslog}
|
|
981
|
+
${widget(component, {
|
|
982
|
+
model: insight,
|
|
983
|
+
minimal: true,
|
|
984
|
+
bounds: bounds ?? null,
|
|
985
|
+
})}></devtools-widget>`;
|
|
986
|
+
|
|
987
|
+
return {
|
|
988
|
+
renderedWidget,
|
|
989
|
+
revealable: new TimelineUtils.Helpers.RevealableInsight(insight),
|
|
990
|
+
accessibleRevealLabel: lockedString(accessibleLabel),
|
|
991
|
+
title: lockedString(title),
|
|
992
|
+
jslogContext: jslog,
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
|
|
917
996
|
async function makePerfInsightWidget(widgetData: PerfInsightAiWidget): Promise<WidgetMakerResponse|null> {
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
const insight = widgetData.data.insightData;
|
|
921
|
-
if (!insight || !Trace.Insights.Models.LCPBreakdown.isLCPBreakdownInsight(insight)) {
|
|
922
|
-
return null;
|
|
923
|
-
}
|
|
924
|
-
// clang-format off
|
|
925
|
-
const renderedWidget = html`<devtools-widget
|
|
926
|
-
class="lcp-breakdown-widget"
|
|
927
|
-
${widget(TimelineInsights.LCPBreakdown.LCPBreakdown, {
|
|
928
|
-
model: insight,
|
|
929
|
-
minimal: true,
|
|
930
|
-
})}></devtools-widget>`;
|
|
931
|
-
// clang-format on
|
|
997
|
+
const insightKey = widgetData.data.insight;
|
|
998
|
+
const insight = widgetData.data.insightData;
|
|
932
999
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1000
|
+
const meta = INSIGHT_METADATA[insightKey];
|
|
1001
|
+
if (!meta) {
|
|
1002
|
+
return null;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
let bounds;
|
|
1006
|
+
if (insightKey === Trace.Insights.Types.InsightKeys.CLS_CULPRITS) {
|
|
1007
|
+
const traceBounds = TraceBounds.TraceBounds.BoundsManager.instance().state()?.micro.entireTraceBounds;
|
|
1008
|
+
if (!traceBounds) {
|
|
942
1009
|
return null;
|
|
1010
|
+
}
|
|
1011
|
+
bounds = traceBounds;
|
|
943
1012
|
}
|
|
1013
|
+
|
|
1014
|
+
return renderInsightWidget(meta.component, insight, meta.jslog, meta.accessibleLabel, meta.title, bounds);
|
|
944
1015
|
}
|
|
945
1016
|
|
|
946
1017
|
async function makeBottomUpTimelineTreeWidget(widgetData: BottomUpTreeAiWidget): Promise<WidgetMakerResponse|null> {
|
|
@@ -167,6 +167,10 @@ const UIStrings = {
|
|
|
167
167
|
* @description Context menu action to copy the description of a tool
|
|
168
168
|
*/
|
|
169
169
|
copyDescription: 'Copy description',
|
|
170
|
+
/**
|
|
171
|
+
* @description Context menu action to cancel an in-progress tool call
|
|
172
|
+
*/
|
|
173
|
+
cancelCall: 'Cancel',
|
|
170
174
|
/**
|
|
171
175
|
* @description Text for the header of the tool run section
|
|
172
176
|
*/
|
|
@@ -498,6 +502,11 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
|
|
|
498
502
|
const payload = parsePayload(call.input);
|
|
499
503
|
input.onRevealTool(call.tool, payload.valueObject as Record<string, unknown> | undefined);
|
|
500
504
|
}, {jslogContext: 'webmcp.edit-and-run', disabled: isUnregistered});
|
|
505
|
+
if (call.result === undefined) {
|
|
506
|
+
contextMenu.defaultSection().appendItem(i18nString(UIStrings.cancelCall), () => {
|
|
507
|
+
call.cancel();
|
|
508
|
+
}, {jslogContext: 'webmcp.cancel-call'});
|
|
509
|
+
}
|
|
501
510
|
}}>
|
|
502
511
|
<td>
|
|
503
512
|
<div class="name-cell">
|
|
@@ -598,11 +607,11 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
|
|
|
598
607
|
` : html`
|
|
599
608
|
<devtools-list class="square-corners">
|
|
600
609
|
${tools.map(tool => html`
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
610
|
+
<div class=${Directives.classMap({'tool-item': true, selected: tool === input.selectedTool?.tool})}
|
|
611
|
+
@click=${() => input.onToolSelect(tool)}
|
|
612
|
+
@contextmenu=${(e: Event) => onToolContextMenu(e, tool)}>
|
|
613
|
+
<div class="tool-name-container">
|
|
614
|
+
<div class="tool-name source-code">${tool.name}</div>
|
|
606
615
|
<div class="tool-icons">
|
|
607
616
|
${getIconGroupsFromStats(toolStats.stats.get(tool)).map(group => html`
|
|
608
617
|
<icon-button
|
|
@@ -613,8 +622,8 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
|
|
|
613
622
|
} as IconButton.IconButton.IconButtonData}
|
|
614
623
|
@click=${(e: Event) => e.stopPropagation()}></icon-button>`)}
|
|
615
624
|
</div>
|
|
616
|
-
|
|
617
|
-
|
|
625
|
+
</div>
|
|
626
|
+
<div class="tool-description">${tool.description}</div>
|
|
618
627
|
</div>`)}
|
|
619
628
|
</devtools-list>
|
|
620
629
|
`}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Copyright 2026 The Chromium Authors
|
|
2
|
+
// Use of this source code is governed by a BSD-style license that can be
|
|
3
|
+
// found in the LICENSE file.
|
|
4
|
+
|
|
5
|
+
import * as Bindings from '../../models/bindings/bindings.js';
|
|
6
|
+
import type * as Workspace from '../../models/workspace/workspace.js';
|
|
7
|
+
import * as UI from '../../ui/legacy/legacy.js';
|
|
8
|
+
|
|
9
|
+
export interface ViewInput {
|
|
10
|
+
error: Bindings.SymbolizedError.SymbolizedError;
|
|
11
|
+
ignoreListManager?: Workspace.IgnoreListManager.IgnoreListManager;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const DEFAULT_VIEW = (_input: ViewInput, _output: object, _target: HTMLElement): void => {};
|
|
15
|
+
|
|
16
|
+
export class SymbolizedErrorWidget extends UI.Widget.Widget {
|
|
17
|
+
#error?: Bindings.SymbolizedError.SymbolizedError;
|
|
18
|
+
#view: typeof DEFAULT_VIEW;
|
|
19
|
+
#ignoreListManager?: Workspace.IgnoreListManager.IgnoreListManager;
|
|
20
|
+
|
|
21
|
+
constructor(element?: HTMLElement, view: typeof DEFAULT_VIEW = DEFAULT_VIEW) {
|
|
22
|
+
super(element);
|
|
23
|
+
this.#view = view;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
set ignoreListManager(ignoreListManager: Workspace.IgnoreListManager.IgnoreListManager) {
|
|
27
|
+
this.#ignoreListManager = ignoreListManager;
|
|
28
|
+
this.requestUpdate();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get ignoreListManager(): Workspace.IgnoreListManager.IgnoreListManager|undefined {
|
|
32
|
+
return this.#ignoreListManager;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
set error(error: Bindings.SymbolizedError.SymbolizedError) {
|
|
36
|
+
this.#error?.removeEventListener(Bindings.SymbolizedError.Events.UPDATED, this.requestUpdate, this);
|
|
37
|
+
this.#error = error;
|
|
38
|
+
if (this.isShowing()) {
|
|
39
|
+
this.#error?.addEventListener(Bindings.SymbolizedError.Events.UPDATED, this.requestUpdate, this);
|
|
40
|
+
}
|
|
41
|
+
this.requestUpdate();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get error(): Bindings.SymbolizedError.SymbolizedError|undefined {
|
|
45
|
+
return this.#error;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
override wasShown(): void {
|
|
49
|
+
super.wasShown();
|
|
50
|
+
this.#error?.addEventListener(Bindings.SymbolizedError.Events.UPDATED, this.requestUpdate, this);
|
|
51
|
+
this.requestUpdate();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
override willHide(): void {
|
|
55
|
+
super.willHide();
|
|
56
|
+
this.#error?.removeEventListener(Bindings.SymbolizedError.Events.UPDATED, this.requestUpdate, this);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
override performUpdate(): void {
|
|
60
|
+
if (!this.#error) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const input: ViewInput = {
|
|
64
|
+
error: this.#error,
|
|
65
|
+
ignoreListManager: this.#ignoreListManager,
|
|
66
|
+
};
|
|
67
|
+
this.#view(input, {}, this.contentElement);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -13,6 +13,7 @@ import './ConsoleViewMessage.js';
|
|
|
13
13
|
import './ConsolePrompt.js';
|
|
14
14
|
import './ConsoleView.js';
|
|
15
15
|
import './ConsolePanel.js';
|
|
16
|
+
import './SymbolizedErrorWidget.js';
|
|
16
17
|
import './PromptBuilder.js';
|
|
17
18
|
|
|
18
19
|
import * as ConsoleContextSelector from './ConsoleContextSelector.js';
|
|
@@ -27,6 +28,7 @@ import * as ConsoleView from './ConsoleView.js';
|
|
|
27
28
|
import * as ConsoleViewMessage from './ConsoleViewMessage.js';
|
|
28
29
|
import * as ConsoleViewport from './ConsoleViewport.js';
|
|
29
30
|
import * as PromptBuilder from './PromptBuilder.js';
|
|
31
|
+
import * as SymbolizedErrorWidget from './SymbolizedErrorWidget.js';
|
|
30
32
|
|
|
31
33
|
export {
|
|
32
34
|
ConsoleContextSelector,
|
|
@@ -41,4 +43,5 @@ export {
|
|
|
41
43
|
ConsoleViewMessage,
|
|
42
44
|
ConsoleViewport,
|
|
43
45
|
PromptBuilder,
|
|
46
|
+
SymbolizedErrorWidget,
|
|
44
47
|
};
|
|
@@ -460,10 +460,14 @@ export interface ViewInput {
|
|
|
460
460
|
descendantDecorations: Decoration[];
|
|
461
461
|
decorationsTooltip: string;
|
|
462
462
|
indent: number;
|
|
463
|
+
|
|
464
|
+
editorState: CodeMirror.EditorState|null;
|
|
465
|
+
editorWidth: number|null;
|
|
463
466
|
}
|
|
464
467
|
|
|
465
468
|
export interface ViewOutput {
|
|
466
469
|
contentElement?: HTMLElement;
|
|
470
|
+
editorRef?: TextEditor.TextEditor.TextEditor;
|
|
467
471
|
}
|
|
468
472
|
|
|
469
473
|
export function adornerRef(): DirectiveResult<typeof Lit.Directives.RefDirective> {
|
|
@@ -936,11 +940,12 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
|
|
|
936
940
|
const gutterContainerClasses = {
|
|
937
941
|
'has-decorations': input.decorations.length || input.descendantDecorations.length,
|
|
938
942
|
'gutter-container': true,
|
|
943
|
+
hidden: Boolean(input.editorState),
|
|
939
944
|
};
|
|
940
945
|
// clang-format off
|
|
941
946
|
render(html`
|
|
942
947
|
<div ${ref(el => { output.contentElement = el as HTMLElement; })}>
|
|
943
|
-
${input.node ? html`<span class="highlight">${renderTitle(
|
|
948
|
+
${input.node ? html`<span class="highlight ${input.editorState ? 'hidden' : ''}">${renderTitle(
|
|
944
949
|
input.node,
|
|
945
950
|
input.isClosingTag,
|
|
946
951
|
input.expanded,
|
|
@@ -951,7 +956,7 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
|
|
|
951
956
|
input.onExpand,
|
|
952
957
|
)}</span>` : nothing}
|
|
953
958
|
${input.isHovered || input.isSelected ? html`
|
|
954
|
-
<div class="selection fill" style=${`margin-left: ${-input.indent}px`}></div>
|
|
959
|
+
<div class="selection fill ${input.editorState ? 'hidden' : ''}" style=${`margin-left: ${-input.indent}px`}></div>
|
|
955
960
|
` : nothing}
|
|
956
961
|
<div class=${Lit.Directives.classMap(gutterContainerClasses)}
|
|
957
962
|
style="left: ${-input.indent}px"
|
|
@@ -964,7 +969,7 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
|
|
|
964
969
|
${input.descendantDecorations.map(d => html`<div class="elements-gutter-decoration elements-has-decorated-children" style="--decoration-color: ${d.color}"></div>`)}
|
|
965
970
|
</div>` : nothing}
|
|
966
971
|
</div>
|
|
967
|
-
${hasAdorners ? html`<div class="adorner-container ${
|
|
972
|
+
${hasAdorners ? html`<div class="adorner-container ${(input.editorState) ? 'hidden' : ''}">
|
|
968
973
|
${maybeRenderAdAdorner(input)}
|
|
969
974
|
${input.showViewSourceAdorner ? html`<devtools-adorner
|
|
970
975
|
.name=${ElementsComponents.AdornerManager.RegisteredAdorners.VIEW_SOURCE}
|
|
@@ -1123,10 +1128,10 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
|
|
|
1123
1128
|
</devtools-adorner>` : nothing}
|
|
1124
1129
|
</div>`: nothing}
|
|
1125
1130
|
${input.isSelected ? html`
|
|
1126
|
-
<span class="selected-hint" title=${i18nString(UIStrings.useSInTheConsoleToReferToThis, { PH1: '$0' })} aria-hidden="true"></span>
|
|
1131
|
+
<span class="selected-hint ${input.editorState ? 'hidden' : ''}" title=${i18nString(UIStrings.useSInTheConsoleToReferToThis, { PH1: '$0' })} aria-hidden="true"></span>
|
|
1127
1132
|
` : nothing}
|
|
1128
1133
|
${input.showAiButton ? html`
|
|
1129
|
-
<span class="ai-button-container">
|
|
1134
|
+
<span class="ai-button-container ${input.editorState ? 'hidden' : ''}">
|
|
1130
1135
|
<devtools-floating-button
|
|
1131
1136
|
icon-name=${AIAssistance.AiUtils.getIconName()}
|
|
1132
1137
|
title=${input.aiButtonTitle || ''}
|
|
@@ -1136,6 +1141,15 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
|
|
|
1136
1141
|
</devtools-floating-button>
|
|
1137
1142
|
</span>
|
|
1138
1143
|
` : nothing}
|
|
1144
|
+
${input.editorState ? html`<div @keydown=${(event: KeyboardEvent) => {
|
|
1145
|
+
if (event.key === 'Escape') {
|
|
1146
|
+
event.consume(true);
|
|
1147
|
+
}
|
|
1148
|
+
}} class="source-code elements-tree-editor" style="width: ${input.editorWidth ?? 0}px;">
|
|
1149
|
+
<devtools-text-editor .state=${input.editorState} ${ref(el => {
|
|
1150
|
+
output.editorRef = el as TextEditor.TextEditor.TextEditor;
|
|
1151
|
+
})}></devtools-text-editor>
|
|
1152
|
+
</div>`: nothing}
|
|
1139
1153
|
</div>
|
|
1140
1154
|
`, target);
|
|
1141
1155
|
// clang-format on
|
|
@@ -1151,7 +1165,9 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
|
|
|
1151
1165
|
private inClipboard: boolean;
|
|
1152
1166
|
#hovered: boolean;
|
|
1153
1167
|
private editing: EditorHandles|null;
|
|
1154
|
-
|
|
1168
|
+
#editorRef?: TextEditor.TextEditor.TextEditor;
|
|
1169
|
+
#editorState: CodeMirror.EditorState|null = null;
|
|
1170
|
+
#editorWidth: number|null = null;
|
|
1155
1171
|
expandAllButtonElement: UI.TreeOutline.TreeElement|null;
|
|
1156
1172
|
#elementIssues = new Map<string, IssuesManager.Issue.Issue>();
|
|
1157
1173
|
#nodeElementToIssue = new Map<Element, IssuesManager.Issue.Issue[]>();
|
|
@@ -1288,9 +1304,6 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
|
|
|
1288
1304
|
|
|
1289
1305
|
// ClearNode param is used to clean DOM after in-place editing..
|
|
1290
1306
|
performUpdate(clearNode = false): void {
|
|
1291
|
-
if (this.editing) {
|
|
1292
|
-
return;
|
|
1293
|
-
}
|
|
1294
1307
|
const output: ViewOutput = {};
|
|
1295
1308
|
DEFAULT_VIEW(
|
|
1296
1309
|
{
|
|
@@ -1380,10 +1393,13 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
|
|
|
1380
1393
|
void action.execute();
|
|
1381
1394
|
}
|
|
1382
1395
|
},
|
|
1396
|
+
editorState: this.#editorState,
|
|
1397
|
+
editorWidth: this.#editorWidth,
|
|
1383
1398
|
},
|
|
1384
1399
|
output, this.listItemElement);
|
|
1385
1400
|
|
|
1386
1401
|
this.#contentElement = output.contentElement;
|
|
1402
|
+
this.#editorRef = output.editorRef;
|
|
1387
1403
|
if (this.#updateRecord) {
|
|
1388
1404
|
this.#updateRecord = null;
|
|
1389
1405
|
}
|
|
@@ -1683,10 +1699,7 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
|
|
|
1683
1699
|
}
|
|
1684
1700
|
}
|
|
1685
1701
|
|
|
1686
|
-
|
|
1687
|
-
if (this.editing) {
|
|
1688
|
-
this.editing.cancel();
|
|
1689
|
-
}
|
|
1702
|
+
clearView(): void {
|
|
1690
1703
|
// Update the element to clean up adorner registrations with the
|
|
1691
1704
|
// ElementsPanel.
|
|
1692
1705
|
// We do not change the ElementsTreeElement state in case the
|
|
@@ -1744,9 +1757,17 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
|
|
|
1744
1757
|
descendantDecorations: [],
|
|
1745
1758
|
decorationsTooltip: '',
|
|
1746
1759
|
indent: 0,
|
|
1760
|
+
editorState: null,
|
|
1761
|
+
editorWidth: null,
|
|
1747
1762
|
},
|
|
1748
1763
|
{}, this.listItemElement);
|
|
1764
|
+
}
|
|
1749
1765
|
|
|
1766
|
+
override onunbind(): void {
|
|
1767
|
+
if (this.editing) {
|
|
1768
|
+
this.editing.cancel();
|
|
1769
|
+
}
|
|
1770
|
+
this.clearView();
|
|
1750
1771
|
if (this.treeOutline && this.treeOutline.treeElementByNode.get(this.nodeInternal) === this) {
|
|
1751
1772
|
this.treeOutline.treeElementByNode.delete(this.nodeInternal);
|
|
1752
1773
|
}
|
|
@@ -2553,7 +2574,6 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
|
|
|
2553
2574
|
this.editing = {
|
|
2554
2575
|
commit: editorHandles.commit,
|
|
2555
2576
|
cancel: editorHandles.cancel,
|
|
2556
|
-
editor: undefined,
|
|
2557
2577
|
resize: () => {},
|
|
2558
2578
|
};
|
|
2559
2579
|
}
|
|
@@ -2569,31 +2589,13 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
|
|
|
2569
2589
|
return;
|
|
2570
2590
|
}
|
|
2571
2591
|
|
|
2572
|
-
const initialValue = convertUnicodeCharsToHTMLEntities(maybeInitialValue).text;
|
|
2573
|
-
this.htmlEditElement = document.createElement('div');
|
|
2574
|
-
this.htmlEditElement.className = 'source-code elements-tree-editor';
|
|
2575
|
-
|
|
2576
|
-
// Hide header items.
|
|
2577
|
-
let child: (ChildNode|null) = this.listItemElement.firstChild;
|
|
2578
|
-
while (child) {
|
|
2579
|
-
if (child instanceof HTMLElement) {
|
|
2580
|
-
child.style.display = 'none';
|
|
2581
|
-
}
|
|
2582
|
-
child = child.nextSibling;
|
|
2583
|
-
}
|
|
2584
2592
|
// Hide children item.
|
|
2585
2593
|
if (this.childrenListElement) {
|
|
2586
2594
|
this.childrenListElement.style.display = 'none';
|
|
2587
2595
|
}
|
|
2596
|
+
const initialValue = convertUnicodeCharsToHTMLEntities(maybeInitialValue).text;
|
|
2588
2597
|
// Append editor.
|
|
2589
|
-
this.
|
|
2590
|
-
this.htmlEditElement.addEventListener('keydown', event => {
|
|
2591
|
-
if (event.key === 'Escape') {
|
|
2592
|
-
event.consume(true);
|
|
2593
|
-
}
|
|
2594
|
-
});
|
|
2595
|
-
|
|
2596
|
-
const editor = new TextEditor.TextEditor.TextEditor(CodeMirror.EditorState.create({
|
|
2598
|
+
this.#editorState = CodeMirror.EditorState.create({
|
|
2597
2599
|
doc: initialValue,
|
|
2598
2600
|
extensions: [
|
|
2599
2601
|
CodeMirror.keymap.of([
|
|
@@ -2623,58 +2625,49 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
|
|
|
2623
2625
|
}),
|
|
2624
2626
|
CodeMirror.EditorView.domEventHandlers({
|
|
2625
2627
|
focusout: event => {
|
|
2628
|
+
if (!this.#editorRef) {
|
|
2629
|
+
return;
|
|
2630
|
+
}
|
|
2626
2631
|
// The relatedTarget is null when no element gains focus, e.g. switching windows.
|
|
2627
2632
|
const relatedTarget = (event.relatedTarget as Node | null);
|
|
2628
|
-
if (relatedTarget && !relatedTarget.isSelfOrDescendant(
|
|
2633
|
+
if (relatedTarget && !relatedTarget.isSelfOrDescendant(this.#editorRef)) {
|
|
2629
2634
|
this.editing?.commit();
|
|
2630
2635
|
}
|
|
2631
2636
|
},
|
|
2632
2637
|
}),
|
|
2633
2638
|
],
|
|
2634
|
-
})
|
|
2635
|
-
this.
|
|
2639
|
+
});
|
|
2640
|
+
this.performUpdate();
|
|
2636
2641
|
resize.call(this);
|
|
2637
|
-
this
|
|
2638
|
-
|
|
2639
|
-
|
|
2642
|
+
this.#editorRef?.focus();
|
|
2643
|
+
this.editing = {commit: commit.bind(this), cancel: dispose.bind(this), resize: resize.bind(this)};
|
|
2640
2644
|
this.treeOutline?.setMultilineEditing(this.editing);
|
|
2641
2645
|
|
|
2642
2646
|
function resize(this: ElementsTreeElement): void {
|
|
2643
|
-
if (this.treeOutline
|
|
2644
|
-
this
|
|
2647
|
+
if (this.treeOutline) {
|
|
2648
|
+
this.#editorWidth = this.treeOutline.visibleWidth() - this.computeLeftIndent() - 30;
|
|
2649
|
+
this.performUpdate();
|
|
2645
2650
|
}
|
|
2646
2651
|
}
|
|
2647
2652
|
|
|
2648
2653
|
function commit(this: ElementsTreeElement): void {
|
|
2649
|
-
if (this
|
|
2650
|
-
commitCallback(initialValue, this.
|
|
2654
|
+
if (this.#editorRef) {
|
|
2655
|
+
commitCallback(initialValue, this.#editorRef.editor.state.doc.toString());
|
|
2651
2656
|
}
|
|
2652
2657
|
dispose.call(this);
|
|
2653
2658
|
}
|
|
2654
2659
|
|
|
2655
2660
|
function dispose(this: ElementsTreeElement): void {
|
|
2656
|
-
if (!this
|
|
2661
|
+
if (!this.#editorRef) {
|
|
2657
2662
|
return;
|
|
2658
2663
|
}
|
|
2659
2664
|
this.editing = null;
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
if (this.htmlEditElement) {
|
|
2663
|
-
this.listItemElement.removeChild(this.htmlEditElement);
|
|
2664
|
-
}
|
|
2665
|
-
this.htmlEditElement = undefined;
|
|
2665
|
+
this.#editorState = null;
|
|
2666
|
+
this.performUpdate();
|
|
2666
2667
|
// Unhide children item.
|
|
2667
2668
|
if (this.childrenListElement) {
|
|
2668
2669
|
this.childrenListElement.style.removeProperty('display');
|
|
2669
2670
|
}
|
|
2670
|
-
// Unhide header items.
|
|
2671
|
-
let child: (ChildNode|null) = this.listItemElement.firstChild;
|
|
2672
|
-
while (child) {
|
|
2673
|
-
if (child instanceof HTMLElement) {
|
|
2674
|
-
child.style.removeProperty('display');
|
|
2675
|
-
}
|
|
2676
|
-
child = child.nextSibling;
|
|
2677
|
-
}
|
|
2678
2671
|
|
|
2679
2672
|
if (this.treeOutline) {
|
|
2680
2673
|
this.treeOutline.setMultilineEditing(null);
|
|
@@ -3016,7 +3009,7 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
|
|
|
3016
3009
|
}
|
|
3017
3010
|
|
|
3018
3011
|
toggleEditAsHTML(callback?: ((arg0: boolean) => void), startEditing?: boolean): void {
|
|
3019
|
-
if (this.editing && this
|
|
3012
|
+
if (this.editing && this.#editorState) {
|
|
3020
3013
|
this.editing.commit();
|
|
3021
3014
|
return;
|
|
3022
3015
|
}
|
|
@@ -3222,7 +3215,6 @@ export function convertUnicodeCharsToHTMLEntities(text: string): {
|
|
|
3222
3215
|
export interface EditorHandles {
|
|
3223
3216
|
commit: () => void;
|
|
3224
3217
|
cancel: () => void;
|
|
3225
|
-
editor?: TextEditor.TextEditor.TextEditor;
|
|
3226
3218
|
resize: () => void;
|
|
3227
3219
|
}
|
|
3228
3220
|
|
|
@@ -103,7 +103,7 @@ export const DEFAULT_VIEW: View = (input, _output, target) => {
|
|
|
103
103
|
${input.objectTree && input.allChildrenFiltered ? html`
|
|
104
104
|
<div class="gray-info-message">${i18nString(UIStrings.noMatchingProperty)}</div>
|
|
105
105
|
` : nothing}
|
|
106
|
-
<devtools-tree @treeelementexpand=${onExpand} .template=${html`
|
|
106
|
+
<devtools-tree show-selection-on-keyboard-focus @treeelementexpand=${onExpand} .template=${html`
|
|
107
107
|
<ul role=tree class="source-code object-properties-section">
|
|
108
108
|
<style>${ObjectUI.ObjectPropertiesSection.objectValueStyles}</style>;
|
|
109
109
|
<style>${ObjectUI.ObjectPropertiesSection.objectPropertiesSectionStyles}</style>;
|