Display JS component stack in native view exceptions
Reviewed By: mdvacca Differential Revision: D7578033 fbshipit-source-id: 4dc393cddf8487db58cc3a9fefbff220983ba9da
This commit is contained in:
parent
c4ab03a18e
commit
ff9b3c6517
|
@ -72,6 +72,7 @@ export type NativeMethodsMixinType = {
|
|||
type SecretInternalsType = {
|
||||
NativeMethodsMixin: NativeMethodsMixinType,
|
||||
ReactNativeComponentTree: any,
|
||||
computeComponentStackForErrorReporting(tag: number): string,
|
||||
// TODO (bvaughn) Decide which additional types to expose here?
|
||||
// And how much information to fill in for the above types.
|
||||
};
|
||||
|
|
|
@ -9,16 +9,25 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
var JSDevSupportModule = {
|
||||
getJSHierarchy: function (tag: string) {
|
||||
const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
||||
const renderers = hook._renderers;
|
||||
const keys = Object.keys(renderers);
|
||||
const renderer = renderers[keys[0]];
|
||||
const JSDevSupport = require('NativeModules').JSDevSupport;
|
||||
const ReactNative = require('ReactNative');
|
||||
|
||||
var result = renderer.getInspectorDataForViewTag(tag);
|
||||
var path = result.hierarchy.map( (item) => item.name).join(' -> ');
|
||||
require('NativeModules').JSDevSupport.setResult(path, null);
|
||||
const JSDevSupportModule = {
|
||||
getJSHierarchy: function (tag: number) {
|
||||
try {
|
||||
const {computeComponentStackForErrorReporting} =
|
||||
ReactNative.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
|
||||
const componentStack = computeComponentStackForErrorReporting(tag);
|
||||
if (!componentStack) {
|
||||
JSDevSupport.onFailure(
|
||||
JSDevSupport.ERROR_CODE_VIEW_NOT_FOUND,
|
||||
'Component stack doesn\'t exist for tag ' + tag);
|
||||
} else {
|
||||
JSDevSupport.onSuccess(componentStack);
|
||||
}
|
||||
} catch (e) {
|
||||
JSDevSupport.onFailure(JSDevSupport.ERROR_CODE_EXCEPTION, e.message);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -48,15 +48,6 @@ import javax.inject.Provider;
|
|||
return new JSCHeapCapture(reactContext);
|
||||
}
|
||||
}));
|
||||
moduleSpecList.add(
|
||||
ModuleSpec.nativeModuleSpec(
|
||||
JSDevSupport.class,
|
||||
new Provider<NativeModule>() {
|
||||
@Override
|
||||
public NativeModule get() {
|
||||
return new JSDevSupport(reactContext);
|
||||
}
|
||||
}));
|
||||
moduleSpecList.add(
|
||||
ModuleSpec.nativeModuleSpec(
|
||||
JSCSamplingProfiler.class,
|
||||
|
|
|
@ -42,11 +42,11 @@ import com.facebook.react.modules.deviceinfo.DeviceInfoModule;
|
|||
import com.facebook.react.uimanager.DisplayMetricsHolder;
|
||||
import com.facebook.react.uimanager.IllegalViewOperationException;
|
||||
import com.facebook.react.uimanager.JSTouchDispatcher;
|
||||
import com.facebook.react.uimanager.common.MeasureSpecProvider;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.RootView;
|
||||
import com.facebook.react.uimanager.common.SizeMonitoringFrameLayout;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import com.facebook.react.uimanager.common.MeasureSpecProvider;
|
||||
import com.facebook.react.uimanager.common.SizeMonitoringFrameLayout;
|
||||
import com.facebook.react.uimanager.events.EventDispatcher;
|
||||
import com.facebook.systrace.Systrace;
|
||||
import javax.annotation.Nullable;
|
||||
|
@ -572,18 +572,13 @@ public class ReactRootView extends SizeMonitoringFrameLayout
|
|||
}
|
||||
|
||||
@Override
|
||||
public void handleException(Throwable t) {
|
||||
public void handleException(final Throwable t) {
|
||||
if (mReactInstanceManager == null
|
||||
|| mReactInstanceManager.getCurrentReactContext() == null) {
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
|
||||
// Adding special exception management for StackOverflowError for logging purposes.
|
||||
// This will be removed in the future.
|
||||
Exception e = (t instanceof StackOverflowError) ?
|
||||
new IllegalViewOperationException("StackOverflowException", this, t) :
|
||||
t instanceof Exception ? (Exception) t : new RuntimeException(t);
|
||||
|
||||
Exception e = new IllegalViewOperationException(t.getMessage(), this, t);
|
||||
mReactInstanceManager.getCurrentReactContext().handleException(e);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,10 +22,7 @@ import android.hardware.SensorManager;
|
|||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.debug.holder.PrinterHolder;
|
||||
import com.facebook.debug.tags.ReactDebugOverlayTags;
|
||||
|
@ -45,7 +42,6 @@ import com.facebook.react.common.ReactConstants;
|
|||
import com.facebook.react.common.ShakeDetector;
|
||||
import com.facebook.react.common.futures.SimpleSettableFuture;
|
||||
import com.facebook.react.devsupport.DevServerHelper.PackagerCommandListener;
|
||||
import com.facebook.react.devsupport.InspectorPackagerConnection;
|
||||
import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener;
|
||||
import com.facebook.react.devsupport.interfaces.DevOptionHandler;
|
||||
import com.facebook.react.devsupport.interfaces.DevSupportManager;
|
||||
|
@ -55,24 +51,18 @@ import com.facebook.react.devsupport.interfaces.StackFrame;
|
|||
import com.facebook.react.modules.debug.interfaces.DeveloperSettings;
|
||||
import com.facebook.react.packagerconnection.RequestHandler;
|
||||
import com.facebook.react.packagerconnection.Responder;
|
||||
|
||||
import com.facebook.react.uimanager.IllegalViewOperationException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
|
@ -274,7 +264,6 @@ public class DevSupportManagerImpl implements
|
|||
new DevLoadingViewController(applicationContext, reactInstanceManagerHelper);
|
||||
|
||||
mExceptionLoggers.add(new JSExceptionLogger());
|
||||
mExceptionLoggers.add(new StackOverflowExceptionLogger());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -322,66 +311,6 @@ public class DevSupportManagerImpl implements
|
|||
}
|
||||
}
|
||||
|
||||
private class StackOverflowExceptionLogger implements ExceptionLogger {
|
||||
|
||||
@Override
|
||||
public void log(Exception e) {
|
||||
if (e instanceof IllegalViewOperationException
|
||||
&& e.getCause() instanceof StackOverflowError) {
|
||||
IllegalViewOperationException ivoe = (IllegalViewOperationException) e;
|
||||
View view = ivoe.getView();
|
||||
if (view != null)
|
||||
logDeepestJSHierarchy(view);
|
||||
}
|
||||
}
|
||||
|
||||
private void logDeepestJSHierarchy(View view) {
|
||||
if (mCurrentContext == null || view == null) return;
|
||||
|
||||
final Pair<View, Integer> deepestPairView = getDeepestNativeView(view);
|
||||
|
||||
View deepestView = deepestPairView.first;
|
||||
Integer tagId = deepestView.getId();
|
||||
final int depth = deepestPairView.second;
|
||||
JSDevSupport JSDevSupport = mCurrentContext.getNativeModule(JSDevSupport.class);
|
||||
JSDevSupport.getJSHierarchy(tagId.toString(), new JSDevSupport.DevSupportCallback() {
|
||||
@Override
|
||||
public void onSuccess(String hierarchy) {
|
||||
FLog.e(ReactConstants.TAG,
|
||||
"StackOverflowError when rendering JS Hierarchy (depth of native hierarchy = " +
|
||||
depth + "): \n" + hierarchy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception ex) {
|
||||
FLog.e(ReactConstants.TAG, ex,
|
||||
"Error retrieving JS Hierarchy (depth of native hierarchy = " + depth + ").");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Pair<View, Integer> getDeepestNativeView(View root) {
|
||||
Queue<Pair<View, Integer>> queue = new LinkedList<>();
|
||||
Pair<View, Integer> maxPair = new Pair<>(root, 1);
|
||||
|
||||
queue.add(maxPair);
|
||||
while (!queue.isEmpty()) {
|
||||
Pair<View, Integer> current = queue.poll();
|
||||
if (current.second > maxPair.second) {
|
||||
maxPair = current;
|
||||
}
|
||||
if (current.first instanceof ViewGroup) {
|
||||
ViewGroup viewGroup = (ViewGroup) current.first;
|
||||
Integer depth = current.second + 1;
|
||||
for (int i = 0 ; i < viewGroup.getChildCount() ; i++) {
|
||||
queue.add(new Pair<>(viewGroup.getChildAt(i), depth));
|
||||
}
|
||||
}
|
||||
}
|
||||
return maxPair;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showNewJavaError(String message, Throwable e) {
|
||||
FLog.e(ReactConstants.TAG, "Exception in native call", e);
|
||||
|
|
|
@ -2,23 +2,31 @@
|
|||
|
||||
package com.facebook.react.devsupport;
|
||||
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@ReactModule(name = "JSDevSupport", needsEagerInit = true)
|
||||
@ReactModule(name = JSDevSupport.MODULE_NAME)
|
||||
public class JSDevSupport extends ReactContextBaseJavaModule {
|
||||
|
||||
static final String MODULE_NAME = "JSDevSupport";
|
||||
|
||||
public static final int ERROR_CODE_EXCEPTION = 0;
|
||||
public static final int ERROR_CODE_VIEW_NOT_FOUND = 1;
|
||||
|
||||
@Nullable
|
||||
private volatile DevSupportCallback mCurrentCallback = null;
|
||||
|
||||
public interface JSDevSupportModule extends JavaScriptModule {
|
||||
void getJSHierarchy(String reactTag);
|
||||
void getJSHierarchy(int reactTag);
|
||||
}
|
||||
|
||||
public JSDevSupport(ReactApplicationContext reactContext) {
|
||||
|
@ -29,19 +37,25 @@ public class JSDevSupport extends ReactContextBaseJavaModule {
|
|||
|
||||
void onSuccess(String data);
|
||||
|
||||
void onFailure(Exception error);
|
||||
void onFailure(int errorCode, Exception error);
|
||||
}
|
||||
|
||||
public synchronized void getJSHierarchy(String reactTag, DevSupportCallback callback) {
|
||||
if (mCurrentCallback != null) {
|
||||
callback.onFailure(new RuntimeException("JS Hierarchy download already in progress."));
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Notifies the callback with either the JS hierarchy of the deepest leaf from the given root view
|
||||
* or with an error.
|
||||
*/
|
||||
public synchronized void computeDeepestJSHierarchy(View root, DevSupportCallback callback) {
|
||||
final Pair<View, Integer> deepestPairView = ViewHierarchyUtil.getDeepestLeaf(root);
|
||||
View deepestView = deepestPairView.first;
|
||||
Integer tagId = deepestView.getId();
|
||||
getJSHierarchy(tagId, callback);
|
||||
}
|
||||
|
||||
public synchronized void getJSHierarchy(int reactTag, DevSupportCallback callback) {
|
||||
JSDevSupportModule
|
||||
jsDevSupportModule = getReactApplicationContext().getJSModule(JSDevSupportModule.class);
|
||||
if (jsDevSupportModule == null) {
|
||||
callback.onFailure(new JSCHeapCapture.CaptureException(MODULE_NAME +
|
||||
callback.onFailure(ERROR_CODE_EXCEPTION, new JSCHeapCapture.CaptureException(MODULE_NAME +
|
||||
" module not registered."));
|
||||
return;
|
||||
}
|
||||
|
@ -51,20 +65,31 @@ public class JSDevSupport extends ReactContextBaseJavaModule {
|
|||
|
||||
@SuppressWarnings("unused")
|
||||
@ReactMethod
|
||||
public synchronized void setResult(String data, String error) {
|
||||
public synchronized void onSuccess(String data) {
|
||||
if (mCurrentCallback != null) {
|
||||
if (error == null) {
|
||||
mCurrentCallback.onSuccess(data);
|
||||
} else {
|
||||
mCurrentCallback.onFailure(new RuntimeException(error));
|
||||
}
|
||||
mCurrentCallback.onSuccess(data);
|
||||
}
|
||||
mCurrentCallback = null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@ReactMethod
|
||||
public synchronized void onFailure(int errorCode, String error) {
|
||||
if (mCurrentCallback != null) {
|
||||
mCurrentCallback.onFailure(errorCode, new RuntimeException(error));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getConstants() {
|
||||
HashMap<String, Object> constants = new HashMap<>();
|
||||
constants.put("ERROR_CODE_EXCEPTION", ERROR_CODE_EXCEPTION);
|
||||
constants.put("ERROR_CODE_VIEW_NOT_FOUND", ERROR_CODE_VIEW_NOT_FOUND);
|
||||
return constants;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "JSDevSupport";
|
||||
return MODULE_NAME;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
package com.facebook.react.devsupport;
|
||||
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
/**
|
||||
* Helper for computing information about the view hierarchy
|
||||
*/
|
||||
public class ViewHierarchyUtil {
|
||||
|
||||
/**
|
||||
* Returns the view instance and depth of the deepest leaf view from the given root view.
|
||||
*/
|
||||
public static Pair<View, Integer> getDeepestLeaf(View root) {
|
||||
Queue<Pair<View, Integer>> queue = new LinkedList<>();
|
||||
Pair<View, Integer> maxPair = new Pair<>(root, 1);
|
||||
|
||||
queue.add(maxPair);
|
||||
while (!queue.isEmpty()) {
|
||||
Pair<View, Integer> current = queue.poll();
|
||||
if (current.second > maxPair.second) {
|
||||
maxPair = current;
|
||||
}
|
||||
if (current.first instanceof ViewGroup) {
|
||||
ViewGroup viewGroup = (ViewGroup) current.first;
|
||||
Integer depth = current.second + 1;
|
||||
for (int i = 0 ; i < viewGroup.getChildCount() ; i++) {
|
||||
queue.add(new Pair<>(viewGroup.getChildAt(i), depth));
|
||||
}
|
||||
}
|
||||
}
|
||||
return maxPair;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue