Phenomenon
During Korean IME composition in iOS Safari, each composition update fires both a deleteContentBackward event followed by an insertText event (not insertCompositionText). This sequential firing pattern differs from other browsers where only insertCompositionText fires during composition updates, and can cause event handlers to execute twice for a single composition update.
Reproduction example
- Focus a
contenteditableelement on iOS Safari. - Activate Korean IME.
- Start composing a word (e.g., type “ㅎ” then “ㅏ” then “ㄴ” to compose “한”).
- Observe the
beforeinputevents in the browser console or event log.
Observed behavior
When composing Korean text (e.g., typing “한글”):
- User types a character that updates the composition
beforeinputevent fires withinputType: 'deleteContentBackward'to remove the previous composition textbeforeinputevent fires again withinputType: 'insertText'(notinsertCompositionText) to insert the new composition text- Both events have
e.isComposing === true - Event handlers that process both
deleteContentBackwardandinsertTextwill execute twice for each composition update - The fact that
insertText(notinsertCompositionText) fires during composition can cause handlers expectinginsertCompositionTextto miss these events
Expected behavior
- During composition updates, only
insertCompositionTextshould fire (as in Chrome/Edge) - If both events fire, they should be treated as a single atomic operation
- Event handlers should not need special logic to deduplicate composition updates
insertTextshould not fire during active composition (onlyinsertCompositionTextshould)
Impact
This can lead to:
- Performance issues (double processing)
- Incorrect undo/redo stack management
- Duplicate validation or formatting logic execution
- State synchronization issues
Browser Comparison
- iOS Safari: Fires
deleteContentBackwardfollowed byinsertText(notinsertCompositionText) during composition updates - Chrome/Edge: Fires only
insertCompositionTextduring composition updates - Firefox: Behavior varies but generally more consistent with Chrome (fires
insertCompositionText)
Notes and possible direction for workarounds
- Check if the event is part of a composition sequence (
e.isComposing === true) - Avoid processing
deleteContentBackwardevents during composition when they are immediately followed byinsertText - Handle
insertTextevents during composition (not justinsertCompositionText) in iOS Safari - Use a flag to track if a
deleteContentBackwardevent was immediately followed by aninsertTextevent during composition - Treat the
deleteContentBackward+insertTextpair as a single composition update operation