chrome-devtools-frontend 1.0.1706026 → 1.0.1709383

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 (225) hide show
  1. package/.agents/skills/code-review/SKILL.md +121 -0
  2. package/AUTHORS +2 -0
  3. package/front_end/core/common/Color.ts +6 -0
  4. package/front_end/core/common/VersionController.ts +0 -53
  5. package/front_end/core/root/Runtime.ts +0 -41
  6. package/front_end/core/sdk/CSSModel.ts +57 -0
  7. package/front_end/core/sdk/CSSPropertyParser.ts +10 -17
  8. package/front_end/core/sdk/DOMModel.ts +13 -6
  9. package/front_end/core/sdk/DebuggerModel.ts +90 -56
  10. package/front_end/core/sdk/NetworkRequest.ts +70 -12
  11. package/front_end/core/sdk/OverlayModel.ts +47 -0
  12. package/front_end/core/sdk/PageResourceLoader.ts +26 -6
  13. package/front_end/core/sdk/SourceMap.ts +37 -9
  14. package/front_end/core/sdk/SourceMapScopesInfo.ts +223 -86
  15. package/front_end/core/text_utils/ContentData.ts +18 -1
  16. package/front_end/core/text_utils/TextUtils.ts +1 -1
  17. package/front_end/entrypoints/devtools_app/devtools_app.ts +1 -0
  18. package/front_end/entrypoints/heap_snapshot_worker/ContextAnalyzer.ts +102 -4
  19. package/front_end/entrypoints/main/MainImpl.ts +2 -21
  20. package/front_end/generated/InspectorBackendCommands.ts +9 -2
  21. package/front_end/generated/SupportedCSSProperties.ts +12 -3
  22. package/front_end/generated/protocol-mapping.d.ts +31 -0
  23. package/front_end/generated/protocol-proxy-api.d.ts +32 -0
  24. package/front_end/generated/protocol.ts +19 -3
  25. package/front_end/models/ai_assistance/AiAgent2.ts +127 -2
  26. package/front_end/models/ai_assistance/AiConversation.ts +2 -2
  27. package/front_end/models/ai_assistance/AiUtils.ts +4 -0
  28. package/front_end/models/ai_assistance/agents/AiAgent.ts +64 -13
  29. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +2 -2
  30. package/front_end/models/ai_assistance/agents/ExecuteJavascript.ts +4 -1
  31. package/front_end/models/ai_assistance/agents/PerformanceAgent.snapshot.txt +257 -2
  32. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +1 -1
  33. package/front_end/models/ai_assistance/ai_assistance.ts +2 -2
  34. package/front_end/models/ai_assistance/contexts/{AccessibilityContext.snapshot.txt → LighthouseContext.snapshot.txt} +23 -2
  35. package/front_end/models/ai_assistance/contexts/{AccessibilityContext.ts → LighthouseContext.ts} +24 -5
  36. package/front_end/models/ai_assistance/data_formatters/LighthouseFormatter.ts +26 -3
  37. package/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts +15 -16
  38. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +8 -18
  39. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +2 -2
  40. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +81 -103
  41. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +202 -15
  42. package/front_end/models/ai_assistance/skills/Skill.ts +1 -2
  43. package/front_end/models/ai_assistance/skills/SkillRegistry.ts +2 -0
  44. package/front_end/models/ai_assistance/skills/lighthouse.md +33 -0
  45. package/front_end/models/ai_assistance/tools/ExecuteJavaScript.ts +6 -0
  46. package/front_end/models/ai_assistance/tools/GetCookieValues.ts +3 -0
  47. package/front_end/models/ai_assistance/tools/GetDetailedCallTree.ts +2 -0
  48. package/front_end/models/ai_assistance/tools/GetElementAccessibilityDetails.ts +2 -0
  49. package/front_end/models/ai_assistance/tools/GetInsightDetails.ts +2 -0
  50. package/front_end/models/ai_assistance/tools/GetLighthouseAudits.ts +8 -7
  51. package/front_end/models/ai_assistance/tools/GetNetworkRequestDetails.ts +2 -0
  52. package/front_end/models/ai_assistance/tools/GetSourceContent.ts +2 -0
  53. package/front_end/models/ai_assistance/tools/GetStorageBreakdown.ts +2 -0
  54. package/front_end/models/ai_assistance/tools/GetStorageValues.ts +3 -0
  55. package/front_end/models/ai_assistance/tools/GetStyles.ts +2 -0
  56. package/front_end/models/ai_assistance/tools/GetTraceEventByKey.ts +3 -1
  57. package/front_end/models/ai_assistance/tools/GetTraceFunctionCode.ts +2 -0
  58. package/front_end/models/ai_assistance/tools/GetTraceMainThreadSummary.ts +2 -0
  59. package/front_end/models/ai_assistance/tools/GetTraceNetworkSummary.ts +2 -0
  60. package/front_end/models/ai_assistance/tools/GetTraceResourceContent.ts +2 -0
  61. package/front_end/models/ai_assistance/tools/ListCookies.ts +2 -0
  62. package/front_end/models/ai_assistance/tools/ListNetworkRequests.ts +2 -0
  63. package/front_end/models/ai_assistance/tools/ListPageOrigins.ts +2 -0
  64. package/front_end/models/ai_assistance/tools/ListSources.ts +2 -0
  65. package/front_end/models/ai_assistance/tools/ListStorageKeys.ts +2 -0
  66. package/front_end/models/ai_assistance/tools/RecordPerformanceTrace.ts +2 -0
  67. package/front_end/models/ai_assistance/tools/ResolveDevtoolsNodePath.ts +2 -0
  68. package/front_end/models/ai_assistance/tools/RunLighthouse.ts +20 -12
  69. package/front_end/models/ai_assistance/tools/SelectTraceEventByKey.ts +2 -0
  70. package/front_end/models/ai_assistance/tools/Tool.ts +53 -0
  71. package/front_end/models/bindings/CompilerScriptMapping.ts +102 -45
  72. package/front_end/models/bindings/DebuggerLanguagePlugins.ts +25 -33
  73. package/front_end/models/bindings/DebuggerWorkspaceBinding.ts +70 -49
  74. package/front_end/models/bindings/SourceMapStepping.ts +50 -0
  75. package/front_end/models/bindings/StylesSourceMapping.ts +3 -0
  76. package/front_end/models/bindings/bindings.ts +2 -0
  77. package/front_end/models/emulation/DeviceModeModel.ts +85 -62
  78. package/front_end/models/extensions/ExtensionEndpoint.ts +27 -6
  79. package/front_end/models/extensions/LanguageExtensionEndpoint.ts +106 -21
  80. package/front_end/models/extensions/RecorderExtensionEndpoint.ts +4 -2
  81. package/front_end/models/har/Importer.ts +2 -3
  82. package/front_end/models/javascript_metadata/NativeFunctions.ts +6 -2
  83. package/front_end/models/lighthouse/LighthouseReporterTypes.ts +5 -1
  84. package/front_end/models/persistence/NetworkPersistenceManager.ts +4 -3
  85. package/front_end/models/source_map_scopes/NamesResolver.ts +30 -13
  86. package/front_end/models/stack_trace/StackTraceImpl.ts +138 -47
  87. package/front_end/models/stack_trace/StackTraceModel.ts +117 -84
  88. package/front_end/models/stack_trace/Trie.ts +27 -0
  89. package/front_end/models/trace/LanternComputationData.ts +8 -0
  90. package/front_end/models/trace/Processor.ts +2 -1
  91. package/front_end/models/trace/lantern/core/NetworkAnalyzer.ts +7 -2
  92. package/front_end/models/trace/lantern/graph/NetworkNode.ts +14 -0
  93. package/front_end/models/trace/lantern/graph/PageDependencyGraph.ts +1 -1
  94. package/front_end/models/trace/lantern/simulation/ConnectionPool.ts +4 -4
  95. package/front_end/models/trace/lantern/simulation/Simulator.ts +6 -2
  96. package/front_end/models/trace/lantern/simulation/TCPConnection.ts +12 -12
  97. package/front_end/models/trace/lantern/types/Lantern.ts +8 -0
  98. package/front_end/panels/accessibility/ARIAAttributesView.ts +6 -6
  99. package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingView.ts +2 -2
  100. package/front_end/panels/accessibility/AccessibilityNodeView.ts +6 -9
  101. package/front_end/panels/accessibility/AccessibilitySidebarView.ts +83 -69
  102. package/front_end/panels/accessibility/AccessibilitySubPane.ts +24 -9
  103. package/front_end/panels/accessibility/SourceOrderView.ts +7 -3
  104. package/front_end/panels/accessibility/accessibilitySidebarView.css +5 -3
  105. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +14 -12
  106. package/front_end/panels/ai_assistance/components/ChatInput.ts +1 -1
  107. package/front_end/panels/ai_assistance/components/ChatMessage.ts +121 -13
  108. package/front_end/panels/ai_assistance/components/chatMessage.css +47 -0
  109. package/front_end/panels/application/BackgroundServiceView.ts +31 -33
  110. package/front_end/panels/application/IndexedDBModel.ts +8 -20
  111. package/front_end/panels/application/IndexedDBViews.ts +2 -6
  112. package/front_end/panels/application/ServiceWorkerUpdateCycleView.ts +8 -8
  113. package/front_end/panels/application/ServiceWorkersView.ts +44 -52
  114. package/front_end/panels/application/WebMCPView.ts +50 -50
  115. package/front_end/panels/application/serviceWorkersView.css +1 -1
  116. package/front_end/panels/comments/CommentsPane.ts +266 -0
  117. package/front_end/panels/comments/comments-meta.ts +47 -0
  118. package/front_end/panels/comments/comments.ts +9 -0
  119. package/front_end/panels/comments/commentsPane.css +206 -0
  120. package/front_end/panels/common/CommentThreadWidget.ts +30 -1
  121. package/front_end/panels/common/CommentsOverlayWidget.ts +42 -38
  122. package/front_end/panels/common/CommentsStatusBarPill.ts +3 -1
  123. package/front_end/panels/common/common-meta.ts +21 -9
  124. package/front_end/panels/console/ConsoleInsightTeaser.ts +25 -19
  125. package/front_end/panels/console/ConsoleViewMessage.ts +52 -23
  126. package/front_end/panels/console/ConsoleViewport.ts +1 -1
  127. package/front_end/panels/console/SymbolizedErrorWidget.ts +18 -9
  128. package/front_end/panels/console/console-meta.ts +1 -5
  129. package/front_end/panels/elements/CSSValueTraceView.ts +4 -4
  130. package/front_end/panels/elements/ComputedStyleWidget.ts +46 -42
  131. package/front_end/panels/elements/DOMPath.ts +11 -1
  132. package/front_end/panels/elements/DOMTreeContextMenu.ts +15 -0
  133. package/front_end/panels/elements/DOMTreeWidget.ts +237 -606
  134. package/front_end/panels/elements/ElementsPanel.ts +15 -16
  135. package/front_end/panels/elements/ElementsTreeElement.ts +24 -18
  136. package/front_end/panels/elements/EventListenersWidget.ts +5 -3
  137. package/front_end/panels/elements/InspectElementModeController.ts +6 -3
  138. package/front_end/panels/elements/LayoutPane.ts +3 -3
  139. package/front_end/panels/elements/PropertyRenderer.ts +132 -87
  140. package/front_end/panels/elements/StandaloneStylesContainer.ts +7 -3
  141. package/front_end/panels/elements/StylePropertiesSection.ts +49 -4
  142. package/front_end/panels/elements/StylePropertyTreeElement.ts +139 -36
  143. package/front_end/panels/elements/StylesContainer.ts +2 -1
  144. package/front_end/panels/elements/StylesSidebarPane.ts +75 -16
  145. package/front_end/panels/elements/components/CSSPropertyDocsView.ts +2 -1
  146. package/front_end/panels/elements/elements-meta.ts +24 -75
  147. package/front_end/panels/emulation/DeviceModeToolbar.ts +5 -2
  148. package/front_end/panels/emulation/DeviceModeView.ts +5 -2
  149. package/front_end/panels/emulation/emulation-meta.ts +3 -8
  150. package/front_end/panels/layer_viewer/Layers3DView.ts +8 -3
  151. package/front_end/panels/layer_viewer/layer_viewer-meta.ts +2 -5
  152. package/front_end/panels/lighthouse/LighthouseController.ts +9 -4
  153. package/front_end/panels/lighthouse/LighthouseReportRenderer.ts +13 -3
  154. package/front_end/panels/network/NetworkConfigView.ts +261 -190
  155. package/front_end/panels/network/NetworkLogView.ts +7 -5
  156. package/front_end/panels/network/NetworkPanel.ts +4 -3
  157. package/front_end/panels/network/NetworkWaterfallColumn.ts +7 -4
  158. package/front_end/panels/network/RequestPayloadView.ts +22 -14
  159. package/front_end/panels/network/network-meta.ts +23 -34
  160. package/front_end/panels/profiler/HeapDetachedElementsDataGrid.ts +0 -1
  161. package/front_end/panels/profiler/HeapProfileView.ts +20 -24
  162. package/front_end/panels/profiler/ProfilesPanel.ts +8 -3
  163. package/front_end/panels/recorder/RecordingView.ts +42 -47
  164. package/front_end/panels/recorder/StepEditor.ts +1 -0
  165. package/front_end/panels/recorder/recordingView.css +4 -4
  166. package/front_end/panels/screencast/ScreencastView.ts +6 -4
  167. package/front_end/panels/sources/CSSPlugin.ts +11 -0
  168. package/front_end/panels/sources/CoveragePlugin.ts +34 -23
  169. package/front_end/panels/sources/NavigatorView.ts +97 -52
  170. package/front_end/panels/sources/Plugin.ts +15 -1
  171. package/front_end/panels/sources/SourcesView.ts +47 -80
  172. package/front_end/panels/sources/TabbedEditorContainer.ts +63 -34
  173. package/front_end/panels/sources/UISourceCodeFrame.ts +11 -2
  174. package/front_end/panels/sources/navigatorTree.css +2 -2
  175. package/front_end/panels/timeline/TimelineHistoryManager.ts +2 -4
  176. package/front_end/panels/timeline/components/LiveMetricsView.ts +7 -20
  177. package/front_end/panels/timeline/components/MetricCard.ts +262 -315
  178. package/front_end/panels/timeline/components/liveMetricsView.css +4 -0
  179. package/front_end/panels/timeline/components/metricCard.css +2 -24
  180. package/front_end/panels/timeline/overlays/OverlaysImpl.ts +33 -17
  181. package/front_end/panels/timeline/overlays/components/TimeRangeOverlay.ts +182 -130
  182. package/front_end/panels/timeline/overlays/components/timeRangeOverlay.css +76 -74
  183. package/front_end/panels/whats_new/WhatsNewImpl.ts +2 -1
  184. package/front_end/panels/whats_new/whats_new-meta.ts +2 -5
  185. package/front_end/third_party/chromium/README.chromium +1 -1
  186. package/front_end/third_party/web-vitals/README.chromium +2 -2
  187. package/front_end/third_party/web-vitals/package/README.md +5 -3
  188. package/front_end/third_party/web-vitals/package/dist/modules/attribution/onINP.js +1 -1
  189. package/front_end/third_party/web-vitals/package/dist/modules/attribution/onLCP.js +8 -5
  190. package/front_end/third_party/web-vitals/package/package.json +1 -1
  191. package/front_end/third_party/web-vitals/package/src/attribution/onINP.ts +1 -1
  192. package/front_end/third_party/web-vitals/package/src/attribution/onLCP.ts +9 -5
  193. package/front_end/third_party/web-vitals/rebuild.sh +2 -2
  194. package/front_end/ui/comments/CommentAnchorResolver.ts +17 -0
  195. package/front_end/ui/components/suggestion_input/SuggestionInput.ts +10 -1
  196. package/front_end/ui/components/text_editor/AiCodeCompletionTeaser.ts +1 -1
  197. package/front_end/ui/components/text_editor/AiCodeGenerationTeaser.ts +2 -2
  198. package/front_end/ui/components/text_editor/aiCodeCompletionTeaser.css +0 -11
  199. package/front_end/ui/components/text_editor/aiCodeGenerationTeaser.css +0 -11
  200. package/front_end/ui/components/text_editor/config.ts +2 -1
  201. package/front_end/ui/legacy/SoftDropDown.ts +17 -4
  202. package/front_end/ui/legacy/StackPane.ts +141 -0
  203. package/front_end/ui/legacy/TabbedPane.ts +67 -12
  204. package/front_end/ui/legacy/TextPrompt.ts +37 -7
  205. package/front_end/ui/legacy/Toolbar.ts +36 -14
  206. package/front_end/ui/legacy/Widget.ts +3 -3
  207. package/front_end/ui/legacy/components/inline_editor/PositionAreaEditor.ts +85 -0
  208. package/front_end/ui/legacy/components/inline_editor/positionAreaEditor.css +61 -0
  209. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +93 -31
  210. package/front_end/ui/legacy/components/quick_open/FilteredListWidget.ts +15 -7
  211. package/front_end/ui/legacy/components/utils/Linkifier.ts +25 -45
  212. package/front_end/ui/legacy/inspectorCommon.css +11 -0
  213. package/front_end/ui/legacy/legacy.ts +2 -0
  214. package/front_end/ui/legacy/softContextMenu.css +2 -0
  215. package/front_end/ui/legacy/tabbedPane.css +12 -8
  216. package/front_end/ui/settings/ConsoleSettings.ts +7 -0
  217. package/front_end/ui/settings/ElementsSettings.ts +93 -0
  218. package/front_end/ui/settings/EmulationSettings.ts +17 -0
  219. package/front_end/ui/settings/LayerViewerSettings.ts +12 -0
  220. package/front_end/ui/settings/NetworkSettings.ts +26 -0
  221. package/front_end/ui/settings/WhatsNewSettings.ts +11 -0
  222. package/front_end/ui/settings/settings.ts +10 -0
  223. package/front_end/ui/visual_logging/KnownContextValues.ts +17 -0
  224. package/inspector_overlay/highlight_anchor.ts +1 -1
  225. package/package.json +1 -1
