Phenomenon
In iOS Safari with Japanese/Kanji IME, insertFromComposition fires with collapsed targetRanges that do not accurately represent the insertion point. Recalculating ranges based on current selection is necessary to correctly position the composition text.
Reproduction example
- Focus a
contenteditableelement in iOS Safari (iPhone/iPad). - Activate Japanese IME.
- Start composing Japanese/Kanji text (e.g., type “に” then “ほ” then “ん” to compose “日本”).
- Continue typing to update composition or convert to Kanji.
- Observe
beforeinputevents withinputType: 'insertFromComposition'. - Check
getTargetRanges()- they are collapsed (startOffset === endOffset) but do not represent the correct insertion point. - If handlers trust
targetRangesas-is, text will be inserted at wrong position. - Recalculating based on current selection produces correct positioning.
Observed behavior
When updating Japanese/Kanji composition text:
-
beforeinput event:
inputType: 'insertFromComposition'isComposing: truedata: '日本'(the new composition text)getTargetRanges()returns collapsed ranges (startOffset === endOffset)- The collapsed ranges do NOT accurately represent where the composition text should be inserted
-
If ranges are trusted as-is:
- Text may be inserted at wrong location
- Composition text may appear before or after the intended position
- Composition updates may fail or produce incorrect results
-
If ranges are recalculated:
- Using
window.getSelection()to get current selection position - Recalculating insertion point based on current selection
- Text is inserted at correct position
- Composition updates work correctly
- Using
Expected behavior
targetRangesfrominsertFromCompositionshould accurately represent the insertion point- If
targetRangesare collapsed and inaccurate, recalculation should be possible - Handlers should be able to use current selection to determine correct insertion point
- Composition text should be inserted at the correct position
Impact
- Incorrect positioning: Handlers that trust collapsed
targetRangesas-is will insert text at wrong positions - Composition breakage: Composition updates may fail or produce incorrect results
- Platform-specific bugs: Code that works on Desktop Safari may fail on iOS Safari
- IME-specific bugs: Code that works with Korean IME may fail with Japanese/Kanji IME
Browser Comparison
- iOS Safari (Japanese/Kanji):
insertFromCompositionfires with collapsedtargetRangesthat need recalculation - Desktop Safari:
insertFromCompositionfires with accurate collapsedtargetRanges(trust as-is) - iOS Safari (Korean):
insertFromCompositiondoes NOT fire - Chrome/Edge: Generally uses
insertCompositionTextinstead ofinsertFromComposition - Firefox: Behavior varies but generally more consistent with Chrome
Notes and possible direction for workarounds
-
Recalculate collapsed ranges: Recalculate based on current selection in iOS Safari:
element.addEventListener('beforeinput', (e) => { if (e.inputType === 'insertFromComposition') { const targetRanges = e.getTargetRanges?.() || []; if (targetRanges.length > 0) { const range = targetRanges[0]; if (range.collapsed) { // Recalculate based on current selection const selection = window.getSelection(); const currentRange = selection?.rangeCount ? selection.getRangeAt(0) : null; if (currentRange) { // Use currentRange instead of targetRanges handleCompositionInsertion(currentRange); } } else { // Use targetRanges as-is if not collapsed handleCompositionInsertion(range); } } } }); -
Platform and IME detection: Detect iOS Safari with Japanese/Kanji IME:
const isIOSSafari = /iPhone|iPad|iPod/.test(navigator.userAgent) && /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent); const isJapaneseIME = /* detect Japanese IME */; if (isIOSSafari && isJapaneseIME && e.inputType === 'insertFromComposition') { // Recalculate collapsed ranges } -
Use current selection: Always use
window.getSelection()to get accurate position whentargetRangesare collapsed