From 1a4f810ecdf00fa71fce94af863b22ce8eb821a9 Mon Sep 17 00:00:00 2001 From: Martin Lukas Date: Mon, 13 Jul 2026 22:49:23 +0200 Subject: [PATCH] Disable the shared worker on Android (same as is done for iOS already) (#8457) * Revert "Reload when PWA shared worker is killed by Android (#8322)" This reverts commit 62ced9aa2beafe5d806320e03f9741d74292fcd0. * Disable shared worker in PWA on Android --- .../desktop-client/src/browser-preload.js | 3 +- .../platform/client/browser-preload/start.ts | 2 + .../client/browser-preload/worker-bridge.ts | 51 ++++++------------- packages/loot-core/src/shared/platform.api.ts | 1 + .../loot-core/src/shared/platform.electron.ts | 1 + packages/loot-core/src/shared/platform.ts | 2 + .../disable-shared-worker-on-android.md | 6 +++ .../fix-android-shared-worker-liveness.md | 6 --- 8 files changed, 30 insertions(+), 42 deletions(-) create mode 100644 upcoming-release-notes/disable-shared-worker-on-android.md delete mode 100644 upcoming-release-notes/fix-android-shared-worker-liveness.md diff --git a/packages/desktop-client/src/browser-preload.js b/packages/desktop-client/src/browser-preload.js index 9dc50ea45d..9cac0a8ac4 100644 --- a/packages/desktop-client/src/browser-preload.js +++ b/packages/desktop-client/src/browser-preload.js @@ -38,7 +38,8 @@ const worker = startBrowserBackend({ }, createSharedWorker: () => new SharedBrowserServerWorker({ name: 'actual-backend' }), - forceDirectWorker: Platform.isPlaywright || Platform.isIOS, + forceDirectWorker: + Platform.isPlaywright || Platform.isIOS || Platform.isAndroid, }); let isUpdateReadyForDownload = false; diff --git a/packages/loot-core/src/platform/client/browser-preload/start.ts b/packages/loot-core/src/platform/client/browser-preload/start.ts index 836c23021a..04e5be0239 100644 --- a/packages/loot-core/src/platform/client/browser-preload/start.ts +++ b/packages/loot-core/src/platform/client/browser-preload/start.ts @@ -74,6 +74,8 @@ export function startBrowserBackend( 'SharedArrayBufferOverride', ), }); + bridge.markInitialized(); + window.addEventListener('beforeunload', () => { sharedPort.postMessage({ type: 'tab-closing' }); }); diff --git a/packages/loot-core/src/platform/client/browser-preload/worker-bridge.ts b/packages/loot-core/src/platform/client/browser-preload/worker-bridge.ts index 4cb62b29a5..a124f55ef8 100644 --- a/packages/loot-core/src/platform/client/browser-preload/worker-bridge.ts +++ b/packages/loot-core/src/platform/client/browser-preload/worker-bridge.ts @@ -2,11 +2,6 @@ import { initBackend as initSQLBackend } from 'absurd-sql/dist/indexeddb-main-th import { logger } from '#platform/server/log'; -// After sending a __resume-tab ping we wait for any response from the shared worker. -// A live shared worker replies well below this threshold; no response means -// it might have been killed by the OS, and we need to reload. -const RESUME_TAB_RESPONSE_TIMEOUT_MS = 1_000; - type BridgeMessage = { type?: string; [key: string]: unknown }; /** The Worker-like surface the connection layer interacts with. */ @@ -33,8 +28,9 @@ export class WorkerBridge { _onmessage: ((e: MessageEvent) => void) | null; _listeners: Array<{ type: string; handler: (e: MessageEvent) => void }>; _started: boolean; + _isInitialized: boolean; _currentBudgetId: string | null; - _sharedWorkerLivenessTimeout: { start: () => void; clear: () => void }; + _wasHidden: boolean; _onVisibilityChange: () => void; localBackendWorker: Worker | null; backendWorkerUrl: URL; @@ -44,21 +40,19 @@ export class WorkerBridge { this._onmessage = null; this._listeners = []; this._started = false; + this._isInitialized = false; this._currentBudgetId = null; - this._sharedWorkerLivenessTimeout = - this._createSharedWorkerLivenessTimeout(); + this._wasHidden = document.visibilityState === 'hidden'; this.localBackendWorker = null; this.backendWorkerUrl = backendWorkerUrl; this._onVisibilityChange = () => { - if (document.visibilityState === 'hidden' || !this._started) { - return; + if (document.visibilityState === 'hidden') { + this._wasHidden = true; + } else if (this._wasHidden) { + this._wasHidden = false; + this._resumeAssociation(); } - - // Cancel any pending reload from a prior transition, then re-check shared worker's liveness - this._sharedWorkerLivenessTimeout.clear(); - this._resumeAssociation(); - this._sharedWorkerLivenessTimeout.start(); }; // Listen for all messages from the SharedWorker port @@ -112,9 +106,6 @@ export class WorkerBridge { } _onSharedMessage(event: MessageEvent) { - // Cancel any pending reload — the shared worker is alive. - this._sharedWorkerLivenessTimeout.clear(); - const msg = event.data as BridgeMessage; // Elected as leader: create the real backend Worker on this tab @@ -192,6 +183,10 @@ export class WorkerBridge { this._dispatch(event); } + markInitialized() { + this._isInitialized = true; + } + _normalizeBudgetId(budgetId: string | null): string | null { if ( typeof budgetId === 'string' && @@ -212,6 +207,9 @@ export class WorkerBridge { } _resumeAssociation() { + if (!this._isInitialized) { + return; + } this._sharedPort.postMessage({ type: '__resume-tab', budgetId: this._currentBudgetId, @@ -273,21 +271,4 @@ export class WorkerBridge { localWorker.postMessage(initMsg); } - - _createSharedWorkerLivenessTimeout() { - let timer: ReturnType | null = null; - return { - start() { - timer = setTimeout(() => { - window.location.reload(); - }, RESUME_TAB_RESPONSE_TIMEOUT_MS); - }, - clear() { - if (timer !== null) { - clearTimeout(timer); - timer = null; - } - }, - }; - } } diff --git a/packages/loot-core/src/shared/platform.api.ts b/packages/loot-core/src/shared/platform.api.ts index 1d2588eae8..ce51ccfd52 100644 --- a/packages/loot-core/src/shared/platform.api.ts +++ b/packages/loot-core/src/shared/platform.api.ts @@ -5,3 +5,4 @@ export const env: 'web' | 'mobile' | 'unknown' = 'unknown'; export const isBrowser: boolean = false; export const isIOSAgent = false; +export const isAndroid = false; diff --git a/packages/loot-core/src/shared/platform.electron.ts b/packages/loot-core/src/shared/platform.electron.ts index 6c44ede662..4043e64ab4 100644 --- a/packages/loot-core/src/shared/platform.electron.ts +++ b/packages/loot-core/src/shared/platform.electron.ts @@ -20,3 +20,4 @@ export const isBrowser: typeof T.isBrowser = false; export const isIOSAgent: typeof T.isIOSAgent = false; export const isIOS: typeof T.isIOS = false; +export const isAndroid: typeof T.isAndroid = false; diff --git a/packages/loot-core/src/shared/platform.ts b/packages/loot-core/src/shared/platform.ts index 8251c6642d..ce70b1518d 100644 --- a/packages/loot-core/src/shared/platform.ts +++ b/packages/loot-core/src/shared/platform.ts @@ -25,6 +25,8 @@ const userAgent = navigator.userAgent; // True for all browsers on iOS (iPhone/iPad/iPod) — not macOS. export const isIOS = /iP(hone|ad|od)/.test(userAgent); +export const isAndroid = /Android/.test(userAgent); + // True only for Safari on iOS — Chrome (CriOS), Firefox (FxiOS), Edge (EdgiOS), // etc. carry their own tokens and are excluded. export const isIOSAgent = diff --git a/upcoming-release-notes/disable-shared-worker-on-android.md b/upcoming-release-notes/disable-shared-worker-on-android.md new file mode 100644 index 0000000000..35f0f6e597 --- /dev/null +++ b/upcoming-release-notes/disable-shared-worker-on-android.md @@ -0,0 +1,6 @@ +--- +category: Bugfixes +authors: [martin-lukas] +--- + +Fix app becoming unresponsive on Android when reopening after being in the background for a while. diff --git a/upcoming-release-notes/fix-android-shared-worker-liveness.md b/upcoming-release-notes/fix-android-shared-worker-liveness.md deleted file mode 100644 index d425fdc85b..0000000000 --- a/upcoming-release-notes/fix-android-shared-worker-liveness.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfixes -authors: [martin-lukas] ---- - -Fixed issue on Android where app becomes unresponsive when reopening after period of inactivity.