chrome-devtools-frontend 1.0.1706026 → 1.0.1708524

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 (86) hide show
  1. package/.agents/skills/code-review/SKILL.md +121 -0
  2. package/front_end/core/sdk/DOMModel.ts +13 -6
  3. package/front_end/core/sdk/NetworkRequest.ts +43 -7
  4. package/front_end/core/sdk/SourceMap.ts +4 -13
  5. package/front_end/core/sdk/SourceMapScopesInfo.ts +134 -86
  6. package/front_end/core/text_utils/ContentData.ts +18 -1
  7. package/front_end/entrypoints/heap_snapshot_worker/ContextAnalyzer.ts +102 -4
  8. package/front_end/generated/InspectorBackendCommands.ts +7 -1
  9. package/front_end/generated/SupportedCSSProperties.ts +11 -2
  10. package/front_end/generated/protocol-mapping.d.ts +31 -0
  11. package/front_end/generated/protocol-proxy-api.d.ts +32 -0
  12. package/front_end/generated/protocol.ts +11 -0
  13. package/front_end/models/ai_assistance/AiAgent2.ts +1 -0
  14. package/front_end/models/ai_assistance/data_formatters/LighthouseFormatter.ts +26 -3
  15. package/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts +15 -16
  16. package/front_end/models/ai_assistance/skills/Skill.ts +1 -2
  17. package/front_end/models/ai_assistance/skills/SkillRegistry.ts +2 -0
  18. package/front_end/models/ai_assistance/skills/lighthouse.md +33 -0
  19. package/front_end/models/ai_assistance/tools/GetLighthouseAudits.ts +6 -6
  20. package/front_end/models/ai_assistance/tools/RunLighthouse.ts +8 -5
  21. package/front_end/models/bindings/CompilerScriptMapping.ts +102 -45
  22. package/front_end/models/bindings/DebuggerLanguagePlugins.ts +25 -33
  23. package/front_end/models/bindings/DebuggerWorkspaceBinding.ts +33 -39
  24. package/front_end/models/bindings/StylesSourceMapping.ts +3 -0
  25. package/front_end/models/extensions/ExtensionEndpoint.ts +27 -6
  26. package/front_end/models/extensions/LanguageExtensionEndpoint.ts +106 -21
  27. package/front_end/models/extensions/RecorderExtensionEndpoint.ts +4 -2
  28. package/front_end/models/javascript_metadata/NativeFunctions.ts +6 -2
  29. package/front_end/models/persistence/NetworkPersistenceManager.ts +4 -3
  30. package/front_end/models/source_map_scopes/NamesResolver.ts +0 -8
  31. package/front_end/models/stack_trace/StackTraceImpl.ts +138 -47
  32. package/front_end/models/stack_trace/StackTraceModel.ts +117 -84
  33. package/front_end/models/stack_trace/Trie.ts +27 -0
  34. package/front_end/panels/accessibility/ARIAAttributesView.ts +6 -6
  35. package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingView.ts +2 -2
  36. package/front_end/panels/accessibility/AccessibilityNodeView.ts +6 -9
  37. package/front_end/panels/accessibility/AccessibilitySidebarView.ts +83 -69
  38. package/front_end/panels/accessibility/AccessibilitySubPane.ts +24 -9
  39. package/front_end/panels/accessibility/SourceOrderView.ts +7 -3
  40. package/front_end/panels/accessibility/accessibilitySidebarView.css +5 -3
  41. package/front_end/panels/application/serviceWorkersView.css +1 -1
  42. package/front_end/panels/common/CommentThreadWidget.ts +30 -1
  43. package/front_end/panels/common/CommentsOverlayWidget.ts +42 -38
  44. package/front_end/panels/common/common-meta.ts +21 -9
  45. package/front_end/panels/console/ConsoleInsightTeaser.ts +25 -19
  46. package/front_end/panels/console/ConsoleViewMessage.ts +51 -21
  47. package/front_end/panels/console/console-meta.ts +1 -5
  48. package/front_end/panels/elements/DOMTreeWidget.ts +207 -586
  49. package/front_end/panels/elements/ElementsPanel.ts +1 -5
  50. package/front_end/panels/elements/StylePropertyTreeElement.ts +100 -5
  51. package/front_end/panels/lighthouse/LighthouseController.ts +9 -4
  52. package/front_end/panels/network/NetworkConfigView.ts +231 -187
  53. package/front_end/panels/network/RequestPayloadView.ts +22 -14
  54. package/front_end/panels/profiler/HeapDetachedElementsDataGrid.ts +0 -1
  55. package/front_end/panels/recorder/RecordingView.ts +42 -47
  56. package/front_end/panels/recorder/StepEditor.ts +1 -0
  57. package/front_end/panels/recorder/recordingView.css +4 -4
  58. package/front_end/panels/sources/CSSPlugin.ts +11 -0
  59. package/front_end/panels/sources/CoveragePlugin.ts +34 -23
  60. package/front_end/panels/sources/NavigatorView.ts +97 -52
  61. package/front_end/panels/sources/Plugin.ts +15 -1
  62. package/front_end/panels/sources/SourcesView.ts +47 -80
  63. package/front_end/panels/sources/TabbedEditorContainer.ts +27 -2
  64. package/front_end/panels/sources/UISourceCodeFrame.ts +11 -2
  65. package/front_end/panels/sources/navigatorTree.css +2 -2
  66. package/front_end/third_party/chromium/README.chromium +1 -1
  67. package/front_end/ui/comments/CommentAnchorResolver.ts +17 -0
  68. package/front_end/ui/components/suggestion_input/SuggestionInput.ts +10 -1
  69. package/front_end/ui/components/text_editor/AiCodeCompletionTeaser.ts +1 -1
  70. package/front_end/ui/components/text_editor/AiCodeGenerationTeaser.ts +2 -2
  71. package/front_end/ui/components/text_editor/aiCodeCompletionTeaser.css +0 -11
  72. package/front_end/ui/components/text_editor/aiCodeGenerationTeaser.css +0 -11
  73. package/front_end/ui/legacy/SoftDropDown.ts +17 -4
  74. package/front_end/ui/legacy/StackPane.ts +141 -0
  75. package/front_end/ui/legacy/TextPrompt.ts +37 -7
  76. package/front_end/ui/legacy/Toolbar.ts +7 -5
  77. package/front_end/ui/legacy/Widget.ts +3 -3
  78. package/front_end/ui/legacy/components/inline_editor/PositionAreaEditor.ts +85 -0
  79. package/front_end/ui/legacy/components/inline_editor/positionAreaEditor.css +61 -0
  80. package/front_end/ui/legacy/components/utils/Linkifier.ts +21 -45
  81. package/front_end/ui/legacy/inspectorCommon.css +11 -0
  82. package/front_end/ui/legacy/legacy.ts +2 -0
  83. package/front_end/ui/legacy/softContextMenu.css +2 -0
  84. package/front_end/ui/settings/ConsoleSettings.ts +7 -0
  85. package/front_end/ui/visual_logging/KnownContextValues.ts +10 -0
  86. package/package.json +1 -1
