Add renderer surface crash breadcrumbs

This commit is contained in:
Neil
2026-06-26 13:41:13 -07:00
parent d188e7d2f6
commit 0c767a8beb
2 changed files with 99 additions and 5 deletions
+54 -5
View File
@@ -11,13 +11,27 @@ describe('renderer crash diagnostics', () => {
let setIntervalMock: ReturnType<typeof vi.fn>
let clearIntervalMock: ReturnType<typeof vi.fn>
let removeEventListenerMock: ReturnType<typeof vi.fn>
let rootElement: {
childElementCount: number
getBoundingClientRect: ReturnType<typeof vi.fn>
}
let bodyElement: {
getBoundingClientRect: ReturnType<typeof vi.fn>
}
beforeEach(async () => {
vi.resetModules()
listeners = new Map()
recordBreadcrumbMock = vi.fn()
setIntervalMock = vi.fn(() => 1)
setIntervalMock = vi.fn(() => setIntervalMock.mock.calls.length + 1)
clearIntervalMock = vi.fn()
rootElement = {
childElementCount: 1,
getBoundingClientRect: vi.fn(() => ({ width: 1200, height: 800 }))
}
bodyElement = {
getBoundingClientRect: vi.fn(() => ({ width: 1200, height: 800 }))
}
removeEventListenerMock = vi.fn((type: string, listener: Listener) => {
listeners.set(
type,
@@ -38,6 +52,14 @@ describe('renderer crash diagnostics', () => {
removeEventListener: removeEventListenerMock,
setInterval: setIntervalMock,
clearInterval: clearIntervalMock,
innerWidth: 1200,
innerHeight: 800,
devicePixelRatio: 1.5,
getComputedStyle: vi.fn((element: unknown) =>
element === rootElement
? { display: 'block', visibility: 'visible', backgroundColor: 'rgba(0, 0, 0, 0)' }
: { display: 'block', visibility: 'visible', backgroundColor: 'rgb(10, 10, 10)' }
),
performance: {
memory: {
usedJSHeapSize: 32 * 1024 * 1024,
@@ -46,6 +68,12 @@ describe('renderer crash diagnostics', () => {
}
}
})
vi.stubGlobal('document', {
visibilityState: 'visible',
body: bodyElement,
hasFocus: vi.fn(() => true),
getElementById: vi.fn((id: string) => (id === 'root' ? rootElement : null))
})
vi.doMock('../components/browser-pane/webview-registry', () => ({
getBrowserWebviewMemoryProfile: () => ({
browserWebviewCount: 4,
@@ -68,12 +96,12 @@ describe('renderer crash diagnostics', () => {
})
})
it('installs startup, error, rejection, and memory breadcrumbs once', () => {
it('installs startup, error, rejection, memory, and surface breadcrumbs once', () => {
diagnostics.installRendererCrashDiagnostics()
diagnostics.installRendererCrashDiagnostics()
expect(window.addEventListener).toHaveBeenCalledTimes(2)
expect(setIntervalMock).toHaveBeenCalledTimes(1)
expect(setIntervalMock).toHaveBeenCalledTimes(2)
expect(recordBreadcrumbMock).toHaveBeenCalledWith({
name: 'renderer_memory',
data: {
@@ -85,6 +113,27 @@ describe('renderer crash diagnostics', () => {
registeredBrowserGuests: 3
}
})
expect(recordBreadcrumbMock).toHaveBeenCalledWith({
name: 'renderer_surface',
data: {
reason: 'startup',
visibilityState: 'visible',
hasFocus: true,
innerWidth: 1200,
innerHeight: 800,
devicePixelRatio: 1.5,
rootPresent: true,
rootChildElementCount: 1,
rootWidth: 1200,
rootHeight: 800,
bodyWidth: 1200,
bodyHeight: 800,
rootDisplay: 'block',
rootVisibility: 'visible',
rootBackgroundColor: 'rgba(0, 0, 0, 0)',
bodyBackgroundColor: 'rgb(10, 10, 10)'
}
})
listeners.get('error')?.[0]?.({
message: 'boom',
@@ -116,14 +165,14 @@ describe('renderer crash diagnostics', () => {
})
})
it('disposes global listeners and the memory interval', () => {
it('disposes global listeners and diagnostic intervals', () => {
diagnostics.installRendererCrashDiagnostics()
diagnostics._disposeRendererCrashDiagnosticsForTests()
expect(removeEventListenerMock).toHaveBeenCalledWith('error', expect.any(Function))
expect(removeEventListenerMock).toHaveBeenCalledWith('unhandledrejection', expect.any(Function))
expect(clearIntervalMock).toHaveBeenCalledWith(1)
expect(clearIntervalMock).toHaveBeenCalledTimes(2)
expect(listeners.get('error')).toHaveLength(0)
expect(listeners.get('unhandledrejection')).toHaveLength(0)
})
+45
View File
@@ -5,6 +5,7 @@ import type {
import { getBrowserWebviewMemoryProfile } from '../components/browser-pane/webview-registry'
const RENDERER_MEMORY_SAMPLE_INTERVAL_MS = 60_000
const RENDERER_SURFACE_SAMPLE_INTERVAL_MS = 60_000
const BYTES_PER_MEGABYTE = 1024 * 1024
type BrowserPerformanceMemory = {
@@ -15,6 +16,7 @@ type BrowserPerformanceMemory = {
let rendererCrashDiagnosticsInstalled = false
let rendererMemoryInterval: number | null = null
let rendererSurfaceInterval: number | null = null
export function recordRendererCrashBreadcrumb(
name: string,
@@ -49,6 +51,11 @@ export function installRendererCrashDiagnostics(): void {
RENDERER_MEMORY_SAMPLE_INTERVAL_MS
)
}
recordRendererSurface('startup')
rendererSurfaceInterval = window.setInterval(
() => recordRendererSurface('interval'),
RENDERER_SURFACE_SAMPLE_INTERVAL_MS
)
}
export function _disposeRendererCrashDiagnosticsForTests(): void {
@@ -66,6 +73,10 @@ function disposeRendererCrashDiagnostics(): void {
window.clearInterval(rendererMemoryInterval)
rendererMemoryInterval = null
}
if (rendererSurfaceInterval !== null) {
window.clearInterval(rendererSurfaceInterval)
rendererSurfaceInterval = null
}
}
if (typeof import.meta !== 'undefined' && import.meta.hot) {
@@ -114,6 +125,40 @@ function recordRendererMemory(reason: string): void {
)
}
function recordRendererSurface(reason: string): void {
if (typeof document === 'undefined' || typeof window === 'undefined') {
return
}
const root = document.getElementById('root')
const rootRect = root?.getBoundingClientRect()
const bodyRect = document.body?.getBoundingClientRect()
const rootStyle = root ? window.getComputedStyle(root) : undefined
const bodyStyle = document.body ? window.getComputedStyle(document.body) : undefined
recordRendererCrashBreadcrumb(
'renderer_surface',
compactBreadcrumbData({
reason,
visibilityState: document.visibilityState,
hasFocus: typeof document.hasFocus === 'function' ? document.hasFocus() : undefined,
innerWidth: window.innerWidth,
innerHeight: window.innerHeight,
devicePixelRatio: window.devicePixelRatio,
rootPresent: Boolean(root),
rootChildElementCount: root?.childElementCount,
rootWidth: rootRect ? Math.round(rootRect.width) : undefined,
rootHeight: rootRect ? Math.round(rootRect.height) : undefined,
bodyWidth: bodyRect ? Math.round(bodyRect.width) : undefined,
bodyHeight: bodyRect ? Math.round(bodyRect.height) : undefined,
rootDisplay: rootStyle?.display,
rootVisibility: rootStyle?.visibility,
rootBackgroundColor: rootStyle?.backgroundColor,
bodyBackgroundColor: bodyStyle?.backgroundColor
})
)
}
function getPerformanceMemory(): BrowserPerformanceMemory | undefined {
if (typeof window === 'undefined') {
return undefined