Gate external window reveals on renderer readiness

Route second-instance activation, tray clicks, tray menu activation, and drop-target restores through one startup reveal gate. External focus and restore requests now remain queued until the active renderer generation reports Ready or the coordinator exposes the branded failure surface.

Block the gate and reset close-handshake readiness synchronously before every renderer recovery navigation and failure-document load. This keeps early loadFile rejection from leaving the terminal failure surface subject to the normal renderer close-preparation handshake.

Add production-like event binding regressions for all four external reveal paths, authorized Ready and failsafe reveals, and direct closing of the safe failure surface after an early recovery rejection.
This commit is contained in:
Sucukdeluxe
2026-08-13 23:24:29 +02:00
parent e124ab2923
commit 3cb34f8039
3 changed files with 291 additions and 34 deletions
+94
View File
@@ -32,6 +32,97 @@ function createStartupNavigationLoader(window, target, options = {}) {
};
}
function createStartupRevealGate(window, { onBlock } = {}) {
let blocked = true;
let activationPending = false;
function hasWindow() {
return !!window && (typeof window.isDestroyed !== 'function' || !window.isDestroyed());
}
function showAuthorizedSurface(activate) {
if (!hasWindow()) return false;
if (activate && window.isMinimized()) window.restore();
if (!window.isVisible()) window.show();
if (activate) window.focus();
activationPending = false;
return true;
}
const block = () => {
blocked = true;
if (typeof onBlock === 'function') onBlock();
return true;
};
const request = () => {
if (!hasWindow()) return false;
activationPending = true;
if (!blocked) showAuthorizedSurface(true);
return true;
};
const reveal = () => {
blocked = false;
return showAuthorizedSurface(activationPending);
};
const navigate = (operation, ...args) => {
block();
return operation(...args);
};
return { block, navigate, request, reveal };
}
function createStartupExternalRevealBindings({ getWindow, getRevealGate, sendDroppedFiles }) {
function getActiveWindow() {
const window = getWindow();
if (!window || (typeof window.isDestroyed === 'function' && window.isDestroyed())) return null;
return window;
}
function requestReveal() {
if (!getActiveWindow()) return false;
const revealGate = getRevealGate();
if (!revealGate || typeof revealGate.request !== 'function') return false;
revealGate.request();
return true;
}
function handleDropTargetFiles(_event, paths) {
const window = getActiveWindow();
if (!window) return false;
if (!window.isVisible() || window.isMinimized()) requestReveal();
sendDroppedFiles(paths);
return true;
}
return {
bindSecondInstance(app) {
app.on('second-instance', requestReveal);
},
bindTrayClick(tray) {
tray.on('click', requestReveal);
},
createTrayMenuItem(label) {
return { label, click: requestReveal };
},
bindDropTargetFiles(ipcMain) {
ipcMain.on('drop-target:files', handleDropTargetFiles);
}
};
}
function createStartupCloseHandler({ window, shouldPrepareClose, requestClosePreparation }) {
return function handleStartupClose(event) {
if (!shouldPrepareClose() || window.webContents.isDestroyed()) return false;
event.preventDefault();
requestClosePreparation();
return true;
};
}
function createStartupRecoveryCoordinator({
load,
reload,
@@ -368,9 +459,12 @@ function createStartupWindow(BrowserWindow, options) {
module.exports = {
configureStartupRenderer,
createStartupCloseHandler,
createStartupExternalRevealBindings,
createStartupFailureDocument,
createStartupNavigationLoader,
createStartupRecoveryCoordinator,
createStartupRevealGate,
createStartupRendererHandlers,
createStartupWindow,
resolveStartupLanguage