mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-01 19:21:34 -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 assert from 'node:assert/strict';
|
||||||
import test from 'node:test';
|
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 {
|
import {
|
||||||
handleOverlayWindowBeforeInputEvent,
|
handleOverlayWindowBeforeInputEvent,
|
||||||
handleOverlayWindowBlurred,
|
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 }]);
|
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 {
|
export function ensureOverlayWindowLevel(window: BrowserWindow): void {
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
window.setAlwaysOnTop(true, 'screen-saver', 1);
|
window.setAlwaysOnTop(true, 'screen-saver', 1);
|
||||||
@@ -149,10 +174,26 @@ export function createOverlayWindow(
|
|||||||
logger.error('Page failed to load:', errorCode, errorDescription, validatedURL);
|
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.webContents.on('did-finish-load', () => {
|
||||||
window.setTitle(OVERLAY_WINDOW_TITLES[kind]);
|
window.setTitle(OVERLAY_WINDOW_TITLES[kind]);
|
||||||
options.onRuntimeOptionsChanged();
|
options.onRuntimeOptionsChanged();
|
||||||
options.onWindowDidFinishLoad?.();
|
options.onWindowDidFinishLoad?.();
|
||||||
|
scheduleOverlayContentReadyFallback({
|
||||||
|
isContentReady: () => isOverlayWindowContentReady(window),
|
||||||
|
isDestroyed: () => window.isDestroyed(),
|
||||||
|
markContentReady,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
window.webContents.on('page-title-updated', (event) => {
|
window.webContents.on('page-title-updated', (event) => {
|
||||||
@@ -160,13 +201,7 @@ export function createOverlayWindow(
|
|||||||
window.setTitle(OVERLAY_WINDOW_TITLES[kind]);
|
window.setTitle(OVERLAY_WINDOW_TITLES[kind]);
|
||||||
});
|
});
|
||||||
|
|
||||||
window.once('ready-to-show', () => {
|
window.once('ready-to-show', markContentReady);
|
||||||
overlayWindowContentReady.add(window);
|
|
||||||
(window as BrowserWindow & { [OVERLAY_WINDOW_CONTENT_READY_FLAG]?: boolean })[
|
|
||||||
OVERLAY_WINDOW_CONTENT_READY_FLAG
|
|
||||||
] = true;
|
|
||||||
options.onWindowContentReady?.();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (kind === 'visible') {
|
if (kind === 'visible') {
|
||||||
window.webContents.on('devtools-opened', () => {
|
window.webContents.on('devtools-opened', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user