1 // Copyright © 2021-present 650 Industries, Inc. (aka Expo) 2 3 #include "JavaScriptModuleObject.h" 4 #include "JSIInteropModuleRegistry.h" 5 #include "ObjectDeallocator.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 JavaScriptObject::defineProperty(runtime, jsObject, name, 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::newObjectCxxArgs( 203 jsiInteropModuleRegistry->runtimeHolder, 204 thisObject 205 ); 206 jsiInteropModuleRegistry->registerSharedObject(result, jsThisObject); 207 } 208 } catch (jni::JniException &jniException) { 209 rethrowAsCodedError(runtime, jniException); 210 } 211 return jsi::Value::undefined(); 212 }); 213 214 auto descriptor = JavaScriptObject::preparePropertyDescriptor(runtime, 0); 215 descriptor.setProperty(runtime, "value", jsi::Value(runtime, nativeConstructor)); 216 217 JavaScriptObject::defineProperty(runtime, &prototype, nativeConstructorKey, 218 std::move(descriptor)); 219 220 moduleObject->setProperty( 221 runtime, 222 jsi::String::createFromUtf8(runtime, name), 223 jsi::Value(runtime, klass.asFunction(runtime)) 224 ); 225 226 decorateObjectWithFunctions( 227 runtime, 228 jsiInteropModuleRegistry, 229 &prototype, 230 classObject 231 ); 232 } 233 234 jsiObject = moduleObject; 235 return moduleObject; 236 } 237 238 void JavaScriptModuleObject::exportConstants( 239 jni::alias_ref<react::NativeMap::javaobject> constants 240 ) { 241 auto dynamic = constants->cthis()->consume(); 242 assert(dynamic.isObject()); 243 244 for (const auto &[key, value]: dynamic.items()) { 245 this->constants[key.asString()] = value; 246 } 247 } 248 249 void JavaScriptModuleObject::registerSyncFunction( 250 jni::alias_ref<jstring> name, 251 jboolean takesOwner, 252 jint args, 253 jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes, 254 jni::alias_ref<JNIFunctionBody::javaobject> body 255 ) { 256 std::string cName = name->toStdString(); 257 258 methodsMetadata.try_emplace( 259 cName, 260 longLivedObjectCollection_, 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 longLivedObjectCollection_, 282 cName, 283 takesOwner, 284 args, 285 true, 286 jni::make_local(expectedArgTypes), 287 jni::make_global(body) 288 ); 289 } 290 291 void JavaScriptModuleObject::registerClass( 292 jni::alias_ref<jstring> name, 293 jni::alias_ref<JavaScriptModuleObject::javaobject> classObject, 294 jboolean takesOwner, 295 jint args, 296 jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes, 297 jni::alias_ref<JNIFunctionBody::javaobject> body 298 ) { 299 std::string cName = name->toStdString(); 300 MethodMetadata constructor( 301 longLivedObjectCollection_, 302 "constructor", 303 takesOwner, 304 args, 305 false, 306 jni::make_local(expectedArgTypes), 307 jni::make_global(body) 308 ); 309 310 auto pair = std::make_pair(jni::make_global(classObject), std::move(constructor)); 311 312 classes.try_emplace( 313 cName, 314 std::move(pair) 315 ); 316 } 317 318 void JavaScriptModuleObject::registerViewPrototype( 319 jni::alias_ref<JavaScriptModuleObject::javaobject> viewPrototype 320 ) { 321 this->viewPrototype = jni::make_global(viewPrototype); 322 } 323 324 void JavaScriptModuleObject::registerProperty( 325 jni::alias_ref<jstring> name, 326 jni::alias_ref<ExpectedType> expectedArgType, 327 jni::alias_ref<JNIFunctionBody::javaobject> getter, 328 jni::alias_ref<JNIFunctionBody::javaobject> setter 329 ) { 330 auto cName = name->toStdString(); 331 332 auto getterMetadata = MethodMetadata( 333 longLivedObjectCollection_, 334 cName, 335 false, 336 0, 337 false, 338 std::vector<std::unique_ptr<AnyType>>(), 339 jni::make_global(getter) 340 ); 341 342 auto types = std::vector<std::unique_ptr<AnyType>>(); 343 types.push_back(std::make_unique<AnyType>(jni::make_local(expectedArgType))); 344 auto setterMetadata = MethodMetadata( 345 longLivedObjectCollection_, 346 cName, 347 false, 348 1, 349 false, 350 std::move(types), 351 jni::make_global(setter) 352 ); 353 354 auto functions = std::make_pair( 355 std::move(getterMetadata), 356 std::move(setterMetadata) 357 ); 358 359 properties.insert({cName, std::move(functions)}); 360 } 361 362 JavaScriptModuleObject::JavaScriptModuleObject(jni::alias_ref<jhybridobject> jThis) 363 : javaPart_(jni::make_global(jThis)) { 364 longLivedObjectCollection_ = std::make_shared<react::LongLivedObjectCollection>(); 365 } 366 } // namespace expo 367