@@ -10,6 +10,76 @@ import type * as Bindings from '../bindings/bindings.js';
10
10
  import {PrivateAPI} from './ExtensionAPI.js';
11
11
  import {ExtensionEndpoint} from './ExtensionEndpoint.js';
12
12
 
13
+ const isObject = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null;
14
+ const isString = (value: unknown): value is string => typeof value === 'string';
15
+ const isNumber = (value: unknown): value is number => typeof value === 'number';
16
+ const isBoolean = (value: unknown): value is boolean => typeof value === 'boolean';
17
+ const isStringArray = (value: unknown): value is string[] => Array.isArray(value) && value.every(isString);
18
+ const isNumberArray = (value: unknown): value is number[] => Array.isArray(value) && value.every(isNumber);
19
+
20
+ const isRawLocationRange = (value: unknown): value is Chrome.DevTools.RawLocationRange => {
21
+ return isObject(value) && isString(value.rawModuleId) && isNumber(value.startOffset) && isNumber(value.endOffset);
22
+ };
23
+
24
+ const isSourceLocation = (value: unknown): value is Chrome.DevTools.SourceLocation => {
25
+ return isObject(value) && isString(value.rawModuleId) && isString(value.sourceFileURL) &&
26
+ isNumber(value.lineNumber) && isNumber(value.columnNumber);
27
+ };
28
+
29
+ const isVariable = (value: unknown): value is Chrome.DevTools.Variable => {
30
+ return isObject(value) && isString(value.scope) && isString(value.name) && isString(value.type) &&
31
+ (value.nestedName === undefined || isStringArray(value.nestedName));
32
+ };
33
+
34
+ const isScopeInfo = (value: unknown): value is Chrome.DevTools.ScopeInfo => {
35
+ return isObject(value) && isString(value.type) && isString(value.typeName) &&
36
+ (value.icon === undefined || isString(value.icon));
37
+ };
38
+
39
+ const isFunctionInfo = (value: unknown): value is Chrome.DevTools.FunctionInfo => {
40
+ return isObject(value) && isString(value.name);
41
+ };
42
+
43
+ const isForeignObject = (value: unknown): value is Chrome.DevTools.ForeignObject => {
44
+ return isObject(value) && value.type === 'reftype' &&
45
+ (value.valueClass === 'local' || value.valueClass === 'global' || value.valueClass === 'operand') &&
46
+ isNumber(value.index);
47
+ };
48
+
49
+ const isRemoteObject = (value: unknown): value is Chrome.DevTools.RemoteObject => {
50
+ return isObject(value) &&
51
+ (value.type === 'object' || value.type === 'undefined' || value.type === 'string' || value.type === 'number' ||
52
+ value.type === 'boolean' || value.type === 'bigint' || value.type === 'array' || value.type === 'null') &&
53
+ isBoolean(value.hasChildren) && (value.className === undefined || isString(value.className)) &&
54
+ (value.description === undefined || isString(value.description)) &&
55
+ (value.objectId === undefined || isString(value.objectId)) &&
56
+ (value.linearMemoryAddress === undefined || isNumber(value.linearMemoryAddress)) &&
57
+ (value.linearMemorySize === undefined || isNumber(value.linearMemorySize));
58
+ };
59
+
60
+ const isPropertyDescriptor = (value: unknown): value is Chrome.DevTools.PropertyDescriptor => {
61
+ return isObject(value) && isString(value.name) && (isRemoteObject(value.value) || isForeignObject(value.value));
62
+ };
63
+
64
+ const isAddRawModuleResult = (value: unknown): value is string[]|{missingSymbolFiles: string[]} => {
65
+ return isStringArray(value) || (isObject(value) && isStringArray(value.missingSymbolFiles));
66
+ };
67
+
68
+ const isGetFunctionInfoResult =
69
+ (value: unknown): value is {frames: Chrome.DevTools.FunctionInfo[], missingSymbolFiles: string[]}|
70
+ {missingSymbolFiles: string[]}|{frames: Chrome.DevTools.FunctionInfo[]} => {
71
+ if (!isObject(value)) {
72
+ return false;
73
+ }
74
+ if ('frames' in value && (!Array.isArray(value.frames) || !value.frames.every(isFunctionInfo))) {
75
+ return false;
76
+ }
77
+ if ('missingSymbolFiles' in value && !isStringArray(value.missingSymbolFiles)) {
78
+ return false;
79
+ }
80
+ return 'frames' in value || 'missingSymbolFiles' in value;
81
+ };
82
+
13
83
  class LanguageExtensionEndpointImpl extends ExtensionEndpoint {
14
84
  private plugin: LanguageExtensionEndpoint;
15
85
  #pluginManager: Bindings.DebuggerLanguagePlugins.DebuggerLanguagePluginManager;
@@ -90,12 +160,13 @@ export class LanguageExtensionEndpoint implements Bindings.DebuggerLanguagePlugi
90
160
  /**
91
161
  * Notify the plugin about a new script
92
162
  */
93
- addRawModule(rawModuleId: string, symbolsURL: string, rawModule: Chrome.DevTools.RawModule): Promise<string[]> {
163
+ addRawModule(rawModuleId: string, symbolsURL: string,
164
+ rawModule: Chrome.DevTools.RawModule): Promise<string[]|{missingSymbolFiles: string[]}> {
94
165
  if (!this.canAccessURL(symbolsURL) || !this.canAccessURL(rawModule.url)) {
95
166
  return Promise.resolve([]);
96
167
  }
97
- return this.endpoint.sendRequest(
98
- PrivateAPI.LanguageExtensionPluginCommands.AddRawModule, {rawModuleId, symbolsURL, rawModule});
168
+ return this.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.AddRawModule,
169
+ {rawModuleId, symbolsURL, rawModule}, isAddRawModuleResult);
99
170
  }
100
171
 
101
172
  /**
@@ -110,8 +181,10 @@ export class LanguageExtensionEndpoint implements Bindings.DebuggerLanguagePlugi
110
181
  */
111
182
  sourceLocationToRawLocation(sourceLocation: Chrome.DevTools.SourceLocation):
112
183
  Promise<Chrome.DevTools.RawLocationRange[]> {
113
- return this.endpoint.sendRequest(
114
- PrivateAPI.LanguageExtensionPluginCommands.SourceLocationToRawLocation, {sourceLocation});
184
+ return this.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.SourceLocationToRawLocation,
185
+ {sourceLocation},
186
+ (value): value is Chrome.DevTools.RawLocationRange[] =>
187
+ Array.isArray(value) && value.every(isRawLocationRange));
115
188
  }
