UNPKG

nstdlib-nightly

Version:

Node.js standard library converted to runtime-agnostic ES modules.

1,154 lines (1,028 loc) 31 kB
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/event_target.js import { codes as __codes__ } from "nstdlib/lib/internal/errors"; import { validateAbortSignal, validateObject, validateString, validateInternalField, kValidateObjectAllowObjects, } from "nstdlib/lib/internal/validators"; import { customInspectSymbol, kEmptyObject, kEnumerableProperty, } from "nstdlib/lib/internal/util"; import { inspect } from "nstdlib/lib/util"; import * as webidl from "nstdlib/lib/internal/webidl"; import * as EventEmitter from "nstdlib/lib/events"; import { now } from "nstdlib/lib/internal/perf/utils"; const { ERR_EVENT_RECURSION, ERR_INVALID_ARG_TYPE, ERR_INVALID_THIS, ERR_MISSING_ARGS, } = __codes__; const kIsEventTarget = Symbol.for("nodejs.event_target"); const kIsNodeEventTarget = Symbol("kIsNodeEventTarget"); 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 = Symbol.for("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 kType = Symbol("type"); const kDetail = Symbol("detail"); const isTrustedSet = new WeakSet(); const isTrusted = Object.getOwnPropertyDescriptor( { 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 = kEmptyObject) { if (arguments.length === 0) throw new ERR_MISSING_ARGS("type"); validateObject(options, "options"); const { bubbles, cancelable, composed } = options; this.#cancelable = !!cancelable; this.#bubbles = !!bubbles; this.#composed = !!composed; this[kType] = `${type}`; if (options?.[kTrustEvent]) { isTrustedSet.add(this); } this[kTarget] = null; this[kIsBeingDispatched] = 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 = Object.assign({}, options, { depth: Number.isInteger(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"); 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[kTarget]; } /** * @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} */ 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; } } Object.defineProperties(Event.prototype, { [Symbol.toStringTag]: { __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"]; Object.defineProperties( Event, Array.prototype.reduce.call( 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 { /** * @constructor * @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]; } } Object.defineProperties(CustomEvent.prototype, { [Symbol.toStringTag]: { __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 FinalizationRegistry( ({ eventTarget, listener, eventType }) => eventTarget.deref()?.[kRemoveWeakListenerHelper](eventType, listener), ); objectToWeakListenerMap ??= new WeakMap(); 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 WeakRef(listener); weakListeners().registry.register( listener, { __proto__: null, // Weak ref so the listener won't hold the eventTarget alive eventTarget: new WeakRef(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 Map(); self[kMaxEventTargetListeners] = EventEmitter.defaultMaxListeners; self[kMaxEventTargetListenersWarned] = false; self[kHandlers] = new Map(); } 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"); // We validateOptions before the validateListener check because the spec // requires us to hit getters. const { once, capture, passive, signal, isNodeStyleListener, weak, resistStopPropagation, } = validateEventListenerOptions(options); validateAbortSignal(signal, "options.signal"); 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); if (signal) { if (signal.aborted) { return; } // TODO(benjamingr) make this weak somehow? ideally the signal would // not prevent the event target from GC. signal.addEventListener( "abort", () => { this.removeEventListener(type, listener, options); }, { __proto__: null, once: true, [kWeakHandler]: this, [kResistStopPropagation]: true, }, ); } 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 */ 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); } try { let arg; if (handler.isNodeStyleListener) { arg = nodeValue; } else { arg = createEvent(); } const callback = handler.weak ? handler.callback.deref() : handler.callback; let result; if (callback) { result = Function.prototype.call.call(callback, this, arg); if (!handler.isNodeStyleListener) { arg[kIsBeingDispatched] = false; } } if (result !== undefined && result !== null) addCatch(result); } catch (err) { emitUncaughtException(err); } handler = next; } if (event !== undefined) event[kIsBeingDispatched] = false; } [kCreateEvent](nodeValue, type) { return new CustomEvent(type, { detail: nodeValue }); } [customInspectSymbol](depth, options) { if (!isEventTarget(this)) throw new ERR_INVALID_THIS("EventTarget"); const name = this.constructor.name; if (depth < 0) return name; const opts = Object.assign({}, options, { depth: Number.isInteger(options.depth) ? options.depth - 1 : options.depth, }); return `${name} ${inspect({}, opts)}`; } } Object.defineProperties(EventTarget.prototype, { addEventListener: kEnumerableProperty, removeEventListener: kEnumerableProperty, dispatchEvent: kEnumerableProperty, [Symbol.toStringTag]: { __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 Array.from(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; } } Object.defineProperties(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") { Function.prototype.call.call(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-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, event = name) { // 8.1.5.1 Event handlers - basically `on[eventName]` attributes const propName = `on${name}`; function get() { validateInternalField(this, kHandlers, "EventTarget"); return this[kHandlers]?.get(event)?.handler ?? null; } Object.defineProperty(get, "name", { __proto__: null, value: `get ${propName}`, }); function set(value) { validateInternalField(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); } Object.defineProperty(set, "name", { __proto__: null, value: `set ${propName}`, }); Object.defineProperty(emitter, propName, { __proto__: null, get, set, configurable: true, enumerable: true, }); } export { Event }; export { CustomEvent }; export { EventTarget }; export { NodeEventTarget }; export { defineEventHandler }; export { initEventTarget }; export { initNodeEventTarget }; export { kCreateEvent }; export { kNewListener }; export { kTrustEvent }; export { kRemoveListener }; export { kEvents }; export { kWeakHandler }; export { kResistStopPropagation }; export { isEventTarget };