Files
Schnappix/node_modules/expo-modules-core/common/cpp/EventEmitter.cpp
T

321 lines
14 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "JSIUtils.h"
#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