fix(overlay): keep Hachidori attention from pausing playback

Hachidori's hachidori-popup-shown/hidden pair means "the reader needs
mouse events", which includes a left press anywhere on the overlay that
may start a selection. SubMiner treated it as popup visibility, so a
click paused and resumed mpv, and holding the button kept it paused.
The host element only exists after the first lookup, so the first
attempt (gate on an open popup pane when a host exists) still paused
on clicks before any lookup.

Once a Hachidori event has been seen, popup auto-pause now requires an
unhidden popup pane in the host's shadow root, and rechecks after each
successful lookup so drag-select popups still pause.
This commit is contained in:
2026-09-22 21:06:19 -07:00
parent 2958591ec9
commit c39dbb5a8d
4 changed files with 274 additions and 12 deletions
+24 -7
View File
@@ -4,7 +4,9 @@ import {
YOMITAN_POPUP_MOUSE_ENTER_EVENT,
YOMITAN_POPUP_MOUSE_LEAVE_EVENT,
registerDictionaryPopupVisibilityListener,
registerYomitanLookupListener,
PRIMARY_SUB_VISIBLE_ON_YOMITAN_POPUP_CLASS,
isHachidoriPopupOpen,
isYomitanPopupVisible,
isYomitanPopupIframe,
} from '../yomitan-popup.js';
@@ -34,6 +36,7 @@ export function createMouseHandlers(
let yomitanPopupVisible = false;
let hoverPauseRequestId = 0;
let popupPauseRequestId = 0;
let hachidoriReaderSeen = false;
let pausedBySubtitleHover = false;
let pausedByYomitanPopup = false;
let lastPointerPosition: { clientX: number; clientY: number } | null = null;
@@ -256,8 +259,19 @@ export function createMouseHandlers(
options.sendMpvCommand(['set_property', 'pause', 'no']);
}
// Hachidori also claims attention for a left press anywhere on the overlay
// that may become a selection, so once its events identify the reader only
// an open popup pane should pause playback.
function canPauseForYomitanPopup(): boolean {
return (
yomitanPopupVisible &&
options.getYomitanPopupAutoPauseEnabled() &&
(!hachidoriReaderSeen || (typeof document !== 'undefined' && isHachidoriPopupOpen(document)))
);
}
async function maybePauseForYomitanPopup(): Promise<void> {
if (!yomitanPopupVisible || !options.getYomitanPopupAutoPauseEnabled()) {
if (!canPauseForYomitanPopup()) {
return;
}
@@ -277,11 +291,7 @@ export function createMouseHandlers(
return;
}
if (
requestId !== popupPauseRequestId ||
!yomitanPopupVisible ||
!options.getYomitanPopupAutoPauseEnabled()
) {
if (requestId !== popupPauseRequestId || !canPauseForYomitanPopup()) {
return;
}
if (paused !== false) return;
@@ -469,7 +479,8 @@ export function createMouseHandlers(
function setupYomitanObserver(): void {
reconcilePopupInteraction({ allowPause: true });
registerDictionaryPopupVisibilityListener('shown', () => {
registerDictionaryPopupVisibilityListener('shown', (reader) => {
if (reader === 'hachidori') hachidoriReaderSeen = true;
reconcilePopupInteraction({
assumeVisible: true,
allowPause: true,
@@ -481,6 +492,12 @@ export function createMouseHandlers(
disablePopupInteractionIfIdle();
});
// A Hachidori selection opens its popup while the drag's attention claim is
// still held, so no second shown event arrives; its lookup marks the open.
registerYomitanLookupListener(window, () => {
reconcilePopupInteraction({ allowPause: true });
});
window.addEventListener(YOMITAN_POPUP_MOUSE_ENTER_EVENT, () => {
ctx.state.isOverYomitanPopup = true;
reconcilePopupInteraction({ assumeVisible: true, reclaimFocus: true });