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 bool returnGlobalReferences 85 ) { 86 std::vector<jvalue> result(count); 87 88 auto makeGlobalIfNecessary = [env, returnGlobalReferences](jobject obj) -> jobject { 89 if (returnGlobalReferences) { 90 return env->NewGlobalRef(obj); 91 } 92 return obj; 93 }; 94 95 for (unsigned int argIndex = 0; argIndex < count; argIndex++) { 96 const jsi::Value *arg = &args[argIndex]; 97 jvalue *jarg = &result[argIndex]; 98 int desiredType = desiredTypes[argIndex]; 99 100 if (desiredType & CppType::JS_VALUE) { 101 jarg->l = makeGlobalIfNecessary( 102 JavaScriptValue::newObjectCxxArgs( 103 moduleRegistry->runtimeHolder->weak_from_this(), 104 // TODO(@lukmccall): make sure that copy here is necessary 105 std::make_shared<jsi::Value>(jsi::Value(rt, *arg)) 106 ).release() 107 ); 108 } else if (desiredType & CppType::JS_OBJECT) { 109 jarg->l = makeGlobalIfNecessary( 110 JavaScriptObject::newObjectCxxArgs( 111 moduleRegistry->runtimeHolder->weak_from_this(), 112 std::make_shared<jsi::Object>(arg->getObject(rt)) 113 ).release() 114 ); 115 } else if (arg->isNull() || arg->isUndefined()) { 116 jarg->l = nullptr; 117 } else if (arg->isNumber()) { 118 auto &doubleClass = CachedReferencesRegistry::instance() 119 ->getJClass("java/lang/Double"); 120 jmethodID doubleConstructor = doubleClass.getMethod("<init>", "(D)V"); 121 jarg->l = makeGlobalIfNecessary( 122 env->NewObject(doubleClass.clazz, doubleConstructor, arg->getNumber())); 123 } else if (arg->isBool()) { 124 auto &booleanClass = CachedReferencesRegistry::instance() 125 ->getJClass("java/lang/Boolean"); 126 jmethodID booleanConstructor = booleanClass.getMethod("<init>", "(Z)V"); 127 jarg->l = makeGlobalIfNecessary( 128 env->NewObject(booleanClass.clazz, booleanConstructor, arg->getBool())); 129 } else if (arg->isString()) { 130 jarg->l = makeGlobalIfNecessary(env->NewStringUTF(arg->getString(rt).utf8(rt).c_str())); 131 } else if (arg->isObject()) { 132 const jsi::Object object = arg->getObject(rt); 133 134 // TODO(@lukmccall): stop using dynamic 135 auto dynamic = jsi::dynamicFromValue(rt, *arg); 136 if (arg->getObject(rt).isArray(rt)) { 137 jarg->l = makeGlobalIfNecessary( 138 react::ReadableNativeArray::newObjectCxxArgs(std::move(dynamic)).release()); 139 } else { 140 jarg->l = makeGlobalIfNecessary( 141 react::ReadableNativeMap::createWithContents(std::move(dynamic)).release()); 142 } 143 } else { 144 // TODO(@lukmccall): throw an exception 145 jarg->l = nullptr; 146 } 147 } 148 149 return result; 150 } 151 152 MethodMetadata::MethodMetadata( 153 std::string name, 154 int args, 155 bool isAsync, 156 std::unique_ptr<int[]> desiredTypes, 157 jni::global_ref<jobject> &&jBodyReference 158 ) : name(std::move(name)), 159 args(args), 160 isAsync(isAsync), 161 desiredTypes(std::move(desiredTypes)), 162 jBodyReference(std::move(jBodyReference)) {} 163 164 std::shared_ptr<jsi::Function> MethodMetadata::toJSFunction( 165 jsi::Runtime &runtime, 166 JSIInteropModuleRegistry *moduleRegistry 167 ) { 168 if (body == nullptr) { 169 if (isAsync) { 170 body = std::make_shared<jsi::Function>(toAsyncFunction(runtime, moduleRegistry)); 171 } else { 172 body = std::make_shared<jsi::Function>(toSyncFunction(runtime, moduleRegistry)); 173 } 174 } 175 176 return body; 177 } 178 179 jsi::Function MethodMetadata::toSyncFunction( 180 jsi::Runtime &runtime, 181 JSIInteropModuleRegistry *moduleRegistry 182 ) { 183 return jsi::Function::createFromHostFunction( 184 runtime, 185 jsi::PropNameID::forAscii(runtime, name), 186 args, 187 [this, moduleRegistry]( 188 jsi::Runtime &rt, 189 const jsi::Value &thisValue, 190 const jsi::Value *args, 191 size_t count 192 ) -> jsi::Value { 193 return this->callSync( 194 rt, 195 moduleRegistry, 196 args, 197 count 198 ); 199 }); 200 } 201 202 jsi::Value MethodMetadata::callSync( 203 jsi::Runtime &rt, 204 JSIInteropModuleRegistry *moduleRegistry, 205 const jsi::Value *args, 206 size_t count 207 ) { 208 if (this->jBodyReference == nullptr) { 209 return jsi::Value::undefined(); 210 } 211 212 JNIEnv *env = jni::Environment::current(); 213 214 /** 215 * This will push a new JNI stack frame for the LocalReferences in this 216 * function call. When the stack frame for this lambda is popped, 217 * all LocalReferences are deleted. 218 */ 219 jni::JniLocalScope scope(env, (int) count); 220 221 std::vector<jvalue> convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, args, count, 222 false); 223 224 // TODO(@lukmccall): Remove this temp array 225 auto tempArray = env->NewObjectArray( 226 convertedArgs.size(), 227 CachedReferencesRegistry::instance()->getJClass("java/lang/Object").clazz, 228 nullptr 229 ); 230 for (size_t i = 0; i < convertedArgs.size(); i++) { 231 env->SetObjectArrayElement(tempArray, i, convertedArgs[i].l); 232 } 233 234 // Cast in this place is safe, cause we know that this function is promise-less. 235 auto syncFunction = jni::static_ref_cast<JNIFunctionBody>(this->jBodyReference); 236 auto result = syncFunction->invoke( 237 tempArray 238 ); 239 240 if (result == nullptr) { 241 return jsi::Value::undefined(); 242 } 243 244 return jsi::valueFromDynamic(rt, result->cthis()->consume()) 245 .asObject(rt) 246 .asArray(rt) 247 .getValueAtIndex(rt, 0); 248 } 249 250 jsi::Function MethodMetadata::toAsyncFunction( 251 jsi::Runtime &runtime, 252 JSIInteropModuleRegistry *moduleRegistry 253 ) { 254 return jsi::Function::createFromHostFunction( 255 runtime, 256 jsi::PropNameID::forAscii(runtime, name), 257 args, 258 [this, moduleRegistry]( 259 jsi::Runtime &rt, 260 const jsi::Value &thisValue, 261 const jsi::Value *args, 262 size_t count 263 ) -> jsi::Value { 264 JNIEnv *env = jni::Environment::current(); 265 266 /** 267 * This will push a new JNI stack frame for the LocalReferences in this 268 * function call. When the stack frame for this lambda is popped, 269 * all LocalReferences are deleted. 270 */ 271 jni::JniLocalScope scope(env, (int) count); 272 std::vector<jvalue> convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, args, count, 273 true); 274 275 auto Promise = rt.global().getPropertyAsFunction(rt, "Promise"); 276 // Creates a JSI promise 277 jsi::Value promise = Promise.callAsConstructor( 278 rt, 279 createPromiseBody(rt, moduleRegistry, std::move(convertedArgs)) 280 ); 281 return promise; 282 } 283 ); 284 } 285 286 jsi::Function MethodMetadata::createPromiseBody( 287 jsi::Runtime &runtime, 288 JSIInteropModuleRegistry *moduleRegistry, 289 std::vector<jvalue> &&args 290 ) { 291 return jsi::Function::createFromHostFunction( 292 runtime, 293 jsi::PropNameID::forAscii(runtime, "promiseFn"), 294 2, 295 [this, args = std::move(args), moduleRegistry]( 296 jsi::Runtime &rt, 297 const jsi::Value &thisVal, 298 const jsi::Value *promiseConstructorArgs, 299 size_t promiseConstructorArgCount 300 ) { 301 if (promiseConstructorArgCount != 2) { 302 throw std::invalid_argument("Promise fn arg count must be 2"); 303 } 304 305 jsi::Function resolveJSIFn = promiseConstructorArgs[0].getObject(rt).getFunction(rt); 306 jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt); 307 308 auto &runtimeHolder = moduleRegistry->runtimeHolder; 309 jobject resolve = createJavaCallbackFromJSIFunction( 310 std::move(resolveJSIFn), 311 rt, 312 runtimeHolder->jsInvoker 313 ).release(); 314 315 jobject reject = createJavaCallbackFromJSIFunction( 316 std::move(rejectJSIFn), 317 rt, 318 runtimeHolder->jsInvoker 319 ).release(); 320 321 JNIEnv *env = jni::Environment::current(); 322 323 auto &jPromise = CachedReferencesRegistry::instance()->getJClass( 324 "com/facebook/react/bridge/PromiseImpl"); 325 jmethodID jPromiseConstructor = jPromise.getMethod( 326 "<init>", 327 "(Lcom/facebook/react/bridge/Callback;Lcom/facebook/react/bridge/Callback;)V" 328 ); 329 330 // Creates a promise object 331 jobject promise = env->NewObject( 332 jPromise.clazz, 333 jPromiseConstructor, 334 resolve, 335 reject 336 ); 337 338 auto argsSize = args.size(); 339 // TODO(@lukmccall): Remove this temp array 340 auto tempArray = env->NewObjectArray( 341 argsSize, 342 CachedReferencesRegistry::instance()->getJClass("java/lang/Object").clazz, 343 nullptr 344 ); 345 for (size_t i = 0; i < argsSize; i++) { 346 env->SetObjectArrayElement(tempArray, i, args[i].l); 347 } 348 349 // Cast in this place is safe, cause we know that this function expects promise. 350 auto asyncFunction = jni::static_ref_cast<JNIAsyncFunctionBody>(this->jBodyReference); 351 asyncFunction->invoke( 352 tempArray, 353 promise 354 ); 355 356 // We have to remove the local reference to the promise object. 357 // It doesn't mean that the promise will be deallocated, but rather that we move 358 // the ownership to the `JNIAsyncFunctionBody`. 359 env->DeleteLocalRef(promise); 360 361 for (const auto &arg: args) { 362 env->DeleteGlobalRef(arg.l); 363 } 364 env->DeleteLocalRef(tempArray); 365 366 return jsi::Value::undefined(); 367 } 368 ); 369 } 370 371 } // namespace expo 372