diff --git a/lib/internal/event_target.js b/lib/internal/event_target.js index a64360df3caaa9..ce867cc1e35793 100644 --- a/lib/internal/event_target.js +++ b/lib/internal/event_target.js @@ -47,6 +47,7 @@ const { } = EventEmitter; const kEvents = Symbol('kEvents'); +const kIsBeingDispatched = Symbol('kIsBeingDispatched'); const kStop = Symbol('kStop'); const kTarget = Symbol('kTarget'); const kHandlers = Symbol('khandlers'); @@ -105,6 +106,7 @@ class Event { configurable: false }); this[kTarget] = null; + this[kIsBeingDispatched] = false; } [customInspectSymbol](depth, options) { @@ -151,12 +153,12 @@ class Event { // These are not supported in Node.js and are provided purely for // API completeness. - composedPath() { return this[kTarget] ? [this[kTarget]] : []; } + composedPath() { return this[kIsBeingDispatched] ? [this[kTarget]] : []; } get returnValue() { return !this.defaultPrevented; } get bubbles() { return this[kBubbles]; } get composed() { return this[kComposed]; } get eventPhase() { - return this[kTarget] ? Event.AT_TARGET : Event.NONE; + return this[kIsBeingDispatched] ? Event.AT_TARGET : Event.NONE; } get cancelBubble() { return this[kPropagationStopped]; } set cancelBubble(value) { @@ -397,7 +399,7 @@ class EventTarget { if (!isEventTarget(this)) throw new ERR_INVALID_THIS('EventTarget'); - if (event[kTarget] !== null) + if (event[kIsBeingDispatched]) throw new ERR_EVENT_RECURSION(event.type); this[kHybridDispatch](event, event.type, event); @@ -410,11 +412,14 @@ class EventTarget { if (event === undefined) { event = this[kCreateEvent](nodeValue, type); event[kTarget] = this; + event[kIsBeingDispatched] = true; } return event; }; - if (event !== undefined) + if (event !== undefined) { event[kTarget] = this; + event[kIsBeingDispatched] = true; + } const root = this[kEvents].get(type); if (root === undefined || root.next === undefined) @@ -453,6 +458,9 @@ class EventTarget { let result; if (callback) { result = FunctionPrototypeCall(callback, this, arg); + if (!handler.isNodeStyleListener) { + arg[kIsBeingDispatched] = false; + } } if (result !== undefined && result !== null) addCatch(result); @@ -464,7 +472,7 @@ class EventTarget { } if (event !== undefined) - event[kTarget] = undefined; + event[kIsBeingDispatched] = false; } [kCreateEvent](nodeValue, type) { diff --git a/test/parallel/test-eventtarget.js b/test/parallel/test-eventtarget.js index ffdd21a28f5743..cfad9c60a32c01 100644 --- a/test/parallel/test-eventtarget.js +++ b/test/parallel/test-eventtarget.js @@ -162,6 +162,36 @@ let asyncTest = Promise.resolve(); 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(), []); +} + { const eventTarget = new EventTarget(); const event = new Event('foo', { cancelable: true });