Skip to content

Editor textarea: short content unless a screen reader is attached (fixes IME jitter in WebKit) - #333909

Open
PathGao (PathGao) wants to merge 3 commits into
microsoft:mainfrom
PathGao:textarea-short-content-unless-screen-reader
Open

Editor textarea: short content unless a screen reader is attached (fixes IME jitter in WebKit)#333909
PathGao (PathGao) wants to merge 3 commits into
microsoft:mainfrom
PathGao:textarea-short-content-unless-screen-reader

Conversation

@PathGao

@PathGao PathGao (PathGao) commented Sep 2, 2026

Copy link
Copy Markdown

Fixes microsoft/monaco-editor#4796 — "line jump up and down when type chinese, only in safari".

What happens

In browsers without EditContext (WebKit, Firefox) the editor takes input through TextAreaEditContext. During IME composition the textarea becomes a one-row overlay on the composed line, but it holds a page of text, so _render scrolls it to the caret's row: scrollTop = nlBefore * lineHeight.

WebKit then scrolls that textarea itself, on every composition update, by the minimum needed to keep the caret visible — which is a few pixels short of row-aligned because the caret box is shorter than the line box. The next render writes the row-aligned value again. Instrumented on macOS (Monaco 0.55.1 in WKWebView, lineHeight 21, caret on line 2 of the page):

compositionupdate "a j"
  WRITE  top 21 -> kept 21   [scrollHeight 42, clientHeight 21, max 21]   not clamped
  SCROLL top 18              (asked 21, off by 3)                          the element scrolls itself
  WRITE  top 21 -> kept 21                                                 next render

102 self-scrolls over three composed lines, every one off by exactly 3px (line 3: 42 → 39). The first line of the page never jitters because its target offset is 0. "editor.accessibilitySupport": "off" removes it entirely, because that path writes a short single-line string into the textarea and nothing needs scrolling. Full trace in the issue.

The change

