mirror of
https://github.com/stablyai/orca.git
synced 2026-09-30 08:03:12 +00:00
fix(relay): retain web contents through window destruction
This commit is contained in:
@@ -68,14 +68,22 @@ describe('requestMobileMarkdownFromRenderer', () => {
|
||||
await expect(pending).resolves.toMatchObject({ content: '# ok' })
|
||||
})
|
||||
|
||||
it('rejects and removes the pending listener when the renderer is destroyed', async () => {
|
||||
it('rejects and cleans up when the BrowserWindow closes and webContents becomes unavailable', async () => {
|
||||
const { requestMobileMarkdownFromRenderer } = await import('./mobile-markdown-request-relay')
|
||||
const mainWebContents = Object.assign(new EventEmitter(), {
|
||||
const webContents = Object.assign(new EventEmitter(), {
|
||||
send: vi.fn()
|
||||
})
|
||||
let windowClosed = false
|
||||
const mainWindow = Object.assign(new EventEmitter(), {
|
||||
isDestroyed: () => false,
|
||||
webContents: mainWebContents
|
||||
isDestroyed: () => false
|
||||
})
|
||||
Object.defineProperty(mainWindow, 'webContents', {
|
||||
get: () => {
|
||||
if (windowClosed) {
|
||||
throw new Error('webContents unavailable after close')
|
||||
}
|
||||
return webContents
|
||||
}
|
||||
})
|
||||
|
||||
const pending = requestMobileMarkdownFromRenderer(mainWindow as never, {
|
||||
@@ -85,9 +93,12 @@ describe('requestMobileMarkdownFromRenderer', () => {
|
||||
})
|
||||
expect(ipcEmitter.listenerCount('ui:mobileMarkdownResponse')).toBe(1)
|
||||
|
||||
mainWebContents.emit('destroyed')
|
||||
windowClosed = true
|
||||
mainWindow.emit('closed')
|
||||
|
||||
await expect(pending).rejects.toThrow('renderer_unavailable')
|
||||
expect(ipcEmitter.listenerCount('ui:mobileMarkdownResponse')).toBe(0)
|
||||
expect(webContents.listenerCount('destroyed')).toBe(0)
|
||||
expect(webContents.listenerCount('render-process-gone')).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -24,6 +24,7 @@ export async function requestMobileMarkdownFromRenderer(
|
||||
if (mainWindow.isDestroyed()) {
|
||||
throw new Error('renderer_unavailable')
|
||||
}
|
||||
const webContents = mainWindow.webContents
|
||||
const id = randomUUID()
|
||||
return await new Promise((resolve, reject) => {
|
||||
let settled = false
|
||||
@@ -41,9 +42,9 @@ export async function requestMobileMarkdownFromRenderer(
|
||||
if (typeof mainWindow.removeListener === 'function') {
|
||||
mainWindow.removeListener('closed', onRendererUnavailable)
|
||||
}
|
||||
if (typeof mainWindow.webContents.removeListener === 'function') {
|
||||
mainWindow.webContents.removeListener('destroyed', onRendererUnavailable)
|
||||
mainWindow.webContents.removeListener('render-process-gone', onRendererUnavailable)
|
||||
if (typeof webContents.removeListener === 'function') {
|
||||
webContents.removeListener('destroyed', onRendererUnavailable)
|
||||
webContents.removeListener('render-process-gone', onRendererUnavailable)
|
||||
}
|
||||
if (error) {
|
||||
reject(error)
|
||||
@@ -61,7 +62,7 @@ export async function requestMobileMarkdownFromRenderer(
|
||||
event: Electron.IpcMainEvent,
|
||||
response: RuntimeMobileMarkdownResponse
|
||||
): void => {
|
||||
if (event.sender !== mainWindow.webContents) {
|
||||
if (event.sender !== webContents) {
|
||||
return
|
||||
}
|
||||
if (response.id !== id) {
|
||||
@@ -77,12 +78,12 @@ export async function requestMobileMarkdownFromRenderer(
|
||||
if (typeof mainWindow.once === 'function') {
|
||||
mainWindow.once('closed', onRendererUnavailable)
|
||||
}
|
||||
if (typeof mainWindow.webContents.once === 'function') {
|
||||
mainWindow.webContents.once('destroyed', onRendererUnavailable)
|
||||
mainWindow.webContents.once('render-process-gone', onRendererUnavailable)
|
||||
if (typeof webContents.once === 'function') {
|
||||
webContents.once('destroyed', onRendererUnavailable)
|
||||
webContents.once('render-process-gone', onRendererUnavailable)
|
||||
}
|
||||
try {
|
||||
mainWindow.webContents.send('ui:mobileMarkdownRequest', { id, ...request })
|
||||
webContents.send('ui:mobileMarkdownRequest', { id, ...request })
|
||||
} catch {
|
||||
finish(new Error('renderer_unavailable'))
|
||||
}
|
||||
|
||||
@@ -79,24 +79,35 @@ describe('requestTerminalTabCloseFromRenderer', () => {
|
||||
await expect(pending).rejects.toThrow('terminal_tab_pinned')
|
||||
})
|
||||
|
||||
it('rejects and removes the pending listener when the renderer closes', async () => {
|
||||
it('rejects and cleans up when the BrowserWindow closes and webContents becomes unavailable', async () => {
|
||||
const { requestTerminalTabCloseFromRenderer } =
|
||||
await import('./terminal-tab-close-request-relay')
|
||||
const webContents = Object.assign(new EventEmitter(), {
|
||||
isDestroyed: () => false,
|
||||
send: vi.fn()
|
||||
})
|
||||
let windowClosed = false
|
||||
const mainWindow = Object.assign(new EventEmitter(), {
|
||||
isDestroyed: () => false,
|
||||
webContents
|
||||
isDestroyed: () => false
|
||||
})
|
||||
Object.defineProperty(mainWindow, 'webContents', {
|
||||
get: () => {
|
||||
if (windowClosed) {
|
||||
throw new Error('webContents unavailable after close')
|
||||
}
|
||||
return webContents
|
||||
}
|
||||
})
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the EventEmitter test double implements the BrowserWindow events used by this test.
|
||||
const pending = requestTerminalTabCloseFromRenderer(mainWindow as never, 'tab-closed')
|
||||
expect(ipcEmitter.listenerCount('ui:terminalTabCloseResponse')).toBe(1)
|
||||
|
||||
windowClosed = true
|
||||
mainWindow.emit('closed')
|
||||
|
||||
await expect(pending).rejects.toThrow('renderer_unavailable')
|
||||
expect(ipcEmitter.listenerCount('ui:terminalTabCloseResponse')).toBe(0)
|
||||
expect(webContents.listenerCount('destroyed')).toBe(0)
|
||||
expect(webContents.listenerCount('render-process-gone')).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -14,7 +14,11 @@ export async function requestTerminalTabCloseFromRenderer(
|
||||
tabId: string,
|
||||
options: { localPtyTeardownOwnedExternally?: boolean; force?: boolean } = {}
|
||||
): Promise<void> {
|
||||
if (mainWindow.isDestroyed() || mainWindow.webContents.isDestroyed()) {
|
||||
if (mainWindow.isDestroyed()) {
|
||||
throw new Error('renderer_unavailable')
|
||||
}
|
||||
const webContents = mainWindow.webContents
|
||||
if (webContents.isDestroyed()) {
|
||||
throw new Error('renderer_unavailable')
|
||||
}
|
||||
const requestId = randomUUID()
|
||||
@@ -31,9 +35,9 @@ export async function requestTerminalTabCloseFromRenderer(
|
||||
if (typeof mainWindow.removeListener === 'function') {
|
||||
mainWindow.removeListener('closed', onRendererUnavailable)
|
||||
}
|
||||
if (typeof mainWindow.webContents.removeListener === 'function') {
|
||||
mainWindow.webContents.removeListener('destroyed', onRendererUnavailable)
|
||||
mainWindow.webContents.removeListener('render-process-gone', onRendererUnavailable)
|
||||
if (typeof webContents.removeListener === 'function') {
|
||||
webContents.removeListener('destroyed', onRendererUnavailable)
|
||||
webContents.removeListener('render-process-gone', onRendererUnavailable)
|
||||
}
|
||||
if (error) {
|
||||
reject(error)
|
||||
@@ -48,7 +52,7 @@ export async function requestTerminalTabCloseFromRenderer(
|
||||
const onResponse = (event: Electron.IpcMainEvent, response: TerminalTabCloseResponse): void => {
|
||||
// Why: request IDs are visible to renderer code; only the selected main
|
||||
// window may commit or reject its lifecycle transaction.
|
||||
if (event.sender !== mainWindow.webContents || response.requestId !== requestId) {
|
||||
if (event.sender !== webContents || response.requestId !== requestId) {
|
||||
return
|
||||
}
|
||||
if (response.error) {
|
||||
@@ -61,13 +65,13 @@ export async function requestTerminalTabCloseFromRenderer(
|
||||
if (typeof mainWindow.once === 'function') {
|
||||
mainWindow.once('closed', onRendererUnavailable)
|
||||
}
|
||||
if (typeof mainWindow.webContents.once === 'function') {
|
||||
mainWindow.webContents.once('destroyed', onRendererUnavailable)
|
||||
mainWindow.webContents.once('render-process-gone', onRendererUnavailable)
|
||||
if (typeof webContents.once === 'function') {
|
||||
webContents.once('destroyed', onRendererUnavailable)
|
||||
webContents.once('render-process-gone', onRendererUnavailable)
|
||||
}
|
||||
const request: TerminalTabCloseRequest = { requestId, tabId, ...options }
|
||||
try {
|
||||
mainWindow.webContents.send('ui:terminalTabCloseRequest', request)
|
||||
webContents.send('ui:terminalTabCloseRequest', request)
|
||||
} catch {
|
||||
finish(new Error('renderer_unavailable'))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user