Skip to content

Commit 16b11cd

Browse files
committed
events: expand NodeEventTarget functionality
Enable `NodeEventTarget` as a base class for `MessagePort`, by enabling special processing of events for Node.js listeners, and removing implicit constructors/private properties so that classes can be made subclasses of `NodeEventTarget` after they are created. PR-URL: #34057 Refs: https://twitter.com/addaleax/status/1276289101671608320 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: David Carlier <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
1 parent 06765d8 commit 16b11cd

1 file changed

Lines changed: 66 additions & 21 deletions

File tree

‎lib/internal/event_target.js‎

Lines changed: 66 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,13 @@ const kEvents = Symbol('kEvents');
3232
const kStop = Symbol('kStop');
3333
const kTarget = Symbol('kTarget');
3434

35+
const kHybridDispatch = Symbol.for('nodejs.internal.kHybridDispatch');
36+
const kCreateEvent = Symbol('kCreateEvent');
3537
const kNewListener = Symbol('kNewListener');
3638
const kRemoveListener = Symbol('kRemoveListener');
39+
const kIsNodeStyleListener = Symbol('kIsNodeStyleListener');
40+
const kMaxListeners = Symbol('kMaxListeners');
41+
const kMaxListenersWarned = Symbol('kMaxListenersWarned');
3742

