Refresh the page when it comes back from the background
Durations of running builds are ticked client-side, so a page left in the background kept counting up a build that had long finished on the server — on a phone the tab is frozen for an hour and comes back showing a build "running" for an hour. Resuming now re-arms the poll unconditionally instead of only when the page was stopped: a frozen page may never deliver the hidden event, and its throttled interval then fires whenever the browser feels like it. `pageshow` and `focus` are listened to as well, because not every browser reports a returning page as a visibility change; resume events arriving together are collapsed into one fetch. Verified in a browser: hidden for 11s over a 10s interval makes no request, a lone focus event makes exactly one, and a second resume event right after makes none. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
cd0596b17a
commit
1d28ec2a3f
@@ -89,6 +89,8 @@ const FETCH_TIMEOUT_MS = 8000;
|
|||||||
const TABLE_POLL_MS = 10000;
|
const TABLE_POLL_MS = 10000;
|
||||||
const CURRENT_POLL_MS = 3000;
|
const CURRENT_POLL_MS = 3000;
|
||||||
const SYSTEM_POLL_MS = 60000;
|
const SYSTEM_POLL_MS = 60000;
|
||||||
|
// resume events (visibilitychange, pageshow, focus) often arrive in pairs
|
||||||
|
const RESUME_MIN_GAP_MS = 1000;
|
||||||
|
|
||||||
function metaContent(name) {
|
function metaContent(name) {
|
||||||
const element = document.querySelector(`meta[name="${name}"]`);
|
const element = document.querySelector(`meta[name="${name}"]`);
|
||||||
@@ -186,19 +188,34 @@ function setLiveIndicator(ok, detail) {
|
|||||||
// The page's refresh function; actions trigger it for an immediate update.
|
// The page's refresh function; actions trigger it for an immediate update.
|
||||||
let refreshNow = null;
|
let refreshNow = null;
|
||||||
|
|
||||||
/** Polls `refresh`; paused while the tab is hidden, refreshed immediately when visible again. */
|
/**
|
||||||
|
* Polls `refresh`; paused while the tab is hidden, refreshed immediately when the page
|
||||||
|
* becomes visible again — the durations of running builds are ticked client-side, so a
|
||||||
|
* page returning from the background would otherwise keep counting up a build that has
|
||||||
|
* long finished on the server.
|
||||||
|
*
|
||||||
|
* Resuming re-arms the interval unconditionally instead of only when the page was
|
||||||
|
* stopped: a backgrounded page (phone, app switch) may be frozen without ever
|
||||||
|
* delivering the hidden event, and its throttled interval then fires whenever the
|
||||||
|
* browser feels like it. `pageshow` and `focus` are listened to as well, because not
|
||||||
|
* every browser reports a returning page as a visibility change.
|
||||||
|
*/
|
||||||
function startPolling(refresh, intervalMs) {
|
function startPolling(refresh, intervalMs) {
|
||||||
let timer = null;
|
let timer = null;
|
||||||
|
let lastTickAt = 0;
|
||||||
const tick = () => {
|
const tick = () => {
|
||||||
|
lastTickAt = Date.now();
|
||||||
refresh()
|
refresh()
|
||||||
.then(() => setLiveIndicator(true, "last update " + formatTimestamp(new Date().toISOString())))
|
.then(() => setLiveIndicator(true, "last update " + formatTimestamp(new Date().toISOString())))
|
||||||
.catch((error) => setLiveIndicator(false, String(error)));
|
.catch((error) => setLiveIndicator(false, String(error)));
|
||||||
};
|
};
|
||||||
const start = () => {
|
const start = () => {
|
||||||
if (timer === null) {
|
stop();
|
||||||
|
// several resume events can arrive together; one fetch is enough for all of them
|
||||||
|
if (Date.now() - lastTickAt >= RESUME_MIN_GAP_MS) {
|
||||||
tick();
|
tick();
|
||||||
timer = setInterval(tick, intervalMs);
|
|
||||||
}
|
}
|
||||||
|
timer = setInterval(tick, intervalMs);
|
||||||
};
|
};
|
||||||
const stop = () => {
|
const stop = () => {
|
||||||
if (timer !== null) {
|
if (timer !== null) {
|
||||||
@@ -206,7 +223,14 @@ function startPolling(refresh, intervalMs) {
|
|||||||
timer = null;
|
timer = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const resume = () => {
|
||||||
|
if (!document.hidden) {
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
};
|
||||||
document.addEventListener("visibilitychange", () => (document.hidden ? stop() : start()));
|
document.addEventListener("visibilitychange", () => (document.hidden ? stop() : start()));
|
||||||
|
window.addEventListener("pageshow", resume);
|
||||||
|
window.addEventListener("focus", resume);
|
||||||
refreshNow = tick;
|
refreshNow = tick;
|
||||||
start();
|
start();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user