chrome-devtools-mcp 1.1.0 → 1.2.0

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 (105) hide show
  1. package/README.md +12 -3
  2. package/build/src/DevtoolsUtils.js +57 -0
  3. package/build/src/HeapSnapshotManager.js +5 -0
  4. package/build/src/McpContext.js +28 -8
  5. package/build/src/McpPage.js +9 -9
  6. package/build/src/McpResponse.js +101 -53
  7. package/build/src/PageCollector.js +7 -7
  8. package/build/src/ServiceWorkerCollector.js +171 -0
  9. package/build/src/TextSnapshot.js +1 -1
  10. package/build/src/ToolHandler.js +11 -5
  11. package/build/src/WaitForHelper.js +2 -2
  12. package/build/src/bin/chrome-devtools-cli-options.js +22 -4
  13. package/build/src/bin/chrome-devtools-mcp-cli-options.js +19 -4
  14. package/build/src/bin/chrome-devtools-mcp-main.js +5 -5
  15. package/build/src/browser.js +8 -4
  16. package/build/src/daemon/client.js +7 -7
  17. package/build/src/daemon/daemon.js +12 -12
  18. package/build/src/daemon/utils.js +2 -2
  19. package/build/src/formatters/IssueFormatter.js +4 -4
  20. package/build/src/index.js +13 -1
  21. package/build/src/telemetry/ClearcutLogger.js +1 -1
  22. package/build/src/telemetry/WatchdogClient.js +4 -4
  23. package/build/src/telemetry/persistence.js +2 -2
  24. package/build/src/telemetry/watchdog/ClearcutSender.js +10 -10
  25. package/build/src/telemetry/watchdog/main.js +5 -5
  26. package/build/src/third_party/THIRD_PARTY_NOTICES +30 -0
  27. package/build/src/third_party/bundled-packages.json +2 -1
  28. package/build/src/third_party/devtools-formatter-worker.js +1 -0
  29. package/build/src/third_party/devtools-heap-snapshot-worker.js +107 -0
  30. package/build/src/third_party/index.js +5906 -4913
  31. package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsEmptyList.md +1 -0
  32. package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsHttpNotFound.md +1 -0
  33. package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsInvalidContentType.md +1 -0
  34. package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsInvalidResponse.md +1 -0
  35. package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsNoResponse.md +1 -0
  36. package/build/src/third_party/issue-descriptions/emailVerificationRequestDnsFetchFailed.md +1 -0
  37. package/build/src/third_party/issue-descriptions/emailVerificationRequestDnsInvalidRecord.md +1 -0
  38. package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownHttpNotFound.md +1 -0
  39. package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownInvalidContentType.md +1 -0
  40. package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownInvalidResponse.md +1 -0
  41. package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownNoResponse.md +1 -0
  42. package/build/src/third_party/issue-descriptions/emailVerificationRequestInvalidEmail.md +1 -0
  43. package/build/src/third_party/issue-descriptions/emailVerificationRequestJwksHttpNotFound.md +1 -0
  44. package/build/src/third_party/issue-descriptions/emailVerificationRequestJwksInvalidResponse.md +1 -0
  45. package/build/src/third_party/issue-descriptions/emailVerificationRequestKeyBindingSigningFailed.md +1 -0
  46. package/build/src/third_party/issue-descriptions/emailVerificationRequestRpOriginIsOpaque.md +1 -0
  47. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenHttpNotFound.md +1 -0
  48. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidContentType.md +1 -0
  49. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidResponse.md +1 -0
  50. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidSdJwt.md +1 -0
  51. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenMalformedSdJwt.md +1 -0
  52. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenNoResponse.md +1 -0
  53. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidAudience.md +1 -0
  54. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidIssuedAt.md +1 -0
  55. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidNonce.md +1 -0
  56. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidSdHash.md +1 -0
  57. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidTyp.md +1 -0
  58. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingAud.md +1 -0
  59. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingCnf.md +1 -0
  60. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingIat.md +1 -0
  61. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingNonce.md +1 -0
  62. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingSdHash.md +1 -0
  63. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbSignatureFailed.md +1 -0
  64. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidEmail.md +1 -0
  65. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidEmailVerified.md +1 -0
  66. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidHolderKey.md +1 -0
  67. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidIssuedAt.md +1 -0
  68. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidIssuer.md +1 -0
  69. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtJwksMissingKeys.md +1 -0
  70. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingCnf.md +1 -0
  71. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingEmail.md +1 -0
  72. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingIat.md +1 -0
  73. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingIss.md +1 -0
  74. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtSignatureFailed.md +1 -0
  75. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtUnsupportedHeaderAlg.md +1 -0
  76. package/build/src/third_party/issue-descriptions/emailVerificationRequestUserLoggedOut.md +1 -0
  77. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownAccountsEndpointCrossOrigin.md +1 -0
  78. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownHttpNotFound.md +1 -0
  79. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownInvalidContentType.md +1 -0
  80. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownInvalidResponse.md +1 -0
  81. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownIssuanceEndpointCrossOrigin.md +1 -0
  82. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownListEmpty.md +1 -0
  83. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownMissingAccountsEndpoint.md +1 -0
  84. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownMissingIssuanceEndpoint.md +1 -0
  85. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownNoResponse.md +1 -0
  86. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownUnsupportedSigningAlgorithm.md +1 -0
  87. package/build/src/tools/console.js +7 -0
  88. package/build/src/tools/emulation.js +1 -0
  89. package/build/src/tools/extensions.js +5 -2
  90. package/build/src/tools/input.js +11 -3
  91. package/build/src/tools/lighthouse.js +11 -6
  92. package/build/src/tools/memory.js +33 -10
  93. package/build/src/tools/network.js +2 -2
  94. package/build/src/tools/pages.js +13 -5
  95. package/build/src/tools/performance.js +8 -7
  96. package/build/src/tools/screencast.js +2 -1
  97. package/build/src/tools/screenshot.js +1 -1
  98. package/build/src/tools/script.js +2 -2
  99. package/build/src/tools/slim/tools.js +3 -0
  100. package/build/src/tools/snapshot.js +3 -2
  101. package/build/src/tools/thirdPartyDeveloper.js +12 -2
  102. package/build/src/tools/webmcp.js +2 -0
  103. package/build/src/trace-processing/parse.js +5 -5
  104. package/build/src/version.js +1 -1
  105. package/package.json +14 -13
