1 // Copyright © 2021-present 650 Industries, Inc. (aka Expo)
2 
3 #include "JavaScriptObject.h"
4 #include "JavaScriptValue.h"
5 #include "JavaScriptFunction.h"
6 #include "JavaScriptRuntime.h"
7 #include "JSITypeConverter.h"
8 #include "ObjectDeallocator.h"
9 #include "JavaReferencesCache.h"
10 #include "JSIInteropModuleRegistry.h"
11 
12 namespace expo {
13 void JavaScriptObject::registerNatives() {
14   registerHybrid({
15                    makeNativeMethod("hasProperty", JavaScriptObject::jniHasProperty),
16                    makeNativeMethod("getProperty", JavaScriptObject::jniGetProperty),
17                    makeNativeMethod("getPropertyNames", JavaScriptObject::jniGetPropertyNames),
18                    makeNativeMethod("setBoolProperty", JavaScriptObject::setProperty<bool>),
19                    makeNativeMethod("setDoubleProperty", JavaScriptObject::setProperty<double>),
20                    makeNativeMethod("setStringProperty",
21                                     JavaScriptObject::setProperty<jni::alias_ref<jstring>>),
22                    makeNativeMethod("setJSValueProperty",
23                                     JavaScriptObject::setProperty<jni::alias_ref<JavaScriptValue::javaobject>>),
24                    makeNativeMethod("setJSObjectProperty",
25                                     JavaScriptObject::setProperty<jni::alias_ref<JavaScriptObject::javaobject>>),
26                    makeNativeMethod("unsetProperty", JavaScriptObject::unsetProperty),
27                    makeNativeMethod("defineBoolProperty", JavaScriptObject::defineProperty<bool>),
28                    makeNativeMethod("defineDoubleProperty",
29                                     JavaScriptObject::defineProperty<double>),
30                    makeNativeMethod("defineStringProperty",
31                                     JavaScriptObject::defineProperty<jni::alias_ref<jstring>>),
32                    makeNativeMethod("defineJSValueProperty",
33                                     JavaScriptObject::defineProperty<jni::alias_ref<JavaScriptValue::javaobject>>),
34                    makeNativeMethod("defineJSObjectProperty",
35                                     JavaScriptObject::defineProperty<jni::alias_ref<JavaScriptObject::javaobject>>),
36                    makeNativeMethod("defineNativeDeallocator",
37                                     JavaScriptObject::defineNativeDeallocator),
38                  });
39 }
40 
41 JavaScriptObject::JavaScriptObject(
42   std::weak_ptr<JavaScriptRuntime> runtime,
43   std::shared_ptr<jsi::Object> jsObject
44 ) : runtimeHolder(std::move(runtime)), jsObject(std::move(jsObject)) {
45   runtimeHolder.ensureRuntimeIsValid();
46 }
47 
48 JavaScriptObject::JavaScriptObject(
49   WeakRuntimeHolder runtime,
50   std::shared_ptr<jsi::Object> jsObject
51 ) : runtimeHolder(std::move(runtime)), jsObject(std::move(jsObject)) {
52   runtimeHolder.ensureRuntimeIsValid();
53 }
54 
55 std::shared_ptr<jsi::Object> JavaScriptObject::get() {
56   return jsObject;
57 }
58 
59 bool JavaScriptObject::hasProperty(const std::string &name) {
60   auto &jsRuntime = runtimeHolder.getJSRuntime();
61   return jsObject->hasProperty(jsRuntime, name.c_str());
62 }
63 
64 jsi::Value JavaScriptObject::getProperty(const std::string &name) {
65   auto &jsRuntime = runtimeHolder.getJSRuntime();
66   return jsObject->getProperty(jsRuntime, name.c_str());
67 }
68 
69 bool JavaScriptObject::jniHasProperty(jni::alias_ref<jstring> name) {
70   return hasProperty(name->toStdString());
71 }
72 
73 jni::local_ref<JavaScriptValue::javaobject> JavaScriptObject::jniGetProperty(
74   jni::alias_ref<jstring> name
75 ) {
76   auto result = std::make_shared<jsi::Value>(getProperty(name->toStdString()));
77   return JavaScriptValue::newInstance(
78     runtimeHolder.getModuleRegistry(),
79     runtimeHolder,
80     result
81   );
82 }
83 
84 std::vector<std::string> JavaScriptObject::getPropertyNames() {
85   auto &jsRuntime = runtimeHolder.getJSRuntime();
86 
87   jsi::Array properties = jsObject->getPropertyNames(jsRuntime);
88   auto size = properties.size(jsRuntime);
89 
90   std::vector<std::string> names(size);
91   for (size_t i = 0; i < size; i++) {
92     auto propertyName = properties
93       .getValueAtIndex(jsRuntime, i)
94       .asString(jsRuntime)
95       .utf8(jsRuntime);
96     names[i] = propertyName;
97   }
98 
99   return names;
100 }
101 
102 jni::local_ref<jni::JArrayClass<jstring>> JavaScriptObject::jniGetPropertyNames() {
103   std::vector<std::string> cResult = getPropertyNames();
104   auto paredResult = jni::JArrayClass<jstring>::newArray(cResult.size());
105   for (size_t i = 0; i < cResult.size(); i++) {
106     paredResult->setElement(i, jni::make_jstring(cResult[i]).get());
107   }
108 
109   return paredResult;
110 }
111 
112 jni::local_ref<JavaScriptFunction::javaobject> JavaScriptObject::jniAsFunction() {
113   auto &jsRuntime = runtimeHolder.getJSRuntime();
114   auto jsFuncion = std::make_shared<jsi::Function>(jsObject->asFunction(jsRuntime));
115   return JavaScriptFunction::newInstance(runtimeHolder.getModuleRegistry(), runtimeHolder, jsFuncion);
116 }
117 
118 void JavaScriptObject::setProperty(const std::string &name, jsi::Value value) {
119   auto &jsRuntime = runtimeHolder.getJSRuntime();
120   jsObject->setProperty(jsRuntime, name.c_str(), value);
121 }
122 
123 void JavaScriptObject::unsetProperty(jni::alias_ref<jstring> name) {
124   auto &jsRuntime = runtimeHolder.getJSRuntime();
125   auto cName = name->toStdString();
126   jsObject->setProperty(
127     jsRuntime,
128     cName.c_str(),
129     jsi::Value::undefined()
130   );
131 }
132 
133 jsi::Object JavaScriptObject::preparePropertyDescriptor(
134   jsi::Runtime &jsRuntime,
135   int options
136 ) {
137   jsi::Object descriptor(jsRuntime);
138   descriptor.setProperty(jsRuntime, "configurable", (bool) ((1 << 0) & options));
139   descriptor.setProperty(jsRuntime, "enumerable", (bool) ((1 << 1) & options));
140   if ((bool) (1 << 2 & options)) {
141     descriptor.setProperty(jsRuntime, "writable", true);
142   }
143   return descriptor;
144 }
145 
146 void JavaScriptObject::defineProperty(
147   jsi::Runtime &runtime,
148   jsi::Object *jsthis,
149   const std::string &name,
150   jsi::Object descriptor
151 ) {
152   jsi::Object global = runtime.global();
153   jsi::Object objectClass = global.getPropertyAsObject(runtime, "Object");
154   jsi::Function definePropertyFunction = objectClass.getPropertyAsFunction(
155     runtime,
156     "defineProperty"
157   );
158 
159   // This call is basically the same as `Object.defineProperty(object, name, descriptor)` in JS
160   definePropertyFunction.callWithThis(runtime, objectClass, {
161     jsi::Value(runtime, *jsthis),
162     jsi::String::createFromUtf8(runtime, name),
163     std::move(descriptor),
164   });
165 }
166 
167 jni::local_ref<JavaScriptObject::javaobject> JavaScriptObject::newInstance(
168   JSIInteropModuleRegistry *jsiInteropModuleRegistry,
169   std::weak_ptr<JavaScriptRuntime> runtime,
170   std::shared_ptr<jsi::Object> jsObject
171 ) {
172   auto object = JavaScriptObject::newObjectCxxArgs(std::move(runtime), std::move(jsObject));
173   jsiInteropModuleRegistry->jniDeallocator->addReference(object);
174   return object;
175 }
176 
177 void JavaScriptObject::defineNativeDeallocator(
178   jni::alias_ref<JNIFunctionBody::javaobject> deallocator
179 ) {
180   auto &rt = runtimeHolder.getJSRuntime();
181   jni::global_ref<JNIFunctionBody::javaobject> globalRef = jni::make_global(deallocator);
182   std::shared_ptr<ObjectDeallocator> nativeDeallocator = std::make_shared<ObjectDeallocator>(
183     [globalRef = std::move(globalRef)]() mutable {
184       auto args = jni::Environment::current()->NewObjectArray(
185         0,
186         JavaReferencesCache::instance()->getJClass("java/lang/Object").clazz,
187         nullptr
188       );
189       globalRef->invoke(args);
190       globalRef.reset();
191     });
192   auto descriptor = JavaScriptObject::preparePropertyDescriptor(rt, 0);
193   descriptor.setProperty(rt, "value", jsi::Object::createFromHostObject(rt, nativeDeallocator));
194   jsObject->setProperty(rt, "__expo_shared_object_deallocator__", std::move(descriptor));
195 }
196 } // namespace expo
197