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