chrome-devtools-mcp 0.12.1 → 0.14.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 (40) hide show
  1. package/README.md +52 -25
  2. package/build/src/DevtoolsUtils.js +59 -42
  3. package/build/src/McpContext.js +106 -18
  4. package/build/src/McpResponse.js +240 -146
  5. package/build/src/browser.js +4 -2
  6. package/build/src/cli.js +39 -3
  7. package/build/src/formatters/ConsoleFormatter.js +153 -0
  8. package/build/src/formatters/IssueFormatter.js +190 -0
  9. package/build/src/formatters/NetworkFormatter.js +226 -0
  10. package/build/src/formatters/SnapshotFormatter.js +134 -0
  11. package/build/src/logger.js +9 -0
  12. package/build/src/main.js +51 -7
  13. package/build/src/telemetry/clearcut-logger.js +99 -0
  14. package/build/src/telemetry/flag-utils.js +45 -0
  15. package/build/src/telemetry/metric-utils.js +14 -0
  16. package/build/src/telemetry/persistence.js +53 -0
  17. package/build/src/telemetry/types.js +33 -0
  18. package/build/src/telemetry/watchdog/clearcut-sender.js +48 -0
  19. package/build/src/telemetry/watchdog/main.js +98 -0
  20. package/build/src/telemetry/watchdog-client.js +51 -0
  21. package/build/src/third_party/THIRD_PARTY_NOTICES +0 -1417
  22. package/build/src/third_party/devtools-formatter-worker.js +15451 -0
  23. package/build/src/third_party/index.js +2452 -991
  24. package/build/src/tools/categories.js +2 -0
  25. package/build/src/tools/emulation.js +62 -1
  26. package/build/src/tools/extensions.js +79 -0
  27. package/build/src/tools/input.js +88 -12
  28. package/build/src/tools/network.js +17 -3
  29. package/build/src/tools/pages.js +117 -53
  30. package/build/src/tools/performance.js +38 -27
  31. package/build/src/tools/tools.js +2 -0
  32. package/build/src/utils/ExtensionRegistry.js +35 -0
  33. package/build/src/utils/string.js +36 -0
  34. package/package.json +15 -14
  35. package/build/src/formatters/consoleFormatter.js +0 -121
  36. package/build/src/formatters/networkFormatter.js +0 -77
  37. package/build/src/formatters/snapshotFormatter.js +0 -73
  38. package/build/src/third_party/devtools.js +0 -6
  39. package/build/src/third_party/issue-descriptions/SameSiteInvalidSameParty.md +0 -8
  40. package/build/src/third_party/issue-descriptions/SameSiteSamePartyCrossPartyContextSet.md +0 -10
@@ -3,25 +3,30 @@
3
3
  * Copyright 2025 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- import { logger } from '../logger.js';
6
+ import zlib from 'node:zlib';
7
7
  import { zod } from '../third_party/index.js';
8
- import { getInsightOutput, getTraceSummary, parseRawTraceBuffer, traceResultIsSuccess, } from '../trace-processing/parse.js';
8
+ import { parseRawTraceBuffer, traceResultIsSuccess, } from '../trace-processing/parse.js';
9
9
  import { ToolCategory } from './categories.js';
10
10
  import { defineTool } from './ToolDefinition.js';
