1 #include "MethodMetadata.h" 2 #include "JSIInteropModuleRegistry.h" 3 #include "JavaScriptValue.h" 4 #include "JavaScriptObject.h" 5 #include "CachedReferencesRegistry.h" 6 7 #include <utility> 8 9 #include "react/jni/ReadableNativeMap.h" 10 #include "react/jni/ReadableNativeArray.h" 11 12 namespace jni = facebook::jni; 13 namespace jsi = facebook::jsi; 14 namespace react = facebook::react; 15 16 namespace expo { 17 18 // Modified version of the RN implementation 19 // https://github.com/facebook/react-native/blob/7dceb9b63c0bfd5b13bf6d26f9530729506e9097/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp#L57 20 jni::local_ref<react::JCxxCallbackImpl::JavaPart> createJavaCallbackFromJSIFunction( 21 jsi::Function &&function, 22 jsi::Runtime &rt, 23 std::shared_ptr<react::CallInvoker> jsInvoker 24 ) { 25 auto weakWrapper = react::CallbackWrapper::createWeak(std::move(function), rt, 26 std::move(jsInvoker)); 27 28 // This needs to be a shared_ptr because: 29 // 1. It cannot be unique_ptr. std::function is copyable but unique_ptr is 30 // not. 31 // 2. It cannot be weak_ptr since we need this object to live on. 32 // 3. It cannot be a value, because that would be deleted as soon as this 33 // function returns. 34 auto callbackWrapperOwner = 35 std::make_shared<react::RAIICallbackWrapperDestroyer>(weakWrapper); 36 37 std::function<void(folly::dynamic)> fn = 38 [weakWrapper, callbackWrapperOwner, wrapperWasCalled = false]( 39 folly::dynamic responses) mutable { 40 if (wrapperWasCalled) { 41 throw std::runtime_error( 42 "callback 2 arg cannot be called more than once"); 43 } 44 45 auto strongWrapper = weakWrapper.lock(); 46 if (!strongWrapper) { 47 return; 48 } 49 50 strongWrapper->jsInvoker().invokeAsync( 51 [weakWrapper, callbackWrapperOwner, responses]() mutable { 52 auto strongWrapper2 = weakWrapper.lock(); 53 if (!strongWrapper2) { 54 return; 55 } 56 57 jsi::Value args = 58 jsi::valueFromDynamic(strongWrapper2->runtime(), responses); 59 auto argsArray = args.getObject(strongWrapper2->runtime()) 60 .asArray(strongWrapper2->runtime()); 61 jsi::Value arg = argsArray.getValueAtIndex(strongWrapper2->runtime(), 0); 62 63 strongWrapper2->callback().call( 64 strongWrapper2->runtime(), 65 (const jsi::Value *) &arg, 66 (size_t) 1 67 ); 68 69 callbackWrapperOwner.reset(); 70 }); 71 72 wrapperWasCalled = true; 73 }; 74 75 return react::JCxxCallbackImpl::newObjectCxxArgs(fn); 76 } 77 78 std::vector<jvalue> MethodMetadata::convertJSIArgsToJNI( 79 JSIInteropModuleRegistry *moduleRegistry, 80 JNIEnv *env, 81 jsi::Runtime &rt, 82 const jsi::Value *args, 83 size_t count 84 ) { 85 std::vector<jvalue> result(count); 86 87 for (unsigned int argIndex = 0; argIndex < count; argIndex++) { 88 const jsi::Value *arg = &args[argIndex]; 89 jvalue *jarg = &result[argIndex]; 90 int desiredType = desiredTypes[argIndex]; 91 92 if (desiredType & CppType::JS_VALUE) { 93 jarg->l = JavaScriptValue::newObjectCxxArgs( 94 moduleRegistry->runtimeHolder->weak_from_this(), 95 // TODO(@lukmccall): make sure that copy here is necessary 96 std::make_shared<jsi::Value>(jsi::Value(rt, *arg)) 97 ).release(); 98 } else if (desiredType & CppType::JS_OBJECT) { 99 jarg->l = JavaScriptObject::newObjectCxxArgs( 100 moduleRegistry->runtimeHolder->weak_from_this(), 101 std::make_shared<jsi::Object>(arg->getObject(rt)) 102 ).release(); 103 } else if (arg->isNull() || arg->isUndefined()) { 104 jarg->l = nullptr; 105 } else if (arg->isNumber()) { 106 auto &doubleClass = CachedReferencesRegistry::instance() 107 ->getJClass("java/lang/Double"); 108 jmethodID doubleConstructor = doubleClass.getMethod("<init>", "(D)V"); 109 jarg->l = env->NewObject(doubleClass.clazz, doubleConstructor, arg->getNumber()); 110 } else if (arg->isBool()) { 111 auto &booleanClass = CachedReferencesRegistry::instance() 112 ->getJClass("java/lang/Boolean"); 113 jmethodID booleanConstructor = booleanClass.getMethod("<init>", "(Z)V"); 114 jarg->l = env->NewObject(booleanClass.clazz, booleanConstructor, arg->getBool()); 115 } else if (arg->isString()) { 116 jarg->l = env->NewStringUTF(arg->getString(rt).utf8(rt).c_str()); 117 } else if (arg->isObject()) { 118 const jsi::Object object = arg->getObject(rt); 119 120 // TODO(@lukmccall): stop using dynamic 121 auto dynamic = jsi::dynamicFromValue(rt, *arg); 122 if (arg->getObject(rt).isArray(rt)) { 123 jarg->l = react::ReadableNativeArray::newObjectCxxArgs(std::move(dynamic)).release(); 124 } else { 125 jarg->l = react::ReadableNativeMap::createWithContents(std::move(dynamic)).release(); 126 } 127 } else { 128 // TODO(@lukmccall): throw an exception 129 jarg->l = nullptr; 130 } 131 } 132 133 return result; 134 } 135 136 MethodMetadata::MethodMetadata( 137 std::string name, 138 int args, 139 bool isAsync, 140 std::unique_ptr<int[]> desiredTypes, 141 jni::global_ref<jobject> &&jBodyReference 142 ) : name(std::move(name)), 143 args(args), 144 isAsync(isAsync), 145 desiredTypes(std::move(desiredTypes)), 146 jBodyReference(std::move(jBodyReference)) {} 147 148 std::shared_ptr<jsi::Function> MethodMetadata::toJSFunction( 149 jsi::Runtime &runtime, 150 JSIInteropModuleRegistry *moduleRegistry 151 ) { 152 if (body == nullptr) { 153 if (isAsync) { 154 body = std::make_shared<jsi::Function>(toAsyncFunction(runtime, moduleRegistry)); 155 } else { 156 body = std::make_shared<jsi::Function>(toSyncFunction(runtime, moduleRegistry)); 157 } 158 } 159 160 return body; 161 } 162 163 jsi::Function MethodMetadata::toSyncFunction( 164 jsi::Runtime &runtime, 165 JSIInteropModuleRegistry *moduleRegistry 166 ) { 167 return jsi::Function::createFromHostFunction( 168 runtime, 169 jsi::PropNameID::forAscii(runtime, name), 170 args, 171 [this, moduleRegistry]( 172 jsi::Runtime &rt, 173 const jsi::Value &thisValue, 174 const jsi::Value *args, 175 size_t count 176 ) -> jsi::Value { 177 JNIEnv *env = jni::Environment::current(); 178 179 /** 180 * This will push a new JNI stack frame for the LocalReferences in this 181 * function call. When the stack frame for this lambda is popped, 182 * all LocalReferences are deleted. 183 */ 184 jni::JniLocalScope scope(env, (int) count); 185 186 std::vector<jvalue> convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, args, count); 187 188 // TODO(@lukmccall): Remove this temp array 189 auto tempArray = jni::JArrayClass<jobject>::newArray(count); 190 for (size_t i = 0; i < convertedArgs.size(); i++) { 191 tempArray->setElement(i, convertedArgs[i].l); 192 } 193 194 // Cast in this place is safe, cause we know that this function is promise-less. 195 auto syncFunction = jni::static_ref_cast<JNIFunctionBody>(this->jBodyReference); 196 auto result = syncFunction->invoke( 197 std::move(tempArray) 198 ); 199 200 if (result == nullptr) { 201 return jsi::Value::undefined(); 202 } 203 204 return jsi::valueFromDynamic(rt, result->cthis()->consume()) 205 .asObject(rt) 206 .asArray(rt) 207 .getValueAtIndex(rt, 0); 208 }); 209 } 210 211 jsi::Function MethodMetadata::toAsyncFunction( 212 jsi::Runtime &runtime, 213 JSIInteropModuleRegistry *moduleRegistry 214 ) { 215 return jsi::Function::createFromHostFunction( 216 runtime, 217 jsi::PropNameID::forAscii(runtime, name), 218 args, 219 [this, moduleRegistry]( 220 jsi::Runtime &rt, 221 const jsi::Value &thisValue, 222 const jsi::Value *args, 223 size_t count 224 ) -> jsi::Value { 225 JNIEnv *env = jni::Environment::current(); 226 227 /** 228 * This will push a new JNI stack frame for the LocalReferences in this 229 * function call. When the stack frame for this lambda is popped, 230 * all LocalReferences are deleted. 231 */ 232 jni::JniLocalScope scope(env, (int) count); 233 234 std::vector<jvalue> convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, args, count); 235 236 // TODO(@lukmccall): Remove this temp array 237 auto tempArray = jni::JArrayClass<jobject>::newArray(count); 238 for (size_t i = 0; i < convertedArgs.size(); i++) { 239 tempArray->setElement(i, convertedArgs[i].l); 240 } 241 242 auto Promise = rt.global().getPropertyAsFunction(rt, "Promise"); 243 // Creates a JSI promise 244 jsi::Value promise = Promise.callAsConstructor( 245 rt, 246 createPromiseBody(rt, moduleRegistry, std::move(tempArray)) 247 ); 248 return promise; 249 } 250 ); 251 } 252 253 jsi::Function MethodMetadata::createPromiseBody( 254 jsi::Runtime &runtime, 255 JSIInteropModuleRegistry *moduleRegistry, 256 jni::local_ref<jni::JArrayClass<jobject>::javaobject> &&args 257 ) { 258 return jsi::Function::createFromHostFunction( 259 runtime, 260 jsi::PropNameID::forAscii(runtime, "promiseFn"), 261 2, 262 [this, args = std::move(args), moduleRegistry]( 263 jsi::Runtime &rt, 264 const jsi::Value &thisVal, 265 const jsi::Value *promiseConstructorArgs, 266 size_t promiseConstructorArgCount 267 ) { 268 if (promiseConstructorArgCount != 2) { 269 throw std::invalid_argument("Promise fn arg count must be 2"); 270 } 271 272 jsi::Function resolveJSIFn = promiseConstructorArgs[0].getObject(rt).getFunction(rt); 273 jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt); 274 275 auto &runtimeHolder = moduleRegistry->runtimeHolder; 276 jobject resolve = createJavaCallbackFromJSIFunction( 277 std::move(resolveJSIFn), 278 rt, 279 runtimeHolder->jsInvoker 280 ).release(); 281 282 jobject reject = createJavaCallbackFromJSIFunction( 283 std::move(rejectJSIFn), 284 rt, 285 runtimeHolder->jsInvoker 286 ).release(); 287 288 JNIEnv *env = jni::Environment::current(); 289 290 auto &jPromise = CachedReferencesRegistry::instance()->getJClass( 291 "com/facebook/react/bridge/PromiseImpl"); 292 jmethodID jPromiseConstructor = jPromise.getMethod( 293 "<init>", 294 "(Lcom/facebook/react/bridge/Callback;Lcom/facebook/react/bridge/Callback;)V" 295 ); 296 297 // Creates a promise object 298 jobject promise = env->NewObject( 299 jPromise.clazz, 300 jPromiseConstructor, 301 resolve, 302 reject 303 ); 304 305 // Cast in this place is safe, cause we know that this function expects promise. 306 auto asyncFunction = jni::static_ref_cast<JNIAsyncFunctionBody>(this->jBodyReference); 307 asyncFunction->invoke( 308 args, 309 promise 310 ); 311 312 // We have to remove the local reference to the promise object. 313 // It doesn't mean that the promise will be deallocated, but rather that we move 314 // the ownership to the `JNIAsyncFunctionBody`. 315 env->DeleteLocalRef(promise); 316 317 return jsi::Value::undefined(); 318 } 319 ); 320 } 321 322 } // namespace expo 323