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