164f5c95fSŁukasz Kosmaty // Copyright © 2021-present 650 Industries, Inc. (aka Expo)
264f5c95fSŁukasz Kosmaty 
364f5c95fSŁukasz Kosmaty #include "JavaScriptRuntime.h"
414c0f05dSŁukasz Kosmaty #include "JavaScriptValue.h"
514c0f05dSŁukasz Kosmaty #include "JavaScriptObject.h"
6a416e6dbSŁukasz Kosmaty #include "Exceptions.h"
729e8b6f8SŁukasz Kosmaty #include "JSIInteropModuleRegistry.h"
8e1f25825SŁukasz Kosmaty #include "JSIUtils.h"
9a416e6dbSŁukasz Kosmaty 
1057cd185aSKudo Chien #if UNIT_TEST
1157cd185aSKudo Chien 
1257cd185aSKudo Chien #if USE_HERMES
13a416e6dbSŁukasz Kosmaty 
14a416e6dbSŁukasz Kosmaty #include <hermes/hermes.h>
15a416e6dbSŁukasz Kosmaty 
16a416e6dbSŁukasz Kosmaty #include <utility>
17a416e6dbSŁukasz Kosmaty 
18a416e6dbSŁukasz Kosmaty #else
19a416e6dbSŁukasz Kosmaty 
2084f418d7SKudo Chien #if REACT_NATIVE_TARGET_VERSION >= 71
2184f418d7SKudo Chien #include <jsc/JSCRuntime.h>
2284f418d7SKudo Chien #else
23a416e6dbSŁukasz Kosmaty #include <jsi/JSCRuntime.h>
2484f418d7SKudo Chien #endif // REACT_NATIVE_TARGET_VERSION >= 71
25a416e6dbSŁukasz Kosmaty 
26a416e6dbSŁukasz Kosmaty #endif
2764f5c95fSŁukasz Kosmaty 
2857cd185aSKudo Chien #endif // UNIT_TEST
2957cd185aSKudo Chien 
30a89667b6SŁukasz Kosmaty namespace jsi = facebook::jsi;
31a89667b6SŁukasz Kosmaty 
3264f5c95fSŁukasz Kosmaty namespace expo {
3364f5c95fSŁukasz Kosmaty 
34*db6683c6SKudo Chien namespace {
35*db6683c6SKudo Chien 
36*db6683c6SKudo Chien /**
37*db6683c6SKudo Chien  * Dummy CallInvoker that invokes everything immediately.
38*db6683c6SKudo Chien  * Used in the test environment to check the async flow.
39*db6683c6SKudo Chien  */
40*db6683c6SKudo Chien class SyncCallInvoker : public react::CallInvoker {
41*db6683c6SKudo Chien public:
invokeAsync(std::function<void ()> && func)42*db6683c6SKudo Chien   void invokeAsync(std::function<void()> &&func) override {
43e3d1d66aSŁukasz Kosmaty     func();
44e3d1d66aSŁukasz Kosmaty   }
4564f5c95fSŁukasz Kosmaty 
invokeSync(std::function<void ()> && func)46*db6683c6SKudo Chien   void invokeSync(std::function<void()> &&func) override {
47e3d1d66aSŁukasz Kosmaty     func();
48e3d1d66aSŁukasz Kosmaty   }
49e3d1d66aSŁukasz Kosmaty 
50*db6683c6SKudo Chien   ~SyncCallInvoker() override = default;
51*db6683c6SKudo Chien };
52*db6683c6SKudo Chien 
53*db6683c6SKudo Chien #if REACT_NATIVE_TARGET_VERSION >= 73
54*db6683c6SKudo Chien class SyncNativeMethodCallInvoker : public react::NativeMethodCallInvoker {
55*db6683c6SKudo Chien public:
invokeAsync(const std::string & methodName,std::function<void ()> && func)56*db6683c6SKudo Chien   void invokeAsync(const std::string &methodName, std::function<void()> &&func) override {
57*db6683c6SKudo Chien     func();
58*db6683c6SKudo Chien   }
59*db6683c6SKudo Chien 
invokeSync(const std::string & methodName,std::function<void ()> && func)60*db6683c6SKudo Chien   void invokeSync(const std::string &methodName, std::function<void()> &&func) override {
61*db6683c6SKudo Chien     func();
62*db6683c6SKudo Chien   }
63*db6683c6SKudo Chien 
64*db6683c6SKudo Chien   ~SyncNativeMethodCallInvoker() override = default;
65*db6683c6SKudo Chien };
66*db6683c6SKudo Chien #else
67*db6683c6SKudo Chien using SyncNativeMethodCallInvoker = SyncCallInvoker;
68*db6683c6SKudo Chien #endif // REACT_NATIVE_TARGET_VERSION >= 73
69*db6683c6SKudo Chien 
70*db6683c6SKudo Chien } // namespace
71*db6683c6SKudo Chien 
JavaScriptRuntime(JSIInteropModuleRegistry * jsiInteropModuleRegistry)72879827bbSŁukasz Kosmaty JavaScriptRuntime::JavaScriptRuntime(
73879827bbSŁukasz Kosmaty   JSIInteropModuleRegistry *jsiInteropModuleRegistry
74879827bbSŁukasz Kosmaty )
75e3d1d66aSŁukasz Kosmaty   : jsInvoker(std::make_shared<SyncCallInvoker>()),
76*db6683c6SKudo Chien     nativeInvoker(std::make_shared<SyncNativeMethodCallInvoker>()),
77879827bbSŁukasz Kosmaty     jsiInteropModuleRegistry(jsiInteropModuleRegistry) {
7857cd185aSKudo Chien #if !UNIT_TEST
79dedc0ffdSŁukasz Kosmaty   throw std::logic_error(
80dedc0ffdSŁukasz Kosmaty     "The JavaScriptRuntime constructor is only avaiable when UNIT_TEST is defined.");
8157cd185aSKudo Chien #else
8257cd185aSKudo Chien #if USE_HERMES
83e3d1d66aSŁukasz Kosmaty   auto config = ::hermes::vm::RuntimeConfig::Builder()
84e3d1d66aSŁukasz Kosmaty     .withEnableSampleProfiling(false);
85a416e6dbSŁukasz Kosmaty   runtime = facebook::hermes::makeHermesRuntime(config.build());
86e3d1d66aSŁukasz Kosmaty 
87e3d1d66aSŁukasz Kosmaty   // This version of the Hermes uses a Promise implementation that is provided by the RN.
88e3d1d66aSŁukasz Kosmaty   // The `setImmediate` function isn't defined, but is required by the Promise implementation.
89e3d1d66aSŁukasz Kosmaty   // That's why we inject it here.
90e3d1d66aSŁukasz Kosmaty   auto setImmediatePropName = jsi::PropNameID::forUtf8(*runtime, "setImmediate");
91e3d1d66aSŁukasz Kosmaty   runtime->global().setProperty(
92e3d1d66aSŁukasz Kosmaty     *runtime,
93e3d1d66aSŁukasz Kosmaty     setImmediatePropName,
94e3d1d66aSŁukasz Kosmaty     jsi::Function::createFromHostFunction(
95e3d1d66aSŁukasz Kosmaty       *runtime,
96e3d1d66aSŁukasz Kosmaty       setImmediatePropName,
97e3d1d66aSŁukasz Kosmaty       1,
98e3d1d66aSŁukasz Kosmaty       [](jsi::Runtime &rt,
99e3d1d66aSŁukasz Kosmaty          const jsi::Value &thisVal,
100e3d1d66aSŁukasz Kosmaty          const jsi::Value *args,
101e3d1d66aSŁukasz Kosmaty          size_t count) {
102e3d1d66aSŁukasz Kosmaty         args[0].asObject(rt).asFunction(rt).call(rt);
103e3d1d66aSŁukasz Kosmaty         return jsi::Value::undefined();
104e3d1d66aSŁukasz Kosmaty       }
105e3d1d66aSŁukasz Kosmaty     )
106e3d1d66aSŁukasz Kosmaty   );
107a416e6dbSŁukasz Kosmaty #else
108a416e6dbSŁukasz Kosmaty   runtime = facebook::jsc::makeJSCRuntime();
109a416e6dbSŁukasz Kosmaty #endif
11033581d18SŁukasz Kosmaty 
11133581d18SŁukasz Kosmaty   // By default "global" property isn't set.
11233581d18SŁukasz Kosmaty   runtime->global().setProperty(
11333581d18SŁukasz Kosmaty     *runtime,
11433581d18SŁukasz Kosmaty     jsi::PropNameID::forUtf8(*runtime, "global"),
11533581d18SŁukasz Kosmaty     runtime->global()
11633581d18SŁukasz Kosmaty   );
1178bd57a9aSŁukasz Kosmaty 
1188bd57a9aSŁukasz Kosmaty   // Mock the CodedError that in a typical scenario will be defined by the `expo-modules-core`.
1198bd57a9aSŁukasz Kosmaty   // Note: we can't use `class` syntax here, because Hermes doesn't support it.
1208bd57a9aSŁukasz Kosmaty   runtime->evaluateJavaScript(
1218bd57a9aSŁukasz Kosmaty     std::make_shared<jsi::StringBuffer>(
1228bd57a9aSŁukasz Kosmaty       "function CodedError(code, message) {\n"
1238bd57a9aSŁukasz Kosmaty       "    this.code = code;\n"
1248bd57a9aSŁukasz Kosmaty       "    this.message = message;\n"
1258bd57a9aSŁukasz Kosmaty       "    this.stack = (new Error).stack;\n"
1268bd57a9aSŁukasz Kosmaty       "}\n"
1278bd57a9aSŁukasz Kosmaty       "CodedError.prototype = new Error;\n"
1288bd57a9aSŁukasz Kosmaty       "global.ExpoModulesCore_CodedError = CodedError"
1298bd57a9aSŁukasz Kosmaty     ),
1308bd57a9aSŁukasz Kosmaty     "<<evaluated>>"
1318bd57a9aSŁukasz Kosmaty   );
13257cd185aSKudo Chien #endif // !UNIT_TEST
133a416e6dbSŁukasz Kosmaty }
134a416e6dbSŁukasz Kosmaty 
JavaScriptRuntime(JSIInteropModuleRegistry * jsiInteropModuleRegistry,jsi::Runtime * runtime,std::shared_ptr<react::CallInvoker> jsInvoker,std::shared_ptr<NativeMethodCallInvokerCompatible> nativeInvoker)135a416e6dbSŁukasz Kosmaty JavaScriptRuntime::JavaScriptRuntime(
136879827bbSŁukasz Kosmaty   JSIInteropModuleRegistry *jsiInteropModuleRegistry,
137a416e6dbSŁukasz Kosmaty   jsi::Runtime *runtime,
138a416e6dbSŁukasz Kosmaty   std::shared_ptr<react::CallInvoker> jsInvoker,
139*db6683c6SKudo Chien   std::shared_ptr<NativeMethodCallInvokerCompatible> nativeInvoker
140879827bbSŁukasz Kosmaty ) : jsInvoker(std::move(jsInvoker)), nativeInvoker(std::move(nativeInvoker)),
141879827bbSŁukasz Kosmaty     jsiInteropModuleRegistry(jsiInteropModuleRegistry) {
142a416e6dbSŁukasz Kosmaty   // Creating a shared pointer that points to the runtime but doesn't own it, thus doesn't release it.
143a416e6dbSŁukasz Kosmaty   // In this code flow, the runtime should be owned by something else like the CatalystInstance.
144a416e6dbSŁukasz Kosmaty   // See explanation for constructor (8): https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr
145a416e6dbSŁukasz Kosmaty   this->runtime = std::shared_ptr<jsi::Runtime>(std::shared_ptr<jsi::Runtime>(), runtime);
146a416e6dbSŁukasz Kosmaty }
14764f5c95fSŁukasz Kosmaty 
get() const148256b5942SŁukasz Kosmaty jsi::Runtime &JavaScriptRuntime::get() const {
149256b5942SŁukasz Kosmaty   return *runtime;
150a416e6dbSŁukasz Kosmaty }
151a416e6dbSŁukasz Kosmaty 
152a416e6dbSŁukasz Kosmaty jni::local_ref<JavaScriptValue::javaobject>
evaluateScript(const std::string & script)153a416e6dbSŁukasz Kosmaty JavaScriptRuntime::evaluateScript(const std::string &script) {
154a416e6dbSŁukasz Kosmaty   auto scriptBuffer = std::make_shared<jsi::StringBuffer>(script);
155a416e6dbSŁukasz Kosmaty   try {
15629e8b6f8SŁukasz Kosmaty     return JavaScriptValue::newInstance(
15729e8b6f8SŁukasz Kosmaty       jsiInteropModuleRegistry,
158c27399f5SŁukasz Kosmaty       weak_from_this(),
159c27399f5SŁukasz Kosmaty       std::make_shared<jsi::Value>(runtime->evaluateJavaScript(scriptBuffer, "<<evaluated>>"))
160a416e6dbSŁukasz Kosmaty     );
161a416e6dbSŁukasz Kosmaty   } catch (const jsi::JSError &error) {
162a416e6dbSŁukasz Kosmaty     jni::throwNewJavaException(
163a416e6dbSŁukasz Kosmaty       JavaScriptEvaluateException::create(
164a416e6dbSŁukasz Kosmaty         error.getMessage(),
165a416e6dbSŁukasz Kosmaty         error.getStack()
166a416e6dbSŁukasz Kosmaty       ).get()
167a416e6dbSŁukasz Kosmaty     );
168a416e6dbSŁukasz Kosmaty   } catch (const jsi::JSIException &error) {
169a416e6dbSŁukasz Kosmaty     jni::throwNewJavaException(
170a416e6dbSŁukasz Kosmaty       JavaScriptEvaluateException::create(
171a416e6dbSŁukasz Kosmaty         error.what(),
172a416e6dbSŁukasz Kosmaty         ""
173a416e6dbSŁukasz Kosmaty       ).get()
174a416e6dbSŁukasz Kosmaty     );
175a416e6dbSŁukasz Kosmaty   }
176a416e6dbSŁukasz Kosmaty }
177a416e6dbSŁukasz Kosmaty 
global()178a416e6dbSŁukasz Kosmaty jni::local_ref<JavaScriptObject::javaobject> JavaScriptRuntime::global() {
179a416e6dbSŁukasz Kosmaty   auto global = std::make_shared<jsi::Object>(runtime->global());
18029e8b6f8SŁukasz Kosmaty   return JavaScriptObject::newInstance(jsiInteropModuleRegistry, weak_from_this(), global);
18164f5c95fSŁukasz Kosmaty }
182342f32d0SŁukasz Kosmaty 
createObject()183342f32d0SŁukasz Kosmaty jni::local_ref<JavaScriptObject::javaobject> JavaScriptRuntime::createObject() {
184342f32d0SŁukasz Kosmaty   auto newObject = std::make_shared<jsi::Object>(*runtime);
18529e8b6f8SŁukasz Kosmaty   return JavaScriptObject::newInstance(jsiInteropModuleRegistry, weak_from_this(), newObject);
186342f32d0SŁukasz Kosmaty }
187a89667b6SŁukasz Kosmaty 
drainJSEventLoop()188a89667b6SŁukasz Kosmaty void JavaScriptRuntime::drainJSEventLoop() {
189a89667b6SŁukasz Kosmaty   while (!runtime->drainMicrotasks()) {}
190a89667b6SŁukasz Kosmaty }
191dedc0ffdSŁukasz Kosmaty 
installMainObject()192dedc0ffdSŁukasz Kosmaty void JavaScriptRuntime::installMainObject() {
193b702c83dSŁukasz Kosmaty   auto coreModule = jsiInteropModuleRegistry->getCoreModule();
194b702c83dSŁukasz Kosmaty   coreModule->cthis()->jsiInteropModuleRegistry = jsiInteropModuleRegistry;
195b702c83dSŁukasz Kosmaty   mainObject = coreModule->cthis()->getJSIObject(*runtime);
196b702c83dSŁukasz Kosmaty 
197dedc0ffdSŁukasz Kosmaty   auto global = runtime->global();
198dedc0ffdSŁukasz Kosmaty 
199dedc0ffdSŁukasz Kosmaty   jsi::Object descriptor = JavaScriptObject::preparePropertyDescriptor(*runtime, 1 << 1);
200dedc0ffdSŁukasz Kosmaty 
201dedc0ffdSŁukasz Kosmaty   descriptor.setProperty(*runtime, "value", jsi::Value(*runtime, *mainObject));
202dedc0ffdSŁukasz Kosmaty 
203e1f25825SŁukasz Kosmaty   common::definePropertyOnJSIObject(
204e1f25825SŁukasz Kosmaty     *runtime,
205e1f25825SŁukasz Kosmaty     &global,
206e1f25825SŁukasz Kosmaty     "expo",
207dedc0ffdSŁukasz Kosmaty     std::move(descriptor)
208e1f25825SŁukasz Kosmaty   );
209dedc0ffdSŁukasz Kosmaty }
210dedc0ffdSŁukasz Kosmaty 
getMainObject()211dedc0ffdSŁukasz Kosmaty std::shared_ptr<jsi::Object> JavaScriptRuntime::getMainObject() {
212dedc0ffdSŁukasz Kosmaty   return mainObject;
213dedc0ffdSŁukasz Kosmaty }
214dedc0ffdSŁukasz Kosmaty 
getModuleRegistry()215879827bbSŁukasz Kosmaty JSIInteropModuleRegistry *JavaScriptRuntime::getModuleRegistry() {
216879827bbSŁukasz Kosmaty   return jsiInteropModuleRegistry;
217879827bbSŁukasz Kosmaty }
21864f5c95fSŁukasz Kosmaty } // namespace expo
219