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