[Android][Fabric] Fatal "Cannot remove child at index" — focus re-request inside ViewGroup.removeViewInternal throws "descendant of this view" during Fabric removal (0.86.0)
Dieses Issue hat noch niemand übernommen.
Bewertung
- Schwierigkeit
- 4/5
- Geschätzter Aufwand
- 3-5 Tage
- Anfängerfreundlichkeit
- 45/100
Rechercherichtung
Beginne mit den Entfernungsmethoden von ReactViewGroup und vergleiche die Barrierefreiheitsbehandlung in #56182; verfolge anschließend SurfaceMountingManager.removeViewAt und ReactClippingViewManager.removeViewAt. Als erledigt gilt die Aufgabe, wenn die mit der Barrierefreiheit zusammenhängende IllegalArgumentException nicht mehr aus der Fabric-Entfernung entweicht und das beschriebene Android-Barrierefreiheitsszenario den fatalen Fehler Cannot remove child vermeidet.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
Description
With an accessibility service enabled, removing a focused view during a Fabric mount batch reliably (if intermittently) kills the app with:
java.lang.IllegalStateException: Cannot remove child at index N from parent ViewGroup [tag],
only M children in parent. Warning: childCount may be incorrect!
at com.facebook.react.fabric.mounting.SurfaceMountingManager.removeViewAt(SurfaceMountingManager.kt:525)
Caused by: java.lang.IllegalArgumentException: parameter must be a descendant of this view
at android.view.ViewGroup.offsetRectBetweenParentAndChild(ViewGroup.java:6478)
ReactHostImpl.handleHostException then calls destroy() unconditionally, so this is fatal rather than recoverable.
Root cause
The IllegalArgumentException is not thrown by the removal itself — it comes from the accessibility event that the focus re-request fires inside removeViewInternal. Captured on device, read bottom-up:
MountItemDispatcher.dispatchMountItems / IntBufferBatchMountItem.execute
SurfaceMountingManager.removeViewAt(SurfaceMountingManager.kt:504)
ReactClippingViewManager.removeViewAt(ReactClippingViewManager.kt:68)
android.view.ViewGroup.removeViewAt(ViewGroup.java:5702)
android.view.ViewGroup.removeViewInternal(ViewGroup.java:5775)
android.view.View.rootViewRequestFocus(View.java:8827)
android.view.View.requestFocus -> ViewGroup.onRequestFocusInDescendants (recursion)
android.view.View.handleFocusGainInternal(View.java:8591)
android.view.View.onFocusChanged(View.java:8953)
android.view.View.sendAccessibilityEvent(View.java:9178)
android.view.ViewGroup.dispatchPopulateAccessibilityEventInternal(ViewGroup.java:3700) (~10 deep)
android.view.ViewGroup$ChildListForAccessibility.init(ViewGroup.java:9334)
android.view.ViewGroup$ViewLocationHolder.init(ViewGroup.java:9520)
android.view.ViewGroup.offsetDescendantRectToMyCoords(ViewGroup.java:6407)
android.view.ViewGroup.offsetRectBetweenParentAndChild(ViewGroup.java:6478) <- throws
So: the removed child held focus → removeViewInternal calls rootViewRequestFocus() → the focus gain fires an accessibility event → ChildListForAccessibility sorts children via offsetDescendantRectToMyCoords while the removed child is still mid-detach → IllegalArgumentException.
Because removeFromArray() has already run at that point, the removal has actually succeeded and the view tree is consistent. But the exception propagates out of ViewGroup.removeViewAt, and SurfaceMountingManager.removeViewAt's catch (e: RuntimeException) converts it into the fatal IllegalStateException quoted above — reporting a childCount inconsistency that isn't the real problem.
Why #56182 doesn't cover this
#56182 added a soft-catch for exactly this IllegalArgumentException — but only around addChildrenForAccessibility, i.e. the accessibility build path. main still has nothing on the removal path, and ReactViewGroup does not override any of removeViewAt / removeViews / removeViewsInLayout / removeAllViewsInLayout.
Note this is a different bug from #57800, which is the Fabric differ emitting a Remove against a wrong parent tag and throws earlier in the same method (SurfaceMountingManager.kt:444, "Unable to remove a view from a view that is not a ViewGroup", no Caused by). This one always carries the descendant of this view cause and requires an active accessibility service; that one requires neither.
Suggested fix
Mirror the #56182 pattern onto the removal path — wrap the super calls in ReactViewGroup's removal methods and soft-catch an IllegalArgumentException whose message contains descendant of this view. Since the child is already detached when it throws, swallowing leaves the tree consistent.
We currently ship precisely that as a build-time ASM transform (RN core is a prebuilt AAR for us, so a source patch would need react.internal.buildFromSource). With it in place the throw is caught, the app survives, and no Cannot remove child follows — verified on device with the stack above.
Steps to reproduce
No deterministic reproducer — it's a race between a Fabric removal and an accessibility service's tree traversal, and we could not force it with scripted input (adb shell input) even across ~20 list remounts and ~30 fling gestures. It surfaces readily in ordinary manual use. Conditions that reproduce it for us:
- Android 16 or 17 device with at least one accessibility service enabled (no TalkBack needed — a password manager's autofill service is enough).
- A long virtualized list whose rows contain heavy focusable native views (video players, WebViews), with
removeClippedSubviewsenabled so that recycling produces frequent native view removals. - Scroll the list, or remount it (e.g. by changing its
key). - Watch for
Cannot remove child at indexwith aCaused by: parameter must be a descendant of this view.
Two notes for anyone trying to reproduce: the removed view must hold focus for rootViewRequestFocus() to run, and the default logcat ring buffer rotates too fast under WebView logging to catch it after the fact (adb logcat -G 32M helps).
React Native Version
0.86.0
Affected Platforms
Runtime - Android
Areas
Fabric - The New Renderer
Output of npx @react-native-community/cli info
React Native: 0.86.0 (new architecture, bridgeless)
React: 19.2.3
Expo: 57.0.8
Hermes: bundled
Devices observed crashing: Android 16, and Android 17 (Pixel 9 Pro, CP2A.260805.005)
AGP: 8.12.0, Gradle: 9.3.1
Accessibility services active during repro: a password manager's autofill service, plus a
third-party status-bar utility (neither is TalkBack; either alone is sufficient)
— drafted by Claude (AI)
- Vorherrschende Sprache
- C++
- Sterne
- 127k
- Forks
- 25.3k
- PR-Merge-Kennzahlen
- Keine gemergten PRs in 30 T.
Beitragsleitfaden
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus react/react-native
-
Needs: Author Feedback Needs: Repro
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 75/100
react/react-native#58659 · 1 Kommentar ·
-
Needs: Author Feedback Needs: Repro
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 92/100
react/react-native#58621 · 1 Kommentar ·
-
Needs: Author Feedback Needs: Repro
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 85/100
react/react-native#58610 · 1 Kommentar ·
-
Needs: Triage :mag:
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 82/100
react/react-native#58565 · 1 Kommentar · 2 Reaktionen ·
-
Needs: Author Feedback Needs: Repro
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 88/100
react/react-native#58555 · 5 Kommentare · 2 Reaktionen ·
Alle Issues in react/react-native
Ähnliche Issues
-
[CI] Nightly Clang build broken: structured binding captured in a lambda in FileDataSource.cpp Offenbug build
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 91/100
facebookincubator/velox#19194 ·
-
JIT-compiled number -> Decimal conversion silently overflows instead of raising DECIMAL_OVERFLOW Offenfuzz
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 82/100
ClickHouse/ClickHouse#122114 ·
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 84/100
-
module/agent platform/macos type/bug/regression
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 88/100
-
enhancement PyCDE
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100