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 #endif // !UNIT_TEST 133 } 134 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 148 jsi::Runtime &JavaScriptRuntime::get() const { 149 return *runtime; 150 } 151 152 jni::local_ref<JavaScriptValue::javaobject> 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 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 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 188 void JavaScriptRuntime::drainJSEventLoop() { 189 while (!runtime->drainMicrotasks()) {} 190 } 191 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 211 std::shared_ptr<jsi::Object> JavaScriptRuntime::getMainObject() { 212 return mainObject; 213 } 214 215 JSIInteropModuleRegistry *JavaScriptRuntime::getModuleRegistry() { 216 return jsiInteropModuleRegistry; 217 } 218 } // namespace expo 219