diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp index 837535e66d4a..39c1d2e6e10d 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp @@ -47,7 +47,11 @@ FabricMountingManager::~FabricMountingManager() { void FabricMountingManager::onSurfaceStart(SurfaceId surfaceId) { std::lock_guard lock(allocatedViewsMutex_); allocatedViewRegistry_.emplace( - surfaceId, std::unordered_map{{surfaceId, nullptr}}); + surfaceId, + std::unordered_map{ + {surfaceId, + {.preallocatedProps = nullptr, + .state = AllocatedViewState::Mounted}}}); } void FabricMountingManager::onSurfaceStop(SurfaceId surfaceId) { @@ -615,9 +619,9 @@ void FabricMountingManager::executeMount( std::lock_guard allocatedViewsLock(allocatedViewsMutex_); auto allocatedViewsIterator = allocatedViewRegistry_.find(surfaceId); - auto defaultAllocatedViews = std::unordered_map{}; + auto defaultAllocatedViews = std::unordered_map{}; // Do not remove `defaultAllocatedViews` or initialize - // `std::unordered_map{}` inline in below ternary + // `std::unordered_map{}` inline in below ternary // expression - if falsy operand is a value type, the compiler will decide // the expression to be a value type, an unnecessary (sometimes expensive) // copy will happen as a result. @@ -640,13 +644,18 @@ void FabricMountingManager::executeMount( bool isVirtual = mutation.mutatedViewIsVirtual(); switch (mutationType) { case ShadowViewMutation::Create: { - bool shouldCreateView = - !allocatedViewTags.contains(newChildShadowView.tag); + auto [allocatedView, shouldCreateView] = + allocatedViewTags.try_emplace( + newChildShadowView.tag, + AllocatedView{ + .preallocatedProps = nullptr, + .state = AllocatedViewState::Mounted}); if (shouldCreateView) { cppCommonMountItems.push_back( CppMountItem::CreateMountItem(newChildShadowView)); - allocatedViewTags.emplace(newChildShadowView.tag, nullptr); + } else { + allocatedView->second.state = AllocatedViewState::Mounted; } break; } @@ -755,7 +764,8 @@ void FabricMountingManager::executeMount( .push_back( CppMountItem::UpdatePropsMountItem( {}, newChildShadowView)); - } else if (auto& preallocatedProps = allocatedView->second; + } else if (auto& preallocatedProps = + allocatedView->second.preallocatedProps; preallocatedProps != nullptr) { if (preallocatedProps != newChildShadowView.props) { auto preallocatedShadowView = newChildShadowView; @@ -1056,15 +1066,42 @@ void FabricMountingManager::executeMount( } void FabricMountingManager::drainPreallocateViewsQueue() { + std::vector queuedViews; std::vector shadowViews; { std::lock_guard lock(preallocateMutex_); - std::swap(shadowViews, preallocatedViewsQueue_); + std::swap(queuedViews, preallocatedViewsQueue_); + } + + bool pullModel = + ReactNativeFeatureFlags::enableMountingCoordinatorPullModelAndroid(); + { + std::lock_guard allocatedViewsLock(allocatedViewsMutex_); + for (auto& [shadowView, family] : queuedViews) { + if (pullModel && family.expired()) { + continue; + } + auto allocatedViewsIterator = + allocatedViewRegistry_.find(shadowView.surfaceId); + if (allocatedViewsIterator != allocatedViewRegistry_.end() && + allocatedViewsIterator->second + .emplace( + shadowView.tag, + AllocatedView{ + .preallocatedProps = + shouldDiffInsertPropsAgainstPreallocatedProps() + ? shadowView.props + : nullptr, + .state = AllocatedViewState::Preallocated}) + .second) { + shadowViews.push_back(std::move(shadowView)); + } + } } for (const auto& shadowView : shadowViews) { - preallocateShadowView(shadowView); + preallocateRegisteredView(shadowView); } } @@ -1072,6 +1109,12 @@ void FabricMountingManager::destroyUnmountedShadowNode( const ShadowNodeFamily& family) { auto tag = family.getTag(); auto surfaceId = family.getSurfaceId(); + bool pullModel = + ReactNativeFeatureFlags::enableMountingCoordinatorPullModelAndroid(); + + if (!pullModel && family.hasBeenMounted()) { + return; + } // Remove from allocatedViewRegistry so that executeMount does not skip // the Create mount item for this tag. Without this, if the view was @@ -1082,7 +1125,20 @@ void FabricMountingManager::destroyUnmountedShadowNode( { std::lock_guard allocatedViewsLock(allocatedViewsMutex_); auto allocatedViewsIterator = allocatedViewRegistry_.find(surfaceId); - if (allocatedViewsIterator != allocatedViewRegistry_.end()) { + if (pullModel) { + // In the pull model, a committed family can have a preallocated view that + // no mount created (its Create and Delete were never pulled), and a + // mounted view's family can be destroyed before its Delete is pulled. + if (allocatedViewsIterator == allocatedViewRegistry_.end()) { + return; + } + auto allocatedView = allocatedViewsIterator->second.find(tag); + if (allocatedView == allocatedViewsIterator->second.end() || + allocatedView->second.state != AllocatedViewState::Preallocated) { + return; + } + allocatedViewsIterator->second.erase(allocatedView); + } else if (allocatedViewsIterator != allocatedViewRegistry_.end()) { allocatedViewsIterator->second.erase(tag); } } @@ -1113,29 +1169,15 @@ void FabricMountingManager::maybePreallocateShadowNode( { std::lock_guard lock(preallocateMutex_); - preallocatedViewsQueue_.push_back(std::move(shadowView)); + preallocatedViewsQueue_.push_back( + {.shadowView = std::move(shadowView), + .family = shadowNode.getFamilyShared()}); } } -void FabricMountingManager::preallocateShadowView( +void FabricMountingManager::preallocateRegisteredView( const ShadowView& shadowView) { - TraceSection section("FabricMountingManager::preallocateShadowView"); - - { - std::lock_guard lock(allocatedViewsMutex_); - auto allocatedViewsIterator = - allocatedViewRegistry_.find(shadowView.surfaceId); - if (allocatedViewsIterator == allocatedViewRegistry_.end()) { - return; - } - const auto [_, inserted] = allocatedViewsIterator->second.emplace( - shadowView.tag, - shouldDiffInsertPropsAgainstPreallocatedProps() ? shadowView.props - : nullptr); - if (!inserted) { - return; - } - } + TraceSection section("FabricMountingManager::preallocateRegisteredView"); bool isLayoutableShadowNode = shadowView.layoutMetrics != EmptyLayoutMetrics; diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h index 5a7fbc5ff001..b18944e28ac4 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h @@ -7,19 +7,20 @@ #pragma once +#include #include #include #include #include #include +#include namespace facebook::react { struct AnimatedProps; class MountingTransaction; -struct ShadowView; class FabricMountingManager final { public: @@ -36,18 +37,13 @@ class FabricMountingManager final { void destroyUnmountedShadowNode(const ShadowNodeFamily &family); /* - * Drains preallocatedViewsQueue_ by calling preallocateShadowView on each - * item in the queue. Can be called by any thread. + * Drains preallocatedViewsQueue_: preallocates each queued view on the Java + * side and registers its tag in allocatedViewRegistry_ so that executeMount + * skips the redundant Create mount item for this tag. In the pull model, it + * skips the views whose family was destroyed. Can be called by any thread. */ void drainPreallocateViewsQueue(); - /* - * Preallocates a view on the Java side and registers the tag in - * allocatedViewRegistry_ so that executeMount skips the redundant Create - * mount item for this tag. - */ - void preallocateShadowView(const ShadowView &shadowView); - /* * Returns true if the given tag is registered in allocatedViewRegistry_ * for the given surface. A registered tag means executeMount will skip @@ -98,6 +94,8 @@ class FabricMountingManager final { private: bool isOnMainThread(); + void preallocateRegisteredView(const ShadowView &shadowView); + jni::global_ref javaUIManager_; std::recursive_mutex commitMutex_; @@ -107,17 +105,28 @@ class FabricMountingManager final { */ std::mutex preallocateMutex_; + struct QueuedView { + ShadowView shadowView; + std::weak_ptr family; + }; + /* * A queue of views to be preallocated on the Java side. */ - std::vector preallocatedViewsQueue_{}; + std::vector preallocatedViewsQueue_{}; - /* - * Allocated tags per surface. With enablePreallocatedPropsDiffOnInsertAndroid - * a preallocated tag maps to the props it was preallocated with until its - * first Insert; every other tag maps to nullptr. - */ - std::unordered_map> allocatedViewRegistry_{}; + enum class AllocatedViewState { Preallocated, Mounted }; + + struct AllocatedView { + /* + * With enablePreallocatedPropsDiffOnInsertAndroid, the props the view was + * preallocated with, until its first Insert. nullptr otherwise. + */ + Props::Shared preallocatedProps; + AllocatedViewState state; + }; + + std::unordered_map> allocatedViewRegistry_{}; std::recursive_mutex allocatedViewsMutex_; }; diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp index d5c154f1e6f1..e5afef67d2f3 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp @@ -807,11 +807,11 @@ void FabricUIManagerBinding::schedulerDidRequestPreliminaryViewAllocation( return; } mountingManager->maybePreallocateShadowNode(shadowNode); - // Only the Views of ShadowNode that were pre-allocated (forms views) needs - // to be destroyed if the ShadowNode is destroyed but it was never mounted - // on the screen. + // Only the Views of ShadowNode that were pre-allocated (forms views) need + // to be destroyed if the ShadowNode is destroyed and no mount created its + // View. if (shadowNode.getTraits().check(ShadowNodeTraits::Trait::FormsView)) { - shadowNode.getFamilyShared()->onUnmountedFamilyDestroyed( + shadowNode.getFamilyShared()->onFamilyDestroyed( [weakMountingManager = std::weak_ptr(mountingManager)](const ShadowNodeFamily& family) { if (auto mountingManager = weakMountingManager.lock()) { diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/test/FabricMountingManagerTest.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/test/FabricMountingManagerTest.cpp index cb86d39f8220..2d9f4e892ca5 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/test/FabricMountingManagerTest.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/test/FabricMountingManagerTest.cpp @@ -60,10 +60,9 @@ class FabricMountingManagerTest : public ::testing::Test { * and to short-circuit a redundant Create mount item for the root view. * * Bug this catches: if a refactor stops registering the root surface tag - * (e.g. switches to `unordered_set{}` instead of - * `unordered_set({surfaceId})`), the root view would be re-created on - * every commit. Without this assertion, that regression would only surface - * via end-to-end Java tests. + * (e.g. starts each surface with an empty registry), the root view would be + * re-created on every commit. Without this assertion, that regression would + * only surface via end-to-end Java tests. */ TEST_F( FabricMountingManagerTest, @@ -79,7 +78,7 @@ TEST_F( /* * Verifies that `isViewAllocated` returns false for a surface that has not * been started. The contract is "false when the surface is unknown", not - * "throw" or "abort"; `executeMount` and `preallocateShadowView` both rely + * "throw" or "abort"; `executeMount` and `drainPreallocateViewsQueue` both rely * on this to gracefully no-op when a surface has been stopped concurrently. */ TEST_F( @@ -98,7 +97,7 @@ TEST_F( * must return false for *any* tag on that surface, including the surface * tag itself (which `onSurfaceStart` had inserted). * - * Bug this catches: if `onSurfaceStop` only `.clear()`-ed the inner set + * Bug this catches: if `onSurfaceStop` only `.clear()`-ed the inner map * instead of erasing the outer map entry, `executeMount`'s "surface was * stopped" log would never fire, masking commit-after-stop bugs. */ @@ -122,7 +121,7 @@ TEST_F( * the same process. * * Bug this catches: if a future change accidentally shared the - * `unordered_set` across surfaces (e.g. via a misplaced static), tags + * per-surface registry across surfaces (e.g. via a misplaced static), tags * from one surface would falsely appear as allocated on another, and * stopping any surface would purge unrelated surfaces' registries. */ diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/test_helper/FabricMountingManagerTestHelper.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/test_helper/FabricMountingManagerTestHelper.cpp index 473316f6208d..7bf51d7094e2 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/test_helper/FabricMountingManagerTestHelper.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/test_helper/FabricMountingManagerTestHelper.cpp @@ -11,10 +11,26 @@ #include #include #include +#include #include +#include namespace facebook::react { +namespace { + +ShadowView makeShadowView(jint surfaceId, jint tag) { + ShadowView sv{}; + sv.componentName = "View"; + sv.surfaceId = surfaceId; + sv.tag = tag; + sv.props = std::make_shared(); + sv.layoutMetrics.frame.size = {.width = 100, .height = 100}; + return sv; +} + +} // namespace + FabricMountingManagerTestHelper::FabricMountingManagerTestHelper( jni::alias_ref jFabricUIManager) : javaUIManager_(jni::make_global(jFabricUIManager)) { @@ -44,13 +60,45 @@ void FabricMountingManagerTestHelper::stopSurface(jint surfaceId) { void FabricMountingManagerTestHelper::preallocateView( jint surfaceId, jint tag) { - ShadowView sv{}; - sv.componentName = "View"; - sv.surfaceId = surfaceId; - sv.tag = tag; - sv.props = std::make_shared(); - sv.layoutMetrics.frame.size = {.width = 100, .height = 100}; - mountingManager_->preallocateShadowView(sv); + queuePreallocation(surfaceId, tag); + drainPreallocationQueue(); +} + +void FabricMountingManagerTestHelper::queuePreallocation( + jint surfaceId, + jint tag) { + auto props = std::make_shared(); + props->collapsable = false; + auto shadowNode = viewComponentDescriptor_->createShadowNode( + {.props = props}, + viewComponentDescriptor_->createFamily( + {.tag = tag, .surfaceId = surfaceId, .instanceHandle = nullptr})); + mountingManager_->maybePreallocateShadowNode(*shadowNode); + queuedShadowNodes_[tag] = std::move(shadowNode); +} + +void FabricMountingManagerTestHelper::drainPreallocationQueue() { + mountingManager_->drainPreallocateViewsQueue(); +} + +void FabricMountingManagerTestHelper::mountCreate(jint surfaceId, jint tag) { + auto telemetry = TransactionTelemetry{}; + telemetry.willCommit(); + telemetry.willDiff(); + telemetry.didDiff(); + telemetry.willLayout(); + telemetry.didLayout(); + telemetry.didCommit(); + mountingManager_->executeMount( + MountingTransaction{ + surfaceId, + 0, + {ShadowViewMutation::CreateMutation(makeShadowView(surfaceId, tag))}, + telemetry}); +} + +void FabricMountingManagerTestHelper::dropFamily(jint tag) { + queuedShadowNodes_.erase(tag); } void FabricMountingManagerTestHelper::destroyUnmountedView( @@ -61,6 +109,18 @@ void FabricMountingManagerTestHelper::destroyUnmountedView( mountingManager_->destroyUnmountedShadowNode(*family); } +void FabricMountingManagerTestHelper::destroyCommittedView( + jint surfaceId, + jint tag) { + auto family = viewComponentDescriptor_->createFamily( + {.tag = tag, .surfaceId = surfaceId, .instanceHandle = nullptr}); + family->onFamilyDestroyed([mountingManager = mountingManager_]( + const ShadowNodeFamily& destroyedFamily) { + mountingManager->destroyUnmountedShadowNode(destroyedFamily); + }); + family->setMounted(); +} + bool FabricMountingManagerTestHelper::isTagAllocated(jint surfaceId, jint tag) { return mountingManager_->isViewAllocated(surfaceId, tag); } @@ -91,9 +151,22 @@ void FabricMountingManagerTestHelper::registerNatives() { "stopSurface", FabricMountingManagerTestHelper::stopSurface), makeNativeMethod( "preallocateView", FabricMountingManagerTestHelper::preallocateView), + makeNativeMethod( + "queuePreallocation", + FabricMountingManagerTestHelper::queuePreallocation), + makeNativeMethod( + "drainPreallocationQueue", + FabricMountingManagerTestHelper::drainPreallocationQueue), + makeNativeMethod( + "mountCreate", FabricMountingManagerTestHelper::mountCreate), + makeNativeMethod( + "dropFamily", FabricMountingManagerTestHelper::dropFamily), makeNativeMethod( "destroyUnmountedView", FabricMountingManagerTestHelper::destroyUnmountedView), + makeNativeMethod( + "destroyCommittedView", + FabricMountingManagerTestHelper::destroyCommittedView), makeNativeMethod( "isTagAllocated", FabricMountingManagerTestHelper::isTagAllocated), makeNativeMethod( diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/test_helper/FabricMountingManagerTestHelper.h b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/test_helper/FabricMountingManagerTestHelper.h index a1a05e36ab09..fb7cd47612f5 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/test_helper/FabricMountingManagerTestHelper.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/test_helper/FabricMountingManagerTestHelper.h @@ -13,6 +13,8 @@ #include +#include + namespace facebook::react { /** @@ -25,7 +27,12 @@ class FabricMountingManagerTestHelper : public jni::HybridClass tags, @@ -45,6 +52,7 @@ class FabricMountingManagerTestHelper : public jni::HybridClass javaUIManager_; std::shared_ptr mountingManager_; std::unique_ptr viewComponentDescriptor_; + std::unordered_map> queuedShadowNodes_; }; } // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/FabricMountingManagerInstrumentationTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/FabricMountingManagerInstrumentationTest.kt index ef2040b96211..27afe2158e74 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/FabricMountingManagerInstrumentationTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/FabricMountingManagerInstrumentationTest.kt @@ -20,6 +20,8 @@ import com.facebook.react.fabric.mounting.MountingManager import com.facebook.react.fabric.mounting.MountingManager.MountItemExecutor import com.facebook.react.fabric.mounting.mountitems.IntBufferBatchMountItem import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsCxxInterop +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsDefaults import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests import com.facebook.react.uimanager.PixelUtil import com.facebook.react.uimanager.ThemedReactContext @@ -76,6 +78,7 @@ class FabricMountingManagerInstrumentationTest { @After fun tearDown() { ReactNativeFeatureFlags.dangerouslyReset() + ReactNativeFeatureFlagsCxxInterop.dangerouslyReset() } private fun startSurface() { @@ -134,7 +137,7 @@ class FabricMountingManagerInstrumentationTest { /** * Simulates the scenario fixed by D98729251 via IntBufferBatchMountItem: - * 1. Preallocate a view (simulates C++ preallocateShadowView calling Java preallocateView) + * 1. Preallocate a view (simulates the C++ preallocation drain calling Java preallocateView) * 2. Delete it (simulates C++ destroyUnmountedShadowNode calling Java destroyUnmountedView) * 3. CREATE + INSERT via batch mount item (simulates C++ executeMount after the fix erases from * allocatedViewRegistry_, so CREATE is included in the batch) @@ -234,6 +237,15 @@ class FabricMountingManagerInstrumentationTest { private fun createTestHelper(): FabricMountingManagerTestHelper = FabricMountingManagerTestHelper.create(createFabricUIManager()) + /** Enables the pull model in the C++ feature flags that the helper's native code reads. */ + private fun enablePullModel() { + ReactNativeFeatureFlagsCxxInterop.dangerouslyForceOverride( + object : ReactNativeFeatureFlagsDefaults() { + override fun enableMountingCoordinatorPullModelAndroid(): Boolean = true + } + ) + } + /** * Exercises the real C++ FabricMountingManager::onSurfaceStart and verifies the surfaceId tag is * registered in allocatedViewRegistry_. @@ -246,7 +258,8 @@ class FabricMountingManagerInstrumentationTest { } /** - * Exercises real C++ preallocateShadowView — verifies the tag is added to allocatedViewRegistry_. + * Exercises the real C++ preallocation queue — verifies the tag is added to + * allocatedViewRegistry_. */ @Test fun native_preallocateView_addsToRegistry() { @@ -335,4 +348,66 @@ class FabricMountingManagerInstrumentationTest { assertThat(fallbackView.alpha).isEqualTo(0.5f) assertThat(fallbackView.tag).isEqualTo("animated-view") } + + /** + * In the pull model, a view whose family is destroyed before the queue drains must not be + * preallocated: no family callback is left to destroy it. + */ + @Test + fun native_drainPreallocationQueue_pullModel_skipsViewWhoseFamilyWasDestroyed() { + enablePullModel() + val helper = createTestHelper() + helper.startSurface(surfaceId) + helper.queuePreallocation(surfaceId, 42) + helper.dropFamily(42) + + helper.drainPreallocationQueue() + + assertThat(helper.isTagAllocated(surfaceId, 42)).isFalse() + } + + /** Without the pull model, a committed family does not destroy its view. */ + @Test + fun native_destroyCommittedView_keepsPreallocatedView() { + val helper = createTestHelper() + helper.startSurface(surfaceId) + helper.preallocateView(surfaceId, 42) + + helper.destroyCommittedView(surfaceId, 42) + + assertThat(helper.isTagAllocated(surfaceId, 42)).isTrue() + } + + /** + * A pull can skip the commits that created and deleted the view of a committed family. Its + * preallocated view is destroyed with the family. + */ + @Test + fun native_destroyCommittedView_pullModel_destroysPreallocatedView() { + enablePullModel() + val helper = createTestHelper() + helper.startSurface(surfaceId) + helper.preallocateView(surfaceId, 42) + + helper.destroyCommittedView(surfaceId, 42) + + assertThat(helper.isTagAllocated(surfaceId, 42)).isFalse() + } + + /** + * In the pull model, a family can be destroyed before the pending Delete of its mounted view + * executes. A view that a mount created stays registered until that Delete. + */ + @Test + fun native_destroyCommittedView_pullModel_keepsViewCreatedByMount() { + enablePullModel() + val helper = createTestHelper() + helper.startSurface(surfaceId) + helper.preallocateView(surfaceId, 42) + helper.mountCreate(surfaceId, 42) + + helper.destroyCommittedView(surfaceId, 42) + + assertThat(helper.isTagAllocated(surfaceId, 42)).isTrue() + } } diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/FabricMountingManagerTestHelper.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/FabricMountingManagerTestHelper.kt index bba2ec0408b2..be78bc34280b 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/FabricMountingManagerTestHelper.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/FabricMountingManagerTestHelper.kt @@ -13,8 +13,8 @@ import com.facebook.soloader.SoLoader /** * JNI test helper that wraps a real C++ [FabricMountingManager] to exercise native code paths - * around preallocateShadowView, destroyUnmountedShadowNode, and the allocatedViewRegistry_ - * lifecycle. + * around the preallocation queue, executeMount, destroyUnmountedShadowNode, and the + * allocatedViewRegistry_ lifecycle. */ class FabricMountingManagerTestHelper private constructor(fabricUIManager: FabricUIManager) : HybridClassBase() { @@ -31,8 +31,18 @@ class FabricMountingManagerTestHelper private constructor(fabricUIManager: Fabri external fun preallocateView(surfaceId: Int, tag: Int) + external fun queuePreallocation(surfaceId: Int, tag: Int) + + external fun drainPreallocationQueue() + + external fun mountCreate(surfaceId: Int, tag: Int) + + external fun dropFamily(tag: Int) + external fun destroyUnmountedView(surfaceId: Int, tag: Int) + external fun destroyCommittedView(surfaceId: Int, tag: Int) + external fun isTagAllocated(surfaceId: Int, tag: Int): Boolean external fun synchronouslyUpdateAnimatedProps(tags: IntArray, props: Array) diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.cpp b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.cpp index 0279fe8f3da5..7f491effbd74 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.cpp @@ -63,13 +63,27 @@ void ShadowNodeFamily::setMounted() { hasBeenMounted_ = true; } +bool ShadowNodeFamily::hasBeenMounted() const { + return hasBeenMounted_; +} + const ComponentDescriptor& ShadowNodeFamily::getComponentDescriptor() const { return componentDescriptor_; } +void ShadowNodeFamily::onFamilyDestroyed( + std::function callback) { + onFamilyDestroyedCallback_ = std::move(callback); +} + void ShadowNodeFamily::onUnmountedFamilyDestroyed( std::function callback) { - onUnmountedFamilyDestroyedCallback_ = std::move(callback); + onFamilyDestroyed( + [callback = std::move(callback)](const ShadowNodeFamily& family) { + if (!family.hasBeenMounted()) { + callback(family); + } + }); } Tag ShadowNodeFamily::getTag() const { @@ -94,8 +108,8 @@ void ShadowNodeFamily::setInstanceHandle( } ShadowNodeFamily::~ShadowNodeFamily() { - if (!hasBeenMounted_ && onUnmountedFamilyDestroyedCallback_ != nullptr) { - onUnmountedFamilyDestroyedCallback_(*this); + if (onFamilyDestroyedCallback_ != nullptr) { + onFamilyDestroyedCallback_(*this); } } diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h index 838dacacf997..da4ded45d884 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h @@ -88,10 +88,16 @@ class ShadowNodeFamily final : public jsi::NativeState { SharedEventEmitter getEventEmitter() const; + /** + * @param callback will be executed when this ShadowNodeFamily is destroyed. + */ + void onFamilyDestroyed(std::function callback); + /** * @param callback will be executed when an unmounted instance of * ShadowNodeFamily is destroyed. */ + [[deprecated("Use onFamilyDestroyed and check hasBeenMounted() instead")]] void onUnmountedFamilyDestroyed(std::function callback); /* @@ -105,6 +111,11 @@ class ShadowNodeFamily final : public jsi::NativeState { */ void setMounted(); + /** + * Whether this ShadowNodeFamily was ever mounted. + */ + bool hasBeenMounted() const; + /* * Dispatches a state update with given priority. */ @@ -128,8 +139,7 @@ class ShadowNodeFamily final : public jsi::NativeState { void setInstanceHandle(InstanceHandle::Shared &instanceHandle); /** - * Override destructor to call onUnmountedFamilyDestroyedCallback() for - * ShadowViews that were preallocated but never mounted on the screen. + * Calls the callback set by onFamilyDestroyed. */ ~ShadowNodeFamily(); @@ -148,7 +158,7 @@ class ShadowNodeFamily final : public jsi::NativeState { std::shared_ptr mostRecentState_; mutable std::mutex mutex_; - std::function onUnmountedFamilyDestroyedCallback_ = nullptr; + std::function onFamilyDestroyedCallback_ = nullptr; /* * Deprecated. diff --git a/packages/react-native/ReactCommon/react/renderer/core/tests/ShadowNodeFamilyTest.cpp b/packages/react-native/ReactCommon/react/renderer/core/tests/ShadowNodeFamilyTest.cpp index 819beb7b3d8f..24a05850eeb5 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/tests/ShadowNodeFamilyTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/tests/ShadowNodeFamilyTest.cpp @@ -6,6 +6,8 @@ */ #include +#include +#include #include @@ -80,3 +82,67 @@ TEST(ShadowNodeFamilyTest, sealObjectCorrectly) { EXPECT_EQ(&ancestors2[0].first.get(), shadowNodeA.get()); EXPECT_EQ(&ancestors2[1].first.get(), shadowNodeAA.get()); } + +TEST(ShadowNodeFamilyTest, callsDestroyedCallbackWhetherOrNotMounted) { + ComponentDescriptorProviderRegistry componentDescriptorProviderRegistry{}; + auto componentDescriptorRegistry = + componentDescriptorProviderRegistry.createComponentDescriptorRegistry( + ComponentDescriptorParameters{ + .eventDispatcher = EventDispatcher::Shared{}, + .contextContainer = nullptr, + .flavor = nullptr}); + componentDescriptorProviderRegistry.add( + concreteComponentDescriptorProvider()); + auto builder = ComponentBuilder{componentDescriptorRegistry}; + + auto mountedShadowNode = builder.build(Element().tag(1)); + auto unmountedShadowNode = builder.build(Element().tag(2)); + auto destroyedFamilies = std::vector>{}; + for (const auto& shadowNode : {mountedShadowNode, unmountedShadowNode}) { + shadowNode->getFamilyShared()->onFamilyDestroyed( + [&](const ShadowNodeFamily& family) { + destroyedFamilies.emplace_back( + family.getTag(), family.hasBeenMounted()); + }); + } + mountedShadowNode->setMounted(true); + + mountedShadowNode.reset(); + unmountedShadowNode.reset(); + + EXPECT_EQ( + destroyedFamilies, + (std::vector>{{1, true}, {2, false}})); +} + +TEST(ShadowNodeFamilyTest, deprecatedUnmountedCallbackSkipsMountedFamilies) { + ComponentDescriptorProviderRegistry componentDescriptorProviderRegistry{}; + auto componentDescriptorRegistry = + componentDescriptorProviderRegistry.createComponentDescriptorRegistry( + ComponentDescriptorParameters{ + .eventDispatcher = EventDispatcher::Shared{}, + .contextContainer = nullptr, + .flavor = nullptr}); + componentDescriptorProviderRegistry.add( + concreteComponentDescriptorProvider()); + auto builder = ComponentBuilder{componentDescriptorRegistry}; + + auto mountedShadowNode = builder.build(Element().tag(1)); + auto unmountedShadowNode = builder.build(Element().tag(2)); + auto destroyedTags = std::vector{}; + for (const auto& shadowNode : {mountedShadowNode, unmountedShadowNode}) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + shadowNode->getFamilyShared()->onUnmountedFamilyDestroyed( + [&](const ShadowNodeFamily& family) { + destroyedTags.push_back(family.getTag()); + }); +#pragma clang diagnostic pop + } + mountedShadowNode->setMounted(true); + + mountedShadowNode.reset(); + unmountedShadowNode.reset(); + + EXPECT_EQ(destroyedTags, std::vector{2}); +} diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index bf17995a1b2e..551e381eac7e 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -1257,7 +1257,6 @@ class facebook::react::FabricMountingManager { public void onSurfaceStart(facebook::react::SurfaceId surfaceId); public void onSurfaceStop(facebook::react::SurfaceId surfaceId); public void onTransactionAvailable(facebook::react::SurfaceId surfaceId); - public void preallocateShadowView(const facebook::react::ShadowView& shadowView); public void scheduleReactRevisionMerge(facebook::react::SurfaceId surfaceId); public void sendAccessibilityEvent(const facebook::react::ShadowView& shadowView, const std::string& eventType); public void setIsJSResponder(const facebook::react::ShadowView& shadowView, bool isJSResponder, bool blockNativeResponder); @@ -2293,6 +2292,7 @@ class facebook::react::ShadowNode : public facebook::react::Sealable, public fac class facebook::react::ShadowNodeFamily : public facebook::jsi::NativeState { public ShadowNodeFamily(const facebook::react::ShadowNodeFamilyFragment& fragment, facebook::react::SharedEventEmitter eventEmitter, facebook::react::EventDispatcher::Weak eventDispatcher, const facebook::react::ComponentDescriptor& componentDescriptor); + public bool hasBeenMounted() const; public const facebook::react::ComponentDescriptor& getComponentDescriptor() const; public facebook::jsi::Value getInstanceHandle(facebook::jsi::Runtime& runtime) const; public facebook::react::ComponentHandle getComponentHandle() const; @@ -2308,6 +2308,7 @@ class facebook::react::ShadowNodeFamily : public facebook::jsi::NativeState { public using Shared = std::shared_ptr; public using Weak = std::weak_ptr; public void dispatchRawState(facebook::react::StateUpdate&& stateUpdate, facebook::react::EventQueue::UpdateMode updateMode); + public void onFamilyDestroyed(std::function callback); public void onUnmountedFamilyDestroyed(std::function callback); public void setInstanceHandle(facebook::react::InstanceHandle::Shared& instanceHandle); public void setMostRecentState(const std::shared_ptr& state); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api index 88cc6107ec19..15e330cdf9b6 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api @@ -1954,7 +1954,6 @@ class facebook::react::FabricMountingManager { public void onSurfaceStart(facebook::react::SurfaceId surfaceId); public void onSurfaceStop(facebook::react::SurfaceId surfaceId); public void onTransactionAvailable(facebook::react::SurfaceId surfaceId); - public void preallocateShadowView(const facebook::react::ShadowView& shadowView); public void scheduleReactRevisionMerge(facebook::react::SurfaceId surfaceId); public void sendAccessibilityEvent(const facebook::react::ShadowView& shadowView, const std::string& eventType); public void setIsJSResponder(const facebook::react::ShadowView& shadowView, bool isJSResponder, bool blockNativeResponder); @@ -3659,6 +3658,7 @@ class facebook::react::ShadowNode : public facebook::react::Sealable, public fac class facebook::react::ShadowNodeFamily : public facebook::jsi::NativeState { public ShadowNodeFamily(const facebook::react::ShadowNodeFamilyFragment& fragment, facebook::react::SharedEventEmitter eventEmitter, facebook::react::EventDispatcher::Weak eventDispatcher, const facebook::react::ComponentDescriptor& componentDescriptor); + public bool hasBeenMounted() const; public const facebook::react::ComponentDescriptor& getComponentDescriptor() const; public facebook::jsi::Value getInstanceHandle(facebook::jsi::Runtime& runtime) const; public facebook::react::ComponentHandle getComponentHandle() const; @@ -3674,6 +3674,7 @@ class facebook::react::ShadowNodeFamily : public facebook::jsi::NativeState { public using Shared = std::shared_ptr; public using Weak = std::weak_ptr; public void dispatchRawState(facebook::react::StateUpdate&& stateUpdate, facebook::react::EventQueue::UpdateMode updateMode); + public void onFamilyDestroyed(std::function callback); public void onUnmountedFamilyDestroyed(std::function callback); public void setInstanceHandle(facebook::react::InstanceHandle::Shared& instanceHandle); public void setMostRecentState(const std::shared_ptr& state); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index 29e51c6d11da..07a46f57bdd8 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -1257,7 +1257,6 @@ class facebook::react::FabricMountingManager { public void onSurfaceStart(facebook::react::SurfaceId surfaceId); public void onSurfaceStop(facebook::react::SurfaceId surfaceId); public void onTransactionAvailable(facebook::react::SurfaceId surfaceId); - public void preallocateShadowView(const facebook::react::ShadowView& shadowView); public void scheduleReactRevisionMerge(facebook::react::SurfaceId surfaceId); public void sendAccessibilityEvent(const facebook::react::ShadowView& shadowView, const std::string& eventType); public void setIsJSResponder(const facebook::react::ShadowView& shadowView, bool isJSResponder, bool blockNativeResponder); @@ -2286,6 +2285,7 @@ class facebook::react::ShadowNode : public facebook::react::Sealable, public fac class facebook::react::ShadowNodeFamily : public facebook::jsi::NativeState { public ShadowNodeFamily(const facebook::react::ShadowNodeFamilyFragment& fragment, facebook::react::SharedEventEmitter eventEmitter, facebook::react::EventDispatcher::Weak eventDispatcher, const facebook::react::ComponentDescriptor& componentDescriptor); + public bool hasBeenMounted() const; public const facebook::react::ComponentDescriptor& getComponentDescriptor() const; public facebook::jsi::Value getInstanceHandle(facebook::jsi::Runtime& runtime) const; public facebook::react::ComponentHandle getComponentHandle() const; @@ -2301,6 +2301,7 @@ class facebook::react::ShadowNodeFamily : public facebook::jsi::NativeState { public using Shared = std::shared_ptr; public using Weak = std::weak_ptr; public void dispatchRawState(facebook::react::StateUpdate&& stateUpdate, facebook::react::EventQueue::UpdateMode updateMode); + public void onFamilyDestroyed(std::function callback); public void onUnmountedFamilyDestroyed(std::function callback); public void setInstanceHandle(facebook::react::InstanceHandle::Shared& instanceHandle); public void setMostRecentState(const std::shared_ptr& state); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index 29e51c6d11da..07a46f57bdd8 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -1257,7 +1257,6 @@ class facebook::react::FabricMountingManager { public void onSurfaceStart(facebook::react::SurfaceId surfaceId); public void onSurfaceStop(facebook::react::SurfaceId surfaceId); public void onTransactionAvailable(facebook::react::SurfaceId surfaceId); - public void preallocateShadowView(const facebook::react::ShadowView& shadowView); public void scheduleReactRevisionMerge(facebook::react::SurfaceId surfaceId); public void sendAccessibilityEvent(const facebook::react::ShadowView& shadowView, const std::string& eventType); public void setIsJSResponder(const facebook::react::ShadowView& shadowView, bool isJSResponder, bool blockNativeResponder); @@ -2286,6 +2285,7 @@ class facebook::react::ShadowNode : public facebook::react::Sealable, public fac class facebook::react::ShadowNodeFamily : public facebook::jsi::NativeState { public ShadowNodeFamily(const facebook::react::ShadowNodeFamilyFragment& fragment, facebook::react::SharedEventEmitter eventEmitter, facebook::react::EventDispatcher::Weak eventDispatcher, const facebook::react::ComponentDescriptor& componentDescriptor); + public bool hasBeenMounted() const; public const facebook::react::ComponentDescriptor& getComponentDescriptor() const; public facebook::jsi::Value getInstanceHandle(facebook::jsi::Runtime& runtime) const; public facebook::react::ComponentHandle getComponentHandle() const; @@ -2301,6 +2301,7 @@ class facebook::react::ShadowNodeFamily : public facebook::jsi::NativeState { public using Shared = std::shared_ptr; public using Weak = std::weak_ptr; public void dispatchRawState(facebook::react::StateUpdate&& stateUpdate, facebook::react::EventQueue::UpdateMode updateMode); + public void onFamilyDestroyed(std::function callback); public void onUnmountedFamilyDestroyed(std::function callback); public void setInstanceHandle(facebook::react::InstanceHandle::Shared& instanceHandle); public void setMostRecentState(const std::shared_ptr& state); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index ff1bf08374f5..547195861e21 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -4580,6 +4580,7 @@ class facebook::react::ShadowNode : public facebook::react::Sealable, public fac class facebook::react::ShadowNodeFamily : public facebook::jsi::NativeState { public ShadowNodeFamily(const facebook::react::ShadowNodeFamilyFragment& fragment, facebook::react::SharedEventEmitter eventEmitter, facebook::react::EventDispatcher::Weak eventDispatcher, const facebook::react::ComponentDescriptor& componentDescriptor); + public bool hasBeenMounted() const; public const facebook::react::ComponentDescriptor& getComponentDescriptor() const; public facebook::jsi::Value getInstanceHandle(facebook::jsi::Runtime& runtime) const; public facebook::react::ComponentHandle getComponentHandle() const; @@ -4595,6 +4596,7 @@ class facebook::react::ShadowNodeFamily : public facebook::jsi::NativeState { public using Shared = std::shared_ptr; public using Weak = std::weak_ptr; public void dispatchRawState(facebook::react::StateUpdate&& stateUpdate, facebook::react::EventQueue::UpdateMode updateMode); + public void onFamilyDestroyed(std::function callback); public void onUnmountedFamilyDestroyed(std::function callback); public void setInstanceHandle(facebook::react::InstanceHandle::Shared& instanceHandle); public void setMostRecentState(const std::shared_ptr& state); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api index fa368ff91c6c..86c5ebd2cb4d 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api @@ -5870,6 +5870,7 @@ class facebook::react::ShadowNode : public facebook::react::Sealable, public fac class facebook::react::ShadowNodeFamily : public facebook::jsi::NativeState { public ShadowNodeFamily(const facebook::react::ShadowNodeFamilyFragment& fragment, facebook::react::SharedEventEmitter eventEmitter, facebook::react::EventDispatcher::Weak eventDispatcher, const facebook::react::ComponentDescriptor& componentDescriptor); + public bool hasBeenMounted() const; public const facebook::react::ComponentDescriptor& getComponentDescriptor() const; public facebook::jsi::Value getInstanceHandle(facebook::jsi::Runtime& runtime) const; public facebook::react::ComponentHandle getComponentHandle() const; @@ -5885,6 +5886,7 @@ class facebook::react::ShadowNodeFamily : public facebook::jsi::NativeState { public using Shared = std::shared_ptr; public using Weak = std::weak_ptr; public void dispatchRawState(facebook::react::StateUpdate&& stateUpdate, facebook::react::EventQueue::UpdateMode updateMode); + public void onFamilyDestroyed(std::function callback); public void onUnmountedFamilyDestroyed(std::function callback); public void setInstanceHandle(facebook::react::InstanceHandle::Shared& instanceHandle); public void setMostRecentState(const std::shared_ptr& state); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index c47fe92de8f6..17fab88d326f 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -4565,6 +4565,7 @@ class facebook::react::ShadowNode : public facebook::react::Sealable, public fac class facebook::react::ShadowNodeFamily : public facebook::jsi::NativeState { public ShadowNodeFamily(const facebook::react::ShadowNodeFamilyFragment& fragment, facebook::react::SharedEventEmitter eventEmitter, facebook::react::EventDispatcher::Weak eventDispatcher, const facebook::react::ComponentDescriptor& componentDescriptor); + public bool hasBeenMounted() const; public const facebook::react::ComponentDescriptor& getComponentDescriptor() const; public facebook::jsi::Value getInstanceHandle(facebook::jsi::Runtime& runtime) const; public facebook::react::ComponentHandle getComponentHandle() const; @@ -4580,6 +4581,7 @@ class facebook::react::ShadowNodeFamily : public facebook::jsi::NativeState { public using Shared = std::shared_ptr; public using Weak = std::weak_ptr; public void dispatchRawState(facebook::react::StateUpdate&& stateUpdate, facebook::react::EventQueue::UpdateMode updateMode); + public void onFamilyDestroyed(std::function callback); public void onUnmountedFamilyDestroyed(std::function callback); public void setInstanceHandle(facebook::react::InstanceHandle::Shared& instanceHandle); public void setMostRecentState(const std::shared_ptr& state); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index 065b2049a3d8..93ea8c905deb 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -4573,6 +4573,7 @@ class facebook::react::ShadowNode : public facebook::react::Sealable, public fac class facebook::react::ShadowNodeFamily : public facebook::jsi::NativeState { public ShadowNodeFamily(const facebook::react::ShadowNodeFamilyFragment& fragment, facebook::react::SharedEventEmitter eventEmitter, facebook::react::EventDispatcher::Weak eventDispatcher, const facebook::react::ComponentDescriptor& componentDescriptor); + public bool hasBeenMounted() const; public const facebook::react::ComponentDescriptor& getComponentDescriptor() const; public facebook::jsi::Value getInstanceHandle(facebook::jsi::Runtime& runtime) const; public facebook::react::ComponentHandle getComponentHandle() const; @@ -4588,6 +4589,7 @@ class facebook::react::ShadowNodeFamily : public facebook::jsi::NativeState { public using Shared = std::shared_ptr; public using Weak = std::weak_ptr; public void dispatchRawState(facebook::react::StateUpdate&& stateUpdate, facebook::react::EventQueue::UpdateMode updateMode); + public void onFamilyDestroyed(std::function callback); public void onUnmountedFamilyDestroyed(std::function callback); public void setInstanceHandle(facebook::react::InstanceHandle::Shared& instanceHandle); public void setMostRecentState(const std::shared_ptr& state); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index 6fd1e1a03a36..df8ca65e2678 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -1086,6 +1086,7 @@ class facebook::react::ShadowNode : public facebook::react::Sealable, public fac class facebook::react::ShadowNodeFamily : public facebook::jsi::NativeState { public ShadowNodeFamily(const facebook::react::ShadowNodeFamilyFragment& fragment, facebook::react::SharedEventEmitter eventEmitter, facebook::react::EventDispatcher::Weak eventDispatcher, const facebook::react::ComponentDescriptor& componentDescriptor); + public bool hasBeenMounted() const; public const facebook::react::ComponentDescriptor& getComponentDescriptor() const; public facebook::jsi::Value getInstanceHandle(facebook::jsi::Runtime& runtime) const; public facebook::react::ComponentHandle getComponentHandle() const; @@ -1101,6 +1102,7 @@ class facebook::react::ShadowNodeFamily : public facebook::jsi::NativeState { public using Shared = std::shared_ptr; public using Weak = std::weak_ptr; public void dispatchRawState(facebook::react::StateUpdate&& stateUpdate, facebook::react::EventQueue::UpdateMode updateMode); + public void onFamilyDestroyed(std::function callback); public void onUnmountedFamilyDestroyed(std::function callback); public void setInstanceHandle(facebook::react::InstanceHandle::Shared& instanceHandle); public void setMostRecentState(const std::shared_ptr& state); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api index b8fbabd4a816..e5d602abaf35 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api @@ -2248,6 +2248,7 @@ class facebook::react::ShadowNode : public facebook::react::Sealable, public fac class facebook::react::ShadowNodeFamily : public facebook::jsi::NativeState { public ShadowNodeFamily(const facebook::react::ShadowNodeFamilyFragment& fragment, facebook::react::SharedEventEmitter eventEmitter, facebook::react::EventDispatcher::Weak eventDispatcher, const facebook::react::ComponentDescriptor& componentDescriptor); + public bool hasBeenMounted() const; public const facebook::react::ComponentDescriptor& getComponentDescriptor() const; public facebook::jsi::Value getInstanceHandle(facebook::jsi::Runtime& runtime) const; public facebook::react::ComponentHandle getComponentHandle() const; @@ -2263,6 +2264,7 @@ class facebook::react::ShadowNodeFamily : public facebook::jsi::NativeState { public using Shared = std::shared_ptr; public using Weak = std::weak_ptr; public void dispatchRawState(facebook::react::StateUpdate&& stateUpdate, facebook::react::EventQueue::UpdateMode updateMode); + public void onFamilyDestroyed(std::function callback); public void onUnmountedFamilyDestroyed(std::function callback); public void setInstanceHandle(facebook::react::InstanceHandle::Shared& instanceHandle); public void setMostRecentState(const std::shared_ptr& state); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api index e384134caa94..727bda4e2062 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api @@ -1079,6 +1079,7 @@ class facebook::react::ShadowNode : public facebook::react::Sealable, public fac class facebook::react::ShadowNodeFamily : public facebook::jsi::NativeState { public ShadowNodeFamily(const facebook::react::ShadowNodeFamilyFragment& fragment, facebook::react::SharedEventEmitter eventEmitter, facebook::react::EventDispatcher::Weak eventDispatcher, const facebook::react::ComponentDescriptor& componentDescriptor); + public bool hasBeenMounted() const; public const facebook::react::ComponentDescriptor& getComponentDescriptor() const; public facebook::jsi::Value getInstanceHandle(facebook::jsi::Runtime& runtime) const; public facebook::react::ComponentHandle getComponentHandle() const; @@ -1094,6 +1095,7 @@ class facebook::react::ShadowNodeFamily : public facebook::jsi::NativeState { public using Shared = std::shared_ptr; public using Weak = std::weak_ptr; public void dispatchRawState(facebook::react::StateUpdate&& stateUpdate, facebook::react::EventQueue::UpdateMode updateMode); + public void onFamilyDestroyed(std::function callback); public void onUnmountedFamilyDestroyed(std::function callback); public void setInstanceHandle(facebook::react::InstanceHandle::Shared& instanceHandle); public void setMostRecentState(const std::shared_ptr& state); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index e384134caa94..727bda4e2062 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -1079,6 +1079,7 @@ class facebook::react::ShadowNode : public facebook::react::Sealable, public fac class facebook::react::ShadowNodeFamily : public facebook::jsi::NativeState { public ShadowNodeFamily(const facebook::react::ShadowNodeFamilyFragment& fragment, facebook::react::SharedEventEmitter eventEmitter, facebook::react::EventDispatcher::Weak eventDispatcher, const facebook::react::ComponentDescriptor& componentDescriptor); + public bool hasBeenMounted() const; public const facebook::react::ComponentDescriptor& getComponentDescriptor() const; public facebook::jsi::Value getInstanceHandle(facebook::jsi::Runtime& runtime) const; public facebook::react::ComponentHandle getComponentHandle() const; @@ -1094,6 +1095,7 @@ class facebook::react::ShadowNodeFamily : public facebook::jsi::NativeState { public using Shared = std::shared_ptr; public using Weak = std::weak_ptr; public void dispatchRawState(facebook::react::StateUpdate&& stateUpdate, facebook::react::EventQueue::UpdateMode updateMode); + public void onFamilyDestroyed(std::function callback); public void onUnmountedFamilyDestroyed(std::function callback); public void setInstanceHandle(facebook::react::InstanceHandle::Shared& instanceHandle); public void setMostRecentState(const std::shared_ptr& state);