mirror of
https://github.com/basicswap/basicswap.git
synced 2026-05-30 07:41:41 +02:00
Fix: Price Tiles volume/btc display + Better memory clean / tooltip manager.
This commit is contained in:
@@ -1,219 +1,663 @@
|
||||
const MemoryManager = (function() {
|
||||
const config = {
|
||||
tooltipCleanupInterval: 60000,
|
||||
maxTooltipsThreshold: 100,
|
||||
diagnosticsInterval: 300000,
|
||||
tooltipLifespan: 240000,
|
||||
debug: false,
|
||||
autoCleanup: true,
|
||||
elementVerificationInterval: 50000,
|
||||
tooltipSelectors: [
|
||||
'[data-tippy-root]',
|
||||
'[data-tooltip-trigger-id]',
|
||||
'.tooltip',
|
||||
'.tippy-box',
|
||||
'.tippy-content'
|
||||
]
|
||||
};
|
||||
|
||||
const state = {
|
||||
isMonitoringEnabled: false,
|
||||
monitorInterval: null,
|
||||
cleanupInterval: null
|
||||
};
|
||||
let mutationObserver = null;
|
||||
|
||||
const config = {
|
||||
monitorInterval: 30000,
|
||||
cleanupInterval: 60000,
|
||||
debug: false
|
||||
};
|
||||
const safeGet = (obj, path, defaultValue = null) => {
|
||||
if (!obj) return defaultValue;
|
||||
const pathParts = path.split('.');
|
||||
let result = obj;
|
||||
for (const part of pathParts) {
|
||||
if (result === null || result === undefined) return defaultValue;
|
||||
result = result[part];
|
||||
}
|
||||
return result !== undefined ? result : defaultValue;
|
||||
};
|
||||
|
||||
function log(message, ...args) {
|
||||
if (config.debug) {
|
||||
console.log(`[MemoryManager] ${message}`, ...args);
|
||||
const state = {
|
||||
intervals: new Map(),
|
||||
trackedTooltips: new Map(),
|
||||
trackedElements: new WeakMap(),
|
||||
startTime: Date.now(),
|
||||
lastCleanupTime: Date.now(),
|
||||
metrics: {
|
||||
tooltipsCreated: 0,
|
||||
tooltipsDestroyed: 0,
|
||||
orphanedTooltipsRemoved: 0,
|
||||
elementsProcessed: 0,
|
||||
cleanupRuns: 0,
|
||||
manualCleanupRuns: 0,
|
||||
lastMemoryUsage: null
|
||||
}
|
||||
};
|
||||
|
||||
const log = (message, ...args) => {
|
||||
if (!config.debug) return;
|
||||
const now = new Date().toISOString();
|
||||
console.log(`[MemoryManager ${now}]`, message, ...args);
|
||||
};
|
||||
|
||||
const logError = (message, error) => {
|
||||
console.error(`[MemoryManager] ${message}`, error);
|
||||
};
|
||||
|
||||
const trackTooltip = (element, tooltipInstance) => {
|
||||
try {
|
||||
if (!element || !tooltipInstance) return;
|
||||
|
||||
const timestamp = Date.now();
|
||||
const tooltipId = element.getAttribute('data-tooltip-trigger-id') || `tooltip_${timestamp}_${Math.random().toString(36).substring(2, 9)}`;
|
||||
|
||||
state.trackedTooltips.set(tooltipId, {
|
||||
timestamp,
|
||||
element,
|
||||
instance: tooltipInstance,
|
||||
processed: false
|
||||
});
|
||||
|
||||
state.metrics.tooltipsCreated++;
|
||||
|
||||
setTimeout(() => {
|
||||
if (state.trackedTooltips.has(tooltipId)) {
|
||||
destroyTooltip(tooltipId);
|
||||
}
|
||||
}, config.tooltipLifespan);
|
||||
|
||||
return tooltipId;
|
||||
} catch (error) {
|
||||
logError('Error tracking tooltip:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const destroyTooltip = (tooltipId) => {
|
||||
try {
|
||||
const tooltipInfo = state.trackedTooltips.get(tooltipId);
|
||||
if (!tooltipInfo) return false;
|
||||
|
||||
const { element, instance } = tooltipInfo;
|
||||
|
||||
if (instance && typeof instance.destroy === 'function') {
|
||||
instance.destroy();
|
||||
}
|
||||
|
||||
if (element && element.removeAttribute) {
|
||||
element.removeAttribute('data-tooltip-trigger-id');
|
||||
element.removeAttribute('aria-describedby');
|
||||
}
|
||||
|
||||
const tippyRoot = document.querySelector(`[data-for-tooltip-id="${tooltipId}"]`);
|
||||
if (tippyRoot && tippyRoot.parentNode) {
|
||||
tippyRoot.parentNode.removeChild(tippyRoot);
|
||||
}
|
||||
|
||||
state.trackedTooltips.delete(tooltipId);
|
||||
state.metrics.tooltipsDestroyed++;
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
logError(`Error destroying tooltip ${tooltipId}:`, error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const removeOrphanedTooltips = () => {
|
||||
try {
|
||||
const tippyRoots = document.querySelectorAll('[data-tippy-root]');
|
||||
let removed = 0;
|
||||
|
||||
tippyRoots.forEach(root => {
|
||||
const tooltipId = root.getAttribute('data-for-tooltip-id');
|
||||
|
||||
const trigger = tooltipId ?
|
||||
document.querySelector(`[data-tooltip-trigger-id="${tooltipId}"]`) :
|
||||
null;
|
||||
|
||||
if (!trigger || !document.body.contains(trigger)) {
|
||||
if (root.parentNode) {
|
||||
root.parentNode.removeChild(root);
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('[data-tooltip-trigger-id]').forEach(trigger => {
|
||||
const tooltipId = trigger.getAttribute('data-tooltip-trigger-id');
|
||||
const root = document.querySelector(`[data-for-tooltip-id="${tooltipId}"]`);
|
||||
|
||||
if (!root) {
|
||||
trigger.removeAttribute('data-tooltip-trigger-id');
|
||||
trigger.removeAttribute('aria-describedby');
|
||||
removed++;
|
||||
}
|
||||
});
|
||||
|
||||
state.metrics.orphanedTooltipsRemoved += removed;
|
||||
return removed;
|
||||
} catch (error) {
|
||||
logError('Error removing orphaned tooltips:', error);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
const checkMemoryUsage = () => {
|
||||
if (window.performance && window.performance.memory) {
|
||||
const memoryUsage = {
|
||||
usedJSHeapSize: window.performance.memory.usedJSHeapSize,
|
||||
totalJSHeapSize: window.performance.memory.totalJSHeapSize,
|
||||
jsHeapSizeLimit: window.performance.memory.jsHeapSizeLimit,
|
||||
percentUsed: (window.performance.memory.usedJSHeapSize / window.performance.memory.jsHeapSizeLimit * 100).toFixed(2)
|
||||
};
|
||||
|
||||
state.metrics.lastMemoryUsage = memoryUsage;
|
||||
return memoryUsage;
|
||||
}
|
||||
|
||||
const publicAPI = {
|
||||
enableMonitoring: function(interval = config.monitorInterval) {
|
||||
if (state.monitorInterval) {
|
||||
clearInterval(state.monitorInterval);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
state.isMonitoringEnabled = true;
|
||||
config.monitorInterval = interval;
|
||||
const checkForDisconnectedElements = () => {
|
||||
try {
|
||||
const disconnectedElements = new Set();
|
||||
|
||||
this.logMemoryUsage();
|
||||
|
||||
state.monitorInterval = setInterval(() => {
|
||||
this.logMemoryUsage();
|
||||
}, interval);
|
||||
|
||||
console.log(`Memory monitoring enabled - reporting every ${interval/1000} seconds`);
|
||||
return true;
|
||||
},
|
||||
|
||||
disableMonitoring: function() {
|
||||
if (state.monitorInterval) {
|
||||
clearInterval(state.monitorInterval);
|
||||
state.monitorInterval = null;
|
||||
}
|
||||
|
||||
state.isMonitoringEnabled = false;
|
||||
console.log('Memory monitoring disabled');
|
||||
return true;
|
||||
},
|
||||
|
||||
logMemoryUsage: function() {
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
console.log(`=== Memory Monitor [${timestamp}] ===`);
|
||||
|
||||
if (window.performance && window.performance.memory) {
|
||||
console.log('Memory usage:', {
|
||||
usedJSHeapSize: (window.performance.memory.usedJSHeapSize / 1024 / 1024).toFixed(2) + ' MB',
|
||||
totalJSHeapSize: (window.performance.memory.totalJSHeapSize / 1024 / 1024).toFixed(2) + ' MB'
|
||||
});
|
||||
}
|
||||
|
||||
if (navigator.deviceMemory) {
|
||||
console.log('Device memory:', navigator.deviceMemory, 'GB');
|
||||
}
|
||||
|
||||
const nodeCount = document.querySelectorAll('*').length;
|
||||
console.log('DOM node count:', nodeCount);
|
||||
|
||||
if (window.CleanupManager) {
|
||||
const counts = CleanupManager.getResourceCounts();
|
||||
console.log('Managed resources:', counts);
|
||||
}
|
||||
|
||||
if (window.TooltipManager) {
|
||||
const tooltipInstances = document.querySelectorAll('[data-tippy-root]').length;
|
||||
const tooltipTriggers = document.querySelectorAll('[data-tooltip-trigger-id]').length;
|
||||
console.log('Tooltip instances:', tooltipInstances, '- Tooltip triggers:', tooltipTriggers);
|
||||
}
|
||||
|
||||
if (window.CacheManager && window.CacheManager.getStats) {
|
||||
const cacheStats = CacheManager.getStats();
|
||||
console.log('Cache stats:', cacheStats);
|
||||
}
|
||||
|
||||
if (window.IdentityManager && window.IdentityManager.getStats) {
|
||||
const identityStats = window.IdentityManager.getStats();
|
||||
console.log('Identity cache stats:', identityStats);
|
||||
}
|
||||
|
||||
console.log('==============================');
|
||||
},
|
||||
|
||||
enableAutoCleanup: function(interval = config.cleanupInterval) {
|
||||
if (state.cleanupInterval) {
|
||||
clearInterval(state.cleanupInterval);
|
||||
}
|
||||
|
||||
config.cleanupInterval = interval;
|
||||
|
||||
this.forceCleanup();
|
||||
|
||||
state.cleanupInterval = setInterval(() => {
|
||||
this.forceCleanup();
|
||||
}, interval);
|
||||
|
||||
log('Auto-cleanup enabled every', interval/1000, 'seconds');
|
||||
return true;
|
||||
},
|
||||
|
||||
disableAutoCleanup: function() {
|
||||
if (state.cleanupInterval) {
|
||||
clearInterval(state.cleanupInterval);
|
||||
state.cleanupInterval = null;
|
||||
}
|
||||
|
||||
console.log('Memory auto-cleanup disabled');
|
||||
return true;
|
||||
},
|
||||
|
||||
forceCleanup: function() {
|
||||
if (config.debug) {
|
||||
console.log('Running memory cleanup...', new Date().toLocaleTimeString());
|
||||
}
|
||||
|
||||
if (window.CacheManager && CacheManager.cleanup) {
|
||||
CacheManager.cleanup(true);
|
||||
}
|
||||
|
||||
if (window.TooltipManager && TooltipManager.cleanup) {
|
||||
window.TooltipManager.cleanup();
|
||||
}
|
||||
|
||||
document.querySelectorAll('[data-tooltip-trigger-id]').forEach(element => {
|
||||
if (window.TooltipManager && TooltipManager.destroy) {
|
||||
window.TooltipManager.destroy(element);
|
||||
}
|
||||
});
|
||||
|
||||
if (window.chartModule && chartModule.cleanup) {
|
||||
chartModule.cleanup();
|
||||
}
|
||||
|
||||
if (window.gc) {
|
||||
window.gc();
|
||||
} else {
|
||||
const arr = new Array(1000);
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
arr[i] = new Array(10000).join('x');
|
||||
}
|
||||
}
|
||||
|
||||
if (config.debug) {
|
||||
console.log('Memory cleanup completed');
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
setDebugMode: function(enabled) {
|
||||
config.debug = Boolean(enabled);
|
||||
return `Debug mode ${config.debug ? 'enabled' : 'disabled'}`;
|
||||
},
|
||||
|
||||
getStatus: function() {
|
||||
return {
|
||||
monitoring: {
|
||||
enabled: Boolean(state.monitorInterval),
|
||||
interval: config.monitorInterval
|
||||
},
|
||||
autoCleanup: {
|
||||
enabled: Boolean(state.cleanupInterval),
|
||||
interval: config.cleanupInterval
|
||||
},
|
||||
debug: config.debug
|
||||
};
|
||||
},
|
||||
|
||||
initialize: function(options = {}) {
|
||||
if (options.debug !== undefined) {
|
||||
this.setDebugMode(options.debug);
|
||||
}
|
||||
|
||||
if (options.enableMonitoring) {
|
||||
this.enableMonitoring(options.monitorInterval || config.monitorInterval);
|
||||
}
|
||||
|
||||
if (options.enableAutoCleanup) {
|
||||
this.enableAutoCleanup(options.cleanupInterval || config.cleanupInterval);
|
||||
}
|
||||
|
||||
if (window.CleanupManager) {
|
||||
window.CleanupManager.registerResource('memoryManager', this, (mgr) => mgr.dispose());
|
||||
}
|
||||
|
||||
log('MemoryManager initialized');
|
||||
return this;
|
||||
},
|
||||
|
||||
dispose: function() {
|
||||
this.disableMonitoring();
|
||||
this.disableAutoCleanup();
|
||||
log('MemoryManager disposed');
|
||||
state.trackedTooltips.forEach((info, id) => {
|
||||
const { element } = info;
|
||||
if (element && !document.body.contains(element)) {
|
||||
disconnectedElements.add(id);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return publicAPI;
|
||||
disconnectedElements.forEach(id => {
|
||||
destroyTooltip(id);
|
||||
});
|
||||
|
||||
return disconnectedElements.size;
|
||||
} catch (error) {
|
||||
logError('Error checking for disconnected elements:', error);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
const setupMutationObserver = () => {
|
||||
if (mutationObserver) {
|
||||
mutationObserver.disconnect();
|
||||
}
|
||||
|
||||
mutationObserver = new MutationObserver(mutations => {
|
||||
let needsCleanup = false;
|
||||
|
||||
mutations.forEach(mutation => {
|
||||
if (mutation.removedNodes.length) {
|
||||
Array.from(mutation.removedNodes).forEach(node => {
|
||||
if (node.nodeType === 1) {
|
||||
if (node.hasAttribute && node.hasAttribute('data-tooltip-trigger-id')) {
|
||||
const tooltipId = node.getAttribute('data-tooltip-trigger-id');
|
||||
destroyTooltip(tooltipId);
|
||||
needsCleanup = true;
|
||||
}
|
||||
|
||||
if (node.querySelectorAll) {
|
||||
const tooltipTriggers = node.querySelectorAll('[data-tooltip-trigger-id]');
|
||||
if (tooltipTriggers.length > 0) {
|
||||
tooltipTriggers.forEach(el => {
|
||||
const tooltipId = el.getAttribute('data-tooltip-trigger-id');
|
||||
destroyTooltip(tooltipId);
|
||||
});
|
||||
needsCleanup = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (needsCleanup) {
|
||||
removeOrphanedTooltips();
|
||||
}
|
||||
});
|
||||
|
||||
mutationObserver.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
|
||||
return mutationObserver;
|
||||
};
|
||||
|
||||
const performCleanup = (force = false) => {
|
||||
try {
|
||||
log('Starting tooltip cleanup' + (force ? ' (forced)' : ''));
|
||||
|
||||
state.lastCleanupTime = Date.now();
|
||||
state.metrics.cleanupRuns++;
|
||||
|
||||
if (force) {
|
||||
state.metrics.manualCleanupRuns++;
|
||||
}
|
||||
|
||||
document.querySelectorAll('[data-tippy-root]').forEach(root => {
|
||||
const instance = safeGet(root, '_tippy');
|
||||
if (instance && instance._animationFrame) {
|
||||
cancelAnimationFrame(instance._animationFrame);
|
||||
instance._animationFrame = null;
|
||||
}
|
||||
});
|
||||
|
||||
const orphanedRemoved = removeOrphanedTooltips();
|
||||
|
||||
const disconnectedRemoved = checkForDisconnectedElements();
|
||||
|
||||
const tooltipCount = document.querySelectorAll('[data-tippy-root]').length;
|
||||
const triggerCount = document.querySelectorAll('[data-tooltip-trigger-id]').length;
|
||||
|
||||
if (force || tooltipCount > config.maxTooltipsThreshold) {
|
||||
if (tooltipCount > config.maxTooltipsThreshold) {
|
||||
document.querySelectorAll('[data-tooltip-trigger-id]').forEach(trigger => {
|
||||
const tooltipId = trigger.getAttribute('data-tooltip-trigger-id');
|
||||
destroyTooltip(tooltipId);
|
||||
});
|
||||
|
||||
document.querySelectorAll('[data-tippy-root]').forEach(root => {
|
||||
if (root.parentNode) {
|
||||
root.parentNode.removeChild(root);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelectorAll('[data-tooltip-trigger-id], [aria-describedby]').forEach(el => {
|
||||
if (window.CleanupManager && window.CleanupManager.removeListenersByElement) {
|
||||
window.CleanupManager.removeListenersByElement(el);
|
||||
} else {
|
||||
if (el.parentNode) {
|
||||
const clone = el.cloneNode(true);
|
||||
el.parentNode.replaceChild(clone, el);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (window.gc) {
|
||||
window.gc();
|
||||
} else if (force) {
|
||||
const arr = new Array(1000);
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
arr[i] = new Array(10000).join('x');
|
||||
}
|
||||
}
|
||||
|
||||
checkMemoryUsage();
|
||||
|
||||
const result = {
|
||||
orphanedRemoved,
|
||||
disconnectedRemoved,
|
||||
tooltipCount: document.querySelectorAll('[data-tippy-root]').length,
|
||||
triggerCount: document.querySelectorAll('[data-tooltip-trigger-id]').length,
|
||||
memoryUsage: state.metrics.lastMemoryUsage
|
||||
};
|
||||
|
||||
log('Cleanup completed', result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
logError('Error during cleanup:', error);
|
||||
return { error: error.message };
|
||||
}
|
||||
};
|
||||
|
||||
const runDiagnostics = () => {
|
||||
try {
|
||||
log('Running memory diagnostics');
|
||||
|
||||
const memoryUsage = checkMemoryUsage();
|
||||
const tooltipCount = document.querySelectorAll('[data-tippy-root]').length;
|
||||
const triggerCount = document.querySelectorAll('[data-tooltip-trigger-id]').length;
|
||||
|
||||
const diagnostics = {
|
||||
time: new Date().toISOString(),
|
||||
uptime: Date.now() - state.startTime,
|
||||
memoryUsage,
|
||||
elementsCount: {
|
||||
tippyRoots: tooltipCount,
|
||||
tooltipTriggers: triggerCount,
|
||||
orphanedTriggers: triggerCount - tooltipCount > 0 ? triggerCount - tooltipCount : 0,
|
||||
orphanedTooltips: tooltipCount - triggerCount > 0 ? tooltipCount - triggerCount : 0
|
||||
},
|
||||
metrics: { ...state.metrics },
|
||||
issues: []
|
||||
};
|
||||
|
||||
if (tooltipCount > config.maxTooltipsThreshold) {
|
||||
diagnostics.issues.push({
|
||||
severity: 'high',
|
||||
message: `Excessive tooltip count: ${tooltipCount} (threshold: ${config.maxTooltipsThreshold})`,
|
||||
recommendation: 'Run cleanup and check for tooltip creation loops'
|
||||
});
|
||||
}
|
||||
|
||||
if (Math.abs(tooltipCount - triggerCount) > 10) {
|
||||
diagnostics.issues.push({
|
||||
severity: 'medium',
|
||||
message: `Mismatch between tooltips (${tooltipCount}) and triggers (${triggerCount})`,
|
||||
recommendation: 'Remove orphaned tooltips and tooltip triggers'
|
||||
});
|
||||
}
|
||||
|
||||
if (memoryUsage && memoryUsage.percentUsed > 80) {
|
||||
diagnostics.issues.push({
|
||||
severity: 'high',
|
||||
message: `High memory usage: ${memoryUsage.percentUsed}%`,
|
||||
recommendation: 'Force garbage collection and check for memory leaks'
|
||||
});
|
||||
}
|
||||
|
||||
if (config.autoCleanup && diagnostics.issues.some(issue => issue.severity === 'high')) {
|
||||
log('Critical issues detected, triggering automatic cleanup');
|
||||
performCleanup(true);
|
||||
}
|
||||
|
||||
return diagnostics;
|
||||
} catch (error) {
|
||||
logError('Error running diagnostics:', error);
|
||||
return { error: error.message };
|
||||
}
|
||||
};
|
||||
|
||||
const patchTooltipManager = () => {
|
||||
try {
|
||||
if (!window.TooltipManager) {
|
||||
log('TooltipManager not found');
|
||||
return false;
|
||||
}
|
||||
|
||||
log('Patching TooltipManager');
|
||||
|
||||
const originalCreate = window.TooltipManager.create;
|
||||
const originalDestroy = window.TooltipManager.destroy;
|
||||
const originalCleanup = window.TooltipManager.cleanup;
|
||||
|
||||
window.TooltipManager.create = function(element, content, options = {}) {
|
||||
if (!element) return null;
|
||||
|
||||
try {
|
||||
const result = originalCreate.call(this, element, content, options);
|
||||
const tooltipId = element.getAttribute('data-tooltip-trigger-id');
|
||||
|
||||
if (tooltipId) {
|
||||
const tippyInstance = safeGet(element, '_tippy') || null;
|
||||
trackTooltip(element, tippyInstance);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
logError('Error in patched create:', error);
|
||||
return originalCreate.call(this, element, content, options);
|
||||
}
|
||||
};
|
||||
|
||||
window.TooltipManager.destroy = function(element) {
|
||||
if (!element) return;
|
||||
|
||||
try {
|
||||
const tooltipId = element.getAttribute('data-tooltip-trigger-id');
|
||||
|
||||
originalDestroy.call(this, element);
|
||||
|
||||
if (tooltipId) {
|
||||
state.trackedTooltips.delete(tooltipId);
|
||||
state.metrics.tooltipsDestroyed++;
|
||||
}
|
||||
} catch (error) {
|
||||
logError('Error in patched destroy:', error);
|
||||
originalDestroy.call(this, element);
|
||||
}
|
||||
};
|
||||
|
||||
window.TooltipManager.cleanup = function() {
|
||||
try {
|
||||
originalCleanup.call(this);
|
||||
removeOrphanedTooltips();
|
||||
} catch (error) {
|
||||
logError('Error in patched cleanup:', error);
|
||||
originalCleanup.call(this);
|
||||
}
|
||||
};
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
logError('Error patching TooltipManager:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const patchTippy = () => {
|
||||
try {
|
||||
if (typeof tippy !== 'function') {
|
||||
log('tippy.js not found globally');
|
||||
return false;
|
||||
}
|
||||
|
||||
log('Patching global tippy');
|
||||
|
||||
const originalTippy = window.tippy;
|
||||
|
||||
window.tippy = function(...args) {
|
||||
const result = originalTippy.apply(this, args);
|
||||
|
||||
if (Array.isArray(result)) {
|
||||
result.forEach(instance => {
|
||||
const reference = instance.reference;
|
||||
|
||||
if (reference) {
|
||||
const originalShow = instance.show;
|
||||
const originalHide = instance.hide;
|
||||
const originalDestroy = instance.destroy;
|
||||
|
||||
instance.show = function(...showArgs) {
|
||||
return originalShow.apply(this, showArgs);
|
||||
};
|
||||
|
||||
instance.hide = function(...hideArgs) {
|
||||
return originalHide.apply(this, hideArgs);
|
||||
};
|
||||
|
||||
instance.destroy = function(...destroyArgs) {
|
||||
return originalDestroy.apply(this, destroyArgs);
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
Object.assign(window.tippy, originalTippy);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
logError('Error patching tippy:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const startMonitoring = () => {
|
||||
try {
|
||||
stopMonitoring();
|
||||
|
||||
state.intervals.set('cleanup', setInterval(() => {
|
||||
performCleanup();
|
||||
}, config.tooltipCleanupInterval));
|
||||
|
||||
state.intervals.set('diagnostics', setInterval(() => {
|
||||
runDiagnostics();
|
||||
}, config.diagnosticsInterval));
|
||||
|
||||
state.intervals.set('elementVerification', setInterval(() => {
|
||||
checkForDisconnectedElements();
|
||||
}, config.elementVerificationInterval));
|
||||
|
||||
setupMutationObserver();
|
||||
|
||||
log('Monitoring started');
|
||||
return true;
|
||||
} catch (error) {
|
||||
logError('Error starting monitoring:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const stopMonitoring = () => {
|
||||
try {
|
||||
state.intervals.forEach((interval, key) => {
|
||||
clearInterval(interval);
|
||||
});
|
||||
|
||||
state.intervals.clear();
|
||||
|
||||
if (mutationObserver) {
|
||||
mutationObserver.disconnect();
|
||||
mutationObserver = null;
|
||||
}
|
||||
|
||||
log('Monitoring stopped');
|
||||
return true;
|
||||
} catch (error) {
|
||||
logError('Error stopping monitoring:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const autoFix = () => {
|
||||
try {
|
||||
log('Running auto-fix');
|
||||
|
||||
performCleanup(true);
|
||||
|
||||
document.querySelectorAll('[data-tooltip-trigger-id]').forEach(element => {
|
||||
const tooltipId = element.getAttribute('data-tooltip-trigger-id');
|
||||
const duplicates = document.querySelectorAll(`[data-tooltip-trigger-id="${tooltipId}"]`);
|
||||
|
||||
if (duplicates.length > 1) {
|
||||
for (let i = 1; i < duplicates.length; i++) {
|
||||
duplicates[i].removeAttribute('data-tooltip-trigger-id');
|
||||
duplicates[i].removeAttribute('aria-describedby');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const tippyRoots = document.querySelectorAll('[data-tippy-root]');
|
||||
tippyRoots.forEach(root => {
|
||||
if (!document.body.contains(root) && root.parentNode) {
|
||||
root.parentNode.removeChild(root);
|
||||
}
|
||||
});
|
||||
|
||||
if (window.TooltipManager && window.TooltipManager.getInstance) {
|
||||
const manager = window.TooltipManager.getInstance();
|
||||
if (manager && manager.chartRefs && manager.chartRefs.clear) {
|
||||
manager.chartRefs.clear();
|
||||
}
|
||||
|
||||
if (manager && manager.tooltipElementsMap && manager.tooltipElementsMap.clear) {
|
||||
manager.tooltipElementsMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
patchTooltipManager();
|
||||
patchTippy();
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
logError('Error during auto-fix:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const initialize = (options = {}) => {
|
||||
try {
|
||||
Object.assign(config, options);
|
||||
|
||||
if (document.head) {
|
||||
const metaCache = document.createElement('meta');
|
||||
metaCache.setAttribute('http-equiv', 'Cache-Control');
|
||||
metaCache.setAttribute('content', 'no-store, max-age=0');
|
||||
document.head.appendChild(metaCache);
|
||||
}
|
||||
|
||||
patchTooltipManager();
|
||||
patchTippy();
|
||||
|
||||
startMonitoring();
|
||||
|
||||
if (window.CleanupManager && window.CleanupManager.registerResource) {
|
||||
window.CleanupManager.registerResource('memorymanager', MemoryManager, (optimizer) => {
|
||||
optimizer.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
log('Memory Optimizer initialized', config);
|
||||
|
||||
setTimeout(() => {
|
||||
runDiagnostics();
|
||||
}, 5000);
|
||||
|
||||
return MemoryManager;
|
||||
} catch (error) {
|
||||
logError('Error initializing Memory Optimizer:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const dispose = () => {
|
||||
try {
|
||||
log('Disposing Memory Optimizer');
|
||||
|
||||
performCleanup(true);
|
||||
|
||||
stopMonitoring();
|
||||
|
||||
state.trackedTooltips.clear();
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
logError('Error disposing Memory Optimizer:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
initialize,
|
||||
dispose,
|
||||
performCleanup,
|
||||
runDiagnostics,
|
||||
autoFix,
|
||||
getConfig: () => ({ ...config }),
|
||||
getMetrics: () => ({ ...state.metrics }),
|
||||
setDebugMode: (enabled) => {
|
||||
config.debug = Boolean(enabled);
|
||||
return config.debug;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
MemoryManager.initialize();
|
||||
});
|
||||
}
|
||||
|
||||
window.MemoryManager = MemoryManager;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (!window.memoryManagerInitialized) {
|
||||
MemoryManager.initialize();
|
||||
window.memoryManagerInitialized = true;
|
||||
}
|
||||
});
|
||||
|
||||
//console.log('MemoryManager initialized with methods:', Object.keys(MemoryManager));
|
||||
console.log('MemoryManager initialized');
|
||||
console.log('Memory Manager initialized');
|
||||
|
||||
Reference in New Issue
Block a user