Buffer startup drops until renderer readiness

Keep a bounded FIFO of drop payloads while the startup or recovery document is not validated. Flush each payload exactly once only after the generation-checked renderer Ready signal, retain immediate delivery for a ready renderer, and discard stale payloads when the main window is destroyed or replaced.

Wire renderer block, Ready, and window-close transitions through main and cover recovery, safe failure visibility, stale Ready signals, ordered multi-drop delivery, queue bounds, lifecycle cleanup, and duplicate prevention.
This commit is contained in:
Sucukdeluxe
2026-08-13 23:35:04 +02:00
parent 3767a8b81f
commit cfa138198f
3 changed files with 293 additions and 6 deletions
+74 -5
View File
@@ -75,29 +75,95 @@ function createStartupRevealGate(window, { onBlock } = {}) {
return { block, navigate, request, reveal };
}
function createStartupExternalRevealBindings({ getWindow, getRevealGate, sendDroppedFiles }) {
function createStartupExternalRevealBindings({
getWindow,
getRevealGate,
sendDroppedFiles,
maxPendingDropPayloads = 32
}) {
const pendingDropPayloads = [];
const pendingDropLimit = Number.isSafeInteger(maxPendingDropPayloads) && maxPendingDropPayloads > 0
? maxPendingDropPayloads
: 32;
let pendingWindow = null;
let readyWindow = null;
function clearPendingDropPayloads(window) {
if (window && pendingWindow !== window) return false;
pendingDropPayloads.length = 0;
pendingWindow = null;
return true;
}
function clearWindowState(window) {
clearPendingDropPayloads(window);
if (!window || readyWindow === window) readyWindow = null;
}
function getActiveWindow() {
const window = getWindow();
if (!window || (typeof window.isDestroyed === 'function' && window.isDestroyed())) return null;
if (!window || (typeof window.isDestroyed === 'function' && window.isDestroyed())) {
clearWindowState();
return null;
}
if (pendingWindow && pendingWindow !== window) clearPendingDropPayloads();
if (readyWindow && readyWindow !== window) readyWindow = null;
return window;
}
function requestReveal() {
if (!getActiveWindow()) return false;
const activeWindow = getActiveWindow();
if (!activeWindow) return false;
const revealGate = getRevealGate();
if (!revealGate || typeof revealGate.request !== 'function') return false;
revealGate.request();
return true;
}
function queueDropPayload(window, paths) {
if (pendingWindow && pendingWindow !== window) clearPendingDropPayloads();
pendingWindow = window;
if (pendingDropPayloads.length >= pendingDropLimit) pendingDropPayloads.shift();
pendingDropPayloads.push(paths);
}
function handleDropTargetFiles(_event, paths) {
const window = getActiveWindow();
if (!window) return false;
if (!window.isVisible() || window.isMinimized()) requestReveal();
sendDroppedFiles(paths);
if (readyWindow === window) sendDroppedFiles(paths);
else queueDropPayload(window, paths);
return true;
}
function rendererBlocked(window) {
const activeWindow = getActiveWindow();
if (!activeWindow || activeWindow !== window) {
clearWindowState(window);
return false;
}
readyWindow = null;
return true;
}
function rendererReady(window) {
const activeWindow = getActiveWindow();
if (!activeWindow || activeWindow !== window) {
clearWindowState(window);
return false;
}
readyWindow = window;
if (pendingWindow !== window) return true;
const payloads = pendingDropPayloads.splice(0);
pendingWindow = null;
for (const paths of payloads) sendDroppedFiles(paths);
return true;
}
function windowClosed(window) {
clearWindowState(window);
}
return {
bindSecondInstance(app) {
app.on('second-instance', requestReveal);
@@ -110,7 +176,10 @@ function createStartupExternalRevealBindings({ getWindow, getRevealGate, sendDro
},
bindDropTargetFiles(ipcMain) {
ipcMain.on('drop-target:files', handleDropTargetFiles);
}
},
rendererBlocked,
rendererReady,
windowClosed
};
}