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