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