Skip to content

[lexical] Bug Fix: Emit COMPOSITION_END_TAG from the Firefox onInput defer branch#8680

Merged
etrepum merged 1 commit into
facebook:mainfrom
mayrang:fix/firefox-composition-end-tag
Jun 11, 2026
Merged

[lexical] Bug Fix: Emit COMPOSITION_END_TAG from the Firefox onInput defer branch#8680
etrepum merged 1 commit into
facebook:mainfrom
mayrang:fix/firefox-composition-end-tag

Conversation

@mayrang

@mayrang mayrang commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Description

Firefox fires onCompositionEnd before onInput, so $handleCompositionEnd never runs synchronously on Firefox. onCompositionEnd only sets the isFirefoxEndingComposition flag and the deferred composition end runs inside the subsequent onInput, where $onCompositionEndImpl is called. The Chrome/Webkit path runs $addUpdateTag(COMPOSITION_END_TAG) right next to $onCompositionEndImpl; the Firefox defer branch in $handleInput's else path was missing this call, so listeners gated on COMPOSITION_END_TAG never observed the post-commit update on Firefox.

In practice, with a Korean IME active, Firefox routes space through a compositionstart / compositionupdate / compositionend cycle. The markdown shortcut listener in registerMarkdownShortcuts gates its space-trigger fall-through on COMPOSITION_END_TAG, so typing - (and other shortcut-trigger sequences) silently failed to convert into a list on Firefox + Korean IME.

Mirror the Chrome/Webkit path: call $addUpdateTag(COMPOSITION_END_TAG) alongside $onCompositionEndImpl in the Firefox onInput defer branch. Other listeners gated on the tag (lexical-history's composition-grouped merge, the playground autocomplete plugin's post-commit ghost) regain the same signal as a side effect.

A second isFirefoxEndingComposition handler earlier in $handleInput (inside the $shouldPreventDefaultAndInsertText true branch) has the same shape but is left untouched here. Manual probing on Firefox + Korean IME (drag-select replacement, bold/format toggle, TabNode adjacency, heading + Korean) only ever hit the else branch fixed here; the prevent-default branch never fired, consistent with several !anchorNode.isComposing() guards inside $shouldPreventDefaultAndInsertText that effectively gate it out during composition. If a real Firefox scenario reaches that branch, the same one-line mirror should apply there too.

Closes #8679

