diff --git a/apps/electron-backend/src/app/services/playback-keep-awake.service.spec.ts b/apps/electron-backend/src/app/services/playback-keep-awake.service.spec.ts index 0a5c5f7b7..4ed9f67d2 100644 --- a/apps/electron-backend/src/app/services/playback-keep-awake.service.spec.ts +++ b/apps/electron-backend/src/app/services/playback-keep-awake.service.spec.ts @@ -98,6 +98,17 @@ describe('playback keep-awake service', () => { expect(mockedBlocker.stop).toHaveBeenCalledWith(7); }); + it('releases the blocker when the renderer process crashes', () => { + const stub = createSenderStub(1); + + setPlaybackKeepAwake(stub.sender, true); + // A crash emits render-process-gone while the WebContents object + // stays alive; without a reload no other lifetime event follows. + stub.emit('render-process-gone', {}, { reason: 'crashed' }); + + expect(mockedBlocker.stop).toHaveBeenCalledWith(7); + }); + it('releases the blocker on a main-frame navigation (reload)', () => { const stub = createSenderStub(1); @@ -127,10 +138,12 @@ describe('playback keep-awake service', () => { setPlaybackKeepAwake(stub.sender, true); expect(stub.listenerCount('destroyed')).toBe(1); + expect(stub.listenerCount('render-process-gone')).toBe(1); expect(stub.listenerCount('did-start-navigation')).toBe(1); setPlaybackKeepAwake(stub.sender, false); expect(stub.listenerCount('destroyed')).toBe(0); + expect(stub.listenerCount('render-process-gone')).toBe(0); expect(stub.listenerCount('did-start-navigation')).toBe(0); }); diff --git a/apps/electron-backend/src/app/services/playback-keep-awake.service.ts b/apps/electron-backend/src/app/services/playback-keep-awake.service.ts index 46aa7466d..e5055c5e9 100644 --- a/apps/electron-backend/src/app/services/playback-keep-awake.service.ts +++ b/apps/electron-backend/src/app/services/playback-keep-awake.service.ts @@ -13,8 +13,10 @@ import { powerSaveBlocker, WebContents } from 'electron'; * * The renderer's flag must not outlive the page that set it: a reload or a * crashed render process would otherwise pin the display awake until app - * quit. Each activating WebContents therefore gets destroy/navigation - * listeners that withdraw its vote. + * quit. Each activating WebContents therefore gets destroy/crash/navigation + * listeners that withdraw its vote — a crash emits `render-process-gone` + * while the WebContents object stays alive, so `destroyed` alone would miss + * it when no reload follows. */ const activeSenders = new Set(); @@ -62,9 +64,11 @@ function watchSenderLifetime(sender: WebContents): void { } }; sender.on('destroyed', clear); + sender.on('render-process-gone', clear); sender.on('did-start-navigation', onNavigation); senderCleanups.set(senderId, () => { sender.off('destroyed', clear); + sender.off('render-process-gone', clear); sender.off('did-start-navigation', onNavigation); }); }