use listview for redbox stacktrace, open file on click

Reviewed By: @andreicoman11

Differential Revision: D2512364

fb-gh-sync-id: 5f2c90db7eca010185080f726fd3ef0ee519cdbc
This commit is contained in:
Felix Oghina 2015-10-06 08:10:08 -07:00 коммит произвёл facebook-github-bot-4
Родитель 06ef95799a
Коммит b5890e1283
7 изменённых файлов: 339 добавлений и 151 удалений

Просмотреть файл

@ -43,6 +43,7 @@ import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WebsocketJavaScriptExecutor;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.common.ShakeDetector;
import com.facebook.react.devsupport.StackTraceHelper.StackFrame;
import com.facebook.react.modules.debug.DeveloperSettings;
/**
@ -154,8 +155,7 @@ public class DevSupportManager implements NativeModuleCallExceptionHandler {
public void handleException(Exception e) {
if (mIsDevSupportEnabled) {
FLog.e(ReactConstants.TAG, "Exception in native call from JS", e);
CharSequence details = ExceptionFormatterHelper.javaStackTraceToHtml(e.getStackTrace());
showNewError(e.getMessage(), details, JAVA_ERROR_COOKIE);
showNewError(e.getMessage(), StackTraceHelper.convertJavaStackTrace(e), JAVA_ERROR_COOKIE);
} else {
if (e instanceof RuntimeException) {
// Because we are rethrowing the original exception, the original stacktrace will be
@ -179,7 +179,7 @@ public class DevSupportManager implements NativeModuleCallExceptionHandler {
}
public void showNewJSError(String message, ReadableArray details, int errorCookie) {
showNewError(message, ExceptionFormatterHelper.jsStackTraceToHtml(details), errorCookie);
showNewError(message, StackTraceHelper.convertJsStackTrace(details), errorCookie);
}
public void updateJSError(
@ -198,8 +198,9 @@ public class DevSupportManager implements NativeModuleCallExceptionHandler {
errorCookie != mRedBoxDialog.getErrorCookie()) {
return;
}
mRedBoxDialog.setTitle(message);
mRedBoxDialog.setDetails(ExceptionFormatterHelper.jsStackTraceToHtml(details));
mRedBoxDialog.setExceptionDetails(
message,
StackTraceHelper.convertJsStackTrace(details));
mRedBoxDialog.show();
}
});
@ -207,7 +208,7 @@ public class DevSupportManager implements NativeModuleCallExceptionHandler {
private void showNewError(
final String message,
final CharSequence details,
final StackFrame[] stack,
final int errorCookie) {
UiThreadUtil.runOnUiThread(
new Runnable() {
@ -222,8 +223,7 @@ public class DevSupportManager implements NativeModuleCallExceptionHandler {
// show the first and most actionable one.
return;
}
mRedBoxDialog.setTitle(message);
mRedBoxDialog.setDetails(details);
mRedBoxDialog.setExceptionDetails(message, stack);
mRedBoxDialog.setErrorCookie(errorCookie);
mRedBoxDialog.show();
}
@ -520,7 +520,7 @@ public class DevSupportManager implements NativeModuleCallExceptionHandler {
public void run() {
showNewError(
mApplicationContext.getString(R.string.catalyst_remotedbg_error),
ExceptionFormatterHelper.javaStackTraceToHtml(cause.getStackTrace()),
StackTraceHelper.convertJavaStackTrace(cause),
JAVA_ERROR_COOKIE);
}
});
@ -555,13 +555,12 @@ public class DevSupportManager implements NativeModuleCallExceptionHandler {
DebugServerException debugServerException = (DebugServerException) cause;
showNewError(
debugServerException.description,
ExceptionFormatterHelper.debugServerExcStackTraceToHtml(
(DebugServerException) cause),
StackTraceHelper.convertJavaStackTrace(cause),
JAVA_ERROR_COOKIE);
} else {
showNewError(
mApplicationContext.getString(R.string.catalyst_jsload_error),
ExceptionFormatterHelper.javaStackTraceToHtml(cause.getStackTrace()),
StackTraceHelper.convertJavaStackTrace(cause),
JAVA_ERROR_COOKIE);
}
}

Просмотреть файл

@ -1,75 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.devsupport;
import java.io.File;
import android.text.Html;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
/**
* Helper class for displaying errors in an eye-catching form (red box).
*/
/* package */ class ExceptionFormatterHelper {
private static String getStackTraceHtmlComponent(
String methodName, String filename, int lineNumber, int columnNumber) {
StringBuilder stringBuilder = new StringBuilder();
methodName = methodName.replace("<", "&lt;").replace(">", "&gt;");
stringBuilder.append("<font color=#FDE5E5>")
.append(methodName)
.append("</font><br /><font color=#F9B3B3>")
.append(filename)
.append(":")
.append(lineNumber);
if (columnNumber != -1) {
stringBuilder
.append(":")
.append(columnNumber);
}
stringBuilder.append("</font><br /><br />");
return stringBuilder.toString();
}
public static CharSequence jsStackTraceToHtml(ReadableArray stack) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < stack.size(); i++) {
ReadableMap frame = stack.getMap(i);
String methodName = frame.getString("methodName");
String fileName = new File(frame.getString("file")).getName();
int lineNumber = frame.getInt("lineNumber");
int columnNumber = -1;
if (frame.hasKey("column") && !frame.isNull("column")) {
columnNumber = frame.getInt("column");
}
stringBuilder.append(getStackTraceHtmlComponent(
methodName, fileName, lineNumber, columnNumber));
}
return Html.fromHtml(stringBuilder.toString());
}
public static CharSequence javaStackTraceToHtml(StackTraceElement[] stack) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i< stack.length; i++) {
stringBuilder.append(getStackTraceHtmlComponent(
stack[i].getMethodName(), stack[i].getFileName(), stack[i].getLineNumber(), -1));
}
return Html.fromHtml(stringBuilder.toString());
}
public static CharSequence debugServerExcStackTraceToHtml(DebugServerException e) {
String s = getStackTraceHtmlComponent("", e.fileName, e.lineNumber, e.column);
return Html.fromHtml(s);
}
}

