chrome-devtools-mcp-for-extension 0.9.6 → 0.9.8

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.
@@ -49,67 +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: Try regular iframe via DOM.querySelectorAll
73
- await cdp.send('DOM.enable');
74
- const { root } = await cdp.send('DOM.getDocument', { depth: -1 });
75
- // Query all iframes
76
- const { nodeIds } = await cdp.send('DOM.querySelectorAll', {
77
- nodeId: root.nodeId,
78
- selector: 'iframe',
79
- });
80
- // Find the iframe matching the pattern
81
- let targetFrameId = null;
82
- let targetFrameUrl = null;
83
- for (const nodeId of nodeIds) {
84
- const { node } = await cdp.send('DOM.describeNode', { nodeId });
85
- const src = node.attributes?.find((attr, i, arr) => attr === 'src' && arr[i + 1]);
86
- const srcValue = src ? node.attributes[node.attributes.indexOf(src) + 1] : '';
87
- if (urlPattern.test(srcValue)) {
88
- targetFrameId = node.frameId || null;
89
- targetFrameUrl = srcValue;
90
- break;
91
- }
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;
81
+ }
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 };
92
101
  }
93
- if (!targetFrameId || !targetFrameUrl) {
94
- throw new Error(`Iframe not found (tried both OOPIF and regular iframe): ${urlPattern}`);
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}>`;
95
128
  }
96
- // Execute in the frame context using Page.createIsolatedWorld
97
- const { executionContextId } = await cdp.send('Page.createIsolatedWorld', {
98
- frameId: targetFrameId,
99
- });
100
- await cdp.send('Runtime.enable');
101
- const htmlResult = await cdp.send('Runtime.evaluate', {
102
- expression: 'document.documentElement.outerHTML',
103
- returnByValue: true,
104
- contextId: executionContextId,
105
- });
106
- const html = String(htmlResult?.result?.value ?? '');
107
- return {
108
- frameId: targetFrameId,
109
- frameUrl: targetFrameUrl,
110
- html,
111
- };
112
129
  }
130
+ html += '</html>';
131
+ return html;
113
132
  }
114
133
  async function sendToChildSession(cdp, sessionId, method, params) {
115
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.6",
3
+ "version": "0.9.8",
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",