@mcp-b/chrome-devtools-mcp 2.0.2 → 2.0.5

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 (149) hide show
  1. package/README.md +120 -10
  2. package/build/src/McpContext.js +69 -5
  3. package/build/src/browser.js +157 -51
  4. package/build/src/cli.js +11 -5
  5. package/build/src/formatters/IssueFormatter.js +190 -0
  6. package/build/src/main.js +83 -2
  7. package/build/src/telemetry/clearcut-logger.js +102 -0
  8. package/build/src/telemetry/flag-utils.js +45 -0
  9. package/build/src/telemetry/metric-utils.js +14 -0
  10. package/build/src/telemetry/persistence.js +53 -0
  11. package/build/src/telemetry/types.js +33 -0
  12. package/build/src/telemetry/watchdog/clearcut-sender.js +201 -0
  13. package/build/src/telemetry/watchdog/main.js +127 -0
  14. package/build/src/telemetry/watchdog-client.js +60 -0
  15. package/build/src/third_party/devtools-formatter-worker.js +7 -0
  16. package/build/src/third_party/index.js +1 -1
  17. package/build/src/tools/browser.js +92 -0
  18. package/build/src/tools/extension.js +31 -0
  19. package/build/src/tools/extensions.js +79 -0
  20. package/build/src/tools/input.js +6 -1
  21. package/build/src/tools/pages.js +7 -1
  22. package/build/src/tools/script.js +31 -4
  23. package/build/src/tools/tools.js +4 -0
  24. package/build/src/transports/CDPClientTransport.js +184 -0
  25. package/build/src/transports/WebMCPBridgeScript.js +11 -2
  26. package/build/src/utils/ExtensionRegistry.js +35 -0
  27. package/build/src/utils/string.js +36 -0
  28. package/build/vendor/chrome-devtools-frontend/front_end/core/common/Base64.js +20 -2
  29. package/build/vendor/chrome-devtools-frontend/front_end/core/common/Debouncer.js +8 -1
  30. package/build/vendor/chrome-devtools-frontend/front_end/core/common/Gzip.js +11 -0
  31. package/build/vendor/chrome-devtools-frontend/front_end/core/common/Object.js +6 -1
  32. package/build/vendor/chrome-devtools-frontend/front_end/core/common/ParsedURL.js +3 -0
  33. package/build/vendor/chrome-devtools-frontend/front_end/core/common/ResourceType.js +6 -0
  34. package/build/vendor/chrome-devtools-frontend/front_end/core/common/Revealer.js +0 -5
  35. package/build/vendor/chrome-devtools-frontend/front_end/core/common/Settings.js +18 -8
  36. package/build/vendor/chrome-devtools-frontend/front_end/core/host/AidaClient.js +24 -0
  37. package/build/vendor/chrome-devtools-frontend/front_end/core/host/InspectorFrontendHostStub.js +11 -3
  38. package/build/vendor/chrome-devtools-frontend/front_end/core/host/ResourceLoader.js +1 -1
  39. package/build/vendor/chrome-devtools-frontend/front_end/core/host/UserMetrics.js +27 -20
  40. package/build/vendor/chrome-devtools-frontend/front_end/core/i18n/collect-ui-strings.js +7 -8
  41. package/build/vendor/chrome-devtools-frontend/front_end/core/i18n/generate-locales-js.js +4 -5
  42. package/build/vendor/chrome-devtools-frontend/front_end/core/platform/ArrayUtilities.js +10 -0
  43. package/build/vendor/chrome-devtools-frontend/front_end/core/platform/StringUtilities.js +63 -12
  44. package/build/vendor/chrome-devtools-frontend/front_end/core/protocol_client/CDPConnection.js +1 -0
  45. package/build/vendor/chrome-devtools-frontend/front_end/core/protocol_client/InspectorBackend.js +4 -1
  46. package/build/vendor/chrome-devtools-frontend/front_end/core/root/ExperimentNames.js +30 -0
  47. package/build/vendor/chrome-devtools-frontend/front_end/core/root/root.js +2 -1
  48. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/AnimationModel.js +0 -4
  49. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/CSSMatchedStyles.js +69 -9
  50. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/CSSMetadata.js +6 -6
  51. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/CSSModel.js +28 -13
  52. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/CSSProperty.js +1 -1
  53. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/CSSPropertyParserMatchers.js +6 -0
  54. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/ConsoleModel.js +0 -2
  55. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/CookieModel.js +1 -1
  56. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/DOMModel.js +170 -13
  57. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/DebuggerModel.js +5 -39
  58. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/HeapProfilerModel.js +8 -1
  59. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/NetworkManager.js +20 -5
  60. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/NetworkRequest.js +12 -21
  61. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/OverlayModel.js +19 -6
  62. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/RehydratingConnection.js +5 -1
  63. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/ResourceTreeModel.js +8 -5
  64. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/SourceMap.js +15 -10
  65. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/SourceMapManager.js +1 -1
  66. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/SourceMapScopesInfo.js +13 -27
  67. package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/Target.js +3 -1
  68. package/build/vendor/chrome-devtools-frontend/front_end/generated/ARIAProperties.js +1 -7
  69. package/build/vendor/chrome-devtools-frontend/front_end/generated/Deprecation.js +1 -16
  70. package/build/vendor/chrome-devtools-frontend/front_end/generated/InspectorBackendCommands.js +82 -22
  71. package/build/vendor/chrome-devtools-frontend/front_end/generated/SupportedCSSProperties.js +265 -123
  72. package/build/vendor/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.js +2 -1
  73. package/build/vendor/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.js +10 -16
  74. package/build/vendor/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.js +97 -26
  75. package/build/vendor/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AICallTree.js +35 -0
  76. package/build/vendor/chrome-devtools-frontend/front_end/models/annotations/AnnotationRepository.js +163 -0
  77. package/build/vendor/chrome-devtools-frontend/front_end/models/annotations/AnnotationType.js +10 -0
  78. package/build/vendor/chrome-devtools-frontend/front_end/models/annotations/annotations.js +5 -0
  79. package/build/vendor/chrome-devtools-frontend/front_end/models/bindings/CompilerScriptMapping.js +5 -3
  80. package/build/vendor/chrome-devtools-frontend/front_end/models/bindings/DebuggerLanguagePlugins.js +29 -58
  81. package/build/vendor/chrome-devtools-frontend/front_end/models/bindings/DebuggerWorkspaceBinding.js +7 -45
  82. package/build/vendor/chrome-devtools-frontend/front_end/models/emulation/DeviceModeModel.js +1 -1
  83. package/build/vendor/chrome-devtools-frontend/front_end/models/emulation/EmulatedDevices.js +14 -0
  84. package/build/vendor/chrome-devtools-frontend/front_end/models/formatter/FormatterWorkerPool.js +8 -5
  85. package/build/vendor/chrome-devtools-frontend/front_end/models/greendev/Prototypes.js +33 -0
  86. package/build/vendor/chrome-devtools-frontend/front_end/models/greendev/greendev.js +4 -0
  87. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/ContrastCheckTrigger.js +2 -2
  88. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/CookieIssue.js +0 -21
  89. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/CorsIssue.js +1 -38
  90. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/IssueAggregator.js +8 -0
  91. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/IssuesManager.js +6 -12
  92. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/PermissionElementIssue.js +243 -0
  93. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementActivationDisabled.md +7 -0
  94. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementActivationDisabledWithOccluder.md +9 -0
  95. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementActivationDisabledWithOccluderParent.md +9 -0
  96. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementCspFrameAncestorsMissing.md +5 -0
  97. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementFencedFrameDisallowed.md +5 -0
  98. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementFontSizeTooLarge.md +5 -0
  99. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementFontSizeTooSmall.md +5 -0
  100. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementGeolocationDeprecated.md +5 -0
  101. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementInsetBoxShadowUnsupported.md +5 -0
  102. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementInvalidDisplayStyle.md +5 -0
  103. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementInvalidSizeValue.md +5 -0
  104. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementInvalidType.md +5 -0
  105. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementInvalidTypeActivation.md +5 -0
  106. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementLowContrast.md +5 -0
  107. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementNonOpaqueColor.md +5 -0
  108. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementPaddingBottomUnsupported.md +6 -0
  109. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementPaddingRightUnsupported.md +6 -0
  110. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementPermissionsPolicyBlocked.md +5 -0
  111. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementRegistrationFailed.md +5 -0
  112. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementRequestInProgress.md +5 -0
  113. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementSecurityChecksFailed.md +5 -0
  114. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementTypeNotSupported.md +5 -0
  115. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementUntrustedEvent.md +7 -0
  116. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/issues_manager.js +2 -1
  117. package/build/vendor/chrome-devtools-frontend/front_end/models/logs/NetworkLog.js +0 -8
  118. package/build/vendor/chrome-devtools-frontend/front_end/models/source_map_scopes/NamesResolver.js +4 -8
  119. package/build/vendor/chrome-devtools-frontend/front_end/models/stack_trace/StackTrace.js +30 -1
  120. package/build/vendor/chrome-devtools-frontend/front_end/models/stack_trace/StackTraceImpl.js +70 -1
  121. package/build/vendor/chrome-devtools-frontend/front_end/models/stack_trace/StackTraceModel.js +82 -30
  122. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/EventsSerializer.js +10 -2
  123. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/LanternComputationData.js +2 -2
  124. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/ModelImpl.js +0 -3
  125. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/Processor.js +18 -19
  126. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/Styles.js +12 -4
  127. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/extras/Initiators.js +46 -0
  128. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/extras/TraceTree.js +4 -3
  129. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/extras/extras.js +1 -0
  130. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/handlers/LargestImagePaintHandler.js +2 -2
  131. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/handlers/LayoutShiftsHandler.js +1 -1
  132. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/handlers/MetaHandler.js +6 -0
  133. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/handlers/NetworkRequestsHandler.js +10 -1
  134. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/handlers/PageLoadMetricsHandler.js +44 -27
  135. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/helpers/Timing.js +9 -2
  136. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/insights/Common.js +1 -6
  137. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/insights/LCPBreakdown.js +2 -2
  138. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/insights/LCPDiscovery.js +2 -4
  139. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/insights/NetworkDependencyTree.js +3 -2
  140. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/insights/RenderBlocking.js +1 -1
  141. package/build/vendor/chrome-devtools-frontend/front_end/models/trace/types/TraceEvents.js +33 -11
  142. package/build/vendor/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.js +51 -18
  143. package/build/vendor/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/encode/encoder.js +1 -1
  144. package/build/vendor/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/scopes.js +4 -0
  145. package/build/vendor/chrome-devtools-frontend/mcp/HostBindings.js +4 -0
  146. package/build/vendor/chrome-devtools-frontend/mcp/mcp.js +4 -0
  147. package/package.json +28 -21
  148. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/SameSiteInvalidSameParty.md +0 -8
  149. package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/SameSiteSamePartyCrossPartyContextSet.md +0 -10
