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