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