@@ -43,7 +43,7 @@ export async function createMcpServer(serverArgs, options) {
43
43
  context?.setRoots(roots.roots);
44
44
  }
45
45
  catch (e) {
46
- logger('Failed to list roots', e);
46
+ logger?.('Failed to list roots', e);
47
47
  }
48
48
  };
49
49
  server.server.oninitialized = () => {
@@ -66,6 +66,12 @@ export async function createMcpServer(serverArgs, options) {
66
66
  chromeArgs.push(`--proxy-server=${serverArgs.proxyServer}`);
67
67
  }
68
68
  const devtools = serverArgs.experimentalDevtools ?? false;
69
+ const blocklist = serverArgs.blockedUrlPattern
70
+ ? serverArgs.blockedUrlPattern.map(String)
71
+ : undefined;
72
+ const allowlist = serverArgs.allowedUrlPattern
73
+ ? serverArgs.allowedUrlPattern.map(String)
74
+ : undefined;
69
75
  const browser = serverArgs.browserUrl || serverArgs.wsEndpoint || serverArgs.autoConnect
70
76
  ? await ensureBrowserConnected({
71
77
  browserURL: serverArgs.browserUrl,
@@ -77,6 +83,8 @@ export async function createMcpServer(serverArgs, options) {
77
83
  : undefined,
78
84
  userDataDir: serverArgs.userDataDir,
79
85
  devtools,
86
+ blocklist,
87
+ allowlist,
80
88
  })
81
89
  : await ensureBrowserLaunched({
82
90
  headless: serverArgs.headless,
@@ -92,12 +100,16 @@ export async function createMcpServer(serverArgs, options) {
92
100
  devtools,
93
101
  enableExtensions: serverArgs.categoryExtensions,
94
102
  viaCli: serverArgs.viaCli,
103
+ blocklist,
104
+ allowlist,
95
105
  });
96
106
  if (context?.browser !== browser) {
97
107
  context = await McpContext.from(browser, logger, {
98
108
  experimentalDevToolsDebugging: devtools,
99
109
  experimentalIncludeAllPages: serverArgs.experimentalIncludeAllPages,
100
110
  performanceCrux: serverArgs.performanceCrux,
111
+ hasNetworkBlockOrAllowlist: Boolean((blocklist && blocklist.length > 0) ||
112
+ (allowlist && allowlist.length > 0)),
101
113
  });
102
114
  await updateRoots();
103
115
  }
@@ -136,7 +136,7 @@ export class ClearcutLogger {
136
136
  }
137
137
  }
138
138
  catch (err) {
139
- logger('Error in logDailyActiveIfNeeded:', err);
139
+ logger?.('Error in logDailyActiveIfNeeded:', err);
140
140
  }
141
141
  }
142
142
  async logServerError(args) {
@@ -35,10 +35,10 @@ export class WatchdogClient {
35
35
  });
36
36
  this.#childProcess.unref();
37
37
  this.#childProcess.on('error', err => {
38
- logger('Watchdog process error:', err);
38
+ logger?.('Watchdog process error:', err);
39
39
  });
