repo_description: Node.js JavaScript runtime ✨🐢🚀✨
repo_topics: javascript js linux macos mit node nodejs runtime windows
code: 'use strict';
  
  const {
    ArrayFrom,
    ArrayPrototypeReduce,
    Boolean,
    Error,
    FunctionPrototypeCall,
    NumberIsInteger,
    ObjectAssign,
    ObjectDefineProperties,
    ObjectDefineProperty,
    ObjectGetOwnPropertyDescriptor,
    ReflectApply,
    SafeFinalizationRegistry,
    SafeMap,
    SafeWeakMap,
    SafeWeakRef,
    SafeWeakSet,
    String,
    Symbol,
    SymbolFor,
    SymbolToStringTag,
  } = primordials;
  
  const {
    codes: {
      ERR_EVENT_RECURSION,
      ERR_INVALID_ARG_TYPE,
      ERR_INVALID_THIS,
      ERR_MISSING_ARGS,
    },
  } = require('internal/errors');
  const {
    validateAbortSignal,
    validateObject,
    validateString,
    validateThisInternalField,
    kValidateObjectAllowObjects,
  } = require('internal/validators');
  
  const {
    customInspectSymbol,
    kEmptyObject,
    kEnumerableProperty,
  } = require('internal/util');
  const { inspect } = require('util');
  const webidl = require('internal/webidl');
  
  const kIsEventTarget = SymbolFor('nodejs.event_target');
  const kIsNodeEventTarget = Symbol('kIsNodeEventTarget');
  
  const EventEmitter = require('events');
  const {
    kMaxEventTargetListeners,
    kMaxEventTargetListenersWarned,
  } = EventEmitter;
  
  const kEvents = Symbol('kEvents');
  const kIsBeingDispatched = Symbol('kIsBeingDispatched');
  const kStop = Symbol('kStop');
  const kTarget = Symbol('kTarget');
  const kHandlers = Symbol('kHandlers');
  const kWeakHandler = Symbol('kWeak');
  const kResistStopPropagation = Symbol('kResistStopPropagation');
  
  const kHybridDispatch = SymbolFor('nodejs.internal.kHybridDispatch');
  const kRemoveWeakListenerHelper = Symbol('kRemoveWeakListenerHelper');
  const kCreateEvent = Symbol('kCreateEvent');
  const kNewListener = Symbol('kNewListener');
  const kRemoveListener = Symbol('kRemoveListener');
  const kIsNodeStyleListener = Symbol('kIsNodeStyleListener');
  const kTrustEvent = Symbol('kTrustEvent');
  
  const { now } = require('internal/perf/utils');
  
  const kType = Symbol('type');
  const kDetail = Symbol('detail');
  const kInPassiveListener = Symbol('kInPassiveListener');
  
  const isTrustedSet = new SafeWeakSet();
  const isTrusted = ObjectGetOwnPropertyDescriptor({
    get isTrusted() {
      return isTrustedSet.has(this);
    },
  }, 'isTrusted').get;
  
  const isTrustedDescriptor = {
    __proto__: null,
    configurable: false,
    enumerable: true,
    get: isTrusted,
  };
  
  function isEvent(value) {
    return typeof value?.[kType] === 'string';
  }
  
  class Event {
    #cancelable = false;
    #bubbles = false;
    #composed = false;
    #defaultPrevented = false;
    #timestamp = now();
    #propagationStopped = false;
  
    /**
     * @param {string} type
     * @param {{
     *   bubbles?: boolean,
     *   cancelable?: boolean,
     *   composed?: boolean,
     * }} [options]
     */
    constructor(type, options = undefined) {
      if (arguments.length === 0)
        throw new ERR_MISSING_ARGS('type');
      if (options != null)
        validateObject(options, 'options');
      this.#bubbles = !!options?.bubbles;
      this.#cancelable = !!options?.cancelable;
      this.#composed = !!options?.composed;
  
      this[kType] = `${type}`;
      if (options?.[kTrustEvent]) {
        isTrustedSet.add(this);
      }
  
      this[kTarget] = null;
      this[kIsBeingDispatched] = false;
      this[kInPassiveListener] = false;
    }
  
    /**
     * @param {string} type
     * @param {boolean} [bubbles]
     * @param {boolean} [cancelable]
     */
    initEvent(type, bubbles = false, cancelable = false) {
      if (arguments.length === 0)
        throw new ERR_MISSING_ARGS('type');
  
      if (this[kIsBeingDispatched]) {
        return;
      }
      this[kType] = `${type}`;
      this.#bubbles = !!bubbles;
      this.#cancelable = !!cancelable;
    }
  
    [customInspectSymbol](depth, options) {
      if (!isEvent(this))
        throw new ERR_INVALID_THIS('Event');
      const name = this.constructor.name;
      if (depth < 0)
        return name;
  
      const opts = ObjectAssign({}, options, {
        depth: NumberIsInteger(options.depth) ? options.depth - 1 : options.depth,
      });
  
      return `${name} ${inspect({
        type: this[kType],
        defaultPrevented: this.#defaultPrevented,
        cancelable: this.#cancelable,
        timeStamp: this.#timestamp,
      }, opts)}`;
    }
  
    stopImmediatePropagation() {
      if (!isEvent(this))
        throw new ERR_INVALID_THIS('Event');
      // Spec mention "stopImmediatePropagation should set both "stop propagation"
      // and "stop immediate propagation" flags"
      // cf: from https://dom.spec.whatwg.org/#dom-event-stopimmediatepropagation
      this.stopPropagation();
      this[kStop] = true;
    }
  
    preventDefault() {
      if (!isEvent(this))
        throw new ERR_INVALID_THIS('Event');
      if (!this.#cancelable || this[kInPassiveListener]) return;
      this.#defaultPrevented = true;
    }
  
    /**
     * @type {EventTarget}
     */
    get target() {
      if (!isEvent(this))
        throw new ERR_INVALID_THIS('Event');
      return this[kTarget];
    }
  
    /**
     * @type {EventTarget}
     */
    get currentTarget() {
      if (!isEvent(this))
        throw new ERR_INVALID_THIS('Event');
      return this[kIsBeingDispatched] ? this[kTarget] : null;
    }
  
    /**
     * @type {EventTarget}
     */
    get srcElement() {
      if (!isEvent(this))
        throw new ERR_INVALID_THIS('Event');
      return this[kTarget];
    }
  
    /**
     * @type {string}
     */
    get type() {
      if (!isEvent(this))
        throw new ERR_INVALID_THIS('Event');
      return this[kType];
    }
  
    /**
     * @type {boolean}
     */
    get cancelable() {
      if (!isEvent(this))
        throw new ERR_INVALID_THIS('Event');
      return this.#cancelable;
    }
  
    /**
     * @type {boolean}
     */
    get defaultPrevented() {
      if (!isEvent(this))
        throw new ERR_INVALID_THIS('Event');
      return this.#cancelable && this.#defaultPrevented;
    }
  
    /**
     * @type {number}
     */
    get timeStamp() {
      if (!isEvent(this))
        throw new ERR_INVALID_THIS('Event');
      return this.#timestamp;
    }
  
  
    // The following are non-op and unused properties/methods from Web API Event.
    // These are not supported in Node.js and are provided purely for
    // API completeness.
    /**
     * @returns {EventTarget[]}
     */
    composedPath() {
      if (!isEvent(this))
        throw new ERR_INVALID_THIS('Event');
      return this[kIsBeingDispatched] ? [this[kTarget]] : [];
    }
  
    /**
     * @type {boolean}
     */
    get returnValue() {
      if (!isEvent(this))
        throw new ERR_INVALID_THIS('Event');
      return !this.#cancelable || !this.#defaultPrevented;
    }
  
    /**
     * @type {boolean}
     */
    set returnValue(value) {
      if (!isEvent(this))
        throw new ERR_INVALID_THIS('Event');
  
      if (!value) {
        if (!this.#cancelable || this[kInPassiveListener]) return;
        this.#defaultPrevented = true;
      }
    }
  
    /**
     * @type {boolean}
     */
    get bubbles() {
      if (!isEvent(this))
        throw new ERR_INVALID_THIS('Event');
      return this.#bubbles;
    }
  
    /**
     * @type {boolean}
     */
    get composed() {
      if (!isEvent(this))
        throw new ERR_INVALID_THIS('Event');
      return this.#composed;
    }
  
    /**
     * @type {number}
     */
    get eventPhase() {
      if (!isEvent(this))
        throw new ERR_INVALID_THIS('Event');
      return this[kIsBeingDispatched] ? Event.AT_TARGET : Event.NONE;
    }
  
    /**
     * @type {boolean}
     */
    get cancelBubble() {
      if (!isEvent(this))
        throw new ERR_INVALID_THIS('Event');
      return this.#propagationStopped;
    }
  
    /**
     * @type {boolean}
     */
    set cancelBubble(value) {
      if (!isEvent(this))
        throw new ERR_INVALID_THIS('Event');
      if (value) {
        this.#propagationStopped = true;
      }
    }
  
    stopPropagation() {
      if (!isEvent(this))
        throw new ERR_INVALID_THIS('Event');
      this.#propagationStopped = true;
    }
  }
  
  ObjectDefineProperties(
    Event.prototype, {
      [SymbolToStringTag]: {
        __proto__: null,
        writable: false,
        enumerable: false,
        configurable: true,
        value: 'Event',
      },
      initEvent: kEnumerableProperty,
      stopImmediatePropagation: kEnumerableProperty,
      preventDefault: kEnumerableProperty,
      target: kEnumerableProperty,
      currentTarget: kEnumerableProperty,
      srcElement: kEnumerableProperty,
      type: kEnumerableProperty,
      cancelable: kEnumerableProperty,
      defaultPrevented: kEnumerableProperty,
      timeStamp: kEnumerableProperty,
      composedPath: kEnumerableProperty,
      returnValue: kEnumerableProperty,
      bubbles: kEnumerableProperty,
      composed: kEnumerableProperty,
      eventPhase: kEnumerableProperty,
      cancelBubble: kEnumerableProperty,
      stopPropagation: kEnumerableProperty,
      // Don't conform to the spec with isTrusted. The spec defines it as
      // LegacyUnforgeable but defining it in the constructor has a big
      // performance impact and the property doesn't seem to be useful outside of
      // browsers.
      isTrusted: isTrustedDescriptor,
    });
  
  const staticProps = ['NONE', 'CAPTURING_PHASE', 'AT_TARGET', 'BUBBLING_PHASE'];
  
  ObjectDefineProperties(
    Event,
    ArrayPrototypeReduce(staticProps, (result, staticProp, index = 0) => {
      result[staticProp] = {
        __proto__: null,
        writable: false,
        configurable: false,
        enumerable: true,
        value: index,
      };
      return result;
    }, {}),
  );
  
  function isCustomEvent(value) {
    return isEvent(value) && (value?.[kDetail] !== undefined);
  }
  
  class CustomEvent extends Event {
    /**
     * @param {string} type
     * @param {{
     *   bubbles?: boolean,
     *   cancelable?: boolean,
     *   composed?: boolean,
     *   detail?: any,
     * }} [options]
     */
    constructor(type, options = kEmptyObject) {
      if (arguments.length === 0)
        throw new ERR_MISSING_ARGS('type');
      super(type, options);
      this[kDetail] = options?.detail ?? null;
    }
  
    /**
     * @type {any}
     */
    get detail() {
      if (!isCustomEvent(this))
        throw new ERR_INVALID_THIS('CustomEvent');
      return this[kDetail];
    }
  }
  
  ObjectDefineProperties(CustomEvent.prototype, {
    [SymbolToStringTag]: {
      __proto__: null,
      writable: false,
      enumerable: false,
      configurable: true,
      value: 'CustomEvent',
    },
    detail: kEnumerableProperty,
  });
  
  // Weak listener cleanup
  // This has to be lazy for snapshots to work
  let weakListenersState = null;
  // The resource needs to retain the callback so that it doesn't
  // get garbage collected now that it's weak.
  let objectToWeakListenerMap = null;
  function weakListeners() {
    weakListenersState ??= new SafeFinalizationRegistry(
      ({ eventTarget, listener, eventType }) => eventTarget.deref()?.[kRemoveWeakListenerHelper](eventType, listener),
    );
    objectToWeakListenerMap ??= new SafeWeakMap();
    return { registry: weakListenersState, map: objectToWeakListenerMap };
  }
  
  const kFlagOnce = 1 << 0;
  const kFlagCapture = 1 << 1;
  const kFlagPassive = 1 << 2;
  const kFlagNodeStyle = 1 << 3;
  const kFlagWeak = 1 << 4;
  const kFlagRemoved = 1 << 5;
  const kFlagResistStopPropagation = 1 << 6;
  
  // The listeners for an EventTarget are maintained as a linked list.
  // Unfortunately, the way EventTarget is defined, listeners are accounted
  // using the tuple [handler,capture], and even if we don't actually make
  // use of capture or bubbling, in order to be spec compliant we have to
  // take on the additional complexity of supporting it. Fortunately, using
  // the linked list makes dispatching faster, even if adding/removing is
  // slower.
  class Listener {
    constructor(eventTarget, eventType, previous, listener, once, capture, passive,
                isNodeStyleListener, weak, resistStopPropagation) {
      this.next = undefined;
      if (previous !== undefined)
        previous.next = this;
      this.previous = previous;
      this.listener = listener;
  
      let flags = 0b0;
      if (once)
        flags |= kFlagOnce;
      if (capture)
        flags |= kFlagCapture;
      if (passive)
        flags |= kFlagPassive;
      if (isNodeStyleListener)
        flags |= kFlagNodeStyle;
      if (weak)
        flags |= kFlagWeak;
      if (resistStopPropagation)
        flags |= kFlagResistStopPropagation;
      this.flags = flags;
  
      this.removed = false;
  
      if (this.weak) {
        this.callback = new SafeWeakRef(listener);
        weakListeners().registry.register(listener, {
          __proto__: null,
          // Weak ref so the listener won't hold the eventTarget alive
          eventTarget: new SafeWeakRef(eventTarget),
          listener: this,
          eventType,
        }, this);
        // Make the retainer retain the listener in a WeakMap
        weakListeners().map.set(weak, listener);
        this.listener = this.callback;
      } else if (typeof listener === 'function') {
        this.callback = listener;
        this.listener = listener;
      } else {
        this.callback = async (...args) => {
          if (listener.handleEvent)
            await ReflectApply(listener.handleEvent, listener, args);
        };
        this.listener = listener;
      }
    }
  
    get once() {
      return Boolean(this.flags & kFlagOnce);
    }
    get capture() {
      return Boolean(this.flags & kFlagCapture);
    }
    get passive() {
      return Boolean(this.flags & kFlagPassive);
    }
    get isNodeStyleListener() {
      return Boolean(this.flags & kFlagNodeStyle);
    }
    get weak() {
      return Boolean(this.flags & kFlagWeak);
    }
    get resistStopPropagation() {
      return Boolean(this.flags & kFlagResistStopPropagation);
    }
    get removed() {
      return Boolean(this.flags & kFlagRemoved);
    }
    set removed(value) {
      if (value)
        this.flags |= kFlagRemoved;
      else
        this.flags &= ~kFlagRemoved;
    }
  
    same(listener, capture) {
      const myListener = this.weak ? this.listener.deref() : this.listener;
      return myListener === listener && this.capture === capture;
    }
  
    remove() {
      if (this.previous !== undefined)
        this.previous.next = this.next;
      if (this.next !== undefined)
        this.next.previous = this.previous;
      this.removed = true;
      if (this.weak)
        weakListeners().registry.unregister(this);
    }
  }
  
  function initEventTarget(self) {
    self[kEvents] = new SafeMap();
    self[kMaxEventTargetListeners] = EventEmitter.defaultMaxListeners;
    self[kMaxEventTargetListenersWarned] = false;
    self[kHandlers] = new SafeMap();
  }
  
  class EventTarget {
    // Used in checking whether an object is an EventTarget. This is a well-known
    // symbol as EventTarget may be used cross-realm.
    // Ref: https://github.com/nodejs/node/pull/33661
    static [kIsEventTarget] = true;
  
    constructor() {
      initEventTarget(this);
    }
  
    [kNewListener](size, type, listener, once, capture, passive, weak) {
      if (this[kMaxEventTargetListeners] > 0 &&
          size > this[kMaxEventTargetListeners] &&
          !this[kMaxEventTargetListenersWarned]) {
        this[kMaxEventTargetListenersWarned] = true;
        // No error code for this since it is a Warning
        // eslint-disable-next-line no-restricted-syntax
        const w = new Error('Possible EventTarget memory leak detected. ' +
                            `${size} ${type} listeners ` +
                            `added to ${inspect(this, { depth: -1 })}. MaxListeners is ${this[kMaxEventTargetListeners]}. Use ` +
                            'events.setMaxListeners() to increase limit');
        w.name = 'MaxListenersExceededWarning';
        w.target = this;
        w.type = type;
        w.count = size;
        process.emitWarning(w);
      }
    }
    [kRemoveListener](size, type, listener, capture) {}
  
    /**
     * @callback EventTargetCallback
     * @param {Event} event
     */
  
    /**
     * @typedef {{ handleEvent: EventTargetCallback }} EventListener
     */
  
    /**
     * @param {string} type
     * @param {EventTargetCallback|EventListener} listener
     * @param {{
     *   capture?: boolean,
     *   once?: boolean,
     *   passive?: boolean,
     *   signal?: AbortSignal
     * }} [options]
     */
    addEventListener(type, listener, options = kEmptyObject) {
      if (!isEventTarget(this))
        throw new ERR_INVALID_THIS('EventTarget');
      if (arguments.length < 2)
        throw new ERR_MISSING_ARGS('type', 'listener');
  
      let once = false;
      let capture = false;
      let passive = false;
      let isNodeStyleListener = false;
      let weak = false;
      let resistStopPropagation = false;
  
      if (options !== kEmptyObject) {
        // We validateOptions before the validateListener check because the spec
        // requires us to hit getters.
        options = validateEventListenerOptions(options);
  
        once = options.once;
        capture = options.capture;
        passive = options.passive;
        isNodeStyleListener = options.isNodeStyleListener;
        weak = options.weak;
        resistStopPropagation = options.resistStopPropagation;
  
        const signal = options.signal;
  
        validateAbortSignal(signal, 'options.signal');
  
        if (signal) {
          if (signal.aborted) {
            return;
          }
          signal.addEventListener('abort', () => {
            this.removeEventListener(type, listener, options);
          }, { __proto__: null, once: true, [kWeakHandler]: this, [kResistStopPropagation]: true });
        }
      }
  
      if (!validateEventListener(listener)) {
        // The DOM silently allows passing undefined as a second argument
        // No error code for this since it is a Warning
        // eslint-disable-next-line no-restricted-syntax
        const w = new Error(`addEventListener called with ${listener}` +
                            ' which has no effect.');
        w.name = 'AddEventListenerArgumentTypeWarning';
        w.target = this;
        w.type = type;
        process.emitWarning(w);
        return;
      }
  
      type = webidl.converters.DOMString(type);
  
      let root = this[kEvents].get(type);
  
      if (root === undefined) {
        root = { size: 1, next: undefined, resistStopPropagation: Boolean(resistStopPropagation) };
        // This is the first handler in our linked list.
        new Listener(this, type, root, listener, once, capture, passive,
                     isNodeStyleListener, weak, resistStopPropagation);
        this[kNewListener](
          root.size,
          type,
          listener,
          once,
          capture,
          passive,
          weak);
        this[kEvents].set(type, root);
        return;
      }
  
      let handler = root.next;
      let previous = root;
  
      // We have to walk the linked list to see if we have a match
      while (handler !== undefined && !handler.same(listener, capture)) {
        previous = handler;
        handler = handler.next;
      }
  
      if (handler !== undefined) { // Duplicate! Ignore
        return;
      }
  
      new Listener(this, type, previous, listener, once, capture, passive,
                   isNodeStyleListener, weak, resistStopPropagation);
      root.size++;
      root.resistStopPropagation ||= Boolean(resistStopPropagation);
      this[kNewListener](root.size, type, listener, once, capture, passive, weak);
    }
  
    /**
     * @param {string} type
     * @param {EventTargetCallback|EventListener} listener
     * @param {{
     *   capture?: boolean,
     * }} [options]
     */
    removeEventListener(type, listener, options = kEmptyObject) {
      if (!isEventTarget(this))
        throw new ERR_INVALID_THIS('EventTarget');
      if (arguments.length < 2)
        throw new ERR_MISSING_ARGS('type', 'listener');
      if (!validateEventListener(listener))
        return;
  
      type = webidl.converters.DOMString(type);
      const capture = options?.capture === true;
  
      const root = this[kEvents].get(type);
      if (root === undefined || root.next === undefined)
        return;
  
      let handler = root.next;
      while (handler !== undefined) {
        if (handler.same(listener, capture)) {
          handler.remove();
          root.size--;
          if (root.size === 0)
            this[kEvents].delete(type);
          this[kRemoveListener](root.size, type, listener, capture);
          break;
        }
        handler = handler.next;
      }
    }
  
    [kRemoveWeakListenerHelper](type, listener) {
      const root = this[kEvents].get(type);
      if (root === undefined || root.next === undefined)
        return;
  
      const capture = listener.capture === true;
  
      let handler = root.next;
      while (handler !== undefined) {
        if (handler === listener) {
          handler.remove();
          root.size--;
          if (root.size === 0)
            this[kEvents].delete(type);
          // Undefined is passed as the listener as the listener was GCed
          this[kRemoveListener](root.size, type, undefined, capture);
          break;
        }
        handler = handler.next;
      }
    }
  
    /**
     * @param {Event} event
     * @returns {boolean}
     */
    dispatchEvent(event) {
      if (!isEventTarget(this))
        throw new ERR_INVALID_THIS('EventTarget');
      if (arguments.length < 1)
        throw new ERR_MISSING_ARGS('event');
  
      if (!(event instanceof Event))
        throw new ERR_INVALID_ARG_TYPE('event', 'Event', event);
  
      if (event[kIsBeingDispatched])
        throw new ERR_EVENT_RECURSION(event.type);
  
      this[kHybridDispatch](event, event.type, event);
      return event.defaultPrevented !== true;
    }
  
    [kHybridDispatch](nodeValue, type, event) {
      const createEvent = () => {
        if (event === undefined) {
          event = this[kCreateEvent](nodeValue, type);
          event[kTarget] = this;
          event[kIsBeingDispatched] = true;
        }
        return event;
      };
      if (event !== undefined) {
        event[kTarget] = this;
        event[kIsBeingDispatched] = true;
      }
  
      const root = this[kEvents].get(type);
      if (root === undefined || root.next === undefined) {
        if (event !== undefined)
          event[kIsBeingDispatched] = false;
        return true;
      }
  
      let handler = root.next;
      let next;
  
      const iterationCondition = () => {
        if (handler === undefined) {
          return false;
        }
        return root.resistStopPropagation || handler.passive || event?.[kStop] !== true;
      };
      while (iterationCondition()) {
        // Cache the next item in case this iteration removes the current one
        next = handler.next;
  
        if (handler.removed || (event?.[kStop] === true && !handler.resistStopPropagation)) {
          // Deal with the case an event is removed while event handlers are
          // Being processed (removeEventListener called from a listener)
          // And the case of event.stopImmediatePropagation() being called
          // For events not flagged as resistStopPropagation
          handler = next;
          continue;
        }
        if (handler.once) {
          handler.remove();
          root.size--;
          const { listener, capture } = handler;
          this[kRemoveListener](root.size, type, listener, capture);
        }
  
        let arg;
        try {
          if (handler.isNodeStyleListener) {
            arg = nodeValue;
          } else {
            arg = createEvent();
          }
          const callback = handler.weak ?
            handler.callback.deref() : handler.callback;
          let result;
          if (callback) {
            if (handler.passive && !handler.isNodeStyleListener) {
              arg[kInPassiveListener] = true;
            }
            result = FunctionPrototypeCall(callback, this, arg);
            if (!handler.isNodeStyleListener) {
              arg[kIsBeingDispatched] = false;
            }
          }
          if (result !== undefined && result !== null)
            addCatch(result);
        } catch (err) {
          emitUncaughtException(err);
        } finally {
          if (arg?.[kInPassiveListener])
            arg[kInPassiveListener] = false;
        }
  
        handler = next;
      }
  
      if (event !== undefined)
        event[kIsBeingDispatched] = false;
    }
  
    [kCreateEvent](nodeValue, type) {
      return new CustomEvent(type, { detail: nodeValue });
    }
    [customInspectSymbol](depth, options) {
      const name = this.constructor.name;
      if (depth < 0)
        return name;
  
      const opts = ObjectAssign({}, options, {
        depth: NumberIsInteger(options.depth) ? options.depth - 1 : options.depth,
      });
  
      return `${name} ${inspect({}, opts)}`;
    }
  }
  
  ObjectDefineProperties(EventTarget.prototype, {
    addEventListener: kEnumerableProperty,
    removeEventListener: kEnumerableProperty,
    dispatchEvent: kEnumerableProperty,
    [SymbolToStringTag]: {
      __proto__: null,
      writable: false,
      enumerable: false,
      configurable: true,
      value: 'EventTarget',
    },
  });
  
  function initNodeEventTarget(self) {
    initEventTarget(self);
  }
  
  class NodeEventTarget extends EventTarget {
    static [kIsNodeEventTarget] = true;
    static defaultMaxListeners = 10;
  
    constructor() {
      super();
      initNodeEventTarget(this);
    }
  
    /**
     * @param {number} n
     */
    setMaxListeners(n) {
      if (!isNodeEventTarget(this))
        throw new ERR_INVALID_THIS('NodeEventTarget');
      EventEmitter.setMaxListeners(n, this);
    }
  
    /**
     * @returns {number}
     */
    getMaxListeners() {
      if (!isNodeEventTarget(this))
        throw new ERR_INVALID_THIS('NodeEventTarget');
      return this[kMaxEventTargetListeners];
    }
  
    /**
     * @returns {string[]}
     */
    eventNames() {
      if (!isNodeEventTarget(this))
        throw new ERR_INVALID_THIS('NodeEventTarget');
      return ArrayFrom(this[kEvents].keys());
    }
  
    /**
     * @param {string} type
     * @returns {number}
     */
    listenerCount(type) {
      if (!isNodeEventTarget(this))
        throw new ERR_INVALID_THIS('NodeEventTarget');
      const root = this[kEvents].get(String(type));
      return root !== undefined ? root.size : 0;
    }
  
    /**
     * @param {string} type
     * @param {EventTargetCallback|EventListener} listener
     * @param {{
     *   capture?: boolean,
     * }} [options]
     * @returns {NodeEventTarget}
     */
    off(type, listener, options) {
      if (!isNodeEventTarget(this))
        throw new ERR_INVALID_THIS('NodeEventTarget');
      this.removeEventListener(type, listener, options);
      return this;
    }
  
    /**
     * @param {string} type
     * @param {EventTargetCallback|EventListener} listener
     * @param {{
     *   capture?: boolean,
     * }} [options]
     * @returns {NodeEventTarget}
     */
    removeListener(type, listener, options) {
      if (!isNodeEventTarget(this))
        throw new ERR_INVALID_THIS('NodeEventTarget');
      this.removeEventListener(type, listener, options);
      return this;
    }
  
    /**
     * @param {string} type
     * @param {EventTargetCallback|EventListener} listener
     * @returns {NodeEventTarget}
     */
    on(type, listener) {
      if (!isNodeEventTarget(this))
        throw new ERR_INVALID_THIS('NodeEventTarget');
      this.addEventListener(type, listener, { [kIsNodeStyleListener]: true });
      return this;
    }
  
    /**
     * @param {string} type
     * @param {EventTargetCallback|EventListener} listener
     * @returns {NodeEventTarget}
     */
    addListener(type, listener) {
      if (!isNodeEventTarget(this))
        throw new ERR_INVALID_THIS('NodeEventTarget');
      this.addEventListener(type, listener, { [kIsNodeStyleListener]: true });
      return this;
    }
  
    /**
     * @param {string} type
     * @param {any} arg
     * @returns {boolean}
     */
    emit(type, arg) {
      if (!isNodeEventTarget(this))
        throw new ERR_INVALID_THIS('NodeEventTarget');
      validateString(type, 'type');
      const hadListeners = this.listenerCount(type) > 0;
      this[kHybridDispatch](arg, type);
      return hadListeners;
    }
  
    /**
     * @param {string} type
     * @param {EventTargetCallback|EventListener} listener
     * @returns {NodeEventTarget}
     */
    once(type, listener) {
      if (!isNodeEventTarget(this))
        throw new ERR_INVALID_THIS('NodeEventTarget');
      this.addEventListener(type, listener,
                            { once: true, [kIsNodeStyleListener]: true });
      return this;
    }
  
    /**
     * @param {string} [type]
     * @returns {NodeEventTarget}
     */
    removeAllListeners(type) {
      if (!isNodeEventTarget(this))
        throw new ERR_INVALID_THIS('NodeEventTarget');
      if (type !== undefined) {
        this[kEvents].delete(String(type));
      } else {
        this[kEvents].clear();
      }
  
      return this;
    }
  }
  
  ObjectDefineProperties(NodeEventTarget.prototype, {
    setMaxListeners: kEnumerableProperty,
    getMaxListeners: kEnumerableProperty,
    eventNames: kEnumerableProperty,
    listenerCount: kEnumerableProperty,
    off: kEnumerableProperty,
    removeListener: kEnumerableProperty,
    on: kEnumerableProperty,
    addListener: kEnumerableProperty,
    once: kEnumerableProperty,
    emit: kEnumerableProperty,
    removeAllListeners: kEnumerableProperty,
  });
  
  // EventTarget API
  
  function validateEventListener(listener) {
    if (typeof listener === 'function' ||
        typeof listener?.handleEvent === 'function') {
      return true;
    }
  
    if (listener == null)
      return false;
  
    if (typeof listener === 'object') {
      // Require `handleEvent` lazily.
      return true;
    }
  
    throw new ERR_INVALID_ARG_TYPE('listener', 'EventListener', listener);
  }
  
  function validateEventListenerOptions(options) {
    if (typeof options === 'boolean')
      return { capture: options };
  
    if (options === null)
      return kEmptyObject;
    validateObject(options, 'options', kValidateObjectAllowObjects);
    return {
      once: Boolean(options.once),
      capture: Boolean(options.capture),
      passive: Boolean(options.passive),
      signal: options.signal,
      weak: options[kWeakHandler],
      resistStopPropagation: options[kResistStopPropagation] ?? false,
      isNodeStyleListener: Boolean(options[kIsNodeStyleListener]),
    };
  }
  
  // Test whether the argument is an event object. This is far from a fool-proof
  // test, for example this input will result in a false positive:
  // > isEventTarget({ constructor: EventTarget })
  // It stands in its current implementation as a compromise.
  // Ref: https://github.com/nodejs/node/pull/33661
  function isEventTarget(obj) {
    return obj?.constructor?.[kIsEventTarget];
  }
  
  function isNodeEventTarget(obj) {
    return obj?.constructor?.[kIsNodeEventTarget];
  }
  
  function addCatch(promise) {
    const then = promise.then;
    if (typeof then === 'function') {
      FunctionPrototypeCall(then, promise, undefined, function(err) {
        // The callback is called with nextTick to avoid a follow-up
        // rejection from this promise.
        emitUncaughtException(err);
      });
    }
  }
  
  function emitUncaughtException(err) {
    process.nextTick(() => { throw err; });
  }
  
  function makeEventHandler(handler) {
    // Event handlers are dispatched in the order they were first set
    // See https://github.com/nodejs/node/pull/35949#issuecomment-[REDACTED_SSN]
    function eventHandler(...args) {
      if (typeof eventHandler.handler !== 'function') {
        return;
      }
      return ReflectApply(eventHandler.handler, this, args);
    }
    eventHandler.handler = handler;
    return eventHandler;
  }
  
  function defineEventHandler(emitter, name, event = name) {
    // [REDACTED_IP] Event handlers - basically `on[eventName]` attributes
    const propName = `on${name}`;
    function get() {
      validateThisInternalField(this, kHandlers, 'EventTarget');
      return this[kHandlers]?.get(event)?.handler ?? null;
    }
    ObjectDefineProperty(get, 'name', {
      __proto__: null,
      value: `get ${propName}`,
    });
  
    function set(value) {
      validateThisInternalField(this, kHandlers, 'EventTarget');
      let wrappedHandler = this[kHandlers]?.get(event);
      if (wrappedHandler) {
        if (typeof wrappedHandler.handler === 'function') {
          this[kEvents].get(event).size--;
          const size = this[kEvents].get(event).size;
          this[kRemoveListener](size, event, wrappedHandler.handler, false);
        }
        wrappedHandler.handler = value;
        if (typeof wrappedHandler.handler === 'function') {
          this[kEvents].get(event).size++;
          const size = this[kEvents].get(event).size;
          this[kNewListener](size, event, value, false, false, false, false);
        }
      } else {
        wrappedHandler = makeEventHandler(value);
        this.addEventListener(event, wrappedHandler);
      }
      this[kHandlers].set(event, wrappedHandler);
    }
    ObjectDefineProperty(set, 'name', {
      __proto__: null,
      value: `set ${propName}`,
    });
  
    ObjectDefineProperty(emitter, propName, {
      __proto__: null,
      get,
      set,
      configurable: true,
      enumerable: true,
    });
  }
  
  module.exports = {
    Event,
    CustomEvent,
    EventTarget,
    NodeEventTarget,
    defineEventHandler,
    initEventTarget,
    initNodeEventTarget,
    kCreateEvent,
    kNewListener,
    kTrustEvent,
    kRemoveListener,
    kEvents,
    kWeakHandler,
    kResistStopPropagation,
    isEventTarget,
  };
  