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