From 4ade8a251451051853331640e3b63782423dad1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20B=C5=82oniarz?= Date: Fri, 9 Oct 2026 04:41:53 -0700 Subject: [PATCH] Destroy preallocated views that the pull model never mounted (#58890) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: With `enableMountingCoordinatorPullModelAndroid`, the UI thread pulls mount transactions instead of the JS thread pushing every commit. When one commit mounts a subtree and the next commit unmounts it before the UI thread pulls, the pulled transaction contains neither the Create nor the Delete for it. The views preallocated for that subtree stay in `SurfaceMountingManager`, and their tags stay in `FabricMountingManager`'s allocated view registry. The cleanup that destroys never-mounted preallocated views does not run either, because `ShadowNodeFamily` already marked the family as mounted at commit time. In a test app that repeatedly mounts and unmounts 1200 views, nearly all of them leaked as Java views and registry entries. This PR changes how preallocated views are cleaned up when the pull model is enabled: - The allocated view registry records whether each view is only preallocated, or was created by a mount (a Create or the surface root). - `ShadowNodeFamily` gets `onFamilyDestroyed`, whose callback runs for every destroyed family, and a `hasBeenMounted()` getter. `onUnmountedFamilyDestroyed` is deprecated. It keeps its behaviour by wrapping `onFamilyDestroyed` and skipping mounted families. - `destroyUnmountedShadowNode` destroys only views that are still preallocated-only. A view created by a mount is left to its Delete, which can be pulled after the family is destroyed. - Each queued preallocation holds a weak reference to its family. The drain skips views whose family is already destroyed, so it does not preallocate a view that nothing would clean up. `FabricMountingManager::preallocateShadowView` is removed, because the drain now registers and preallocates its views directly and no production code called it. ## Changelog: [ANDROID] [FIXED] - Fix preallocated views leaking when `enableMountingCoordinatorPullModelAndroid` is enabled Test Plan: - `ShadowNodeFamilyTest`: `onFamilyDestroyed` runs for both mounted and unmounted families and reports `hasBeenMounted()` correctly, and the deprecated `onUnmountedFamilyDestroyed` still skips mounted families. There is no open-source target for these gtests, so I built them with the NDK and ran them on an Android emulator. - `FabricMountingManagerInstrumentationTest` has new cases for the following. It has no Gradle target in the open-source repository, so I ran it on an Android emulator through a local androidTest source set that is not part of this PR. - the drain skipping a view whose family was destroyed (pull model); - a committed family destroying its preallocated view (pull model); - a committed family keeping a view that a mount created (pull model). With the native changes reverted, the cases for the destroyed committed family and the drain skip fail. - Release build of a test app on an Android emulator. The app mounts and unmounts a subtree of 1200 views in a loop, then idles. View counts at idle: | Configuration | Java views before → after | Registry entries before → after | | --- | --- | --- | | Pull model | 1197 → 3 | 1197 → 3 | | Pull model + Props 2.0 | 1203 → 3 | 1203 → 3 | There were no crashes or new soft exceptions. - `yarn cxx-api-validate`, `yarn format-check-cpp` and `yarn format-check-kotlin` pass. Differential Revision: D123648108 Pulled By: bartlomiejbloniarz --- .../react/fabric/FabricMountingManager.cpp | 100 +++++++++++++----- .../jni/react/fabric/FabricMountingManager.h | 43 +++++--- .../react/fabric/FabricUIManagerBinding.cpp | 8 +- .../fabric/test/FabricMountingManagerTest.cpp | 13 ++- .../FabricMountingManagerTestHelper.cpp | 87 +++++++++++++-- .../FabricMountingManagerTestHelper.h | 8 ++ ...abricMountingManagerInstrumentationTest.kt | 79 +++++++++++++- .../fabric/FabricMountingManagerTestHelper.kt | 14 ++- .../react/renderer/core/ShadowNodeFamily.cpp | 20 +++- .../react/renderer/core/ShadowNodeFamily.h | 16 ++- .../core/tests/ShadowNodeFamilyTest.cpp | 66 ++++++++++++ .../api-snapshots/ReactAndroidDebugCxx.api | 3 +- .../ReactAndroidFrameworksCxx.api | 3 +- .../api-snapshots/ReactAndroidNewarchCxx.api | 3 +- .../api-snapshots/ReactAndroidReleaseCxx.api | 3 +- .../api-snapshots/ReactAppleDebugCxx.api | 2 + .../api-snapshots/ReactAppleFrameworksCxx.api | 2 + .../api-snapshots/ReactAppleNewarchCxx.api | 2 + .../api-snapshots/ReactAppleReleaseCxx.api | 2 + .../api-snapshots/ReactCommonDebugCxx.api | 2 + .../ReactCommonFrameworksCxx.api | 2 + .../api-snapshots/ReactCommonNewarchCxx.api | 2 + .../api-snapshots/ReactCommonReleaseCxx.api | 2 + 23 files changed, 404 insertions(+), 78 deletions(-) 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);