Test plan

  • pnpm vitest run --project unit — full unit suite 2935/2936 pass (1 pre-existing skip), including the new LexicalFirefoxCompositionEndTag.test.ts. The new test mocks IS_FIREFOX: true, dispatches a compositionend + insertCompositionText sequence at the root element, and asserts that an update tagged with COMPOSITION_END_TAG is observed after the microtask flush. Without the one-line fix the test fails (verified by checking out origin/main's LexicalEvents.ts).
  • tsc / flow / prettier / eslint clean.
  • Manual on Firefox + Korean IME (macOS), comparing playground.lexical.dev (before) vs local pnpm dev of lexical-playground (after). The "after" recording is attached.
    • - + space → bullet list created (the issue's repro).
    • 1. + space → numbered list.
    • # + Korean text → h1 heading.
    • ` wrapped text → inline code formatting.
    • Cmd+Z on a Korean-IME-typed word: step-by-step undo. Chrome behaves the same way, so this is Lexical's existing Korean-IME commit policy and unrelated to this fix.
    • Playground autocomplete ghost: post-commit ghost shows on Firefox after the fix, matching Chrome (the AutocompleteExtension comment already assumed Firefox fires the tagged update synchronously — this PR makes that assumption hold).
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jun 11, 2026
@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lexical Ready Ready Preview, Comment Jun 11, 2026 8:01pm
lexical-playground Ready Ready Preview, Comment Jun 11, 2026 8:01pm

Request Review

…defer branch

Firefox fires onCompositionEnd before onInput, so $handleCompositionEnd never runs synchronously on Firefox. onCompositionEnd only sets the isFirefoxEndingComposition flag and the deferred composition end runs inside the subsequent onInput, where $onCompositionEndImpl is called. The mirrored Chrome/Webkit path runs $addUpdateTag(COMPOSITION_END_TAG) right next to $onCompositionEndImpl; the Firefox defer branch in $handleInput's else path was missing this call, so listeners gated on COMPOSITION_END_TAG never observed the post-commit update on Firefox.

In practice, with the Korean IME active, Firefox routes space through a compositionstart / compositionupdate / compositionend cycle. The markdown shortcut listener in registerMarkdownShortcuts gates its space-trigger fall-through on COMPOSITION_END_TAG, so typing "- " (and other shortcut-trigger sequences) silently failed to convert into a list on Firefox + Korean IME.

Mirror the Chrome/Webkit path: call $addUpdateTag(COMPOSITION_END_TAG) alongside $onCompositionEndImpl in the Firefox onInput defer branch. Other listeners gated on the tag (lexical-history merge, AutocompleteExtension post-commit ghost) regain the same signal as a side effect.

A second isFirefoxEndingComposition handler earlier in $handleInput (inside the $shouldPreventDefaultAndInsertText true branch) has the same shape but is intentionally left untouched here. Manual probing on Firefox + Korean IME (drag-select replacement, bold/format toggle, TabNode adjacency, heading + Korean) only ever hit the else branch fixed here; the prevent-default branch never fired, consistent with several !anchorNode.isComposing() guards inside $shouldPreventDefaultAndInsertText that effectively gate it out during composition. If a real Firefox scenario reaches that branch, the same one-line mirror should apply there too.
@mayrang mayrang force-pushed the fix/firefox-composition-end-tag branch from 1387021 to 4851dd7 Compare June 11, 2026 20:00
@mayrang

mayrang commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

The missing unit test was bugging me, so I went back and found a way to get one working. Verified that it fails on origin/main's LexicalEvents.ts and passes with the fix. Sorry for the extra churn.

@potatowagon potatowagon 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.

Reviewed by Navi (Tater Thoughts Bobblehead) on behalf of @potatowagon.

Assessment: LGTM ✅

What this does: Adds $addUpdateTag(COMPOSITION_END_TAG) to the Firefox-specific deferred composition-end path in $handleInput. On Chrome/Webkit, $handleCompositionEnd already emits this tag; Firefox defers composition end handling until the next onInput event (via the isFirefoxEndingComposition flag), but was missing the tag emission — causing listeners gated on COMPOSITION_END_TAG (markdown shortcut trigger, history merge, autocomplete post-commit) to never fire on Firefox.

What I checked:

  1. Correctness: The tag is added immediately after $onCompositionEndImpl(editor, data || undefined), which matches the Chrome/Webkit ordering where $handleCompositionEnd calls both $onCompositionEndImpl and $addUpdateTag. The placement is correct — same update transaction, same tag semantics.
  2. Edge cases: The isFirefoxEndingComposition guard already ensures this only fires when a prior compositionend event set the flag. Double-firing is prevented by the isFirefoxEndingComposition = false reset immediately after.
  3. Test coverage: Comprehensive unit test mocks the Firefox environment (IS_FIREFOX: true), dispatches compositionend followed by input with isComposing: false, and asserts COMPOSITION_END_TAG appears in the update listener tags. Properly tests the exact deferred path.
  4. No regressions: Single-line addition inside an existing guarded block. No API changes. No new exports. Safe for www consumers (same tag already existed, just was not emitted on this code path).
  5. www compat: No removed/renamed exports, no signature changes. Internal code gating on COMPOSITION_END_TAG will now correctly trigger on Firefox — this is a fix for www, not a risk.

CI status: Core tests all green (unit 22.x+24.x, browser all platforms, integrity). E2e tests still pending (builds passed). CLA signed. Vercel deployed.

Ready to approve once e2e completes green.

@etrepum etrepum added this pull request to the merge queue Jun 11, 2026
Merged via the queue into facebook:main with commit d0b80fd Jun 11, 2026
46 checks passed
@etrepum etrepum mentioned this pull request Jun 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

3 participants