From f851a9f3296de1845a9764765962177258b24d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20B=C5=82oniarz?= Date: Wed, 14 Oct 2026 09:40:10 -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 | 67 ++++++++++++ .../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, 405 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 da3966e9c301..8d37b2ba8b01 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) { @@ -617,9 +621,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. @@ -642,13 +646,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; } @@ -767,7 +776,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; @@ -1068,15 +1078,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); } } @@ -1084,6 +1121,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 @@ -1094,7 +1137,20 @@ void FabricMountingManager::destroyUnmountedShadowNode( { std::lock_guard allocatedViewsLock(allocatedViewsMutex_); auto allocatedViewsIterator = allocatedViewRegistry_.find(surfaceId); - if (allocatedViewsIterator != allocatedViewRegistry_.end()) { + if (pullModel) { + if (allocatedViewsIterator == allocatedViewRegistry_.end()) { + return; + } + auto allocatedView = allocatedViewsIterator->second.find(tag); + // 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 (allocatedView == allocatedViewsIterator->second.end() || + allocatedView->second.state != AllocatedViewState::Preallocated) { + return; + } + allocatedViewsIterator->second.erase(allocatedView); + } else if (allocatedViewsIterator != allocatedViewRegistry_.end()) { allocatedViewsIterator->second.erase(tag); } } @@ -1125,29 +1181,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 8a72bfa3ab71..8cf5fe9d7519 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 @@ -15,7 +15,9 @@ #include #include #include +#include #include +#include namespace facebook::react { @@ -29,6 +31,20 @@ static LayoutConstraints makeLayoutConstraints( .layoutDirection = LayoutDirection::LeftToRight}; } +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)) { @@ -80,13 +96,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( @@ -97,6 +145,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); } @@ -233,9 +293,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 2e2b0192eca6..8328dd8927b9 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 @@ -15,6 +15,8 @@ #include +#include + namespace facebook::react { /** @@ -27,7 +29,12 @@ class FabricMountingManagerTestHelper : public jni::HybridClass tags, @@ -57,6 +64,7 @@ class FabricMountingManagerTestHelper : public jni::HybridClass viewComponentDescriptor_; std::unique_ptr shadowTreeDelegate_; std::unique_ptr shadowTree_; + 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 9f05e5f880b1..90df474de656 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 @@ -21,6 +21,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 @@ -80,6 +82,7 @@ class FabricMountingManagerInstrumentationTest { fun tearDown() { nativeFeatureFlagsHelper?.resetFeatureFlags() ReactNativeFeatureFlags.dangerouslyReset() + ReactNativeFeatureFlagsCxxInterop.dangerouslyReset() } private fun startSurface() { @@ -138,7 +141,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) @@ -262,6 +265,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_. @@ -274,7 +286,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() { @@ -364,6 +377,68 @@ class FabricMountingManagerInstrumentationTest { 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() + } + @Test fun native_updateSurfaceConstraints_resendsPaddingAndOverflowInsetInPixels() { val (helper, view) = mountPaddedView(fixTextLayoutOnPixelDensityChange = true) 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 ce133c1d0a67..740a5b5508e5 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..a581c6926c38 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,68 @@ 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" + // NOLINTNEXTLINE(facebook-hte-Deprecated) + 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 241db0ffda91..5898c45ea840 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -1252,7 +1252,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); @@ -2272,6 +2271,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; @@ -2287,6 +2287,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 011a67c223d2..a786e6925764 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api @@ -1951,7 +1951,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); @@ -3656,6 +3655,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; @@ -3671,6 +3671,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 7bae72ea9b37..660b84987c29 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -1252,7 +1252,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); @@ -2265,6 +2264,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; @@ -2280,6 +2280,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 7bae72ea9b37..660b84987c29 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -1252,7 +1252,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); @@ -2265,6 +2264,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; @@ -2280,6 +2280,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 2d699b6dd5df..270c2e971194 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -4532,6 +4532,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; @@ -4547,6 +4548,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 4293fa4df491..db24a188346b 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api @@ -5862,6 +5862,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; @@ -5877,6 +5878,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 1dac46cf4f3e..851a35fc6c79 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -4517,6 +4517,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; @@ -4532,6 +4533,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 9beb074b5b71..986bf61c700c 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -4525,6 +4525,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; @@ -4540,6 +4541,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 0ef5fda7c553..1a56891b0bb5 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -1078,6 +1078,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; @@ -1093,6 +1094,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 b3011c1fec07..7f05b78712e7 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api @@ -2251,6 +2251,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; @@ -2266,6 +2267,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 a3cbf8e8077f..ec6ed522456f 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api @@ -1071,6 +1071,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; @@ -1086,6 +1087,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 a3cbf8e8077f..ec6ed522456f 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -1071,6 +1071,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; @@ -1086,6 +1087,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);