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