Replaces 10 separate reset()+addParam(0) pairs across the parser with
a single resetZdm() method that inlines both operations, avoiding two
calls to a foreign object per sequence reset.
Remove intermediate byte handling from the fast-path, bail to table
lookup instead. Remove redundant per-step length checks by requiring
i + 2 < length upfront and letting the for-loop guard the rest.
Replace three !== checks (OSC_STRING, DCS_PASSTHROUGH, APC_STRING)
with one < OSC_STRING comparison. States below OSC_STRING are all
safe for the CSI fast-path; states above need the normal table path
for proper subparser abort propagation.
Address reviewer feedback:
- CSI fast-path is now a condition at the top of the codepoint loop
instead of inside CLEAR, avoiding table lookups for the common case
- EXE bytes < 0x18 get a fast-path before the CSI check so control
bytes don't pay the penalty
- PRINT loop unrolling replaced with while-based version that uses
trueish eval and spares individual length checks in the unrolled case
- Removed misplaced PRINT scan-ahead from CSI dispatch
The CSI fast-path check previously ran on every byte of input, causing
3-18% regressions on ESCAPE, OSC, DCS, and other non-CSI sequences.
Moving the check into the CLEAR action handler means it only evaluates
after the state machine already identified an ESC byte. The fast-path
body is extracted into a separate _csiFast method to keep the switch
case body small for V8 optimization.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three related disposable-registration gaps that leak xterm Terminal
instances when the host application unmounts a Terminal. Evidence +
full writeup at juspay/kolu#606.
1. addon-webgl/WebglRenderer._cursorBlinkStateManager was declared
`= new MutableDisposable()` without the `this._register(...)`
wrapper that every sibling MutableDisposable in the class uses.
CursorBlinkStateManager runs a setInterval for the cursor blink
and its `dispose()` correctly clears it — but dispose() was never
called because the disposable wasn't registered. The live timer
kept the renderer (and thus the Terminal) alive past host
unmount.
2. common/TaskQueue.DebouncedIdleTask had no `dispose()` method.
Added one that clears the internal IdleTaskQueue (cancelling any
pending idle callback).
3. browser/services/RenderService._pausedResizeTask (a
DebouncedIdleTask) was not registered. Wrapped with
`this._register(...)` so RenderService.dispose propagates.
Verified in a downstream app (kolu) with a repro that toggles Focus
vs Canvas layout modes:
- Pre-fix: 24/28 disposed xterm Terminals retained past host unmount
+ forced GC.
- Post-fix: 0/28 retained.
Retention chain (before fix):
DOMTimer → ScheduledAction → V8Function
→ CursorBlinkStateManager._renderCallback → WebglRenderer
→ Terminal
Replace `window` with `globalThis` in IdleTaskQueue export.
In Bun and Deno, `isNode` is false but `window` is undefined, causing
a ReferenceError. Using `globalThis` is safe in all JS runtimes and
makes the `isNode` guard unnecessary.
This is the only `in window` expression in code bundled into
@xterm/headless.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>