brave-mcp 1.3.0 → 1.6.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 (73) hide show
  1. package/README.md +57 -5
  2. package/build/src/HeapSnapshotManager.js +83 -16
  3. package/build/src/McpContext.js +218 -367
  4. package/build/src/McpPage.js +242 -31
  5. package/build/src/McpResponse.js +165 -60
  6. package/build/src/PageCollector.js +48 -122
  7. package/build/src/TextSnapshot.js +20 -2
  8. package/build/src/ToolHandler.js +13 -3
  9. package/build/src/WaitForHelper.js +18 -8
  10. package/build/src/bin/brave-devtools-cli-options.js +76 -4
  11. package/build/src/bin/brave-devtools-mcp-cli-options.js +17 -8
  12. package/build/src/bin/brave-devtools-mcp-main.js +2 -2
  13. package/build/src/bin/brave-devtools.js +25 -1
  14. package/build/src/bin/check-latest-version.js +1 -0
  15. package/build/src/browser.js +1 -1
  16. package/build/src/daemon/client.js +33 -3
  17. package/build/src/daemon/daemon.js +3 -2
  18. package/build/src/daemon/utils.js +1 -1
  19. package/build/src/devtools/DevtoolsUtils.js +2 -63
  20. package/build/src/{issue-descriptions.js → devtools/issueDescriptions.js} +8 -6
  21. package/build/src/formatters/HeapSnapshotFormatter.js +43 -0
  22. package/build/src/formatters/IssueFormatter.js +2 -2
  23. package/build/src/formatters/NetworkFormatter.js +3 -1
  24. package/build/src/index.js +14 -5
  25. package/build/src/telemetry/ClearcutLogger.js +1 -1
  26. package/build/src/telemetry/WatchdogClient.js +1 -1
  27. package/build/src/telemetry/metricsRegistry.js +2 -9
  28. package/build/src/telemetry/persistence.js +1 -1
  29. package/build/src/telemetry/transformation.js +20 -0
  30. package/build/src/telemetry/watchdog/ClearcutSender.js +1 -1
  31. package/build/src/telemetry/watchdog/main.js +1 -1
  32. package/build/src/third_party/THIRD_PARTY_NOTICES +14 -280
  33. package/build/src/third_party/bundled-packages.json +4 -6
  34. package/build/src/third_party/devtools-heap-snapshot-worker.js +94 -0
  35. package/build/src/third_party/index.js +3918 -2405
  36. package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +3082 -6343
  37. package/build/src/tools/emulation.js +2 -2
  38. package/build/src/tools/extensions.js +1 -1
  39. package/build/src/tools/input.js +17 -8
  40. package/build/src/tools/lighthouse.js +1 -1
  41. package/build/src/tools/memory.js +76 -6
  42. package/build/src/tools/network.js +4 -4
  43. package/build/src/tools/pages.js +21 -89
  44. package/build/src/tools/performance.js +2 -2
  45. package/build/src/tools/screencast.js +1 -2
  46. package/build/src/tools/script.js +6 -10
  47. package/build/src/tools/snapshot.js +2 -2
  48. package/build/src/tools/thirdPartyDeveloper.js +7 -7
  49. package/build/src/tools/webmcp.js +1 -1
  50. package/build/src/trace-processing/parse.js +1 -1
  51. package/build/src/utils/check-for-updates.js +1 -0
  52. package/build/src/utils/files.js +0 -4
  53. package/build/src/{logger.js → utils/logger.js} +1 -1
  54. package/build/src/{polyfill.js → utils/polyfill.js} +1 -1
  55. package/build/src/version.js +1 -1
  56. package/package.json +21 -8
  57. package/skills/a11y-debugging/SKILL.md +89 -0
  58. package/skills/a11y-debugging/references/a11y-snippets.md +92 -0
  59. package/skills/chrome-devtools/SKILL.md +72 -0
  60. package/skills/chrome-devtools-cli/SKILL.md +153 -0
  61. package/skills/chrome-devtools-cli/references/installation.md +14 -0
  62. package/skills/debug-optimize-lcp/SKILL.md +121 -0
  63. package/skills/debug-optimize-lcp/references/elements-and-size.md +27 -0
  64. package/skills/debug-optimize-lcp/references/lcp-breakdown.md +23 -0
  65. package/skills/debug-optimize-lcp/references/lcp-snippets.md +79 -0
  66. package/skills/debug-optimize-lcp/references/optimization-strategies.md +38 -0
  67. package/skills/memory-leak-debugging/SKILL.md +58 -0
  68. package/skills/memory-leak-debugging/references/common-leaks.md +33 -0
  69. package/skills/memory-leak-debugging/references/compare_snapshots.js +109 -0
  70. package/skills/memory-leak-debugging/references/memlab.md +29 -0
  71. package/skills/troubleshooting/SKILL.md +98 -0
  72. package/build/src/utils/types.js +0 -7
  73. /package/build/src/{Mutex.js → utils/Mutex.js} +0 -0
