chrome-devtools-frontend 1.0.1697595 → 1.0.1698733
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.
- package/.agents/skills/version-control/SKILL.md +21 -9
- package/.stylelintignore +2 -0
- package/front_end/core/common/VersionController.ts +5 -1
- package/front_end/core/platform/HostRuntime.ts +12 -0
- package/front_end/core/platform/api/HostRuntime.ts +13 -0
- package/front_end/core/platform/browser/HostRuntime.ts +34 -0
- package/front_end/core/platform/node/HostRuntime.ts +40 -0
- package/front_end/core/sdk/AnimationModel.ts +21 -99
- package/front_end/core/sdk/CSSModel.ts +4 -2
- package/front_end/core/sdk/CSSPropertyParser.ts +4 -4
- package/front_end/core/sdk/CSSPropertyParserMatchers.ts +4 -44
- package/front_end/core/sdk/Connections.ts +6 -1
- package/front_end/core/sdk/ConsoleModel.ts +1 -12
- package/front_end/core/sdk/DOMModel.ts +3 -51
- package/front_end/core/sdk/DebuggerModel.ts +9 -8
- package/front_end/core/sdk/NetworkRequest.ts +1 -1
- package/front_end/core/sdk/PageFunctions.ts +187 -0
- package/front_end/core/sdk/Resource.ts +1 -1
- package/front_end/core/sdk/RuntimeModel.ts +1 -20
- package/front_end/core/sdk/Script.ts +21 -0
- package/front_end/core/sdk/SourceMap.ts +15 -2
- package/front_end/core/sdk/SourceMapCache.ts +13 -10
- package/front_end/core/sdk/SourceMapManager.ts +11 -9
- package/front_end/core/sdk/SourceMapScopeChainEntry.ts +59 -17
- package/front_end/core/sdk/SourceMapScopesInfo.ts +8 -2
- package/front_end/entrypoints/heap_snapshot_worker/ContextAnalyzer.ts +635 -0
- package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +26 -12
- package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshotLoader.ts +16 -2
- package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshotWorkerDispatcher.ts +4 -4
- package/front_end/entrypoints/heap_snapshot_worker/heap_snapshot_worker.ts +2 -0
- package/front_end/entrypoints/shell/shell.ts +1 -1
- package/front_end/generated/InspectorBackendCommands.ts +1 -1
- package/front_end/generated/protocol.ts +8 -2
- package/front_end/models/ai_assistance/DOMHelpers.ts +95 -0
- package/front_end/models/ai_assistance/EvaluateAction.ts +5 -42
- package/front_end/models/ai_assistance/ExtensionScope.ts +2 -31
- package/front_end/models/ai_assistance/agents/AiAgent.ts +2 -1
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +27 -2
- package/front_end/models/ai_assistance/ai_assistance.ts +6 -4
- package/front_end/models/ai_assistance/contexts/DOMNodeContext.ts +10 -7
- package/front_end/models/ai_assistance/contexts/FileContext.ts +2 -10
- package/front_end/models/ai_assistance/skills/performance.md +4 -3
- package/front_end/models/ai_assistance/tools/GetSourceContent.ts +3 -2
- package/front_end/models/ai_assistance/tools/{GetFunctionCode.ts → GetTraceFunctionCode.ts} +26 -13
- package/front_end/models/ai_assistance/tools/{GetResourceContent.ts → GetTraceResourceContent.ts} +31 -11
- package/front_end/models/ai_assistance/tools/ListSources.ts +21 -14
- package/front_end/models/ai_assistance/tools/Tool.ts +2 -2
- package/front_end/models/ai_assistance/tools/ToolRegistry.ts +6 -6
- package/front_end/models/bindings/CompilerScriptMapping.ts +3 -10
- package/front_end/models/bindings/DebuggerLanguagePlugins.ts +48 -17
- package/front_end/models/bindings/ResourceScriptMapping.ts +2 -2
- package/front_end/models/bindings/StylesSourceMapping.ts +1 -1
- package/front_end/models/comment_manager/CD4ABridge.ts +55 -12
- package/front_end/models/comment_manager/CommentManager.ts +46 -91
- package/front_end/models/comment_manager/CommentThread.ts +145 -0
- package/front_end/models/comment_manager/comment_manager.ts +2 -0
- package/front_end/models/elements/DOMChanges.ts +238 -0
- package/front_end/models/elements/elements.ts +2 -0
- package/front_end/models/emulation/DeviceModeModel.ts +53 -0
- package/front_end/models/extensions/ExtensionAPI.ts +8 -6
- package/front_end/models/extensions/ExtensionEndpoint.ts +11 -8
- package/front_end/models/extensions/HostUrlPattern.ts +4 -3
- package/front_end/models/extensions/LanguageExtensionEndpoint.ts +3 -2
- package/front_end/models/extensions/RecorderExtensionEndpoint.ts +2 -1
- package/front_end/models/formatter/FormatterWorkerPool.ts +4 -6
- package/front_end/models/heap_snapshot/HeapSnapshotModel.ts +55 -0
- package/front_end/models/heap_snapshot/HeapSnapshotProxy.ts +18 -14
- package/front_end/models/issues_manager/IssuesManager.ts +5 -0
- package/front_end/models/issues_manager/WebInstallIssue.ts +63 -0
- package/front_end/models/issues_manager/descriptions/webInstallManifestMissingId.md +3 -0
- package/front_end/models/issues_manager/descriptions/webInstallManifestMissingNameOrShortName.md +3 -0
- package/front_end/models/issues_manager/descriptions/webInstallManifestParsingOrNetworkError.md +3 -0
- package/front_end/models/issues_manager/descriptions/webInstallNoManifest.md +3 -0
- package/front_end/models/issues_manager/descriptions/webInstallStartUrlInvalid.md +3 -0
- package/front_end/models/issues_manager/issues_manager.ts +2 -0
- package/front_end/models/workspace/UISourceCode.ts +20 -0
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +7 -7
- package/front_end/panels/ai_assistance/components/AIv2MarkdownRenderer.ts +4 -10
- package/front_end/panels/ai_assistance/components/MarkdownRendererWithCodeBlock.ts +25 -5
- package/front_end/panels/browser_debugger/ObjectEventListenersSidebarPane.ts +2 -3
- package/front_end/panels/common/CommentThreadWidget.ts +221 -0
- package/front_end/{ui/comments → panels/common}/CommentsOverlayWidget.ts +117 -28
- package/front_end/{ui/comments → panels/common}/CommentsStatusBarPill.ts +5 -5
- package/front_end/panels/common/ExtensionServer.ts +2 -1
- package/front_end/panels/common/commentThreadWidget.css +173 -0
- package/front_end/{ui/comments → panels/common}/commentsOverlay.css +8 -1
- package/front_end/{ui/comments/comments-meta.ts → panels/common/common-meta.ts} +11 -11
- package/front_end/panels/common/common.ts +3 -0
- package/front_end/panels/elements/AdoptedStyleSheetTreeElement.ts +3 -3
- package/front_end/panels/elements/ElementsPanel.ts +5 -1
- package/front_end/panels/elements/ElementsTreeElement.ts +102 -21
- package/front_end/panels/elements/ElementsTreeOutline.ts +326 -91
- package/front_end/panels/elements/EventListenersWidget.ts +0 -1
- package/front_end/panels/elements/PropertiesWidget.ts +11 -10
- package/front_end/panels/elements/PropertyRenderer.ts +29 -25
- package/front_end/panels/elements/StylePropertyTreeElement.ts +6 -6
- package/front_end/panels/elements/elementsTreeOutline.css +7 -1
- package/front_end/panels/emulation/DeviceModeToolbar.ts +2 -3
- package/front_end/panels/event_listeners/EventListenersView.ts +235 -302
- package/front_end/panels/linear_memory_inspector/LinearMemoryInspectorController.ts +6 -7
- package/front_end/panels/linear_memory_inspector/components/linearMemoryInspector.css +5 -1
- package/front_end/panels/linear_memory_inspector/linear_memory_inspector-meta.ts +1 -1
- package/front_end/panels/mobile_throttling/ThrottlingSettingsTab.ts +10 -2
- package/front_end/panels/network/NetworkConfigView.ts +141 -116
- package/front_end/panels/network/NetworkPanel.ts +7 -4
- package/front_end/panels/network/RequestHeadersView.ts +1 -1
- package/front_end/panels/network/forward/BackendLinking.ts +5 -2
- package/front_end/panels/recorder/stepView.css +1 -0
- package/front_end/panels/security/SecurityPanel.ts +5 -5
- package/front_end/panels/security/SecurityPanelSidebar.ts +2 -11
- package/front_end/panels/settings/BackendLinkingSettingsTab.ts +7 -2
- package/front_end/panels/sources/DebuggerPlugin.ts +1 -1
- package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +11 -16
- package/front_end/panels/sources/sources-meta.ts +1 -1
- package/front_end/panels/timeline/TimelineTreeView.ts +11 -1
- package/front_end/panels/timeline/overlays/OverlaysImpl.ts +0 -3
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/ui/comments/CommentOverlayManager.ts +89 -42
- package/front_end/ui/comments/comments.ts +0 -4
- package/front_end/ui/legacy/Treeoutline.ts +4 -0
- package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +15 -8
- package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +4 -0
- package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
- package/package.json +5 -5
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
// Copyright 2026 The Chromium Authors
|
|
2
|
+
// Use of this source code is governed by a BSD-style license that can be
|
|
3
|
+
// found in the LICENSE file.
|
|
4
|
+
|
|
5
|
+
import type * as Platform from '../../core/platform/platform.js';
|
|
6
|
+
import type * as HeapSnapshotModel from '../../models/heap_snapshot/heap_snapshot.js';
|
|
7
|
+
|
|
8
|
+
import type {HeapSnapshot, HeapSnapshotNode} from './HeapSnapshot.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Index of a `system / Script` node within the heap snapshot's node array.
|
|
12
|
+
*/
|
|
13
|
+
type ScriptNodeIndex = Platform.Brand.Brand<number, 'ScriptNodeIndex'>;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Identifier of a scope, as emitted by V8 in the snapshot's scope table. Only
|
|
17
|
+
* unique within a single script.
|
|
18
|
+
*/
|
|
19
|
+
type ScopeId = Platform.Brand.Brand<number, 'ScopeId'>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Index of a `system / ScopeInfo` node within the heap snapshot's node array.
|
|
23
|
+
*/
|
|
24
|
+
type ScopeInfoNodeIndex = Platform.Brand.Brand<number, 'ScopeInfoNodeIndex'>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Index of a V8 `Context` node within the heap snapshot's node array.
|
|
28
|
+
*/
|
|
29
|
+
type ContextNodeIndex = Platform.Brand.Brand<number, 'ContextNodeIndex'>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Values addressed by scope. Since a `ScopeId` is only unique within its
|
|
33
|
+
* script, scopes are keyed by script first.
|
|
34
|
+
*/
|
|
35
|
+
type ByScope<T> = Map<ScriptNodeIndex, Map<ScopeId, T>>;
|
|
36
|
+
|
|
37
|
+
interface EmbeddedScope {
|
|
38
|
+
scriptNodeIndex: ScriptNodeIndex;
|
|
39
|
+
scopeId: ScopeId;
|
|
40
|
+
parent?: EmbeddedScope;
|
|
41
|
+
children: EmbeddedScope[];
|
|
42
|
+
variables: VariableDefinition[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface VariableDefinition {
|
|
46
|
+
name: string;
|
|
47
|
+
slotIndex: number;
|
|
48
|
+
uses: EmbeddedScope[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface ScriptInfo {
|
|
52
|
+
name: string;
|
|
53
|
+
nodeId: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface MatchedContext {
|
|
57
|
+
contextNodeIndex: ContextNodeIndex;
|
|
58
|
+
contextNodeId: number;
|
|
59
|
+
fieldValueNodeIndexes: number[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface ScopeAccumulator {
|
|
63
|
+
scopeInfoNodeIndex: ScopeInfoNodeIndex;
|
|
64
|
+
scopeInfoNodeId: number;
|
|
65
|
+
scriptNodeIndex: ScriptNodeIndex;
|
|
66
|
+
scriptNodeId: number;
|
|
67
|
+
scriptName: string;
|
|
68
|
+
scopeName?: string;
|
|
69
|
+
scopeStart: number;
|
|
70
|
+
scopeEnd: number;
|
|
71
|
+
scope: EmbeddedScope;
|
|
72
|
+
fieldNames: string[];
|
|
73
|
+
contexts: MatchedContext[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface LiveClosure {
|
|
77
|
+
contextNodeIndex: ContextNodeIndex;
|
|
78
|
+
scriptNodeIndex: ScriptNodeIndex;
|
|
79
|
+
scopeId: ScopeId;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface LiveFunction {
|
|
83
|
+
// All context objects reachable through live closures/JSFunctions of this function
|
|
84
|
+
// definition. This includes all context objects reachable through their 'previous'
|
|
85
|
+
// predecessors.
|
|
86
|
+
contextNodeIndexes: Set<ContextNodeIndex>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface ContextNodeInfo {
|
|
90
|
+
contextNodeIndex: ContextNodeIndex;
|
|
91
|
+
contextNodeId: number;
|
|
92
|
+
scopeInfoNodeIndex: ScopeInfoNodeIndex;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Everything collected while walking over all nodes of the snapshot once.
|
|
97
|
+
*/
|
|
98
|
+
interface HeapScan {
|
|
99
|
+
scripts: Map<ScriptNodeIndex, ScriptInfo>;
|
|
100
|
+
contextNodes: ContextNodeInfo[];
|
|
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()`.
|
|
110
|
+
scopeInfoScriptNodeIndexes: Map<ScopeInfoNodeIndex, ScriptNodeIndex|undefined>;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function analyzeContexts(snapshot: HeapSnapshot): HeapSnapshotModel.HeapSnapshotModel.ContextAnalysisResult {
|
|
114
|
+
// (1) Parse embedded scope metadata from the snapshot.
|
|
115
|
+
const scopesByScript = parseEmbeddedScopes(snapshot);
|
|
116
|
+
|
|
117
|
+
// (2) Scan the heap to collect scripts, contexts, live closures, and associate function ScopeInfos
|
|
118
|
+
// with scripts.
|
|
119
|
+
const scan = scanHeap(snapshot);
|
|
120
|
+
|
|
121
|
+
// (3) Group live closures by function scope and record the context chains they can reach.
|
|
122
|
+
const liveFunctionsByScript = buildLiveFunctions(snapshot, scan.liveClosures);
|
|
123
|
+
|
|
124
|
+
// (4) Correlate contexts and their field values with embedded scopes.
|
|
125
|
+
const {scopes, scriptsWithoutScopes} = correlateContextsWithScopes(snapshot, scan, scopesByScript);
|
|
126
|
+
|
|
127
|
+
// (5) Classify fields per context and build the result objects.
|
|
128
|
+
const scopeAnalyses = classifyFields(snapshot, scopes, liveFunctionsByScript);
|
|
129
|
+
|
|
130
|
+
// (6) Sort dead fields, contexts, and scopes, then return the analysis.
|
|
131
|
+
return sortAndBuildResult(scopeAnalyses, scriptsWithoutScopes);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function parseEmbeddedScopes(snapshot: HeapSnapshot): ByScope<EmbeddedScope> {
|
|
135
|
+
const scopesByScript: ByScope<EmbeddedScope> = new Map();
|
|
136
|
+
const profile = snapshot.profile;
|
|
137
|
+
const rawScopes = profile.scopes ?? [];
|
|
138
|
+
const rawVars = profile.scope_context_vars ?? [];
|
|
139
|
+
const rawUses = profile.scope_uses ?? [];
|
|
140
|
+
const meta = profile.snapshot.meta;
|
|
141
|
+
|
|
142
|
+
const scopeFields = meta.scope_fields ?? [];
|
|
143
|
+
const varFields = meta.scope_context_var_fields ?? [];
|
|
144
|
+
const useFields = meta.scope_use_fields ?? [];
|
|
145
|
+
|
|
146
|
+
const scopeStride = scopeFields.length;
|
|
147
|
+
const varStride = varFields.length;
|
|
148
|
+
const useStride = useFields.length;
|
|
149
|
+
|
|
150
|
+
if (scopeStride === 0) {
|
|
151
|
+
return scopesByScript;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const scriptNodeIndexOffset = scopeFields.indexOf('script_node_index');
|
|
155
|
+
const scopeIdOffset = scopeFields.indexOf('scope_id');
|
|
156
|
+
const depthOffset = scopeFields.indexOf('depth');
|
|
157
|
+
const varsCountOffset = scopeFields.indexOf('scope_context_vars_count');
|
|
158
|
+
const usesCountOffset = scopeFields.indexOf('scope_uses_count');
|
|
159
|
+
|
|
160
|
+
const varNameOffset = varFields.indexOf('name');
|
|
161
|
+
const useDeclaringScopeIdOffset = useFields.indexOf('declaring_scope_id');
|
|
162
|
+
const useSlotIndexOffset = useFields.indexOf('slot_index');
|
|
163
|
+
|
|
164
|
+
const strings = snapshot.strings;
|
|
165
|
+
|
|
166
|
+
// Pass 1: Create all scopes and variable definitions, and link parents via depth.
|
|
167
|
+
let varOffset = 0;
|
|
168
|
+
let currentScriptNodeIndex: ScriptNodeIndex|undefined;
|
|
169
|
+
const scopeStack: EmbeddedScope[] = [];
|
|
170
|
+
|
|
171
|
+
for (let i = 0; i < rawScopes.length; i += scopeStride) {
|
|
172
|
+
const scriptNodeIndex = rawScopes[i + scriptNodeIndexOffset] as ScriptNodeIndex;
|
|
173
|
+
const scopeId = rawScopes[i + scopeIdOffset] as ScopeId;
|
|
174
|
+
const depth = rawScopes[i + depthOffset];
|
|
175
|
+
const varsCount = rawScopes[i + varsCountOffset];
|
|
176
|
+
|
|
177
|
+
const variables: VariableDefinition[] = [];
|
|
178
|
+
for (let j = 0; j < varsCount; ++j) {
|
|
179
|
+
const nameIndex = rawVars[varOffset + j * varStride + varNameOffset];
|
|
180
|
+
variables.push({
|
|
181
|
+
name: strings[nameIndex],
|
|
182
|
+
slotIndex: j,
|
|
183
|
+
uses: [],
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
varOffset += varsCount * varStride;
|
|
187
|
+
|
|
188
|
+
const scope: EmbeddedScope = {
|
|
189
|
+
scriptNodeIndex,
|
|
190
|
+
scopeId,
|
|
191
|
+
children: [],
|
|
192
|
+
variables,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
if (currentScriptNodeIndex !== scriptNodeIndex) {
|
|
196
|
+
currentScriptNodeIndex = scriptNodeIndex;
|
|
197
|
+
scopeStack.length = 0;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (depth > 0) {
|
|
201
|
+
const parent = scopeStack[depth - 1];
|
|
202
|
+
scope.parent = parent;
|
|
203
|
+
parent.children.push(scope);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// The tree hierarchy is encoded through a depth field in the heap snapshot. Here we
|
|
207
|
+
// set the scope as the innermost scope by trimming the stack.
|
|
208
|
+
scopeStack[depth] = scope;
|
|
209
|
+
scopeStack.length = depth + 1; // Trim the stack.
|
|
210
|
+
|
|
211
|
+
let scriptScopes = scopesByScript.get(scriptNodeIndex);
|
|
212
|
+
if (!scriptScopes) {
|
|
213
|
+
scriptScopes = new Map<ScopeId, EmbeddedScope>();
|
|
214
|
+
scopesByScript.set(scriptNodeIndex, scriptScopes);
|
|
215
|
+
}
|
|
216
|
+
scriptScopes.set(scopeId, scope);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Pass 2: Resolve variable uses directly from rawUses.
|
|
220
|
+
let useOffset = 0;
|
|
221
|
+
for (let i = 0; i < rawScopes.length; i += scopeStride) {
|
|
222
|
+
const scriptNodeIndex = rawScopes[i + scriptNodeIndexOffset] as ScriptNodeIndex;
|
|
223
|
+
const scopeId = rawScopes[i + scopeIdOffset] as ScopeId;
|
|
224
|
+
const usesCount = rawScopes[i + usesCountOffset];
|
|
225
|
+
|
|
226
|
+
const scope = scopesByScript.get(scriptNodeIndex)?.get(scopeId);
|
|
227
|
+
if (scope) {
|
|
228
|
+
for (let j = 0; j < usesCount; ++j) {
|
|
229
|
+
const declaringScopeId = rawUses[useOffset + j * useStride + useDeclaringScopeIdOffset] as ScopeId;
|
|
230
|
+
const slotIndex = rawUses[useOffset + j * useStride + useSlotIndexOffset];
|
|
231
|
+
const declaringScope = scopesByScript.get(scriptNodeIndex)?.get(declaringScopeId);
|
|
232
|
+
const variable = declaringScope?.variables[slotIndex];
|
|
233
|
+
if (variable && !variable.uses.includes(scope)) {
|
|
234
|
+
variable.uses.push(scope);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
useOffset += usesCount * useStride;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return scopesByScript;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function scanHeap(snapshot: HeapSnapshot): HeapScan {
|
|
245
|
+
const scripts = new Map<ScriptNodeIndex, ScriptInfo>();
|
|
246
|
+
const contextNodes: ContextNodeInfo[] = [];
|
|
247
|
+
const liveClosures: LiveClosure[] = [];
|
|
248
|
+
const scopeInfoScriptNodeIndexes = new Map<ScopeInfoNodeIndex, ScriptNodeIndex|undefined>();
|
|
249
|
+
const node = snapshot.createNode();
|
|
250
|
+
const nodes = snapshot.nodes;
|
|
251
|
+
const nodeFieldCount = snapshot.nodeFieldCount;
|
|
252
|
+
const nodeClosureType = snapshot.nodeClosureType;
|
|
253
|
+
|
|
254
|
+
for (let nodeIndex = 0; nodeIndex < nodes.length; nodeIndex += nodeFieldCount) {
|
|
255
|
+
node.nodeIndex = nodeIndex;
|
|
256
|
+
const rawName = node.rawName();
|
|
257
|
+
|
|
258
|
+
if (rawName.startsWith('system / Script')) {
|
|
259
|
+
processScript(scripts, node);
|
|
260
|
+
} else if (snapshot.isContextObject(node)) {
|
|
261
|
+
processContext(contextNodes, node);
|
|
262
|
+
} else if (node.rawType() === nodeClosureType) {
|
|
263
|
+
processClosure(liveClosures, node);
|
|
264
|
+
} else if (rawName.startsWith('system / SharedFunctionInfo')) {
|
|
265
|
+
processSharedFunctionInfo(scopeInfoScriptNodeIndexes, node);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return {scripts, contextNodes, liveClosures, scopeInfoScriptNodeIndexes};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function processScript(scripts: Map<ScriptNodeIndex, ScriptInfo>, node: HeapSnapshotNode): void {
|
|
273
|
+
const rawName = node.rawName();
|
|
274
|
+
const scriptNodeIndex = node.nodeIndex as ScriptNodeIndex;
|
|
275
|
+
const scriptNamePrefix = 'system / Script / ';
|
|
276
|
+
let name = rawName.startsWith(scriptNamePrefix) ? rawName.substring(scriptNamePrefix.length) : '';
|
|
277
|
+
if (!name) {
|
|
278
|
+
name = node.findInternalEdgeTarget('source_url')?.name() ?? '';
|
|
279
|
+
}
|
|
280
|
+
scripts.set(scriptNodeIndex, {
|
|
281
|
+
name,
|
|
282
|
+
nodeId: node.id(),
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function processContext(contextNodes: ContextNodeInfo[], node: HeapSnapshotNode): void {
|
|
287
|
+
const scopeInfoNodeIndex = node.findInternalEdgeTarget('scope_info')?.nodeIndex as ScopeInfoNodeIndex | undefined;
|
|
288
|
+
if (scopeInfoNodeIndex === undefined) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
contextNodes.push({
|
|
292
|
+
contextNodeIndex: node.nodeIndex as ContextNodeIndex,
|
|
293
|
+
contextNodeId: node.id(),
|
|
294
|
+
scopeInfoNodeIndex,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function processClosure(liveClosures: LiveClosure[], node: HeapSnapshotNode): void {
|
|
299
|
+
const sharedFunctionInfo = node.findInternalEdgeTarget('shared');
|
|
300
|
+
const closureContext = node.findInternalEdgeTarget('context');
|
|
301
|
+
if (!sharedFunctionInfo || !closureContext) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const script = sharedFunctionInfo.findInternalEdgeTarget('script');
|
|
305
|
+
if (!script) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const scopeId = sharedFunctionInfo.findInternalEdgeTarget('scope_id')?.nodeValueAsInt() as ScopeId | undefined;
|
|
309
|
+
if (scopeId !== undefined) {
|
|
310
|
+
liveClosures.push({
|
|
311
|
+
contextNodeIndex: closureContext.nodeIndex as ContextNodeIndex,
|
|
312
|
+
scriptNodeIndex: script.nodeIndex as ScriptNodeIndex,
|
|
313
|
+
scopeId,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
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
|
+
function buildLiveFunctions(snapshot: HeapSnapshot, liveClosures: LiveClosure[]): ByScope<LiveFunction> {
|
|
329
|
+
const liveFunctionsByScript: ByScope<LiveFunction> = new Map();
|
|
330
|
+
const node = snapshot.createNode();
|
|
331
|
+
|
|
332
|
+
for (const closure of liveClosures) {
|
|
333
|
+
let scriptFunctions = liveFunctionsByScript.get(closure.scriptNodeIndex);
|
|
334
|
+
if (!scriptFunctions) {
|
|
335
|
+
scriptFunctions = new Map<ScopeId, LiveFunction>();
|
|
336
|
+
liveFunctionsByScript.set(closure.scriptNodeIndex, scriptFunctions);
|
|
337
|
+
}
|
|
338
|
+
let liveFunction = scriptFunctions.get(closure.scopeId);
|
|
339
|
+
if (!liveFunction) {
|
|
340
|
+
liveFunction = {
|
|
341
|
+
contextNodeIndexes: new Set(),
|
|
342
|
+
};
|
|
343
|
+
scriptFunctions.set(closure.scopeId, liveFunction);
|
|
344
|
+
}
|
|
345
|
+
let contextNodeIndex: ContextNodeIndex|undefined = closure.contextNodeIndex;
|
|
346
|
+
while (contextNodeIndex !== undefined && !liveFunction.contextNodeIndexes.has(contextNodeIndex)) {
|
|
347
|
+
node.nodeIndex = contextNodeIndex;
|
|
348
|
+
if (!snapshot.isContextObject(node)) {
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
liveFunction.contextNodeIndexes.add(contextNodeIndex);
|
|
352
|
+
contextNodeIndex = node.findInternalEdgeTarget('previous')?.nodeIndex as ContextNodeIndex | undefined;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return liveFunctionsByScript;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function correlateContextsWithScopes(snapshot: HeapSnapshot, scan: HeapScan, scopesByScript: ByScope<EmbeddedScope>): {
|
|
360
|
+
scopes: Map<ScopeInfoNodeIndex, ScopeAccumulator>,
|
|
361
|
+
scriptsWithoutScopes: HeapSnapshotModel.HeapSnapshotModel.ScriptWithoutScopes[],
|
|
362
|
+
} {
|
|
363
|
+
const scriptsWithoutScopesByScript =
|
|
364
|
+
new Map<ScriptNodeIndex, HeapSnapshotModel.HeapSnapshotModel.ScriptWithoutScopes>();
|
|
365
|
+
const scopes = new Map<ScopeInfoNodeIndex, ScopeAccumulator>();
|
|
366
|
+
const node = snapshot.createNode();
|
|
367
|
+
|
|
368
|
+
for (const context of scan.contextNodes) {
|
|
369
|
+
const scopeInfoNodeIndex = context.scopeInfoNodeIndex;
|
|
370
|
+
const existingScope = scopes.get(scopeInfoNodeIndex);
|
|
371
|
+
if (existingScope) {
|
|
372
|
+
const fieldValueNodeIndexes: number[] = [];
|
|
373
|
+
node.nodeIndex = context.contextNodeIndex;
|
|
374
|
+
for (const edges = node.edges(); edges.hasNext(); edges.next()) {
|
|
375
|
+
const edge = edges.item();
|
|
376
|
+
if (edge.type() === 'context') {
|
|
377
|
+
fieldValueNodeIndexes.push(edge.nodeIndex());
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
existingScope.contexts.push({
|
|
381
|
+
contextNodeIndex: context.contextNodeIndex,
|
|
382
|
+
contextNodeId: context.contextNodeId,
|
|
383
|
+
fieldValueNodeIndexes,
|
|
384
|
+
});
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
node.nodeIndex = scopeInfoNodeIndex;
|
|
389
|
+
const scopeId = node.findInternalEdgeTarget('scope_id')?.nodeValueAsInt() as ScopeId | undefined;
|
|
390
|
+
if (scopeId === undefined) {
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const scopeInfoNodeId = node.id();
|
|
395
|
+
const scopeStart = node.findInternalEdgeTarget('start_position')?.nodeValueAsInt() ?? 0;
|
|
396
|
+
const scopeEnd = node.findInternalEdgeTarget('end_position')?.nodeValueAsInt() ?? 0;
|
|
397
|
+
const scopeName = node.findInternalEdgeTarget('function_name')?.name();
|
|
398
|
+
|
|
399
|
+
const scriptNodeIndex =
|
|
400
|
+
resolveScopeInfoScriptNodeIndex(snapshot, scan.scopeInfoScriptNodeIndexes, scopeInfoNodeIndex);
|
|
401
|
+
const script = scriptNodeIndex !== undefined ? scan.scripts.get(scriptNodeIndex) : undefined;
|
|
402
|
+
if (scriptNodeIndex === undefined || !script) {
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const existingScriptWithoutScopes = scriptsWithoutScopesByScript.get(scriptNodeIndex);
|
|
407
|
+
if (existingScriptWithoutScopes) {
|
|
408
|
+
existingScriptWithoutScopes.contextCount++;
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const embeddedScopes = scopesByScript.get(scriptNodeIndex);
|
|
413
|
+
if (!embeddedScopes) {
|
|
414
|
+
scriptsWithoutScopesByScript.set(scriptNodeIndex, {
|
|
415
|
+
scriptNodeIndex,
|
|
416
|
+
scriptNodeId: script.nodeId,
|
|
417
|
+
scriptName: script.name,
|
|
418
|
+
contextCount: 1,
|
|
419
|
+
});
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const embeddedScope = embeddedScopes.get(scopeId);
|
|
424
|
+
if (!embeddedScope) {
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const fieldNames: string[] = [];
|
|
429
|
+
const fieldValueNodeIndexes: number[] = [];
|
|
430
|
+
node.nodeIndex = context.contextNodeIndex;
|
|
431
|
+
for (const edges = node.edges(); edges.hasNext(); edges.next()) {
|
|
432
|
+
const edge = edges.item();
|
|
433
|
+
if (edge.type() === 'context') {
|
|
434
|
+
fieldNames.push(edge.name());
|
|
435
|
+
fieldValueNodeIndexes.push(edge.nodeIndex());
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
scopes.set(scopeInfoNodeIndex, {
|
|
440
|
+
scopeInfoNodeIndex,
|
|
441
|
+
scopeInfoNodeId,
|
|
442
|
+
scriptNodeIndex,
|
|
443
|
+
scriptNodeId: script.nodeId,
|
|
444
|
+
scriptName: script.name,
|
|
445
|
+
scopeName,
|
|
446
|
+
scopeStart,
|
|
447
|
+
scopeEnd,
|
|
448
|
+
scope: embeddedScope,
|
|
449
|
+
fieldNames,
|
|
450
|
+
contexts: [{
|
|
451
|
+
contextNodeIndex: context.contextNodeIndex,
|
|
452
|
+
contextNodeId: context.contextNodeId,
|
|
453
|
+
fieldValueNodeIndexes,
|
|
454
|
+
}],
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return {
|
|
459
|
+
scopes,
|
|
460
|
+
scriptsWithoutScopes: [...scriptsWithoutScopesByScript.values()],
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function resolveScopeInfoScriptNodeIndex(
|
|
465
|
+
snapshot: HeapSnapshot,
|
|
466
|
+
scopeInfoScriptNodeIndexes: Map<ScopeInfoNodeIndex, ScriptNodeIndex|undefined>,
|
|
467
|
+
scopeInfoNodeIndex: ScopeInfoNodeIndex,
|
|
468
|
+
): ScriptNodeIndex|undefined {
|
|
469
|
+
if (scopeInfoScriptNodeIndexes.has(scopeInfoNodeIndex)) {
|
|
470
|
+
return scopeInfoScriptNodeIndexes.get(scopeInfoNodeIndex);
|
|
471
|
+
}
|
|
472
|
+
const visited: ScopeInfoNodeIndex[] = [];
|
|
473
|
+
const seen = new Set<ScopeInfoNodeIndex>();
|
|
474
|
+
const node = snapshot.createNode();
|
|
475
|
+
let currentNodeIndex = scopeInfoNodeIndex;
|
|
476
|
+
let scriptNodeIndex: ScriptNodeIndex|undefined;
|
|
477
|
+
while (!seen.has(currentNodeIndex)) {
|
|
478
|
+
if (scopeInfoScriptNodeIndexes.has(currentNodeIndex)) {
|
|
479
|
+
scriptNodeIndex = scopeInfoScriptNodeIndexes.get(currentNodeIndex);
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
seen.add(currentNodeIndex);
|
|
483
|
+
visited.push(currentNodeIndex);
|
|
484
|
+
node.nodeIndex = currentNodeIndex;
|
|
485
|
+
const outerScopeInfo = node.findInternalEdgeTarget('outer_scope_info');
|
|
486
|
+
if (!outerScopeInfo) {
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
currentNodeIndex = outerScopeInfo.nodeIndex as ScopeInfoNodeIndex;
|
|
490
|
+
}
|
|
491
|
+
for (const visitedNodeIndex of visited) {
|
|
492
|
+
scopeInfoScriptNodeIndexes.set(visitedNodeIndex, scriptNodeIndex);
|
|
493
|
+
}
|
|
494
|
+
return scriptNodeIndex;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function classifyFields(
|
|
498
|
+
snapshot: HeapSnapshot,
|
|
499
|
+
scopes: Map<ScopeInfoNodeIndex, ScopeAccumulator>,
|
|
500
|
+
liveFunctionsByScript: ByScope<LiveFunction>,
|
|
501
|
+
): HeapSnapshotModel.HeapSnapshotModel.ScopeAnalysis[] {
|
|
502
|
+
const scopeAnalyses: HeapSnapshotModel.HeapSnapshotModel.ScopeAnalysis[] = [];
|
|
503
|
+
const node = snapshot.createNode();
|
|
504
|
+
|
|
505
|
+
for (const scope of scopes.values()) {
|
|
506
|
+
const scopeContexts: HeapSnapshotModel.HeapSnapshotModel.ContextAnalysis[] = [];
|
|
507
|
+
for (const context of scope.contexts) {
|
|
508
|
+
let contextDeadFieldsRetainedSizeSum = 0;
|
|
509
|
+
const deadFields: HeapSnapshotModel.HeapSnapshotModel.ContextField[] = [];
|
|
510
|
+
for (let fieldIndex = 0; fieldIndex < context.fieldValueNodeIndexes.length; ++fieldIndex) {
|
|
511
|
+
const fieldName = scope.fieldNames[fieldIndex];
|
|
512
|
+
const variable = scope.scope.variables.find(v => v.name === fieldName);
|
|
513
|
+
if (!variable) {
|
|
514
|
+
// Missing variable definition. Nothing to check in this case.
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (isVariableUsedInContext(liveFunctionsByScript, scope.scriptNodeIndex, context.contextNodeIndex, variable)) {
|
|
518
|
+
// Only report dead fields.
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
const fieldValueNodeIndex = context.fieldValueNodeIndexes[fieldIndex];
|
|
522
|
+
node.nodeIndex = fieldValueNodeIndex;
|
|
523
|
+
const retainedSize = node.retainedSize();
|
|
524
|
+
deadFields.push({
|
|
525
|
+
name: fieldName,
|
|
526
|
+
valueNodeIndex: fieldValueNodeIndex,
|
|
527
|
+
valueNodeId: node.id(),
|
|
528
|
+
valueName: node.name(),
|
|
529
|
+
valueType: node.type(),
|
|
530
|
+
selfSize: node.selfSize(),
|
|
531
|
+
retainedSize,
|
|
532
|
+
});
|
|
533
|
+
contextDeadFieldsRetainedSizeSum += retainedSize;
|
|
534
|
+
}
|
|
535
|
+
if (deadFields.length === 0) {
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
node.nodeIndex = context.contextNodeIndex;
|
|
539
|
+
scopeContexts.push({
|
|
540
|
+
contextNodeIndex: context.contextNodeIndex,
|
|
541
|
+
contextNodeId: node.id(),
|
|
542
|
+
retainedSize: node.retainedSize(),
|
|
543
|
+
deadFieldsRetainedSizeSum: contextDeadFieldsRetainedSizeSum,
|
|
544
|
+
deadFields,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
if (scopeContexts.length === 0) {
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
scopeAnalyses.push({
|
|
551
|
+
scopeInfoNodeIndex: scope.scopeInfoNodeIndex,
|
|
552
|
+
scopeInfoNodeId: scope.scopeInfoNodeId,
|
|
553
|
+
scriptNodeIndex: scope.scriptNodeIndex,
|
|
554
|
+
scriptNodeId: scope.scriptNodeId,
|
|
555
|
+
scriptName: scope.scriptName,
|
|
556
|
+
scopeName: scope.scopeName,
|
|
557
|
+
scopeStart: scope.scopeStart,
|
|
558
|
+
scopeEnd: scope.scopeEnd,
|
|
559
|
+
contextFieldCount: scope.fieldNames.length,
|
|
560
|
+
contexts: scopeContexts,
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return scopeAnalyses;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Determines whether a specific allocated Context instance (`contextNodeIndex`)
|
|
568
|
+
// is reachable by any live closure (or inner function instantiable by a live closure)
|
|
569
|
+
// that accesses this variable.
|
|
570
|
+
function isVariableUsedInContext(
|
|
571
|
+
liveFunctionsByScript: ByScope<LiveFunction>,
|
|
572
|
+
scriptNodeIndex: ScriptNodeIndex,
|
|
573
|
+
contextNodeIndex: ContextNodeIndex,
|
|
574
|
+
variable: VariableDefinition,
|
|
575
|
+
): boolean {
|
|
576
|
+
const liveFunctionsByScopeId = liveFunctionsByScript.get(scriptNodeIndex);
|
|
577
|
+
if (!liveFunctionsByScopeId) {
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
for (const usingScope of variable.uses) {
|
|
581
|
+
// Walk up the lexical scope chain from the variable's use site.
|
|
582
|
+
// This checks if the use site is inside a live closure itself (or one
|
|
583
|
+
// of its nested block scopes) OR inside an uninstantiated function
|
|
584
|
+
// enclosed by a live closure.
|
|
585
|
+
//
|
|
586
|
+
// Executing a live outer closure can instantiate any function nested
|
|
587
|
+
// inside it, so those inner functions can access the outer closure's
|
|
588
|
+
// context chain even when no closure exists for them yet on the heap.
|
|
589
|
+
//
|
|
590
|
+
// Example:
|
|
591
|
+
// function outer() {
|
|
592
|
+
// const x = 1;
|
|
593
|
+
// return function middle() { // <- liveFunction (on heap)
|
|
594
|
+
// return function inner() {
|
|
595
|
+
// return x; // <- use site
|
|
596
|
+
// };
|
|
597
|
+
// };
|
|
598
|
+
// }
|
|
599
|
+
// No closure object exists on the heap for `inner` yet (so `inner` is
|
|
600
|
+
// not in `liveFunctionsByScript`). However, walking up from `inner`'s
|
|
601
|
+
// scope reaches `middle`. Since `middle` is a live closure on the heap
|
|
602
|
+
// whose context chain includes `outer`'s Context containing `x`,
|
|
603
|
+
// executing `middle()` in the future will instantiate `inner`, giving it
|
|
604
|
+
// access to `outer`'s Context containing `x`.
|
|
605
|
+
for (let curr: EmbeddedScope|undefined = usingScope; curr; curr = curr.parent) {
|
|
606
|
+
const liveFunction = liveFunctionsByScopeId.get(curr.scopeId);
|
|
607
|
+
if (liveFunction?.contextNodeIndexes.has(contextNodeIndex)) {
|
|
608
|
+
return true;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return false;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function sortAndBuildResult(
|
|
616
|
+
scopeAnalyses: HeapSnapshotModel.HeapSnapshotModel.ScopeAnalysis[],
|
|
617
|
+
scriptsWithoutScopes: HeapSnapshotModel.HeapSnapshotModel.ScriptWithoutScopes[],
|
|
618
|
+
): HeapSnapshotModel.HeapSnapshotModel.ContextAnalysisResult {
|
|
619
|
+
const compareContexts = (
|
|
620
|
+
left: HeapSnapshotModel.HeapSnapshotModel.ContextAnalysis,
|
|
621
|
+
right: HeapSnapshotModel.HeapSnapshotModel.ContextAnalysis,
|
|
622
|
+
): number => {
|
|
623
|
+
return right.deadFieldsRetainedSizeSum - left.deadFieldsRetainedSizeSum || right.retainedSize - left.retainedSize ||
|
|
624
|
+
left.contextNodeIndex - right.contextNodeIndex;
|
|
625
|
+
};
|
|
626
|
+
for (const scope of scopeAnalyses) {
|
|
627
|
+
for (const context of scope.contexts) {
|
|
628
|
+
context.deadFields.sort((left, right) => right.retainedSize - left.retainedSize);
|
|
629
|
+
}
|
|
630
|
+
scope.contexts.sort(compareContexts);
|
|
631
|
+
}
|
|
632
|
+
scopeAnalyses.sort((left, right) => compareContexts(left.contexts[0], right.contexts[0]));
|
|
633
|
+
scriptsWithoutScopes.sort((left, right) => left.scriptNodeIndex - right.scriptNodeIndex);
|
|
634
|
+
return {scopes: scopeAnalyses, scriptsWithoutScopes};
|
|
635
|
+
}
|