1'use strict';
2
3const {
4  ArrayFrom,
5  Boolean,
6  Error,
7  FunctionPrototypeCall,
8  NumberIsInteger,
9  ObjectAssign,
10  ObjectDefineProperties,
11  ObjectDefineProperty,
12  ObjectGetOwnPropertyDescriptor,
13  ObjectGetOwnPropertyDescriptors,
14  ObjectSetPrototypeOf,
15  ObjectValues,
16  ReflectApply,
17  SafeArrayIterator,
18  SafeFinalizationRegistry,
19  SafeMap,
20  SafeWeakMap,
21  SafeWeakRef,
22  SafeWeakSet,
23  String,
24  Symbol,
25  SymbolFor,
26  SymbolToStringTag,
27} = primordials;
28
29const {
30  codes: {
31    ERR_INVALID_ARG_TYPE,
32    ERR_EVENT_RECURSION,
33    ERR_MISSING_ARGS,
34    ERR_INVALID_THIS,
35  },
36} = require('internal/errors');
37const { validateObject, validateString } = require('internal/validators');
38
39const {
40  customInspectSymbol,
41  kEmptyObject,
42  kEnumerableProperty,
43} = require('internal/util');
44const { inspect } = require('util');
45const webidl = require('internal/webidl');
46
47const kIsEventTarget = SymbolFor('nodejs.event_target');
48const kIsNodeEventTarget = Symbol('kIsNodeEventTarget');
49
50const EventEmitter = require('events');
51const {
52  kMaxEventTargetListeners,
53  kMaxEventTargetListenersWarned,
54} = EventEmitter;
55
56const kEvents = Symbol('kEvents');
57const kIsBeingDispatched = Symbol('kIsBeingDispatched');
58const kStop = Symbol('kStop');
59const kTarget = Symbol('kTarget');
60const kHandlers = Symbol('kHandlers');
61const kWeakHandler = Symbol('kWeak');
62const kResistStopPropagation = Symbol('kResistStopPropagation');
63
64const kHybridDispatch = SymbolFor('nodejs.internal.kHybridDispatch');
65const kRemoveWeakListenerHelper = Symbol('nodejs.internal.removeWeakListenerHelper');
66const kCreateEvent = Symbol('kCreateEvent');
67const kNewListener = Symbol('kNewListener');
68const kRemoveListener = Symbol('kRemoveListener');
69const kIsNodeStyleListener = Symbol('kIsNodeStyleListener');
70const kTrustEvent = Symbol('kTrustEvent');
71
72const { now } = require('internal/perf/utils');
73
74const kType = Symbol('type');
75const kDetail = Symbol('detail');
76
77const isTrustedSet = new SafeWeakSet();
78const isTrusted = ObjectGetOwnPropertyDescriptor({
79  get isTrusted() {
80    return isTrustedSet.has(this);
81  },
82}, 'isTrusted').get;
83
84const isTrustedDescriptor = {
85  __proto__: null,
86  configurable: false,
87  enumerable: true,
88  get: isTrusted,
89};
90
91function isEvent(value) {
92  return typeof value?.[kType] === 'string';
93}
94
95class Event {
96  #cancelable = false;
97  #bubbles = false;
98  #composed = false;
99  #defaultPrevented = false;
100  #timestamp = now();
101  #propagationStopped = false;
102
103  /**
104   * @param {string} type
105   * @param {{
106   *   bubbles?: boolean,
107   *   cancelable?: boolean,
108   *   composed?: boolean,
109   * }} [options]
110   */
111  constructor(type, options = kEmptyObject) {
112    if (arguments.length === 0)
113      throw new ERR_MISSING_ARGS('type');
114    validateObject(options, 'options');
115    const { bubbles, cancelable, composed } = options;
116    this.#cancelable = !!cancelable;
117    this.#bubbles = !!bubbles;
118    this.#composed = !!composed;
119
120    this[kType] = `${type}`;
121    if (options?.[kTrustEvent]) {
122      isTrustedSet.add(this);
123    }
124
125    this[kTarget] = null;
126    this[kIsBeingDispatched] = false;
127  }
128
129  [customInspectSymbol](depth, options) {
130    if (!isEvent(this))
131      throw new ERR_INVALID_THIS('Event');
132    const name = this.constructor.name;
133    if (depth < 0)
134      return name;
135
136    const opts = ObjectAssign({}, options, {
137      depth: NumberIsInteger(options.depth) ? options.depth - 1 : options.depth,
138    });
139
140    return `${name} ${inspect({
141      type: this[kType],
142      defaultPrevented: this.#defaultPrevented,
143      cancelable: this.#cancelable,
144      timeStamp: this.#timestamp,
145    }, opts)}`;
146  }
147
148  stopImmediatePropagation() {
149    if (!isEvent(this))
150      throw new ERR_INVALID_THIS('Event');
151    this[kStop] = true;
152  }
153
154  preventDefault() {
155    if (!isEvent(this))
156      throw new ERR_INVALID_THIS('Event');
157    this.#defaultPrevented = true;
158  }
159
160  /**
161   * @type {EventTarget}
162   */
163  get target() {
164    if (!isEvent(this))
165      throw new ERR_INVALID_THIS('Event');
166    return this[kTarget];
167  }
168
169  /**
170   * @type {EventTarget}
171   */
172  get currentTarget() {
173    if (!isEvent(this))
174      throw new ERR_INVALID_THIS('Event');
175    return this[kTarget];
176  }
177
178  /**
179   * @type {EventTarget}
180   */
181  get srcElement() {
182    if (!isEvent(this))
183      throw new ERR_INVALID_THIS('Event');
184    return this[kTarget];
185  }
186
187  /**
188   * @type {string}
189   */
190  get type() {
191    if (!isEvent(this))
192      throw new ERR_INVALID_THIS('Event');
193    return this[kType];
194  }
195
196  /**
197   * @type {boolean}
198   */
199  get cancelable() {
200    if (!isEvent(this))
201      throw new ERR_INVALID_THIS('Event');
202    return this.#cancelable;
203  }
204
205  /**
206   * @type {boolean}
207   */
208  get defaultPrevented() {
209    if (!isEvent(this))
210      throw new ERR_INVALID_THIS('Event');
211    return this.#cancelable && this.#defaultPrevented;
212  }
213
214  /**
215   * @type {number}
216   */
217  get timeStamp() {
218    if (!isEvent(this))
219      throw new ERR_INVALID_THIS('Event');
220    return this.#timestamp;
221  }
222
223
224  // The following are non-op and unused properties/methods from Web API Event.
225  // These are not supported in Node.js and are provided purely for
226  // API completeness.
227  /**
228   * @returns {EventTarget[]}
229   */
230  composedPath() {
231    if (!isEvent(this))
232      throw new ERR_INVALID_THIS('Event');
233    return this[kIsBeingDispatched] ? [this[kTarget]] : [];
234  }
235
236  /**
237   * @type {boolean}
238   */
239  get returnValue() {
240    if (!isEvent(this))
241      throw new ERR_INVALID_THIS('Event');
242    return !this.#cancelable || !this.#defaultPrevented;
243  }
244
245  /**
246   * @type {boolean}
247   */
248  get bubbles() {
249    if (!isEvent(this))
250      throw new ERR_INVALID_THIS('Event');
251    return this.#bubbles;
252  }
253
254  /**
255   * @type {boolean}
256   */
257  get composed() {
258    if (!isEvent(this))
259      throw new ERR_INVALID_THIS('Event');
260    return this.#composed;
261  }
262
263  /**
264   * @type {number}
265   */
266  get eventPhase() {
267    if (!isEvent(this))
268      throw new ERR_INVALID_THIS('Event');
269    return this[kIsBeingDispatched] ? Event.AT_TARGET : Event.NONE;
270  }
271
272  /**
273   * @type {boolean}
274   */
275  get cancelBubble() {
276    if (!isEvent(this))
277      throw new ERR_INVALID_THIS('Event');
278    return this.#propagationStopped;
279  }
280
281  /**
282   * @type {boolean}
283   */
284  set cancelBubble(value) {
285    if (!isEvent(this))
286      throw new ERR_INVALID_THIS('Event');
287    if (value) {
288      this.stopPropagation();
289    }
290  }
291
292  stopPropagation() {
293    if (!isEvent(this))
294      throw new ERR_INVALID_THIS('Event');
295    this.#propagationStopped = true;
296  }
297
298  static NONE = 0;
299  static CAPTURING_PHASE = 1;
300  static AT_TARGET = 2;
301  static BUBBLING_PHASE = 3;
302}
303
304ObjectDefineProperties(
305  Event.prototype, {
306    [SymbolToStringTag]: {
307      __proto__: null,
308      writable: false,
309      enumerable: false,
310      configurable: true,
311      value: 'Event',
312    },
313    stopImmediatePropagation: kEnumerableProperty,
314    preventDefault: kEnumerableProperty,
315    target: kEnumerableProperty,
316    currentTarget: kEnumerableProperty,
317    srcElement: kEnumerableProperty,
318    type: kEnumerableProperty,
319    cancelable: kEnumerableProperty,
320    defaultPrevented: kEnumerableProperty,
321    timeStamp: kEnumerableProperty,
322    composedPath: kEnumerableProperty,
323    returnValue: kEnumerableProperty,
324    bubbles: kEnumerableProperty,
325    composed: kEnumerableProperty,
326    eventPhase: kEnumerableProperty,
327    cancelBubble: kEnumerableProperty,
328    stopPropagation: kEnumerableProperty,
329    // Don't conform to the spec with isTrusted. The spec defines it as
330    // LegacyUnforgeable but defining it in the constructor has a big
331    // performance impact and the property doesn't seem to be useful outside of
332    // browsers.
333    isTrusted: isTrustedDescriptor,
334  });
335
336function isCustomEvent(value) {
337  return isEvent(value) && (value?.[kDetail] !== undefined);
338}
339
340class CustomEvent extends Event {
341  /**
342   * @constructor
343   * @param {string} type
344   * @param {{
345   *   bubbles?: boolean,
346   *   cancelable?: boolean,
347   *   composed?: boolean,
348   *   detail?: any,
349   * }} [options]
350   */
351  constructor(type, options = kEmptyObject) {
352    if (arguments.length === 0)
353      throw new ERR_MISSING_ARGS('type');
354    super(type, options);
355    this[kDetail] = options?.detail ?? null;
356  }
357
358  /**
359   * @type {any}
360   */
361  get detail() {
362    if (!isCustomEvent(this))
363      throw new ERR_INVALID_THIS('CustomEvent');
364    return this[kDetail];
365  }
366}
367
368ObjectDefineProperties(CustomEvent.prototype, {
369  [SymbolToStringTag]: {
370    __proto__: null,
371    writable: false,
372    enumerable: false,
373    configurable: true,
374    value: 'CustomEvent',
375  },
376  detail: kEnumerableProperty,
377});
378
379class NodeCustomEvent extends Event {
380  constructor(type, options) {
381    super(type, options);
382    if (options?.detail) {
383      this.detail = options.detail;
384    }
385  }
386}
387
388// Weak listener cleanup
389// This has to be lazy for snapshots to work
390let weakListenersState = null;
391// The resource needs to retain the callback so that it doesn't
392// get garbage collected now that it's weak.
393let objectToWeakListenerMap = null;
394function weakListeners() {
395  weakListenersState ??= new SafeFinalizationRegistry(
396    ({ eventTarget, listener, eventType }) => eventTarget.deref()?.[kRemoveWeakListenerHelper](eventType, listener),
397  );
398  objectToWeakListenerMap ??= new SafeWeakMap();
399  return { registry: weakListenersState, map: objectToWeakListenerMap };
400}
401
402const kFlagOnce = 1 << 0;
403const kFlagCapture = 1 << 1;
404const kFlagPassive = 1 << 2;
405const kFlagNodeStyle = 1 << 3;
406const kFlagWeak = 1 << 4;
407const kFlagRemoved = 1 << 5;
408const kFlagResistStopPropagation = 1 << 6;
409
410// The listeners for an EventTarget are maintained as a linked list.
411// Unfortunately, the way EventTarget is defined, listeners are accounted
412// using the tuple [handler,capture], and even if we don't actually make
413// use of capture or bubbling, in order to be spec compliant we have to
414// take on the additional complexity of supporting it. Fortunately, using
415// the linked list makes dispatching faster, even if adding/removing is
416// slower.
417class Listener {
418  constructor(eventTarget, eventType, previous, listener, once, capture, passive,
419              isNodeStyleListener, weak, resistStopPropagation) {
420    this.next = undefined;
421    if (previous !== undefined)
422      previous.next = this;
423    this.previous = previous;
424    this.listener = listener;
425
426    let flags = 0b0;
427    if (once)
428      flags |= kFlagOnce;
429    if (capture)
430      flags |= kFlagCapture;
431    if (passive)
432      flags |= kFlagPassive;
433    if (isNodeStyleListener)
434      flags |= kFlagNodeStyle;
435    if (weak)
436      flags |= kFlagWeak;
437    if (resistStopPropagation)
438      flags |= kFlagResistStopPropagation;
439    this.flags = flags;
440
441    this.removed = false;
442
443    if (this.weak) {
444      this.callback = new SafeWeakRef(listener);
445      weakListeners().registry.register(listener, {
446        __proto__: null,
447        // Weak ref so the listener won't hold the eventTarget alive
448        eventTarget: new SafeWeakRef(eventTarget),
449        listener: this,
450        eventType,
451      }, this);
452      // Make the retainer retain the listener in a WeakMap
453      weakListeners().map.set(weak, listener);
454      this.listener = this.callback;
455    } else if (typeof listener === 'function') {
456      this.callback = listener;
457      this.listener = listener;
458    } else {
459      this.callback = async (...args) => {
460        if (listener.handleEvent)
461          await ReflectApply(listener.handleEvent, listener, args);
462      };
463      this.listener = listener;
464    }
465  }
466
467  get once() {
468    return Boolean(this.flags & kFlagOnce);
469  }
470  get capture() {
471    return Boolean(this.flags & kFlagCapture);
472  }
473  get passive() {
474    return Boolean(this.flags & kFlagPassive);
475  }
476  get isNodeStyleListener() {
477    return Boolean(this.flags & kFlagNodeStyle);
478  }
479  get weak() {
480    return Boolean(this.flags & kFlagWeak);
481  }
482  get resistStopPropagation() {
483    return Boolean(this.flags & kFlagResistStopPropagation);
484  }
485  get removed() {
486    return Boolean(this.flags & kFlagRemoved);
487  }
488  set removed(value) {
489    if (value)
490      this.flags |= kFlagRemoved;
491    else
492      this.flags &= ~kFlagRemoved;
493  }
494
495  same(listener, capture) {
496    const myListener = this.weak ? this.listener.deref() : this.listener;
497    return myListener === listener && this.capture === capture;
498  }
499
500  remove() {
501    if (this.previous !== undefined)
502      this.previous.next = this.next;
503    if (this.next !== undefined)
504      this.next.previous = this.previous;
505    this.removed = true;
506    if (this.weak)
507      weakListeners().registry.unregister(this);
508  }
509}
510
511function initEventTarget(self) {
512  self[kEvents] = new SafeMap();
513  self[kMaxEventTargetListeners] = EventEmitter.defaultMaxListeners;
514  self[kMaxEventTargetListenersWarned] = false;
515}
516
517class EventTarget {
518  // Used in checking whether an object is an EventTarget. This is a well-known
519  // symbol as EventTarget may be used cross-realm.
520  // Ref: https://github.com/nodejs/node/pull/33661
521  static [kIsEventTarget] = true;
522
523  constructor() {
524    initEventTarget(this);
525  }
526
527  [kNewListener](size, type, listener, once, capture, passive, weak) {
528    if (this[kMaxEventTargetListeners] > 0 &&
529        size > this[kMaxEventTargetListeners] &&
530        !this[kMaxEventTargetListenersWarned]) {
531      this[kMaxEventTargetListenersWarned] = true;
532      // No error code for this since it is a Warning
533      // eslint-disable-next-line no-restricted-syntax
534      const w = new Error('Possible EventTarget memory leak detected. ' +
535                          `${size} ${type} listeners ` +
536                          `added to ${inspect(this, { depth: -1 })}. Use ` +
537                          'events.setMaxListeners() to increase limit');
538      w.name = 'MaxListenersExceededWarning';
539      w.target = this;
540      w.type = type;
541      w.count = size;
542      process.emitWarning(w);
543    }
544  }
545  [kRemoveListener](size, type, listener, capture) {}
546
547  /**
548   * @callback EventTargetCallback
549   * @param {Event} event
550   */
551
552  /**
553   * @typedef {{ handleEvent: EventTargetCallback }} EventListener
554   */
555
556  /**
557   * @param {string} type
558   * @param {EventTargetCallback|EventListener} listener
559   * @param {{
560   *   capture?: boolean,
561   *   once?: boolean,
562   *   passive?: boolean,
563   *   signal?: AbortSignal
564   * }} [options]
565   */
566  addEventListener(type, listener, options = kEmptyObject) {
567    if (!isEventTarget(this))
568      throw new ERR_INVALID_THIS('EventTarget');
569    if (arguments.length < 2)
570      throw new ERR_MISSING_ARGS('type', 'listener');
571
572    // We validateOptions before the validateListener check because the spec
573    // requires us to hit getters.
574    const {
575      once,
576      capture,
577      passive,
578      signal,
579      isNodeStyleListener,
580      weak,
581      resistStopPropagation,
582    } = validateEventListenerOptions(options);
583
584    if (!validateEventListener(listener)) {
585      // The DOM silently allows passing undefined as a second argument
586      // No error code for this since it is a Warning
587      // eslint-disable-next-line no-restricted-syntax
588      const w = new Error(`addEventListener called with ${listener}` +
589                          ' which has no effect.');
590      w.name = 'AddEventListenerArgumentTypeWarning';
591      w.target = this;
592      w.type = type;
593      process.emitWarning(w);
594      return;
595    }
596    type = webidl.converters.DOMString(type);
597
598    if (signal) {
599      if (signal.aborted) {
600        return;
601      }
602      // TODO(benjamingr) make this weak somehow? ideally the signal would
603      // not prevent the event target from GC.
604      signal.addEventListener('abort', () => {
605        this.removeEventListener(type, listener, options);
606      }, { __proto__: null, once: true, [kWeakHandler]: this, [kResistStopPropagation]: true });
607    }
608
609    let root = this[kEvents].get(type);
610
611    if (root === undefined) {
612      root = { size: 1, next: undefined, resistStopPropagation: Boolean(resistStopPropagation) };
613      // This is the first handler in our linked list.
614      new Listener(this, type, root, listener, once, capture, passive,
615                   isNodeStyleListener, weak, resistStopPropagation);
616      this[kNewListener](
617        root.size,
618        type,
619        listener,
620        once,
621        capture,
622        passive,
623        weak);
624      this[kEvents].set(type, root);
625      return;
626    }
627
628    let handler = root.next;
629    let previous = root;
630
631    // We have to walk the linked list to see if we have a match
632    while (handler !== undefined && !handler.same(listener, capture)) {
633      previous = handler;
634      handler = handler.next;
635    }
636
637    if (handler !== undefined) { // Duplicate! Ignore
638      return;
639    }
640
641    new Listener(this, type, previous, listener, once, capture, passive,
642                 isNodeStyleListener, weak, resistStopPropagation);
643    root.size++;
644    root.resistStopPropagation ||= Boolean(resistStopPropagation);
645    this[kNewListener](root.size, type, listener, once, capture, passive, weak);
646  }
647
648  /**
649   * @param {string} type
650   * @param {EventTargetCallback|EventListener} listener
651   * @param {{
652   *   capture?: boolean,
653   * }} [options]
654   */
655  removeEventListener(type, listener, options = kEmptyObject) {
656    if (!isEventTarget(this))
657      throw new ERR_INVALID_THIS('EventTarget');
658    if (arguments.length < 2)
659      throw new ERR_MISSING_ARGS('type', 'listener');
660    if (!validateEventListener(listener))
661      return;
662
663    type = webidl.converters.DOMString(type);
664    const capture = options?.capture === true;
665
666    const root = this[kEvents].get(type);
667    if (root === undefined || root.next === undefined)
668      return;
669
670    let handler = root.next;
671    while (handler !== undefined) {
672      if (handler.same(listener, capture)) {
673        handler.remove();
674        root.size--;
675        if (root.size === 0)
676          this[kEvents].delete(type);
677        this[kRemoveListener](root.size, type, listener, capture);
678        break;
679      }
680      handler = handler.next;
681    }
682  }
683
684  [kRemoveWeakListenerHelper](type, listener) {
685    const root = this[kEvents].get(type);
686    if (root === undefined || root.next === undefined)
687      return;
688
689    const capture = listener.capture === true;
690
691    let handler = root.next;
692    while (handler !== undefined) {
693      if (handler === listener) {
694        handler.remove();
695        root.size--;
696        if (root.size === 0)
697          this[kEvents].delete(type);
698        // Undefined is passed as the listener as the listener was GCed
699        this[kRemoveListener](root.size, type, undefined, capture);
700        break;
701      }
702      handler = handler.next;
703    }
704  }
705
706  /**
707   * @param {Event} event
708   */
709  dispatchEvent(event) {
710    if (!isEventTarget(this))
711      throw new ERR_INVALID_THIS('EventTarget');
712    if (arguments.length < 1)
713      throw new ERR_MISSING_ARGS('event');
714
715    if (!(event instanceof Event))
716      throw new ERR_INVALID_ARG_TYPE('event', 'Event', event);
717
718    if (event[kIsBeingDispatched])
719      throw new ERR_EVENT_RECURSION(event.type);
720
721    this[kHybridDispatch](event, event.type, event);
722
723    return event.defaultPrevented !== true;
724  }
725
726  [kHybridDispatch](nodeValue, type, event) {
727    const createEvent = () => {
728      if (event === undefined) {
729        event = this[kCreateEvent](nodeValue, type);
730        event[kTarget] = this;
731        event[kIsBeingDispatched] = true;
732      }
733      return event;
734    };
735    if (event !== undefined) {
736      event[kTarget] = this;
737      event[kIsBeingDispatched] = true;
738    }
739
740    const root = this[kEvents].get(type);
741    if (root === undefined || root.next === undefined) {
742      if (event !== undefined)
743        event[kIsBeingDispatched] = false;
744      return true;
745    }
746
747    let handler = root.next;
748    let next;
749
750    const iterationCondition = () => {
751      if (handler === undefined) {
752        return false;
753      }
754      return root.resistStopPropagation || handler.passive || event?.[kStop] !== true;
755    };
756    while (iterationCondition()) {
757      // Cache the next item in case this iteration removes the current one
758      next = handler.next;
759
760      if (handler.removed || (event?.[kStop] === true && !handler.resistStopPropagation)) {
761        // Deal with the case an event is removed while event handlers are
762        // Being processed (removeEventListener called from a listener)
763        // And the case of event.stopImmediatePropagation() being called
764        // For events not flagged as resistStopPropagation
765        handler = next;
766        continue;
767      }
768      if (handler.once) {
769        handler.remove();
770        root.size--;
771        const { listener, capture } = handler;
772        this[kRemoveListener](root.size, type, listener, capture);
773      }
774
775      try {
776        let arg;
777        if (handler.isNodeStyleListener) {
778          arg = nodeValue;
779        } else {
780          arg = createEvent();
781        }
782        const callback = handler.weak ?
783          handler.callback.deref() : handler.callback;
784        let result;
785        if (callback) {
786          result = FunctionPrototypeCall(callback, this, arg);
787          if (!handler.isNodeStyleListener) {
788            arg[kIsBeingDispatched] = false;
789          }
790        }
791        if (result !== undefined && result !== null)
792          addCatch(result);
793      } catch (err) {
794        emitUncaughtException(err);
795      }
796
797      handler = next;
798    }
799
800    if (event !== undefined)
801      event[kIsBeingDispatched] = false;
802  }
803
804  [kCreateEvent](nodeValue, type) {
805    return new NodeCustomEvent(type, { detail: nodeValue });
806  }
807  [customInspectSymbol](depth, options) {
808    if (!isEventTarget(this))
809      throw new ERR_INVALID_THIS('EventTarget');
810    const name = this.constructor.name;
811    if (depth < 0)
812      return name;
813
814    const opts = ObjectAssign({}, options, {
815      depth: NumberIsInteger(options.depth) ? options.depth - 1 : options.depth,
816    });
817
818    return `${name} ${inspect({}, opts)}`;
819  }
820}
821
822ObjectDefineProperties(EventTarget.prototype, {
823  addEventListener: kEnumerableProperty,
824  removeEventListener: kEnumerableProperty,
825  dispatchEvent: kEnumerableProperty,
826  [SymbolToStringTag]: {
827    __proto__: null,
828    writable: false,
829    enumerable: false,
830    configurable: true,
831    value: 'EventTarget',
832  },
833});
834
835function initNodeEventTarget(self) {
836  initEventTarget(self);
837}
838
839class NodeEventTarget extends EventTarget {
840  static [kIsNodeEventTarget] = true;
841  static defaultMaxListeners = 10;
842
843  constructor() {
844    super();
845    initNodeEventTarget(this);
846  }
847
848  /**
849   * @param {number} n
850   */
851  setMaxListeners(n) {
852    if (!isNodeEventTarget(this))
853      throw new ERR_INVALID_THIS('NodeEventTarget');
854    EventEmitter.setMaxListeners(n, this);
855  }
856
857  /**
858   * @returns {number}
859   */
860  getMaxListeners() {
861    if (!isNodeEventTarget(this))
862      throw new ERR_INVALID_THIS('NodeEventTarget');
863    return this[kMaxEventTargetListeners];
864  }
865
866  /**
867   * @returns {string[]}
868   */
869  eventNames() {
870    if (!isNodeEventTarget(this))
871      throw new ERR_INVALID_THIS('NodeEventTarget');
872    return ArrayFrom(this[kEvents].keys());
873  }
874
875  /**
876   * @param {string} type
877   * @returns {number}
878   */
879  listenerCount(type) {
880    if (!isNodeEventTarget(this))
881      throw new ERR_INVALID_THIS('NodeEventTarget');
882    const root = this[kEvents].get(String(type));
883    return root !== undefined ? root.size : 0;
884  }
885
886  /**
887   * @param {string} type
888   * @param {EventTargetCallback|EventListener} listener
889   * @param {{
890   *   capture?: boolean,
891   * }} [options]
892   * @returns {NodeEventTarget}
893   */
894  off(type, listener, options) {
895    if (!isNodeEventTarget(this))
896      throw new ERR_INVALID_THIS('NodeEventTarget');
897    this.removeEventListener(type, listener, options);
898    return this;
899  }
900
901  /**
902   * @param {string} type
903   * @param {EventTargetCallback|EventListener} listener
904   * @param {{
905   *   capture?: boolean,
906   * }} [options]
907   * @returns {NodeEventTarget}
908   */
909  removeListener(type, listener, options) {
910    if (!isNodeEventTarget(this))
911      throw new ERR_INVALID_THIS('NodeEventTarget');
912    this.removeEventListener(type, listener, options);
913    return this;
914  }
915
916  /**
917   * @param {string} type
918   * @param {EventTargetCallback|EventListener} listener
919   * @returns {NodeEventTarget}
920   */
921  on(type, listener) {
922    if (!isNodeEventTarget(this))
923      throw new ERR_INVALID_THIS('NodeEventTarget');
924    this.addEventListener(type, listener, { [kIsNodeStyleListener]: true });
925    return this;
926  }
927
928  /**
929   * @param {string} type
930   * @param {EventTargetCallback|EventListener} listener
931   * @returns {NodeEventTarget}
932   */
933  addListener(type, listener) {
934    if (!isNodeEventTarget(this))
935      throw new ERR_INVALID_THIS('NodeEventTarget');
936    this.addEventListener(type, listener, { [kIsNodeStyleListener]: true });
937    return this;
938  }
939
940  /**
941   * @param {string} type
942   * @param {any} arg
943   * @returns {boolean}
944   */
945  emit(type, arg) {
946    if (!isNodeEventTarget(this))
947      throw new ERR_INVALID_THIS('NodeEventTarget');
948    validateString(type, 'type');
949    const hadListeners = this.listenerCount(type) > 0;
950    this[kHybridDispatch](arg, type);
951    return hadListeners;
952  }
953
954  /**
955   * @param {string} type
956   * @param {EventTargetCallback|EventListener} listener
957   * @returns {NodeEventTarget}
958   */
959  once(type, listener) {
960    if (!isNodeEventTarget(this))
961      throw new ERR_INVALID_THIS('NodeEventTarget');
962    this.addEventListener(type, listener,
963                          { once: true, [kIsNodeStyleListener]: true });
964    return this;
965  }
966
967  /**
968   * @param {string} [type]
969   * @returns {NodeEventTarget}
970   */
971  removeAllListeners(type) {
972    if (!isNodeEventTarget(this))
973      throw new ERR_INVALID_THIS('NodeEventTarget');
974    if (type !== undefined) {
975      this[kEvents].delete(String(type));
976    } else {
977      this[kEvents].clear();
978    }
979
980    return this;
981  }
982}
983
984ObjectDefineProperties(NodeEventTarget.prototype, {
985  setMaxListeners: kEnumerableProperty,
986  getMaxListeners: kEnumerableProperty,
987  eventNames: kEnumerableProperty,
988  listenerCount: kEnumerableProperty,
989  off: kEnumerableProperty,
990  removeListener: kEnumerableProperty,
991  on: kEnumerableProperty,
992  addListener: kEnumerableProperty,
993  once: kEnumerableProperty,
994  emit: kEnumerableProperty,
995  removeAllListeners: kEnumerableProperty,
996});
997
998// EventTarget API
999
1000function validateEventListener(listener) {
1001  if (typeof listener === 'function' ||
1002      typeof listener?.handleEvent === 'function') {
1003    return true;
1004  }
1005
1006  if (listener == null)
1007    return false;
1008
1009  if (typeof listener === 'object') {
1010    // Require `handleEvent` lazily.
1011    return true;
1012  }
1013
1014  throw new ERR_INVALID_ARG_TYPE('listener', 'EventListener', listener);
1015}
1016
1017function validateEventListenerOptions(options) {
1018  if (typeof options === 'boolean')
1019    return { capture: options };
1020
1021  if (options === null)
1022    return kEmptyObject;
1023  validateObject(options, 'options', {
1024    allowArray: true, allowFunction: true,
1025  });
1026  return {
1027    once: Boolean(options.once),
1028    capture: Boolean(options.capture),
1029    passive: Boolean(options.passive),
1030    signal: options.signal,
1031    weak: options[kWeakHandler],
1032    resistStopPropagation: options[kResistStopPropagation] ?? false,
1033    isNodeStyleListener: Boolean(options[kIsNodeStyleListener]),
1034  };
1035}
1036
1037// Test whether the argument is an event object. This is far from a fool-proof
1038// test, for example this input will result in a false positive:
1039// > isEventTarget({ constructor: EventTarget })
1040// It stands in its current implementation as a compromise.
1041// Ref: https://github.com/nodejs/node/pull/33661
1042function isEventTarget(obj) {
1043  return obj?.constructor?.[kIsEventTarget];
1044}
1045
1046function isNodeEventTarget(obj) {
1047  return obj?.constructor?.[kIsNodeEventTarget];
1048}
1049
1050function addCatch(promise) {
1051  const then = promise.then;
1052  if (typeof then === 'function') {
1053    FunctionPrototypeCall(then, promise, undefined, function(err) {
1054      // The callback is called with nextTick to avoid a follow-up
1055      // rejection from this promise.
1056      emitUncaughtException(err);
1057    });
1058  }
1059}
1060
1061function emitUncaughtException(err) {
1062  process.nextTick(() => { throw err; });
1063}
1064
1065function makeEventHandler(handler) {
1066  // Event handlers are dispatched in the order they were first set
1067  // See https://github.com/nodejs/node/pull/35949#issuecomment-722496598
1068  function eventHandler(...args) {
1069    if (typeof eventHandler.handler !== 'function') {
1070      return;
1071    }
1072    return ReflectApply(eventHandler.handler, this, args);
1073  }
1074  eventHandler.handler = handler;
1075  return eventHandler;
1076}
1077
1078function defineEventHandler(emitter, name) {
1079  // 8.1.5.1 Event handlers - basically `on[eventName]` attributes
1080  ObjectDefineProperty(emitter, `on${name}`, {
1081    __proto__: null,
1082    get() {
1083      return this[kHandlers]?.get(name)?.handler ?? null;
1084    },
1085    set(value) {
1086      if (!this[kHandlers]) {
1087        this[kHandlers] = new SafeMap();
1088      }
1089      let wrappedHandler = this[kHandlers]?.get(name);
1090      if (wrappedHandler) {
1091        if (typeof wrappedHandler.handler === 'function') {
1092          this[kEvents].get(name).size--;
1093          const size = this[kEvents].get(name).size;
1094          this[kRemoveListener](size, name, wrappedHandler.handler, false);
1095        }
1096        wrappedHandler.handler = value;
1097        if (typeof wrappedHandler.handler === 'function') {
1098          this[kEvents].get(name).size++;
1099          const size = this[kEvents].get(name).size;
1100          this[kNewListener](size, name, value, false, false, false, false);
1101        }
1102      } else {
1103        wrappedHandler = makeEventHandler(value);
1104        this.addEventListener(name, wrappedHandler);
1105      }
1106      this[kHandlers].set(name, wrappedHandler);
1107    },
1108    configurable: true,
1109    enumerable: true,
1110  });
1111}
1112
1113const EventEmitterMixin = (Superclass) => {
1114  class MixedEventEmitter extends Superclass {
1115    constructor(...args) {
1116      args = new SafeArrayIterator(args);
1117      super(...args);
1118      FunctionPrototypeCall(EventEmitter, this);
1119    }
1120  }
1121  const protoProps = ObjectGetOwnPropertyDescriptors(EventEmitter.prototype);
1122  delete protoProps.constructor;
1123  const propertiesValues = ObjectValues(protoProps);
1124  for (let i = 0; i < propertiesValues.length; i++) {
1125    // We want to use null-prototype objects to not rely on globally mutable
1126    // %Object.prototype%.
1127    ObjectSetPrototypeOf(propertiesValues[i], null);
1128  }
1129  ObjectDefineProperties(MixedEventEmitter.prototype, protoProps);
1130  return MixedEventEmitter;
1131};
1132
1133module.exports = {
1134  Event,
1135  CustomEvent,
1136  EventEmitterMixin,
1137  EventTarget,
1138  NodeEventTarget,
1139  defineEventHandler,
1140  initEventTarget,
1141  initNodeEventTarget,
1142  kCreateEvent,
1143  kNewListener,
1144  kTrustEvent,
1145  kRemoveListener,
1146  kEvents,
1147  kWeakHandler,
1148  kResistStopPropagation,
1149  isEventTarget,
1150};
1151