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