Repository navigation
Find a Text's clickable spans only when a touch needs them - #59583
Open
hryhoriiK97 wants to merge 2 commits into
Open
hryhoriiK97 wants to merge 2 commits into
hryhoriiK97 wants to merge 2 commits into
Conversation
PreparedLayoutTextView collected the ClickableSpans of its text every time the prepared layout changed, but the list was only checked for being empty, by touch and focus handling. Touch handling now checks whether the text has a ClickableSpan on first use and forgets the answer when the layout changes. Losing focus clears the selection when there is one, which is the state it resets, instead of looking at the spans. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Visual regressionNo visual differences at ios: no capture |
|
@andrewdacenko has imported this pull request. If you are a Meta employee, you can view this in D128363239. |
christophpurrer
approved these changes
Oct 30, 2026
christophpurrer
left a comment
Contributor
There was a problem hiding this comment.
Review automatically exported from Phabricator review in Meta.
andrewdacenko
requested changes
Nov 2, 2026
| fun recycleView() { | ||
| BackgroundStyleApplicator.reset(this) | ||
| overflow = Overflow.VISIBLE | ||
| clickableSpans = emptyList() |
Contributor
There was a problem hiding this comment.
The old code explicitly cleared clickableSpans = emptyList() here. Consider adding hasClickableSpansCache = null for defensive consistency — if a recycled view ever receives a touch before a new preparedLayout is set, the cache would still hold the stale value from the previous text.
Contributor
Author
There was a problem hiding this comment.
@andrewdacenko done in e5873ce: recycleView() now clears hasClickableSpansCache too, and I added a test for it. Thanks!
Setting `preparedLayout` to null already resets it, but clear it explicitly too, like the other per-text state in recycleView(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
andrewdacenko
approved these changes
Nov 4, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary:
PreparedLayoutTextViewcollects theClickableSpans of its text every time its prepared layout changes, which is every time the text is updated: it walks the text withnextSpanTransition, callsgetSpansfor each segment and copies the results into a list. The list itself is never read. Touch handling only checks whether it is empty before hit testing a tap, and focus handling only checks it before clearing the selection.With this change, touch handling checks whether the text has a
ClickableSpanon its first touch after a layout change, with a singlegetSpans, and keeps the answer until the next layout change. Losing focus clears the selection when there is one, which is the state it resets, instead of looking at the spans. Texts that are never touched no longer pay for the scan.This is the main thread work done for every update of a
Textwhose spannable has spans, so it adds up on screens where many texts change often (lists of messages, prices, scores, counters).Changelog:
[ANDROID] [CHANGED] - Find the clickable spans of a Text only when it is touched, instead of on every text update
Test Plan:
PreparedLayoutTextViewClickableSpanTest: tapping aClickableSpanclicks it; after the text is replaced by one without clickable spans a tap no longer clicks; after the text is replaced by one with a clickable span a tap clicks it../gradlew :packages:react-native:ReactAndroid:testDebugUnitTest --tests 'com.facebook.react.views.text.*'passes (104 tests).yarn format-check-kotlinreports no changes.PreparedLayoutTextView.ktidentical tomainat 9bc1a28), without and with this change, installed alternately on a moto g57 power (Android 16). Each install first opens a screen with a static text containing a nested<Text onPress>link and taps the link:onPressfired once on every install of both builds (10 installs). Then a Playground screen of 200 rich text rows (bold, colored, italic and a link per row) where every row changes every 100 ms runs for 30 s, and the main and JS thread CPU time is read from/proc:Three earlier rounds, with a build that differed only in the focus check (which this screen never runs), gave the same result: main thread 4.97 / 5.15 / 4.93 s before and 4.68 / 4.65 / 4.70 s after (−6.8%).
In a simpleperf profile of the same screen before this change, collecting the spans (
nextSpanTransition,getSpansand the list copies in thepreparedLayoutsetter, called fromPreparedLayoutTextViewManager.updateExtraData) was about 5% of the main thread.selection != null(the selection is private).🤖 Generated with Claude Code