Fix: Better memory/tooltip/clean-up managers, various fixes.

This commit is contained in:
gerlofvanek
2025-05-08 21:01:02 +02:00
parent d57a148ff4
commit 868b2475c1
7 changed files with 1266 additions and 856 deletions

View File

@@ -311,7 +311,6 @@ CleanupManager.addListener(document, 'visibilitychange', () => {
window.TooltipManager.cleanup(); window.TooltipManager.cleanup();
} }
// Run memory optimization
if (window.MemoryManager) { if (window.MemoryManager) {
MemoryManager.forceCleanup(); MemoryManager.forceCleanup();
} }
@@ -1883,7 +1882,6 @@ function setupMemoryMonitoring() {
}, { once: true }); }, { once: true });
} }
// Init
function initialize() { function initialize() {
const filterElements = { const filterElements = {
stateSelect: document.getElementById('state'), stateSelect: document.getElementById('state'),
@@ -1901,8 +1899,6 @@ function initialize() {
if (filterElements.coinFrom) filterElements.coinFrom.value = 'any'; if (filterElements.coinFrom) filterElements.coinFrom.value = 'any';
if (filterElements.coinTo) filterElements.coinTo.value = 'any'; if (filterElements.coinTo) filterElements.coinTo.value = 'any';
setupMemoryMonitoring();
setTimeout(() => { setTimeout(() => {
WebSocketManager.initialize(); WebSocketManager.initialize();
setupEventListeners(); setupEventListeners();
@@ -1921,12 +1917,6 @@ function initialize() {
updateBidsTable(); updateBidsTable();
}, 100); }, 100);
setInterval(() => {
if ((state.data.sent.length + state.data.received.length) > 1000) {
optimizeMemoryUsage();
}
}, 5 * 60 * 1000); // Check every 5 minutes
window.cleanupBidsTable = cleanup; window.cleanupBidsTable = cleanup;
} }

View File

@@ -1,12 +1,12 @@
const CleanupManager = (function() { const CleanupManager = (function() {
const state = { const state = {
eventListeners: [], eventListeners: [],
timeouts: [], timeouts: [],
intervals: [], intervals: [],
animationFrames: [], animationFrames: [],
resources: new Map(), resources: new Map(),
debug: false debug: false,
memoryOptimizationInterval: null
}; };
function log(message, ...args) { function log(message, ...args) {
@@ -232,6 +232,229 @@ const CleanupManager = (function() {
}; };
}, },
setupMemoryOptimization: function(options = {}) {
const memoryCheckInterval = options.interval || 2 * 60 * 1000; // Default: 2 minutes
const maxCacheSize = options.maxCacheSize || 100;
const maxDataSize = options.maxDataSize || 1000;
if (state.memoryOptimizationInterval) {
this.clearInterval(state.memoryOptimizationInterval);
}
this.addListener(document, 'visibilitychange', () => {
if (document.hidden) {
log('Tab hidden - running memory optimization');
this.optimizeMemory({
maxCacheSize: maxCacheSize,
maxDataSize: maxDataSize
});
} else if (window.TooltipManager) {
window.TooltipManager.cleanup();
}
});
state.memoryOptimizationInterval = this.setInterval(() => {
if (document.hidden) {
log('Periodic memory optimization');
this.optimizeMemory({
maxCacheSize: maxCacheSize,
maxDataSize: maxDataSize
});
}
}, memoryCheckInterval);
log('Memory optimization setup complete');
return state.memoryOptimizationInterval;
},
optimizeMemory: function(options = {}) {
log('Running memory optimization');
if (window.TooltipManager && typeof window.TooltipManager.cleanup === 'function') {
window.TooltipManager.cleanup();
}
if (window.IdentityManager && typeof window.IdentityManager.limitCacheSize === 'function') {
window.IdentityManager.limitCacheSize(options.maxCacheSize || 100);
}
this.cleanupOrphanedResources();
if (window.gc) {
try {
window.gc();
log('Forced garbage collection');
} catch (e) {
}
}
document.dispatchEvent(new CustomEvent('memoryOptimized', {
detail: {
timestamp: Date.now(),
maxDataSize: options.maxDataSize || 1000
}
}));
log('Memory optimization complete');
},
cleanupOrphanedResources: function() {
let removedListeners = 0;
const validListeners = [];
for (let i = 0; i < state.eventListeners.length; i++) {
const listener = state.eventListeners[i];
if (!listener.element) {
removedListeners++;
continue;
}
try {
const isDetached = !(listener.element instanceof Node) ||
!document.body.contains(listener.element) ||
(listener.element.classList && listener.element.classList.contains('hidden')) ||
(listener.element.style && listener.element.style.display === 'none');
if (isDetached) {
try {
if (listener.element instanceof Node) {
listener.element.removeEventListener(listener.type, listener.handler, listener.options);
}
removedListeners++;
} catch (e) {
}
} else {
validListeners.push(listener);
}
} catch (e) {
log(`Error checking listener (removing): ${e.message}`);
removedListeners++;
}
}
if (removedListeners > 0) {
state.eventListeners = validListeners;
log(`Removed ${removedListeners} event listeners for detached/hidden elements`);
}
let removedResources = 0;
const resourcesForRemoval = [];
state.resources.forEach((info, id) => {
const resource = info.resource;
try {
if (resource instanceof Element && !document.body.contains(resource)) {
resourcesForRemoval.push(id);
}
if (resource && resource.element) {
if (resource.element instanceof Node && !document.body.contains(resource.element)) {
resourcesForRemoval.push(id);
}
}
} catch (e) {
log(`Error checking resource ${id}: ${e.message}`);
}
});
resourcesForRemoval.forEach(id => {
this.unregisterResource(id);
removedResources++;
});
if (removedResources > 0) {
log(`Removed ${removedResources} orphaned resources`);
}
if (window.TooltipManager) {
if (typeof window.TooltipManager.cleanupOrphanedTooltips === 'function') {
try {
window.TooltipManager.cleanupOrphanedTooltips();
} catch (e) {
if (typeof window.TooltipManager.cleanup === 'function') {
try {
window.TooltipManager.cleanup();
} catch (err) {
log(`Error cleaning up tooltips: ${err.message}`);
}
}
}
} else if (typeof window.TooltipManager.cleanup === 'function') {
try {
window.TooltipManager.cleanup();
} catch (e) {
log(`Error cleaning up tooltips: ${e.message}`);
}
}
}
try {
this.cleanupTooltipDOM();
} catch (e) {
log(`Error in cleanupTooltipDOM: ${e.message}`);
}
},
cleanupTooltipDOM: function() {
let removedElements = 0;
try {
const tooltipSelectors = [
'[role="tooltip"]',
'[id^="tooltip-"]',
'.tippy-box',
'[data-tippy-root]'
];
tooltipSelectors.forEach(selector => {
try {
const elements = document.querySelectorAll(selector);
elements.forEach(element => {
try {
if (!(element instanceof Element)) return;
const isDetached = !element.parentElement ||
!document.body.contains(element.parentElement) ||
element.classList.contains('hidden') ||
element.style.display === 'none' ||
element.style.visibility === 'hidden';
if (isDetached) {
try {
element.remove();
removedElements++;
} catch (e) {
}
}
} catch (err) {
}
});
} catch (err) {
log(`Error querying for ${selector}: ${err.message}`);
}
});
} catch (e) {
log(`Error in tooltip DOM cleanup: ${e.message}`);
}
if (removedElements > 0) {
log(`Removed ${removedElements} detached tooltip elements`);
}
},
setDebugMode: function(enabled) { setDebugMode: function(enabled) {
state.debug = Boolean(enabled); state.debug = Boolean(enabled);
log(`Debug mode ${state.debug ? 'enabled' : 'disabled'}`); log(`Debug mode ${state.debug ? 'enabled' : 'disabled'}`);
@@ -247,6 +470,17 @@ const CleanupManager = (function() {
if (options.debug !== undefined) { if (options.debug !== undefined) {
this.setDebugMode(options.debug); this.setDebugMode(options.debug);
} }
if (typeof window !== 'undefined' && !options.noAutoCleanup) {
this.addListener(window, 'beforeunload', () => {
this.clearAll();
});
}
if (typeof window !== 'undefined' && !options.noMemoryOptimization) {
this.setupMemoryOptimization(options.memoryOptions || {});
}
log('CleanupManager initialized'); log('CleanupManager initialized');
return this; return this;
} }
@@ -255,16 +489,20 @@ const CleanupManager = (function() {
return publicAPI; return publicAPI;
})(); })();
if (typeof module !== 'undefined' && module.exports) {
module.exports = CleanupManager;
}
window.CleanupManager = CleanupManager; if (typeof window !== 'undefined') {
window.CleanupManager = CleanupManager;
}
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
document.addEventListener('DOMContentLoaded', function() { if (document.readyState === 'complete' || document.readyState === 'interactive') {
if (!window.cleanupManagerInitialized) { CleanupManager.initialize({ debug: false });
CleanupManager.initialize(); } else {
window.cleanupManagerInitialized = true; document.addEventListener('DOMContentLoaded', () => {
CleanupManager.initialize({ debug: false });
}, { once: true });
} }
}); }
//console.log('CleanupManager initialized with methods:', Object.keys(CleanupManager));
console.log('CleanupManager initialized');

File diff suppressed because it is too large Load Diff

View File

@@ -59,7 +59,7 @@ const PriceManager = (function() {
return fetchPromise; return fetchPromise;
} }
console.log('PriceManager: Fetching latest prices.'); //console.log('PriceManager: Fetching latest prices.');
lastFetchTime = Date.now(); lastFetchTime = Date.now();
fetchPromise = this.fetchPrices() fetchPromise = this.fetchPrices()
.then(prices => { .then(prices => {
@@ -89,7 +89,7 @@ const PriceManager = (function() {
? window.config.coins.map(c => c.symbol).filter(symbol => symbol && symbol.trim() !== '') ? window.config.coins.map(c => c.symbol).filter(symbol => symbol && symbol.trim() !== '')
: ['BTC', 'XMR', 'PART', 'BCH', 'PIVX', 'FIRO', 'DASH', 'LTC', 'DOGE', 'DCR', 'NMC', 'WOW']); : ['BTC', 'XMR', 'PART', 'BCH', 'PIVX', 'FIRO', 'DASH', 'LTC', 'DOGE', 'DCR', 'NMC', 'WOW']);
console.log('PriceManager: lookupFiatRates ' + coinSymbols.join(', ')); //console.log('PriceManager: lookupFiatRates ' + coinSymbols.join(', '));
if (!coinSymbols.length) { if (!coinSymbols.length) {
throw new Error('No valid coins configured'); throw new Error('No valid coins configured');

View File

@@ -1,6 +1,5 @@
const TooltipManager = (function() { const TooltipManager = (function() {
let instance = null; let instance = null;
const tooltipInstanceMap = new WeakMap(); const tooltipInstanceMap = new WeakMap();
class TooltipManagerImpl { class TooltipManagerImpl {
@@ -8,20 +7,22 @@ const TooltipManager = (function() {
if (instance) { if (instance) {
return instance; return instance;
} }
this.pendingAnimationFrames = new Set();
this.pendingTimeouts = new Set();
this.tooltipIdCounter = 0; this.tooltipIdCounter = 0;
this.maxTooltips = 200; this.maxTooltips = 200;
this.cleanupThreshold = 1.2; this.cleanupThreshold = 1.2;
this.disconnectedCheckInterval = null;
this.cleanupInterval = null;
this.mutationObserver = null;
this.debug = false; this.debug = false;
this.tooltipData = new WeakMap(); this.tooltipData = new WeakMap();
this.setupStyles(); this.resources = {};
this.setupMutationObserver();
this.startPeriodicCleanup(); if (window.CleanupManager) {
this.startDisconnectedElementsCheck(); CleanupManager.registerResource(
'tooltipManager',
this,
(manager) => manager.dispose()
);
}
instance = this; instance = this;
} }
@@ -47,9 +48,7 @@ const TooltipManager = (function() {
this.performPeriodicCleanup(true); this.performPeriodicCleanup(true);
} }
const rafId = requestAnimationFrame(() => { const createTooltip = () => {
this.pendingAnimationFrames.delete(rafId);
if (!document.body.contains(element)) return; if (!document.body.contains(element)) return;
const rect = element.getBoundingClientRect(); const rect = element.getBoundingClientRect();
@@ -67,30 +66,29 @@ const TooltipManager = (function() {
} }
} else { } else {
retryCount++; retryCount++;
const timeoutId = setTimeout(() => { CleanupManager.setTimeout(() => {
this.pendingTimeouts.delete(timeoutId); CleanupManager.requestAnimationFrame(retryCreate);
const newRafId = requestAnimationFrame(retryCreate);
this.pendingAnimationFrames.add(newRafId);
}, 100); }, 100);
this.pendingTimeouts.add(timeoutId);
} }
}; };
const initialTimeoutId = setTimeout(() => { CleanupManager.setTimeout(() => {
this.pendingTimeouts.delete(initialTimeoutId); CleanupManager.requestAnimationFrame(retryCreate);
const retryRafId = requestAnimationFrame(retryCreate);
this.pendingAnimationFrames.add(retryRafId);
}, 100); }, 100);
this.pendingTimeouts.add(initialTimeoutId);
} }
}); };
this.pendingAnimationFrames.add(rafId); CleanupManager.requestAnimationFrame(createTooltip);
return null; return null;
} }
createTooltipInstance(element, content, options = {}) { createTooltipInstance(element, content, options = {}) {
if (!element || !document.body.contains(element) || !window.tippy) { if (!element || !document.body.contains(element)) {
return null;
}
if (typeof window.tippy !== 'function') {
console.error('Tippy.js is not available.');
return null; return null;
} }
@@ -130,7 +128,7 @@ const TooltipManager = (function() {
}, },
onHidden(instance) { onHidden(instance) {
if (!document.body.contains(element)) { if (!document.body.contains(element)) {
setTimeout(() => { CleanupManager.setTimeout(() => {
if (instance && instance.destroy) { if (instance && instance.destroy) {
instance.destroy(); instance.destroy();
} }
@@ -168,9 +166,26 @@ const TooltipManager = (function() {
}); });
element.setAttribute('data-tooltip-trigger-id', tooltipId); element.setAttribute('data-tooltip-trigger-id', tooltipId);
tooltipInstanceMap.set(element, tippyInstance[0]); tooltipInstanceMap.set(element, tippyInstance[0]);
const resourceId = CleanupManager.registerResource(
'tooltip',
{ element, instance: tippyInstance[0] },
(resource) => {
try {
if (resource.instance && resource.instance.destroy) {
resource.instance.destroy();
}
if (resource.element) {
resource.element.removeAttribute('data-tooltip-trigger-id');
resource.element.removeAttribute('aria-describedby');
}
} catch (e) {
console.warn('Error destroying tooltip during cleanup:', e);
}
}
);
return tippyInstance[0]; return tippyInstance[0];
} }
@@ -214,181 +229,292 @@ const TooltipManager = (function() {
} }
} }
getActiveTooltipInstances() {
const result = [];
try {
document.querySelectorAll('[data-tooltip-trigger-id]').forEach(element => {
const instance = element._tippy ? [element._tippy] : null;
if (instance) {
result.push([element, instance]);
}
});
} catch (error) {
console.error('Error getting active tooltip instances:', error);
}
return result;
}
cleanup() { cleanup() {
this.log('Running tooltip cleanup'); this.log('Running tooltip cleanup');
this.pendingAnimationFrames.forEach(id => { try {
cancelAnimationFrame(id); if ((window.location.pathname.includes('/offers') || window.location.pathname.includes('/bids')) &&
}); (document.querySelector('[data-tippy-root]:hover') || document.querySelector('[data-tooltip-trigger-id]:hover'))) {
this.pendingAnimationFrames.clear(); console.log('Skipping tooltip cleanup - tooltip is being hovered');
return;
this.pendingTimeouts.forEach(id => {
clearTimeout(id);
});
this.pendingTimeouts.clear();
const elements = document.querySelectorAll('[data-tooltip-trigger-id]');
const batchSize = 20;
const processElementsBatch = (startIdx) => {
const endIdx = Math.min(startIdx + batchSize, elements.length);
for (let i = startIdx; i < endIdx; i++) {
this.destroy(elements[i]);
} }
if (endIdx < elements.length) { const elements = document.querySelectorAll('[data-tooltip-trigger-id]:not(:hover)');
const rafId = requestAnimationFrame(() => { const batchSize = 20;
this.pendingAnimationFrames.delete(rafId);
processElementsBatch(endIdx); const processElementsBatch = (startIdx) => {
}); const endIdx = Math.min(startIdx + batchSize, elements.length);
this.pendingAnimationFrames.add(rafId);
for (let i = startIdx; i < endIdx; i++) {
this.destroy(elements[i]);
}
if (endIdx < elements.length) {
CleanupManager.requestAnimationFrame(() => {
processElementsBatch(endIdx);
});
} else {
this.cleanupOrphanedTooltips();
}
};
if (elements.length > 0) {
processElementsBatch(0);
} else { } else {
this.cleanupOrphanedTooltips(); this.cleanupOrphanedTooltips();
} }
}; } catch (error) {
console.error('Error during cleanup:', error);
}
}
if (elements.length > 0) { thoroughCleanup() {
processElementsBatch(0); this.log('Running thorough tooltip cleanup');
} else {
this.cleanupOrphanedTooltips(); try {
this.cleanup();
this.cleanupAllTooltips();
this.log('Thorough tooltip cleanup completed');
} catch (error) {
console.error('Error in thorough tooltip cleanup:', error);
}
}
cleanupAllTooltips() {
this.log('Cleaning up all tooltips');
try {
if ((window.location.pathname.includes('/offers') || window.location.pathname.includes('/bids')) &&
document.querySelector('#offers-body tr:hover')) {
this.log('Skipping all tooltips cleanup on offers/bids page with row hover');
return;
}
const tooltipRoots = document.querySelectorAll('[data-tippy-root]');
const tooltipTriggers = document.querySelectorAll('[data-tooltip-trigger-id]');
const tooltipElements = document.querySelectorAll('.tooltip');
const isHovered = element => {
try {
return element.matches && element.matches(':hover');
} catch (e) {
return false;
}
};
tooltipRoots.forEach(root => {
if (!isHovered(root) && root.parentNode) {
root.parentNode.removeChild(root);
}
});
tooltipTriggers.forEach(trigger => {
if (!isHovered(trigger)) {
trigger.removeAttribute('data-tooltip-trigger-id');
trigger.removeAttribute('aria-describedby');
if (trigger._tippy) {
try {
trigger._tippy.destroy();
trigger._tippy = null;
} catch (e) {}
}
}
});
tooltipElements.forEach(tooltip => {
if (!isHovered(tooltip) && tooltip.parentNode) {
let closestHoveredRow = false;
try {
if (tooltip.closest && tooltip.closest('tr') && isHovered(tooltip.closest('tr'))) {
closestHoveredRow = true;
}
} catch (e) {}
if (!closestHoveredRow) {
const style = window.getComputedStyle(tooltip);
const isVisible = style.display !== 'none' &&
style.visibility !== 'hidden' &&
style.opacity !== '0';
if (!isVisible) {
tooltip.parentNode.removeChild(tooltip);
}
}
}
});
} catch (error) {
console.error('Error cleaning up all tooltips:', error);
} }
} }
cleanupOrphanedTooltips() { cleanupOrphanedTooltips() {
const tippyElements = document.querySelectorAll('[data-tippy-root]'); try {
let removed = 0; const tippyElements = document.querySelectorAll('[data-tippy-root]');
let removed = 0;
tippyElements.forEach(element => { tippyElements.forEach(element => {
const tooltipId = element.getAttribute('data-for-tooltip-id'); const tooltipId = element.getAttribute('data-for-tooltip-id');
const trigger = tooltipId ? const trigger = tooltipId ?
document.querySelector(`[data-tooltip-trigger-id="${tooltipId}"]`) : document.querySelector(`[data-tooltip-trigger-id="${tooltipId}"]`) :
null; null;
if (!trigger || !document.body.contains(trigger)) { if (!trigger || !document.body.contains(trigger)) {
if (element.parentNode) { if (element.parentNode) {
element.parentNode.removeChild(element); element.parentNode.removeChild(element);
removed++; removed++;
} }
}
});
if (removed > 0) {
this.log(`Removed ${removed} orphaned tooltip elements`);
}
return removed;
}
setupMutationObserver() {
if (this.mutationObserver) {
this.mutationObserver.disconnect();
}
this.mutationObserver = new MutationObserver(mutations => {
let needsCleanup = false;
mutations.forEach(mutation => {
if (mutation.removedNodes.length) {
Array.from(mutation.removedNodes).forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.hasAttribute && node.hasAttribute('data-tooltip-trigger-id')) {
this.destroy(node);
needsCleanup = true;
}
if (node.querySelectorAll) {
const tooltipTriggers = node.querySelectorAll('[data-tooltip-trigger-id]');
if (tooltipTriggers.length > 0) {
tooltipTriggers.forEach(trigger => {
this.destroy(trigger);
});
needsCleanup = true;
}
}
}
});
} }
}); });
if (needsCleanup) { if (removed > 0) {
this.cleanupOrphanedTooltips(); this.log(`Removed ${removed} orphaned tooltip elements`);
} }
});
this.mutationObserver.observe(document.body, { return removed;
childList: true, } catch (error) {
subtree: true console.error('Error cleaning up orphaned tooltips:', error);
}); return 0;
}
}
return this.mutationObserver; setupMutationObserver() {
try {
const mutationObserver = new MutationObserver(mutations => {
let needsCleanup = false;
mutations.forEach(mutation => {
if (mutation.removedNodes.length) {
Array.from(mutation.removedNodes).forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.hasAttribute && node.hasAttribute('data-tooltip-trigger-id')) {
this.destroy(node);
needsCleanup = true;
}
if (node.querySelectorAll) {
const tooltipTriggers = node.querySelectorAll('[data-tooltip-trigger-id]');
if (tooltipTriggers.length > 0) {
tooltipTriggers.forEach(trigger => {
this.destroy(trigger);
});
needsCleanup = true;
}
}
}
});
}
});
if (needsCleanup) {
this.cleanupOrphanedTooltips();
}
});
mutationObserver.observe(document.body, {
childList: true,
subtree: true
});
this.resources.mutationObserver = CleanupManager.registerResource(
'mutationObserver',
mutationObserver,
(observer) => observer.disconnect()
);
return mutationObserver;
} catch (error) {
console.error('Error setting up mutation observer:', error);
return null;
}
} }
startDisconnectedElementsCheck() { startDisconnectedElementsCheck() {
if (this.disconnectedCheckInterval) { try {
clearInterval(this.disconnectedCheckInterval); this.resources.disconnectedCheckInterval = CleanupManager.setInterval(() => {
this.checkForDisconnectedElements();
}, 60000);
} catch (error) {
console.error('Error starting disconnected elements check:', error);
} }
this.disconnectedCheckInterval = setInterval(() => {
this.checkForDisconnectedElements();
}, 60000);
} }
checkForDisconnectedElements() { checkForDisconnectedElements() {
const elements = document.querySelectorAll('[data-tooltip-trigger-id]'); try {
let removedCount = 0; const elements = document.querySelectorAll('[data-tooltip-trigger-id]');
let removedCount = 0;
elements.forEach(element => { elements.forEach(element => {
if (!document.body.contains(element)) { if (!document.body.contains(element)) {
this.destroy(element); this.destroy(element);
removedCount++; removedCount++;
}
});
if (removedCount > 0) {
this.log(`Removed ${removedCount} tooltips for disconnected elements`);
this.cleanupOrphanedTooltips();
} }
}); } catch (error) {
console.error('Error checking for disconnected elements:', error);
if (removedCount > 0) {
this.log(`Removed ${removedCount} tooltips for disconnected elements`);
this.cleanupOrphanedTooltips();
} }
} }
startPeriodicCleanup() { startPeriodicCleanup() {
if (this.cleanupInterval) { try {
clearInterval(this.cleanupInterval); this.resources.cleanupInterval = CleanupManager.setInterval(() => {
this.performPeriodicCleanup();
}, 120000);
} catch (error) {
console.error('Error starting periodic cleanup:', error);
} }
this.cleanupInterval = setInterval(() => {
this.performPeriodicCleanup();
}, 120000);
} }
performPeriodicCleanup(force = false) { performPeriodicCleanup(force = false) {
this.cleanupOrphanedTooltips(); try {
if ((window.location.pathname.includes('/offers') || window.location.pathname.includes('/bids')) &&
this.checkForDisconnectedElements(); !force) {
return;
const tooltipCount = document.querySelectorAll('[data-tippy-root]').length;
if (force || tooltipCount > this.maxTooltips) {
this.log(`Performing aggressive cleanup (${tooltipCount} tooltips)`);
this.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');
}
} }
this.cleanupOrphanedTooltips();
this.checkForDisconnectedElements();
const tooltipCount = document.querySelectorAll('[data-tippy-root]').length;
if (force || tooltipCount > this.maxTooltips) {
this.log(`Performing aggressive cleanup (${tooltipCount} tooltips)`);
this.cleanup();
}
} catch (error) {
console.error('Error performing periodic cleanup:', error);
} }
} }
setupStyles() { setupStyles() {
if (document.getElementById('tooltip-styles')) return; if (document.getElementById('tooltip-styles')) return;
document.head.insertAdjacentHTML('beforeend', ` try {
<style id="tooltip-styles"> const style = document.createElement('style');
style.id = 'tooltip-styles';
style.textContent = `
[data-tippy-root] { [data-tippy-root] {
position: fixed !important; position: fixed !important;
z-index: 9999 !important; z-index: 9999 !important;
@@ -472,62 +598,166 @@ const TooltipManager = (function() {
.tippy-box[data-placement^='right'] > .tippy-arrow { .tippy-box[data-placement^='right'] > .tippy-arrow {
left: 0; left: 0;
} }
</style> `;
`); document.head.appendChild(style);
this.resources.tooltipStyles = CleanupManager.registerResource(
'tooltipStyles',
style,
(styleElement) => {
if (styleElement && styleElement.parentNode) {
styleElement.parentNode.removeChild(styleElement);
}
}
);
} catch (error) {
console.error('Error setting up styles:', error);
try {
document.head.insertAdjacentHTML('beforeend', `
<style id="tooltip-styles">
[data-tippy-root] {
position: fixed !important;
z-index: 9999 !important;
pointer-events: none !important;
}
.tippy-box {
font-size: 0.875rem;
line-height: 1.25rem;
font-weight: 500;
border-radius: 0.5rem;
color: white;
position: relative !important;
pointer-events: auto !important;
}
.tippy-content {
padding: 0.5rem 0.75rem !important;
}
.tippy-box .bg-gray-400 {
background-color: rgb(156 163 175);
padding: 0.5rem 0.75rem;
}
.tippy-box:has(.bg-gray-400) .tippy-arrow {
color: rgb(156 163 175);
}
.tippy-box .bg-red-500 {
background-color: rgb(239 68 68);
padding: 0.5rem 0.75rem;
}
.tippy-box:has(.bg-red-500) .tippy-arrow {
color: rgb(239 68 68);
}
.tippy-box .bg-gray-300 {
background-color: rgb(209 213 219);
padding: 0.5rem 0.75rem;
}
.tippy-box:has(.bg-gray-300) .tippy-arrow {
color: rgb(209 213 219);
}
.tippy-box .bg-green-700 {
background-color: rgb(21 128 61);
padding: 0.5rem 0.75rem;
}
.tippy-box:has(.bg-green-700) .tippy-arrow {
color: rgb(21 128 61);
}
.tippy-box[data-placement^='top'] > .tippy-arrow::before {
border-top-color: currentColor;
}
.tippy-box[data-placement^='bottom'] > .tippy-arrow::before {
border-bottom-color: currentColor;
}
.tippy-box[data-placement^='left'] > .tippy-arrow::before {
border-left-color: currentColor;
}
.tippy-box[data-placement^='right'] > .tippy-arrow::before {
border-right-color: currentColor;
}
.tippy-box[data-placement^='top'] > .tippy-arrow {
bottom: 0;
}
.tippy-box[data-placement^='bottom'] > .tippy-arrow {
top: 0;
}
.tippy-box[data-placement^='left'] > .tippy-arrow {
right: 0;
}
.tippy-box[data-placement^='right'] > .tippy-arrow {
left: 0;
}
</style>
`);
const styleElement = document.getElementById('tooltip-styles');
if (styleElement) {
this.resources.tooltipStyles = CleanupManager.registerResource(
'tooltipStyles',
styleElement,
(elem) => {
if (elem && elem.parentNode) {
elem.parentNode.removeChild(elem);
}
}
);
}
} catch (e) {
console.error('Failed to add tooltip styles:', e);
}
}
} }
initializeTooltips(selector = '[data-tooltip-target]') { initializeTooltips(selector = '[data-tooltip-target]') {
document.querySelectorAll(selector).forEach(element => { try {
const targetId = element.getAttribute('data-tooltip-target'); document.querySelectorAll(selector).forEach(element => {
if (!targetId) return; const targetId = element.getAttribute('data-tooltip-target');
if (!targetId) return;
const tooltipContent = document.getElementById(targetId); const tooltipContent = document.getElementById(targetId);
if (tooltipContent) { if (tooltipContent) {
this.create(element, tooltipContent.innerHTML, { this.create(element, tooltipContent.innerHTML, {
placement: element.getAttribute('data-tooltip-placement') || 'top' placement: element.getAttribute('data-tooltip-placement') || 'top'
}); });
} }
}); });
} catch (error) {
console.error('Error initializing tooltips:', error);
}
} }
dispose() { dispose() {
this.log('Disposing TooltipManager'); this.log('Disposing TooltipManager');
this.cleanup(); try {
this.cleanup();
this.pendingAnimationFrames.forEach(id => { Object.values(this.resources).forEach(resourceId => {
cancelAnimationFrame(id); if (resourceId) {
}); CleanupManager.unregisterResource(resourceId);
this.pendingAnimationFrames.clear(); }
});
this.pendingTimeouts.forEach(id => { this.resources = {};
clearTimeout(id);
});
this.pendingTimeouts.clear();
if (this.disconnectedCheckInterval) { instance = null;
clearInterval(this.disconnectedCheckInterval); return true;
this.disconnectedCheckInterval = null; } catch (error) {
console.error('Error disposing TooltipManager:', error);
return false;
} }
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
this.cleanupInterval = null;
}
if (this.mutationObserver) {
this.mutationObserver.disconnect();
this.mutationObserver = null;
}
const styleElement = document.getElementById('tooltip-styles');
if (styleElement && styleElement.parentNode) {
styleElement.parentNode.removeChild(styleElement);
}
instance = null;
return true;
} }
setDebugMode(enabled) { setDebugMode(enabled) {
@@ -536,16 +766,26 @@ const TooltipManager = (function() {
} }
initialize(options = {}) { initialize(options = {}) {
if (options.maxTooltips) { try {
this.maxTooltips = options.maxTooltips; if (options.maxTooltips) {
} this.maxTooltips = options.maxTooltips;
}
if (options.debug !== undefined) { if (options.debug !== undefined) {
this.setDebugMode(options.debug); this.setDebugMode(options.debug);
} }
this.log('TooltipManager initialized'); this.setupStyles();
return this; this.setupMutationObserver();
this.startPeriodicCleanup();
this.startDisconnectedElementsCheck();
this.log('TooltipManager initialized');
return this;
} catch (error) {
console.error('Error initializing TooltipManager:', error);
return this;
}
} }
} }
@@ -580,6 +820,11 @@ const TooltipManager = (function() {
return manager.cleanup(...args); return manager.cleanup(...args);
}, },
thoroughCleanup: function() {
const manager = this.getInstance();
return manager.thoroughCleanup();
},
initializeTooltips: function(...args) { initializeTooltips: function(...args) {
const manager = this.getInstance(); const manager = this.getInstance();
return manager.initializeTooltips(...args); return manager.initializeTooltips(...args);
@@ -590,6 +835,11 @@ const TooltipManager = (function() {
return manager.setDebugMode(enabled); return manager.setDebugMode(enabled);
}, },
getActiveTooltipInstances: function() {
const manager = this.getInstance();
return manager.getActiveTooltipInstances();
},
dispose: function(...args) { dispose: function(...args) {
const manager = this.getInstance(); const manager = this.getInstance();
return manager.dispose(...args); return manager.dispose(...args);
@@ -597,37 +847,53 @@ const TooltipManager = (function() {
}; };
})(); })();
function installTooltipManager() {
const originalTooltipManager = window.TooltipManager;
window.TooltipManager = TooltipManager;
window.TooltipManager.initialize({
maxTooltips: 200,
debug: false
});
document.addEventListener('DOMContentLoaded', function() {
if (!window.tooltipManagerInitialized) {
window.TooltipManager.initializeTooltips();
window.tooltipManagerInitialized = true;
}
});
return originalTooltipManager;
}
if (typeof document !== 'undefined') {
if (document.readyState === 'complete' || document.readyState === 'interactive') {
installTooltipManager();
} else {
document.addEventListener('DOMContentLoaded', installTooltipManager);
}
}
if (typeof module !== 'undefined' && module.exports) { if (typeof module !== 'undefined' && module.exports) {
module.exports = TooltipManager; module.exports = TooltipManager;
} }
window.TooltipManager = TooltipManager; if (typeof window !== 'undefined') {
console.log('TooltipManager initialized'); window.TooltipManager = TooltipManager;
}
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
function initializeTooltipManager() {
if (!window.tooltipManagerInitialized) {
if (!window.CleanupManager) {
console.warn('CleanupManager not found. TooltipManager will run with limited functionality.');
window.CleanupManager = window.CleanupManager || {
registerResource: (type, resource, cleanup) => {
return Math.random().toString(36).substring(2, 9);
},
unregisterResource: () => {},
setTimeout: (callback, delay) => setTimeout(callback, delay),
setInterval: (callback, delay) => setInterval(callback, delay),
requestAnimationFrame: (callback) => requestAnimationFrame(callback),
addListener: (element, type, handler, options) => {
element.addEventListener(type, handler, options);
return handler;
}
};
}
window.TooltipManager.initialize({
maxTooltips: 200,
debug: false
});
window.TooltipManager.initializeTooltips();
window.tooltipManagerInitialized = true;
}
}
if (document.readyState === 'complete' || document.readyState === 'interactive') {
initializeTooltipManager();
} else {
document.addEventListener('DOMContentLoaded', initializeTooltipManager, { once: true });
}
}
if (typeof window !== 'undefined' && typeof console !== 'undefined') {
console.log('TooltipManager initialized');
}

View File

@@ -1370,7 +1370,6 @@ function createRecipientTooltip(uniqueId, identityInfo, identity, successRate, t
return 'text-red-600'; return 'text-red-600';
}; };
const truncateText = (text, maxLength) => { const truncateText = (text, maxLength) => {
if (text.length <= maxLength) return text; if (text.length <= maxLength) return text;
return text.substring(0, maxLength) + '...'; return text.substring(0, maxLength) + '...';
@@ -2167,6 +2166,18 @@ document.addEventListener('DOMContentLoaded', async function() {
tableRateModule.init(); tableRateModule.init();
} }
document.addEventListener('memoryOptimized', (e) => {
if (jsonData && jsonData.length > e.detail.maxDataSize) {
console.log(`Trimming offers data from ${jsonData.length} to ${e.detail.maxDataSize} items`);
jsonData = jsonData.slice(0, e.detail.maxDataSize);
}
if (originalJsonData && originalJsonData.length > e.detail.maxDataSize) {
console.log(`Trimming original offers data from ${originalJsonData.length} to ${e.detail.maxDataSize} items`);
originalJsonData = originalJsonData.slice(0, e.detail.maxDataSize);
}
});
await initializeTableAndData(); await initializeTableAndData();
if (window.PriceManager) { if (window.PriceManager) {
@@ -2179,7 +2190,7 @@ document.addEventListener('DOMContentLoaded', async function() {
if (window.WebSocketManager) { if (window.WebSocketManager) {
WebSocketManager.addMessageHandler('message', async (data) => { WebSocketManager.addMessageHandler('message', async (data) => {
if (data.event === 'new_offer' || data.event === 'offer_revoked') { if (data.event === 'new_offer' || data.event === 'offer_revoked') {
console.log('WebSocket event received:', data.event); //console.log('WebSocket event received:', data.event);
try { try {
const previousPrices = latestPrices; const previousPrices = latestPrices;
@@ -2230,7 +2241,7 @@ document.addEventListener('DOMContentLoaded', async function() {
updatePaginationInfo(); updatePaginationInfo();
console.log('WebSocket-triggered refresh completed successfully'); //console.log('WebSocket-triggered refresh completed successfully');
} catch (error) { } catch (error) {
console.error('Error during WebSocket-triggered refresh:', error); console.error('Error during WebSocket-triggered refresh:', error);
NetworkManager.handleNetworkError(error); NetworkManager.handleNetworkError(error);
@@ -2357,28 +2368,10 @@ function cleanup() {
if (window.CleanupManager) { if (window.CleanupManager) {
window.CleanupManager.removeListenersByElement(row); window.CleanupManager.removeListenersByElement(row);
} }
while (row.attributes && row.attributes.length > 0) {
row.removeAttribute(row.attributes[0].name);
}
while (row.firstChild) {
row.removeChild(row.firstChild);
}
}); });
offersBody.innerHTML = ''; offersBody.innerHTML = '';
} }
const filterForm = document.getElementById('filterForm');
if (filterForm) {
CleanupManager.removeListenersByElement(filterForm);
filterForm.querySelectorAll('select').forEach(select => {
CleanupManager.removeListenersByElement(select);
});
}
jsonData = null; jsonData = null;
originalJsonData = null; originalJsonData = null;
latestPrices = null; latestPrices = null;
@@ -2388,13 +2381,12 @@ function cleanup() {
} }
if (window.MemoryManager) { if (window.MemoryManager) {
if (window.MemoryManager.cleanupTooltips) { if (window.MemoryManager.forceCleanup) {
window.MemoryManager.cleanupTooltips(true); window.MemoryManager.forceCleanup();
} }
window.MemoryManager.forceCleanup();
} }
console.log('Offers.js cleanup completed'); //console.log('Offers.js cleanup completed');
} catch (error) { } catch (error) {
console.error('Error during cleanup:', error); console.error('Error during cleanup:', error);
} }

View File

@@ -578,7 +578,7 @@ const chartModule = {
this.chartRefs.set(element, chart); this.chartRefs.set(element, chart);
}, },
destroyChart: function() { destroyChart: function() {
if (chartModule.chart) { if (chartModule.chart) {
try { try {
const chartInstance = chartModule.chart; const chartInstance = chartModule.chart;
@@ -592,12 +592,17 @@ const chartModule = {
if (canvas) { if (canvas) {
chartModule.chartRefs.delete(canvas); chartModule.chartRefs.delete(canvas);
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
} }
} catch (e) { } catch (e) {
console.error('Error destroying chart:', e); console.error('Error destroying chart:', e);
} }
} }
}, },
initChart: function() { initChart: function() {
this.destroyChart(); this.destroyChart();