116
189
 
117
190
  /**
@@ -119,27 +192,31 @@ export class LanguageExtensionEndpoint implements Bindings.DebuggerLanguagePlugi
119
192
  */
120
193
  rawLocationToSourceLocation(rawLocation: Chrome.DevTools.RawLocation): Promise<Chrome.DevTools.SourceLocation[]> {
121
194
  return this.endpoint.sendRequest(
122
- PrivateAPI.LanguageExtensionPluginCommands.RawLocationToSourceLocation, {rawLocation});
195
+ PrivateAPI.LanguageExtensionPluginCommands.RawLocationToSourceLocation, {rawLocation},
196
+ (value): value is Chrome.DevTools.SourceLocation[] => Array.isArray(value) && value.every(isSourceLocation));
123
197
  }
124
198
 
125
199
  getScopeInfo(type: string): Promise<Chrome.DevTools.ScopeInfo> {
126
- return this.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetScopeInfo, {type});
200
+ return this.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetScopeInfo, {type}, isScopeInfo);
127
201
  }
128
202
 
129
203
  /**
130
204
  * List all variables in lexical scope at a given location in a raw module
131
205
  */
132
206
  listVariablesInScope(rawLocation: Chrome.DevTools.RawLocation): Promise<Chrome.DevTools.Variable[]> {
133
- return this.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.ListVariablesInScope, {rawLocation});
207
+ return this.endpoint.sendRequest(
208
+ PrivateAPI.LanguageExtensionPluginCommands.ListVariablesInScope, {rawLocation},
209
+ (value): value is Chrome.DevTools.Variable[] => Array.isArray(value) && value.every(isVariable));
134
210
  }
