chrome-devtools-mcp 1.2.0 → 1.3.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.
package/README.md CHANGED
@@ -514,12 +514,15 @@ If you run into any issues, checkout our [troubleshooting guide](./docs/troubles
514
514
  - [`take_snapshot`](docs/tool-reference.md#take_snapshot)
515
515
  - [`screencast_start`](docs/tool-reference.md#screencast_start)
516
516
  - [`screencast_stop`](docs/tool-reference.md#screencast_stop)
517
- - **Memory** (6 tools)
517
+ - **Memory** (9 tools)
518
518
  - [`take_heapsnapshot`](docs/tool-reference.md#take_heapsnapshot)
519
519
  - [`close_heapsnapshot`](docs/tool-reference.md#close_heapsnapshot)
520
520
  - [`get_heapsnapshot_class_nodes`](docs/tool-reference.md#get_heapsnapshot_class_nodes)
521
521
  - [`get_heapsnapshot_details`](docs/tool-reference.md#get_heapsnapshot_details)
522
+ - [`get_heapsnapshot_dominators`](docs/tool-reference.md#get_heapsnapshot_dominators)
523
+ - [`get_heapsnapshot_edges`](docs/tool-reference.md#get_heapsnapshot_edges)
522
524
  - [`get_heapsnapshot_retainers`](docs/tool-reference.md#get_heapsnapshot_retainers)
525
+ - [`get_heapsnapshot_retaining_paths`](docs/tool-reference.md#get_heapsnapshot_retaining_paths)
523
526
  - [`get_heapsnapshot_summary`](docs/tool-reference.md#get_heapsnapshot_summary)
524
527
  - **Extensions** (5 tools)
525
528
  - [`install_extension`](docs/tool-reference.md#install_extension)
@@ -630,7 +633,7 @@ The Chrome DevTools MCP server supports the following configuration option:
630
633
  - **Type:** string
631
634
 
632
635
  - **`--categoryExperimentalWebmcp`/ `--category-experimental-webmcp`**
633
- Set to true to enable debugging WebMCP tools. Requires Chrome 149+ with the following flags: `--enable-features=WebMCPTesting,DevToolsWebMCPSupport`
636
+ Set to true to enable debugging WebMCP tools. Requires Chrome 149+ with the following flags: `--enable-features=WebMCP,DevToolsWebMCPSupport`
634
637
  - **Type:** boolean
635
638
 
636
639
  - **`--chromeArg`/ `--chrome-arg`**
@@ -684,6 +687,23 @@ The Chrome DevTools MCP server supports the following configuration option:
684
687
  - **Type:** boolean
685
688
  - **Default:** `true`
686
689
 
690
+ - **`--screenshotFormat`/ `--screenshot-format`**
691
+ 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").
692
+ - **Type:** string
693
+ - **Choices:** `jpeg`, `png`, `webp`
694
+
695
+ - **`--screenshotQuality`/ `--screenshot-quality`**
696
+ 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.
697
+ - **Type:** number
698
+
699
+ - **`--screenshotMaxWidth`/ `--screenshot-max-width`**
700
+ 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.
701
+ - **Type:** number
702
+
703
+ - **`--screenshotMaxHeight`/ `--screenshot-max-height`**
704
+ 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.
705
+ - **Type:** number
706
+
687
707
  - **`--slim`**
688
708
  Exposes a "slim" set of 3 tools covering navigation, script execution and screenshots only. Useful for basic browser tasks.
689
709
  - **Type:** boolean
@@ -5,7 +5,6 @@
5
5
  */
6
6
  import fsSync from 'node:fs';
7
7
  import path from 'node:path';
8
- import { isNodeLike } from './formatters/HeapSnapshotFormatter.js';
9
8
  import { DevTools } from './third_party/index.js';
10
9
  import { createIdGenerator, stableIdSymbol, } from './utils/id.js';
11
10
  export class HeapSnapshotManager {
@@ -67,28 +66,38 @@ export class HeapSnapshotManager {
67
66
  const provider = snapshot.createNodesProviderForClass(className, filter);
68
67
  return await provider.serializeItemsRange(0, Infinity);
69
68
  }
70
- async findNodeIndexById(filePath, nodeId) {
69
+ async getRetainers(filePath, nodeId) {
71
70
  const snapshot = await this.getSnapshot(filePath);
72
- const aggregates = await this.getAggregates(filePath);
73
- const filter = new DevTools.HeapSnapshotModel.HeapSnapshotModel.NodeFilter();
74
- for (const classKey of Object.keys(aggregates)) {
75
- const provider = snapshot.createNodesProviderForClass(classKey, filter);
76
- const range = await provider.serializeItemsRange(0, Infinity);
77
- for (const item of range.items) {
78
- if (isNodeLike(item) && item.id === nodeId) {
79
- return item.nodeIndex;
80
- }
81
- }
71
+ const nodeIndex = await snapshot.nodeIndexForId(nodeId);
72
+ if (nodeIndex === undefined) {
73
+ throw new Error(`Node with ID ${nodeId} not found`);
82
74
  }
83
- return undefined;
75
+ const provider = snapshot.createRetainingEdgesProvider(nodeIndex);
76
+ return await provider.serializeItemsRange(0, Infinity);
84
77
  }
85
- async getRetainers(filePath, nodeId) {
86
- const nodeIndex = await this.findNodeIndexById(filePath, nodeId);
78
+ async getRetainingPaths(filePath, nodeId, maxDepth, maxNodes, maxSiblings) {
79
+ const snapshot = await this.getSnapshot(filePath);
80
+ const nodeIndex = await snapshot.nodeIndexForId(nodeId);
87
81
  if (nodeIndex === undefined) {
88
82
  throw new Error(`Node with ID ${nodeId} not found`);
89
83
  }
84
+ return await snapshot.getRetainingPaths(nodeIndex, maxDepth, maxNodes, maxSiblings);
85
+ }
86
+ async getDominatorsOf(filePath, nodeId) {
90
87
  const snapshot = await this.getSnapshot(filePath);
91
- const provider = snapshot.createRetainingEdgesProvider(nodeIndex);
88
+ const nodeIndex = await snapshot.nodeIndexForId(nodeId);
89
+ if (nodeIndex === undefined) {
90
+ throw new Error(`Node with ID ${nodeId} not found`);
91
+ }
92
+ return await snapshot.getDominatorsOf(nodeIndex);
93
+ }
94
+ async getEdges(filePath, nodeId) {
95
+ const snapshot = await this.getSnapshot(filePath);
96
+ const nodeIndex = await snapshot.nodeIndexForId(nodeId);
97
+ if (nodeIndex === undefined) {
98
+ throw new Error(`Node with ID ${nodeId} not found`);
99
+ }
100
+ const provider = snapshot.createEdgesProvider(nodeIndex);
92
101
  return await provider.serializeItemsRange(0, Infinity);
93
102
  }
94
103
  #getCachedSnapshot(filePath) {
@@ -8,7 +8,7 @@ import fsPromises from 'node:fs/promises';
8
8
  import os from 'node:os';
9
9
  import path from 'node:path';
10
10
  import { fileURLToPath, pathToFileURL } from 'node:url';
11
- import { UniverseManager } from './DevtoolsUtils.js';
11
+ import { overrideDevToolsGlobals, UniverseManager, } from './devtools/DevtoolsUtils.js';
12
12
  import { HeapSnapshotManager } from './HeapSnapshotManager.js';
13
13
  import { McpPage } from './McpPage.js';
14
14
  import { NetworkCollector, ConsoleCollector, } from './PageCollector.js';
@@ -47,6 +47,11 @@ export class McpContext {
47
47
  #heapSnapshotManager = new HeapSnapshotManager();
48
48
  #roots = undefined;
49
49
  constructor(browser, logger, options, locatorClass) {
50
+ overrideDevToolsGlobals({
51
+ loadResource: (url) => {
52
+ return this.loadResource(url);
53
+ },
54
+ });
50
55
  this.browser = browser;
51
56
  this.logger = logger;
52
57
  this.#locatorClass = locatorClass;
@@ -659,5 +664,34 @@ export class McpContext {
659
664
  hasHeapSnapshots() {
660
665
  return this.#heapSnapshotManager.hasSnapshots();
661
666
  }
667
+ async getHeapSnapshotRetainingPaths(filePath, nodeId, maxDepth, maxNodes, maxSiblings) {
668
+ return await this.#heapSnapshotManager.getRetainingPaths(filePath, nodeId, maxDepth, maxNodes, maxSiblings);
669
+ }
670
+ async getHeapSnapshotDominators(filePath, nodeId) {
671
+ return await this.#heapSnapshotManager.getDominatorsOf(filePath, nodeId);
672
+ }
673
+ async loadResource(path) {
674
+ const url = new URL(path);
675
+ switch (url.protocol) {
676
+ case 'https:':
677
+ case 'http:': {
678
+ // TODO: Verify allow/block list
679
+ const response = await fetch(url);
680
+ if (!response.ok) {
681
+ throw new Error(`Failed to load resource: ${url}`);
682
+ }
683
+ return response.text();
684
+ }
685
+ case 'file:': {
686
+ await this.validatePath(fileURLToPath(url));
687
+ return await fsPromises.readFile(url, 'utf-8');
688
+ }
689
+ default:
690
+ throw new Error(`Unsupported protocol for: ${url}`);
691
+ }
692
+ }
693
+ async getHeapSnapshotEdges(filePath, nodeId) {
694
+ return await this.#heapSnapshotManager.getEdges(filePath, nodeId);
695
+ }
662
696
  }
663
697
  //# sourceMappingURL=McpContext.js.map
@@ -73,6 +73,9 @@ async function getToolGroups(page) {
73
73
  return [];
74
74
  }
75
75
  const toolGroups = await page.pptrPage.evaluate(() => {
76
+ if (window.__dtmcp) {
77
+ window.__dtmcp.toolGroups = [];
78
+ }
76
79
  return new Promise(resolve => {
77
80
  const event = new CustomEvent('devtoolstooldiscovery');
78
81
  const groups = [];
@@ -85,7 +88,8 @@ async function getToolGroups(page) {
85
88
  window.__dtmcp.toolGroups = [];
86
89
  }
87
90
  if (typeof toolGroup.name !== 'string' ||
88
- typeof toolGroup.description !== 'string' ||
91
+ (toolGroup.description &&
92
+ typeof toolGroup.description !== 'string') ||
89
93
  !Array.isArray(toolGroup.tools)) {
90
94
  console.error('Invalid toolGroup:', toolGroup);
91
95
  return;
@@ -330,6 +334,20 @@ export class McpResponse {
330
334
  pagination: options,
331
335
  };
332
336
  }
337
+ setHeapSnapshotRetainingPaths(retainingPaths) {
338
+ this.#heapSnapshotOptions = {
339
+ ...this.#heapSnapshotOptions,
340
+ include: true,
341
+ retainingPaths,
342
+ };
343
+ }
344
+ setHeapSnapshotDominators(dominators) {
345
+ this.#heapSnapshotOptions = {
346
+ ...this.#heapSnapshotOptions,
347
+ include: true,
348
+ dominators,
349
+ };
350
+ }
333
351
  attachImage(value) {
334
352
  this.#images.push(value);
335
353
  }
@@ -528,7 +546,7 @@ export class McpResponse {
528
546
  errorMessage: this.#error?.message,
529
547
  }, useToon);
530
548
  }
531
- format(toolName, context, data, useToon) {
549
+ async format(toolName, context, data, useToon) {
532
550
  const structuredContent = {};
533
551
  const response = [];
534
552
  if (this.#textResponseLines.length) {
@@ -608,8 +626,12 @@ Call ${handleDialog.name} to handle it before continuing.`);
608
626
  const contextLabel = isolatedContextName
609
627
  ? ` isolatedContext=${isolatedContextName}`
610
628
  : '';
611
- parts.push(`${context.getPageId(page)}: ${page.url()}${context.isPageSelected(page) ? ' [selected]' : ''}${contextLabel}`);
612
- structuredPages.push(createStructuredPage(page, context));
629
+ const title = await fetchPageTitle(page);
630
+ const pageLabel = title
631
+ ? `${truncateTitle(title)} (${page.url()})`
632
+ : page.url();
633
+ parts.push(`${context.getPageId(page)}: ${pageLabel}${context.isPageSelected(page) ? ' [selected]' : ''}${contextLabel}`);
634
+ structuredPages.push(createStructuredPage(page, context, title));
613
635
  }
614
636
  response.push(...parts);
615
637
  structuredContent.pages = structuredPages;
@@ -623,8 +645,12 @@ Call ${handleDialog.name} to handle it before continuing.`);
623
645
  const contextLabel = isolatedContextName
624
646
  ? ` isolatedContext=${isolatedContextName}`
625
647
  : '';
626
- response.push(`${context.getPageId(page)}: ${page.url()}${context.isPageSelected(page) ? ' [selected]' : ''}${contextLabel}`);
627
- structuredExtensionPages.push(createStructuredPage(page, context));
648
+ const title = await fetchPageTitle(page);
649
+ const pageLabel = title
650
+ ? `${truncateTitle(title)} (${page.url()})`
651
+ : page.url();
652
+ response.push(`${context.getPageId(page)}: ${pageLabel}${context.isPageSelected(page) ? ' [selected]' : ''}${contextLabel}`);
653
+ structuredExtensionPages.push(createStructuredPage(page, context, title));
628
654
  }
629
655
  structuredContent.extensionPages = structuredExtensionPages;
630
656
  }
@@ -752,6 +778,36 @@ Call ${handleDialog.name} to handle it before continuing.`);
752
778
  response.push(...paginationData.info);
753
779
  structuredContent.heapSnapshotNodes = paginationData.items;
754
780
  }
781
+ const retainingPaths = this.#heapSnapshotOptions.retainingPaths;
782
+ if (retainingPaths) {
783
+ response.push('### Retaining Paths');
784
+ const { paths, limitsReached } = retainingPaths;
785
+ if (paths.length === 0) {
786
+ response.push('No retaining paths found.');
787
+ }
788
+ else {
789
+ response.push(HeapSnapshotFormatter.formatRetainingPaths(paths));
790
+ }
791
+ const reached = Object.entries(limitsReached)
792
+ .filter(([, hit]) => hit)
793
+ .map(([limit]) => limit);
794
+ if (reached.length > 0) {
795
+ response.push(`Note: results are truncated, the following limits were reached: ${reached.join(', ')}.`);
796
+ }
797
+ structuredContent.heapSnapshotRetainingPaths =
798
+ retainingPaths;
799
+ }
800
+ const dominators = this.#heapSnapshotOptions.dominators;
801
+ if (dominators) {
802
+ response.push('### Dominator Chain');
803
+ if (dominators.length === 0) {
804
+ response.push('No dominators found.');
805
+ }
806
+ else {
807
+ response.push(HeapSnapshotFormatter.formatDominators(dominators));
808
+ }
809
+ structuredContent.heapSnapshotDominators = dominators;
810
+ }
755
811
  }
756
812
  if (data.detailedNetworkRequest) {
757
813
  response.push(data.detailedNetworkRequest.toStringDetailed());
@@ -905,11 +961,25 @@ Call ${handleDialog.name} to handle it before continuing.`);
905
961
  this.#textResponseLines = [];
906
962
  }
907
963
  }
908
- function createStructuredPage(page, context) {
964
+ function truncateTitle(title, maxLength = 50) {
965
+ if (title.length <= maxLength) {
966
+ return title;
967
+ }
968
+ return title.slice(0, maxLength - 3) + '...';
969
+ }
970
+ async function fetchPageTitle(page) {
971
+ return Promise.race([
972
+ page.title().catch(() => ''),
973
+ new Promise(resolve => setTimeout(() => resolve(''), 1000)),
974
+ ]);
975
+ }
976
+ function createStructuredPage(page, context, rawTitle) {
909
977
  const isolatedContextName = context.getIsolatedContextName(page);
978
+ const title = truncateTitle(rawTitle);
910
979
  const entry = {
911
980
  id: context.getPageId(page),
912
981
  url: page.url(),
982
+ title,
913
983
  selected: context.isPageSelected(page),
914
984
  };
915
985
  if (isolatedContextName) {
@@ -3,7 +3,7 @@
3
3
  * Copyright 2025 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- import { FakeIssuesManager } from './DevtoolsUtils.js';
6
+ import { FakeIssuesManager } from './devtools/DevtoolsUtils.js';
7
7
  import { logger } from './logger.js';
8
8
  import { DevTools } from './third_party/index.js';
9
9
  import { createIdGenerator, stableIdSymbol, } from './utils/id.js';
@@ -312,6 +312,54 @@ export const commands = {
312
312
  },
313
313
  },
314
314
  },
315
+ get_heapsnapshot_dominators: {
316
+ description: 'Loads a memory heapsnapshot and returns the dominator chain for a specific node ID. This helps to identify which objects are keeping the target node alive. (requires flag: --memoryDebugging=true)',
317
+ category: 'Memory',
318
+ args: {
319
+ filePath: {
320
+ name: 'filePath',
321
+ type: 'string',
322
+ description: 'A path to a .heapsnapshot file to read.',
323
+ required: true,
324
+ },
325
+ nodeId: {
326
+ name: 'nodeId',
327
+ type: 'number',
328
+ description: 'The node ID to get the dominator chain for.',
329
+ required: true,
330
+ },
331
+ },
332
+ },
333
+ get_heapsnapshot_edges: {
334
+ description: 'Loads a memory heapsnapshot and returns outgoing edges (references) for a specific node ID. (requires flag: --memoryDebugging=true)',
335
+ category: 'Memory',
336
+ args: {
337
+ filePath: {
338
+ name: 'filePath',
339
+ type: 'string',
340
+ description: 'A path to a .heapsnapshot file to read.',
341
+ required: true,
342
+ },
343
+ nodeId: {
344
+ name: 'nodeId',
345
+ type: 'number',
346
+ description: 'The node ID to get outgoing edges for.',
347
+ required: true,
348
+ },
349
+ pageIdx: {
350
+ name: 'pageIdx',
351
+ type: 'number',
352
+ description: 'The page index for pagination.',
353
+ required: false,
354
+ },
355
+ pageSize: {
356
+ name: 'pageSize',
357
+ type: 'number',
358
+ description: 'The page size for pagination.',
359
+ required: false,
360
+ },
361
+ },
362
+ },
315
363
  get_heapsnapshot_retainers: {
316
364
  description: 'Loads a memory heapsnapshot and returns retainers for a specific node ID. (requires flag: --memoryDebugging=true)',
317
365
  category: 'Memory',
@@ -342,6 +390,42 @@ export const commands = {
342
390
  },
343
391
  },
344
392
  },
393
+ get_heapsnapshot_retaining_paths: {
394
+ description: 'Loads a memory heapsnapshot and returns retaining paths for a specific node ID. This helps to understand why a node is not being garbage collected. (requires flag: --memoryDebugging=true)',
395
+ category: 'Memory',
396
+ args: {
397
+ filePath: {
398
+ name: 'filePath',
399
+ type: 'string',
400
+ description: 'A path to a .heapsnapshot file to read.',
401
+ required: true,
402
+ },
403
+ nodeId: {
404
+ name: 'nodeId',
405
+ type: 'number',
406
+ description: 'The node ID to get retaining paths for.',
407
+ required: true,
408
+ },
409
+ maxDepth: {
410
+ name: 'maxDepth',
411
+ type: 'number',
412
+ description: 'The maximum depth to search for retaining paths.',
413
+ required: false,
414
+ },
415
+ maxNodes: {
416
+ name: 'maxNodes',
417
+ type: 'number',
418
+ description: 'The maximum number of nodes to return.',
419
+ required: false,
420
+ },
421
+ maxSiblings: {
422
+ name: 'maxSiblings',
423
+ type: 'number',
424
+ description: 'The maximum number of siblings to return.',
425
+ required: false,
426
+ },
427
+ },
428
+ },
345
429
  get_heapsnapshot_summary: {
346
430
  description: 'Loads a memory heapsnapshot and returns snapshot summary stats. (requires flag: --memoryDebugging=true)',
347
431
  category: 'Memory',
@@ -185,7 +185,7 @@ export const cliOptions = {
185
185
  },
186
186
  categoryExperimentalWebmcp: {
187
187
  type: 'boolean',
188
- describe: 'Set to true to enable debugging WebMCP tools. Requires Chrome 149+ with the following flags: `--enable-features=WebMCPTesting,DevToolsWebMCPSupport`',
188
+ describe: 'Set to true to enable debugging WebMCP tools. Requires Chrome 149+ with the following flags: `--enable-features=WebMCP,DevToolsWebMCPSupport`',
189
189
  },
190
190
  chromeArg: {
191
191
  type: 'array',
@@ -256,6 +256,50 @@ export const cliOptions = {
256
256
  hidden: true,
257
257
  describe: 'Include watchdog PID in Clearcut request headers (for testing).',
258
258
  },
259
+ screenshotFormat: {
260
+ type: 'string',
261
+ description: '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").',
262
+ choices: ['jpeg', 'png', 'webp'],
263
+ },
264
+ screenshotQuality: {
265
+ type: 'number',
266
+ description: '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.',
267
+ coerce: (value) => {
268
+ if (value === undefined) {
269
+ return;
270
+ }
271
+ if (!Number.isInteger(value) || value < 0 || value > 100) {
272
+ throw new Error(`Invalid screenshotQuality ${value}. Expected an integer between 0 and 100.`);
273
+ }
274
+ return value;
275
+ },
276
+ },
277
+ screenshotMaxWidth: {
278
+ type: 'number',
279
+ description: '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.',
280
+ coerce: (value) => {
281
+ if (value === undefined) {
282
+ return;
283
+ }
284
+ if (!Number.isInteger(value) || value <= 0) {
285
+ throw new Error(`Invalid screenshotMaxWidth ${value}. Expected a positive integer.`);
286
+ }
287
+ return value;
288
+ },
289
+ },
290
+ screenshotMaxHeight: {
291
+ type: 'number',
292
+ description: '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.',
293
+ coerce: (value) => {
294
+ if (value === undefined) {
295
+ return;
296
+ }
297
+ if (!Number.isInteger(value) || value <= 0) {
298
+ throw new Error(`Invalid screenshotMaxHeight ${value}. Expected a positive integer.`);
299
+ }
300
+ return value;
301
+ },
302
+ },
259
303
  slim: {
260
304
  type: 'boolean',
261
305
  describe: 'Exposes a "slim" set of 3 tools covering navigation, script execution and screenshots only. Useful for basic browser tasks.',
@@ -3,7 +3,7 @@
3
3
  * Copyright 2025 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- import { CDPSessionEvent } from './third_party/index.js';
6
+ import { CDPSessionEvent } from '../third_party/index.js';
7
7
  /**
8
8
  * This class makes a puppeteer connection look like DevTools CDPConnection.
9
9
  *
@@ -3,9 +3,10 @@
3
3
  * Copyright 2025 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
+ import { Mutex } from '../Mutex.js';
7
+ import { DevTools } from '../third_party/index.js';
6
8
  import { PuppeteerDevToolsConnection } from './DevToolsConnectionAdapter.js';
7
- import { Mutex } from './Mutex.js';
8
- import { DevTools } from './third_party/index.js';
9
+ import { McpHostBindingAdapter } from './McpHostBindingAdapter.js';
9
10
  /**
10
11
  * A mock implementation of an issues manager that only implements the methods
11
12
  * that are actually used by the IssuesAggregator
@@ -16,79 +17,82 @@ export class FakeIssuesManager extends DevTools.Common.ObjectWrapper
16
17
  return [];
17
18
  }
18
19
  }
19
- // DevTools CDP errors can get noisy.
20
- DevTools.ProtocolClient.InspectorBackend.test.suppressRequestErrors = true;
21
- // Stub out Network emulation commands on the DevTools Agent prototype globally.
22
- // This prevents the DevTools Frontend from ever resetting/clearing Puppeteer's
23
- // active network blocking/throttling rules during target setup or session lifetime.
24
- const networkAgentPrototype = DevTools.ProtocolClient.InspectorBackend.inspectorBackend.agentPrototypes.get('Network');
25
- if (networkAgentPrototype) {
26
- Object.defineProperty(networkAgentPrototype, 'invoke_emulateNetworkConditionsByRule', {
27
- value: () => {
28
- return Promise.resolve({
29
- ruleIds: [],
30
- getError: () => undefined,
31
- });
32
- },
33
- writable: true,
34
- configurable: true,
35
- enumerable: true,
36
- });
37
- Object.defineProperty(networkAgentPrototype, 'invoke_overrideNetworkState', {
38
- value: () => {
39
- return Promise.resolve({
40
- getError: () => undefined,
41
- });
42
- },
43
- writable: true,
44
- configurable: true,
45
- enumerable: true,
46
- });
47
- Object.defineProperty(networkAgentPrototype, 'invoke_enable', {
48
- value: () => {
49
- return Promise.resolve({
50
- getError: () => undefined,
51
- });
52
- },
53
- writable: true,
54
- configurable: true,
55
- enumerable: true,
56
- });
57
- Object.defineProperty(networkAgentPrototype, 'invoke_disable', {
58
- value: () => {
59
- return Promise.resolve({
60
- getError: () => undefined,
61
- });
20
+ export function overrideDevToolsGlobals({ loadResource, }) {
21
+ DevTools.Host.InspectorFrontendHost.installInspectorFrontendHost(new McpHostBindingAdapter(loadResource));
22
+ // DevTools CDP errors can get noisy.
23
+ DevTools.ProtocolClient.InspectorBackend.test.suppressRequestErrors = true;
24
+ // Stub out Network emulation commands on the DevTools Agent prototype globally.
25
+ // This prevents the DevTools Frontend from ever resetting/clearing Puppeteer's
26
+ // active network blocking/throttling rules during target setup or session lifetime.
27
+ const networkAgentPrototype = DevTools.ProtocolClient.InspectorBackend.inspectorBackend.agentPrototypes.get('Network');
28
+ if (networkAgentPrototype) {
29
+ Object.defineProperty(networkAgentPrototype, 'invoke_emulateNetworkConditionsByRule', {
30
+ value: () => {
31
+ return Promise.resolve({
32
+ ruleIds: [],
33
+ getError: () => undefined,
34
+ });
35
+ },
36
+ writable: true,
37
+ configurable: true,
38
+ enumerable: true,
39
+ });
40
+ Object.defineProperty(networkAgentPrototype, 'invoke_overrideNetworkState', {
41
+ value: () => {
42
+ return Promise.resolve({
43
+ getError: () => undefined,
44
+ });
45
+ },
46
+ writable: true,
47
+ configurable: true,
48
+ enumerable: true,
49
+ });
50
+ Object.defineProperty(networkAgentPrototype, 'invoke_enable', {
51
+ value: () => {
52
+ return Promise.resolve({
53
+ getError: () => undefined,
54
+ });
55
+ },
56
+ writable: true,
57
+ configurable: true,
58
+ enumerable: true,
59
+ });
60
+ Object.defineProperty(networkAgentPrototype, 'invoke_disable', {
61
+ value: () => {
62
+ return Promise.resolve({
63
+ getError: () => undefined,
64
+ });
65
+ },
66
+ writable: true,
67
+ configurable: true,
68
+ enumerable: true,
69
+ });
70
+ Object.defineProperty(networkAgentPrototype, 'invoke_setBlockedURLs', {
71
+ value: () => {
72
+ return Promise.resolve({
73
+ getError: () => undefined,
74
+ });
75
+ },
76
+ writable: true,
77
+ configurable: true,
78
+ enumerable: true,
79
+ });
80
+ }
81
+ DevTools.I18n.DevToolsLocale.DevToolsLocale.instance({
82
+ create: true,
83
+ data: {
84
+ navigatorLanguage: 'en-US',
85
+ settingLanguage: 'en-US',
86
+ lookupClosestDevToolsLocale: l => l,
62
87
  },
63
- writable: true,
64
- configurable: true,
65
- enumerable: true,
66
88
  });
67
- Object.defineProperty(networkAgentPrototype, 'invoke_setBlockedURLs', {
68
- value: () => {
69
- return Promise.resolve({
70
- getError: () => undefined,
71
- });
72
- },
73
- writable: true,
74
- configurable: true,
75
- enumerable: true,
89
+ DevTools.I18n.i18n.registerLocaleDataForTest('en-US', {});
90
+ DevTools.Formatter.FormatterWorkerPool.FormatterWorkerPool.instance({
91
+ forceNew: true,
92
+ entrypointURL: import.meta
93
+ .resolve('../third_party/devtools-formatter-worker.js'),
76
94
  });
77
95
  }
78
- DevTools.I18n.DevToolsLocale.DevToolsLocale.instance({
79
- create: true,
80
- data: {
81
- navigatorLanguage: 'en-US',
82
- settingLanguage: 'en-US',
83
- lookupClosestDevToolsLocale: l => l,
84
- },
85
- });
86
- DevTools.I18n.i18n.registerLocaleDataForTest('en-US', {});
87
- DevTools.Formatter.FormatterWorkerPool.FormatterWorkerPool.instance({
88
- forceNew: true,
89
- entrypointURL: import.meta
90
- .resolve('./third_party/devtools-formatter-worker.js'),
91
- });
92
96
  export class UniverseManager {
93
97
  #browser;
94
98
  #createUniverseFor;