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