Skip to content

Text visually disappears after IME composition inside brackets on Chrome #1688

@miya

Description

@miya

Describe the issue

Summary

When confirming an IME composition inside brackets with bracketMatching() enabled (default afterCursor: true), text before the opening bracket visually disappears on Chrome. The document data remains intact, and the display recovers when the cursor moves to another line. This does not reproduce on Safari.

Environment

  • @codemirror/language 6.12.2
  • @codemirror/view 6.40.0
  • @codemirror/state 6.6.0
  • Browser: Chrome (also Firefox; Safari is unaffected)
  • Input method: IME (tested with Japanese and Chinese)

Steps to reproduce

  1. Enable bracketMatching() with the default afterCursor: true
  2. Type a line containing brackets, e.g.: ## Template (test)
  3. Place the cursor inside the brackets: ## Template (|test)
  4. Delete test and re-type using IME; confirm with Enter or Space
  5. After confirmation, ## Template visually disappears — only (confirmed text) is rendered

Expected: ## Template (confirmed text) displays correctly.
Actual: Everything before ( vanishes from the display. Document state is correct. Moving the cursor to a different line restores the missing text.

Analysis

The paused flag added in @codemirror/language 6.12.2 (for #1324) correctly defers bracket matching decoration updates while view.composing is true. However, once compositionend fires, view.composing immediately becomes false, and the very next update cycle synchronously recalculates bracket decorations with no post-composition delay.

On Chrome, the sequence is:

  1. IME confirmation → compositionend fires synchronously
  2. view.composing drops to false
  3. DOM observer flushes, transaction is dispatched
  4. bracketMatcher.update() runs, sees composition has ended, unpauses
  5. With afterCursor: true, cursor is at …(confirmed text|)matchBrackets finds ) before the cursor and its matching (, applies Decoration.mark (cm-matchingBracket) to both
  6. The mark wrapping ( mutates the DOM before Chrome's rendering engine has settled its post-composition state, causing the preceding text node to drop from the render tree

Safari is unaffected because @codemirror/view delays compositionend dispatch by 20ms via setTimeout when Safari fires an insertText event during composition. This gives the browser time to finalize DOM changes before decorations are reapplied. Chrome receives no such delay.

Workaround

Disable the default bracketMatching() and re-add it with afterCursor: false. This prevents the cursor-before-bracket match that triggers the problematic decoration, but at the cost of losing bracket highlighting when the cursor is immediately before a bracket.

Suggested fix

Add a short post-composition guard to the bracketMatcher plugin, similar to the compositionEndedAt checks already used in @codemirror/view for Safari key handling. For example, after compositionend, keep the plugin paused for ~80ms before allowing decoration recalculation. This would give all browsers time to settle their post-composition DOM state.

Browser and platform

MacOS: 15.7.4(24G517, Google Chrome: v46.0.7680.154

Reproduction link

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions