Skip to content

Commit 54b4824

Browse files
NickGerlemanfacebook-github-bot
authored andcommitted
Fix Crash When Measuring Text in Stopped Surface (facebook#55995)
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 Reviewed By: javache Differential Revision: D95763252
1 parent 3b8455c commit 54b4824

File tree

6 files changed

+36
-61
lines changed

6 files changed

+36
-61
lines changed

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

Lines changed: 3 additions & 22 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;
@@ -537,7 +536,7 @@ private NativeArray measureLines(
537536

538537
return (NativeArray)
539538
TextLayoutManager.measureLines(
540-
mReactApplicationContext,
539+
mReactApplicationContext.getAssets(),
541540
attributedString,
542541
paragraphAttributes,
543542
PixelUtil.toPixelFromDIP(width),
@@ -611,7 +610,6 @@ public long measure(
611610
@ThreadConfined(ANY)
612611
@UnstableReactNativeAPI
613612
public long measureText(
614-
int surfaceId,
615613
ReadableMapBuffer attributedString,
616614
ReadableMapBuffer paragraphAttributes,
617615
float minWidth,
@@ -620,24 +618,10 @@ public long measureText(
620618
float maxHeight,
621619
@Nullable float[] attachmentsPositions) {
622620

623-
ReactContext context;
624-
if (surfaceId > 0) {
625-
SurfaceMountingManager surfaceMountingManager =
626-
mMountingManager.getSurfaceManagerEnforced(surfaceId, "measureText");
627-
if (surfaceMountingManager.isStopped()) {
628-
return 0;
629-
}
630-
context = surfaceMountingManager.getContext();
631-
Assertions.assertNotNull(
632-
context, "Context in SurfaceMountingManager is null. surfaceId: " + surfaceId);
633-
} else {
634-
context = mReactApplicationContext;
635-
}
636-
637621
ViewManager textViewManager = mViewManagerRegistry.get(ReactTextViewManager.REACT_CLASS);
638622

639623
return TextLayoutManager.measureText(
640-
context,
624+
mReactApplicationContext.getAssets(),
641625
attributedString,
642626
paragraphAttributes,
643627
getYogaSize(minWidth, maxWidth),
@@ -654,19 +638,16 @@ public long measureText(
654638
@ThreadConfined(ANY)
655639
@UnstableReactNativeAPI
656640
public PreparedLayout prepareTextLayout(
657-
int surfaceId,
658641
ReadableMapBuffer attributedString,
659642
ReadableMapBuffer paragraphAttributes,
660643
float minWidth,
661644
float maxWidth,
662645
float minHeight,
663646
float maxHeight) {
664-
SurfaceMountingManager surfaceMountingManager =
665-
mMountingManager.getSurfaceManagerEnforced(surfaceId, "prepareTextLayout");
666647
ViewManager textViewManager = mViewManagerRegistry.get(ReactTextViewManager.REACT_CLASS);
667648

668649
return TextLayoutManager.createPreparedLayout(
669-
Preconditions.checkNotNull(surfaceMountingManager.getContext()),
650+
mReactApplicationContext.getAssets(),
670651
attributedString,
671652
paragraphAttributes,
672653
getYogaSize(minWidth, maxWidth),

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77

88
package com.facebook.react.views.text
99

10-
import android.content.Context
1110
import android.graphics.Rect
1211
import android.text.Layout
1312
import android.text.TextPaint
13+
import android.util.DisplayMetrics
1414
import com.facebook.react.bridge.Arguments
1515
import com.facebook.react.bridge.WritableArray
1616
import com.facebook.react.bridge.buildReadableMap
@@ -22,8 +22,7 @@ internal object FontMetricsUtil {
2222
private const val AMPLIFICATION_FACTOR = 100f
2323

2424
@JvmStatic
25-
fun getFontMetrics(text: CharSequence, layout: Layout, context: Context): WritableArray {
26-
val dm = context.resources.displayMetrics
25+
fun getFontMetrics(text: CharSequence, layout: Layout, dm: DisplayMetrics): WritableArray {
2726
val lines = Arguments.createArray()
2827

2928
// To calculate xHeight and capHeight we have to render an "x" and "T" and manually measure

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: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
package com.facebook.react.views.text
99

10-
import android.content.Context
10+
import android.content.res.AssetManager
1111
import android.graphics.Color
1212
import android.graphics.Typeface
1313
import android.os.Build
@@ -31,6 +31,7 @@ import com.facebook.react.common.ReactConstants
3131
import com.facebook.react.common.mapbuffer.MapBuffer
3232
import com.facebook.react.common.mapbuffer.ReadableMapBuffer
3333
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
34+
import com.facebook.react.uimanager.DisplayMetricsHolder
3435
import com.facebook.react.uimanager.PixelUtil
3536
import com.facebook.react.uimanager.PixelUtil.dpToPx
3637
import com.facebook.react.uimanager.PixelUtil.pxToDp
@@ -217,7 +218,7 @@ internal object TextLayoutManager {
217218
}
218219

219220
private fun buildSpannableFromFragments(
220-
context: Context,
221+
assets: 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+
assets,
300301
),
301302
)
302303
)
@@ -352,7 +353,7 @@ internal object TextLayoutManager {
352353
)
353354

354355
private fun buildSpannableFromFragmentsOptimized(
355-
context: Context,
356+
assets: 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+
assets,
476477
),
477478
start,
478479
end,
@@ -528,7 +529,7 @@ internal object TextLayoutManager {
528529
}
529530

530531
fun getOrCreateSpannableForText(
531-
context: Context,
532+
assets: 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+
assets,
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+
assets: 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(assets, 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(assets, 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+
assets: 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+
assets,
761762
)
762763
paint.setTypeface(typeface)
763764

@@ -779,28 +780,28 @@ internal object TextLayoutManager {
779780
*/
780781
private fun scratchPaintWithAttributes(
781782
baseTextAttributes: TextAttributeProps,
782-
context: Context,
783+
assets: 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, assets)
790791
return paint
791792
}
792793

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

802803
private fun createLayoutForMeasurement(
803-
context: Context,
804+
assets: AssetManager,
804805
attributedString: MapBuffer,
805806
paragraphAttributes: MapBuffer,
806807
width: Float,
@@ -809,15 +810,15 @@ internal object TextLayoutManager {
809810
heightYogaMeasureMode: YogaMeasureMode,
810811
reactTextViewManagerCallback: ReactTextViewManagerCallback?,
811812
): Layout {
812-
val text = getOrCreateSpannableForText(context, attributedString, reactTextViewManagerCallback)
813+
val text = getOrCreateSpannableForText(assets, attributedString, reactTextViewManagerCallback)
813814

814815
val paint: TextPaint
815816
if (attributedString.contains(AS_KEY_CACHE_ID)) {
816817
paint = text.getSpans(0, 0, ReactTextPaintHolderSpan::class.java)[0].textPaint
817818
} else {
818819
val baseTextAttributes =
819820
TextAttributeProps.fromMapBuffer(attributedString.getMapBuffer(AS_KEY_BASE_ATTRIBUTES))
820-
paint = scratchPaintWithAttributes(baseTextAttributes, context)
821+
paint = scratchPaintWithAttributes(baseTextAttributes, assets)
821822
}
822823

823824
return createLayout(
@@ -922,7 +923,7 @@ internal object TextLayoutManager {
922923

923924
@JvmStatic
924925
fun createPreparedLayout(
925-
context: Context,
926+
assets: AssetManager,
926927
attributedString: ReadableMapBuffer,
927928
paragraphAttributes: ReadableMapBuffer,
928929
width: Float,
@@ -935,7 +936,7 @@ internal object TextLayoutManager {
935936
val reactTags = IntArray(fragments.count)
936937
val text =
937938
createSpannableFromAttributedString(
938-
context,
939+
assets,
939940
fragments,
940941
reactTextViewManagerCallback,
941942
reactTags,
@@ -945,7 +946,7 @@ internal object TextLayoutManager {
945946
val result =
946947
createLayout(
947948
text,
948-
newPaintWithAttributes(baseTextAttributes, context),
949+
newPaintWithAttributes(baseTextAttributes, assets),
949950
attributedString,
950951
paragraphAttributes,
951952
width,
@@ -1085,7 +1086,7 @@ internal object TextLayoutManager {
10851086

10861087
@JvmStatic
10871088
fun measureText(
1088-
context: Context,
1089+
assets: AssetManager,
10891090
attributedString: MapBuffer,
10901091
paragraphAttributes: MapBuffer,
10911092
width: Float,
@@ -1098,7 +1099,7 @@ internal object TextLayoutManager {
10981099
// TODO(5578671): Handle text direction (see View#getTextDirectionHeuristic)
10991100
val layout =
11001101
createLayoutForMeasurement(
1101-
context,
1102+
assets,
11021103
attributedString,
11031104
paragraphAttributes,
11041105
width,
@@ -1355,7 +1356,7 @@ internal object TextLayoutManager {
13551356

13561357
@JvmStatic
13571358
fun measureLines(
1358-
context: Context,
1359+
assetManager: AssetManager,
13591360
attributedString: MapBuffer,
13601361
paragraphAttributes: MapBuffer,
13611362
width: Float,
@@ -1364,7 +1365,7 @@ internal object TextLayoutManager {
13641365
): WritableArray {
13651366
val layout =
13661367
createLayoutForMeasurement(
1367-
context,
1368+
assetManager,
13681369
attributedString,
13691370
paragraphAttributes,
13701371
width,
@@ -1373,7 +1374,8 @@ internal object TextLayoutManager {
13731374
YogaMeasureMode.EXACTLY,
13741375
reactTextViewManagerCallback,
13751376
)
1376-
return FontMetricsUtil.getFontMetrics(layout.text, layout, context)
1377+
return FontMetricsUtil.getFontMetrics(
1378+
layout.text, layout, DisplayMetricsHolder.getWindowDisplayMetrics())
13771379
}
13781380

13791381
private fun isBoring(text: Spannable, paint: TextPaint): BoringLayout.Metrics? =

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
)

0 commit comments

Comments
 (0)