chrome-devtools-frontend 1.0.1705227 → 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 (159) hide show
  1. package/.agents/skills/code-review/SKILL.md +121 -0
  2. package/eslint.config.mjs +1 -0
  3. package/front_end/Images/src/nodejsHex.svg +28 -0
  4. package/front_end/core/sdk/CPUThrottlingManager.ts +14 -10
  5. package/front_end/core/sdk/DOMModel.ts +73 -21
  6. package/front_end/core/sdk/DebuggerModel.ts +0 -4
  7. package/front_end/core/sdk/NetworkRequest.ts +43 -7
  8. package/front_end/core/sdk/PageResourceLoader.ts +12 -11
  9. package/front_end/core/sdk/SourceMap.ts +11 -18
  10. package/front_end/core/sdk/SourceMapFunctionRanges.ts +22 -37
  11. package/front_end/core/sdk/SourceMapScopeChainEntry.ts +3 -3
  12. package/front_end/core/sdk/SourceMapScopesInfo.ts +183 -147
  13. package/front_end/core/text_utils/ContentData.ts +18 -1
  14. package/front_end/entrypoints/formatter_worker/formatter_worker-entrypoint.ts +7 -2
  15. package/front_end/entrypoints/heap_snapshot_worker/ContextAnalyzer.ts +238 -20
  16. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshotLoader.ts +16 -5
  17. package/front_end/entrypoints/inspector_main/nodeIcon.css +1 -1
  18. package/front_end/generated/InspectorBackendCommands.ts +8 -2
  19. package/front_end/generated/SupportedCSSProperties.ts +11 -2
  20. package/front_end/generated/protocol-mapping.d.ts +31 -0
  21. package/front_end/generated/protocol-proxy-api.d.ts +32 -0
  22. package/front_end/generated/protocol.ts +12 -1
  23. package/front_end/models/ai_assistance/AiAgent2.ts +1 -0
  24. package/front_end/models/ai_assistance/AiConversation.ts +16 -3
  25. package/front_end/models/ai_assistance/data_formatters/LighthouseFormatter.ts +26 -3
  26. package/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts +15 -16
  27. package/front_end/models/ai_assistance/skills/Skill.ts +1 -2
  28. package/front_end/models/ai_assistance/skills/SkillRegistry.ts +2 -0
  29. package/front_end/models/ai_assistance/skills/accessibility.md +8 -7
  30. package/front_end/models/ai_assistance/skills/lighthouse.md +33 -0
  31. package/front_end/models/ai_assistance/tools/GetLighthouseAudits.ts +6 -6
  32. package/front_end/models/ai_assistance/tools/RunLighthouse.ts +8 -5
  33. package/front_end/models/bindings/CompilerScriptMapping.ts +102 -45
  34. package/front_end/models/bindings/DebuggerLanguagePlugins.ts +25 -33
  35. package/front_end/models/bindings/DebuggerWorkspaceBinding.ts +33 -39
  36. package/front_end/models/bindings/StylesSourceMapping.ts +3 -0
  37. package/front_end/models/extensions/ExtensionEndpoint.ts +27 -6
  38. package/front_end/models/extensions/LanguageExtensionEndpoint.ts +106 -21
  39. package/front_end/models/extensions/RecorderExtensionEndpoint.ts +4 -2
  40. package/front_end/models/formatter/FormatterWorkerPool.ts +6 -1
  41. package/front_end/models/javascript_metadata/NativeFunctions.ts +12 -4
  42. package/front_end/models/persistence/NetworkPersistenceManager.ts +4 -3
  43. package/front_end/models/source_map_scopes/NamesResolver.ts +7 -17
  44. package/front_end/models/stack_trace/StackTraceImpl.ts +138 -47
  45. package/front_end/models/stack_trace/StackTraceModel.ts +117 -84
  46. package/front_end/models/stack_trace/Trie.ts +27 -0
  47. package/front_end/panels/accessibility/ARIAAttributesView.ts +6 -6
  48. package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingView.ts +2 -2
  49. package/front_end/panels/accessibility/AccessibilityNodeView.ts +6 -9
  50. package/front_end/panels/accessibility/AccessibilitySidebarView.ts +83 -69
  51. package/front_end/panels/accessibility/AccessibilitySubPane.ts +24 -9
  52. package/front_end/panels/accessibility/SourceOrderView.ts +7 -3
  53. package/front_end/panels/accessibility/accessibilitySidebarView.css +5 -3
  54. package/front_end/panels/application/BackgroundServiceView.ts +1 -0
  55. package/front_end/panels/application/components/AdsView.ts +6 -6
  56. package/front_end/panels/application/components/BackForwardCacheView.ts +1 -1
  57. package/front_end/panels/application/serviceWorkersView.css +1 -1
  58. package/front_end/panels/common/CommentThreadWidget.ts +30 -1
  59. package/front_end/panels/common/CommentsOverlayWidget.ts +42 -38
  60. package/front_end/panels/common/common-meta.ts +21 -9
  61. package/front_end/panels/console/ConsoleInsightTeaser.ts +25 -19
  62. package/front_end/panels/console/ConsoleViewMessage.ts +51 -21
  63. package/front_end/panels/console/console-meta.ts +1 -5
  64. package/front_end/panels/elements/CSSRuleValidator.ts +3 -0
  65. package/front_end/panels/elements/ClassesPaneWidget.ts +4 -1
  66. package/front_end/panels/elements/DOMTreeWidget.ts +207 -586
  67. package/front_end/panels/elements/ElementsPanel.ts +1 -5
  68. package/front_end/panels/elements/LayoutPane.ts +17 -20
  69. package/front_end/panels/elements/StylePropertyTreeElement.ts +100 -5
  70. package/front_end/panels/lighthouse/LighthouseController.ts +9 -4
  71. package/front_end/panels/network/NetworkConfigView.ts +231 -187
  72. package/front_end/panels/network/NetworkLogViewColumns.ts +10 -3
  73. package/front_end/panels/network/RequestPayloadView.ts +22 -14
  74. package/front_end/panels/network/ShowMoreDetailsWidget.ts +1 -1
  75. package/front_end/panels/profiler/HeapDetachedElementsDataGrid.ts +0 -1
  76. package/front_end/panels/recorder/RecordingView.ts +42 -47
  77. package/front_end/panels/recorder/StepEditor.ts +1 -0
  78. package/front_end/panels/recorder/recordingView.css +4 -4
  79. package/front_end/panels/security/SecurityPanel.ts +14 -7
  80. package/front_end/panels/sources/BreakpointEditDialog.ts +13 -1
  81. package/front_end/panels/sources/CSSPlugin.ts +11 -0
  82. package/front_end/panels/sources/CoveragePlugin.ts +34 -23
  83. package/front_end/panels/sources/DebuggerPlugin.ts +66 -54
  84. package/front_end/panels/sources/EditingLocationHistoryManager.ts +6 -6
  85. package/front_end/panels/sources/NavigatorView.ts +97 -52
  86. package/front_end/panels/sources/OpenFileQuickOpen.ts +2 -2
  87. package/front_end/panels/sources/Plugin.ts +18 -3
  88. package/front_end/panels/sources/ResourceOriginPlugin.ts +14 -17
  89. package/front_end/panels/sources/SourcesPanel.ts +1 -1
  90. package/front_end/panels/sources/SourcesView.ts +67 -157
  91. package/front_end/panels/sources/TabbedEditorContainer.ts +102 -23
  92. package/front_end/panels/sources/UISourceCodeFrame.ts +33 -15
  93. package/front_end/panels/sources/navigatorTree.css +2 -2
  94. package/front_end/panels/sources/sourcesView.css +17 -27
  95. package/front_end/panels/timeline/overlays/components/EntryLabelOverlay.ts +1 -0
  96. package/front_end/third_party/chromium/README.chromium +1 -1
  97. package/front_end/third_party/source-map-scopes-codec/README.chromium +2 -2
  98. package/front_end/third_party/source-map-scopes-codec/package/CONTRIBUTING.md +14 -1
  99. package/front_end/third_party/source-map-scopes-codec/package/README.md +8 -6
  100. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/builder/builder.d.ts +4 -1
  101. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/builder/builder.d.ts.map +1 -1
  102. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/builder/safe_builder.d.ts +3 -1
  103. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/builder/safe_builder.d.ts.map +1 -1
  104. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/decode/decode.d.ts.map +1 -1
  105. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/scopes.d.ts +4 -3
  106. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/scopes.d.ts.map +1 -1
  107. package/front_end/third_party/source-map-scopes-codec/package/deno.json +6 -2
  108. package/front_end/third_party/source-map-scopes-codec/package/package.json +1 -1
  109. package/front_end/third_party/source-map-scopes-codec/package/src/builder/builder.js +18 -2
  110. package/front_end/third_party/source-map-scopes-codec/package/src/builder/builder.js.map +1 -1
  111. package/front_end/third_party/source-map-scopes-codec/package/src/builder/builder.ts +22 -3
  112. package/front_end/third_party/source-map-scopes-codec/package/src/builder/safe_builder.js +33 -5
  113. package/front_end/third_party/source-map-scopes-codec/package/src/builder/safe_builder.js.map +1 -1
  114. package/front_end/third_party/source-map-scopes-codec/package/src/builder/safe_builder.ts +41 -5
  115. package/front_end/third_party/source-map-scopes-codec/package/src/codec.js +0 -2
  116. package/front_end/third_party/source-map-scopes-codec/package/src/codec.js.map +1 -1
  117. package/front_end/third_party/source-map-scopes-codec/package/src/codec.ts +4 -3
  118. package/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.js +99 -36
  119. package/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.js.map +1 -1
  120. package/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.ts +143 -49
  121. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encode.js +3 -1
  122. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encode.js.map +1 -1
  123. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encode.ts +3 -1
  124. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encoder.js +40 -18
  125. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encoder.js.map +1 -1
  126. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encoder.ts +51 -21
  127. package/front_end/third_party/source-map-scopes-codec/package/src/scopes.ts +4 -3
  128. package/front_end/third_party/source-map-scopes-codec/package/src/vlq.js +18 -4
  129. package/front_end/third_party/source-map-scopes-codec/package/src/vlq.js.map +1 -1
  130. package/front_end/third_party/source-map-scopes-codec/package/src/vlq.ts +18 -4
  131. package/front_end/ui/comments/CommentAnchorResolver.ts +17 -0
  132. package/front_end/ui/comments/CommentOverlayManager.ts +23 -10
  133. package/front_end/ui/components/suggestion_input/SuggestionInput.ts +10 -1
  134. package/front_end/ui/components/text_editor/AiCodeCompletionTeaser.ts +1 -1
  135. package/front_end/ui/components/text_editor/AiCodeGenerationTeaser.ts +2 -2
  136. package/front_end/ui/components/text_editor/aiCodeCompletionTeaser.css +0 -11
  137. package/front_end/ui/components/text_editor/aiCodeGenerationTeaser.css +0 -11
  138. package/front_end/ui/components/text_editor/javascript.ts +128 -52
  139. package/front_end/ui/legacy/SearchableView.ts +125 -74
  140. package/front_end/ui/legacy/SoftDropDown.ts +17 -4
  141. package/front_end/ui/legacy/StackPane.ts +141 -0
  142. package/front_end/ui/legacy/TextPrompt.ts +37 -7
  143. package/front_end/ui/legacy/Toolbar.ts +7 -5
  144. package/front_end/ui/legacy/Widget.ts +3 -3
  145. package/front_end/ui/legacy/components/inline_editor/PositionAreaEditor.ts +85 -0
  146. package/front_end/ui/legacy/components/inline_editor/positionAreaEditor.css +61 -0
  147. package/front_end/ui/legacy/components/source_frame/FontView.ts +5 -5
  148. package/front_end/ui/legacy/components/source_frame/ImageView.ts +13 -13
  149. package/front_end/ui/legacy/components/source_frame/ResourceSourceFrame.ts +2 -6
  150. package/front_end/ui/legacy/components/source_frame/SourceFrame.ts +3 -2
  151. package/front_end/ui/legacy/components/utils/Linkifier.ts +21 -45
  152. package/front_end/ui/legacy/inspectorCommon.css +11 -0
  153. package/front_end/ui/legacy/legacy.ts +2 -0
  154. package/front_end/ui/legacy/softContextMenu.css +2 -0
  155. package/front_end/ui/settings/ConsoleSettings.ts +7 -0
  156. package/front_end/ui/visual_logging/KnownContextValues.ts +10 -0
  157. package/mcp/mcp.ts +1 -0
  158. package/package.json +1 -1
  159. package/front_end/Images/nodeIcon.avif +0 -0
@@ -13,19 +13,16 @@ export interface NamedFunctionRange {
13
13
  }
14
14
 
15
15
  /**
16
- * Turns a list of {@link NamedFunctionRange}s into a single {@link OriginalScope} tree nested
17
- * according to the start/end position. Each range is turned into a OriginalScope with the `isStackFrame`
16
+ * Turns a list of {@link NamedFunctionRange}s into a list of {@link OriginalScope} trees nested
17
+ * according to the start/end position. Each range is turned into an OriginalScope with the `isStackFrame`
18
18
  * bit set to denote it as a function and a generic "Function" label.
19
19
  *
20
- * We nest all these function scopes underneath a single global scope that always starts at (0, 0) and
21
- * reaches to the largest end position.
22
- *
23
20
  * `ranges` can be unsorted but will be sorted in-place.
24
21
  *
25
22
  * @throws if the ranges are not nested properly. Concretely: start < end for each range, and no
26
23
  * "straddling" (i.e. partially overlapping ranges).
27
24
  */
28
- export function buildOriginalScopes(ranges: NamedFunctionRange[]): ScopesCodec.OriginalScope {
25
+ export function buildOriginalScopes(ranges: NamedFunctionRange[]): ScopesCodec.OriginalScope[] {
29
26
  validateStartBeforeEnd(ranges);
30
27
 
31
28
  // 1. Sort ranges by ascending start position.
@@ -33,27 +30,15 @@ export function buildOriginalScopes(ranges: NamedFunctionRange[]): ScopesCodec.O
33
30
  // with the higher end position first, because it's the parent.
34
31
  ranges.sort((a, b) => comparePositions(a.start, b.start) || comparePositions(b.end, a.end));
35
32
 
36
- const root: ScopesCodec.OriginalScope = {
37
- start: {line: 0, column: 0},
38
- end: {line: Number.POSITIVE_INFINITY, column: Number.POSITIVE_INFINITY},
39
- kind: 'Global',
40
- isStackFrame: false,
41
- children: [],
42
- variables: [],
43
- };
44
-
45
- // 2. Build the tree from the ranges.
46
- const stack: ScopesCodec.OriginalScope[] = [root];
33
+ // 2. Build the trees from the ranges.
34
+ const roots: ScopesCodec.OriginalScope[] = [];
35
+ const stack: ScopesCodec.OriginalScope[] = [];
47
36
  for (const range of ranges) {
48
37
  // Pop all scopes that precede the current entry (to find the right parent).
49
- let stackTop = stack.at(-1) as ScopesCodec.OriginalScope;
50
- while (true) {
51
- if (comparePositions(stackTop.end, range.start) <= 0) {
52
- stack.pop();
53
- stackTop = stack.at(-1) as ScopesCodec.OriginalScope;
54
- } else {
55
- break;
56
- }
38
+ let stackTop = stack.at(-1);
39
+ while (stackTop && comparePositions(stackTop.end, range.start) <= 0) {
40
+ stack.pop();
41
+ stackTop = stack.at(-1);
57
42
  }
58
43
 
59
44
  /*
@@ -66,22 +51,21 @@ export function buildOriginalScopes(ranges: NamedFunctionRange[]): ScopesCodec.O
66
51
  *
67
52
  *i.e.: B.start < A.end < B.end
68
53
  */
69
- if (comparePositions(range.start, stackTop.end) < 0 && comparePositions(stackTop.end, range.end) < 0) {
70
- throw new Error(`Range ${JSON.stringify(range)} and ${JSON.stringify(stackTop)} partially overlap.`);
54
+ if (stackTop && comparePositions(range.start, stackTop.end) < 0 && comparePositions(stackTop.end, range.end) < 0) {
55
+ throw new Error(`Range ${JSON.stringify(range)} and ${
56
+ JSON.stringify(stackTop, (key, value) => key === 'parent' ? undefined : value)} partially overlap.`);
71
57
  }
72
58
 
73
- const scope = createScopeFrom(range);
74
- stackTop.children.push(scope);
59
+ const scope = createScopeFrom(range, stackTop);
60
+ if (stackTop) {
61
+ stackTop.children.push(scope);
62
+ } else {
63
+ roots.push(scope);
64
+ }
75
65
  stack.push(scope);
76
66
  }
77
67
 
78
- // 3. Update root.end.
79
- const lastChild = root.children.at(-1);
80
- if (lastChild) {
81
- root.end = lastChild.end;
82
- }
83
-
84
- return root;
68
+ return roots;
85
69
  }
86
70
 
87
71
  function validateStartBeforeEnd(ranges: NamedFunctionRange[]): void {
@@ -92,11 +76,12 @@ function validateStartBeforeEnd(ranges: NamedFunctionRange[]): void {
92
76
  }
93
77
  }
94
78
 
95
- function createScopeFrom(range: NamedFunctionRange): ScopesCodec.OriginalScope {
79
+ function createScopeFrom(range: NamedFunctionRange, parent?: ScopesCodec.OriginalScope): ScopesCodec.OriginalScope {
96
80
  return {
97
81
  ...range,
98
82
  kind: 'Function',
99
83
  isStackFrame: true,
84
+ parent,
100
85
  children: [],
101
86
  variables: [],
102
87
  };
@@ -183,7 +183,7 @@ class SourceMapScopeRemoteObject extends RemoteObjectImpl {
183
183
  if (value === null) {
184
184
  return SourceMapScopeRemoteObject.#unavailableProperty(variable);
185
185
  }
186
- return new RemoteObjectProperty(variable, value, /* enumerable */ false, /* writable */ false, /* isOwn */ true,
186
+ return new RemoteObjectProperty(variable, value, /* enumerable */ true, /* writable */ false, /* isOwn */ true,
187
187
  /* wasThrown */ false);
188
188
  });
189
189
 
@@ -269,7 +269,7 @@ class SourceMapScopeRemoteObject extends RemoteObjectImpl {
269
269
  }
270
270
 
271
271
  static #unavailableProperty(name: string): RemoteObjectProperty {
272
- return new RemoteObjectProperty(
273
- name, null, /* enumerable */ false, /* writeable */ false, /* isOwn */ true, /* wasThrown */ false);
272
+ return new RemoteObjectProperty(name, null, /* enumerable */ true, /* writeable */ false, /* isOwn */ true,
273
+ /* wasThrown */ false);
274
274
  }
275
275
  }
@@ -14,15 +14,18 @@ import {SourceMapScopeChainEntry} from './SourceMapScopeChainEntry.js';
14
14
 
15
15
  export class SourceMapScopesInfo {
16
16
  readonly #sourceMap: SourceMap;
17
- readonly #originalScopes: Array<ScopesCodec.OriginalScope|null>;
17
+ readonly #originalScopes: Array<ScopesCodec.OriginalScope[]|null>;
18
18
  readonly #generatedRanges: ScopesCodec.GeneratedRange[];
19
+ /** Whether the scope information was derived from the AST and mappings (see {@link createFromAst}). */
20
+ readonly #isFromAst: boolean;
19
21
 
20
22
  #cachedVariablesAndBindingsPresent: boolean|null = null;
21
23
 
22
- constructor(sourceMap: SourceMap, scopeInfo: ScopesCodec.ScopeInfo) {
24
+ constructor(sourceMap: SourceMap, scopeInfo: ScopesCodec.ScopeInfo, {isFromAst = false}: {isFromAst?: boolean} = {}) {
23
25
  this.#sourceMap = sourceMap;
24
26
  this.#originalScopes = scopeInfo.scopes;
25
27
  this.#generatedRanges = scopeInfo.ranges;
28
+ this.#isFromAst = isFromAst;
26
29
  }
27
30
 
28
31
  /**
@@ -35,20 +38,10 @@ export class SourceMapScopesInfo {
35
38
  sourceMap: SourceMap, scopeTree: Formatter.FormatterWorkerPool.ScopeTreeNode,
36
39
  text: TextUtils.Text.Text): SourceMapScopesInfo {
37
40
  const numSourceUrls = sourceMap.sourceURLs().length;
38
- const scopeBySourceUrl: ScopesCodec.OriginalScope[] = [];
39
- for (let i = 0; i < numSourceUrls; i++) {
40
- const scope: ScopesCodec.OriginalScope = {
41
- start: {line: 0, column: 0},
42
- end: {line: Number.POSITIVE_INFINITY, column: Number.POSITIVE_INFINITY},
43
- isStackFrame: false,
44
- variables: [],
45
- children: [],
46
- };
47
- scopeBySourceUrl.push(scope);
48
- }
41
+ const scopesBySourceUrl: ScopesCodec.OriginalScope[][] = Array.from({length: numSourceUrls}, () => []);
49
42
 
50
43
  // Convert the entire scopeTree. Returns a root range that encompasses everything,
51
- // and inserts scopes by sourceIndex into the above scopeBySourceUrl.
44
+ // and inserts scopes by sourceIndex into the above scopesBySourceUrl.
52
45
  const stack: Array<{
53
46
  node: Formatter.FormatterWorkerPool.ScopeTreeNode,
54
47
  parentRange?: ScopesCodec.GeneratedRange,
@@ -114,10 +107,8 @@ export class SourceMapScopesInfo {
114
107
 
115
108
  let nextParentScopeHint = parentScopeHint;
116
109
  if (canMapOriginalPosition && scope) {
117
- const rootScope = scopeBySourceUrl[sourceIndex];
118
- const startSearchFrom =
119
- (parentScopeHint && containsOriginal(parentScopeHint, scope)) ? parentScopeHint : rootScope;
120
- insertInScope(startSearchFrom, scope);
110
+ const startParent = (parentScopeHint && containsOriginal(parentScopeHint, scope)) ? parentScopeHint : undefined;
111
+ insertInScope(sourceIndex, startParent, scope);
121
112
  nextParentScopeHint = scope;
122
113
  }
123
114
 
@@ -126,20 +117,22 @@ export class SourceMapScopesInfo {
126
117
  }
127
118
  }
128
119
 
129
- return new SourceMapScopesInfo(sourceMap, {scopes: scopeBySourceUrl, ranges: rootRange ? [rootRange] : []});
120
+ return new SourceMapScopesInfo(sourceMap, {scopes: scopesBySourceUrl, ranges: rootRange ? [rootRange] : []},
121
+ {isFromAst: true});
130
122
 
131
123
  /**
132
124
  * Finds the correct place in the tree to insert the new scope.
133
125
  * Maintains the invariant that children are sorted and contained by their parent.
134
126
  */
135
- function insertInScope(rootScope: ScopesCodec.OriginalScope, newScope: ScopesCodec.OriginalScope): void {
136
- let parent = rootScope;
127
+ function insertInScope(sourceIndex: number, parent: ScopesCodec.OriginalScope|undefined,
128
+ newScope: ScopesCodec.OriginalScope): void {
129
+ let children = parent ? parent.children : scopesBySourceUrl[sourceIndex];
137
130
  // Check if the newScope fits strictly inside any of the existing children.
138
131
  // We iterate to find the deepest parent to avoid Maximum Call Stack Size Exceeded
139
132
  // errors on highly nested scripts.
140
133
  while (true) {
141
134
  let deeperParent: ScopesCodec.OriginalScope|null = null;
142
- for (const child of parent.children) {
135
+ for (const child of children) {
143
136
  if (containsOriginal(child, newScope)) {
144
137
  deeperParent = child;
145
138
  break;
@@ -147,17 +140,18 @@ export class SourceMapScopesInfo {
147
140
  }
148
141
  if (deeperParent) {
149
142
  parent = deeperParent;
143
+ children = deeperParent.children;
150
144
  } else {
151
145
  break;
152
146
  }
153
147
  }
154
148
 
155
- // When here, newScope belongs directly in parent.
156
- // However, newScope might encompass some of parent's existing children (due
149
+ // When here, newScope belongs directly in parent (or at the root of the source file).
150
+ // However, newScope might encompass some of the existing children (due
157
151
  // to compiler transform quirks or arbitrary insertion order). We must move
158
152
  // those children inside newScope.
159
153
  const childrenToKeep: ScopesCodec.OriginalScope[] = [];
160
- for (const child of parent.children) {
154
+ for (const child of children) {
161
155
  if (containsOriginal(newScope, child)) {
162
156
  // child is actually inside newScope, so re-parent it.
163
157
  newScope.children.push(child);
@@ -177,8 +171,12 @@ export class SourceMapScopesInfo {
177
171
  childrenToKeep.splice(insertIndex, 0, newScope);
178
172
  }
179
173
 
180
- // Update parent's children to only be the ones that don't belong to newScope.
181
- parent.children = childrenToKeep;
174
+ // Update parent's children (or root scopes) to only be the ones that don't belong to newScope.
175
+ if (parent) {
176
+ parent.children = childrenToKeep;
177
+ } else {
178
+ scopesBySourceUrl[sourceIndex] = childrenToKeep;
179
+ }
182
180
  newScope.parent = parent;
183
181
  }
184
182
 
@@ -196,7 +194,7 @@ export class SourceMapScopesInfo {
196
194
  }
197
195
  }
198
196
 
199
- addOriginalScopes(scopes: Array<ScopesCodec.OriginalScope|null>): void {
197
+ addOriginalScopes(scopes: Array<ScopesCodec.OriginalScope[]|null>): void {
200
198
  for (const scope of scopes) {
201
199
  this.#originalScopes.push(scope);
202
200
  }
@@ -209,62 +207,41 @@ export class SourceMapScopesInfo {
209
207
  }
210
208
 
211
209
  hasOriginalScopes(sourceIdx: number): boolean {
212
- return Boolean(this.#originalScopes[sourceIdx]);
210
+ return Boolean(this.#originalScopes[sourceIdx]?.length);
213
211
  }
214
212
 
215
213
  isEmpty(): boolean {
216
- const noScopes = this.#originalScopes.every(scope => scope === null);
214
+ const noScopes = this.#originalScopes.every(scopes => scopes === null || scopes.length === 0);
217
215
  return noScopes && !this.#generatedRanges.length;
218
216
  }
219
217
 
220
- addOriginalScopesAtIndex(sourceIdx: number, scope: ScopesCodec.OriginalScope): void {
221
- if (!this.#originalScopes[sourceIdx]) {
222
- this.#originalScopes[sourceIdx] = scope;
218
+ addOriginalScopesAtIndex(sourceIdx: number, scopes: ScopesCodec.OriginalScope[]): void {
219
+ if (!this.#originalScopes[sourceIdx]?.length) {
220
+ this.#originalScopes[sourceIdx] = scopes;
223
221
  } else {
224
222
  throw new Error(`Trying to re-augment existing scopes for source at index: ${sourceIdx}`);
225
223
  }
226
224
  }
227
225
 
228
- /**
229
- * @returns true, iff the function surrounding the provided position is marked as "hidden".
230
- */
231
- isOutlinedFrame(generatedLine: number, generatedColumn: number): boolean {
232
- const rangeChain = this.#findGeneratedRangeChain(generatedLine, generatedColumn);
233
- return this.#isOutlinedFrame(rangeChain);
234
- }
235
-
236
- #isOutlinedFrame(rangeChain: ScopesCodec.GeneratedRange[]): boolean {
237
- for (let i = rangeChain.length - 1; i >= 0; --i) {
238
- if (rangeChain[i].isStackFrame) {
239
- return rangeChain[i].isHidden;
240
- }
226
+ #generatedFrameKind(rangeChain: ScopesCodec.GeneratedRange[]): GeneratedFrameKind {
227
+ const functionRange = rangeChain.findLast(range => range.isStackFrame);
228
+ if (!functionRange) {
229
+ // Top-level code.
230
+ return GeneratedFrameKind.VISIBLE;
241
231
  }
242
- return false;
243
- }
244
-
245
- /**
246
- * @returns true, iff the range surrounding the provided position contains multiple
247
- * inlined original functions.
248
- */
249
- hasInlinedFrames(generatedLine: number, generatedColumn: number): boolean {
250
- const rangeChain = this.#findGeneratedRangeChain(generatedLine, generatedColumn);
251
- for (let i = rangeChain.length - 1; i >= 0; --i) {
252
- if (rangeChain[i].isStackFrame) {
253
- // We stop looking for inlined original functions once we reach the current frame.
254
- return false;
255
- }
256
- if (rangeChain[i].callSite) {
257
- return true;
258
- }
232
+ if (!functionRange.originalScope) {
233
+ // For scope information derived from the AST, we merely failed to map the function.
234
+ return this.#isFromAst ? GeneratedFrameKind.VISIBLE : GeneratedFrameKind.HIDDEN;
259
235
  }
260
- return false;
236
+ return functionRange.isHidden ? GeneratedFrameKind.OUTLINED : GeneratedFrameKind.VISIBLE;
261
237
  }
262
238
 
263
239
  /**
264
240
  * Given a generated position, this returns all the surrounding generated ranges from outer
265
- * to inner.
241
+ * to inner. When `inlineFrameIndex > 0`, drops inner ranges up to the specified virtual
242
+ * call frame.
266
243
  */
267
- #findGeneratedRangeChain(line: number, column: number): ScopesCodec.GeneratedRange[] {
244
+ #findGeneratedRangeChain(line: number, column: number, inlineFrameIndex = 0): ScopesCodec.GeneratedRange[] {
268
245
  const result: ScopesCodec.GeneratedRange[] = [];
269
246
 
270
247
  (function walkRanges(ranges: ScopesCodec.GeneratedRange[]) {
@@ -277,6 +254,17 @@ export class SourceMapScopesInfo {
277
254
  }
278
255
  })(this.#generatedRanges);
279
256
 
257
+ // Drop ranges in the chain until we reach our desired inlined range.
258
+ for (let inlineIndex = 0; inlineIndex < inlineFrameIndex;) {
259
+ const range = result.pop();
260
+ if (!range) {
261
+ break;
262
+ }
263
+ if (range.callSite) {
264
+ ++inlineIndex;
265
+ }
266
+ }
267
+
280
268
  return result;
281
269
  }
282
270
 
@@ -295,12 +283,8 @@ export class SourceMapScopesInfo {
295
283
  // We check whether any original scope has a non-empty list of variables, and
296
284
  // generated ranges with a non-empty binding list.
297
285
 
298
- function walkTree(nodes: Array<ScopesCodec.OriginalScope|null>|ScopesCodec.GeneratedRange[]): boolean {
286
+ function walkTree(nodes: ScopesCodec.OriginalScope[]|ScopesCodec.GeneratedRange[]): boolean {
299
287
  for (const node of nodes) {
300
- if (!node) {
301
- continue;
302
- }
303
-
304
288
  if ('variables' in node && node.variables.length > 0) {
305
289
  return true;
306
290
  }
@@ -315,7 +299,7 @@ export class SourceMapScopesInfo {
315
299
  }
316
300
  return false;
317
301
  }
318
- return walkTree(this.#originalScopes) && walkTree(this.#generatedRanges);
302
+ return this.#originalScopes.some(scopes => scopes !== null && walkTree(scopes)) && walkTree(this.#generatedRanges);
319
303
  }
320
304
 
321
305
  /**
@@ -387,28 +371,14 @@ export class SourceMapScopesInfo {
387
371
  return result;
388
372
  }
389
373
 
390
- /** Similar to #findGeneratedRangeChain, but takes inlineFrameIndex of virtual call frames into account */
391
374
  #findGeneratedRangeChainForFrame(callFrame: CallFrame): ScopesCodec.GeneratedRange[] {
392
375
  const {line, column} = scriptRelativePosition(callFrame.location());
393
- const rangeChain = this.#findGeneratedRangeChain(line, column);
394
- if (callFrame.inlineFrameIndex === 0) {
395
- return rangeChain;
396
- }
397
-
398
- // Drop ranges in the chain until we reach our desired inlined range.
399
- for (let inlineIndex = 0; inlineIndex < callFrame.inlineFrameIndex;) {
400
- const range = rangeChain.pop();
401
- if (range?.callSite) {
402
- ++inlineIndex;
403
- }
404
- }
405
-
406
- return rangeChain;
376
+ return this.#findGeneratedRangeChain(line, column, callFrame.inlineFrameIndex);
407
377
  }
408
378
 
409
- resolveMappedVariablesAtPosition(line: number, column: number,
410
- ignoreInnerBlockScopes = false): Array<Map<string, string|null>>|null {
411
- const rangeChain = this.#findGeneratedRangeChain(line, column);
379
+ resolveMappedVariablesAtPosition(line: number, column: number, ignoreInnerBlockScopes = false,
380
+ inlineFrameIndex = 0): Array<Map<string, string|null>>|null {
381
+ const rangeChain = this.#findGeneratedRangeChain(line, column, inlineFrameIndex);
412
382
  const startScope = rangeChain.at(-1)?.originalScope;
413
383
  const innerMostScope =
414
384
  (startScope && ignoreInnerBlockScopes && this.#findFunctionScopeInOriginalScopeChain(startScope)) || startScope;
@@ -431,54 +401,53 @@ export class SourceMapScopesInfo {
431
401
  /**
432
402
  * Returns the authored function scope of the function containing the provided generated position.
433
403
  */
434
- findOriginalFunctionScope({line, column}: ScopesCodec.Position):
404
+ findOriginalFunctionScope(position: ScopesCodec.Position):
435
405
  {scope: ScopesCodec.OriginalScope, url?: Platform.DevToolsPath.UrlString}|null {
406
+ const rangeChain = this.#findGeneratedRangeChain(position.line, position.column);
407
+ const functionScope =
408
+ this.#findFunctionScopeInOriginalScopeChain(this.#innerMostOriginalScope(rangeChain, position));
409
+ return functionScope ? {scope: functionScope, url: this.#sourceURLOfScope(functionScope)} : null;
410
+ }
411
+
412
+ /**
413
+ * Returns the inner-most original scope containing the generated `position`. `rangeChain` must be the generated
414
+ * range chain of `position`.
415
+ */
416
+ #innerMostOriginalScope(rangeChain: ScopesCodec.GeneratedRange[],
417
+ position: ScopesCodec.Position): ScopesCodec.OriginalScope|undefined {
436
418
  // There are 2 approaches:
437
419
  // 1) Find the inner-most generated range containing the provided generated position
438
- // and use it's OriginalScope (then walk it outwards until we hit a function).
420
+ // and use its OriginalScope.
439
421
  // 2) Use the mappings to turn the generated position into an original position.
440
422
  // Then find the inner-most original scope containing that original position.
441
- // Then walk it outwards until we hit a function.
442
423
  //
443
424
  // Both approaches should yield the same result (assuming the mappings are spec compliant
444
425
  // w.r.t. generated ranges). But in the case of "pasta" scopes and extension provided
445
426
  // scope info, we only have the OriginalScope parts and mappings without GeneratedRanges.
446
-
447
- let originalInnerMostScope: ScopesCodec.OriginalScope|undefined;
448
-
449
427
  if (this.#generatedRanges.length > 0) {
450
- const rangeChain = this.#findGeneratedRangeChain(line, column);
451
- originalInnerMostScope = rangeChain.at(-1)?.originalScope;
452
- } else {
453
- // No GeneratedRanges. Try to use mappings.
454
- const entry = this.#sourceMap.findEntry(line, column);
455
- if (entry?.sourceIndex === undefined) {
456
- return null;
457
- }
458
- originalInnerMostScope =
459
- this.#findOriginalScopeChain(
460
- {sourceIndex: entry.sourceIndex, line: entry.sourceLineNumber, column: entry.sourceColumnNumber})
461
- .at(-1);
428
+ return rangeChain.at(-1)?.originalScope;
462
429
  }
463
430
 
464
- if (!originalInnerMostScope) {
465
- return null;
466
- }
467
-
468
- const functionScope = this.#findFunctionScopeInOriginalScopeChain(originalInnerMostScope);
469
- if (!functionScope) {
470
- return null;
431
+ // No GeneratedRanges. Try to use mappings.
432
+ const entry = this.#sourceMap.findEntry(position.line, position.column);
433
+ if (entry?.sourceIndex === undefined) {
434
+ return undefined;
471
435
  }
436
+ return this
437
+ .#findOriginalScopeChain(
438
+ {sourceIndex: entry.sourceIndex, line: entry.sourceLineNumber, column: entry.sourceColumnNumber})
439
+ .at(-1);
440
+ }
472
441
 
442
+ /** @returns the URL of the original source that `scope` belongs to. */
443
+ #sourceURLOfScope(scope: ScopesCodec.OriginalScope): Platform.DevToolsPath.UrlString|undefined {
473
444
  // Find the root scope for some given original source, to get the source url.
474
- let rootScope: ScopesCodec.OriginalScope = functionScope;
445
+ let rootScope: ScopesCodec.OriginalScope = scope;
475
446
  while (rootScope.parent) {
476
447
  rootScope = rootScope.parent;
477
448
  }
478
- const sourceIndex = this.#originalScopes.indexOf(rootScope);
479
- const url = sourceIndex !== -1 ? this.#sourceMap.sourceURLForSourceIndex(sourceIndex) : undefined;
480
-
481
- return functionScope ? {scope: functionScope, url} : null;
449
+ const sourceIndex = this.#originalScopes.findIndex(scopes => scopes?.includes(rootScope));
450
+ return sourceIndex !== -1 ? this.#sourceMap.sourceURLForSourceIndex(sourceIndex) : undefined;
482
451
  }
483
452
 
484
453
  /**
@@ -486,8 +455,8 @@ export class SourceMapScopesInfo {
486
455
  * to inner.
487
456
  */
488
457
  #findOriginalScopeChain({sourceIndex, line, column}: ScopesCodec.OriginalPosition): ScopesCodec.OriginalScope[] {
489
- const scope = this.#originalScopes[sourceIndex];
490
- if (!scope) {
458
+ const scopes = this.#originalScopes[sourceIndex];
459
+ if (!scopes) {
491
460
  return [];
492
461
  }
493
462
 
@@ -500,7 +469,7 @@ export class SourceMapScopesInfo {
500
469
  result.push(scope);
501
470
  walkScopes(scope.children);
502
471
  }
503
- })([scope]);
472
+ })(scopes);
504
473
 
505
474
  return result;
506
475
  }
@@ -525,33 +494,54 @@ export class SourceMapScopesInfo {
525
494
  }
526
495
 
527
496
  /**
528
- * Returns one or more original stack frames for this single "raw frame" or call-site.
529
- *
530
- * @returns An empty array if no mapping at the call-site was found, or the resulting frames
531
- * in top-to-bottom order in case of inlining.
532
- * @throws If this range is marked "hidden". Outlining needs to be handled externally as
533
- * outlined function segments in stack traces can span across bundles.
497
+ * Translates a single "raw frame" or call-site, including outlined functions. It's the caller's responsibility to
498
+ * merge outlined frames with their caller(s) (see {@link GeneratedFrameKind}).
534
499
  */
535
- translateCallSite(generatedLine: number, generatedColumn: number): TranslatedFrame[] {
500
+ translateRawFrame(generatedLine: number, generatedColumn: number): RawFrameTranslation {
536
501
  const rangeChain = this.#findGeneratedRangeChain(generatedLine, generatedColumn);
537
- if (this.#isOutlinedFrame(rangeChain)) {
538
- throw new Error('SourceMapScopesInfo is unable to translate an outlined function by itself');
502
+ const kind = this.#generatedFrameKind(rangeChain);
503
+ if (kind === GeneratedFrameKind.HIDDEN) {
504
+ return {kind, frames: []};
539
505
  }
506
+ const frame = this.#translateTopFrame(generatedLine, generatedColumn, rangeChain);
507
+ return {kind, frames: frame ? [frame, ...this.#translateInlinedCallers(rangeChain)] : []};
508
+ }
509
+
510
+ /**
511
+ * The top-most frame is translated the same, regardless of whether we have inlined functions: The name is the
512
+ * original function surrounding the generated position, and the location is the mapped generated position.
513
+ *
514
+ * If the generated position has no mapping, the generated ranges may still tell which authored function (or
515
+ * which file, for top-level code) the position belongs to. The frame then has no position.
516
+ */
517
+ #translateTopFrame(generatedLine: number, generatedColumn: number,
518
+ rangeChain: ScopesCodec.GeneratedRange[]): TranslatedFrame|null {
519
+ const position = {line: generatedLine, column: generatedColumn};
520
+ const innerMostScope = this.#innerMostOriginalScope(rangeChain, position);
521
+ const functionScope = this.#findFunctionScopeInOriginalScopeChain(innerMostScope);
522
+ const name = functionScope ? (functionScope.name ?? '') : undefined;
540
523
 
541
524
  const mapping = this.#sourceMap.findEntry(generatedLine, generatedColumn);
542
- if (mapping?.sourceIndex === undefined) {
543
- return [];
525
+ if (mapping?.sourceIndex !== undefined) {
526
+ return {
527
+ line: mapping.sourceLineNumber,
528
+ column: mapping.sourceColumnNumber,
529
+ name,
530
+ url: mapping.sourceURL,
531
+ functionStart: functionScope?.start,
532
+ };
544
533
  }
534
+ if (!innerMostScope) {
535
+ return null;
536
+ }
537
+ return {name, url: this.#sourceURLOfScope(functionScope ?? innerMostScope), functionStart: functionScope?.start};
538
+ }
545
539
 
546
- // The top-most frame is translated the same even if we have inlined functions.
547
- const result: TranslatedFrame[] = [{
548
- line: mapping.sourceLineNumber,
549
- column: mapping.sourceColumnNumber,
550
- name: this.findOriginalFunctionName({line: generatedLine, column: generatedColumn}) ?? undefined,
551
- url: mapping.sourceURL,
552
- }];
553
-
554
- // Walk the range chain inside out until we find a generated function and for each inlined function add a frame.
540
+ /**
541
+ * Walk the range chain inside out until we find a generated function and for each inlined function add a frame.
542
+ */
543
+ #translateInlinedCallers(rangeChain: ScopesCodec.GeneratedRange[]): TranslatedFrame[] {
544
+ const result: TranslatedFrame[] = [];
555
545
  for (let i = rangeChain.length - 1; i >= 0 && !rangeChain[i].isStackFrame; --i) {
556
546
  const range = rangeChain[i];
557
547
  if (!range.callSite) {
@@ -559,11 +549,13 @@ export class SourceMapScopesInfo {
559
549
  }
560
550
 
561
551
  const originalScopeChain = this.#findOriginalScopeChain(range.callSite);
552
+ const functionScope = this.#findFunctionScopeInOriginalScopeChain(originalScopeChain.at(-1));
562
553
  result.push({
563
554
  line: range.callSite.line,
564
555
  column: range.callSite.column,
565
- name: this.#findFunctionNameInOriginalScopeChain(originalScopeChain.at(-1)) ?? undefined,
556
+ name: functionScope ? (functionScope.name ?? '') : undefined,
566
557
  url: this.#sourceMap.sourceURLForSourceIndex(range.callSite.sourceIndex),
558
+ functionStart: functionScope?.start,
567
559
  });
568
560
  }
569
561
 
@@ -571,17 +563,61 @@ export class SourceMapScopesInfo {
571
563
  }
572
564
  }
573
565
 
566
+ /**
567
+ * Describes how the generated function surrounding a generated position shows up in stack traces.
568
+ *
569
+ * Compilers tend to introduce functions (and calls to them) that don't exist in the authored code. The scopes
570
+ * proposal distinguishes two cases:
571
+ *
572
+ * 1) The generated function contains authored code, e.g. a block scope that was turned into a function. The
573
+ * generated range is marked "hidden", but links to the original scope via its definition. We can pause
574
+ * in such a function, but the frame is merged with its caller(s) in stack traces: The outlined code
575
+ * logically belongs to the (authored) function that transitively calls it.
576
+ *
577
+ * 2) The generated function doesn't represent any authored code, e.g. a compiler helper. The generated range
578
+ * has no definition. The scopes spec is being updated to say that such a range can be ignored in stack
579
+ * traces, so we drop these frames.
580
+ */
581
+ export const enum GeneratedFrameKind {
582
+ /** A regular (possibly with inlined functions) generated function, or top-level code. */
583
+ VISIBLE = 'VISIBLE',
584
+ /** A generated function marked as "hidden" that has a definition (case 1). */
585
+ OUTLINED = 'OUTLINED',
586
+ /** A generated function without a definition (case 2). */
587
+ HIDDEN = 'HIDDEN',
588
+ }
589
+
590
+ /** See {@link SourceMapScopesInfo.translateRawFrame}. */
591
+ export interface RawFrameTranslation {
592
+ kind: GeneratedFrameKind;
593
+ /**
594
+ * [top, ...inlinedCallers] in top-to-bottom order. Empty for {@link GeneratedFrameKind.HIDDEN} frames, or if
595
+ * neither the mappings nor the generated ranges know anything about the generated position.
596
+ *
597
+ * For {@link GeneratedFrameKind.OUTLINED} frames, the top frame is named after the authored function the outlined
598
+ * code belongs to.
599
+ */
600
+ frames: TranslatedFrame[];
601
+ }
602
+
574
603
  /**
575
604
  * Represents a stack frame in original terms. It closely aligns with StackTrace.StackTrace.Frame,
576
605
  * but since we can't import that type here we mirror it here somewhat.
577
606
  *
578
- * Equivalent to Pick<StackTrace.StackTrace.Frame, 'line'|'column'|'name'|'url'>.
607
+ * Equivalent to Pick<StackTrace.StackTrace.Frame, 'line'|'column'|'name'|'url'>, except that the position is
608
+ * optional.
579
609
  */
580
610
  export interface TranslatedFrame {
581
- line: number;
582
- column: number;
611
+ /**
612
+ * `line` and `column` are undefined iff the generated position has no mapping. The frame then only identifies
613
+ * the authored function: `name`, `functionStart` and the `url` of the function's source.
614
+ */
615
+ line?: number;
616
+ column?: number;
583
617
  name?: string;
584
618
  url?: Platform.DevToolsPath.UrlString;
619
+ /** Start of the original function scope containing this frame's position. Undefined for top-level code. */
620
+ functionStart?: ScopesCodec.Position;
585
621
  }
586
622
 
587
623
  export function findExpression(range: ScopesCodec.GeneratedRange|undefined, index: number, line = 0,