Skip to content

Investigate: measurement/clone perf findings from upstream RN text PRs (#59063–#59065) #996

Open

Description

@eszlamczyk

Three upstream React Native performance PRs touch areas close to ours:

None of them change our code paths directly, since we have our own shadow nodes and MeasurementStore. Reading them against our code turned up a few things to look into. None of these have been measured or reproduced yet. They come from reading the code.

1. Android shadow nodes don't keep forceHeightRecalculationCounter_ across clones (same class of bug as #59063)

MarkdownTextShadowNode.h, MarkdownContainerShadowNode.h, MarkdownTextInputShadowNode.h

The clone constructor calls dirtyLayoutIfNeeded() but doesn't copy forceHeightRecalculationCounter_ from the source node, so every clone starts at 0. Once Kotlin has sent a counter above 0, every clone calls dirtyLayout(). That includes the clones Yoga makes of unchanged siblings when a container is laid out again.

Each extra measure costs a JNI call to FabricUIManager.measure, toDynamic(props) of the full markdown and style, and computePropsHash in Kotlin. The cache hit stops it before a StaticLayout is built. Likely worst case: a chat with N markdown messages where one is streaming re-measures the other N−1 on every commit.

iOS already copies the counter (EnrichedMarkdownTextShadowNode.mm).

To investigate: count measureContent calls (or add a trace section) in "N messages, stream one", then copy the counter in the Android clone constructors and compare.

2. The input's first height update may be dropped on Android

InputLayoutManager.kt sends forceHeightRecalculationCounter++ (post-increment), so the first update sends 0. That's the same as the C++ default, so it wouldn't dirty the layout. Everywhere else uses ++counter.

3. Code block height measurement does two passes

CodeBlockContainerView.measureCodeBlockNodeHeight calls Layout.getDesiredWidth (a full measuring pass) only to pick the width of a StaticLayout that doesn't wrap, and then uses only its height. With BREAK_STRATEGY_SIMPLE and no wrapping, the height doesn't depend on the width, so a very large width should give the same height in one pass. This is the idea behind #59064.

4. Add trace sections

We have no tracing on any platform. Sections that check isEnabled would make our work visible in Perfetto and Instruments next to the RN sections from #59065:

  • Kotlin Trace: measureAndCache, parse/render, store
  • C++: the measurement managers
  • iOS os_signpost: ENRMMeasureMarkdownViewFree

This would also give us a way to measure items 1 and 3.

5. Minor (iOS)

On a cache miss, ENRMMeasureMarkdownContent builds MeasurementCacheKey twice. Each build copies the markdown and recomputes computeStyleFingerprint.

Activity

  1. added
    enhancementNew feature or request
    domain: textEnrichedMarkdownText component
    library: react-nativeTasks that are specifically for react-native-enriched-markdown
    on Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain: textEnrichedMarkdownText componentenhancementNew feature or requestlibrary: react-nativeTasks that are specifically for react-native-enriched-markdownplatform: androidAffects the android platformplatform: iosAffects the ios platform

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions