chrome-devtools-frontend 1.0.1704172 → 1.0.1706026

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 (100) hide show
  1. package/AUTHORS +1 -0
  2. package/docs/architecture_of_devtools.md +1 -1
  3. package/docs/cookbook/localization.md +2 -2
  4. package/eslint.config.mjs +1 -0
  5. package/front_end/Images/src/nodejsHex.svg +28 -0
  6. package/front_end/core/common/README.md +4 -3
  7. package/front_end/core/sdk/CPUThrottlingManager.ts +14 -10
  8. package/front_end/core/sdk/DOMModel.ts +65 -20
  9. package/front_end/core/sdk/DebuggerModel.ts +0 -4
  10. package/front_end/core/sdk/PageResourceLoader.ts +12 -11
  11. package/front_end/core/sdk/SourceMap.ts +7 -5
  12. package/front_end/core/sdk/SourceMapFunctionRanges.ts +22 -37
  13. package/front_end/core/sdk/SourceMapScopeChainEntry.ts +3 -3
  14. package/front_end/core/sdk/SourceMapScopesInfo.ts +50 -62
  15. package/front_end/entrypoints/formatter_actions/formatter_actions.ts +18 -0
  16. package/front_end/entrypoints/formatter_worker/Substitute.ts +9 -8
  17. package/front_end/entrypoints/formatter_worker/formatter_worker-entrypoint.ts +9 -4
  18. package/front_end/entrypoints/heap_snapshot_worker/ContextAnalyzer.ts +140 -20
  19. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshotLoader.ts +16 -5
  20. package/front_end/entrypoints/inspector_main/nodeIcon.css +1 -1
  21. package/front_end/foundation/Universe.ts +9 -0
  22. package/front_end/generated/InspectorBackendCommands.ts +4 -3
  23. package/front_end/generated/protocol.ts +17 -1
  24. package/front_end/models/ai_assistance/AiConversation.ts +16 -3
  25. package/front_end/models/ai_assistance/skills/accessibility.md +8 -7
  26. package/front_end/models/formatter/FormatterWorkerPool.ts +14 -4
  27. package/front_end/models/javascript_metadata/NativeFunctions.ts +6 -2
  28. package/front_end/models/source_map_scopes/NamesResolver.ts +69 -42
  29. package/front_end/models/source_map_scopes/ScopeChainModel.ts +13 -45
  30. package/front_end/models/source_map_scopes/ScopeChainResolver.ts +101 -0
  31. package/front_end/models/source_map_scopes/source_map_scopes.ts +2 -0
  32. package/front_end/panels/application/BackgroundServiceView.ts +1 -0
  33. package/front_end/panels/application/components/AdsView.ts +6 -6
  34. package/front_end/panels/application/components/BackForwardCacheView.ts +1 -1
  35. package/front_end/panels/console/ConsolePrompt.ts +2 -1
  36. package/front_end/panels/elements/CSSRuleValidator.ts +3 -0
  37. package/front_end/panels/elements/ClassesPaneWidget.ts +4 -1
  38. package/front_end/panels/elements/LayoutPane.ts +17 -20
  39. package/front_end/panels/network/NetworkLogViewColumns.ts +10 -3
  40. package/front_end/panels/network/ShowMoreDetailsWidget.ts +1 -1
  41. package/front_end/panels/security/SecurityPanel.ts +14 -7
  42. package/front_end/panels/sources/BreakpointEditDialog.ts +13 -1
  43. package/front_end/panels/sources/DebuggerPlugin.ts +69 -58
  44. package/front_end/panels/sources/EditingLocationHistoryManager.ts +6 -6
  45. package/front_end/panels/sources/OpenFileQuickOpen.ts +2 -2
  46. package/front_end/panels/sources/Plugin.ts +3 -2
  47. package/front_end/panels/sources/ResourceOriginPlugin.ts +14 -17
  48. package/front_end/panels/sources/ScopeChainSidebarPane.ts +2 -2
  49. package/front_end/panels/sources/SourcesPanel.ts +1 -1
  50. package/front_end/panels/sources/SourcesView.ts +25 -82
  51. package/front_end/panels/sources/TabbedEditorContainer.ts +75 -21
  52. package/front_end/panels/sources/UISourceCodeFrame.ts +22 -13
  53. package/front_end/panels/sources/sourcesView.css +17 -27
  54. package/front_end/panels/timeline/overlays/components/EntryLabelOverlay.ts +1 -0
  55. package/front_end/third_party/chromium/README.chromium +1 -1
  56. package/front_end/third_party/source-map-scopes-codec/README.chromium +2 -2
  57. package/front_end/third_party/source-map-scopes-codec/package/CONTRIBUTING.md +14 -1
  58. package/front_end/third_party/source-map-scopes-codec/package/README.md +8 -6
  59. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/builder/builder.d.ts +4 -1
  60. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/builder/builder.d.ts.map +1 -1
  61. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/builder/safe_builder.d.ts +3 -1
  62. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/builder/safe_builder.d.ts.map +1 -1
  63. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/decode/decode.d.ts.map +1 -1
  64. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/scopes.d.ts +4 -3
  65. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/scopes.d.ts.map +1 -1
  66. package/front_end/third_party/source-map-scopes-codec/package/deno.json +6 -2
  67. package/front_end/third_party/source-map-scopes-codec/package/package.json +1 -1
  68. package/front_end/third_party/source-map-scopes-codec/package/src/builder/builder.js +18 -2
  69. package/front_end/third_party/source-map-scopes-codec/package/src/builder/builder.js.map +1 -1
  70. package/front_end/third_party/source-map-scopes-codec/package/src/builder/builder.ts +22 -3
  71. package/front_end/third_party/source-map-scopes-codec/package/src/builder/safe_builder.js +33 -5
  72. package/front_end/third_party/source-map-scopes-codec/package/src/builder/safe_builder.js.map +1 -1
  73. package/front_end/third_party/source-map-scopes-codec/package/src/builder/safe_builder.ts +41 -5
  74. package/front_end/third_party/source-map-scopes-codec/package/src/codec.js +0 -2
  75. package/front_end/third_party/source-map-scopes-codec/package/src/codec.js.map +1 -1
  76. package/front_end/third_party/source-map-scopes-codec/package/src/codec.ts +4 -3
  77. package/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.js +99 -36
  78. package/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.js.map +1 -1
  79. package/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.ts +143 -49
  80. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encode.js +3 -1
  81. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encode.js.map +1 -1
  82. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encode.ts +3 -1
  83. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encoder.js +40 -18
  84. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encoder.js.map +1 -1
  85. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encoder.ts +51 -21
  86. package/front_end/third_party/source-map-scopes-codec/package/src/scopes.ts +4 -3
  87. package/front_end/third_party/source-map-scopes-codec/package/src/vlq.js +18 -4
  88. package/front_end/third_party/source-map-scopes-codec/package/src/vlq.js.map +1 -1
  89. package/front_end/third_party/source-map-scopes-codec/package/src/vlq.ts +18 -4
  90. package/front_end/ui/comments/CommentOverlayManager.ts +23 -10
  91. package/front_end/ui/components/text_editor/javascript.ts +128 -52
  92. package/front_end/ui/legacy/README.md +12 -12
  93. package/front_end/ui/legacy/SearchableView.ts +125 -74
  94. package/front_end/ui/legacy/components/source_frame/FontView.ts +5 -5
  95. package/front_end/ui/legacy/components/source_frame/ImageView.ts +13 -13
  96. package/front_end/ui/legacy/components/source_frame/ResourceSourceFrame.ts +2 -6
  97. package/front_end/ui/legacy/components/source_frame/SourceFrame.ts +3 -2
  98. package/mcp/mcp.ts +1 -0
  99. package/package.json +1 -1
  100. package/front_end/Images/nodeIcon.avif +0 -0
