chrome-devtools-frontend 1.0.1613465 → 1.0.1614363

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 (65) hide show
  1. package/.agents/skills/verification/SKILL.md +5 -1
  2. package/AUTHORS +1 -0
  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/greendev_floaty.ts +0 -1
  11. package/front_end/entrypoints/main/MainImpl.ts +0 -6
  12. package/front_end/entrypoints/shell/shell.ts +4 -0
  13. package/front_end/entrypoints/trace_app/trace_app.ts +4 -0
  14. package/front_end/generated/InspectorBackendCommands.ts +2 -2
  15. package/front_end/generated/protocol.ts +5 -3
  16. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +10 -2
  17. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +23 -7
  18. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +161 -36
  19. package/front_end/models/ai_assistance/agents/README.md +77 -0
  20. package/front_end/models/heap_snapshot/HeapSnapshotProxy.ts +6 -4
  21. package/front_end/models/javascript_metadata/NativeFunctions.js +673 -639
  22. package/front_end/models/stack_trace/DetailedErrorStackParser.ts +142 -0
  23. package/front_end/models/stack_trace/StackTrace.ts +18 -0
  24. package/front_end/models/stack_trace/StackTraceImpl.ts +96 -4
  25. package/front_end/models/stack_trace/Trie.ts +21 -0
  26. package/front_end/models/stack_trace/stack_trace_impl.ts +2 -0
  27. package/front_end/panels/ai_assistance/components/ChatMessage.ts +13 -3
  28. package/front_end/panels/ai_assistance/components/ChatView.ts +4 -3
  29. package/front_end/panels/ai_assistance/components/ExportForAgentsDialog.ts +6 -2
  30. package/front_end/panels/ai_assistance/components/WalkthroughView.ts +4 -3
  31. package/front_end/panels/ai_assistance/components/optInChangeDialog.css +1 -2
  32. package/front_end/panels/application/WebMCPView.ts +249 -17
  33. package/front_end/panels/application/components/ProtocolHandlersView.ts +2 -2
  34. package/front_end/panels/common/AiCodeCompletionDisclaimer.ts +7 -0
  35. package/front_end/panels/console/ConsoleContextSelector.ts +1 -1
  36. package/front_end/panels/css_overview/CSSOverviewCompletedView.ts +2 -3
  37. package/front_end/panels/css_overview/CSSOverviewModel.ts +1 -2
  38. package/front_end/panels/elements/StylesSidebarPane.ts +3 -1
  39. package/front_end/panels/emulation/DeviceModeToolbar.ts +13 -4
  40. package/front_end/panels/network/RequestConditionsDrawer.ts +4 -2
  41. package/front_end/panels/network/RequestPayloadView.ts +8 -3
  42. package/front_end/panels/network/RequestTimingView.ts +6 -7
  43. package/front_end/panels/performance_monitor/PerformanceMonitor.ts +7 -5
  44. package/front_end/panels/profiler/HeapSnapshotDataGrids.ts +1 -1
  45. package/front_end/panels/profiler/HeapSnapshotGridNodes.ts +9 -17
  46. package/front_end/panels/protocol_monitor/JSONEditor.ts +29 -3
  47. package/front_end/panels/settings/AISettingsTab.ts +14 -3
  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/TextPrompt.ts +1 -1
  54. package/front_end/ui/legacy/components/color_picker/ContrastDetails.ts +1 -2
  55. package/front_end/ui/legacy/components/color_picker/ContrastOverlay.ts +4 -5
  56. package/front_end/ui/legacy/components/source_frame/XMLView.ts +12 -7
  57. package/front_end/ui/lit/lit.ts +4 -1
  58. package/front_end/ui/lit/render.ts +81 -0
  59. package/front_end/ui/visual_logging/KnownContextValues.ts +8 -1
  60. package/package.json +1 -1
  61. /package/front_end/third_party/codemirror/package/addon/runmode/{runmode-standalone.mjs.d.ts → runmode-standalone.d.mts} +0 -0
  62. /package/front_end/third_party/codemirror/package/mode/css/{css.mjs.d.ts → css.d.mts} +0 -0
  63. /package/front_end/third_party/codemirror/package/mode/javascript/{javascript.mjs.d.ts → javascript.d.mts} +0 -0
  64. /package/front_end/third_party/codemirror/package/mode/xml/{xml.mjs.d.ts → xml.d.mts} +0 -0
  65. /package/front_end/third_party/lighthouse/report-assets/{report-generator.mjs.d.ts → report-generator.d.mts} +0 -0
