mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-09 17:36:15 -08:00
fix(settings): complete a close left unanswered by settings teardown
Leaving settings and closing the window in the same instant could race: the renderer's close-request listener was gone while its fire-and-forget disarm was still in flight, so a close intercepted in that window was silently swallowed. The guard is mount-long in the renderer, so a disarm can only mean settings is gone and its form already settled — the main process now completes the pending intercepted intent (close or quit) when a disarm finds one unanswered. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bb46a707ec
commit
6946ac3131
2 files changed
+43
No files matched your search
@@ -258,6 +258,38 @@ describe('WindowCloseGuard', () => {
|
||||
expect(win.fireClose()).toBe(false);
|
||||
});
|
||||
|
||||
it('completes a close left unanswered when the renderer disarms', () => {
|
||||
// Teardown race: close intercepted, then the settings page is torn
|
||||
// down and disarms before answering — the close must not be
|
||||
// swallowed.
|
||||
const { guard, win } = createArmedGuard();
|
||||
|
||||
win.fireClose();
|
||||
guard.setGuardActive(false);
|
||||
|
||||
expect(win.close).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('resumes an unanswered quit when the renderer disarms', () => {
|
||||
const { app, guard, win } = createArmedGuard();
|
||||
|
||||
app.fireBeforeQuit();
|
||||
win.fireClose();
|
||||
guard.setGuardActive(false);
|
||||
|
||||
expect(app.quit).toHaveBeenCalledTimes(1);
|
||||
expect(win.close).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('closes nothing on a plain disarm without a pending intent', () => {
|
||||
const { app, guard, win } = createArmedGuard();
|
||||
|
||||
guard.setGuardActive(false);
|
||||
|
||||
expect(win.close).not.toHaveBeenCalled();
|
||||
expect(app.quit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('drops an unanswered quit intent when the renderer navigates away', () => {
|
||||
const { app, guard, win } = createArmedGuard();
|
||||
|
||||
|
||||
@@ -101,6 +101,17 @@ export class WindowCloseGuard {
|
||||
|
||||
setGuardActive(active: boolean): void {
|
||||
this.guardActive = active;
|
||||
|
||||
// A disarm with an unanswered intercepted close is the teardown
|
||||
// race: the renderer left settings between the interception and
|
||||
// this disarm reaching us, so nobody remains to confirm and the
|
||||
// user's close would be silently swallowed. The guard is mount-long
|
||||
// in the renderer, so a disarm only ever means "settings is gone" —
|
||||
// its form was already settled by the router guard, nothing is left
|
||||
// to protect, and the intercepted intent can complete.
|
||||
if (!active && this.pendingIntent !== null) {
|
||||
this.confirmClose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user