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, classRef]: classes) { 141 auto classObject = jni::static_ref_cast<JavaScriptModuleObject::javaobject>( 142 jni::make_local(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, 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 = thisValue.asObject(runtime); 173 decorateObjectWithProperties(runtime, jsiInteropModuleRegistry, &thisObject, classObject); 174 // TODO(@lukmccall): call the constructor from the class definition 175 return jsi::Value::undefined(); 176 }); 177 178 auto descriptor = JavaScriptObject::preparePropertyDescriptor(runtime, 0); 179 descriptor.setProperty(runtime, "value", jsi::Value(runtime, nativeConstructor)); 180 181 JavaScriptObject::defineProperty(runtime, &prototype, nativeConstructorKey, 182 std::move(descriptor)); 183 184 moduleObject->setProperty( 185 runtime, 186 jsi::String::createFromUtf8(runtime, name), 187 jsi::Value(runtime, klass.asFunction(runtime)) 188 ); 189 190 decorateObjectWithFunctions( 191 runtime, 192 jsiInteropModuleRegistry, 193 &prototype, 194 classObject 195 ); 196 } 197 198 jsiObject = moduleObject; 199 return moduleObject; 200 } 201 202 void JavaScriptModuleObject::exportConstants( 203 jni::alias_ref<react::NativeMap::javaobject> constants 204 ) { 205 auto dynamic = constants->cthis()->consume(); 206 assert(dynamic.isObject()); 207 208 for (const auto &[key, value]: dynamic.items()) { 209 this->constants[key.asString()] = value; 210 } 211 } 212 213 void JavaScriptModuleObject::registerSyncFunction( 214 jni::alias_ref<jstring> name, 215 jboolean takesOwner, 216 jint args, 217 jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes, 218 jni::alias_ref<JNIFunctionBody::javaobject> body 219 ) { 220 std::string cName = name->toStdString(); 221 222 methodsMetadata.try_emplace( 223 cName, 224 longLivedObjectCollection_, 225 cName, 226 takesOwner, 227 args, 228 false, 229 jni::make_local(expectedArgTypes), 230 jni::make_global(body) 231 ); 232 } 233 234 void JavaScriptModuleObject::registerAsyncFunction( 235 jni::alias_ref<jstring> name, 236 jboolean takesOwner, 237 jint args, 238 jni::alias_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes, 239 jni::alias_ref<JNIAsyncFunctionBody::javaobject> body 240 ) { 241 std::string cName = name->toStdString(); 242 243 methodsMetadata.try_emplace( 244 cName, 245 longLivedObjectCollection_, 246 cName, 247 takesOwner, 248 args, 249 true, 250 jni::make_local(expectedArgTypes), 251 jni::make_global(body) 252 ); 253 } 254 255 void JavaScriptModuleObject::registerClass( 256 jni::alias_ref<jstring> name, 257 jni::alias_ref<JavaScriptModuleObject::javaobject> classObject 258 ) { 259 std::string cName = name->toStdString(); 260 classes[cName] = jni::make_global(classObject); 261 } 262 263 void JavaScriptModuleObject::registerViewPrototype( 264 jni::alias_ref<JavaScriptModuleObject::javaobject> viewPrototype 265 ) { 266 this->viewPrototype = jni::make_global(viewPrototype); 267 } 268 269 void JavaScriptModuleObject::registerProperty( 270 jni::alias_ref<jstring> name, 271 jni::alias_ref<ExpectedType> expectedArgType, 272 jni::alias_ref<JNIFunctionBody::javaobject> getter, 273 jni::alias_ref<JNIFunctionBody::javaobject> setter 274 ) { 275 auto cName = name->toStdString(); 276 277 auto getterMetadata = MethodMetadata( 278 longLivedObjectCollection_, 279 cName, 280 false, 281 0, 282 false, 283 std::vector<std::unique_ptr<AnyType>>(), 284 jni::make_global(getter) 285 ); 286 287 auto types = std::vector<std::unique_ptr<AnyType>>(); 288 types.push_back(std::make_unique<AnyType>(jni::make_local(expectedArgType))); 289 auto setterMetadata = MethodMetadata( 290 longLivedObjectCollection_, 291 cName, 292 false, 293 1, 294 false, 295 std::move(types), 296 jni::make_global(setter) 297 ); 298 299 auto functions = std::make_pair( 300 std::move(getterMetadata), 301 std::move(setterMetadata) 302 ); 303 304 properties.insert({cName, std::move(functions)}); 305 } 306 307 JavaScriptModuleObject::JavaScriptModuleObject(jni::alias_ref<jhybridobject> jThis) 308 : javaPart_(jni::make_global(jThis)) { 309 longLivedObjectCollection_ = std::make_shared<react::LongLivedObjectCollection>(); 310 } 311 } // namespace expo 312