mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
fix: cancel pane webgl refresh frame (#3397)
This commit is contained in:
@@ -23,7 +23,12 @@ import {
|
||||
detachPaneFitResizeObserver
|
||||
} from './pane-fit-resize-observer'
|
||||
import { buildDefaultTerminalOptions } from './pane-terminal-options'
|
||||
import { ENABLE_WEBGL_RENDERER, attachWebgl, disposeWebgl } from './pane-webgl-renderer'
|
||||
import {
|
||||
ENABLE_WEBGL_RENDERER,
|
||||
attachWebgl,
|
||||
cancelPendingWebglRefresh,
|
||||
disposeWebgl
|
||||
} from './pane-webgl-renderer'
|
||||
import { shouldFocusTerminalFromPanePointerDown } from './pane-pointer-focus'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -120,6 +125,7 @@ export function createPaneDOM(
|
||||
fitAddon,
|
||||
fitResizeObserver: null,
|
||||
pendingInitialFitRafId: null,
|
||||
pendingWebglRefreshRafId: null,
|
||||
pendingObservedFitRafId: null,
|
||||
searchAddon,
|
||||
serializeAddon,
|
||||
@@ -299,6 +305,7 @@ export function disposePane(
|
||||
cancelAnimationFrame(pane.pendingInitialFitRafId)
|
||||
pane.pendingInitialFitRafId = null
|
||||
}
|
||||
cancelPendingWebglRefresh(pane)
|
||||
detachPaneFitResizeObserver(pane)
|
||||
if (pane.compositionHandler) {
|
||||
pane.terminal.element?.removeEventListener('compositionstart', pane.compositionHandler, true)
|
||||
|
||||
@@ -107,6 +107,8 @@ export type ManagedPaneInternal = {
|
||||
fitResizeObserver: ResizeObserver | null
|
||||
// Stored so disposePane() can cancel the first post-open fit if a pane closes before paint.
|
||||
pendingInitialFitRafId?: number | null
|
||||
// Stored so disposePane() can cancel the post-WebGL-teardown refresh frame.
|
||||
pendingWebglRefreshRafId?: number | null
|
||||
pendingObservedFitRafId: number | null
|
||||
serializeAddon: SerializeAddon
|
||||
unicode11Addon: Unicode11Addon
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { ManagedPaneInternal } from './pane-manager-types'
|
||||
import { disposePane } from './pane-lifecycle'
|
||||
import { disposeWebgl } from './pane-webgl-renderer'
|
||||
|
||||
function createPane(
|
||||
overrides: Partial<Pick<ManagedPaneInternal, 'pendingWebglRefreshRafId' | 'webglAddon'>> = {}
|
||||
): ManagedPaneInternal {
|
||||
const leafId = '11111111-1111-4111-8111-111111111111' as never
|
||||
return {
|
||||
id: 1,
|
||||
leafId,
|
||||
stablePaneId: leafId,
|
||||
terminal: {
|
||||
element: null,
|
||||
rows: 24,
|
||||
refresh: vi.fn(),
|
||||
dispose: vi.fn()
|
||||
} as never,
|
||||
container: {} as never,
|
||||
xtermContainer: {} as never,
|
||||
linkTooltip: {} as never,
|
||||
terminalGpuAcceleration: 'off',
|
||||
gpuRenderingEnabled: false,
|
||||
webglAttachmentDeferred: false,
|
||||
webglDisabledAfterContextLoss: false,
|
||||
hasComplexScriptOutput: false,
|
||||
fitAddon: {
|
||||
fit: vi.fn(),
|
||||
dispose: vi.fn()
|
||||
} as never,
|
||||
fitResizeObserver: null,
|
||||
pendingInitialFitRafId: null,
|
||||
pendingWebglRefreshRafId: null,
|
||||
pendingObservedFitRafId: null,
|
||||
searchAddon: { dispose: vi.fn() } as never,
|
||||
serializeAddon: { dispose: vi.fn() } as never,
|
||||
unicode11Addon: { dispose: vi.fn() } as never,
|
||||
webLinksAddon: { dispose: vi.fn() } as never,
|
||||
webglAddon: { dispose: vi.fn() } as never,
|
||||
ligaturesAddon: null,
|
||||
compositionHandler: null,
|
||||
pendingSplitScrollState: null,
|
||||
pendingSplitScrollBufferDisposable: null,
|
||||
debugLabel: null,
|
||||
...overrides
|
||||
}
|
||||
}
|
||||
|
||||
describe('pane WebGL refresh lifecycle', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
it('tracks the deferred refresh frame after WebGL teardown', () => {
|
||||
vi.stubGlobal(
|
||||
'requestAnimationFrame',
|
||||
vi.fn(() => 29)
|
||||
)
|
||||
const pane = createPane()
|
||||
|
||||
disposeWebgl(pane, { refreshDimensions: true })
|
||||
|
||||
expect(pane.webglAddon).toBeNull()
|
||||
expect(pane.pendingWebglRefreshRafId).toBe(29)
|
||||
})
|
||||
|
||||
it('cancels a pending WebGL refresh when the pane is disposed', () => {
|
||||
const cancelAnimationFrame = vi.fn()
|
||||
vi.stubGlobal('cancelAnimationFrame', cancelAnimationFrame)
|
||||
const pane = createPane({
|
||||
pendingWebglRefreshRafId: 31,
|
||||
webglAddon: null
|
||||
})
|
||||
const panes = new Map([[pane.id, pane]])
|
||||
|
||||
disposePane(pane, panes)
|
||||
|
||||
expect(cancelAnimationFrame).toHaveBeenCalledWith(31)
|
||||
expect(pane.pendingWebglRefreshRafId).toBeNull()
|
||||
expect(panes.has(pane.id)).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -43,10 +43,18 @@ function refreshTerminalAfterWebglAttach(pane: ManagedPaneInternal): void {
|
||||
}
|
||||
}
|
||||
|
||||
export function cancelPendingWebglRefresh(pane: ManagedPaneInternal): void {
|
||||
if (pane.pendingWebglRefreshRafId != null) {
|
||||
cancelAnimationFrame(pane.pendingWebglRefreshRafId)
|
||||
pane.pendingWebglRefreshRafId = null
|
||||
}
|
||||
}
|
||||
|
||||
export function disposeWebgl(
|
||||
pane: ManagedPaneInternal,
|
||||
options?: { refreshDimensions?: boolean }
|
||||
): void {
|
||||
cancelPendingWebglRefresh(pane)
|
||||
if (!pane.webglAddon) {
|
||||
return
|
||||
}
|
||||
@@ -60,7 +68,8 @@ export function disposeWebgl(
|
||||
// Why: VS Code refreshes terminal dimensions after WebGL teardown because
|
||||
// DOM and WebGL renderer cell metrics differ. Without this, Linux DOM
|
||||
// scrollbars can desync and trigger visible reflow jitter.
|
||||
requestAnimationFrame(() => {
|
||||
pane.pendingWebglRefreshRafId = requestAnimationFrame(() => {
|
||||
pane.pendingWebglRefreshRafId = null
|
||||
try {
|
||||
pane.fitAddon.fit()
|
||||
pane.terminal.refresh(0, pane.terminal.rows - 1)
|
||||
|
||||
Reference in New Issue
Block a user