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 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 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 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> 83 JavaScriptModuleObject::initHybrid(jni::alias_ref<jhybridobject> jThis) { 84 return makeCxxInstance(jThis); 85 } 86 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 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 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 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 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 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 316 void JavaScriptModuleObject::registerViewPrototype( 317 jni::alias_ref<JavaScriptModuleObject::javaobject> viewPrototype 318 ) { 319 this->viewPrototype = jni::make_global(viewPrototype); 320 } 321 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 359 JavaScriptModuleObject::JavaScriptModuleObject(jni::alias_ref<jhybridobject> jThis) 360 : javaPart_(jni::make_global(jThis)) { 361 } 362 } // namespace expo 363