diff --git a/lib/internal/event_target.js b/lib/internal/event_target.js index 133edc23b2423a..e371ee06a1b83b 100644 --- a/lib/internal/event_target.js +++ b/lib/internal/event_target.js @@ -36,12 +36,13 @@ class Event { #cancelable = false; #timestamp = perf_hooks.performance.now(); - // Neither of these are currently used in the Node.js implementation + // None of these are currently used in the Node.js implementation // of EventTarget because there is no concept of bubbling or // composition. We preserve their values in Event but they are // non-ops and do not carry any semantics in Node.js #bubbles = false; #composed = false; + #propagationStopped = false; constructor(type, options) { @@ -54,6 +55,7 @@ class Event { this.#cancelable = !!cancelable; this.#bubbles = !!bubbles; this.#composed = !!composed; + this.#propagationStopped = false; this.#type = String(type); // isTrusted is special (LegacyUnforgeable) Object.defineProperty(this, 'isTrusted', { @@ -62,6 +64,7 @@ class Event { enumerable: true, configurable: false }); + this[kTarget] = null; } [customInspectSymbol](depth, options) { @@ -113,11 +116,14 @@ class Event { get eventPhase() { return this[kTarget] ? 2 : 0; // Equivalent to AT_TARGET or NONE } - cancelBubble() { - // Non-op in Node.js. Alias for stopPropagation + get cancelBubble() { return this.#propagationStopped; } + set cancelBubble(value) { + if (value) { + this.stopPropagation(); + } } stopPropagation() { - // Non-op in Node.js + this.#propagationStopped = true; } get [Symbol.toStringTag]() { return 'Event'; } @@ -240,7 +246,7 @@ class EventTarget { } if (this.#emitting.has(event.type) || - event[kTarget] !== undefined) { + event[kTarget] !== null) { throw new ERR_EVENT_RECURSION(event.type); } @@ -407,6 +413,9 @@ function validateListener(listener) { } function validateEventListenerOptions(options) { + if (typeof options === 'boolean') { + options = { capture: options }; + } if (options == null || typeof options !== 'object') throw new ERR_INVALID_ARG_TYPE('options', 'object', options); const { diff --git a/test/parallel/test-eventtarget.js b/test/parallel/test-eventtarget.js index 82a89caae1fea4..579ecfd75b3471 100644 --- a/test/parallel/test-eventtarget.js +++ b/test/parallel/test-eventtarget.js @@ -36,6 +36,7 @@ ok(EventTarget); strictEqual(ev.composed, false); strictEqual(ev.isTrusted, false); strictEqual(ev.eventPhase, 0); + strictEqual(ev.cancelBubble, false); // Not cancelable ev.preventDefault(); @@ -50,6 +51,24 @@ ok(EventTarget); 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'); @@ -132,7 +151,14 @@ ok(EventTarget); 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); +} { const eventTarget = new NodeEventTarget(); strictEqual(eventTarget.listenerCount('foo'), 0); @@ -359,6 +385,7 @@ ok(EventTarget); { 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); @@ -408,6 +435,20 @@ ok(EventTarget); { const target = new EventTarget(); strictEqual(target.toString(), '[object EventTarget]'); - const event = new Event(); + const event = new Event('foo'); strictEqual(event.toString(), '[object Event]'); } +{ + 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); +}