mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-10-26 23:35:17 -07:00
fix(plugins): ignore stale plugin panel close notices
The overlay's modal-closed notice now carries the panel id, and the runtime only finishes the active panel when the ids match. A late notice for a replaced panel no longer aborts the newer one.
This commit is contained in:
9 files changed
+46
-21
No files matched your search
@@ -575,8 +575,8 @@ test('registerIpcHandlers ignores malformed fire-and-forget payloads', () => {
|
|||||||
const openedModals: unknown[] = [];
|
const openedModals: unknown[] = [];
|
||||||
registerIpcHandlers(
|
registerIpcHandlers(
|
||||||
{
|
{
|
||||||
onOverlayModalClosed: (modal) => {
|
onOverlayModalClosed: (modal, _senderWindow, instanceId) => {
|
||||||
closedModals.push(modal);
|
closedModals.push(instanceId === undefined ? modal : [modal, instanceId]);
|
||||||
},
|
},
|
||||||
onOverlayModalOpened: (modal) => {
|
onOverlayModalOpened: (modal) => {
|
||||||
openedModals.push(modal);
|
openedModals.push(modal);
|
||||||
@@ -637,7 +637,9 @@ test('registerIpcHandlers ignores malformed fire-and-forget payloads', () => {
|
|||||||
handlers.on.get(IPC_CHANNELS.command.overlayModalClosed)!({}, 'not-a-modal');
|
handlers.on.get(IPC_CHANNELS.command.overlayModalClosed)!({}, 'not-a-modal');
|
||||||
handlers.on.get(IPC_CHANNELS.command.overlayModalClosed)!({}, 'subsync');
|
handlers.on.get(IPC_CHANNELS.command.overlayModalClosed)!({}, 'subsync');
|
||||||
handlers.on.get(IPC_CHANNELS.command.overlayModalClosed)!({}, 'kiku');
|
handlers.on.get(IPC_CHANNELS.command.overlayModalClosed)!({}, 'kiku');
|
||||||
assert.deepEqual(closedModals, ['subsync', 'kiku']);
|
handlers.on.get(IPC_CHANNELS.command.overlayModalClosed)!({}, 'plugin-panel', 4);
|
||||||
|
handlers.on.get(IPC_CHANNELS.command.overlayModalClosed)!({}, 'plugin-panel', 'bad');
|
||||||
|
assert.deepEqual(closedModals, ['subsync', 'kiku', ['plugin-panel', 4], 'plugin-panel']);
|
||||||
|
|
||||||
handlers.on.get(IPC_CHANNELS.command.overlayModalOpened)!({}, 'bad');
|
handlers.on.get(IPC_CHANNELS.command.overlayModalOpened)!({}, 'bad');
|
||||||
handlers.on.get(IPC_CHANNELS.command.overlayModalOpened)!({}, 'subsync');
|
handlers.on.get(IPC_CHANNELS.command.overlayModalOpened)!({}, 'subsync');
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ export interface IpcServiceDeps {
|
|||||||
onOverlayModalClosed: (
|
onOverlayModalClosed: (
|
||||||
modal: OverlayHostedModal,
|
modal: OverlayHostedModal,
|
||||||
senderWindow: ElectronBrowserWindow | null,
|
senderWindow: ElectronBrowserWindow | null,
|
||||||
|
instanceId?: number,
|
||||||
) => void;
|
) => void;
|
||||||
onOverlayModalOpened?: (
|
onOverlayModalOpened?: (
|
||||||
modal: OverlayHostedModal,
|
modal: OverlayHostedModal,
|
||||||
@@ -317,6 +318,7 @@ export interface IpcDepsRuntimeOptions {
|
|||||||
onOverlayModalClosed: (
|
onOverlayModalClosed: (
|
||||||
modal: OverlayHostedModal,
|
modal: OverlayHostedModal,
|
||||||
senderWindow: ElectronBrowserWindow | null,
|
senderWindow: ElectronBrowserWindow | null,
|
||||||
|
instanceId?: number,
|
||||||
) => void;
|
) => void;
|
||||||
onOverlayModalOpened?: (
|
onOverlayModalOpened?: (
|
||||||
modal: OverlayHostedModal,
|
modal: OverlayHostedModal,
|
||||||
@@ -527,13 +529,20 @@ export function registerIpcHandlers(deps: IpcServiceDeps, ipc: IpcMainRegistrar
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
ipc.on(IPC_CHANNELS.command.overlayModalClosed, (event: unknown, modal: unknown) => {
|
ipc.on(
|
||||||
const parsedModal = parseOverlayHostedModal(modal);
|
IPC_CHANNELS.command.overlayModalClosed,
|
||||||
if (!parsedModal) return;
|
(event: unknown, modal: unknown, instanceId: unknown) => {
|
||||||
const senderWindow =
|
const parsedModal = parseOverlayHostedModal(modal);
|
||||||
electron.BrowserWindow?.fromWebContents((event as IpcMainEvent).sender) ?? null;
|
if (!parsedModal) return;
|
||||||
deps.onOverlayModalClosed(parsedModal, senderWindow);
|
const senderWindow =
|
||||||
});
|
electron.BrowserWindow?.fromWebContents((event as IpcMainEvent).sender) ?? null;
|
||||||
|
deps.onOverlayModalClosed(
|
||||||
|
parsedModal,
|
||||||
|
senderWindow,
|
||||||
|
Number.isInteger(instanceId) ? (instanceId as number) : undefined,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
ipc.on(IPC_CHANNELS.command.overlayModalOpened, (event: unknown, modal: unknown) => {
|
ipc.on(IPC_CHANNELS.command.overlayModalOpened, (event: unknown, modal: unknown) => {
|
||||||
const parsedModal = parseOverlayHostedModal(modal);
|
const parsedModal = parseOverlayHostedModal(modal);
|
||||||
if (!parsedModal) return;
|
if (!parsedModal) return;
|
||||||
|
|||||||
+3
-3
@@ -6024,12 +6024,12 @@ const { registerIpcRuntimeHandlers } = composeIpcRuntimeHandlers({
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onOverlayModalClosed: (modal, senderWindow) => {
|
onOverlayModalClosed: (modal, senderWindow, instanceId) => {
|
||||||
if (modal === 'media-timing-review') {
|
if (modal === 'media-timing-review') {
|
||||||
void mediaTimingReviewRuntime.dispose();
|
void mediaTimingReviewRuntime.dispose();
|
||||||
}
|
}
|
||||||
if (modal === 'plugin-panel') {
|
if (modal === 'plugin-panel' && instanceId !== undefined) {
|
||||||
pluginPanelRuntime.handleModalClosed();
|
pluginPanelRuntime.handleModalClosed(instanceId);
|
||||||
}
|
}
|
||||||
if (modal === 'subtitle-sidebar' && senderWindow === overlayManager.getMainWindow()) {
|
if (modal === 'subtitle-sidebar' && senderWindow === overlayManager.getMainWindow()) {
|
||||||
subtitleSidebarRequestedOpen = false;
|
subtitleSidebarRequestedOpen = false;
|
||||||
|
|||||||
@@ -303,7 +303,7 @@ test('subtitle sidebar open state is restored for replacement visible overlay wi
|
|||||||
/onOverlayModalOpened:\s*\(modal,\s*senderWindow\)\s*=>\s*\{(?<body>[\s\S]*?)\n \},/,
|
/onOverlayModalOpened:\s*\(modal,\s*senderWindow\)\s*=>\s*\{(?<body>[\s\S]*?)\n \},/,
|
||||||
)?.groups?.body;
|
)?.groups?.body;
|
||||||
const closedBlock = source.match(
|
const closedBlock = source.match(
|
||||||
/onOverlayModalClosed:\s*\(modal,\s*senderWindow\)\s*=>\s*\{(?<body>[\s\S]*?)\n \},/,
|
/onOverlayModalClosed:\s*\(modal,\s*senderWindow(?:,\s*instanceId)?\)\s*=>\s*\{(?<body>[\s\S]*?)\n \},/,
|
||||||
)?.groups?.body;
|
)?.groups?.body;
|
||||||
const depsBlock = source.match(/getSubtitleSidebarOpen:\s*\(\)\s*=>\s*(?<body>[^\n,]+)/)?.groups
|
const depsBlock = source.match(/getSubtitleSidebarOpen:\s*\(\)\s*=>\s*(?<body>[^\n,]+)/)?.groups
|
||||||
?.body;
|
?.body;
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ test('closing from the overlay aborts the handle and ignores later updates', asy
|
|||||||
await new Promise((resolve) => setImmediate(resolve));
|
await new Promise((resolve) => setImmediate(resolve));
|
||||||
const sentBefore = h.sent.length;
|
const sentBefore = h.sent.length;
|
||||||
|
|
||||||
h.runtime.handleModalClosed();
|
h.runtime.handleModalClosed(1);
|
||||||
panel.update({ markdown: 'too late' });
|
panel.update({ markdown: 'too late' });
|
||||||
|
|
||||||
assert.equal(panel.signal.aborted, true);
|
assert.equal(panel.signal.aborted, true);
|
||||||
@@ -52,6 +52,17 @@ test('closing from the overlay aborts the handle and ignores later updates', asy
|
|||||||
assert.equal(h.sent.length, sentBefore);
|
assert.equal(h.sent.length, sentBefore);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('a late close acknowledgement for a replaced panel leaves the new panel open', () => {
|
||||||
|
const h = createHarness(Promise.resolve(true));
|
||||||
|
const first = h.runtime.open({ title: 'One', markdown: '' });
|
||||||
|
first.close();
|
||||||
|
const second = h.runtime.open({ title: 'Two', markdown: '' });
|
||||||
|
|
||||||
|
h.runtime.handleModalClosed(1);
|
||||||
|
|
||||||
|
assert.equal(second.signal.aborted, false);
|
||||||
|
});
|
||||||
|
|
||||||
test('opening a new panel aborts the previous one', () => {
|
test('opening a new panel aborts the previous one', () => {
|
||||||
const h = createHarness(Promise.resolve(true));
|
const h = createHarness(Promise.resolve(true));
|
||||||
const first = h.runtime.open({ title: 'One', markdown: '' });
|
const first = h.runtime.open({ title: 'One', markdown: '' });
|
||||||
|
|||||||
@@ -103,9 +103,9 @@ export function createPluginPanelRuntime(deps: {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
/** The user closed the panel in the overlay. */
|
/** The overlay closed `panelId`; a late notice for an already replaced panel is ignored. */
|
||||||
handleModalClosed(): void {
|
handleModalClosed(panelId: number): void {
|
||||||
if (active) finish(active);
|
if (active?.panelId === panelId) finish(active);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
+2
-2
@@ -532,8 +532,8 @@ const electronAPI: ElectronAPI = {
|
|||||||
mediaId,
|
mediaId,
|
||||||
direction,
|
direction,
|
||||||
),
|
),
|
||||||
notifyOverlayModalClosed: (modal) => {
|
notifyOverlayModalClosed: (modal, instanceId) => {
|
||||||
ipcRenderer.send(IPC_CHANNELS.command.overlayModalClosed, modal);
|
ipcRenderer.send(IPC_CHANNELS.command.overlayModalClosed, modal, instanceId);
|
||||||
},
|
},
|
||||||
notifyOverlayModalOpened: (modal) => {
|
notifyOverlayModalOpened: (modal) => {
|
||||||
ipcRenderer.send(IPC_CHANNELS.command.overlayModalOpened, modal);
|
ipcRenderer.send(IPC_CHANNELS.command.overlayModalOpened, modal);
|
||||||
|
|||||||
@@ -60,12 +60,13 @@ export function createPluginPanelModal(
|
|||||||
function closePluginPanelModal(): void {
|
function closePluginPanelModal(): void {
|
||||||
if (!ctx.state.pluginPanelModalOpen) return;
|
if (!ctx.state.pluginPanelModalOpen) return;
|
||||||
|
|
||||||
|
const closedPanelId = panelId ?? undefined;
|
||||||
ctx.state.pluginPanelModalOpen = false;
|
ctx.state.pluginPanelModalOpen = false;
|
||||||
panelId = null;
|
panelId = null;
|
||||||
options.syncSettingsModalSubtitleSuppression();
|
options.syncSettingsModalSubtitleSuppression();
|
||||||
ctx.dom.pluginPanelModal.classList.add('hidden');
|
ctx.dom.pluginPanelModal.classList.add('hidden');
|
||||||
ctx.dom.pluginPanelModal.setAttribute('aria-hidden', 'true');
|
ctx.dom.pluginPanelModal.setAttribute('aria-hidden', 'true');
|
||||||
window.electronAPI.notifyOverlayModalClosed('plugin-panel');
|
window.electronAPI.notifyOverlayModalClosed('plugin-panel', closedPanelId);
|
||||||
if (!ctx.state.isOverSubtitle && !options.modalStateReader.isAnyModalOpen()) {
|
if (!ctx.state.isOverSubtitle && !options.modalStateReader.isAnyModalOpen()) {
|
||||||
ctx.dom.overlay.classList.remove('interactive');
|
ctx.dom.overlay.classList.remove('interactive');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -625,6 +625,8 @@ export interface ElectronAPI {
|
|||||||
| 'character-dictionary'
|
| 'character-dictionary'
|
||||||
| 'changelog'
|
| 'changelog'
|
||||||
| 'plugin-panel',
|
| 'plugin-panel',
|
||||||
|
/** Which instance closed, for modals that swap content in place (the plugin panel's id). */
|
||||||
|
instanceId?: number,
|
||||||
) => void;
|
) => void;
|
||||||
notifyOverlayModalOpened: (
|
notifyOverlayModalOpened: (
|
||||||
modal:
|
modal:
|
||||||
|
|||||||
Reference in new issue
Block a user