@@ -3,19 +3,26 @@
3
3
  * Copyright 2025 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
+ import { readFile } from 'node:fs/promises';
6
7
  import { zod } from '../third_party/index.js';
7
8
  import { ToolCategory } from './categories.js';
8
9
  import { defineTool } from './ToolDefinition.js';
9
10
  export const evaluateScript = defineTool({
10
11
  name: 'evaluate_script',
11
- description: `Evaluate a JavaScript function inside the currently selected page. Returns the response as JSON
12
- so returned values have to JSON-serializable.`,
12
+ description: `Evaluate a JavaScript function or inject a script file into the currently selected page.
13
+
14
+ When \`function\` is provided, evaluates it and returns the result as JSON (values must be JSON-serializable).
15
+ When \`filePath\` is provided, reads the file from disk and injects it as a <script> tag (useful for large scripts like polyfills that are too big to pass inline).
16
+ Exactly one of \`function\` or \`filePath\` must be provided.`,
13
17
  annotations: {
14
18
  category: ToolCategory.DEBUGGING,
15
19
  readOnlyHint: false,
16
20
  },
17
21
  schema: {
18
- function: zod.string().describe(`A JavaScript function declaration to be executed by the tool in the currently selected page.
22
+ function: zod
23
+ .string()
24
+ .optional()
25
+ .describe(`A JavaScript function declaration to be executed by the tool in the currently selected page.
19
26
  Example without arguments: \`() => {
20
27
  return document.title
21
28
  }\` or \`async () => {
@@ -25,6 +32,10 @@ Example with arguments: \`(el) => {
25
32
  return el.innerText;
26
33
  }\`
27
34
  `),
35
+ filePath: zod
36
+ .string()
37
+ .optional()
38
+ .describe('Absolute path to a local JavaScript file to inject into the page via <script> tag. The file is read server-side so there are no size limits or CSP/mixed-content restrictions. Use this for large scripts (polyfills, bundled tools, etc).'),
28
39
  args: zod
29
40
  .array(zod.object({
30
41
  uid: zod
@@ -32,9 +43,25 @@ Example with arguments: \`(el) => {
32
43
  .describe('The uid of an element on the page from the page content snapshot'),
33
44
  }))
34
45
  .optional()
35
- .describe(`An optional list of arguments to pass to the function.`),
46
+ .describe(`An optional list of arguments to pass to the function. Only used with \`function\`, not \`filePath\`.`),
36
47
  },
37
48
  handler: async (request, response, context) => {
49
+ const { filePath } = request.params;
50
+ // File injection mode
51
+ if (filePath) {
52
+ if (request.params.function) {
53
+ throw new Error('Provide either `function` or `filePath`, not both.');
54
+ }
55
+ const content = await readFile(filePath, 'utf-8');
56
+ const page = context.getSelectedPage();
57
+ await page.addScriptTag({ content });
58
+ response.appendResponseLine(`Injected script from \`${filePath}\` (${content.length} bytes) into page.`);
59
+ return;
60
+ }
61
+ // Function evaluation mode (original behavior)
62
+ if (!request.params.function) {
63
+ throw new Error('Either `function` or `filePath` must be provided.');
64
+ }
38
65
  const args = [];
39
66
  try {
40
67
  const frames = new Set();
@@ -3,8 +3,10 @@
3
3
  * Copyright 2025 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
+ import * as browserTools from './browser.js';
6
7
  import * as consoleTools from './console.js';
7
8
  import * as emulationTools from './emulation.js';
9
+ import * as extensionTools from './extension.js';
8
10
  import * as inputTools from './input.js';
9
11
  import * as networkTools from './network.js';
10
12
  import * as pagesTools from './pages.js';
@@ -14,8 +16,10 @@ import * as scriptTools from './script.js';
14
16
  import * as snapshotTools from './snapshot.js';
15
17
  import * as webmcpTools from './webmcp.js';
16
18
  const tools = [
19
+ ...Object.values(browserTools),
17
20
  ...Object.values(consoleTools),
18
21
  ...Object.values(emulationTools),
22
+ ...Object.values(extensionTools),
19
23
  ...Object.values(inputTools),
20
24
  ...Object.values(networkTools),
21
25
  ...Object.values(pagesTools),
@@ -0,0 +1,184 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ /**
7
+ * MCP Client Transport that connects to an extension's MCP server via CDP.
8
+ *
9
+ * Discovers the extension's service worker, attaches to it, and establishes
10
+ * a bidirectional message channel using Runtime.evaluate (client → server)
11
+ * and Runtime.addBinding (server → client).
12
+ *
13
+ * CDP attaches ONLY to the service worker. Pages remain clean with no
14
+ * navigator.webdriver flag, bypassing bot detection entirely.
15
+ */
16
+ export class CDPClientTransport {
17
+ #browser;
18
+ #extensionId;
19
+ #connectTimeout;
20
+ #session = null;
21
+ #started = false;
22
+ #closed = false;
23
+ #bindingHandler = null;
24
+ #disconnectHandler = null;
25
+ onclose;
26
+ onerror;
27
+ onmessage;
28
+ constructor(options) {
29
+ this.#browser = options.browser;
30
+ this.#extensionId = options.extensionId;
31
+ this.#connectTimeout = options.connectTimeout ?? 10_000;
32
+ }
33
+ async start() {
34
+ if (this.#started) {
35
+ throw new Error('CDPClientTransport already started. If using Client class, note that connect() calls start() automatically.');
36
+ }
37
+ if (this.#closed) {
38
+ throw new Error('CDPClientTransport has been closed');
39
+ }
40
+ this.#started = true;
41
+ try {
42
+ // Handle browser disconnect
43
+ this.#disconnectHandler = () => {
44
+ if (this.#closed)
45
+ return;
46
+ this.#browser = null;
47
+ this.onclose?.();
48
+ };
49
+ this.#browser.on('disconnected', this.#disconnectHandler);
50
+ // Find extension service worker target with retry
51
+ const swTarget = await this.#findExtensionServiceWorker();
52
+ if (!swTarget) {
53
+ throw new Error(this.#extensionId
54
+ ? `Extension ${this.#extensionId} service worker not found`
55
+ : 'No extension service worker found. Is an MCP-enabled extension installed?');
56
+ }
57
+ // Create a CDP session directly on the service worker target
58
+ this.#session = await swTarget.createCDPSession();
59
+ // Enable Runtime domain
60
+ await this.#session.send('Runtime.enable');
61
+ // Add binding for receiving messages from the extension
62
+ await this.#session.send('Runtime.addBinding', {
63
+ name: '__mcpCDPToClient',
64
+ });
65
+ // Set up handler for binding calls
66
+ this.#bindingHandler = (event) => {
67
+ if (event.name !== '__mcpCDPToClient')
68
+ return;
69
+ if (this.#closed)
70
+ return;
71
+ try {
72
+ const message = JSON.parse(event.payload);
73
+ this.onmessage?.(message);
74
+ }
75
+ catch (err) {
76
+ this.onerror?.(new Error(`Failed to parse message from extension: ${err}`));
77
+ }
78
+ };
79
+ this.#session.on('Runtime.bindingCalled', this.#bindingHandler);
80
+ // Verify the extension has the CDP transport ready
81
+ const { result } = await this.#session.send('Runtime.evaluate', {
82
+ expression: 'globalThis.__mcpCDPTransport?.isReady === true',
83
+ returnByValue: true,
84
+ });
85
+ if (!result.value) {
86
+ throw new Error('Extension CDP transport not ready. Ensure the extension has CDP bridge support enabled.');
87
+ }
88
+ // Connect our binding to the extension's transport
89
+ await this.#session.send('Runtime.evaluate', {
90
+ expression: `
91
+ globalThis.__mcpCDPTransport._sendBinding = (jsonStr) => {
92
+ __mcpCDPToClient(jsonStr);
93
+ };
94
+ `,
95
+ });
96
+ }
97
+ catch (err) {
98
+ this.#started = false;
99
+ await this.#cleanup();
100
+ throw err;
101
+ }
102
+ }
103
+ async send(message) {
104
+ if (!this.#started) {
105
+ throw new Error('CDPClientTransport not started');
106
+ }
107
+ if (this.#closed) {
108
+ throw new Error('CDPClientTransport has been closed');
109
+ }
110
+ if (!this.#session) {
111
+ throw new Error('CDP session not available');
112
+ }
113
+ const jsonStr = JSON.stringify(message);
114
+ try {
115
+ await this.#session.send('Runtime.evaluate', {
116
+ expression: `globalThis.__mcpCDPTransport.receiveMessage(${JSON.stringify(jsonStr)})`,
117
+ });
118
+ }
119
+ catch (err) {
120
+ const error = new Error(`Failed to send message to extension: ${err}`);
121
+ this.onerror?.(error);
122
+ throw error;
123
+ }
124
+ }
125
+ async close() {
126
+ if (this.#closed)
127
+ return;
128
+ this.#closed = true;
129
+ this.#started = false;
130
+ await this.#cleanup();
131
+ if (this.#browser && this.#disconnectHandler) {
132
+ this.#browser.off('disconnected', this.#disconnectHandler);
133
+ this.#disconnectHandler = null;
134
+ }
135
+ this.onclose?.();
136
+ }
137
+ /**
138
+ * Find the extension's service worker target with retry.
139
+ */
140
+ async #findExtensionServiceWorker() {
141
+ const deadline = Date.now() + this.#connectTimeout;
142
+ const retryInterval = 1_000;
143
+ while (Date.now() < deadline) {
144
+ const targets = this.#browser.targets();
145
+ const sw = targets.find(t => {
146
+ if (t.type() !== 'service_worker')
147
+ return false;
148
+ if (!t.url().startsWith('chrome-extension://'))
149
+ return false;
150
+ if (this.#extensionId && !t.url().includes(this.#extensionId))
151
+ return false;
152
+ return true;
153
+ });
154
+ if (sw)
155
+ return sw;
156
+ const remaining = deadline - Date.now();
157
+ if (remaining > retryInterval) {
158
+ await new Promise(resolve => setTimeout(resolve, retryInterval));
159
+ }
160
+ else {
161
+ break;
162
+ }
163
+ }
164
+ return null;
165
+ }
166
+ /**
167
+ * Clean up CDP resources.
168
+ */
169
+ async #cleanup() {
170
+ if (this.#session && this.#bindingHandler) {
171
+ this.#session.off('Runtime.bindingCalled', this.#bindingHandler);
172
+ this.#bindingHandler = null;
173
+ }
174
+ if (this.#session) {
175
+ try {
176
+ await this.#session.detach();
177
+ }
178
+ catch {
179
+ // Ignore detach errors
180
+ }
181
+ this.#session = null;
182
+ }
183
+ }
184
+ }
@@ -100,6 +100,15 @@ export const WEB_MCP_BRIDGE_SCRIPT = `
100
100
  return false;
101
101
  }
102
102
 
103
+ function getTargetOrigin() {
104
+ var origin = window.location && typeof window.location.origin === 'string'
105
+ ? window.location.origin
106
+ : '';
107
+ // about:blank, file://, and sandboxed contexts expose "null" origin.
108
+ // Use "*" so postMessage remains functional in those contexts.
109
+ return origin && origin !== 'null' ? origin : '*';
110
+ }
111
+
103
112
  // Initial check
104
113
  checkWebMCPAvailable();
105
114
 
@@ -125,7 +134,7 @@ export const WEB_MCP_BRIDGE_SCRIPT = `
125
134
  type: 'mcp',
