From a3cdde0c3569950ea5c218d39c81f0ac3f1f1887 Mon Sep 17 00:00:00 2001 From: Benjamin Gruenbaum Date: Fri, 6 Nov 2020 11:01:59 +0200 Subject: [PATCH] events: fire handlers in correct oder PR-URL: https://github.com/nodejs/node/pull/35931 Reviewed-By: Anna Henningsen Reviewed-By: Antoine du Hamel Reviewed-By: Daijiro Wachi Reviewed-By: Rich Trott --- lib/internal/event_target.js | 42 ++++++++++++++++++++++++------- test/parallel/test-eventtarget.js | 12 +++++++++ 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/lib/internal/event_target.js b/lib/internal/event_target.js index fef80c65d97ba3..3d28205737d601 100644 --- a/lib/internal/event_target.js +++ b/lib/internal/event_target.js @@ -8,6 +8,7 @@ const { Object, ObjectDefineProperty, ObjectGetOwnPropertyDescriptor, + ReflectApply, SafeMap, String, Symbol, @@ -577,24 +578,47 @@ function emitUnhandledRejectionOrErr(that, err, event) { process.emit('error', err, event); } +function makeEventHandler(handler) { + // Event handlers are dispatched in the order they were first set + // See https://github.com/nodejs/node/pull/35949#issuecomment-722496598 + function eventHandler(...args) { + if (typeof eventHandler.handler !== 'function') { + return; + } + return ReflectApply(eventHandler.handler, this, args); + } + eventHandler.handler = handler; + return eventHandler; +} + function defineEventHandler(emitter, name) { // 8.1.5.1 Event handlers - basically `on[eventName]` attributes ObjectDefineProperty(emitter, `on${name}`, { get() { - return this[kHandlers]?.get(name); + return this[kHandlers]?.get(name)?.handler; }, set(value) { - const oldValue = this[kHandlers]?.get(name); - if (oldValue) { - this.removeEventListener(name, oldValue); - } - if (typeof value === 'function') { - this.addEventListener(name, value); - } if (!this[kHandlers]) { this[kHandlers] = new SafeMap(); } - this[kHandlers].set(name, value); + let wrappedHandler = this[kHandlers]?.get(name); + if (wrappedHandler) { + if (typeof wrappedHandler.handler === 'function') { + this[kEvents].get(name).size--; + const size = this[kEvents].get(name).size; + this[kRemoveListener](size, name, wrappedHandler.handler, false); + } + wrappedHandler.handler = value; + if (typeof wrappedHandler.handler === 'function') { + this[kEvents].get(name).size++; + const size = this[kEvents].get(name).size; + this[kNewListener](size, name, value, false, false, false); + } + } else { + wrappedHandler = makeEventHandler(value); + this.addEventListener(name, wrappedHandler); + } + this[kHandlers].set(name, wrappedHandler); }, configurable: true, enumerable: true diff --git a/test/parallel/test-eventtarget.js b/test/parallel/test-eventtarget.js index 96d755a8f32fe2..fa280027c61f29 100644 --- a/test/parallel/test-eventtarget.js +++ b/test/parallel/test-eventtarget.js @@ -524,3 +524,15 @@ let asyncTest = Promise.resolve(); strictEqual(descriptor.configurable, true); strictEqual(descriptor.enumerable, true); } +{ + 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]); +}