mirror of
https://github.com/oven-sh/bun
synced 2026-02-12 11:59:00 +00:00
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:
11
.vscode/launch.json
generated
vendored
11
.vscode/launch.json
generated
vendored
@@ -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",
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
768
test/js/node/test/parallel/test-eventtarget.js
Normal file
768
test/js/node/test/parallel/test-eventtarget.js
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user