fix: recover main window when renderer process crashes

This commit is contained in:
ezequielnick
2026-07-01 12:42:08 +08:00
parent 0c891030df
commit 9a65959c38
3 changed files with 57 additions and 1 deletions

View File

@@ -4,6 +4,10 @@
- 机场插件机制,服务商请参考: [`docs/plugin/机场服务端对接指南-v2.md`](../../docs/plugin/机场服务端对接指南-v2.md)
## 修复 (Fix)
- 部分场景下的白屏问题
# 1.9.6
## 新功能 (Feat)

View File

@@ -101,6 +101,7 @@ export const appLogger = createLogger('app')
// 为了保持向后兼容性,创建各模块的日志实例(都指向同一个应用日志)
export const floatingWindowLogger = createLogger('floating-window')
export const mainWindowLogger = createLogger('main-window')
export const coreLogger = createLogger('mihomo-core')
export const apiLogger = createLogger('mihomo-api')
export const configLogger = createLogger('config')

View File

@@ -8,6 +8,7 @@ import { quitWithoutCore, stopCore } from './core/manager'
import { triggerSysProxy } from './sys/sysproxy'
import { hideDockIcon, showDockIcon } from './resolve/tray'
import { dataDir } from './utils/dirs'
import { mainWindowLogger } from './utils/logger'
interface WindowState {
width: number
@@ -53,6 +54,11 @@ function ensureVisibleOnScreen(state: WindowState): WindowState {
export let mainWindow: BrowserWindow | null = null
let quitTimeout: NodeJS.Timeout | null = null
let createWindowPromise: Promise<void> | null = null
// 主窗口 renderer 崩溃自动恢复的防抖,避免崩溃循环时无限重建
const MAIN_WINDOW_CRASH_WINDOW = 60 * 1000
const MAIN_WINDOW_MAX_CRASH_RECOVERIES = 3
let mainWindowCrashTimestamps: number[] = []
type AutoQuitWithoutCoreMode = NonNullable<IAppConfig['autoQuitWithoutCoreMode']>
export async function createWindow(): Promise<void> {
@@ -94,7 +100,8 @@ async function createWindowInternal(): Promise<void> {
height: 49
},
autoHideMenuBar: true,
...(process.platform === 'linux' ? { icon: icon } : {}),
// Win 显式指定 icon避免异常/恢复路径下任务栏与窗口图标依赖默认 exe
...(process.platform === 'linux' || process.platform === 'win32' ? { icon: icon } : {}),
webPreferences: {
preload: join(__dirname, '../preload/index.cjs'),
spellcheck: false,
@@ -153,6 +160,46 @@ function setupWindowEvents(window: BrowserWindow, config: WindowConfig): void {
window.webContents.reload()
})
// renderer 崩溃时外壳仍在isDestroyed() 为 false、did-fail-load 不触发,会白屏;销毁并按需重建
window.webContents.on('render-process-gone', (_event, details) => {
mainWindowLogger.error('Main window render process gone', details.reason).catch(() => {})
if (mainWindow !== window || window.isDestroyed()) return
const wasVisible = window.isVisible()
mainWindow = null
window.destroy()
const now = Date.now()
mainWindowCrashTimestamps = mainWindowCrashTimestamps.filter(
(timestamp) => now - timestamp < MAIN_WINDOW_CRASH_WINDOW
)
mainWindowCrashTimestamps.push(now)
if (mainWindowCrashTimestamps.length > MAIN_WINDOW_MAX_CRASH_RECOVERIES) {
mainWindowLogger
.error(
`Main window renderer crashed ${mainWindowCrashTimestamps.length} times within ${MAIN_WINDOW_CRASH_WINDOW}ms, stop auto-recovery`
)
.catch(() => {})
return
}
// 可见时立即重建,否则留待下次 showMainWindow(),避免后台崩溃突然弹窗
if (wasVisible) {
void createWindow().then(() => {
clearQuitTimeout()
mainWindow?.show()
mainWindow?.focusOnWebView()
})
}
})
window.webContents.on('unresponsive', () => {
mainWindowLogger.error('Main window unresponsive').catch(() => {})
})
window.on('show', () => {
showDockIcon()
})
@@ -250,6 +297,10 @@ export function showMainWindow(): void {
if (mainWindow && !mainWindow.isDestroyed()) {
clearQuitTimeout()
// 兜底renderer 已崩溃但 render-process-gone 尚未触发时,先 reload 再显示,避免白屏
if (mainWindow.webContents.isCrashed()) {
mainWindow.webContents.reload()
}
mainWindow.show()
mainWindow.focusOnWebView()
return