fix(terminal): bound fullscreen atlas recovery (#12061)
This commit is contained in:
parent
5c7fba5bb5
commit
2f73775ffc
|
|
@ -7476,6 +7476,129 @@
|
|||
],
|
||||
"demotionRule": "Disable the probe if it exceeds its per-reveal budget or produces false-positive anomaly noise."
|
||||
},
|
||||
{
|
||||
"id": "terminal-render.atlas-recovery-fanout",
|
||||
"title": "Terminal atlas recovery stays bounded to visible renderers",
|
||||
"maturity": "experimental",
|
||||
"protection": "partial",
|
||||
"owner": "terminal-rendering",
|
||||
"layer": "renderer-unit-and-electron",
|
||||
"surfaces": [
|
||||
"WebGL rendering",
|
||||
"document visibility",
|
||||
"hidden terminal output",
|
||||
"paired terminal traffic"
|
||||
],
|
||||
"platforms": ["macos", "linux", "windows"],
|
||||
"providers": ["local", "daemon", "ssh", "remote-runtime"],
|
||||
"coveredPlatforms": ["macos"],
|
||||
"coveredProviders": ["local"],
|
||||
"coverageNotes": "A deterministic renderer contract models one visible manager plus 64 mounted hidden managers and hidden synchronized/TUI output. A macOS Electron run verifies that a document visibility cycle preserves real WebGL atlases and terminal pixels. Production v1.4.163 evidence linked the same 49-manager fanout to paired traffic, but the candidate has not been rerun against an isolated live paired server.",
|
||||
"motivatingLinks": [
|
||||
"https://github.com/stablyai/orca/pull/7054",
|
||||
"https://github.com/stablyai/orca/pull/7604"
|
||||
],
|
||||
"invariant": "Ordinary document visibility transitions and hidden terminal output must not clear the shared WebGL glyph atlas. Heavy reset-and-refresh recovery may touch only managers with visible terminal surfaces; hidden managers recover when revealed. Genuine OS resume remains a heavy recovery trigger.",
|
||||
"oracle": "Dispatch a visible document visibilitychange and require atlas-preserving wake recovery; register one visible manager and 64 hidden managers and require exactly one reset and one refresh; parse hidden synchronized and high-confidence TUI output and require zero global atlas-recovery schedules. Separately, drive an Electron visibility cycle with two real WebGL panes, require zero atlas clears, and retain at least 85% of each pane's baseline ink pixels.",
|
||||
"commands": [
|
||||
"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/use-terminal-window-wake-recovery.test.ts src/renderer/src/components/terminal-pane/use-terminal-pane-global-effects.test.ts src/renderer/src/lib/pane-manager/pane-manager-registry.test.ts src/renderer/src/components/terminal-pane/pty-connection.test.ts",
|
||||
"pnpm run ensure:electron-runtime && pnpm exec playwright test tests/e2e/terminal-document-visibility-webgl-recovery.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1",
|
||||
"pnpm run ensure:electron-runtime && pnpm exec playwright test tests/e2e/terminal-document-visibility-webgl-recovery.spec.ts --config tests/playwright.config.ts --project electron-headful --workers=1"
|
||||
],
|
||||
"testFiles": [
|
||||
"src/renderer/src/components/terminal-pane/use-terminal-window-wake-recovery.test.ts",
|
||||
"src/renderer/src/components/terminal-pane/use-terminal-pane-global-effects.test.ts",
|
||||
"src/renderer/src/lib/pane-manager/pane-manager-registry.test.ts",
|
||||
"src/renderer/src/components/terminal-pane/pty-connection.test.ts",
|
||||
"tests/e2e/terminal-document-visibility-webgl-recovery.spec.ts"
|
||||
],
|
||||
"assertionRefs": [
|
||||
{
|
||||
"file": "src/renderer/src/components/terminal-pane/use-terminal-window-wake-recovery.test.ts",
|
||||
"assertions": ["preserves the glyph atlas when a fullscreen Space becomes visible"]
|
||||
},
|
||||
{
|
||||
"file": "src/renderer/src/components/terminal-pane/use-terminal-pane-global-effects.test.ts",
|
||||
"assertions": [
|
||||
"preserves WebGL texture atlases when the active terminal document becomes visible"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "src/renderer/src/lib/pane-manager/pane-manager-registry.test.ts",
|
||||
"assertions": ["bounds atlas recovery to visible managers"]
|
||||
},
|
||||
{
|
||||
"file": "src/renderer/src/components/terminal-pane/pty-connection.test.ts",
|
||||
"assertions": [
|
||||
"defers hidden synchronized-output atlas recovery until reveal",
|
||||
"defers hidden high-confidence TUI redraw recovery until reveal",
|
||||
"advances hidden rewrite state without scheduling atlas recovery"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "tests/e2e/terminal-document-visibility-webgl-recovery.spec.ts",
|
||||
"assertions": [
|
||||
"preserves the WebGL atlas and keeps terminal text painted after document visibility resumes"
|
||||
]
|
||||
}
|
||||
],
|
||||
"evidenceRuns": [
|
||||
{
|
||||
"date": "2026-08-01",
|
||||
"runner": "local",
|
||||
"platform": "macos",
|
||||
"command": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/use-terminal-window-wake-recovery.test.ts src/renderer/src/components/terminal-pane/use-terminal-pane-global-effects.test.ts src/renderer/src/lib/pane-manager/pane-manager-registry.test.ts src/renderer/src/components/terminal-pane/pty-connection.test.ts",
|
||||
"result": "passed",
|
||||
"durationSeconds": 26,
|
||||
"summary": "All 577 focused renderer contracts passed, including atlas-preserving visibility, visible-only recovery fanout, and hidden-output rewrite-state coverage."
|
||||
},
|
||||
{
|
||||
"date": "2026-08-01",
|
||||
"runner": "local",
|
||||
"platform": "macos",
|
||||
"command": "pnpm run ensure:electron-runtime && pnpm exec playwright test tests/e2e/terminal-document-visibility-webgl-recovery.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1",
|
||||
"result": "passed",
|
||||
"durationSeconds": 18,
|
||||
"summary": "The changed-spec CI topology retained both real WebGL terminal panes with zero atlas clears after a deterministic document visibility cycle."
|
||||
},
|
||||
{
|
||||
"date": "2026-08-01",
|
||||
"runner": "local",
|
||||
"platform": "macos",
|
||||
"command": "pnpm run ensure:electron-runtime && pnpm exec playwright test tests/e2e/terminal-document-visibility-webgl-recovery.spec.ts --config tests/playwright.config.ts --project electron-headful --workers=1",
|
||||
"result": "passed",
|
||||
"durationSeconds": 23,
|
||||
"summary": "Two real WebGL terminal panes retained painted glyphs with zero atlas clears after a deterministic document visibility cycle. BrowserWindow.hide did not change document visibility in the harness, so the test used its explicit visibility-event fallback."
|
||||
}
|
||||
],
|
||||
"runtimeBudget": {
|
||||
"p95Seconds": 60,
|
||||
"scope": "focused renderer contracts plus one prebuilt Electron visibility test"
|
||||
},
|
||||
"flakeHistory": {
|
||||
"status": "unknown",
|
||||
"evidence": "The focused unit oracle and one local Electron run passed; CI soak history is not yet available."
|
||||
},
|
||||
"redGreenEvidence": {
|
||||
"status": "complete",
|
||||
"evidence": "With the fix disabled, the byte-identical unit oracle observed clearGlyphAtlases=true, reset/refreshed all 65 managers, and scheduled hidden synchronized/TUI recovery one to three times. Restoring the fix made every assertion pass."
|
||||
},
|
||||
"performanceBudget": {
|
||||
"required": true,
|
||||
"evidence": "A recovery with one visible and 64 hidden managers performs one reset and one refresh instead of 65 of each. Hidden synchronized/TUI output schedules zero global recovery work; no polling, cooldown, provider call, or parking change is added."
|
||||
},
|
||||
"promotionCriteria": [
|
||||
"Accumulate stable macOS Electron runs with real BrowserWindow visibility transitions.",
|
||||
"Run an isolated headed paired-server terminal flood and verify bounded renderer CPU and atlas diagnostics.",
|
||||
"Add Linux and Windows WebGL visibility evidence before claiming cross-platform visual coverage."
|
||||
],
|
||||
"knownGaps": [
|
||||
"The Electron harness used a deterministic visibility-event fallback because BrowserWindow.hide did not change document.visibilityState.",
|
||||
"No isolated live paired-server candidate run is recorded; remote-runtime coverage is a provider-agnostic renderer contract plus production incident evidence.",
|
||||
"The gate counts recovery fanout and pixel retention but does not impose an end-to-end renderer frame-latency threshold."
|
||||
],
|
||||
"demotionRule": "Demote if hidden managers re-enter reset/refresh recovery, ordinary visibility clears the atlas, real WebGL pixels regress, or the focused Electron test cannot remain deterministic."
|
||||
},
|
||||
{
|
||||
"id": "terminal-render.atlas-identity-invalidation",
|
||||
"title": "A WebGL atlas identity change rebuilds cached glyph vertices",
|
||||
|
|
|
|||
|
|
@ -12072,7 +12072,7 @@ describe('connectPanePty', () => {
|
|||
})
|
||||
})
|
||||
|
||||
it('schedules WebGL atlas recovery after hidden synchronized output parses', async () => {
|
||||
it('defers hidden synchronized-output atlas recovery until reveal', async () => {
|
||||
const { connectPanePty } = await import('./pty-connection')
|
||||
const transport = createMockTransport('pty-id')
|
||||
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
|
||||
|
|
@ -12112,13 +12112,13 @@ describe('connectPanePty', () => {
|
|||
|
||||
parseCallbacks[0]?.()
|
||||
|
||||
expect(scheduleTerminalWebglAtlasRecovery).toHaveBeenCalledTimes(3)
|
||||
expect(scheduleTerminalWebglAtlasRecovery).not.toHaveBeenCalled()
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
|
||||
it('recognizes hidden synchronized output markers split across PTY chunks', async () => {
|
||||
it('defers split hidden synchronized-output markers until reveal', async () => {
|
||||
const { connectPanePty } = await import('./pty-connection')
|
||||
const transport = createMockTransport('pty-id')
|
||||
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
|
||||
|
|
@ -12154,7 +12154,7 @@ describe('connectPanePty', () => {
|
|||
|
||||
parseCallbacks[0]?.()
|
||||
|
||||
expect(scheduleTerminalWebglAtlasRecovery).toHaveBeenCalledTimes(3)
|
||||
expect(scheduleTerminalWebglAtlasRecovery).not.toHaveBeenCalled()
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
|
|
@ -12199,7 +12199,7 @@ describe('connectPanePty', () => {
|
|||
}
|
||||
})
|
||||
|
||||
it('schedules hidden atlas recovery for high-confidence TUI redraw controls', async () => {
|
||||
it('defers hidden high-confidence TUI redraw recovery until reveal', async () => {
|
||||
const { connectPanePty } = await import('./pty-connection')
|
||||
const transport = createMockTransport('pty-id')
|
||||
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
|
||||
|
|
@ -12230,13 +12230,13 @@ describe('connectPanePty', () => {
|
|||
|
||||
parseCallbacks[0]?.()
|
||||
|
||||
expect(scheduleTerminalWebglAtlasRecovery).toHaveBeenCalledTimes(1)
|
||||
expect(scheduleTerminalWebglAtlasRecovery).not.toHaveBeenCalled()
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
|
||||
it('advances hidden rewrite state when synchronized output already requests recovery', async () => {
|
||||
it('advances hidden rewrite state without scheduling atlas recovery', async () => {
|
||||
const { connectPanePty } = await import('./pty-connection')
|
||||
const transport = createMockTransport('pty-id')
|
||||
const capturedDataCallback: { current: ((data: string) => void) | null } = { current: null }
|
||||
|
|
@ -12270,7 +12270,7 @@ describe('connectPanePty', () => {
|
|||
vi.advanceTimersByTime(50)
|
||||
expect(writes).toEqual(['prompt rewrite\r', '\x1b[?2026hredraw frame\x1b[?2026l'])
|
||||
parseCallbacks.shift()?.()
|
||||
expect(scheduleTerminalWebglAtlasRecovery).toHaveBeenCalledTimes(1)
|
||||
expect(scheduleTerminalWebglAtlasRecovery).not.toHaveBeenCalled()
|
||||
scheduleTerminalWebglAtlasRecovery.mockClear()
|
||||
|
||||
capturedDataCallback.current?.('plain after frame')
|
||||
|
|
@ -12313,7 +12313,7 @@ describe('connectPanePty', () => {
|
|||
capturedDataCallback.current?.('\x1b[?2026h')
|
||||
vi.advanceTimersByTime(50)
|
||||
parseCallbacks.shift()?.()
|
||||
expect(scheduleTerminalWebglAtlasRecovery).toHaveBeenCalledTimes(1)
|
||||
expect(scheduleTerminalWebglAtlasRecovery).not.toHaveBeenCalled()
|
||||
scheduleTerminalWebglAtlasRecovery.mockClear()
|
||||
writes.length = 0
|
||||
|
||||
|
|
|
|||
|
|
@ -6250,16 +6250,20 @@ export function connectPanePty(
|
|||
const renderRefreshDecision = foregroundOutput
|
||||
? shouldForceForegroundRenderRefresh(data)
|
||||
: { refresh: false, inPlaceRewrite: false, recoverWebglAtlasAfterParse: false }
|
||||
const recoverHiddenWebglAtlasAfterParse =
|
||||
!foregroundOutput && hiddenOutputNeedsAtlasRecoveryAfterParse(data)
|
||||
if (!foregroundOutput) {
|
||||
// Advance hidden rewrite state; reveal owns atlas recovery.
|
||||
void hiddenOutputNeedsAtlasRecoveryAfterParse(data)
|
||||
}
|
||||
const recoverWebglAtlasAfterParse =
|
||||
renderRefreshDecision.recoverWebglAtlasAfterParse || recoverHiddenWebglAtlasAfterParse
|
||||
foreground && renderRefreshDecision.recoverWebglAtlasAfterParse
|
||||
// Why: atlas recovery must repaint from the parsed xterm buffer, not a pre-write snapshot a late TUI redraw can stale.
|
||||
const onParsedAtlasRecovery = recoverWebglAtlasAfterParse
|
||||
? scheduleTerminalWebglAtlasRecovery
|
||||
: renderRefreshDecision.inPlaceRewrite
|
||||
? alternateScreenRewriteAtlasRecoveryOnParsed()
|
||||
: undefined
|
||||
const onParsedAtlasRecovery = foreground
|
||||
? recoverWebglAtlasAfterParse
|
||||
? scheduleTerminalWebglAtlasRecovery
|
||||
: renderRefreshDecision.inPlaceRewrite
|
||||
? alternateScreenRewriteAtlasRecoveryOnParsed()
|
||||
: undefined
|
||||
: undefined
|
||||
const foregroundRenderRefreshNeeded = renderRefreshDecision.refresh
|
||||
// Why: Claude Code's in-place prompt redraws on Windows ConPTY can paint one frame late; a follow-up repaint fixes the column desync without a resize.
|
||||
const nativeWindowsInPlaceRewriteFollowup = nativeWindowsRewriteNeedsFollowupRenderRefresh({
|
||||
|
|
|
|||
|
|
@ -974,7 +974,7 @@ describe('useTerminalPaneGlobalEffects', () => {
|
|||
expect(manager.refreshAllPanes).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('clears WebGL texture atlases when the active visible terminal document becomes visible', () => {
|
||||
it('preserves WebGL texture atlases when the active terminal document becomes visible', () => {
|
||||
let visibilityState: DocumentVisibilityState = 'hidden'
|
||||
const documentListeners = new Map<string, EventListenerOrEventListenerObject>()
|
||||
vi.stubGlobal('document', {
|
||||
|
|
@ -1024,6 +1024,7 @@ describe('useTerminalPaneGlobalEffects', () => {
|
|||
throw new Error('expected visibilitychange listener')
|
||||
}
|
||||
manager.resetWebglTextureAtlases.mockClear()
|
||||
manager.scheduleRevealPresent.mockClear()
|
||||
siblingManager.resetWebglTextureAtlases.mockClear()
|
||||
listener(new Event('visibilitychange'))
|
||||
expect(manager.resetWebglTextureAtlases).not.toHaveBeenCalled()
|
||||
|
|
@ -1032,8 +1033,9 @@ describe('useTerminalPaneGlobalEffects', () => {
|
|||
visibilityState = 'visible'
|
||||
listener(new Event('visibilitychange'))
|
||||
|
||||
expect(manager.resetWebglTextureAtlases).toHaveBeenCalledTimes(1)
|
||||
expect(siblingManager.resetWebglTextureAtlases).toHaveBeenCalledTimes(1)
|
||||
expect(manager.resetWebglTextureAtlases).not.toHaveBeenCalled()
|
||||
expect(siblingManager.resetWebglTextureAtlases).not.toHaveBeenCalled()
|
||||
expect(manager.scheduleRevealPresent).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('registers document visibility recovery for visible inactive terminals but not hidden ones', () => {
|
||||
|
|
|
|||
|
|
@ -142,6 +142,7 @@ export function useTerminalPaneGlobalEffects({
|
|||
if (!manager) {
|
||||
return
|
||||
}
|
||||
manager.setAtlasRecoveryVisible?.(rendererVisible)
|
||||
const wasVisible = wasVisibleRef.current
|
||||
const wasWorktreeActive = wasWorktreeActiveRef.current
|
||||
isActiveRef.current = isActive
|
||||
|
|
|
|||
|
|
@ -79,6 +79,22 @@ describe('useTerminalWindowWakeRecovery', () => {
|
|||
})
|
||||
})
|
||||
|
||||
it('preserves the glyph atlas when a fullscreen Space becomes visible', () => {
|
||||
renderWakeRecoveryHook()
|
||||
Object.defineProperty(document, 'visibilityState', {
|
||||
configurable: true,
|
||||
value: 'visible'
|
||||
})
|
||||
|
||||
document.dispatchEvent(new Event('visibilitychange'))
|
||||
|
||||
expect(recoverVisibleTerminalWindowWakeMock).toHaveBeenLastCalledWith({
|
||||
manager,
|
||||
isActive: true,
|
||||
clearGlyphAtlases: false
|
||||
})
|
||||
})
|
||||
|
||||
it('records a wake-recovery breadcrumb with the trigger source and atlas decision', () => {
|
||||
// Why: a post-wake garble report attributes to the trigger that ran (or its
|
||||
// absence). Pin that focus records source=focus/atlas=false and system
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ export function useTerminalWindowWakeRecovery({
|
|||
const onFocus = (): void => recoverVisibleWake(false, 'focus')
|
||||
const onVisibilityChange = (): void => {
|
||||
if (typeof document !== 'undefined' && document.visibilityState === 'visible') {
|
||||
recoverVisibleWake(true, 'visibilitychange')
|
||||
recoverVisibleWake(false, 'visibilitychange')
|
||||
}
|
||||
}
|
||||
// Why: Linux has no window-occlusion tracking, so visibilitychange never
|
||||
|
|
|
|||
|
|
@ -82,6 +82,34 @@ describe('pane manager registry', () => {
|
|||
expect(order).toEqual(['first-reset', 'second-reset', 'first-refresh', 'second-refresh'])
|
||||
})
|
||||
|
||||
it('bounds atlas recovery to visible managers', () => {
|
||||
const visible = {
|
||||
resetWebglTextureAtlases: vi.fn<() => void>(),
|
||||
refreshAllPanes: vi.fn<() => void>(),
|
||||
isVisibleForAtlasRecovery: () => true
|
||||
}
|
||||
registerLivePaneManager(visible)
|
||||
registeredManagers.push(visible)
|
||||
const hidden = Array.from({ length: 64 }, () => ({
|
||||
resetWebglTextureAtlases: vi.fn<() => void>(),
|
||||
refreshAllPanes: vi.fn<() => void>(),
|
||||
isVisibleForAtlasRecovery: () => false
|
||||
}))
|
||||
for (const manager of hidden) {
|
||||
registerLivePaneManager(manager)
|
||||
registeredManagers.push(manager)
|
||||
}
|
||||
|
||||
resetAndRefreshAllTerminalWebglAtlases()
|
||||
|
||||
expect(visible.resetWebglTextureAtlases).toHaveBeenCalledOnce()
|
||||
expect(visible.refreshAllPanes).toHaveBeenCalledOnce()
|
||||
expect(
|
||||
hidden.every((manager) => manager.resetWebglTextureAtlases.mock.calls.length === 0)
|
||||
).toBe(true)
|
||||
expect(hidden.every((manager) => manager.refreshAllPanes.mock.calls.length === 0)).toBe(true)
|
||||
})
|
||||
|
||||
it('continues reset-and-refresh recovery when one manager throws', () => {
|
||||
const broken = {
|
||||
resetWebglTextureAtlases: vi.fn<() => void>(() => {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ type RegisteredPaneManager = {
|
|||
getRenderingDiagnostics?: () => PaneRenderingDiagnostics[]
|
||||
getPanes?: () => { id: number; terminal: unknown }[]
|
||||
getPaneCount?: () => number
|
||||
isVisibleForAtlasRecovery?: () => boolean
|
||||
}
|
||||
|
||||
const liveManagers = new Set<RegisteredPaneManager>()
|
||||
|
|
@ -48,9 +49,15 @@ export function resetAllTerminalWebglAtlases(): void {
|
|||
export function resetAndRefreshAllTerminalWebglAtlases(): void {
|
||||
// Why: the atlas wipe is the heavy recovery path; recording it lets a freeze
|
||||
// report show whether a post-wake repaint actually ran. Silent breadcrumb.
|
||||
recordTerminalWebglDiagnostic('webgl-atlas-reset', { managers: liveManagers.size })
|
||||
const recoveryManagers = Array.from(liveManagers).filter(
|
||||
(manager) => manager.isVisibleForAtlasRecovery?.() !== false
|
||||
)
|
||||
recordTerminalWebglDiagnostic('webgl-atlas-reset', {
|
||||
managers: recoveryManagers.length,
|
||||
mountedManagers: liveManagers.size
|
||||
})
|
||||
const resetManagers: RegisteredPaneManager[] = []
|
||||
for (const manager of liveManagers) {
|
||||
for (const manager of recoveryManagers) {
|
||||
try {
|
||||
manager.resetWebglTextureAtlases()
|
||||
resetManagers.push(manager)
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ export class PaneManager {
|
|||
private styleOptions: PaneStyleOptions = {}
|
||||
private destroyed = false
|
||||
private renderingSuspended: boolean
|
||||
private atlasRecoveryVisible: boolean
|
||||
private identities = new PaneIdentityRegistry()
|
||||
private pendingPaneReparentFrameIds = new Set<number>()
|
||||
|
||||
|
|
@ -82,6 +83,7 @@ export class PaneManager {
|
|||
this.root = root
|
||||
this.options = options
|
||||
this.renderingSuspended = options.initialRenderingSuspended === true
|
||||
this.atlasRecoveryVisible = !this.renderingSuspended
|
||||
// Why: atlas recovery must reach every live manager — see
|
||||
// resetAllTerminalWebglAtlases for the shared-atlas rationale.
|
||||
registerLivePaneManager(this)
|
||||
|
|
@ -358,6 +360,14 @@ export class PaneManager {
|
|||
resetPaneWebglTextureAtlases(this.panes.values())
|
||||
}
|
||||
|
||||
setAtlasRecoveryVisible(visible: boolean): void {
|
||||
this.atlasRecoveryVisible = visible
|
||||
}
|
||||
|
||||
isVisibleForAtlasRecovery(): boolean {
|
||||
return this.atlasRecoveryVisible && !this.destroyed
|
||||
}
|
||||
|
||||
scheduleRevealRepaint(): void {
|
||||
// Why: the settled-frame callback can fire after destroy(); repainting
|
||||
// disposed panes could throw in attach and latch the global WebGL
|
||||
|
|
|
|||
|
|
@ -280,8 +280,8 @@ async function dispatchDocumentVisibilityCycle(page: Page): Promise<void> {
|
|||
})
|
||||
}
|
||||
|
||||
test.describe('terminal document visibility WebGL recovery @headful', () => {
|
||||
test('clears the WebGL atlas and keeps terminal text painted after document visibility resumes', async ({
|
||||
test.describe('terminal document visibility WebGL recovery', () => {
|
||||
test('preserves the WebGL atlas and keeps terminal text painted after document visibility resumes', async ({
|
||||
electronApp,
|
||||
orcaPage
|
||||
}, testInfo) => {
|
||||
|
|
@ -293,7 +293,7 @@ test.describe('terminal document visibility WebGL recovery @headful', () => {
|
|||
await waitForPaneCount(orcaPage, 2)
|
||||
|
||||
const webglActive = await forceWebgl(orcaPage)
|
||||
test.skip(!webglActive, 'WebGL was not active in this headful environment')
|
||||
test.skip(!webglActive, 'WebGL was not active in this Electron environment')
|
||||
|
||||
await writeStableTerminalContent(orcaPage)
|
||||
expect(await patchAtlasCounter(orcaPage)).toBe(true)
|
||||
|
|
@ -317,25 +317,16 @@ test.describe('terminal document visibility WebGL recovery @headful', () => {
|
|||
console.log(
|
||||
`[visibility-webgl] browserWindowVisibilityWorked=${browserWindowVisibilityWorked}`
|
||||
)
|
||||
if (browserWindowVisibilityWorked) {
|
||||
await expect
|
||||
.poll(() => readAtlasResetCount(orcaPage), {
|
||||
timeout: 2_000,
|
||||
message: 'BrowserWindow visibility resume did not clear the WebGL atlas'
|
||||
})
|
||||
.toBeGreaterThan(0)
|
||||
} else {
|
||||
if (!browserWindowVisibilityWorked) {
|
||||
await resetAtlasResetCount(orcaPage)
|
||||
await dispatchDocumentVisibilityCycle(orcaPage)
|
||||
await expect
|
||||
.poll(() => readAtlasResetCount(orcaPage), {
|
||||
timeout: 2_000,
|
||||
message: 'document visibility resume did not clear the WebGL atlas'
|
||||
})
|
||||
.toBeGreaterThan(0)
|
||||
}
|
||||
|
||||
await waitForTerminalPaint(orcaPage)
|
||||
expect(
|
||||
await readAtlasResetCount(orcaPage),
|
||||
'ordinary document visibility resume cleared the shared WebGL atlas'
|
||||
).toBe(0)
|
||||
|
||||
const afterResume = await terminalScreenshots(orcaPage)
|
||||
for (const [index, baselineShot] of baseline.entries()) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue