Skip to content

Commit d3d870c

Browse files
NickGerlemanmeta-codesync[bot]
authored andcommitted
Fix Crash When Measuring Text in Stopped Surface (facebook#55995)
Summary: Pull Request resolved: facebook#55995 `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 3b8455c commit d3d870c

File tree

5 files changed

+29
-40
lines changed

5 files changed

+29
-40
lines changed

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

Lines changed: 2 additions & 12 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;
@@ -620,24 +619,18 @@ public long measureText(
620619
float maxHeight,
621620
@Nullable float[] attachmentsPositions) {
622621

623-
ReactContext context;
624622
if (surfaceId > 0) {
625623
SurfaceMountingManager surfaceMountingManager =
626624
mMountingManager.getSurfaceManagerEnforced(surfaceId, "measureText");
627625
if (surfaceMountingManager.isStopped()) {
628626
return 0;
629627
}
630-
context = surfaceMountingManager.getContext();
631-
Assertions.assertNotNull(
632-
context, "Context in SurfaceMountingManager is null. surfaceId: " + surfaceId);
633-
} else {
634-
context = mReactApplicationContext;
635628
}
636629

637630
ViewManager textViewManager = mViewManagerRegistry.get(ReactTextViewManager.REACT_CLASS);
638631

639632
return TextLayoutManager.measureText(
640-
context,
633+
mReactApplicationContext.getAssets(),
641634
attributedString,
642635
paragraphAttributes,
643636
getYogaSize(minWidth, maxWidth),
@@ -654,19 +647,16 @@ public long measureText(
654647
@ThreadConfined(ANY)
655648
@UnstableReactNativeAPI
656649
public PreparedLayout prepareTextLayout(
657-
int surfaceId,
658650
ReadableMapBuffer attributedString,
659651
ReadableMapBuffer paragraphAttributes,
660652
float minWidth,
661653
float maxWidth,
662654
float minHeight,
663655
float maxHeight) {
664-
SurfaceMountingManager surfaceMountingManager =
665-
mMountingManager.getSurfaceManagerEnforced(surfaceId, "prepareTextLayout");
666656
ViewManager textViewManager = mViewManagerRegistry.get(ReactTextViewManager.REACT_CLASS);
667657

668658
return TextLayoutManager.createPreparedLayout(
669-
Preconditions.checkNotNull(surfaceMountingManager.getContext()),
659+
mReactApplicationContext.getAssets(),
670660
attributedString,
671661
paragraphAttributes,
672662
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: 25 additions & 24 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
@@ -217,7 +217,7 @@ internal object TextLayoutManager {
217217
}
218218

219219
private fun buildSpannableFromFragments(
220-
context: Context,
220+
assetManager: AssetManager,
221221
fragments: MapBuffer,
222222
sb: SpannableStringBuilder,
223223
ops: MutableList<SetSpanOperation>,
@@ -296,7 +296,7 @@ internal object TextLayoutManager {
296296
textAttributes.fontWeight,
297297
textAttributes.fontFeatureSettings,
298298
textAttributes.fontFamily,
299-
context.assets,
299+
assetManager,
300300
),
301301
)
302302
)
@@ -352,7 +352,7 @@ internal object TextLayoutManager {
352352
)
353353

354354
private fun buildSpannableFromFragmentsOptimized(
355-
context: Context,
355+
assetManager: AssetManager,
356356
fragments: MapBuffer,
357357
outputReactTags: IntArray?,
358358
): Spannable {
@@ -472,7 +472,7 @@ internal object TextLayoutManager {
472472
fragment.props.fontWeight,
473473
fragment.props.fontFeatureSettings,
474474
fragment.props.fontFamily,
475-
context.assets,
475+
assetManager,
476476
),
477477
start,
478478
end,
@@ -528,7 +528,7 @@ internal object TextLayoutManager {
528528
}
529529

530530
fun getOrCreateSpannableForText(
531-
context: Context,
531+
assetManager: AssetManager,
532532
attributedString: MapBuffer,
533533
reactTextViewManagerCallback: ReactTextViewManagerCallback?,
534534
): Spannable {
@@ -539,7 +539,7 @@ internal object TextLayoutManager {
539539
} else {
540540
text =
541541
createSpannableFromAttributedString(
542-
context,
542+
assetManager,
543543
attributedString.getMapBuffer(AS_KEY_FRAGMENTS),
544544
reactTextViewManagerCallback,
545545
null,
@@ -550,13 +550,13 @@ internal object TextLayoutManager {
550550
}
551551

552552
private fun createSpannableFromAttributedString(
553-
context: Context,
553+
assetManager: AssetManager,
554554
fragments: MapBuffer,
555555
reactTextViewManagerCallback: ReactTextViewManagerCallback?,
556556
outputReactTags: IntArray?,
557557
): Spannable {
558558
if (ReactNativeFeatureFlags.enableAndroidTextMeasurementOptimizations()) {
559-
val spannable = buildSpannableFromFragmentsOptimized(context, fragments, outputReactTags)
559+
val spannable = buildSpannableFromFragmentsOptimized(assetManager, fragments, outputReactTags)
560560

561561
reactTextViewManagerCallback?.onPostProcessSpannable(spannable)
562562
return spannable
@@ -568,7 +568,7 @@ internal object TextLayoutManager {
568568
// a new spannable will be wiped out
569569
val ops: MutableList<SetSpanOperation> = ArrayList()
570570

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

573573
// TODO T31905686: add support for inline Images
574574
// While setting the Spans on the final text, we also check whether any of them are images.
@@ -740,7 +740,7 @@ internal object TextLayoutManager {
740740
private fun updateTextPaint(
741741
paint: TextPaint,
742742
baseTextAttributes: TextAttributeProps,
743-
context: Context,
743+
assetManager: AssetManager,
744744
) {
745745
if (baseTextAttributes.fontSize != ReactConstants.UNSET) {
746746
paint.textSize = baseTextAttributes.fontSize.toFloat()
@@ -757,7 +757,7 @@ internal object TextLayoutManager {
757757
baseTextAttributes.fontStyle,
758758
baseTextAttributes.fontWeight,
759759
baseTextAttributes.fontFamily,
760-
context.assets,
760+
assetManager,
761761
)
762762
paint.setTypeface(typeface)
763763

@@ -779,28 +779,28 @@ internal object TextLayoutManager {
779779
*/
780780
private fun scratchPaintWithAttributes(
781781
baseTextAttributes: TextAttributeProps,
782-
context: Context,
782+
assetManager: AssetManager,
783783
): TextPaint {
784784
val paint = checkNotNull(textPaintInstance.get())
785785
paint.setTypeface(null)
786786
paint.textSize = 12f
787787
paint.isFakeBoldText = false
788788
paint.textSkewX = 0f
789-
updateTextPaint(paint, baseTextAttributes, context)
789+
updateTextPaint(paint, baseTextAttributes, assetManager)
790790
return paint
791791
}
792792

793793
private fun newPaintWithAttributes(
794794
baseTextAttributes: TextAttributeProps,
795-
context: Context,
795+
assetManager: AssetManager,
796796
): TextPaint {
797797
val paint = TextPaint(TextPaint.ANTI_ALIAS_FLAG)
798-
updateTextPaint(paint, baseTextAttributes, context)
798+
updateTextPaint(paint, baseTextAttributes, assetManager)
799799
return paint
800800
}
801801

802802
private fun createLayoutForMeasurement(
803-
context: Context,
803+
assetManager: AssetManager,
804804
attributedString: MapBuffer,
805805
paragraphAttributes: MapBuffer,
806806
width: Float,
@@ -809,15 +809,16 @@ internal object TextLayoutManager {
809809
heightYogaMeasureMode: YogaMeasureMode,
810810
reactTextViewManagerCallback: ReactTextViewManagerCallback?,
811811
): Layout {
812-
val text = getOrCreateSpannableForText(context, attributedString, reactTextViewManagerCallback)
812+
val text =
813+
getOrCreateSpannableForText(assetManager, 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, assetManager)
821822
}
822823

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

923924
@JvmStatic
924925
fun createPreparedLayout(
925-
context: Context,
926+
assetManager: 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+
assetManager,
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, assetManager),
949950
attributedString,
950951
paragraphAttributes,
951952
width,
@@ -1085,7 +1086,7 @@ internal object TextLayoutManager {
10851086

10861087
@JvmStatic
10871088
fun measureText(
1088-
context: Context,
1089+
assetManager: 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+
assetManager,
11021103
attributedString,
11031104
paragraphAttributes,
11041105
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)