@@ -0,0 +1,142 @@
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
+
8
+ import type {RawFrame} from './Trie.js';
9
+
10
+ /**
11
+ * Takes a V8 Error#stack string and extracts structured information.
12
+ */
13
+ export function parseRawFramesFromErrorStack(stack: string): RawFrame[] {
14
+ const lines = stack.split('\n');
15
+ const rawFrames: RawFrame[] = [];
16
+ for (const line of lines) {
17
+ const match = /^\s*at\s+(.*)/.exec(line);
18
+ if (!match) {
19
+ continue;
20
+ }
21
+
22
+ let lineContent = match[1];
23
+ let isAsync = false;
24
+ if (lineContent.startsWith('async ')) {
25
+ isAsync = true;
26
+ lineContent = lineContent.substring(6);
27
+ }
28
+
29
+ let isConstructor = false;
30
+ if (lineContent.startsWith('new ')) {
31
+ isConstructor = true;
32
+ lineContent = lineContent.substring(4);
33
+ }
34
+
35
+ let functionName = '';
36
+ let url = '';
37
+ let lineNumber = -1;
38
+ let columnNumber = -1;
39
+ let typeName: string|undefined;
40
+ let methodName: string|undefined;
41
+ let isEval = false;
42
+ let isWasm = false;
43
+ let wasmModuleName: string|undefined;
44
+ let wasmFunctionIndex: number|undefined;
45
+ let promiseIndex: number|undefined;
46
+ let evalOrigin: RawFrame|undefined;
47
+
48
+ const openParenIndex = lineContent.indexOf(' (');
49
+ if (lineContent.endsWith(')') && openParenIndex !== -1) {
50
+ functionName = lineContent.substring(0, openParenIndex).trim();
51
+ let location = lineContent.substring(openParenIndex + 2, lineContent.length - 1);
52
+
53
+ if (location.startsWith('eval at ')) {
54
+ isEval = true;
55
+ const commaIndex = location.lastIndexOf(', ');
56
+ let evalOriginStr = location;
57
+ if (commaIndex !== -1) {
58
+ evalOriginStr = location.substring(0, commaIndex);
59
+ location = location.substring(commaIndex + 2);
60
+ } else {
61
+ location = '';
62
+ }
63
+
64
+ if (evalOriginStr.startsWith('eval at ')) {
65
+ evalOriginStr = evalOriginStr.substring(8);
66
+ }
67
+ const innerOpenParen = evalOriginStr.indexOf(' (');
68
+ let evalFunctionName = evalOriginStr;
69
+ let evalLocation = '';
70
+ if (innerOpenParen !== -1) {
71
+ evalFunctionName = evalOriginStr.substring(0, innerOpenParen).trim();
72
+ evalLocation = evalOriginStr.substring(innerOpenParen + 2, evalOriginStr.length - 1);
73
+ evalOrigin = parseRawFramesFromErrorStack(` at ${evalFunctionName} (${evalLocation})`)[0];
74
+ } else {
75
+ evalOrigin = parseRawFramesFromErrorStack(` at ${evalFunctionName}`)[0];
76
+ }
77
+ }
78
+
79
+ if (location.startsWith('index ')) {
80
+ promiseIndex = parseInt(location.substring(6), 10);
81
+ url = '';
82
+ } else if (location.includes(':wasm-function[')) {
83
+ isWasm = true;
84
+ const wasmMatch = /^(.*):wasm-function\[(\d+)\]:(0x[0-9a-fA-F]+)$/.exec(location);
85
+ if (wasmMatch) {
86
+ url = wasmMatch[1];
87
+ wasmFunctionIndex = parseInt(wasmMatch[2], 10);
88
+ columnNumber = parseInt(wasmMatch[3], 16);
89
+ }
90
+ } else {
91
+ const splitResult = Common.ParsedURL.ParsedURL.splitLineAndColumn(location);
92
+ url = splitResult.url;
93
+ lineNumber = splitResult.lineNumber ?? -1;
94
+ columnNumber = splitResult.columnNumber ?? -1;
95
+ }
96
+ } else {
97
+ const splitResult = Common.ParsedURL.ParsedURL.splitLineAndColumn(lineContent);
98
+ url = splitResult.url;
99
+ lineNumber = splitResult.lineNumber ?? -1;
100
+ columnNumber = splitResult.columnNumber ?? -1;
101
+ }
102
+
103
+ // Handle "typeName.methodName [as alias]"
104
+ if (functionName) {
105
+ const aliasMatch = /(.*)\s+\[as\s+(.*)\]/.exec(functionName);
106
+ if (aliasMatch) {
107
+ methodName = aliasMatch[2];
108
+ functionName = aliasMatch[1];
109
+ }
110
+
111
+ const dotIndex = functionName.indexOf('.');
112
+ if (dotIndex !== -1) {
113
+ typeName = functionName.substring(0, dotIndex);
114
+ methodName = methodName ?? functionName.substring(dotIndex + 1);
115
+ }
116
+
117
+ if (isWasm && typeName) {
118
+ wasmModuleName = typeName;
119
+ }
120
+ }
121
+
122
+ rawFrames.push({
123
+ url: url as Platform.DevToolsPath.UrlString,
124
+ functionName,
125
+ lineNumber,
126
+ columnNumber,
127
+ parsedFrameInfo: {
128
+ isAsync,
129
+ isConstructor,
130
+ isEval,
131
+ evalOrigin,
132
+ isWasm,
133
+ wasmModuleName,
134
+ wasmFunctionIndex,
135
+ typeName,
136
+ methodName,
137
+ promiseIndex,
138
+ },
139
+ });
140
+ }
141
+ return rawFrames;
142
+ }
@@ -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,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,
@@ -729,7 +729,7 @@ export function renderStep({step, isLoading, markdownRenderer, isLast}: {
729
729
  // clang-format off
730
730
  return html`
731
731
  <details class=${stepClasses}
732
- jslog=${VisualLogging.section('step')}
732
+ jslog=${VisualLogging.expand('step').track({click: true})}
733
733
  .open=${Boolean(step.requestApproval)}>
734
734
  <summary>
735
735
  <div class="summary">
@@ -755,6 +755,7 @@ interface WidgetMakerResponse {
755
755
  customRevealTitle?: Platform.UIString.LocalizedString;
756
756
  // Can be null if the widget is only used to add the Reveal CTA.
757
757
  title: Lit.LitTemplate|Platform.UIString.LocalizedString|null;
758
+ jslogContext?: string;
758
759
  }
759
760
 
760
761
  const nodeCache = new Map<Protocol.DOM.BackendNodeId, SDK.DOMModel.DOMNode>();
@@ -823,6 +824,7 @@ async function makeComputedStyleWidget(widgetData: ComputedStyleAiWidget): Promi
823
824
  </span>
824
825
  </span>`,
825
826
  // clang-format on
827
+ jslogContext: 'computed-styles',
826
828
  };
827
829
  }
