Skip to content

Commit

Permalink
Add native EventEmitter (#1123)
Browse files Browse the repository at this point in the history
* Add native EventEmitter

* add listeners, listenerCount and eventNames

* add global functions

* add Object to EventEmitter conversion

* fix upon review
  • Loading branch information
zhuzilin committed Aug 21, 2022
1 parent d8f40e0 commit 3d8bc14
Show file tree
Hide file tree
Showing 16 changed files with 1,241 additions and 1 deletion.
24 changes: 24 additions & 0 deletions src/bun.js/bindings/ZigGlobalObject.cpp
Expand Up @@ -78,6 +78,7 @@
#include "JSURLSearchParams.h"
#include "JSDOMException.h"
#include "JSEventTarget.h"
#include "JSEventEmitter.h"
#include "EventTargetConcrete.h"
#include "JSAbortSignal.h"
#include "JSCustomEvent.h"
Expand Down Expand Up @@ -156,6 +157,7 @@ using JSBuffer = WebCore::JSBuffer;
#include <JavaScriptCore/DFGAbstractHeap.h>

#include "../modules/BufferModule.h"
#include "../modules/EventsModule.h"
#include "../modules/ProcessModule.h"

// #include <iostream>
Expand Down Expand Up @@ -2634,6 +2636,16 @@ static JSC_DEFINE_HOST_FUNCTION(functionFulfillModuleSync,
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::jsUndefined()));
RELEASE_AND_RETURN(scope, JSValue::encode(JSC::jsUndefined()));
}
case SyntheticModuleType::Events: {
auto source = JSC::SourceCode(
JSC::SyntheticSourceProvider::create(
generateEventsSourceCode,
JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath("node:events"_s)), WTFMove(moduleKey)));

