mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-08 22:42:05 +08:00
Add Copy and Dismiss Button in RN Android Red Box
Summary:
Add "Copy" and "Dismiss" button when the RN Android redbox is shown, consistent with that in RN iOS.
- "Copy" button copies all the messages shown in the redbox to the host system clipboard, the solution is posting redbox messages to packager and the the packager copies the messages onto the host clipboard.
- "Dismiss" button always exits the redbox dialog.
- Add shortcut as "Dismiss (ESC)" and "Reload (R, R).
Notice: Copy button is only supported on Mac OS by now (warning in packager on other platforms), because it's not easy for us to test on Windows or Linux. Will put the codes for other platforms on Github issues, hoping anyone could help test and add this feature, then send us a pull request.
Redbox Dialog in RN Android before:
{F61310489}
Redbox Dialog in RN Android now:
{F61659189}
Follow-up:
- We can adjust the button styles in redboxes.
- We can consider to add shortcut for "Copy" button.
Reviewed By: foghina
Differential Revision: D3392155
fbshipit-source-id: fc5dc2186718cac8706fb3c17d336160e61e3f4e
This commit is contained in:
committed by
Facebook Github Bot 5
parent
ca0c6dbe36
commit
dc3fce06ea
@@ -9,9 +9,12 @@
|
||||
|
||||
package com.facebook.react.devsupport;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.devsupport.StackTraceHelper.StackFrame;
|
||||
import com.facebook.react.modules.debug.DeveloperSettings;
|
||||
|
||||
/**
|
||||
@@ -40,4 +43,6 @@ public interface DevSupportManager extends NativeModuleCallExceptionHandler {
|
||||
void reloadSettings();
|
||||
void handleReloadJS();
|
||||
void isPackagerRunning(DevServerHelper.PackagerStatusCallback callback);
|
||||
@Nullable String getLastErrorTitle();
|
||||
@Nullable StackFrame[] getLastErrorStack();
|
||||
}
|
||||
|
||||
@@ -31,8 +31,6 @@ import android.content.IntentFilter;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Debug;
|
||||
import android.os.Environment;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
@@ -110,6 +108,10 @@ public class DevSupportManagerImpl implements DevSupportManager {
|
||||
private boolean mIsShakeDetectorStarted = false;
|
||||
private boolean mIsDevSupportEnabled = false;
|
||||
private @Nullable RedBoxHandler mRedBoxHandler;
|
||||
private @Nullable String mLastErrorTitle;
|
||||
private @Nullable StackFrame[] mLastErrorStack;
|
||||
private int mLastErrorCookie = 0;
|
||||
private @Nullable ErrorType mLastErrorType;
|
||||
|
||||
public DevSupportManagerImpl(
|
||||
Context applicationContext,
|
||||
@@ -234,12 +236,12 @@ public class DevSupportManagerImpl implements DevSupportManager {
|
||||
// belongs to the most recent showNewJSError
|
||||
if (mRedBoxDialog == null ||
|
||||
!mRedBoxDialog.isShowing() ||
|
||||
errorCookie != mRedBoxDialog.getErrorCookie()) {
|
||||
errorCookie != mLastErrorCookie) {
|
||||
return;
|
||||
}
|
||||
StackFrame[] stack = StackTraceHelper.convertJsStackTrace(details);
|
||||
mRedBoxDialog.setExceptionDetails(message, stack);
|
||||
mRedBoxDialog.setErrorCookie(errorCookie);
|
||||
updateLastErrorInfo(message, stack, errorCookie, ErrorType.JS);
|
||||
// JS errors are reported here after source mapping.
|
||||
if (mRedBoxHandler != null) {
|
||||
mRedBoxHandler.handleRedbox(message, stack, RedBoxHandler.ErrorType.JS);
|
||||
@@ -276,7 +278,7 @@ public class DevSupportManagerImpl implements DevSupportManager {
|
||||
return;
|
||||
}
|
||||
mRedBoxDialog.setExceptionDetails(message, stack);
|
||||
mRedBoxDialog.setErrorCookie(errorCookie);
|
||||
updateLastErrorInfo(message, stack, errorCookie, errorType);
|
||||
// Only report native errors here. JS errors are reported
|
||||
// inside {@link #updateJSError} after source mapping.
|
||||
if (mRedBoxHandler != null && errorType == ErrorType.NATIVE) {
|
||||
@@ -589,6 +591,27 @@ public class DevSupportManagerImpl implements DevSupportManager {
|
||||
mDevServerHelper.isPackagerRunning(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getLastErrorTitle() {
|
||||
return mLastErrorTitle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable StackFrame[] getLastErrorStack() {
|
||||
return mLastErrorStack;
|
||||
}
|
||||
|
||||
private void updateLastErrorInfo(
|
||||
final String message,
|
||||
final StackFrame[] stack,
|
||||
final int errorCookie,
|
||||
final ErrorType errorType) {
|
||||
mLastErrorTitle = message;
|
||||
mLastErrorStack = stack;
|
||||
mLastErrorCookie = errorCookie;
|
||||
mLastErrorType = errorType;
|
||||
}
|
||||
|
||||
private void reloadJSInProxyMode(final AlertDialog progressDialog) {
|
||||
// When using js proxy, there is no need to fetch JS bundle as proxy executor will do that
|
||||
// anyway
|
||||
|
||||
@@ -9,9 +9,12 @@
|
||||
|
||||
package com.facebook.react.devsupport;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.bridge.DefaultNativeModuleCallExceptionHandler;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.devsupport.StackTraceHelper.StackFrame;
|
||||
import com.facebook.react.modules.debug.DeveloperSettings;
|
||||
|
||||
/**
|
||||
@@ -121,6 +124,16 @@ public class DisabledDevSupportManager implements DevSupportManager {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getLastErrorTitle() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable StackFrame[] getLastErrorStack() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleException(Exception e) {
|
||||
mDefaultNativeModuleCallExceptionHandler.handleException(e);
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.R;
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
@@ -46,7 +47,8 @@ import org.json.JSONObject;
|
||||
|
||||
private ListView mStackView;
|
||||
private Button mReloadJs;
|
||||
private int mCookie = 0;
|
||||
private Button mDismiss;
|
||||
private Button mCopyToClipboard;
|
||||
|
||||
private static class StackAdapter extends BaseAdapter {
|
||||
private static final int VIEW_TYPE_COUNT = 2;
|
||||
@@ -124,10 +126,7 @@ import org.json.JSONObject;
|
||||
StackFrame frame = mStack[position - 1];
|
||||
FrameViewHolder holder = (FrameViewHolder) convertView.getTag();
|
||||
holder.mMethodView.setText(frame.getMethod());
|
||||
final int column = frame.getColumn();
|
||||
// If the column is 0, don't show it in red box.
|
||||
final String columnString = column <= 0 ? "" : ":" + column;
|
||||
holder.mFileView.setText(frame.getFileName() + ":" + frame.getLine() + columnString);
|
||||
holder.mFileView.setText(StackTraceHelper.formatFrameSource(frame));
|
||||
return convertView;
|
||||
}
|
||||
}
|
||||
@@ -175,6 +174,35 @@ import org.json.JSONObject;
|
||||
}
|
||||
}
|
||||
|
||||
private static class CopyToHostClipBoardTask extends AsyncTask<String, Void, Void> {
|
||||
private final DevSupportManager mDevSupportManager;
|
||||
|
||||
private CopyToHostClipBoardTask(DevSupportManager devSupportManager) {
|
||||
mDevSupportManager = devSupportManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(String... clipBoardString) {
|
||||
try {
|
||||
String sendClipBoardUrl =
|
||||
Uri.parse(mDevSupportManager.getSourceUrl()).buildUpon()
|
||||
.path("/copy-to-clipboard")
|
||||
.query(null)
|
||||
.build()
|
||||
.toString();
|
||||
for (String string: clipBoardString) {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
RequestBody body = RequestBody.create(null, string);
|
||||
Request request = new Request.Builder().url(sendClipBoardUrl).post(body).build();
|
||||
client.newCall(request).execute();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FLog.e(ReactConstants.TAG, "Could not copy to the host clipboard", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected RedBoxDialog(Context context, DevSupportManager devSupportManager) {
|
||||
super(context, R.style.Theme_Catalyst_RedBox);
|
||||
|
||||
@@ -187,27 +215,39 @@ import org.json.JSONObject;
|
||||
|
||||
mStackView = (ListView) findViewById(R.id.rn_redbox_stack);
|
||||
mStackView.setOnItemClickListener(this);
|
||||
mReloadJs = (Button) findViewById(R.id.rn_redbox_reloadjs);
|
||||
mReloadJs = (Button) findViewById(R.id.rn_redbox_reload_button);
|
||||
mReloadJs.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mDevSupportManager.handleReloadJS();
|
||||
}
|
||||
});
|
||||
mDismiss = (Button) findViewById(R.id.rn_redbox_dismiss_button);
|
||||
mDismiss.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
mCopyToClipboard = (Button) findViewById(R.id.rn_redbox_copy_button);
|
||||
mCopyToClipboard.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
String title = mDevSupportManager.getLastErrorTitle();
|
||||
StackFrame[] stack = mDevSupportManager.getLastErrorStack();
|
||||
Assertions.assertNotNull(title);
|
||||
Assertions.assertNotNull(stack);
|
||||
new CopyToHostClipBoardTask(mDevSupportManager).executeOnExecutor(
|
||||
AsyncTask.THREAD_POOL_EXECUTOR,
|
||||
StackTraceHelper.formatStackTrace(title, stack));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setExceptionDetails(String title, StackFrame[] stack) {
|
||||
mStackView.setAdapter(new StackAdapter(title, stack));
|
||||
}
|
||||
|
||||
public void setErrorCookie(int cookie) {
|
||||
mCookie = cookie;
|
||||
}
|
||||
|
||||
public int getErrorCookie() {
|
||||
return mCookie;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
new OpenStackFrameTask(mDevSupportManager).executeOnExecutor(
|
||||
|
||||
@@ -124,4 +124,32 @@ public class StackTraceHelper {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a {@link StackFrame} to a String (method name is not included).
|
||||
*/
|
||||
public static String formatFrameSource(StackFrame frame) {
|
||||
String lineInfo = "";
|
||||
final int column = frame.getColumn();
|
||||
// If the column is 0, don't show it in red box.
|
||||
final String columnString = column <= 0 ? "" : ":" + column;
|
||||
lineInfo += frame.getFileName() + ":" + frame.getLine() + columnString;
|
||||
return lineInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an array of {@link StackFrame}s with the error title to a String.
|
||||
*/
|
||||
public static String formatStackTrace(String title, StackFrame[] stack) {
|
||||
StringBuilder stackTrace = new StringBuilder();
|
||||
stackTrace.append(title).append("\n");
|
||||
for (StackFrame frame: stack) {
|
||||
stackTrace.append(frame.getMethod())
|
||||
.append("\n")
|
||||
.append(" ")
|
||||
.append(formatFrameSource(frame))
|
||||
.append("\n");
|
||||
}
|
||||
|
||||
return stackTrace.toString();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user