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)

Đang mở
#58,081 0 bình luận 1 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Ngôn ngữ chính
C++
Star
127k
Fork
25.3k
Merge trung bình
1 ngày 23 giờ
Pull request đã merge (30 ngày)
4

Mô tả

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.

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu với rn-modal-a11y-repro được liên kết: chạy cycle.yaml trong một bản build Debug iOS và so sánh lần hiển thị thứ nhất với lần hiển thị thứ hai bằng Maestro hierarchy. Sau đó, theo dõi vòng đời modal của Fabric xung quanh RCTFabricModalHostViewController và các thông báo accessibility đã được báo cáo; được xem là hoàn thành khi các chu kỳ present/dismiss lặp lại vẫn giữ modal và các phần tử của màn hình nền trong accessibility tree, bao gồm cả flow hiện có được điều khiển bởi accessibility.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
react-native
Lĩnh vực
accessibility, mobile
Loại issue
Lỗi
Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức độ hoạt động
Sôi nổi
Độ rõ ràng
Khá rõ ràng
Mức phù hợp với người mới
52/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.