mirror of
https://github.com/stablyai/orca.git
synced 2026-10-23 16:03:49 +00:00
fix(terminal): bound reveal recovery work
This commit is contained in:
@@ -12350,7 +12350,7 @@
|
||||
},
|
||||
"performanceBudget": {
|
||||
"required": true,
|
||||
"evidence": "Recovery remains event-driven and limited to visible managers. The deterministic 64-hidden-manager count test requires zero hidden atlas clears or presents. A display:none retry is deduped per pane and capped at 16 animation frames; no polling, provider calls, PTY scans, or background output work was added."
|
||||
"evidence": "The tab-reveal present path runs only at event-driven recovery boundaries and adds no polling, provider calls, PTY scans, or background output work. The shared terminal-render.atlas-recovery-fanout contracts separately prove zero hidden-manager atlas clears or presents and a per-pane display:none retry deduped and capped at 16 animation frames."
|
||||
},
|
||||
"promotionCriteria": [
|
||||
"Establish a stable material-diff threshold across platforms via nightly runs.",
|
||||
|
||||
@@ -204,6 +204,7 @@ describe('useTerminalWindowWakeRecovery', () => {
|
||||
const pane = { id: 1, terminal: {} }
|
||||
const resizeManager = { getPanes: () => [pane] } as unknown as PaneManager
|
||||
repairPaneWebglCanvasDprMismatchMock.mockReturnValue(true)
|
||||
vi.stubGlobal('devicePixelRatio', 1)
|
||||
const { unmount } = renderHook(() =>
|
||||
useTerminalWindowWakeRecovery({
|
||||
isVisible: true,
|
||||
@@ -213,6 +214,10 @@ describe('useTerminalWindowWakeRecovery', () => {
|
||||
})
|
||||
)
|
||||
|
||||
window.dispatchEvent(new Event('resize'))
|
||||
expect(repairPaneWebglCanvasDprMismatchMock).not.toHaveBeenCalled()
|
||||
|
||||
vi.stubGlobal('devicePixelRatio', 2)
|
||||
window.dispatchEvent(new Event('resize'))
|
||||
|
||||
expect(repairPaneWebglCanvasDprMismatchMock).toHaveBeenCalledWith(pane)
|
||||
@@ -220,6 +225,7 @@ describe('useTerminalWindowWakeRecovery', () => {
|
||||
expect(recoverVisibleTerminalWindowWakeMock).not.toHaveBeenCalled()
|
||||
|
||||
unmount()
|
||||
vi.stubGlobal('devicePixelRatio', 3)
|
||||
window.dispatchEvent(new Event('resize'))
|
||||
expect(repairPaneWebglCanvasDprMismatchMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
@@ -31,6 +31,7 @@ export function useTerminalWindowWakeRecovery({
|
||||
}
|
||||
let wakeRecoveryFrameId: number | null = null
|
||||
let settledClearGlyphAtlases = false
|
||||
let observedDevicePixelRatio = window.devicePixelRatio
|
||||
const cancelScheduledWakeRecovery = (): void => {
|
||||
if (wakeRecoveryFrameId === null || typeof cancelAnimationFrame !== 'function') {
|
||||
wakeRecoveryFrameId = null
|
||||
@@ -117,6 +118,11 @@ export function useTerminalWindowWakeRecovery({
|
||||
// Why: Chromium emits window resize on devicePixelRatio changes even when
|
||||
// the CSS box is unchanged (monitor move / undock). xterm's own observer
|
||||
// misses that while the canvas had no box (laptop lid closed).
|
||||
const devicePixelRatio = window.devicePixelRatio
|
||||
if (devicePixelRatio === observedDevicePixelRatio) {
|
||||
return
|
||||
}
|
||||
observedDevicePixelRatio = devicePixelRatio
|
||||
const manager = managerRef.current
|
||||
if (!manager || !isVisibleRef.current) {
|
||||
return
|
||||
|
||||
@@ -254,15 +254,17 @@ describe('terminal WebGL addon lifecycle', () => {
|
||||
const { pane, renderService } = createPausedPane('none')
|
||||
|
||||
presentPaneViewport(pane)
|
||||
presentPaneViewport(pane)
|
||||
expect(queued).toHaveLength(1)
|
||||
let callbacks = 0
|
||||
while (queued.length > 0) {
|
||||
queued.shift()?.(callbacks * 16)
|
||||
callbacks += 1
|
||||
}
|
||||
|
||||
expect(callbacks).toBe(17)
|
||||
expect(callbacks).toBe(16)
|
||||
expect(renderService._isPaused).toBe(true)
|
||||
expect(renderService.refreshRows).toHaveBeenCalledTimes(1)
|
||||
expect(renderService.refreshRows).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('still forces the paused render through for a pane that has a layout box', () => {
|
||||
|
||||
@@ -170,6 +170,10 @@ function schedulePresentWhenDisplayed(pane: ManagedPaneInternal): void {
|
||||
return
|
||||
}
|
||||
if (isManagedPaneDisplayNone(pane)) {
|
||||
if (remaining === 1) {
|
||||
pendingDisplayedPresentRetries.delete(pane)
|
||||
return
|
||||
}
|
||||
pendingDisplayedPresentRetries.set(pane, remaining - 1)
|
||||
globalThis.requestAnimationFrame(tick)
|
||||
return
|
||||
|
||||
@@ -6,6 +6,7 @@ function makePane(args: {
|
||||
backingWidth: number
|
||||
expectedWidth: number
|
||||
dpr: number
|
||||
cachedDpr?: number
|
||||
backingHeight?: number
|
||||
expectedHeight?: number
|
||||
connected?: boolean
|
||||
@@ -32,6 +33,7 @@ function makePane(args: {
|
||||
(args.hasRenderer ?? true)
|
||||
? {
|
||||
_canvas: canvas,
|
||||
_devicePixelRatio: args.cachedDpr ?? args.dpr,
|
||||
dimensions: {
|
||||
device: {
|
||||
canvas: { width: args.expectedWidth, height: args.expectedHeight ?? 1200 }
|
||||
@@ -79,6 +81,19 @@ describe('repairPaneWebglCanvasDprMismatch', () => {
|
||||
expect(handleResize).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('repairs when the canvas and renderer dimensions share a stale dpr cache', () => {
|
||||
const { pane, handleDevicePixelRatioChange, handleResize } = makePane({
|
||||
backingWidth: 1080,
|
||||
expectedWidth: 1080,
|
||||
dpr: 2,
|
||||
cachedDpr: 1
|
||||
})
|
||||
|
||||
expect(repairPaneWebglCanvasDprMismatch(pane)).toBe(true)
|
||||
expect(handleDevicePixelRatioChange).toHaveBeenCalledTimes(1)
|
||||
expect(handleResize).toHaveBeenCalledWith(120, 40)
|
||||
})
|
||||
|
||||
it('is a no-op when backing matches the renderer device dimensions', () => {
|
||||
const { pane, handleResize, refresh } = makePane({
|
||||
backingWidth: 2160,
|
||||
|
||||
@@ -14,6 +14,7 @@ import { recordTerminalWebglDiagnostic } from '../../../../shared/terminal-webgl
|
||||
*/
|
||||
type XtermRendererInternals = {
|
||||
_canvas?: HTMLCanvasElement
|
||||
_devicePixelRatio?: number
|
||||
_gl?: { canvas?: HTMLCanvasElement }
|
||||
dimensions?: {
|
||||
device?: { canvas?: { width?: number; height?: number } }
|
||||
@@ -41,10 +42,14 @@ export function repairPaneWebglCanvasDprMismatch(pane: ManagedPane): boolean {
|
||||
}
|
||||
const staleBackingWidth = canvas.width
|
||||
const staleBackingHeight = canvas.height
|
||||
const cachedDevicePixelRatio = renderer._devicePixelRatio
|
||||
const devicePixelRatioChanged =
|
||||
typeof cachedDevicePixelRatio === 'number' && cachedDevicePixelRatio !== view.devicePixelRatio
|
||||
// xterm rounds its CSS canvas size before ResizeObserver converts it back to
|
||||
// device pixels; allow that round trip without forcing layout on every fit.
|
||||
const roundingTolerance = Math.max(1, Math.ceil(view.devicePixelRatio / 2))
|
||||
if (
|
||||
!devicePixelRatioChanged &&
|
||||
Math.abs(staleBackingWidth - expectedWidth) <= roundingTolerance &&
|
||||
Math.abs(staleBackingHeight - expectedHeight) <= roundingTolerance
|
||||
) {
|
||||
@@ -64,6 +69,7 @@ export function repairPaneWebglCanvasDprMismatch(pane: ManagedPane): boolean {
|
||||
paneId: pane.id,
|
||||
staleBackingWidth,
|
||||
expectedBackingWidth: expectedWidth,
|
||||
...(cachedDevicePixelRatio === undefined ? {} : { cachedDevicePixelRatio }),
|
||||
devicePixelRatio: view.devicePixelRatio
|
||||
})
|
||||
return true
|
||||
|
||||
@@ -20,7 +20,7 @@ import { compareTerminalScreenshots } from './terminal-screenshot-diff'
|
||||
* is then swallowed and the freshly-cleared render model never repaints.
|
||||
*
|
||||
* This spec drives the REAL production reveal path (manager.resetWebglTextureAtlases
|
||||
* -> resetWebglTextureAtlas -> forceRepaintThroughRenderPause) against a real
|
||||
* -> resetWebglTextureAtlas -> forceFullViewportPresent) against a real
|
||||
* xterm Terminal + RenderService. It:
|
||||
* 1. proves the bug: while paused, a plain refresh() renders nothing;
|
||||
* 2. proves the fix: the real reveal repaint forces a full-viewport render
|
||||
|
||||
Reference in New Issue
Block a user