chrome-devtools-frontend 1.0.1613625 → 1.0.1615539
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/AUTHORS +1 -0
- package/docs/contributing/infrastructure.md +0 -1
- package/front_end/core/common/VersionController.ts +17 -1
- package/front_end/core/host/UserMetrics.ts +0 -1
- package/front_end/core/root/ExperimentNames.ts +0 -1
- package/front_end/core/sdk/OverlayModel.ts +2 -4
- package/front_end/core/sdk/sdk-meta.ts +13 -0
- package/front_end/entrypoints/device_mode_emulation_frame/device_mode_emulation_frame.ts +4 -0
- package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +0 -1
- package/front_end/entrypoints/greendev_floaty/floaty.css +3 -0
- package/front_end/entrypoints/greendev_floaty/greendev_floaty.ts +0 -1
- package/front_end/entrypoints/main/MainImpl.ts +0 -6
- package/front_end/entrypoints/shell/shell.ts +4 -0
- package/front_end/entrypoints/trace_app/trace_app.ts +4 -0
- package/front_end/generated/InspectorBackendCommands.ts +6 -4
- package/front_end/generated/protocol-mapping.d.ts +14 -0
- package/front_end/generated/protocol-proxy-api.d.ts +10 -0
- package/front_end/generated/protocol.ts +33 -3
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +10 -2
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +23 -7
- package/front_end/models/ai_assistance/agents/PerformanceAgent.snapshot.txt +4 -1
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +200 -46
- package/front_end/models/issues_manager/IssuesManager.ts +4 -0
- package/front_end/models/javascript_metadata/NativeFunctions.js +673 -639
- package/front_end/models/stack_trace/DetailedErrorStackParser.ts +161 -0
- package/front_end/models/stack_trace/StackTrace.ts +18 -0
- package/front_end/models/stack_trace/StackTraceImpl.ts +96 -4
- package/front_end/models/stack_trace/StackTraceModel.ts +39 -0
- package/front_end/models/stack_trace/Trie.ts +21 -0
- package/front_end/models/stack_trace/stack_trace_impl.ts +2 -0
- package/front_end/panels/ai_assistance/components/AccessibilityAgentMarkdownRenderer.ts +55 -14
- package/front_end/panels/ai_assistance/components/ChatView.ts +4 -3
- package/front_end/panels/ai_assistance/components/ExportForAgentsDialog.ts +4 -1
- package/front_end/panels/ai_assistance/components/optInChangeDialog.css +1 -2
- package/front_end/panels/application/WebMCPView.ts +270 -18
- package/front_end/panels/application/components/ProtocolHandlersView.ts +2 -2
- package/front_end/panels/application/webMCPView.css +4 -1
- package/front_end/panels/common/AiCodeCompletionDisclaimer.ts +7 -0
- package/front_end/panels/console/ConsoleContextSelector.ts +1 -1
- package/front_end/panels/console/ConsoleViewMessage.ts +8 -2
- package/front_end/panels/console/consoleView.css +4 -0
- package/front_end/panels/css_overview/CSSOverviewCompletedView.ts +2 -3
- package/front_end/panels/css_overview/CSSOverviewModel.ts +1 -2
- package/front_end/panels/network/RequestConditionsDrawer.ts +4 -2
- package/front_end/panels/network/RequestPayloadView.ts +8 -3
- package/front_end/panels/network/RequestTimingView.ts +6 -7
- package/front_end/panels/performance_monitor/PerformanceMonitor.ts +7 -5
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/third_party/codemirror/codemirror-tsconfig.json +4 -4
- package/front_end/third_party/lighthouse/lighthouse-tsconfig.json +1 -1
- package/front_end/tsconfig.json +2 -1
- package/front_end/ui/legacy/EmptyWidget.ts +2 -2
- package/front_end/ui/legacy/components/color_picker/ContrastDetails.ts +1 -2
- package/front_end/ui/legacy/components/color_picker/ContrastOverlay.ts +4 -5
- package/front_end/ui/legacy/components/source_frame/XMLView.ts +12 -7
- package/front_end/ui/lit/lit.ts +4 -1
- package/front_end/ui/lit/render.ts +81 -0
- package/front_end/ui/visual_logging/KnownContextValues.ts +3 -1
- package/package.json +1 -1
- /package/front_end/third_party/codemirror/package/addon/runmode/{runmode-standalone.mjs.d.ts → runmode-standalone.d.mts} +0 -0
- /package/front_end/third_party/codemirror/package/mode/css/{css.mjs.d.ts → css.d.mts} +0 -0
- /package/front_end/third_party/codemirror/package/mode/javascript/{javascript.mjs.d.ts → javascript.d.mts} +0 -0
- /package/front_end/third_party/codemirror/package/mode/xml/{xml.mjs.d.ts → xml.d.mts} +0 -0
- /package/front_end/third_party/lighthouse/report-assets/{report-generator.mjs.d.ts → report-generator.d.mts} +0 -0
|
@@ -0,0 +1,161 @@
|
|
|
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 Common from '../../core/common/common.js';
|
|
6
|
+
import type * as Platform from '../../core/platform/platform.js';
|
|
7
|
+
import type * as Protocol from '../../generated/protocol.js';
|
|
8
|
+
|
|
9
|
+
import type {RawFrame} from './Trie.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Takes a V8 Error#stack string and extracts structured information.
|
|
13
|
+
*/
|
|
14
|
+
export function parseRawFramesFromErrorStack(stack: string): RawFrame[] {
|
|
15
|
+
const lines = stack.split('\n');
|
|
16
|
+
const rawFrames: RawFrame[] = [];
|
|
17
|
+
for (const line of lines) {
|
|
18
|
+
const match = /^\s*at\s+(.*)/.exec(line);
|
|
19
|
+
if (!match) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let lineContent = match[1];
|
|
24
|
+
let isAsync = false;
|
|
25
|
+
if (lineContent.startsWith('async ')) {
|
|
26
|
+
isAsync = true;
|
|
27
|
+
lineContent = lineContent.substring(6);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let isConstructor = false;
|
|
31
|
+
if (lineContent.startsWith('new ')) {
|
|
32
|
+
isConstructor = true;
|
|
33
|
+
lineContent = lineContent.substring(4);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let functionName = '';
|
|
37
|
+
let url = '';
|
|
38
|
+
let lineNumber = -1;
|
|
39
|
+
let columnNumber = -1;
|
|
40
|
+
let typeName: string|undefined;
|
|
41
|
+
let methodName: string|undefined;
|
|
42
|
+
let isEval = false;
|
|
43
|
+
let isWasm = false;
|
|
44
|
+
let wasmModuleName: string|undefined;
|
|
45
|
+
let wasmFunctionIndex: number|undefined;
|
|
46
|
+
let promiseIndex: number|undefined;
|
|
47
|
+
let evalOrigin: RawFrame|undefined;
|
|
48
|
+
|
|
49
|
+
const openParenIndex = lineContent.indexOf(' (');
|
|
50
|
+
if (lineContent.endsWith(')') && openParenIndex !== -1) {
|
|
51
|
+
functionName = lineContent.substring(0, openParenIndex).trim();
|
|
52
|
+
let location = lineContent.substring(openParenIndex + 2, lineContent.length - 1);
|
|
53
|
+
|
|
54
|
+
if (location.startsWith('eval at ')) {
|
|
55
|
+
isEval = true;
|
|
56
|
+
const commaIndex = location.lastIndexOf(', ');
|
|
57
|
+
let evalOriginStr = location;
|
|
58
|
+
if (commaIndex !== -1) {
|
|
59
|
+
evalOriginStr = location.substring(0, commaIndex);
|
|
60
|
+
location = location.substring(commaIndex + 2);
|
|
61
|
+
} else {
|
|
62
|
+
location = '';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (evalOriginStr.startsWith('eval at ')) {
|
|
66
|
+
evalOriginStr = evalOriginStr.substring(8);
|
|
67
|
+
}
|
|
68
|
+
const innerOpenParen = evalOriginStr.indexOf(' (');
|
|
69
|
+
let evalFunctionName = evalOriginStr;
|
|
70
|
+
let evalLocation = '';
|
|
71
|
+
if (innerOpenParen !== -1) {
|
|
72
|
+
evalFunctionName = evalOriginStr.substring(0, innerOpenParen).trim();
|
|
73
|
+
evalLocation = evalOriginStr.substring(innerOpenParen + 2, evalOriginStr.length - 1);
|
|
74
|
+
evalOrigin = parseRawFramesFromErrorStack(` at ${evalFunctionName} (${evalLocation})`)[0];
|
|
75
|
+
} else {
|
|
76
|
+
evalOrigin = parseRawFramesFromErrorStack(` at ${evalFunctionName}`)[0];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (location.startsWith('index ')) {
|
|
81
|
+
promiseIndex = parseInt(location.substring(6), 10);
|
|
82
|
+
url = '';
|
|
83
|
+
} else if (location.includes(':wasm-function[')) {
|
|
84
|
+
isWasm = true;
|
|
85
|
+
const wasmMatch = /^(.*):wasm-function\[(\d+)\]:(0x[0-9a-fA-F]+)$/.exec(location);
|
|
86
|
+
if (wasmMatch) {
|
|
87
|
+
url = wasmMatch[1];
|
|
88
|
+
wasmFunctionIndex = parseInt(wasmMatch[2], 10);
|
|
89
|
+
columnNumber = parseInt(wasmMatch[3], 16);
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
const splitResult = Common.ParsedURL.ParsedURL.splitLineAndColumn(location);
|
|
93
|
+
url = splitResult.url;
|
|
94
|
+
lineNumber = splitResult.lineNumber ?? -1;
|
|
95
|
+
columnNumber = splitResult.columnNumber ?? -1;
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
const splitResult = Common.ParsedURL.ParsedURL.splitLineAndColumn(lineContent);
|
|
99
|
+
url = splitResult.url;
|
|
100
|
+
lineNumber = splitResult.lineNumber ?? -1;
|
|
101
|
+
columnNumber = splitResult.columnNumber ?? -1;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Handle "typeName.methodName [as alias]"
|
|
105
|
+
if (functionName) {
|
|
106
|
+
const aliasMatch = /(.*)\s+\[as\s+(.*)\]/.exec(functionName);
|
|
107
|
+
if (aliasMatch) {
|
|
108
|
+
methodName = aliasMatch[2];
|
|
109
|
+
functionName = aliasMatch[1];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const dotIndex = functionName.indexOf('.');
|
|
113
|
+
if (dotIndex !== -1) {
|
|
114
|
+
typeName = functionName.substring(0, dotIndex);
|
|
115
|
+
methodName = methodName ?? functionName.substring(dotIndex + 1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (isWasm && typeName) {
|
|
119
|
+
wasmModuleName = typeName;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
rawFrames.push({
|
|
124
|
+
url: url as Platform.DevToolsPath.UrlString,
|
|
125
|
+
functionName,
|
|
126
|
+
lineNumber,
|
|
127
|
+
columnNumber,
|
|
128
|
+
parsedFrameInfo: {
|
|
129
|
+
isAsync,
|
|
130
|
+
isConstructor,
|
|
131
|
+
isEval,
|
|
132
|
+
evalOrigin,
|
|
133
|
+
isWasm,
|
|
134
|
+
wasmModuleName,
|
|
135
|
+
wasmFunctionIndex,
|
|
136
|
+
typeName,
|
|
137
|
+
methodName,
|
|
138
|
+
promiseIndex,
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return rawFrames;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Error#stack output only contains script URLs. In some cases we are able to
|
|
147
|
+
* retrieve additional exception details from V8 that we can use to augment
|
|
148
|
+
* the parsed Error#stack with script IDs.
|
|
149
|
+
*/
|
|
150
|
+
export function augmentRawFramesWithScriptIds(
|
|
151
|
+
rawFrames: RawFrame[], protocolStackTrace: Protocol.Runtime.StackTrace): void {
|
|
152
|
+
for (const rawFrame of rawFrames) {
|
|
153
|
+
const protocolFrame = protocolStackTrace.callFrames.find(
|
|
154
|
+
frame => rawFrame.url === frame.url && rawFrame.lineNumber === frame.lineNumber &&
|
|
155
|
+
rawFrame.columnNumber === frame.columnNumber);
|
|
156
|
+
if (protocolFrame) {
|
|
157
|
+
// @ts-expect-error scriptId is a readonly property.
|
|
158
|
+
rawFrame.scriptId = protocolFrame.scriptId;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -8,6 +8,7 @@ import type * as Workspace from '../workspace/workspace.js';
|
|
|
8
8
|
|
|
9
9
|
export type StackTrace = BaseStackTrace<Fragment>;
|
|
10
10
|
export type DebuggableStackTrace = BaseStackTrace<DebuggableFragment>;
|
|
11
|
+
export type ParsedErrorStackTrace = BaseStackTrace<ParsedErrorStackFragment>;
|
|
11
12
|
|
|
12
13
|
export interface BaseStackTrace<SyncFragmentT extends Fragment> extends Common.EventTarget.EventTarget<EventTypes> {
|
|
13
14
|
readonly syncFragment: SyncFragmentT;
|
|
@@ -26,6 +27,10 @@ export interface DebuggableFragment {
|
|
|
26
27
|
readonly frames: readonly DebuggableFrame[];
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
export interface ParsedErrorStackFragment {
|
|
31
|
+
readonly frames: readonly ParsedErrorStackFrame[];
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
export interface Frame {
|
|
30
35
|
readonly url?: string;
|
|
31
36
|
readonly uiSourceCode?: Workspace.UISourceCode.UISourceCode;
|
|
@@ -41,6 +46,19 @@ export interface Frame {
|
|
|
41
46
|
readonly rawName?: string;
|
|
42
47
|
}
|
|
43
48
|
|
|
49
|
+
export interface ParsedErrorStackFrame extends Frame {
|
|
50
|
+
readonly isAsync?: boolean;
|
|
51
|
+
readonly isConstructor?: boolean;
|
|
52
|
+
readonly isEval?: boolean;
|
|
53
|
+
readonly evalOrigin?: ParsedErrorStackFrame;
|
|
54
|
+
readonly isWasm?: boolean;
|
|
55
|
+
readonly wasmModuleName?: string;
|
|
56
|
+
readonly wasmFunctionIndex?: number;
|
|
57
|
+
readonly typeName?: string;
|
|
58
|
+
readonly methodName?: string;
|
|
59
|
+
readonly promiseIndex?: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
44
62
|
export interface DebuggableFrame extends Frame {
|
|
45
63
|
readonly sdkFrame: SDK.DebuggerModel.CallFrame;
|
|
46
64
|
}
|
|
@@ -7,11 +7,12 @@ import type * as SDK from '../../core/sdk/sdk.js';
|
|
|
7
7
|
import type * as Workspace from '../workspace/workspace.js';
|
|
8
8
|
|
|
9
9
|
import type * as StackTrace from './stack_trace.js';
|
|
10
|
-
import type {FrameNode} from './Trie.js';
|
|
10
|
+
import type {FrameNode, ParsedFrameInfo} from './Trie.js';
|
|
11
11
|
|
|
12
|
-
export type AnyStackTraceImpl = StackTraceImpl<FragmentImpl|DebuggableFragmentImpl>;
|
|
12
|
+
export type AnyStackTraceImpl = StackTraceImpl<FragmentImpl|DebuggableFragmentImpl|ParsedErrorStackFragmentImpl>;
|
|
13
13
|
|
|
14
|
-
export class StackTraceImpl<SyncFragmentT extends FragmentImpl|DebuggableFragmentImpl =
|
|
14
|
+
export class StackTraceImpl<SyncFragmentT extends FragmentImpl|DebuggableFragmentImpl|ParsedErrorStackFragmentImpl =
|
|
15
|
+
FragmentImpl> extends
|
|
15
16
|
Common.ObjectWrapper.ObjectWrapper<StackTrace.StackTrace.EventTypes> implements
|
|
16
17
|
StackTrace.StackTrace.BaseStackTrace<SyncFragmentT> {
|
|
17
18
|
readonly syncFragment: SyncFragmentT;
|
|
@@ -23,7 +24,9 @@ export class StackTraceImpl<SyncFragmentT extends FragmentImpl|DebuggableFragmen
|
|
|
23
24
|
this.asyncFragments = asyncFragments;
|
|
24
25
|
|
|
25
26
|
const fragment =
|
|
26
|
-
syncFragment instanceof DebuggableFragmentImpl
|
|
27
|
+
(syncFragment instanceof DebuggableFragmentImpl || syncFragment instanceof ParsedErrorStackFragmentImpl) ?
|
|
28
|
+
syncFragment.fragment :
|
|
29
|
+
syncFragment as FragmentImpl;
|
|
27
30
|
fragment.stackTraces.add(this);
|
|
28
31
|
|
|
29
32
|
this.asyncFragments.forEach(asyncFragment => asyncFragment.fragment.stackTraces.add(this));
|
|
@@ -99,6 +102,95 @@ export class FrameImpl implements StackTrace.StackTrace.Frame {
|
|
|
99
102
|
}
|
|
100
103
|
}
|
|
101
104
|
|
|
105
|
+
export class ParsedErrorStackFragmentImpl implements StackTrace.StackTrace.ParsedErrorStackFragment {
|
|
106
|
+
constructor(readonly fragment: FragmentImpl) {
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
get frames(): ParsedErrorStackFrameImpl[] {
|
|
110
|
+
if (!this.fragment.node) {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const frames: ParsedErrorStackFrameImpl[] = [];
|
|
115
|
+
|
|
116
|
+
for (const node of this.fragment.node.getCallStack()) {
|
|
117
|
+
for (const frame of node.frames) {
|
|
118
|
+
frames.push(new ParsedErrorStackFrameImpl(frame, node.parsedFrameInfo, node.evalOriginFrames));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return frames;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export class ParsedErrorStackFrameImpl implements StackTrace.StackTrace.ParsedErrorStackFrame {
|
|
127
|
+
readonly #frame: FrameImpl;
|
|
128
|
+
readonly #parsedFrameInfo?: ParsedFrameInfo;
|
|
129
|
+
readonly #evalOriginFrames?: FrameImpl[];
|
|
130
|
+
|
|
131
|
+
constructor(frame: FrameImpl, parsedFrameInfo?: ParsedFrameInfo, evalOriginFrames?: FrameImpl[]) {
|
|
132
|
+
this.#frame = frame;
|
|
133
|
+
this.#parsedFrameInfo = parsedFrameInfo;
|
|
134
|
+
this.#evalOriginFrames = evalOriginFrames;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
get url(): string|undefined {
|
|
138
|
+
return this.#frame.url;
|
|
139
|
+
}
|
|
140
|
+
get uiSourceCode(): Workspace.UISourceCode.UISourceCode|undefined {
|
|
141
|
+
return this.#frame.uiSourceCode;
|
|
142
|
+
}
|
|
143
|
+
get name(): string|undefined {
|
|
144
|
+
return this.#frame.name;
|
|
145
|
+
}
|
|
146
|
+
get line(): number {
|
|
147
|
+
return this.#frame.line;
|
|
148
|
+
}
|
|
149
|
+
get column(): number {
|
|
150
|
+
return this.#frame.column;
|
|
151
|
+
}
|
|
152
|
+
get missingDebugInfo(): StackTrace.StackTrace.MissingDebugInfo|undefined {
|
|
153
|
+
return this.#frame.missingDebugInfo;
|
|
154
|
+
}
|
|
155
|
+
get rawName(): string|undefined {
|
|
156
|
+
return this.#frame.rawName;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
get isAsync(): boolean|undefined {
|
|
160
|
+
return this.#parsedFrameInfo?.isAsync;
|
|
161
|
+
}
|
|
162
|
+
get isConstructor(): boolean|undefined {
|
|
163
|
+
return this.#parsedFrameInfo?.isConstructor;
|
|
164
|
+
}
|
|
165
|
+
get isEval(): boolean|undefined {
|
|
166
|
+
return this.#parsedFrameInfo?.isEval;
|
|
167
|
+
}
|
|
168
|
+
get evalOrigin(): ParsedErrorStackFrameImpl|undefined {
|
|
169
|
+
if (!this.#evalOriginFrames || this.#evalOriginFrames.length === 0) {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
return new ParsedErrorStackFrameImpl(this.#evalOriginFrames[0], this.#parsedFrameInfo?.evalOrigin?.parsedFrameInfo);
|
|
173
|
+
}
|
|
174
|
+
get isWasm(): boolean|undefined {
|
|
175
|
+
return this.#parsedFrameInfo?.isWasm;
|
|
176
|
+
}
|
|
177
|
+
get wasmModuleName(): string|undefined {
|
|
178
|
+
return this.#parsedFrameInfo?.wasmModuleName;
|
|
179
|
+
}
|
|
180
|
+
get wasmFunctionIndex(): number|undefined {
|
|
181
|
+
return this.#parsedFrameInfo?.wasmFunctionIndex;
|
|
182
|
+
}
|
|
183
|
+
get typeName(): string|undefined {
|
|
184
|
+
return this.#parsedFrameInfo?.typeName;
|
|
185
|
+
}
|
|
186
|
+
get methodName(): string|undefined {
|
|
187
|
+
return this.#parsedFrameInfo?.methodName;
|
|
188
|
+
}
|
|
189
|
+
get promiseIndex(): number|undefined {
|
|
190
|
+
return this.#parsedFrameInfo?.promiseIndex;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
102
194
|
/**
|
|
103
195
|
* A DebuggableFragmentImpl wraps an existing FragmentImpl. This is important: We can pause at the
|
|
104
196
|
* same location multiple times and the paused information changes each and everytime while the underlying
|
|
@@ -6,6 +6,7 @@ import * as Common from '../../core/common/common.js';
|
|
|
6
6
|
import * as SDK from '../../core/sdk/sdk.js';
|
|
7
7
|
import type * as Protocol from '../../generated/protocol.js';
|
|
8
8
|
|
|
9
|
+
import {augmentRawFramesWithScriptIds, parseRawFramesFromErrorStack} from './DetailedErrorStackParser.js';
|
|
9
10
|
// eslint-disable-next-line @devtools/es-modules-import
|
|
10
11
|
import * as StackTrace from './stack_trace.js';
|
|
11
12
|
import {
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
DebuggableFragmentImpl,
|
|
15
16
|
FragmentImpl,
|
|
16
17
|
FrameImpl,
|
|
18
|
+
ParsedErrorStackFragmentImpl,
|
|
17
19
|
StackTraceImpl
|
|
18
20
|
} from './StackTraceImpl.js';
|
|
19
21
|
import {type FrameNode, type RawFrame, Trie} from './Trie.js';
|
|
@@ -54,6 +56,23 @@ export class StackTraceModel extends SDK.SDKModel.SDKModel<unknown> {
|
|
|
54
56
|
return new StackTraceImpl(syncFragment, asyncFragments);
|
|
55
57
|
}
|
|
56
58
|
|
|
59
|
+
async createFromErrorStackLikeString(
|
|
60
|
+
stack: string, rawFramesToUIFrames: TranslateRawFrames,
|
|
61
|
+
exceptionDetails?: Protocol.Runtime.ExceptionDetails): Promise<StackTrace.StackTrace.ParsedErrorStackTrace> {
|
|
62
|
+
const rawFrames = parseRawFramesFromErrorStack(stack);
|
|
63
|
+
if (exceptionDetails?.stackTrace) {
|
|
64
|
+
augmentRawFramesWithScriptIds(rawFrames, exceptionDetails.stackTrace);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const [syncFragment, asyncFragments] = await Promise.all([
|
|
68
|
+
this.#createFragment(rawFrames, rawFramesToUIFrames),
|
|
69
|
+
exceptionDetails?.stackTrace ? this.#createAsyncFragments(exceptionDetails.stackTrace, rawFramesToUIFrames) :
|
|
70
|
+
Promise.resolve([]),
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
return new StackTraceImpl(new ParsedErrorStackFragmentImpl(syncFragment), asyncFragments);
|
|
74
|
+
}
|
|
75
|
+
|
|
57
76
|
async createFromDebuggerPaused(
|
|
58
77
|
pausedDetails: SDK.DebuggerModel.DebuggerPausedDetails,
|
|
59
78
|
rawFramesToUIFrames: TranslateRawFrames): Promise<StackTrace.StackTrace.DebuggableStackTrace> {
|
|
@@ -162,12 +181,32 @@ export class StackTraceModel extends SDK.SDKModel.SDKModel<unknown> {
|
|
|
162
181
|
const uiFrames = await rawFramesToUIFrames(rawFrames, this.target());
|
|
163
182
|
console.assert(rawFrames.length === uiFrames.length, 'Broken rawFramesToUIFrames implementation');
|
|
164
183
|
|
|
184
|
+
const evalOriginPromises: Array<ReturnType<TranslateRawFrames>> = [];
|
|
185
|
+
for (const node of fragment.node.getCallStack()) {
|
|
186
|
+
if (node.parsedFrameInfo?.evalOrigin) {
|
|
187
|
+
// Evaluate each eval origin individually, as they are not a contiguous stack trace.
|
|
188
|
+
evalOriginPromises.push(rawFramesToUIFrames([node.parsedFrameInfo.evalOrigin], this.target()));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const evalUiFrames = await Promise.all(evalOriginPromises);
|
|
193
|
+
|
|
165
194
|
let i = 0;
|
|
195
|
+
let evalI = 0;
|
|
166
196
|
for (const node of fragment.node.getCallStack()) {
|
|
167
197
|
node.frames = uiFrames[i++].map(
|
|
168
198
|
frame => new FrameImpl(
|
|
169
199
|
frame.url, frame.uiSourceCode, frame.name, frame.line, frame.column, frame.missingDebugInfo,
|
|
170
200
|
node.rawFrame.functionName));
|
|
201
|
+
|
|
202
|
+
if (node.parsedFrameInfo?.evalOrigin) {
|
|
203
|
+
const evalOriginRawFrame = node.parsedFrameInfo.evalOrigin;
|
|
204
|
+
// evalUiFrames[evalI] is Array<Array<Frame>>, and since we passed a 1-element array, we take [0]
|
|
205
|
+
node.evalOriginFrames = evalUiFrames[evalI++][0].map(
|
|
206
|
+
frame => new FrameImpl(
|
|
207
|
+
frame.url, frame.uiSourceCode, frame.name, frame.line, frame.column, frame.missingDebugInfo,
|
|
208
|
+
evalOriginRawFrame.functionName));
|
|
209
|
+
}
|
|
171
210
|
}
|
|
172
211
|
}
|
|
173
212
|
|
|
@@ -6,6 +6,19 @@ import type * as Protocol from '../../generated/protocol.js';
|
|
|
6
6
|
|
|
7
7
|
import type {FragmentImpl, FrameImpl} from './StackTraceImpl.js';
|
|
8
8
|
|
|
9
|
+
export interface ParsedFrameInfo {
|
|
10
|
+
readonly isAsync?: boolean;
|
|
11
|
+
readonly isConstructor?: boolean;
|
|
12
|
+
readonly isEval?: boolean;
|
|
13
|
+
readonly evalOrigin?: RawFrame;
|
|
14
|
+
readonly isWasm?: boolean;
|
|
15
|
+
readonly wasmModuleName?: string;
|
|
16
|
+
readonly wasmFunctionIndex?: number;
|
|
17
|
+
readonly typeName?: string;
|
|
18
|
+
readonly methodName?: string;
|
|
19
|
+
readonly promiseIndex?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
9
22
|
/**
|
|
10
23
|
* Intentionally very close to a {@link Protocol.Runtime.CallFrame} but with optional `scriptId`.
|
|
11
24
|
*/
|
|
@@ -15,6 +28,8 @@ export interface RawFrame {
|
|
|
15
28
|
readonly functionName?: string;
|
|
16
29
|
readonly lineNumber: number;
|
|
17
30
|
readonly columnNumber: number;
|
|
31
|
+
|
|
32
|
+
readonly parsedFrameInfo?: ParsedFrameInfo;
|
|
18
33
|
}
|
|
19
34
|
|
|
20
35
|
/**
|
|
@@ -42,10 +57,13 @@ export class FrameNode implements FrameNodeBase<FrameNode, AnyFrameNode> {
|
|
|
42
57
|
frames: FrameImpl[] = [];
|
|
43
58
|
|
|
44
59
|
fragment?: FragmentImpl;
|
|
60
|
+
parsedFrameInfo?: ParsedFrameInfo;
|
|
61
|
+
evalOriginFrames?: FrameImpl[];
|
|
45
62
|
|
|
46
63
|
constructor(rawFrame: RawFrame, parent: AnyFrameNode) {
|
|
47
64
|
this.rawFrame = rawFrame;
|
|
48
65
|
this.parent = parent;
|
|
66
|
+
this.parsedFrameInfo = rawFrame.parsedFrameInfo;
|
|
49
67
|
}
|
|
50
68
|
|
|
51
69
|
/**
|
|
@@ -98,6 +116,9 @@ export class Trie {
|
|
|
98
116
|
|
|
99
117
|
const compareResult = compareRawFrames(child.rawFrame, rawFrame);
|
|
100
118
|
if (compareResult === 0) {
|
|
119
|
+
if (rawFrame.parsedFrameInfo && !child.parsedFrameInfo) {
|
|
120
|
+
child.parsedFrameInfo = rawFrame.parsedFrameInfo;
|
|
121
|
+
}
|
|
101
122
|
return child;
|
|
102
123
|
}
|
|
103
124
|
if (compareResult > 0) {
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
// Use of this source code is governed by a BSD-style license that can be
|
|
3
3
|
// found in the LICENSE file.
|
|
4
4
|
|
|
5
|
+
import * as DetailedErrorStackParser from './DetailedErrorStackParser.js';
|
|
5
6
|
import * as StackTraceImpl from './StackTraceImpl.js';
|
|
6
7
|
import * as StackTraceModel from './StackTraceModel.js';
|
|
7
8
|
import * as Trie from './Trie.js';
|
|
8
9
|
|
|
9
10
|
export {
|
|
11
|
+
DetailedErrorStackParser,
|
|
10
12
|
StackTraceImpl,
|
|
11
13
|
StackTraceModel,
|
|
12
14
|
Trie,
|
|
@@ -13,6 +13,18 @@ import {MarkdownRendererWithCodeBlock} from './MarkdownRendererWithCodeBlock.js'
|
|
|
13
13
|
const {html} = Lit.StaticHtml;
|
|
14
14
|
const {until} = Lit.Directives;
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Represents the different types of links that can be parsed from the AI agent's response.
|
|
18
|
+
* The agent can linkify a node either by its backend node ID or by its full DOM path.
|
|
19
|
+
*/
|
|
20
|
+
type ParsedLink = {
|
|
21
|
+
type: 'path',
|
|
22
|
+
path: string,
|
|
23
|
+
}|{
|
|
24
|
+
type: 'node',
|
|
25
|
+
nodeId: Protocol.DOM.BackendNodeId,
|
|
26
|
+
};
|
|
27
|
+
|
|
16
28
|
export class AccessibilityAgentMarkdownRenderer extends MarkdownRendererWithCodeBlock {
|
|
17
29
|
constructor(
|
|
18
30
|
private mainFrameId = '',
|
|
@@ -22,28 +34,54 @@ export class AccessibilityAgentMarkdownRenderer extends MarkdownRendererWithCode
|
|
|
22
34
|
|
|
23
35
|
override templateForToken(token: Marked.Marked.MarkedToken): Lit.LitTemplate|null {
|
|
24
36
|
if (token.type === 'link' && token.href.startsWith('#')) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
37
|
+
const parsed = this.#parseLink(token.href);
|
|
38
|
+
if (parsed) {
|
|
39
|
+
const resultPromise = parsed.type === 'path' ? this.#linkifyPath(parsed.path, token.text) :
|
|
40
|
+
this.#linkifyNode(parsed.nodeId, token.text);
|
|
30
41
|
|
|
31
|
-
|
|
32
|
-
if (token.href.startsWith('#node-')) {
|
|
33
|
-
nodeId = Number(token.href.replace('#node-', '')) as Protocol.DOM.BackendNodeId;
|
|
34
|
-
} else if (token.href.startsWith('#')) {
|
|
35
|
-
nodeId = Number(token.href.replace('#', '')) as Protocol.DOM.BackendNodeId;
|
|
42
|
+
return html`<span>${until(resultPromise.then(node => node || token.text), token.text)}</span>`;
|
|
36
43
|
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return super.templateForToken(token);
|
|
47
|
+
}
|
|
37
48
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Parses a link href to determine if it's a node ID or a DOM path.
|
|
51
|
+
*
|
|
52
|
+
* The AI agent is instructed to use #node-ID or #path-PATH, but
|
|
53
|
+
* sometimes it omits the prefixes, in which case we try to detect
|
|
54
|
+
* paths by looking for `#1,HTML` which is often how paths in LH
|
|
55
|
+
* start.
|
|
56
|
+
*/
|
|
57
|
+
#parseLink(href: string): ParsedLink|null {
|
|
58
|
+
if (href.startsWith('#path-')) {
|
|
59
|
+
return {type: 'path', path: href.replace('#path-', '')};
|
|
60
|
+
}
|
|
61
|
+
if (href.startsWith('#1,HTML')) {
|
|
62
|
+
return {type: 'path', path: href.slice(1)};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let nodeIdStr = '';
|
|
66
|
+
if (href.startsWith('#node-')) {
|
|
67
|
+
nodeIdStr = href.replace('#node-', '');
|
|
68
|
+
} else if (href.startsWith('#')) {
|
|
69
|
+
nodeIdStr = href.slice(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (nodeIdStr.trim() !== '') {
|
|
73
|
+
const nodeId = Number(nodeIdStr);
|
|
74
|
+
if (Number.isInteger(nodeId)) {
|
|
75
|
+
return {type: 'node', nodeId: nodeId as Protocol.DOM.BackendNodeId};
|
|
41
76
|
}
|
|
42
77
|
}
|
|
43
78
|
|
|
44
|
-
return
|
|
79
|
+
return null;
|
|
45
80
|
}
|
|
46
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Linkifies a node using its backend node ID.
|
|
84
|
+
*/
|
|
47
85
|
async #linkifyNode(backendNodeId: Protocol.DOM.BackendNodeId, label: string): Promise<Lit.LitTemplate|undefined> {
|
|
48
86
|
if (backendNodeId === undefined) {
|
|
49
87
|
return;
|
|
@@ -68,6 +106,9 @@ export class AccessibilityAgentMarkdownRenderer extends MarkdownRendererWithCode
|
|
|
68
106
|
return linkedNode;
|
|
69
107
|
}
|
|
70
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Linkifies a node using its full DOM path (e.g. "1,HTML,1,BODY,...").
|
|
111
|
+
*/
|
|
71
112
|
async #linkifyPath(path: string, label: string): Promise<Lit.LitTemplate|undefined> {
|
|
72
113
|
const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
|
|
73
114
|
const domModel = target?.model(SDK.DOMModel.DOMModel);
|
|
@@ -96,7 +96,7 @@ export interface Props {
|
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
interface ChatWidgetInput extends Props {
|
|
99
|
+
export interface ChatWidgetInput extends Props {
|
|
100
100
|
handleScroll: (ev: Event) => void;
|
|
101
101
|
handleSuggestionClick: (title: string) => void;
|
|
102
102
|
handleMessageContainerRef: (el: Element|undefined) => void;
|
|
@@ -367,12 +367,13 @@ export class ChatView extends HTMLElement {
|
|
|
367
367
|
};
|
|
368
368
|
|
|
369
369
|
async #getSummary(): Promise<string> {
|
|
370
|
-
|
|
370
|
+
const cacheKey = this.#props.conversationMarkdown.replace(/\*\*Export Timestamp \(UTC\):\*\* .*\n\n/, '');
|
|
371
|
+
if (this.#cachedSummary?.markdown === cacheKey) {
|
|
371
372
|
return this.#cachedSummary.summary;
|
|
372
373
|
}
|
|
373
374
|
try {
|
|
374
375
|
const summary = await this.#props.generateConversationSummary(this.#props.conversationMarkdown);
|
|
375
|
-
this.#cachedSummary = {markdown:
|
|
376
|
+
this.#cachedSummary = {markdown: cacheKey, summary};
|
|
376
377
|
return summary;
|
|
377
378
|
} catch (err) {
|
|
378
379
|
console.error(err);
|
|
@@ -123,7 +123,10 @@ export const DEFAULT_VIEW: View = (input, _output, target): void => {
|
|
|
123
123
|
${i18nString(UIStrings.generatingSummary)}
|
|
124
124
|
</span>
|
|
125
125
|
` : Lit.nothing}
|
|
126
|
-
|
|
126
|
+
${isPrompt ?
|
|
127
|
+
html`<textarea class="prompt" readonly .value=${input.state.isPromptLoading ? '' : exportText}></textarea>` :
|
|
128
|
+
html`<textarea class="conversation" readonly .value=${exportText}></textarea>`
|
|
129
|
+
}
|
|
127
130
|
</main>
|
|
128
131
|
<div class="disclaimer">${i18nString(UIStrings.disclaimer)}</div>
|
|
129
132
|
<footer>
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
gap: var(--sys-size-8);
|
|
26
26
|
margin-bottom: var(--sys-size-8);
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
h1 {
|
|
29
29
|
margin: 0;
|
|
30
30
|
color: var(--sys-color-on-surface);
|
|
31
31
|
font: var(--sys-typescale-headline5);
|
|
@@ -47,7 +47,6 @@
|
|
|
47
47
|
devtools-icon {
|
|
48
48
|
width: var(--sys-size-9);
|
|
49
49
|
height: var(--sys-size-9);
|
|
50
|
-
color: var(--sys-color-on-primary);
|
|
51
50
|
}
|
|
52
51
|
}
|
|
53
52
|
}
|