mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Integrate logbox into earlyjs c++ pipeline (#46935)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/46935 Now, whenever the earlyjs c++ pipeline handles an error, it'll invoke callbacks registered via javascript. ## The API ``` if (global.RN$registerExceptionListener != null) { global.RN$registerExceptionListener( (error: ExtendedExceptionData & {preventDefault: () => mixed}) => { error.preventDefault(); // show logbox }, ); } ``` ## The Future API We want something more aligned with the HTML spec: ``` addEventListener('error', (event) => { event.preventDefault(); // show logbox }); ``` ## Fatals vs soft errors The earlyjs pipeline covers just main bundle execution for now. So, it displays logbox only if there was a soft error. If there was a fatal error, it'll just continue to display redbox as usual. Changelog: [Internal] Reviewed By: javache Differential Revision: D63769385 fbshipit-source-id: 9c01e1cfe0ec80842af2e5bcfbf0adfb040dbcf3
This commit is contained in:
committed by
Facebook GitHub Bot
parent
509abff063
commit
9619ba5ce6
+18
-5
@@ -52,6 +52,17 @@ if (__DEV__) {
|
||||
|
||||
isLogBoxInstalled = true;
|
||||
|
||||
if (global.RN$registerExceptionListener != null) {
|
||||
global.RN$registerExceptionListener(
|
||||
(error: ExtendedExceptionData & {preventDefault: () => mixed}) => {
|
||||
if (!error.isFatal) {
|
||||
error.preventDefault();
|
||||
addException(error);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Trigger lazy initialization of module.
|
||||
require('../NativeModules/specs/NativeLogBox');
|
||||
|
||||
@@ -122,13 +133,15 @@ if (__DEV__) {
|
||||
}
|
||||
},
|
||||
|
||||
addException(error: ExtendedExceptionData): void {
|
||||
if (isLogBoxInstalled) {
|
||||
LogBoxData.addException(error);
|
||||
}
|
||||
},
|
||||
addException,
|
||||
};
|
||||
|
||||
function addException(error: ExtendedExceptionData): void {
|
||||
if (isLogBoxInstalled) {
|
||||
LogBoxData.addException(error);
|
||||
}
|
||||
}
|
||||
|
||||
const isRCTLogAdviceWarning = (...args: Array<mixed>) => {
|
||||
// RCTLogAdvice is a native logging function designed to show users
|
||||
// a message in the console, but not show it to them in Logbox.
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "JsErrorHandler.h"
|
||||
#include <cxxreact/ErrorUtils.h>
|
||||
#include <glog/logging.h>
|
||||
#include <react/bridging/Bridging.h>
|
||||
#include <string>
|
||||
#include "StackTraceParser.h"
|
||||
|
||||
@@ -35,10 +36,65 @@ bool isEmptyString(jsi::Runtime& runtime, const jsi::Value& value) {
|
||||
std::string stringifyToCpp(jsi::Runtime& runtime, const jsi::Value& value) {
|
||||
return value.toString(runtime).utf8(runtime);
|
||||
}
|
||||
|
||||
bool isTruthy(jsi::Runtime& runtime, const jsi::Value& value) {
|
||||
auto Boolean = runtime.global().getPropertyAsFunction(runtime, "Boolean");
|
||||
return Boolean.call(runtime, value).getBool();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
template <>
|
||||
struct Bridging<JsErrorHandler::ParsedError::StackFrame> {
|
||||
static jsi::Value toJs(
|
||||
jsi::Runtime& runtime,
|
||||
const JsErrorHandler::ParsedError::StackFrame& frame) {
|
||||
auto stackFrame = jsi::Object(runtime);
|
||||
auto file = bridging::toJs(runtime, frame.file, nullptr);
|
||||
auto lineNumber = bridging::toJs(runtime, frame.lineNumber, nullptr);
|
||||
auto column = bridging::toJs(runtime, frame.column, nullptr);
|
||||
|
||||
stackFrame.setProperty(runtime, "file", file);
|
||||
stackFrame.setProperty(runtime, "methodName", frame.methodName);
|
||||
stackFrame.setProperty(runtime, "lineNumber", lineNumber);
|
||||
stackFrame.setProperty(runtime, "column", column);
|
||||
return stackFrame;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Bridging<JsErrorHandler::ParsedError> {
|
||||
static jsi::Value toJs(
|
||||
jsi::Runtime& runtime,
|
||||
const JsErrorHandler::ParsedError& error) {
|
||||
auto data = jsi::Object(runtime);
|
||||
data.setProperty(runtime, "message", error.message);
|
||||
data.setProperty(
|
||||
runtime,
|
||||
"originalMessage",
|
||||
bridging::toJs(runtime, error.originalMessage, nullptr));
|
||||
data.setProperty(
|
||||
runtime, "name", bridging::toJs(runtime, error.name, nullptr));
|
||||
data.setProperty(
|
||||
runtime,
|
||||
"componentStack",
|
||||
bridging::toJs(runtime, error.componentStack, nullptr));
|
||||
|
||||
auto stack = jsi::Array(runtime, error.stack.size());
|
||||
for (size_t i = 0; i < error.stack.size(); i++) {
|
||||
auto& frame = error.stack[i];
|
||||
stack.setValueAtIndex(runtime, i, bridging::toJs(runtime, frame));
|
||||
}
|
||||
|
||||
data.setProperty(runtime, "stack", stack);
|
||||
data.setProperty(runtime, "id", error.id);
|
||||
data.setProperty(runtime, "isFatal", error.isFatal);
|
||||
data.setProperty(runtime, "extraData", error.extraData);
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
std::ostream& operator<<(
|
||||
std::ostream& os,
|
||||
const JsErrorHandler::ParsedError::StackFrame& frame) {
|
||||
@@ -96,12 +152,12 @@ void JsErrorHandler::handleError(
|
||||
jsi::JSError& error,
|
||||
bool isFatal) {
|
||||
// TODO: Current error parsing works and is stable. Can investigate using
|
||||
// REGEX_HERMES to get additional Hermes data, though it requires JS setup.
|
||||
if (isFatal) {
|
||||
_hasHandledFatalError = true;
|
||||
}
|
||||
|
||||
// REGEX_HERMES to get additional Hermes data, though it requires JS setup
|
||||
if (_isRuntimeReady) {
|
||||
if (isFatal) {
|
||||
_hasHandledFatalError = true;
|
||||
}
|
||||
|
||||
try {
|
||||
handleJSError(runtime, error, isFatal);
|
||||
return;
|
||||
@@ -114,6 +170,13 @@ void JsErrorHandler::handleError(
|
||||
}
|
||||
}
|
||||
|
||||
emitError(runtime, error, isFatal);
|
||||
}
|
||||
|
||||
void JsErrorHandler::emitError(
|
||||
jsi::Runtime& runtime,
|
||||
jsi::JSError& error,
|
||||
bool isFatal) {
|
||||
auto message = error.getMessage();
|
||||
auto errorObj = error.value().getObject(runtime);
|
||||
auto componentStackValue = errorObj.getProperty(runtime, "componentStack");
|
||||
@@ -182,9 +245,48 @@ void JsErrorHandler::handleError(
|
||||
.extraData = std::move(extraData),
|
||||
};
|
||||
|
||||
auto data = bridging::toJs(runtime, parsedError).asObject(runtime);
|
||||
|
||||
auto isComponentError =
|
||||
isTruthy(runtime, errorObj.getProperty(runtime, "isComponentError"));
|
||||
data.setProperty(runtime, "isComponentError", isComponentError);
|
||||
|
||||
std::shared_ptr<bool> shouldPreventDefault = std::make_shared<bool>(false);
|
||||
auto preventDefault = jsi::Function::createFromHostFunction(
|
||||
runtime,
|
||||
jsi::PropNameID::forAscii(runtime, "preventDefault"),
|
||||
0,
|
||||
[shouldPreventDefault](
|
||||
jsi::Runtime& /*rt*/,
|
||||
const jsi::Value& /*thisVal*/,
|
||||
const jsi::Value* /*args*/,
|
||||
size_t /*count*/) {
|
||||
*shouldPreventDefault = true;
|
||||
return jsi::Value::undefined();
|
||||
});
|
||||
|
||||
data.setProperty(runtime, "preventDefault", preventDefault);
|
||||
|
||||
for (auto& errorListener : _errorListeners) {
|
||||
errorListener(runtime, jsi::Value(runtime, data));
|
||||
}
|
||||
|
||||
if (*shouldPreventDefault) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isFatal) {
|
||||
_hasHandledFatalError = true;
|
||||
}
|
||||
|
||||
_onJsError(runtime, parsedError);
|
||||
}
|
||||
|
||||
void JsErrorHandler::registerErrorListener(
|
||||
const std::function<void(jsi::Runtime&, jsi::Value)>& errorListener) {
|
||||
_errorListeners.push_back(errorListener);
|
||||
}
|
||||
|
||||
bool JsErrorHandler::hasHandledFatalError() {
|
||||
return _hasHandledFatalError;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ class JsErrorHandler {
|
||||
|
||||
void handleError(jsi::Runtime& runtime, jsi::JSError& error, bool isFatal);
|
||||
bool hasHandledFatalError();
|
||||
void registerErrorListener(
|
||||
const std::function<void(jsi::Runtime&, jsi::Value)>& listener);
|
||||
void setRuntimeReady();
|
||||
bool isRuntimeReady();
|
||||
void notifyOfFatalError();
|
||||
@@ -60,6 +62,9 @@ class JsErrorHandler {
|
||||
OnJsError _onJsError;
|
||||
bool _hasHandledFatalError;
|
||||
bool _isRuntimeReady{};
|
||||
std::vector<std::function<void(jsi::Runtime&, jsi::Value)>> _errorListeners;
|
||||
|
||||
void emitError(jsi::Runtime& runtime, jsi::JSError& error, bool isFatal);
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
@@ -51,6 +51,7 @@ Pod::Spec.new do |s|
|
||||
s.dependency "React-jsi"
|
||||
s.dependency "React-cxxreact"
|
||||
s.dependency "glog"
|
||||
s.dependency "ReactCommon/turbomodule/bridging"
|
||||
add_dependency(s, "React-debug")
|
||||
|
||||
if ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == "1"
|
||||
|
||||
@@ -429,6 +429,47 @@ void ReactInstance::initializeRuntime(
|
||||
return jsi::Value(true);
|
||||
}));
|
||||
|
||||
defineReadOnlyGlobal(
|
||||
runtime,
|
||||
"RN$registerExceptionListener",
|
||||
jsi::Function::createFromHostFunction(
|
||||
runtime,
|
||||
jsi::PropNameID::forAscii(runtime, "registerExceptionListener"),
|
||||
1,
|
||||
[errorListeners = std::vector<std::shared_ptr<jsi::Function>>(),
|
||||
jsErrorHandler = jsErrorHandler_](
|
||||
jsi::Runtime& runtime,
|
||||
const jsi::Value& /*unused*/,
|
||||
const jsi::Value* args,
|
||||
size_t count) mutable {
|
||||
if (count < 1) {
|
||||
throw jsi::JSError(
|
||||
runtime,
|
||||
"registerExceptionListener: requires 1 argument: fn");
|
||||
}
|
||||
|
||||
if (!args[0].isObject() ||
|
||||
!args[0].getObject(runtime).isFunction(runtime)) {
|
||||
throw jsi::JSError(
|
||||
runtime,
|
||||
"registerExceptionListener: The first argument must be a function");
|
||||
}
|
||||
|
||||
auto errorListener = std::make_shared<jsi::Function>(
|
||||
args[0].getObject(runtime).getFunction(runtime));
|
||||
errorListeners.emplace_back(errorListener);
|
||||
|
||||
jsErrorHandler->registerErrorListener(
|
||||
[weakErrorListener = std::weak_ptr<jsi::Function>(
|
||||
errorListener)](jsi::Runtime& runtime, jsi::Value data) {
|
||||
if (auto strongErrorListener = weakErrorListener.lock()) {
|
||||
strongErrorListener->call(runtime, data);
|
||||
}
|
||||
});
|
||||
|
||||
return jsi::Value::undefined();
|
||||
}));
|
||||
|
||||
defineReadOnlyGlobal(
|
||||
runtime,
|
||||
"RN$registerCallableModule",
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <react/runtime/BufferedRuntimeExecutor.h>
|
||||
#include <react/runtime/JSRuntimeFactory.h>
|
||||
#include <react/runtime/TimerManager.h>
|
||||
#include <vector>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user