mirror of
https://github.com/facebook/react-native.git
synced 2024-11-21 12:39:27 +00:00
Implement always available js error handling (#47466)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/47466 Now, when the useAlwaysAvailableJSErrorHandling feature flag is true, React Native will use the earlyjs c++ error reporting pipeline for handling all javascript errors! Changelog: [Internal] Reviewed By: javache Differential Revision: D64715159 fbshipit-source-id: 597a5278eb792f87dca10e06fa9816b3a8c47b84
This commit is contained in:
parent
791afd3209
commit
8b053a4fca
@ -553,6 +553,48 @@ if (global.nativeLoggingHook) {
|
||||
assert: consoleAssertPolyfill,
|
||||
};
|
||||
|
||||
// TODO(T206796580): This was copy-pasted from ExceptionsManager.js
|
||||
// Delete the copy there after the c++ pipeline is rolled out everywhere.
|
||||
if (global.RN$useAlwaysAvailableJSErrorHandling === true) {
|
||||
let originalConsoleError = console.error;
|
||||
console.reportErrorsAsExceptions = true;
|
||||
function stringifySafe(arg) {
|
||||
return inspect(arg, {depth: 10}).replaceAll(/\n\s*/g, ' ');
|
||||
}
|
||||
console.error = function (...args) {
|
||||
originalConsoleError.apply(this, args);
|
||||
if (!console.reportErrorsAsExceptions) {
|
||||
return;
|
||||
}
|
||||
if (global.RN$inExceptionHandler?.()) {
|
||||
return;
|
||||
}
|
||||
let error;
|
||||
|
||||
const firstArg = args[0];
|
||||
if (firstArg?.stack) {
|
||||
// RN$handleException will console.error this with high enough fidelity.
|
||||
error = firstArg;
|
||||
} else {
|
||||
if (typeof firstArg === 'string' && firstArg.startsWith('Warning: ')) {
|
||||
// React warnings use console.error so that a stack trace is shown, but
|
||||
// we don't (currently) want these to show a redbox
|
||||
return;
|
||||
}
|
||||
const message = args
|
||||
.map(arg => (typeof arg === 'string' ? arg : stringifySafe(arg)))
|
||||
.join(' ');
|
||||
|
||||
error = new Error(message);
|
||||
error.name = 'console.error';
|
||||
}
|
||||
|
||||
const isFatal = false;
|
||||
const reportToConsole = false;
|
||||
global.RN$handleException(error, isFatal, reportToConsole);
|
||||
};
|
||||
}
|
||||
|
||||
Object.defineProperty(console, '_isPolyfilled', {
|
||||
value: true,
|
||||
enumerable: false,
|
||||
|
@ -19,10 +19,10 @@ type Fn<Args, Return> = (...Args) => Return;
|
||||
* when loading a module. This will report any errors encountered before
|
||||
* ExceptionsManager is configured.
|
||||
*/
|
||||
let _globalHandler: ErrorHandler = function onError(
|
||||
e: mixed,
|
||||
isFatal: boolean,
|
||||
) {
|
||||
let _globalHandler: ErrorHandler =
|
||||
global.RN$useAlwaysAvailableJSErrorHandling === true
|
||||
? global.RN$handleException
|
||||
: (e: mixed, isFatal: boolean) => {
|
||||
throw e;
|
||||
};
|
||||
|
||||
|
@ -177,10 +177,7 @@ function reactConsoleErrorHandler(...args) {
|
||||
if (!console.reportErrorsAsExceptions) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
inExceptionHandler ||
|
||||
(global.RN$inExceptionHandler && global.RN$inExceptionHandler())
|
||||
) {
|
||||
if (inExceptionHandler || global.RN$inExceptionHandler?.()) {
|
||||
// The fundamental trick here is that are multiple entry point to logging errors:
|
||||
// (see D19743075 for more background)
|
||||
//
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
if (global.RN$useAlwaysAvailableJSErrorHandling !== true) {
|
||||
/**
|
||||
* Sets up the console and exception handling (redbox) for React Native.
|
||||
* You can use this module directly, or just require InitializeCore.
|
||||
@ -31,3 +32,4 @@ if (!global.__fbDisableExceptionsManager) {
|
||||
const ErrorUtils = require('../vendor/core/ErrorUtils');
|
||||
ErrorUtils.setGlobalHandler(handleError);
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ if (__DEV__) {
|
||||
if (global.RN$registerExceptionListener != null) {
|
||||
global.RN$registerExceptionListener(
|
||||
(error: ExtendedExceptionData & {preventDefault: () => mixed}) => {
|
||||
if (!error.isFatal) {
|
||||
if (global.RN$isRuntimeReady?.() || !error.isFatal) {
|
||||
error.preventDefault();
|
||||
addException(error);
|
||||
}
|
||||
|
@ -19,4 +19,5 @@ target_link_libraries(jserrorhandler
|
||||
jsi
|
||||
folly_runtime
|
||||
mapbufferjni
|
||||
react_featureflags
|
||||
)
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <cxxreact/ErrorUtils.h>
|
||||
#include <glog/logging.h>
|
||||
#include <react/bridging/Bridging.h>
|
||||
#include <react/featureflags/ReactNativeFeatureFlags.h>
|
||||
#include <string>
|
||||
#include "StackTraceParser.h"
|
||||
|
||||
@ -228,7 +229,9 @@ void JsErrorHandler::handleError(
|
||||
bool logToConsole) {
|
||||
// TODO: Current error parsing works and is stable. Can investigate using
|
||||
// REGEX_HERMES to get additional Hermes data, though it requires JS setup
|
||||
if (_isRuntimeReady) {
|
||||
|
||||
if (!ReactNativeFeatureFlags::useAlwaysAvailableJSErrorHandling() &&
|
||||
_isRuntimeReady) {
|
||||
if (isFatal) {
|
||||
_hasHandledFatalError = true;
|
||||
}
|
||||
@ -325,7 +328,7 @@ void JsErrorHandler::handleErrorWithCppPipeline(
|
||||
auto id = nextExceptionId();
|
||||
|
||||
ParsedError parsedError = {
|
||||
.message = "EarlyJsError: " + message,
|
||||
.message = _isRuntimeReady ? message : ("EarlyJsError: " + message),
|
||||
.originalMessage = originalMessage,
|
||||
.name = name,
|
||||
.componentStack = componentStack,
|
||||
|
@ -52,6 +52,7 @@ Pod::Spec.new do |s|
|
||||
s.dependency "React-cxxreact"
|
||||
s.dependency "glog"
|
||||
s.dependency "ReactCommon/turbomodule/bridging"
|
||||
add_dependency(s, "React-featureflags")
|
||||
add_dependency(s, "React-debug")
|
||||
|
||||
if ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == "1"
|
||||
|
@ -409,6 +409,27 @@ void ReactInstance::initializeRuntime(
|
||||
|
||||
defineReactInstanceFlags(runtime, options);
|
||||
|
||||
defineReadOnlyGlobal(
|
||||
runtime,
|
||||
"RN$useAlwaysAvailableJSErrorHandling",
|
||||
jsi::Value(
|
||||
ReactNativeFeatureFlags::useAlwaysAvailableJSErrorHandling()));
|
||||
|
||||
defineReadOnlyGlobal(
|
||||
runtime,
|
||||
"RN$isRuntimeReady",
|
||||
jsi::Function::createFromHostFunction(
|
||||
runtime,
|
||||
jsi::PropNameID::forAscii(runtime, "isRuntimeReady"),
|
||||
0,
|
||||
[jsErrorHandler = jsErrorHandler_](
|
||||
jsi::Runtime& /*runtime*/,
|
||||
const jsi::Value& /*unused*/,
|
||||
const jsi::Value* /*args*/,
|
||||
size_t /*count*/) {
|
||||
return jsErrorHandler->isRuntimeReady();
|
||||
}));
|
||||
|
||||
defineReadOnlyGlobal(
|
||||
runtime,
|
||||
"RN$inExceptionHandler",
|
||||
@ -444,6 +465,9 @@ void ReactInstance::initializeRuntime(
|
||||
}
|
||||
|
||||
auto isFatal = isTruthy(runtime, args[1]);
|
||||
|
||||
if (!ReactNativeFeatureFlags::
|
||||
useAlwaysAvailableJSErrorHandling()) {
|
||||
if (jsErrorHandler->isRuntimeReady()) {
|
||||
if (isFatal) {
|
||||
jsErrorHandler->notifyOfFatalError();
|
||||
@ -451,6 +475,7 @@ void ReactInstance::initializeRuntime(
|
||||
|
||||
return jsi::Value(false);
|
||||
}
|
||||
}
|
||||
|
||||
auto jsError =
|
||||
jsi::JSError(runtime, jsi::Value(runtime, args[0]));
|
||||
|
Loading…
Reference in New Issue
Block a user