Skip to content

Commit 68aeffe

Browse files
astreetFacebook Github Bot
authored andcommitted
Queue JS calls that come in before JS bundle has started loading instead of crashing
Summary: This mimics (some of) the behavior we have on iOS where if you call a JS module method before the JS bundle has started loading, we just queue up those calls and execute them after the bundle has started loading. Reviewed By: javache Differential Revision: D4117581 fbshipit-source-id: 58c5a6f87aeeb86083385334d92f2716a0574ba1
1 parent 52d90da commit 68aeffe

File tree

3 files changed

+46
-13
lines changed

3 files changed

+46
-13
lines changed

ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ void callFunction(
4040
*/
4141
void destroy();
4242
boolean isDestroyed();
43-
boolean isAcceptingCalls();
4443

4544
/**
4645
* Initialize all the native modules

ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ public class ReactContext extends ContextWrapper {
4040

4141
private static final String EARLY_JS_ACCESS_EXCEPTION_MESSAGE =
4242
"Tried to access a JS module before the React instance was fully set up. Calls to " +
43-
"ReactContext#getJSModule should be protected by ReactContext#hasActiveCatalystInstance().";
43+
"ReactContext#getJSModule should only happen once initialize() has been called on your " +
44+
"native module.";
4445

4546
private final CopyOnWriteArraySet<LifecycleEventListener> mLifecycleEventListeners =
4647
new CopyOnWriteArraySet<>();
@@ -143,7 +144,7 @@ public CatalystInstance getCatalystInstance() {
143144
}
144145

145146
public boolean hasActiveCatalystInstance() {
146-
return mCatalystInstance != null && mCatalystInstance.isAcceptingCalls();
147+
return mCatalystInstance != null && !mCatalystInstance.isDestroyed();
147148
}
148149

149150
public LifecycleState getLifecycleState() {

ReactAndroid/src/main/java/com/facebook/react/cxxbridge/CatalystInstanceImpl.java

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import javax.annotation.Nullable;
1313

1414
import java.lang.ref.WeakReference;
15+
import java.util.ArrayList;
1516
import java.util.Collection;
1617
import java.util.concurrent.CopyOnWriteArrayList;
1718
import java.util.concurrent.atomic.AtomicInteger;
@@ -57,6 +58,25 @@ public class CatalystInstanceImpl implements CatalystInstance {
5758

5859
private static final AtomicInteger sNextInstanceIdForTrace = new AtomicInteger(1);
5960

61+
private static class PendingJSCall {
62+
63+
public ExecutorToken mExecutorToken;
64+
public String mModule;
65+
public String mMethod;
66+
public NativeArray mArguments;
67+
68+
public PendingJSCall(
69+
ExecutorToken executorToken,
70+
String module,
71+
String method,
72+
NativeArray arguments) {
73+
mExecutorToken = executorToken;
74+
mModule = module;
75+
mMethod = method;
76+
mArguments = arguments;
77+
}
78+
}
79+
6080
// Access from any thread
6181
private final ReactQueueConfigurationImpl mReactQueueConfiguration;
6282
private final CopyOnWriteArrayList<NotThreadSafeBridgeIdleDebugListener> mBridgeIdleListeners;
@@ -67,6 +87,8 @@ public class CatalystInstanceImpl implements CatalystInstance {
6787
private final TraceListener mTraceListener;
6888
private final JavaScriptModuleRegistry mJSModuleRegistry;
6989
private final JSBundleLoader mJSBundleLoader;
90+
private final ArrayList<PendingJSCall> mJSCallsPendingInit = new ArrayList<PendingJSCall>();
91+
private final Object mJSCallsPendingInitLock = new Object();
7092
private ExecutorToken mMainExecutorToken;
7193

7294
private final NativeModuleRegistry mJavaRegistry;
@@ -168,10 +190,20 @@ public void runJSBundle() {
168190
mJSBundleHasLoaded = true;
169191
// incrementPendingJSCalls();
170192
mJSBundleLoader.loadScript(CatalystInstanceImpl.this);
171-
// Loading the bundle is queued on the JS thread, but may not have
172-
// run yet. It's save to set this here, though, since any work it
173-
// gates will be queued on the JS thread behind the load.
174-
mAcceptCalls = true;
193+
194+
synchronized (mJSCallsPendingInitLock) {
195+
// Loading the bundle is queued on the JS thread, but may not have
196+
// run yet. It's save to set this here, though, since any work it
197+
// gates will be queued on the JS thread behind the load.
198+
mAcceptCalls = true;
199+
200+
for (PendingJSCall call : mJSCallsPendingInit) {
201+
callJSFunction(call.mExecutorToken, call.mModule, call.mMethod, call.mArguments);
202+
}
203+
mJSCallsPendingInit.clear();
204+
}
205+
206+
175207
// This is registered after JS starts since it makes a JS call
176208
Systrace.registerListener(mTraceListener);
177209
}
@@ -193,7 +225,13 @@ public void callFunction(
193225
return;
194226
}
195227
if (!mAcceptCalls) {
196-
throw new RuntimeException("Attempt to call JS function before JS bundle is loaded.");
228+
// Most of the time the instance is initialized and we don't need to acquire the lock
229+
synchronized (mJSCallsPendingInitLock) {
230+
if (!mAcceptCalls) {
231+
mJSCallsPendingInit.add(new PendingJSCall(executorToken, module, method, arguments));
232+
return;
233+
}
234+
}
197235
}
198236

199237
callJSFunction(executorToken, module, method, arguments);
@@ -244,11 +282,6 @@ public boolean isDestroyed() {
244282
return mDestroyed;
245283
}
246284

247-
@Override
248-
public boolean isAcceptingCalls() {
249-
return !mDestroyed && mAcceptCalls;
250-
}
251-
252285
/**
253286
* Initialize all the native modules
254287
*/

0 commit comments

Comments
 (0)