/**
* DebugControl - System Debug Information and Message Display Control
*
* Provides comprehensive debugging capabilities including system message display,
* error tracking, performance monitoring, and development tools. Essential for
* troubleshooting and development workflows within the TestDrive-JSUI environment.
*
* Features:
* - Real-time debug message display with categorization
* - Error tracking with stack trace information
* - Performance metrics and timing measurements
* - System information display (browser, viewport, etc.)
* - Message filtering and search capabilities
* - Export functionality for debug logs
* - Integration with MarkitectDebugSystem
*
* Dependencies:
* - ControlBase (base control functionality)
* - MarkitectDebugSystem (optional, for enhanced debugging)
*/
/**
* DebugControl - Development and debugging information control
*
* This control serves as a central hub for all debugging activities,
* providing developers with essential information for troubleshooting
* and performance optimization.
*/
class DebugControl extends ControlBase {
constructor() {
super();
// Configure for debug functionality
this.config = {
icon: 'π',
title: 'Debug',
className: 'debug-control',
defaultContent: 'Debug information loading...',
ariaLabel: 'Debug Information Control',
position: 'w' // West positioning
};
// Debug control state
this.messages = [];
this.maxMessages = 100;
this.messageFilter = 'all'; // 'all', 'error', 'warn', 'info', 'debug'
this.autoScroll = true;
this.isRecording = true;
this.startTime = Date.now();
this.performanceMarks = new Map();
this.initializeDebugCapture();
}
/**
* Initialize debug message capture
*/
initializeDebugCapture() {
return this.safeOperation(() => {
// Capture console messages
this.originalConsole = {
log: console.log,
error: console.error,
warn: console.warn,
info: console.info,
debug: console.debug
};
// Override console methods to capture messages
console.log = (...args) => {
this.originalConsole.log(...args);
this.addDebugMessage('LOG', args.join(' '), 'info');
};
console.error = (...args) => {
this.originalConsole.error(...args);
this.addDebugMessage('ERROR', args.join(' '), 'error');
};
console.warn = (...args) => {
this.originalConsole.warn(...args);
this.addDebugMessage('WARN', args.join(' '), 'warn');
};
console.info = (...args) => {
this.originalConsole.info(...args);
this.addDebugMessage('INFO', args.join(' '), 'info');
};
console.debug = (...args) => {
this.originalConsole.debug(...args);
this.addDebugMessage('DEBUG', args.join(' '), 'debug');
};
// Capture global errors
window.addEventListener('error', (event) => {
this.addDebugMessage('ERROR', `${event.message} at ${event.filename}:${event.lineno}`, 'error');
});
// Capture unhandled promise rejections
window.addEventListener('unhandledrejection', (event) => {
this.addDebugMessage('PROMISE_REJECT', `Unhandled promise rejection: ${event.reason}`, 'error');
});
}, null, 'initializeDebugCapture');
}
/**
* Add a debug message to the log
*/
addDebugMessage(category, message, level = 'info') {
return this.safeOperation(() => {
if (!this.isRecording) return;
const debugMessage = {
id: Date.now() + Math.random(),
timestamp: Date.now(),
category,
message,
level,
displayTime: new Date().toLocaleTimeString(),
relativeTime: Date.now() - this.startTime
};
this.messages.push(debugMessage);
// Limit message history
if (this.messages.length > this.maxMessages) {
this.messages.shift();
}
// Update display if visible
if (this.element && this.isExpanded) {
this.updateMessageDisplay();
}
}, null, 'addDebugMessage');
}
/**
* Get messages filtered by current filter setting
*/
getFilteredMessages() {
if (this.messageFilter === 'all') {
return this.messages;
}
return this.messages.filter(msg => msg.level === this.messageFilter);
}
/**
* Generate system information HTML
*/
generateSystemInfoHTML() {
return this.safeOperation(() => {
const systemInfo = {
userAgent: navigator.userAgent,
viewport: `${window.innerWidth}x${window.innerHeight}`,
screen: `${screen.width}x${screen.height}`,
colorDepth: screen.colorDepth,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
language: navigator.language,
cookieEnabled: navigator.cookieEnabled,
onlineStatus: navigator.onLine ? 'Online' : 'Offline',
protocol: window.location.protocol,
memory: performance.memory ?
`Used: ${Math.round(performance.memory.usedJSHeapSize / 1024 / 1024)}MB` :
'Not available'
};
// Get Markitect version from config or default
const markitectVersion = window.markitectConfig?.version || 'Unknown';
return `
Markitect: ${markitectVersion}
Viewport: ${systemInfo.viewport}
Screen: ${systemInfo.screen}
Memory: ${systemInfo.memory}
Language: ${systemInfo.language}
Status: ${systemInfo.onlineStatus}
Protocol: ${systemInfo.protocol}
`;
}, '', 'generateSystemInfoHTML');
}
/**
* Generate performance metrics HTML
*/
generatePerformanceHTML() {
return this.safeOperation(() => {
const timing = performance.timing;
const navigation = performance.getEntriesByType('navigation')[0];
const metrics = {
pageLoad: timing.loadEventEnd - timing.navigationStart,
domReady: timing.domContentLoadedEventEnd - timing.navigationStart,
firstByte: timing.responseStart - timing.navigationStart,
uptime: Date.now() - this.startTime,
messagesCount: this.messages.length
};
return `
`;
}, '', 'generatePerformanceHTML');
}
/**
* Generate debug messages HTML
*/
generateMessagesHTML() {
return this.safeOperation(() => {
const filteredMessages = this.getFilteredMessages();
if (filteredMessages.length === 0) {
return `
No ${this.messageFilter === 'all' ? '' : this.messageFilter + ' '}messages yet
`;
}
const messagesHTML = filteredMessages.slice(-20).map(msg => {
const levelColors = {
error: '#dc3545',
warn: '#ffc107',
info: '#17a2b8',
debug: '#6c757d'
};
const backgroundColor = levelColors[msg.level] || '#6c757d';
const textColor = msg.level === 'warn' ? '#000' : '#fff';
return `
${msg.category}
${msg.displayTime}
${msg.message}
`;
}).join('');
return `
${messagesHTML}
`;
}, 'Error displaying messages
', 'generateMessagesHTML');
}
/**
* Generate control buttons HTML
*/
generateControlButtonsHTML() {
return `
ποΈ Clear
πΎ Export
${this.isRecording ? 'βΈοΈ Pause' : 'βΆοΈ Record'}
π§ͺ Test
`;
}
/**
* Generate filter controls HTML
*/
generateFilterControlsHTML() {
const filters = ['all', 'error', 'warn', 'info', 'debug'];
const filterButtons = filters.map(filter => {
const isActive = this.messageFilter === filter;
return `
${filter.toUpperCase()}
`;
}).join('');
return `
`;
}
/**
* Update the message display
*/
updateMessageDisplay() {
return this.safeOperation(() => {
const messagesContainer = this.element?.querySelector('.messages-container');
if (messagesContainer) {
const parent = messagesContainer.parentElement;
parent.innerHTML = this.generateMessagesHTML();
// Auto-scroll to bottom if enabled
if (this.autoScroll) {
const newContainer = parent.querySelector('.messages-container');
if (newContainer) {
newContainer.scrollTop = newContainer.scrollHeight;
}
}
}
}, null, 'updateMessageDisplay');
}
/**
* Clear all debug messages
*/
clearMessages() {
this.messages = [];
if (window.MarkitectDebugSystem) {
window.MarkitectDebugSystem.clearMessages();
}
this.buildContent();
}
/**
* Export debug messages to file
*/
exportMessages() {
return this.safeOperation(() => {
const exportData = {
timestamp: new Date().toISOString(),
session: {
startTime: new Date(this.startTime).toISOString(),
duration: Date.now() - this.startTime,
messageCount: this.messages.length
},
system: {
userAgent: navigator.userAgent,
viewport: `${window.innerWidth}x${window.innerHeight}`,
url: window.location.href
},
messages: this.messages
};
const dataStr = JSON.stringify(exportData, null, 2);
const dataBlob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = `debug-log-${new Date().toISOString().split('T')[0]}.json`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
this.addDebugMessage('EXPORT', 'Debug log exported successfully', 'info');
}, null, 'exportMessages');
}
/**
* Toggle message recording
*/
toggleRecording() {
this.isRecording = !this.isRecording;
this.buildContent();
this.addDebugMessage('CONTROL', `Recording ${this.isRecording ? 'started' : 'paused'}`, 'info');
}
/**
* Add a test message
*/
addTestMessage() {
const testMessages = [
{ category: 'TEST', message: 'This is a test info message', level: 'info' },
{ category: 'TEST', message: 'This is a test warning message', level: 'warn' },
{ category: 'TEST', message: 'This is a test error message', level: 'error' },
{ category: 'TEST', message: 'This is a test debug message', level: 'debug' }
];
const randomMessage = testMessages[Math.floor(Math.random() * testMessages.length)];
this.addDebugMessage(randomMessage.category, randomMessage.message, randomMessage.level);
}
/**
* Set message filter
*/
setMessageFilter(filter) {
this.messageFilter = filter;
this.buildContent();
}
/**
* Generate debug control content (called by base class buildContent)
*/
generateContent() {
return this.safeOperation(() => {
return `
${this.generateSystemInfoHTML()}
${this.generatePerformanceHTML()}
${this.generateFilterControlsHTML()}
${this.generateMessagesHTML()}
${this.generateControlButtonsHTML()}
Recording: ${this.isRecording ? 'π’ Active' : 'π΄ Paused'} |
Filter: ${this.messageFilter.toUpperCase()} |
Messages: ${this.getFilteredMessages().length}/${this.messages.length}
`;
}, 'Error generating debug content', 'generateContent');
}
/**
* Override buildContent to add control reference
*/
buildContent() {
super.buildContent();
// Store reference to this control for onclick handlers
if (this.element) {
this.element.debugControl = this;
}
}
/**
* Clean up resources when control is destroyed
*/
destroy() {
// Restore original console methods
if (this.originalConsole) {
console.log = this.originalConsole.log;
console.error = this.originalConsole.error;
console.warn = this.originalConsole.warn;
console.info = this.originalConsole.info;
console.debug = this.originalConsole.debug;
}
super.destroy();
}
}
// Export for module systems or attach to global for direct usage
if (typeof module !== 'undefined' && module.exports) {
module.exports = DebugControl;
} else {
window.DebugControl = DebugControl;
}