mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-01 07:21:33 -07:00
fix(overlay): fall back to did-finish-load when ready-to-show never fires
A hidden overlay window stops producing frames once it is explicitly hidden and then reloaded (the Yomitan content-script reload right after startup), so ready-to-show never fires and the content-ready gate keeps the window hidden forever behind the Overlay loading spinner. Mark content ready from did-finish-load after a 1.5s grace period so the overlay always becomes showable; ready-to-show still wins when it fires.
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: overlay
|
||||
|
||||
- Fixed the overlay never loading (stuck on the "Overlay loading" OSD spinner) when the Yomitan content-script reload raced overlay window creation at startup, most visible when playing from the anime browser on Linux/Wayland: a hidden window stops painting after that reload, so the ready-to-show signal that gates showing the overlay never fired. Content-ready now falls back to did-finish-load after a short grace period.
|
||||
@@ -1,6 +1,11 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { ensureOverlayWindowLevel, updateOverlayWindowBounds } from './overlay-window';
|
||||
import {
|
||||
ensureOverlayWindowLevel,
|
||||
updateOverlayWindowBounds,
|
||||
scheduleOverlayContentReadyFallback,
|
||||
OVERLAY_CONTENT_READY_FALLBACK_DELAY_MS,
|
||||
} from './overlay-window';
|
||||
import {
|
||||
handleOverlayWindowBeforeInputEvent,
|
||||
handleOverlayWindowBlurred,
|
||||
@@ -322,3 +327,59 @@ test('updateOverlayWindowBounds aligns Linux overlay content bounds to mpv geome
|
||||
|
||||
assert.deepEqual(calls, [{ x: 0, y: -14, width: 3440, height: 1454 }]);
|
||||
});
|
||||
|
||||
test('scheduleOverlayContentReadyFallback marks content ready when ready-to-show never fired', () => {
|
||||
const events: string[] = [];
|
||||
const scheduled: Array<() => void> = [];
|
||||
|
||||
scheduleOverlayContentReadyFallback({
|
||||
isContentReady: () => false,
|
||||
isDestroyed: () => false,
|
||||
markContentReady: () => events.push('mark'),
|
||||
setTimeoutFn: (callback, delayMs) => {
|
||||
events.push(`schedule:${delayMs}`);
|
||||
scheduled.push(callback);
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepEqual(events, [`schedule:${OVERLAY_CONTENT_READY_FALLBACK_DELAY_MS}`]);
|
||||
scheduled[0]?.();
|
||||
assert.deepEqual(events, [`schedule:${OVERLAY_CONTENT_READY_FALLBACK_DELAY_MS}`, 'mark']);
|
||||
});
|
||||
|
||||
test('scheduleOverlayContentReadyFallback no-ops once content is already ready', () => {
|
||||
const scheduled: Array<() => void> = [];
|
||||
const events: string[] = [];
|
||||
|
||||
scheduleOverlayContentReadyFallback({
|
||||
isContentReady: () => true,
|
||||
isDestroyed: () => false,
|
||||
markContentReady: () => events.push('mark'),
|
||||
setTimeoutFn: (callback) => {
|
||||
scheduled.push(callback);
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
scheduled[0]?.();
|
||||
assert.deepEqual(events, []);
|
||||
});
|
||||
|
||||
test('scheduleOverlayContentReadyFallback no-ops after the window is destroyed', () => {
|
||||
const scheduled: Array<() => void> = [];
|
||||
const events: string[] = [];
|
||||
|
||||
scheduleOverlayContentReadyFallback({
|
||||
isContentReady: () => false,
|
||||
isDestroyed: () => true,
|
||||
markContentReady: () => events.push('mark'),
|
||||
setTimeoutFn: (callback) => {
|
||||
scheduled.push(callback);
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
scheduled[0]?.();
|
||||
assert.deepEqual(events, []);
|
||||
});
|
||||
|
||||
@@ -77,6 +77,31 @@ export function updateOverlayWindowBounds(
|
||||
});
|
||||
}
|
||||
|
||||
// ready-to-show is the primary content-ready signal, but it never fires when the
|
||||
// page navigates while the window is hidden: an explicit hide() ends the
|
||||
// paintWhenInitiallyHidden grace, the hidden widget stops producing frames, and
|
||||
// the new document never paints. The Yomitan content-script reload right after
|
||||
// startup hits exactly that, and content-ready gates showing the window, so
|
||||
// without a fallback the overlay deadlocks behind the "Overlay loading" OSD.
|
||||
export const OVERLAY_CONTENT_READY_FALLBACK_DELAY_MS = 1500;
|
||||
|
||||
export function scheduleOverlayContentReadyFallback(deps: {
|
||||
isContentReady: () => boolean;
|
||||
isDestroyed: () => boolean;
|
||||
markContentReady: () => void;
|
||||
setTimeoutFn?: (callback: () => void, delayMs: number) => unknown;
|
||||
delayMs?: number;
|
||||
}): void {
|
||||
const setTimeoutFn =
|
||||
deps.setTimeoutFn ?? ((callback: () => void, delayMs: number): unknown => setTimeout(callback, delayMs));
|
||||
setTimeoutFn(() => {
|
||||
if (deps.isContentReady() || deps.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
deps.markContentReady();
|
||||
}, deps.delayMs ?? OVERLAY_CONTENT_READY_FALLBACK_DELAY_MS);
|
||||
}
|
||||
|
||||
export function ensureOverlayWindowLevel(window: BrowserWindow): void {
|
||||
if (process.platform === 'darwin') {
|
||||
window.setAlwaysOnTop(true, 'screen-saver', 1);
|
||||
@@ -149,10 +174,26 @@ export function createOverlayWindow(
|
||||
logger.error('Page failed to load:', errorCode, errorDescription, validatedURL);
|
||||
});
|
||||
|
||||
const markContentReady = (): void => {
|
||||
if (isOverlayWindowContentReady(window)) {
|
||||
return;
|
||||
}
|
||||
overlayWindowContentReady.add(window);
|
||||
(window as BrowserWindow & { [OVERLAY_WINDOW_CONTENT_READY_FLAG]?: boolean })[
|
||||
OVERLAY_WINDOW_CONTENT_READY_FLAG
|
||||
] = true;
|
||||
options.onWindowContentReady?.();
|
||||
};
|
||||
|
||||
window.webContents.on('did-finish-load', () => {
|
||||
window.setTitle(OVERLAY_WINDOW_TITLES[kind]);
|
||||
options.onRuntimeOptionsChanged();
|
||||
options.onWindowDidFinishLoad?.();
|
||||
scheduleOverlayContentReadyFallback({
|
||||
isContentReady: () => isOverlayWindowContentReady(window),
|
||||
isDestroyed: () => window.isDestroyed(),
|
||||
markContentReady,
|
||||
});
|
||||
});
|
||||
|
||||
window.webContents.on('page-title-updated', (event) => {
|
||||
@@ -160,13 +201,7 @@ export function createOverlayWindow(
|
||||
window.setTitle(OVERLAY_WINDOW_TITLES[kind]);
|
||||
});
|
||||
|
||||
window.once('ready-to-show', () => {
|
||||
overlayWindowContentReady.add(window);
|
||||
(window as BrowserWindow & { [OVERLAY_WINDOW_CONTENT_READY_FLAG]?: boolean })[
|
||||
OVERLAY_WINDOW_CONTENT_READY_FLAG
|
||||
] = true;
|
||||
options.onWindowContentReady?.();
|
||||
});
|
||||
window.once('ready-to-show', markContentReady);
|
||||
|
||||
if (kind === 'visible') {
|
||||
window.webContents.on('devtools-opened', () => {
|
||||
|
||||
Reference in New Issue
Block a user