chrome-devtools-mcp-for-extension 0.9.7 → 0.9.9

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.
@@ -346,6 +346,7 @@ export async function launch(options) {
346
346
  headless,
347
347
  args,
348
348
  ignoreDefaultArgs: ['--disable-extensions', '--enable-automation'],
349
+ enableExtensions: extensionPaths.length > 0 ? extensionPaths : undefined,
349
350
  });
350
351
  // Log actual spawn args for debugging
351
352
  const spawnArgs = browser.process()?.spawnargs;
@@ -49,76 +49,86 @@ export async function waitForExtensionChildTarget(cdp, pattern, timeoutMs = 8000
49
49
  });
50
50
  }
51
51
  export async function inspectIframe(cdp, urlPattern, waitMs = 8000) {
52
- // Try OOPIF detection first
52
+ // Strategy: Use DOMSnapshot.captureSnapshot to get iframe-inclusive DOM structure
53
+ // Note: chrome-extension:// iframes may not be included due to SOP/extension isolation
54
+ await cdp.send('DOMSnapshot.enable');
53
55
  try {
54
- await enableOopifAutoAttach(cdp);
55
- const child = await waitForExtensionChildTarget(cdp, urlPattern, waitMs);
56
- // Enable Page/Runtime in child session
57
- await sendToChildSession(cdp, child.sessionId, 'Page.enable', {});
58
- await sendToChildSession(cdp, child.sessionId, 'Runtime.enable', {});
59
- // Evaluate outerHTML in child session
60
- const htmlResult = await sendToChildSession(cdp, child.sessionId, 'Runtime.evaluate', {
61
- expression: 'document.documentElement.outerHTML',
62
- returnByValue: true,
56
+ // Capture full DOM snapshot including iframes
57
+ const snapshot = await cdp.send('DOMSnapshot.captureSnapshot', {
58
+ computedStyles: [],
59
+ includeDOMRects: false,
60
+ includePaintOrder: false,
63
61
  });
64
- const html = String(htmlResult?.result?.value ?? '');
65
- return {
66
- frameId: child.targetId,
67
- frameUrl: child.url,
68
- html,
69
- };
62
+ // Search for matching iframe in the snapshot
63
+ const result = findIframeInSnapshot(snapshot, urlPattern);
64
+ if (result) {
65
+ return {
66
+ frameId: null,
67
+ frameUrl: result.url,
68
+ html: result.html,
69
+ };
70
+ }
71
+ // Extension iframe not found in snapshot - this is expected behavior
72
+ // due to Same-Origin Policy and Chrome extension isolation
73
+ throw new Error('EXTENSION_FRAME_UNREADABLE: Chrome extension iframes are isolated by ' +
74
+ 'Same-Origin Policy and extension security model. The iframe exists but ' +
75
+ 'cannot be read from the page context. This is expected Chrome behavior.');
70
76
  }
71
- catch (oopifError) {
72
- // Fallback: Access iframe content through parent page's DOM
73
- await cdp.send('Runtime.enable');
74
- const iframeAccessScript = `
75
- (function() {
76
- const pattern = ${urlPattern};
77
- const iframes = Array.from(document.querySelectorAll('iframe'));
78
-
79
- for (const iframe of iframes) {
80
- if (pattern.test(iframe.src)) {
81
- try {
82
- // Try to access contentDocument/contentWindow
83
- const doc = iframe.contentDocument || iframe.contentWindow?.document;
84
- if (doc) {
85
- return {
86
- success: true,
87
- src: iframe.src,
88
- html: doc.documentElement.outerHTML
89
- };
90
- }
91
- } catch (securityError) {
92
- // If blocked by same-origin policy, return error details
93
- return {
94
- success: false,
95
- src: iframe.src,
96
- error: 'SecurityError: ' + securityError.message
97
- };
98
- }
99
- }
77
+ catch (error) {
78
+ // If DOMSnapshot fails or iframe not found, provide clear explanation
79
+ if (error.message?.includes('EXTENSION_FRAME_UNREADABLE')) {
80
+ throw error;
100
81
  }
101
-
102
- return {
103
- success: false,
104
- error: 'Iframe not found matching pattern: ' + pattern.toString()
105
- };
106
- })()
107
- `;
108
- const evalResult = await cdp.send('Runtime.evaluate', {
109
- expression: iframeAccessScript,
110
- returnByValue: true,
111
- });
112
- const data = evalResult?.result?.value;
113
- if (!data?.success) {
114
- throw new Error(`Iframe access failed: ${data?.error || 'Unknown error'}`);
82
+ throw new Error(`Failed to capture DOM snapshot: ${error.message}. ` +
83
+ 'Note: Chrome extension iframes are typically unreadable due to security policies.');
84
+ }
85
+ finally {
86
+ await cdp.send('DOMSnapshot.disable');
87
+ }
88
+ }
89
+ function findIframeInSnapshot(snapshot, urlPattern) {
90
+ // DOMSnapshot structure:
91
+ // - documents: array of document snapshots
92
+ // - strings: string table for deduplication
93
+ const documents = snapshot.documents || [];
94
+ for (const doc of documents) {
95
+ const baseURL = doc.baseURL;
96
+ // Check if this document matches the pattern
97
+ if (baseURL && urlPattern.test(baseURL)) {
98
+ // Reconstruct HTML from snapshot
99
+ const html = reconstructHTMLFromSnapshot(doc, snapshot.strings || []);
100
+ return { url: baseURL, html };
101
+ }
102
+ }
103
+ return null;
104
+ }
105
+ function reconstructHTMLFromSnapshot(doc, strings) {
106
+ // Simple reconstruction - get text content from nodes
107
+ // Note: DOMSnapshot doesn't provide perfect HTML reconstruction
108
+ // but gives us the essential content
109
+ const nodes = doc.nodes || {};
110
+ const nodeNames = nodes.nodeName || [];
111
+ const nodeValues = nodes.nodeValue || [];
112
+ const textValues = nodes.textValue || {};
113
+ // Build a simple HTML representation
114
+ let html = '<html>';
115
+ // Try to find body content
116
+ for (let i = 0; i < nodeNames.length; i++) {
117
+ const nameIdx = nodeNames[i];
118
+ const name = strings[nameIdx] || '';
119
+ const valueIdx = nodeValues[i];
120
+ const value = valueIdx >= 0 ? strings[valueIdx] : '';
121
+ if (name === '#document' || name === 'HTML')
122
+ continue;
123
+ if (name === '#text' && value) {
124
+ html += value;
125
+ }
126
+ else if (name) {
127
+ html += `<${name}>`;
115
128
  }
116
- return {
117
- frameId: null,
118
- frameUrl: data.src || '',
119
- html: data.html || '',
120
- };
121
129
  }
130
+ html += '</html>';
131
+ return html;
122
132
  }
123
133
  async function sendToChildSession(cdp, sessionId, method, params) {
124
134
  const id = Math.floor(Math.random() * 1000000);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-devtools-mcp-for-extension",
3
- "version": "0.9.7",
3
+ "version": "0.9.9",
4
4
  "description": "MCP server for Chrome extension development with Web Store automation. Fork of chrome-devtools-mcp with extension-specific tools.",
5
5
  "type": "module",
6
6
  "bin": "./build/src/index.js",
@@ -52,7 +52,7 @@
52
52
  "@modelcontextprotocol/sdk": "1.18.1",
53
53
  "archiver": "^7.0.1",
54
54
  "debug": "4.4.3",
55
- "puppeteer-core": "24.22.3",
55
+ "puppeteer": "24.22.3",
56
56
  "yargs": "18.0.0"
57
57
  },
58
58
  "devDependencies": {
@@ -72,7 +72,6 @@
72
72
  "eslint-import-resolver-typescript": "^4.4.4",
73
73
  "globals": "^16.4.0",
74
74
  "prettier": "^3.6.2",
75
- "puppeteer": "24.22.3",
76
75
  "sinon": "^21.0.0",
77
76
  "typescript-eslint": "^8.43.0",
78
77
  "typescript": "^5.9.2"