Skip to content

fix(renderer): WebGL longtask auto-fallback to canvas renderer#83

Open
aakhter wants to merge 1 commit into
Ark0N:masterfrom
aakhter:fix/webgl-longtask-fallback
Open

fix(renderer): WebGL longtask auto-fallback to canvas renderer#83
aakhter wants to merge 1 commit into
Ark0N:masterfrom
aakhter:fix/webgl-longtask-fallback

Conversation

@aakhter
Copy link
Copy Markdown
Contributor

@aakhter aakhter commented May 15, 2026

Summary

The xterm WebGL renderer can stall the main thread for hundreds of ms under GPU pressure (driver hiccup, integrated-GPU memory pressure, hardware-accelerated browser layers contending for the GPU). Symptom: the page becomes intermittently unresponsive and Chrome eventually shows the Page Unresponsive dialog. Today the only mitigation is ?nowebgl, which the user has to remember and re-apply on every load.

This patch installs a PerformanceObserver after WebGL init that watches for sustained main-thread stalls and falls back to the DOM renderer automatically.

Behavior

  • Threshold: 3 long tasks of ≥ 200ms each within a 30s rolling window.
  • 5s grace period after init — skips noisy initial-load stalls so a slow first paint doesn't trip the guard.
  • On trigger: dispose the WebGL addon, write a sticky disable to localStorage (codeman-webgl-disabled) with { reason, at }, and terminal.refresh() so the canvas renderer takes over without a page reload.
  • Subsequent loads honor the sticky disable for 7 days, then auto-expire so users retry after a driver/Chrome update.
  • ?webgl=force clears the sticky entry and forces WebGL to re-init.
  • ?nowebgl unchanged — still wins as the manual escape hatch.
  • The same disable path is reused by the existing onContextLoss callback, so a hard context loss also persists across reloads.

Files

  • src/web/public/app.js_initWebGL onContextLoss now persists + schedules the watchdog; two new helpers: _installWebGLLongTaskGuard() and _disableWebGLSticky(reason).
  • src/web/public/terminal-ui.js — WebGL init reads the sticky entry with 7-day expiry, honors ?webgl=force, threads sticky into skipWebGL alongside the existing mobile + ?nowebgl gates.

Browser support

PerformanceObserver longtask is supported in Chromium and Edge. The try/catch around .observe({ type: 'longtask' }) makes Firefox/Safari (no longtask entry type) silently no-op — they just keep WebGL with no watchdog.

Test plan

  • Normal load on Chrome desktop: WebGL initializes, watchdog installs, no false trips after 30s of normal use.
  • Force a long task during typing (e.g. for (let i=0;i<3;i++) { const t=performance.now(); while(performance.now()-t<250){} await new Promise(r=>setTimeout(r,1000)); } in DevTools console): after 3 stalls within 30s, the canvas renderer takes over silently and localStorage.codeman-webgl-disabled is set.
  • Reload: WebGL stays off, console logs the sticky-disable message.
  • ?webgl=force: sticky entry cleared, WebGL re-initializes.
  • After 7 days (or manually setting at to an old timestamp): sticky entry auto-clears on next load.
  • Firefox/Safari: WebGL stays on (no longtask entry type — guard silently no-ops).

🤖 Generated with Claude Code

The xterm WebGL renderer can stall the main thread for hundreds of ms
under GPU pressure (driver hiccup, integrated-GPU memory pressure,
hardware-accelerated browser layers contending for the GPU). Symptom:
the page becomes intermittently unresponsive and Chrome eventually
shows the "Page Unresponsive" dialog. Today the only mitigation is
?nowebgl, which the user has to remember and re-apply on every load.

This patch installs a PerformanceObserver after WebGL init that
watches for sustained main-thread stalls and falls back to the DOM
renderer automatically:

- Threshold: 3 long tasks of >=200ms each within a 30-second window.
- 5-second grace period after init skips the noisy initial-load
  stalls so a slow first paint does not trip the guard.
- On trigger: dispose the WebGL addon, write a sticky disable to
  localStorage with a reason and timestamp, and refresh the terminal
  so the canvas renderer takes over without a page reload.
- Subsequent loads honor the sticky disable for 7 days, then auto-
  expire so users retry after a driver/Chrome update.
- Force re-enable any time with ?webgl=force (also clears the
  sticky entry).
- Existing ?nowebgl behaviour is unchanged.
- The same disable path is reused by the existing onContextLoss
  callback so a hard context loss also persists across reloads.

Files:
- src/web/public/app.js: _initWebGL onContextLoss now persists +
  schedules the watchdog; new _installWebGLLongTaskGuard and
  _disableWebGLSticky helpers.
- src/web/public/terminal-ui.js: WebGL init checks the sticky entry
  with 7-day expiry, honors ?webgl=force, threads sticky into
  skipWebGL alongside the existing mobile + ?nowebgl gates.

PerformanceObserver longtask is widely supported (Chromium, Edge);
the try/catch around .observe() makes Firefox/Safari (which lack the
longtask entry type) silently no-op and just keep WebGL.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant