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