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   // By default "global" property isn't set in the Hermes.
41   runtime->global().setProperty(
42     *runtime,
43     jsi::PropNameID::forUtf8(*runtime, "global"),
44     runtime->global()
45   );
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 
72 JavaScriptRuntime::JavaScriptRuntime(
73   jsi::Runtime *runtime,
74   std::shared_ptr<react::CallInvoker> jsInvoker,
75   std::shared_ptr<react::CallInvoker> nativeInvoker
76 ) : jsInvoker(std::move(jsInvoker)), nativeInvoker(std::move(nativeInvoker)) {
77   // Creating a shared pointer that points to the runtime but doesn't own it, thus doesn't release it.
78   // In this code flow, the runtime should be owned by something else like the CatalystInstance.
79   // See explanation for constructor (8): https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr
80   this->runtime = std::shared_ptr<jsi::Runtime>(std::shared_ptr<jsi::Runtime>(), runtime);
81 }
82 
83 jsi::Runtime *JavaScriptRuntime::get() {
84   return runtime.get();
85 }
86 
87 jni::local_ref<JavaScriptValue::javaobject>
88 JavaScriptRuntime::evaluateScript(const std::string &script) {
89   auto scriptBuffer = std::make_shared<jsi::StringBuffer>(script);
90   std::shared_ptr<jsi::Value> result;
91   try {
92     result = std::make_shared<jsi::Value>(
93       runtime->evaluateJavaScript(scriptBuffer, "<<evaluated>>")
94     );
95   } catch (const jsi::JSError &error) {
96     jni::throwNewJavaException(
97       JavaScriptEvaluateException::create(
98         error.getMessage(),
99         error.getStack()
100       ).get()
101     );
102   } catch (const jsi::JSIException &error) {
103     jni::throwNewJavaException(
104       JavaScriptEvaluateException::create(
105         error.what(),
106         ""
107       ).get()
108     );
109   }
110 
111   return JavaScriptValue::newObjectCxxArgs(weak_from_this(), result);
112 }
113 
114 jni::local_ref<JavaScriptObject::javaobject> JavaScriptRuntime::global() {
115   auto global = std::make_shared<jsi::Object>(runtime->global());
116   return JavaScriptObject::newObjectCxxArgs(weak_from_this(), global);
117 }
118 
119 jni::local_ref<JavaScriptObject::javaobject> JavaScriptRuntime::createObject() {
120   auto newObject = std::make_shared<jsi::Object>(*runtime);
121   return JavaScriptObject::newObjectCxxArgs(weak_from_this(), newObject);
122 }
123 
124 void JavaScriptRuntime::drainJSEventLoop() {
125   while (!runtime->drainMicrotasks()) {}
126 }
127 } // namespace expo
128