1 // Copyright © 2021-present 650 Industries, Inc. (aka Expo)
2 
3 #include "JavaScriptRuntime.h"
4 #include "JavaScriptValue.h"
5 #include "JavaScriptObject.h"
6 #include "Exceptions.h"
7 #include "JSIInteropModuleRegistry.h"
8 #include "JSIUtils.h"
9 
10 #if UNIT_TEST
11 
12 #if USE_HERMES
13 
14 #include <hermes/hermes.h>
15 
16 #include <utility>
17 
18 #else
19 
20 #if REACT_NATIVE_TARGET_VERSION >= 71
21 #include <jsc/JSCRuntime.h>
22 #else
23 #include <jsi/JSCRuntime.h>
24 #endif // REACT_NATIVE_TARGET_VERSION >= 71
25 
26 #endif
27 
28 #endif // UNIT_TEST
29 
30 namespace jsi = facebook::jsi;
31 
32 namespace expo {
33 
34 namespace {
35 
36 /**
37  * Dummy CallInvoker that invokes everything immediately.
38  * Used in the test environment to check the async flow.
39  */
40 class SyncCallInvoker : public react::CallInvoker {
41 public:
42   void invokeAsync(std::function<void()> &&func) override {
43     func();
44   }
45 
46   void invokeSync(std::function<void()> &&func) override {
47     func();
48   }
49 
50   ~SyncCallInvoker() override = default;
51 };
52 
53 #if REACT_NATIVE_TARGET_VERSION >= 73
54 class SyncNativeMethodCallInvoker : public react::NativeMethodCallInvoker {
55 public:
56   void invokeAsync(const std::string &methodName, std::function<void()> &&func) override {
57     func();
58   }
59 
60   void invokeSync(const std::string &methodName, std::function<void()> &&func) override {
61     func();
62   }
63 
64   ~SyncNativeMethodCallInvoker() override = default;
65 };
66 #else
67 using SyncNativeMethodCallInvoker = SyncCallInvoker;
68 #endif // REACT_NATIVE_TARGET_VERSION >= 73
69 
70 } // namespace
71 
72 JavaScriptRuntime::JavaScriptRuntime(
73   JSIInteropModuleRegistry *jsiInteropModuleRegistry
74 )
75   : jsInvoker(std::make_shared<SyncCallInvoker>()),
76     nativeInvoker(std::make_shared<SyncNativeMethodCallInvoker>()),
77     jsiInteropModuleRegistry(jsiInteropModuleRegistry) {
78 #if !UNIT_TEST
79   throw std::logic_error(
80     "The JavaScriptRuntime constructor is only avaiable when UNIT_TEST is defined.");
81 #else
82 #if USE_HERMES
83   auto config = ::hermes::vm::RuntimeConfig::Builder()
84     .withEnableSampleProfiling(false);
85   runtime = facebook::hermes::makeHermesRuntime(config.build());
86 
87   // This version of the Hermes uses a Promise implementation that is provided by the RN.
88   // The `setImmediate` function isn't defined, but is required by the Promise implementation.
89   // That's why we inject it here.
90   auto setImmediatePropName = jsi::PropNameID::forUtf8(*runtime, "setImmediate");
91   runtime->global().setProperty(
92     *runtime,
93     setImmediatePropName,
94     jsi::Function::createFromHostFunction(
95       *runtime,
96       setImmediatePropName,
97       1,
98       [](jsi::Runtime &rt,
99          const jsi::Value &thisVal,
100          const jsi::Value *args,
101          size_t count) {
102         args[0].asObject(rt).asFunction(rt).call(rt);
103         return jsi::Value::undefined();
104       }
105     )
106   );
107 #else
108   runtime = facebook::jsc::makeJSCRuntime();
109 #endif
110 
111   // By default "global" property isn't set.
112   runtime->global().setProperty(
113     *runtime,
114     jsi::PropNameID::forUtf8(*runtime, "global"),
115     runtime->global()
116   );
117 
118   // Mock the CodedError that in a typical scenario will be defined by the `expo-modules-core`.
119   // Note: we can't use `class` syntax here, because Hermes doesn't support it.
120   runtime->evaluateJavaScript(
121     std::make_shared<jsi::StringBuffer>(
122       "function CodedError(code, message) {\n"
123       "    this.code = code;\n"
124       "    this.message = message;\n"
125       "    this.stack = (new Error).stack;\n"
126       "}\n"
127       "CodedError.prototype = new Error;\n"
128       "global.ExpoModulesCore_CodedError = CodedError"
129     ),
130     "<<evaluated>>"
131   );
132 #endif // !UNIT_TEST
133 }
134 
135 JavaScriptRuntime::JavaScriptRuntime(
136   JSIInteropModuleRegistry *jsiInteropModuleRegistry,
137   jsi::Runtime *runtime,
138   std::shared_ptr<react::CallInvoker> jsInvoker,
139   std::shared_ptr<NativeMethodCallInvokerCompatible> nativeInvoker
140 ) : jsInvoker(std::move(jsInvoker)), nativeInvoker(std::move(nativeInvoker)),
141     jsiInteropModuleRegistry(jsiInteropModuleRegistry) {
142   // Creating a shared pointer that points to the runtime but doesn't own it, thus doesn't release it.
143   // In this code flow, the runtime should be owned by something else like the CatalystInstance.
144   // See explanation for constructor (8): https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr
145   this->runtime = std::shared_ptr<jsi::Runtime>(std::shared_ptr<jsi::Runtime>(), runtime);
146 }
147 
148 jsi::Runtime &JavaScriptRuntime::get() const {
149   return *runtime;
150 }
151 
152 jni::local_ref<JavaScriptValue::javaobject>
153 JavaScriptRuntime::evaluateScript(const std::string &script) {
154   auto scriptBuffer = std::make_shared<jsi::StringBuffer>(script);
155   try {
156     return JavaScriptValue::newInstance(
157       jsiInteropModuleRegistry,
158       weak_from_this(),
159       std::make_shared<jsi::Value>(runtime->evaluateJavaScript(scriptBuffer, "<<evaluated>>"))
160     );
161   } catch (const jsi::JSError &error) {
162     jni::throwNewJavaException(
163       JavaScriptEvaluateException::create(
164         error.getMessage(),
165         error.getStack()
166       ).get()
167     );
168   } catch (const jsi::JSIException &error) {
169     jni::throwNewJavaException(
170       JavaScriptEvaluateException::create(
171         error.what(),
172         ""
173       ).get()
174     );
175   }
176 }
177 
178 jni::local_ref<JavaScriptObject::javaobject> JavaScriptRuntime::global() {
179   auto global = std::make_shared<jsi::Object>(runtime->global());
180   return JavaScriptObject::newInstance(jsiInteropModuleRegistry, weak_from_this(), global);
181 }
182 
183 jni::local_ref<JavaScriptObject::javaobject> JavaScriptRuntime::createObject() {
184   auto newObject = std::make_shared<jsi::Object>(*runtime);
185   return JavaScriptObject::newInstance(jsiInteropModuleRegistry, weak_from_this(), newObject);
186 }
187 
188 void JavaScriptRuntime::drainJSEventLoop() {
189   while (!runtime->drainMicrotasks()) {}
190 }
191 
192 void JavaScriptRuntime::installMainObject() {
193   auto coreModule = jsiInteropModuleRegistry->getCoreModule();
194   coreModule->cthis()->jsiInteropModuleRegistry = jsiInteropModuleRegistry;
195   mainObject = coreModule->cthis()->getJSIObject(*runtime);
196 
197   auto global = runtime->global();
198 
199   jsi::Object descriptor = JavaScriptObject::preparePropertyDescriptor(*runtime, 1 << 1);
200 
201   descriptor.setProperty(*runtime, "value", jsi::Value(*runtime, *mainObject));
202 
203   common::definePropertyOnJSIObject(
204     *runtime,
205     &global,
206     "expo",
207     std::move(descriptor)
208   );
209 }
210 
211 std::shared_ptr<jsi::Object> JavaScriptRuntime::getMainObject() {
212   return mainObject;
213 }
214 
215 JSIInteropModuleRegistry *JavaScriptRuntime::getModuleRegistry() {
216   return jsiInteropModuleRegistry;
217 }
218 } // namespace expo
219