Skip to content

Commit a6e0abc

Browse files
NickGerlemanfacebook-github-bot
authored andcommitted
Fix Crash When Measuring Text in Stopped Surface
Summary: `FabricUIManager.prepareTextLayout()` crashes when measuring text for a surface that has already been stopped. It looks up the surface's `ThemedReactContext` via `surfaceMountingManager.getContext()`, which returns `null` after `stopSurface()` completes. The only thing the `Context` is used for downstream is `context.assets` (the `AssetManager`) for custom font resolution. Since `AssetManager` from the application context is equivalent for this purpose (fonts are app-global, not activity-specific), we refactor the API to pass `AssetManager` directly from `mReactApplicationContext`, eliminating the surface lookup entirely. This also makes the API clearer: the prepared layout does not depend on activity context (e.g. for light/dark theme). Changelog: [Android][Fixed] - Fix crash when measuring text in a stopped surface Differential Revision: D95763252
1 parent 41a1941 commit a6e0abc

5 files changed

Lines changed: 30 additions & 29 deletions

File tree

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import androidx.annotation.AnyThread;
2828
import androidx.annotation.Nullable;
2929
import androidx.annotation.UiThread;
30-
import androidx.core.util.Preconditions;
3130
import androidx.core.view.ViewCompat.FocusDirection;
3231
import com.facebook.common.logging.FLog;
3332
import com.facebook.infer.annotation.Assertions;
@@ -650,23 +649,25 @@ public long measureText(
650649
attachmentsPositions);
651650
}
652651

