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