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 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 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 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 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 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 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 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 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 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 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