1 #include <cxxabi.h>
2 #include <utility>
3
4 #include "FrozenObject.h"
5 #include "MutableValue.h"
6 #include "MutableValueSetterProxy.h"
7 #include "RemoteObject.h"
8 #include "RuntimeDecorator.h"
9 #include "RuntimeManager.h"
10 #include "ShareableValue.h"
11 #include "SharedParent.h"
12
13 namespace reanimated {
14 class ShareableValue;
15 const char *HIDDEN_HOST_OBJECT_PROP = "__reanimatedHostObjectRef";
16 const char *ALREADY_CONVERTED = "__alreadyConverted";
17 const char *CALL_ASYNC = "__callAsync";
18 const char *PRIMAL_FUNCTION = "__primalFunction";
19 const char *CALLBACK_ERROR_SUFFIX =
20 "\n\nPossible solutions are:\n"
21 "a) If you want to synchronously execute this method, mark it as a Worklet\n"
22 "b) If you want to execute this method on the JS thread, wrap it using runOnJS";
23
addHiddenProperty(jsi::Runtime & rt,jsi::Value && value,const jsi::Object & obj,const char * name)24 void addHiddenProperty(
25 jsi::Runtime &rt,
26 jsi::Value &&value,
27 const jsi::Object &obj,
28 const char *name) {
29 jsi::Object globalObject = rt.global().getPropertyAsObject(rt, "Object");
30 jsi::Function defineProperty =
31 globalObject.getPropertyAsFunction(rt, "defineProperty");
32 jsi::String internalPropName = jsi::String::createFromUtf8(rt, name);
33 jsi::Object paramForDefineProperty(rt);
34 paramForDefineProperty.setProperty(rt, "enumerable", false);
35 paramForDefineProperty.setProperty(rt, "value", value);
36 defineProperty.call(rt, obj, internalPropName, paramForDefineProperty);
37 }
38
freeze(jsi::Runtime & rt,const jsi::Object & obj)39 void freeze(jsi::Runtime &rt, const jsi::Object &obj) {
40 jsi::Object globalObject = rt.global().getPropertyAsObject(rt, "Object");
41 jsi::Function freeze = globalObject.getPropertyAsFunction(rt, "freeze");
42 freeze.call(rt, obj);
43 }
44
adaptCache(jsi::Runtime & rt,const jsi::Value & value)45 void ShareableValue::adaptCache(jsi::Runtime &rt, const jsi::Value &value) {
46 // when adapting from host object we can assign cached value immediately such
47 // that we avoid running `toJSValue` in the future when given object is
48 // accessed
49 if (RuntimeDecorator::isWorkletRuntime(rt)) {
50 if (remoteValue.expired()) {
51 remoteValue = getWeakRef(rt);
52 }
53 (*remoteValue.lock()) = jsi::Value(rt, value);
54 } else {
55 hostValue = std::make_unique<jsi::Value>(rt, value);
56 }
57 }
58
adapt(jsi::Runtime & rt,const jsi::Value & value,ValueType objectType)59 void ShareableValue::adapt(
60 jsi::Runtime &rt,
61 const jsi::Value &value,
62 ValueType objectType) {
63 if (value.isObject()) {
64 jsi::Object object = value.asObject(rt);
65 jsi::Value hiddenValue = object.getProperty(rt, HIDDEN_HOST_OBJECT_PROP);
66 if (!(hiddenValue.isUndefined())) {
67 jsi::Object hiddenProperty = hiddenValue.asObject(rt);
68 if (hiddenProperty.isHostObject<FrozenObject>(rt)) {
69 type = ValueType::FrozenObjectType;
70 if (object.hasProperty(rt, "__workletHash") && object.isFunction(rt)) {
71 type = ValueType::WorkletFunctionType;
72 }
73 valueContainer = std::make_unique<FrozenObjectWrapper>(
74 hiddenProperty.getHostObject<FrozenObject>(rt));
75 if (object.hasProperty(rt, ALREADY_CONVERTED)) {
76 adaptCache(rt, value);
77 }
78 return;
79 }
80 }
81 }
82
83 if (objectType == ValueType::MutableValueType) {
84 type = ValueType::MutableValueType;
85 valueContainer =
86 std::make_unique<MutableValueWrapper>(std::make_shared<MutableValue>(
87 rt, value, runtimeManager, runtimeManager->scheduler));
88 } else if (value.isUndefined()) {
89 type = ValueType::UndefinedType;
90 } else if (value.isNull()) {
91 type = ValueType::NullType;
92 } else if (value.isBool()) {
93 type = ValueType::BoolType;
94 valueContainer = std::make_unique<BooleanValueWrapper>(value.getBool());
95 } else if (value.isNumber()) {
96 type = ValueType::NumberType;
97 valueContainer = std::make_unique<NumberValueWrapper>(value.asNumber());
98 } else if (value.isString()) {
99 type = ValueType::StringType;
100 valueContainer =
101 std::make_unique<StringValueWrapper>(value.asString(rt).utf8(rt));
102 } else if (value.isObject()) {
103 auto object = value.asObject(rt);
104 if (object.isFunction(rt)) {
105 if (object.getProperty(rt, "__workletHash").isUndefined()) {
106 // not a worklet, we treat this as a host function
107 type = ValueType::HostFunctionType;
108 containsHostFunction = true;
109
110 // Check if it's a hostFunction wrapper
111 jsi::Value primalFunction = object.getProperty(rt, PRIMAL_FUNCTION);
112 if (!primalFunction.isUndefined()) {
113 jsi::Object handlerAsObject = primalFunction.asObject(rt);
114 std::shared_ptr<HostFunctionHandler> handler =
115 handlerAsObject.getHostObject<HostFunctionHandler>(rt);
116 valueContainer = std::make_unique<HostFunctionWrapper>(handler);
117 } else {
118 valueContainer = std::make_unique<HostFunctionWrapper>(
119 std::make_shared<HostFunctionHandler>(
120 std::make_shared<jsi::Function>(object.asFunction(rt)), rt));
121 }
122
123 } else {
124 // a worklet
125 type = ValueType::WorkletFunctionType;
126 valueContainer = std::make_unique<FrozenObjectWrapper>(
127 std::make_shared<FrozenObject>(rt, object, runtimeManager));
128 auto &frozenObject = ValueWrapper::asFrozenObject(valueContainer);
129 containsHostFunction |= frozenObject->containsHostFunction;
130 if (RuntimeDecorator::isReactRuntime(rt) && !containsHostFunction) {
131 addHiddenProperty(
132 rt,
133 createHost(rt, frozenObject),
134 object,
135 HIDDEN_HOST_OBJECT_PROP);
136 }
137 }
138 } else if (object.isArray(rt)) {
139 type = ValueType::FrozenArrayType;
140 auto array = object.asArray(rt);
141 valueContainer = std::make_unique<FrozenArrayWrapper>();
142 auto &frozenArray = ValueWrapper::asFrozenArray(valueContainer);
143 for (size_t i = 0, size = array.size(rt); i < size; i++) {
144 auto sv = adapt(rt, array.getValueAtIndex(rt, i), runtimeManager);
145 containsHostFunction |= sv->containsHostFunction;
146 frozenArray.push_back(sv);
147 }
148 } else if (object.isHostObject<MutableValue>(rt)) {
149 type = ValueType::MutableValueType;
150 valueContainer = std::make_unique<MutableValueWrapper>(
151 object.getHostObject<MutableValue>(rt));
152 adaptCache(rt, value);
153 } else if (object.isHostObject<RemoteObject>(rt)) {
154 type = ValueType::RemoteObjectType;
155 valueContainer = std::make_unique<RemoteObjectWrapper>(
156 object.getHostObject<RemoteObject>(rt));
157 adaptCache(rt, value);
158 } else if (objectType == ValueType::RemoteObjectType) {
159 type = ValueType::RemoteObjectType;
160 valueContainer =
161 std::make_unique<RemoteObjectWrapper>(std::make_shared<RemoteObject>(
162 rt, object, runtimeManager, runtimeManager->scheduler));
163 } else {
164 // create frozen object based on a copy of a given object
165 type = ValueType::FrozenObjectType;
166 valueContainer = std::make_unique<FrozenObjectWrapper>(
167 std::make_shared<FrozenObject>(rt, object, runtimeManager));
168 auto &frozenObject = ValueWrapper::asFrozenObject(valueContainer);
169 containsHostFunction |= frozenObject->containsHostFunction;
170 if (RuntimeDecorator::isReactRuntime(rt)) {
171 if (!containsHostFunction) {
172 addHiddenProperty(
173 rt,
174 createHost(rt, frozenObject),
175 object,
176 HIDDEN_HOST_OBJECT_PROP);
177 }
178 freeze(rt, object);
179 }
180 }
181 } else if (value.isSymbol()) {
182 type = ValueType::StringType;
183 valueContainer =
184 std::make_unique<StringValueWrapper>(value.asSymbol(rt).toString(rt));
185 } else {
186 throw "Invalid value type";
187 }
188 }
189
adapt(jsi::Runtime & rt,const jsi::Value & value,RuntimeManager * runtimeManager,ValueType valueType)190 std::shared_ptr<ShareableValue> ShareableValue::adapt(
191 jsi::Runtime &rt,
192 const jsi::Value &value,
193 RuntimeManager *runtimeManager,
194 ValueType valueType) {
195 auto sv = std::shared_ptr<ShareableValue>(
196 new ShareableValue(runtimeManager, runtimeManager->scheduler));
197 sv->adapt(rt, value, valueType);
198 return sv;
199 }
200
getValue(jsi::Runtime & rt)201 jsi::Value ShareableValue::getValue(jsi::Runtime &rt) {
202 // TODO: maybe we can cache toJSValue results on a per-runtime basis, need to
203 // avoid ref loops
204 if (&rt == runtimeManager->runtime.get()) {
205 // Getting value on the same runtime where it was created, prepare
206 // remoteValue
207 if (remoteValue.expired()) {
208 remoteValue = getWeakRef(rt);
209 }
210
211 if (remoteValue.lock()->isUndefined()) {
212 (*remoteValue.lock()) = toJSValue(rt);
213 }
214 return jsi::Value(rt, *remoteValue.lock());
215 } else {
216 // Getting value on a different runtime than where it was created from,
217 // prepare hostValue
218 if (hostValue.get() == nullptr) {
219 hostValue = std::make_unique<jsi::Value>(toJSValue(rt));
220 }
221 return jsi::Value(rt, *hostValue);
222 }
223 }
224
createHost(jsi::Runtime & rt,std::shared_ptr<jsi::HostObject> host)225 jsi::Object ShareableValue::createHost(
226 jsi::Runtime &rt,
227 std::shared_ptr<jsi::HostObject> host) {
228 return jsi::Object::createFromHostObject(rt, host);
229 }
230
createFrozenWrapper(jsi::Runtime & rt,std::shared_ptr<FrozenObject> frozenObject)231 jsi::Value createFrozenWrapper(
232 jsi::Runtime &rt,
233 std::shared_ptr<FrozenObject> frozenObject) {
234 jsi::Object __reanimatedHiddenHost =
235 jsi::Object::createFromHostObject(rt, frozenObject);
236 jsi::Object obj = frozenObject->shallowClone(rt);
237 jsi::Object globalObject = rt.global().getPropertyAsObject(rt, "Object");
238 jsi::Function freeze = globalObject.getPropertyAsFunction(rt, "freeze");
239 if (!frozenObject->containsHostFunction) {
240 addHiddenProperty(
241 rt, std::move(__reanimatedHiddenHost), obj, HIDDEN_HOST_OBJECT_PROP);
242 addHiddenProperty(rt, true, obj, ALREADY_CONVERTED);
243 }
244 return freeze.call(rt, obj);
245 }
246
toJSValue(jsi::Runtime & rt)247 jsi::Value ShareableValue::toJSValue(jsi::Runtime &rt) {
248 switch (type) {
249 case ValueType::UndefinedType:
250 return jsi::Value::undefined();
251 case ValueType::NullType:
252 return jsi::Value::null();
253 case ValueType::BoolType:
254 return jsi::Value(ValueWrapper::asBoolean(valueContainer));
255 case ValueType::NumberType:
256 return jsi::Value(ValueWrapper::asNumber(valueContainer));
257 case ValueType::StringType: {
258 auto &stringValue = ValueWrapper::asString(valueContainer);
259 return jsi::Value(rt, jsi::String::createFromUtf8(rt, stringValue));
260 }
261 case ValueType::FrozenObjectType: {
262 auto &frozenObject = ValueWrapper::asFrozenObject(valueContainer);
263 return createFrozenWrapper(rt, frozenObject);
264 }
265 case ValueType::FrozenArrayType: {
266 auto &frozenArray = ValueWrapper::asFrozenArray(valueContainer);
267 jsi::Array array(rt, frozenArray.size());
268 for (size_t i = 0; i < frozenArray.size(); i++) {
269 array.setValueAtIndex(rt, i, frozenArray[i]->toJSValue(rt));
270 }
271 return array;
272 }
273 case ValueType::RemoteObjectType: {
274 auto &remoteObject = ValueWrapper::asRemoteObject(valueContainer);
275 if (RuntimeDecorator::isWorkletRuntime(rt)) {
276 remoteObject->maybeInitializeOnWorkletRuntime(rt);
277 }
278 return createHost(rt, remoteObject);
279 }
280 case ValueType::MutableValueType: {
281 auto &mutableObject = ValueWrapper::asMutableValue(valueContainer);
282 return createHost(rt, mutableObject);
283 }
284 case ValueType::HostFunctionType: {
285 auto hostFunctionWrapper =
286 ValueWrapper::asHostFunctionWrapper(valueContainer);
287 auto &hostRuntime = hostFunctionWrapper->value->hostRuntime;
288 if (hostRuntime == &rt) {
289 // function is accessed from the same runtime it was crated, we just
290 // return same function obj
291 return jsi::Value(
292 rt, *hostFunctionWrapper->value->getPureFunction().get());
293 } else {
294 // function is accessed from a different runtime, we wrap function in
295 // host func that'd enqueue call on an appropriate thread
296
297 auto runtimeManager = this->runtimeManager;
298 auto hostFunction = hostFunctionWrapper->value;
299
300 auto warnFunction = [runtimeManager, hostFunction](
301 jsi::Runtime &rt,
302 const jsi::Value &thisValue,
303 const jsi::Value *args,
304 size_t count) -> jsi::Value {
305 jsi::Value jsThis = rt.global().getProperty(rt, "jsThis");
306 std::string workletLocation = jsThis.asObject(rt)
307 .getProperty(rt, "__location")
308 .toString(rt)
309 .utf8(rt);
310 std::string exceptionMessage = "Tried to synchronously call ";
311 if (hostFunction->functionName.empty()) {
312 exceptionMessage += "anonymous function";
313 } else {
314 exceptionMessage += "function {" + hostFunction->functionName + "}";
315 }
316 exceptionMessage +=
317 " from a different thread.\n\nOccurred in worklet location: ";
318 exceptionMessage += workletLocation;
319 exceptionMessage += CALLBACK_ERROR_SUFFIX;
320 runtimeManager->errorHandler->setError(exceptionMessage);
321 runtimeManager->errorHandler->raise();
322
323 return jsi::Value::undefined();
324 };
325
326 auto clb = [runtimeManager, hostFunction, hostRuntime](
327 jsi::Runtime &rt,
328 const jsi::Value &thisValue,
329 const jsi::Value *args,
330 size_t count) -> jsi::Value {
331 // TODO: we should find thread based on runtime such that we could
332 // also call UI methods from RN and not only RN methods from UI
333
334 std::vector<std::shared_ptr<ShareableValue>> params;
335 for (int i = 0; i < count; ++i) {
336 params.push_back(
337 ShareableValue::adapt(rt, args[i], runtimeManager));
338 }
339
340 std::function<void()> job = [hostFunction, hostRuntime, params] {
341 jsi::Value *args = new jsi::Value[params.size()];
342 for (int i = 0; i < params.size(); ++i) {
343 args[i] = params[i]->getValue(*hostRuntime);
344 }
345 jsi::Value returnedValue =
346 hostFunction->getPureFunction().get()->call(
347 *hostRuntime,
348 static_cast<const jsi::Value *>(args),
349 static_cast<size_t>(params.size()));
350
351 delete[] args;
352 // ToDo use returned value to return promise
353 };
354
355 runtimeManager->scheduler->scheduleOnJS(job);
356 return jsi::Value::undefined();
357 };
358 jsi::Function wrapperFunction = jsi::Function::createFromHostFunction(
359 rt, jsi::PropNameID::forAscii(rt, "hostFunction"), 0, warnFunction);
360 jsi::Function res = jsi::Function::createFromHostFunction(
361 rt, jsi::PropNameID::forAscii(rt, "hostFunction"), 0, clb);
362 addHiddenProperty(rt, std::move(res), wrapperFunction, CALL_ASYNC);
363 jsi::Object functionHandler =
364 createHost(rt, hostFunctionWrapper->value);
365 addHiddenProperty(
366 rt, std::move(functionHandler), wrapperFunction, PRIMAL_FUNCTION);
367 return wrapperFunction;
368 }
369 }
370 case ValueType::WorkletFunctionType: {
371 auto runtimeManager = this->runtimeManager;
372 auto &frozenObject = ValueWrapper::asFrozenObject(this->valueContainer);
373 if (RuntimeDecorator::isWorkletRuntime(rt)) {
374 // when running on worklet thread we prep a function
375
376 auto jsThis = std::make_shared<jsi::Object>(
377 frozenObject->shallowClone(*runtimeManager->runtime));
378 std::shared_ptr<jsi::Function> funPtr(
379 runtimeManager->workletsCache->getFunction(rt, frozenObject));
380 auto name = funPtr->getProperty(rt, "name").asString(rt).utf8(rt);
381
382 auto clb = [=](jsi::Runtime &rt,
383 const jsi::Value &thisValue,
384 const jsi::Value *args,
385 size_t count) mutable -> jsi::Value {
386 const jsi::String jsThisName =
387 jsi::String::createFromAscii(rt, "jsThis");
388 jsi::Object global = rt.global();
389 jsi::Value oldJSThis = global.getProperty(rt, jsThisName);
390 global.setProperty(rt, jsThisName, *jsThis); // set jsThis
391
392 jsi::Value res = jsi::Value::undefined();
393 try {
394 if (thisValue.isObject()) {
395 res =
396 funPtr->callWithThis(rt, thisValue.asObject(rt), args, count);
397 } else {
398 res = funPtr->call(rt, args, count);
399 }
400 } catch (std::exception &e) {
401 std::string str = e.what();
402 runtimeManager->errorHandler->setError(str);
403 runtimeManager->errorHandler->raise();
404 } catch (...) {
405 if (demangleExceptionName(
406 abi::__cxa_current_exception_type()->name()) ==
407 "facebook::jsi::JSError") {
408 throw jsi::JSError(rt, "Javascript worklet error");
409 }
410 // TODO find out a way to get the error's message on hermes
411 jsi::Value location = jsThis->getProperty(rt, "__location");
412 std::string str = "Javascript worklet error";
413 if (location.isString()) {
414 str += "\nIn file: " + location.asString(rt).utf8(rt);
415 }
416 runtimeManager->errorHandler->setError(str);
417 runtimeManager->errorHandler->raise();
418 }
419 global.setProperty(rt, jsThisName, oldJSThis); // clean jsThis
420 return res;
421 };
422 return jsi::Function::createFromHostFunction(
423 rt, jsi::PropNameID::forAscii(rt, name.c_str()), 0, clb);
424 } else {
425 // when run outside of UI thread we enqueue a call on the UI thread
426 auto clb = [=](jsi::Runtime &rt,
427 const jsi::Value &thisValue,
428 const jsi::Value *args,
429 size_t count) -> jsi::Value {
430 // TODO: we should find thread based on runtime such that we could
431 // also call UI methods from RN and not only RN methods from UI
432
433 std::vector<std::shared_ptr<ShareableValue>> params;
434 for (int i = 0; i < count; ++i) {
435 params.push_back(
436 ShareableValue::adapt(rt, args[i], runtimeManager));
437 }
438
439 runtimeManager->scheduler->scheduleOnUI([=] {
440 jsi::Runtime &rt = *runtimeManager->runtime.get();
441 auto jsThis = createFrozenWrapper(rt, frozenObject).getObject(rt);
442 auto code =
443 jsThis.getProperty(rt, "asString").asString(rt).utf8(rt);
444 std::shared_ptr<jsi::Function> funPtr(
445 runtimeManager->workletsCache->getFunction(rt, frozenObject));
446
447 jsi::Value *args = new jsi::Value[params.size()];
448 for (int i = 0; i < params.size(); ++i) {
449 args[i] = params[i]->getValue(rt);
450 }
451
452 jsi::Value returnedValue;
453 const jsi::String jsThisName =
454 jsi::String::createFromAscii(rt, "jsThis");
455 jsi::Object global = rt.global();
456 jsi::Value oldJSThis = global.getProperty(rt, jsThisName);
457 global.setProperty(rt, jsThisName, jsThis); // set jsThis
458 try {
459 returnedValue = funPtr->call(
460 rt,
461 static_cast<const jsi::Value *>(args),
462 static_cast<size_t>(params.size()));
463 } catch (std::exception &e) {
464 std::string str = e.what();
465 runtimeManager->errorHandler->setError(str);
466 runtimeManager->errorHandler->raise();
467 } catch (...) {
468 if (demangleExceptionName(
469 abi::__cxa_current_exception_type()->name()) ==
470 "facebook::jsi::JSError") {
471 throw jsi::JSError(rt, "Javascript worklet error");
472 }
473 // TODO find out a way to get the error's message on hermes
474 jsi::Value location = jsThis.getProperty(rt, "__location");
475 std::string str = "Javascript worklet error";
476 if (location.isString()) {
477 str += "\nIn file: " + location.asString(rt).utf8(rt);
478 }
479 runtimeManager->errorHandler->setError(str);
480 runtimeManager->errorHandler->raise();
481 }
482 global.setProperty(rt, jsThisName, oldJSThis); // clean jsThis
483
484 delete[] args;
485 // ToDo use returned value to return promise
486 });
487 return jsi::Value::undefined();
488 };
489 return jsi::Function::createFromHostFunction(
490 rt, jsi::PropNameID::forAscii(rt, "_workletFunction"), 0, clb);
491 }
492 }
493 default: {
494 throw "Unable to find conversion method for this type";
495 }
496 }
497 throw "convert error";
498 }
499
demangleExceptionName(std::string toDemangle)500 std::string ShareableValue::demangleExceptionName(std::string toDemangle) {
501 int status = 0;
502 char *buff =
503 __cxxabiv1::__cxa_demangle(toDemangle.c_str(), nullptr, nullptr, &status);
504 if (!buff) {
505 return toDemangle;
506 }
507 std::string demangled = buff;
508 std::free(buff);
509 return demangled;
510 }
511
512 } // namespace reanimated
513