SIGN IN SIGN UP
facebook / react-native UNCLAIMED

A framework for building native applications using React

0 0 94 C++

Cache parent lookups during event dispatch (#56853)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/56853

The W3C event-dispatch pipeline (gated on `enableNativeEventTargetEventDispatching`) walks the parent chain of the dispatched target on every event: once in `EventTarget.getEventPath` for the capture/bubble path, plus up to two additional walks in the responder system for touch/scroll/selection events. Each `parentNode` read is a JSI hop into C++ that re-walks the family chain in the current shadow-tree revision, so on event-heavy screens (e.g., a list firing many `onLayout` events during mount) the per-event walk cost adds up.

This change adds a per-instance parent cache that all event-dispatch consumers share. RN host trees are append-only and the shadow tree is stable during dispatch, so once a node is reachable from the dispatch path its parent is permanently stable from that pipeline's point of view. The cache stores the resolved parent in a symbol-keyed slot on the first lookup; subsequent lookups (within the same dispatch and across future dispatches on the same tree) collapse to a property load.

- Add `getEventTargetParent(target)` in `EventTargetInternals` (plus the cache slot and a sentinel for cached nulls).
- Route `EventTarget.getEventPath` through the new utility.
- Route the `parentElement` walks in `ReactNativeResponder` (`getLowestCommonAncestor`, `negotiateResponder` path build, and the `skipSelf` step) through the same utility, with an `instanceof ReadOnlyElement` filter so the responder's element-only invariant is preserved.
- Leave the `parentNode` getter on `ReadOnlyNode` untouched — user-visible reads still take the canonical JSI path and detached-node reads continue to return `null`.
- Add two stable-tree scenarios to `EventDispatching-benchmark-itest.js` (`beforeAll` mounts once, the benchmarked function dispatches per iteration) so the cache win is measurable and any regression in the rebuild-per-iter scenarios is also visible.

## Benchmark results

Ran `yarn fantom EventDispatching-benchmark --benchmarks` from `xplat/js/react-native-github/`, comparing the cache disabled (utility short-circuited to the canonical getter) against the cache enabled. Numbers below are p50 latency; both runs use Hermes-bytecode optimized mode (the default for `*-benchmark-itest.js`).

New stable-tree scenarios with the new pipeline (`enableNativeEventTargetEventDispatching` ON):

| Scenario (depth 50, stable tree)                 | Cache OFF | Cache ON | Improvement         |
| ------------------------------------------------ | --------- | -------- | ------------------- |
| dispatch event, bubbling, handlers on ancestors  | 0.271 ms  | 0.165 ms | 39% faster (1.64×)  |
| dispatch event, no handlers on ancestors         | 0.265 ms  | 0.161 ms | 39% faster (1.65×)  |

Pre-existing rebuild-per-iter scenarios with the new pipeline (no measurable change — cache is empty on each iteration):

| Scenario (flag ON)                    | Cache OFF | Cache ON | Δ |
| ------------------------------------- | --------- | -------- | - |
| flat (1 handler)                      | 0.042 ms  | 0.042 ms | — |
| nested 10 deep (bubbling)             | 0.105 ms  | 0.106 ms | — |
| nested 50 deep (bubbling)             | 0.378 ms  | 0.381 ms | — |
| nested 10 deep (no handlers)          | 0.103 ms  | 0.104 ms | — |
| stopPropagation, nested 10 deep       | 0.089 ms  | 0.091 ms | — |
| render + dispatch, flat               | 0.082 ms  | 0.083 ms | — |

Legacy pipeline (`enableNativeEventTargetEventDispatching` OFF) was unchanged across both runs, confirming the cache change does not leak outside the new pipeline.

Changelog: [Internal]

Reviewed By: andrewdacenko

Differential Revision: D105337953

fbshipit-source-id: cd157aaa34049906de00566a901a87f0e447b644
R
Rubén Norte committed
e705e8d971ff30f2e2c91f5d672dc18dd062be74
Parent: 3b9c581
Committed by meta-codesync[bot] <215208954+meta-codesync[bot]@users.noreply.github.com> on 5/15/2026, 7:23:02 PM