diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm index 5deab25265a1ca..7df790a129e38a 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm @@ -178,18 +178,6 @@ - (void)hostDidStart:(RCTHost *)host { } -- (void)host:(RCTHost *)host - didReceiveJSErrorStack:(NSArray *> *)stack - message:(NSString *)message - originalMessage:(NSString *_Nullable)originalMessage - name:(NSString *_Nullable)name - componentStack:(NSString *_Nullable)componentStack - exceptionId:(NSUInteger)exceptionId - isFatal:(BOOL)isFatal - extraData:(NSDictionary *)extraData -{ -} - #pragma mark - Bridge and Bridge Adapter properties - (RCTBridge *)bridge diff --git a/packages/react-native/Libraries/Core/ExceptionsManager.js b/packages/react-native/Libraries/Core/ExceptionsManager.js index e64a3a0fd6e54f..d10b6245e28682 100644 --- a/packages/react-native/Libraries/Core/ExceptionsManager.js +++ b/packages/react-native/Libraries/Core/ExceptionsManager.js @@ -141,24 +141,28 @@ let inExceptionHandler = false; * Logs exceptions to the (native) console and displays them */ function handleException(e: mixed, isFatal: boolean) { - let error: Error; - if (e instanceof Error) { - error = e; - } else { - // Workaround for reporting errors caused by `throw 'some string'` - // Unfortunately there is no way to figure out the stacktrace in this - // case, so if you ended up here trying to trace an error, look for - // `throw ''` somewhere in your codebase. - error = new SyntheticError(e); - } - try { - inExceptionHandler = true; - /* $FlowFixMe[class-object-subtyping] added when improving typing for this - * parameters */ - // $FlowFixMe[incompatible-call] - reportException(error, isFatal, /*reportToConsole*/ true); - } finally { - inExceptionHandler = false; + // TODO(T196834299): We should really use a c++ turbomodule for this + const reportToConsole = true; + if (!global.RN$handleException || !global.RN$handleException(e, isFatal)) { + let error: Error; + if (e instanceof Error) { + error = e; + } else { + // Workaround for reporting errors caused by `throw 'some string'` + // Unfortunately there is no way to figure out the stacktrace in this + // case, so if you ended up here trying to trace an error, look for + // `throw ''` somewhere in your codebase. + error = new SyntheticError(e); + } + try { + inExceptionHandler = true; + /* $FlowFixMe[class-object-subtyping] added when improving typing for this + * parameters */ + // $FlowFixMe[incompatible-call] + reportException(error, isFatal, reportToConsole); + } finally { + inExceptionHandler = false; + } } } @@ -170,7 +174,10 @@ function reactConsoleErrorHandler(...args) { if (!console.reportErrorsAsExceptions) { return; } - if (inExceptionHandler) { + if ( + inExceptionHandler || + (global.RN$inExceptionHandler && global.RN$inExceptionHandler()) + ) { // The fundamental trick here is that are multiple entry point to logging errors: // (see D19743075 for more background) // @@ -224,14 +231,21 @@ function reactConsoleErrorHandler(...args) { error.name = 'console.error'; } - reportException( - /* $FlowFixMe[class-object-subtyping] added when improving typing for this - * parameters */ - // $FlowFixMe[incompatible-call] - error, - false, // isFatal - false, // reportToConsole - ); + const isFatal = false; + const reportToConsole = false; + if ( + !global.RN$handleException || + !global.RN$handleException(error, isFatal, reportToConsole) + ) { + reportException( + /* $FlowFixMe[class-object-subtyping] added when improving typing for this + * parameters */ + // $FlowFixMe[incompatible-call] + error, + isFatal, + reportToConsole, + ); + } } /** diff --git a/packages/react-native/Libraries/Core/__mocks__/NativeExceptionsManager.js b/packages/react-native/Libraries/Core/__mocks__/NativeExceptionsManager.js index a43017ce012eee..cfe7f55e61b119 100644 --- a/packages/react-native/Libraries/Core/__mocks__/NativeExceptionsManager.js +++ b/packages/react-native/Libraries/Core/__mocks__/NativeExceptionsManager.js @@ -14,7 +14,6 @@ import typeof NativeExceptionsManager from '../NativeExceptionsManager'; export default ({ reportFatalException: jest.fn(), reportSoftException: jest.fn(), - updateExceptionMessage: jest.fn(), dismissRedbox: jest.fn(), reportException: jest.fn(), }: NativeExceptionsManager); diff --git a/packages/react-native/Libraries/Core/__tests__/ExceptionsManager-test.js b/packages/react-native/Libraries/Core/__tests__/ExceptionsManager-test.js index 2dde1759c3df05..ee4bf4ebfb66ee 100644 --- a/packages/react-native/Libraries/Core/__tests__/ExceptionsManager-test.js +++ b/packages/react-native/Libraries/Core/__tests__/ExceptionsManager-test.js @@ -67,8 +67,6 @@ function runExceptionsManagerTests() { return { default: { reportException: jest.fn(), - // Used to show symbolicated messages, not part of this test. - updateExceptionMessage: () => {}, }, }; }); diff --git a/packages/react-native/Libraries/Core/setUpErrorHandling.js b/packages/react-native/Libraries/Core/setUpErrorHandling.js index d1aaad73794060..32846d42f83506 100644 --- a/packages/react-native/Libraries/Core/setUpErrorHandling.js +++ b/packages/react-native/Libraries/Core/setUpErrorHandling.js @@ -21,13 +21,7 @@ ExceptionsManager.installConsoleErrorReporter(); if (!global.__fbDisableExceptionsManager) { const handleError = (e: mixed, isFatal: boolean) => { try { - // TODO(T196834299): We should really use a c++ turbomodule for this - if ( - !global.RN$handleException || - !global.RN$handleException(e, isFatal) - ) { - ExceptionsManager.handleException(e, isFatal); - } + ExceptionsManager.handleException(e, isFatal); } catch (ee) { console.log('Failed to print error: ', ee.message); throw e; diff --git a/packages/react-native/React/CoreModules/RCTExceptionsManager.h b/packages/react-native/React/CoreModules/RCTExceptionsManager.h index f23bb2153b809e..d47ad65cf28042 100644 --- a/packages/react-native/React/CoreModules/RCTExceptionsManager.h +++ b/packages/react-native/React/CoreModules/RCTExceptionsManager.h @@ -11,7 +11,6 @@ NS_ASSUME_NONNULL_BEGIN @protocol RCTExceptionsManagerDelegate - - (void)handleSoftJSExceptionWithMessage:(nullable NSString *)message stack:(nullable NSArray *)stack exceptionId:(NSNumber *)exceptionId @@ -20,12 +19,6 @@ NS_ASSUME_NONNULL_BEGIN stack:(nullable NSArray *)stack exceptionId:(NSNumber *)exceptionId extraDataAsJSON:(nullable NSString *)extraDataAsJSON; - -@optional -- (void)updateJSExceptionWithMessage:(nullable NSString *)message - stack:(nullable NSArray *)stack - exceptionId:(NSNumber *)exceptionId; - @end @interface RCTExceptionsManager : NSObject @@ -41,7 +34,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)reportJsException:(nullable NSString *)message stack:(nullable NSArray *)stack exceptionId:(double)exceptionId - isFatal:(bool)isFatal; + isFatal:(bool)isFatal __attribute__((deprecated)); @property (nonatomic, weak) id delegate; diff --git a/packages/react-native/React/CoreModules/RCTExceptionsManager.mm b/packages/react-native/React/CoreModules/RCTExceptionsManager.mm index 7a5de3849e2b92..d7f8f647604a90 100644 --- a/packages/react-native/React/CoreModules/RCTExceptionsManager.mm +++ b/packages/react-native/React/CoreModules/RCTExceptionsManager.mm @@ -99,27 +99,6 @@ - (void)reportFatal:(NSString *)message [self reportFatal:message stack:stack exceptionId:exceptionId extraDataAsJSON:nil]; } -RCT_EXPORT_METHOD(updateExceptionMessage - : (NSString *)message stack - : (NSArray *)stack exceptionId - : (double)exceptionId) -{ - if (RCTRedBoxGetEnabled()) { - RCTRedBox *redbox = [_moduleRegistry moduleForName:"RedBox"]; - [redbox updateErrorMessage:message withStack:stack errorCookie:(int)exceptionId]; - } - - if (_delegate && [_delegate respondsToSelector:@selector(updateJSExceptionWithMessage:stack:exceptionId:)]) { - [_delegate updateJSExceptionWithMessage:message stack:stack exceptionId:[NSNumber numberWithDouble:exceptionId]]; - } -} - -// Deprecated. Use reportFatalException directly instead. -RCT_EXPORT_METHOD(reportUnhandledException : (NSString *)message stack : (NSArray *)stack) -{ - [self reportFatalException:message stack:stack exceptionId:-1]; -} - RCT_EXPORT_METHOD(dismissRedbox) {} RCT_EXPORT_METHOD(reportException : (JS::NativeExceptionsManager::ExceptionData &)data) diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 5626ce74dd4dae..9dac0c2d58ade9 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -2212,7 +2212,6 @@ public abstract class com/facebook/react/devsupport/DevSupportManagerBase : com/ public fun startInspector ()V public fun stopInspector ()V public fun toggleElementInspector ()V - public fun updateJSError (Ljava/lang/String;Lcom/facebook/react/bridge/ReadableArray;I)V } public abstract interface class com/facebook/react/devsupport/DevSupportManagerBase$CallbackWithBundleLoader { @@ -2380,7 +2379,6 @@ public class com/facebook/react/devsupport/ReleaseDevSupportManager : com/facebo public fun startInspector ()V public fun stopInspector ()V public fun toggleElementInspector ()V - public fun updateJSError (Ljava/lang/String;Lcom/facebook/react/bridge/ReadableArray;I)V } public class com/facebook/react/devsupport/StackTraceHelper { @@ -2516,7 +2514,6 @@ public abstract interface class com/facebook/react/devsupport/interfaces/DevSupp public abstract fun startInspector ()V public abstract fun stopInspector ()V public abstract fun toggleElementInspector ()V - public abstract fun updateJSError (Ljava/lang/String;Lcom/facebook/react/bridge/ReadableArray;I)V } public abstract interface class com/facebook/react/devsupport/interfaces/DevSupportManager$PackagerLocationCustomizer { @@ -3138,7 +3135,6 @@ public class com/facebook/react/modules/core/ExceptionsManagerModule : com/faceb public fun reportException (Lcom/facebook/react/bridge/ReadableMap;)V public fun reportFatalException (Ljava/lang/String;Lcom/facebook/react/bridge/ReadableArray;D)V public fun reportSoftException (Ljava/lang/String;Lcom/facebook/react/bridge/ReadableArray;D)V - public fun updateExceptionMessage (Ljava/lang/String;Lcom/facebook/react/bridge/ReadableArray;D)V } public class com/facebook/react/modules/core/HeadlessJsTaskSupportModule : com/facebook/fbreact/specs/NativeHeadlessJsTaskSupportSpec { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java index 35dba5ee6e3e26..f91d9add84937c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java @@ -279,26 +279,6 @@ public Pair processErrorCustomizers(Pair { - // Since we only show the first JS error in a succession of JS errors, make sure we only - // update the error message for that error message. This assumes that updateJSError - // belongs to the most recent showNewJSError - if ((mRedBoxSurfaceDelegate != null && !mRedBoxSurfaceDelegate.isShowing()) - || errorCookie != mLastErrorCookie) { - return; - } - - // The RedBox surface delegate will always show the latest error - updateLastErrorInfo( - message, StackTraceHelper.convertJsStackTrace(details), errorCookie, ErrorType.JS); - mRedBoxSurfaceDelegate.show(); - }); - } - @Override public void hideRedboxDialog() { if (mRedBoxSurfaceDelegate == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReleaseDevSupportManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReleaseDevSupportManager.kt index ba9e0aa03c5220..02398843112d28 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReleaseDevSupportManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReleaseDevSupportManager.kt @@ -53,12 +53,6 @@ public open class ReleaseDevSupportManager : DevSupportManager { override public fun destroyRootView(rootView: View?): Unit = Unit - override public fun updateJSError( - message: String?, - details: ReadableArray?, - errorCookie: Int - ): Unit = Unit - override public fun hideRedboxDialog(): Unit = Unit override public fun showDevOptionsDialog(): Unit = Unit diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.kt index 67bc3b0e21bf60..a778ac54577676 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.kt @@ -48,8 +48,6 @@ public interface DevSupportManager : JSExceptionHandler { public fun showNewJSError(message: String?, details: ReadableArray?, errorCookie: Int) - public fun updateJSError(message: String?, details: ReadableArray?, errorCookie: Int) - public fun hideRedboxDialog() public fun showDevOptionsDialog() diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/ExceptionsManagerModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/ExceptionsManagerModule.kt index 60c143a2eb3409..863c87b33eb0a1 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/ExceptionsManagerModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/ExceptionsManagerModule.kt @@ -60,17 +60,6 @@ public open class ExceptionsManagerModule(private val devSupportManager: DevSupp } } - override fun updateExceptionMessage( - title: String?, - details: ReadableArray?, - exceptionIdDouble: Double - ) { - val exceptionId = exceptionIdDouble.toInt() - if (devSupportManager.devSupportEnabled) { - devSupportManager.updateJSError(title, details, exceptionId) - } - } - override fun dismissRedbox() { if (devSupportManager.devSupportEnabled) { devSupportManager.hideRedboxDialog() diff --git a/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp b/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp index 8bcbe849d1fe9b..2c156e6e43126d 100644 --- a/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp +++ b/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp @@ -50,6 +50,34 @@ void objectAssign( auto assign = Object.getPropertyAsFunction(runtime, "assign"); assign.callWithThis(runtime, Object, target, value); } + +jsi::Object wrapInErrorIfNecessary( + jsi::Runtime& runtime, + const jsi::Value& value) { + auto Error = runtime.global().getPropertyAsFunction(runtime, "Error"); + auto isError = + value.isObject() && value.asObject(runtime).instanceOf(runtime, Error); + auto error = isError + ? value.getObject(runtime) + : Error.callAsConstructor(runtime, value).getObject(runtime); + return error; +} + +class SetFalseOnDestruct { + std::shared_ptr _value; + + public: + SetFalseOnDestruct(const SetFalseOnDestruct&) = delete; + SetFalseOnDestruct& operator=(const SetFalseOnDestruct&) = delete; + SetFalseOnDestruct(SetFalseOnDestruct&&) = delete; + SetFalseOnDestruct& operator=(SetFalseOnDestruct&&) = delete; + explicit SetFalseOnDestruct(std::shared_ptr value) + : _value(std::move(value)) {} + ~SetFalseOnDestruct() { + *_value = false; + } +}; + } // namespace namespace facebook::react { @@ -150,7 +178,7 @@ std::ostream& operator<<( JsErrorHandler::JsErrorHandler(JsErrorHandler::OnJsError onJsError) : _onJsError(std::move(onJsError)), - _hasHandledFatalError(false){ + _inErrorHandler(std::make_shared(false)){ }; @@ -159,7 +187,8 @@ JsErrorHandler::~JsErrorHandler() {} void JsErrorHandler::handleError( jsi::Runtime& runtime, jsi::JSError& error, - bool isFatal) { + bool isFatal, + 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) { @@ -179,15 +208,19 @@ void JsErrorHandler::handleError( } } - emitError(runtime, error, isFatal); + handleErrorWithCppPipeline(runtime, error, isFatal, logToConsole); } -void JsErrorHandler::emitError( +void JsErrorHandler::handleErrorWithCppPipeline( jsi::Runtime& runtime, jsi::JSError& error, - bool isFatal) { + bool isFatal, + bool logToConsole) { + *_inErrorHandler = true; + SetFalseOnDestruct temp{_inErrorHandler}; + auto message = error.getMessage(); - auto errorObj = error.value().getObject(runtime); + auto errorObj = wrapInErrorIfNecessary(runtime, error.value()); auto componentStackValue = errorObj.getProperty(runtime, "componentStack"); if (!isLooselyNull(componentStackValue)) { message += "\n" + stringifyToCpp(runtime, componentStackValue); @@ -266,6 +299,14 @@ void JsErrorHandler::emitError( isTruthy(runtime, errorObj.getProperty(runtime, "isComponentError")); data.setProperty(runtime, "isComponentError", isComponentError); + if (logToConsole) { + auto console = runtime.global().getPropertyAsObject(runtime, "console"); + auto errorFn = console.getPropertyAsFunction(runtime, "error"); + auto finalMessage = + jsi::String::createFromUtf8(runtime, parsedError.message); + errorFn.callWithThis(runtime, console, finalMessage); + } + std::shared_ptr shouldPreventDefault = std::make_shared(false); auto preventDefault = jsi::Function::createFromHostFunction( runtime, @@ -318,4 +359,8 @@ void JsErrorHandler::notifyOfFatalError() { _hasHandledFatalError = true; } +bool JsErrorHandler::inErrorHandler() { + return *_inErrorHandler; +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.h b/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.h index b8d784ac63eb97..8d6f2c6467586c 100644 --- a/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.h +++ b/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.h @@ -43,13 +43,18 @@ class JsErrorHandler { explicit JsErrorHandler(OnJsError onJsError); ~JsErrorHandler(); - void handleError(jsi::Runtime& runtime, jsi::JSError& error, bool isFatal); + void handleError( + jsi::Runtime& runtime, + jsi::JSError& error, + bool isFatal, + bool logToConsole = true); bool hasHandledFatalError(); void registerErrorListener( const std::function& listener); void setRuntimeReady(); bool isRuntimeReady(); void notifyOfFatalError(); + bool inErrorHandler(); private: /** @@ -62,9 +67,14 @@ class JsErrorHandler { OnJsError _onJsError; bool _hasHandledFatalError; bool _isRuntimeReady{}; + std::shared_ptr _inErrorHandler; std::vector> _errorListeners; - void emitError(jsi::Runtime& runtime, jsi::JSError& error, bool isFatal); + void handleErrorWithCppPipeline( + jsi::Runtime& runtime, + jsi::JSError& error, + bool isFatal, + bool logToConsole); }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp b/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp index 291cfa2caa4ed4..20a268761a5dee 100644 --- a/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp +++ b/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp @@ -391,17 +391,6 @@ bool isTruthy(jsi::Runtime& runtime, const jsi::Value& value) { return Boolean.call(runtime, value).getBool(); } -jsi::Value wrapInErrorIfNecessary( - jsi::Runtime& runtime, - const jsi::Value& value) { - auto Error = runtime.global().getPropertyAsFunction(runtime, "Error"); - auto isError = - value.isObject() && value.asObject(runtime).instanceOf(runtime, Error); - auto error = isError ? value.getObject(runtime) - : Error.callAsConstructor(runtime, value); - return jsi::Value(runtime, error); -} - } // namespace void ReactInstance::initializeRuntime( @@ -420,6 +409,21 @@ void ReactInstance::initializeRuntime( defineReactInstanceFlags(runtime, options); + defineReadOnlyGlobal( + runtime, + "RN$inExceptionHandler", + jsi::Function::createFromHostFunction( + runtime, + jsi::PropNameID::forAscii(runtime, "inExceptionHandler"), + 0, + [jsErrorHandler = jsErrorHandler_]( + jsi::Runtime& /*runtime*/, + const jsi::Value& /*unused*/, + const jsi::Value* /*args*/, + size_t /*count*/) { + return jsErrorHandler->inErrorHandler(); + })); + // TODO(T196834299): We should really use a C++ turbomodule for this defineReadOnlyGlobal( runtime, @@ -427,7 +431,7 @@ void ReactInstance::initializeRuntime( jsi::Function::createFromHostFunction( runtime, jsi::PropNameID::forAscii(runtime, "handleException"), - 2, + 3, [jsErrorHandler = jsErrorHandler_]( jsi::Runtime& runtime, const jsi::Value& /*unused*/, @@ -436,7 +440,7 @@ void ReactInstance::initializeRuntime( if (count < 2) { throw jsi::JSError( runtime, - "handleException requires 2 arguments: error, isFatal"); + "handleException requires 3 arguments: error, isFatal, logToConsole (optional)"); } auto isFatal = isTruthy(runtime, args[1]); @@ -448,9 +452,16 @@ void ReactInstance::initializeRuntime( return jsi::Value(false); } - auto jsError = jsi::JSError( - runtime, wrapInErrorIfNecessary(runtime, args[0])); - jsErrorHandler->handleError(runtime, jsError, isFatal); + auto jsError = + jsi::JSError(runtime, jsi::Value(runtime, args[0])); + + if (count == 2) { + jsErrorHandler->handleError(runtime, jsError, isFatal); + } else { + auto logToConsole = isTruthy(runtime, args[2]); + jsErrorHandler->handleError( + runtime, jsError, isFatal, logToConsole); + } return jsi::Value(true); })); diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/React-RuntimeApple.podspec b/packages/react-native/ReactCommon/react/runtime/platform/ios/React-RuntimeApple.podspec index e14016ae6340b1..3cc0f51a90d712 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/React-RuntimeApple.podspec +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/React-RuntimeApple.podspec @@ -22,7 +22,7 @@ folly_version = folly_config[:version] folly_dep_name = folly_config[:dep_name] boost_config = get_boost_config() -boost_compiler_flags = boost_config[:compiler_flags] +boost_compiler_flags = boost_config[:compiler_flags] header_search_paths = [ "$(PODS_ROOT)/boost", @@ -70,6 +70,8 @@ Pod::Spec.new do |s| s.dependency "React-jserrorhandler" s.dependency "React-jsinspector" + add_dependency(s, "ReactCodegen") + if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1" s.dependency "hermes-engine" s.dependency "React-RuntimeHermes" diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.h b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.h index 25e213a11c27b5..37bef80f625763 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.h +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.h @@ -27,6 +27,15 @@ typedef NSURL *_Nullable (^RCTHostBundleURLProvider)(void); @protocol RCTHostDelegate +- (void)hostDidStart:(RCTHost *)host; + +@optional +- (void)loadBundleAtURL:(NSURL *)sourceURL + onProgress:(RCTSourceLoadProgressBlock)onProgress + onComplete:(RCTSourceLoadBlock)loadCallback; + +// TODO(T205780509): Remove this api in react native v0.78 +// The bridgeless js error handling api will just call into exceptionsmanager directly - (void)host:(RCTHost *)host didReceiveJSErrorStack:(NSArray *> *)stack message:(NSString *)message @@ -35,15 +44,7 @@ typedef NSURL *_Nullable (^RCTHostBundleURLProvider)(void); componentStack:(NSString *_Nullable)componentStack exceptionId:(NSUInteger)exceptionId isFatal:(BOOL)isFatal - extraData:(NSDictionary *)extraData; - -- (void)hostDidStart:(RCTHost *)host; - -@optional -- (void)loadBundleAtURL:(NSURL *)sourceURL - onProgress:(RCTSourceLoadProgressBlock)onProgress - onComplete:(RCTSourceLoadBlock)loadCallback; - + extraData:(NSDictionary *)extraData __attribute__((deprecated)); @end @protocol RCTHostRuntimeDelegate diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm index 75fe99c2353cbd..814e8503d9e997 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm @@ -315,7 +315,7 @@ - (void)dealloc #pragma mark - RCTInstanceDelegate -- (void)instance:(RCTInstance *)instance +- (BOOL)instance:(RCTInstance *)instance didReceiveJSErrorStack:(NSArray *> *)stack message:(NSString *)message originalMessage:(NSString *_Nullable)originalMessage @@ -325,6 +325,12 @@ - (void)instance:(RCTInstance *)instance isFatal:(BOOL)isFatal extraData:(NSDictionary *)extraData { + if (![_hostDelegate respondsToSelector:@selector(host: + didReceiveJSErrorStack:message:originalMessage:name:componentStack + :exceptionId:isFatal:extraData:)]) { + return NO; + } + [_hostDelegate host:self didReceiveJSErrorStack:stack message:message @@ -334,6 +340,7 @@ - (void)instance:(RCTInstance *)instance exceptionId:exceptionId isFatal:isFatal extraData:extraData]; + return YES; } - (void)instance:(RCTInstance *)instance didInitializeRuntime:(facebook::jsi::Runtime &)runtime diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.h b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.h index b77c8994e35a2f..0593dccc87a0c6 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.h +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.h @@ -37,7 +37,15 @@ RCT_EXTERN void RCTInstanceSetRuntimeDiagnosticFlags(NSString *_Nullable flags); @protocol RCTInstanceDelegate -- (void)instance:(RCTInstance *)instance +- (void)instance:(RCTInstance *)instance didInitializeRuntime:(facebook::jsi::Runtime &)runtime; + +- (void)loadBundleAtURL:(NSURL *)sourceURL + onProgress:(RCTSourceLoadProgressBlock)onProgress + onComplete:(RCTSourceLoadBlock)loadCallback; + +// TODO(T205780509): Remove this api in react native v0.78 +// The bridgeless js error handling api will just call into exceptionsmanager directly +- (BOOL)instance:(RCTInstance *)instance didReceiveJSErrorStack:(NSArray *> *)stack message:(NSString *)message originalMessage:(NSString *_Nullable)originalMessage @@ -45,14 +53,7 @@ RCT_EXTERN void RCTInstanceSetRuntimeDiagnosticFlags(NSString *_Nullable flags); componentStack:(NSString *_Nullable)componentStack exceptionId:(NSUInteger)exceptionId isFatal:(BOOL)isFatal - extraData:(NSDictionary *)extraData; - -- (void)instance:(RCTInstance *)instance didInitializeRuntime:(facebook::jsi::Runtime &)runtime; - -- (void)loadBundleAtURL:(NSURL *)sourceURL - onProgress:(RCTSourceLoadProgressBlock)onProgress - onComplete:(RCTSourceLoadBlock)loadCallback; - + extraData:(NSDictionary *)extraData __attribute__((deprecated)); @end /** diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm index 09ee426ea0af80..16117722794050 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm @@ -9,6 +9,7 @@ #import +#import #import #import #import @@ -467,32 +468,57 @@ - (void)_loadScriptFromSource:(RCTSource *)source - (void)_handleJSError:(const JsErrorHandler::ParsedError &)error withRuntime:(jsi::Runtime &)runtime { - NSString *message = @(error.message.c_str()); + NSMutableDictionary *errorData = [NSMutableDictionary new]; + errorData[@"message"] = @(error.message.c_str()); + if (error.originalMessage) { + errorData[@"originalMessage"] = @(error.originalMessage->c_str()); + } + if (error.name) { + errorData[@"name"] = @(error.name->c_str()); + } + if (error.componentStack) { + errorData[@"componentStack"] = @(error.componentStack->c_str()); + } + NSMutableArray *> *stack = [NSMutableArray new]; for (const JsErrorHandler::ParsedError::StackFrame &frame : error.stack) { - [stack addObject:@{ - @"file" : frame.file ? @((*frame.file).c_str()) : [NSNull null], - @"methodName" : @(frame.methodName.c_str()), - @"lineNumber" : frame.lineNumber ? @(*frame.lineNumber) : [NSNull null], - @"column" : frame.column ? @(*frame.column) : [NSNull null], - }]; + NSMutableDictionary *stackFrame = [NSMutableDictionary new]; + if (frame.file) { + stackFrame[@"file"] = @(frame.file->c_str()); + } + stackFrame[@"methodName"] = @(frame.methodName.c_str()); + if (frame.lineNumber) { + stackFrame[@"lineNumber"] = @(*frame.lineNumber); + } + if (frame.column) { + stackFrame[@"column"] = @(*frame.column); + } + [stack addObject:stackFrame]; } - NSString *originalMessage = error.originalMessage ? @(error.originalMessage->c_str()) : nil; - NSString *name = error.name ? @(error.name->c_str()) : nil; - NSString *componentStack = error.componentStack ? @(error.componentStack->c_str()) : nil; + errorData[@"stack"] = stack; + errorData[@"id"] = @(error.id); + errorData[@"isFatal"] = @(error.isFatal); + id extraData = TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsi::Value(runtime, error.extraData), nullptr); + if (extraData) { + errorData[@"extraData"] = extraData; + } - [_delegate instance:self - didReceiveJSErrorStack:stack - message:message - originalMessage:originalMessage - name:name - componentStack:componentStack - exceptionId:error.id - isFatal:error.isFatal - extraData:extraData]; + if (![_delegate instance:self + didReceiveJSErrorStack:errorData[@"stack"] + message:errorData[@"message"] + originalMessage:errorData[@"originalMessage"] + name:errorData[@"name"] + componentStack:errorData[@"componentStack"] + exceptionId:error.id + isFatal:errorData[@"isFatal"] + extraData:errorData[@"extraData"]]) { + JS::NativeExceptionsManager::ExceptionData jsErrorData{errorData}; + id exceptionsManager = [_turboModuleManager moduleForName:"ExceptionsManager"]; + [exceptionsManager reportException:jsErrorData]; + } } @end diff --git a/packages/react-native/src/private/specs/modules/NativeExceptionsManager.js b/packages/react-native/src/private/specs/modules/NativeExceptionsManager.js index 1da0633dc17db1..0b684c681e7071 100644 --- a/packages/react-native/src/private/specs/modules/NativeExceptionsManager.js +++ b/packages/react-native/src/private/specs/modules/NativeExceptionsManager.js @@ -47,11 +47,6 @@ export interface Spec extends TurboModule { exceptionId: number, ) => void; +reportException?: (data: ExceptionData) => void; - +updateExceptionMessage: ( - message: string, - stack: Array, - exceptionId: number, - ) => void; // TODO(T53311281): This is a noop on iOS now. Implement it. +dismissRedbox?: () => void; } @@ -74,13 +69,6 @@ const ExceptionsManager = { ) { NativeModule.reportSoftException(message, stack, exceptionId); }, - updateExceptionMessage( - message: string, - stack: Array, - exceptionId: number, - ) { - NativeModule.updateExceptionMessage(message, stack, exceptionId); - }, dismissRedbox(): void { if (Platform.OS !== 'ios' && NativeModule.dismissRedbox) { // TODO(T53311281): This is a noop on iOS now. Implement it.