Major enhancements to browser automation and debugging capabilities: **Console Capture Features:** - Add console output file option (CLI, env var, session config) - Enhanced CDP console capture for service worker messages - Browser-level security warnings and mixed content errors - Network failure and loading error capture - All console contexts written to structured log files - Chrome extension for comprehensive console message interception **Offline Mode Support:** - Add browser_set_offline tool for DevTools-equivalent offline mode - Integrate offline mode into browser_configure tool - Support for testing network failure scenarios and service worker behavior **Extension Management:** - Improved extension installation messaging about session persistence - Console capture extension with debugger API access - Clear communication about extension lifecycle to MCP clients **Technical Implementation:** - CDP session management across multiple domains (Runtime, Network, Security, Log) - Service worker context console message interception - Browser context factory integration for offline mode - Pure Chromium configuration for optimal extension support All features provide MCP clients with powerful debugging capabilities equivalent to Chrome DevTools console and offline functionality.
193 lines
5.8 KiB
JavaScript
193 lines
5.8 KiB
JavaScript
// Background script for comprehensive console capture
|
|
console.log('Console Capture Extension: Background script loaded');
|
|
|
|
// Track active debug sessions
|
|
const debugSessions = new Map();
|
|
|
|
// Message storage for each tab
|
|
const tabConsoleMessages = new Map();
|
|
|
|
chrome.tabs.onCreated.addListener((tab) => {
|
|
if (tab.id) {
|
|
attachDebugger(tab.id);
|
|
}
|
|
});
|
|
|
|
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
|
if (changeInfo.status === 'loading' && tab.url && !tab.url.startsWith('chrome://')) {
|
|
attachDebugger(tabId);
|
|
}
|
|
});
|
|
|
|
chrome.tabs.onRemoved.addListener((tabId) => {
|
|
if (debugSessions.has(tabId)) {
|
|
try {
|
|
chrome.debugger.detach({ tabId });
|
|
} catch (e) {
|
|
// Ignore errors when detaching
|
|
}
|
|
debugSessions.delete(tabId);
|
|
tabConsoleMessages.delete(tabId);
|
|
}
|
|
});
|
|
|
|
async function attachDebugger(tabId) {
|
|
try {
|
|
// Don't attach to chrome:// pages or if already attached
|
|
if (debugSessions.has(tabId)) {
|
|
return;
|
|
}
|
|
|
|
// Attach debugger
|
|
await chrome.debugger.attach({ tabId }, '1.3');
|
|
debugSessions.set(tabId, true);
|
|
|
|
console.log(`Console Capture Extension: Attached to tab ${tabId}`);
|
|
|
|
// Enable domains for comprehensive console capture
|
|
await chrome.debugger.sendCommand({ tabId }, 'Runtime.enable');
|
|
await chrome.debugger.sendCommand({ tabId }, 'Log.enable');
|
|
await chrome.debugger.sendCommand({ tabId }, 'Network.enable');
|
|
await chrome.debugger.sendCommand({ tabId }, 'Security.enable');
|
|
|
|
// Initialize console messages array for this tab
|
|
if (!tabConsoleMessages.has(tabId)) {
|
|
tabConsoleMessages.set(tabId, []);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.log(`Console Capture Extension: Failed to attach to tab ${tabId}:`, error);
|
|
debugSessions.delete(tabId);
|
|
}
|
|
}
|
|
|
|
// Listen for debugger events
|
|
chrome.debugger.onEvent.addListener((source, method, params) => {
|
|
const tabId = source.tabId;
|
|
if (!tabId || !debugSessions.has(tabId)) return;
|
|
|
|
let consoleMessage = null;
|
|
|
|
try {
|
|
switch (method) {
|
|
case 'Runtime.consoleAPICalled':
|
|
consoleMessage = {
|
|
type: params.type || 'log',
|
|
text: params.args?.map(arg =>
|
|
arg.value !== undefined ? String(arg.value) :
|
|
arg.unserializableValue || '[object]'
|
|
).join(' ') || '',
|
|
location: `runtime:${params.stackTrace?.callFrames?.[0]?.lineNumber || 0}`,
|
|
source: 'js-console',
|
|
timestamp: Date.now()
|
|
};
|
|
break;
|
|
|
|
case 'Runtime.exceptionThrown':
|
|
const exception = params.exceptionDetails;
|
|
consoleMessage = {
|
|
type: 'error',
|
|
text: exception?.text || exception?.exception?.description || 'Runtime Exception',
|
|
location: `runtime:${exception?.lineNumber || 0}`,
|
|
source: 'js-exception',
|
|
timestamp: Date.now()
|
|
};
|
|
break;
|
|
|
|
case 'Log.entryAdded':
|
|
const entry = params.entry;
|
|
if (entry && entry.text) {
|
|
consoleMessage = {
|
|
type: entry.level || 'info',
|
|
text: entry.text,
|
|
location: `browser-log:${entry.lineNumber || 0}`,
|
|
source: 'browser-log',
|
|
timestamp: Date.now()
|
|
};
|
|
}
|
|
break;
|
|
|
|
case 'Network.loadingFailed':
|
|
if (params.errorText) {
|
|
consoleMessage = {
|
|
type: 'error',
|
|
text: `Network Error: ${params.errorText} - ${params.blockedReason || 'Unknown reason'}`,
|
|
location: 'network-layer',
|
|
source: 'network-error',
|
|
timestamp: Date.now()
|
|
};
|
|
}
|
|
break;
|
|
|
|
case 'Security.securityStateChanged':
|
|
if (params.securityState === 'insecure' && params.explanations) {
|
|
for (const explanation of params.explanations) {
|
|
if (explanation.description && explanation.description.toLowerCase().includes('mixed content')) {
|
|
consoleMessage = {
|
|
type: 'error',
|
|
text: `Security Warning: ${explanation.description}`,
|
|
location: 'security-layer',
|
|
source: 'security-warning',
|
|
timestamp: Date.now()
|
|
};
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (consoleMessage) {
|
|
// Store the message
|
|
const messages = tabConsoleMessages.get(tabId) || [];
|
|
messages.push(consoleMessage);
|
|
tabConsoleMessages.set(tabId, messages);
|
|
|
|
console.log(`Console Capture Extension: Captured message from tab ${tabId}:`, consoleMessage);
|
|
|
|
// Send to content script for potential file writing
|
|
chrome.tabs.sendMessage(tabId, {
|
|
type: 'CONSOLE_MESSAGE',
|
|
message: consoleMessage
|
|
}).catch(() => {
|
|
// Ignore errors if content script not ready
|
|
});
|
|
}
|
|
|
|
} catch (error) {
|
|
console.log('Console Capture Extension: Error processing event:', error);
|
|
}
|
|
});
|
|
|
|
// Handle detach events
|
|
chrome.debugger.onDetach.addListener((source, reason) => {
|
|
const tabId = source.tabId;
|
|
if (tabId && debugSessions.has(tabId)) {
|
|
console.log(`Console Capture Extension: Detached from tab ${tabId}, reason: ${reason}`);
|
|
debugSessions.delete(tabId);
|
|
tabConsoleMessages.delete(tabId);
|
|
}
|
|
});
|
|
|
|
// API to get console messages for a tab
|
|
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
if (request.type === 'GET_CONSOLE_MESSAGES') {
|
|
const tabId = request.tabId || sender.tab?.id;
|
|
if (tabId) {
|
|
const messages = tabConsoleMessages.get(tabId) || [];
|
|
sendResponse({ messages });
|
|
} else {
|
|
sendResponse({ messages: [] });
|
|
}
|
|
return true;
|
|
}
|
|
});
|
|
|
|
// Initialize for existing tabs
|
|
chrome.tabs.query({}, (tabs) => {
|
|
for (const tab of tabs) {
|
|
if (tab.id && tab.url && !tab.url.startsWith('chrome://')) {
|
|
attachDebugger(tab.id);
|
|
}
|
|
}
|
|
}); |