1 // Copyright © 2021-present 650 Industries, Inc. (aka Expo)
2
3 #include "JavaScriptModuleObject.h"
4 #include "JSIInteropModuleRegistry.h"
5 #include "JSIUtils.h"
6
7 #include <folly/dynamic.h>
8 #include <jsi/JSIDynamic.h>
9 #include <react/jni/ReadableNativeArray.h>
10 #include <fbjni/detail/Hybrid.h>
11 #include <ReactCommon/TurboModuleUtils.h>
12 #include <jni/JCallback.h>
13 #include <jsi/JSIDynamic.h>
14 #include <fbjni/fbjni.h>
15 #include <jsi/jsi.h>
16
17 #include <utility>
18 #include <tuple>
19 #include <algorithm>
20 #include <sstream>
21
22 namespace jni = facebook::jni;
23 namespace jsi = facebook::jsi;
24 namespace react = facebook::react;
25
26 namespace expo {
27
decorateObjectWithFunctions(jsi::Runtime & runtime,JSIInteropModuleRegistry * jsiInteropModuleRegistry,jsi::Object * jsObject,JavaScriptModuleObject * objectData)28 void decorateObjectWithFunctions(
29 jsi::Runtime &runtime,
30 JSIInteropModuleRegistry *jsiInteropModuleRegistry,
31 jsi::Object *jsObject,
32 JavaScriptModuleObject *objectData) {
33 for (auto &[name, method]: objectData->methodsMetadata) {
34 jsObject->setProperty(
35 runtime,
36 jsi::String::createFromUtf8(runtime, name),
37 jsi::Value(runtime, *method.toJSFunction(runtime, jsiInteropModuleRegistry))
38 );
39 }
40 }
41
decorateObjectWithProperties(jsi::Runtime & runtime,JSIInteropModuleRegistry * jsiInteropModuleRegistry,jsi::Object * jsObject,JavaScriptModuleObject * objectData)42 void decorateObjectWithProperties(
43 jsi::Runtime &runtime,
44 JSIInteropModuleRegistry *jsiInteropModuleRegistry,
45 jsi::Object *jsObject,
46 JavaScriptModuleObject *objectData) {
47 for (auto &[name, property]: objectData->properties) {
48 auto &[getter, setter] = property;
49
50 auto descriptor = JavaScriptObject::preparePropertyDescriptor(runtime,
51 1 << 1 /* enumerable */);
52 descriptor.setProperty(
53 runtime,
54 "get",
55 jsi::Value(runtime, *getter.toJSFunction(runtime,
56 jsiInteropModuleRegistry))
57 );
58 descriptor.setProperty(
59 runtime,
60 "set",
61 jsi::Value(runtime, *setter.toJSFunction(runtime,
62 jsiInteropModuleRegistry))
63 );
64 common::definePropertyOnJSIObject(runtime, jsObject, name.c_str(), std::move(descriptor));
65 }
66 }
67
decorateObjectWithConstants(jsi::Runtime & runtime,JSIInteropModuleRegistry * jsiInteropModuleRegistry,jsi::Object * jsObject,JavaScriptModuleObject * objectData)68 void decorateObjectWithConstants(
69 jsi::Runtime &runtime,
70 JSIInteropModuleRegistry *jsiInteropModuleRegistry,
71 jsi::Object *jsObject,
72 JavaScriptModuleObject *objectData) {
73 for (const auto &[name, value]: objectData->constants) {
74 jsObject->setProperty(
75 runtime,
76 jsi::String::createFromUtf8(runtime, name),
77 jsi::valueFromDynamic(runtime, value)
78 );
79 }
80 }
81
82 jni::local_ref<jni::HybridClass<JavaScriptModuleObject>::jhybriddata>
initHybrid(jni::alias_ref<jhybridobject> jThis)83 JavaScriptModuleObject::initHybrid(jni::alias_ref<jhybridobject> jThis) {
84 return makeCxxInstance(jThis);
85 }
86
registerNatives()87 void JavaScriptModuleObject::registerNatives() {
88 registerHybrid({
89 makeNativeMethod("initHybrid", JavaScriptModuleObject::initHybrid),
90 makeNativeMethod("exportConstants", JavaScriptModuleObject::exportConstants),
91 makeNativeMethod("registerSyncFunction",
92 JavaScriptModuleObject::registerSyncFunction),
93 makeNativeMethod("registerAsyncFunction",
94 JavaScriptModuleObject::registerAsyncFunction),
95 makeNativeMethod("registerProperty",
96 JavaScriptModuleObject::registerProperty),
97 makeNativeMethod("registerClass",
98 JavaScriptModuleObject::registerClass),
99 makeNativeMethod("registerViewPrototype",
100 JavaScriptModuleObject::registerViewPrototype)
101 });
102 }
103
getJSIObject(jsi::Runtime & runtime)104 std::shared_ptr<jsi::Object> JavaScriptModuleObject::getJSIObject(jsi::Runtime &runtime) {
105 if (auto object = jsiObject.lock()) {
106 return object;
107 }
108
109 auto moduleObject = std::make_shared<jsi::Object>(runtime);
110
111 decorateObjectWithConstants(
112 runtime,
113 jsiInteropModuleRegistry,
114 moduleObject.get(),
115 this
116 );
117 decorateObjectWithProperties(
118 runtime,
119 jsiInteropModuleRegistry,
120 moduleObject.get(),
121 this
122 );
123 decorateObjectWithFunctions(
124 runtime,
125 jsiInteropModuleRegistry,
126 moduleObject.get(),
127 this
128 );
129
130 if (viewPrototype) {
131 auto viewPrototypeObject = viewPrototype->cthis();
132 viewPrototypeObject->jsiInteropModuleRegistry = jsiInteropModuleRegistry;
133 auto viewPrototypeJSIObject = viewPrototypeObject->getJSIObject(runtime);
134 moduleObject->setProperty(
135 runtime,
136 "ViewPrototype",
137 jsi::Value(runtime, *viewPrototypeJSIObject)
138 );
139 }
140
141 for (auto &[name, classInfo]: classes) {
142 auto &[classRef, constructor] = classInfo;
143 auto classObject = classRef->cthis();
144 classObject->jsiInteropModuleRegistry = jsiInteropModuleRegistry;
145
146 std::string nativeConstructorKey("__native_constructor__");
147
148 // Create a string buffer of the source code to evaluate.
149 std::stringstream source;
150 source << "(function " << name << "(...args) { this." << nativeConstructorKey
151 << "(...args); return this; })";
152 std::shared_ptr<jsi::StringBuffer> sourceBuffer = std::make_shared<jsi::StringBuffer>(
153 source.str());
154
155 // Evaluate the code and obtain returned value (the constructor function).
156 jsi::Object klass = runtime.evaluateJavaScript(sourceBuffer, "").asObject(runtime);
157
158 // Set the native constructor in the prototype.
159 jsi::Object prototype = klass.getPropertyAsObject(runtime, "prototype");
160 jsi::PropNameID nativeConstructorPropId = jsi::PropNameID::forAscii(runtime,
161 nativeConstructorKey);
162 jsi::Function nativeConstructor = jsi::Function::createFromHostFunction(
163 runtime,
164 nativeConstructorPropId,
165 // The paramCount is not obligatory to match, it only affects the `length` property of the function.
166 0,
167 [classObject, &constructor = constructor, jsiInteropModuleRegistry = jsiInteropModuleRegistry](
168 jsi::Runtime &runtime,
169 const jsi::Value &thisValue,
170 const jsi::Value *args,
171 size_t count
172 ) -> jsi::Value {
173 auto thisObject = std::make_shared<jsi::Object>(thisValue.asObject(runtime));
174 decorateObjectWithProperties(runtime, jsiInteropModuleRegistry, thisObject.get(),
175 classObject);
176 try {
177 JNIEnv *env = jni::Environment::current();
178 /**
179 * This will push a new JNI stack frame for the LocalReferences in this
180 * function call. When the stack frame for this lambda is popped,
181 * all LocalReferences are deleted.
182 */
183 jni::JniLocalScope scope(env, (int) count);
184 auto result = constructor.callJNISync(
185 env,
186 runtime,
187 jsiInteropModuleRegistry,
188 thisValue,
189 args,
190 count
191 );
192 if (result == nullptr) {
193 return jsi::Value::undefined();
194 }
195 jobject unpackedResult = result.get();
196 jclass resultClass = env->GetObjectClass(unpackedResult);
197 if (env->IsAssignableFrom(
198 resultClass,
199 JavaReferencesCache::instance()->getJClass(
200 "expo/modules/kotlin/sharedobjects/SharedObject").clazz
201 )) {
202 auto jsThisObject = JavaScriptObject::newInstance(
203 jsiInteropModuleRegistry,
204 jsiInteropModuleRegistry->runtimeHolder,
205 thisObject
206 );
207 jsiInteropModuleRegistry->registerSharedObject(result, jsThisObject);
208 }
209 } catch (jni::JniException &jniException) {
210 rethrowAsCodedError(runtime, jniException);
211 }
212 return jsi::Value::undefined();
213 });
214
215 auto descriptor = JavaScriptObject::preparePropertyDescriptor(runtime, 0);
216 descriptor.setProperty(runtime, "value", jsi::Value(runtime, nativeConstructor));
217
218 common::definePropertyOnJSIObject(runtime, &prototype, nativeConstructorKey.c_str(),
219 std::move(descriptor));
220
221 moduleObject->setProperty(
222 runtime,
223 jsi::String::createFromUtf8(runtime, name),
224 jsi::Value(runtime, klass.asFunction(runtime))
225 );
226
227 decorateObjectWithFunctions(
228 runtime,
229 jsiInteropModuleRegistry,
230 &prototype,
231 classObject
232 );
233 }
234
235 jsiObject = moduleObject;
236 return moduleObject;
237 }
238
exportConstants(jni::alias_ref<react::NativeMap::javaobject> constants)239 void JavaScriptModuleObject::exportConstants(
240 jni::alias_ref<react::NativeMap::javaobject> constants
241 ) {
242 auto dynamic = constants->cthis()->consume();
243 assert(dynamic.isObject());
244
245 for (const auto &[key, value]: dynamic.items()) {
246 this->constants[key.asString()] = value;
247 }
248 }
249
registerSyncFunction(jni::alias_ref<jstring> name,jboolean takesOwner,jint args,jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,jni::alias_ref<JNIFunctionBody::javaobject> body)250 void JavaScriptModuleObject::registerSyncFunction(
251 jni::alias_ref<jstring> name,
252 jboolean takesOwner,
253 jint args,
254 jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,
255 jni::alias_ref<JNIFunctionBody::javaobject> body
256 ) {
257 std::string cName = name->toStdString();
258
259 methodsMetadata.try_emplace(
260 cName,
261 cName,
262 takesOwner,
263 args,
264 false,
265 jni::make_local(expectedArgTypes),
266 jni::make_global(body)
267 );
268 }
269
registerAsyncFunction(jni::alias_ref<jstring> name,jboolean takesOwner,jint args,jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,jni::alias_ref<JNIAsyncFunctionBody::javaobject> body)270 void JavaScriptModuleObject::registerAsyncFunction(
271 jni::alias_ref<jstring> name,
272 jboolean takesOwner,
273 jint args,
274 jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,
275 jni::alias_ref<JNIAsyncFunctionBody::javaobject> body
276 ) {
277 std::string cName = name->toStdString();
278
279 methodsMetadata.try_emplace(
280 cName,
281 cName,
282 takesOwner,
283 args,
284 true,
285 jni::make_local(expectedArgTypes),
286 jni::make_global(body)
287 );
288 }
289
registerClass(jni::alias_ref<jstring> name,jni::alias_ref<JavaScriptModuleObject::javaobject> classObject,jboolean takesOwner,jint args,jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,jni::alias_ref<JNIFunctionBody::javaobject> body)290 void JavaScriptModuleObject::registerClass(
291 jni::alias_ref<jstring> name,
292 jni::alias_ref<JavaScriptModuleObject::javaobject> classObject,
293 jboolean takesOwner,
294 jint args,
295 jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,
296 jni::alias_ref<JNIFunctionBody::javaobject> body
297 ) {
298 std::string cName = name->toStdString();
299 MethodMetadata constructor(
300 "constructor",
301 takesOwner,
302 args,
303 false,
304 jni::make_local(expectedArgTypes),
305 jni::make_global(body)
306 );
307
308 auto pair = std::make_pair(jni::make_global(classObject), std::move(constructor));
309
310 classes.try_emplace(
311 cName,
312 std::move(pair)
313 );
314 }
315
registerViewPrototype(jni::alias_ref<JavaScriptModuleObject::javaobject> viewPrototype)316 void JavaScriptModuleObject::registerViewPrototype(
317 jni::alias_ref<JavaScriptModuleObject::javaobject> viewPrototype
318 ) {
319 this->viewPrototype = jni::make_global(viewPrototype);
320 }
321
registerProperty(jni::alias_ref<jstring> name,jboolean getterTakesOwner,jni::alias_ref<jni::JArrayClass<ExpectedType>> getterExpectedArgsTypes,jni::alias_ref<JNIFunctionBody::javaobject> getter,jboolean setterTakesOwner,jni::alias_ref<jni::JArrayClass<ExpectedType>> setterExpectedArgsTypes,jni::alias_ref<JNIFunctionBody::javaobject> setter)322 void JavaScriptModuleObject::registerProperty(
323 jni::alias_ref<jstring> name,
324 jboolean getterTakesOwner,
325 jni::alias_ref<jni::JArrayClass<ExpectedType>> getterExpectedArgsTypes,
326 jni::alias_ref<JNIFunctionBody::javaobject> getter,
327 jboolean setterTakesOwner,
328 jni::alias_ref<jni::JArrayClass<ExpectedType>> setterExpectedArgsTypes,
329 jni::alias_ref<JNIFunctionBody::javaobject> setter
330 ) {
331 auto cName = name->toStdString();
332
333 auto getterMetadata = MethodMetadata(
334 cName,
335 getterTakesOwner,
336 getterExpectedArgsTypes->size(),
337 false,
338 jni::make_local(getterExpectedArgsTypes),
339 jni::make_global(getter)
340 );
341
342 auto setterMetadata = MethodMetadata(
343 cName,
344 setterTakesOwner,
345 setterExpectedArgsTypes->size(),
346 false,
347 jni::make_local(setterExpectedArgsTypes),
348 jni::make_global(setter)
349 );
350
351 auto functions = std::make_pair(
352 std::move(getterMetadata),
353 std::move(setterMetadata)
354 );
355
356 properties.insert({cName, std::move(functions)});
357 }
358
JavaScriptModuleObject(jni::alias_ref<jhybridobject> jThis)359 JavaScriptModuleObject::JavaScriptModuleObject(jni::alias_ref<jhybridobject> jThis)
360 : javaPart_(jni::make_global(jThis)) {
361 }
362 } // namespace expo
363