@@ -16,13 +16,16 @@ export class SourceMapScopesInfo {
16
16
  readonly #sourceMap: SourceMap;
17
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
  /**
@@ -114,7 +117,8 @@ export class SourceMapScopesInfo {
114
117
  }
115
118
  }
116
119
 
117
- return new SourceMapScopesInfo(sourceMap, {scopes: scopesBySourceUrl, ranges: rootRange ? [rootRange] : []});
120
+ return new SourceMapScopesInfo(sourceMap, {scopes: scopesBySourceUrl, ranges: rootRange ? [rootRange] : []},
121
+ {isFromAst: true});
118
122
 
119
123
  /**
120
124
  * Finds the correct place in the tree to insert the new scope.
@@ -211,6 +215,10 @@ export class SourceMapScopesInfo {
211
215
  return noScopes && !this.#generatedRanges.length;
212
216
  }
213
217
 
218
+ hasGeneratedRanges(): boolean {
219
+ return this.#generatedRanges.length > 0;
220
+ }
221
+
214
222
  addOriginalScopesAtIndex(sourceIdx: number, scopes: ScopesCodec.OriginalScope[]): void {
215
223
  if (!this.#originalScopes[sourceIdx]?.length) {
216
224
  this.#originalScopes[sourceIdx] = scopes;
@@ -219,39 +227,17 @@ export class SourceMapScopesInfo {
219
227
  }
220
228
  }
221
229
 
222
- /**
223
- * @returns true, iff the function surrounding the provided position is marked as "hidden".
224
- */
225
- isOutlinedFrame(generatedLine: number, generatedColumn: number): boolean {
226
- const rangeChain = this.#findGeneratedRangeChain(generatedLine, generatedColumn);
227
- return this.#isOutlinedFrame(rangeChain);
228
- }
229
-
230
- #isOutlinedFrame(rangeChain: ScopesCodec.GeneratedRange[]): boolean {
231
- for (let i = rangeChain.length - 1; i >= 0; --i) {
232
- if (rangeChain[i].isStackFrame) {
233
- return rangeChain[i].isHidden;
234
- }
230
+ #generatedFrameKind(rangeChain: ScopesCodec.GeneratedRange[]): GeneratedFrameKind {
231
+ const functionRange = rangeChain.findLast(range => range.isStackFrame);
232
+ if (!functionRange) {
233
+ // Top-level code.
234
+ return GeneratedFrameKind.VISIBLE;
235
235
  }
236
- return false;
237
- }
238
-
239
- /**
240
- * @returns true, iff the range surrounding the provided position contains multiple
241
- * inlined original functions.
242
- */
243
- hasInlinedFrames(generatedLine: number, generatedColumn: number): boolean {
244
- const rangeChain = this.#findGeneratedRangeChain(generatedLine, generatedColumn);
245
- for (let i = rangeChain.length - 1; i >= 0; --i) {
246
- if (rangeChain[i].isStackFrame) {
247
- // We stop looking for inlined original functions once we reach the current frame.
248
- return false;
249
- }
250
- if (rangeChain[i].callSite) {
251
- return true;
252
- }
236
+ if (!functionRange.originalScope) {
237
+ // For scope information derived from the AST, we merely failed to map the function.
238
+ return this.#isFromAst ? GeneratedFrameKind.VISIBLE : GeneratedFrameKind.HIDDEN;
253
239
  }
254
- return false;
240
+ return functionRange.isHidden ? GeneratedFrameKind.OUTLINED : GeneratedFrameKind.VISIBLE;
255
241
  }
256
242
 
257
243
  /**
@@ -419,54 +405,53 @@ export class SourceMapScopesInfo {
419
405
  /**
420
406
  * Returns the authored function scope of the function containing the provided generated position.
421
407
  */
422
- findOriginalFunctionScope({line, column}: ScopesCodec.Position):
408
+ findOriginalFunctionScope(position: ScopesCodec.Position):
423
409
  {scope: ScopesCodec.OriginalScope, url?: Platform.DevToolsPath.UrlString}|null {
410
+ const rangeChain = this.#findGeneratedRangeChain(position.line, position.column);
411
+ const functionScope =
412
+ this.#findFunctionScopeInOriginalScopeChain(this.#innerMostOriginalScope(rangeChain, position));
413
+ return functionScope ? {scope: functionScope, url: this.#sourceURLOfScope(functionScope)} : null;
414
+ }
415
+
416
+ /**
417
+ * Returns the inner-most original scope containing the generated `position`. `rangeChain` must be the generated
418
+ * range chain of `position`.
419
+ */
420
+ #innerMostOriginalScope(rangeChain: ScopesCodec.GeneratedRange[],
421
+ position: ScopesCodec.Position): ScopesCodec.OriginalScope|undefined {
424
422
  // There are 2 approaches:
425
423
  // 1) Find the inner-most generated range containing the provided generated position
426
- // and use it's OriginalScope (then walk it outwards until we hit a function).
424
+ // and use its OriginalScope.
427
425
  // 2) Use the mappings to turn the generated position into an original position.
428
426
  // Then find the inner-most original scope containing that original position.
429
- // Then walk it outwards until we hit a function.
430
427
  //
431
428
  // Both approaches should yield the same result (assuming the mappings are spec compliant
432
429
  // w.r.t. generated ranges). But in the case of "pasta" scopes and extension provided
433
430
  // scope info, we only have the OriginalScope parts and mappings without GeneratedRanges.
434
-
435
- let originalInnerMostScope: ScopesCodec.OriginalScope|undefined;
436
-
437
431
  if (this.#generatedRanges.length > 0) {
438
- const rangeChain = this.#findGeneratedRangeChain(line, column);
439
- originalInnerMostScope = rangeChain.at(-1)?.originalScope;
440
- } else {
441
- // No GeneratedRanges. Try to use mappings.
442
- const entry = this.#sourceMap.findEntry(line, column);
443
- if (entry?.sourceIndex === undefined) {
444
- return null;
445
- }
446
- originalInnerMostScope =
447
- this.#findOriginalScopeChain(
448
- {sourceIndex: entry.sourceIndex, line: entry.sourceLineNumber, column: entry.sourceColumnNumber})
449
- .at(-1);
432
+ return rangeChain.at(-1)?.originalScope;
450
433
  }
451
434
 
452
- if (!originalInnerMostScope) {
453
- return null;
454
- }
455
-
456
- const functionScope = this.#findFunctionScopeInOriginalScopeChain(originalInnerMostScope);
457
- if (!functionScope) {
458
- return null;
435
+ // No GeneratedRanges. Try to use mappings.
436
+ const entry = this.#sourceMap.findEntry(position.line, position.column);
437
+ if (entry?.sourceIndex === undefined) {
438
+ return undefined;
459
439
  }
440
+ return this
441
+ .#findOriginalScopeChain(
442
+ {sourceIndex: entry.sourceIndex, line: entry.sourceLineNumber, column: entry.sourceColumnNumber})
443
+ .at(-1);
444
+ }
460
445
 
446
+ /** @returns the URL of the original source that `scope` belongs to. */
447
+ #sourceURLOfScope(scope: ScopesCodec.OriginalScope): Platform.DevToolsPath.UrlString|undefined {
461
448
  // Find the root scope for some given original source, to get the source url.
462
- let rootScope: ScopesCodec.OriginalScope = functionScope;
449
+ let rootScope: ScopesCodec.OriginalScope = scope;
463
450
  while (rootScope.parent) {
464
451
  rootScope = rootScope.parent;
465
452
  }
466
453
  const sourceIndex = this.#originalScopes.findIndex(scopes => scopes?.includes(rootScope));
467
- const url = sourceIndex !== -1 ? this.#sourceMap.sourceURLForSourceIndex(sourceIndex) : undefined;
468
-
469
- return functionScope ? {scope: functionScope, url} : null;
454
+ return sourceIndex !== -1 ? this.#sourceMap.sourceURLForSourceIndex(sourceIndex) : undefined;
470
455
  }