writeNativeTextAreaContent already treats AccessibilitySupport.Unknown as "no screen reader": it skips render-time writes unless isScreenReaderOptimized() (#192278). getScreenReaderContent made the opposite call for the same state and handed it the paged content. This aligns the two: Unknown gets the short content the Disabled branch already produces, and the textarea is not sized for wrapping either. With no overflow there is nothing for the browser to scroll and nothing to fight over.

Android is left exactly as it was: its Unknown case still reaches the Android word branch that composition diffing relies on (the first review caught that the initial version returned EMPTY before it).

Enabled — a screen reader known to be attached, or "editor.accessibilitySupport": "on", which is what the docs ask screen-reader users on the web to set — is untouched.

Second commit: write order in the composition branch

The composition branch of _render wrote scrollTop/scrollLeft before _doRender sized the textarea, while the non-composition branch a few lines down writes them after. A scroll offset is clamped against the element's size at the moment it is written, so the sibling branch's order is the one that cannot lose part of the write. This was the first thing I tried against the jitter and it made no measurable difference on its own (the offsets were never clamped; they were moved afterwards — see the issue), so it is here as consistency, not as the fix.

Things I tried first, and why they are not the fix

  • Writing the two offsets after _doRender (the sibling non-composition branch does it in that order): no effect. The write is never clamped; the value is moved afterwards.
  • Re-asserting the offsets from the textarea's scroll event: removes the jitter, but keeps the alignment depending on a value the browser owns.
  • Repositioning the overlay to follow the browser's scroll: moving a focused editable makes WebKit reveal its caret again through the ancestors, and the whole editor scrolls — worse, and no longer IME-specific.
  • Giving the overlay its full content height and clipping to one row: works in principle, but _doRender couples height and line-height, and the change is much larger than this one.

Verification

Built Monaco with this exact change into the app that reproduces the issue (macOS 26, WKWebView, system Chinese IME): composition on any line no longer jitters, ASCII typing, select-all, copy, paste, undo and cross-line cursor movement behave as before. src/vs/editor/test/browser/controller/textAreaInput.test.ts mocks getScreenReaderContent, so no existing assertion covers this decision.

Not verified: Firefox (the other browser on this code path) and screen-reader behaviour with Unknown — by design that case now behaves like Disabled, which is the everyday desktop path without a screen reader.

🤖 Generated with Claude Code

`writeNativeTextAreaContent` already treats AccessibilitySupport.Unknown as
"no screen reader": it skips render-time writes unless
isScreenReaderOptimized() (microsoft#192278). `getScreenReaderContent` disagreed and
handed Unknown the paged content, so a one-row textarea carried several
rows of text and had to be scrolled to the caret's row.

WebKit scrolls a focused textarea to keep its caret visible on every IME
composition update, landing a few pixels short of the row-aligned offset
`_render` writes; the next render writes it back, and the composed text
jitters on every keystroke (microsoft/monaco-editor#4796). With the short
content there is no overflow to scroll and nothing to fight over.

Behaviour with a screen reader attached (Enabled) is unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… sizing it

The composition branch of `_render` wrote `scrollTop`/`scrollLeft` before
`_doRender` gave the textarea its size for the row, while the
non-composition branch a few lines down does it the other way round. A
scroll offset is clamped against the element's size at the time it is
written, so the order the sibling branch uses is the one that cannot lose
part of the write. No behavioural difference was measured for the jitter in
monaco-editor#4796 — with the short content there is nothing to scroll —
this only makes the two branches agree.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new condition bypasses required Android composition handling when accessibility support is unknown.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Aligns textarea content and wrapping with confirmed screen-reader support to prevent WebKit IME jitter.

Changes:

  • Uses short textarea content unless accessibility support is enabled.
  • Restricts textarea wrapping to screen-reader mode.
File summaries
File Description
src/vs/editor/browser/controller/editContext/textArea/textAreaEditContext.ts Updates textarea content and wrapping behavior.
Review details

Suppressed comments (1)

src/vs/editor/browser/controller/editContext/textArea/textAreaEditContext.ts:571

  • This inline explanation exceeds the project's one-line limit for comments inside methods. It can be stated concisely without restating the preceding screen-reader wrapping rationale.
		// Without one, the textarea only ever holds the short content `getScreenReaderContent` writes,
		// and there is nothing to wrap.
  • Files reviewed: 1/1 changed files
  • Comments generated: 2
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

getScreenReaderContent: (): TextAreaState => {
if (this._accessibilitySupport === AccessibilitySupport.Disabled) {
// We know for a fact that a screen reader is not attached
if (this._accessibilitySupport !== AccessibilitySupport.Enabled) {
Comment on lines +211 to +215
// No screen reader is known to be attached. `writeNativeTextAreaContent` already
// treats this case as "no screen reader" and skips render-time writes (#192278);
// handing it the paged content anyway put several rows of text into a one-row
// textarea, and during IME composition WebKit scrolls that textarea to keep its
// caret visible, fighting the row alignment `_render` maintains (monaco-editor#4796).
@PathGao

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

Android + Unknown went through the short-content branch and returned
EMPTY before reaching the Android word handling that composition diffing
relies on. The Android cases now behave exactly as before; only the
non-Android Unknown case takes the short content.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@PathGao

Copy link
Copy Markdown
Author

Addressed in 9b39c2c: the Android cases now behave exactly as before — Unknown on Android still reaches the word branch, only the non-Android Unknown case takes the short content — and the in-method comments are one line each.

PathGao (PathGao) added a commit to sftwrdotdev/Markpad that referenced this pull request Sep 2, 2026
…p back (#724) (#746)

Monaco's hidden textarea becomes a one-row overlay on the composed line
during IME composition, but it holds a page of text, so Monaco scrolls it to
the caret's row. WebKit scrolls it back to "caret just visible" — 3px short
of the row — on every composition update, and the next render scrolls it
forward again. That is the jitter: the composed text sinks and snaps back
on every keystroke, on every line but the first of the page.

Monaco already treats `accessibilitySupport: 'auto'` in a browser as "no
screen reader" when deciding whether to write into the textarea
(vscode#192278), but not when deciding what to write; it hands that state
a page. A build-time patch makes the two decisions agree, so the overlay
holds one short line, has no overflow, and gives the browser nothing to
scroll. Proposed upstream as microsoft/vscode#333909; the patch and its
test go when a Monaco release carries it.

Co-authored-by: Claude Fable 5.1 <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.

line jump up and down when type chinese, only in safari

3 participants