perf: pause background polling and animations when tab is hidden

- Background polling pauses when tab is not visible
- Timer-based animations replaced with efficient frame-based updates
- Reduced CPU usage when OpenChamber is in background
This commit is contained in:
Bohdan Triapitsyn
2026-04-06 21:05:02 +03:00
parent 2f1377ec01
commit 16a4157440
4 changed files with 159 additions and 12 deletions
@@ -405,6 +405,10 @@ export function DesktopHostSwitcherDialog({
};
void run();
const interval = window.setInterval(() => {
// Skip polling when tab is hidden to reduce background work
if (typeof document !== 'undefined' && document.visibilityState !== 'visible') {
return;
}
void run();
}, 1_500);
@@ -1255,6 +1259,10 @@ export function DesktopHostSwitcherButton({ headerIconButtonClass }: DesktopHost
void run();
const interval = window.setInterval(() => {
// Skip polling when tab is hidden to reduce background work
if (typeof document !== 'undefined' && document.visibilityState !== 'visible') {
return;
}
void run();
}, 10_000);
return () => {
@@ -250,12 +250,41 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
return;
}
const intervalId = setInterval(() => {
setChatRenderPreviewTick((prev) => (prev + 1) % 24);
}, 420);
// Use requestAnimationFrame for smoother animation without setInterval overhead
let rafId: number | null = null;
let lastTime = Date.now();
const tick = () => {
const now = Date.now();
// Update every ~420ms
if (now - lastTime >= 420) {
setChatRenderPreviewTick((prev) => (prev + 1) % 24);
lastTime = now;
}
rafId = requestAnimationFrame(tick);
};
// Only run when visible
if (typeof document === 'undefined' || document.visibilityState === 'visible') {
rafId = requestAnimationFrame(tick);
}
const onVisibility = () => {
if (document.visibilityState === 'visible' && rafId === null) {
rafId = requestAnimationFrame(tick);
} else if (document.visibilityState !== 'visible' && rafId !== null) {
cancelAnimationFrame(rafId);
rafId = null;
}
};
document.addEventListener('visibilitychange', onVisibility);
return () => {
clearInterval(intervalId);
document.removeEventListener('visibilitychange', onVisibility);
if (rafId !== null) {
cancelAnimationFrame(rafId);
}
};
}, [shouldAnimateChatPreview]);
@@ -486,6 +486,9 @@ export const TunnelSettings: React.FC = () => {
return;
}
let rafId: number | null = null;
let lastTime = Date.now();
const updateRemaining = () => {
const remaining = tunnelInfo.bootstrapExpiresAt ? tunnelInfo.bootstrapExpiresAt - Date.now() : 0;
if (remaining <= 0) {
@@ -495,14 +498,79 @@ export const TunnelSettings: React.FC = () => {
}
};
const tick = () => {
const now = Date.now();
// Update only once per second
if (now - lastTime >= 1_000) {
updateRemaining();
lastTime = now;
}
rafId = requestAnimationFrame(tick);
};
updateRemaining();
const timer = window.setInterval(updateRemaining, 1000);
return () => window.clearInterval(timer);
// Only run when visible
if (typeof document === 'undefined' || document.visibilityState === 'visible') {
rafId = requestAnimationFrame(tick);
}
const onVisibility = () => {
if (document.visibilityState === 'visible' && rafId === null) {
rafId = requestAnimationFrame(tick);
} else if (document.visibilityState !== 'visible' && rafId !== null) {
cancelAnimationFrame(rafId);
rafId = null;
}
};
document.addEventListener('visibilitychange', onVisibility);
return () => {
document.removeEventListener('visibilitychange', onVisibility);
if (rafId !== null) {
cancelAnimationFrame(rafId);
}
};
}, [tunnelInfo?.bootstrapExpiresAt]);
React.useEffect(() => {
const timer = window.setInterval(() => setNowTs(Date.now()), 1000);
return () => window.clearInterval(timer);
// Use requestAnimationFrame for smoother updates without setInterval overhead
let rafId: number | null = null;
let lastTime = Date.now();
const tick = () => {
const now = Date.now();
// Update only once per second
if (now - lastTime >= 1_000) {
setNowTs(now);
lastTime = now;
}
rafId = requestAnimationFrame(tick);
};
// Only run when visible
if (typeof document === 'undefined' || document.visibilityState === 'visible') {
rafId = requestAnimationFrame(tick);
}
const onVisibility = () => {
if (document.visibilityState === 'visible' && rafId === null) {
rafId = requestAnimationFrame(tick);
} else if (document.visibilityState !== 'visible' && rafId !== null) {
cancelAnimationFrame(rafId);
rafId = null;
}
};
document.addEventListener('visibilitychange', onVisibility);
return () => {
document.removeEventListener('visibilitychange', onVisibility);
if (rafId !== null) {
cancelAnimationFrame(rafId);
}
};
}, []);
React.useEffect(() => {
@@ -530,6 +598,10 @@ export const TunnelSettings: React.FC = () => {
};
const timer = window.setInterval(() => {
// Skip polling when tab is hidden
if (typeof document !== 'undefined' && document.visibilityState !== 'visible') {
return;
}
void refreshSessions();
}, 5000);
@@ -305,6 +305,10 @@ export const RemoteInstancesPage: React.FC = () => {
return;
}
const interval = window.setInterval(() => {
// Skip polling when tab is hidden to reduce background work
if (typeof document !== 'undefined' && document.visibilityState !== 'visible') {
return;
}
void refreshStatuses();
}, 2_000);
return () => {
@@ -313,11 +317,41 @@ export const RemoteInstancesPage: React.FC = () => {
}, [refreshStatuses, selectedId]);
React.useEffect(() => {
const interval = window.setInterval(() => {
setClockMs(Date.now());
}, 1_000);
// Use requestAnimationFrame for smoother clock updates without setInterval overhead
let rafId: number | null = null;
let lastTime = Date.now();
const tick = () => {
const now = Date.now();
// Update only once per second
if (now - lastTime >= 1_000) {
setClockMs(now);
lastTime = now;
}
rafId = requestAnimationFrame(tick);
};
// Only run when visible
if (typeof document === 'undefined' || document.visibilityState === 'visible') {
rafId = requestAnimationFrame(tick);
}
const onVisibility = () => {
if (document.visibilityState === 'visible' && rafId === null) {
rafId = requestAnimationFrame(tick);
} else if (document.visibilityState !== 'visible' && rafId !== null) {
cancelAnimationFrame(rafId);
rafId = null;
}
};
document.addEventListener('visibilitychange', onVisibility);
return () => {
window.clearInterval(interval);
document.removeEventListener('visibilitychange', onVisibility);
if (rafId !== null) {
cancelAnimationFrame(rafId);
}
};
}, []);
@@ -525,6 +559,10 @@ export const RemoteInstancesPage: React.FC = () => {
void run();
const interval = window.setInterval(() => {
// Skip polling when tab is hidden
if (typeof document !== 'undefined' && document.visibilityState !== 'visible') {
return;
}
void run();
}, 1_000);