package/README.md CHANGED
@@ -239,9 +239,9 @@ To use the Chrome DevTools MCP server follow the instructions from <a href="http
239
239
  "chrome-devtools": {
240
240
  "command": "npx",
241
241
  "args": [
242
+ "-y",
242
243
  "chrome-devtools-mcp@latest",
243
- "--browser-url=http://127.0.0.1:9222",
244
- "-y"
244
+ "--browser-url=http://127.0.0.1:9222"
245
245
  ]
246
246
  }
247
247
  }
@@ -451,6 +451,16 @@ Alternatively, follow the <a href="https://github.com/google-gemini/gemini-cli/b
451
451
  using the standard config from above.
452
452
  </details>
453
453
 
454
+ <details>
455
+ <summary>Grok Build CLI</summary>
456
+
457
+ ```bash
458
+ grok mcp add chrome-devtools npx chrome-devtools-mcp@latest
459
+ ```
460
+
461
+ See the <a href="https://docs.x.ai/build/features/skills-plugins-marketplaces">docs</a> for more options
462
+ </details>
463
+
454
464
  <details>
455
465
  <summary>JetBrains AI Assistant & Junie</summary>
456
466
 
@@ -632,12 +642,14 @@ If you run into any issues, checkout our [troubleshooting guide](./docs/troubles
632
642
  - [`take_snapshot`](docs/tool-reference.md#take_snapshot)
633
643
  - [`screencast_start`](docs/tool-reference.md#screencast_start)
634
644
  - [`screencast_stop`](docs/tool-reference.md#screencast_stop)
635
- - **Memory** (9 tools)
645
+ - **Memory** (11 tools)
636
646
  - [`take_heapsnapshot`](docs/tool-reference.md#take_heapsnapshot)
637
647
  - [`close_heapsnapshot`](docs/tool-reference.md#close_heapsnapshot)
648
+ - [`compare_heapsnapshots`](docs/tool-reference.md#compare_heapsnapshots)
638
649
  - [`get_heapsnapshot_class_nodes`](docs/tool-reference.md#get_heapsnapshot_class_nodes)
639
650
  - [`get_heapsnapshot_details`](docs/tool-reference.md#get_heapsnapshot_details)
640
651
  - [`get_heapsnapshot_dominators`](docs/tool-reference.md#get_heapsnapshot_dominators)
652
+ - [`get_heapsnapshot_duplicate_strings`](docs/tool-reference.md#get_heapsnapshot_duplicate_strings)
641
653
  - [`get_heapsnapshot_edges`](docs/tool-reference.md#get_heapsnapshot_edges)
642
654
  - [`get_heapsnapshot_retainers`](docs/tool-reference.md#get_heapsnapshot_retainers)
643
655
  - [`get_heapsnapshot_retaining_paths`](docs/tool-reference.md#get_heapsnapshot_retaining_paths)
@@ -671,14 +683,17 @@ The Brave DevTools MCP server supports the following configuration options:
671
683
  - **`--browserUrl`/ `--browser-url`, `-u`**
672
684
  Connect to a running, debuggable Brave instance (e.g. `http://127.0.0.1:9222`).
673
685
  - **Type:** string
686
+ - **Default:** `false`
674
687
 
675
688
  - **`--wsEndpoint`/ `--ws-endpoint`, `-w`**
676
689
  WebSocket endpoint to connect to a running Brave instance (e.g., ws://127.0.0.1:9222/devtools/browser/<id>). Alternative to --browserUrl.
677
690
  - **Type:** string
691
+ - **Default:** `false`
678
692
 
679
693
  - **`--wsHeaders`/ `--ws-headers`**
680
694
  Custom headers for WebSocket connection in JSON format (e.g., '{"Authorization":"Bearer token"}'). Only works with --wsEndpoint.
681
695
  - **Type:** string
696
+ - **Default:** `false`
682
697
 
683
698
  - **`--headless`**
684
699
  Whether to run in headless (no UI) mode.
@@ -688,87 +703,108 @@ The Brave DevTools MCP server supports the following configuration options:
688
703
  - **`--executablePath`/ `--executable-path`, `-e`**
689
704
  Path to custom Brave executable. Can also be set via BRAVE_PATH environment variable.
690
705
  - **Type:** string
706
+ - **Default:** `false`
691
707
 
692
708
  - **`--isolated`**
693
709
  If specified, creates a temporary user-data-dir that is automatically cleaned up after the browser is closed. Defaults to false.
694
710
  - **Type:** boolean
711
+ - **Default:** `false`
695
712
 
696
713
  - **`--userDataDir`/ `--user-data-dir`**
697
714
  Path to the user data directory for Brave. Default is $HOME/.cache/brave-devtools-mcp/brave-profile$CHANNEL_SUFFIX_IF_NON_RELEASE
698
715
  - **Type:** string
716
+ - **Default:** `false`
699
717
 
700
718
  - **`--channel`**
701
719
  Specify a different Brave channel that should be used. The default is the release channel.
702
720
  - **Type:** string
703
721
  - **Choices:** `release`, `beta`, `nightly`, `dev`
722
+ - **Default:** `false`
704
723
 
705
724
  - **`--logFile`/ `--log-file`**
706
725
  Path to a file to write debug logs to. Set the env variable `DEBUG` to `*` to enable verbose logs. Useful for submitting bug reports.
707
726
  - **Type:** string
727
+ - **Default:** `false`
708
728
 
709
729
  - **`--viewport`**
710
730
  Initial viewport size for the Brave instances started by the server. For example, `1280x720`. In headless mode, max size is 3840x2160px.
711
731
  - **Type:** string
732
+ - **Default:** `false`
712
733
 
713
734
  - **`--proxyServer`/ `--proxy-server`**
714
735
  Proxy server configuration for Brave passed as --proxy-server when launching the browser. See https://www.chromium.org/developers/design-documents/network-settings/ for details.
715
736
  - **Type:** string
737
+ - **Default:** `false`
716
738
 
717
739
  - **`--acceptInsecureCerts`/ `--accept-insecure-certs`**
718
740
  If enabled, ignores errors relative to self-signed and expired certificates. Use with caution.
719
741
  - **Type:** boolean
742
+ - **Default:** `false`
720
743
 
721
744
  - **`--experimentalPageIdRouting`/ `--experimental-page-id-routing`**
722
745
  Whether to expose pageId on page-scoped tools and route requests by page ID (useful for concurrent agent sessions).
723
746
  - **Type:** boolean
747
+ - **Default:** `false`
724
748
 
725
749
  - **`--experimentalDevtools`/ `--experimental-devtools`**
726
750
  Whether to enable automation over DevTools targets
727
751
  - **Type:** boolean
752
+ - **Default:** `false`
728
753
 
729
754
  - **`--experimentalVision`/ `--experimental-vision`**
730
755
  Whether to enable coordinate-based tools such as click_at(x,y). Usually requires a computer-use model able to produce accurate coordinates by looking at screenshots.
731
756
  - **Type:** boolean
757
+ - **Default:** `false`
732
758
 
733
759
  - **`--memoryDebugging`/ `--memory-debugging`, `-experimentalMemory`**
734
760
  Whether to enable memory debugging tools.
735
761
  - **Type:** boolean
762
+ - **Default:** `false`
736
763
 
737
764
  - **`--experimentalStructuredContent`/ `--experimental-structured-content`**
738
765
  Whether to output structured formatted content.
739
766
  - **Type:** boolean
767
+ - **Default:** `false`
740
768
 
741
769
  - **`--experimentalIncludeAllPages`/ `--experimental-include-all-pages`**
742
770
  Whether to include all kinds of pages such as webviews or background pages as pages.
743
771
  - **Type:** boolean
772
+ - **Default:** `false`
744
773
 
745
774
  - **`--experimentalScreencast`/ `--experimental-screencast`**
746
775
  Exposes experimental screencast tools (requires ffmpeg). Install ffmpeg https://www.ffmpeg.org/download.html and ensure it is available in the MCP server PATH.
747
776
  - **Type:** boolean
777
+ - **Default:** `false`
748
778
 
749
779
  - **`--experimentalFfmpegPath`/ `--experimental-ffmpeg-path`**
750
780
  Path to ffmpeg executable for screencast recording.
751
781
  - **Type:** string
782
+ - **Default:** `false`
752
783
 
753
784
  - **`--categoryExperimentalWebmcp`/ `--category-experimental-webmcp`**
754
785
  Set to true to enable debugging WebMCP tools. Requires Brave with the following flags: `--enable-features=WebMCP,DevToolsWebMCPSupport`
755
786
  - **Type:** boolean
787
+ - **Default:** `false`
756
788
 
757
789
  - **`--braveArg`/ `--brave-arg`**
758
790
  Additional arguments for Brave. Only applies when Brave is launched by brave-devtools-mcp.
759
791
  - **Type:** array
792
+ - **Default:** `false`
760
793
 
761
794
  - **`--blockedUrlPattern`/ `--blocked-url-pattern`**
762
- Restricts network access by blocking specified URL patterns (uses https://urlpattern.spec.whatwg.org/). Silently detaches from targets with blocked URLs upon connection, and blocks runtime requests (including navigations and subresources). Accepts an array of patterns.
795
+ Restricts browser's network access by blocking specified URL patterns (uses https://urlpattern.spec.whatwg.org/). Silently detaches from targets with blocked URLs upon connection, and blocks runtime requests (including navigations and subresources). Accepts an array of patterns.
763
796
  - **Type:** array
797
+ - **Default:** `false`
764
798
 
765
799
  - **`--allowedUrlPattern`/ `--allowed-url-pattern`**
766
- Restricts network access by allowing only specified URL patterns (uses https://urlpattern.spec.whatwg.org/). Requires Chrome 149+. Silently detaches from targets with unallowed URLs upon connection, and blocks runtime requests (including navigations and subresources). Accepts an array of patterns.
800
+ Restricts browser's network access by allowing only specified URL patterns (uses https://urlpattern.spec.whatwg.org/). Requires Chrome 149+. Silently detaches from targets with unallowed URLs upon connection, and blocks runtime requests (including navigations and subresources). Accepts an array of patterns.
767
801
  - **Type:** array
802
+ - **Default:** `false`
768
803
 
769
804
  - **`--ignoreDefaultBraveArg`/ `--ignore-default-brave-arg`**
770
805
  Explicitly disable default arguments for Brave. Only applies when Brave is launched by brave-devtools-mcp.
771
806
  - **Type:** array
807
+ - **Default:** `false`
772
808
 
773
809
  - **`--categoryEmulation`/ `--category-emulation`**
774
810
  Set to false to exclude tools related to emulation.
@@ -809,28 +845,38 @@ The Brave DevTools MCP server supports the following configuration options:
809
845
  Override the default output format used by take_screenshot when the caller does not specify one. JPEG and WebP are ~3-5x smaller than PNG, which helps reduce context size in AI conversations. Unset preserves the existing default ("png").
810
846
  - **Type:** string
811
847
  - **Choices:** `jpeg`, `png`, `webp`
848
+ - **Default:** `false`
812
849
 
813
850
  - **`--screenshotQuality`/ `--screenshot-quality`**
814
851
  Override the default compression quality (0-100) used by take_screenshot for JPEG and WebP when the caller does not specify one. Lower values mean smaller files. Ignored for PNG. Unset preserves the Puppeteer default.
815
852
  - **Type:** number
853
+ - **Default:** `false`
816
854
 
817
855
  - **`--screenshotMaxWidth`/ `--screenshot-max-width`**
818
856
  Maximum width in pixels for screenshots. If the captured image is wider, it is downscaled (preserving aspect ratio) before being returned. Reduces context size in AI conversations. Unset means no resize.
819
857
  - **Type:** number
858
+ - **Default:** `false`
820
859
 
821
860
  - **`--screenshotMaxHeight`/ `--screenshot-max-height`**
822
861
  Maximum height in pixels for screenshots. If the captured image is taller, it is downscaled (preserving aspect ratio) before being returned. Can be combined with --screenshot-max-width; the smaller scale factor wins. Unset means no resize.
823
862
  - **Type:** number
863
+ - **Default:** `false`
824
864
 
825
865
  - **`--slim`**
826
866
  Exposes a "slim" set of 3 tools covering navigation, script execution and screenshots only. Useful for basic browser tasks.
827
867
  - **Type:** boolean
868
+ - **Default:** `false`
828
869
 
829
870
  - **`--redactNetworkHeaders`/ `--redact-network-headers`**
830
871
  If true, redacts some of the network headers considered sensitive before returning to the client.
831
872
  - **Type:** boolean
832
873
  - **Default:** `false`
833
874
 
875
+ - **`--allowUnrestrictedPaths`/ `--allow-unrestricted-paths`**
876
+ If set, disables the default path restriction that applies when the MCP client does not negotiate the roots capability. By default, file-writing tools are restricted to the OS temp directory when no roots are configured. Use this only when connecting a trusted local client that does not implement MCP roots and requires access to paths outside the temp directory.
877
+ - **Type:** boolean
878
+ - **Default:** `false`
879
+
834
880
  <!-- END AUTO GENERATED OPTIONS -->
835
881
 
836
882
  Pass them via the `args` property in the JSON configuration. For example:
@@ -930,3 +976,9 @@ Please consult [these instructions](./docs/debugging-android.md) (originally wri
930
976
  ## Known limitations
931
977
 
932
978
  See [Troubleshooting](./docs/troubleshooting.md).
979
+
980
+ ## Integrating as a browser subagent
981
+
982
+ If you are developing agentic tooling and want to provide an integrated browser subagent as part of your product, we recommend building on top of Chrome DevTools for agents.
983
+
984
+ For a reference implementation, see the [Gemini CLI browser agent documentation](https://geminicli.com/docs/core/subagents/#browser-agent).
@@ -8,6 +8,7 @@ import path from 'node:path';
8
8
  import { DevTools } from './third_party/index.js';
9
9
  import { createIdGenerator, stableIdSymbol, } from './utils/id.js';
10
10
  export class HeapSnapshotManager {
11
+ #snapshotIdGenerator = createIdGenerator();
11
12
  #snapshots = new Map();
12
13
  async getSnapshot(filePath) {
13
14
  const absolutePath = path.resolve(filePath);
@@ -15,28 +16,36 @@ export class HeapSnapshotManager {
15
16
  if (cached) {
16
17
  return cached.snapshot;
17
18
  }
18
- const { snapshot, worker } = await this.#loadSnapshot(absolutePath);
19
+ const uid = this.#snapshotIdGenerator();
20
+ const { snapshot, worker } = await this.#loadSnapshot(absolutePath, uid);
19
21
  this.#snapshots.set(absolutePath, {
20
22
  snapshot,
21
23
  worker,
22
- idToClassKey: new Map(),
24
+ idToClassKey: [''],
23
25
  classKeyToId: new Map(),
24
- idGenerator: createIdGenerator(),
25
26
  });
26
27
  return snapshot;
27
28
  }
28
- async getAggregates(filePath) {
29
+ async getAggregates(filePath, filterName) {
29
30
  const snapshot = await this.getSnapshot(filePath);
30
31
  const filter = new DevTools.HeapSnapshotModel.HeapSnapshotModel.NodeFilter();
32
+ if (filterName) {
33
+ filter.filterName = filterName;
34
+ }
31
35
  const aggregates = await snapshot.aggregatesWithFilter(filter);
32
- for (const key of Object.keys(aggregates)) {
36
+ let objectCount = 0;
37
+ let totalSelfSize = 0;
38
+ for (const [key, aggregate] of Object.entries(aggregates)) {
33
39
  const id = await this.getOrCreateIdForClassKey(filePath, key);
34
- const aggregate = aggregates[key];
35
- if (aggregate) {
36
- aggregate[stableIdSymbol] = id;
37
- }
40
+ aggregate[stableIdSymbol] = id;
41
+ objectCount += aggregate.count;
42
+ totalSelfSize += aggregate.self;
38
43
  }
39
- return aggregates;
44
+ return {
45
+ aggregates,
46
+ objectCount,
47
+ totalSelfSize,
48
+ };
40
49
  }
41
50
  async getStats(filePath) {
42
51
  const snapshot = await this.getSnapshot(filePath);
@@ -50,15 +59,18 @@ export class HeapSnapshotManager {
50
59
  const cached = this.#getCachedSnapshot(filePath);
51
60
  let id = cached.classKeyToId.get(classKey);
52
61
  if (!id) {
53
- id = cached.idGenerator();
62
+ id = cached.idToClassKey.length;
54
63
  cached.classKeyToId.set(classKey, id);
55
- cached.idToClassKey.set(id, classKey);
64
+ cached.idToClassKey.push(classKey);
56
65
  }
57
66
  return id;
58
67
  }
59
- async getNodesById(filePath, id) {
68
+ async getNodesById(filePath, id, filterName) {
60
69
  const snapshot = await this.getSnapshot(filePath);
61
70
  const filter = new DevTools.HeapSnapshotModel.HeapSnapshotModel.NodeFilter();
71
+ if (filterName) {
72
+ filter.filterName = filterName;
73
+ }
62
74
  const className = await this.resolveClassKeyFromId(filePath, id);
63
75
  if (!className) {
64
76
  throw new Error(`Class with ID ${id} not found in heap snapshot`);
@@ -100,6 +112,38 @@ export class HeapSnapshotManager {
100
112
  const provider = snapshot.createEdgesProvider(nodeIndex);
101
113
  return await provider.serializeItemsRange(0, Infinity);
102
114
  }
115
+ async getClassDiffs(baseFilePath, currentFilePath) {
116
+ const rawDiffs = await this.#getSortedRawClassDiffs(baseFilePath, currentFilePath);
117
+ return rawDiffs.map(rawDiff => ({
118
+ className: rawDiff.name,
119
+ addedCount: rawDiff.addedCount,
120
+ removedCount: rawDiff.removedCount,
121
+ countDelta: rawDiff.countDelta,
122
+ addedSize: rawDiff.addedSize,
123
+ removedSize: rawDiff.removedSize,
124
+ sizeDelta: rawDiff.sizeDelta,
125
+ }));
126
+ }
127
+ async getDetailedClassDiff(baseFilePath, currentFilePath, classIndex) {
128
+ const classDiffs = await this.#getSortedRawClassDiffs(baseFilePath, currentFilePath);
129
+ const rawDiff = classDiffs[classIndex];
130
+ if (!rawDiff) {
131
+ throw new Error(`Invalid classIndex: ${classIndex}. Total classes with changes: ${classDiffs.length}`);
132
+ }
133
+ return {
134
+ className: rawDiff.name,
135
+ addedCount: rawDiff.addedCount,
136
+ removedCount: rawDiff.removedCount,
137
+ countDelta: rawDiff.countDelta,
138
+ addedSize: rawDiff.addedSize,
139
+ removedSize: rawDiff.removedSize,
140
+ sizeDelta: rawDiff.sizeDelta,
141
+ addedIds: rawDiff.addedIds ?? [],
142
+ addedSelfSizes: rawDiff.addedSelfSizes ?? [],
143
+ deletedIds: rawDiff.deletedIds ?? [],
144
+ deletedSelfSizes: rawDiff.deletedSelfSizes ?? [],
145
+ };
146
+ }
103
147
  #getCachedSnapshot(filePath) {
104
148
  const absolutePath = path.resolve(filePath);
105
149
  const cached = this.#snapshots.get(absolutePath);
@@ -108,16 +152,35 @@ export class HeapSnapshotManager {
108
152
  }
109
153
  return cached;
110
154
  }
155
+ async #getSortedRawClassDiffs(baseFilePath, currentFilePath) {
156
+ const baseSnapshot = await this.getSnapshot(baseFilePath);
157
+ const currentSnapshot = await this.getSnapshot(currentFilePath);
158
+ const interfaceDefinitions = await currentSnapshot.interfaceDefinitions();
159
+ const aggregatesForDiff = await baseSnapshot.aggregatesForDiff(interfaceDefinitions);
160
+ const baseSnapshotId = baseSnapshot.uid;
161
+ if (baseSnapshotId === undefined) {
162
+ throw new Error('Base snapshot UID is undefined');
163
+ }
164
+ // DevTools calculateSnapshotDiff uses the first parameter (baseSnapshotId)
165
+ // as a cache key. We pass the unique UID of the base snapshot.
166
+ const rawDiffs = await currentSnapshot.calculateSnapshotDiff(baseSnapshotId, aggregatesForDiff);
167
+ // Return a filtered and sorted array here to ensure that
168
+ // compare_heapsnapshot_summary and compare_heapsnapshot_details agree
169
+ // on indices.
170
+ return Object.values(rawDiffs)
171
+ .filter(diff => diff.addedCount > 0 || diff.removedCount > 0)
172
+ .sort((a, b) => b.sizeDelta - a.sizeDelta);
173
+ }
111
174
  async resolveClassKeyFromId(filePath, id) {
112
175
  const cached = this.#getCachedSnapshot(filePath);
113
- return cached.idToClassKey.get(id);
176
+ return cached.idToClassKey[id];
114
177
  }
115
- async #loadSnapshot(absolutePath) {
178
+ async #loadSnapshot(absolutePath, uid) {
116
179
  const workerProxy = new DevTools.HeapSnapshotModel.HeapSnapshotProxy.HeapSnapshotWorkerProxy(() => {
117
180
  /* noop */
118
181
  }, import.meta.resolve('./third_party/devtools-heap-snapshot-worker.js'));
119
182
  const { promise: snapshotPromise, resolve: resolveSnapshot } = Promise.withResolvers();
120
- const loaderProxy = workerProxy.createLoader(1, snapshotProxy => {
183
+ const loaderProxy = workerProxy.createLoader(uid, snapshotProxy => {
121
184
  resolveSnapshot(snapshotProxy);
122
185
  });
123
186
  const fileStream = fsSync.createReadStream(absolutePath, {
@@ -131,6 +194,10 @@ export class HeapSnapshotManager {
131
194
  const snapshot = await snapshotPromise;
132
195
  return { snapshot, worker: workerProxy };
133
196
  }
197
+ async getDuplicateStrings(filePath) {
198
+ const snapshot = await this.getSnapshot(filePath);
199
+ return await snapshot.getDuplicateStrings();
200
+ }
134
201
  hasSnapshots() {
135
202
  return this.#snapshots.size > 0;
136
203
  }