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