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