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