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