165 lines
6.2 KiB
JavaScript
165 lines
6.2 KiB
JavaScript
const pollTimeoutMs = 2_500
|
|
|
|
function delay(ms) {
|
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
}
|
|
|
|
async function pollRendererDiagnostics(read) {
|
|
const readPromise = Promise.resolve().then(read)
|
|
readPromise.catch(() => undefined)
|
|
// Why: the Wayland GPU stall can freeze renderer protocol calls, so
|
|
// diagnostics need a short deadline too.
|
|
const result = await Promise.race([
|
|
readPromise.then((value) => ({ timedOut: false, value })),
|
|
delay(pollTimeoutMs).then(() => ({ timedOut: true, value: null }))
|
|
])
|
|
if (result.timedOut) {
|
|
throw new Error(`Timed out polling renderer diagnostics after ${pollTimeoutMs}ms.`)
|
|
}
|
|
return result.value
|
|
}
|
|
|
|
export async function collectRendererDiagnostics(page) {
|
|
if (!page) {
|
|
return null
|
|
}
|
|
try {
|
|
return await pollRendererDiagnostics(() =>
|
|
page.evaluate(async () => {
|
|
const timed = (label, promise) =>
|
|
Promise.race([
|
|
Promise.resolve(promise).then(
|
|
(value) => ({ value }),
|
|
(error) => ({
|
|
error: error instanceof Error ? error.message : String(error)
|
|
})
|
|
),
|
|
new Promise((resolve) =>
|
|
setTimeout(() => resolve({ error: `Timed out collecting ${label}` }), 1_000)
|
|
)
|
|
])
|
|
const rectFor = (element) => {
|
|
if (!(element instanceof Element)) {
|
|
return null
|
|
}
|
|
const rect = element.getBoundingClientRect()
|
|
return {
|
|
x: rect.x,
|
|
y: rect.y,
|
|
width: rect.width,
|
|
height: rect.height
|
|
}
|
|
}
|
|
const styleFor = (element) => {
|
|
if (!(element instanceof Element)) {
|
|
return null
|
|
}
|
|
const style = getComputedStyle(element)
|
|
return {
|
|
display: style.display,
|
|
visibility: style.visibility,
|
|
opacity: style.opacity
|
|
}
|
|
}
|
|
const store = window.__store
|
|
const state = store?.getState?.()
|
|
const worktreeId = state?.activeWorktreeId ?? null
|
|
const tabId = state?.activeTabId ?? null
|
|
const tabs = worktreeId ? (state?.tabsByWorktree?.[worktreeId] ?? []) : []
|
|
const activeTab = tabId ? (tabs.find((tab) => tab.id === tabId) ?? null) : null
|
|
const layout = tabId ? (state?.terminalLayoutsByTabId?.[tabId] ?? null) : null
|
|
const tabCount = worktreeId ? (state?.tabsByWorktree?.[worktreeId]?.length ?? 0) : null
|
|
const manager = tabId ? window.__paneManagers?.get(tabId) : null
|
|
const activePane = manager?.getActivePane?.() ?? null
|
|
const paneDiagnostics = (manager?.getPanes?.() ?? []).map((pane) => {
|
|
const xtermElement = pane.container?.querySelector?.('.xterm') ?? pane.terminal?.element
|
|
const viewport = pane.container?.querySelector?.('.xterm-viewport') ?? null
|
|
const buffer = pane.terminal?.buffer?.active ?? null
|
|
return {
|
|
paneId: pane.id ?? null,
|
|
leafId: pane.leafId ?? null,
|
|
isActive: activePane?.id === pane.id,
|
|
datasetPtyId: pane.container?.dataset?.ptyId ?? null,
|
|
terminalCols: pane.terminal?.cols ?? null,
|
|
terminalRows: pane.terminal?.rows ?? null,
|
|
bufferState: buffer
|
|
? {
|
|
baseY: buffer.baseY,
|
|
viewportY: buffer.viewportY,
|
|
cursorY: buffer.cursorY,
|
|
length: buffer.length
|
|
}
|
|
: null,
|
|
containerConnected: pane.container?.isConnected ?? null,
|
|
containerRect: rectFor(pane.container),
|
|
containerStyle: styleFor(pane.container),
|
|
xtermRect: rectFor(xtermElement),
|
|
viewportRect: rectFor(viewport),
|
|
viewportScroll: viewport
|
|
? {
|
|
scrollTop: viewport.scrollTop,
|
|
scrollHeight: viewport.scrollHeight,
|
|
clientHeight: viewport.clientHeight
|
|
}
|
|
: null
|
|
}
|
|
})
|
|
return {
|
|
hasStore: Boolean(store),
|
|
workspaceSessionReady: state?.workspaceSessionReady ?? null,
|
|
hydrationSucceeded: state?.hydrationSucceeded ?? null,
|
|
activeRepoId: state?.activeRepoId ?? null,
|
|
activeWorktreeId: worktreeId,
|
|
activeWorkspaceKey: state?.activeWorkspaceKey ?? null,
|
|
activeTabType: state?.activeTabType ?? null,
|
|
activeTabId: tabId,
|
|
repoIds: (state?.repos ?? []).map((repo) => repo.id),
|
|
worktreeIdsByRepo: Object.fromEntries(
|
|
Object.entries(state?.worktreesByRepo ?? {}).map(([id, worktrees]) => [
|
|
id,
|
|
worktrees.map((worktree) => worktree.id)
|
|
])
|
|
),
|
|
tabIdsByWorktree: Object.fromEntries(
|
|
Object.entries(state?.tabsByWorktree ?? {}).map(([id, worktreeTabs]) => [
|
|
id,
|
|
worktreeTabs.map((tab) => tab.id)
|
|
])
|
|
),
|
|
tabCount,
|
|
activeTab: activeTab
|
|
? {
|
|
id: activeTab.id,
|
|
ptyId: activeTab.ptyId ?? null,
|
|
title: activeTab.title ?? null,
|
|
pendingActivationSpawn: activeTab.pendingActivationSpawn ?? null
|
|
}
|
|
: null,
|
|
livePtyIdsForTab: tabId ? (state?.ptyIdsByTabId?.[tabId] ?? null) : null,
|
|
terminalLayout: layout
|
|
? {
|
|
activeLeafId: layout.activeLeafId ?? null,
|
|
expandedLeafId: layout.expandedLeafId ?? null,
|
|
ptyIdsByLeafId: layout.ptyIdsByLeafId ?? null,
|
|
root: layout.root ?? null
|
|
}
|
|
: null,
|
|
hasPaneManager: Boolean(manager),
|
|
paneDiagnostics,
|
|
renderingDiagnostics: manager?.getRenderingDiagnostics?.() ?? null,
|
|
ptyConnectDiagnostics: globalThis.__ptyConnectDiag ?? null,
|
|
ptySessions: await timed('PTY sessions', window.api?.pty?.listSessions?.()),
|
|
rendererDeliveryDebug: await timed(
|
|
'renderer delivery debug',
|
|
window.api?.pty?.getRendererDeliveryDebugSnapshot?.()
|
|
)
|
|
}
|
|
})
|
|
)
|
|
} catch (error) {
|
|
return {
|
|
error: error instanceof Error ? error.message : String(error)
|
|
}
|
|
}
|
|
}
|