828
830
 
@@ -836,6 +838,7 @@ async function makeCoreWebVitalsWidget(widgetData: CoreVitalsAiWidget): Promise<
836
838
  renderedWidget,
837
839
  revealable: new TimelineUtils.Helpers.RevealableCoreVitals(widgetData.data.insightSetKey),
838
840
  title: lockedString(UIStringsNotTranslate.coreVitals),
841
+ jslogContext: 'core-web-vitals',
839
842
  };
840
843
  }
841
844
 
@@ -872,6 +875,7 @@ async function makeStylePropertiesWidget(widgetData: StylePropertiesAiWidget): P
872
875
  node: domNodeForId,
873
876
  })}
874
877
  ></devtools-widget>`,
878
+ jslogContext: 'standalone-styles',
875
879
  };
876
880
  }
877
881
 
@@ -894,6 +898,7 @@ async function makeLcpBreakdownWidget(widgetData: LcpBreakdownAiWidget): Promise
894
898
  renderedWidget,
895
899
  revealable: new TimelineUtils.Helpers.RevealableInsight(insight),
896
900
  title: lockedString(UIStringsNotTranslate.lcpBreakdown),
901
+ jslogContext: 'lcp-breakdown',
897
902
  };
898
903
  }
899
904
 
@@ -922,7 +927,8 @@ async function makeBottomUpTimelineTreeWidget(widgetData: BottomUpTreeAiWidget):
922
927
  return {
923
928
  renderedWidget,
924
929
  revealable: new TimelineUtils.Helpers.RevealableBottomUpProfile(widgetData.data.bounds),
925
- title: lockedString(UIStringsNotTranslate.bottomUpTree)
930
+ title: lockedString(UIStringsNotTranslate.bottomUpTree),
931
+ jslogContext: 'bottom-up',
926
932
  };
927
933
  }
928
934
 
@@ -947,6 +953,7 @@ function renderWidgetResponse(response: WidgetMakerResponse|null): Lit.LitTempla
947
953
  <devtools-button class="widget-reveal-button"
948
954
  .variant=${Buttons.Button.Variant.TEXT}
949
955
  .accessibleLabel=${lockedString(UIStringsNotTranslate.reveal)}
956
+ .jslogContext=${'reveal'}
950
957
  @click=${onReveal}
951
958
  >
952
959
  ${response.customRevealTitle ?? lockedString(UIStringsNotTranslate.reveal)}
@@ -956,7 +963,7 @@ function renderWidgetResponse(response: WidgetMakerResponse|null): Lit.LitTempla
956
963
 
957
964
  // clang-format off
958
965
  return html`
