[lexical] Bug Fix: Emit COMPOSITION_END_TAG from the Firefox onInput defer branch#8680
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…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.
1387021 to
4851dd7
Compare
|
The missing unit test was bugging me, so I went back and found a way to get one working. Verified that it fails on |
potatowagon
left a comment
There was a problem hiding this comment.
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:
- Correctness: The tag is added immediately after
$onCompositionEndImpl(editor, data || undefined), which matches the Chrome/Webkit ordering where$handleCompositionEndcalls both$onCompositionEndImpland$addUpdateTag. The placement is correct — same update transaction, same tag semantics. - Edge cases: The
isFirefoxEndingCompositionguard already ensures this only fires when a priorcompositionendevent set the flag. Double-firing is prevented by theisFirefoxEndingComposition = falsereset immediately after. - Test coverage: Comprehensive unit test mocks the Firefox environment (
IS_FIREFOX: true), dispatchescompositionendfollowed byinputwithisComposing: false, and assertsCOMPOSITION_END_TAGappears in the update listener tags. Properly tests the exact deferred path. - 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).
- www compat: No removed/renamed exports, no signature changes. Internal code gating on
COMPOSITION_END_TAGwill 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.
Description
Firefox fires
onCompositionEndbeforeonInput, so$handleCompositionEndnever runs synchronously on Firefox.onCompositionEndonly sets theisFirefoxEndingCompositionflag and the deferred composition end runs inside the subsequentonInput, where$onCompositionEndImplis called. The Chrome/Webkit path runs$addUpdateTag(COMPOSITION_END_TAG)right next to$onCompositionEndImpl; the Firefox defer branch in$handleInput'selsepath was missing this call, so listeners gated onCOMPOSITION_END_TAGnever 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
registerMarkdownShortcutsgates its space-trigger fall-through onCOMPOSITION_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$onCompositionEndImplin the FirefoxonInputdefer 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
isFirefoxEndingCompositionhandler earlier in$handleInput(inside the$shouldPreventDefaultAndInsertTexttrue 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 theelsebranch fixed here; the prevent-default branch never fired, consistent with several!anchorNode.isComposing()guards inside$shouldPreventDefaultAndInsertTextthat 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 newLexicalFirefoxCompositionEndTag.test.ts. The new test mocksIS_FIREFOX: true, dispatches acompositionend+insertCompositionTextsequence at the root element, and asserts that an update tagged withCOMPOSITION_END_TAGis observed after the microtask flush. Without the one-line fix the test fails (verified by checking outorigin/main'sLexicalEvents.ts).pnpm devoflexical-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.AutocompleteExtensioncomment already assumed Firefox fires the tagged update synchronously — this PR makes that assumption hold).