From 0e8677d22b266106239e2269064b3bc0c8cdb229 Mon Sep 17 00:00:00 2001 From: Manuele Conti Date: Tue, 24 Mar 2026 22:03:16 +0100 Subject: [PATCH] Harden dev menu rebuild and extension startup paths Guard development-only menu and devtools initialization paths against BrowserWindow teardown races. This avoids Object has been destroyed errors when the menu is rebuilt during window shutdown, adds an explicit catch for asynchronous menu rebuild failures, and downgrades optional React DevTools installation failures to a compact warning instead of a noisy stack trace during development startup. --- src/main/main.ts | 10 +++++++--- src/main/menu.ts | 21 ++++++++++++++++++++- src/main/modules/ipc/main.ts | 6 +++++- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/main/main.ts b/src/main/main.ts index a448eb19e..a3041ecdc 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -92,12 +92,16 @@ const installExtensions = async () => { const forceDownload = !!process.env.UPGRADE_EXTENSIONS const extensions = ['REACT_DEVELOPER_TOOLS'] - return installer - .default( + try { + return await installer.default( extensions.map((name) => installer[name as keyof typeof Installer]), forceDownload, ) - .catch(console.log) + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + console.warn(`Skipping development extension installation: ${message}`) + return [] + } } const createMainWindow = async () => { diff --git a/src/main/menu.ts b/src/main/menu.ts index f2bde2c96..28f3aae80 100644 --- a/src/main/menu.ts +++ b/src/main/menu.ts @@ -32,7 +32,19 @@ export default class MenuBuilder { this.projectService = new ProjectService(mainWindow) } + private hasLiveWindow(): boolean { + return !this.mainWindow.isDestroyed() + } + + private getFallbackMenu(): Menu { + return Menu.getApplicationMenu() ?? Menu.buildFromTemplate([]) + } + async buildMenu(): Promise { + if (!this.hasLiveWindow()) { + return this.getFallbackMenu() + } + if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true') { this.setupDevelopmentEnvironment() } @@ -129,13 +141,18 @@ export default class MenuBuilder { */ setupDevelopmentEnvironment(): void { + if (!this.hasLiveWindow()) return + this.mainWindow.webContents.on('context-menu', (_, props) => { + if (!this.hasLiveWindow()) return + const { x, y } = props Menu.buildFromTemplate([ { label: 'Inspect element', click: () => { + if (!this.hasLiveWindow()) return this.mainWindow.webContents.inspectElement(x, y) }, }, @@ -147,7 +164,9 @@ export default class MenuBuilder { const newTheme = nativeTheme.shouldUseDarkColors ? 'light' : 'dark' nativeTheme.themeSource = newTheme store.set('theme', newTheme) - this.mainWindow.webContents.send('system:update-theme') + if (this.hasLiveWindow()) { + this.mainWindow.webContents.send('system:update-theme') + } void this.buildMenu() } diff --git a/src/main/modules/ipc/main.ts b/src/main/modules/ipc/main.ts index ae73dde85..2cf1d4a46 100644 --- a/src/main/modules/ipc/main.ts +++ b/src/main/modules/ipc/main.ts @@ -840,7 +840,11 @@ class MainProcessBridge implements MainIpcModule { this.simulatorModule.stop() this.mainWindow?.webContents.reload() } - handleWindowRebuildMenu = () => void this.menuBuilder.buildMenu() + handleWindowRebuildMenu = () => { + void this.menuBuilder.buildMenu().catch((error) => { + console.error('Error rebuilding application menu:', error) + }) + } // Hardware handlers handleHardwareGetAvailableCommunicationPorts = async () => this.hardwareModule.getAvailableSerialPorts()