fix(browser): explicitly close guest web contents on unregister

webview.remove() in the renderer does not synchronously destroy the
underlying Chromium web contents — the media session stays alive until
GC, letting media keys (F8 play/pause) still control a closed tab.
Call webContents.close() from the main process during unregisterGuest
to tear down the guest immediately.
This commit is contained in:
Jinwoo-H
2026-07-29 19:41:00 -07:00
committed by Jinjing
parent 80c42d38c7
commit c0b1cd98b9
3 changed files with 18 additions and 4 deletions
@@ -59,7 +59,8 @@ function makeGuest(id: number) {
executeJavaScriptInIsolatedWorld: guestExecuteJavaScriptInIsolatedWorldMock,
getZoomFactor: guestGetZoomFactorMock,
capturePage: guestCapturePageMock,
getURL: vi.fn(() => 'https://example.com/')
getURL: vi.fn(() => 'https://example.com/'),
close: vi.fn()
} as unknown as Electron.WebContents
}
+8 -3
View File
@@ -1181,7 +1181,8 @@ describe('browserManager', () => {
setWindowOpenHandler: guestSetWindowOpenHandlerMock,
on: guestOnMock,
off: guestOffMock,
openDevTools: guestOpenDevToolsMock
openDevTools: guestOpenDevToolsMock,
close: vi.fn()
}
webContentsFromIdMock.mockReturnValue(guest)
@@ -1314,7 +1315,8 @@ describe('browserManager', () => {
setWindowOpenHandler: guestSetWindowOpenHandlerMock,
on: guestOnMock,
off: guestOffMock,
openDevTools: guestOpenDevToolsMock
openDevTools: guestOpenDevToolsMock,
close: vi.fn()
}
webContentsFromIdMock.mockImplementation((id: number) => {
if (id === guest.id) {
@@ -2009,7 +2011,8 @@ describe('browserManager', () => {
setWindowOpenHandler: guestSetWindowOpenHandlerMock,
on: guestOnMock,
off: guestOffMock,
openDevTools: guestOpenDevToolsMock
openDevTools: guestOpenDevToolsMock,
close: vi.fn()
}
const item = createDownloadItem()
webContentsFromIdMock.mockImplementation((id: number) => {
@@ -2032,6 +2035,7 @@ describe('browserManager', () => {
browserManager.unregisterGuest('browser-1')
expect(guest.close).toHaveBeenCalledTimes(1)
expect(item.cancel).toHaveBeenCalledTimes(1)
expect(rendererSendMock).toHaveBeenCalledWith(
'browser:download-finished',
@@ -2739,6 +2743,7 @@ describe('browserManager', () => {
on: guestOnMock,
off: guestOffMock,
openDevTools: guestOpenDevToolsMock,
close: vi.fn(),
getURL: vi.fn(() => 'https://example.com/'),
debugger: {
isAttached: vi.fn(() => false),
+8
View File
@@ -1073,6 +1073,14 @@ export class BrowserManager {
const wcId = this.webContentsIdByTabId.get(browserTabId)
if (wcId !== undefined) {
this.tabIdByWebContentsId.delete(wcId)
// Why: webview.remove() in the renderer does not synchronously destroy
// the guest web contents — Chromium keeps its media session alive until
// GC, so media keys (F8 play/pause) still control a "dead" tab.
// Explicitly closing the web contents here tears it down immediately.
const wc = webContents.fromId(wcId)
if (wc && !wc.isDestroyed()) {
wc.close()
}
}
this.webContentsIdByTabId.delete(browserTabId)
this.rendererWebContentsIdByTabId.delete(browserTabId)