crash gracefully on fatal js errors (take two)

Reviewed By: AaaChiuuu

Differential Revision: D2773513

fb-gh-sync-id: f8e50ad12e808caf1d85c6c7859c04fcabdaaeae
This commit is contained in:
Felix Oghina 2016-01-04 13:16:58 -08:00 committed by facebook-github-bot-7
parent f2bdb79782
commit 4866ec2767
3 changed files with 33 additions and 14 deletions

View File

@ -19,6 +19,7 @@ import android.app.Application;
import android.content.Intent; import android.content.Intent;
import com.facebook.infer.annotation.Assertions; import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener; import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener;
import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContext;
@ -159,6 +160,7 @@ public abstract class ReactInstanceManager {
protected boolean mUseDeveloperSupport; protected boolean mUseDeveloperSupport;
protected @Nullable LifecycleState mInitialLifecycleState; protected @Nullable LifecycleState mInitialLifecycleState;
protected @Nullable UIImplementationProvider mUIImplementationProvider; protected @Nullable UIImplementationProvider mUIImplementationProvider;
protected @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
protected Builder() { protected Builder() {
} }
@ -242,6 +244,16 @@ public abstract class ReactInstanceManager {
return this; return this;
} }
/**
* Set the exception handler for all native module calls. If not set, the default
* {@link DevSupportManager} will be used, which shows a redbox in dev mode and rethrows
* (crashes the app) in prod mode.
*/
public Builder setNativeModuleCallExceptionHandler(NativeModuleCallExceptionHandler handler) {
mNativeModuleCallExceptionHandler = handler;
return this;
}
/** /**
* Instantiates a new {@link ReactInstanceManagerImpl}. * Instantiates a new {@link ReactInstanceManagerImpl}.
* Before calling {@code build}, the following must be called: * Before calling {@code build}, the following must be called:
@ -274,7 +286,8 @@ public abstract class ReactInstanceManager {
mUseDeveloperSupport, mUseDeveloperSupport,
mBridgeIdleDebugListener, mBridgeIdleDebugListener,
Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"), Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"),
mUIImplementationProvider); mUIImplementationProvider,
mNativeModuleCallExceptionHandler);
} }
} }
} }

View File

@ -36,6 +36,7 @@ import com.facebook.react.bridge.JavaScriptExecutor;
import com.facebook.react.bridge.JavaScriptModule; import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.JavaScriptModulesConfig; import com.facebook.react.bridge.JavaScriptModulesConfig;
import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
import com.facebook.react.bridge.NativeModuleRegistry; import com.facebook.react.bridge.NativeModuleRegistry;
import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener; import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener;
import com.facebook.react.bridge.ProxyJavaScriptExecutor; import com.facebook.react.bridge.ProxyJavaScriptExecutor;
@ -103,6 +104,7 @@ import com.facebook.systrace.Systrace;
private volatile boolean mHasStartedCreatingInitialContext = false; private volatile boolean mHasStartedCreatingInitialContext = false;
private final UIImplementationProvider mUIImplementationProvider; private final UIImplementationProvider mUIImplementationProvider;
private final MemoryPressureRouter mMemoryPressureRouter; private final MemoryPressureRouter mMemoryPressureRouter;
private final @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
private final ReactInstanceDevCommandsHandler mDevInterface = private final ReactInstanceDevCommandsHandler mDevInterface =
new ReactInstanceDevCommandsHandler() { new ReactInstanceDevCommandsHandler() {
@ -198,7 +200,8 @@ import com.facebook.systrace.Systrace;
boolean useDeveloperSupport, boolean useDeveloperSupport,
@Nullable NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener, @Nullable NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener,
LifecycleState initialLifecycleState, LifecycleState initialLifecycleState,
UIImplementationProvider uiImplementationProvider) { UIImplementationProvider uiImplementationProvider,
NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
initializeSoLoaderIfNecessary(applicationContext); initializeSoLoaderIfNecessary(applicationContext);
// TODO(9577825): remove this // TODO(9577825): remove this
@ -222,6 +225,7 @@ import com.facebook.systrace.Systrace;
mLifecycleState = initialLifecycleState; mLifecycleState = initialLifecycleState;
mUIImplementationProvider = uiImplementationProvider; mUIImplementationProvider = uiImplementationProvider;
mMemoryPressureRouter = new MemoryPressureRouter(applicationContext); mMemoryPressureRouter = new MemoryPressureRouter(applicationContext);
mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
} }
@Override @Override
@ -660,13 +664,16 @@ import com.facebook.systrace.Systrace;
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
} }
NativeModuleCallExceptionHandler exceptionHandler = mNativeModuleCallExceptionHandler != null
? mNativeModuleCallExceptionHandler
: mDevSupportManager;
CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder() CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
.setCatalystQueueConfigurationSpec(CatalystQueueConfigurationSpec.createDefault()) .setCatalystQueueConfigurationSpec(CatalystQueueConfigurationSpec.createDefault())
.setJSExecutor(jsExecutor) .setJSExecutor(jsExecutor)
.setRegistry(nativeModuleRegistry) .setRegistry(nativeModuleRegistry)
.setJSModulesConfig(javaScriptModulesConfig) .setJSModulesConfig(javaScriptModulesConfig)
.setJSBundleLoader(jsBundleLoader) .setJSBundleLoader(jsBundleLoader)
.setNativeModuleCallExceptionHandler(mDevSupportManager); .setNativeModuleCallExceptionHandler(exceptionHandler);
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstance"); Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstance");
CatalystInstance catalystInstance; CatalystInstance catalystInstance;

View File

@ -155,7 +155,7 @@ public class DevSupportManager implements NativeModuleCallExceptionHandler {
public void handleException(Exception e) { public void handleException(Exception e) {
if (mIsDevSupportEnabled) { if (mIsDevSupportEnabled) {
FLog.e(ReactConstants.TAG, "Exception in native call from JS", e); FLog.e(ReactConstants.TAG, "Exception in native call from JS", e);
showNewError(e.getMessage(), StackTraceHelper.convertJavaStackTrace(e), JAVA_ERROR_COOKIE); showNewJavaError(e.getMessage(), e);
} else { } else {
if (e instanceof RuntimeException) { if (e instanceof RuntimeException) {
// Because we are rethrowing the original exception, the original stacktrace will be // Because we are rethrowing the original exception, the original stacktrace will be
@ -167,6 +167,10 @@ public class DevSupportManager implements NativeModuleCallExceptionHandler {
} }
} }
public void showNewJavaError(String message, Throwable e) {
showNewError(message, StackTraceHelper.convertJavaStackTrace(e), JAVA_ERROR_COOKIE);
}
/** /**
* Add option item to dev settings dialog displayed by this manager. In the case user select given * Add option item to dev settings dialog displayed by this manager. In the case user select given
* option from that dialog, the appropriate handler passed as {@param optionHandler} will be * option from that dialog, the appropriate handler passed as {@param optionHandler} will be
@ -555,10 +559,9 @@ public class DevSupportManager implements NativeModuleCallExceptionHandler {
new Runnable() { new Runnable() {
@Override @Override
public void run() { public void run() {
showNewError( showNewJavaError(
mApplicationContext.getString(R.string.catalyst_remotedbg_error), mApplicationContext.getString(R.string.catalyst_remotedbg_error),
StackTraceHelper.convertJavaStackTrace(cause), cause);
JAVA_ERROR_COOKIE);
} }
}); });
} }
@ -590,15 +593,11 @@ public class DevSupportManager implements NativeModuleCallExceptionHandler {
public void run() { public void run() {
if (cause instanceof DebugServerException) { if (cause instanceof DebugServerException) {
DebugServerException debugServerException = (DebugServerException) cause; DebugServerException debugServerException = (DebugServerException) cause;
showNewError( showNewJavaError(debugServerException.description, cause);
debugServerException.description,
StackTraceHelper.convertJavaStackTrace(cause),
JAVA_ERROR_COOKIE);
} else { } else {
showNewError( showNewJavaError(
mApplicationContext.getString(R.string.catalyst_jsload_error), mApplicationContext.getString(R.string.catalyst_jsload_error),
StackTraceHelper.convertJavaStackTrace(cause), cause);
JAVA_ERROR_COOKIE);
} }
} }
}); });