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