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