1 // Copyright © 2021-present 650 Industries, Inc. (aka Expo) 2 3 #pragma once 4 5 #include "JSIObjectWrapper.h" 6 #include "JSITypeConverter.h" 7 #include "JavaScriptRuntime.h" 8 #include "WeakRuntimeHolder.h" 9 #include "JNIFunctionBody.h" 10 #include "JNIDeallocator.h" 11 12 #include <fbjni/fbjni.h> 13 #include <jsi/jsi.h> 14 15 #include <memory> 16 17 namespace jni = facebook::jni; 18 namespace jsi = facebook::jsi; 19 20 namespace expo { 21 class JavaScriptValue; 22 23 class JavaScriptFunction; 24 25 /** 26 * Represents any JavaScript object. Its purpose is to exposes `jsi::Object` API back to Kotlin. 27 */ 28 class JavaScriptObject : public jni::HybridClass<JavaScriptObject, Destructible>, JSIObjectWrapper { 29 public: 30 static auto constexpr 31 kJavaDescriptor = "Lexpo/modules/kotlin/jni/JavaScriptObject;"; 32 static auto constexpr TAG = "JavaScriptObject"; 33 34 static void registerNatives(); 35 36 static jni::local_ref<JavaScriptObject::javaobject> newInstance( 37 JSIInteropModuleRegistry *jsiInteropModuleRegistry, 38 std::weak_ptr<JavaScriptRuntime> runtime, 39 std::shared_ptr<jsi::Object> jsObject 40 ); 41 42 JavaScriptObject( 43 std::weak_ptr<JavaScriptRuntime> runtime, 44 std::shared_ptr<jsi::Object> jsObject 45 ); 46 47 JavaScriptObject( 48 WeakRuntimeHolder runtime, 49 std::shared_ptr<jsi::Object> jsObject 50 ); 51 52 std::shared_ptr<jsi::Object> get() override; 53 54 /** 55 * @return a bool whether the object has a property with the given name 56 */ 57 bool hasProperty(const std::string &name); 58 59 /** 60 * @return the property of the object with the given name. 61 * If the name isn't a property on the object, returns the `jsi::Value::undefined` value. 62 */ 63 jsi::Value getProperty(const std::string &name); 64 65 /** 66 * @return a vector consisting of all enumerable property names in the object and its prototype chain. 67 */ 68 std::vector<std::string> getPropertyNames(); 69 70 void setProperty(const std::string &name, jsi::Value value); 71 72 static jsi::Object preparePropertyDescriptor(jsi::Runtime &jsRuntime, int options); 73 74 static void defineProperty( 75 jsi::Runtime &runtime, 76 jsi::Object *jsthis, 77 const std::string &name, 78 jsi::Object descriptor 79 ); 80 81 void defineNativeDeallocator( 82 jni::alias_ref<JNIFunctionBody::javaobject> deallocator 83 ); 84 85 protected: 86 WeakRuntimeHolder runtimeHolder; 87 std::shared_ptr<jsi::Object> jsObject; 88 89 private: 90 friend HybridBase; 91 92 bool jniHasProperty(jni::alias_ref<jstring> name); 93 94 jni::local_ref<jni::HybridClass<JavaScriptValue, Destructible>::javaobject> jniGetProperty( 95 jni::alias_ref<jstring> name 96 ); 97 98 jni::local_ref<jni::JArrayClass<jstring>> jniGetPropertyNames(); 99 100 jni::local_ref<jni::HybridClass<JavaScriptFunction, Destructible>::javaobject> jniAsFunction(); 101 102 /** 103 * Unsets property with the given name. 104 */ 105 void unsetProperty(jni::alias_ref<jstring> name); 106 107 /** 108 * A template to generate different versions of the `setProperty` method based on the `jsi_type_converter` trait. 109 * Those generated methods will be exported and visible in the Kotlin codebase. 110 * On the other hand, we could just make one function that would take a generic Java Object, 111 * but then we would have to decide what to do with it and how to convert it to jsi::Value 112 * in cpp. That would be expensive. So it's easier to ensure that 113 * we call the correct version of `setProperty` in the Kotlin code. 114 * 115 * This template will work only if the jsi_type_converter exists for a given type. 116 */ 117 template< 118 class T, 119 typename = std::enable_if_t<is_jsi_type_converter_defined<T>> 120 > 121 void setProperty(jni::alias_ref<jstring> name, T value) { 122 jsi::Runtime &jsRuntime = runtimeHolder.getJSRuntime(); 123 124 auto cName = name->toStdString(); 125 jsObject->setProperty( 126 jsRuntime, 127 cName.c_str(), 128 jsi_type_converter<T>::convert(jsRuntime, value) 129 ); 130 } 131 132 template< 133 class T, 134 typename = std::enable_if_t<is_jsi_type_converter_defined<T>> 135 > 136 void defineProperty(jni::alias_ref<jstring> name, T value, int options) { 137 jsi::Runtime &jsRuntime = runtimeHolder.getJSRuntime(); 138 139 auto cName = name->toStdString(); 140 jsi::Object descriptor = preparePropertyDescriptor(jsRuntime, options); 141 descriptor.setProperty(jsRuntime, "value", jsi_type_converter<T>::convert(jsRuntime, value)); 142 JavaScriptObject::defineProperty(jsRuntime, jsObject.get(), cName, std::move(descriptor)); 143 } 144 }; 145 } // namespace expo 146