135
211
 
136
212
  /**
137
213
  * List all function names (including inlined frames) at location
138
214
  */
139
- getFunctionInfo(rawLocation: Chrome.DevTools.RawLocation): Promise<{
140
- frames: Chrome.DevTools.FunctionInfo[],
141
- }> {
142
- return this.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetFunctionInfo, {rawLocation});
215
+ getFunctionInfo(rawLocation: Chrome.DevTools.RawLocation):
216
+ Promise<{frames: Chrome.DevTools.FunctionInfo[], missingSymbolFiles: string[]}|{missingSymbolFiles: string[]}|
217
+ {frames: Chrome.DevTools.FunctionInfo[]}> {
218
+ return this.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetFunctionInfo, {rawLocation},
219
+ isGetFunctionInfoResult);
143
220
  }
144
221
 
145
222
  /**
@@ -147,8 +224,9 @@ export class LanguageExtensionEndpoint implements Bindings.DebuggerLanguagePlugi
147
224
  * that rawLocation is in.
148
225
  */
149
226
  getInlinedFunctionRanges(rawLocation: Chrome.DevTools.RawLocation): Promise<Chrome.DevTools.RawLocationRange[]> {
150
- return this.endpoint.sendRequest(
151
- PrivateAPI.LanguageExtensionPluginCommands.GetInlinedFunctionRanges, {rawLocation});
227
+ return this.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetInlinedFunctionRanges, {rawLocation},
228
+ (value): value is Chrome.DevTools.RawLocationRange[] =>
229
+ Array.isArray(value) && value.every(isRawLocationRange));
152
230
  }
153
231
 
154
232
  /**
@@ -156,22 +234,29 @@ export class LanguageExtensionEndpoint implements Bindings.DebuggerLanguagePlugi
156
234
  * called by the function or inline frame that rawLocation is in.
157
235
  */
158
236
  getInlinedCalleesRanges(rawLocation: Chrome.DevTools.RawLocation): Promise<Chrome.DevTools.RawLocationRange[]> {
159
- return this.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetInlinedCalleesRanges, {rawLocation});
237
+ return this.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetInlinedCalleesRanges, {rawLocation},
238
+ (value): value is Chrome.DevTools.RawLocationRange[] =>
239
+ Array.isArray(value) && value.every(isRawLocationRange));
160
240
  }
161
241
 
162
242
  async getMappedLines(rawModuleId: string, sourceFileURL: string): Promise<number[]|undefined> {
163
243
  return await this.endpoint.sendRequest(
164
- PrivateAPI.LanguageExtensionPluginCommands.GetMappedLines, {rawModuleId, sourceFileURL});
244
+ PrivateAPI.LanguageExtensionPluginCommands.GetMappedLines, {rawModuleId, sourceFileURL},
245
+ (value): value is number[]|undefined => value === undefined || isNumberArray(value));
165
246
  }
166
247
 
167
- async evaluate(expression: string, context: Chrome.DevTools.RawLocation, stopId: number):
168
- Promise<Chrome.DevTools.RemoteObject> {
169
- return await this.endpoint.sendRequest(
170
- PrivateAPI.LanguageExtensionPluginCommands.FormatValue, {expression, context, stopId});
248
+ async evaluate(expression: string, context: Chrome.DevTools.RawLocation,
249
+ stopId: number): Promise<Chrome.DevTools.RemoteObject|Chrome.DevTools.ForeignObject|null> {
250
+ return await this.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.FormatValue,
251
+ {expression, context, stopId},
252
+ (value): value is Chrome.DevTools.RemoteObject|Chrome.DevTools.ForeignObject|
253
+ null => value === null || isRemoteObject(value) || isForeignObject(value));
171
254
  }
172
255
 
173
256
  getProperties(objectId: Chrome.DevTools.RemoteObjectId): Promise<Chrome.DevTools.PropertyDescriptor[]> {
174
- return this.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetProperties, {objectId});
257
+ return this.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetProperties, {objectId},
258
+ (value): value is Chrome.DevTools.PropertyDescriptor[] =>
259
+ Array.isArray(value) && value.every(isPropertyDescriptor));
175
260
  }
176
261
 
