Firefox Android may report parent contenteditable range in beforeinput for nested inner editor
OS: Android 13+ · Device: Phone Any · Browser: Firefox 120+ · Keyboard: Gboard / stock
Open case →Scenario
On some engines (notably Firefox for Android / Fenix), beforeinput getTargetRanges() may describe the outer contenteditable host instead of the inner focused editor. Custom handlers that trust targetRanges alone may delete or insert in the parent surface while the user believes they are typing in a nested field.
Nested contenteditable layouts (outer page shell with contenteditable="true" and an inner contenteditable="true" for a caption, comment box, or embedded widget) are fragile. Even when focus and the visible caret are inside the inner host, InputEvent.getTargetRanges() may still return a StaticRange spanning the outer editable root on certain builds. That is a different failure mode from “empty targetRanges”: the array is non-empty but semantically wrong, so fallback-to-selection and trust-targetRanges strategies both break in opposite ways.
The Input Events specification assumes getTargetRanges() reflects the DOM ranges that will be modified by the incoming edit. With nested editables, the editing engine must disambiguate which host owns the current editing session. When the engine attaches the range to the outer host, beforeinput listeners that call preventDefault() and apply their own mutation using targetRanges[0] will corrupt the outer document—while keyboard input still visually appears tied to the inner field in some steps.
contenteditable receives keystrokes but getTargetRanges() points at the parent range; text can be applied to the wrong tree location relative to editor expectations.contenteditable="plaintext-only", and custom wrappers.outer.addEventListener('beforeinput', (e) => {
const tr = e.getTargetRanges?.() ?? [];
if (tr.length) {
const { startContainer } = tr[0];
console.log('range root editable:', startContainer.parentElement?.closest('[contenteditable]'));
}
});
deleteContentBackward uses a range that removes parent content instead of the inner field.getTargetRanges() with nested editables; treat as high risk until verified fixed.Validate range against focused node: Before applying targetRanges[0], require focusedEditable.contains(startContainer) where focusedEditable is your innermost contenteditable="true" that matches document.activeElement or shadow-aware active element.
Prefer active editable host: If targetRanges and getSelection() disagree, prefer the range whose commonAncestorContainer is contained in the same host as document.activeElement.
Avoid nested contenteditable: Where possible, use a single outer host and inner contenteditable="false" islands with programmatic editing, or isolate nested fields in iframes (trade-offs apply).
Feature-detect bad coupling: On Firefox Android, run a one-time manual test harness logging activeElement, getSelection(), and getTargetRanges() on a single keypress; cache a “do not trust targetRanges for nested” flag per UA if needed.
targetRanges as automatically correct when multiple editables nest.targetRanges when debugging mobile Firefox reports.targetRanges and fallback strategiesVisual view of how this scenario connects to its concrete cases and environments. Nodes can be dragged and clicked.
Each row is a concrete case for this scenario, with a dedicated document and playground.
| Case | OS | Device | Browser | Keyboard | Status |
|---|---|---|---|---|---|
| ce-0586-firefox-android-nested-gettargetranges-parent-range | Android 13+ | Phone Any | Firefox 120+ | Gboard / stock | draft |
Open a case to see the detailed description and its dedicated playground.
OS: Android 13+ · Device: Phone Any · Browser: Firefox 120+ · Keyboard: Gboard / stock
Open case →Other scenarios that share similar tags or category.
The getTargetRanges() method in beforeinput events may return an empty array or undefined in various scenarios, including text prediction, certain IME compositions, or specific browser/device combinations. When getTargetRanges() is unavailable, developers must rely on window.getSelection() as a fallback, but this may be less accurate.
On Chrome Mobile for Android, typing certain punctuation characters (commas, colons, semicolons, quotes, etc.) in the middle of a word causes the cursor to jump to the end of the word instead of staying at the insertion point.
In Chrome on Android, input events may fire when a contenteditable element gains or loses focus, even without content changes. This behavior can lead to unintended side effects in applications relying on input events for content modification detection.
When a parent element has contenteditable="true" and a child element has contenteditable="false", the inheritance behavior is inconsistent across browsers. Some browsers allow editing in the child, while others correctly prevent it. The behavior may also differ when the child has contenteditable="inherit" or no contenteditable attribute.
When a contenteditable region contains child elements with contenteditable="false", the behavior is inconsistent. Some browsers allow editing within these elements, while others correctly prevent it.
Have questions, suggestions, or want to share your experience? Join the discussion below.