diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManager.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManager.cpp index 4db7ec727978..da64334cc4eb 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManager.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManager.cpp @@ -23,8 +23,8 @@ Size measureHostComponent( return {}; } - const auto fabricUIManager = - contextContainer->at>("FabricUIManager"); + auto fabricUIManager = + contextContainer->at>("FabricUIManager"); static const auto measure = jni::findClassStatic("com/facebook/react/fabric/FabricUIManager") ->getMethodinsert("FabricUIManager", globalJavaUiManager); + auto fabricUIManagerOwner = + std::make_shared>(globalJavaUiManager); + // The alias and its owner must stay registered for the container's lifetime. + contextContainer->insert("FabricUIManagerOwner", fabricUIManagerOwner); + contextContainer->insert( + "FabricUIManager", jni::alias_ref(*fabricUIManagerOwner)); animationChoreographer_ = std::make_shared(); 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 0486e4529769..8734121cf914 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 @@ -79,6 +79,63 @@ std::vector toTags(jni::alias_ref tags) { return {region.get(), region.get() + tags->size()}; } +bool fabricUIManagerAliasUsesOwnedHandle( + [[maybe_unused]] jni::alias_ref javaClass, + jni::alias_ref javaUIManager) { + auto contextContainer = + javaUIManager->getBinding()->getScheduler()->getContextContainer(); + auto owner = contextContainer->at>>( + "FabricUIManagerOwner"); + auto alias = contextContainer->at>("FabricUIManager"); + auto env = jni::Environment::current(); + return alias.get() == owner->get() && + env->GetObjectRefType(alias.get()) == JNIGlobalRefType && + jni::isSameObject(alias.get(), javaUIManager.get()); +} + +jni::local_ref readFabricUIManagerFromContext( + [[maybe_unused]] jni::alias_ref javaClass, + jni::alias_ref javaUIManager, + jboolean useGlobalRef, + jboolean uninstall) { + static const auto bindingField = + JFabricUIManager::javaClassStatic() + ->getField("mBinding"); + static const auto unregister = + jni::findClassStatic("com/facebook/react/fabric/FabricUIManagerBinding") + ->getMethod("unregister"); + auto javaBinding = javaUIManager->getFieldValue(bindingField); + auto contextContainer = + javaBinding->cthis()->getScheduler()->getContextContainer(); + if (useGlobalRef != 0) { + auto global = + contextContainer->at>("FabricUIManager"); + if (uninstall != 0) { + std::weak_ptr> owner = + contextContainer->at>>( + "FabricUIManagerOwner"); + unregister(javaBinding); + contextContainer->erase("FabricUIManager"); + contextContainer->erase("FabricUIManagerOwner"); + if (!owner.expired()) { + jni::throwNewJavaException( + "java/lang/AssertionError", + "FabricUIManager owner survived teardown"); + } + contextContainer.reset(); + } + return jni::make_local( + jni::static_ref_cast( + jni::alias_ref(global))); + } + if (uninstall != 0) { + unregister(javaBinding); + } + auto alias = contextContainer->at>("FabricUIManager"); + return jni::make_local( + jni::static_ref_cast(alias)); +} + } // namespace FabricMountingManagerTestHelper::FabricMountingManagerTestHelper( @@ -402,6 +459,11 @@ FabricMountingManagerTestHelper::pulledDeleteTags() { void FabricMountingManagerTestHelper::registerNatives() { registerHybrid({ + makeNativeMethod( + "fabricUIManagerAliasUsesOwnedHandle", + fabricUIManagerAliasUsesOwnedHandle), + makeNativeMethod( + "readFabricUIManagerFromContext", readFabricUIManagerFromContext), makeNativeMethod( "initHybrid", FabricMountingManagerTestHelper::initHybrid), makeNativeMethod( 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 f88622d5e48d..781e0b709113 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 @@ -252,7 +252,7 @@ class FabricMountingManagerInstrumentationTest { } private fun mountPaddedView( - fixTextLayoutOnPixelDensityChange: Boolean + fixTextLayoutOnPixelDensityChange: Boolean, ): Pair { val fabricUIManager = createFabricUIManager(listOf(PaddingViewManager())) val helper = FabricMountingManagerTestHelper.create(fabricUIManager) @@ -281,7 +281,7 @@ class FabricMountingManagerInstrumentationTest { ReactNativeFeatureFlagsCxxInterop.dangerouslyForceOverride( object : ReactNativeFeatureFlagsDefaults() { override fun enableMountingCoordinatorPullModelAndroid(): Boolean = true - } + }, ) } @@ -501,7 +501,7 @@ class FabricMountingManagerInstrumentationTest { override fun enablePropsUpdateReconciliationAndroid(): Boolean = pullModel override fun enablePreallocatedPropsDiffOnInsertAndroid(): Boolean = pullModel - } + }, ) } @@ -582,6 +582,71 @@ class FabricMountingManagerInstrumentationTest { return surfaceId to { fabricUIManager.stopSurface(surfaceId) } } + @Test + fun native_fabricUIManagerContext_aliasUsesOwnedGlobalHandle() { + val fabricUIManager = createFabricUIManager() + registerBinding(fabricUIManager) + + assertThat(FabricMountingManagerTestHelper.fabricUIManagerAliasUsesOwnedHandle(fabricUIManager)) + .isTrue() + assertThat( + FabricMountingManagerTestHelper.readFabricUIManagerFromContext( + fabricUIManager, + useGlobalRef = false, + uninstall = false, + ), + ) + .isSameAs(fabricUIManager) + } + + @Test + fun native_fabricUIManagerContext_legacyCopiesLeaveAliasValid() { + val fabricUIManager = createFabricUIManager() + registerBinding(fabricUIManager) + + repeat(64) { + assertThat( + FabricMountingManagerTestHelper.readFabricUIManagerFromContext( + fabricUIManager, + useGlobalRef = true, + uninstall = false, + ), + ) + .isSameAs(fabricUIManager) + assertThat( + FabricMountingManagerTestHelper.fabricUIManagerAliasUsesOwnedHandle(fabricUIManager), + ) + .isTrue() + assertThat( + FabricMountingManagerTestHelper.readFabricUIManagerFromContext( + fabricUIManager, + useGlobalRef = false, + uninstall = false, + ), + ) + .isSameAs(fabricUIManager) + } + } + + @Test + fun native_fabricUIManagerContext_readsSurviveUninstall() { + for (useGlobalRef in listOf(false, true)) { + repeat(8) { + val fabricUIManager = createFabricUIManager() + registerBinding(fabricUIManager) + + assertThat( + FabricMountingManagerTestHelper.readFabricUIManagerFromContext( + fabricUIManager, + useGlobalRef = useGlobalRef, + uninstall = true, + ), + ) + .isSameAs(fabricUIManager) + } + } + } + @Test fun native_stopSurfaceWithSurfaceHandler_pullModel_pullsDeletes() { assertStoppingSurfacePullsDeletes(pullModel = true, ::startBridgelessSurface) 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 5edd4f0c00cb..733690fbd41e 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 @@ -90,6 +90,16 @@ private constructor(fabricUIManager: FabricUIManager) : HybridClassBase() { SoLoader.loadLibrary("fabricjni_test_helper") } + @JvmStatic + external fun fabricUIManagerAliasUsesOwnedHandle(fabricUIManager: FabricUIManager): Boolean + + @JvmStatic + external fun readFabricUIManagerFromContext( + fabricUIManager: FabricUIManager, + useGlobalRef: Boolean, + uninstall: Boolean, + ): FabricUIManager + @JvmStatic fun create(fabricUIManager: FabricUIManager): FabricMountingManagerTestHelper { return FabricMountingManagerTestHelper(fabricUIManager) diff --git a/packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewComponentDescriptor.cpp b/packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewComponentDescriptor.cpp index 4e79bc4edf5d..c993d03b5f86 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewComponentDescriptor.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/modal/ModalHostViewComponentDescriptor.cpp @@ -21,8 +21,8 @@ State::Shared ModalHostViewComponentDescriptor::createInitialState( int surfaceId = family->getSurfaceId(); - const jni::global_ref& fabricUIManager = - contextContainer_->at>("FabricUIManager"); + auto fabricUIManager = + contextContainer_->at>("FabricUIManager"); static auto getEncodedScreenSizeWithoutVerticalInsets = jni::findClassStatic(UIManagerJavaDescriptor) diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputComponentDescriptor.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputComponentDescriptor.h index 5c08a1bf6787..40b0b970ddd5 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputComponentDescriptor.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputComponentDescriptor.h @@ -44,8 +44,7 @@ class AndroidTextInputComponentDescriptor final : public ConcreteComponentDescri if (surfaceIdToThemePaddingMap_.find(surfaceId) != surfaceIdToThemePaddingMap_.end()) { theme = surfaceIdToThemePaddingMap_[surfaceId]; } else { - const jni::global_ref &fabricUIManager = - contextContainer_->at>("FabricUIManager"); + auto fabricUIManager = contextContainer_->at>("FabricUIManager"); auto env = jni::Environment::current(); auto defaultTextInputPaddingArray = env->NewFloatArray(4); diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/android/react/renderer/graphics/PlatformColorParser.cpp b/packages/react-native/ReactCommon/react/renderer/graphics/platform/android/react/renderer/graphics/PlatformColorParser.cpp index 68f962ff320f..c50edbb9de11 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/android/react/renderer/graphics/PlatformColorParser.cpp +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/android/react/renderer/graphics/PlatformColorParser.cpp @@ -91,8 +91,8 @@ SharedColor parsePlatformColor( if (iterator != getColorCache.end()) { resolvedColor = iterator->second; } else { - const auto& fabricUIManager = - contextContainer.at>("FabricUIManager"); + auto fabricUIManager = + contextContainer.at>("FabricUIManager"); // Boxed Integer: null is an explicit miss; a non-null value may be 0 // (transparent black). static auto getColorFromJava = diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageFetcher.cpp b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageFetcher.cpp index 9cc2f96b9f24..2339b4195cf6 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageFetcher.cpp +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageFetcher.cpp @@ -48,7 +48,7 @@ void ImageFetcher::flushImageRequests() { } auto fabricUIManager_ = - contextContainer_->at>("FabricUIManager"); + contextContainer_->at>("FabricUIManager"); static auto prefetchResources = fabricUIManager_->getClass() ->getMethod& fabricUIManager = - contextContainer->at>("FabricUIManager"); + auto fabricUIManager = + contextContainer->at>("FabricUIManager"); static auto measure = jni::findClassStatic("com/facebook/react/fabric/FabricUIManager") @@ -245,8 +245,8 @@ LinesMeasurements TextLayoutManager::measureLines( const auto& attributedString = attributedStringBox.getValue(); auto doMeasureLines = [&]() { - const jni::global_ref& fabricUIManager = - contextContainer_->at>("FabricUIManager"); + auto fabricUIManager = + contextContainer_->at>("FabricUIManager"); static auto measureLines = jni::findClassStatic("com/facebook/react/fabric/FabricUIManager") ->getMethodat>("FabricUIManager"); + auto fabricUIManager = + contextContainer_->at>("FabricUIManager"); auto attributedStringMB = JReadableMapBuffer::createWithContents( toMapBuffer(attributedString)); auto paragraphAttributesMB = JReadableMapBuffer::createWithContents( @@ -364,8 +364,8 @@ TextLayoutManager::PreparedTextLayout TextLayoutManager::prepareLayout( javaReactTags->setRegion( 0, static_cast(reactTags.size()), reactTags.data()); - const auto& fabricUIManager = - contextContainer_->at>("FabricUIManager"); + auto fabricUIManager = + contextContainer_->at>("FabricUIManager"); return PreparedTextLayout{ jni::make_global(reusePreparedLayoutWithNewReactTags( fabricUIManager, preparedText->get(), javaReactTags.get()))}; @@ -378,8 +378,8 @@ TextMeasurement TextLayoutManager::measurePreparedLayout( const PreparedTextLayout& preparedLayout, const TextLayoutContext& /*layoutContext*/, const LayoutConstraints& layoutConstraints) const { - const auto& fabricUIManager = - contextContainer_->at>("FabricUIManager"); + auto fabricUIManager = + contextContainer_->at>("FabricUIManager"); static auto measurePreparedLayout = jni::findClassStatic("com/facebook/react/fabric/FabricUIManager")