diff --git a/Common/cpp/NativeModules/NativeReanimatedModule.cpp b/Common/cpp/NativeModules/NativeReanimatedModule.cpp index 0aa408ec59f..4330e4fdca4 100644 --- a/Common/cpp/NativeModules/NativeReanimatedModule.cpp +++ b/Common/cpp/NativeModules/NativeReanimatedModule.cpp @@ -58,8 +58,11 @@ NativeReanimatedModule::NativeReanimatedModule( platformDepMethodsHolder.configurePropsFunction) #endif { - auto requestAnimationFrame = [=](FrameCallback callback) { - frameCallbacks.push_back(callback); + auto requestAnimationFrame = [=](jsi::Runtime &rt, const jsi::Value &fn) { + auto jsFunction = std::make_shared(rt, fn); + frameCallbacks.push_back([=](double timestamp) { + runtimeHelper->runOnUIGuarded(*jsFunction, jsi::Value(timestamp)); + }); maybeRequestRender(); }; @@ -174,20 +177,23 @@ NativeReanimatedModule::NativeReanimatedModule( void NativeReanimatedModule::installCoreFunctions( jsi::Runtime &rt, - const jsi::Value &valueUnpacker, - const jsi::Value &layoutAnimationStartFunction) { + const jsi::Value &callGuard, + const jsi::Value &valueUnpacker) { if (!runtimeHelper) { // initialize runtimeHelper here if not already present. We expect only one // instace of the helper to exists. runtimeHelper = std::make_shared(&rt, this->runtime.get(), scheduler); } + runtimeHelper->callGuard = + std::make_unique(runtimeHelper.get(), callGuard); runtimeHelper->valueUnpacker = - std::make_shared(runtimeHelper.get(), valueUnpacker); + std::make_unique(runtimeHelper.get(), valueUnpacker); } NativeReanimatedModule::~NativeReanimatedModule() { if (runtimeHelper) { + runtimeHelper->callGuard = nullptr; runtimeHelper->valueUnpacker = nullptr; // event handler registry and frame callbacks store some JSI values from UI // runtime, so they have to go away before we tear down the runtime @@ -206,11 +212,10 @@ void NativeReanimatedModule::scheduleOnUI( assert( shareableWorklet->valueType() == Shareable::WorkletType && "only worklets can be scheduled to run on UI"); - auto uiRuntime = runtimeHelper->uiRuntime(); frameCallbacks.push_back([=](double timestamp) { - jsi::Runtime &rt = *uiRuntime; + jsi::Runtime &rt = *runtimeHelper->uiRuntime(); auto workletValue = shareableWorklet->getJSValue(rt); - workletValue.asObject(rt).asFunction(rt).call(rt); + runtimeHelper->runOnUIGuarded(workletValue); }); maybeRequestRender(); } diff --git a/Common/cpp/NativeModules/NativeReanimatedModule.h b/Common/cpp/NativeModules/NativeReanimatedModule.h index 1ae695b9c95..d15f196ebbb 100644 --- a/Common/cpp/NativeModules/NativeReanimatedModule.h +++ b/Common/cpp/NativeModules/NativeReanimatedModule.h @@ -49,8 +49,8 @@ class NativeReanimatedModule : public NativeReanimatedModuleSpec, void installCoreFunctions( jsi::Runtime &rt, - const jsi::Value &workletMaker, - const jsi::Value &layoutAnimationStartFunction) override; + const jsi::Value &callGuard, + const jsi::Value &valueUnpacker) override; jsi::Value makeShareableClone(jsi::Runtime &rt, const jsi::Value &value) override; diff --git a/Common/cpp/NativeModules/NativeReanimatedModuleSpec.h b/Common/cpp/NativeModules/NativeReanimatedModuleSpec.h index 3e7393c9022..387004d050c 100644 --- a/Common/cpp/NativeModules/NativeReanimatedModuleSpec.h +++ b/Common/cpp/NativeModules/NativeReanimatedModuleSpec.h @@ -24,8 +24,8 @@ class JSI_EXPORT NativeReanimatedModuleSpec : public TurboModule { public: virtual void installCoreFunctions( jsi::Runtime &rt, - const jsi::Value &valueUnpacker, - const jsi::Value &layoutAnimationStartFunction) = 0; + const jsi::Value &callGuard, + const jsi::Value &valueUnpacker) = 0; // SharedValue virtual jsi::Value makeShareableClone( diff --git a/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp b/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp index bc9f1e5795c..0c772b5364b 100644 --- a/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp +++ b/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #if __has_include() @@ -79,6 +80,35 @@ ReanimatedHermesRuntime::ReanimatedHermesRuntime( // that the thread was indeed `quit` before jsQueue->quitSynchronous(); #endif + +#ifdef DEBUG + facebook::hermes::HermesRuntime *wrappedRuntime = runtime_.get(); + jsi::Value evalWithSourceMap = jsi::Function::createFromHostFunction( + *runtime_, + jsi::PropNameID::forAscii(*runtime_, "evalWithSourceMap"), + 3, + [wrappedRuntime]( + jsi::Runtime &rt, + const jsi::Value &thisValue, + const jsi::Value *args, + size_t count) -> jsi::Value { + auto code = std::make_shared( + args[0].asString(rt).utf8(rt)); + std::string sourceURL; + if (count > 1 && args[1].isString()) { + sourceURL = args[1].asString(rt).utf8(rt); + } + std::shared_ptr sourceMap; + if (count > 2 && args[2].isString()) { + sourceMap = std::make_shared( + args[2].asString(rt).utf8(rt)); + } + return wrappedRuntime->evaluateJavaScriptWithSourceMap( + code, sourceMap, sourceURL); + }); + runtime_->global().setProperty( + *runtime_, "evalWithSourceMap", evalWithSourceMap); +#endif // DEBUG } ReanimatedHermesRuntime::~ReanimatedHermesRuntime() { diff --git a/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.h b/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.h index 526cc1ab690..aac9dec1640 100644 --- a/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.h +++ b/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.h @@ -116,7 +116,7 @@ class ReanimatedHermesRuntime ~ReanimatedHermesRuntime(); private: - std::shared_ptr runtime_; + std::unique_ptr runtime_; ReanimatedReentrancyCheck reentrancyCheck_; }; diff --git a/Common/cpp/SharedItems/Shareables.cpp b/Common/cpp/SharedItems/Shareables.cpp index 1cb4bbc3b9e..8708b0405a1 100644 --- a/Common/cpp/SharedItems/Shareables.cpp +++ b/Common/cpp/SharedItems/Shareables.cpp @@ -13,7 +13,9 @@ CoreFunction::CoreFunction( rnFunction_ = std::make_unique(workletObject.asFunction(rt)); functionBody_ = workletObject.getProperty(rt, "asString").asString(rt).utf8(rt); - location_ = workletObject.getProperty(rt, "__location").asString(rt).utf8(rt); + location_ = "worklet_" + + std::to_string(static_cast( + workletObject.getProperty(rt, "__workletHash").getNumber())); } std::unique_ptr &CoreFunction::getFunction(jsi::Runtime &rt) { diff --git a/Common/cpp/SharedItems/Shareables.h b/Common/cpp/SharedItems/Shareables.h index 7a26f815a82..748414100c0 100644 --- a/Common/cpp/SharedItems/Shareables.h +++ b/Common/cpp/SharedItems/Shareables.h @@ -20,7 +20,29 @@ using namespace facebook; namespace reanimated { -class CoreFunction; +class JSRuntimeHelper; + +// Core functions are not allowed to capture outside variables, otherwise they'd +// try to access _closure variable which is something we want to avoid for +// simplicity reasons. +class CoreFunction { + private: + std::unique_ptr rnFunction_; + std::unique_ptr uiFunction_; + std::string functionBody_; + std::string location_; + JSRuntimeHelper + *runtimeHelper_; // runtime helper holds core function references, so we + // use normal pointer here to avoid ref cycles. + std::unique_ptr &getFunction(jsi::Runtime &rt); + + public: + CoreFunction(JSRuntimeHelper *runtimeHelper, const jsi::Value &workletObject); + template + jsi::Value call(jsi::Runtime &rt, Args &&...args) { + return getFunction(rt)->call(rt, args...); + } +}; class JSRuntimeHelper { private: @@ -36,7 +58,8 @@ class JSRuntimeHelper { : rnRuntime_(rnRuntime), uiRuntime_(uiRuntime), scheduler_(scheduler) {} volatile bool uiRuntimeDestroyed; - std::shared_ptr valueUnpacker; + std::unique_ptr callGuard; + std::unique_ptr valueUnpacker; inline jsi::Runtime *uiRuntime() const { return uiRuntime_; @@ -61,27 +84,19 @@ class JSRuntimeHelper { void scheduleOnJS(std::function job) { scheduler_->scheduleOnJS(job); } -}; - -// Core functions are not allowed to capture outside variables, otherwise they'd -// try to access _closure variable which is something we want to avoid for -// simplicity reasons. -class CoreFunction { - private: - std::unique_ptr rnFunction_; - std::unique_ptr uiFunction_; - std::string functionBody_; - std::string location_; - JSRuntimeHelper - *runtimeHelper_; // runtime helper holds core function references, so we - // use normal pointer here to avoid ref cycles. - std::unique_ptr &getFunction(jsi::Runtime &rt); - public: - CoreFunction(JSRuntimeHelper *runtimeHelper, const jsi::Value &workletObject); template - jsi::Value call(jsi::Runtime &rt, Args &&...args) { - return getFunction(rt)->call(rt, args...); + inline void runOnUIGuarded(const jsi::Value &function, Args &&...args) { + // We only use callGuard in debug mode, otherwise we call the provided + // function directly. CallGuard provides a way of capturing exceptions in + // JavaScript and propagating them to the main React Native thread such that + // they can be presented using RN's LogBox. + jsi::Runtime &rt = *uiRuntime_; +#ifdef DEBUG + callGuard->call(rt, function, args...); +#else + function.asObject(rt).asFunction(rt).call(rt, args...); +#endif } }; diff --git a/Common/cpp/Tools/RuntimeDecorator.cpp b/Common/cpp/Tools/RuntimeDecorator.cpp index bf316351980..daa3adc449b 100644 --- a/Common/cpp/Tools/RuntimeDecorator.cpp +++ b/Common/cpp/Tools/RuntimeDecorator.cpp @@ -32,7 +32,30 @@ void RuntimeDecorator::decorateRuntime( rt.global().setProperty(rt, "global", rt.global()); - rt.global().setProperty(rt, "jsThis", jsi::Value::undefined()); +#ifdef DEBUG + auto evalWithSourceUrl = [](jsi::Runtime &rt, + const jsi::Value &thisValue, + const jsi::Value *args, + size_t count) -> jsi::Value { + auto code = std::make_shared( + args[0].asString(rt).utf8(rt)); + std::string url; + if (count > 1 && args[1].isString()) { + url = args[1].asString(rt).utf8(rt); + } + + return rt.evaluateJavaScript(code, url); + }; + + rt.global().setProperty( + rt, + "evalWithSourceUrl", + jsi::Function::createFromHostFunction( + rt, + jsi::PropNameID::forAscii(rt, "evalWithSourceUrl"), + 1, + evalWithSourceUrl)); +#endif // DEBUG auto callback = [](jsi::Runtime &rt, const jsi::Value &thisValue, @@ -208,11 +231,7 @@ void RuntimeDecorator::decorateUIRuntime( const jsi::Value &thisValue, const jsi::Value *args, const size_t count) -> jsi::Value { - auto fun = - std::make_shared(args[0].asObject(rt).asFunction(rt)); - requestFrame([&rt, fun](double timestampMs) { - fun->call(rt, jsi::Value(timestampMs)); - }); + requestFrame(rt, std::move(args[0])); return jsi::Value::undefined(); }; jsi::Value requestAnimationFrame = jsi::Function::createFromHostFunction( diff --git a/Common/cpp/Tools/RuntimeDecorator.h b/Common/cpp/Tools/RuntimeDecorator.h index 5b0f9b2ac2a..f1d8cddb2c1 100644 --- a/Common/cpp/Tools/RuntimeDecorator.h +++ b/Common/cpp/Tools/RuntimeDecorator.h @@ -11,7 +11,8 @@ using namespace facebook; namespace reanimated { -using RequestFrameFunction = std::function)>; +using RequestFrameFunction = + std::function; using ScheduleOnJSFunction = std::function; using MakeShareableCloneFunction = diff --git a/__tests__/__snapshots__/plugin.test.js.snap b/__tests__/__snapshots__/plugin.test.js.snap index 4e5e827a76e..babaa71a130 100644 --- a/__tests__/__snapshots__/plugin.test.js.snap +++ b/__tests__/__snapshots__/plugin.test.js.snap @@ -7,6 +7,8 @@ var objX = { }; var f = function () { + var _e = [new Error(), -3, -20]; + var _f = function _f() { return { res: x + objX.x @@ -21,13 +23,15 @@ var f = function () { }; _f.asString = \\"function f(){const{x,objX}=this._closure;return{res:x+objX.x};}\\"; _f.__workletHash = 5359970077727; - _f.__location = \\"${ process.cwd() }/jest tests fixture (6:6)\\"; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; return _f; }();" `; - exports[`babel plugin doesn't capture globals 1`] = ` "var f = function () { + var _e = [new Error(), 1, -20]; + var _f = function _f() { console.log('test'); }; @@ -35,7 +39,8 @@ exports[`babel plugin doesn't capture globals 1`] = ` _f._closure = {}; _f.asString = \\"function f(){console.log('test');}\\"; _f.__workletHash = 13298016111221; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:6)\\"; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; return _f; }();" `; @@ -48,6 +53,8 @@ exports[`babel plugin doesn't transform standard callback functions 1`] = ` exports[`babel plugin doesn't transform string literals 1`] = ` "var foo = function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(x) { var bar = 'worklet'; var baz = \\"worklet\\"; @@ -56,7 +63,8 @@ exports[`babel plugin doesn't transform string literals 1`] = ` _f._closure = {}; _f.asString = \\"function foo(x){const bar='worklet';const baz=\\\\\\"worklet\\\\\\";}\\"; _f.__workletHash = 9810417751380; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:6)\\"; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; return _f; }();" `; @@ -65,6 +73,8 @@ exports[`babel plugin supports recursive calls 1`] = ` "var a = 1; var foo = function () { + var _e = [new Error(), -2, -20]; + var _f = function _f(t) { if (t > 0) { return a + foo(t - 1); @@ -76,7 +86,8 @@ var foo = function () { }; _f.asString = \\"function foo(t){const foo=this._recur;const{a}=this._closure;if(t>0){return a+foo(t-1);}}\\"; _f.__workletHash = 2022702330805; - _f.__location = \\"${ process.cwd() }/jest tests fixture (3:6)\\"; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; return _f; }();" `; @@ -91,6 +102,8 @@ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && function Box() { var offset = (0, _reactNativeReanimated.useSharedValue)(0); var animatedStyles = (0, _reactNativeReanimated.useAnimatedStyle)(function () { + var _e = [new Error(), -2, -20]; + var _f = function _f() { return { transform: [{ @@ -102,9 +115,10 @@ function Box() { _f._closure = { offset: offset }; - _f.asString = \\"function _f(){const{offset}=this._closure;return{transform:[{translateX:offset.value*255}]};}\\"; - _f.__workletHash = 361788175040; - _f.__location = \\"${ process.cwd() }/jest tests fixture (10:48)\\"; + _f.asString = \\"function anonymous(){const{offset}=this._closure;return{transform:[{translateX:offset.value*255}]};}\\"; + _f.__workletHash = 16669311443114; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; _f.__optimalization = 3; return _f; }()); @@ -121,6 +135,8 @@ function Box() { exports[`babel plugin transforms spread operator in worklets for arrays 1`] = ` "var foo = function () { + var _e = [new Error(), 1, -20]; + var _f = function _f() { var bar = [4, 5]; var baz = [1].concat([2, 3], bar); @@ -129,13 +145,16 @@ exports[`babel plugin transforms spread operator in worklets for arrays 1`] = ` _f._closure = {}; _f.asString = \\"function foo(){const bar=[4,5];const baz=[1,...[2,3],...bar];}\\"; _f.__workletHash = 3161057533258; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:6)\\"; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; return _f; }();" `; exports[`babel plugin transforms spread operator in worklets for function arguments 1`] = ` "var foo = function () { + var _e = [new Error(), 1, -20]; + var _f = function _f() { for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; @@ -147,7 +166,8 @@ exports[`babel plugin transforms spread operator in worklets for function argume _f._closure = {}; _f.asString = \\"function foo(...args){console.log(args);}\\"; _f.__workletHash = 9866931756941; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:6)\\"; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; return _f; }();" `; @@ -158,6 +178,8 @@ exports[`babel plugin transforms spread operator in worklets for function calls var _toConsumableArray2 = _interopRequireDefault(require(\\"@babel/runtime/helpers/toConsumableArray\\")); var foo = function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(arg) { var _console; @@ -167,13 +189,16 @@ var foo = function () { _f._closure = {}; _f.asString = \\"function foo(arg){console.log(...arg);}\\"; _f.__workletHash = 2015887751437; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:6)\\"; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; return _f; }();" `; exports[`babel plugin transforms spread operator in worklets for objects 1`] = ` "var foo = function () { + var _e = [new Error(), 1, -20]; + var _f = function _f() { var bar = { d: 4, @@ -190,27 +215,33 @@ exports[`babel plugin transforms spread operator in worklets for objects 1`] = ` _f._closure = {}; _f.asString = \\"function foo(){const bar={d:4,e:5};const baz={a:1,...{b:2,c:3},...bar};}\\"; _f.__workletHash = 792186851025; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:6)\\"; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; return _f; }();" `; exports[`babel plugin workletizes ArrowFunctionExpression 1`] = ` "var foo = function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(x) { return x + 2; }; _f._closure = {}; - _f.asString = \\"function _f(x){return x+2;}\\"; - _f.__workletHash = 11411090164019; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:18)\\"; + _f.asString = \\"function anonymous(x){return x+2;}\\"; + _f.__workletHash = 16347365292089; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; return _f; }();" `; exports[`babel plugin workletizes FunctionDeclaration 1`] = ` "var foo = function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(x) { return x + 2; }; @@ -218,7 +249,8 @@ exports[`babel plugin workletizes FunctionDeclaration 1`] = ` _f._closure = {}; _f.asString = \\"function foo(x){return x+2;}\\"; _f.__workletHash = 4679479961836; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:6)\\"; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; return _f; }();" `; @@ -238,6 +270,8 @@ var Foo = function () { (0, _createClass2.default)(Foo, [{ key: \\"bar\\", get: function () { + var _e = [new Error(), -2, -20]; + var _f = function _f() { return x + 2; }; @@ -248,6 +282,7 @@ var Foo = function () { _f.asString = \\"function get(){const{x}=this._closure;return x+2;}\\"; _f.__workletHash = 10436985806815; _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; return _f; }() }]); @@ -257,6 +292,8 @@ var Foo = function () { exports[`babel plugin workletizes hook wrapped ArrowFunctionExpression automatically 1`] = ` "var animatedStyle = useAnimatedStyle(function () { + var _e = [new Error(), 1, -20]; + var _f = function _f() { return { width: 50 @@ -264,9 +301,10 @@ exports[`babel plugin workletizes hook wrapped ArrowFunctionExpression automatic }; _f._closure = {}; - _f.asString = \\"function _f(){return{width:50};}\\"; - _f.__workletHash = 9756190407413; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:45)\\"; + _f.asString = \\"function anonymous(){return{width:50};}\\"; + _f.__workletHash = 9645206935615; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; _f.__optimalization = 3; return _f; }());" @@ -274,6 +312,8 @@ exports[`babel plugin workletizes hook wrapped ArrowFunctionExpression automatic exports[`babel plugin workletizes hook wrapped named FunctionExpression automatically 1`] = ` "var animatedStyle = useAnimatedStyle(function () { + var _e = [new Error(), 1, -20]; + var _f = function _f() { return { width: 50 @@ -283,7 +323,8 @@ exports[`babel plugin workletizes hook wrapped named FunctionExpression automati _f._closure = {}; _f.asString = \\"function foo(){return{width:50};}\\"; _f.__workletHash = 6275510763626; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:45)\\"; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; _f.__optimalization = 3; return _f; }());" @@ -291,6 +332,8 @@ exports[`babel plugin workletizes hook wrapped named FunctionExpression automati exports[`babel plugin workletizes hook wrapped unnamed FunctionExpression automatically 1`] = ` "var animatedStyle = useAnimatedStyle(function () { + var _e = [new Error(), 1, -20]; + var _f = function _f() { return { width: 50 @@ -298,9 +341,10 @@ exports[`babel plugin workletizes hook wrapped unnamed FunctionExpression automa }; _f._closure = {}; - _f.asString = \\"function _f(){return{width:50};}\\"; - _f.__workletHash = 9756190407413; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:45)\\"; + _f.asString = \\"function anonymous(){return{width:50};}\\"; + _f.__workletHash = 9645206935615; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; _f.__optimalization = 3; return _f; }());" @@ -321,6 +365,8 @@ var Foo = function () { (0, _createClass2.default)(Foo, [{ key: \\"bar\\", value: function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(x) { return x + 2; }; @@ -329,6 +375,7 @@ var Foo = function () { _f.asString = \\"function bar(x){return x+2;}\\"; _f.__workletHash = 16974800582491; _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; return _f; }() }]); @@ -338,6 +385,8 @@ var Foo = function () { exports[`babel plugin workletizes named FunctionExpression 1`] = ` "var foo = function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(x) { return x + 2; }; @@ -345,7 +394,8 @@ exports[`babel plugin workletizes named FunctionExpression 1`] = ` _f._closure = {}; _f.asString = \\"function foo(x){return x+2;}\\"; _f.__workletHash = 4679479961836; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:18)\\"; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; return _f; }();" `; @@ -353,14 +403,17 @@ exports[`babel plugin workletizes named FunctionExpression 1`] = ` exports[`babel plugin workletizes object hook wrapped ArrowFunctionExpression automatically 1`] = ` "useAnimatedGestureHandler({ onStart: function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(event) { console.log(event); }; _f._closure = {}; - _f.asString = \\"function _f(event){console.log(event);}\\"; - _f.__workletHash = 2164830539996; - _f.__location = \\"${ process.cwd() }/jest tests fixture (3:17)\\"; + _f.asString = \\"function anonymous(event){console.log(event);}\\"; + _f.__workletHash = 1022605193782; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; return _f; }() });" @@ -369,6 +422,8 @@ exports[`babel plugin workletizes object hook wrapped ArrowFunctionExpression au exports[`babel plugin workletizes object hook wrapped ObjectMethod automatically 1`] = ` "useAnimatedGestureHandler({ onStart: function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(event) { console.log(event); }; @@ -376,7 +431,8 @@ exports[`babel plugin workletizes object hook wrapped ObjectMethod automatically _f._closure = {}; _f.asString = \\"function onStart(event){console.log(event);}\\"; _f.__workletHash = 338158776260; - _f.__location = \\"${ process.cwd() }/jest tests fixture (3:8)\\"; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; return _f; }() });" @@ -385,6 +441,8 @@ exports[`babel plugin workletizes object hook wrapped ObjectMethod automatically exports[`babel plugin workletizes object hook wrapped named FunctionExpression automatically 1`] = ` "useAnimatedGestureHandler({ onStart: function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(event) { console.log(event); }; @@ -392,7 +450,8 @@ exports[`babel plugin workletizes object hook wrapped named FunctionExpression a _f._closure = {}; _f.asString = \\"function onStart(event){console.log(event);}\\"; _f.__workletHash = 338158776260; - _f.__location = \\"${ process.cwd() }/jest tests fixture (3:17)\\"; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; return _f; }() });" @@ -401,14 +460,17 @@ exports[`babel plugin workletizes object hook wrapped named FunctionExpression a exports[`babel plugin workletizes object hook wrapped unnamed FunctionExpression automatically 1`] = ` "useAnimatedGestureHandler({ onStart: function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(event) { console.log(event); }; _f._closure = {}; - _f.asString = \\"function _f(event){console.log(event);}\\"; - _f.__workletHash = 2164830539996; - _f.__location = \\"${ process.cwd() }/jest tests fixture (3:17)\\"; + _f.asString = \\"function anonymous(event){console.log(event);}\\"; + _f.__workletHash = 1022605193782; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; return _f; }() });" @@ -418,34 +480,43 @@ exports[`babel plugin workletizes possibly chained gesture object callback funct "var _reactNativeGestureHandler = require(\\"react-native-gesture-handler\\"); var foo = _reactNativeGestureHandler.Gesture.Tap().numberOfTaps(2).onBegin(function () { + var _e = [new Error(), 1, -20]; + var _f = function _f() { console.log('onBegin'); }; _f._closure = {}; - _f.asString = \\"function _f(){console.log('onBegin');}\\"; - _f.__workletHash = 13662490049850; - _f.__location = \\"${ process.cwd() }/jest tests fixture (6:17)\\"; + _f.asString = \\"function anonymous(){console.log('onBegin');}\\"; + _f.__workletHash = 15393478329680; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; return _f; }()).onStart(function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(_event) { console.log('onStart'); }; _f._closure = {}; - _f.asString = \\"function _f(_event){console.log('onStart');}\\"; - _f.__workletHash = 16334902412526; - _f.__location = \\"${ process.cwd() }/jest tests fixture (9:17)\\"; + _f.asString = \\"function anonymous(_event){console.log('onStart');}\\"; + _f.__workletHash = 12748187344900; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; return _f; }()).onEnd(function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(_event, _success) { console.log('onEnd'); }; _f._closure = {}; - _f.asString = \\"function _f(_event,_success){console.log('onEnd');}\\"; - _f.__workletHash = 4053780716017; - _f.__location = \\"${ process.cwd() }/jest tests fixture (12:15)\\"; + _f.asString = \\"function anonymous(_event,_success){console.log('onEnd');}\\"; + _f.__workletHash = 232586479291; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; return _f; }());" `; @@ -465,6 +536,8 @@ var Foo = function () { (0, _createClass2.default)(Foo, null, [{ key: \\"bar\\", value: function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(x) { return x + 2; }; @@ -473,6 +546,7 @@ var Foo = function () { _f.asString = \\"function bar(x){return x+2;}\\"; _f.__workletHash = 16974800582491; _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; return _f; }() }]); @@ -482,14 +556,17 @@ var Foo = function () { exports[`babel plugin workletizes unnamed FunctionExpression 1`] = ` "var foo = function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(x) { return x + 2; }; _f._closure = {}; - _f.asString = \\"function _f(x){return x+2;}\\"; - _f.__workletHash = 11411090164019; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:18)\\"; + _f.asString = \\"function anonymous(x){return x+2;}\\"; + _f.__workletHash = 16347365292089; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; return _f; }();" `; diff --git a/package.json b/package.json index fc6dd8ef2f9..77a3b4b5077 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,8 @@ "invariant": "^2.2.4", "lodash.isequal": "^4.5.0", "setimmediate": "^1.0.5", - "string-hash-64": "^1.0.3" + "string-hash-64": "^1.0.3", + "convert-source-map": "^1.7.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0", diff --git a/plugin.js b/plugin.js index 5c8d2e438d9..73bbda0122b 100644 --- a/plugin.js +++ b/plugin.js @@ -4,6 +4,7 @@ const hash = require('string-hash-64'); const traverse = require('@babel/traverse').default; const { transformSync } = require('@babel/core'); const fs = require('fs'); +const convertSourceMap = require('convert-source-map'); /** * holds a map of function names as keys and array of argument indexes as values which should be automatically workletized(they have to be functions)(starting from 0) */ @@ -76,6 +77,7 @@ const globals = new Set([ '_removeShadowNodeFromRegistry', 'RegExp', 'Error', + 'ErrorUtils', 'global', '_measure', '_scrollTo', @@ -380,10 +382,12 @@ function buildWorkletString(t, fun, closureVariables, name, inputMap) { } } + const includeSourceMap = shouldGenerateSourceMap(); + const transformed = transformSync(code, { plugins: [prependClosureVariablesIfNecessary()], - compact: !shouldGenerateSourceMap(), - sourceMaps: shouldGenerateSourceMap() ? 'inline' : false, + compact: !includeSourceMap, + sourceMaps: includeSourceMap, inputSourceMap: inputMap, ast: false, babelrc: false, @@ -391,7 +395,17 @@ function buildWorkletString(t, fun, closureVariables, name, inputMap) { comments: false, }); - return transformed.code; + let sourceMap; + if (includeSourceMap) { + sourceMap = convertSourceMap.fromObject(transformed.map).toObject(); + // sourcesContent field contains a full source code of the file which contains the worklet + // and is not needed by the source map interpreter in order to symbolicate a stack trace. + // Therefore, we remove it to reduce the bandwith and avoid sending it potentially multiple times + // in files that contain multiple worklets. Along with sourcesContent. + delete sourceMap.sourcesContent; + } + + return [transformed.code, JSON.stringify(sourceMap)]; } function makeWorkletName(t, fun) { @@ -404,7 +418,7 @@ function makeWorkletName(t, fun) { if (t.isFunctionExpression(fun) && t.isIdentifier(fun.node.id)) { return fun.node.id.name; } - return '_f'; // fallback for ArrowFunctionExpression and unnamed FunctionExpression + return 'anonymous'; // fallback for ArrowFunctionExpression and unnamed FunctionExpression } function makeWorklet(t, fun, state) { @@ -513,7 +527,7 @@ function makeWorklet(t, fun, state) { funExpression = clone; } - const funString = buildWorkletString( + const [funString, sourceMapString] = buildWorkletString( t, transformed.ast, variables, @@ -528,12 +542,14 @@ function makeWorklet(t, fun, state) { location = path.relative(state.cwd, location); } - const loc = fun && fun.node && fun.node.loc && fun.node.loc.start; - if (loc) { - const { line, column } = loc; - if (typeof line === 'number' && typeof column === 'number') { - location = `${location} (${line}:${column})`; - } + let lineOffset = 1; + if (closure.size > 0) { + // When worklet captures some variables, we append closure destructing at + // the beginning of the function body. This effectively results in line + // numbers shifting by the number of captured variables (size of the + // closure) + 2 (for the opening and closing brackets of the destruct + // statement) + lineOffset -= closure.size + 2; } const statements = [ @@ -578,6 +594,50 @@ function makeWorklet(t, fun, state) { ), ]; + if (sourceMapString) { + statements.push( + t.expressionStatement( + t.assignmentExpression( + '=', + t.memberExpression( + privateFunctionId, + t.identifier('__sourceMap'), + false + ), + t.stringLiteral(sourceMapString) + ) + ) + ); + } + + if (!isRelease()) { + statements.unshift( + t.variableDeclaration('const', [ + t.variableDeclarator( + t.identifier('_e'), + t.arrayExpression([ + t.newExpression(t.identifier('Error'), []), + t.numericLiteral(lineOffset), + t.numericLiteral(-20), // the placement of opening bracket after Exception in line that defined '_e' variable + ]) + ), + ]) + ); + statements.push( + t.expressionStatement( + t.assignmentExpression( + '=', + t.memberExpression( + privateFunctionId, + t.identifier('__stackDetails'), + false + ), + t.identifier('_e') + ) + ) + ); + } + if (options && options.optFlags) { statements.push( t.expressionStatement( diff --git a/src/reanimated2/NativeReanimated/NativeReanimated.ts b/src/reanimated2/NativeReanimated/NativeReanimated.ts index 660fa6cc90f..04952384960 100644 --- a/src/reanimated2/NativeReanimated/NativeReanimated.ts +++ b/src/reanimated2/NativeReanimated/NativeReanimated.ts @@ -47,8 +47,17 @@ export class NativeReanimated { throw new Error('stub implementation, used on the web only'); } - installCoreFunctions(valueUnpacker: (value: T) => T): void { - return this.InnerNativeModule.installCoreFunctions(valueUnpacker); + installCoreFunctions( + callGuard: , U>( + fn: (...args: T) => U, + ...args: T + ) => void, + valueUnpacker: (value: T) => T + ): void { + return this.InnerNativeModule.installCoreFunctions( + callGuard, + valueUnpacker + ); } makeShareableClone(value: T): ShareableRef { diff --git a/src/reanimated2/core.ts b/src/reanimated2/core.ts index 8a2ea15a941..f1a19e08cee 100644 --- a/src/reanimated2/core.ts +++ b/src/reanimated2/core.ts @@ -5,13 +5,13 @@ import { makeShareableCloneRecursive, makeShareable as makeShareableUnwrapped, } from './shareables'; -import { runOnUI, runOnJS } from './threads'; import { startMapper as startMapperUnwrapped } from './mappers'; import { makeMutable as makeMutableUnwrapped, makeRemote as makeRemoteUnwrapped, } from './mutables'; import { LayoutAnimationFunction } from './layoutReanimation'; +import { initializeUIRuntime } from './initializers'; export { stopMapper } from './mappers'; export { runOnJS, runOnUI } from './threads'; @@ -106,49 +106,6 @@ export function getViewProp(viewTag: string, propName: string): Promise { }); } -function valueUnpacker(objectToUnpack: any, category?: string): any { - 'worklet'; - let workletsCache = global.__workletsCache; - let handleCache = global.__handleCache; - if (workletsCache === undefined) { - // init - workletsCache = global.__workletsCache = new Map(); - handleCache = global.__handleCache = new WeakMap(); - } - if (objectToUnpack.__workletHash) { - let workletFun = workletsCache.get(objectToUnpack.__workletHash); - if (workletFun === undefined) { - // eslint-disable-next-line no-eval - workletFun = eval('(' + objectToUnpack.asString + '\n)') as ( - ...args: any[] - ) => any; - workletsCache.set(objectToUnpack.__workletHash, workletFun); - } - const functionInstance = workletFun.bind(objectToUnpack); - objectToUnpack._recur = functionInstance; - return functionInstance; - } else if (objectToUnpack.__init) { - let value = handleCache!.get(objectToUnpack); - if (value === undefined) { - value = objectToUnpack.__init(); - handleCache!.set(objectToUnpack, value); - } - return value; - } else if (category === 'RemoteFunction') { - const fun = () => { - throw new Error(`Tried to synchronously call a non-worklet function on the UI thread. - -Possible solutions are: - a) If you want to synchronously execute this method, mark it as a worklet - b) If you want to execute this function on the JS thread, wrap it using \`runOnJS\``); - }; - fun.__remoteFunction = objectToUnpack; - return fun; - } else { - throw new Error('data type not recognized by unpack method'); - } -} - export function registerEventHandler( eventHash: string, eventHandler: (event: T) => void @@ -191,21 +148,9 @@ export function unregisterSensor(listenerId: number): void { return NativeReanimatedModule.unregisterSensor(listenerId); } -NativeReanimatedModule.installCoreFunctions(valueUnpacker); - +// initialize UI runtime if applicable if (!isWeb() && isConfigured()) { - const capturableConsole = console; - runOnUI(() => { - 'worklet'; - // @ts-ignore TypeScript doesn't like that there are missing methods in console object, but we don't provide all the methods for the UI runtime console version - global.console = { - debug: runOnJS(capturableConsole.debug), - log: runOnJS(capturableConsole.log), - warn: runOnJS(capturableConsole.warn), - error: runOnJS(capturableConsole.error), - info: runOnJS(capturableConsole.info), - }; - })(); + initializeUIRuntime(); } type FeaturesConfig = { diff --git a/src/reanimated2/errors.ts b/src/reanimated2/errors.ts new file mode 100644 index 00000000000..287214296e9 --- /dev/null +++ b/src/reanimated2/errors.ts @@ -0,0 +1,57 @@ +type StackDetails = [Error, number, number]; + +const _workletStackDetails = new Map(); + +export function registerWorkletStackDetails( + hash: number, + stackDetails: StackDetails +) { + _workletStackDetails.set(hash, stackDetails); +} + +function getBundleOffset(error: Error): [string, number, number] { + const frame = error.stack?.split('\n')?.[0]; + if (frame) { + const parsedFrame = /@([^@]+):(\d+):(\d+)/.exec(frame); + if (parsedFrame) { + const [, file, line, col] = parsedFrame; + return [file, Number(line), Number(col)]; + } + } + return ['unknown', 0, 0]; +} + +function processStack(stack: string): string { + const workletStackEntries = stack.match(/worklet_(\d+):(\d+):(\d+)/g); + let result = stack; + workletStackEntries?.forEach((match) => { + const [, hash, origLine, origCol] = match.split(/:|_/).map(Number); + const errorDetails = _workletStackDetails.get(hash); + if (!errorDetails) { + return; + } + const [error, lineOffset, colOffset] = errorDetails; + const [bundleFile, bundleLine, bundleCol] = getBundleOffset(error); + const line = origLine + bundleLine + lineOffset; + const col = origCol + bundleCol + colOffset; + + result = result.replace(match, `${bundleFile}:${line}:${col}`); + }); + return result; +} + +export function reportFatalErrorOnJS({ + message, + stack, +}: { + message: string; + stack?: string; +}) { + const error = new Error(); + error.message = message; + error.stack = stack ? processStack(stack) : undefined; + error.name = 'ReanimatedError'; + // @ts-ignore React Native's ErrorUtils implementation extends the Error type with jsEngine field + error.jsEngine = 'reanimated'; + global.ErrorUtils.reportFatalError(error); +} diff --git a/src/reanimated2/globals.d.ts b/src/reanimated2/globals.d.ts index c5764c49903..0af77e01543 100644 --- a/src/reanimated2/globals.d.ts +++ b/src/reanimated2/globals.d.ts @@ -18,6 +18,12 @@ declare global { const _frameTimestamp: number | null; const _eventTimestamp: number; const __reanimatedModuleProxy: NativeReanimated; + const evalWithSourceMap: ( + js: string, + sourceURL: string, + sourceMap: string + ) => any; + const evalWithSourceUrl: (js: string, sourceURL: string) => any; const _log: (s: string) => void; const _getCurrentTime: () => number; const _getTimestamp: () => number; @@ -63,6 +69,9 @@ declare global { const ReanimatedDataMock: { now: () => number; }; + const ErrorUtils: { + reportFatalError: (error: Error) => void; + }; const _frameCallbackRegistry: FrameCallbackRegistryUI; const console: Console; @@ -74,6 +83,12 @@ declare global { _frameTimestamp: number | null; _eventTimestamp: number; __reanimatedModuleProxy: NativeReanimated; + evalWithSourceMap: ( + js: string, + sourceURL: string, + sourceMap: string + ) => any; + evalWithSourceUrl: (js: string, sourceURL: string) => any; _log: (s: string) => void; _getCurrentTime: () => number; _getTimestamp: () => number; @@ -116,6 +131,9 @@ declare global { ReanimatedDataMock: { now: () => number; }; + ErrorUtils: { + reportFatalError: (error: Error) => void; + }; _frameCallbackRegistry: FrameCallbackRegistryUI; __workletsCache?: Map any>; __handleCache?: WeakMap; diff --git a/src/reanimated2/initializers.ts b/src/reanimated2/initializers.ts new file mode 100644 index 00000000000..b7d85487bfe --- /dev/null +++ b/src/reanimated2/initializers.ts @@ -0,0 +1,113 @@ +import { reportFatalErrorOnJS } from './errors'; +import NativeReanimatedModule from './NativeReanimated'; +import { runOnUI, runOnJS } from './threads'; + +// callGuard is only used with debug builds +function callGuardDEV, U>( + fn: (...args: T) => U, + ...args: T +): void { + 'worklet'; + try { + fn(...args); + } catch (e) { + if (global.ErrorUtils) { + global.ErrorUtils.reportFatalError(e as Error); + } else { + throw e; + } + } +} + +function valueUnpacker(objectToUnpack: any, category?: string): any { + 'worklet'; + let workletsCache = global.__workletsCache; + let handleCache = global.__handleCache; + if (workletsCache === undefined) { + // init + workletsCache = global.__workletsCache = new Map(); + handleCache = global.__handleCache = new WeakMap(); + } + if (objectToUnpack.__workletHash) { + let workletFun = workletsCache.get(objectToUnpack.__workletHash); + if (workletFun === undefined) { + if (global.evalWithSourceMap) { + // if the runtime (hermes only for now) supports loading source maps + // we want to use the proper filename for the location as it guarantees + // that debugger understands and loads the source code of the file where + // the worklet is defined. + workletFun = global.evalWithSourceMap( + '(' + objectToUnpack.asString + '\n)', + objectToUnpack.__location, + objectToUnpack.__sourceMap + ) as (...args: any[]) => any; + } else if (global.evalWithSourceUrl) { + // if the runtime doesn't support loading source maps, in dev mode we + // can pass source url when evaluating the worklet. Now, instead of using + // the actual file location we use worklet hash, as it the allows us to + // properly symbolicate traces (see errors.ts for details) + workletFun = global.evalWithSourceUrl( + '(' + objectToUnpack.asString + '\n)', + `worklet_${objectToUnpack.__workletHash}` + ) as (...args: any[]) => any; + } else { + // in release we use the regular eval to save on JSI calls + // eslint-disable-next-line no-eval + workletFun = eval('(' + objectToUnpack.asString + '\n)') as ( + ...args: any[] + ) => any; + } + workletsCache.set(objectToUnpack.__workletHash, workletFun); + } + const functionInstance = workletFun.bind(objectToUnpack); + objectToUnpack._recur = functionInstance; + return functionInstance; + } else if (objectToUnpack.__init) { + let value = handleCache!.get(objectToUnpack); + if (value === undefined) { + value = objectToUnpack.__init(); + handleCache!.set(objectToUnpack, value); + } + return value; + } else if (category === 'RemoteFunction') { + const fun = () => { + throw new Error(`Tried to synchronously call a non-worklet function on the UI thread. + +Possible solutions are: + a) If you want to synchronously execute this method, mark it as a worklet + b) If you want to execute this function on the JS thread, wrap it using \`runOnJS\``); + }; + fun.__remoteFunction = objectToUnpack; + return fun; + } else { + throw new Error('data type not recognized by unpack method'); + } +} + +export function initializeUIRuntime() { + NativeReanimatedModule.installCoreFunctions(callGuardDEV, valueUnpacker); + + const capturableConsole = console; + runOnUI(() => { + 'worklet'; + // setup error handler + global.ErrorUtils = { + reportFatalError: (error: Error) => { + runOnJS(reportFatalErrorOnJS)({ + message: error.message, + stack: error.stack, + }); + }, + }; + + // setup console + // @ts-ignore TypeScript doesn't like that there are missing methods in console object, but we don't provide all the methods for the UI runtime console version + global.console = { + debug: runOnJS(capturableConsole.debug), + log: runOnJS(capturableConsole.log), + warn: runOnJS(capturableConsole.warn), + error: runOnJS(capturableConsole.error), + info: runOnJS(capturableConsole.info), + }; + })(); +} diff --git a/src/reanimated2/js-reanimated/JSReanimated.ts b/src/reanimated2/js-reanimated/JSReanimated.ts index 977dc5a2c0c..04ee23bf217 100644 --- a/src/reanimated2/js-reanimated/JSReanimated.ts +++ b/src/reanimated2/js-reanimated/JSReanimated.ts @@ -3,8 +3,6 @@ import { ShareableRef } from '../commonTypes'; import { isJest } from '../PlatformChecker'; export default class JSReanimated extends NativeReanimated { - _valueUnpacker?: (value: T) => void = undefined; - constructor() { super(false); if (isJest()) { @@ -21,8 +19,14 @@ export default class JSReanimated extends NativeReanimated { return { __hostObjectShareableJSRef: value }; } - installCoreFunctions(valueUnpacker: (value: T) => T): void { - this._valueUnpacker = valueUnpacker; + installCoreFunctions( + _callGuard: , U>( + fn: (...args: T) => U, + ...args: T + ) => void, + _valueUnpacker: (value: T) => T + ): void { + // noop } scheduleOnUI(worklet: ShareableRef) { diff --git a/src/reanimated2/shareables.ts b/src/reanimated2/shareables.ts index 84aedc65643..dc7e77c309b 100644 --- a/src/reanimated2/shareables.ts +++ b/src/reanimated2/shareables.ts @@ -1,6 +1,7 @@ import NativeReanimatedModule from './NativeReanimated'; import { ShareableRef } from './commonTypes'; import { shouldBeUseWeb } from './PlatformChecker'; +import { registerWorkletStackDetails } from './errors'; // for web/chrome debugger/jest environments this file provides a stub implementation // where no shareable references are used. Instead, the objects themselves are used @@ -53,6 +54,12 @@ export function makeShareableCloneRecursive(value: any): ShareableRef { // this is a remote function toAdapt = value; } else { + if (__DEV__ && value.__workletHash !== undefined) { + registerWorkletStackDetails( + value.__workletHash, + value.__stackDetails + ); + } toAdapt = {}; for (const [key, element] of Object.entries(value)) { toAdapt[key] = makeShareableCloneRecursive(element);