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