fix: avoid stacking browser download handlers (#2879)

This commit is contained in:
Neil 2026-05-26 20:14:38 -07:00 committed by GitHub
parent 721ef42ab8
commit 03e43ef165
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 51 additions and 10 deletions

View File

@ -171,7 +171,8 @@ describe('attachMainWindowServices', () => {
setPermissionRequestHandler: setPermissionRequestHandlerMock,
setPermissionCheckHandler: setPermissionCheckHandlerMock,
setDisplayMediaRequestHandler: setDisplayMediaRequestHandlerMock,
on: vi.fn()
on: vi.fn(),
removeListener: vi.fn()
})
systemPreferencesAskForMediaAccessMock.mockResolvedValue(true)
systemPreferencesGetMediaAccessStatusMock.mockReturnValue('granted')
@ -316,7 +317,8 @@ describe('attachMainWindowServices', () => {
setPermissionRequestHandler: setPermissionRequestHandlerMock,
setPermissionCheckHandler: setPermissionCheckHandlerMock,
setDisplayMediaRequestHandler: setDisplayMediaRequestHandlerMock,
on: browserSessionOnMock
on: browserSessionOnMock,
removeListener: vi.fn()
})
const mainWindowOnMock = vi.fn()
@ -378,12 +380,38 @@ describe('attachMainWindowServices', () => {
})
})
it('replaces the persistent browser-session download handler on re-attach', () => {
const browserSessionOnMock = vi.fn()
const browserSessionRemoveListenerMock = vi.fn()
sessionFromPartitionMock.mockReturnValue({
setPermissionRequestHandler: setPermissionRequestHandlerMock,
setPermissionCheckHandler: setPermissionCheckHandlerMock,
setDisplayMediaRequestHandler: setDisplayMediaRequestHandlerMock,
on: browserSessionOnMock,
removeListener: browserSessionRemoveListenerMock
})
attachMainWindowServices(createMainWindow() as never, createStore(), createRuntime() as never)
attachMainWindowServices(createMainWindow() as never, createStore(), createRuntime() as never)
const downloadOnCalls = browserSessionOnMock.mock.calls.filter(
([eventName]) => eventName === 'will-download'
)
const downloadRemoveCalls = browserSessionRemoveListenerMock.mock.calls.filter(
([eventName]) => eventName === 'will-download'
)
expect(downloadOnCalls).toHaveLength(2)
expect(downloadRemoveCalls).toHaveLength(2)
expect(downloadRemoveCalls[1][1]).toBe(downloadOnCalls[0][1])
})
it('clears browser guest registrations when the main window closes', () => {
sessionFromPartitionMock.mockReturnValue({
setPermissionRequestHandler: setPermissionRequestHandlerMock,
setPermissionCheckHandler: setPermissionCheckHandlerMock,
setDisplayMediaRequestHandler: setDisplayMediaRequestHandlerMock,
on: vi.fn()
on: vi.fn(),
removeListener: vi.fn()
})
const mainWindowOnMock = vi.fn()
const mainWindow = createMainWindow()

View File

@ -2,7 +2,7 @@
import { randomUUID } from 'node:crypto'
import { app, ipcMain, session } from 'electron'
import type { BrowserWindow } from 'electron'
import type { BrowserWindow, Session } from 'electron'
import type { Store } from '../persistence'
import type { CreateWorktreeResult, WorktreeStartupLaunch } from '../../shared/types'
import { ORCA_BROWSER_PARTITION } from '../../shared/constants'
@ -196,12 +196,7 @@ export function attachMainWindowServices(
// signature while still denying the request.
callback({ video: undefined, audio: undefined })
})
browserSession.on('will-download', (_event, item, webContents) => {
// Why: browser-tab downloads need explicit product UX before arbitrary sites
// can write files through Orca. Pause the item and route it through
// BrowserManager so the user must explicitly accept the save path first.
browserManager.handleGuestWillDownload({ guestWebContentsId: webContents.id, item })
})
registerBrowserDownloadHandler(browserSession)
mainWindow.on('closed', () => {
// Why: parked browser webviews can outlive the visible tab body until the
@ -212,6 +207,24 @@ export function attachMainWindowServices(
})
}
function handleBrowserWillDownload(
_event: Electron.Event,
item: Electron.DownloadItem,
webContents: Electron.WebContents
): void {
// Why: browser-tab downloads need explicit product UX before arbitrary sites
// can write files through Orca. Pause the item and route it through
// BrowserManager so the user must explicitly accept the save path first.
browserManager.handleGuestWillDownload({ guestWebContentsId: webContents.id, item })
}
function registerBrowserDownloadHandler(browserSession: Session): void {
// Why: browser sessions are process-persistent while main windows can be
// recreated; replace the named handler so re-attach does not stack listeners.
browserSession.removeListener('will-download', handleBrowserWillDownload)
browserSession.on('will-download', handleBrowserWillDownload)
}
function registerAppReloadHandler(
mainWindow: BrowserWindow,
onBeforeRendererReload?: (args: { webContentsId: number; ignoreCache: boolean }) => void