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::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 JavaScriptObject::defineProperty(runtime, &prototype, nativeConstructorKey, 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 jni::alias_ref<ExpectedType> expectedArgType, 325 jni::alias_ref<JNIFunctionBody::javaobject> getter, 326 jni::alias_ref<JNIFunctionBody::javaobject> setter 327 ) { 328 auto cName = name->toStdString(); 329 330 auto getterMetadata = MethodMetadata( 331 cName, 332 false, 333 0, 334 false, 335 std::vector<std::unique_ptr<AnyType>>(), 336 jni::make_global(getter) 337 ); 338 339 auto types = std::vector<std::unique_ptr<AnyType>>(); 340 types.push_back(std::make_unique<AnyType>(jni::make_local(expectedArgType))); 341 auto setterMetadata = MethodMetadata( 342 cName, 343 false, 344 1, 345 false, 346 std::move(types), 347 jni::make_global(setter) 348 ); 349 350 auto functions = std::make_pair( 351 std::move(getterMetadata), 352 std::move(setterMetadata) 353 ); 354 355 properties.insert({cName, std::move(functions)}); 356 } 357 358 JavaScriptModuleObject::JavaScriptModuleObject(jni::alias_ref<jhybridobject> jThis) 359 : javaPart_(jni::make_global(jThis)) { 360 } 361 } // namespace expo 362