UNPKG

ai-debug-local-mcp

Version:

🎯 ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh

200 lines • 7.76 kB
// Event Listener Management System // Comprehensive solution for memory leak prevention in browser environments /** * EventListenerManager - Prevents memory leaks by tracking and managing event listeners * * Key Features: * - Tracks all addEventListener calls for automatic cleanup * - Prevents duplicate listeners * - Graceful error handling during cleanup * - Memory leak detection and reporting * - Backward compatible with standard addEventListener */ export class EventListenerManager { listeners = new Map(); listenerIdCounter = 0; /** * Add an event listener with automatic tracking for cleanup * Prevents duplicate listeners for the same target/event/handler combination */ addEventListener(target, event, handler, options) { if (!target || !event || !handler) { console.warn('EventListenerManager: Invalid parameters for addEventListener'); return; } // Generate unique ID for this listener const listenerId = this.generateListenerId(target, event, handler); // Prevent duplicate listeners if (this.listeners.has(listenerId)) { console.warn(`EventListenerManager: Duplicate listener prevented for ${event}`); return; } try { // Add the actual event listener target.addEventListener(event, handler, options); // Track the listener for cleanup this.listeners.set(listenerId, { target, event, handler, addedAt: new Date(), id: listenerId }); } catch (error) { console.error('EventListenerManager: Failed to add event listener:', error); } } /** * Remove a specific event listener */ removeEventListener(target, event, handler) { const listenerId = this.generateListenerId(target, event, handler); const listener = this.listeners.get(listenerId); if (!listener) { return false; } try { target.removeEventListener(event, handler); this.listeners.delete(listenerId); return true; } catch (error) { console.error('EventListenerManager: Failed to remove event listener:', error); // Still remove from tracking even if removal failed this.listeners.delete(listenerId); return false; } } /** * Remove all tracked event listeners * Essential for preventing memory leaks */ cleanup() { const cleanupErrors = []; for (const [listenerId, listener] of this.listeners.entries()) { try { listener.target.removeEventListener(listener.event, listener.handler); } catch (error) { cleanupErrors.push(error); console.error(`EventListenerManager: Failed to cleanup listener ${listenerId}:`, error); } } // Clear all tracking regardless of cleanup errors this.listeners.clear(); if (cleanupErrors.length > 0) { console.warn(`EventListenerManager: ${cleanupErrors.length} cleanup errors occurred`); } } /** * Get count of currently tracked listeners */ getActiveListenerCount() { return this.listeners.size; } /** * Get listeners filtered by event type */ getListenersByType(eventType) { return Array.from(this.listeners.values()).filter(listener => listener.event === eventType); } /** * Get detailed information about all tracked listeners */ getDetailedListenerInfo() { return Array.from(this.listeners.values()); } /** * Detect potential memory leaks based on listener patterns */ detectPotentialLeaks() { const eventCounts = new Map(); // Group listeners by event type for (const listener of this.listeners.values()) { if (!eventCounts.has(listener.event)) { eventCounts.set(listener.event, []); } eventCounts.get(listener.event).push(listener); } const potentialLeaks = []; for (const [event, listeners] of eventCounts.entries()) { // Consider it a potential leak if: // 1. More than 10 listeners for the same event type // 2. Listeners older than 10 minutes const oldListeners = listeners.filter(l => Date.now() - l.addedAt.getTime() > 10 * 60 * 1000 // 10 minutes ); if (listeners.length > 10 || oldListeners.length > 0) { const oldestListener = listeners.reduce((oldest, current) => current.addedAt < oldest.addedAt ? current : oldest); potentialLeaks.push({ event, count: listeners.length, oldestListener: oldestListener.addedAt, targets: listeners.map(l => this.getTargetDescription(l.target)) }); } } return potentialLeaks; } /** * Generate unique ID for listener to prevent duplicates */ generateListenerId(target, event, handler) { // Use a combination of target type, event, and handler reference const targetId = this.getTargetDescription(target); const handlerId = typeof handler === 'function' ? handler.toString().slice(0, 50) // Use function signature : 'object-handler'; // Don't increment counter for duplicate checks - use consistent ID return `${targetId}:${event}:${handlerId}`; } /** * Get description of event target for debugging */ getTargetDescription(target) { // Check for document/window safely (they might not exist in test environments) if (typeof document !== 'undefined' && target === document) return 'document'; if (typeof window !== 'undefined' && target === window) return 'window'; // Check if Element exists and target is an instance of it (safe for Node.js environments) if (typeof Element !== 'undefined' && target instanceof Element) { const tagName = target.tagName?.toLowerCase() || 'element'; const id = target.id ? `#${target.id}` : ''; const className = target.className ? `.${target.className.split(' ').join('.')}` : ''; return `${tagName}${id}${className}`; } return target.constructor?.name || 'unknown-target'; } /** * Get debugging statistics */ getStats() { const eventTypes = {}; const targetTypes = {}; let oldestListener = null; for (const listener of this.listeners.values()) { // Count by event type eventTypes[listener.event] = (eventTypes[listener.event] || 0) + 1; // Count by target type const targetType = this.getTargetDescription(listener.target); targetTypes[targetType] = (targetTypes[targetType] || 0) + 1; // Track oldest listener if (!oldestListener || listener.addedAt < oldestListener) { oldestListener = listener.addedAt; } } return { totalListeners: this.listeners.size, eventTypes, oldestListener, targetTypes }; } } /** * Global instance for easy access across the application * This can be used to ensure all event listeners are tracked globally */ export const globalEventListenerManager = new EventListenerManager(); //# sourceMappingURL=event-listener-manager.js.map