Skip to content

Commit c93a898

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 3024927 commit c93a898

File tree

1 file changed

+66
-21
lines changed

1 file changed

+66
-21
lines changed

lib/internal/event_target.js

+66-21
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,13 @@ const kEvents = Symbol('kEvents');
3131
const kStop = Symbol('kStop');
3232
const kTarget = Symbol('kTarget');
3333

34+
const kHybridDispatch = Symbol.for('nodejs.internal.kHybridDispatch');
35+
const kCreateEvent = Symbol('kCreateEvent');
3436
const kNewListener = Symbol('kNewListener');
3537
const kRemoveListener = Symbol('kRemoveListener');
38+
const kIsNodeStyleListener = Symbol('kIsNodeStyleListener');
39+
const kMaxListeners = Symbol('kMaxListeners');
40+
const kMaxListenersWarned = Symbol('kMaxListenersWarned');
3641

3742
// Lazy load perf_hooks to avoid the additional overhead on startup
3843
let perf_hooks;
@@ -157,7 +162,7 @@ Object.defineProperty(Event.prototype, SymbolToStringTag, {
157162
// the linked list makes dispatching faster, even if adding/removing is
158163
// slower.
159164
class Listener {
160-
constructor(previous, listener, once, capture, passive) {
165+
constructor(previous, listener, once, capture, passive, isNodeStyleListener) {
161166
this.next = undefined;
162167
if (previous !== undefined)
163168
previous.next = this;
@@ -166,6 +171,7 @@ class Listener {
166171
this.once = once;
167172
this.capture = capture;
168173
this.passive = passive;
174+
this.isNodeStyleListener = isNodeStyleListener;
169175

170176
this.callback =
171177
typeof listener === 'function' ?
@@ -185,13 +191,17 @@ class Listener {
185191
}
186192
}
187193

194+
function initEventTarget(self) {
195+
self[kEvents] = new Map();
196+
}
197+
188198
class EventTarget {
189199
// Used in checking whether an object is an EventTarget. This is a well-known
190200
// symbol as EventTarget may be used cross-realm. See discussion in #33661.
191201
static [kIsEventTarget] = true;
192202

193203
constructor() {
194-
this[kEvents] = new Map();
204+
initEventTarget(this);
195205
}
196206

197207
[kNewListener](size, type, listener, once, capture, passive) {}
@@ -206,7 +216,8 @@ class EventTarget {
206216
const {
207217
once,
208218
capture,
209-
passive
219+
passive,
220+
isNodeStyleListener
210221
} = validateEventListenerOptions(options);
211222

212223
if (!shouldAddListener(listener)) {
@@ -228,7 +239,7 @@ class EventTarget {
228239
if (root === undefined) {
229240
root = { size: 1, next: undefined };
230241
// This is the first handler in our linked list.
231-
new Listener(root, listener, once, capture, passive);
242+
new Listener(root, listener, once, capture, passive, isNodeStyleListener);
232243
this[kNewListener](root.size, type, listener, once, capture, passive);
233244
this[kEvents].set(type, root);
234245
return;
@@ -247,7 +258,8 @@ class EventTarget {
247258
return;
248259
}
249260

250-
new Listener(previous, listener, once, capture, passive);
261+
new Listener(previous, listener, once, capture, passive,
262+
isNodeStyleListener);
251263
root.size++;
252264
this[kNewListener](root.size, type, listener, once, capture, passive);
253265
}
@@ -290,39 +302,61 @@ class EventTarget {
290302
if (event[kTarget] !== null)
291303
throw new ERR_EVENT_RECURSION(event.type);
292304

293-
const root = this[kEvents].get(event.type);
305+
this[kHybridDispatch](event, event.type, event);
306+
307+
return event.defaultPrevented !== true;
308+
}
309+
310+
[kHybridDispatch](nodeValue, type, event) {
311+
const createEvent = () => {
312+
if (event === undefined) {
313+
event = this[kCreateEvent](nodeValue, type);
314+
event[kTarget] = this;
315+
}
316+
return event;
317+
};
318+
319+
const root = this[kEvents].get(type);
294320
if (root === undefined || root.next === undefined)
295321
return true;
296322

297-
event[kTarget] = this;
323+
if (event !== undefined)
324+
event[kTarget] = this;
298325

299326
let handler = root.next;
300327
let next;
301328

302329
while (handler !== undefined &&
303-
(handler.passive || event[kStop] !== true)) {
330+
(handler.passive || event?.[kStop] !== true)) {
304331
// Cache the next item in case this iteration removes the current one
305332
next = handler.next;
306333

307334
if (handler.once) {
308335
handler.remove();
309336
root.size--;
337+
const { listener, capture } = handler;
338+
this[kRemoveListener](root.size, type, listener, capture);
310339
}
311340

312341
try {
313-
const result = handler.callback.call(this, event);
342+
let arg;
343+
if (handler.isNodeStyleListener) {
344+
arg = nodeValue;
345+
} else {
346+
arg = createEvent();
347+
}
348+
const result = handler.callback.call(this, arg);
314349
if (result !== undefined && result !== null)
315-
addCatch(this, result, event);
350+
addCatch(this, result, createEvent());
316351
} catch (err) {
317-
emitUnhandledRejectionOrErr(this, err, event);
352+
emitUnhandledRejectionOrErr(this, err, createEvent());
318353
}
319354

320355
handler = next;
321356
}
322357

323-
event[kTarget] = undefined;
324-
325-
return event.defaultPrevented !== true;
358+
if (event !== undefined)
359+
event[kTarget] = undefined;
326360
}
327361

328362
[customInspectSymbol](depth, options) {
@@ -350,15 +384,19 @@ Object.defineProperty(EventTarget.prototype, SymbolToStringTag, {
350384
value: 'EventTarget',
351385
});
352386

353-
const kMaxListeners = Symbol('maxListeners');
354-
const kMaxListenersWarned = Symbol('maxListenersWarned');
387+
function initNodeEventTarget(self) {
388+
initEventTarget(self);
389+
// eslint-disable-next-line no-use-before-define
390+
self[kMaxListeners] = NodeEventTarget.defaultMaxListeners;
391+
self[kMaxListenersWarned] = false;
392+
}
393+
355394
class NodeEventTarget extends EventTarget {
356395
static defaultMaxListeners = 10;
357396

358397
constructor() {
359398
super();
360-
this[kMaxListeners] = NodeEventTarget.defaultMaxListeners;
361-
this[kMaxListenersWarned] = false;
399+
initNodeEventTarget(this);
362400
}
363401

364402
[kNewListener](size, type, listener, once, capture, passive) {
@@ -410,17 +448,18 @@ class NodeEventTarget extends EventTarget {
410448
}
411449

412450
on(type, listener) {
413-
this.addEventListener(type, listener);
451+
this.addEventListener(type, listener, { [kIsNodeStyleListener]: true });
414452
return this;
415453
}
416454

417455
addListener(type, listener) {
418-
this.addEventListener(type, listener);
456+
this.addEventListener(type, listener, { [kIsNodeStyleListener]: true });
419457
return this;
420458
}
421459

422460
once(type, listener) {
423-
this.addEventListener(type, listener, { once: true });
461+
this.addEventListener(type, listener,
462+
{ once: true, [kIsNodeStyleListener]: true });
424463
return this;
425464
}
426465

@@ -470,6 +509,7 @@ function validateEventListenerOptions(options) {
470509
once: Boolean(options.once),
471510
capture: Boolean(options.capture),
472511
passive: Boolean(options.passive),
512+
isNodeStyleListener: Boolean(options[kIsNodeStyleListener])
473513
};
474514
}
475515

@@ -520,4 +560,9 @@ module.exports = {
520560
EventTarget,
521561
NodeEventTarget,
522562
defineEventHandler,
563+
initEventTarget,
564+
initNodeEventTarget,
565+
kCreateEvent,
566+
kNewListener,
567+
kRemoveListener,
523568
};

0 commit comments

Comments
 (0)