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(), 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 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 cName, 281 takesOwner, 282 args, 283 true, 284 jni::make_local(expectedArgTypes), 285 jni::make_global(body) 286 ); 287 } 288 289 void JavaScriptModuleObject::registerClass( 290 jni::alias_ref<jstring> name, 291 jni::alias_ref<JavaScriptModuleObject::javaobject> classObject, 292 jboolean takesOwner, 293 jint args, 294 jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes, 295 jni::alias_ref<JNIFunctionBody::javaobject> body 296 ) { 297 std::string cName = name->toStdString(); 298 MethodMetadata constructor( 299 "constructor", 300 takesOwner, 301 args, 302 false, 303 jni::make_local(expectedArgTypes), 304 jni::make_global(body) 305 ); 306 307 auto pair = std::make_pair(jni::make_global(classObject), std::move(constructor)); 308 309 classes.try_emplace( 310 cName, 311 std::move(pair) 312 ); 313 } 314 315 void JavaScriptModuleObject::registerViewPrototype( 316 jni::alias_ref<JavaScriptModuleObject::javaobject> viewPrototype 317 ) { 318 this->viewPrototype = jni::make_global(viewPrototype); 319 } 320 321 void JavaScriptModuleObject::registerProperty( 322 jni::alias_ref<jstring> name, 323 jni::alias_ref<ExpectedType> expectedArgType, 324 jni::alias_ref<JNIFunctionBody::javaobject> getter, 325 jni::alias_ref<JNIFunctionBody::javaobject> setter 326 ) { 327 auto cName = name->toStdString(); 328 329 auto getterMetadata = MethodMetadata( 330 cName, 331 false, 332 0, 333 false, 334 std::vector<std::unique_ptr<AnyType>>(), 335 jni::make_global(getter) 336 ); 337 338 auto types = std::vector<std::unique_ptr<AnyType>>(); 339 types.push_back(std::make_unique<AnyType>(jni::make_local(expectedArgType))); 340 auto setterMetadata = MethodMetadata( 341 cName, 342 false, 343 1, 344 false, 345 std::move(types), 346 jni::make_global(setter) 347 ); 348 349 auto functions = std::make_pair( 350 std::move(getterMetadata), 351 std::move(setterMetadata) 352 ); 353 354 properties.insert({cName, std::move(functions)}); 355 } 356 357 JavaScriptModuleObject::JavaScriptModuleObject(jni::alias_ref<jhybridobject> jThis) 358 : javaPart_(jni::make_global(jThis)) { 359 } 360 } // namespace expo 361