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:
@@ -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({
|
function createStartupRecoveryCoordinator({
|
||||||
load,
|
load,
|
||||||
reload,
|
reload,
|
||||||
@@ -368,9 +459,12 @@ function createStartupWindow(BrowserWindow, options) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
configureStartupRenderer,
|
configureStartupRenderer,
|
||||||
|
createStartupCloseHandler,
|
||||||
|
createStartupExternalRevealBindings,
|
||||||
createStartupFailureDocument,
|
createStartupFailureDocument,
|
||||||
createStartupNavigationLoader,
|
createStartupNavigationLoader,
|
||||||
createStartupRecoveryCoordinator,
|
createStartupRecoveryCoordinator,
|
||||||
|
createStartupRevealGate,
|
||||||
createStartupRendererHandlers,
|
createStartupRendererHandlers,
|
||||||
createStartupWindow,
|
createStartupWindow,
|
||||||
resolveStartupLanguage
|
resolveStartupLanguage
|
||||||
|
|||||||
@@ -7,9 +7,12 @@ app.setName('Multi Hoster Uploader');
|
|||||||
app.setAppUserModelId('com.multihoster.uploader');
|
app.setAppUserModelId('com.multihoster.uploader');
|
||||||
const {
|
const {
|
||||||
configureStartupRenderer,
|
configureStartupRenderer,
|
||||||
|
createStartupCloseHandler,
|
||||||
|
createStartupExternalRevealBindings,
|
||||||
createStartupFailureDocument,
|
createStartupFailureDocument,
|
||||||
createStartupNavigationLoader,
|
createStartupNavigationLoader,
|
||||||
createStartupRecoveryCoordinator,
|
createStartupRecoveryCoordinator,
|
||||||
|
createStartupRevealGate,
|
||||||
createStartupRendererHandlers,
|
createStartupRendererHandlers,
|
||||||
createStartupWindow,
|
createStartupWindow,
|
||||||
resolveStartupLanguage
|
resolveStartupLanguage
|
||||||
@@ -137,9 +140,15 @@ const uploadBatchMutationGates = new WeakMap();
|
|||||||
const uploadRecoveryStates = new WeakMap();
|
const uploadRecoveryStates = new WeakMap();
|
||||||
let lastSessionSummary = null;
|
let lastSessionSummary = null;
|
||||||
let startupRecoveryCoordinator = null;
|
let startupRecoveryCoordinator = null;
|
||||||
|
let startupRevealGate = null;
|
||||||
let startupRendererHandlers = null;
|
let startupRendererHandlers = null;
|
||||||
let sourceDeleteJournal = null;
|
let sourceDeleteJournal = null;
|
||||||
const RENDERER_READY_TIMEOUT_MS = 15000;
|
const RENDERER_READY_TIMEOUT_MS = 15000;
|
||||||
|
const startupExternalRevealBindings = createStartupExternalRevealBindings({
|
||||||
|
getWindow: () => mainWindow,
|
||||||
|
getRevealGate: () => startupRevealGate,
|
||||||
|
sendDroppedFiles: paths => safeSend('drop-target:files', paths)
|
||||||
|
});
|
||||||
const pendingUploadFinalizations = new Map();
|
const pendingUploadFinalizations = new Map();
|
||||||
|
|
||||||
function requestUploadFinalization(summary, historyPersisted) {
|
function requestUploadFinalization(summary, historyPersisted) {
|
||||||
@@ -284,13 +293,7 @@ const _hasSingleInstanceLock = app.requestSingleInstanceLock();
|
|||||||
if (!_hasSingleInstanceLock) {
|
if (!_hasSingleInstanceLock) {
|
||||||
app.quit();
|
app.quit();
|
||||||
} else {
|
} else {
|
||||||
app.on('second-instance', () => {
|
startupExternalRevealBindings.bindSecondInstance(app);
|
||||||
if (mainWindow) {
|
|
||||||
if (mainWindow.isMinimized()) mainWindow.restore();
|
|
||||||
if (!mainWindow.isVisible()) mainWindow.show();
|
|
||||||
mainWindow.focus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
// Rotation memory that survives batch-done → new UploadManager within the
|
// Rotation memory that survives batch-done → new UploadManager within the
|
||||||
// same app session. Without this, clicking "Retry failed" after a batch
|
// same app session. Without this, clicking "Retry failed" after a batch
|
||||||
@@ -1453,12 +1456,19 @@ function createWindow() {
|
|||||||
lastRestoredCloseAttempt = null;
|
lastRestoredCloseAttempt = null;
|
||||||
configStore.setWritesQuiesced(false);
|
configStore.setWritesQuiesced(false);
|
||||||
clearCloseFlushTimer();
|
clearCloseFlushTimer();
|
||||||
|
const currentStartupRevealGate = createStartupRevealGate(mainWindow, {
|
||||||
mainWindow.on('close', (event) => {
|
onBlock: () => {
|
||||||
if (closeFlushApproved || !closeHandshakeReady || mainWindow.webContents.isDestroyed()) return;
|
closeHandshakeReady = false;
|
||||||
event.preventDefault();
|
restoreClosePreparation(closePreparationAttempt);
|
||||||
requestClosePreparation();
|
}
|
||||||
});
|
});
|
||||||
|
startupRevealGate = currentStartupRevealGate;
|
||||||
|
|
||||||
|
mainWindow.on('close', createStartupCloseHandler({
|
||||||
|
window: mainWindow,
|
||||||
|
shouldPrepareClose: () => !closeFlushApproved && closeHandshakeReady,
|
||||||
|
requestClosePreparation
|
||||||
|
}));
|
||||||
|
|
||||||
mainWindow.webContents.setBackgroundThrottling(false);
|
mainWindow.webContents.setBackgroundThrottling(false);
|
||||||
|
|
||||||
@@ -1488,7 +1498,7 @@ function createWindow() {
|
|||||||
const loadStartupDocument = createStartupNavigationLoader(mainWindow, rendererTarget, rendererOptions);
|
const loadStartupDocument = createStartupNavigationLoader(mainWindow, rendererTarget, rendererOptions);
|
||||||
const loadRendererSurface = async () => {
|
const loadRendererSurface = async () => {
|
||||||
try {
|
try {
|
||||||
return await loadStartupDocument();
|
return await currentStartupRevealGate.navigate(loadStartupDocument);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
_writeCrashLog('LOAD FILE FAILED', error);
|
_writeCrashLog('LOAD FILE FAILED', error);
|
||||||
debugLog(`LOAD FILE FAILED: ${error && error.stack ? error.stack : error}`);
|
debugLog(`LOAD FILE FAILED: ${error && error.stack ? error.stack : error}`);
|
||||||
@@ -1498,10 +1508,10 @@ function createWindow() {
|
|||||||
startupRecoveryCoordinator = createStartupRecoveryCoordinator({
|
startupRecoveryCoordinator = createStartupRecoveryCoordinator({
|
||||||
load: loadRendererSurface,
|
load: loadRendererSurface,
|
||||||
reload: loadRendererSurface,
|
reload: loadRendererSurface,
|
||||||
reveal: () => {
|
reveal: currentStartupRevealGate.reveal,
|
||||||
if (mainWindow && !mainWindow.isDestroyed() && !mainWindow.isVisible()) mainWindow.show();
|
showFailure: () => currentStartupRevealGate.navigate(
|
||||||
},
|
() => mainWindow.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(createStartupFailureDocument(startupLanguage))}`)
|
||||||
showFailure: () => mainWindow.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(createStartupFailureDocument(startupLanguage))}`),
|
),
|
||||||
close: () => app.exit(1),
|
close: () => app.exit(1),
|
||||||
readyTimeoutMs: RENDERER_READY_TIMEOUT_MS
|
readyTimeoutMs: RENDERER_READY_TIMEOUT_MS
|
||||||
});
|
});
|
||||||
@@ -1509,10 +1519,7 @@ function createWindow() {
|
|||||||
window: mainWindow,
|
window: mainWindow,
|
||||||
ipcMain,
|
ipcMain,
|
||||||
coordinator: startupRecoveryCoordinator,
|
coordinator: startupRecoveryCoordinator,
|
||||||
onDocumentLoadStarted: () => {
|
onDocumentLoadStarted: currentStartupRevealGate.block,
|
||||||
closeHandshakeReady = false;
|
|
||||||
restoreClosePreparation(closePreparationAttempt);
|
|
||||||
},
|
|
||||||
onRendererCrashed: (details) => {
|
onRendererCrashed: (details) => {
|
||||||
_writeCrashLog('RENDER PROCESS GONE', new Error(details.reason || 'unknown'), details);
|
_writeCrashLog('RENDER PROCESS GONE', new Error(details.reason || 'unknown'), details);
|
||||||
debugLog(`RENDER PROCESS GONE: reason=${details.reason} exitCode=${details.exitCode}`);
|
debugLog(`RENDER PROCESS GONE: reason=${details.reason} exitCode=${details.exitCode}`);
|
||||||
@@ -1530,6 +1537,7 @@ function createWindow() {
|
|||||||
const currentStartupRecoveryCoordinator = startupRecoveryCoordinator;
|
const currentStartupRecoveryCoordinator = startupRecoveryCoordinator;
|
||||||
mainWindow.once('closed', () => {
|
mainWindow.once('closed', () => {
|
||||||
currentStartupRendererHandlers.dispose();
|
currentStartupRendererHandlers.dispose();
|
||||||
|
if (startupRevealGate === currentStartupRevealGate) startupRevealGate = null;
|
||||||
if (startupRendererHandlers === currentStartupRendererHandlers) startupRendererHandlers = null;
|
if (startupRendererHandlers === currentStartupRendererHandlers) startupRendererHandlers = null;
|
||||||
if (startupRecoveryCoordinator === currentStartupRecoveryCoordinator) startupRecoveryCoordinator = null;
|
if (startupRecoveryCoordinator === currentStartupRecoveryCoordinator) startupRecoveryCoordinator = null;
|
||||||
});
|
});
|
||||||
@@ -1553,9 +1561,7 @@ function createTray() {
|
|||||||
tray = new Tray(icon || nativeImage.createEmpty());
|
tray = new Tray(icon || nativeImage.createEmpty());
|
||||||
refreshTrayLanguage();
|
refreshTrayLanguage();
|
||||||
|
|
||||||
tray.on('click', () => {
|
startupExternalRevealBindings.bindTrayClick(tray);
|
||||||
if (mainWindow) { mainWindow.show(); mainWindow.focus(); }
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
tray = null;
|
tray = null;
|
||||||
debugLog(`createTray failed (non-fatal): ${err && err.message ? err.message : err}`);
|
debugLog(`createTray failed (non-fatal): ${err && err.message ? err.message : err}`);
|
||||||
@@ -1925,7 +1931,7 @@ function refreshTrayLanguage() {
|
|||||||
if (!tray || tray.isDestroyed()) return;
|
if (!tray || tray.isDestroyed()) return;
|
||||||
tray.setToolTip('Multi Hoster Uploader');
|
tray.setToolTip('Multi Hoster Uploader');
|
||||||
tray.setContextMenu(Menu.buildFromTemplate([
|
tray.setContextMenu(Menu.buildFromTemplate([
|
||||||
{ label: shellText('Öffnen', 'Open'), click: () => { if (mainWindow) { mainWindow.show(); mainWindow.focus(); } } },
|
startupExternalRevealBindings.createTrayMenuItem(shellText('Öffnen', 'Open')),
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ label: shellText('Beenden', 'Quit'), click: () => { app.quit(); } }
|
{ label: shellText('Beenden', 'Quit'), click: () => { app.quit(); } }
|
||||||
]));
|
]));
|
||||||
@@ -3701,15 +3707,7 @@ ipcMain.handle('hide-drop-target', () => {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('drop-target:files', (_event, paths) => {
|
startupExternalRevealBindings.bindDropTargetFiles(ipcMain);
|
||||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
||||||
if (!mainWindow.isVisible() || mainWindow.isMinimized()) {
|
|
||||||
mainWindow.show();
|
|
||||||
mainWindow.focus();
|
|
||||||
}
|
|
||||||
safeSend('drop-target:files', paths);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// --- Shutdown after finish ---
|
// --- Shutdown after finish ---
|
||||||
let shutdownMode = 'nothing';
|
let shutdownMode = 'nothing';
|
||||||
|
|||||||
@@ -5,9 +5,12 @@ const fs = require('node:fs');
|
|||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const {
|
const {
|
||||||
configureStartupRenderer,
|
configureStartupRenderer,
|
||||||
|
createStartupCloseHandler,
|
||||||
|
createStartupExternalRevealBindings,
|
||||||
createStartupFailureDocument,
|
createStartupFailureDocument,
|
||||||
createStartupNavigationLoader,
|
createStartupNavigationLoader,
|
||||||
createStartupRecoveryCoordinator,
|
createStartupRecoveryCoordinator,
|
||||||
|
createStartupRevealGate,
|
||||||
createStartupRendererHandlers,
|
createStartupRendererHandlers,
|
||||||
createStartupWindow,
|
createStartupWindow,
|
||||||
resolveStartupLanguage
|
resolveStartupLanguage
|
||||||
@@ -92,6 +95,38 @@ function createRendererFrame(frameToken, url = `file:///renderer/index.html?star
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createRevealWindow({ minimized = true, visible = false } = {}) {
|
||||||
|
const events = [];
|
||||||
|
return {
|
||||||
|
events,
|
||||||
|
webContents: {
|
||||||
|
isDestroyed() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isDestroyed() {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
isMinimized() {
|
||||||
|
return minimized;
|
||||||
|
},
|
||||||
|
isVisible() {
|
||||||
|
return visible;
|
||||||
|
},
|
||||||
|
restore() {
|
||||||
|
events.push('restore');
|
||||||
|
minimized = false;
|
||||||
|
},
|
||||||
|
show() {
|
||||||
|
events.push('show');
|
||||||
|
visible = true;
|
||||||
|
},
|
||||||
|
focus() {
|
||||||
|
events.push('focus');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
test('configureStartupRenderer leaves hardware acceleration enabled for a local Windows session', () => {
|
test('configureStartupRenderer leaves hardware acceleration enabled for a local Windows session', () => {
|
||||||
let calls = 0;
|
let calls = 0;
|
||||||
configureStartupRenderer({ disableHardwareAcceleration() { calls++; } }, { SESSIONNAME: 'Console' }, 'win32');
|
configureStartupRenderer({ disableHardwareAcceleration() { calls++; } }, { SESSIONNAME: 'Console' }, 'win32');
|
||||||
@@ -144,6 +179,136 @@ test('ready-to-show cannot reveal the main window before renderer Ready', async
|
|||||||
assert.equal(startup.window.showCalls, 0);
|
assert.equal(startup.window.showCalls, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('all production external reveal paths wait for an authorized startup surface', () => {
|
||||||
|
const paths = [
|
||||||
|
['second-instance', bindings => {
|
||||||
|
const app = new EventEmitter();
|
||||||
|
bindings.bindSecondInstance(app);
|
||||||
|
app.emit('second-instance');
|
||||||
|
}],
|
||||||
|
['tray click', bindings => {
|
||||||
|
const tray = new EventEmitter();
|
||||||
|
bindings.bindTrayClick(tray);
|
||||||
|
tray.emit('click');
|
||||||
|
}],
|
||||||
|
['tray menu', bindings => {
|
||||||
|
bindings.createTrayMenuItem('Open').click();
|
||||||
|
}],
|
||||||
|
['drop target files', bindings => {
|
||||||
|
const ipcMain = new EventEmitter();
|
||||||
|
bindings.bindDropTargetFiles(ipcMain);
|
||||||
|
ipcMain.emit('drop-target:files', {}, ['queued.mkv']);
|
||||||
|
}]
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const [name, invoke] of paths) {
|
||||||
|
const window = createRevealWindow();
|
||||||
|
const revealGate = createStartupRevealGate(window);
|
||||||
|
const droppedFiles = [];
|
||||||
|
const bindings = createStartupExternalRevealBindings({
|
||||||
|
getWindow: () => window,
|
||||||
|
getRevealGate: () => revealGate,
|
||||||
|
sendDroppedFiles: paths => droppedFiles.push(paths)
|
||||||
|
});
|
||||||
|
|
||||||
|
invoke(bindings);
|
||||||
|
|
||||||
|
assert.deepEqual(window.events, [], name);
|
||||||
|
assert.deepEqual(droppedFiles, name === 'drop target files' ? [['queued.mkv']] : [], name);
|
||||||
|
|
||||||
|
revealGate.reveal();
|
||||||
|
|
||||||
|
assert.deepEqual(window.events, ['restore', 'show', 'focus'], name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renderer Ready and the safe failure surface can authorize the gated window', async () => {
|
||||||
|
const readyWindow = createRevealWindow({ minimized: false });
|
||||||
|
const readyGate = createStartupRevealGate(readyWindow);
|
||||||
|
const readyCoordinator = createStartupRecoveryCoordinator({
|
||||||
|
load() {},
|
||||||
|
reload() {},
|
||||||
|
reveal: readyGate.reveal,
|
||||||
|
close() {}
|
||||||
|
});
|
||||||
|
const generation = readyCoordinator.rendererLoadStarted();
|
||||||
|
readyCoordinator.rendererLoaded(generation);
|
||||||
|
|
||||||
|
readyCoordinator.rendererReady(generation);
|
||||||
|
|
||||||
|
assert.deepEqual(readyWindow.events, ['show']);
|
||||||
|
|
||||||
|
const failureWindow = createRevealWindow({ minimized: false });
|
||||||
|
const failureGate = createStartupRevealGate(failureWindow);
|
||||||
|
let failureSurfaceLoads = 0;
|
||||||
|
const failureCoordinator = createStartupRecoveryCoordinator({
|
||||||
|
async load() {
|
||||||
|
throw new Error('navigation failed');
|
||||||
|
},
|
||||||
|
reload() {},
|
||||||
|
reveal: failureGate.reveal,
|
||||||
|
async showFailure() {
|
||||||
|
failureSurfaceLoads++;
|
||||||
|
},
|
||||||
|
close() {}
|
||||||
|
});
|
||||||
|
|
||||||
|
await failureCoordinator.loadInitial();
|
||||||
|
|
||||||
|
assert.equal(failureSurfaceLoads, 1);
|
||||||
|
assert.deepEqual(failureWindow.events, ['show']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('failed recovery navigation leaves the safe failure surface directly closable', async () => {
|
||||||
|
const window = createRevealWindow({ minimized: false });
|
||||||
|
let closeHandshakeReady = true;
|
||||||
|
const readinessDuringNavigation = [];
|
||||||
|
const revealGate = createStartupRevealGate(window, {
|
||||||
|
onBlock() {
|
||||||
|
closeHandshakeReady = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const coordinator = createStartupRecoveryCoordinator({
|
||||||
|
load() {},
|
||||||
|
reload() {
|
||||||
|
return revealGate.navigate(() => {
|
||||||
|
readinessDuringNavigation.push(closeHandshakeReady);
|
||||||
|
throw new Error('recovery load rejected before navigation');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
reveal: revealGate.reveal,
|
||||||
|
showFailure() {
|
||||||
|
return revealGate.navigate(() => {
|
||||||
|
readinessDuringNavigation.push(closeHandshakeReady);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
close() {}
|
||||||
|
});
|
||||||
|
|
||||||
|
await coordinator.rendererCrashed({ reason: 'crashed' });
|
||||||
|
|
||||||
|
let closePreparationCalls = 0;
|
||||||
|
let preventDefaultCalls = 0;
|
||||||
|
const closeHandler = createStartupCloseHandler({
|
||||||
|
window,
|
||||||
|
shouldPrepareClose: () => closeHandshakeReady,
|
||||||
|
requestClosePreparation() {
|
||||||
|
closePreparationCalls++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const intercepted = closeHandler({
|
||||||
|
preventDefault() {
|
||||||
|
preventDefaultCalls++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(readinessDuringNavigation, [false, false]);
|
||||||
|
assert.deepEqual(window.events, ['show']);
|
||||||
|
assert.equal(intercepted, false);
|
||||||
|
assert.equal(preventDefaultCalls, 0);
|
||||||
|
assert.equal(closePreparationCalls, 0);
|
||||||
|
});
|
||||||
|
|
||||||
test('startup load forwards a rejected navigation to the error handler', async () => {
|
test('startup load forwards a rejected navigation to the error handler', async () => {
|
||||||
const startup = createStartupWindow(TestBrowserWindow, {});
|
const startup = createStartupWindow(TestBrowserWindow, {});
|
||||||
let handledError;
|
let handledError;
|
||||||
|
|||||||
Reference in New Issue
Block a user