test-eventtarget.js (#19547)

Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
Co-authored-by: Ciro Spaciari <ciro.spaciari@gmail.com>
This commit is contained in:
pfg
2025-05-16 23:17:35 -07:00
committed by GitHub
parent 89c5e40544
commit 342fe232d0
11 changed files with 889 additions and 21 deletions

11
.vscode/launch.json generated vendored
View File

@@ -1113,6 +1113,17 @@
"BUN_GARBAGE_COLLECTOR_LEVEL": "2",
},
},
{
"type": "bun",
"name": "[JS] bun run [file]",
"runtime": "${workspaceFolder}/build/debug/bun-debug",
"runtimeArgs": ["run", "${file}"],
"cwd": "${workspaceFolder}",
"env": {
"BUN_DEBUG_QUIET_LOGS": "1",
"BUN_GARBAGE_COLLECTOR_LEVEL": "2",
},
},
{
"type": "midas-rr",
"request": "attach",

View File

@@ -1390,12 +1390,40 @@ extern "C" void Bun__Process__emitWarning(Zig::GlobalObject* globalObject, Encod
JSValue::decode(ctor));
}
JSValue Process::emitWarning(JSC::JSGlobalObject* lexicalGlobalObject, JSValue warning, JSValue type, JSValue code, JSValue ctor)
JSValue Process::emitWarningErrorInstance(JSC::JSGlobalObject* lexicalGlobalObject, JSValue errorInstance)
{
Zig::GlobalObject* globalObject = defaultGlobalObject(lexicalGlobalObject);
VM& vm = getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
auto* process = jsCast<Process*>(globalObject->processObject());
auto warningName = errorInstance.get(lexicalGlobalObject, vm.propertyNames->name);
RETURN_IF_EXCEPTION(scope, {});
if (isJSValueEqualToASCIILiteral(globalObject, warningName, "DeprecationWarning"_s)) {
if (Bun__Node__ProcessNoDeprecation) {
return jsUndefined();
}
if (Bun__Node__ProcessThrowDeprecation) {
// // Delay throwing the error to guarantee that all former warnings were properly logged.
// return process.nextTick(() => {
// throw warning;
// });
auto func = JSFunction::create(vm, globalObject, 1, ""_s, jsFunction_throwValue, JSC::ImplementationVisibility::Private);
process->queueNextTick(globalObject, func, errorInstance);
return jsUndefined();
}
}
// process.nextTick(doEmitWarning, warning);
auto func = JSFunction::create(vm, globalObject, 1, ""_s, jsFunction_emitWarning, JSC::ImplementationVisibility::Private);
process->queueNextTick(globalObject, func, errorInstance);
return jsUndefined();
}
JSValue Process::emitWarning(JSC::JSGlobalObject* lexicalGlobalObject, JSValue warning, JSValue type, JSValue code, JSValue ctor)
{
Zig::GlobalObject* globalObject = defaultGlobalObject(lexicalGlobalObject);
VM& vm = getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue detail = jsUndefined();
if (Bun__Node__ProcessNoDeprecation && isJSValueEqualToASCIILiteral(globalObject, type, "DeprecationWarning"_s)) {
@@ -1453,25 +1481,7 @@ JSValue Process::emitWarning(JSC::JSGlobalObject* lexicalGlobalObject, JSValue w
if (!detail.isUndefined()) errorInstance->putDirect(vm, vm.propertyNames->detail, detail, JSC::PropertyAttribute::DontEnum | 0);
// ErrorCaptureStackTrace(warning, ctor || process.emitWarning);
if (isJSValueEqualToASCIILiteral(globalObject, type, "DeprecationWarning"_s)) {
if (Bun__Node__ProcessNoDeprecation) {
return jsUndefined();
}
if (Bun__Node__ProcessThrowDeprecation) {
// // Delay throwing the error to guarantee that all former warnings were properly logged.
// return process.nextTick(() => {
// throw warning;
// });
auto func = JSFunction::create(vm, globalObject, 1, ""_s, jsFunction_throwValue, JSC::ImplementationVisibility::Private);
process->queueNextTick(globalObject, func, errorInstance);
return jsUndefined();
}
}
// process.nextTick(doEmitWarning, warning);
auto func = JSFunction::create(vm, globalObject, 1, ""_s, jsFunction_emitWarning, JSC::ImplementationVisibility::Private);
process->queueNextTick(globalObject, func, errorInstance);
return jsUndefined();
RELEASE_AND_RETURN(scope, emitWarningErrorInstance(lexicalGlobalObject, errorInstance));
}
JSC_DEFINE_HOST_FUNCTION(Process_emitWarning, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))

View File

@@ -65,6 +65,7 @@ public:
// This is equivalent to `process.nextTick(() => process.emit(eventName, event))` from JavaScript.
void emitOnNextTick(Zig::GlobalObject* globalObject, ASCIILiteral eventName, JSValue event);
static JSValue emitWarningErrorInstance(JSC::JSGlobalObject* lexicalGlobalObject, JSValue errorInstance);
static JSValue emitWarning(JSC::JSGlobalObject* lexicalGlobalObject, JSValue warning, JSValue type, JSValue code, JSValue ctor);
JSString* cachedCwd() { return m_cachedCwd.get(); }

View File

@@ -70,6 +70,7 @@ const errors: ErrorCodeMapping = [
["ERR_DNS_SET_SERVERS_FAILED", Error],
["ERR_ENCODING_INVALID_ENCODED_DATA", TypeError],
["ERR_ENCODING_NOT_SUPPORTED", RangeError],
["ERR_EVENT_RECURSION", Error],
["ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE", Error],
["ERR_FORMDATA_PARSE_ERROR", TypeError],
["ERR_FS_CP_DIR_TO_NON_DIR", Error],

View File

@@ -73,9 +73,11 @@ enum ExceptionCode : uint8_t {
// Used to indicate to the bindings that a JS exception was thrown below and it should be propagated.
ExistingExceptionError,
// Node errors
InvalidThisError,
InvalidURLError,
CryptoOperationFailedError,
EVENT_RECURSION,
};
} // namespace WebCore

View File

@@ -185,6 +185,9 @@ JSValue createDOMException(JSGlobalObject* lexicalGlobalObject, ExceptionCode ec
case ExceptionCode::CryptoOperationFailedError:
return Bun::createError(lexicalGlobalObject, Bun::ErrorCode::ERR_CRYPTO_OPERATION_FAILED, message.isEmpty() ? "Crypto operation failed"_s : message);
case ExceptionCode::EVENT_RECURSION:
return Bun::createError(lexicalGlobalObject, Bun::ErrorCode::ERR_EVENT_RECURSION, message);
default: {
// FIXME: All callers to createDOMException need to pass in the correct global object.
// For now, we're going to assume the lexicalGlobalObject. Which is wrong in cases like this:

View File

@@ -56,6 +56,7 @@
#include <wtf/SetForScope.h>
#include <wtf/StdLibExtras.h>
#include <wtf/Vector.h>
#include "ErrorCode.h"
namespace WebCore {
@@ -231,9 +232,13 @@ bool EventTarget::hasActiveEventListeners(const AtomString& eventType) const
ExceptionOr<bool> EventTarget::dispatchEventForBindings(Event& event)
{
if (!event.isInitialized() || event.isBeingDispatched())
if (!event.isInitialized())
return Exception { InvalidStateError };
if (event.isBeingDispatched()) {
return Exception { EVENT_RECURSION, makeString("The event \""_s, event.type(), "\" is already being dispatched"_s) };
}
if (!scriptExecutionContext())
return false;

View File

@@ -55,6 +55,8 @@
#include <wtf/GetPtr.h>
#include <wtf/PointerPreparations.h>
#include <wtf/URL.h>
#include "ErrorCode.h"
#include "NodeValidator.h"
namespace WebCore {
using namespace JSC;
@@ -158,6 +160,10 @@ template<> JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSEventDOMConstructor::c
auto type = convert<IDLAtomStringAdaptor<IDLDOMString>>(*lexicalGlobalObject, argument0.value());
RETURN_IF_EXCEPTION(throwScope, {});
EnsureStillAliveScope argument1 = callFrame->argument(1);
if (!argument1.value().isUndefinedOrNull() && !argument1.value().isObject() && !argument1.value().isCallable()) {
Bun::ERR::INVALID_ARG_TYPE(throwScope, lexicalGlobalObject, "options"_s, "object"_s, argument1.value());
}
RETURN_IF_EXCEPTION(throwScope, {});
auto eventInitDict = convert<IDLDictionary<EventInit>>(*lexicalGlobalObject, argument1.value());
RETURN_IF_EXCEPTION(throwScope, {});
auto object = Event::create(WTFMove(type), WTFMove(eventInitDict));

View File

@@ -20,6 +20,7 @@
#include "config.h"
#include "JSEventListener.h"
#include "BunProcess.h"
// #include "BeforeUnloadEvent.h"
// #include "ContentSecurityPolicy.h"
#include "EventNames.h"
@@ -123,6 +124,22 @@ void JSEventListener::visitJSFunction(SlotVisitor& visitor) { visitJSFunctionImp
// event.setReturnValue(returnValue);
// }
JSC_DEFINE_HOST_FUNCTION(jsFunctionEmitUncaughtException, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame))
{
auto exception = callFrame->argument(0);
reportException(lexicalGlobalObject, exception);
return JSValue::encode(JSC::jsUndefined());
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionEmitUncaughtExceptionNextTick, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame))
{
Zig::GlobalObject* globalObject = defaultGlobalObject(lexicalGlobalObject);
Bun::Process* process = jsCast<Bun::Process*>(globalObject->processObject());
auto exception = callFrame->argument(0);
auto func = JSFunction::create(globalObject->vm(), globalObject, 1, String(), jsFunctionEmitUncaughtException, JSC::ImplementationVisibility::Private);
process->queueNextTick(lexicalGlobalObject, func, exception);
return JSC::JSValue::encode(JSC::jsUndefined());
}
void JSEventListener::handleEvent(ScriptExecutionContext& scriptExecutionContext, Event& event)
{
if (scriptExecutionContext.isJSExecutionForbidden())
@@ -234,6 +251,32 @@ void JSEventListener::handleEvent(ScriptExecutionContext& scriptExecutionContext
if (handleExceptionIfNeeded(uncaughtException))
return;
// Node handles promises in the return value and throws an uncaught exception on nextTick if it rejects.
// See event_target.js function addCatch in node
if (retval.isObject()) {
auto then = retval.get(lexicalGlobalObject, vm.propertyNames->then);
if (UNLIKELY(scope.exception())) {
auto* exception = scope.exception();
scope.clearException();
event.target()->uncaughtExceptionInEventHandler();
reportException(lexicalGlobalObject, exception);
return;
}
if (then.isCallable()) {
MarkedArgumentBuffer arglist;
arglist.append(JSValue(JSC::jsUndefined()));
arglist.append(JSValue(JSC::JSFunction::create(vm, lexicalGlobalObject, 1, String(), jsFunctionEmitUncaughtExceptionNextTick, ImplementationVisibility::Public, NoIntrinsic))); // err => process.nextTick(() => throw err)
JSC::call(lexicalGlobalObject, then, retval, arglist, "Promise.then is not callable"_s);
if (UNLIKELY(scope.exception())) {
auto* exception = scope.exception();
scope.clearException();
event.target()->uncaughtExceptionInEventHandler();
reportException(lexicalGlobalObject, exception);
return;
}
}
}
if (!m_isAttribute) {
// This is an EventListener and there is therefore no need for any return value handling.
return;

View File

@@ -55,6 +55,7 @@
#include <wtf/GetPtr.h>
#include <wtf/PointerPreparations.h>
#include <wtf/URL.h>
#include "BunProcess.h"
namespace WebCore {
using namespace JSC;
@@ -223,6 +224,23 @@ static inline JSC::EncodedJSValue jsEventTargetPrototypeFunction_addEventListene
EnsureStillAliveScope argument2 = callFrame->argument(2);
auto options = argument2.value().isUndefined() ? false : convert<IDLUnion<IDLDictionary<AddEventListenerOptions>, IDLBoolean>>(*lexicalGlobalObject, argument2.value());
RETURN_IF_EXCEPTION(throwScope, {});
// Emit a warning if listener is null, as it has no effect
if (!listener) {
String warningMessage;
if (argument1.value().isNull()) {
warningMessage = "addEventListener called with null listener, which has no effect."_s;
} else {
warningMessage = "addEventListener called with undefined listener, which has no effect."_s;
}
auto errorInstance = JSC::ErrorInstance::create(vm, lexicalGlobalObject->errorStructure(JSC::ErrorType::Error), warningMessage, JSValue(), nullptr, RuntimeType::TypeNothing, JSC::ErrorType::Error);
errorInstance->putDirect(vm, vm.propertyNames->name, jsString(vm, String("AddEventListenerArgumentTypeWarning"_s)));
JSObject& target = *castedThis;
errorInstance->putDirect(vm, vm.propertyNames->target, &target);
RETURN_IF_EXCEPTION(throwScope, {});
errorInstance->putDirect(vm, vm.propertyNames->type, jsString(vm, WTFMove(type)));
Bun::Process::emitWarningErrorInstance(lexicalGlobalObject, errorInstance);
RETURN_IF_EXCEPTION(throwScope, {});
}
auto result = JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.addEventListenerForBindings(WTFMove(type), WTFMove(listener), WTFMove(options)); }));
RETURN_IF_EXCEPTION(throwScope, {});
vm.writeBarrier(&static_cast<JSObject&>(*castedThis), argument1.value());

View File

@@ -0,0 +1,768 @@
// Flags: --expose-internals --no-warnings --expose-gc
'use strict';
const common = require('../common');
let defineEventHandler;
let kWeakHandler;
if (typeof Bun === "undefined") {
({
defineEventHandler,
kWeakHandler,
} = require('internal/event_target'));
}
const {
ok,
deepStrictEqual,
strictEqual,
throws,
} = require('assert');
const { once } = require('events');
const { inspect } = require('util');
const { setTimeout: delay } = require('timers/promises');
// The globals are defined.
ok(Event);
ok(EventTarget);
// The warning event has special behavior regarding attaching listeners
let lastWarning;
process.on('warning', (e) => {
lastWarning = e;
});
// Utility promise for parts of the test that need to wait for eachother -
// Namely tests for warning events
/* eslint-disable no-unused-vars */
let asyncTest = Promise.resolve();
// First, test Event
{
const ev = new Event('foo');
strictEqual(ev.type, 'foo');
strictEqual(ev.cancelable, false);
strictEqual(ev.defaultPrevented, false);
strictEqual(typeof ev.timeStamp, 'number');
// Compatibility properties with the DOM
deepStrictEqual(ev.composedPath(), []);
strictEqual(ev.returnValue, true);
strictEqual(ev.bubbles, false);
strictEqual(ev.composed, false);
strictEqual(ev.isTrusted, false);
strictEqual(ev.eventPhase, 0);
strictEqual(ev.cancelBubble, false);
// Not cancelable
ev.preventDefault();
strictEqual(ev.defaultPrevented, false);
}
{
[
'foo',
1,
false,
].forEach((i) => (
throws(() => new Event('foo', i), {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: 'The "options" argument must be of type object.' +
common.invalidArgTypeHelper(i),
})
));
}
{
const ev = new Event('foo');
strictEqual(ev.cancelBubble, false);
ev.cancelBubble = true;
strictEqual(ev.cancelBubble, true);
}
{
const ev = new Event('foo');
strictEqual(ev.cancelBubble, false);
ev.stopPropagation();
strictEqual(ev.cancelBubble, true);
}
{
const ev = new Event('foo');
strictEqual(ev.cancelBubble, false);
ev.cancelBubble = 'some-truthy-value';
strictEqual(ev.cancelBubble, true);
}
{
// No argument behavior - throw TypeError
throws(() => {
new Event();
}, TypeError);
// Too many arguments passed behavior - ignore additional arguments
const ev = new Event('foo', {}, {});
strictEqual(ev.type, 'foo');
}
{
const ev = new Event('foo');
strictEqual(ev.cancelBubble, false);
ev.cancelBubble = true;
strictEqual(ev.cancelBubble, true);
}
{
const ev = new Event('foo');
strictEqual(ev.cancelBubble, false);
ev.stopPropagation();
strictEqual(ev.cancelBubble, true);
}
{
const ev = new Event('foo');
strictEqual(ev.cancelBubble, false);
ev.cancelBubble = 'some-truthy-value';
strictEqual(ev.cancelBubble, true);
}
{
const ev = new Event('foo', { cancelable: true });
strictEqual(ev.type, 'foo');
strictEqual(ev.cancelable, true);
strictEqual(ev.defaultPrevented, false);
ev.preventDefault();
strictEqual(ev.defaultPrevented, true);
throws(() => new Event(Symbol()), TypeError);
}
{
const ev = new Event('foo');
strictEqual(ev.isTrusted, false);
}
{
const eventTarget = new EventTarget();
const ev1 = common.mustCall(function(event) {
strictEqual(event.type, 'foo');
strictEqual(this, eventTarget);
strictEqual(event.eventPhase, 2);
}, 2);
const ev2 = {
handleEvent: common.mustCall(function(event) {
strictEqual(event.type, 'foo');
strictEqual(this, ev2);
}),
};
eventTarget.addEventListener('foo', ev1);
eventTarget.addEventListener('foo', ev2, { once: true });
ok(eventTarget.dispatchEvent(new Event('foo')));
eventTarget.dispatchEvent(new Event('foo'));
eventTarget.removeEventListener('foo', ev1);
eventTarget.dispatchEvent(new Event('foo'));
}
{
// event subclassing
const SubEvent = class extends Event {};
const ev = new SubEvent('foo');
const eventTarget = new EventTarget();
const fn = common.mustCall((event) => strictEqual(event, ev));
eventTarget.addEventListener('foo', fn, { once: true });
eventTarget.dispatchEvent(ev);
}
{
// Same event dispatched multiple times.
const event = new Event('foo');
const eventTarget1 = new EventTarget();
const eventTarget2 = new EventTarget();
eventTarget1.addEventListener('foo', common.mustCall((event) => {
strictEqual(event.eventPhase, Event.AT_TARGET);
strictEqual(event.target, eventTarget1);
deepStrictEqual(event.composedPath(), [eventTarget1]);
}));
eventTarget2.addEventListener('foo', common.mustCall((event) => {
strictEqual(event.eventPhase, Event.AT_TARGET);
strictEqual(event.target, eventTarget2);
deepStrictEqual(event.composedPath(), [eventTarget2]);
}));
eventTarget1.dispatchEvent(event);
strictEqual(event.eventPhase, Event.NONE);
strictEqual(event.target, eventTarget1);
deepStrictEqual(event.composedPath(), []);
eventTarget2.dispatchEvent(event);
strictEqual(event.eventPhase, Event.NONE);
strictEqual(event.target, eventTarget2);
deepStrictEqual(event.composedPath(), []);
}
{
// Same event dispatched multiple times, without listeners added.
const event = new Event('foo');
const eventTarget1 = new EventTarget();
const eventTarget2 = new EventTarget();
eventTarget1.dispatchEvent(event);
strictEqual(event.eventPhase, Event.NONE);
strictEqual(event.target, eventTarget1);
deepStrictEqual(event.composedPath(), []);
eventTarget2.dispatchEvent(event);
strictEqual(event.eventPhase, Event.NONE);
strictEqual(event.target, eventTarget2);
deepStrictEqual(event.composedPath(), []);
}
{
const eventTarget = new EventTarget();
const event = new Event('foo', { cancelable: true });
eventTarget.addEventListener('foo', (event) => event.preventDefault());
ok(!eventTarget.dispatchEvent(event));
}
{
// Adding event listeners with a boolean useCapture
const eventTarget = new EventTarget();
const event = new Event('foo');
const fn = common.mustCall((event) => strictEqual(event.type, 'foo'));
eventTarget.addEventListener('foo', fn, false);
eventTarget.dispatchEvent(event);
}
{
// The `options` argument can be `null`.
const eventTarget = new EventTarget();
const event = new Event('foo');
const fn = common.mustCall((event) => strictEqual(event.type, 'foo'));
eventTarget.addEventListener('foo', fn, null);
eventTarget.dispatchEvent(event);
}
{
const target = new EventTarget();
const listener = {};
// AddEventListener should not require handleEvent to be
// defined on an EventListener.
target.addEventListener('foo', listener);
listener.handleEvent = common.mustCall(function(event) {
strictEqual(event.type, 'foo');
strictEqual(this, listener);
});
target.dispatchEvent(new Event('foo'));
}
{
const target = new EventTarget();
const listener = {};
// do not throw
target.removeEventListener('foo', listener);
target.addEventListener('foo', listener);
target.removeEventListener('foo', listener);
listener.handleEvent = common.mustNotCall();
target.dispatchEvent(new Event('foo'));
}
{
const uncaughtException = common.mustCall((err, origin) => {
strictEqual(err.message, 'boom');
strictEqual(origin, 'uncaughtException');
}, 4);
// Make sure that we no longer call 'error' on error.
process.on('error', common.mustNotCall());
// Don't call rejection even for async handlers.
process.on('unhandledRejection', common.mustNotCall());
process.on('uncaughtException', uncaughtException);
const eventTarget = new EventTarget();
const ev1 = async () => { throw new Error('boom'); };
const ev2 = () => { throw new Error('boom'); };
const ev3 = { handleEvent() { throw new Error('boom'); } };
const ev4 = { async handleEvent() { throw new Error('boom'); } };
// Errors in a handler won't stop calling the others.
eventTarget.addEventListener('foo', ev1, { once: true });
eventTarget.addEventListener('foo', ev2, { once: true });
eventTarget.addEventListener('foo', ev3, { once: true });
eventTarget.addEventListener('foo', ev4, { once: true });
eventTarget.dispatchEvent(new Event('foo'));
}
{
const eventTarget = new EventTarget();
// Once handler only invoked once
const ev = common.mustCall((event) => {
// Can invoke the same event name recursively
eventTarget.dispatchEvent(new Event('foo'));
});
// Errors in a handler won't stop calling the others.
eventTarget.addEventListener('foo', ev, { once: true });
eventTarget.dispatchEvent(new Event('foo'));
}
{
// Coercion to string works
strictEqual((new Event(1)).type, '1');
strictEqual((new Event(false)).type, 'false');
strictEqual((new Event({})).type, String({}));
const target = new EventTarget();
[
'foo',
{}, // No type event
undefined,
1,
false,
].forEach((i) => {
throws(() => target.dispatchEvent(i), {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: typeof Bun === "undefined" ? 'The "event" argument must be an instance of Event.' +
common.invalidArgTypeHelper(i) : 'Argument 1 (\'event\') to EventTarget.dispatchEvent must be an instance of Event',
});
});
const err = (arg) => ({
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
...typeof Bun === "undefined" ? {message: 'The "listener" argument must be an instance of EventListener.' +
common.invalidArgTypeHelper(arg)} : {},
});
[
'foo',
1,
false,
].forEach((i) => throws(() => target.addEventListener('foo', i), err(i)));
}
{
const target = new EventTarget();
once(target, 'foo').then(common.mustCall());
target.dispatchEvent(new Event('foo'));
}
{
const target = new EventTarget();
const event = new Event('foo');
strictEqual(event.cancelBubble, false);
event.stopImmediatePropagation();
strictEqual(event.cancelBubble, true);
target.addEventListener('foo', common.mustNotCall());
target.dispatchEvent(event);
}
{
const target = new EventTarget();
const event = new Event('foo');
target.addEventListener('foo', common.mustCall((event) => {
event.stopImmediatePropagation();
}));
target.addEventListener('foo', common.mustNotCall());
target.dispatchEvent(event);
}
{
const target = new EventTarget();
const event = new Event('foo');
target.addEventListener('foo', common.mustCall((event) => {
event.stopImmediatePropagation();
}));
target.addEventListener('foo', common.mustNotCall());
target.dispatchEvent(event);
}
{
const target = new EventTarget();
const event = new Event('foo');
strictEqual(event.target, null);
target.addEventListener('foo', common.mustCall((event) => {
strictEqual(event.target, target);
strictEqual(event.currentTarget, target);
strictEqual(event.srcElement, target);
}));
target.dispatchEvent(event);
}
{
const target1 = new EventTarget();
const target2 = new EventTarget();
const event = new Event('foo');
target1.addEventListener('foo', common.mustCall((event) => {
throws(() => target2.dispatchEvent(event), {
code: 'ERR_EVENT_RECURSION',
});
}));
target1.dispatchEvent(event);
}
{
const target = new EventTarget();
const a = common.mustCall(() => target.removeEventListener('foo', a));
const b = common.mustCall(2);
target.addEventListener('foo', a);
target.addEventListener('foo', b);
target.dispatchEvent(new Event('foo'));
target.dispatchEvent(new Event('foo'));
}
{
const target = new EventTarget();
const a = common.mustCall(3);
target.addEventListener('foo', a, { capture: true });
target.addEventListener('foo', a, { capture: false });
target.dispatchEvent(new Event('foo'));
target.removeEventListener('foo', a, { capture: true });
target.dispatchEvent(new Event('foo'));
target.removeEventListener('foo', a, { capture: false });
target.dispatchEvent(new Event('foo'));
}
{
const target = new EventTarget();
strictEqual(target.toString(), '[object EventTarget]');
const event = new Event('');
strictEqual(event.toString(), '[object Event]');
}
if (typeof Bun === "undefined") { // Node internal
const target = new EventTarget();
defineEventHandler(target, 'foo');
target.onfoo = common.mustCall();
target.dispatchEvent(new Event('foo'));
}
if (typeof Bun === "undefined") { // Node internal
const target = new EventTarget();
defineEventHandler(target, 'foo');
strictEqual(target.onfoo, null);
}
if (typeof Bun === "undefined") { // Node internal
const target = new EventTarget();
defineEventHandler(target, 'foo');
let count = 0;
target.onfoo = () => count++;
target.onfoo = common.mustCall(() => count++);
target.dispatchEvent(new Event('foo'));
strictEqual(count, 1);
}
if (typeof Bun === "undefined") { // Node internal
const target = new EventTarget();
defineEventHandler(target, 'foo');
let count = 0;
target.addEventListener('foo', () => count++);
target.onfoo = common.mustCall(() => count++);
target.dispatchEvent(new Event('foo'));
strictEqual(count, 2);
}
if (typeof Bun === "undefined") { // Node internal
const target = new EventTarget();
defineEventHandler(target, 'foo');
const fn = common.mustNotCall();
target.onfoo = fn;
strictEqual(target.onfoo, fn);
target.onfoo = null;
target.dispatchEvent(new Event('foo'));
}
{
// `this` value of dispatchEvent
const target = new EventTarget();
const target2 = new EventTarget();
const event = new Event('foo');
ok(target.dispatchEvent.call(target2, event));
[
'foo',
{},
[],
1,
...(typeof Bun === "undefined" ? [
// In the web standard, EventTarget.prototype.dispatchEvent === globalThis.dispatchEvent, and calling with this as null or undefined will call it on the global object
// Node does not have globalThis.dispatchEvent.
null,
undefined,
] : []),
false,
Symbol(),
/a/,
].forEach((i) => {
throws(() => target.dispatchEvent.call(i, event), {
code: 'ERR_INVALID_THIS',
});
});
}
{
// Event Statics
strictEqual(Event.NONE, 0);
strictEqual(Event.CAPTURING_PHASE, 1);
strictEqual(Event.AT_TARGET, 2);
strictEqual(Event.BUBBLING_PHASE, 3);
strictEqual(new Event('foo').eventPhase, Event.NONE);
const target = new EventTarget();
target.addEventListener('foo', common.mustCall((e) => {
strictEqual(e.eventPhase, Event.AT_TARGET);
}), { once: true });
target.dispatchEvent(new Event('foo'));
// Event is a function
strictEqual(Event.length, 1);
}
{
const target = new EventTarget();
const ev = new Event('toString');
const fn = common.mustCall((event) => strictEqual(event.type, 'toString'));
target.addEventListener('toString', fn);
target.dispatchEvent(ev);
}
{
const target = new EventTarget();
const ev = new Event('__proto__');
const fn = common.mustCall((event) => strictEqual(event.type, '__proto__'));
target.addEventListener('__proto__', fn);
target.dispatchEvent(ev);
}
{
const eventTarget = new EventTarget();
// Single argument throws
throws(() => eventTarget.addEventListener('foo'), TypeError);
// Null events - does not throw
eventTarget.addEventListener('foo', null);
eventTarget.removeEventListener('foo', null);
eventTarget.addEventListener('foo', undefined);
eventTarget.removeEventListener('foo', undefined);
// Strings, booleans
throws(() => eventTarget.addEventListener('foo', 'hello'), TypeError);
throws(() => eventTarget.addEventListener('foo', false), TypeError);
throws(() => eventTarget.addEventListener('foo', Symbol()), TypeError);
asyncTest = asyncTest.then(async () => {
const eventTarget = new EventTarget();
// Single argument throws
throws(() => eventTarget.addEventListener('foo'), TypeError);
// Null events - does not throw
eventTarget.addEventListener('foo', null);
eventTarget.removeEventListener('foo', null);
// Warnings always happen after nextTick, so wait for a timer of 0
await delay(0);
strictEqual(lastWarning.name, 'AddEventListenerArgumentTypeWarning');
strictEqual(lastWarning.target, eventTarget);
lastWarning = null;
eventTarget.addEventListener('foo', undefined);
await delay(0);
strictEqual(lastWarning.name, 'AddEventListenerArgumentTypeWarning');
strictEqual(lastWarning.target, eventTarget);
eventTarget.removeEventListener('foo', undefined);
// Strings, booleans
throws(() => eventTarget.addEventListener('foo', 'hello'), TypeError);
throws(() => eventTarget.addEventListener('foo', false), TypeError);
throws(() => eventTarget.addEventListener('foo', Symbol()), TypeError);
});
}
{
const eventTarget = new EventTarget();
const event = new Event('foo');
eventTarget.dispatchEvent(event);
strictEqual(event.target, eventTarget);
}
{
// Event target exported keys
const eventTarget = new EventTarget();
deepStrictEqual(Object.keys(eventTarget), []);
deepStrictEqual(Object.getOwnPropertyNames(eventTarget), []);
const parentKeys = Object.keys(Object.getPrototypeOf(eventTarget)).sort();
const keys = ['addEventListener', 'dispatchEvent', 'removeEventListener'];
deepStrictEqual(parentKeys, keys);
}
{
// Subclassing
class SubTarget extends EventTarget {}
const target = new SubTarget();
target.addEventListener('foo', common.mustCall());
target.dispatchEvent(new Event('foo'));
}
{
// Test event order
const target = new EventTarget();
let state = 0;
target.addEventListener('foo', common.mustCall(() => {
strictEqual(state, 0);
state++;
}));
target.addEventListener('foo', common.mustCall(() => {
strictEqual(state, 1);
}));
target.dispatchEvent(new Event('foo'));
}
if (typeof Bun === "undefined") { // Node internal
const target = new EventTarget();
defineEventHandler(target, 'foo');
const descriptor = Object.getOwnPropertyDescriptor(target, 'onfoo');
strictEqual(descriptor.configurable, true);
strictEqual(descriptor.enumerable, true);
}
if (typeof Bun === "undefined") { // Node internal
const target = new EventTarget();
defineEventHandler(target, 'foo');
const output = [];
target.addEventListener('foo', () => output.push(1));
target.onfoo = common.mustNotCall();
target.addEventListener('foo', () => output.push(3));
target.onfoo = () => output.push(2);
target.addEventListener('foo', () => output.push(4));
target.dispatchEvent(new Event('foo'));
deepStrictEqual(output, [1, 2, 3, 4]);
}
if (typeof Bun === "undefined") { // Node internal
const target = new EventTarget();
defineEventHandler(target, 'foo', 'bar');
const output = [];
target.addEventListener('bar', () => output.push(1));
target.onfoo = () => output.push(2);
target.dispatchEvent(new Event('bar'));
deepStrictEqual(output, [1, 2]);
}
{
const et = new EventTarget();
const listener = common.mustNotCall();
et.addEventListener('foo', common.mustCall((e) => {
et.removeEventListener('foo', listener);
}));
et.addEventListener('foo', listener);
et.dispatchEvent(new Event('foo'));
}
{
const ev = new Event('test');
const evConstructorName = inspect(ev, {
depth: -1,
});
if (typeof Bun === "undefined") {
strictEqual(evConstructorName, 'Event');
} else {
strictEqual(evConstructorName, '[Event]');
}
const inspectResult = inspect(ev, {
depth: 1,
});
ok(inspectResult.includes('Event'));
}
{
const et = new EventTarget();
const inspectResult = inspect(et, {
depth: 1,
});
ok(inspectResult.includes('EventTarget'));
}
{
const ev = new Event('test');
strictEqual(ev.constructor.name, 'Event');
const et = new EventTarget();
strictEqual(et.constructor.name, 'EventTarget');
}
if (typeof Bun === "undefined") { // Node internal
// Weak event listeners work
const et = new EventTarget();
const listener = common.mustCall();
et.addEventListener('foo', listener, { [kWeakHandler]: et });
et.dispatchEvent(new Event('foo'));
}
if (typeof Bun === "undefined") { // Node internal
// Weak event listeners can be removed and weakness is not part of the key
const et = new EventTarget();
const listener = common.mustNotCall();
et.addEventListener('foo', listener, { [kWeakHandler]: et });
et.removeEventListener('foo', listener);
et.dispatchEvent(new Event('foo'));
}
if (typeof Bun === "undefined") { // Node internal
// Test listeners are held weakly
const et = new EventTarget();
et.addEventListener('foo', common.mustNotCall(), { [kWeakHandler]: {} });
setImmediate(() => {
global.gc();
et.dispatchEvent(new Event('foo'));
});
}
{
const et = new EventTarget();
throws(() => et.addEventListener(), {
code: 'ERR_MISSING_ARGS',
name: 'TypeError',
});
throws(() => et.addEventListener('foo'), {
code: 'ERR_MISSING_ARGS',
name: 'TypeError',
});
throws(() => et.removeEventListener(), {
code: 'ERR_MISSING_ARGS',
name: 'TypeError',
});
throws(() => et.removeEventListener('foo'), {
code: 'ERR_MISSING_ARGS',
name: 'TypeError',
});
throws(() => et.dispatchEvent(), {
code: 'ERR_MISSING_ARGS',
name: 'TypeError',
});
}
{
const et = new EventTarget();
throws(() => {
et.addEventListener(Symbol('symbol'), () => {});
}, TypeError);
throws(() => {
et.removeEventListener(Symbol('symbol'), () => {});
}, TypeError);
}
{
// Test that event listeners are removed by signal even when
// signal's abort event propagation stopped
const controller = new AbortController();
const { signal } = controller;
signal.addEventListener('abort', (e) => e.stopImmediatePropagation(), { once: true });
const et = new EventTarget();
et.addEventListener('foo', common.mustNotCall(), { signal });
controller.abort();
et.dispatchEvent(new Event('foo'));
}
{
const event = new Event('foo');
strictEqual(event.cancelBubble, false);
event.cancelBubble = true;
strictEqual(event.cancelBubble, true);
}
{
// A null eventInitDict should not throw an error.
new Event('', null);
new Event('', undefined);
}