177
262
  releaseObject(objectId: Chrome.DevTools.RemoteObjectId): Promise<void> {
@@ -8,6 +8,8 @@ import {PrivateAPI} from './ExtensionAPI.js';
8
8
  import {ExtensionEndpoint} from './ExtensionEndpoint.js';
9
9
  import type {RecorderPluginManager} from './RecorderPluginManager.js';
10
10
 
11
+ const isString = (value: unknown): value is string => typeof value === 'string';
12
+
11
13
  export class RecorderExtensionEndpoint extends ExtensionEndpoint {
12
14
  private readonly name: string;
13
15
  private readonly mediaType?: string;
@@ -63,7 +65,7 @@ export class RecorderExtensionEndpoint extends ExtensionEndpoint {
63
65
  * [1]: https://github.com/puppeteer/replay/blob/main/src/Schema.ts#L245
64
66
  */
65
67
  stringify(recording: Object): Promise<string> {
66
- return this.sendRequest(PrivateAPI.RecorderExtensionPluginCommands.Stringify, {recording});
68
+ return this.sendRequest(PrivateAPI.RecorderExtensionPluginCommands.Stringify, {recording}, isString);
67
69
  }
68
70
 
69
71
  /**
@@ -74,7 +76,7 @@ export class RecorderExtensionEndpoint extends ExtensionEndpoint {
74
76
  * [1]: https://github.com/puppeteer/replay/blob/main/src/Schema.ts#L243
75
77
  */
76
78
  stringifyStep(step: Object): Promise<string> {
77
- return this.sendRequest(PrivateAPI.RecorderExtensionPluginCommands.StringifyStep, {step});
79
+ return this.sendRequest(PrivateAPI.RecorderExtensionPluginCommands.StringifyStep, {step}, isString);
78
80
  }
79
81
 
80
82
  /**
@@ -2035,7 +2035,7 @@ export const NativeFunctions: readonly NativeFunctionValue[] = [
2035
2035
  },
2036
2036
  {
2037
2037
  name: "read",
2038
- signatures: [["?options"]],
2038
+ signatures: [["?formats"]],
2039
2039
  receivers: ["Clipboard"]
2040
2040
  },
2041
2041
  {
@@ -3220,7 +3220,7 @@ export const NativeFunctions: readonly NativeFunctionValue[] = [
3220
3220
  {
3221
3221
  name: "focus",
3222
3222
  signatures: [["?options"]],
3223
- receivers: ["HTMLOrSVGElement","SVGElement","MathMLElement","HTMLElement"]
3223
+ receivers: ["HTMLOrSVGElement","SVGElement","MathMLElement","HTMLElement","CSSPseudoElement","Focusable"]
3224
3224
  },
3225
3225
  {
3226
3226
  name: "assign",
@@ -9167,6 +9167,10 @@ export const NativeFunctions: readonly NativeFunctionValue[] = [
9167
9167
  name: "scrollIntoViewIfNeeded",
9168
9168
  signatures: [["?centerIfNeeded"]]
9169
9169
  },
9170
+ {
9171
+ name: "Focusable",
9172
+ signatures: [["target"]]
9173
+ },
9170
9174
  {
9171
9175
  name: "getBoxQuads",
9172
9176
  signatures: [["?options"]]
@@ -579,13 +579,14 @@ export class NetworkPersistenceManager extends Common.ObjectWrapper.ObjectWrappe
579
579
 
580
580
  static isForbiddenNetworkUrl(urlString: Platform.DevToolsPath.UrlString): boolean {
581
581
  const trimmedUrl = urlString.trim().toLowerCase();
582
- if (trimmedUrl.startsWith('data:') || trimmedUrl.startsWith('blob:') || trimmedUrl.startsWith('javascript:') ||
583
- trimmedUrl.startsWith('about:') || trimmedUrl.startsWith('mailto:') || trimmedUrl.startsWith('vbscript:')) {
582
+ if (trimmedUrl.startsWith('chrome:') || trimmedUrl.startsWith('data:') || trimmedUrl.startsWith('blob:') ||
583
+ trimmedUrl.startsWith('javascript:') || trimmedUrl.startsWith('about:') || trimmedUrl.startsWith('mailto:') ||
584
+ trimmedUrl.startsWith('vbscript:')) {
584
585
  return true;
585
586
  }
586
587
  const url = Common.ParsedURL.ParsedURL.fromString(urlString);
587
588
  if (!url) {
588
- return false;
589
+ return true;
589
590
  }
590
591
  if ((url.scheme === 'http' || url.scheme === 'https') && (!url.host || url.host === '.' || url.host === '..')) {
591
592
  return true;
@@ -820,14 +820,6 @@ async function getFunctionNameFromScopeStart(script: SDK.Script.Script, rawLineN
820
820
  return name;
821
821
  }
822
822
 
823
- export async function resolveDebuggerFrameFunctionName(frame: SDK.DebuggerModel.CallFrame): Promise<string|null> {
824
- const startLocation = frame.localScope()?.range()?.start;
825
- if (!startLocation) {
826
- return null;
827
- }
828
- return await getFunctionNameFromScopeStart(frame.script, startLocation.lineNumber, startLocation.columnNumber);
829
- }
830
-
831
823
  export async function resolveProfileFrameFunctionName(
832
824
  {scriptId, lineNumber, columnNumber}: Partial<Protocol.Runtime.CallFrame>, target: SDK.Target.Target|null,
833
825
  debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding): Promise<string|null> {
@@ -8,7 +8,7 @@ import type * as Workspace from '../workspace/workspace.js';
8
8
 
9
9
  // eslint-disable-next-line @devtools/es-modules-import
10
10
  import type * as StackTrace from './stack_trace.js';
11
- import type {EvalOrigin, FrameNode, ParsedFrameInfo} from './Trie.js';
11
+ import {type EvalOrigin, FrameKind, type FrameNode, type ParsedFrameInfo} from './Trie.js';
12
12
 
13
13
  export type AnyStackTraceImpl = StackTraceImpl<FragmentImpl|DebuggableFragmentImpl|ParsedErrorStackFragmentImpl>;
14
14
 
@@ -57,17 +57,7 @@ export class FragmentImpl implements StackTrace.StackTrace.Fragment {
57
57
  }
58
58
 
59
59
  get frames(): FrameImpl[] {
60
- if (!this.node) {
61
- return [];
62
- }
63
-
64
- const frames: FrameImpl[] = [];
65
-
66
- for (const node of this.node.getCallStack()) {
67
- frames.push(...node.frames);
68
- }
69
-
70
- return frames;
60
+ return this.node ? consolidate([...this.node.getCallStack()]).map(({frame}) => frame) : [];
71
61
  }
72
62
  }
73
63
 
@@ -108,6 +98,90 @@ export class FrameImpl implements StackTrace.StackTrace.Frame {
108
98
  }
109
99
  }
110
100
 
101
+ /** A frame of a fragment after outlined frames were merged with their callers. */
102
+ export interface LogicalFrame {
103
+ /** Node frame for non-merged groups (same identity as `node.frames[inlineIndex]`), a fresh copy for merged groups. */
104
+ readonly frame: FrameImpl;
105
+ /** The node this frame was translated from. */
106
+ readonly node: FrameNode;
107
+ /** Index of `node` in the call stack (0 = top). Equals the index into `DebuggerPausedDetails.callFrames`. */
108
+ readonly nodeIndex: number;
109
+ /** Index of `frame` in `node.frames`. Equals `CallFrame.inlineFrameIndex`. */
110
+ readonly inlineIndex: number;
111
+ /** Set only on the last frame of a group: the node that physically invoked this logical frame. */
112
+ readonly invocationNode?: FrameNode;
113
+ }
114
+
115
+ /**
116
+ * Drops HIDDEN nodes and merges each OUTLINED node with its callers into one group of logical frames.
117
+ *
118
+ * The chain continues with callers whose `functionKeys.top` equals the previous member's `functionKeys.bottom`,
119
+ * skipping HIDDEN and not-authored nodes. It ends with the first VISIBLE member (the terminator), or before a
120
+ * non-matching caller.
121
+ */
122
+ export function consolidate(callStack: readonly FrameNode[]): LogicalFrame[] {
123
+ const result: LogicalFrame[] = [];
124
+ for (let i = 0; i < callStack.length; ++i) {
125
+ const node = callStack[i];
126
+ if (node.kind === FrameKind.HIDDEN) {
127
+ continue;
128
+ }
129
+ if (node.kind === FrameKind.VISIBLE || !node.functionKeys) {
130
+ node.frames.forEach((frame, inlineIndex) => result.push({
131
+ frame,
132
+ node,
133
+ nodeIndex: i,
134
+ inlineIndex,
135
+ invocationNode: inlineIndex === node.frames.length - 1 ? node : undefined,
136
+ }));
137
+ continue;
138
+ }
139
+
140
+ // `node` is OUTLINED: start a chain.
141
+ const group = node.frames.map((frame, inlineIndex) => ({frame, node, nodeIndex: i, inlineIndex}));
142
+ let bottom = node.functionKeys.bottom;
143
+ let terminator: FrameNode|undefined;
144
+ let lastConsumed = i;
145
+ for (let j = i + 1; j < callStack.length && !terminator; ++j) {
146
+ const caller = callStack[j];
147
+ if (caller.kind === FrameKind.HIDDEN || isNotAuthored(caller)) {
148
+ continue;
149
+ }
150
+ if (!caller.functionKeys || caller.functionKeys.top !== bottom) {
151
+ break;
152
+ }
153
+ // Skip `caller.frames[0]`: it's the same logical frame as the previous member's bottom frame.
154
+ for (let k = 1; k < caller.frames.length; ++k) {
155
+ group.push({frame: caller.frames[k], node: caller, nodeIndex: j, inlineIndex: k});
156
+ }
157
+ bottom = caller.functionKeys.bottom;
158
+ terminator = caller.kind === FrameKind.VISIBLE ? caller : undefined;
159
+ lastConsumed = j;
160
+ }
161
+ // HIDDEN and not-authored nodes after the last chain member are processed by the outer loop.
162
+ i = lastConsumed;
163
+
164
+ const rawName = terminator?.rawFrame.functionName;
165
+ group.forEach(({frame: f, ...rest}, idx) => result.push({
166
+ ...rest,
167
+ frame: new FrameImpl(f.url, f.uiSourceCode, f.name, f.line, f.column, f.missingDebugInfo, rawName, f.isWasm,
168
+ idx < group.length - 1),
169
+ invocationNode: idx === group.length - 1 ? terminator : undefined,
170
+ }));
171
+ }
172
+ return result;
173
+ }
174
+
175
+ /**
176
+ * Frames that the authored function can't have called directly (e.g. `Array.prototype.forEach` calling an outlined
177
+ * callback, or an unmapped runtime helper). A chain looks past them.
178
+ *
179
+ * Builtins are unmapped like any other frame without authored code, see `TranslatedRawFrame.unmapped`.
180
+ */
181
+ function isNotAuthored(node: FrameNode): boolean {
182
+ return node.kind === FrameKind.VISIBLE && !node.functionKeys && node.isUnmapped;
183
+ }
184
+
111
185
  /**
112
186
  * Converts the internal recursive `EvalOrigin` trie representation into the public-facing
113
187
  * linear `ParsedErrorStackFrameImpl` representation.
@@ -125,9 +199,10 @@ function createParsedErrorStackFrameImplFromEvalOrigin(
125
199
  return undefined;
126
200
  }
127
201
  const frame = evalOrigin.frames[0];
128
- const nestedOrigin = createParsedErrorStackFrameImplFromEvalOrigin(
129
- evalOrigin.evalOrigin, parsedFrameInfo?.evalOrigin?.parsedFrameInfo);
130
- return new ParsedErrorStackFrameImpl(frame, parsedFrameInfo?.evalOrigin?.parsedFrameInfo, nestedOrigin);
202
+ const info = parsedFrameInfo?.evalOrigin?.parsedFrameInfo;
203
+ const nestedOrigin = createParsedErrorStackFrameImplFromEvalOrigin(evalOrigin.evalOrigin, info);
204
+ // An eval origin is a single location that is also its own invocation.
205
+ return new ParsedErrorStackFrameImpl(frame, info, info, nestedOrigin);
131
206
  }
132
207
 
133
208
  export class ParsedErrorStackFragmentImpl implements StackTrace.StackTrace.ParsedErrorStackFragment {
@@ -139,27 +214,33 @@ export class ParsedErrorStackFragmentImpl implements StackTrace.StackTrace.Parse
139
214
  return [];
140
215
  }
141
216
 
142
- const frames: ParsedErrorStackFrameImpl[] = [];
143
-
144
- for (const node of this.fragment.node.getCallStack()) {
145
- const evalOrigin = createParsedErrorStackFrameImplFromEvalOrigin(node.evalOrigin, node.parsedFrameInfo);
146
- for (const frame of node.frames) {
147
- frames.push(new ParsedErrorStackFrameImpl(frame, node.parsedFrameInfo, evalOrigin));
217
+ const evalOrigins = new Map<FrameNode, ParsedErrorStackFrameImpl|undefined>();
218
+ return consolidate([...this.fragment.node.getCallStack()]).map(({frame, node, invocationNode}) => {
219
+ if (!evalOrigins.has(node)) {
220
+ evalOrigins.set(node, createParsedErrorStackFrameImplFromEvalOrigin(node.evalOrigin, node.parsedFrameInfo));
148
221
  }
149
- }
150
-
151
- return frames;
222
+ return new ParsedErrorStackFrameImpl(frame, node.parsedFrameInfo, invocationNode?.parsedFrameInfo,
223
+ evalOrigins.get(node));
224
+ });
152
225
  }
153
226
  }
154
227
 
228
+ /**
229
+ * Location properties (e.g. `isAsync`) describe where execution is, and come from the node a frame was translated
230
+ * from. Invocation properties (e.g. `isConstructor`) describe how the physical function was called, and only exist
231
+ * on the last frame of a group of inlined or merged frames.
232
+ */
155
233
  export class ParsedErrorStackFrameImpl implements StackTrace.StackTrace.ParsedErrorStackFrame {
156
234
  readonly #frame: FrameImpl;
157
- readonly #parsedFrameInfo?: ParsedFrameInfo;
235
+ readonly #locationInfo?: ParsedFrameInfo;
236
+ readonly #invocationInfo?: ParsedFrameInfo;
158
237
  readonly #evalOrigin?: ParsedErrorStackFrameImpl;
159
238
 
160
- constructor(frame: FrameImpl, parsedFrameInfo?: ParsedFrameInfo, evalOrigin?: ParsedErrorStackFrameImpl) {
239
+ constructor(frame: FrameImpl, locationInfo?: ParsedFrameInfo, invocationInfo?: ParsedFrameInfo,
240
+ evalOrigin?: ParsedErrorStackFrameImpl) {
161
241
  this.#frame = frame;
162
- this.#parsedFrameInfo = parsedFrameInfo;
242
+ this.#locationInfo = locationInfo;
243
+ this.#invocationInfo = invocationInfo;
163
244
  this.#evalOrigin = evalOrigin;
164
245
  }
165
246
 
@@ -186,13 +267,13 @@ export class ParsedErrorStackFrameImpl implements StackTrace.StackTrace.ParsedEr
186
267
  }
187
268
 
188
269
  get isAsync(): boolean|undefined {
189
- return this.#parsedFrameInfo?.isAsync;
270
+ return this.#locationInfo?.isAsync;
190
271
  }
191
272
  get isConstructor(): boolean|undefined {
192
- return this.#parsedFrameInfo?.isConstructor;
273
+ return this.#invocationInfo?.isConstructor;
193
274
  }
194
275
  get isEval(): boolean|undefined {
195
- return this.#parsedFrameInfo?.isEval;
276
+ return this.#locationInfo?.isEval;
196
277
  }
197
278
  get evalOrigin(): ParsedErrorStackFrameImpl|undefined {
198
279
  return this.#evalOrigin;
@@ -204,19 +285,19 @@ export class ParsedErrorStackFrameImpl implements StackTrace.StackTrace.ParsedEr
204
285
  return this.#frame.isInline;
205
286
  }
206
287
  get wasmModuleName(): string|undefined {
207
- return this.#parsedFrameInfo?.wasmModuleName;
288
+ return this.#locationInfo?.wasmModuleName;
208
289
  }
209
290
  get wasmFunctionIndex(): number|undefined {
210
- return this.#parsedFrameInfo?.wasmFunctionIndex;
291
+ return this.#locationInfo?.wasmFunctionIndex;
211
292
  }
212
293
  get typeName(): string|undefined {
213
- return this.#parsedFrameInfo?.typeName;
294
+ return this.#invocationInfo?.typeName;
214
295
  }
215
296
  get methodName(): string|undefined {
216
- return this.#parsedFrameInfo?.methodName;
297
+ return this.#invocationInfo?.methodName;
217
298
  }
218
299
  get promiseIndex(): number|undefined {
219
- return this.#parsedFrameInfo?.promiseIndex;
300
+ return this.#locationInfo?.promiseIndex;
220
301
  }
221
302
  }
222
303
 
@@ -226,6 +307,12 @@ export class ParsedErrorStackFrameImpl implements StackTrace.StackTrace.ParsedEr
226
307
  * FragmentImpl will stay the same.
227
308
  */
228
309
  export class DebuggableFragmentImpl implements StackTrace.StackTrace.DebuggableFragment {
310
+ /**
311
+ * Virtual call frames for inlined frames, so that reading `frames` repeatedly (e.g. after `UPDATED`) yields
312
+ * identical `sdkFrame`s. A new DebuggableFragmentImpl is created per pause, so this lives as long as `callFrames`.
313
+ */
314
+ readonly #virtualCallFrames = new Map<string, SDK.DebuggerModel.CallFrame>();
315
+
229
316
  constructor(readonly fragment: FragmentImpl, private readonly callFrames: SDK.DebuggerModel.CallFrame[]) {
230
317
  }
231
318
 
@@ -234,20 +321,24 @@ export class DebuggableFragmentImpl implements StackTrace.StackTrace.DebuggableF
234
321
  return [];
235
322
  }
236
323
 
237
- const frames: DebuggableFrameImpl[] = [];
324
+ return consolidate([...this.fragment.node.getCallStack()]).map(({frame, nodeIndex, inlineIndex}) => {
325
+ return new DebuggableFrameImpl(frame, this.#sdkFrameFor(nodeIndex, inlineIndex, frame.name ?? ''));
326
+ });
327
+ }
238
328
 
239
- let index = 0;
240
- for (const node of this.fragment.node.getCallStack()) {
241
- for (const [inlineIdx, frame] of node.frames.entries()) {
242
- // Create virtual frames for inlined frames.
243
- const sdkFrame = inlineIdx === 0 ? this.callFrames[index] :
244
- this.callFrames[index].createVirtualCallFrame(inlineIdx, frame.name ?? '');
245
- frames.push(new DebuggableFrameImpl(frame, sdkFrame));
246
- }
247
- index++;
329
+ #sdkFrameFor(nodeIndex: number, inlineIndex: number, name: string): SDK.DebuggerModel.CallFrame {
330
+ const physical = this.callFrames[nodeIndex];
331
+ if (inlineIndex === 0) {
332
+ return physical;
248
333
  }
249
-
250
- return frames;
334
+ // The name is part of the key, as a re-translation can change the name at the same indices.
335
+ const key = `${nodeIndex}:${inlineIndex}:${name}`;
336
+ let frame = this.#virtualCallFrames.get(key);
337
+ if (!frame) {
338
+ frame = physical.createVirtualCallFrame(inlineIndex, name);
339
+ this.#virtualCallFrames.set(key, frame);
340
+ }
341
+ return frame;
251
342
  }
252
343
  }
253
344