40
40
  this.#childProcess.on('exit', (code, signal) => {
41
- logger(`Watchdog exited with code ${code} and signal ${signal}`);
41
+ logger?.(`Watchdog exited with code ${code} and signal ${signal}`);
42
42
  });
43
43
  }
44
44
  send(message) {
@@ -50,11 +50,11 @@ export class WatchdogClient {
50
50
  this.#childProcess.stdin.write(line);
51
51
  }
52
52
  catch (err) {
53
- logger('Failed to write to watchdog stdin', err);
53
+ logger?.('Failed to write to watchdog stdin', err);
54
54
  }
55
55
  }
56
56
  else {
57
- logger('Watchdog stdin not available, dropping message');
57
+ logger?.('Watchdog stdin not available, dropping message');
58
58
  }
59
59
  }
60
60
  }
@@ -45,7 +45,7 @@ export class FilePersistence {
45
45
  return JSON.parse(content);
46
46
  }
47
47
  catch (error) {
48
- logger(`Failed to read telemetry state from ${filePath}:`, error);
48
+ logger?.(`Failed to read telemetry state from ${filePath}:`, error);
49
49
  void ClearcutLogger.get()?.logServerError({
50
50
  errorCode: ErrorCode.ERROR_CODE_PERSISTENCE_FILE_READ_FAILED,
51
51
  });
@@ -62,7 +62,7 @@ export class FilePersistence {
62
62
  }
63
63
  catch (error) {
64
64
  // Ignore errors during state saving to avoid crashing the server
65
- logger(`Failed to save telemetry state to ${filePath}:`, error);
65
+ logger?.(`Failed to save telemetry state to ${filePath}:`, error);
66
66
  void ClearcutLogger.get()?.logServerError({
67
67
  errorCode: ErrorCode.ERROR_CODE_PERSISTENCE_FILE_SAVE_FAILED,
68
68
  });
@@ -48,7 +48,7 @@ export class ClearcutSender {
48
48
  app_version: this.#appVersion,
49
49
  os_type: this.#osType,
50
50
  };
51
- logger('Enqueing telemetry event', JSON.stringify(eventToSend, null, 2));
51
+ logger?.('Enqueing telemetry event', JSON.stringify(eventToSend, null, 2));
52
52
  this.#addToBuffer(eventToSend);
53
53
  if (!this.#timerStarted) {
54
54
  this.#timerStarted = true;
@@ -69,10 +69,10 @@ export class ClearcutSender {
69
69
  this.#finalFlush(),
70
70
  new Promise(resolve => setTimeout(resolve, SHUTDOWN_TIMEOUT_MS)),
71
71
  ]);
72
- logger('Final flush completed');
72
+ logger?.('Final flush completed');
73
73
  }
74
74
  catch (error) {
75
- logger('Final flush failed:', error);
75
+ logger?.('Final flush failed:', error);
76
76
  }
77
77
  }
78
78
  async #flush() {
@@ -97,7 +97,7 @@ export class ClearcutSender {
97
97
  }
98
98
  }
99
99
  else if (result.isPermanentError) {
100
- logger('Permanent error, dropped batch of', eventsToSend.length, 'events');
100
+ logger?.('Permanent error, dropped batch of', eventsToSend.length, 'events');
101
101
  }
