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       JNIEnv *env = jni::Environment::current();
178 
179       /**
180        * This will push a new JNI stack frame for the LocalReferences in this
181        * function call. When the stack frame for this lambda is popped,
182        * all LocalReferences are deleted.
183        */
184       jni::JniLocalScope scope(env, (int) count);
185 
186       std::vector<jvalue> convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, args, count);
187 
188       // TODO(@lukmccall): Remove this temp array
189       auto tempArray = jni::JArrayClass<jobject>::newArray(count);
190       for (size_t i = 0; i < convertedArgs.size(); i++) {
191         tempArray->setElement(i, convertedArgs[i].l);
192       }
193 
194       // Cast in this place is safe, cause we know that this function is promise-less.
195       auto syncFunction = jni::static_ref_cast<JNIFunctionBody>(this->jBodyReference);
196       auto result = syncFunction->invoke(
197         std::move(tempArray)
198       );
199 
200       if (result == nullptr) {
201         return jsi::Value::undefined();
202       }
203 
204       return jsi::valueFromDynamic(rt, result->cthis()->consume())
205         .asObject(rt)
206         .asArray(rt)
207         .getValueAtIndex(rt, 0);
208     });
209 }
210 
211 jsi::Function MethodMetadata::toAsyncFunction(
212   jsi::Runtime &runtime,
213   JSIInteropModuleRegistry *moduleRegistry
214 ) {
215   return jsi::Function::createFromHostFunction(
216     runtime,
217     jsi::PropNameID::forAscii(runtime, name),
218     args,
219     [this, moduleRegistry](
220       jsi::Runtime &rt,
221       const jsi::Value &thisValue,
222       const jsi::Value *args,
223       size_t count
224     ) -> jsi::Value {
225       JNIEnv *env = jni::Environment::current();
226 
227       /**
228        * This will push a new JNI stack frame for the LocalReferences in this
229        * function call. When the stack frame for this lambda is popped,
230        * all LocalReferences are deleted.
231        */
232       jni::JniLocalScope scope(env, (int) count);
233 
234       std::vector<jvalue> convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, args, count);
235 
236       // TODO(@lukmccall): Remove this temp array
237       auto tempArray = jni::JArrayClass<jobject>::newArray(count);
238       for (size_t i = 0; i < convertedArgs.size(); i++) {
239         tempArray->setElement(i, convertedArgs[i].l);
240       }
241 
242       auto Promise = rt.global().getPropertyAsFunction(rt, "Promise");
243       // Creates a JSI promise
244       jsi::Value promise = Promise.callAsConstructor(
245         rt,
246         createPromiseBody(rt, moduleRegistry, std::move(tempArray))
247       );
248       return promise;
249     }
250   );
251 }
252 
253 jsi::Function MethodMetadata::createPromiseBody(
254   jsi::Runtime &runtime,
255   JSIInteropModuleRegistry *moduleRegistry,
256   jni::local_ref<jni::JArrayClass<jobject>::javaobject> &&args
257 ) {
258   return jsi::Function::createFromHostFunction(
259     runtime,
260     jsi::PropNameID::forAscii(runtime, "promiseFn"),
261     2,
262     [this, args = std::move(args), moduleRegistry](
263       jsi::Runtime &rt,
264       const jsi::Value &thisVal,
265       const jsi::Value *promiseConstructorArgs,
266       size_t promiseConstructorArgCount
267     ) {
268       if (promiseConstructorArgCount != 2) {
269         throw std::invalid_argument("Promise fn arg count must be 2");
270       }
271 
272       jsi::Function resolveJSIFn = promiseConstructorArgs[0].getObject(rt).getFunction(rt);
273       jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt);
274 
275       auto &runtimeHolder = moduleRegistry->runtimeHolder;
276       jobject resolve = createJavaCallbackFromJSIFunction(
277         std::move(resolveJSIFn),
278         rt,
279         runtimeHolder->jsInvoker
280       ).release();
281 
282       jobject reject = createJavaCallbackFromJSIFunction(
283         std::move(rejectJSIFn),
284         rt,
285         runtimeHolder->jsInvoker
286       ).release();
287 
288       JNIEnv *env = jni::Environment::current();
289 
290       auto &jPromise = CachedReferencesRegistry::instance()->getJClass(
291         "com/facebook/react/bridge/PromiseImpl");
292       jmethodID jPromiseConstructor = jPromise.getMethod(
293         "<init>",
294         "(Lcom/facebook/react/bridge/Callback;Lcom/facebook/react/bridge/Callback;)V"
295       );
296 
297       // Creates a promise object
298       jobject promise = env->NewObject(
299         jPromise.clazz,
300         jPromiseConstructor,
301         resolve,
302         reject
303       );
304 
305       // Cast in this place is safe, cause we know that this function expects promise.
306       auto asyncFunction = jni::static_ref_cast<JNIAsyncFunctionBody>(this->jBodyReference);
307       asyncFunction->invoke(
308         args,
309         promise
310       );
311 
312       // We have to remove the local reference to the promise object.
313       // It doesn't mean that the promise will be deallocated, but rather that we move
314       // the ownership to the `JNIAsyncFunctionBody`.
315       env->DeleteLocalRef(promise);
316 
317       return jsi::Value::undefined();
318     }
319   );
320 }
321 
322 } // namespace expo
323