Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions patches/react-native/pr-59748-1e10181fc9.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.kt b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.kt
index e6c12f6..4541ff3 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.kt
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.kt
@@ -995,7 +995,8 @@ public open class ReactAccessibilityDelegate( // The View this delegate is attac
isSpeakingNode(childNodeInfo, child) &&
!isAccessibilityFocusable(childNodeInfo, child)
) {
- val childNodeDescription = getTalkbackDescription(child, null)
+ // Initializing this node again can recursively synthesize its entire subtree again.
+ val childNodeDescription = getTalkbackDescription(child, childNodeInfo)
if (!childNodeDescription.isNullOrEmpty()) {
concatChildDescription.append(childNodeDescription.toString() + delimiter)
}
29 changes: 29 additions & 0 deletions patches/react-native/pr-59748-1e10181fc9.patch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## ReactAccessibilityDelegate.kt Patch - Content Capture rebuilds accessibility subtrees on the UI thread

On Pixels running Android 16 and 17, Content Capture builds an
`AccessibilityNodeInfo` for every view that appears, on the UI thread, whether
or not an accessibility service is on. Every app gets a session through
Android's scam detection. For React Native views this goes through
`ReactAccessibilityDelegate.getTalkbackDescription`, which initializes each
child's node to check whether it speaks, then calls itself with `null` for that
child. The recursive call builds and initializes the same node again, all the
way down the subtree, so the work grows exponentially with nesting depth.

Upstream issue, with a public repro and profiles:
https://github.com/react/react-native/issues/59743 (100–280ms freezes when a
screen re-appears). We found the same cost while profiling the Home feed's list
on a Pixel 7a (APP-3833): Content Capture was about 20–25% of the main thread
while scrolling, and RN's text accessibility delegate alone about 14%.

Backport of https://github.com/react/react-native/pull/59748: pass the node the
loop already initialized instead of `null`. In 0.88.1,
`createNodeInfoFromView` is exactly `AccessibilityNodeInfoCompat.obtain()` plus
`ViewCompat.onInitializeAccessibilityNodeInfo`, which is what the loop just did
for `childNodeInfo`. `getTalkbackDescription` copies the node it is given with
`obtain(info)` and never recycles it, so descriptions are unchanged. The PR's
test fixture of 8 nested groups goes from 511 node initializations to 9.

The PR is open and unreviewed; the hash in the file name is its head commit,
`1e10181fc9041e7e0dfc5810d31ed31731675646`.

**TODO: Remove once a React Native release contains #59748.**
14 changes: 14 additions & 0 deletions patches/react-native@0.88.1.patch
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,20 @@ index 0a1ea2abc558505f734b584066feef723bb93c00..97d829a24760741d1a3d8cbfa5c6b4c8
}
}
into(hermesDir)
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.kt b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.kt
index e6c12f6368e4d43aae88b594386ec1b5b10ab43a..4541ff34f8fc41011064fa681937efa3e9a1acb0 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.kt
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.kt
@@ -995,7 +995,8 @@ public open class ReactAccessibilityDelegate( // The View this delegate is attac
isSpeakingNode(childNodeInfo, child) &&
!isAccessibilityFocusable(childNodeInfo, child)
) {
- val childNodeDescription = getTalkbackDescription(child, null)
+ // Initializing this node again can recursively synthesize its entire subtree again.
+ val childNodeDescription = getTalkbackDescription(child, childNodeInfo)
if (!childNodeDescription.isNullOrEmpty()) {
concatChildDescription.append(childNodeDescription.toString() + delimiter)
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt
index ae2e36ca1ed64d9f6cb44e6c2519a17fcca3ca18..0eeeb63b1225a3c575abb257a79aaef609659e2f 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt
Expand Down
Loading
Loading