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