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