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 }); 99 } 100 101 std::shared_ptr<jsi::Object> JavaScriptModuleObject::getJSIObject(jsi::Runtime &runtime) { 102 if (auto object = jsiObject.lock()) { 103 return object; 104 } 105 106 auto moduleObject = std::make_shared<jsi::Object>(runtime); 107 108 decorateObjectWithConstants( 109 runtime, 110 jsiInteropModuleRegistry, 111 moduleObject.get(), 112 this 113 ); 114 decorateObjectWithProperties( 115 runtime, 116 jsiInteropModuleRegistry, 117 moduleObject.get(), 118 this 119 ); 120 decorateObjectWithFunctions( 121 runtime, 122 jsiInteropModuleRegistry, 123 moduleObject.get(), 124 this 125 ); 126 127 for (auto &[name, classRef]: classes) { 128 auto classObject = jni::static_ref_cast<JavaScriptModuleObject::javaobject>( 129 jni::make_local(classRef))->cthis(); 130 131 std::string nativeConstructorKey("__native_constructor__"); 132 133 // Create a string buffer of the source code to evaluate. 134 std::stringstream source; 135 source << "(function " << name << "(...args) { this." << nativeConstructorKey 136 << "(...args); return this; })"; 137 std::shared_ptr<jsi::StringBuffer> sourceBuffer = std::make_shared<jsi::StringBuffer>( 138 source.str()); 139 140 // Evaluate the code and obtain returned value (the constructor function). 141 jsi::Object klass = runtime.evaluateJavaScript(sourceBuffer, "").asObject(runtime); 142 143 // Set the native constructor in the prototype. 144 jsi::Object prototype = klass.getPropertyAsObject(runtime, "prototype"); 145 jsi::PropNameID nativeConstructorPropId = jsi::PropNameID::forAscii(runtime, 146 nativeConstructorKey); 147 jsi::Function nativeConstructor = jsi::Function::createFromHostFunction( 148 runtime, 149 nativeConstructorPropId, 150 // The paramCount is not obligatory to match, it only affects the `length` property of the function. 151 0, 152 [classObject, jsiInteropModuleRegistry = jsiInteropModuleRegistry]( 153 jsi::Runtime &runtime, 154 const jsi::Value &thisValue, 155 const jsi::Value *args, 156 size_t count 157 ) -> jsi::Value { 158 auto thisObject = thisValue.asObject(runtime); 159 decorateObjectWithProperties(runtime, jsiInteropModuleRegistry, &thisObject, classObject); 160 // TODO(@lukmccall): call the constructor from the class definition 161 return jsi::Value::undefined(); 162 }); 163 164 auto descriptor = JavaScriptObject::preparePropertyDescriptor(runtime, 0); 165 descriptor.setProperty(runtime, "value", jsi::Value(runtime, nativeConstructor)); 166 167 JavaScriptObject::defineProperty(runtime, &prototype, nativeConstructorKey, 168 std::move(descriptor)); 169 170 moduleObject->setProperty( 171 runtime, 172 jsi::String::createFromUtf8(runtime, name), 173 jsi::Value(runtime, klass.asFunction(runtime)) 174 ); 175 176 decorateObjectWithFunctions( 177 runtime, 178 jsiInteropModuleRegistry, 179 &prototype, 180 classObject 181 ); 182 } 183 184 jsiObject = moduleObject; 185 return moduleObject; 186 } 187 188 void JavaScriptModuleObject::exportConstants( 189 jni::alias_ref<react::NativeMap::javaobject> constants 190 ) { 191 auto dynamic = constants->cthis()->consume(); 192 assert(dynamic.isObject()); 193 194 for (const auto &[key, value]: dynamic.items()) { 195 this->constants[key.asString()] = value; 196 } 197 } 198 199 void JavaScriptModuleObject::registerSyncFunction( 200 jni::alias_ref<jstring> name, 201 jboolean takesOwner, 202 jint args, 203 jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes, 204 jni::alias_ref<JNIFunctionBody::javaobject> body 205 ) { 206 std::string cName = name->toStdString(); 207 208 methodsMetadata.try_emplace( 209 cName, 210 longLivedObjectCollection_, 211 cName, 212 takesOwner, 213 args, 214 false, 215 jni::make_local(expectedArgTypes), 216 jni::make_global(body) 217 ); 218 } 219 220 void JavaScriptModuleObject::registerAsyncFunction( 221 jni::alias_ref<jstring> name, 222 jboolean takesOwner, 223 jint args, 224 jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes, 225 jni::alias_ref<JNIAsyncFunctionBody::javaobject> body 226 ) { 227 std::string cName = name->toStdString(); 228 229 methodsMetadata.try_emplace( 230 cName, 231 longLivedObjectCollection_, 232 cName, 233 takesOwner, 234 args, 235 true, 236 jni::make_local(expectedArgTypes), 237 jni::make_global(body) 238 ); 239 } 240 241 void JavaScriptModuleObject::registerClass( 242 jni::alias_ref<jstring> name, 243 jni::alias_ref<JavaScriptModuleObject::javaobject> classObject 244 ) { 245 std::string cName = name->toStdString(); 246 classes[cName] = jni::make_global(classObject); 247 } 248 249 void JavaScriptModuleObject::registerProperty( 250 jni::alias_ref<jstring> name, 251 jni::alias_ref<ExpectedType> expectedArgType, 252 jni::alias_ref<JNIFunctionBody::javaobject> getter, 253 jni::alias_ref<JNIFunctionBody::javaobject> setter 254 ) { 255 auto cName = name->toStdString(); 256 257 auto getterMetadata = MethodMetadata( 258 longLivedObjectCollection_, 259 cName, 260 false, 261 0, 262 false, 263 std::vector<std::unique_ptr<AnyType>>(), 264 jni::make_global(getter) 265 ); 266 267 auto types = std::vector<std::unique_ptr<AnyType>>(); 268 types.push_back(std::make_unique<AnyType>(jni::make_local(expectedArgType))); 269 auto setterMetadata = MethodMetadata( 270 longLivedObjectCollection_, 271 cName, 272 false, 273 1, 274 false, 275 std::move(types), 276 jni::make_global(setter) 277 ); 278 279 auto functions = std::make_pair( 280 std::move(getterMetadata), 281 std::move(setterMetadata) 282 ); 283 284 properties.insert({cName, std::move(functions)}); 285 } 286 287 JavaScriptModuleObject::JavaScriptModuleObject(jni::alias_ref<jhybridobject> jThis) 288 : javaPart_(jni::make_global(jThis)) { 289 longLivedObjectCollection_ = std::make_shared<react::LongLivedObjectCollection>(); 290 } 291 } // namespace expo 292