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: 42 void invokeAsync(std::function<void()> &&func) override { 43 func(); 44 } 45 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: 56 void invokeAsync(const std::string &methodName, std::function<void()> &&func) override { 57 func(); 58 } 59 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 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 133 installMainObject(); 134 #endif // !UNIT_TEST 135 } 136 137 JavaScriptRuntime::JavaScriptRuntime( 138 JSIInteropModuleRegistry *jsiInteropModuleRegistry, 139 jsi::Runtime *runtime, 140 std::shared_ptr<react::CallInvoker> jsInvoker, 141 std::shared_ptr<NativeMethodCallInvokerCompatible> nativeInvoker 142 ) : jsInvoker(std::move(jsInvoker)), nativeInvoker(std::move(nativeInvoker)), 143 jsiInteropModuleRegistry(jsiInteropModuleRegistry) { 144 // Creating a shared pointer that points to the runtime but doesn't own it, thus doesn't release it. 145 // In this code flow, the runtime should be owned by something else like the CatalystInstance. 146 // See explanation for constructor (8): https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr 147 this->runtime = std::shared_ptr<jsi::Runtime>(std::shared_ptr<jsi::Runtime>(), runtime); 148 installMainObject(); 149 } 150 151 jsi::Runtime &JavaScriptRuntime::get() const { 152 return *runtime; 153 } 154 155 jni::local_ref<JavaScriptValue::javaobject> 156 JavaScriptRuntime::evaluateScript(const std::string &script) { 157 auto scriptBuffer = std::make_shared<jsi::StringBuffer>(script); 158 try { 159 return JavaScriptValue::newInstance( 160 jsiInteropModuleRegistry, 161 weak_from_this(), 162 std::make_shared<jsi::Value>(runtime->evaluateJavaScript(scriptBuffer, "<<evaluated>>")) 163 ); 164 } catch (const jsi::JSError &error) { 165 jni::throwNewJavaException( 166 JavaScriptEvaluateException::create( 167 error.getMessage(), 168 error.getStack() 169 ).get() 170 ); 171 } catch (const jsi::JSIException &error) { 172 jni::throwNewJavaException( 173 JavaScriptEvaluateException::create( 174 error.what(), 175 "" 176 ).get() 177 ); 178 } 179 } 180 181 jni::local_ref<JavaScriptObject::javaobject> JavaScriptRuntime::global() { 182 auto global = std::make_shared<jsi::Object>(runtime->global()); 183 return JavaScriptObject::newInstance(jsiInteropModuleRegistry, weak_from_this(), global); 184 } 185 186 jni::local_ref<JavaScriptObject::javaobject> JavaScriptRuntime::createObject() { 187 auto newObject = std::make_shared<jsi::Object>(*runtime); 188 return JavaScriptObject::newInstance(jsiInteropModuleRegistry, weak_from_this(), newObject); 189 } 190 191 void JavaScriptRuntime::drainJSEventLoop() { 192 while (!runtime->drainMicrotasks()) {} 193 } 194 195 void JavaScriptRuntime::installMainObject() { 196 auto coreModule = jsiInteropModuleRegistry->getCoreModule(); 197 coreModule->cthis()->jsiInteropModuleRegistry = jsiInteropModuleRegistry; 198 mainObject = coreModule->cthis()->getJSIObject(*runtime); 199 200 auto global = runtime->global(); 201 202 jsi::Object descriptor = JavaScriptObject::preparePropertyDescriptor(*runtime, 1 << 1); 203 204 descriptor.setProperty(*runtime, "value", jsi::Value(*runtime, *mainObject)); 205 206 common::definePropertyOnJSIObject( 207 *runtime, 208 &global, 209 "expo", 210 std::move(descriptor) 211 ); 212 } 213 214 std::shared_ptr<jsi::Object> JavaScriptRuntime::getMainObject() { 215 return mainObject; 216 } 217 218 JSIInteropModuleRegistry *JavaScriptRuntime::getModuleRegistry() { 219 return jsiInteropModuleRegistry; 220 } 221 } // namespace expo 222