102
102
  else {
103
103
  // Transient error: Requeue events at the front of the buffer
@@ -108,7 +108,7 @@ export class ClearcutSender {
108
108
  catch (error) {
109
109
  // Safety catch for unexpected errors, requeue events
110
110
  this.#buffer = [...eventsToSend, ...this.#buffer];
111
- logger('Flush failed unexpectedly:', error);
111
+ logger?.('Flush failed unexpectedly:', error);
112
112
  }
113
113
  finally {
114
114
  this.#isFlushing = false;
@@ -118,7 +118,7 @@ export class ClearcutSender {
118
118
  #addToBuffer(event) {
119
119
  if (this.#buffer.length >= MAX_BUFFER_SIZE) {
120
120
  this.#buffer.shift();
121
- logger('Telemetry buffer overflow: dropped oldest event');
121
+ logger?.('Telemetry buffer overflow: dropped oldest event');
122
122
  }
123
123
  this.#buffer.push({
124
124
  event,
@@ -126,18 +126,18 @@ export class ClearcutSender {
126
126
  });
127
127
  }
128
128
  #scheduleFlush(delayMs) {
129
- logger(`Scheduling flush in ${delayMs}`);
129
+ logger?.(`Scheduling flush in ${delayMs}`);
130
130
  if (this.#flushTimer) {
131
131
  clearTimeout(this.#flushTimer);
132
132
  }
133
133
  this.#flushTimer = setTimeout(() => {
134
134
  this.#flush().catch(err => {
135
- logger('Flush error:', err);
135
+ logger?.('Flush error:', err);
136
136
  });
137
137
  }, delayMs);
138
138
  }
139
139
  async #sendBatch(events) {
140
- logger(`Sending batch of ${events.length}`);
140
+ logger?.(`Sending batch of ${events.length}`);
141
141
  const requestBody = {
142
142
  log_source: LOG_SOURCE,
143
143
  request_time_ms: Date.now().toString(),
@@ -176,7 +176,7 @@ export class ClearcutSender {
176
176
  if (status >= 500 || status === 429) {
177
177
  return { success: false };
178
178
  }
179
- logger('Telemetry permanent error:', status);
179
+ logger?.('Telemetry permanent error:', status);
180
180
  return { success: false, isPermanentError: true };
181
181
  }
182
182
  catch {
@@ -65,7 +65,7 @@ function main() {
65
65
  process.exit(code);
66
66
  });
67
67
  };
68
- logger('Watchdog started', JSON.stringify({
68
+ logger?.('Watchdog started', JSON.stringify({
69
69
  pid: process.pid,
70
70
  parentPid,
71
71
  version: appVersion,
@@ -84,15 +84,15 @@ function main() {
84
84
  return;
85
85
  }
86
86
  isShuttingDown = true;
87
- logger(`Parent death detected (${reason}). Sending shutdown event...`);
87
+ logger?.(`Parent death detected (${reason}). Sending shutdown event...`);
88
88
  sender
89
89
  .sendShutdownEvent()
90
90
  .then(() => {
91
- logger('Shutdown event sent. Exiting.');
91
+ logger?.('Shutdown event sent. Exiting.');
92
92
  exit(0);
93
93
  })
94
94
  .catch(err => {
95
- logger('Failed to send shutdown event', err);
95
+ logger?.('Failed to send shutdown event', err);
96
96
  exit(1);
97
97
  });
98
98
  }
@@ -114,7 +114,7 @@ function main() {
114
114
  }
115
115
  }
116
116
  catch (err) {
117
- logger('Failed to parse IPC message', err);
117
+ logger?.('Failed to parse IPC message', err);
118
118
  }
119
119
  });
120
120
  }
@@ -906,6 +906,36 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
906
906
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
907
907
 
908
908
 
909
+ -------------------- DEPENDENCY DIVIDER --------------------
910
+
911
+ Name: @toon-format/toon
912
+ URL: https://toonformat.dev
913
+ Version: 2.2.0
914
+ License: MIT
915
+
916
+ MIT License
917
+
918
+ Copyright (c) 2025-PRESENT Johann Schopplich
919
+
920
+ Permission is hereby granted, free of charge, to any person obtaining a copy
921
+ of this software and associated documentation files (the "Software"), to deal
922
+ in the Software without restriction, including without limitation the rights
923
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
924
+ copies of the Software, and to permit persons to whom the Software is
925
+ furnished to do so, subject to the following conditions:
926
+
927
+ The above copyright notice and this permission notice shall be included in all
928
+ copies or substantial portions of the Software.
929
+
930
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
931
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
932
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
933
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
934
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
935
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
936
+ SOFTWARE.
937
+
938
+
909
939
  -------------------- DEPENDENCY DIVIDER --------------------
910
940
 
911
941
  Name: chrome-devtools-frontend
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "@modelcontextprotocol/sdk": "1.29.0",
3
- "chrome-devtools-frontend": "1.0.1632065",
3
+ "@toon-format/toon": "^2.2.0",
4
+ "chrome-devtools-frontend": "1.0.1641723",
4
5
  "core-js": "3.49.0",
5
6
  "debug": "4.4.3",
6
7
  "lighthouse": "13.3.0",
@@ -2648,6 +2648,7 @@ var ExperimentName;
2648
2648
  ExperimentName["USE_SOURCE_MAP_SCOPES"] = "use-source-map-scopes";
2649
2649
  ExperimentName["DURABLE_MESSAGES"] = "durable-messages";
2650
2650
  ExperimentName["JPEG_XL"] = "jpeg-xl";
2651
+ ExperimentName["PLUS_BUTTON"] = "plus-button";
2651
2652
  })(ExperimentName || (ExperimentName = {}));
