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.
Files changed (64) hide show
  1. package/AUTHORS +1 -0
  2. package/docs/contributing/infrastructure.md +0 -1
  3. package/front_end/core/common/VersionController.ts +17 -1
  4. package/front_end/core/host/UserMetrics.ts +0 -1
  5. package/front_end/core/root/ExperimentNames.ts +0 -1
  6. package/front_end/core/sdk/OverlayModel.ts +2 -4
  7. package/front_end/core/sdk/sdk-meta.ts +13 -0
  8. package/front_end/entrypoints/device_mode_emulation_frame/device_mode_emulation_frame.ts +4 -0
  9. package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +0 -1
  10. package/front_end/entrypoints/greendev_floaty/floaty.css +3 -0
  11. package/front_end/entrypoints/greendev_floaty/greendev_floaty.ts +0 -1
  12. package/front_end/entrypoints/main/MainImpl.ts +0 -6
  13. package/front_end/entrypoints/shell/shell.ts +4 -0
  14. package/front_end/entrypoints/trace_app/trace_app.ts +4 -0
  15. package/front_end/generated/InspectorBackendCommands.ts +6 -4
  16. package/front_end/generated/protocol-mapping.d.ts +14 -0
  17. package/front_end/generated/protocol-proxy-api.d.ts +10 -0
  18. package/front_end/generated/protocol.ts +33 -3
  19. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +10 -2
  20. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +23 -7
  21. package/front_end/models/ai_assistance/agents/PerformanceAgent.snapshot.txt +4 -1
  22. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +200 -46
  23. package/front_end/models/issues_manager/IssuesManager.ts +4 -0
  24. package/front_end/models/javascript_metadata/NativeFunctions.js +673 -639
  25. package/front_end/models/stack_trace/DetailedErrorStackParser.ts +161 -0
  26. package/front_end/models/stack_trace/StackTrace.ts +18 -0
  27. package/front_end/models/stack_trace/StackTraceImpl.ts +96 -4
  28. package/front_end/models/stack_trace/StackTraceModel.ts +39 -0
  29. package/front_end/models/stack_trace/Trie.ts +21 -0
  30. package/front_end/models/stack_trace/stack_trace_impl.ts +2 -0
  31. package/front_end/panels/ai_assistance/components/AccessibilityAgentMarkdownRenderer.ts +55 -14
  32. package/front_end/panels/ai_assistance/components/ChatView.ts +4 -3
  33. package/front_end/panels/ai_assistance/components/ExportForAgentsDialog.ts +4 -1
  34. package/front_end/panels/ai_assistance/components/optInChangeDialog.css +1 -2
  35. package/front_end/panels/application/WebMCPView.ts +270 -18
  36. package/front_end/panels/application/components/ProtocolHandlersView.ts +2 -2
  37. package/front_end/panels/application/webMCPView.css +4 -1
  38. package/front_end/panels/common/AiCodeCompletionDisclaimer.ts +7 -0
  39. package/front_end/panels/console/ConsoleContextSelector.ts +1 -1
  40. package/front_end/panels/console/ConsoleViewMessage.ts +8 -2
  41. package/front_end/panels/console/consoleView.css +4 -0
  42. package/front_end/panels/css_overview/CSSOverviewCompletedView.ts +2 -3
  43. package/front_end/panels/css_overview/CSSOverviewModel.ts +1 -2
  44. package/front_end/panels/network/RequestConditionsDrawer.ts +4 -2
  45. package/front_end/panels/network/RequestPayloadView.ts +8 -3
  46. package/front_end/panels/network/RequestTimingView.ts +6 -7
  47. package/front_end/panels/performance_monitor/PerformanceMonitor.ts +7 -5
  48. package/front_end/third_party/chromium/README.chromium +1 -1
  49. package/front_end/third_party/codemirror/codemirror-tsconfig.json +4 -4
  50. package/front_end/third_party/lighthouse/lighthouse-tsconfig.json +1 -1
  51. package/front_end/tsconfig.json +2 -1
  52. package/front_end/ui/legacy/EmptyWidget.ts +2 -2
  53. package/front_end/ui/legacy/components/color_picker/ContrastDetails.ts +1 -2
  54. package/front_end/ui/legacy/components/color_picker/ContrastOverlay.ts +4 -5
  55. package/front_end/ui/legacy/components/source_frame/XMLView.ts +12 -7
  56. package/front_end/ui/lit/lit.ts +4 -1
  57. package/front_end/ui/lit/render.ts +81 -0
  58. package/front_end/ui/visual_logging/KnownContextValues.ts +3 -1
  59. package/package.json +1 -1
  60. /package/front_end/third_party/codemirror/package/addon/runmode/{runmode-standalone.mjs.d.ts → runmode-standalone.d.mts} +0 -0
  61. /package/front_end/third_party/codemirror/package/mode/css/{css.mjs.d.ts → css.d.mts} +0 -0
  62. /package/front_end/third_party/codemirror/package/mode/javascript/{javascript.mjs.d.ts → javascript.d.mts} +0 -0
  63. /package/front_end/third_party/codemirror/package/mode/xml/{xml.mjs.d.ts → xml.d.mts} +0 -0
  64. /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 = FragmentImpl> extends
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 ? syncFragment.fragment : syncFragment as FragmentImpl;
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
- if (token.href.startsWith('#path-')) {
26
- const path = token.href.replace('#path-', '');
27
- return html`<span>${
28
- until(this.#linkifyPath(path, token.text).then(node => node || token.text), token.text)}</span>`;
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
- let nodeId = undefined;
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
- if (nodeId) {
39
- return html`<span>${
40
- until(this.#linkifyNode(nodeId, token.text).then(node => node || token.text), token.text)}</span>`;
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 super.templateForToken(token);
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
- if (this.#cachedSummary?.markdown === this.#props.conversationMarkdown) {
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: this.#props.conversationMarkdown, summary};
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
- <textarea readonly .value=${isPrompt && input.state.isPromptLoading ? '' : exportText}></textarea>
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
- h2 {
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
  }