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 // TODO(@lukmccall): throw an exception 154 jarg->l = nullptr; 155 } 156 } 157 158 return result; 159 } 160 161 MethodMetadata::MethodMetadata( 162 std::string name, 163 int args, 164 bool isAsync, 165 std::unique_ptr<int[]> desiredTypes, 166 jni::global_ref<jobject> &&jBodyReference 167 ) : name(std::move(name)), 168 args(args), 169 isAsync(isAsync), 170 desiredTypes(std::move(desiredTypes)), 171 jBodyReference(std::move(jBodyReference)) {} 172 173 std::shared_ptr<jsi::Function> MethodMetadata::toJSFunction( 174 jsi::Runtime &runtime, 175 JSIInteropModuleRegistry *moduleRegistry 176 ) { 177 if (body == nullptr) { 178 if (isAsync) { 179 body = std::make_shared<jsi::Function>(toAsyncFunction(runtime, moduleRegistry)); 180 } else { 181 body = std::make_shared<jsi::Function>(toSyncFunction(runtime, moduleRegistry)); 182 } 183 } 184 185 return body; 186 } 187 188 jsi::Function MethodMetadata::toSyncFunction( 189 jsi::Runtime &runtime, 190 JSIInteropModuleRegistry *moduleRegistry 191 ) { 192 return jsi::Function::createFromHostFunction( 193 runtime, 194 jsi::PropNameID::forAscii(runtime, name), 195 args, 196 [this, moduleRegistry]( 197 jsi::Runtime &rt, 198 const jsi::Value &thisValue, 199 const jsi::Value *args, 200 size_t count 201 ) -> jsi::Value { 202 try { 203 return this->callSync( 204 rt, 205 moduleRegistry, 206 args, 207 count 208 ); 209 } catch (jni::JniException &jniException) { 210 jni::local_ref<jni::JThrowable> unboxedThrowable = jniException.getThrowable(); 211 if (unboxedThrowable->isInstanceOf(CodedException::javaClassLocal())) { 212 auto codedException = jni::static_ref_cast<CodedException>(unboxedThrowable); 213 auto code = codedException->getCode(); 214 auto message = codedException->getLocalizedMessage(); 215 216 if (rt.global().hasProperty(rt, "ExpoModulesCore_CodedError")) { 217 auto jsCodedError = rt.global() 218 .getProperty(rt, "ExpoModulesCore_CodedError") 219 .asObject(rt) 220 .asFunction(rt); 221 222 throw jsi::JSError( 223 message.value_or(""), 224 rt, 225 jsCodedError.callAsConstructor( 226 rt, 227 jsi::String::createFromUtf8(rt, code), 228 jsi::String::createFromUtf8(rt, message.value_or("")) 229 ) 230 ); 231 } 232 } 233 234 // Rethrow error if we can't wrap it. 235 throw; 236 } 237 }); 238 } 239 240 jsi::Value MethodMetadata::callSync( 241 jsi::Runtime &rt, 242 JSIInteropModuleRegistry *moduleRegistry, 243 const jsi::Value *args, 244 size_t count 245 ) { 246 if (this->jBodyReference == nullptr) { 247 return jsi::Value::undefined(); 248 } 249 250 JNIEnv *env = jni::Environment::current(); 251 252 /** 253 * This will push a new JNI stack frame for the LocalReferences in this 254 * function call. When the stack frame for this lambda is popped, 255 * all LocalReferences are deleted. 256 */ 257 jni::JniLocalScope scope(env, (int) count); 258 259 std::vector<jvalue> convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, args, count, 260 false); 261 262 // TODO(@lukmccall): Remove this temp array 263 auto tempArray = env->NewObjectArray( 264 convertedArgs.size(), 265 CachedReferencesRegistry::instance()->getJClass("java/lang/Object").clazz, 266 nullptr 267 ); 268 for (size_t i = 0; i < convertedArgs.size(); i++) { 269 env->SetObjectArrayElement(tempArray, i, convertedArgs[i].l); 270 } 271 272 // Cast in this place is safe, cause we know that this function is promise-less. 273 auto syncFunction = jni::static_ref_cast<JNIFunctionBody>(this->jBodyReference); 274 auto result = syncFunction->invoke( 275 tempArray 276 ); 277 278 if (result == nullptr) { 279 return jsi::Value::undefined(); 280 } 281 282 return jsi::valueFromDynamic(rt, result->cthis()->consume()) 283 .asObject(rt) 284 .asArray(rt) 285 .getValueAtIndex(rt, 0); 286 } 287 288 jsi::Function MethodMetadata::toAsyncFunction( 289 jsi::Runtime &runtime, 290 JSIInteropModuleRegistry *moduleRegistry 291 ) { 292 return jsi::Function::createFromHostFunction( 293 runtime, 294 jsi::PropNameID::forAscii(runtime, name), 295 args, 296 [this, moduleRegistry]( 297 jsi::Runtime &rt, 298 const jsi::Value &thisValue, 299 const jsi::Value *args, 300 size_t count 301 ) -> jsi::Value { 302 JNIEnv *env = jni::Environment::current(); 303 304 /** 305 * This will push a new JNI stack frame for the LocalReferences in this 306 * function call. When the stack frame for this lambda is popped, 307 * all LocalReferences are deleted. 308 */ 309 jni::JniLocalScope scope(env, (int) count); 310 std::vector<jvalue> convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, args, count, 311 true); 312 313 auto Promise = rt.global().getPropertyAsFunction(rt, "Promise"); 314 // Creates a JSI promise 315 jsi::Value promise = Promise.callAsConstructor( 316 rt, 317 createPromiseBody(rt, moduleRegistry, std::move(convertedArgs)) 318 ); 319 return promise; 320 } 321 ); 322 } 323 324 jsi::Function MethodMetadata::createPromiseBody( 325 jsi::Runtime &runtime, 326 JSIInteropModuleRegistry *moduleRegistry, 327 std::vector<jvalue> &&args 328 ) { 329 return jsi::Function::createFromHostFunction( 330 runtime, 331 jsi::PropNameID::forAscii(runtime, "promiseFn"), 332 2, 333 [this, args = std::move(args), moduleRegistry]( 334 jsi::Runtime &rt, 335 const jsi::Value &thisVal, 336 const jsi::Value *promiseConstructorArgs, 337 size_t promiseConstructorArgCount 338 ) { 339 if (promiseConstructorArgCount != 2) { 340 throw std::invalid_argument("Promise fn arg count must be 2"); 341 } 342 343 jsi::Function resolveJSIFn = promiseConstructorArgs[0].getObject(rt).getFunction(rt); 344 jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt); 345 346 auto &runtimeHolder = moduleRegistry->runtimeHolder; 347 jobject resolve = createJavaCallbackFromJSIFunction( 348 std::move(resolveJSIFn), 349 rt, 350 runtimeHolder->jsInvoker 351 ).release(); 352 353 jobject reject = createJavaCallbackFromJSIFunction( 354 std::move(rejectJSIFn), 355 rt, 356 runtimeHolder->jsInvoker 357 ).release(); 358 359 JNIEnv *env = jni::Environment::current(); 360 361 auto &jPromise = CachedReferencesRegistry::instance()->getJClass( 362 "com/facebook/react/bridge/PromiseImpl"); 363 jmethodID jPromiseConstructor = jPromise.getMethod( 364 "<init>", 365 "(Lcom/facebook/react/bridge/Callback;Lcom/facebook/react/bridge/Callback;)V" 366 ); 367 368 // Creates a promise object 369 jobject promise = env->NewObject( 370 jPromise.clazz, 371 jPromiseConstructor, 372 resolve, 373 reject 374 ); 375 376 auto argsSize = args.size(); 377 // TODO(@lukmccall): Remove this temp array 378 auto tempArray = env->NewObjectArray( 379 argsSize, 380 CachedReferencesRegistry::instance()->getJClass("java/lang/Object").clazz, 381 nullptr 382 ); 383 for (size_t i = 0; i < argsSize; i++) { 384 env->SetObjectArrayElement(tempArray, i, args[i].l); 385 } 386 387 // Cast in this place is safe, cause we know that this function expects promise. 388 auto asyncFunction = jni::static_ref_cast<JNIAsyncFunctionBody>(this->jBodyReference); 389 asyncFunction->invoke( 390 tempArray, 391 promise 392 ); 393 394 // We have to remove the local reference to the promise object. 395 // It doesn't mean that the promise will be deallocated, but rather that we move 396 // the ownership to the `JNIAsyncFunctionBody`. 397 env->DeleteLocalRef(promise); 398 399 for (const auto &arg: args) { 400 env->DeleteGlobalRef(arg.l); 401 } 402 env->DeleteLocalRef(tempArray); 403 404 return jsi::Value::undefined(); 405 } 406 ); 407 } 408 409 } // namespace expo 410