Просмотреть файл

@ -11,28 +11,166 @@ package com.facebook.react.devsupport;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Typeface;
import android.text.method.ScrollingMovementMethod;
import android.net.Uri;
import android.os.AsyncTask;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import com.facebook.common.logging.FLog;
import com.facebook.react.R;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.devsupport.StackTraceHelper.StackFrame;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import org.json.JSONObject;
/**
* Dialog for displaying JS errors in an eye-catching form (red box).
*/
/* package */ class RedBoxDialog extends Dialog {
/* package */ class RedBoxDialog extends Dialog implements AdapterView.OnItemClickListener {
private final DevSupportManager mDevSupportManager;
private TextView mTitle;
private TextView mDetails;
private ListView mStackView;
private Button mReloadJs;
private int mCookie = 0;
private static class StackAdapter extends BaseAdapter {
private static final int VIEW_TYPE_COUNT = 2;
private static final int VIEW_TYPE_TITLE = 0;
private static final int VIEW_TYPE_STACKFRAME = 1;
private final String mTitle;
private final StackFrame[] mStack;
private static class FrameViewHolder {
private final TextView mMethodView;
private final TextView mFileView;
private FrameViewHolder(View v) {
mMethodView = (TextView) v.findViewById(R.id.rn_frame_method);
mFileView = (TextView) v.findViewById(R.id.rn_frame_file);
}
}
public StackAdapter(String title, StackFrame[] stack) {
mTitle = title;
mStack = stack;
}
@Override
public boolean areAllItemsEnabled() {
return false;
}
@Override
public boolean isEnabled(int position) {
return position > 0;
}
@Override
public int getCount() {
return mStack.length + 1;
}
@Override
public Object getItem(int position) {
return position == 0 ? mTitle : mStack[position - 1];
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public int getViewTypeCount() {
return VIEW_TYPE_COUNT;
}
@Override
public int getItemViewType(int position) {
return position == 0 ? VIEW_TYPE_TITLE : VIEW_TYPE_STACKFRAME;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (position == 0) {
TextView title = convertView != null
? (TextView) convertView
: (TextView) LayoutInflater.from(parent.getContext())
.inflate(R.layout.redbox_item_title, parent, false);
title.setText(mTitle);
return title;
} else {
if (convertView == null) {
convertView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.redbox_item_frame, parent, false);
convertView.setTag(new FrameViewHolder(convertView));
}
StackFrame frame = mStack[position - 1];
FrameViewHolder holder = (FrameViewHolder) convertView.getTag();
holder.mMethodView.setText(frame.getMethod());
holder.mFileView.setText(frame.getFileName() + ":" + frame.getLine());
return convertView;
}
}
}
private static class OpenStackFrameTask extends AsyncTask<StackFrame, Void, Void> {
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private final DevSupportManager mDevSupportManager;
private OpenStackFrameTask(DevSupportManager devSupportManager) {
mDevSupportManager = devSupportManager;
}
@Override
protected Void doInBackground(StackFrame... stackFrames) {
try {
String openStackFrameUrl =
Uri.parse(mDevSupportManager.getSourceUrl()).buildUpon()
.path("/open-stack-frame")
.query(null)
.build()
.toString();
OkHttpClient client = new OkHttpClient();
for (StackFrame frame: stackFrames) {
String payload = stackFrameToJson(frame).toString();
RequestBody body = RequestBody.create(JSON, payload);
Request request = new Request.Builder().url(openStackFrameUrl).post(body).build();
client.newCall(request).execute();
}
} catch (Exception e) {
FLog.e(ReactConstants.TAG, "Could not open stack frame", e);
}
return null;
}
private static JSONObject stackFrameToJson(StackFrame frame) {
return new JSONObject(
MapBuilder.of(
"file", frame.getFile(),
"methodName", frame.getMethod(),
"lineNumber", frame.getLine(),
"column", frame.getColumn()
));
}
}
protected RedBoxDialog(Context context, DevSupportManager devSupportManager) {
super(context, R.style.Theme_Catalyst_RedBox);
@ -42,12 +180,9 @@ import com.facebook.react.R;
mDevSupportManager = devSupportManager;
mTitle = (TextView) findViewById(R.id.catalyst_redbox_title);
mDetails = (TextView) findViewById(R.id.catalyst_redbox_details);
mDetails.setTypeface(Typeface.MONOSPACE);
mDetails.setHorizontallyScrolling(true);
mDetails.setMovementMethod(new ScrollingMovementMethod());
mReloadJs = (Button) findViewById(R.id.catalyst_redbox_reloadjs);
mStackView = (ListView) findViewById(R.id.rn_redbox_stack);
mStackView.setOnItemClickListener(this);
mReloadJs = (Button) findViewById(R.id.rn_redbox_reloadjs);
mReloadJs.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@ -56,12 +191,8 @@ import com.facebook.react.R;
});
}
public void setTitle(String title) {
mTitle.setText(title);
}
public void setDetails(CharSequence details) {
mDetails.setText(details);
public void setExceptionDetails(String title, StackFrame[] stack) {
mStackView.setAdapter(new StackAdapter(title, stack));
}
public void setErrorCookie(int cookie) {
@ -72,6 +203,13 @@ import com.facebook.react.R;
return mCookie;
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
new OpenStackFrameTask(mDevSupportManager).executeOnExecutor(
AsyncTask.THREAD_POOL_EXECUTOR,
(StackFrame) mStackView.getAdapter().getItem(position));
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MENU) {

Просмотреть файл

@ -0,0 +1,127 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.devsupport;
import java.io.File;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
/**
* Helper class converting JS and Java stack traces into arrays of {@link StackFrame} objects.
*/
/* package */ class StackTraceHelper {
/**
* Represents a generic entry in a stack trace, be it originally from JS or Java.
*/
public static class StackFrame {
private final String mFile;
private final String mMethod;
private final int mLine;
private final int mColumn;
private final String mFileName;
private StackFrame(String file, String method, int line, int column) {
mFile = file;
mMethod = method;
mLine = line;
mColumn = column;
mFileName = new File(file).getName();
}
private StackFrame(String file, String fileName, String method, int line, int column) {
mFile = file;
mFileName = fileName;
mMethod = method;
mLine = line;
mColumn = column;
}
/**
* Get the file this stack frame points to.
*
* JS traces return the full path to the file here, while Java traces only return the file name
* (the path is not known).
*/
public String getFile() {
return mFile;
}
/**
* Get the name of the method this frame points to.
*/
public String getMethod() {
return mMethod;
}
/**
* Get the line number this frame points to in the file returned by {@link #getFile()}.
*/
public int getLine() {
return mLine;
}
/**
* Get the column this frame points to in the file returned by {@link #getFile()}.
*/
public int getColumn() {
return mColumn;
}
/**
* Get just the name of the file this frame points to.
*
* For JS traces this is different from {@link #getFile()} in that it only returns the file
* name, not the full path. For Java traces there is no difference.
*/
public String getFileName() {
return mFileName;
}
}
/**
* Convert a JavaScript stack trace (see {@code parseErrorStack} JS module) to an array of
* {@link StackFrame}s.
*/
public static StackFrame[] convertJsStackTrace(ReadableArray stack) {
StackFrame[] result = new StackFrame[stack.size()];
for (int i = 0; i < stack.size(); i++) {
ReadableMap frame = stack.getMap(i);
String methodName = frame.getString("methodName");
String fileName = frame.getString("file");
int lineNumber = frame.getInt("lineNumber");
int columnNumber = -1;
if (frame.hasKey("column") && !frame.isNull("column")) {
columnNumber = frame.getInt("column");
}
result[i] = new StackFrame(fileName, methodName, lineNumber, columnNumber);
}
return result;
}
/**
* Convert a {@link Throwable} to an array of {@link StackFrame}s.
*/
public static StackFrame[] convertJavaStackTrace(Throwable exception) {
StackTraceElement[] stackTrace = exception.getStackTrace();
StackFrame[] result = new StackFrame[stackTrace.length];
for (int i = 0; i < stackTrace.length; i++) {
result[i] = new StackFrame(
stackTrace[i].getClassName(),
stackTrace[i].getFileName(),
stackTrace[i].getMethodName(),
stackTrace[i].getLineNumber(),
0);
}
return result;
}
}

Просмотреть файл

@ -0,0 +1,23 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="8dp"
>
<TextView
android:id="@+id/rn_frame_method"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:textSize="10sp"
android:fontFamily="monospace"
/>
<TextView
android:id="@+id/rn_frame_file"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#E6B8B8"
android:textSize="8sp"
android:fontFamily="monospace"
/>
</LinearLayout>

Просмотреть файл

@ -0,0 +1,10 @@
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/catalyst_redbox_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:gravity="center_vertical"
android:textColor="@android:color/white"
android:textSize="14sp"
android:textStyle="bold"
/>

Просмотреть файл

@ -1,54 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#E80000"
>
<TextView
android:id="@+id/catalyst_redbox_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp"
android:layout_marginRight="8dp"
android:layout_marginLeft="8dp"
android:drawableLeft="@android:drawable/ic_dialog_alert"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:textAppearance="@android:style/TextAppearance.Holo.Medium"
tools:text="Error"
/>
<ImageView
android:id="@+id/catalyst_redbox_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@id/catalyst_redbox_title"
android:scaleType="fitXY"
android:gravity="fill_horizontal"
android:src="@android:drawable/divider_horizontal_dark"
/>
<ListView
android:id="@+id/rn_redbox_stack"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
/>
<Button
android:id="@+id/catalyst_redbox_reloadjs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="@string/catalyst_reloadjs"
android:textColor="@android:color/black"
/>
<TextView
android:id="@+id/catalyst_redbox_details"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_below="@id/catalyst_redbox_divider"
android:layout_above="@id/catalyst_redbox_reloadjs"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:padding="8dp"
android:textSize="10sp"
android:scrollbars="vertical|horizontal"
android:gravity="center_vertical"
tools:text="Error message"
/>
</RelativeLayout>
android:id="@+id/rn_redbox_reloadjs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/catalyst_reloadjs"
/>
</LinearLayout>