first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,325 @@
#ifdef __APPLE__
#include <ExpoModulesJSI/JSIUtils.h>
#else
#include "JSIUtils.h"
#endif
#include "EventEmitter.h"
#include "LazyObject.h"
#include <cxxreact/ErrorUtils.h>
namespace expo::EventEmitter {
#pragma mark - Listeners
void Listeners::add(jsi::Runtime &runtime, const std::string& eventName, const jsi::Function &listener) noexcept {
listenersMap[eventName].emplace_back(runtime, listener);
}
void Listeners::remove(jsi::Runtime &runtime, const std::string& eventName, const jsi::Function &listener) noexcept {
if (!listenersMap.contains(eventName)) {
return;
}
jsi::Value listenerValue(runtime, listener);
listenersMap[eventName].remove_if([&](const jsi::Value &item) {
return jsi::Value::strictEquals(runtime, listenerValue, item);
});
}
void Listeners::removeAll(const std::string& eventName) noexcept {
if (listenersMap.contains(eventName)) {
listenersMap[eventName].clear();
}
}
void Listeners::clear() noexcept {
listenersMap.clear();
}
size_t Listeners::listenersCount(const std::string& eventName) noexcept {
if (!listenersMap.contains(eventName)) {
return 0;
}
return listenersMap[eventName].size();
}
void Listeners::call(jsi::Runtime &runtime, const std::string& eventName, const jsi::Object &thisObject, const jsi::Value *args, size_t count) noexcept {
if (!listenersMap.contains(eventName)) {
return;
}
ListenersList &listenersList = listenersMap[eventName];
size_t listSize = listenersList.size();
if (listSize == 0) {
// Nothing to call.
return;
}
if (listSize == 1) {
// The most common scenario just call the only listener.
try {
listenersList
.front()
.asObject(runtime)
.asFunction(runtime)
.callWithThis(runtime, thisObject, args, count);
} catch (jsi::JSError& error) {
facebook::react::handleJSError(runtime, error, false);
}
return;
}
// When there are more than one listener, we copy the list to a vector as the list may be modified during the loop.
std::vector<jsi::Function> listenersVector;
listenersVector.reserve(listSize);
// Copy listeners to vector already as jsi::Function so we don't additionally copy jsi::Value
for (const jsi::Value &listener : listenersList) {
listenersVector.push_back(listener.asObject(runtime).asFunction(runtime));
}
// Call listeners from the vector. The list can be modified by the listeners but it will not affect this loop,
// i.e. newly added listeners will not be called and removed listeners will be called one last time.
// This is compliant with the EventEmitter in Node.js
for (const jsi::Function &listener : listenersVector) {
// As opposed to Node.js and fbemitter, when the listener throws an error the behavior is the same as on web.
// That is, it doesn't stop the execution of subsequent listeners and the error is not propagated to the `emit` function.
// The motivation behind this is that errors thrown from a module or user's code shouldn't affect other modules' behavior.
try {
listener.callWithThis(runtime, thisObject, args, count);
} catch (jsi::JSError& error) {
facebook::react::handleJSError(runtime, error, false);
}
}
}
#pragma mark - NativeState
NativeState::NativeState() : jsi::NativeState() {}
NativeState::~NativeState() {
listeners.clear();
}
NativeState::Shared NativeState::get(jsi::Runtime &runtime, const jsi::Object &object, bool createIfMissing) {
if (object.hasNativeState<NativeState>(runtime)) {
return object.getNativeState<NativeState>(runtime);
}
if (createIfMissing) {
NativeState::Shared state = std::make_shared<NativeState>();
object.setNativeState(runtime, state);
return state;
}
return nullptr;
}
#pragma mark - Utils
void callObservingFunction(jsi::Runtime &runtime, const jsi::Object &object, const char* functionName, const std::string& eventName) {
jsi::Value fnValue = object.getProperty(runtime, functionName);
if (!fnValue.isObject()) {
// Skip it if there is no observing function.
return;
}
fnValue
.getObject(runtime)
.asFunction(runtime)
.callWithThis(runtime, object, {
jsi::Value(runtime, jsi::String::createFromUtf8(runtime, eventName))
});
}
void addListener(jsi::Runtime &runtime, const jsi::Object &emitter, const std::string &eventName, const jsi::Function &listener) {
if (NativeState::Shared state = NativeState::get(runtime, emitter, true)) {
state->listeners.add(runtime, eventName, listener);
if (state->listeners.listenersCount(eventName) == 1) {
callObservingFunction(runtime, emitter, "__expo_onStartListeningToEvent", eventName);
callObservingFunction(runtime, emitter, "startObserving", eventName);
}
}
}
void removeListener(jsi::Runtime &runtime, const jsi::Object &emitter, const std::string &eventName, const jsi::Function &listener) {
if (NativeState::Shared state = NativeState::get(runtime, emitter, false)) {
size_t listenersCountBefore = state->listeners.listenersCount(eventName);
state->listeners.remove(runtime, eventName, listener);
if (listenersCountBefore >= 1 && state->listeners.listenersCount(eventName) == 0) {
callObservingFunction(runtime, emitter, "__expo_onStopListeningToEvent", eventName);
callObservingFunction(runtime, emitter, "stopObserving", eventName);
}
}
}
void removeAllListeners(jsi::Runtime &runtime, const jsi::Object &emitter, const std::string &eventName) {
if (NativeState::Shared state = NativeState::get(runtime, emitter, false)) {
size_t listenersCountBefore = state->listeners.listenersCount(eventName);
state->listeners.removeAll(eventName);
if (listenersCountBefore >= 1) {
callObservingFunction(runtime, emitter, "__expo_onStopListeningToEvent", eventName);
callObservingFunction(runtime, emitter, "stopObserving", eventName);
}
}
}
void emitEvent(jsi::Runtime &runtime, const jsi::Object &emitter, const std::string &eventName, const jsi::Value *args, size_t count) {
if (NativeState::Shared state = NativeState::get(runtime, emitter, false)) {
state->listeners.call(runtime, eventName, emitter, args, count);
}
}
size_t getListenerCount(jsi::Runtime &runtime, const jsi::Object &emitter, const std::string &eventName) {
if (NativeState::Shared state = NativeState::get(runtime, emitter, false)) {
return state->listeners.listenersCount(eventName);
}
return 0;
}
jsi::Value createEventSubscription(jsi::Runtime &runtime, const std::string &eventName, const jsi::Object &emitter, const jsi::Function &listener) {
jsi::Object subscription(runtime);
jsi::PropNameID removeProp = jsi::PropNameID::forAscii(runtime, "remove", 6);
std::shared_ptr<jsi::Value> emitterValue = std::make_shared<jsi::Value>(runtime, emitter);
std::shared_ptr<jsi::Value> listenerValue = std::make_shared<jsi::Value>(runtime, listener);
jsi::HostFunctionType removeSubscription = [eventName, emitterValue, listenerValue](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value {
jsi::Object emitter = emitterValue->getObject(runtime);
jsi::Function listener = listenerValue->getObject(runtime).getFunction(runtime);
removeListener(runtime, emitter, eventName, listener);
return jsi::Value::undefined();
};
subscription.setProperty(runtime, removeProp, jsi::Function::createFromHostFunction(runtime, removeProp, 0, removeSubscription));
return jsi::Value(runtime, subscription);
}
#pragma mark - Public API
void emitEvent(jsi::Runtime &runtime, jsi::Object &emitter, const std::string &eventName, const std::vector<jsi::Value> &arguments) {
emitEvent(runtime, emitter, eventName, arguments.data(), arguments.size());
}
jsi::Function getClass(jsi::Runtime &runtime) {
return common::getCoreObject(runtime)
.getPropertyAsFunction(runtime, "EventEmitter");
}
void installClass(jsi::Runtime &runtime) {
jsi::Function eventEmitterClass = common::createClass(runtime, "EventEmitter", [](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value {
// To provide backwards compatibility with the old EventEmitter where the native module object was passed as an argument.
// We're checking if the argument is already an instance of the new emitter and if so, just return it without unnecessarily wrapping it.
if (count > 0) {
// We need the tmp object to correctly unwrap the lazy object.
// For some reason, if we inline the retrieval of the first argument, the instanceOf check fails on Android.
// This is probably because the object is copied somewhere in the process.
const jsi::Object &tmp = args[0].asObject(runtime);
const jsi::Object &firstArg = LazyObject::unwrapObjectIfNecessary(runtime, tmp);
jsi::Function constructor = thisValue.getObject(runtime).getPropertyAsFunction(runtime, "constructor");
if (firstArg.instanceOf(runtime, constructor)) {
return jsi::Value(runtime, args[0]);
}
}
return jsi::Value(runtime, thisValue);
});
jsi::Object prototype = eventEmitterClass.getPropertyAsObject(runtime, "prototype");
jsi::HostFunctionType addListenerHost = [](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value {
std::string eventName = args[0].asString(runtime).utf8(runtime);
jsi::Function listener = args[1].asObject(runtime).asFunction(runtime);
jsi::Object thisObject = thisValue.getObject(runtime);
// `this` might be an object that is representing a host object, in which case it's not possible to get the native state.
// For native modules we need to unwrap it to get the object used under the hood by `LazyObject` host object.
const jsi::Object &emitter = LazyObject::unwrapObjectIfNecessary(runtime, thisObject);
addListener(runtime, emitter, eventName, listener);
return createEventSubscription(runtime, eventName, emitter, listener);
};
jsi::HostFunctionType removeListenerHost = [](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value {
std::string eventName = args[0].asString(runtime).utf8(runtime);
jsi::Function listener = args[1].asObject(runtime).asFunction(runtime);
jsi::Object thisObject = thisValue.getObject(runtime);
// Unwrap `this` object if it's a lazy object (e.g. native module).
const jsi::Object &emitter = LazyObject::unwrapObjectIfNecessary(runtime, thisObject);
removeListener(runtime, emitter, eventName, listener);
return jsi::Value::undefined();
};
jsi::HostFunctionType removeAllListenersHost = [](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value {
std::string eventName = args[0].asString(runtime).utf8(runtime);
jsi::Object thisObject = thisValue.getObject(runtime);
// Unwrap `this` object if it's a lazy object (e.g. native module).
const jsi::Object &emitter = LazyObject::unwrapObjectIfNecessary(runtime, thisObject);
removeAllListeners(runtime, emitter, eventName);
return jsi::Value::undefined();
};
jsi::HostFunctionType emit = [](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value {
std::string eventName = args[0].asString(runtime).utf8(runtime);
jsi::Object thisObject = thisValue.getObject(runtime);
// Unwrap `this` object if it's a lazy object (e.g. native module).
const jsi::Object &emitter = LazyObject::unwrapObjectIfNecessary(runtime, thisObject);
// Make a new pointer that skips the first argument which is the event name.
const jsi::Value *eventArgs = count > 1 ? &args[1] : nullptr;
emitEvent(runtime, emitter, eventName, eventArgs, count - 1);
return jsi::Value::undefined();
};
jsi::HostFunctionType listenerCountHost = [](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value {
std::string eventName = args[0].asString(runtime).utf8(runtime);
jsi::Object thisObject = thisValue.getObject(runtime);
// Unwrap `this` object if it's a lazy object (e.g. native module).
const jsi::Object &emitter = LazyObject::unwrapObjectIfNecessary(runtime, thisObject);
return jsi::Value((int)getListenerCount(runtime, emitter, eventName));
};
// Added for compatibility with the old EventEmitter API.
jsi::HostFunctionType removeSubscriptionHost = [](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value {
jsi::Object subscription = args[0].asObject(runtime);
subscription.getProperty(runtime, "remove")
.asObject(runtime)
.asFunction(runtime)
.callWithThis(runtime, subscription, {});
return jsi::Value::undefined();
};
jsi::PropNameID addListenerProp = jsi::PropNameID::forAscii(runtime, "addListener", 11);
jsi::PropNameID removeListenerProp = jsi::PropNameID::forAscii(runtime, "removeListener", 14);
jsi::PropNameID removeAllListenersProp = jsi::PropNameID::forAscii(runtime, "removeAllListeners", 18);
jsi::PropNameID emitProp = jsi::PropNameID::forAscii(runtime, "emit", 4);
jsi::PropNameID listenerCountProp = jsi::PropNameID::forAscii(runtime, "listenerCount", 13);
jsi::PropNameID removeSubscriptionProp = jsi::PropNameID::forAscii(runtime, "removeSubscription", 18);
prototype.setProperty(runtime, addListenerProp, jsi::Function::createFromHostFunction(runtime, addListenerProp, 2, addListenerHost));
prototype.setProperty(runtime, removeListenerProp, jsi::Function::createFromHostFunction(runtime, removeListenerProp, 2, removeListenerHost));
prototype.setProperty(runtime, removeAllListenersProp, jsi::Function::createFromHostFunction(runtime, removeAllListenersProp, 1, removeAllListenersHost));
prototype.setProperty(runtime, emitProp, jsi::Function::createFromHostFunction(runtime, emitProp, 2, emit));
prototype.setProperty(runtime, listenerCountProp, jsi::Function::createFromHostFunction(runtime, listenerCountProp, 1, listenerCountHost));
prototype.setProperty(runtime, removeSubscriptionProp, jsi::Function::createFromHostFunction(runtime, removeSubscriptionProp, 1, removeSubscriptionHost));
common::getCoreObject(runtime)
.setProperty(runtime, "EventEmitter", eventEmitterClass);
}
} // namespace expo::EventEmitter

View File

@@ -0,0 +1,111 @@
#pragma once
#ifdef __cplusplus
#include <unordered_map>
#include <list>
#include <jsi/jsi.h>
namespace jsi = facebook::jsi;
namespace expo::EventEmitter {
/**
Class containing and managing listeners of the event emitter.
*/
class Listeners {
private:
friend class NativeState;
friend void addListener(jsi::Runtime &runtime, const jsi::Object &emitter, const std::string &eventName, const jsi::Function &listener);
friend void removeListener(jsi::Runtime &runtime, const jsi::Object &emitter, const std::string &eventName, const jsi::Function &listener);
friend void removeAllListeners(jsi::Runtime &runtime, const jsi::Object &emitter, const std::string &eventName);
friend void emitEvent(jsi::Runtime &runtime, const jsi::Object &emitter, const std::string &eventName, const jsi::Value *args, size_t count);
friend size_t getListenerCount(jsi::Runtime &runtime, const jsi::Object &emitter, const std::string &eventName);
/**
Type of the list containing listeners for the specific event name.
*/
using ListenersList = std::list<jsi::Value>;
/**
Type of the map where the keys are event names and the values are lists of listeners.
*/
using ListenersMap = std::unordered_map<std::string, ListenersList>;
/**
Map with the events and listeners.
*/
ListenersMap listenersMap;
/**
Adds a listener for the given event name.
*/
void add(jsi::Runtime &runtime, const std::string& eventName, const jsi::Function &listener) noexcept;
/**
Removes the listener for the given event name.
*/
void remove(jsi::Runtime &runtime, const std::string& eventName, const jsi::Function &listener) noexcept;
/**
Removes all listeners for the given event name.
*/
void removeAll(const std::string& eventName) noexcept;
/**
Clears the entire map of events and listeners.
*/
void clear() noexcept;
/**
Returns a number of listeners added for the given event name.
*/
size_t listenersCount(const std::string& eventName) noexcept;
/**
Calls listeners for the given event name, with the given `this` object and payload arguments.
*/
void call(jsi::Runtime &runtime, const std::string& eventName, const jsi::Object &thisObject, const jsi::Value *args, size_t count) noexcept;
};
/**
Class representing a native state of objects that emit events.
*/
class JSI_EXPORT NativeState : public jsi::NativeState {
public:
using Shared = std::shared_ptr<NativeState>;
NativeState();
~NativeState() override;
/**
A structure containing event listeners.
*/
Listeners listeners;
/**
Gets event emitter's native state from the given object.
If `createIfMissing` is set to `true`, the state will be automatically created.
*/
static Shared get(jsi::Runtime &runtime, const jsi::Object &object, bool createIfMissing = false);
};
/**
Emits an event with the given name and arguments to the emitter object.
Does nothing if the given object is not an instance of the EventEmitter class.
*/
void emitEvent(jsi::Runtime &runtime, jsi::Object &emitter, const std::string &eventName, const std::vector<jsi::Value> &arguments);
/**
Gets `expo.EventEmitter` class from the given runtime.
*/
jsi::Function getClass(jsi::Runtime &runtime);
/**
Installs `expo.EventEmitter` class in the given runtime.
*/
void installClass(jsi::Runtime &runtime);
} // namespace expo::EventEmitter
#endif // __cplusplus

View File

@@ -0,0 +1,34 @@
// Copyright 2024-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
#include <ReactCommon/CallInvoker.h>
#include <ReactCommon/RuntimeExecutor.h>
namespace jsi = facebook::jsi;
namespace react = facebook::react;
namespace expo {
class BridgelessJSCallInvoker : public react::CallInvoker {
public:
explicit BridgelessJSCallInvoker(react::RuntimeExecutor runtimeExecutor) : runtimeExecutor_(std::move(runtimeExecutor)) {}
void invokeAsync(react::CallFunc &&func) noexcept override {
runtimeExecutor_([func = std::move(func)](jsi::Runtime &runtime) { func(runtime); });
}
void invokeSync(react::CallFunc &&func) override {
throw std::runtime_error("Synchronous native -> JS calls are currently not supported.");
}
private:
react::RuntimeExecutor runtimeExecutor_;
}; // class BridgelessJSCallInvoker
} // namespace expo
#endif // __cplusplus

View File

@@ -0,0 +1,147 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#include <sstream>
#include <utility>
#include "JSIUtils.h"
namespace expo::common {
jsi::Function createClass(jsi::Runtime &runtime, const char *name, ClassConstructor constructor) {
std::string nativeConstructorKey("__native_constructor__");
// Create a string buffer of the source code to evaluate.
std::stringstream source;
source << "(function " << name << "(...args) { return this." << nativeConstructorKey << "(...args); })";
std::shared_ptr<jsi::StringBuffer> sourceBuffer = std::make_shared<jsi::StringBuffer>(source.str());
// Evaluate the code and obtain returned value (the constructor function).
jsi::Object klass = runtime.evaluateJavaScript(sourceBuffer, "").asObject(runtime);
// Set the native constructor in the prototype.
jsi::Object prototype = klass.getPropertyAsObject(runtime, "prototype");
jsi::PropNameID nativeConstructorPropId = jsi::PropNameID::forAscii(runtime, nativeConstructorKey);
jsi::Function nativeConstructor = jsi::Function::createFromHostFunction(
runtime,
nativeConstructorPropId,
// The paramCount is not obligatory to match, it only affects the `length` property of the function.
0,
[constructor = std::move(constructor)](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value {
if (constructor) {
return constructor(runtime, thisValue, args, count);
}
return jsi::Value(runtime, thisValue);
});
jsi::Object descriptor(runtime);
descriptor.setProperty(runtime, "value", jsi::Value(runtime, nativeConstructor));
defineProperty(runtime, &prototype, nativeConstructorKey.c_str(), std::move(descriptor));
return klass.asFunction(runtime);
}
jsi::Function createInheritingClass(jsi::Runtime &runtime, const char *className, jsi::Function &baseClass, ClassConstructor constructor) {
jsi::PropNameID prototypePropNameId = jsi::PropNameID::forAscii(runtime, "prototype", 9);
jsi::Object baseClassPrototype = baseClass
.getProperty(runtime, prototypePropNameId)
.asObject(runtime);
jsi::Function klass = createClass(runtime, className, std::move(constructor));
jsi::Object klassPrototype = klass.getProperty(runtime, prototypePropNameId).asObject(runtime);
klassPrototype.setProperty(runtime, "__proto__", baseClassPrototype);
return klass;
}
jsi::Object createObjectWithPrototype(jsi::Runtime &runtime, jsi::Object *prototype) {
// Get the "Object" class.
jsi::Object objectClass = runtime
.global()
.getPropertyAsObject(runtime, "Object");
// Call "Object.create(prototype)" to create an object with the given prototype without calling the constructor.
jsi::Object object = objectClass
.getPropertyAsFunction(runtime, "create")
.callWithThis(runtime, objectClass, {
jsi::Value(runtime, *prototype)
})
.asObject(runtime);
return object;
}
std::vector<jsi::PropNameID> jsiArrayToPropNameIdsVector(jsi::Runtime &runtime, const jsi::Array &array) {
size_t size = array.size(runtime);
std::vector<jsi::PropNameID> vector;
vector.reserve(size);
for (size_t i = 0; i < size; i++) {
jsi::String name = array.getValueAtIndex(runtime, i).getString(runtime);
vector.push_back(jsi::PropNameID::forString(runtime, name));
}
return vector;
}
void defineProperty(jsi::Runtime &runtime, jsi::Object *object, const char *name, const PropertyDescriptor& descriptor) {
jsi::Object jsDescriptor(runtime);
// These three flags are all `false` by default, so set the property only when `true`.
if (descriptor.configurable) {
jsDescriptor.setProperty(runtime, "configurable", jsi::Value(true));
}
if (descriptor.enumerable) {
jsDescriptor.setProperty(runtime, "enumerable", jsi::Value(true));
}
if (descriptor.writable) {
jsDescriptor.setProperty(runtime, "writable", jsi::Value(true));
}
if (descriptor.get) {
jsi::PropNameID getPropName = jsi::PropNameID::forAscii(runtime, "get", 3);
jsi::Function get = jsi::Function::createFromHostFunction(
runtime,
getPropName,
0,
[getter = descriptor.get](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value {
return getter(runtime, thisValue.asObject(runtime));
});
jsDescriptor.setProperty(runtime, getPropName, get);
}
if (descriptor.set) {
jsi::PropNameID setPropName = jsi::PropNameID::forAscii(runtime, "set", 3);
jsi::Function set = jsi::Function::createFromHostFunction(
runtime,
setPropName,
1,
[setter = descriptor.set](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value {
setter(runtime, thisValue.asObject(runtime), jsi::Value(runtime, args[0]));
return jsi::Value::undefined();
});
jsDescriptor.setProperty(runtime, setPropName, set);
}
if (!descriptor.value.isUndefined()) {
jsi::PropNameID valuePropName = jsi::PropNameID::forAscii(runtime, "value", 5);
jsDescriptor.setProperty(runtime, valuePropName, descriptor.value);
}
defineProperty(runtime, object, name, std::move(jsDescriptor));
}
void defineProperty(jsi::Runtime &runtime, jsi::Object *object, const char *name, jsi::Object descriptor) {
jsi::Object global = runtime.global();
jsi::Object objectClass = global.getPropertyAsObject(runtime, "Object");
jsi::Function definePropertyFunction = objectClass.getPropertyAsFunction(runtime, "defineProperty");
// This call is basically the same as `Object.defineProperty(object, name, descriptor)` in JS
definePropertyFunction.callWithThis(runtime, objectClass, {
jsi::Value(runtime, *object),
jsi::String::createFromUtf8(runtime, name),
std::move(descriptor),
});
}
} // namespace expo::common

View File

@@ -0,0 +1,76 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
#include <jsi/jsi.h>
namespace jsi = facebook::jsi;
namespace expo::common {
#pragma mark - Helpers
/**
Gets the core Expo object, i.e. `global.expo`.
*/
inline jsi::Object getCoreObject(jsi::Runtime &runtime) {
return runtime.global().getPropertyAsObject(runtime, "expo");
}
#pragma mark - Classes
/**
Type of the native constructor of the JS classes.
*/
typedef std::function<jsi::Value(jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count)> ClassConstructor;
/**
Creates a class with the given name and native constructor.
*/
jsi::Function createClass(jsi::Runtime &runtime, const char *name, ClassConstructor constructor = nullptr);
/**
Creates a class (function) that inherits from the provided base class.
*/
jsi::Function createInheritingClass(jsi::Runtime &runtime, const char *className, jsi::Function &baseClass, ClassConstructor constructor = nullptr);
/**
Creates an object from the given prototype, without calling the constructor.
*/
jsi::Object createObjectWithPrototype(jsi::Runtime &runtime, jsi::Object *prototype);
#pragma mark - Conversions
/**
Converts `jsi::Array` to a vector with prop name ids (`std::vector<jsi::PropNameID>`).
*/
std::vector<jsi::PropNameID> jsiArrayToPropNameIdsVector(jsi::Runtime &runtime, const jsi::Array &array);
#pragma mark - Properties
/**
Represents a JS property descriptor used in the `Object.defineProperty` function.
*/
struct PropertyDescriptor {
const bool configurable = false;
const bool enumerable = false;
const bool writable = false;
const jsi::Value value = jsi::Value::undefined();
const std::function<jsi::Value(jsi::Runtime &runtime, jsi::Object thisObject)> get = 0;
const std::function<void(jsi::Runtime &runtime, jsi::Object thisObject, jsi::Value newValue)> set = 0;
}; // PropertyDescriptor
/**
Defines the property on the object with the provided descriptor options.
*/
void defineProperty(jsi::Runtime &runtime, jsi::Object *object, const char *name, const PropertyDescriptor& descriptor);
/**
Calls `Object.defineProperty(object, name, descriptor)`.
*/
void defineProperty(jsi::Runtime &runtime, jsi::Object *object, const char *name, jsi::Object descriptor);
} // namespace expo::common
#endif // __cplusplus

View File

@@ -0,0 +1,24 @@
#include "MemoryBuffer.h"
namespace expo {
using namespace facebook;
MemoryBuffer::MemoryBuffer(uint8_t* data, size_t size, CleanupFunc&& cleanupFunc)
: data_(data), size_(size), cleanupFunc(std::move(cleanupFunc)) {}
MemoryBuffer::~MemoryBuffer() {
if (cleanupFunc != nullptr) {
cleanupFunc();
}
}
uint8_t* MemoryBuffer::data() {
return data_;
}
size_t MemoryBuffer::size() const {
return size_;
}
}

View File

@@ -0,0 +1,35 @@
#ifdef __cplusplus
#pragma once
#include <functional>
#include <jsi/jsi.h>
namespace jsi = facebook::jsi;
namespace expo {
using CleanupFunc = std::function<void()>;
/**
* A JSI-compatible memory buffer that allows creating JSI ArrayBuffers from natively-managed memory.
* Since ArrayBuffers created from native memory don't automatically deallocate during GC,
* this class handles deallocation through a custom cleanup function.
*/
class MemoryBuffer final : public jsi::MutableBuffer {
public:
MemoryBuffer(uint8_t* data, size_t size, CleanupFunc&& cleanupFunc);
~MemoryBuffer() override;
uint8_t* data() override;
[[nodiscard]] size_t size() const override;
private:
uint8_t* data_;
size_t size_;
CleanupFunc cleanupFunc;
};
}
#endif

View File

@@ -0,0 +1,19 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#include "ObjectDeallocator.h"
#include "JSIUtils.h"
namespace expo::common {
void setDeallocator(
jsi::Runtime &runtime,
const std::shared_ptr<jsi::Object> &jsThis,
ObjectDeallocator::Block deallocatorBlock
) {
std::shared_ptr<ObjectDeallocator> objectDeallocator = std::make_shared<ObjectDeallocator>(
std::move(deallocatorBlock)
);
jsThis->setNativeState(runtime, objectDeallocator);
}
} // namespace expo::common

View File

@@ -0,0 +1,38 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#ifdef __cplusplus
#pragma once
#import <jsi/jsi.h>
namespace jsi = facebook::jsi;
namespace expo::common {
class JSI_EXPORT ObjectDeallocator : public jsi::NativeState {
public:
typedef std::function<void()> Block;
ObjectDeallocator(Block deallocator) : deallocator(std::move(deallocator)) {};
~ObjectDeallocator() override {
deallocator();
}
const Block deallocator;
}; // class ObjectDeallocator
/**
Sets the deallocator block on a given object, which is called when the object is being deallocated.
*/
void setDeallocator(
jsi::Runtime &runtime,
const std::shared_ptr<jsi::Object> &jsThis,
ObjectDeallocator::Block deallocatorBlock
);
} // namespace expo::common
#endif

View File

@@ -0,0 +1,39 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
#include <ReactCommon/CallInvoker.h>
#include <jsi/jsi.h>
namespace jsi = facebook::jsi;
namespace react = facebook::react;
namespace expo {
/**
* Dummy CallInvoker that invokes everything immediately.
* Used in the test environment to check the async flow.
*/
class TestingSyncJSCallInvoker : public react::CallInvoker {
public:
explicit TestingSyncJSCallInvoker(const std::shared_ptr<jsi::Runtime>& runtime) : runtime(runtime) {}
void invokeAsync(react::CallFunc &&func) noexcept override {
func(*runtime.lock());
}
void invokeSync(react::CallFunc &&func) override {
func(*runtime.lock());
}
~TestingSyncJSCallInvoker() override = default;
std::weak_ptr<jsi::Runtime> runtime;
};
} // namespace expo
#endif // __cplusplus

View File

@@ -0,0 +1,70 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#include <unordered_map>
#include "TypedArray.h"
namespace expo {
std::unordered_map<std::string, TypedArrayKind> nameToKindMap = {
{"Int8Array", TypedArrayKind::Int8Array},
{"Int16Array", TypedArrayKind::Int16Array},
{"Int32Array", TypedArrayKind::Int32Array},
{"Uint8Array", TypedArrayKind::Uint8Array},
{"Uint8ClampedArray", TypedArrayKind::Uint8ClampedArray},
{"Uint16Array", TypedArrayKind::Uint16Array},
{"Uint32Array", TypedArrayKind::Uint32Array},
{"Float32Array", TypedArrayKind::Float32Array},
{"Float64Array", TypedArrayKind::Float64Array},
{"BigInt64Array", TypedArrayKind::BigInt64Array},
{"BigUint64Array", TypedArrayKind::BigUint64Array},
};
TypedArrayKind getTypedArrayKindForName(const std::string &name) {
return nameToKindMap.at(name);
}
TypedArray::TypedArray(jsi::Runtime &runtime, const jsi::Object &obj)
: jsi::Object(jsi::Value(runtime, obj).asObject(runtime)) {}
TypedArrayKind TypedArray::getKind(jsi::Runtime &runtime) const {
auto constructorName = this->getPropertyAsObject(runtime, "constructor")
.getProperty(runtime, "name")
.asString(runtime)
.utf8(runtime);
return getTypedArrayKindForName(constructorName);
};
size_t TypedArray::byteOffset(jsi::Runtime &runtime) const {
return static_cast<size_t>(getProperty(runtime, "byteOffset").asNumber());
}
size_t TypedArray::byteLength(jsi::Runtime &runtime) const {
return static_cast<size_t>(getProperty(runtime, "byteLength").asNumber());
}
jsi::ArrayBuffer TypedArray::getBuffer(jsi::Runtime &runtime) const {
auto buffer = getProperty(runtime, "buffer");
if (buffer.isObject() && buffer.asObject(runtime).isArrayBuffer(runtime)) {
return buffer.asObject(runtime).getArrayBuffer(runtime);
} else {
throw std::runtime_error("no ArrayBuffer attached");
}
}
void* TypedArray::getRawPointer(jsi::Runtime &runtime) const {
return reinterpret_cast<void *>(getBuffer(runtime).data(runtime) + byteOffset(runtime));
}
bool isTypedArray(jsi::Runtime &runtime, const jsi::Object &jsObj) {
jsi::Object ArrayBuffer = runtime
.global()
.getPropertyAsObject(runtime, "ArrayBuffer");
jsi::Value isViewResult = ArrayBuffer
.getPropertyAsFunction(runtime, "isView")
.callWithThis(runtime, ArrayBuffer, {jsi::Value(runtime, jsObj)});
return isViewResult.getBool();
}
} // namespace expo

View File

@@ -0,0 +1,50 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#ifdef __cplusplus
#pragma once
#include <jsi/jsi.h>
namespace jsi = facebook::jsi;
namespace expo {
// Please keep it in-sync with the `EXTypedArrayKind` in Objective-C.
// We need to maintain two implementations to expose this enum to Swift.
enum class TypedArrayKind {
Int8Array = 1,
Int16Array = 2,
Int32Array = 3,
Uint8Array = 4,
Uint8ClampedArray = 5,
Uint16Array = 6,
Uint32Array = 7,
Float32Array = 8,
Float64Array = 9,
BigInt64Array = 10,
BigUint64Array = 11,
};
class TypedArray : public jsi::Object {
public:
TypedArray(jsi::Runtime &, const jsi::Object &);
TypedArray(TypedArray &&) noexcept = default;
TypedArray &operator=(TypedArray &&) noexcept = default;
TypedArrayKind getKind(jsi::Runtime &runtime) const;
size_t byteOffset(jsi::Runtime &runtime) const;
size_t byteLength(jsi::Runtime &runtime) const;
jsi::ArrayBuffer getBuffer(jsi::Runtime &runtime) const;
void* getRawPointer(jsi::Runtime &runtime) const;
};
bool isTypedArray(jsi::Runtime &runtime, const jsi::Object &jsObj);
} // namespace expo
#endif // __cplusplus

View File

@@ -0,0 +1,62 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#ifdef __APPLE__
#include <ExpoModulesJSI/JSIUtils.h>
#else
#include "JSIUtils.h"
#endif
#include "LazyObject.h"
namespace expo {
LazyObject::LazyObject(LazyObjectInitializer initializer) : initializer(std::move(initializer)) {}
LazyObject::~LazyObject() {
backedObject = nullptr;
}
jsi::Value LazyObject::get(jsi::Runtime &runtime, const jsi::PropNameID &name) {
if (!backedObject) {
if (name.utf8(runtime) == "$$typeof") {
// React Native asks for this property for some reason, we can just ignore it.
return jsi::Value::undefined();
}
initializeBackedObject(runtime);
}
return backedObject ? backedObject->getProperty(runtime, name) : jsi::Value::undefined();
}
void LazyObject::set(jsi::Runtime &runtime, const jsi::PropNameID &name, const jsi::Value &value) {
if (!backedObject) {
initializeBackedObject(runtime);
}
if (backedObject) {
backedObject->setProperty(runtime, name, value);
}
}
std::vector<jsi::PropNameID> LazyObject::getPropertyNames(jsi::Runtime &runtime) {
if (!backedObject) {
initializeBackedObject(runtime);
}
if (backedObject) {
jsi::Array propertyNames = backedObject->getPropertyNames(runtime);
return common::jsiArrayToPropNameIdsVector(runtime, propertyNames);
}
return {};
}
const jsi::Object &LazyObject::unwrapObjectIfNecessary(jsi::Runtime &runtime, const jsi::Object &object) {
if (object.isHostObject<LazyObject>(runtime)) {
LazyObject::Shared lazyObject = object.getHostObject<LazyObject>(runtime);
if (!lazyObject->backedObject) {
lazyObject->initializeBackedObject(runtime);
}
return *lazyObject->backedObject;
}
return object;
}
} // namespace expo

56
node_modules/expo-modules-core/common/cpp/LazyObject.h generated vendored Normal file
View File

@@ -0,0 +1,56 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
#include <jsi/jsi.h>
namespace jsi = facebook::jsi;
namespace expo {
/**
A function that is responsible for initializing the backed object.
*/
typedef std::function<std::shared_ptr<jsi::Object>(jsi::Runtime &)> LazyObjectInitializer;
/**
A host object that defers the creating of the raw object until any property is accessed for the first time.
*/
class JSI_EXPORT LazyObject : public jsi::HostObject {
public:
using Shared = std::shared_ptr<LazyObject>;
explicit LazyObject(LazyObjectInitializer initializer);
~LazyObject() override;
jsi::Value get(jsi::Runtime &, const jsi::PropNameID &name) override;
void set(jsi::Runtime &, const jsi::PropNameID &name, const jsi::Value &value) override;
std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime &rt) override;
/**
If the given object is a host object of type `LazyObject`, it returns its backed object.
Otherwise, the given object is returned back.
*/
static const jsi::Object &unwrapObjectIfNecessary(jsi::Runtime &runtime, const jsi::Object &object);
private:
const LazyObjectInitializer initializer;
std::shared_ptr<jsi::Object> backedObject;
/**
Initializes the backed object. It shouldn't be invoked more than once, so first make sure that `backedObject` is a null pointer.
*/
inline void initializeBackedObject(jsi::Runtime &runtime) {
backedObject = initializer(runtime);
}
}; // class LazyObject
} // namespace expo
#endif // __cplusplus

View File

@@ -0,0 +1,21 @@
// Copyright 2024-present 650 Industries. All rights reserved.
#ifdef __APPLE__
#include <ExpoModulesJSI/JSIUtils.h>
#else
#include "JSIUtils.h"
#endif
#include "EventEmitter.h"
#include "NativeModule.h"
namespace expo::NativeModule {
void installClass(jsi::Runtime &runtime) {
jsi::Function eventEmitterClass = EventEmitter::getClass(runtime);
jsi::Function nativeModuleClass = common::createInheritingClass(runtime, "NativeModule", eventEmitterClass);
common::getCoreObject(runtime).setProperty(runtime, "NativeModule", nativeModuleClass);
}
} // namespace expo::NativeModule

View File

@@ -0,0 +1,40 @@
// Copyright 2024-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
#include <jsi/jsi.h>
#ifdef __APPLE__
#include <ExpoModulesJSI/JSIUtils.h>
#else
#include "JSIUtils.h"
#endif
namespace jsi = facebook::jsi;
namespace expo::NativeModule {
/**
Gets `expo.NativeModule` class in the given runtime.
*/
inline jsi::Function getClass(jsi::Runtime &runtime) {
return common::getCoreObject(runtime).getPropertyAsFunction(runtime, "NativeModule");
}
/**
Installs `expo.NativeModule` class in the given runtime.
*/
void installClass(jsi::Runtime &runtime);
/**
Creates a new instance of the native module.
*/
inline jsi::Object createInstance(jsi::Runtime &runtime) {
return getClass(runtime).callAsConstructor(runtime).getObject(runtime);
}
} // namespace expo::NativeModule
#endif // __cplusplus

View File

@@ -0,0 +1,97 @@
// Copyright 2024-present 650 Industries. All rights reserved.
#ifdef __APPLE__
#include <ExpoModulesJSI/JSIUtils.h>
#else
#include "JSIUtils.h"
#endif
#include "SharedObject.h"
namespace expo::SharedObject {
#pragma mark - NativeState
NativeState::NativeState(ObjectId objectId, ObjectReleaser releaser)
: EventEmitter::NativeState(), objectId(objectId), releaser(std::move(releaser)) {}
NativeState::~NativeState() {
releaser(objectId);
}
#pragma mark - Utils
void installBaseClass(jsi::Runtime &runtime, const ObjectReleaser& releaser) {
jsi::Function baseClass = EventEmitter::getClass(runtime);
jsi::Function klass = expo::common::createInheritingClass(runtime, "SharedObject", baseClass);
jsi::Object prototype = klass.getPropertyAsObject(runtime, "prototype");
jsi::Function releaseFunction = jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "release"),
1,
[releaser](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value {
jsi::Object thisObject = thisValue.getObject(runtime);
if (thisObject.hasNativeState<NativeState>(runtime)) {
auto nativeState = thisObject.getNativeState<NativeState>(runtime);
releaser(nativeState->objectId);
// Should we reset the native state?
thisObject.setNativeState(runtime, nullptr);
}
return jsi::Value::undefined();
});
// Implements a JSON serializer for shared objects, whose properties are defined in the prototype instead of the instance itself.
// By default `JSON.stringify` visits only enumerable own properties.
jsi::Function toJSONFunction = jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "toJSON"),
0,
[](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value {
jsi::Object thisObject = thisValue.getObject(runtime);
jsi::Object json = jsi::Object(runtime);
jsi::Array propertyNames = thisObject.getPropertyNames(runtime);
for (size_t i = 0, size = propertyNames.size(runtime); i < size; i++) {
jsi::String propertyName = propertyNames.getValueAtIndex(runtime, i).getString(runtime);
jsi::Value value = thisObject.getProperty(runtime, propertyName);
if (!value.isObject() || !value.getObject(runtime).isFunction(runtime)) {
json.setProperty(runtime, propertyName, value);
}
}
return jsi::Value(runtime, json);
});
prototype.setProperty(runtime, "release", releaseFunction);
prototype.setProperty(runtime, "toJSON", toJSONFunction);
// This property should be deprecated, but it's still used when passing as a view prop.
defineProperty(runtime, &prototype, "__expo_shared_object_id__", common::PropertyDescriptor {
.get = [](jsi::Runtime &runtime, jsi::Object thisObject) {
if (thisObject.hasNativeState<NativeState>(runtime)) {
auto nativeState = thisObject.getNativeState<NativeState>(runtime);
return jsi::Value((int)nativeState->objectId);
}
return jsi::Value(0);
}
});
common::getCoreObject(runtime)
.setProperty(runtime, "SharedObject", klass);
}
jsi::Function getBaseClass(jsi::Runtime &runtime) {
return common::getCoreObject(runtime)
.getPropertyAsFunction(runtime, "SharedObject");
}
jsi::Function createClass(jsi::Runtime &runtime, const char *className, common::ClassConstructor constructor) {
jsi::Function baseSharedObjectClass = getBaseClass(runtime);
return common::createInheritingClass(runtime, className, baseSharedObjectClass, std::move(constructor));
}
} // namespace expo::SharedObject

View File

@@ -0,0 +1,66 @@
// Copyright 2024-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
#ifdef __APPLE__
#include <ExpoModulesJSI/JSIUtils.h>
#include <ExpoModulesJSI/ObjectDeallocator.h>
#else
#include "JSIUtils.h"
#include "ObjectDeallocator.h"
#endif
#include <jsi/jsi.h>
#include "EventEmitter.h"
namespace jsi = facebook::jsi;
namespace expo::SharedObject {
/**
Type of the shared object IDs.
*/
typedef long ObjectId;
/**
Defines an object releaser block of the shared object.
*/
typedef std::function<void(const ObjectId)> ObjectReleaser;
/**
Installs a base JavaScript class for all shared object with a shared release block.
*/
void installBaseClass(jsi::Runtime &runtime, const ObjectReleaser& releaser);
/**
Returns the base JavaScript class for all shared objects, i.e. `global.expo.SharedObject`.
*/
jsi::Function getBaseClass(jsi::Runtime &runtime);
/**
Creates a concrete shared object class with the given name and constructor.
*/
jsi::Function createClass(jsi::Runtime &runtime, const char *className, common::ClassConstructor constructor);
/**
Class representing a native state of the shared object.
*/
class JSI_EXPORT NativeState : public EventEmitter::NativeState {
public:
const ObjectId objectId = 0;
const ObjectReleaser releaser;
/**
The default constructor that initializes a native state for the shared object with given ID.
*/
NativeState(ObjectId objectId, ObjectReleaser releaser);
~NativeState() override;
}; // class NativeState
} // namespace expo::SharedObject
#endif // __cplusplus

View File

@@ -0,0 +1,25 @@
// Copyright 2024-present 650 Industries. All rights reserved.
#include "SharedRef.h"
namespace expo::SharedRef {
void installBaseClass(jsi::Runtime &runtime) {
jsi::Function baseClass = SharedObject::getBaseClass(runtime);
jsi::Function klass = expo::common::createInheritingClass(runtime, "SharedRef", baseClass);
common::getCoreObject(runtime)
.setProperty(runtime, "SharedRef", klass);
}
jsi::Function getBaseClass(jsi::Runtime &runtime) {
return common::getCoreObject(runtime)
.getPropertyAsFunction(runtime, "SharedRef");
}
jsi::Function createClass(jsi::Runtime &runtime, const char *className, common::ClassConstructor constructor) {
jsi::Function baseSharedObjectClass = getBaseClass(runtime);
return common::createInheritingClass(runtime, className, baseSharedObjectClass, std::move(constructor));
}
} // namespace expo::SharedRef

32
node_modules/expo-modules-core/common/cpp/SharedRef.h generated vendored Normal file
View File

@@ -0,0 +1,32 @@
// Copyright 2024-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
#include "SharedObject.h"
#include <jsi/jsi.h>
namespace jsi = facebook::jsi;
namespace expo::SharedRef {
/**
Installs a base JavaScript class for all shared references.
*/
void installBaseClass(jsi::Runtime &runtime);
/**
Returns the base JavaScript class for all shared refs, i.e. `global.expo.SharedRef`.
*/
jsi::Function getBaseClass(jsi::Runtime &runtime);
/**
Creates a concrete shared ref class with the given name and constructor.
*/
jsi::Function createClass(jsi::Runtime &runtime, const char *className, common::ClassConstructor constructor);
} // namespace expo::SharedRef
#endif // __cplusplus

View File

@@ -0,0 +1,78 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#include "ExpoViewComponentDescriptor.h"
#include <react/renderer/core/ShadowNode.h>
#include <cmath>
namespace expo {
ExpoViewComponentDescriptor::ExpoViewComponentDescriptor(facebook::react::ComponentDescriptorParameters const &parameters)
: facebook::react::ConcreteComponentDescriptor<ExpoViewShadowNode>(parameters) {
}
facebook::react::ComponentHandle ExpoViewComponentDescriptor::getComponentHandle() const {
return reinterpret_cast<facebook::react::ComponentHandle>(getComponentName());
}
facebook::react::ComponentName ExpoViewComponentDescriptor::getComponentName() const {
return std::static_pointer_cast<std::string const>(this->flavor_)->c_str();
}
void ExpoViewComponentDescriptor::adopt(facebook::react::ShadowNode &shadowNode) const {
react_native_assert(dynamic_cast<ExpoViewShadowNode *>(&shadowNode));
const auto snode = dynamic_cast<ExpoViewShadowNode *>(&shadowNode);
const auto state = snode->getStateData();
auto width = state._width;
auto height = state._height;
if (!isnan(width) || !isnan(height)) {
auto const &props = *std::static_pointer_cast<const facebook::react::ViewProps>(snode->getProps());
// The node has width and/or height set as style props, so we should not override it
auto widthProp = props.yogaStyle.dimension(facebook::yoga::Dimension::Width);
auto heightProp = props.yogaStyle.dimension(facebook::yoga::Dimension::Height);
if (widthProp.value().isDefined()) {
// view has fixed dimension size set in props, so we should not autosize it in that axis
width = widthProp.value().unwrap();
}
if (heightProp.value().isDefined()) {
height = heightProp.value().unwrap();
}
snode->setSize({width, height});
}
// handle layout style prop update
auto styleWidth = state._styleWidth;
auto styleHeight = state._styleHeight;
if (!isnan(styleWidth) || !isnan(styleHeight)) {
auto const &props = *std::static_pointer_cast<const facebook::react::ViewProps>(snode->getProps());
auto& style = const_cast<facebook::yoga::Style&>(props.yogaStyle);
bool changedStyle = false;
if (!isnan(styleWidth)) {
style.setDimension(facebook::yoga::Dimension::Width, facebook::yoga::StyleSizeLength::points(styleWidth));
changedStyle = true;
}
if (!isnan(styleHeight)) {
style.setDimension(facebook::yoga::Dimension::Height, facebook::yoga::StyleSizeLength::points(styleHeight));
changedStyle = true;
}
// Update yoga props and dirty layout if we changed the style
if (changedStyle) {
auto* expoNode = const_cast<ExpoViewShadowNode*>(snode);
expoNode->updateYogaProps();
expoNode->dirtyLayout();
}
}
ConcreteComponentDescriptor::adopt(shadowNode);
}
} // namespace expo

View File

@@ -0,0 +1,28 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/core/ShadowNode.h>
#include "ExpoViewShadowNode.h"
namespace expo {
class ExpoViewComponentDescriptor : public facebook::react::ConcreteComponentDescriptor<ExpoViewShadowNode> {
public:
using Flavor = std::shared_ptr<std::string const>;
ExpoViewComponentDescriptor(facebook::react::ComponentDescriptorParameters const &parameters);
facebook::react::ComponentHandle getComponentHandle() const override;
facebook::react::ComponentName getComponentName() const override;
void adopt(facebook::react::ShadowNode &shadowNode) const override;
};
} // namespace expo
#endif // __cplusplus

View File

@@ -0,0 +1,15 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#include "ExpoViewEventEmitter.h"
#include <utility>
using namespace facebook;
namespace expo {
void ExpoViewEventEmitter::dispatch(std::string eventName, const react::ValueFactory& payloadFactory) const {
dispatchEvent(std::move(eventName), payloadFactory);
}
} // namespace expo

View File

@@ -0,0 +1,28 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
#include <react/renderer/components/view/ViewEventEmitter.h>
#include <jsi/jsi.h>
namespace react = facebook::react;
namespace expo {
class ExpoViewEventEmitter : public facebook::react::ViewEventEmitter {
public:
using facebook::react::ViewEventEmitter::ViewEventEmitter;
using Shared = std::shared_ptr<const ExpoViewEventEmitter>;
/**
Dispatches an event to send from the native view to JavaScript.
This is basically exposing `dispatchEvent` from `facebook::react::EventEmitter` for public use.
*/
void dispatch(std::string eventName, const react::ValueFactory& payloadFactory) const;
};
} // namespace expo
#endif // __cplusplus

View File

@@ -0,0 +1,34 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#include "ExpoViewProps.h"
#include <react/renderer/core/propsConversions.h>
#include <react/renderer/components/view/ViewProps.h>
namespace react = facebook::react;
namespace expo {
/**
Borrows the props map from the source props and applies the update given in the raw props.
*/
std::unordered_map<std::string, folly::dynamic> propsMapFromProps(const ExpoViewProps &sourceProps, const react::RawProps &rawProps) {
std::unordered_map<std::string, folly::dynamic> propsMap = sourceProps.propsMap;
// Iterate over values in the raw props object.
// Note that it contains only updated props.
const auto& dynamicRawProps = static_cast<folly::dynamic>(rawProps);
for (const auto& propsPair : dynamicRawProps.items()) {
const auto &propName = propsPair.first.getString();
propsMap[propName] = static_cast<folly::dynamic>(propsPair.second);
}
return propsMap;
}
ExpoViewProps::ExpoViewProps(const react::PropsParserContext &context,
const ExpoViewProps &sourceProps,
const react::RawProps &rawProps)
: react::ViewProps(context, sourceProps, rawProps),
propsMap(propsMapFromProps(sourceProps, rawProps)) {}
} // namespace expo

View File

@@ -0,0 +1,30 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
#include <folly/dynamic.h>
#include <react/renderer/components/view/ViewProps.h>
#include <react/renderer/core/PropsParserContext.h>
namespace expo {
class ExpoViewProps : public facebook::react::ViewProps {
public:
ExpoViewProps() = default;
ExpoViewProps(const facebook::react::PropsParserContext &context,
const ExpoViewProps &sourceProps,
const facebook::react::RawProps &rawProps);
#pragma mark - Props
/**
A map with props stored as `folly::dynamic` objects.
*/
std::unordered_map<std::string, folly::dynamic> propsMap;
};
} // namespace expo
#endif // __cplusplus

View File

@@ -0,0 +1,45 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#include "ExpoViewShadowNode.h"
namespace react = facebook::react;
namespace expo {
extern const char ExpoViewComponentName[] = "ExpoFabricView";
ExpoViewShadowNode::ExpoViewShadowNode(
const react::ShadowNodeFragment &fragment,
const react::ShadowNodeFamily::Shared &family,
react::ShadowNodeTraits traits)
: ConcreteViewShadowNode(fragment, family, traits) {
initialize();
}
ExpoViewShadowNode::ExpoViewShadowNode(
const react::ShadowNode &sourceShadowNode,
const react::ShadowNodeFragment &fragment)
: ConcreteViewShadowNode(sourceShadowNode, fragment) {
initialize();
}
void ExpoViewShadowNode::initialize() noexcept {
auto &viewProps = static_cast<const ExpoViewProps &>(*props_);
if (viewProps.collapsableChildren) {
traits_.set(react::ShadowNodeTraits::Trait::ChildrenFormStackingContext);
} else {
traits_.unset(react::ShadowNodeTraits::Trait::ChildrenFormStackingContext);
}
if (YGNodeStyleGetDisplay(&yogaNode_) == YGDisplayContents) {
auto it = viewProps.propsMap.find("disableForceFlatten");
bool disableForceFlatten = (it != viewProps.propsMap.end()) ? it->second.getBool() : false;
if (disableForceFlatten) {
traits_.unset(react::ShadowNodeTraits::Trait::ForceFlattenView);
}
}
}
} // namespace expo

View File

@@ -0,0 +1,42 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include "ExpoViewEventEmitter.h"
#include "ExpoViewProps.h"
#include "ExpoViewState.h"
namespace expo {
extern const char ExpoViewComponentName[];
class ExpoViewShadowNode final : public facebook::react::ConcreteViewShadowNode<
ExpoViewComponentName, ExpoViewProps,
ExpoViewEventEmitter, ExpoViewState> {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
ExpoViewShadowNode(const facebook::react::ShadowNodeFragment &fragment,
const facebook::react::ShadowNodeFamily::Shared &family,
facebook::react::ShadowNodeTraits traits);
ExpoViewShadowNode(const facebook::react::ShadowNode &sourceShadowNode,
const facebook::react::ShadowNodeFragment &fragment);
public:
static facebook::react::ShadowNodeTraits BaseTraits() {
auto traits = ConcreteViewShadowNode::BaseTraits();
return traits;
}
private:
void initialize() noexcept;
};
} // namespace expo
#endif // __cplusplus

View File

@@ -0,0 +1,77 @@
// Copyright 2022-present 650 Industries. All rights reserved.
#pragma once
#ifdef __cplusplus
#ifdef ANDROID
#include <folly/dynamic.h>
#include <react/renderer/mapbuffer/MapBuffer.h>
#include <react/renderer/mapbuffer/MapBufferBuilder.h>
#endif
namespace expo {
class ExpoViewState final {
public:
ExpoViewState() {}
ExpoViewState(float width, float height) {
if (width >= 0) {
_width = width;
} else {
_width = std::numeric_limits<float>::quiet_NaN();
}
if (height >= 0) {
_height = height;
} else {
_height = std::numeric_limits<float>::quiet_NaN();
}
};
static ExpoViewState withStyleDimensions(float styleWidth, float styleHeight) {
ExpoViewState state;
if (styleWidth >= 0) {
state._styleWidth = styleWidth;
} else {
state._styleWidth = std::numeric_limits<float>::quiet_NaN();
}
if (styleHeight >= 0) {
state._styleHeight = styleHeight;
} else {
state._styleHeight = std::numeric_limits<float>::quiet_NaN();
}
return state;
}
#ifdef ANDROID
ExpoViewState(ExpoViewState const &previousState, folly::dynamic data)
: _width(isNonnullProperty(data, "width") ? (float)data["width"].getDouble() : std::numeric_limits<float>::quiet_NaN()),
_height(isNonnullProperty(data, "height") ? (float)data["height"].getDouble() : std::numeric_limits<float>::quiet_NaN()),
_styleWidth(isNonnullProperty(data, "styleWidth") ? (float)data["styleWidth"].getDouble() : std::numeric_limits<float>::quiet_NaN()),
_styleHeight(isNonnullProperty(data, "styleHeight") ? (float)data["styleHeight"].getDouble() : std::numeric_limits<float>::quiet_NaN()) {
}
folly::dynamic getDynamic() const {
return {};
};
facebook::react::MapBuffer getMapBuffer() const {
return facebook::react::MapBufferBuilder::EMPTY();
};
static inline bool isNonnullProperty(const folly::dynamic &value, const std::string &name) {
return value.count(name) && !value[name].isNull();
}
#endif
float _width = std::numeric_limits<float>::quiet_NaN();
float _height = std::numeric_limits<float>::quiet_NaN();
float _styleWidth = std::numeric_limits<float>::quiet_NaN();
float _styleHeight = std::numeric_limits<float>::quiet_NaN();
};
} // namespace expo
#endif // __cplusplus