652+
/**
653+
* Prepares a text layout for the given attributed string and paragraph attributes. Uses the
654+
* application context's {@link android.content.res.AssetManager} for custom font resolution,
655+
* avoiding dependency on any specific surface's activity context.
656+
*/
653657
@AnyThread
654658
@ThreadConfined(ANY)
655659
@UnstableReactNativeAPI
656660
public PreparedLayout prepareTextLayout(
657-
int surfaceId,
658661
ReadableMapBuffer attributedString,
659662
ReadableMapBuffer paragraphAttributes,
660663
float minWidth,
661664
float maxWidth,
662665
float minHeight,
663666
float maxHeight) {
664-
SurfaceMountingManager surfaceMountingManager =
665-
mMountingManager.getSurfaceManagerEnforced(surfaceId, "prepareTextLayout");
666667
ViewManager textViewManager = mViewManagerRegistry.get(ReactTextViewManager.REACT_CLASS);
667668

668669
return TextLayoutManager.createPreparedLayout(
669-
Preconditions.checkNotNull(surfaceMountingManager.getContext()),
670+
mReactApplicationContext.getAssets(),
670671
attributedString,
671672
paragraphAttributes,
672673
getYogaSize(minWidth, maxWidth),

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ public constructor(
157157
state.getMapBuffer(TX_STATE_KEY_PARAGRAPH_ATTRIBUTES.toInt())
158158
val spanned: Spannable =
159159
TextLayoutManager.getOrCreateSpannableForText(
160-
view.context,
160+
view.context.assets,
161161
attributedString,
162162
reactTextViewManagerCallback,
163163
)

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.kt

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package com.facebook.react.views.text
99

1010
import android.content.Context
11+
import android.content.res.AssetManager
1112
import android.graphics.Color
1213
import android.graphics.Typeface
1314
import android.os.Build
@@ -217,7 +218,7 @@ internal object TextLayoutManager {
217218
}
218219

219220
private fun buildSpannableFromFragments(
220-
context: Context,
221+
assetManager: AssetManager,
221222
fragments: MapBuffer,
222223
sb: SpannableStringBuilder,
223224
ops: MutableList<SetSpanOperation>,
@@ -296,7 +297,7 @@ internal object TextLayoutManager {
296297
textAttributes.fontWeight,
297298
textAttributes.fontFeatureSettings,
298299
textAttributes.fontFamily,
299-
context.assets,
300+
assetManager,
300301
),
301302
)
302303
)
@@ -352,7 +353,7 @@ internal object TextLayoutManager {
352353
)
353354

354355
private fun buildSpannableFromFragmentsOptimized(
355-
context: Context,
356+
assetManager: AssetManager,
356357
fragments: MapBuffer,
357358
outputReactTags: IntArray?,
358359
): Spannable {
@@ -472,7 +473,7 @@ internal object TextLayoutManager {
472473
fragment.props.fontWeight,
473474
fragment.props.fontFeatureSettings,
474475
fragment.props.fontFamily,
475-
context.assets,
476+
assetManager,
476477
),
477478
start,
478479
end,
@@ -528,7 +529,7 @@ internal object TextLayoutManager {
528529
}
529530

530531
fun getOrCreateSpannableForText(
531-
context: Context,
532+
assetManager: AssetManager,
532533
attributedString: MapBuffer,
533534
reactTextViewManagerCallback: ReactTextViewManagerCallback?,
534535
): Spannable {
@@ -539,7 +540,7 @@ internal object TextLayoutManager {
539540
} else {
540541
text =
541542
createSpannableFromAttributedString(
542-
context,
543+
assetManager,
543544
attributedString.getMapBuffer(AS_KEY_FRAGMENTS),
544545
reactTextViewManagerCallback,
545546
null,
@@ -550,13 +551,13 @@ internal object TextLayoutManager {
550551
}
551552

552553
private fun createSpannableFromAttributedString(
553-
context: Context,
554+
assetManager: AssetManager,
554555
fragments: MapBuffer,
555556
reactTextViewManagerCallback: ReactTextViewManagerCallback?,
556557
outputReactTags: IntArray?,
557558
): Spannable {
558559
if (ReactNativeFeatureFlags.enableAndroidTextMeasurementOptimizations()) {
559-
val spannable = buildSpannableFromFragmentsOptimized(context, fragments, outputReactTags)
560+
val spannable = buildSpannableFromFragmentsOptimized(assetManager, fragments, outputReactTags)
560561

561562
reactTextViewManagerCallback?.onPostProcessSpannable(spannable)
562563
return spannable
@@ -568,7 +569,7 @@ internal object TextLayoutManager {
568569
// a new spannable will be wiped out
569570
val ops: MutableList<SetSpanOperation> = ArrayList()
570571

571-
buildSpannableFromFragments(context, fragments, sb, ops, outputReactTags)
572+
buildSpannableFromFragments(assetManager, fragments, sb, ops, outputReactTags)
572573

573574
// TODO T31905686: add support for inline Images
574575
// While setting the Spans on the final text, we also check whether any of them are images.
@@ -740,7 +741,7 @@ internal object TextLayoutManager {
740741
private fun updateTextPaint(
741742
paint: TextPaint,
742743
baseTextAttributes: TextAttributeProps,
743-
context: Context,
744+
assetManager: AssetManager,
744745
) {
745746
if (baseTextAttributes.fontSize != ReactConstants.UNSET) {
746747
paint.textSize = baseTextAttributes.fontSize.toFloat()
@@ -757,7 +758,7 @@ internal object TextLayoutManager {
757758
baseTextAttributes.fontStyle,
758759
baseTextAttributes.fontWeight,
759760
baseTextAttributes.fontFamily,
760-
context.assets,
761+
assetManager,
761762
)
762763
paint.setTypeface(typeface)
763764

@@ -779,23 +780,23 @@ internal object TextLayoutManager {
779780
*/
780781
private fun scratchPaintWithAttributes(
781782
baseTextAttributes: TextAttributeProps,
782-
context: Context,
783+
assetManager: AssetManager,
783784
): TextPaint {
784785
val paint = checkNotNull(textPaintInstance.get())
785786
paint.setTypeface(null)
786787
paint.textSize = 12f
787788
paint.isFakeBoldText = false
788789
paint.textSkewX = 0f
789-
updateTextPaint(paint, baseTextAttributes, context)
790+
updateTextPaint(paint, baseTextAttributes, assetManager)
790791
return paint
791792
}
792793

793794
private fun newPaintWithAttributes(
794795
baseTextAttributes: TextAttributeProps,
795-
context: Context,
796+
assetManager: AssetManager,
796797
): TextPaint {
797798
val paint = TextPaint(TextPaint.ANTI_ALIAS_FLAG)
798-
updateTextPaint(paint, baseTextAttributes, context)
799+
updateTextPaint(paint, baseTextAttributes, assetManager)
799800
return paint
800801
}
801802

@@ -809,15 +810,16 @@ internal object TextLayoutManager {
809810
heightYogaMeasureMode: YogaMeasureMode,
810811
reactTextViewManagerCallback: ReactTextViewManagerCallback?,
811812
): Layout {
812-
val text = getOrCreateSpannableForText(context, attributedString, reactTextViewManagerCallback)
813+
val text =
814+
getOrCreateSpannableForText(context.assets, attributedString, reactTextViewManagerCallback)
813815

814816
val paint: TextPaint
815817
if (attributedString.contains(AS_KEY_CACHE_ID)) {
816818
paint = text.getSpans(0, 0, ReactTextPaintHolderSpan::class.java)[0].textPaint
817819
} else {
818820
val baseTextAttributes =
819821
TextAttributeProps.fromMapBuffer(attributedString.getMapBuffer(AS_KEY_BASE_ATTRIBUTES))
820-
paint = scratchPaintWithAttributes(baseTextAttributes, context)
822+
paint = scratchPaintWithAttributes(baseTextAttributes, context.assets)
821823
}
822824

823825
return createLayout(
@@ -922,7 +924,7 @@ internal object TextLayoutManager {
922924

923925
@JvmStatic
924926
fun createPreparedLayout(
925-
context: Context,
927+
assetManager: AssetManager,
926928
attributedString: ReadableMapBuffer,
927929
paragraphAttributes: ReadableMapBuffer,
928930
width: Float,
@@ -935,7 +937,7 @@ internal object TextLayoutManager {
935937
val reactTags = IntArray(fragments.count)
936938
val text =
937939
createSpannableFromAttributedString(
938-
context,
940+
assetManager,
939941
fragments,
940942
reactTextViewManagerCallback,
941943
reactTags,
@@ -945,7 +947,7 @@ internal object TextLayoutManager {
945947
val result =
946948
createLayout(
947949
text,
948-
newPaintWithAttributes(baseTextAttributes, context),
950+
newPaintWithAttributes(baseTextAttributes, assetManager),
949951
attributedString,
950952
paragraphAttributes,
951953
width,

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1015,7 +1015,7 @@ public open class ReactTextInputManager public constructor() :
10151015

10161016
val spanned =
10171017
TextLayoutManager.getOrCreateSpannableForText(
1018-
view.context,
1018+
view.context.assets,
10191019
attributedString,
10201020
reactTextViewManagerCallback,
10211021
)

packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/android/react/renderer/textlayoutmanager/TextLayoutManager.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,6 @@ TextLayoutManager::PreparedTextLayout TextLayoutManager::prepareLayout(
304304
static auto prepareTextLayout =
305305
jni::findClassStatic("com/facebook/react/fabric/FabricUIManager")
306306
->getMethod<JPreparedLayout::javaobject(
307-
jint,
308307
JReadableMapBuffer::javaobject,
309308
JReadableMapBuffer::javaobject,
310309
jfloat,
@@ -335,7 +334,6 @@ TextLayoutManager::PreparedTextLayout TextLayoutManager::prepareLayout(
335334

336335
return PreparedTextLayout{jni::make_global(prepareTextLayout(
337336
fabricUIManager,
338-
layoutContext.surfaceId,
339337
attributedStringMB.get(),
340338
paragraphAttributesMB.get(),
341339
minimumSize.width,

0 commit comments

Comments
 (0)