fix: dispose renderer crash diagnostics hooks (#4216)

This commit is contained in:
Neil
2026-05-31 08:38:54 -07:00
committed by GitHub
parent eff94f4a91
commit e82e48d290
2 changed files with 51 additions and 1 deletions
@@ -9,12 +9,21 @@ describe('renderer crash diagnostics', () => {
let listeners: Map<string, Listener[]>
let recordBreadcrumbMock: ReturnType<typeof vi.fn>
let setIntervalMock: ReturnType<typeof vi.fn>
let clearIntervalMock: ReturnType<typeof vi.fn>
let removeEventListenerMock: ReturnType<typeof vi.fn>
beforeEach(async () => {
vi.resetModules()
listeners = new Map()
recordBreadcrumbMock = vi.fn()
setIntervalMock = vi.fn(() => 1)
clearIntervalMock = vi.fn()
removeEventListenerMock = vi.fn((type: string, listener: Listener) => {
listeners.set(
type,
(listeners.get(type) ?? []).filter((candidate) => candidate !== listener)
)
})
vi.stubGlobal('window', {
api: {
crashReports: {
@@ -26,7 +35,9 @@ describe('renderer crash diagnostics', () => {
current.push(listener)
listeners.set(type, current)
}),
removeEventListener: removeEventListenerMock,
setInterval: setIntervalMock,
clearInterval: clearIntervalMock,
performance: {
memory: {
usedJSHeapSize: 32 * 1024 * 1024,
@@ -97,6 +108,18 @@ describe('renderer crash diagnostics', () => {
})
})
it('disposes global listeners and the memory interval', () => {
diagnostics.installRendererCrashDiagnostics()
diagnostics._disposeRendererCrashDiagnosticsForTests()
expect(removeEventListenerMock).toHaveBeenCalledWith('error', expect.any(Function))
expect(removeEventListenerMock).toHaveBeenCalledWith('unhandledrejection', expect.any(Function))
expect(clearIntervalMock).toHaveBeenCalledWith(1)
expect(listeners.get('error')).toHaveLength(0)
expect(listeners.get('unhandledrejection')).toHaveLength(0)
})
it('does not throw when preload is unavailable', () => {
vi.stubGlobal('window', {})
+28 -1
View File
@@ -13,6 +13,7 @@ type BrowserPerformanceMemory = {
}
let rendererCrashDiagnosticsInstalled = false
let rendererMemoryInterval: number | null = null
export function recordRendererCrashBreadcrumb(
name: string,
@@ -42,10 +43,36 @@ export function installRendererCrashDiagnostics(): void {
if (getPerformanceMemory()) {
recordRendererMemory('startup')
window.setInterval(() => recordRendererMemory('interval'), RENDERER_MEMORY_SAMPLE_INTERVAL_MS)
rendererMemoryInterval = window.setInterval(
() => recordRendererMemory('interval'),
RENDERER_MEMORY_SAMPLE_INTERVAL_MS
)
}
}
export function _disposeRendererCrashDiagnosticsForTests(): void {
disposeRendererCrashDiagnostics()
}
function disposeRendererCrashDiagnostics(): void {
if (!rendererCrashDiagnosticsInstalled || typeof window === 'undefined') {
return
}
rendererCrashDiagnosticsInstalled = false
window.removeEventListener('error', recordRendererError)
window.removeEventListener('unhandledrejection', recordRendererUnhandledRejection)
if (rendererMemoryInterval !== null) {
window.clearInterval(rendererMemoryInterval)
rendererMemoryInterval = null
}
}
if (typeof import.meta !== 'undefined' && import.meta.hot) {
// Why: Vite can replace this module without a full renderer reload. Remove
// global diagnostics hooks so dev sessions do not accumulate listeners.
import.meta.hot.dispose(disposeRendererCrashDiagnostics)
}
function recordRendererError(event: ErrorEvent): void {
recordRendererCrashBreadcrumb(
'renderer_error',