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 jobjectArray MethodMetadata::convertJSIArgsToJNI( 82 JSIInteropModuleRegistry *moduleRegistry, 83 JNIEnv *env, 84 jsi::Runtime &rt, 85 const jsi::Value *args, 86 size_t count 87 ) { 88 auto argumentArray = env->NewObjectArray( 89 count, 90 JavaReferencesCache::instance()->getJClass("java/lang/Object").clazz, 91 nullptr 92 ); 93 94 std::vector<jobject> result(count); 95 96 for (unsigned int argIndex = 0; argIndex < count; argIndex++) { 97 const jsi::Value &arg = args[argIndex]; 98 auto &type = argTypes[argIndex]; 99 if (arg.isNull() || arg.isUndefined()) { 100 // If value is null or undefined, we just passes a null 101 // Kotlin code will check if expected type is nullable. 102 result[argIndex] = nullptr; 103 } else { 104 if (type->converter->canConvert(rt, arg)) { 105 auto converterValue = type->converter->convert(rt, env, moduleRegistry, arg); 106 env->SetObjectArrayElement(argumentArray, argIndex, converterValue); 107 env->DeleteLocalRef(converterValue); 108 } else { 109 auto stringRepresentation = arg.toString(rt).utf8(rt); 110 jni::throwNewJavaException( 111 UnexpectedException::create( 112 "Cannot convert '" + stringRepresentation + "' to a Kotlin type.").get() 113 ); 114 } 115 } 116 } 117 118 return argumentArray; 119 } 120 121 MethodMetadata::MethodMetadata( 122 std::string name, 123 int args, 124 bool isAsync, 125 jni::local_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes, 126 jni::global_ref<jobject> &&jBodyReference 127 ) : name(std::move(name)), 128 args(args), 129 isAsync(isAsync), 130 jBodyReference(std::move(jBodyReference)) { 131 argTypes.reserve(args); 132 for (size_t i = 0; i < args; i++) { 133 auto expectedType = expectedArgTypes->getElement(i); 134 argTypes.push_back( 135 std::make_unique<AnyType>(std::move(expectedType)) 136 ); 137 } 138 } 139 140 MethodMetadata::MethodMetadata( 141 std::string name, 142 int args, 143 bool isAsync, 144 std::vector<std::unique_ptr<AnyType>> &&expectedArgTypes, 145 jni::global_ref<jobject> &&jBodyReference 146 ) : name(std::move(name)), 147 args(args), 148 isAsync(isAsync), 149 argTypes(std::move(expectedArgTypes)), 150 jBodyReference(std::move(jBodyReference) 151 ) {} 152 153 std::shared_ptr<jsi::Function> MethodMetadata::toJSFunction( 154 jsi::Runtime &runtime, 155 JSIInteropModuleRegistry *moduleRegistry 156 ) { 157 if (body == nullptr) { 158 if (isAsync) { 159 body = std::make_shared<jsi::Function>(toAsyncFunction(runtime, moduleRegistry)); 160 } else { 161 body = std::make_shared<jsi::Function>(toSyncFunction(runtime, moduleRegistry)); 162 } 163 } 164 165 return body; 166 } 167 168 jsi::Function MethodMetadata::toSyncFunction( 169 jsi::Runtime &runtime, 170 JSIInteropModuleRegistry *moduleRegistry 171 ) { 172 return jsi::Function::createFromHostFunction( 173 runtime, 174 moduleRegistry->jsRegistry->getPropNameID(runtime, name), 175 args, 176 [this, moduleRegistry]( 177 jsi::Runtime &rt, 178 const jsi::Value &thisValue, 179 const jsi::Value *args, 180 size_t count 181 ) -> jsi::Value { 182 try { 183 return this->callSync( 184 rt, 185 moduleRegistry, 186 args, 187 count 188 ); 189 } catch (jni::JniException &jniException) { 190 rethrowAsCodedError(rt, moduleRegistry, jniException); 191 } 192 }); 193 } 194 195 jsi::Value MethodMetadata::callSync( 196 jsi::Runtime &rt, 197 JSIInteropModuleRegistry *moduleRegistry, 198 const jsi::Value *args, 199 size_t count 200 ) { 201 if (this->jBodyReference == nullptr) { 202 return jsi::Value::undefined(); 203 } 204 205 JNIEnv *env = jni::Environment::current(); 206 207 /** 208 * This will push a new JNI stack frame for the LocalReferences in this 209 * function call. When the stack frame for this lambda is popped, 210 * all LocalReferences are deleted. 211 */ 212 jni::JniLocalScope scope(env, (int) count); 213 214 auto convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, args, count); 215 216 // Cast in this place is safe, cause we know that this function is promise-less. 217 auto syncFunction = jni::static_ref_cast<JNIFunctionBody>(this->jBodyReference); 218 auto result = syncFunction->invoke( 219 convertedArgs 220 ); 221 222 env->DeleteLocalRef(convertedArgs); 223 if (result == nullptr) { 224 return jsi::Value::undefined(); 225 } 226 227 return jsi::valueFromDynamic(rt, result->cthis()->consume()) 228 .asObject(rt) 229 .asArray(rt) 230 .getValueAtIndex(rt, 0); 231 } 232 233 jsi::Function MethodMetadata::toAsyncFunction( 234 jsi::Runtime &runtime, 235 JSIInteropModuleRegistry *moduleRegistry 236 ) { 237 return jsi::Function::createFromHostFunction( 238 runtime, 239 moduleRegistry->jsRegistry->getPropNameID(runtime, name), 240 args, 241 [this, moduleRegistry]( 242 jsi::Runtime &rt, 243 const jsi::Value &thisValue, 244 const jsi::Value *args, 245 size_t count 246 ) -> jsi::Value { 247 JNIEnv *env = jni::Environment::current(); 248 249 /** 250 * This will push a new JNI stack frame for the LocalReferences in this 251 * function call. When the stack frame for this lambda is popped, 252 * all LocalReferences are deleted. 253 */ 254 jni::JniLocalScope scope(env, (int) count); 255 256 try { 257 auto convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, args, count); 258 auto globalConvertedArgs = (jobjectArray) env->NewGlobalRef(convertedArgs); 259 env->DeleteLocalRef(convertedArgs); 260 261 auto &Promise = moduleRegistry->jsRegistry->getObject<jsi::Function>( 262 JSReferencesCache::JSKeys::PROMISE 263 ); 264 // Creates a JSI promise 265 jsi::Value promise = Promise.callAsConstructor( 266 rt, 267 createPromiseBody(rt, moduleRegistry, globalConvertedArgs) 268 ); 269 return promise; 270 } catch (jni::JniException &jniException) { 271 rethrowAsCodedError(rt, moduleRegistry, jniException); 272 } 273 } 274 ); 275 } 276 277 jsi::Function MethodMetadata::createPromiseBody( 278 jsi::Runtime &runtime, 279 JSIInteropModuleRegistry *moduleRegistry, 280 jobjectArray globalArgs 281 ) { 282 return jsi::Function::createFromHostFunction( 283 runtime, 284 moduleRegistry->jsRegistry->getPropNameID(runtime, "promiseFn"), 285 2, 286 [this, globalArgs, moduleRegistry]( 287 jsi::Runtime &rt, 288 const jsi::Value &thisVal, 289 const jsi::Value *promiseConstructorArgs, 290 size_t promiseConstructorArgCount 291 ) { 292 if (promiseConstructorArgCount != 2) { 293 throw std::invalid_argument("Promise fn arg count must be 2"); 294 } 295 296 jsi::Function resolveJSIFn = promiseConstructorArgs[0].getObject(rt).getFunction(rt); 297 jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt); 298 299 auto &runtimeHolder = moduleRegistry->runtimeHolder; 300 jobject resolve = createJavaCallbackFromJSIFunction( 301 std::move(resolveJSIFn), 302 rt, 303 runtimeHolder->jsInvoker 304 ).release(); 305 306 jobject reject = createJavaCallbackFromJSIFunction( 307 std::move(rejectJSIFn), 308 rt, 309 runtimeHolder->jsInvoker 310 ).release(); 311 312 JNIEnv *env = jni::Environment::current(); 313 314 auto &jPromise = JavaReferencesCache::instance()->getJClass( 315 "com/facebook/react/bridge/PromiseImpl"); 316 jmethodID jPromiseConstructor = jPromise.getMethod( 317 "<init>", 318 "(Lcom/facebook/react/bridge/Callback;Lcom/facebook/react/bridge/Callback;)V" 319 ); 320 321 // Creates a promise object 322 jobject promise = env->NewObject( 323 jPromise.clazz, 324 jPromiseConstructor, 325 resolve, 326 reject 327 ); 328 329 // Cast in this place is safe, cause we know that this function expects promise. 330 auto asyncFunction = jni::static_ref_cast<JNIAsyncFunctionBody>(this->jBodyReference); 331 asyncFunction->invoke( 332 globalArgs, 333 promise 334 ); 335 336 // We have to remove the local reference to the promise object. 337 // It doesn't mean that the promise will be deallocated, but rather that we move 338 // the ownership to the `JNIAsyncFunctionBody`. 339 env->DeleteLocalRef(promise); 340 env->DeleteGlobalRef(globalArgs); 341 342 return jsi::Value::undefined(); 343 } 344 ); 345 } 346 } // namespace expo 347