Skip to content

fix(ime): scroll to bottom when composing while scrolled into scrollback - #6203

Open
52mzd wants to merge 1 commit into
xtermjs:masterfrom
52mzd:fix/composition-scrollback-sync
Open

52mzd wants to merge 1 commit into
xtermjs:masterfrom
52mzd:fix/composition-scrollback-sync

Conversation

@52mzd

@52mzd 52mzd commented Oct 3, 2026

Copy link
Copy Markdown

Problem

When composition (IME input) starts or updates while the viewport is scrolled into the scrollback, the helper textarea — and with it the IME candidate window — stays frozen at a stale position:

  • _syncTextArea is guarded by isCursorInViewport (CoreBrowserTerminal.ts), so the resync added in fix(ime): resync textarea position when composition starts #5759 is a no-op when the cursor row is scrolled out of view.
  • CompositionHelper.updateCompositionElements has the same guard, so every compositionupdate/onRender reposition is skipped as well.

Regular keystrokes recover from this state because _keyDown scrolls to the bottom on user input (scrollOnUserInput). Composition events never go through onData, and the keydown (keyCode 229) that would normally trigger that scroll is not guaranteed to cover the composition path:

  • Event order varies across browsers (e.g. WebKit fires the composition/beforeinput events before the character keydown on some platforms — observed with a runtime probe on macOS 26 WKWebView).
  • Embedders may consume the keydown via customKeyEventHandler before the scroll branch runs.
  • The user can scroll into the scrollback mid-composition (mouse wheel), where no keydown fires at all before the next compositionupdate.

Fix

Mirror the scrollOnUserInput behavior of _keyDown in the compositionstart and compositionupdate listeners, before _syncTextArea / updateCompositionElements run. This makes the isCursorInViewport guard hold by construction when composing, instead of relying on a preceding keydown:

if (this.options.scrollOnUserInput && this.buffer.ybase !== this.buffer.ydisp) {
  this.scrollToBottom(true);
}

With scrollOnUserInput: false (or when already at the bottom) nothing changes.

Test

3 new integration tests in test/playwright/Terminal.test.ts (IME composition describe), verified red on master and green with the fix, on Chromium/Firefox/WebKit:

  • compositionstart scrolls to bottom when composing from the scrollback
  • compositionupdate scrolls to bottom when scrolled up mid-composition
  • compositionstart does not scroll when scrollOnUserInput is disabled

Note the setup scrolls with the mouse wheel rather than the scrollLines API: the API path goes through the viewport's smooth scroll animation, which can snap back to the bottom in headless and does not reproduce a stable scrolled-up viewport.

🤖 Generated with Claude Code

Co-Authored-By: Claude Code <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