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