fix: recover inert macos webview startup

This commit is contained in:
lucaronin
2026-05-01 04:47:41 +02:00
parent 0df5c7882d
commit 1c010ddc22
7 changed files with 209 additions and 2 deletions

View File

@@ -0,0 +1,48 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import {
FRONTEND_READY_EVENT_NAME,
STARTUP_RELOAD_ATTEMPT_KEY,
markFrontendReady,
reloadFrontendOnceIfStartupFailed,
} from './frontendReady'
describe('frontend readiness recovery', () => {
beforeEach(() => {
window.__tolariaFrontendReady = false
sessionStorage.clear()
})
it('marks the frontend ready and clears a pending startup reload', () => {
const onReady = vi.fn()
window.addEventListener(FRONTEND_READY_EVENT_NAME, onReady, { once: true })
sessionStorage.setItem(STARTUP_RELOAD_ATTEMPT_KEY, '1')
markFrontendReady()
expect(window.__tolariaFrontendReady).toBe(true)
expect(sessionStorage.getItem(STARTUP_RELOAD_ATTEMPT_KEY)).toBeNull()
expect(onReady).toHaveBeenCalledOnce()
})
it('reloads once when React reports a startup error before readiness', () => {
const reload = vi.fn()
const firstAttempt = reloadFrontendOnceIfStartupFailed({ reload })
const secondAttempt = reloadFrontendOnceIfStartupFailed({ reload })
expect(firstAttempt).toBe(true)
expect(secondAttempt).toBe(false)
expect(reload).toHaveBeenCalledOnce()
expect(sessionStorage.getItem(STARTUP_RELOAD_ATTEMPT_KEY)).toBe('1')
})
it('does not reload after the frontend has reported readiness', () => {
const reload = vi.fn()
markFrontendReady()
const didReload = reloadFrontendOnceIfStartupFailed({ reload })
expect(didReload).toBe(false)
expect(reload).not.toHaveBeenCalled()
})
})

View File

@@ -0,0 +1,74 @@
export const FRONTEND_READY_EVENT_NAME = 'tolaria:frontend-ready'
export const STARTUP_RELOAD_ATTEMPT_KEY = 'tolaria:startup-reload-attempted'
declare global {
interface Window {
__tolariaFrontendReady?: boolean
}
}
type FrontendReadyOptions = {
storage?: Storage
win?: Window
}
type StartupReloadOptions = FrontendReadyOptions & {
reload?: () => void
}
function getSessionStorage(win: Window): Storage | null {
try {
return win.sessionStorage
} catch {
return null
}
}
function removeSessionItem(storage: Storage | null, key: string): void {
try {
storage?.removeItem(key)
} catch {
// Storage can be unavailable in hardened WebView/privacy modes.
}
}
function readSessionItem(storage: Storage | null, key: string): string | null {
try {
return storage?.getItem(key) ?? null
} catch {
return null
}
}
function writeSessionItem(storage: Storage | null, key: string, value: string): boolean {
try {
storage?.setItem(key, value)
return storage !== null
} catch {
return false
}
}
export function markFrontendReady(options: FrontendReadyOptions = {}): void {
const win = options.win ?? window
const storage = options.storage ?? getSessionStorage(win)
win.__tolariaFrontendReady = true
removeSessionItem(storage, STARTUP_RELOAD_ATTEMPT_KEY)
win.dispatchEvent(new Event(FRONTEND_READY_EVENT_NAME))
}
export function reloadFrontendOnceIfStartupFailed(
options: StartupReloadOptions = {},
): boolean {
const win = options.win ?? window
const storage = options.storage ?? getSessionStorage(win)
if (win.__tolariaFrontendReady === true) return false
if (readSessionItem(storage, STARTUP_RELOAD_ATTEMPT_KEY) === '1') return false
if (!writeSessionItem(storage, STARTUP_RELOAD_ATTEMPT_KEY, '1')) return false
const reload = options.reload ?? (() => win.location.reload())
reload()
return true
}