@@ -14,7 +14,7 @@ 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
19
 
20
20
  #cachedVariablesAndBindingsPresent: boolean|null = null;
@@ -35,20 +35,10 @@ export class SourceMapScopesInfo {
35
35
  sourceMap: SourceMap, scopeTree: Formatter.FormatterWorkerPool.ScopeTreeNode,
36
36
  text: TextUtils.Text.Text): SourceMapScopesInfo {
37
37
  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
- }
38
+ const scopesBySourceUrl: ScopesCodec.OriginalScope[][] = Array.from({length: numSourceUrls}, () => []);
49
39
 
50
40
  // Convert the entire scopeTree. Returns a root range that encompasses everything,
51
- // and inserts scopes by sourceIndex into the above scopeBySourceUrl.
41
+ // and inserts scopes by sourceIndex into the above scopesBySourceUrl.
52
42
  const stack: Array<{
53
43
  node: Formatter.FormatterWorkerPool.ScopeTreeNode,
54
44
  parentRange?: ScopesCodec.GeneratedRange,
@@ -114,10 +104,8 @@ export class SourceMapScopesInfo {
114
104
 
115
105
  let nextParentScopeHint = parentScopeHint;
116
106
  if (canMapOriginalPosition && scope) {
117
- const rootScope = scopeBySourceUrl[sourceIndex];
118
- const startSearchFrom =
119
- (parentScopeHint && containsOriginal(parentScopeHint, scope)) ? parentScopeHint : rootScope;
120
- insertInScope(startSearchFrom, scope);
107
+ const startParent = (parentScopeHint && containsOriginal(parentScopeHint, scope)) ? parentScopeHint : undefined;
108
+ insertInScope(sourceIndex, startParent, scope);
121
109
  nextParentScopeHint = scope;
122
110
  }
123
111
 
@@ -126,20 +114,21 @@ export class SourceMapScopesInfo {
126
114
  }
127
115
  }
128
116
 
129
- return new SourceMapScopesInfo(sourceMap, {scopes: scopeBySourceUrl, ranges: rootRange ? [rootRange] : []});
117
+ return new SourceMapScopesInfo(sourceMap, {scopes: scopesBySourceUrl, ranges: rootRange ? [rootRange] : []});
130
118
 
131
119
  /**
132
120
  * Finds the correct place in the tree to insert the new scope.
133
121
  * Maintains the invariant that children are sorted and contained by their parent.
134
122
  */
135
- function insertInScope(rootScope: ScopesCodec.OriginalScope, newScope: ScopesCodec.OriginalScope): void {
136
- let parent = rootScope;
123
+ function insertInScope(sourceIndex: number, parent: ScopesCodec.OriginalScope|undefined,
124
+ newScope: ScopesCodec.OriginalScope): void {
125
+ let children = parent ? parent.children : scopesBySourceUrl[sourceIndex];
137
126
  // Check if the newScope fits strictly inside any of the existing children.
138
127
  // We iterate to find the deepest parent to avoid Maximum Call Stack Size Exceeded
139
128
  // errors on highly nested scripts.
140
129
  while (true) {
141
130
  let deeperParent: ScopesCodec.OriginalScope|null = null;
142
- for (const child of parent.children) {
131
+ for (const child of children) {
143
132
  if (containsOriginal(child, newScope)) {
144
133
  deeperParent = child;
145
134
  break;
@@ -147,17 +136,18 @@ export class SourceMapScopesInfo {
147
136
  }
148
137
  if (deeperParent) {
149
138
  parent = deeperParent;
139
+ children = deeperParent.children;
150
140
  } else {
151
141
  break;
152
142
  }
153
143
  }
154
144
 
155
- // When here, newScope belongs directly in parent.
156
- // However, newScope might encompass some of parent's existing children (due
145
+ // When here, newScope belongs directly in parent (or at the root of the source file).
146
+ // However, newScope might encompass some of the existing children (due
157
147
  // to compiler transform quirks or arbitrary insertion order). We must move
158
148
  // those children inside newScope.
159
149
  const childrenToKeep: ScopesCodec.OriginalScope[] = [];
160
- for (const child of parent.children) {
150
+ for (const child of children) {
161
151
  if (containsOriginal(newScope, child)) {
162
152
  // child is actually inside newScope, so re-parent it.
163
153
  newScope.children.push(child);
@@ -177,8 +167,12 @@ export class SourceMapScopesInfo {
177
167
  childrenToKeep.splice(insertIndex, 0, newScope);
178
168
  }
179
169
 
180
- // Update parent's children to only be the ones that don't belong to newScope.
181
- parent.children = childrenToKeep;
170
+ // Update parent's children (or root scopes) to only be the ones that don't belong to newScope.
171
+ if (parent) {
172
+ parent.children = childrenToKeep;
173
+ } else {
174
+ scopesBySourceUrl[sourceIndex] = childrenToKeep;
175
+ }
182
176
  newScope.parent = parent;
183
177
  }
184
178
 
@@ -196,7 +190,7 @@ export class SourceMapScopesInfo {
196
190
  }
197
191
  }
198
192
 
199
- addOriginalScopes(scopes: Array<ScopesCodec.OriginalScope|null>): void {
193
+ addOriginalScopes(scopes: Array<ScopesCodec.OriginalScope[]|null>): void {
200
194
  for (const scope of scopes) {
201
195
  this.#originalScopes.push(scope);
202
196
  }
@@ -209,17 +203,17 @@ export class SourceMapScopesInfo {
209
203
  }
210
204
 
211
205
  hasOriginalScopes(sourceIdx: number): boolean {
212
- return Boolean(this.#originalScopes[sourceIdx]);
206
+ return Boolean(this.#originalScopes[sourceIdx]?.length);
213
207
  }
214
208
 
215
209
  isEmpty(): boolean {
216
- const noScopes = this.#originalScopes.every(scope => scope === null);
210
+ const noScopes = this.#originalScopes.every(scopes => scopes === null || scopes.length === 0);
217
211
  return noScopes && !this.#generatedRanges.length;
218
212
  }
219
213
 
220
- addOriginalScopesAtIndex(sourceIdx: number, scope: ScopesCodec.OriginalScope): void {
221
- if (!this.#originalScopes[sourceIdx]) {
222
- this.#originalScopes[sourceIdx] = scope;
214
+ addOriginalScopesAtIndex(sourceIdx: number, scopes: ScopesCodec.OriginalScope[]): void {
215
+ if (!this.#originalScopes[sourceIdx]?.length) {
216
+ this.#originalScopes[sourceIdx] = scopes;
223
217
  } else {
224
218
  throw new Error(`Trying to re-augment existing scopes for source at index: ${sourceIdx}`);
225
219
  }
@@ -262,9 +256,10 @@ export class SourceMapScopesInfo {
262
256
 
263
257
  /**
264
258
  * Given a generated position, this returns all the surrounding generated ranges from outer
265
- * to inner.
259
+ * to inner. When `inlineFrameIndex > 0`, drops inner ranges up to the specified virtual
260
+ * call frame.
266
261
  */
267
- #findGeneratedRangeChain(line: number, column: number): ScopesCodec.GeneratedRange[] {
262
+ #findGeneratedRangeChain(line: number, column: number, inlineFrameIndex = 0): ScopesCodec.GeneratedRange[] {
268
263
  const result: ScopesCodec.GeneratedRange[] = [];
269
264
 
270
265
  (function walkRanges(ranges: ScopesCodec.GeneratedRange[]) {
@@ -277,6 +272,17 @@ export class SourceMapScopesInfo {
277
272
  }
278
273
  })(this.#generatedRanges);
279
274
 
275
+ // Drop ranges in the chain until we reach our desired inlined range.
276
+ for (let inlineIndex = 0; inlineIndex < inlineFrameIndex;) {
277
+ const range = result.pop();
278
+ if (!range) {
279
+ break;
280
+ }
281
+ if (range.callSite) {
282
+ ++inlineIndex;
283
+ }
284
+ }
285
+
280
286
  return result;
281
287
  }
282
288
 
@@ -295,12 +301,8 @@ export class SourceMapScopesInfo {
295
301
  // We check whether any original scope has a non-empty list of variables, and
296
302
  // generated ranges with a non-empty binding list.
297
303
 
298
- function walkTree(nodes: Array<ScopesCodec.OriginalScope|null>|ScopesCodec.GeneratedRange[]): boolean {
304
+ function walkTree(nodes: ScopesCodec.OriginalScope[]|ScopesCodec.GeneratedRange[]): boolean {
299
305
  for (const node of nodes) {
300
- if (!node) {
301
- continue;
302
- }
303
-
304
306
  if ('variables' in node && node.variables.length > 0) {
305
307
  return true;
306
308
  }
@@ -315,7 +317,7 @@ export class SourceMapScopesInfo {
315
317
  }
316
318
  return false;
317
319
  }
318
- return walkTree(this.#originalScopes) && walkTree(this.#generatedRanges);
320
+ return this.#originalScopes.some(scopes => scopes !== null && walkTree(scopes)) && walkTree(this.#generatedRanges);
319
321
  }
320
322
 
321
323
  /**
@@ -387,28 +389,14 @@ export class SourceMapScopesInfo {
387
389
  return result;
388
390
  }
389
391
 
390
- /** Similar to #findGeneratedRangeChain, but takes inlineFrameIndex of virtual call frames into account */
391
392
  #findGeneratedRangeChainForFrame(callFrame: CallFrame): ScopesCodec.GeneratedRange[] {
392
393
  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;
394
+ return this.#findGeneratedRangeChain(line, column, callFrame.inlineFrameIndex);
407
395
  }
408
396
 
409
- resolveMappedVariablesAtPosition(line: number, column: number,
410
- ignoreInnerBlockScopes = false): Array<Map<string, string|null>>|null {
411
- const rangeChain = this.#findGeneratedRangeChain(line, column);
397
+ resolveMappedVariablesAtPosition(line: number, column: number, ignoreInnerBlockScopes = false,
398
+ inlineFrameIndex = 0): Array<Map<string, string|null>>|null {
399
+ const rangeChain = this.#findGeneratedRangeChain(line, column, inlineFrameIndex);
412
400
  const startScope = rangeChain.at(-1)?.originalScope;
413
401
  const innerMostScope =
414
402
  (startScope && ignoreInnerBlockScopes && this.#findFunctionScopeInOriginalScopeChain(startScope)) || startScope;
@@ -475,7 +463,7 @@ export class SourceMapScopesInfo {
475
463
  while (rootScope.parent) {
476
464
  rootScope = rootScope.parent;
477
465
  }
478
- const sourceIndex = this.#originalScopes.indexOf(rootScope);
466
+ const sourceIndex = this.#originalScopes.findIndex(scopes => scopes?.includes(rootScope));
479
467
  const url = sourceIndex !== -1 ? this.#sourceMap.sourceURLForSourceIndex(sourceIndex) : undefined;
480
468
 
481
469
  return functionScope ? {scope: functionScope, url} : null;
@@ -486,8 +474,8 @@ export class SourceMapScopesInfo {
486
474
  * to inner.
487
475
  */
488
476
  #findOriginalScopeChain({sourceIndex, line, column}: ScopesCodec.OriginalPosition): ScopesCodec.OriginalScope[] {
489
- const scope = this.#originalScopes[sourceIndex];
490
- if (!scope) {
477
+ const scopes = this.#originalScopes[sourceIndex];
478
+ if (!scopes) {
491
479
  return [];
492
480
  }
493
481
 
@@ -500,7 +488,7 @@ export class SourceMapScopesInfo {
500
488
  result.push(scope);
501
489
  walkScopes(scope.children);
502
490
  }
503
- })([scope]);
491
+ })(scopes);
504
492
 
505
493
  return result;
506
494
  }
@@ -51,6 +51,24 @@ export const enum ScopeKind {
51
51
  ARROW_FUNCTION = 4,
52
52
  }
53
53
 
54
+ /**
55
+ * Describes a single scope for `javaScriptSubstitute`.
56
+ */
57
+ export interface ScopeVariableMapping {
58
+ /**
59
+ * Authored variable name -> generated binding expression, or `null` if the variable is unavailable.
60
+ */
61
+ bindings: Map<string, string|null>;
62
+ /**
63
+ * Generated identifiers declared by this scope. Together with the free identifiers of `bindings`, these
64
+ * shadow bindings of outer scopes that refer to the same generated identifiers.
65
+ *
66
+ * This is needed when a generated identifier doesn't show up in any binding expression, e.g. because
67
+ * multiple generated identifiers map to the same authored name, or the authored name is unknown.
68
+ */
69
+ generatedNames: string[];
70
+ }
71
+
54
72
  export interface ScopeTreeNode {
55
73
  variables: Array<{name: string, kind: DefinitionKind, offsets: number[]}>;
56
74
  start: number;
@@ -3,13 +3,13 @@
3
3
  // found in the LICENSE file.
4
4
 
5
5
  import * as Acorn from '../../third_party/acorn/acorn.js';
6
- import {DefinitionKind} from '../formatter_actions/formatter_actions.js';
6
+ import {DefinitionKind, type ScopeVariableMapping} from '../formatter_actions/formatter_actions.js';
7
7
 
8
8
  import {ECMA_VERSION} from './AcornTokenizer.js';
9
9
  import {ScopeVariableAnalysis, type VariableUses} from './ScopeParser.js';
10
10
 
11
- export function substituteExpression(expression: string, nameMaps: Array<Map<string, string|null>>): string {
12
- const replacements = computeSubstitution(expression, nameMaps);
11
+ export function substituteExpression(expression: string, scopes: ScopeVariableMapping[]): string {
12
+ const replacements = computeSubstitution(expression, scopes);
13
13
  return applySubstitution(expression, replacements);
14
14
  }
15
15
 
@@ -52,12 +52,12 @@ function parseBindingExpression(expression: string): {
52
52
  }
53
53
 
54
54
  /**
55
- * Given an |expression| and a mapping from names to new names, the |computeSubstitution|
55
+ * Given an |expression| and a list of |scopes| (from inner-most to outer-most), the |computeSubstitution|
56
56
  * function returns a list of replacements sorted by the offset. The function throws if
57
57
  * it cannot parse the expression or the substitution is impossible to perform (for example
58
58
  * if the substitution target is 'this' within a function, it would become bound there).
59
59
  **/
60
- function computeSubstitution(expression: string, nameMaps: Array<Map<string, string|null>>): Replacement[] {
60
+ function computeSubstitution(expression: string, scopes: ScopeVariableMapping[]): Replacement[] {
61
61
  // Parse the expression and find variables and scopes.
62
62
  const root = Acorn.parse(expression, {
63
63
  ecmaVersion: ECMA_VERSION,
@@ -75,10 +75,11 @@ function computeSubstitution(expression: string, nameMaps: Array<Map<string, str
75
75
  const allNames = scopeVariables.getAllNames();
76
76
  const nameMap = new Map<string, string|null>();
77
77
  const parsedBindings = new Map<string, ReturnType<typeof parseBindingExpression>>();
78
+ // Generated identifiers declared by the scopes visited so far. Outer bindings referring to them are shadowed.
78
79
  const shadowedNames = new Set<string>();
79
- for (const scopeMap of nameMaps) {
80
- const scopeNames = new Set<string>();
81
- for (const [name, rename] of scopeMap.entries()) {
80
+ for (const {bindings, generatedNames} of scopes) {
81
+ const scopeNames = new Set<string>(generatedNames);
82
+ for (const [name, rename] of bindings.entries()) {
82
83
  let parsed: ReturnType<typeof parseBindingExpression>|undefined;
83
84
  if (rename !== null) {
84
85
  try {
@@ -3,7 +3,7 @@
3
3
  // found in the LICENSE file.
4
4
 
5
5
  import * as Platform from '../../core/platform/platform.js';
6
- import {FormatterActions} from '../formatter_actions/formatter_actions.js';
6
+ import {FormatterActions, type ScopeVariableMapping} from '../formatter_actions/formatter_actions.js';
7
7
 
8
8
  import * as FormatterWorker from './formatter_worker.js';
9
9
 
@@ -13,7 +13,7 @@ Platform.HostRuntime.HOST_RUNTIME.workerScope.onmessage = function(event): void
13
13
  indentString: string,
14
14
  content: string,
15
15
  mimeType: string,
16
- mapping: Array<Map<string, string|null>>,
16
+ mapping: ScopeVariableMapping[],
17
17
  sourceType: 'module'|'script',
18
18
  } = event.data.params;
19
19
  if (!method) {
@@ -29,8 +29,13 @@ Platform.HostRuntime.HOST_RUNTIME.workerScope.onmessage = function(event): void
29
29
  FormatterWorker.CSSRuleParser.parseCSS(params.content, self.postMessage);
30
30
  break;
31
31
  case FormatterActions.JAVASCRIPT_SUBSTITUTE: {
32
- Platform.HostRuntime.HOST_RUNTIME.workerScope.postMessage(
33
- FormatterWorker.Substitute.substituteExpression(params.content, params.mapping));
32
+ let result: string|{error: string};
33
+ try {
34
+ result = FormatterWorker.Substitute.substituteExpression(params.content, params.mapping);
35
+ } catch (error) {
36
+ result = {error: error instanceof Error ? error.message : String(error)};
37
+ }
38
+ Platform.HostRuntime.HOST_RUNTIME.workerScope.postMessage(result);
34
39
  break;
35
40
  }
36
41
  case FormatterActions.JAVASCRIPT_SCOPE_TREE: {
@@ -99,14 +99,9 @@ interface HeapScan {
99
99
  scripts: Map<ScriptNodeIndex, ScriptInfo>;
100
100
  contextNodes: ContextNodeInfo[];
101
101
  liveClosures: LiveClosure[];
102
- // Maps a ScopeInfo to the script it belongs to. Only contains the ScopeInfos
103
- // directly referenced by a SharedFunctionInfo, the remaining ones are resolved
104
- // and memoized on demand by `resolveScopeInfoScriptNodeIndex`.
105
- //
106
- // A stored `undefined` is a memoized failed lookup, i.e. walking the
107
- // `outer_scope_info` chain of that ScopeInfo did not reach a script. It is
108
- // therefore not the same as an absent entry, which merely means "not resolved
109
- // yet", so lookups have to distinguish the two through `has()`.
102
+ // Maps a ScopeInfo to the script it belongs to. A stored `undefined` is a memoized
103
+ // failed lookup. It is therefore not the same as an absent entry, which merely means
104
+ // "not resolved yet".
110
105
  scopeInfoScriptNodeIndexes: Map<ScopeInfoNodeIndex, ScriptNodeIndex|undefined>;
111
106
  }
112
107
 
@@ -257,12 +252,11 @@ function scanHeap(snapshot: HeapSnapshot): HeapScan {
257
252
 
258
253
  if (rawName.startsWith('system / Script')) {
259
254
  processScript(scripts, node);
255
+ processScriptScopeInfos(scopeInfoScriptNodeIndexes, node);
260
256
  } else if (snapshot.isContextObject(node)) {
261
257
  processContext(contextNodes, node);
262
258
  } else if (node.rawType() === nodeClosureType) {
263
259
  processClosure(liveClosures, node);
264
- } else if (rawName.startsWith('system / SharedFunctionInfo')) {
265
- processSharedFunctionInfo(scopeInfoScriptNodeIndexes, node);
266
260
  }
267
261
  }
268
262
 
@@ -283,6 +277,137 @@ function processScript(scripts: Map<ScriptNodeIndex, ScriptInfo>, node: HeapSnap
283
277
  });
284
278
  }
285
279
 
280
+ function processScriptScopeInfos(
281
+ scopeInfoScriptNodeIndexes: Map<ScopeInfoNodeIndex, ScriptNodeIndex|undefined>,
282
+ scriptNode: HeapSnapshotNode,
283
+ ): void {
284
+ const infos = scriptNode.findInternalEdgeTarget('infos');
285
+ if (!infos) {
286
+ return;
287
+ }
288
+
289
+ const scriptNodeIndex = scriptNode.nodeIndex as ScriptNodeIndex;
290
+ const scopeInfoNode = scriptNode.snapshot.createNode();
291
+
292
+ // A script created by a direct `eval` records the scope its `eval` call appeared in. That scope
293
+ // and the ones enclosing it belong to the calling script, so walks from this script's ScopeInfos
294
+ // have to stop there. This is like V8's `Scope::DeserializeScopeChain` detecting eval boundaries.
295
+ //
296
+ // Stopping at the root scope (see `attributeScopeInfoChain`) is not enough here: the eval scope
297
+ // only gets a ScopeInfo if it needs a context. Without one, the ScopeInfo chains of this script's
298
+ // functions skip it and lead straight to the call-site scope, without passing a root scope.
299
+ //
300
+ // The call-site scope itself is assigned by the calling script, which lists it in its `infos`.
301
+ const evalFromScopeInfo = scriptNode.findInternalEdgeTarget('eval_from_scope_info');
302
+ const stopAtNodeIndex = evalFromScopeInfo?.rawName() === 'system / ScopeInfo' ?
303
+ evalFromScopeInfo.nodeIndex as ScopeInfoNodeIndex :
304
+ undefined;
305
+
306
+ for (let iter = infos.edges(); iter.hasNext(); iter.next()) {
307
+ const info = iter.edge.node();
308
+ const infoName = info.rawName();
309
+
310
+ // Besides SharedFunctionInfos, V8 lists the ScopeInfo of the innermost scope with a context
311
+ // around each direct `eval` call of this script. It belongs to this script, as do the scopes
312
+ // enclosing it up to the root scope of this script.
313
+ if (infoName === 'system / ScopeInfo') {
314
+ attributeScopeInfoChain(scopeInfoScriptNodeIndexes, info.nodeIndex as ScopeInfoNodeIndex, stopAtNodeIndex,
315
+ scriptNodeIndex, scopeInfoNode);
316
+ continue;
317
+ }
318
+
319
+ if (!infoName.startsWith('system / SharedFunctionInfo')) {
320
+ continue;
321
+ }
322
+ const sharedFunctionInfo = info;
323
+
324
+ // A compiled function has a ScopeInfo of its own in `name_or_scope_info`. It belongs to this
325
+ // script, as do the scopes enclosing it up to the root scope of this script.
326
+ const scopeInfo = sharedFunctionInfo.findInternalEdgeTarget('name_or_scope_info');
327
+ if (scopeInfo?.rawName() === 'system / ScopeInfo') {
328
+ attributeScopeInfoChain(scopeInfoScriptNodeIndexes, scopeInfo.nodeIndex as ScopeInfoNodeIndex, stopAtNodeIndex,
329
+ scriptNodeIndex, scopeInfoNode);
330
+ continue;
331
+ }
332
+
333
+ // While a function is uncompiled it has no ScopeInfo of its own, so use the ScopeInfo of its
334
+ // enclosing scope instead. A script's root function is always compiled, but if it weren't, its
335
+ // enclosing scope would lie outside of this script, so skip it.
336
+ const scopeId = sharedFunctionInfo.findInternalEdgeTarget('scope_id')?.nodeValueAsInt();
337
+ if (scopeId === undefined || isScriptRootScopeId(scopeId)) {
338
+ continue;
339
+ }
340
+
341
+ // Except for the root function, the enclosing ScopeInfo belongs to this script. The exception
342
+ // is a function in eval'd code whose eval scope has no context: its enclosing ScopeInfo is then
343
+ // the scope around the `eval` call in the calling script (`eval_from_scope_info`), where
344
+ // `attributeScopeInfoChain` stops.
345
+ const outerScopeInfo = sharedFunctionInfo.findInternalEdgeTarget('raw_outer_scope_info_or_feedback_metadata');
346
+ if (outerScopeInfo?.rawName() === 'system / ScopeInfo') {
347
+ attributeScopeInfoChain(scopeInfoScriptNodeIndexes, outerScopeInfo.nodeIndex as ScopeInfoNodeIndex,
348
+ stopAtNodeIndex, scriptNodeIndex, scopeInfoNode);
349
+ }
350
+ }
351
+ }
352
+
353
+ /**
354
+ * Assigns the ScopeInfo at `scopeInfoNodeIndex` and the ones enclosing it to the script at
355
+ * `scriptNodeIndex`. Stops at the root scope of the script, or at `stopAtNodeIndex`, the
356
+ * `eval_from_scope_info` of the script, which belongs to the calling script.
357
+ *
358
+ * Only this function assigns ScopeInfos while scanning the heap, and it always continues up to
359
+ * such a boundary. An already assigned ScopeInfo therefore means that its enclosing ScopeInfos
360
+ * are assigned as well, so the walk can stop there too.
361
+ */
362
+ function attributeScopeInfoChain(
363
+ scopeInfoScriptNodeIndexes: Map<ScopeInfoNodeIndex, ScriptNodeIndex|undefined>,
364
+ scopeInfoNodeIndex: ScopeInfoNodeIndex,
365
+ stopAtNodeIndex: ScopeInfoNodeIndex|undefined,
366
+ scriptNodeIndex: ScriptNodeIndex,
367
+ scopeInfoNode: HeapSnapshotNode,
368
+ ): void {
369
+ let currentNodeIndex = scopeInfoNodeIndex;
370
+ for (;;) {
371
+ if (currentNodeIndex === stopAtNodeIndex) {
372
+ // This is the call site of the `eval` that created this script. It belongs to the calling
373
+ // script.
374
+ return;
375
+ }
376
+ if (scopeInfoScriptNodeIndexes.has(currentNodeIndex)) {
377
+ // This scope info was already assigned to a script.
378
+ return;
379
+ }
380
+ scopeInfoScriptNodeIndexes.set(currentNodeIndex, scriptNodeIndex);
381
+
382
+ scopeInfoNode.nodeIndex = currentNodeIndex;
383
+ // A root scope's `outer_scope_info` leads into another script (e.g. the one calling `eval`), so
384
+ // stop here. This only catches root scopes that have a context. The call site of a context-less
385
+ // eval scope is caught through `stopAtNodeIndex` above.
386
+ if (isScriptRootScopeInfo(scopeInfoNode)) {
387
+ return;
388
+ }
389
+ // The outer scope of a non-root scope can be assigned to the same script.
390
+ const outerScopeInfo = scopeInfoNode.findInternalEdgeTarget('outer_scope_info');
391
+ if (!outerScopeInfo) {
392
+ return;
393
+ }
394
+ currentNodeIndex = outerScopeInfo.nodeIndex as ScopeInfoNodeIndex;
395
+ }
396
+ }
397
+
398
+ function isScriptRootScopeInfo(node: HeapSnapshotNode): boolean {
399
+ const scopeId = node.findInternalEdgeTarget('scope_id')?.nodeValueAsInt();
400
+ return scopeId !== undefined && isScriptRootScopeId(scopeId);
401
+ }
402
+
403
+ function isScriptRootScopeId(scopeId: number): boolean {
404
+ // -2 is the scope id V8 gives the outermost scope of a script: its script, eval or module scope.
405
+ // V8 derives scope ids from source positions and uses negative ids for scopes that would otherwise
406
+ // start at position 0. The other one is -1 for a wrapped function (`ScriptCompiler::CompileFunction`),
407
+ // which is still enclosed by an eval scope of the same script.
408
+ return scopeId === -2;
409
+ }
410
+
286
411
  function processContext(contextNodes: ContextNodeInfo[], node: HeapSnapshotNode): void {
287
412
  const scopeInfoNodeIndex = node.findInternalEdgeTarget('scope_info')?.nodeIndex as ScopeInfoNodeIndex | undefined;
288
413
  if (scopeInfoNodeIndex === undefined) {
@@ -315,16 +440,6 @@ function processClosure(liveClosures: LiveClosure[], node: HeapSnapshotNode): vo
315
440
  }
316
441
  }
317
442
 
318
- function processSharedFunctionInfo(scopeInfoScriptNodeIndexes: Map<ScopeInfoNodeIndex, ScriptNodeIndex|undefined>,
319
- node: HeapSnapshotNode): void {
320
- const scopeInfo = node.findInternalEdgeTarget('name_or_scope_info');
321
- const script = node.findInternalEdgeTarget('script');
322
- if (scopeInfo?.rawName() !== 'system / ScopeInfo' || !script?.rawName().startsWith('system / Script')) {
323
- return;
324
- }
325
- scopeInfoScriptNodeIndexes.set(scopeInfo.nodeIndex as ScopeInfoNodeIndex, script.nodeIndex as ScriptNodeIndex);
326
- }
327
-
328
443
  function buildLiveFunctions(snapshot: HeapSnapshot, liveClosures: LiveClosure[]): ByScope<LiveFunction> {
329
444
  const liveFunctionsByScript: ByScope<LiveFunction> = new Map();
330
445
  const node = snapshot.createNode();
@@ -482,6 +597,11 @@ function resolveScopeInfoScriptNodeIndex(
482
597
  seen.add(currentNodeIndex);
483
598
  visited.push(currentNodeIndex);
484
599
  node.nodeIndex = currentNodeIndex;
600
+ if (isScriptRootScopeInfo(node)) {
601
+ // This scope belongs to a script of its own, so leaving it through
602
+ // `outer_scope_info` would attribute it to the wrong script.
603
+ break;
604
+ }
485
605
  const outerScopeInfo = node.findInternalEdgeTarget('outer_scope_info');
486
606
  if (!outerScopeInfo) {
487
607
  break;
@@ -60,16 +60,22 @@ export class HeapSnapshotLoader {
60
60
  return result;
61
61
  }
62
62
 
63
- #parseUintArray(): boolean {
63
+ /**
64
+ * Parses (possibly negative) integers from `#json` into `#array`. Assumes
65
+ * valid input. Returns true if more input is needed and false once the
66
+ * closing bracket was reached.
67
+ */
68
+ #parseIntArray(): boolean {
64
69
  let index = 0;
65
70
  const char0 = '0'.charCodeAt(0);
66
71
  const char9 = '9'.charCodeAt(0);
72
+ const minus = '-'.charCodeAt(0);
67
73
  const closingBracket = ']'.charCodeAt(0);
68
74
  const length = this.#json.length;
69
75
  while (true) {
70
76
  while (index < length) {
71
77
  const code = this.#json.charCodeAt(index);
72
- if (char0 <= code && code <= char9) {
78
+ if ((char0 <= code && code <= char9) || code === minus) {
73
79
  break;
74
80
  } else if (code === closingBracket) {
75
81
  this.#json = this.#json.slice(index + 1);
@@ -81,8 +87,13 @@ export class HeapSnapshotLoader {
81
87
  this.#json = '';
82
88
  return true;
83
89
  }
84
- let nextNumber = 0;
85
90
  const startIndex = index;
91
+ let negative = false;
92
+ if (this.#json.charCodeAt(index) === minus) {
93
+ negative = true;
94
+ ++index;
95
+ }
96
+ let nextNumber = 0;
86
97
  while (index < length) {
87
98
  const code = this.#json.charCodeAt(index);
88
99
  if (char0 > code || code > char9) {
@@ -99,7 +110,7 @@ export class HeapSnapshotLoader {
99
110
  if (!this.#array) {
100
111
  throw new Error('Array not instantiated');
101
112
  }
102
- this.#array.setValue(this.#arrayIndex++, nextNumber);
113
+ this.#array.setValue(this.#arrayIndex++, negative ? -nextNumber : nextNumber);
103
114
  }
104
115
  }
105
116
 
@@ -170,7 +181,7 @@ export class HeapSnapshotLoader {
170
181
  this.#array = length === undefined ? Platform.TypedArrayUtilities.createExpandableBigUint32Array() :
171
182
  Platform.TypedArrayUtilities.createFixedBigUint32Array(length);
172
183
  this.#arrayIndex = 0;
173
- while (this.#parseUintArray()) {
184
+ while (this.#parseIntArray()) {
174
185
  if (length) {
175
186
  this.#progress.updateProgress(title, this.#arrayIndex, this.#array.length);
176
187
  } else {
@@ -8,7 +8,7 @@
8
8
  width: var(--sys-size-12);
9
9
  height: 26px;
10
10
  /* stylelint-disable-next-line custom-property-pattern */
11
- background-image: var(--image-file-nodeIcon);
11
+ background-image: var(--image-file-nodejsHex);
12
12
  background-size: 17px 17px;
13
13
  background-repeat: no-repeat;
14
14
  background-position: center;
@@ -21,6 +21,7 @@ import * as LiveMetrics from '../models/live-metrics/live-metrics.js';
21
21
  import * as Logs from '../models/logs/logs.js';
22
22
  import * as Persistence from '../models/persistence/persistence.js';
23
23
  import * as ProjectSettings from '../models/project_settings/project_settings.js';
24
+ import * as SourceMapScopes from '../models/source_map_scopes/source_map_scopes.js';
24
25
  import * as Workspace from '../models/workspace/workspace.js';
25
26
  import * as WorkspaceDiff from '../models/workspace_diff/workspace_diff.js';
26
27
 
@@ -160,6 +161,10 @@ export class Universe {
160
161
  resourceMapping, targetManager, ignoreListManager, workspace);
161
162
  context.set(Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding, debuggerWorkspaceBinding);
162
163
 
164
+ const scopeChainResolver =
165
+ new SourceMapScopes.ScopeChainResolver.ScopeChainResolver(targetManager, debuggerWorkspaceBinding);
166
+ context.set(SourceMapScopes.ScopeChainResolver.ScopeChainResolver, scopeChainResolver);
167
+
163
168
  const presentationConsoleMessageManager =
164
169
  new Bindings.PresentationConsoleMessageHelper.PresentationConsoleMessageManager(
165
170
  targetManager, workspace, debuggerWorkspaceBinding, cssWorkspaceBinding);
@@ -383,6 +388,10 @@ export class Universe {
383
388
  null;
384
389
  }
385
390
 
391
+ get scopeChainResolver(): SourceMapScopes.ScopeChainResolver.ScopeChainResolver {
392
+ return this.context.get(SourceMapScopes.ScopeChainResolver.ScopeChainResolver);
393
+ }
394
+
386
395
  get settings(): Common.Settings.Settings {
387
396
  return this.context.get(Common.Settings.Settings);
388
397
  }