471
456
 
472
457
  /**
@@ -513,33 +498,54 @@ export class SourceMapScopesInfo {
513
498
  }
514
499
 
515
500
  /**
516
- * Returns one or more original stack frames for this single "raw frame" or call-site.
517
- *
518
- * @returns An empty array if no mapping at the call-site was found, or the resulting frames
519
- * in top-to-bottom order in case of inlining.
520
- * @throws If this range is marked "hidden". Outlining needs to be handled externally as
521
- * outlined function segments in stack traces can span across bundles.
501
+ * Translates a single "raw frame" or call-site, including outlined functions. It's the caller's responsibility to
502
+ * merge outlined frames with their caller(s) (see {@link GeneratedFrameKind}).
522
503
  */
523
- translateCallSite(generatedLine: number, generatedColumn: number): TranslatedFrame[] {
504
+ translateRawFrame(generatedLine: number, generatedColumn: number): RawFrameTranslation {
524
505
  const rangeChain = this.#findGeneratedRangeChain(generatedLine, generatedColumn);
525
- if (this.#isOutlinedFrame(rangeChain)) {
526
- throw new Error('SourceMapScopesInfo is unable to translate an outlined function by itself');
506
+ const kind = this.#generatedFrameKind(rangeChain);
507
+ if (kind === GeneratedFrameKind.HIDDEN) {
508
+ return {kind, frames: []};
527
509
  }
510
+ const frame = this.#translateTopFrame(generatedLine, generatedColumn, rangeChain);
511
+ return {kind, frames: frame ? [frame, ...this.#translateInlinedCallers(rangeChain)] : []};
512
+ }
513
+
514
+ /**
515
+ * The top-most frame is translated the same, regardless of whether we have inlined functions: The name is the
516
+ * original function surrounding the generated position, and the location is the mapped generated position.
517
+ *
518
+ * If the generated position has no mapping, the generated ranges may still tell which authored function (or
519
+ * which file, for top-level code) the position belongs to. The frame then has no position.
520
+ */
521
+ #translateTopFrame(generatedLine: number, generatedColumn: number,
522
+ rangeChain: ScopesCodec.GeneratedRange[]): TranslatedFrame|null {
523
+ const position = {line: generatedLine, column: generatedColumn};
524
+ const innerMostScope = this.#innerMostOriginalScope(rangeChain, position);
525
+ const functionScope = this.#findFunctionScopeInOriginalScopeChain(innerMostScope);
526
+ const name = functionScope ? (functionScope.name ?? '') : undefined;
528
527
 
529
528
  const mapping = this.#sourceMap.findEntry(generatedLine, generatedColumn);
530
- if (mapping?.sourceIndex === undefined) {
531
- return [];
529
+ if (mapping?.sourceIndex !== undefined) {
530
+ return {
531
+ line: mapping.sourceLineNumber,
532
+ column: mapping.sourceColumnNumber,
533
+ name,
534
+ url: mapping.sourceURL,
535
+ functionStart: functionScope?.start,
536
+ };
532
537
  }
538
+ if (!innerMostScope) {
539
+ return null;
540
+ }
541
+ return {name, url: this.#sourceURLOfScope(functionScope ?? innerMostScope), functionStart: functionScope?.start};
542
+ }
533
543
 
534
- // The top-most frame is translated the same even if we have inlined functions.
535
- const result: TranslatedFrame[] = [{
536
- line: mapping.sourceLineNumber,
537
- column: mapping.sourceColumnNumber,
538
- name: this.findOriginalFunctionName({line: generatedLine, column: generatedColumn}) ?? undefined,
539
- url: mapping.sourceURL,
540
- }];
541
-
542
- // Walk the range chain inside out until we find a generated function and for each inlined function add a frame.
544
+ /**
545
+ * Walk the range chain inside out until we find a generated function and for each inlined function add a frame.
546
+ */
547
+ #translateInlinedCallers(rangeChain: ScopesCodec.GeneratedRange[]): TranslatedFrame[] {
548
+ const result: TranslatedFrame[] = [];
543
549
  for (let i = rangeChain.length - 1; i >= 0 && !rangeChain[i].isStackFrame; --i) {
544
550
  const range = rangeChain[i];
545
551
  if (!range.callSite) {
@@ -547,29 +553,160 @@ export class SourceMapScopesInfo {
547
553
  }
548
554
 
549
555
  const originalScopeChain = this.#findOriginalScopeChain(range.callSite);
556
+ const functionScope = this.#findFunctionScopeInOriginalScopeChain(originalScopeChain.at(-1));
550
557
  result.push({
551
558
  line: range.callSite.line,
552
559
  column: range.callSite.column,
553
- name: this.#findFunctionNameInOriginalScopeChain(originalScopeChain.at(-1)) ?? undefined,
560
+ name: functionScope ? (functionScope.name ?? '') : undefined,
554
561
  url: this.#sourceMap.sourceURLForSourceIndex(range.callSite.sourceIndex),
562
+ functionStart: functionScope?.start,
555
563
  });
556
564
  }
557
565
 
558
566
  return result;
559
567
  }
568
+
569
+ /**
570
+ * @returns the body of the innermost inlined function at the position (the innermost range with a `callSite`
571
+ * within the generated function), or null if the position is not inside an inlined function.
572
+ *
573
+ * The innermost inlined function is the logical frame the position belongs to, i.e. the analogue of the top frame
574
+ * of a real call stack. Stepping over its body therefore goes up exactly one logical frame: into the caller, which
575
+ * may itself be inlined. An outer range would leave several logical frames at once.
576
+ */
577
+ inlinedFunctionRange(generatedLine: number, generatedColumn: number): PositionRange|null {
578
+ const rangeChain = this.#findGeneratedRangeChain(generatedLine, generatedColumn);
579
+ for (let i = rangeChain.length - 1; i >= 0 && !rangeChain[i].isStackFrame; --i) {
580
+ if (rangeChain[i].callSite) {
581
+ return {start: rangeChain[i].start, end: rangeChain[i].end};
582
+ }
583
+ }
584
+ return null;
585
+ }
586
+
587
+ /**
588
+ * @returns the bodies of all functions that were inlined directly into the logical function at the position
589
+ * (the innermost inlined function, or else the generated function). Doesn't descend into inlined
590
+ * functions or nested generated functions.
591
+ */
592
+ inlinedCalleeRanges(generatedLine: number, generatedColumn: number): PositionRange[] {
593
+ const rangeChain = this.#findGeneratedRangeChain(generatedLine, generatedColumn);
594
+ let body: ScopesCodec.GeneratedRange|undefined;
595
+ for (let i = rangeChain.length - 1; i >= 0 && !body; --i) {
596
+ if (rangeChain[i].callSite || rangeChain[i].isStackFrame) {
597
+ body = rangeChain[i];
598
+ }
599
+ }
600
+ const result: PositionRange[] = [];
601
+ (function walk(range: ScopesCodec.GeneratedRange) {
602
+ for (const child of range.children) {
603
+ if (child.isStackFrame) {
604
+ continue;
605
+ }
606
+ if (child.callSite) {
607
+ result.push({start: child.start, end: child.end});
608
+ } else {
609
+ walk(child);
610
+ }
611
+ }
612
+ })(body ?? {children: this.#generatedRanges} as ScopesCodec.GeneratedRange);
613
+ return result;
614
+ }
615
+
616
+ /**
617
+ * @returns true, iff any generated function is outlined, i.e. marked as "hidden" but with a definition (see
618
+ * {@link GeneratedFrameKind.OUTLINED}). Hidden functions without a definition are compiler helpers.
619
+ */
620
+ hasOutlinedFunctions(): boolean {
621
+ const hasOutlined = (ranges: ScopesCodec.GeneratedRange[]): boolean =>
622
+ ranges.some(range => (range.isStackFrame && range.isHidden && range.originalScope !== undefined) ||
623
+ hasOutlined(range.children));
624
+ return hasOutlined(this.#generatedRanges);
625
+ }
626
+
627
+ /**
628
+ * @returns the "artificial" generated functions (in the DWARF sense): functions that contain no authored code at all
629
+ * (no original scope anywhere in their subtree), e.g. compiler helpers. Sorted by start position,
630
+ * non-overlapping.
631
+ */
632
+ artificialFunctionRanges(): PositionRange[] {
633
+ const hasOriginalScope = (range: ScopesCodec.GeneratedRange): boolean =>
634
+ range.originalScope !== undefined || range.children.some(hasOriginalScope);
635
+ const result: PositionRange[] = [];
636
+ (function walk(ranges: ScopesCodec.GeneratedRange[]) {
637
+ for (const range of ranges) {
638
+ if (range.isStackFrame && !hasOriginalScope(range)) {
639
+ result.push({start: range.start, end: range.end});
640
+ } else {
641
+ walk(range.children);
642
+ }
643
+ }
644
+ })(this.#generatedRanges);
645
+ return result;
646
+ }
647
+ }
648
+
649
+ /** A range of generated positions, relative to the start of the script. `end` is exclusive. */
650
+ export interface PositionRange {
651
+ readonly start: ScopesCodec.Position;
652
+ readonly end: ScopesCodec.Position;
653
+ }
654
+
655
+ /**
656
+ * Describes how the generated function surrounding a generated position shows up in stack traces.
657
+ *
658
+ * Compilers tend to introduce functions (and calls to them) that don't exist in the authored code. The scopes
659
+ * proposal distinguishes two cases:
660
+ *
661
+ * 1) The generated function contains authored code, e.g. a block scope that was turned into a function. The
662
+ * generated range is marked "hidden", but links to the original scope via its definition. We can pause
663
+ * in such a function, but the frame is merged with its caller(s) in stack traces: The outlined code
664
+ * logically belongs to the (authored) function that transitively calls it.
665
+ *
666
+ * 2) The generated function doesn't represent any authored code, e.g. a compiler helper. The generated range
667
+ * has no definition. The scopes spec is being updated to say that such a range can be ignored in stack
668
+ * traces, so we drop these frames.
669
+ */
670
+ export const enum GeneratedFrameKind {
671
+ /** A regular (possibly with inlined functions) generated function, or top-level code. */
672
+ VISIBLE = 'VISIBLE',
673
+ /** A generated function marked as "hidden" that has a definition (case 1). */
674
+ OUTLINED = 'OUTLINED',
675
+ /** A generated function without a definition (case 2). */
676
+ HIDDEN = 'HIDDEN',
677
+ }
678
+
679
+ /** See {@link SourceMapScopesInfo.translateRawFrame}. */
680
+ export interface RawFrameTranslation {
681
+ kind: GeneratedFrameKind;
682
+ /**
683
+ * [top, ...inlinedCallers] in top-to-bottom order. Empty for {@link GeneratedFrameKind.HIDDEN} frames, or if
684
+ * neither the mappings nor the generated ranges know anything about the generated position.
685
+ *
686
+ * For {@link GeneratedFrameKind.OUTLINED} frames, the top frame is named after the authored function the outlined
687
+ * code belongs to.
688
+ */
689
+ frames: TranslatedFrame[];
560
690
  }
561
691
 
562
692
  /**
563
693
  * Represents a stack frame in original terms. It closely aligns with StackTrace.StackTrace.Frame,
564
694
  * but since we can't import that type here we mirror it here somewhat.
565
695
  *
566
- * Equivalent to Pick<StackTrace.StackTrace.Frame, 'line'|'column'|'name'|'url'>.
696
+ * Equivalent to Pick<StackTrace.StackTrace.Frame, 'line'|'column'|'name'|'url'>, except that the position is
697
+ * optional.
567
698
  */
568
699
  export interface TranslatedFrame {
569
- line: number;
570
- column: number;
700
+ /**
701
+ * `line` and `column` are undefined iff the generated position has no mapping. The frame then only identifies
702
+ * the authored function: `name`, `functionStart` and the `url` of the function's source.
703
+ */
704
+ line?: number;
705
+ column?: number;
571
706
  name?: string;
572
707
  url?: Platform.DevToolsPath.UrlString;
708
+ /** Start of the original function scope containing this frame's position. Undefined for top-level code. */
709
+ functionStart?: ScopesCodec.Position;
573
710
  }
574
711
 
575
712
  export function findExpression(range: ScopesCodec.GeneratedRange|undefined, index: number, line = 0,
@@ -19,6 +19,23 @@ const objectUrlRegistry = new FinalizationRegistry<string>(url => {
19
19
  */
20
20
  const MAX_BLOB_SIZE_BYTES = 10 * 1024 * 1024;
21
21
 
22
+ /**
23
+ * Returns `charset` if `TextDecoder` supports it, and 'utf-8' otherwise.
24
+ * The charset usually comes straight from a `Content-Type` header, and
25
+ * `TextDecoder` has no non-throwing way to check a label.
26
+ */
27
+ function supportedCharsetOrUtf8(charset: string|undefined): string {
28
+ if (!charset) {
29
+ return 'utf-8';
30
+ }
31
+ try {
32
+ new TextDecoder(charset);
33
+ return charset;
34
+ } catch {
35
+ return 'utf-8';
36
+ }
37
+ }
38
+
22
39
  /**
23
40
  * This class is a small wrapper around either raw binary or text data.
24
41
  * As the binary data can actually contain textual data, we also store the
@@ -44,7 +61,7 @@ export class ContentData {
44
61
  #imagePreviewUrl?: string;
45
62
 
46
63
  constructor(data: string, isBase64: boolean, mimeType: string, charset?: string) {
47
- this.charset = charset || 'utf-8';
64
+ this.charset = supportedCharsetOrUtf8(charset);
48
65
  if (isBase64) {
49
66
  this.#contentAsBase64 = data;
50
67
  } else {
@@ -9,7 +9,7 @@ import {SearchMatch} from './ContentProvider.js';
9
9
  import type {Text} from './Text.js';
10
10
 
11
11
  const KEY_VALUE_FILTER_REGEXP = /(?:^|\s)(\-)?([\w\-]+):([^\s]+)/;
12
- const REGEXP_FILTER_REGEXP = /(?:^|\s)(\-)?\/([^\/\\]+(\\.[^\/]*)*)\//;
12
+ const REGEXP_FILTER_REGEXP = /(?:^|\s)(\-)?\/((?:[^\/\\]|\\.)+)\//;
13
13
  const TEXT_FILTER_REGEXP = /(?:^|\s)(\-)?([^\s]+)/;
14
14
  const SPACE_CHAR_REGEXP = /\s/;
15
15
 
@@ -2,6 +2,7 @@
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
4
  import '../shell/shell.js';
5
+ import '../../panels/comments/comments-meta.js';
5
6
  import '../../panels/css_overview/css_overview-meta.js';
6
7
  import '../../panels/elements/elements-meta.js';
7
8
  import '../../panels/browser_debugger/browser_debugger-meta.js';
@@ -28,6 +28,11 @@ type ScopeInfoNodeIndex = Platform.Brand.Brand<number, 'ScopeInfoNodeIndex'>;
28
28
  */
29
29
  type ContextNodeIndex = Platform.Brand.Brand<number, 'ContextNodeIndex'>;
30
30
 
31
+ /**
32
+ * Index of a `system / Map` node within the heap snapshot's node array.
33
+ */
34
+ type MapNodeIndex = Platform.Brand.Brand<number, 'MapNodeIndex'>;
35
+
31
36
  /**
32
37
  * Values addressed by scope. Since a `ScopeId` is only unique within its
33
38
  * script, scopes are keyed by script first.
@@ -74,6 +79,8 @@ interface ScopeAccumulator {
74
79
  }
75
80
 
76
81
  interface LiveClosure {
82
+ // The JSFunction or generator object this entry was created for.
83
+ ownerNodeIndex: number;
77
84
  contextNodeIndex: ContextNodeIndex;
78
85
  scriptNodeIndex: ScriptNodeIndex;
79
86
  scopeId: ScopeId;
@@ -99,6 +106,8 @@ interface HeapScan {
99
106
  scripts: Map<ScriptNodeIndex, ScriptInfo>;
100
107
  contextNodes: ContextNodeInfo[];
101
108
  liveClosures: LiveClosure[];
109
+ // Top-level functions of modules that finished evaluating.
110
+ finishedModuleFunctionNodeIndexes: Set<number>;
102
111
  // Maps a ScopeInfo to the script it belongs to. A stored `undefined` is a memoized
103
112
  // failed lookup. It is therefore not the same as an absent entry, which merely means
104
113
  // "not resolved yet".
@@ -114,7 +123,7 @@ export function analyzeContexts(snapshot: HeapSnapshot): HeapSnapshotModel.HeapS
114
123
  const scan = scanHeap(snapshot);
115
124
 
116
125
  // (3) Group live closures by function scope and record the context chains they can reach.
117
- const liveFunctionsByScript = buildLiveFunctions(snapshot, scan.liveClosures);
126
+ const liveFunctionsByScript = buildLiveFunctions(snapshot, scan.liveClosures, scan.finishedModuleFunctionNodeIndexes);
118
127
 
119
128
  // (4) Correlate contexts and their field values with embedded scopes.
120
129
  const {scopes, scriptsWithoutScopes} = correlateContextsWithScopes(snapshot, scan, scopesByScript);
@@ -240,6 +249,9 @@ function scanHeap(snapshot: HeapSnapshot): HeapScan {
240
249
  const scripts = new Map<ScriptNodeIndex, ScriptInfo>();
241
250
  const contextNodes: ContextNodeInfo[] = [];
242
251
  const liveClosures: LiveClosure[] = [];
252
+ const finishedModuleFunctionNodeIndexes = new Set<number>();
253
+ // Caches for each map whether it belongs to a generator object. See `isGeneratorObject`.
254
+ const generatorMapNodeIndexes = new Map<MapNodeIndex, boolean>();
243
255
  const scopeInfoScriptNodeIndexes = new Map<ScopeInfoNodeIndex, ScriptNodeIndex|undefined>();
244
256
  const node = snapshot.createNode();
245
257
  const nodes = snapshot.nodes;
@@ -257,10 +269,12 @@ function scanHeap(snapshot: HeapSnapshot): HeapScan {
257
269
  processContext(contextNodes, node);
258
270
  } else if (node.rawType() === nodeClosureType) {
259
271
  processClosure(liveClosures, node);
272
+ } else if (isGeneratorObject(generatorMapNodeIndexes, node)) {
273
+ processGeneratorObject(liveClosures, finishedModuleFunctionNodeIndexes, node);
260
274
  }
261
275
  }
262
276
 
263
- return {scripts, contextNodes, liveClosures, scopeInfoScriptNodeIndexes};
277
+ return {scripts, contextNodes, liveClosures, finishedModuleFunctionNodeIndexes, scopeInfoScriptNodeIndexes};
264
278
  }
265
279
 
266
280
  function processScript(scripts: Map<ScriptNodeIndex, ScriptInfo>, node: HeapSnapshotNode): void {
@@ -426,6 +440,11 @@ function processClosure(liveClosures: LiveClosure[], node: HeapSnapshotNode): vo
426
440
  if (!sharedFunctionInfo || !closureContext) {
427
441
  return;
428
442
  }
443
+ addLiveClosure(liveClosures, node, sharedFunctionInfo, closureContext);
444
+ }
445
+
446
+ function addLiveClosure(liveClosures: LiveClosure[], ownerNode: HeapSnapshotNode, sharedFunctionInfo: HeapSnapshotNode,
447
+ contextNode: HeapSnapshotNode): void {
429
448
  const script = sharedFunctionInfo.findInternalEdgeTarget('script');
430
449
  if (!script) {
431
450
  return;
@@ -433,18 +452,97 @@ function processClosure(liveClosures: LiveClosure[], node: HeapSnapshotNode): vo
433
452
  const scopeId = sharedFunctionInfo.findInternalEdgeTarget('scope_id')?.nodeValueAsInt() as ScopeId | undefined;
434
453
  if (scopeId !== undefined) {
435
454
  liveClosures.push({
436
- contextNodeIndex: closureContext.nodeIndex as ContextNodeIndex,
455
+ ownerNodeIndex: ownerNode.nodeIndex,
456
+ contextNodeIndex: contextNode.nodeIndex as ContextNodeIndex,
437
457
  scriptNodeIndex: script.nodeIndex as ScriptNodeIndex,
438
458
  scopeId,
439
459
  });
440
460
  }
441
461
  }
442
462
 
443
- function buildLiveFunctions(snapshot: HeapSnapshot, liveClosures: LiveClosure[]): ByScope<LiveFunction> {
463
+ // V8's `JSGeneratorObject::kGeneratorClosed`: the generator has finished and can't be resumed.
464
+ const GENERATOR_CLOSED = -1;
465
+
466
+ /**
467
+ * Handles generator objects, which V8 uses for generators, async functions, async generators and
468
+ * the top-level code of modules.
469
+ */
470
+ function processGeneratorObject(liveClosures: LiveClosure[], finishedModuleFunctionNodeIndexes: Set<number>,
471
+ node: HeapSnapshotNode): void {
472
+ const continuation = node.findInternalEdgeTarget('continuation')?.nodeValueAsInt();
473
+ const generatorFunction = node.findInternalEdgeTarget('function');
474
+ if (continuation === undefined || generatorFunction?.rawType() !== node.snapshot.nodeClosureType) {
475
+ return;
476
+ }
477
+ const sharedFunctionInfo = generatorFunction.findInternalEdgeTarget('shared');
478
+ if (!sharedFunctionInfo) {
479
+ return;
480
+ }
481
+ if (continuation === GENERATOR_CLOSED) {
482
+ const scopeId = sharedFunctionInfo.findInternalEdgeTarget('scope_id')?.nodeValueAsInt();
483
+
484
+ // Only the top-level code of a module is both a generator and a root scope.
485
+ if (scopeId !== undefined && isScriptRootScopeId(scopeId)) {
486
+ // A module stores a reference to its generator object. The generator object then
487
+ // references the top-level JSFunction. Once the generator object is finished its
488
+ // JSFunction can't run anymore and user code can't call it. Here we record such closures
489
+ // in order to skip them later in buildLiveFunctions().
490
+ finishedModuleFunctionNodeIndexes.add(generatorFunction.nodeIndex);
491
+ }
492
+
493
+ // A finished generator can't run again, so it doesn't get a LiveClosure. That way the fields
494
+ // of its context can be reported as dead.
495
+ return;
496
+ }
497
+ const generatorContext = node.findInternalEdgeTarget('context');
498
+ if (generatorContext) {
499
+ // The context of the generator's function is the one the function was created in. The body
500
+ // of the generator, however, runs in a context of its own, which only the generator object
501
+ // refers to. A generator therefore counts as a closure of its function with the context
502
+ // it resumes in.
503
+ addLiveClosure(liveClosures, node, sharedFunctionInfo, generatorContext);
504
+ }
505
+ }
506
+
507
+ // V8's instance types of `JSGeneratorObject` and its subclasses.
508
+ const GENERATOR_INSTANCE_TYPE_NAMES = new Set([
509
+ 'JS_GENERATOR_OBJECT_TYPE',
510
+ 'JS_ASYNC_FUNCTION_OBJECT_TYPE',
511
+ 'JS_ASYNC_GENERATOR_OBJECT_TYPE',
512
+ ]);
513
+
514
+ /**
515
+ * Checks the instance type of the object's map, so that other objects with fields of the same name
516
+ * aren't mistaken for generator objects. The result is cached for each map.
517
+ */
518
+ function isGeneratorObject(generatorMapNodeIndexes: Map<MapNodeIndex, boolean>, node: HeapSnapshotNode): boolean {
519
+ if (node.rawType() !== node.snapshot.nodeObjectType) {
520
+ return false;
521
+ }
522
+ const map = node.findInternalEdgeTarget('map');
523
+ if (!map) {
524
+ return false;
525
+ }
526
+ const mapNodeIndex = map.nodeIndex as MapNodeIndex;
527
+ let isGenerator = generatorMapNodeIndexes.get(mapNodeIndex);
528
+ if (isGenerator === undefined) {
529
+ const instanceTypeName = map.findInternalEdgeTarget('instance_type_name')?.rawName();
530
+ isGenerator = instanceTypeName !== undefined && GENERATOR_INSTANCE_TYPE_NAMES.has(instanceTypeName);
531
+ generatorMapNodeIndexes.set(mapNodeIndex, isGenerator);
532
+ }
533
+ return isGenerator;
534
+ }
535
+
536
+ function buildLiveFunctions(snapshot: HeapSnapshot, liveClosures: LiveClosure[],
537
+ finishedModuleFunctionNodeIndexes: Set<number>): ByScope<LiveFunction> {
444
538
  const liveFunctionsByScript: ByScope<LiveFunction> = new Map();
445
539
  const node = snapshot.createNode();
446
540
 
447
541
  for (const closure of liveClosures) {
542
+ if (finishedModuleFunctionNodeIndexes.has(closure.ownerNodeIndex)) {
543
+ // The top-level code of this module has finished and can't run anymore.
544
+ continue;
545
+ }
448
546
  let scriptFunctions = liveFunctionsByScript.get(closure.scriptNodeIndex);
449
547
  if (!scriptFunctions) {
450
548
  scriptFunctions = new Map<ScopeId, LiveFunction>();