126
135
  direction: 'client-to-server',
127
136
  payload: payload
128
- }, window.location.origin);
137
+ }, getTargetOrigin());
129
138
 
130
139
  return true;
131
140
  } catch (err) {
@@ -169,7 +178,7 @@ export const WEB_MCP_BRIDGE_SCRIPT = `
169
178
  type: 'mcp',
170
179
  direction: 'client-to-server',
171
180
  payload: 'mcp-check-ready'
172
- }, window.location.origin);
181
+ }, getTargetOrigin());
173
182
  },
174
183
 
175
184
  /**
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import fs from 'node:fs/promises';
7
+ import path from 'node:path';
8
+ export class ExtensionRegistry {
9
+ #extensions = new Map();
10
+ async registerExtension(id, extensionPath) {
11
+ const manifestPath = path.join(extensionPath, 'manifest.json');
12
+ const manifestContent = await fs.readFile(manifestPath, 'utf-8');
13
+ const manifest = JSON.parse(manifestContent);
14
+ const name = manifest.name ?? 'Unknown';
15
+ const version = manifest.version ?? 'Unknown';
16
+ const extension = {
17
+ id,
18
+ name,
19
+ version,
20
+ isEnabled: true,
21
+ path: extensionPath,
22
+ };
23
+ this.#extensions.set(extension.id, extension);
24
+ return extension;
25
+ }
26
+ remove(id) {
27
+ this.#extensions.delete(id);
28
+ }
29
+ list() {
30
+ return Array.from(this.#extensions.values());
31
+ }
32
+ getById(id) {
33
+ return this.#extensions.get(id);
34
+ }
35
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ /**
7
+ * Converts a given string to snake_case.
8
+ * This function handles camelCase, PascalCase, and acronyms, including transitions between letters and numbers.
9
+ * It uses Unicode-aware regular expressions (`\p{L}`, `\p{N}`, `\p{Lu}`, `\p{Ll}` with the `u` flag)
10
+ * to correctly process letters and numbers from various languages.
11
+ *
12
+ * @param text The input string to convert to snake_case.
13
+ * @returns The snake_case version of the input string.
14
+ */
15
+ export function toSnakeCase(text) {
16
+ if (!text) {
17
+ return '';
18
+ }
19
+ // First, handle case-based transformations to insert underscores correctly.
20
+ // 1. Add underscore between a letter and a number.
21
+ // e.g., "version2" -> "version_2"
22
+ // 2. Add underscore between an uppercase letter sequence and a following uppercase+lowercase sequence.
23
+ // e.g., "APIFlags" -> "API_Flags"
24
+ // 3. Add underscore between a lowercase/number and an uppercase letter.
25
+ // e.g., "lastName" -> "last_Name", "version_2Update" -> "version_2_Update"
26
+ // 4. Replace sequences of non-alphanumeric with a single underscore
27
+ // 5. Remove any leading or trailing underscores.
28
+ const result = text
29
+ .replace(/(\p{L})(\p{N})/gu, '$1_$2') // 1
30
+ .replace(/(\p{Lu}+)(\p{Lu}\p{Ll})/gu, '$1_$2') // 2
31
+ .replace(/(\p{Ll}|\p{N})(\p{Lu})/gu, '$1_$2') // 3
32
+ .toLowerCase()
33
+ .replace(/[^\p{L}\p{N}]+/gu, '_') // 4
34
+ .replace(/^_|_$/g, ''); // 5
35
+ return result;
36
+ }
@@ -29,13 +29,31 @@ export function decode(input) {
29
29
  }
30
30
  return bytes;
31
31
  }
32
+ /**
33
+ * Note: if input can be very large (larger than the max string size), callers should
34
+ * expect this to throw an error.
35
+ */
32
36
  export function encode(input) {
33
37
  return new Promise((resolve, reject) => {
34
38
  const reader = new FileReader();
35
- reader.onerror = () => reject(new Error('failed to convert to base64'));
39
+ reader.onerror = () => reject(new Error('failed to convert to base64: internal error'));
36
40
  reader.onload = () => {
41
+ // The input was too large to encode as a string. The caller should anticipate
42
+ // this and use a workaround. See TimelinePanel.ts innerSaveToFile for an example.
43
+ // For more information, see crbug.com/436482118.
44
+ if (reader.result === '') {
45
+ reject(new Error('failed to convert to base64: input too large to encode as base64 string'));
46
+ return;
47
+ }
48
+ // This string can be very large, so take care to not double memory. `split`
49
+ // was used here before, which always results in new strings in V8. By using
50
+ // slice instead, we leverage the sliced string optimization in V8 and avoid
51
+ // doubling the memory requirement (even if temporarily: that is a potential
52
+ // source of OOM crashes given large enough input, such as is common with
53
+ // Performance traces).
37
54
  const blobAsUrl = reader.result;
38
- const [, base64] = blobAsUrl.split(',', 2);
55
+ const index = blobAsUrl.indexOf(',');
56
+ const base64 = blobAsUrl.slice(index + 1);
39
57
  resolve(base64);
40
58
  };
41
59
  reader.readAsDataURL(new Blob([input]));
@@ -8,7 +8,14 @@ export const debounce = function (func, delay) {
8
8
  let timer;
9
9
  const debounced = (...args) => {
10
10
  clearTimeout(timer);
11
- timer = setTimeout(() => func(...args), delay);
11
+ timer = setTimeout(() => func(...args), testDebounceOverride ? 0 : delay);
12
12
  };
13
13
  return debounced;
14
14
  };
15
+ let testDebounceOverride = false;
16
+ export function enableTestOverride() {
17
+ testDebounceOverride = true;
18
+ }
19
+ export function disableTestOverride() {
20
+ testDebounceOverride = false;
21
+ }
@@ -64,3 +64,14 @@ export function compressStream(stream) {
64
64
  const cs = new CompressionStream('gzip');
65
65
  return stream.pipeThrough(cs);
66
66
  }
67
+ export function createMonitoredStream(stream, onProgress) {
68
+ let bytesRead = 0;
69
+ const progressTransformer = new TransformStream({
70
+ transform(chunk, controller) {
71
+ bytesRead += chunk.byteLength;
72
+ onProgress(bytesRead);
73
+ controller.enqueue(chunk);
74
+ }
75
+ });
76
+ return stream.pipeThrough(progressTransformer);
77
+ }
@@ -57,7 +57,12 @@ export class ObjectWrapper {
57
57
  // new listeners.
58
58
  for (const listener of [...listeners]) {
59
59
  if (!listener.disposed) {
60
- listener.listener.call(listener.thisObject, event);
60
+ try {
61
+ listener.listener.call(listener.thisObject, event);
62
+ }
63
+ catch (err) {
64
+ console.error(`Event listener for ${String(eventType)} throw an error:`, err);
65
+ }
61
66
  }
62
67
  }
63
68
  }
@@ -263,6 +263,9 @@ export class ParsedURL {
263
263
  return '';
264
264
  }
265
265
  static extractName(url) {
266
+ if (url.endsWith('/')) {
267
+ url = url.slice(0, -1);
268
+ }
266
269
  let index = url.lastIndexOf('/');
267
270
  const pathAndQuery = index !== -1 ? url.substr(index + 1) : url;
268
271
  index = pathAndQuery.indexOf('?');
@@ -229,6 +229,12 @@ export class ResourceType {
229
229
  const regex = new RegExp('^application(.*json$|\/json\+.*)');
230
230
  return regex.test(contentType) ? 'application/json' : contentType;
231
231
  }
232
+ /**
233
+ * Checks whether the given MIME type represents JavaScript content.
234
+ */
235
+ static isJavaScriptMimeType(mimeType) {
236
+ return mimeType === 'application/javascript' || mimeType === 'text/javascript';
237
+ }
232
238
  /**
233
239
  * Adds suffixes iff the mimeType is 'text/javascript' to denote whether the JS is minified or from
234
240
  * a source map.
@@ -31,10 +31,6 @@ const UIStrings = {
31
31
  * @description The UI destination when right clicking an item that can be revealed
32
32
  */
33
33
  applicationPanel: 'Application panel',
34
- /**
35
- * @description The UI destination when right clicking an item that can be revealed
36
- */
37
- securityPanel: 'Security panel',
38
34
  /**
39
35
  * @description The UI destination when right clicking an item that can be revealed
40
36
  */
@@ -158,7 +154,6 @@ export const RevealerDestination = {
158
154
  TIMELINE_PANEL: i18nLazyString(UIStrings.timelinePanel),
159
155
  APPLICATION_PANEL: i18nLazyString(UIStrings.applicationPanel),
160
156
  SOURCES_PANEL: i18nLazyString(UIStrings.sourcesPanel),
161
- SECURITY_PANEL: i18nLazyString(UIStrings.securityPanel),
162
157
  MEMORY_INSPECTOR_PANEL: i18nLazyString(UIStrings.memoryInspectorPanel),
163
158
  ANIMATIONS_PANEL: i18nLazyString(UIStrings.animationsPanel),
164
159
  };
@@ -162,18 +162,28 @@ export class Settings {
162
162
  return this.#registry;
163
163
  }
164
164
  }
165
- export const NOOP_STORAGE = {
166
- register: () => { },
167
- set: () => { },
168
- get: () => Promise.resolve(''),
169
- remove: () => { },
170
- clear: () => { },
171
- };
165
+ export class InMemoryStorage {
166
+ #store = new Map();
167
+ register(_setting) {
168
+ }
169
+ set(key, value) {
170
+ this.#store.set(key, value);
171
+ }
172
+ get(key) {
173
+ return this.#store.get(key);
174
+ }
175
+ remove(key) {
176
+ this.#store.delete(key);
177
+ }
178
+ clear() {
179
+ this.#store.clear();
180
+ }
181
+ }
172
182
  export class SettingsStorage {
173
183
  object;
174
184
  backingStore;
175
185
  storagePrefix;
176
- constructor(object, backingStore = NOOP_STORAGE, storagePrefix = '') {
186
+ constructor(object, backingStore = new InMemoryStorage(), storagePrefix = '') {
177
187
  this.object = object;
178
188
  this.backingStore = backingStore;
179
189
  this.storagePrefix = storagePrefix;
@@ -194,6 +194,12 @@ export class AidaClient {
194
194
  if (!InspectorFrontendHostInstance.doAidaConversation) {
195
195
  throw new Error('doAidaConversation is not available');
196
196
  }
197
+ // Disable logging for now.
198
+ // For context, see b/454563259#comment35.
199
+ // We should be able to remove this ~end of April.
200
+ if (Root.Runtime.hostConfig.devToolsGeminiRebranding?.enabled) {
201
+ request.metadata.disable_user_content_logging = true;
202
+ }
197
203
  const stream = (() => {
198
204
  let { promise, resolve, reject } = Promise.withResolvers();
199
205
  options?.signal?.addEventListener('abort', () => {
@@ -316,6 +322,12 @@ export class AidaClient {
316
322
  };
317
323
  }
318
324
  registerClientEvent(clientEvent) {
325
+ // Disable logging for now.
326
+ // For context, see b/454563259#comment35.
327
+ // We should be able to remove this ~end of April.
328
+ if (Root.Runtime.hostConfig.devToolsGeminiRebranding?.enabled) {
329
+ clientEvent.disable_user_content_logging = true;
330
+ }
319
331
  const { promise, resolve } = Promise.withResolvers();
320
332
  InspectorFrontendHostInstance.registerAidaClientEvent(JSON.stringify({
321
333
  client: CLIENT_NAME,
@@ -328,6 +340,12 @@ export class AidaClient {
328
340
  if (!InspectorFrontendHostInstance.aidaCodeComplete) {
329
341
  throw new Error('aidaCodeComplete is not available');
330
342
  }
343
+ // Disable logging for now.
344
+ // For context, see b/454563259#comment35.
345
+ // We should be able to remove this ~end of April.
346
+ if (Root.Runtime.hostConfig.devToolsGeminiRebranding?.enabled) {
347
+ request.metadata.disable_user_content_logging = true;
348
+ }
331
349
  const { promise, resolve } = Promise.withResolvers();
332
350
  InspectorFrontendHostInstance.aidaCodeComplete(JSON.stringify(request), resolve);
333
351
  const completeCodeResult = await promise;
@@ -369,6 +387,12 @@ export class AidaClient {
369
387
  return { generatedSamples, metadata };
370
388
  }
371
389
  async generateCode(request, options) {
390
+ // Disable logging for now.
391
+ // For context, see b/454563259#comment35.
392
+ // We should be able to remove this ~end of April.
393
+ if (Root.Runtime.hostConfig.devToolsGeminiRebranding?.enabled) {
394
+ request.metadata.disable_user_content_logging = true;
395
+ }
372
396
  const response = await DispatchHttpRequestClient.makeHttpRequest({
373
397
  service: SERVICE_NAME,
374
398
  path: '/v1/aida:generateCode',
@@ -87,6 +87,9 @@ export class InspectorFrontendHostStub {
87
87
  setInjectedScriptForOrigin(_origin, _script) {
88
88
  }
89
89
  inspectedURLChanged(url) {
90
+ if (!('document' in globalThis)) {
91
+ return;
92
+ }
90
93
  document.title = i18nString(UIStrings.devtoolsS, { PH1: url.replace(/^https?:\/\//, '') });
91
94
  }
92
95
  copyText(text) {
@@ -175,6 +178,12 @@ export class InspectorFrontendHostStub {
175
178
  }
176
179
  this.recordedPerformanceHistograms.push({ histogramName, duration });
177
180
  }
181
+ recordPerformanceHistogramMedium(histogramName, duration) {
182
+ if (this.recordedPerformanceHistograms.length >= MAX_RECORDED_HISTOGRAMS_SIZE) {
183
+ this.recordedPerformanceHistograms.shift();
184
+ }
185
+ this.recordedPerformanceHistograms.push({ histogramName, duration });
186
+ }
178
187
  recordUserMetricsAction(_umaName) {
179
188
  }
180
189
  recordNewBadgeUsage(_featureName) {
@@ -297,9 +306,6 @@ export class InspectorFrontendHostStub {
297
306
  devToolsFlexibleLayout: {
298
307
  verticalDrawerEnabled: true,
299
308
  },
300
- devToolsStartingStyleDebugging: {
301
- enabled: false,
302
- },
303
309
  };
304
310
  if ('hostConfigForTesting' in globalThis) {
305
311
  const { hostConfigForTesting } = globalThis;
@@ -427,4 +433,6 @@ export class InspectorFrontendHostStub {
427
433
  }
428
434
  recordFunctionCall(_event) {
429
435
  }
436
+ setChromeFlag(_flagName, _value) {
437
+ }
430
438
  }
@@ -169,7 +169,7 @@ async function fetchToString(url) {
169
169
  }
170
170
  function canBeRemoteFilePath(url) {
171
171
  try {
172
- const urlObject = new URL(url);
172
+ const urlObject = new URL(new URL(url).toString()); // Normalize first.
173
173
  return urlObject.protocol === 'file:' && urlObject.host !== '';
174
174
  }
175
175
  catch {