#include "Shareables.h" using namespace facebook; namespace reanimated { jsi::Function getValueUnpacker(jsi::Runtime &rt) { auto valueUnpacker = rt.global().getProperty(rt, "__valueUnpacker"); assert(valueUnpacker.isObject() && "valueUnpacker not found"); return valueUnpacker.asObject(rt).asFunction(rt); } #ifndef NDEBUG static const auto callGuardLambda = [](facebook::jsi::Runtime &rt, const facebook::jsi::Value &thisVal, const facebook::jsi::Value *args, size_t count) { return args[0].asObject(rt).asFunction(rt).call(rt, args + 1, count - 1); }; jsi::Function getCallGuard(jsi::Runtime &rt) { auto callGuard = rt.global().getProperty(rt, "__callGuardDEV"); if (callGuard.isObject()) { // Use JS implementation if `__callGuardDEV` has already been installed. // This is the desired behavior. return callGuard.asObject(rt).asFunction(rt); } // Otherwise, fallback to C++ JSI implementation. This is necessary so that we // can install `__callGuardDEV` itself and should happen only once. Note that // the C++ implementation doesn't intercept errors and simply throws them as // C++ exceptions which crashes the app. We assume that installing the guard // doesn't throw any errors. return jsi::Function::createFromHostFunction( rt, jsi::PropNameID::forAscii(rt, "callGuard"), 1, callGuardLambda); } #endif // NDEBUG jsi::Value makeShareableClone( jsi::Runtime &rt, const jsi::Value &value, const jsi::Value &shouldRetainRemote) { std::shared_ptr shareable; if (value.isObject()) { auto object = value.asObject(rt); if (!object.getProperty(rt, "__workletHash").isUndefined()) { shareable = std::make_shared(rt, object); } else if (!object.getProperty(rt, "__init").isUndefined()) { shareable = std::make_shared(rt, object); } else if (object.isFunction(rt)) { auto function = object.asFunction(rt); if (function.isHostFunction(rt)) { shareable = std::make_shared(rt, std::move(function)); } else { shareable = std::make_shared(rt, std::move(function)); } } else if (object.isArray(rt)) { if (shouldRetainRemote.isBool() && shouldRetainRemote.getBool()) { shareable = std::make_shared>( rt, object.asArray(rt)); } else { shareable = std::make_shared(rt, object.asArray(rt)); } } else if (object.isArrayBuffer(rt)) { shareable = std::make_shared(rt, object.getArrayBuffer(rt)); } else if (object.isHostObject(rt)) { if (object.isHostObject(rt)) { return object; } shareable = std::make_shared(rt, object.getHostObject(rt)); } else { if (shouldRetainRemote.isBool() && shouldRetainRemote.getBool()) { shareable = std::make_shared>(rt, object); } else { shareable = std::make_shared(rt, object); } } } else if (value.isString()) { shareable = std::make_shared(value.asString(rt).utf8(rt)); } else if (value.isUndefined()) { shareable = std::make_shared(); } else if (value.isNull()) { shareable = std::make_shared(nullptr); } else if (value.isBool()) { shareable = std::make_shared(value.getBool()); } else if (value.isNumber()) { shareable = std::make_shared(value.getNumber()); #if REACT_NATIVE_MINOR_VERSION >= 71 } else if (value.isBigInt()) { shareable = std::make_shared(rt, value.getBigInt(rt)); #endif } else if (value.isSymbol()) { // TODO: this is only a placeholder implementation, here we replace symbols // with strings in order to make certain objects to be captured. There isn't // yet any usecase for using symbols on the UI runtime so it is fine to keep // it like this for now. shareable = std::make_shared(value.getSymbol(rt).toString(rt)); } else { throw std::runtime_error( "[Reanimated] Attempted to convert an unsupported value type."); } return ShareableJSRef::newHostObject(rt, shareable); } std::shared_ptr extractShareableOrThrow( jsi::Runtime &rt, const jsi::Value &maybeShareableValue, const std::string &errorMessage) { if (maybeShareableValue.isObject()) { auto object = maybeShareableValue.asObject(rt); if (object.isHostObject(rt)) { return object.getHostObject(rt)->value(); } throw std::runtime_error( "[Reanimated] Attempted to extract from a HostObject that wasn't converted to a Shareable."); } else if (maybeShareableValue.isUndefined()) { return Shareable::undefined(); } throw std::runtime_error(errorMessage); } Shareable::~Shareable() {} std::shared_ptr Shareable::undefined() { static auto undefined = std::make_shared(); return undefined; } template jsi::Value RetainingShareable::getJSValue(jsi::Runtime &rt) { if (&rt == primaryRuntime_) { // TODO: it is suboptimal to generate new object every time getJS is // called on host runtime – the objects we are generating already exists // and we should possibly just grab a hold of such object and use it here // instead of creating a new JS representation. As far as I understand the // only case where it can be realistically called this way is when a // shared value is created and then accessed on the same runtime return BaseClass::toJSValue(rt); } if (secondaryValue_ == nullptr) { auto value = BaseClass::toJSValue(rt); secondaryValue_ = std::make_unique(rt, value); secondaryRuntime_ = &rt; return value; } if (&rt == secondaryRuntime_) { return jsi::Value(rt, *secondaryValue_); } return BaseClass::toJSValue(rt); } ShareableJSRef::~ShareableJSRef() {} ShareableArray::ShareableArray(jsi::Runtime &rt, const jsi::Array &array) : Shareable(ArrayType) { auto size = array.size(rt); data_.reserve(size); for (size_t i = 0; i < size; i++) { data_.push_back(extractShareableOrThrow(rt, array.getValueAtIndex(rt, i))); } } jsi::Value ShareableArray::toJSValue(jsi::Runtime &rt) { auto size = data_.size(); auto ary = jsi::Array(rt, size); for (size_t i = 0; i < size; i++) { ary.setValueAtIndex(rt, i, data_[i]->getJSValue(rt)); } return ary; } jsi::Value ShareableArrayBuffer::toJSValue(jsi::Runtime &rt) { auto size = static_cast(data_.size()); auto arrayBuffer = rt.global() .getPropertyAsFunction(rt, "ArrayBuffer") .callAsConstructor(rt, size) .getObject(rt) .getArrayBuffer(rt); memcpy(arrayBuffer.data(rt), data_.data(), size); return arrayBuffer; } ShareableObject::ShareableObject(jsi::Runtime &rt, const jsi::Object &object) : Shareable(ObjectType) { auto propertyNames = object.getPropertyNames(rt); auto size = propertyNames.size(rt); data_.reserve(size); for (size_t i = 0; i < size; i++) { auto key = propertyNames.getValueAtIndex(rt, i).asString(rt); auto value = extractShareableOrThrow(rt, object.getProperty(rt, key)); data_.emplace_back(key.utf8(rt), value); } } jsi::Value ShareableObject::toJSValue(jsi::Runtime &rt) { auto obj = jsi::Object(rt); for (size_t i = 0, size = data_.size(); i < size; i++) { obj.setProperty( rt, data_[i].first.c_str(), data_[i].second->getJSValue(rt)); } return obj; } jsi::Value ShareableHostObject::toJSValue(jsi::Runtime &rt) { return jsi::Object::createFromHostObject(rt, hostObject_); } jsi::Value ShareableHostFunction::toJSValue(jsi::Runtime &rt) { return jsi::Function::createFromHostFunction( rt, jsi::PropNameID::forUtf8(rt, name_), paramCount_, hostFunction_); } jsi::Value ShareableWorklet::toJSValue(jsi::Runtime &rt) { assert( std::any_of( data_.cbegin(), data_.cend(), [](const auto &item) { return item.first == "__workletHash"; }) && "ShareableWorklet doesn't have `__workletHash` property"); jsi::Value obj = ShareableObject::toJSValue(rt); return getValueUnpacker(rt).call(rt, obj); } jsi::Value ShareableRemoteFunction::toJSValue(jsi::Runtime &rt) { if (&rt == runtime_) { return jsi::Value(rt, *function_); } else { #ifndef NDEBUG return getValueUnpacker(rt).call( rt, ShareableJSRef::newHostObject(rt, shared_from_this()), jsi::String::createFromAscii(rt, "RemoteFunction")); #else return ShareableJSRef::newHostObject(rt, shared_from_this()); #endif } } jsi::Value ShareableHandle::toJSValue(jsi::Runtime &rt) { if (initializer_ != nullptr) { auto initObj = initializer_->getJSValue(rt); remoteValue_ = std::make_unique(getValueUnpacker(rt).call(rt, initObj)); remoteRuntime_ = &rt; initializer_ = nullptr; // we can release ref to initializer as this // method should be called at most once } return jsi::Value(rt, *remoteValue_); } jsi::Value ShareableString::toJSValue(jsi::Runtime &rt) { return jsi::String::createFromUtf8(rt, data_); } #if REACT_NATIVE_MINOR_VERSION >= 71 jsi::Value ShareableBigInt::toJSValue(jsi::Runtime &rt) { return rt.global() .getPropertyAsFunction(rt, "BigInt") .call(rt, jsi::String::createFromUtf8(rt, string_)); } #endif jsi::Value ShareableScalar::toJSValue(jsi::Runtime &) { switch (valueType_) { case Shareable::UndefinedType: return jsi::Value(); case Shareable::NullType: return jsi::Value(nullptr); case Shareable::BooleanType: return jsi::Value(data_.boolean); case Shareable::NumberType: return jsi::Value(data_.number); default: throw std::runtime_error( "[Reanimated] Attempted to convert object that's not of a scalar type."); } } } /* namespace reanimated */