Skip to content

Commit dc1c28a

Browse files
andrewdacenkometa-codesync[bot]
authored andcommitted
Resolve current React context from the captured host
Summary: `ReactDelegate` selects its host when it is constructed, but `currentReactContext` previously re-read the process-wide architecture flag to decide which host to query. If those states disagreed, a delegate containing a `ReactHost` could incorrectly access its absent legacy host and crash. Resolve the context from the host captured by the delegate instead. Prefer `ReactHost` when present; otherwise return the context from an already initialized `ReactNativeHost`. The legacy path remains non-creating when no instance exists. Add focused coverage for bridgeless and legacy hosts, including a contradictory architecture flag and initialized, uninitialized, and absent legacy hosts. Changelog: [Android][Fixed] - Prevent `ReactDelegate.currentReactContext` from querying the wrong host when architecture state changes Differential Revision: D119096676
1 parent 51020ed commit dc1c28a

2 files changed

Lines changed: 151 additions & 5 deletions

File tree

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactDelegate.kt

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
package com.facebook.react
99

10+
import android.annotation.SuppressLint
1011
import android.app.Activity
1112
import android.content.Intent
1213
import android.content.res.Configuration
@@ -425,16 +426,20 @@ public open class ReactDelegate {
425426
* Do not store a reference to this, if the React instance is reloaded or destroyed, this context
426427
* will no longer be valid.
427428
*/
429+
@get:SuppressLint("DeprecatedClass")
428430
public val currentReactContext: ReactContext?
429431
get() {
430-
return if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
431-
if (reactHost != null) {
432-
reactHost?.currentReactContext
432+
reactHost?.let {
433+
return it.currentReactContext
434+
}
435+
436+
val reactNativeHost = reactNativeHost ?: return null
437+
synchronized(reactNativeHost) {
438+
return if (reactNativeHost.hasInstance()) {
439+
reactNativeHost.reactInstanceManager.currentReactContext
433440
} else {
434441
null
435442
}
436-
} else {
437-
getReactInstanceManager().currentReactContext
438443
}
439444
}
440445
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.react
9+
10+
import android.annotation.SuppressLint
11+
import android.app.Activity
12+
import android.app.Application
13+
import com.facebook.react.bridge.ReactContext
14+
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
15+
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsDefaults
16+
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests
17+
import org.assertj.core.api.Assertions.assertThat
18+
import org.junit.After
19+
import org.junit.Before
20+
import org.junit.Test
21+
import org.junit.runner.RunWith
22+
import org.mockito.kotlin.doReturn
23+
import org.mockito.kotlin.mock
24+
import org.robolectric.Robolectric
25+
import org.robolectric.RobolectricTestRunner
26+
27+
@SuppressLint(
28+
"ActivityStoredInField",
29+
"DeprecatedClass",
30+
"DeprecatedMethod",
31+
"DeprecatedSuperclass",
32+
)
33+
@RunWith(RobolectricTestRunner::class)
34+
class ReactDelegateTest {
35+
36+
private lateinit var activity: Activity
37+
38+
@Before
39+
fun setUp() {
40+
ReactNativeFeatureFlagsForTests.setUp()
41+
activity = Robolectric.buildActivity(Activity::class.java).create().get()
42+
}
43+
44+
@After
45+
fun tearDown() {
46+
ReactNativeFeatureFlags.dangerouslyReset()
47+
}
48+
49+
@Test
50+
fun currentReactContext_withReactHost_returnsContext() {
51+
overrideBridgelessArchitecture(true)
52+
val reactContext = mock<ReactContext>()
53+
val reactHost = mock<ReactHost> { on { currentReactContext } doReturn reactContext }
54+
val delegate = ReactDelegate(activity, reactHost, "test-app", null)
55+
56+
assertThat(delegate.currentReactContext).isSameAs(reactContext)
57+
}
58+
59+
@Test
60+
fun currentReactContext_withReactHost_ignoresArchitectureFlag() {
61+
overrideBridgelessArchitecture(false)
62+
val reactContext = mock<ReactContext>()
63+
val reactHost = mock<ReactHost> { on { currentReactContext } doReturn reactContext }
64+
val delegate = ReactDelegate(activity, reactHost, "test-app", null)
65+
66+
assertThat(delegate.currentReactContext).isSameAs(reactContext)
67+
}
68+
69+
@Suppress("DEPRECATION")
70+
@Test
71+
fun currentReactContext_withoutReactNativeHost_returnsNull() {
72+
overrideBridgelessArchitecture(false)
73+
val delegate = ReactDelegate(activity, null as ReactNativeHost?, "test-app", null)
74+
75+
assertThat(delegate.currentReactContext).isNull()
76+
}
77+
78+
@Suppress("DEPRECATION")
79+
@Test
80+
fun currentReactContext_withUninitializedReactNativeHost_doesNotCreateInstance() {
81+
overrideBridgelessArchitecture(false)
82+
val reactNativeHost =
83+
object : ReactNativeHost(activity.application as Application) {
84+
override fun getUseDeveloperSupport(): Boolean = false
85+
86+
override fun getPackages(): List<ReactPackage> = emptyList()
87+
}
88+
val delegate = ReactDelegate(activity, reactNativeHost, "test-app", null)
89+
90+
assertThat(delegate.currentReactContext).isNull()
91+
assertThat(reactNativeHost.hasInstance()).isFalse()
92+
}
93+
94+
@Suppress("DEPRECATION")
95+
@Test
96+
fun currentReactContext_withInitializedReactNativeHost_returnsContext() {
97+
overrideBridgelessArchitecture(false)
98+
val reactContext = mock<ReactContext>()
99+
val instanceManager =
100+
mock<ReactInstanceManager> { on { currentReactContext } doReturn reactContext }
101+
val reactNativeHost = createInitializedReactNativeHost(instanceManager)
102+
val delegate = ReactDelegate(activity, reactNativeHost, "test-app", null)
103+
104+
assertThat(delegate.currentReactContext).isSameAs(reactContext)
105+
}
106+
107+
@Suppress("DEPRECATION")
108+
@Test
109+
fun currentReactContext_withReactNativeHost_ignoresArchitectureFlag() {
110+
overrideBridgelessArchitecture(true)
111+
val reactContext = mock<ReactContext>()
112+
val instanceManager =
113+
mock<ReactInstanceManager> { on { currentReactContext } doReturn reactContext }
114+
val reactNativeHost = createInitializedReactNativeHost(instanceManager)
115+
val delegate = ReactDelegate(activity, reactNativeHost, "test-app", null)
116+
117+
assertThat(delegate.currentReactContext).isSameAs(reactContext)
118+
}
119+
120+
@Suppress("DEPRECATION")
121+
private fun createInitializedReactNativeHost(
122+
instanceManager: ReactInstanceManager,
123+
): ReactNativeHost =
124+
object : ReactNativeHost(activity.application as Application) {
125+
override val reactInstanceManager: ReactInstanceManager = instanceManager
126+
127+
override fun hasInstance(): Boolean = true
128+
129+
override fun getUseDeveloperSupport(): Boolean = false
130+
131+
override fun getPackages(): List<ReactPackage> = emptyList()
132+
}
133+
134+
private fun overrideBridgelessArchitecture(enabled: Boolean) {
135+
ReactNativeFeatureFlags.override(
136+
object : ReactNativeFeatureFlagsDefaults() {
137+
override fun enableBridgelessArchitecture(): Boolean = enabled
138+
},
139+
)
140+
}
141+
}

0 commit comments

Comments
 (0)