3843
// Lazy load perf_hooks to avoid the additional overhead on startup
3944
let perf_hooks;
@@ -161,7 +166,7 @@ Object.defineProperty(Event.prototype, SymbolToStringTag, {
161166
// the linked list makes dispatching faster, even if adding/removing is
162167
// slower.
163168
class Listener {
164-
constructor(previous, listener, once, capture, passive) {
169+
constructor(previous, listener, once, capture, passive, isNodeStyleListener) {
165170
this.next = undefined;
166171
if (previous !== undefined)
167172
previous.next = this;
@@ -170,6 +175,7 @@ class Listener {
170175
this.once = once;
171176
this.capture = capture;
172177
this.passive = passive;
178+
this.isNodeStyleListener = isNodeStyleListener;
173179

174180
this.callback =
175181
typeof listener === 'function' ?
@@ -189,13 +195,17 @@ class Listener {
189195
}
190196
}
191197

198+
function initEventTarget(self) {
199+
self[kEvents] = new Map();
200+
}
201+
192202
class EventTarget {
193203
// Used in checking whether an object is an EventTarget. This is a well-known
194204
// symbol as EventTarget may be used cross-realm. See discussion in #33661.
195205
static [kIsEventTarget] = true;
196206

197207
constructor() {
198-
this[kEvents] = new Map();
208+
initEventTarget(this);
199209
}
200210

201211
[kNewListener](size, type, listener, once, capture, passive) {}
@@ -210,7 +220,8 @@ class EventTarget {
210220
const {
211221
once,
212222
capture,
213-
passive
223+
passive,
224+
isNodeStyleListener
214225
} = validateEventListenerOptions(options);
215226

216227
if (!shouldAddListener(listener)) {
@@ -232,7 +243,7 @@ class EventTarget {
232243
if (root === undefined) {
233244
root = { size: 1, next: undefined };
234245
// This is the first handler in our linked list.
235-
new Listener(root, listener, once, capture, passive);
246+
new Listener(root, listener, once, capture, passive, isNodeStyleListener);
236247
this[kNewListener](root.size, type, listener, once, capture, passive);
237248
this[kEvents].set(type, root);
238249
return;
@@ -251,7 +262,8 @@ class EventTarget {
251262
return;
252263
}
253264

254-
new Listener(previous, listener, once, capture, passive);
265+
new Listener(previous, listener, once, capture, passive,
266+
isNodeStyleListener);
255267
root.size++;
256268
this[kNewListener](root.size, type, listener, once, capture, passive);
257269
}
@@ -294,39 +306,61 @@ class EventTarget {
294306
if (event[kTarget] !== null)
295307
throw new ERR_EVENT_RECURSION(event.type);
296308

297-
const root = this[kEvents].get(event.type);
309+
this[kHybridDispatch](event, event.type, event);
310+
311+
return event.defaultPrevented !== true;
312+
}
313+
314+
[kHybridDispatch](nodeValue, type, event) {
315+
const createEvent = () => {
316+
if (event === undefined) {
317+
event = this[kCreateEvent](nodeValue, type);
318+
event[kTarget] = this;
319+
}
320+
return event;
321+
};
322+
323+
const root = this[kEvents].get(type);
298324
if (root === undefined || root.next === undefined)
299325
return true;
300326

301-
event[kTarget] = this;
327+
if (event !== undefined)
328+
event[kTarget] = this;
302329

303330
let handler = root.next;
304331
let next;
305332

306333
while (handler !== undefined &&
307-
(handler.passive || event[kStop] !== true)) {
334+
(handler.passive || event?.[kStop] !== true)) {
308335
// Cache the next item in case this iteration removes the current one
309336
next = handler.next;
310337

311338
if (handler.once) {
312339
handler.remove();
313340
root.size--;
341+
const { listener, capture } = handler;
342+
this[kRemoveListener](root.size, type, listener, capture);
314343
}
315344

316345
try {
317-
const result = handler.callback.call(this, event);
346+
let arg;
347+
if (handler.isNodeStyleListener) {
348+
arg = nodeValue;
349+
} else {
350+
arg = createEvent();
351+
}
352+
const result = handler.callback.call(this, arg);
318353
if (result !== undefined && result !== null)
319-
addCatch(this, result, event);
354+
addCatch(this, result, createEvent());
320355
} catch (err) {
321-
emitUnhandledRejectionOrErr(this, err, event);
356+
emitUnhandledRejectionOrErr(this, err, createEvent());
322357
}
323358

324359
handler = next;
325360
}
326361

327-
event[kTarget] = undefined;
328-
329-
return event.defaultPrevented !== true;
362+
if (event !== undefined)
363+
event[kTarget] = undefined;
330364
}
331365

332366
[customInspectSymbol](depth, options) {
@@ -354,15 +388,19 @@ Object.defineProperty(EventTarget.prototype, SymbolToStringTag, {
354388
value: 'EventTarget',
355389
});
356390

357-
const kMaxListeners = Symbol('maxListeners');
358-
const kMaxListenersWarned = Symbol('maxListenersWarned');
391+
function initNodeEventTarget(self) {
392+
initEventTarget(self);
393+
// eslint-disable-next-line no-use-before-define
394+
self[kMaxListeners] = NodeEventTarget.defaultMaxListeners;
395+
self[kMaxListenersWarned] = false;
396+
}
397+
359398
class NodeEventTarget extends EventTarget {
360399
static defaultMaxListeners = 10;
361400

362401
constructor() {
363402
super();
364-
this[kMaxListeners] = NodeEventTarget.defaultMaxListeners;
365-
this[kMaxListenersWarned] = false;
403+
initNodeEventTarget(this);
366404
}
367405

368406
[kNewListener](size, type, listener, once, capture, passive) {
@@ -414,17 +452,18 @@ class NodeEventTarget extends EventTarget {
414452
}
415453

416454
on(type, listener) {
417-
this.addEventListener(type, listener);
455+
this.addEventListener(type, listener, { [kIsNodeStyleListener]: true });
418456
return this;
419457
}
420458

421459
addListener(type, listener) {
422-
this.addEventListener(type, listener);
460+
this.addEventListener(type, listener, { [kIsNodeStyleListener]: true });
423461
return this;
424462
}
425463

426464
once(type, listener) {
427-
this.addEventListener(type, listener, { once: true });
465+
this.addEventListener(type, listener,
466+
{ once: true, [kIsNodeStyleListener]: true });
428467
return this;
429468
}
430469

@@ -474,6 +513,7 @@ function validateEventListenerOptions(options) {
474513
once: Boolean(options.once),
475514
capture: Boolean(options.capture),
476515
passive: Boolean(options.passive),
516+
isNodeStyleListener: Boolean(options[kIsNodeStyleListener])
477517
};
478518
}
479519

@@ -524,4 +564,9 @@ module.exports = {
524564
EventTarget,
525565
NodeEventTarget,
526566
defineEventHandler,
567+
initEventTarget,
568+
initNodeEventTarget,
569+
kCreateEvent,
570+
kNewListener,
571+
kRemoveListener,
527572
};

0 commit comments

Comments
 (0)