worker orbs: drive the render loop with setInterval on firefox (permanent 1fps fix)

the dashboard orb canvas kept falling back to ~1fps on firefox after the page settled. root
cause: firefox throttles requestAnimationFrame to ~1fps for a canvas it heuristically deems
occluded. the WAA keepalive only delayed the heuristic; it re-fired over time.

real fix: on firefox, drive tick() with a setInterval(~60fps) instead of self-scheduling rAF —
setInterval is not subject to the canvas-occlusion rAF throttle, so the orbs stay at full
framerate indefinitely. chrome is untouched (keeps vsync-aligned rAF). same render workload
(idle still drops to 20fps via the existing sleep skip); background tabs still parked by the
visibilitychange handler → stopLoop clears the interval. kept the keepalive (harmless).
This commit is contained in:
BoulderBadgeDad 2026-06-27 18:27:31 -07:00
parent 80d84c6de3
commit 88b68b8073

View file

@ -68,6 +68,13 @@
let errorHeat = 0; // 0..1 aggregate "stress" — bumps on real worker errors, decays over time
let state = 'idle';
let animFrame = null;
let intervalId = null;
// Firefox throttles requestAnimationFrame to ~1fps for a canvas it heuristically deems
// occluded — the dashboard orb canvas falls into this after the page settles (the keepalive
// only delays it). A setInterval-driven loop is NOT subject to that canvas-occlusion rAF
// throttle, so on Firefox we drive the render with a timer. Chrome keeps rAF untouched
// (works fine + vsync-aligned). Background tabs are still parked via onVisibility→stopLoop.
const _isFirefox = !!(window.CSS && CSS.supports && CSS.supports('-moz-appearance', 'none'));
let onDashboard = false;
let expandProgress = 0;
let staggerTimers = [];
@ -566,6 +573,12 @@
})();
function startLoop() {
if (_isFirefox) {
if (intervalId) return;
// ~60fps timer — immune to Firefox's canvas-occlusion rAF throttle.
intervalId = setInterval(tick, 1000 / 60);
return;
}
if (animFrame) return;
tick();
}
@ -575,10 +588,17 @@
cancelAnimationFrame(animFrame);
animFrame = null;
}
if (intervalId) {
clearInterval(intervalId);
intervalId = null;
}
}
function tick() {
animFrame = requestAnimationFrame(tick);
// Chrome self-schedules via rAF; Firefox is driven by the setInterval above.
if (!_isFirefox) {
animFrame = requestAnimationFrame(tick);
}
if (!canvas || !ctx) return;
// Yield the frame to active scrolling (orbs freeze, resume on idle).