globalObject->moduleLoader()->provideFetch(globalObject, key, WTFMove(source));
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::jsUndefined()));
RELEASE_AND_RETURN(scope, JSValue::encode(JSC::jsUndefined()));
}
default: {
auto provider = Zig::SourceProvider::create(res.result.value);
globalObject->moduleLoader()->provideFetch(globalObject, key, JSC::SourceCode(provider));
Expand Down Expand Up @@ -2719,6 +2731,18 @@ JSC::JSInternalPromise* GlobalObject::moduleLoaderFetch(JSGlobalObject* globalOb
scope.release();
return promise;
}
case SyntheticModuleType::Events: {
auto source = JSC::SourceCode(
JSC::SyntheticSourceProvider::create(generateEventsSourceCode,
JSC::SourceOrigin(), WTFMove(moduleKey)));

auto sourceCode = JSSourceCode::create(vm, WTFMove(source));
RETURN_IF_EXCEPTION(scope, promise->rejectWithCaughtException(globalObject, scope));

promise->resolve(globalObject, sourceCode);
scope.release();
return promise;
}
default: {
auto provider = Zig::SourceProvider::create(res.result.value);
auto jsSourceCode = JSC::JSSourceCode::create(vm, JSC::SourceCode(provider));
Expand Down
1 change: 1 addition & 0 deletions src/bun.js/bindings/exports.zig
Expand Up @@ -246,6 +246,7 @@ pub const ResolvedSource = extern struct {

@"node:buffer" = 1024,
@"node:process" = 1025,
@"node:events" = 1026,
};
};

Expand Down
1 change: 1 addition & 0 deletions src/bun.js/bindings/headers-handwritten.h
Expand Up @@ -180,6 +180,7 @@ typedef struct {
enum SyntheticModuleType : uint64_t {
Buffer = 1024,
Process = 1025,
Events = 1026,
};

extern "C" ZigErrorCode Zig_ErrorCodeParserError;
Expand Down
1 change: 1 addition & 0 deletions src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h
Expand Up @@ -881,5 +881,6 @@ class DOMClientIsoSubspaces {
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForEvent;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForEventListener;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForEventTarget;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForEventEmitter;
};
} // namespace WebCore
1 change: 1 addition & 0 deletions src/bun.js/bindings/webcore/DOMConstructors.h
Expand Up @@ -856,6 +856,7 @@ enum class DOMConstructorID : uint16_t {

// --bun--
Buffer,
EventEmitter,
};

static constexpr unsigned numberOfDOMConstructorsBase = 846;
Expand Down
1 change: 1 addition & 0 deletions src/bun.js/bindings/webcore/DOMIsoSubspaces.h
Expand Up @@ -874,6 +874,7 @@ class DOMIsoSubspaces {
std::unique_ptr<IsoSubspace> m_subspaceForEvent;
std::unique_ptr<IsoSubspace> m_subspaceForEventListener;
std::unique_ptr<IsoSubspace> m_subspaceForEventTarget;
std::unique_ptr<IsoSubspace> m_subspaceForEventEmitter;

std::unique_ptr<IsoSubspace> m_subspaceForZigGlobalObject;

Expand Down
245 changes: 245 additions & 0 deletions src/bun.js/bindings/webcore/EventEmitter.cpp
@@ -0,0 +1,245 @@
#include <iostream>
#include "config.h"
#include "Event.h"

#include "EventEmitter.h"

#include "AddEventListenerOptions.h"
#include "DOMWrapperWorld.h"
#include "EventNames.h"
#include "JSErrorHandler.h"
#include "JSEventListener.h"
#include <wtf/MainThread.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/Ref.h>
#include <wtf/SetForScope.h>
#include <wtf/StdLibExtras.h>
#include <wtf/Vector.h>

namespace WebCore {

WTF_MAKE_ISO_ALLOCATED_IMPL(EventEmitter);
WTF_MAKE_ISO_ALLOCATED_IMPL(EventEmitterWithInlineData);

Ref<EventEmitter> EventEmitter::create(ScriptExecutionContext& context)
{
return adoptRef(*new EventEmitter(context));
}

bool EventEmitter::addListener(const AtomString& eventType, Ref<EventListener>&& listener, bool once, bool prepend)
{
bool listenerCreatedFromScript = is<JSEventListener>(listener) && !downcast<JSEventListener>(listener.get()).wasCreatedFromMarkup();

if (prepend) {
if (!ensureEventEmitterData().eventListenerMap.prepend(eventType, listener.copyRef(), { false, false, once }))
return false;
} else {
if (!ensureEventEmitterData().eventListenerMap.add(eventType, listener.copyRef(), { false, false, once }))
return false;
}


eventListenersDidChange();
return true;
}

void EventEmitter::addListenerForBindings(const AtomString& eventType, RefPtr<EventListener>&& listener, bool once, bool prepend)
{
if (!listener)
return;

addListener(eventType, listener.releaseNonNull(), once, prepend);
}

void EventEmitter::removeListenerForBindings(const AtomString& eventType, RefPtr<EventListener>&& listener)
{
if (!listener)
return;

removeListener(eventType, *listener);
}

bool EventEmitter::removeListener(const AtomString& eventType, EventListener& listener)
{
auto* data = eventTargetData();
if (!data)
return false;

if (data->eventListenerMap.remove(eventType, listener, false)) {
if (eventNames().isWheelEventType(eventType))
invalidateEventListenerRegions();

eventListenersDidChange();
return true;
}
return false;
}

void EventEmitter::removeAllListenersForBindings(const AtomString& eventType)
{
removeAllListeners(eventType);
}

bool EventEmitter::removeAllListeners(const AtomString& eventType)
{
auto* data = eventTargetData();
if (!data)
return false;

if (data->eventListenerMap.removeAll(eventType)) {
if (eventNames().isWheelEventType(eventType))
invalidateEventListenerRegions();

eventListenersDidChange();
return true;
}
return false;
}

bool EventEmitter::hasActiveEventListeners(const AtomString& eventType) const
{
auto* data = eventTargetData();
return data && data->eventListenerMap.containsActive(eventType);
}

bool EventEmitter::emitForBindings(const AtomString& eventType, const MarkedArgumentBuffer& arguments)
{
if (!scriptExecutionContext())
return false;

emit(eventType, arguments);
return true;
}

void EventEmitter::emit(const AtomString& eventType, const MarkedArgumentBuffer& arguments)
{
fireEventListeners(eventType, arguments);
}

void EventEmitter::uncaughtExceptionInEventHandler()
{
}

Vector<AtomString> EventEmitter::getEventNames()
{
auto* data = eventTargetData();
if (!data)
return {};
return data->eventListenerMap.eventTypes();
}

int EventEmitter::listenerCount(const AtomString& eventType)
{
auto* data = eventTargetData();
if (!data)
return 0;
int result = 0;
if (auto* listenersVector = data->eventListenerMap.find(eventType)) {
for (auto& registeredListener : *listenersVector) {
if (UNLIKELY(registeredListener->wasRemoved()))
continue;

if (JSC::JSObject* jsFunction = registeredListener->callback().jsFunction()) {
result++;
}
}
}
return result;
}

Vector<JSObject*> EventEmitter::getListeners(const AtomString& eventType)
{
auto* data = eventTargetData();
if (!data)
return {};
Vector<JSObject*> listeners;
if (auto* listenersVector = data->eventListenerMap.find(eventType)) {
for (auto& registeredListener : *listenersVector) {
if (UNLIKELY(registeredListener->wasRemoved()))
continue;

if (JSC::JSObject* jsFunction = registeredListener->callback().jsFunction()) {
listeners.append(jsFunction);
}
}
}
return listeners;
}

static const AtomString& legacyType(const Event& event)
{

return nullAtom();
}

// https://dom.spec.whatwg.org/#concept-event-listener-invoke
void EventEmitter::fireEventListeners(const AtomString& eventType, const MarkedArgumentBuffer& arguments)
{
ASSERT_WITH_SECURITY_IMPLICATION(ScriptDisallowedScope::isEventAllowedInMainThread());

auto* data = eventTargetData();
if (!data)
return;

SetForScope firingEventListenersScope(data->isFiringEventListeners, true);

if (auto* listenersVector = data->eventListenerMap.find(eventType)) {
innerInvokeEventListeners(eventType, *listenersVector, arguments);
return;
}
}

// Intentionally creates a copy of the listeners vector to avoid event listeners added after this point from being run.
// Note that removal still has an effect due to the removed field in RegisteredEventListener.
// https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke
void EventEmitter::innerInvokeEventListeners(const AtomString& eventType, EventListenerVector listeners, const MarkedArgumentBuffer& arguments)
{
Ref<EventEmitter> protectedThis(*this);
ASSERT(!listeners.isEmpty());
ASSERT(scriptExecutionContext());

auto& context = *scriptExecutionContext();
VM& vm = context.vm();

for (auto& registeredListener : listeners) {
if (UNLIKELY(registeredListener->wasRemoved()))
continue;

// Make sure the JS wrapper and function stay alive until the end of this scope. Otherwise,
// event listeners with 'once' flag may get collected as soon as they get unregistered below,
// before we call the js function.
JSC::EnsureStillAliveScope wrapperProtector(registeredListener->callback().wrapper());
JSC::EnsureStillAliveScope jsFunctionProtector(registeredListener->callback().jsFunction());

// Do this before invocation to avoid reentrancy issues.
if (registeredListener->isOnce())
removeListener(eventType, registeredListener->callback());

if (JSC::JSObject* jsFunction = registeredListener->callback().jsFunction()) {
JSC::JSGlobalObject* lexicalGlobalObject = jsFunction->globalObject();
auto callData = JSC::getCallData(jsFunction);
JSC::call(jsFunction->globalObject(), jsFunction, callData, JSC::jsUndefined(), arguments);
}
}
}

Vector<AtomString> EventEmitter::eventTypes()
{
if (auto* data = eventTargetData())
return data->eventListenerMap.eventTypes();
return {};
}

const EventListenerVector& EventEmitter::eventListeners(const AtomString& eventType)
{
auto* data = eventTargetData();
auto* listenerVector = data ? data->eventListenerMap.find(eventType) : nullptr;
static NeverDestroyed<EventListenerVector> emptyVector;
return listenerVector ? *listenerVector : emptyVector.get();
}

void EventEmitter::invalidateEventListenerRegions()
{
}

} // namespace WebCore

0 comments on commit 3d8bc14

Please sign in to comment.