Skip to content

Commit fc494cd

Browse files
AmazingAngclaude
andcommitted
Fix mobile WebView crash: stop RAF loop when hidden, cap animation buffers
- animatePulse RAF loop now fully stops when document.hidden instead of spinning idle frames; visibilitychange listener restarts it on resume - Cap tradeFlashesRef (100), overlayBurstRef (200), closedAnimsRef (100), newMarketAnimRef (100) to prevent unbounded memory growth - Root cause of Binance/OKX wallet browser "site can't be reached" after a few minutes: animation refs grew indefinitely + RAF allocated GeoJSON objects every frame even when page was backgrounded Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
1 parent aa18043 commit fc494cd

1 file changed

Lines changed: 25 additions & 1 deletion

File tree

src/components/WorldMap.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ function WorldMapInner({
439439

440440
const animatePulse = () => {
441441
if (prefersReducedMotion || document.hidden) {
442-
pulseRef.current = requestAnimationFrame(animatePulse);
442+
// Stop RAF loop when hidden — visibilitychange listener restarts it
443443
return;
444444
}
445445
phase = (phase + 0.04) % (2 * Math.PI);
@@ -638,6 +638,16 @@ function WorldMapInner({
638638
pulseRef.current = requestAnimationFrame(animatePulse);
639639
};
640640
pulseRef.current = requestAnimationFrame(animatePulse);
641+
642+
// Restart RAF loop when tab becomes visible again
643+
const onVisChange = () => {
644+
if (!document.hidden) {
645+
cancelAnimationFrame(pulseRef.current);
646+
pulseRef.current = requestAnimationFrame(animatePulse);
647+
}
648+
};
649+
document.addEventListener("visibilitychange", onVisChange);
650+
map.once("remove", () => document.removeEventListener("visibilitychange", onVisChange));
641651
});
642652

643653
return () => {
@@ -1692,6 +1702,11 @@ function WorldMapInner({
16921702
closedAnimsRef.current.set(id, { startTime: now, ...pos });
16931703
}
16941704
}
1705+
// Cap closed animations map
1706+
if (closedAnimsRef.current.size > 100) {
1707+
const it = closedAnimsRef.current.keys();
1708+
while (closedAnimsRef.current.size > 100) closedAnimsRef.current.delete(it.next().value!);
1709+
}
16951710
prevMarketIdsRef.current = currentIds;
16961711
} else if (tierChanged) {
16971712
// Reset tracking on tier change
@@ -1802,6 +1817,11 @@ function WorldMapInner({
18021817
lat: coords[0],
18031818
});
18041819
}
1820+
// Cap new market animations map
1821+
if (newMarketAnimRef.current.size > 100) {
1822+
const it = newMarketAnimRef.current.keys();
1823+
while (newMarketAnimRef.current.size > 100) newMarketAnimRef.current.delete(it.next().value!);
1824+
}
18051825
}, [newMarkets]);
18061826

18071827
// Feature 4: Track whale trade flash animations
@@ -1826,6 +1846,8 @@ function WorldMapInner({
18261846
isSmart: trade.isSmartWallet,
18271847
});
18281848
}
1849+
// Cap animation array to prevent unbounded memory growth
1850+
if (tradeFlashesRef.current.length > 100) tradeFlashesRef.current.splice(0, tradeFlashesRef.current.length - 100);
18291851
// Prune dedup set to prevent unbounded memory growth
18301852
if (seenTradeKeysRef.current.size > 500) {
18311853
const keep = new Set<string>();
@@ -1874,6 +1896,8 @@ function WorldMapInner({
18741896
color: meta.color,
18751897
});
18761898
});
1899+
// Cap burst array
1900+
if (overlayBurstRef.current.length > 200) overlayBurstRef.current.splice(0, overlayBurstRef.current.length - 200);
18771901
}
18781902
}
18791903
}

0 commit comments

Comments
 (0)