2652
2653
 
2653
2654
  // Copyright 2016 The Chromium Authors
@@ -2843,6 +2843,7 @@ var ExperimentName;
2843
2843
  ExperimentName["USE_SOURCE_MAP_SCOPES"] = "use-source-map-scopes";
2844
2844
  ExperimentName["DURABLE_MESSAGES"] = "durable-messages";
2845
2845
  ExperimentName["JPEG_XL"] = "jpeg-xl";
2846
+ ExperimentName["PLUS_BUTTON"] = "plus-button";
2846
2847
  })(ExperimentName || (ExperimentName = {}));
2847
2848
 
2848
2849
  // Copyright 2021 The Chromium Authors
@@ -5992,6 +5993,9 @@ class HeapSnapshotProxy extends HeapSnapshotProxyObject {
5992
5993
  ignoreNodeInRetainersView(nodeIndex) {
5993
5994
  return this.callMethodPromise('ignoreNodeInRetainersView', nodeIndex);
5994
5995
  }
5996
+ getRetainingPaths(nodeIndex, maxDepth, maxNodes, maxSiblings) {
5997
+ return this.callMethodPromise('getRetainingPaths', nodeIndex, maxDepth, maxNodes, maxSiblings);
5998
+ }
5995
5999
  unignoreNodeInRetainersView(nodeIndex) {
5996
6000
  return this.callMethodPromise('unignoreNodeInRetainersView', nodeIndex);
5997
6001
  }
@@ -8359,6 +8363,109 @@ class HeapSnapshot {
8359
8363
  const indexProvider = new HeapSnapshotRetainerEdgeIndexProvider(this);
8360
8364
  return new HeapSnapshotEdgesProvider(this, filter, node.retainers(), indexProvider);
8361
8365
  }
8366
+ getRetainingPaths(nodeIndex, maxDepth = 30, maxNodes = 5000, maxSiblings = 100) {
8367
+ const { nodeFieldCount, firstRetainerIndex, retainingNodes, retainingEdges, edgeTypeOffset, edgeWeakType, containmentEdges } = this;
8368
+ const distances = this.#nodeDistancesForRetainersView ?? this.nodeDistances;
8369
+ let traversedNodesCount = 0;
8370
+ const visiting = new Set();
8371
+ const visited = new Map();
8372
+ const rootDistance = 2;
8373
+ const limitsReached = {};
8374
+ const buildForest = (currentIndex, currentDepth) => {
8375
+ traversedNodesCount++;
8376
+ if (traversedNodesCount > maxNodes) {
8377
+ limitsReached.nodes = true;
8378
+ return [];
8379
+ }
8380
+ if (currentDepth >= maxDepth) {
8381
+ limitsReached.depth = true;
8382
+ return [];
8383
+ }
8384
+ const ordinal = currentIndex / nodeFieldCount;
8385
+ const currentDistance = distances[ordinal];
8386
+ if (currentDistance <= rootDistance) {
8387
+ return [];
8388
+ }
8389
+ if (visiting.has(currentIndex)) {
8390
+ return [];
8391
+ }
8392
+ const cachedDepth = visited.get(currentIndex);
8393
+ if (cachedDepth !== undefined) {
8394
+ if (currentDepth >= cachedDepth) {
8395
+ return [];
8396
+ }
8397
+ }
8398
+ visiting.add(currentIndex);
8399
+ const beginRetainerIndex = firstRetainerIndex[ordinal];
8400
+ const endRetainerIndex = firstRetainerIndex[ordinal + 1];
8401
+ const retainers = [];
8402
+ for (let retainerIndex = beginRetainerIndex; retainerIndex < endRetainerIndex; ++retainerIndex) {
8403
+ const retainerNodeIndex = retainingNodes[retainerIndex];
8404
+ const retainerNodeOrdinal = retainerNodeIndex / nodeFieldCount;
8405
+ const dist = distances[retainerNodeOrdinal];
8406
+ const globalEdgeIndex = retainingEdges[retainerIndex];
8407
+ if (this.isEdgeIgnoredInRetainersView(globalEdgeIndex)) {
8408
+ continue;
8409
+ }
8410
+ const edgeType = containmentEdges.getValue(globalEdgeIndex + edgeTypeOffset);
8411
+ if (edgeType === edgeWeakType) {
8412
+ continue;
8413
+ }
8414
+ if (dist >= 0) {
8415
+ const remainingDepth = maxDepth - currentDepth;
8416
+ const neededDepth = dist - rootDistance;
8417
+ if (neededDepth < remainingDepth) {
8418
+ retainers.push({ retainerIndex, dist, nodeIndex: retainerNodeIndex });
8419
+ }
8420
+ else {
8421
+ limitsReached.depth = true;
8422
+ }
8423
+ }
8424
+ }
8425
+ retainers.sort((a, b) => a.dist - b.dist);
8426
+ const length = Math.min(retainers.length, maxSiblings);
8427
+ if (retainers.length > maxSiblings) {
8428
+ limitsReached.siblings = true;
8429
+ }
8430
+ const forest = [];
8431
+ for (let i = 0; i < length; i++) {
8432
+ const retainer = retainers[i];
8433
+ const edge = this.createRetainingEdge(retainer.retainerIndex);
8434
+ const globalEdgeIndex = retainingEdges[retainer.retainerIndex];
8435
+ const isRoot = retainer.dist === rootDistance;
8436
+ let children = [];
8437
+ if (isRoot) {
8438
+ traversedNodesCount++;
8439
+ if (traversedNodesCount > maxNodes) {
8440
+ limitsReached.nodes = true;
8441
+ break;
8442
+ }
8443
+ }
8444
+ else {
8445
+ children = buildForest(retainer.nodeIndex, currentDepth + 1);
8446
+ if (children.length === 0) {
8447
+ continue;
8448
+ }
8449
+ }
8450
+ const retainerNode = this.createNode(retainer.nodeIndex);
8451
+ forest.push({
8452
+ edgeIndex: globalEdgeIndex,
8453
+ edgeName: edge.name(),
8454
+ edgeType: edge.type(),
8455
+ nodeId: retainerNode.id(),
8456
+ nodeIndex: retainer.nodeIndex,
8457
+ nodeName: retainerNode.name(),
8458
+ distance: retainer.dist,
8459
+ children,
8460
+ });
8461
+ }
8462
+ visiting.delete(currentIndex);
8463
+ visited.set(currentIndex, currentDepth);
8464
+ return forest;
8465
+ };
8466
+ const paths = buildForest(nodeIndex, 0);
8467
+ return { paths, limitsReached };
8468
+ }
8362
8469
  createAddedNodesProvider(baseSnapshotId, classKey) {
8363
8470
  const snapshotDiff = this.#snapshotDiffs[baseSnapshotId];
8364
8471
  const diffForClass = snapshotDiff[classKey];