From dc454c0c0f1ae84c76771ebc6d06bb6e983ba9bc Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Tue, 15 Sep 2026 11:08:34 -0400 Subject: [PATCH 1/2] Name the no-tag sentinel and use it for the hardcoded -1 tags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit React tags are positive; -1 marks the absence of one in several places with a bare literal. kNoTag in ReactPrimitives.h gives the convention a name — mirroring NO_SURFACE_ID on the Android side — and replaces the literals across the renderer: ShadowViewMutation's parentTag default, factories and comparison, the stub view tree (folding its duplicate NO_VIEW_TAG constant into kNoTag), the CppMountItem mirrors on Android, UIManagerViewTransitionDelegate's nativeTag and the view transition fallback that feeds it, and the pointer-events no-override comparisons. --- .../ReactAndroid/src/main/jni/react/fabric/MountItem.cpp | 6 +++--- .../ReactAndroid/src/main/jni/react/fabric/MountItem.h | 2 +- .../ReactCommon/react/renderer/core/ReactPrimitives.h | 5 +++++ .../react/renderer/mounting/ShadowViewMutation.cpp | 6 +++--- .../react/renderer/mounting/ShadowViewMutation.h | 2 +- .../ReactCommon/react/renderer/mounting/stubs/StubView.h | 4 +--- .../react/renderer/mounting/stubs/StubViewTree.cpp | 8 ++++---- .../react/renderer/uimanager/PointerEventsProcessor.cpp | 5 +++-- .../renderer/uimanager/UIManagerViewTransitionDelegate.h | 2 +- .../renderer/viewtransition/ViewTransitionModule.cpp | 2 +- scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api | 1 + scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api | 1 + scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api | 1 + scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api | 1 + scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api | 1 + scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api | 1 + scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api | 1 + scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api | 1 + scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api | 1 + 19 files changed, 32 insertions(+), 19 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/MountItem.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/MountItem.cpp index 48557476cf0c..4b1ff61fe4bc 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/MountItem.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/MountItem.cpp @@ -79,7 +79,7 @@ CppMountItem CppMountItem::UpdateEventEmitterMountItem( const ShadowView& shadowView) { return { .type = CppMountItem::Type::UpdateEventEmitter, - .parentTag = -1, + .parentTag = kNoTag, .oldChildShadowView = {}, .newChildShadowView = shadowView, .index = -1}; @@ -88,7 +88,7 @@ CppMountItem CppMountItem::UpdatePaddingMountItem( const ShadowView& shadowView) { return { .type = CppMountItem::Type::UpdatePadding, - .parentTag = -1, + .parentTag = kNoTag, .oldChildShadowView = {}, .newChildShadowView = shadowView, .index = -1}; @@ -97,7 +97,7 @@ CppMountItem CppMountItem::UpdateOverflowInsetMountItem( const ShadowView& shadowView) { return { .type = CppMountItem::Type::UpdateOverflowInset, - .parentTag = -1, + .parentTag = kNoTag, .oldChildShadowView = {}, .newChildShadowView = shadowView, .index = -1}; diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/MountItem.h b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/MountItem.h index 3384e4df3b4c..0901de24f001 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/MountItem.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/MountItem.h @@ -59,7 +59,7 @@ struct CppMountItem final { #pragma mark - Fields Type type = {Create}; - Tag parentTag = -1; + Tag parentTag = kNoTag; ShadowView oldChildShadowView = {}; ShadowView newChildShadowView = {}; int index = {}; diff --git a/packages/react-native/ReactCommon/react/renderer/core/ReactPrimitives.h b/packages/react-native/ReactCommon/react/renderer/core/ReactPrimitives.h index bedbbad08c73..8df3a9c8b247 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ReactPrimitives.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ReactPrimitives.h @@ -18,6 +18,11 @@ namespace facebook::react { */ using Tag = int32_t; +/* + * Value representing an unset tag. + */ +constexpr Tag kNoTag = -1; + /* * An id of a running Surface instance that is used to refer to the instance. */ diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowViewMutation.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowViewMutation.cpp index 25318623afda..bd146530fb27 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowViewMutation.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowViewMutation.cpp @@ -14,7 +14,7 @@ namespace facebook::react { ShadowViewMutation ShadowViewMutation::CreateMutation(ShadowView shadowView) { return { /* .type = */ Create, - /* .parentTag = */ -1, + /* .parentTag = */ kNoTag, /* .oldChildShadowView = */ {}, /* .newChildShadowView = */ std::move(shadowView), /* .index = */ -1, @@ -24,7 +24,7 @@ ShadowViewMutation ShadowViewMutation::CreateMutation(ShadowView shadowView) { ShadowViewMutation ShadowViewMutation::DeleteMutation(ShadowView shadowView) { return { /* .type = */ Delete, - /* .parentTag = */ -1, + /* .parentTag = */ kNoTag, /* .oldChildShadowView = */ std::move(shadowView), /* .newChildShadowView = */ {}, /* .index = */ -1, @@ -131,7 +131,7 @@ std::vector getDebugProps( mutation.newChildShadowView, options)} : DebugStringConvertibleObject{}, - mutation.parentTag != -1 + mutation.parentTag != kNoTag ? DebugStringConvertibleObject{"parent", getDebugDescription( mutation.parentTag, diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowViewMutation.h b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowViewMutation.h index f74bf8551d08..7782a5a7ef2d 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowViewMutation.h +++ b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowViewMutation.h @@ -94,7 +94,7 @@ struct ShadowViewMutation final { #pragma mark - Fields Type type = {Create}; - Tag parentTag = -1; + Tag parentTag = kNoTag; ShadowView oldChildShadowView = {}; ShadowView newChildShadowView = {}; int index = -1; diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/stubs/StubView.h b/packages/react-native/ReactCommon/react/renderer/mounting/stubs/StubView.h index 2d34fb112bd1..ffd166a0b0dc 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/stubs/StubView.h +++ b/packages/react-native/ReactCommon/react/renderer/mounting/stubs/StubView.h @@ -19,8 +19,6 @@ namespace facebook::react { -static const int NO_VIEW_TAG = -1; - class StubView final { public: using Shared = std::shared_ptr; @@ -42,7 +40,7 @@ class StubView final { LayoutMetrics layoutMetrics; State::Shared state; std::vector children; - Tag parentTag{NO_VIEW_TAG}; + Tag parentTag{kNoTag}; }; bool operator==(const StubView &lhs, const StubView &rhs); diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/stubs/StubViewTree.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/stubs/StubViewTree.cpp index 24e19640bf39..53c2e40a242b 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/stubs/StubViewTree.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mounting/stubs/StubViewTree.cpp @@ -51,7 +51,7 @@ void StubViewTree::mutate(const ShadowViewMutationList& mutations) { for (const auto& mutation : mutations) { switch (mutation.type) { case ShadowViewMutation::Create: { - react_native_assert(mutation.parentTag == -1); + react_native_assert(mutation.parentTag == kNoTag); react_native_assert(mutation.oldChildShadowView == ShadowView{}); react_native_assert(mutation.newChildShadowView.props); auto stubView = std::make_shared(); @@ -81,7 +81,7 @@ void StubViewTree::mutate(const ShadowViewMutationList& mutations) { << "] ##" << std::hash{}(mutation.oldChildShadowView); }); - react_native_assert(mutation.parentTag == -1); + react_native_assert(mutation.parentTag == kNoTag); react_native_assert(mutation.newChildShadowView == ShadowView{}); auto tag = mutation.oldChildShadowView.tag; react_native_assert(hasTag(tag)); @@ -118,7 +118,7 @@ void StubViewTree::mutate(const ShadowViewMutationList& mutations) { << parentTag << "] @" << mutation.index << "(" << parentStubView->children.size() << " children)"; }); - react_native_assert(childStubView->parentTag == NO_VIEW_TAG); + react_native_assert(childStubView->parentTag == kNoTag); react_native_assert( mutation.index >= 0 && parentStubView->children.size() >= @@ -182,7 +182,7 @@ void StubViewTree::mutate(const ShadowViewMutationList& mutations) { static_cast(mutation.index) && parentStubView->children[mutation.index]->tag == childStubView->tag); - childStubView->parentTag = NO_VIEW_TAG; + childStubView->parentTag = kNoTag; parentStubView->children.erase( parentStubView->children.begin() + mutation.index); } diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/PointerEventsProcessor.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/PointerEventsProcessor.cpp index 4dfd3a979e75..1e0626f3080b 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/PointerEventsProcessor.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/PointerEventsProcessor.cpp @@ -389,8 +389,9 @@ void PointerEventsProcessor::processPendingPointerCapture( } auto pendingOverrideTag = - (hasPendingOverride) ? pendingOverride->getTag() : -1; - auto activeOverrideTag = (hasActiveOverride) ? activeOverride->getTag() : -1; + (hasPendingOverride) ? pendingOverride->getTag() : kNoTag; + auto activeOverrideTag = + (hasActiveOverride) ? activeOverride->getTag() : kNoTag; if (hasActiveOverride && activeOverrideTag != pendingOverrideTag) { auto retargeted = retargetPointerEvent(event, *activeOverride, uiManager); diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerViewTransitionDelegate.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerViewTransitionDelegate.h index 4c2482875ff4..53f796a808f3 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerViewTransitionDelegate.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerViewTransitionDelegate.h @@ -52,7 +52,7 @@ class UIManagerViewTransitionDelegate { Float y{0}; Float width{0}; Float height{0}; - Tag nativeTag{-1}; + Tag nativeTag{kNoTag}; }; virtual std::optional getViewTransitionInstance( diff --git a/packages/react-native/ReactCommon/react/renderer/viewtransition/ViewTransitionModule.cpp b/packages/react-native/ReactCommon/react/renderer/viewtransition/ViewTransitionModule.cpp index 57edf2878844..e550342b8373 100644 --- a/packages/react-native/ReactCommon/react/renderer/viewtransition/ViewTransitionModule.cpp +++ b/packages/react-native/ReactCommon/react/renderer/viewtransition/ViewTransitionModule.cpp @@ -554,7 +554,7 @@ ViewTransitionModule::getViewTransitionInstance( auto pseudoElementIt = oldPseudoElementNodes_.find(name); auto nativeTag = pseudoElementIt != oldPseudoElementNodes_.end() ? pseudoElementIt->second->getTag() - : -1; + : kNoTag; return ViewTransitionInstance{ .x = view.layoutMetrics.originFromRoot.x, .y = view.layoutMetrics.originFromRoot.y, diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 6ef9e8fdfd6d..63ef62b545f6 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -482,6 +482,7 @@ constexpr char* const facebook::react::TextLayoutManagerKey; constexpr facebook::react::HighResDuration facebook::react::DEFAULT_DURATION_THRESHOLD; constexpr facebook::react::HighResDuration facebook::react::LONG_TASK_DURATION_THRESHOLD; constexpr facebook::react::ReactNativeVersionType facebook::react::ReactNativeVersion; +constexpr facebook::react::Tag facebook::react::kNoTag; constexpr float facebook::react::kDefaultEpsilon; constexpr size_t facebook::react::EVENT_BUFFER_SIZE; constexpr size_t facebook::react::LONG_TASK_BUFFER_SIZE; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index 7a94e046a082..a17dd79e89b6 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -482,6 +482,7 @@ constexpr char* const facebook::react::TextLayoutManagerKey; constexpr facebook::react::HighResDuration facebook::react::DEFAULT_DURATION_THRESHOLD; constexpr facebook::react::HighResDuration facebook::react::LONG_TASK_DURATION_THRESHOLD; constexpr facebook::react::ReactNativeVersionType facebook::react::ReactNativeVersion; +constexpr facebook::react::Tag facebook::react::kNoTag; constexpr float facebook::react::kDefaultEpsilon; constexpr size_t facebook::react::EVENT_BUFFER_SIZE; constexpr size_t facebook::react::LONG_TASK_BUFFER_SIZE; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index 5881ae10d0f9..fefaf2892d66 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -482,6 +482,7 @@ constexpr char* const facebook::react::TextLayoutManagerKey; constexpr facebook::react::HighResDuration facebook::react::DEFAULT_DURATION_THRESHOLD; constexpr facebook::react::HighResDuration facebook::react::LONG_TASK_DURATION_THRESHOLD; constexpr facebook::react::ReactNativeVersionType facebook::react::ReactNativeVersion; +constexpr facebook::react::Tag facebook::react::kNoTag; constexpr float facebook::react::kDefaultEpsilon; constexpr size_t facebook::react::EVENT_BUFFER_SIZE; constexpr size_t facebook::react::LONG_TASK_BUFFER_SIZE; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index 4cb8f69152e0..891cf3086134 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -3407,6 +3407,7 @@ constexpr char* const facebook::react::TextLayoutManagerKey; constexpr facebook::react::HighResDuration facebook::react::DEFAULT_DURATION_THRESHOLD; constexpr facebook::react::HighResDuration facebook::react::LONG_TASK_DURATION_THRESHOLD; constexpr facebook::react::ReactNativeVersionType facebook::react::ReactNativeVersion; +constexpr facebook::react::Tag facebook::react::kNoTag; constexpr float facebook::react::kDefaultEpsilon; constexpr size_t facebook::react::EVENT_BUFFER_SIZE; constexpr size_t facebook::react::LONG_TASK_BUFFER_SIZE; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index efcf207217fd..3afba2f024d4 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -3399,6 +3399,7 @@ constexpr char* const facebook::react::TextLayoutManagerKey; constexpr facebook::react::HighResDuration facebook::react::DEFAULT_DURATION_THRESHOLD; constexpr facebook::react::HighResDuration facebook::react::LONG_TASK_DURATION_THRESHOLD; constexpr facebook::react::ReactNativeVersionType facebook::react::ReactNativeVersion; +constexpr facebook::react::Tag facebook::react::kNoTag; constexpr float facebook::react::kDefaultEpsilon; constexpr size_t facebook::react::EVENT_BUFFER_SIZE; constexpr size_t facebook::react::LONG_TASK_BUFFER_SIZE; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index fe198bfd356b..7dbc2971df75 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -3407,6 +3407,7 @@ constexpr char* const facebook::react::TextLayoutManagerKey; constexpr facebook::react::HighResDuration facebook::react::DEFAULT_DURATION_THRESHOLD; constexpr facebook::react::HighResDuration facebook::react::LONG_TASK_DURATION_THRESHOLD; constexpr facebook::react::ReactNativeVersionType facebook::react::ReactNativeVersion; +constexpr facebook::react::Tag facebook::react::kNoTag; constexpr float facebook::react::kDefaultEpsilon; constexpr size_t facebook::react::EVENT_BUFFER_SIZE; constexpr size_t facebook::react::LONG_TASK_BUFFER_SIZE; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index e6e84e48c02a..588cc00096be 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -178,6 +178,7 @@ constexpr char* const facebook::react::TextLayoutManagerKey; constexpr facebook::react::HighResDuration facebook::react::DEFAULT_DURATION_THRESHOLD; constexpr facebook::react::HighResDuration facebook::react::LONG_TASK_DURATION_THRESHOLD; constexpr facebook::react::ReactNativeVersionType facebook::react::ReactNativeVersion; +constexpr facebook::react::Tag facebook::react::kNoTag; constexpr float facebook::react::kDefaultEpsilon; constexpr size_t facebook::react::EVENT_BUFFER_SIZE; constexpr size_t facebook::react::LONG_TASK_BUFFER_SIZE; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api index ca7b7c2e26b2..6b46a6ec25ff 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api @@ -178,6 +178,7 @@ constexpr char* const facebook::react::TextLayoutManagerKey; constexpr facebook::react::HighResDuration facebook::react::DEFAULT_DURATION_THRESHOLD; constexpr facebook::react::HighResDuration facebook::react::LONG_TASK_DURATION_THRESHOLD; constexpr facebook::react::ReactNativeVersionType facebook::react::ReactNativeVersion; +constexpr facebook::react::Tag facebook::react::kNoTag; constexpr float facebook::react::kDefaultEpsilon; constexpr size_t facebook::react::EVENT_BUFFER_SIZE; constexpr size_t facebook::react::LONG_TASK_BUFFER_SIZE; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index acb87a6fd70f..155dd1cfa9c5 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -178,6 +178,7 @@ constexpr char* const facebook::react::TextLayoutManagerKey; constexpr facebook::react::HighResDuration facebook::react::DEFAULT_DURATION_THRESHOLD; constexpr facebook::react::HighResDuration facebook::react::LONG_TASK_DURATION_THRESHOLD; constexpr facebook::react::ReactNativeVersionType facebook::react::ReactNativeVersion; +constexpr facebook::react::Tag facebook::react::kNoTag; constexpr float facebook::react::kDefaultEpsilon; constexpr size_t facebook::react::EVENT_BUFFER_SIZE; constexpr size_t facebook::react::LONG_TASK_BUFFER_SIZE; From 110bae87782b3a281a70ced66b02aa92309a84b2 Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Tue, 15 Sep 2026 11:11:50 -0400 Subject: [PATCH 2/2] Process synchronous event beats in the frame that requested them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EventEmitter::experimental_flushSync only requests a beat, processed at the next EventBeat::induce. On iOS the run loop observer that induces the beat runs before Core Animation's commit observer, so a request made from layoutSubviews — inside CA's commit cycle — is only processed one frame later. AppleEventBeat now additionally schedules an induce in the display phase of the current commit cycle. Core Animation runs a commit as layout → display → commit, so a zero-sized layer marked as needing display during layout has its display called after the whole layout pass and before the transaction is committed. The layer is attached to the window of the requesting view: experimental_flushSync carries the tag of the emitting view — cached from its ShadowNodeFamily when the family is attached at creation, before the emitter is published, so reading it takes no lock — through EventDispatcher and EventQueue to EventBeat::requestSynchronous, with kNoTag meaning no view attribution; a no-argument overload keeps unattributed requesters unchanged. AppleEventBeat resolves the tag to the view's window layer through a resolver injected by RCTSurfacePresenter (findComponentViewWithTag: on the mounting registry, a nullable, non-creating, main-thread lookup). The requesting view's window is by definition the root of the layer tree whose layout emitted the request, so the flusher is guaranteed a display phase in the current commit cycle, including for content UIKit mounts in a window of its own, like a full screen modal or LogBox. Requests from several windows in one cycle each dirty their own layer; the first display to fire drains the queue and the rest no-op on the request flag. VirtualView's synchronous flushes get the same targeting through their own emitter. A related fix in EventBeat itself: a synchronous request is no longer stranded behind an already-scheduled asynchronous beat (it would silently lose its this-frame guarantee, and the leftover flag would make an unrelated later beat blocking). AppleEventBeat.cpp becomes .mm for the Objective-C. Covered by new unit tests in EventBeatTest.cpp, which drive the protected induce through a subclass standing in for the platform. The C++ API snapshots are regenerated; the deltas are the requestSynchronous overload pair, EventEmitter::getTag, the resolver type, and the AppleEventBeat constructor and destructor. --- .../React/Fabric/AppleEventBeat.cpp | 31 --- .../React/Fabric/AppleEventBeat.h | 34 +++- .../React/Fabric/AppleEventBeat.mm | 122 ++++++++++++ .../React/Fabric/RCTSurfacePresenter.mm | 11 +- .../react/renderer/core/EventBeat.cpp | 13 +- .../react/renderer/core/EventBeat.h | 13 +- .../react/renderer/core/EventDispatcher.cpp | 4 +- .../react/renderer/core/EventDispatcher.h | 3 +- .../react/renderer/core/EventEmitter.cpp | 9 + .../react/renderer/core/EventEmitter.h | 9 +- .../react/renderer/core/EventQueue.cpp | 4 +- .../react/renderer/core/EventQueue.h | 3 +- .../runtimescheduler/tests/EventBeatTest.cpp | 188 ++++++++++++++++++ .../api-snapshots/ReactAndroidDebugCxx.api | 4 +- .../api-snapshots/ReactAndroidNewarchCxx.api | 4 +- .../api-snapshots/ReactAndroidReleaseCxx.api | 4 +- .../api-snapshots/ReactAppleDebugCxx.api | 10 +- .../api-snapshots/ReactAppleNewarchCxx.api | 10 +- .../api-snapshots/ReactAppleReleaseCxx.api | 10 +- .../api-snapshots/ReactCommonDebugCxx.api | 4 +- .../api-snapshots/ReactCommonNewarchCxx.api | 4 +- .../api-snapshots/ReactCommonReleaseCxx.api | 4 +- 22 files changed, 442 insertions(+), 56 deletions(-) delete mode 100644 packages/react-native/React/Fabric/AppleEventBeat.cpp create mode 100644 packages/react-native/React/Fabric/AppleEventBeat.mm create mode 100644 packages/react-native/ReactCommon/react/renderer/runtimescheduler/tests/EventBeatTest.cpp diff --git a/packages/react-native/React/Fabric/AppleEventBeat.cpp b/packages/react-native/React/Fabric/AppleEventBeat.cpp deleted file mode 100644 index 4a3d533a0cd9..000000000000 --- a/packages/react-native/React/Fabric/AppleEventBeat.cpp +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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. - */ - -#include "AppleEventBeat.h" - -#include - -namespace facebook::react { - -AppleEventBeat::AppleEventBeat( - std::shared_ptr ownerBox, - std::unique_ptr uiRunLoopObserver, - RuntimeScheduler& runtimeScheduler) - : EventBeat(std::move(ownerBox), runtimeScheduler), - uiRunLoopObserver_(std::move(uiRunLoopObserver)) { - uiRunLoopObserver_->setDelegate(this); - uiRunLoopObserver_->enable(); -} - -void AppleEventBeat::activityDidChange( - const RunLoopObserver::Delegate* delegate, - RunLoopObserver::Activity /*activity*/) const noexcept { - react_native_assert(delegate == this); - induce(); -} - -} // namespace facebook::react diff --git a/packages/react-native/React/Fabric/AppleEventBeat.h b/packages/react-native/React/Fabric/AppleEventBeat.h index 256e0f0983ad..4124fc7860aa 100644 --- a/packages/react-native/React/Fabric/AppleEventBeat.h +++ b/packages/react-native/React/Fabric/AppleEventBeat.h @@ -7,10 +7,18 @@ #pragma once +#include +#include +#include + +#import + #include #include #include +@class RCTEventBeatFlusherLayer; + namespace facebook::react { class RuntimeScheduler; @@ -19,13 +27,34 @@ class RuntimeScheduler; * Event beat associated with JavaScript runtime. * The beat is called on `RuntimeExecutor`'s thread induced by the UI thread * event loop. + * + * A synchronous request made while Core Animation is laying out the current + * frame (the run loop observer that induces the beat has already run at that + * point) is additionally induced from the display phase of the same commit + * cycle, so that its effects are mounted before the frame is presented. The + * induce is scheduled on the layer of the requesting view's window — the root + * of the tree Core Animation is laying out when the request is made from + * layout. */ class AppleEventBeat : public EventBeat, public RunLoopObserver::Delegate { public: + /* + * Resolves the layer of the window containing the view with the given tag. + * Called on the main thread; returns nil when the view is not mounted or + * not attached to a window. + */ + using WindowLayerResolver = std::function; + AppleEventBeat( std::shared_ptr ownerBox, std::unique_ptr uiRunLoopObserver, - RuntimeScheduler &RuntimeScheduler); + RuntimeScheduler &RuntimeScheduler, + WindowLayerResolver windowLayerResolver); + + ~AppleEventBeat() override; + + using EventBeat::requestSynchronous; + void requestSynchronous(Tag tag) const override; #pragma mark - RunLoopObserver::Delegate @@ -34,6 +63,9 @@ class AppleEventBeat : public EventBeat, public RunLoopObserver::Delegate { private: std::unique_ptr uiRunLoopObserver_; + WindowLayerResolver windowLayerResolver_; + NSMapTable *layers_; + void (^onDisplay_)(void); }; } // namespace facebook::react diff --git a/packages/react-native/React/Fabric/AppleEventBeat.mm b/packages/react-native/React/Fabric/AppleEventBeat.mm new file mode 100644 index 000000000000..1086c9bc40b8 --- /dev/null +++ b/packages/react-native/React/Fabric/AppleEventBeat.mm @@ -0,0 +1,122 @@ +/* + * 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. + */ + +#include "AppleEventBeat.h" + +#import +#import + +#include + +/* + * A zero-sized layer whose only purpose is to run a callback during the + * display phase of a Core Animation commit. Core Animation processes a commit + * as layout → display → (repeat until stable) → commit, so a layer marked as + * needing display during the layout phase has its `display` called after the + * whole layout pass but before the transaction is committed. + */ +@interface RCTEventBeatFlusherLayer : CALayer +- (instancetype)initWithOnDisplay:(void (^)(void))onDisplay; +@end + +@implementation RCTEventBeatFlusherLayer { + void (^_onDisplay)(void); +} + +- (instancetype)initWithOnDisplay:(void (^)(void))onDisplay +{ + if (self = [super init]) { + _onDisplay = [onDisplay copy]; + self.frame = CGRectZero; + } + return self; +} + +- (void)display +{ + _onDisplay(); +} + +// The layer is not a visual element; never participate in animations. +- (id)actionForKey:(NSString *)event +{ + return nil; +} + +@end + +namespace facebook::react { + +AppleEventBeat::AppleEventBeat(std::shared_ptr ownerBox, + std::unique_ptr uiRunLoopObserver, + RuntimeScheduler &runtimeScheduler, + WindowLayerResolver windowLayerResolver) + : EventBeat(std::move(ownerBox), runtimeScheduler), + uiRunLoopObserver_(std::move(uiRunLoopObserver)), + windowLayerResolver_(std::move(windowLayerResolver)), + layers_([NSMapTable weakToStrongObjectsMapTable]) +{ + std::weak_ptr weakOwner = ownerBox_->owner; + onDisplay_ = ^{ + // The owner (indirectly) retains the event beat; if it is gone, so is + // the beat this induces. + auto owner = weakOwner.lock(); + if (!owner) { + return; + } + this->induce(); + }; + + uiRunLoopObserver_->setDelegate(this); + uiRunLoopObserver_->enable(); +} + +AppleEventBeat::~AppleEventBeat() +{ + // The beat can be destroyed on any thread; layer mutations belong on the + // main thread. The block only retains the layers, and a display happening + // before it executes is made safe by the owner check above. + NSMapTable *layers = layers_; + RCTExecuteOnMainQueue(^{ + for (RCTEventBeatFlusherLayer *layer in layers.objectEnumerator) { + [layer removeFromSuperlayer]; + } + [layers removeAllObjects]; + }); +} + +void AppleEventBeat::requestSynchronous(Tag tag) const +{ + EventBeat::requestSynchronous(tag); + + if (tag == kNoTag || !RCTIsMainQueue()) { + return; + } + CALayer *hostLayer = windowLayerResolver_ ? windowLayerResolver_(tag) : nil; + if (hostLayer == nil) { + return; + } + RCTEventBeatFlusherLayer *layer = [layers_ objectForKey:hostLayer]; + if (layer == nil) { + layer = [[RCTEventBeatFlusherLayer alloc] initWithOnDisplay:onDisplay_]; + [layers_ setObject:layer forKey:hostLayer]; + } + if (layer.superlayer != hostLayer) { + [layer removeFromSuperlayer]; + [hostLayer addSublayer:layer]; + } + [layer setNeedsDisplay]; +} + +void AppleEventBeat::activityDidChange(const RunLoopObserver::Delegate *delegate, + RunLoopObserver::Activity /*activity*/) const noexcept +{ + react_native_assert(delegate == this); + induce(); +} + +} // namespace facebook::react diff --git a/packages/react-native/React/Fabric/RCTSurfacePresenter.mm b/packages/react-native/React/Fabric/RCTSurfacePresenter.mm index 0e4bbe376463..fa91a0896c50 100644 --- a/packages/react-native/React/Fabric/RCTSurfacePresenter.mm +++ b/packages/react-native/React/Fabric/RCTSurfacePresenter.mm @@ -292,11 +292,16 @@ - (RCTScheduler *)_createScheduler toolbox.runtimeExecutor = runtimeExecutor; toolbox.bridgelessBindingsExecutor = _bridgelessBindingsExecutor; - toolbox.eventBeatFactory = - [runtimeScheduler](std::shared_ptr ownerBox) -> std::unique_ptr { + RCTMountingManager *mountingManager = _mountingManager; + toolbox.eventBeatFactory = [runtimeScheduler, + mountingManager](std::shared_ptr ownerBox) -> std::unique_ptr { auto runLoopObserver = std::make_unique(RunLoopObserver::Activity::BeforeWaiting, ownerBox->owner); - return std::make_unique(std::move(ownerBox), std::move(runLoopObserver), *runtimeScheduler); + auto windowLayerResolver = [mountingManager](Tag tag) -> CALayer * { + return [mountingManager.componentViewRegistry findComponentViewWithTag:tag].window.layer; + }; + return std::make_unique( + std::move(ownerBox), std::move(runLoopObserver), *runtimeScheduler, std::move(windowLayerResolver)); }; RCTScheduler *scheduler = [[RCTScheduler alloc] initWithToolbox:toolbox]; diff --git a/packages/react-native/ReactCommon/react/renderer/core/EventBeat.cpp b/packages/react-native/ReactCommon/react/renderer/core/EventBeat.cpp index cdb05f4719fd..e8cf9ffdda29 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/EventBeat.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/EventBeat.cpp @@ -26,6 +26,10 @@ void EventBeat::request() const { } void EventBeat::requestSynchronous() const { + requestSynchronous(kNoTag); +} + +void EventBeat::requestSynchronous(Tag /*tag*/) const { react_native_assert( beatCallback_ && "Unexpected state: EventBeat::setBeatCallback was not called before EventBeat::requestSynchronous."); @@ -53,7 +57,14 @@ void EventBeat::induce() const { isEventBeatRequested_ = false; if (isBeatCallbackScheduled_) { - return; + // An asynchronous beat is already scheduled but has not run yet. A + // synchronous request must not be stranded behind it (it would silently + // lose its this-frame guarantee, and the leftover flag would make an + // unrelated later beat blocking), so it proceeds and processes the queue + // now; the already scheduled beat will simply find an empty queue. + if (!isSynchronousRequested_) { + return; + } } isBeatCallbackScheduled_ = true; diff --git a/packages/react-native/ReactCommon/react/renderer/core/EventBeat.h b/packages/react-native/ReactCommon/react/renderer/core/EventBeat.h index 6f25d5f45c69..b29096537a12 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/EventBeat.h +++ b/packages/react-native/ReactCommon/react/renderer/core/EventBeat.h @@ -9,6 +9,7 @@ #include +#include #include #include #include @@ -110,8 +111,18 @@ class EventBeat { * thread────────────────────┴─────────────────────────┴▶ * Both JS and UI thread are * blocked. + * + * `tag` is the view the request originates from, or `kNoTag` when unknown. + * Platform implementations use it to schedule an induce where that view + * renders, and fall back to their ordinary beat timing without it. */ - virtual void requestSynchronous() const; + virtual void requestSynchronous(Tag tag) const; + + /* + * Convenience for requesters with no view attribution. + */ + void requestSynchronous() const; + /* * The callback will be executed once a consumer (for example EventQueue) diff --git a/packages/react-native/ReactCommon/react/renderer/core/EventDispatcher.cpp b/packages/react-native/ReactCommon/react/renderer/core/EventDispatcher.cpp index 5fa5e6821a51..70f9c63ee77e 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/EventDispatcher.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/EventDispatcher.cpp @@ -37,8 +37,8 @@ void EventDispatcher::dispatchEvent(RawEvent&& rawEvent) const { eventQueue_.enqueueEvent(std::move(rawEvent)); } -void EventDispatcher::experimental_flushSync() const { - eventQueue_.experimental_flushSync(); +void EventDispatcher::experimental_flushSync(Tag tag) const { + eventQueue_.experimental_flushSync(tag); } void EventDispatcher::dispatchStateUpdate( diff --git a/packages/react-native/ReactCommon/react/renderer/core/EventDispatcher.h b/packages/react-native/ReactCommon/react/renderer/core/EventDispatcher.h index 5aaa48364f9e..65ab667e1a01 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/EventDispatcher.h +++ b/packages/react-native/ReactCommon/react/renderer/core/EventDispatcher.h @@ -9,6 +9,7 @@ #include +#include #include #include #include @@ -46,7 +47,7 @@ class EventDispatcher { /* * Experimental API exposed to support EventEmitter::experimental_flushSync. */ - void experimental_flushSync() const; + void experimental_flushSync(Tag tag) const; /* * Dispatches a raw event with asynchronous batched priority. Before the diff --git a/packages/react-native/ReactCommon/react/renderer/core/EventEmitter.cpp b/packages/react-native/ReactCommon/react/renderer/core/EventEmitter.cpp index 2d6ac50e3730..a22692867662 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/EventEmitter.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/EventEmitter.cpp @@ -7,6 +7,8 @@ #include "EventEmitter.h" +#include + #include #include #include @@ -229,8 +231,15 @@ void EventEmitter::setEnabled(bool enabled) { } } +Tag EventEmitter::getTag() const { + return tag_; +} + void EventEmitter::setShadowNodeFamily( std::weak_ptr shadowNodeFamily) { + if (auto family = shadowNodeFamily.lock()) { + tag_ = family->getTag(); + } shadowNodeFamily_ = std::move(shadowNodeFamily); } diff --git a/packages/react-native/ReactCommon/react/renderer/core/EventEmitter.h b/packages/react-native/ReactCommon/react/renderer/core/EventEmitter.h index 8863d9a6f4fe..1f68e044a980 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/EventEmitter.h +++ b/packages/react-native/ReactCommon/react/renderer/core/EventEmitter.h @@ -65,6 +65,12 @@ class EventEmitter { const SharedEventTarget &getEventTarget() const; + /* + * The tag of the view this emitter belongs to, or `kNoTag` when none is + * attached. + */ + Tag getTag() const; + /* * Experimental API that will change in the future. */ @@ -77,7 +83,7 @@ class EventEmitter { } syncFunc(); - eventDispatcher->experimental_flushSync(); + eventDispatcher->experimental_flushSync(getTag()); } /* @@ -134,6 +140,7 @@ class EventEmitter { friend class UIManagerBinding; SharedEventTarget eventTarget_; + Tag tag_{kNoTag}; std::weak_ptr shadowNodeFamily_; EventDispatcher::Weak eventDispatcher_; diff --git a/packages/react-native/ReactCommon/react/renderer/core/EventQueue.cpp b/packages/react-native/ReactCommon/react/renderer/core/EventQueue.cpp index 6e99fd71bb7a..27b4ee16df91 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/EventQueue.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/EventQueue.cpp @@ -86,8 +86,8 @@ void EventQueue::onEnqueue() const { eventBeat_->request(); } -void EventQueue::experimental_flushSync() const { - eventBeat_->requestSynchronous(); +void EventQueue::experimental_flushSync(Tag tag) const { + eventBeat_->requestSynchronous(tag); } void EventQueue::onBeat(jsi::Runtime& runtime) const { diff --git a/packages/react-native/ReactCommon/react/renderer/core/EventQueue.h b/packages/react-native/ReactCommon/react/renderer/core/EventQueue.h index 93fc8119e72d..d67875ac5fd1 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/EventQueue.h +++ b/packages/react-native/ReactCommon/react/renderer/core/EventQueue.h @@ -9,6 +9,7 @@ #include +#include #include #include #include @@ -63,7 +64,7 @@ class EventQueue { /* * Experimental API exposed to support EventEmitter::experimental_flushSync. */ - void experimental_flushSync() const; + void experimental_flushSync(Tag tag) const; protected: /* diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/tests/EventBeatTest.cpp b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/tests/EventBeatTest.cpp new file mode 100644 index 000000000000..40fe0cd0d7f4 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/tests/EventBeatTest.cpp @@ -0,0 +1,188 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "StubQueue.h" + +namespace facebook::react { + +class EventBeatTestFeatureFlags : public ReactNativeFeatureFlagsDefaults { + public: + bool enableBridgelessArchitecture() override { + return true; + } +}; + +/* + * `induce` is protected: production code induces from platform beat + * subclasses. The tests drive it directly, standing in for the platform. + */ +class TestEventBeat : public EventBeat { + public: + using EventBeat::EventBeat; + using EventBeat::induce; +}; + +class EventBeatTest : public testing::Test { + protected: + void SetUp() override { + ReactNativeFeatureFlags::dangerouslyReset(); + ReactNativeFeatureFlags::override( + std::make_unique()); + + runtime_ = facebook::hermes::makeHermesRuntime( + ::hermes::vm::RuntimeConfig::Builder().build()); + stubQueue_ = std::make_unique(); + + RuntimeExecutor runtimeExecutor = + [this]( + std::function&& callback) { + stubQueue_->runOnQueue([this, callback = std::move(callback)]() { + callback(*runtime_); + }); + }; + + runtimeScheduler_ = std::make_unique(runtimeExecutor); + + ownerBox_ = std::make_shared(); + owner_ = std::make_shared(0); + ownerBox_->owner = owner_; + eventBeat_ = std::make_unique(ownerBox_, *runtimeScheduler_); + } + + void TearDown() override { + ReactNativeFeatureFlags::dangerouslyReset(); + } + + std::unique_ptr runtime_; + std::unique_ptr stubQueue_; + std::unique_ptr runtimeScheduler_; + std::shared_ptr ownerBox_; + std::shared_ptr owner_; + std::unique_ptr eventBeat_; +}; + +TEST_F(EventBeatTest, induceWithoutRequestIsNoop) { + int beatCount = 0; + eventBeat_->setBeatCallback([&beatCount](jsi::Runtime& /*runtime*/) { + beatCount++; + }); + + eventBeat_->induce(); + + EXPECT_EQ(beatCount, 0); + EXPECT_EQ(stubQueue_->size(), 0); +} + +TEST_F(EventBeatTest, synchronousRequestIsProcessedAtInduce) { + int beatCount = 0; + eventBeat_->setBeatCallback([&beatCount](jsi::Runtime& /*runtime*/) { + beatCount++; + }); + + eventBeat_->requestSynchronous(); + EXPECT_EQ(beatCount, 0); + + // Platform implementations induce the beat at a point where the effects of + // synchronous events can still make the current frame (the display phase on + // Apple, before the draw on Android). The beat callback runs synchronously + // before `induce` returns, with both threads blocked. + std::thread driver([this]() { + stubQueue_->waitForTask(); + stubQueue_->tick(); + }); + eventBeat_->induce(); + driver.join(); + + EXPECT_EQ(beatCount, 1); + + // The request was consumed: another induce does nothing. + eventBeat_->induce(); + EXPECT_EQ(beatCount, 1); + EXPECT_EQ(stubQueue_->size(), 0); +} + +TEST_F(EventBeatTest, requestMadeDuringBeatIsProcessedByASubsequentInduce) { + int beatCount = 0; + eventBeat_->setBeatCallback([&](jsi::Runtime& /*runtime*/) { + beatCount++; + if (beatCount == 1) { + // A synchronous request made from within the beat (e.g. an event whose + // handler causes another synchronous event). Platform implementations + // defer the induce for it (the display phase flusher on Apple, the next + // pre-draw on Android) rather than inducing from within the beat. + eventBeat_->requestSynchronous(); + } + }); + + eventBeat_->requestSynchronous(); + std::thread driver([this]() { + stubQueue_->waitForTask(); + stubQueue_->tick(); + }); + eventBeat_->induce(); + driver.join(); + + EXPECT_EQ(beatCount, 1); + + // The request made during the beat is not lost: the next induce processes + // it. + std::thread driver2([this]() { + stubQueue_->waitForTask(); + stubQueue_->tick(); + }); + eventBeat_->induce(); + driver2.join(); + + EXPECT_EQ(beatCount, 2); +} + +TEST_F(EventBeatTest, synchronousRequestIsNotStrandedBehindScheduledBeat) { + int beatCount = 0; + eventBeat_->setBeatCallback([&beatCount](jsi::Runtime& /*runtime*/) { + beatCount++; + }); + + // An asynchronous beat is scheduled but has not run yet. + eventBeat_->request(); + eventBeat_->induce(); + EXPECT_EQ(beatCount, 0); + + // A synchronous request arriving now must still be processed by its induce + // instead of being silently deferred behind the scheduled beat. + eventBeat_->requestSynchronous(); + std::thread inducer([this]() { eventBeat_->induce(); }); + + // Wait until the synchronous access request joins the already-queued work + // item, so that the tick order below is deterministic. + stubQueue_->waitForTasks(2); + + // The scheduled beat's work item yields to the pending synchronous access + // without executing. + stubQueue_->tick(); + EXPECT_EQ(beatCount, 0); + + // The synchronous access processes the beat within its induce. + stubQueue_->tick(); + inducer.join(); + EXPECT_EQ(beatCount, 1); + + // The beat that yielded resumes afterwards; it was not lost. + stubQueue_->tick(); + EXPECT_EQ(beatCount, 2); + EXPECT_EQ(stubQueue_->size(), 0); +} + +} // namespace facebook::react diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 63ef62b545f6..834b58eb3769 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -2236,8 +2236,9 @@ class facebook::react::EventBeat { public using BeatCallback = std::function; public using Factory = std::function(std::shared_ptr ownerBox)>; public virtual void request() const; - public virtual void requestSynchronous() const; + public virtual void requestSynchronous(facebook::react::Tag tag) const; public virtual ~EventBeat() = default; + public void requestSynchronous() const; public void setBeatCallback(facebook::react::EventBeat::BeatCallback beatCallback); } @@ -2271,6 +2272,7 @@ class facebook::react::EventDispatcher { class facebook::react::EventEmitter { public EventEmitter(facebook::react::SharedEventTarget eventTarget, facebook::react::EventDispatcher::Weak eventDispatcher); public const facebook::react::SharedEventTarget& getEventTarget() const; + public facebook::react::Tag getTag() const; public static facebook::react::ValueFactory defaultPayloadFactory(); public static std::mutex& DispatchMutex(); public static std::string normalizeEventType(std::string type); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index a17dd79e89b6..bc48b20f503e 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -2219,8 +2219,9 @@ class facebook::react::EventBeat { public using BeatCallback = std::function; public using Factory = std::function(std::shared_ptr ownerBox)>; public virtual void request() const; - public virtual void requestSynchronous() const; + public virtual void requestSynchronous(facebook::react::Tag tag) const; public virtual ~EventBeat() = default; + public void requestSynchronous() const; public void setBeatCallback(facebook::react::EventBeat::BeatCallback beatCallback); } @@ -2254,6 +2255,7 @@ class facebook::react::EventDispatcher { class facebook::react::EventEmitter { public EventEmitter(facebook::react::SharedEventTarget eventTarget, facebook::react::EventDispatcher::Weak eventDispatcher); public const facebook::react::SharedEventTarget& getEventTarget() const; + public facebook::react::Tag getTag() const; public static facebook::react::ValueFactory defaultPayloadFactory(); public static std::mutex& DispatchMutex(); public static std::string normalizeEventType(std::string type); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index fefaf2892d66..2441e79bda08 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -2234,8 +2234,9 @@ class facebook::react::EventBeat { public using BeatCallback = std::function; public using Factory = std::function(std::shared_ptr ownerBox)>; public virtual void request() const; - public virtual void requestSynchronous() const; + public virtual void requestSynchronous(facebook::react::Tag tag) const; public virtual ~EventBeat() = default; + public void requestSynchronous() const; public void setBeatCallback(facebook::react::EventBeat::BeatCallback beatCallback); } @@ -2269,6 +2270,7 @@ class facebook::react::EventDispatcher { class facebook::react::EventEmitter { public EventEmitter(facebook::react::SharedEventTarget eventTarget, facebook::react::EventDispatcher::Weak eventDispatcher); public const facebook::react::SharedEventTarget& getEventTarget() const; + public facebook::react::Tag getTag() const; public static facebook::react::ValueFactory defaultPayloadFactory(); public static std::mutex& DispatchMutex(); public static std::string normalizeEventType(std::string type); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index 891cf3086134..bd2bdc122ad5 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -4235,8 +4235,12 @@ class facebook::react::AppRegistryBinding { } class facebook::react::AppleEventBeat : public facebook::react::EventBeat, public facebook::react::RunLoopObserver::Delegate { - public AppleEventBeat(std::shared_ptr ownerBox, std::unique_ptr uiRunLoopObserver, facebook::react::RuntimeScheduler& RuntimeScheduler); + public AppleEventBeat(std::shared_ptr ownerBox, std::unique_ptr uiRunLoopObserver, facebook::react::RuntimeScheduler& RuntimeScheduler, facebook::react::AppleEventBeat::WindowLayerResolver windowLayerResolver); + public using WindowLayerResolver = std::function; public virtual void activityDidChange(const facebook::react::RunLoopObserver::Delegate* delegate, facebook::react::RunLoopObserver::Activity activity) const noexcept override; + public virtual void requestSynchronous(facebook::react::Tag tag) const override; + public void requestSynchronous() const; + public ~AppleEventBeat() override; } class facebook::react::AsyncArrayBuffer { @@ -4749,8 +4753,9 @@ class facebook::react::EventBeat { public using BeatCallback = std::function; public using Factory = std::function(std::shared_ptr ownerBox)>; public virtual void request() const; - public virtual void requestSynchronous() const; + public virtual void requestSynchronous(facebook::react::Tag tag) const; public virtual ~EventBeat() = default; + public void requestSynchronous() const; public void setBeatCallback(facebook::react::EventBeat::BeatCallback beatCallback); } @@ -4772,6 +4777,7 @@ class facebook::react::EventDispatcher { class facebook::react::EventEmitter { public EventEmitter(facebook::react::SharedEventTarget eventTarget, facebook::react::EventDispatcher::Weak eventDispatcher); public const facebook::react::SharedEventTarget& getEventTarget() const; + public facebook::react::Tag getTag() const; public static facebook::react::ValueFactory defaultPayloadFactory(); public static std::mutex& DispatchMutex(); public static std::string normalizeEventType(std::string type); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index 3afba2f024d4..4099a2828d71 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -4222,8 +4222,12 @@ class facebook::react::AppRegistryBinding { } class facebook::react::AppleEventBeat : public facebook::react::EventBeat, public facebook::react::RunLoopObserver::Delegate { - public AppleEventBeat(std::shared_ptr ownerBox, std::unique_ptr uiRunLoopObserver, facebook::react::RuntimeScheduler& RuntimeScheduler); + public AppleEventBeat(std::shared_ptr ownerBox, std::unique_ptr uiRunLoopObserver, facebook::react::RuntimeScheduler& RuntimeScheduler, facebook::react::AppleEventBeat::WindowLayerResolver windowLayerResolver); + public using WindowLayerResolver = std::function; public virtual void activityDidChange(const facebook::react::RunLoopObserver::Delegate* delegate, facebook::react::RunLoopObserver::Activity activity) const noexcept override; + public virtual void requestSynchronous(facebook::react::Tag tag) const override; + public void requestSynchronous() const; + public ~AppleEventBeat() override; } class facebook::react::AsyncArrayBuffer { @@ -4725,8 +4729,9 @@ class facebook::react::EventBeat { public using BeatCallback = std::function; public using Factory = std::function(std::shared_ptr ownerBox)>; public virtual void request() const; - public virtual void requestSynchronous() const; + public virtual void requestSynchronous(facebook::react::Tag tag) const; public virtual ~EventBeat() = default; + public void requestSynchronous() const; public void setBeatCallback(facebook::react::EventBeat::BeatCallback beatCallback); } @@ -4748,6 +4753,7 @@ class facebook::react::EventDispatcher { class facebook::react::EventEmitter { public EventEmitter(facebook::react::SharedEventTarget eventTarget, facebook::react::EventDispatcher::Weak eventDispatcher); public const facebook::react::SharedEventTarget& getEventTarget() const; + public facebook::react::Tag getTag() const; public static facebook::react::ValueFactory defaultPayloadFactory(); public static std::mutex& DispatchMutex(); public static std::string normalizeEventType(std::string type); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index 7dbc2971df75..7e2828712970 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -4233,8 +4233,12 @@ class facebook::react::AppRegistryBinding { } class facebook::react::AppleEventBeat : public facebook::react::EventBeat, public facebook::react::RunLoopObserver::Delegate { - public AppleEventBeat(std::shared_ptr ownerBox, std::unique_ptr uiRunLoopObserver, facebook::react::RuntimeScheduler& RuntimeScheduler); + public AppleEventBeat(std::shared_ptr ownerBox, std::unique_ptr uiRunLoopObserver, facebook::react::RuntimeScheduler& RuntimeScheduler, facebook::react::AppleEventBeat::WindowLayerResolver windowLayerResolver); + public using WindowLayerResolver = std::function; public virtual void activityDidChange(const facebook::react::RunLoopObserver::Delegate* delegate, facebook::react::RunLoopObserver::Activity activity) const noexcept override; + public virtual void requestSynchronous(facebook::react::Tag tag) const override; + public void requestSynchronous() const; + public ~AppleEventBeat() override; } class facebook::react::AsyncArrayBuffer { @@ -4747,8 +4751,9 @@ class facebook::react::EventBeat { public using BeatCallback = std::function; public using Factory = std::function(std::shared_ptr ownerBox)>; public virtual void request() const; - public virtual void requestSynchronous() const; + public virtual void requestSynchronous(facebook::react::Tag tag) const; public virtual ~EventBeat() = default; + public void requestSynchronous() const; public void setBeatCallback(facebook::react::EventBeat::BeatCallback beatCallback); } @@ -4770,6 +4775,7 @@ class facebook::react::EventDispatcher { class facebook::react::EventEmitter { public EventEmitter(facebook::react::SharedEventTarget eventTarget, facebook::react::EventDispatcher::Weak eventDispatcher); public const facebook::react::SharedEventTarget& getEventTarget() const; + public facebook::react::Tag getTag() const; public static facebook::react::ValueFactory defaultPayloadFactory(); public static std::mutex& DispatchMutex(); public static std::string normalizeEventType(std::string type); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index 588cc00096be..6e19a9c0ca16 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -1477,8 +1477,9 @@ class facebook::react::EventBeat { public using BeatCallback = std::function; public using Factory = std::function(std::shared_ptr ownerBox)>; public virtual void request() const; - public virtual void requestSynchronous() const; + public virtual void requestSynchronous(facebook::react::Tag tag) const; public virtual ~EventBeat() = default; + public void requestSynchronous() const; public void setBeatCallback(facebook::react::EventBeat::BeatCallback beatCallback); } @@ -1500,6 +1501,7 @@ class facebook::react::EventDispatcher { class facebook::react::EventEmitter { public EventEmitter(facebook::react::SharedEventTarget eventTarget, facebook::react::EventDispatcher::Weak eventDispatcher); public const facebook::react::SharedEventTarget& getEventTarget() const; + public facebook::react::Tag getTag() const; public static facebook::react::ValueFactory defaultPayloadFactory(); public static std::mutex& DispatchMutex(); public static std::string normalizeEventType(std::string type); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api index 6b46a6ec25ff..b2be2b5c0eff 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api @@ -1461,8 +1461,9 @@ class facebook::react::EventBeat { public using BeatCallback = std::function; public using Factory = std::function(std::shared_ptr ownerBox)>; public virtual void request() const; - public virtual void requestSynchronous() const; + public virtual void requestSynchronous(facebook::react::Tag tag) const; public virtual ~EventBeat() = default; + public void requestSynchronous() const; public void setBeatCallback(facebook::react::EventBeat::BeatCallback beatCallback); } @@ -1484,6 +1485,7 @@ class facebook::react::EventDispatcher { class facebook::react::EventEmitter { public EventEmitter(facebook::react::SharedEventTarget eventTarget, facebook::react::EventDispatcher::Weak eventDispatcher); public const facebook::react::SharedEventTarget& getEventTarget() const; + public facebook::react::Tag getTag() const; public static facebook::react::ValueFactory defaultPayloadFactory(); public static std::mutex& DispatchMutex(); public static std::string normalizeEventType(std::string type); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index 155dd1cfa9c5..89bb5d946036 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -1475,8 +1475,9 @@ class facebook::react::EventBeat { public using BeatCallback = std::function; public using Factory = std::function(std::shared_ptr ownerBox)>; public virtual void request() const; - public virtual void requestSynchronous() const; + public virtual void requestSynchronous(facebook::react::Tag tag) const; public virtual ~EventBeat() = default; + public void requestSynchronous() const; public void setBeatCallback(facebook::react::EventBeat::BeatCallback beatCallback); } @@ -1498,6 +1499,7 @@ class facebook::react::EventDispatcher { class facebook::react::EventEmitter { public EventEmitter(facebook::react::SharedEventTarget eventTarget, facebook::react::EventDispatcher::Weak eventDispatcher); public const facebook::react::SharedEventTarget& getEventTarget() const; + public facebook::react::Tag getTag() const; public static facebook::react::ValueFactory defaultPayloadFactory(); public static std::mutex& DispatchMutex(); public static std::string normalizeEventType(std::string type);