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