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   >
setProperty(jni::alias_ref<jstring> name,T value)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   >
defineProperty(jni::alias_ref<jstring> name,T value,int options)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