fix(renderer): WebGL longtask auto-fallback to canvas renderer#83
Open
aakhter wants to merge 1 commit into
Open
fix(renderer): WebGL longtask auto-fallback to canvas renderer#83aakhter wants to merge 1 commit into
aakhter wants to merge 1 commit into
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
PerformanceObserverafter WebGL init that watches for sustained main-thread stalls and falls back to the DOM renderer automatically.Behavior
localStorage(codeman-webgl-disabled) with{ reason, at }, andterminal.refresh()so the canvas renderer takes over without a page reload.?webgl=forceclears the sticky entry and forces WebGL to re-init.?nowebglunchanged — still wins as the manual escape hatch.onContextLosscallback, so a hard context loss also persists across reloads.Files
src/web/public/app.js—_initWebGLonContextLossnow 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 intoskipWebGLalongside the existing mobile +?nowebglgates.Browser support
PerformanceObserverlongtask is supported in Chromium and Edge. Thetry/catcharound.observe({ type: 'longtask' })makes Firefox/Safari (no longtask entry type) silently no-op — they just keep WebGL with no watchdog.Test plan
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 andlocalStorage.codeman-webgl-disabledis set.?webgl=force: sticky entry cleared, WebGL re-initializes.atto an old timestamp): sticky entry auto-clears on next load.🤖 Generated with Claude Code