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