diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 2872a11d5ba..5d6870bc1e2 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -7663,6 +7663,7 @@ public class com/facebook/react/views/text/ReactTextView : androidx/appcompat/wi protected fun onDraw (Landroid/graphics/Canvas;)V public fun onFinishTemporaryDetach ()V protected fun onLayout (ZIIII)V + protected fun onMeasure (II)V public fun onStartTemporaryDetach ()V public fun reactTagForTouch (FF)I public fun setAdjustFontSizeToFit (Z)V diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/SystraceSection.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/SystraceSection.kt new file mode 100644 index 00000000000..2c43f2658ac --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/SystraceSection.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.internal + +import com.facebook.systrace.Systrace + +/** + * Helper to guarantee firing Systrace begin and end markers around a try with resources statement. + */ +public class SystraceSection(sectionName: String) : AutoCloseable { + init { + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, sectionName) + } + + override fun close() { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE) + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java index b76400468be..7f0752c3896 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java @@ -35,6 +35,7 @@ import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.facebook.react.common.ReactConstants; +import com.facebook.react.internal.SystraceSection; import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags; import com.facebook.react.uimanager.BackgroundStyleApplicator; import com.facebook.react.uimanager.LengthPercentage; @@ -375,85 +376,96 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie @Override protected void onDraw(Canvas canvas) { - if (mAdjustsFontSizeToFit && getSpanned() != null && mShouldAdjustSpannableFontSize) { - mShouldAdjustSpannableFontSize = false; - TextLayoutManager.adjustSpannableFontToFit( - getSpanned(), - getWidth(), - YogaMeasureMode.EXACTLY, - getHeight(), - YogaMeasureMode.EXACTLY, - mMinimumFontSize, - mNumberOfLines, - getIncludeFontPadding(), - getBreakStrategy(), - getHyphenationFrequency(), - // always passing ALIGN_NORMAL here should be fine, since this method doesn't depend on - // how exacly lines are aligned, just their width - Layout.Alignment.ALIGN_NORMAL); - setText(getSpanned()); - } - - if (ReactNativeFeatureFlags.enableBackgroundStyleApplicator()) { - if (mOverflow != Overflow.VISIBLE) { - BackgroundStyleApplicator.clipToPaddingBox(this, canvas); + try (SystraceSection s = new SystraceSection("ReactTextView.onDraw")) { + if (mAdjustsFontSizeToFit && getSpanned() != null && mShouldAdjustSpannableFontSize) { + mShouldAdjustSpannableFontSize = false; + TextLayoutManager.adjustSpannableFontToFit( + getSpanned(), + getWidth(), + YogaMeasureMode.EXACTLY, + getHeight(), + YogaMeasureMode.EXACTLY, + mMinimumFontSize, + mNumberOfLines, + getIncludeFontPadding(), + getBreakStrategy(), + getHyphenationFrequency(), + // always passing ALIGN_NORMAL here should be fine, since this method doesn't depend on + // how exacly lines are aligned, just their width + Layout.Alignment.ALIGN_NORMAL); + setText(getSpanned()); } - } else { - mReactBackgroundManager.maybeClipToPaddingBox(canvas); - } - super.onDraw(canvas); + if (ReactNativeFeatureFlags.enableBackgroundStyleApplicator()) { + if (mOverflow != Overflow.VISIBLE) { + BackgroundStyleApplicator.clipToPaddingBox(this, canvas); + } + } else { + mReactBackgroundManager.maybeClipToPaddingBox(canvas); + } + + super.onDraw(canvas); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + try (SystraceSection s = new SystraceSection("ReactTextView.onMeasure")) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } } public void setText(ReactTextUpdate update) { - mContainsImages = update.containsImages(); - // Android's TextView crashes when it tries to relayout if LayoutParams are - // null; explicitly set the LayoutParams to prevent this crash. See: - // https://github.com/facebook/react-native/pull/7011 - if (getLayoutParams() == null) { - setLayoutParams(EMPTY_LAYOUT_PARAMS); - } - Spannable spannable = update.getText(); - if (mLinkifyMaskType > 0) { - Linkify.addLinks(spannable, mLinkifyMaskType); - setMovementMethod(LinkMovementMethod.getInstance()); - } - setText(spannable); - float paddingLeft = update.getPaddingLeft(); - float paddingTop = update.getPaddingTop(); - float paddingRight = update.getPaddingRight(); - float paddingBottom = update.getPaddingBottom(); - - // In Fabric padding is set by the update of Layout Metrics and not as part of the "setText" - // operation - // TODO T56559197: remove this condition when we migrate 100% to Fabric - if (paddingLeft != ReactConstants.UNSET - && paddingTop != ReactConstants.UNSET - && paddingRight != ReactConstants.UNSET - && paddingBottom != ReactConstants.UNSET) { - - setPadding( - (int) Math.floor(paddingLeft), - (int) Math.floor(paddingTop), - (int) Math.floor(paddingRight), - (int) Math.floor(paddingBottom)); - } - - int nextTextAlign = update.getTextAlign(); - if (nextTextAlign != getGravityHorizontal()) { - setGravityHorizontal(nextTextAlign); - } - if (getBreakStrategy() != update.getTextBreakStrategy()) { - setBreakStrategy(update.getTextBreakStrategy()); - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - if (getJustificationMode() != update.getJustificationMode()) { - setJustificationMode(update.getJustificationMode()); + try (SystraceSection s = new SystraceSection("ReactTextView.setText(ReactTextUpdate)")) { + mContainsImages = update.containsImages(); + // Android's TextView crashes when it tries to relayout if LayoutParams are + // null; explicitly set the LayoutParams to prevent this crash. See: + // https://github.com/facebook/react-native/pull/7011 + if (getLayoutParams() == null) { + setLayoutParams(EMPTY_LAYOUT_PARAMS); } - } + Spannable spannable = update.getText(); + if (mLinkifyMaskType > 0) { + Linkify.addLinks(spannable, mLinkifyMaskType); + setMovementMethod(LinkMovementMethod.getInstance()); + } + setText(spannable); + float paddingLeft = update.getPaddingLeft(); + float paddingTop = update.getPaddingTop(); + float paddingRight = update.getPaddingRight(); + float paddingBottom = update.getPaddingBottom(); - // Ensure onLayout is called so the inline views can be repositioned. - requestLayout(); + // In Fabric padding is set by the update of Layout Metrics and not as part of the "setText" + // operation + // TODO T56559197: remove this condition when we migrate 100% to Fabric + if (paddingLeft != ReactConstants.UNSET + && paddingTop != ReactConstants.UNSET + && paddingRight != ReactConstants.UNSET + && paddingBottom != ReactConstants.UNSET) { + + setPadding( + (int) Math.floor(paddingLeft), + (int) Math.floor(paddingTop), + (int) Math.floor(paddingRight), + (int) Math.floor(paddingBottom)); + } + + int nextTextAlign = update.getTextAlign(); + if (nextTextAlign != getGravityHorizontal()) { + setGravityHorizontal(nextTextAlign); + } + if (getBreakStrategy() != update.getTextBreakStrategy()) { + setBreakStrategy(update.getTextBreakStrategy()); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (getJustificationMode() != update.getJustificationMode()) { + setJustificationMode(update.getJustificationMode()); + } + } + + // Ensure onLayout is called so the inline views can be repositioned. + requestLayout(); + } } @Override diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java index 424f54fc5be..51955ac0c1b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java @@ -17,6 +17,7 @@ import com.facebook.react.R; import com.facebook.react.common.MapBuilder; import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.common.mapbuffer.MapBuffer; +import com.facebook.react.internal.SystraceSection; import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.uimanager.IViewManagerWithChildren; import com.facebook.react.uimanager.ReactAccessibilityDelegate; @@ -86,24 +87,26 @@ public class ReactTextViewManager @Override public void updateExtraData(ReactTextView view, Object extraData) { - ReactTextUpdate update = (ReactTextUpdate) extraData; - Spannable spannable = update.getText(); - if (update.containsImages()) { - TextInlineImageSpan.possiblyUpdateInlineImageSpans(spannable, view); - } - view.setText(update); + try (SystraceSection s = new SystraceSection("ReactTextViewManager.updateExtraData")) { + ReactTextUpdate update = (ReactTextUpdate) extraData; + Spannable spannable = update.getText(); + if (update.containsImages()) { + TextInlineImageSpan.possiblyUpdateInlineImageSpans(spannable, view); + } + view.setText(update); - // If this text view contains any clickable spans, set a view tag and reset the accessibility - // delegate so that these can be picked up by the accessibility system. - ReactClickableSpan[] clickableSpans = - spannable.getSpans(0, update.getText().length(), ReactClickableSpan.class); + // If this text view contains any clickable spans, set a view tag and reset the accessibility + // delegate so that these can be picked up by the accessibility system. + ReactClickableSpan[] clickableSpans = + spannable.getSpans(0, update.getText().length(), ReactClickableSpan.class); - if (clickableSpans.length > 0) { - view.setTag( - R.id.accessibility_links, - new ReactAccessibilityDelegate.AccessibilityLinks(clickableSpans, spannable)); - ReactAccessibilityDelegate.resetDelegate( - view, view.isFocusable(), view.getImportantForAccessibility()); + if (clickableSpans.length > 0) { + view.setTag( + R.id.accessibility_links, + new ReactAccessibilityDelegate.AccessibilityLinks(clickableSpans, spannable)); + ReactAccessibilityDelegate.resetDelegate( + view, view.isFocusable(), view.getImportantForAccessibility()); + } } } @@ -135,11 +138,13 @@ public class ReactTextViewManager @Override public Object updateState( ReactTextView view, ReactStylesDiffMap props, StateWrapper stateWrapper) { - MapBuffer stateMapBuffer = stateWrapper.getStateDataMapBuffer(); - if (stateMapBuffer != null) { - return getReactTextUpdate(view, props, stateMapBuffer); - } else { - return null; + try (SystraceSection s = new SystraceSection("ReactTextViewManager.updateState")) { + MapBuffer stateMapBuffer = stateWrapper.getStateDataMapBuffer(); + if (stateMapBuffer != null) { + return getReactTextUpdate(view, props, stateMapBuffer); + } else { + return null; + } } }