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