959
- <div class=${classes}>
966
+ <div class=${classes} jslog=${ifDefined(response.jslogContext ? VisualLogging.section(response.jslogContext) : undefined)}>
960
967
  ${response.title ? html`
961
968
  <div class="widget-header">
962
969
  <h3 class="widget-name">${response.title}</h3>
@@ -986,6 +993,7 @@ async function makePerformanceTraceWidget(widgetData: PerformanceTraceAiWidget):
986
993
  title: null,
987
994
  revealable: new Timeline.TimelinePanel.ParsedTraceRevealable(widgetData.data.parsedTrace),
988
995
  customRevealTitle: lockedString(UIStringsNotTranslate.revealTrace),
996
+ jslogContext: 'performance-trace',
989
997
  };
990
998
  }
991
999
 
@@ -1045,6 +1053,7 @@ async function makeDomTreeWidget(widgetData: DomTreeAiWidget): Promise<WidgetMak
1045
1053
  renderedWidget,
1046
1054
  revealable: new SDK.DOMModel.DeferredDOMNode(root.domModel().target(), root.backendNodeId()),
1047
1055
  title: lockedString(UIStringsNotTranslate.lcpElement),
1056
+ jslogContext: 'dom-snapshot',
1048
1057
  };
1049
1058
  }
1050
1059
 
@@ -1607,5 +1616,6 @@ async function makeTimelineRangeSummaryWidget(widgetData: TimelineRangeSummaryAi
1607
1616
  renderedWidget: template,
1608
1617
  revealable: new TimelineUtils.Helpers.RevealableTimeRange(bounds),
1609
1618
  title: lockedString(UIStringsNotTranslate.performanceSummary),
1619
+ jslogContext: 'timeline-range-summary',
1610
1620
  };
1611
1621
  }
@@ -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);
@@ -10,6 +10,7 @@ import * as Buttons from '../../../ui/components/buttons/buttons.js';
10
10
  import * as Snackbars from '../../../ui/components/snackbars/snackbars.js';
11
11
  import * as UI from '../../../ui/legacy/legacy.js';
12
12
  import * as Lit from '../../../ui/lit/lit.js';
13
+ import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
13
14
 
14
15
  import styles from './exportForAgentsDialog.css.js';
15
16
 
@@ -85,7 +86,7 @@ export const DEFAULT_VIEW: View = (input, _output, target): void => {
85
86
 
86
87
  render(html`
87
88
  <style>${styles}</style>
88
- <div class="export-for-agents-dialog">
89
+ <div class="export-for-agents-dialog" jslog=${VisualLogging.dialog('ai-export-for-agents')}>
89
90
  <header>
90
91
  <h1 id="export-for-agents-dialog-title" tabindex="-1">
91
92
  ${i18nString(UIStrings.exportForAgents)}
@@ -122,7 +123,10 @@ export const DEFAULT_VIEW: View = (input, _output, target): void => {
122
123
  ${i18nString(UIStrings.generatingSummary)}
123
124
  </span>
124
125
  ` : Lit.nothing}
125
- <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
+ }
126
130
  </main>
127
131
  <div class="disclaimer">${i18nString(UIStrings.disclaimer)}</div>
128
132
  <footer>
@@ -9,6 +9,7 @@ import * as Input from '../../../ui/components/input/input.js';
9
9
  import type {MarkdownLitRenderer} from '../../../ui/components/markdown_view/MarkdownView.js';
10
10
  import * as UI from '../../../ui/legacy/legacy.js';
11
11
  import * as Lit from '../../../ui/lit/lit.js';
12
+ import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
12
13
 
13
14
  import chatMessageStyles from './chatMessage.css.js';
14
15
  import {type ModelChatMessage, renderStep, type Step, titleForStep} from './ChatMessage.js';
@@ -119,14 +120,14 @@ function renderInlineWalkthrough(input: ViewInput, stepsOutput: Lit.LitTemplate,
119
120
 
120
121
  // clang-format off
121
122
  return html`
122
- <div class="inline-wrapper" ?data-open=${input.isExpanded}>
123
+ <div class="inline-wrapper" ?data-open=${input.isExpanded} jslog=${VisualLogging.section('walkthrough-container')}>
123
124
  <span class="inline-icon">
124
125
  ${input.isLoading ?
125
126
  html`<devtools-spinner aria-label=${lockedString(UIStrings.inProgress)}></devtools-spinner>` :
126
127
  html`<devtools-icon name=${icon}></devtools-icon>`
127
128
  }
128
129
  </span>
129
- <details class="walkthrough-inline" ?open=${input.isExpanded} @toggle=${onToggle}>
130
+ <details class="walkthrough-inline" ?open=${input.isExpanded} @toggle=${onToggle} jslog=${VisualLogging.expand('walkthrough').track({click: true})}>
130
131
  <summary ?data-has-widgets=${!input.isLoading && hasWidgets}>
131
132
  <span class="walkthrough-inline-title">
132
133
  ${input.isExpanded ?
@@ -151,7 +152,7 @@ function renderSidebarWalkthrough(input: ViewInput, stepsOutput: Lit.LitTemplate
151
152
 
152
153
  // clang-format off
153
154
  return html`
154
- <div class="walkthrough-view">
155
+ <div class="walkthrough-view" jslog=${VisualLogging.section('walkthrough-container')}>
155
156
  <div class="walkthrough-header">
156
157
  <h2 class="walkthrough-title">${i18nString(UIStrings.title)}</h2>
157
158
  <devtools-button
@@ -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
  }