11
+ const filePathSchema = zod
12
+ .string()
13
+ .optional()
14
+ .describe('The absolute file path, or a file path relative to the current working directory, to save the raw trace data. For example, trace.json.gz (compressed) or trace.json (uncompressed).');
11
15
  export const startTrace = defineTool({
12
16
  name: 'performance_start_trace',
13
- description: 'Starts a performance trace recording on the selected page. This can be used to look for performance problems and insights to improve the performance of the page. It will also report Core Web Vital (CWV) scores for the page.',
17
+ description: `Starts a performance trace recording on the selected page. This can be used to look for performance problems and insights to improve the performance of the page. It will also report Core Web Vital (CWV) scores for the page.`,
14
18
  annotations: {
15
19
  category: ToolCategory.PERFORMANCE,
16
- readOnlyHint: true,
20
+ readOnlyHint: false,
17
21
  },
18
22
  schema: {
19
23
  reload: zod
20
24
  .boolean()
21
- .describe('Determines if, once tracing has started, the page should be automatically reloaded.'),
25
+ .describe('Determines if, once tracing has started, the current selected page should be automatically reloaded. Navigate the page to the right URL using the navigate_page tool BEFORE starting the trace if reload or autoStop is set to true.'),
22
26
  autoStop: zod
23
27
  .boolean()
24
28
  .describe('Determines if the trace recording should be automatically stopped.'),
29
+ filePath: filePathSchema,
25
30
  },
26
31
  handler: async (request, response, context) => {
27
32
  if (context.isRunningPerformanceTrace()) {
@@ -68,7 +73,7 @@ export const startTrace = defineTool({
68
73
  }
69
74
  if (request.params.autoStop) {
70
75
  await new Promise(resolve => setTimeout(resolve, 5_000));
71
- await stopTracingAndAppendOutput(page, response, context);
76
+ await stopTracingAndAppendOutput(page, response, context, request.params.filePath);
72
77
  }
73
78
  else {
74
79
  response.appendResponseLine(`The performance trace is being recorded. Use performance_stop_trace to stop it.`);
@@ -80,15 +85,17 @@ export const stopTrace = defineTool({
80
85
  description: 'Stops the active performance trace recording on the selected page.',
81
86
  annotations: {
82
87
  category: ToolCategory.PERFORMANCE,
83
- readOnlyHint: true,
88
+ readOnlyHint: false,
84
89
  },
85
- schema: {},
86
- handler: async (_request, response, context) => {
90
+ schema: {
91
+ filePath: filePathSchema,
92
+ },
93
+ handler: async (request, response, context) => {
87
94
  if (!context.isRunningPerformanceTrace()) {
88
95
  return;
89
96
  }
90
97
  const page = context.getSelectedPage();
91
- await stopTracingAndAppendOutput(page, response, context);
98
+ await stopTracingAndAppendOutput(page, response, context, request.params.filePath);
92
99
  },
93
100
  });
94
101
  export const analyzeInsight = defineTool({
@@ -112,35 +119,39 @@ export const analyzeInsight = defineTool({
112
119
  response.appendResponseLine('No recorded traces found. Record a performance trace so you have Insights to analyze.');
113
120
  return;
114
121
  }
115
- const insightOutput = getInsightOutput(lastRecording, request.params.insightSetId, request.params.insightName);
116
- if ('error' in insightOutput) {
117
- response.appendResponseLine(insightOutput.error);
118
- return;
119
- }
120
- response.appendResponseLine(insightOutput.output);
122
+ response.attachTraceInsight(lastRecording, request.params.insightSetId, request.params.insightName);
121
123
  },
122
124
  });
123
- async function stopTracingAndAppendOutput(page, response, context) {
125
+ async function stopTracingAndAppendOutput(page, response, context, filePath) {
124
126
  try {
125
127
  const traceEventsBuffer = await page.tracing.stop();
128
+ if (filePath && traceEventsBuffer) {
129
+ let dataToWrite = traceEventsBuffer;
130
+ if (filePath.endsWith('.gz')) {
131
+ dataToWrite = await new Promise((resolve, reject) => {
132
+ zlib.gzip(traceEventsBuffer, (error, result) => {
133
+ if (error) {
134
+ reject(error);
135
+ }
136
+ else {
137
+ resolve(result);
138
+ }
139
+ });
140
+ });
141
+ }
142
+ const file = await context.saveFile(dataToWrite, filePath);
143
+ response.appendResponseLine(`The raw trace data was saved to ${file.filename}.`);
144
+ }
126
145
  const result = await parseRawTraceBuffer(traceEventsBuffer);
127
146
  response.appendResponseLine('The performance trace has been stopped.');
128
147
  if (traceResultIsSuccess(result)) {
129
148
  context.storeTraceRecording(result);
130
- const traceSummaryText = getTraceSummary(result);
131
- response.appendResponseLine(traceSummaryText);
149
+ response.attachTraceSummary(result);
132
150
  }
133
151
  else {
134
- response.appendResponseLine('There was an unexpected error parsing the trace:');
135
- response.appendResponseLine(result.error);
152
+ throw new Error(`There was an unexpected error parsing the trace: ${result.error}`);
136
153
  }
137
154
  }
138
- catch (e) {
139
- const errorText = e instanceof Error ? e.message : JSON.stringify(e);
140
- logger(`Error stopping performance trace: ${errorText}`);
141
- response.appendResponseLine('An error occurred generating the response for this trace:');
142
- response.appendResponseLine(errorText);
143
- }
144
155
  finally {
145
156
  context.setIsRunningPerformanceTrace(false);
146
157
  }
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import * as consoleTools from './console.js';
7
7
  import * as emulationTools from './emulation.js';
8
+ import * as extensionTools from './extensions.js';
8
9
  import * as inputTools from './input.js';
9
10
  import * as networkTools from './network.js';
10
11
  import * as pagesTools from './pages.js';
@@ -15,6 +16,7 @@ import * as snapshotTools from './snapshot.js';
15
16
  const tools = [
16
17
  ...Object.values(consoleTools),
17
18
  ...Object.values(emulationTools),
19
+ ...Object.values(extensionTools),
18
20
  ...Object.values(inputTools),
19
21
  ...Object.values(networkTools),
20
22
  ...Object.values(pagesTools),
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-devtools-mcp",
3
- "version": "0.12.1",
3
+ "version": "0.14.0",
4
4
  "description": "MCP server for Chrome DevTools",
5
5
  "type": "module",
6
6
  "bin": "./build/src/index.js",
@@ -16,14 +16,14 @@
16
16
  "docs:generate": "node --experimental-strip-types scripts/generate-docs.ts",
17
17
  "start": "npm run build && node build/src/index.js",
18
18
  "start-debug": "DEBUG=mcp:* DEBUG_COLORS=false npm run build && node build/src/index.js",
19
- "test:node20": "node --import ./build/tests/setup.js --test-reporter spec --test-force-exit --test build/tests",
20
- "test:no-build": "node --import ./build/tests/setup.js --no-warnings=ExperimentalWarning --experimental-print-required-tla --test-reporter spec --test-force-exit --test \"build/tests/**/*.test.js\"",
21
- "test": "npm run build && npm run test:no-build",
22
- "test:only": "npm run build && npm run test:only:no-build",
23
- "test:only:no-build": "node --import ./build/tests/setup.js --no-warnings=ExperimentalWarning --test-reporter spec --test-force-exit --test --test-only \"build/tests/**/*.test.js\"",
24
- "test:update-snapshots": "npm run build && node --import ./build/tests/setup.js --no-warnings=ExperimentalWarning --test-force-exit --test --test-update-snapshots \"build/tests/**/*.test.js\"",
19
+ "test": "npm run build && node scripts/test.mjs",
20
+ "test:no-build": "node scripts/test.mjs",
21
+ "test:only": "npm run build && node scripts/test.mjs --test-only",
22
+ "test:update-snapshots": "npm run build && node scripts/test.mjs --test-update-snapshots",
25
23
  "prepare": "node --experimental-strip-types scripts/prepare.ts",
26
- "verify-server-json-version": "node --experimental-strip-types scripts/verify-server-json-version.ts"
24
+ "verify-server-json-version": "node --experimental-strip-types scripts/verify-server-json-version.ts",
25
+ "eval": "npm run build && node --experimental-strip-types scripts/eval_gemini.ts",
26
+ "count-tokens": "node --experimental-strip-types scripts/count_tokens.ts"
27
27
  },
28
28
  "files": [
29
29
  "build/src",
@@ -41,7 +41,8 @@
41
41
  "mcpName": "io.github.ChromeDevTools/chrome-devtools-mcp",
42
42
  "devDependencies": {
43
43
  "@eslint/js": "^9.35.0",
44
- "@modelcontextprotocol/sdk": "1.24.3",
44
+ "@google/genai": "^1.37.0",
45
+ "@modelcontextprotocol/sdk": "1.25.3",
45
46
  "@rollup/plugin-commonjs": "^29.0.0",
46
47
  "@rollup/plugin-json": "^6.1.0",
47
48
  "@rollup/plugin-node-resolve": "^16.0.3",
@@ -53,16 +54,16 @@
53
54
  "@types/yargs": "^17.0.33",
54
55
  "@typescript-eslint/eslint-plugin": "^8.43.0",
55
56
  "@typescript-eslint/parser": "^8.43.0",
56
- "chrome-devtools-frontend": "1.0.1555430",
57
- "core-js": "3.47.0",
57
+ "chrome-devtools-frontend": "1.0.1573331",
58
+ "core-js": "3.48.0",
58
59
  "debug": "4.4.3",
59
60
  "eslint": "^9.35.0",
60
61
  "eslint-import-resolver-typescript": "^4.4.4",
61
62
  "eslint-plugin-import": "^2.32.0",
62
- "globals": "^16.4.0",
63
+ "globals": "^17.0.0",
63
64
  "prettier": "^3.6.2",
64
- "puppeteer": "24.33.0",
65
- "rollup": "4.53.3",
65
+ "puppeteer": "24.36.0",
66
+ "rollup": "4.56.0",
66
67
  "rollup-plugin-cleanup": "^3.2.1",
67
68
  "rollup-plugin-license": "^3.6.0",
68
69
  "sinon": "^21.0.0",
@@ -1,121 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2025 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
- // The short format for a console message, based on a previous format.
7
- export function formatConsoleEventShort(msg) {
8
- if (msg.type === 'issue') {
9
- return `msgid=${msg.consoleMessageStableId} [${msg.type}] ${msg.message} (count: ${msg.count})`;
10
- }
11
- return `msgid=${msg.consoleMessageStableId} [${msg.type}] ${msg.message} (${msg.args?.length ?? 0} args)`;
12
- }
13
- function getArgs(msg) {
14
- const args = [...(msg.args ?? [])];
15
- // If there is no text, the first argument serves as text (see formatMessage).
16
- if (!msg.message) {
17
- args.shift();
18
- }
19
- return args;
20
- }
21
- // The verbose format for a console message, including all details.
22
- export function formatConsoleEventVerbose(msg, context) {
23
- const aggregatedIssue = msg.item;
24
- const result = [
25
- `ID: ${msg.consoleMessageStableId}`,
26
- `Message: ${msg.type}> ${aggregatedIssue ? formatIssue(aggregatedIssue, msg.description, context) : msg.message}`,
27
- aggregatedIssue ? undefined : formatArgs(msg),
28
- ].filter(line => !!line);
29
- return result.join('\n');
30
- }
31
- function formatArg(arg) {
32
- return typeof arg === 'object' ? JSON.stringify(arg) : String(arg);
33
- }
34
- function formatArgs(consoleData) {
35
- const args = getArgs(consoleData);
36
- if (!args.length) {
37
- return '';
38
- }
39
- const result = ['### Arguments'];
40
- for (const [key, arg] of args.entries()) {
41
- result.push(`Arg #${key}: ${formatArg(arg)}`);
42
- }
43
- return result.join('\n');
44
- }
45
- export function formatIssue(issue, description, context) {
46
- const result = [];
47
- let processedMarkdown = description?.trim();
48
- // Remove heading in order not to conflict with the whole console message response markdown
49
- if (processedMarkdown?.startsWith('# ')) {
50
- processedMarkdown = processedMarkdown.substring(2).trimStart();
51
- }
52
- if (processedMarkdown)
53
- result.push(processedMarkdown);
54
- const links = issue.getDescription()?.links;
55
- if (links && links.length > 0) {
56
- result.push('Learn more:');
57
- for (const link of links) {
58
- result.push(`[${link.linkTitle}](${link.link})`);
59
- }
60
- }
61
- const issues = issue.getAllIssues();
62
- const affectedResources = [];
63
- for (const singleIssue of issues) {
64
- const details = singleIssue.details();
65
- if (!details)
66
- continue;
67
- // We send the remaining details as untyped JSON because the DevTools
68
- // frontend code is currently not re-usable.
69
- // eslint-disable-next-line
70
- const data = structuredClone(details);
71
- let uid;
72
- let request;
73
- if ('violatingNodeId' in details && details.violatingNodeId && context) {
74
- uid = context.resolveCdpElementId(details.violatingNodeId);
75
- delete data.violatingNodeId;
76
- }
77
- if ('nodeId' in details && details.nodeId && context) {
78
- uid = context.resolveCdpElementId(details.nodeId);
79
- delete data.nodeId;
80
- }
81
- if ('documentNodeId' in details && details.documentNodeId && context) {
82
- uid = context.resolveCdpElementId(details.documentNodeId);
83
- delete data.documentNodeId;
84
- }
85
- if ('request' in details && details.request) {
86
- request = details.request.url;
87
- if (details.request.requestId && context) {
88
- const resolvedId = context.resolveCdpRequestId(details.request.requestId);
89
- if (resolvedId) {
90
- request = resolvedId;
91
- delete data.request.requestId;
92
- }
93
- }
94
- }
95
- // These fields has no use for the MCP client (redundant or irrelevant).
96
- delete data.errorType;
97
- delete data.frameId;
98
- affectedResources.push({
99
- uid,
100
- data: data,
101
- request,
102
- });
103
- }
104
- if (affectedResources.length) {
105
- result.push('### Affected resources');
106
- }
107
- result.push(...affectedResources.map(item => {
108
- const details = [];
109
- if (item.uid)
110
- details.push(`uid=${item.uid}`);
111
- if (item.request) {
112
- details.push((typeof item.request === 'number' ? `reqid=` : 'url=') + item.request);
113
- }
114
- if (item.data)
115
- details.push(`data=${JSON.stringify(item.data)}`);
116
- return details.join(' ');
117
- }));
118
- if (result.length === 0)
119
- return 'No affected resources found';
120
- return result.join('\n');
121
- }
@@ -1,77 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2025 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
- import { isUtf8 } from 'node:buffer';
7
- const BODY_CONTEXT_SIZE_LIMIT = 10000;
8
- export function getShortDescriptionForRequest(request, id, selectedInDevToolsUI = false) {
9
- // TODO truncate the URL
10
- return `reqid=${id} ${request.method()} ${request.url()} ${getStatusFromRequest(request)}${selectedInDevToolsUI ? ` [selected in the DevTools Network panel]` : ''}`;
11
- }
12
- export function getStatusFromRequest(request) {
13
- const httpResponse = request.response();
14
- const failure = request.failure();
15
- let status;
16
- if (httpResponse) {
17
- const responseStatus = httpResponse.status();
18
- status =
19
- responseStatus >= 200 && responseStatus <= 299
20
- ? `[success - ${responseStatus}]`
21
- : `[failed - ${responseStatus}]`;
22
- }
23
- else if (failure) {
24
- status = `[failed - ${failure.errorText}]`;
25
- }
26
- else {
27
- status = '[pending]';
28
- }
29
- return status;
30
- }
31
- export function getFormattedHeaderValue(headers) {
32
- const response = [];
33
- for (const [name, value] of Object.entries(headers)) {
34
- response.push(`- ${name}:${value}`);
35
- }
36
- return response;
37
- }
38
- export async function getFormattedResponseBody(httpResponse, sizeLimit = BODY_CONTEXT_SIZE_LIMIT) {
39
- try {
40
- const responseBuffer = await httpResponse.buffer();
41
- if (isUtf8(responseBuffer)) {
42
- const responseAsTest = responseBuffer.toString('utf-8');
43
- if (responseAsTest.length === 0) {
44
- return `<empty response>`;
45
- }
46
- return `${getSizeLimitedString(responseAsTest, sizeLimit)}`;
47
- }
48
- return `<binary data>`;
49
- }
50
- catch {
51
- return `<not available anymore>`;
52
- }
53
- }
54
- export async function getFormattedRequestBody(httpRequest, sizeLimit = BODY_CONTEXT_SIZE_LIMIT) {
55
- if (httpRequest.hasPostData()) {
56
- const data = httpRequest.postData();
57
- if (data) {
58
- return `${getSizeLimitedString(data, sizeLimit)}`;
59
- }
60
- try {
61
- const fetchData = await httpRequest.fetchPostData();
62
- if (fetchData) {
63
- return `${getSizeLimitedString(fetchData, sizeLimit)}`;
64
- }
65
- }
66
- catch {
67
- return `<not available anymore>`;
68
- }
69
- }
70
- return;
71
- }
72
- function getSizeLimitedString(text, sizeLimit) {
73
- if (text.length > sizeLimit) {
74
- return `${text.substring(0, sizeLimit) + '... <truncated>'}`;
75
- }
76
- return `${text}`;
77
- }
@@ -1,73 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2025 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
- export function formatSnapshotNode(root, snapshot, depth = 0) {
7
- const chunks = [];
8
- if (depth === 0) {
9
- // Top-level content of the snapshot.
10
- if (snapshot?.verbose &&
11
- snapshot?.hasSelectedElement &&
12
- !snapshot.selectedElementUid) {
13
- chunks.push(`Note: there is a selected element in the DevTools Elements panel but it is not included into the current a11y tree snapshot.
14
- Get a verbose snapshot to include all elements if you are interested in the selected element.\n\n`);
15
- }
16
- }
17
- const attributes = getAttributes(root);
18
- const line = ' '.repeat(depth * 2) +
19
- attributes.join(' ') +
20
- (root.id === snapshot?.selectedElementUid
21
- ? ' [selected in the DevTools Elements panel]'
22
- : '') +
23
- '\n';
24
- chunks.push(line);
25
- for (const child of root.children) {
26
- chunks.push(formatSnapshotNode(child, snapshot, depth + 1));
27
- }
28
- return chunks.join('');
29
- }
30
- function getAttributes(serializedAXNodeRoot) {
31
- const attributes = [`uid=${serializedAXNodeRoot.id}`];
32
- if (serializedAXNodeRoot.role) {
33
- // To match representation in DevTools.
34
- attributes.push(serializedAXNodeRoot.role === 'none'
35
- ? 'ignored'
36
- : serializedAXNodeRoot.role);
37
- }
38
- if (serializedAXNodeRoot.name) {
39
- attributes.push(`"${serializedAXNodeRoot.name}"`);
40
- }
41
- const excluded = new Set([
42
- 'id',
43
- 'role',
44
- 'name',
45
- 'elementHandle',
46
- 'children',
47
- 'backendNodeId',
48
- ]);
49
- const booleanPropertyMap = {
50
- disabled: 'disableable',
51
- expanded: 'expandable',
52
- focused: 'focusable',
53
- selected: 'selectable',
54
- };
55
- for (const attr of Object.keys(serializedAXNodeRoot).sort()) {
56
- if (excluded.has(attr)) {
57
- continue;
58
- }
59
- const value = serializedAXNodeRoot[attr];
60
- if (typeof value === 'boolean') {
61
- if (booleanPropertyMap[attr]) {
62
- attributes.push(booleanPropertyMap[attr]);
63
- }
64
- if (value) {
65
- attributes.push(attr);
66
- }
67
- }
68
- else if (typeof value === 'string' || typeof value === 'number') {
69
- attributes.push(`${attr}="${value}"`);
70
- }
71
- }
72
- return attributes;
73
- }
@@ -1,6 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2025 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
- export { AgentFocus, TraceEngine, PerformanceTraceFormatter, PerformanceInsightFormatter, AggregatedIssue, Issue, Target as SDKTarget, DebuggerModel, Foundation, TargetManager, MarkdownIssueDescription, Marked, ProtocolClient, Common, I18n, createIssuesFromProtocolIssue, IssueAggregator, } from '../../node_modules/chrome-devtools-frontend/mcp/mcp.js';
@@ -1,8 +0,0 @@
1
- # Mark SameParty cookies as Secure and do not use SameSite=Strict for SameParty cookies
2
-
3
- Cookies marked with `SameParty` must also be marked with `Secure`. In addition, cookies marked
4
- with `SameParty` cannot use `SameSite=Strict`.
5
-
6
- Resolve this issue by updating the attributes of the cookie:
7
- * Remove `SameParty` if the cookie should only be used by the same site but not the same first-party set
8
- * Remove `SameSite=Strict` and specify `Secure` if the cookie should be available to all sites of the same first-party set
@@ -1,10 +0,0 @@
1
- # Make sure a cookie is using the SameParty attribute correctly
2
-
3
- Setting cross-site cookies with the `SameParty` attribute is only possible if
4
- both domains are a part of the same First-Party Set.
5
-
6
- To allow setting cross-site cookies, try one of the following:
7
- * If the domains satisfy the First-Party Set criteria, add them to the same First-Party Set.
8
- * If the domains don't satisfy the First-Party Set criteria, remove the `SameParty` attribute and specify `SameSite=None`.
9
-
10
- If you don't have the option to do any of the above, cookies are not intended to be set in cross-site contexts.