Describe the bug
Synchronous per-cell UITextView TextKit layout blocks the main thread → multi-second iOS AppHangs for messages containing tables.
Summary — Rendering a markdown message with a table allocates and lays out one TextKit 1 UITextView per cell, synchronously on the main thread. Large tables block long enough to trip the iOS AppHang watchdog — app hangs of 10–19s routinely, up to ~85s, including fatal watchdog kills. iOS only, worse on iPad.
Environment — Library 0.6.0. React Native 0.85.3, React 19.2.3, Expo SDK 56, Fabric. (Production captures below were taken on RN 0.83 / Expo 55 builds; the 0.6.0 table-render path is unchanged, so it persists on the current stack.)
Mechanism — two synchronous sub-paths, both must be covered by a fix:
(a) Table render (18.6–19.4s hang):
- [
TableContainerView addTextToCell:data:width:height:]
[UITextView setFrame:] → [NSTextLayoutManager ensureLayoutForBounds:] ← per cell
(b) Shadow-node measure (14.2–15.0s hang):
EnrichedMarkdownShadowNode::measureContent → setupMockEnrichedMarkdown_
→ [EnrichedMarkdown renderMarkdownSynchronously:]
- Cost =
ensureLayoutForBounds: (full typesetting) per cell; scales with cell count × text length.
Already tried (doesn't fix this path) — upgraded to 0.6.0 (helped a separate dealloc storm, not this render); a plain-text fast path (can't cover tables).
Question — Per-cell synchronous TextKit typesetting blocks the main thread (up to ~85s on large tables). What approach would you recommend / accept a PR toward to keep large-table rendering off the synchronous main-thread layout path — a lighter non-UITextView cell renderer, virtualized/lazy layout, or batched typesetting?
Describe the bug
Synchronous per-cell
UITextView TextKitlayout blocks the main thread → multi-second iOS AppHangs for messages containing tables.Summary — Rendering a markdown message with a table allocates and lays out one
TextKit 1 UITextViewper cell, synchronously on the main thread. Large tables block long enough to trip the iOS AppHang watchdog — app hangs of 10–19s routinely, up to ~85s, including fatal watchdog kills. iOS only, worse on iPad.Environment — Library 0.6.0. React Native 0.85.3, React 19.2.3, Expo SDK 56, Fabric. (Production captures below were taken on RN 0.83 / Expo 55 builds; the 0.6.0 table-render path is unchanged, so it persists on the current stack.)
Mechanism — two synchronous sub-paths, both must be covered by a fix:
(a) Table render (18.6–19.4s hang):
TableContainerView addTextToCell:data:width:height:][UITextView setFrame:]→[NSTextLayoutManager ensureLayoutForBounds:]← per cell(b) Shadow-node measure (14.2–15.0s hang):
EnrichedMarkdownShadowNode::measureContent→setupMockEnrichedMarkdown_→
[EnrichedMarkdown renderMarkdownSynchronously:]ensureLayoutForBounds: (full typesetting) per cell; scales with cell count × text length.Already tried (doesn't fix this path) — upgraded to 0.6.0 (helped a separate dealloc storm, not this render); a plain-text fast path (can't cover tables).
Question — Per-cell synchronous TextKit typesetting blocks the main thread (up to ~85s on large tables). What approach would you recommend / accept a PR toward to keep large-table rendering off the synchronous main-thread layout path — a lighter non-UITextView cell renderer, virtualized/lazy layout, or batched typesetting?