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