react / react/react-native

[iOS][Fabric] Modal content vanishes from the accessibility tree on repeated present/dismiss cycles (renders on screen, invisible to XCUITest/VoiceOver; dev builds)

Abierto
#58,081 0 comentarios 1 reacción 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

Lenguaje dominante
C++
Estrellas
127k
Forks
25.3k
Merge medio
1 d 23 h
PR fusionados (30 d)
4

Descripción

Description

On the new architecture (Fabric), after one successful present/dismiss cycle of a <Modal transparent animationType="slide">, the next presentation renders on screen but registers nothing in the accessibility tree — the AX tree contains only status-bar/system elements. Touch still works: tapping the modal's close button by coordinates dismisses it and fully restores the accessibility tree; presenting again after that heal is blind again.

Reproduced in a bare community-CLI template (no Expo, no navigation library, one Modal), on both 0.86.2 and 0.87.0, using an XCUITest-based driver (Maestro) as the accessibility ground truth:

  • Fresh launch → presentation 1 fully accessible → presentation 2 blind. Deterministic across app relaunches (Debug builds).
  • maestro hierarchy in the blind state shows only the status bar (time, battery, cellular) and the app root — no modal subtree, no base-screen elements.
  • Dismissing by coordinate tap restores everything; the next presentation is blind again.
  • Build-mode asymmetry: Debug reproduces on the 2nd presentation every time; Release stayed clean for 40 cycles on the same app/simulator.

The failure state lives in the app process, not the test driver:

  • Each open/close cycle above runs in its own maestro test invocation (fresh driver process each time) and the poison persists across them.
  • In the production app where we first hit this (larger surface, several modal hosts), a fresh XCUITest runner still sees the blind tree, and killing/respawning testmanagerd mid-blindness changes nothing. There the trigger index varied slightly (2nd–3rd presentation, including cross-modal-host sequences: sheet ✓ → menu ✗, and menu ✓ menu ✓ → sheet ✗), so the exact index looks timing-sensitive; the minimal app settles on "2nd presentation".
  • The unified log shows correctly paired kAXUserTestingNotification payloads (RCTFabricModalHostViewController ViewDidAppear ↔ ViewDidDisappear) for every presentation including the blind ones — the modal VC lifecycle looks healthy while AX registration is missing.
  • Waiting 45s in the blind state does not self-heal; only dismissal heals.
  • AccessibilityInfo.setAccessibilityFocus on the modal content in onShow does not prevent it.

Impact: for assistive-technology users on dev builds, a blind modal's close control is not an accessibility element, so there is no announced way out (we could not script VoiceOver on the simulator to confirm end-to-end; the tree evidence above is from XCUITest snapshots, which share the AX infrastructure). For teams running XCUITest/Maestro/Appium suites against dev builds, any test past the second modal presentation fails on missing elements. Since Release builds did not reproduce in our runs, production users are plausibly unaffected — but we have not found what dev-mode dependency gates the bug, so we can't say that with confidence.

Possibly related, but describing different symptoms: #50152 (invisible undismissed modal layer), #48611, #49717.

Steps to reproduce
  1. Clone https://github.com/hubyrod/rn-modal-a11y-repro (bare cli init template + one App.tsx: a button presenting a transparent slide-animation Modal with a close button; Maestro flows included).
  2. npm install && cd ios && pod install && cd ..
  3. npx react-native run-ios --mode Debug (iOS simulator).
  4. maestro test cycle.yaml — passes (open → close via accessibility taps).
  5. maestro test cycle.yaml again — fails: the modal is visibly presented but "CLOSE" is not in the accessibility tree. maestro hierarchy shows only status-bar elements.
  6. maestro test close-by-point.yaml (coordinate tap) — the modal dismisses and the tree is restored. maestro test open-only.yaml — blind again.
React Native Version

0.86.2 (also reproduced identically on a fresh 0.87.0 template)

Affected Platforms

Runtime - iOS, Build - MacOS

Output of npx react-native info
System:
  OS: macOS 26.6.2
  CPU: (14) arm64 Apple M4 Pro
  Memory: 3.53 GB / 48.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 22.14.0
    path: /Users/hugo/.nvm/versions/node/v22.14.0/bin/node
  Yarn:
    version: 1.22.22
    path: /opt/homebrew/bin/yarn
  npm:
    version: 11.3.0
    path: /Users/hugo/.nvm/versions/node/v22.14.0/bin/npm
  Watchman: Not Found
Managers:
  CocoaPods:
    version: 1.17.0
    path: /opt/homebrew/bin/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 25.5
      - iOS 26.5
      - macOS 26.5
      - tvOS 26.5
      - visionOS 26.5
      - watchOS 26.5
  Android SDK: Not Found
IDEs:
  Android Studio: 2025.2 AI-252.28238.7.2523.14688667
  Xcode:
    version: 26.6/17F113
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.20
    path: /usr/bin/javac
  Ruby:
    version: 4.0.6
    path: /opt/homebrew/bin/ruby
npmPackages:
  "@react-native-community/cli":
    installed: 20.1.0
    wanted: 20.1.0
  react:
    installed: 19.2.3
    wanted: 19.2.3
  react-native:
    installed: 0.86.2
    wanted: 0.86.2
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: true
iOS:
  hermesEnabled: true
  newArchEnabled: true

Simulator: iPhone 17, iOS 26.2 runtime. AX probe: Maestro 2.4.0 (XCUITest driver).

Stacktrace or Logs
No crash. Accessibility hierarchy in the blind state (modal visibly presented,
screenshot available) reduces to status-bar/system elements only:

  "accessibilityText" : "02:13"                — status bar time
  "accessibilityText" : "100 % battery power"  — status bar
  "accessibilityText" : "Cellular"             — status bar
  "accessibilityText" : "ModalA11yRepro"       — app root node (no children with content)

After a coordinate-tap dismissal, the same probe returns the full tree
(OPEN button, counter text). Unified log shows paired
kAXUserTestingNotification payloads from RCTFabricModalHostViewController
(ViewDidAppear/ViewDidDisappear) for blind presentations as well.
Reproducer

https://github.com/hubyrod/rn-modal-a11y-repro

Screenshots and Videos

Screenshot of the blind state (modal fully rendered, presentations: 2) alongside the status-bar-only hierarchy dump is available on request — the reproducer regenerates both in under a minute.

Guía de contribución

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Línea de trabajo

Comienza con el rn-modal-a11y-repro enlazado: ejecuta cycle.yaml en una compilación Debug iOS y compara la primera y la segunda presentación con la jerarquía de Maestro. Después, sigue el ciclo de vida del modal de Fabric alrededor de RCTFabricModalHostViewController y las notificaciones de accesibilidad notificadas; se considera terminado cuando los ciclos repetidos de presentación/cierre mantienen el modal y los elementos de la pantalla base en el árbol de accesibilidad, incluido el flujo existente impulsado por la accesibilidad.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
react-native
Área
accessibility, mobile
Tipo de issue
Error
Dificultad
4/5
Tiempo estimado
3-5 días
Estado de actividad
Activo
Claridad
Bastante claro
Aptitud para principiantes
52/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.