Repository: denoland/deno
Pull request: fix(ext/node): use primordials in `ext/node/polyfills/internal/event_target.mjs`

Towards #24236. Replaces JS builtins with equivalent primordials.

<!--
Before submitting a PR, please read https://docs.deno.com/runtime/manual/references/contributing

1. Give the PR a descriptive title.

  Examples of good title:
    - fix(std/http): Fix race condition in server
    - docs(console): Update docstrings
    - feat(doc): Handle nested reexports

  Examples of bad title:
    - fix #7123
    - update docs
    - fix bugs

2. Ensure there is a related issue and it is referenced in the PR text.
3. Ensure there are tests that cover the changes.
4. Ensure `cargo test` passes.
5. Ensure `./tools/format.js` passes without changing files.
6. Ensure `./tools/lint.js` passes.
7. Open as a draft PR if your work is still in progress. The CI won't run
   all steps, but you can add '[ci]' to a commit message to force it to.
8. If you would like to run the benchmarks on the CI, add the 'ci-bench' label.
-->


Changed files:
filename: ext/node/polyfills/internal/event_target.mjs
patch: @@ -1,15 +1,37 @@
 // Copyright 2018-2025 the Deno authors. MIT license.
 // Copyright Node.js contributors. All rights reserved. MIT License.
 
-// TODO(petamoriken): enable prefer-primordials for node polyfills
-// deno-lint-ignore-file prefer-primordials
-
 import { primordials } from "ext:core/mod.js";
 const {
+  ArrayFrom,
+  Boolean,
+  Error,
+  FinalizationRegistryPrototypeRegister,
+  FinalizationRegistryPrototypeUnregister,
+  FunctionPrototypeCall,
   NumberIsInteger,
+  MapPrototypeClear,
+  MapPrototypeDelete,
+  MapPrototypeGet,
+  MapPrototypeKeys,
+  MapPrototypeSet,
   ObjectAssign,
+  ObjectDefineProperties,
+  ObjectDefineProperty,
+  ObjectGetOwnPropertyDescriptors,
+  ObjectPrototypeIsPrototypeOf,
+  ReflectApply,
+  SafeArrayIterator,
+  SafeFinalizationRegistry,
+  SafeMap,
+  SafeWeakMap,
+  SafeWeakRef,
+  String,
+  Symbol,
   SymbolFor,
   SymbolToStringTag,
+  WeakMapPrototypeSet,
+  WeakRefPrototypeDeref,
 } = primordials;
 import {
   ERR_EVENT_RECURSION,
@@ -35,7 +57,7 @@ import {
 } from "ext:deno_node/internal/util.mjs";
 import { inspect } from "node:util";
 
-const kIsEventTarget = Symbol.for("nodejs.event_target");
+const kIsEventTarget = SymbolFor("nodejs.event_target");
 const kIsNodeEventTarget = Symbol("kIsNodeEventTarget");
 
 import { kEvents } from "ext:deno_node/_events.mjs";
@@ -52,7 +74,7 @@ const kHandlers = Symbol("khandlers");
 const kWeakHandler = Symbol("kWeak");
 const kResistStopPropagation = Symbol("kResistStopPropagation");
 
-const kHybridDispatch = Symbol.for("nodejs.internal.kHybridDispatch");
+const kHybridDispatch = SymbolFor("nodejs.internal.kHybridDispatch");
 const kCreateEvent = Symbol("kCreateEvent");
 const kNewListener = Symbol("kNewListener");
 const kRemoveListener = Symbol("kRemoveListener");
@@ -298,10 +320,11 @@ class Event extends WebEvent {
   static BUBBLING_PHASE = 3;
 }
 
-Object.defineProperties(
+ObjectDefineProperties(
   Event.prototype,
   {
-    [Symbol.toStringTag]: {
+    [SymbolToStringTag]: {
+      __proto__: null,
       writable: true,
       enumerable: false,
       configurable: true,
@@ -327,6 +350,7 @@ Object.defineProperties(
     // precedence than Symbol.for('nodejs.util.inspect.custom') and shadow it.
     // Overwriting it to `undefined` cancels the shadowing.
     [SymbolFor("Deno.privateCustomInspect")]: {
+      __proto__: null,
       value: undefined,
       writable: true,
       enumerable: false,
@@ -386,10 +410,10 @@ let weakListenersState = null;
 // get garbage collected now that it's weak.
 let objectToWeakListenerMap = null;
 function weakListeners() {
-  weakListenersState ??= new FinalizationRegistry(
+  weakListenersState ??= new SafeFinalizationRegistry(
     (listener) => listener.remove(),
   );
-  objectToWeakListenerMap ??= new WeakMap();
+  objectToWeakListenerMap ??= new SafeWeakMap();
   return { registry: weakListenersState, map: objectToWeakListenerMap };
 }
 
@@ -425,16 +449,22 @@ class Listener {
     this.weak = Boolean(weak); // Don't retain the object
 
     if (this.weak) {
-      this.callback = new WeakRef(listener);
-      weakListeners().registry.register(listener, this, this);
+      this.callback = new SafeWeakRef(listener);
+      FinalizationRegistryPrototypeRegister(
+        weakListeners().registry,
+        listener,
+        this,
+        this,
+      );
       // Make the retainer retain the listener in a WeakMap
-      weakListeners().map.set(weak, listener);
+      WeakMapPrototypeSet(weakListeners().map, weak, listener);
       this.listener = this.callback;
     } else if (typeof listener === "function") {
       this.callback = listener;
       this.listener = listener;
     } else {
-      this.callback = Function.prototype.bind.call(
+      this.callback = FunctionPrototypeCall(
+        Function.prototype.bind,
         listener.handleEvent,
         listener,
       );
@@ -443,7 +473,9 @@ class Listener {
   }
 
   same(listener, capture) {
-    const myListener = this.weak ? this.listener.deref() : this.listener;
+    const myListener = this.weak
+      ? WeakRefPrototypeDeref(this.listener)
+      : this.listener;
     return myListener === listener && this.capture === capture;
   }
 
@@ -456,13 +488,13 @@ class Listener {
     }
     this.removed = true;
     if (this.weak) {
-      weakListeners().registry.unregister(this);
+      FinalizationRegistryPrototypeUnregister(weakListeners().registry, this);
     }
   }
 }
 
 function initEventTarget(self) {
-  self[kEvents] = new Map();
+  self[kEvents] = new SafeMap();
   self[kMaxEventTargetListeners] = EventEmitter.defaultMaxListeners;
   self[kMaxEventTargetListenersWarned] = false;
 }
@@ -521,7 +553,7 @@ class EventTarget extends WebEventTarget {
    *   signal?: AbortSignal
    * }} [options]
    */
-  addEventListener(type, listener, options = {}) {
+  addEventListener(type, listener, options = { __proto__: null }) {
     if (!isEventTarget(this)) {
       throw new ERR_INVALID_THIS("EventTarget");
     }
@@ -567,7 +599,7 @@ class EventTarget extends WebEventTarget {
       }, { once: true, [kWeakHandler]: this });
     }
 
-    let root = this[kEvents].get(type);
+    let root = MapPrototypeGet(this[kEvents], type);
 
     if (root === undefined) {
       root = { size: 1, next: undefined };
@@ -590,7 +622,7 @@ class EventTarget extends WebEventTarget {
         passive,
         weak,
       );
-      this[kEvents].set(type, root);
+      MapPrototypeSet(this[kEvents], type, root);
       return;
     }
 
@@ -627,7 +659,7 @@ class EventTarget extends WebEventTarget {
    *   capture?: boolean,
    * }} [options]
    */
-  removeEventListener(type, listener, options = {}) {
+  removeEventListener(type, listener, options = { __proto__: null }) {
     if (!isEventTarget(this)) {
       throw new ERR_INVALID_THIS("EventTarget");
     }
@@ -638,7 +670,7 @@ class EventTarget extends WebEventTarget {
     type = String(type);
     const capture = options?.capture === true;
 
-    const root = this[kEvents].get(type);
+    const root = MapPrototypeGet(this[kEvents], type);
     if (root === undefined || root.next === undefined) {
       return;
     }
@@ -649,7 +681,7 @@ class EventTarget extends WebEventTarget {
         handler.remove();
         root.size--;
         if (root.size === 0) {
-          this[kEvents].delete(type);
+          MapPrototypeDelete(this[kEvents], type);
         }
         this[kRemoveListener](root.size, type, listener, capture);
         break;
@@ -666,7 +698,7 @@ class EventTarget extends WebEventTarget {
       throw new ERR_INVALID_THIS("EventTarget");
     }
 
-    if (!(event instanceof globalThis.Event)) {
+    if (!ObjectPrototypeIsPrototypeOf(globalThis.Event.prototype, event)) {
       throw new ERR_INVALID_ARG_TYPE("event", "Event", event);
     }
 
@@ -693,7 +725,7 @@ class EventTarget extends WebEventTarget {
       event[kIsBeingDispatched] = true;
     }
 
-    const root = this[kEvents].get(type);
+    const root = MapPrototypeGet(this[kEvents], type);
     if (root === undefined || root.next === undefined) {
       if (event !== undefined) {
         event[kIsBeingDispatched] = false;
@@ -732,11 +764,11 @@ class EventTarget extends WebEventTarget {
           arg = createEvent();
         }
         const callback = handler.weak
-          ? handler.callback.deref()
+          ? WeakRefPrototypeDeref(handler.callback)
           : handler.callback;
         let result;
         if (callback) {
-          result = callback.call(this, arg);
+          result = FunctionPrototypeCall(callback, this, arg);
           if (!handler.isNodeStyleListener) {
             arg[kIsBeingDispatched] = false;
           }
@@ -776,11 +808,12 @@ class EventTarget extends WebEventTarget {
   }
 }
 
-Object.defineProperties(EventTarget.prototype, {
+ObjectDefineProperties(EventTarget.prototype, {
   addEventListener: kEnumerableProperty,
   removeEventListener: kEnumerableProperty,
   dispatchEvent: kEnumerableProperty,
-  [Symbol.toStringTag]: {
+  [SymbolToStringTag]: {
+    __proto__: null,
     writable: true,
     enumerable: false,
     configurable: true,
@@ -828,7 +861,7 @@ class NodeEventTarget extends EventTarget {
     if (!isNodeEventTarget(this)) {
       throw new ERR_INVALID_THIS("NodeEventTarget");
     }
-    return Array.from(this[kEvents].keys());
+    return ArrayFrom(MapPrototypeKeys(this[kEvents]));
   }
 
   /**
@@ -839,7 +872,7 @@ class NodeEventTarget extends EventTarget {
     if (!isNodeEventTarget(this)) {
       throw new ERR_INVALID_THIS("NodeEventTarget");
     }
-    const root = this[kEvents].get(String(type));
+    const root = MapPrototypeGet(this[kEvents], String(type));
     return root !== undefined ? root.size : 0;
   }
 
@@ -941,16 +974,16 @@ class NodeEventTarget extends EventTarget {
       throw new ERR_INVALID_THIS("NodeEventTarget");
     }
     if (type !== undefined) {
-      this[kEvents].delete(String(type));
+      MapPrototypeDelete(this[kEvents], String(type));
     } else {
-      this[kEvents].clear();
+      MapPrototypeClear(this[kEvents]);
     }
 
     return this;
   }
 }
 
-Object.defineProperties(NodeEventTarget.prototype, {
+ObjectDefineProperties(NodeEventTarget.prototype, {
   setMaxListeners: kEnumerableProperty,
   getMaxListeners: kEnumerableProperty,
   eventNames: kEnumerableProperty,
@@ -1004,7 +1037,7 @@ function validateEventListenerOptions(options) {
 }
 
 function isEventTarget(obj) {
-  return obj instanceof globalThis.EventTarget;
+  return ObjectPrototypeIsPrototypeOf(globalThis.EventTarget.prototype, obj);
 }
 
 function isNodeEventTarget(obj) {
@@ -1014,7 +1047,7 @@ function isNodeEventTarget(obj) {
 function addCatch(promise) {
   const then = promise.then;
   if (typeof then === "function") {
-    then.call(promise, undefined, function (err) {
+    FunctionPrototypeCall(then, promise, undefined, function (err) {
       // The callback is called with nextTick to avoid a follow-up
       // rejection from this promise.
       emitUncaughtException(err);
@@ -1035,40 +1068,44 @@ function makeEventHandler(handler) {
     if (typeof eventHandler.handler !== "function") {
       return;
     }
-    return Reflect.apply(eventHandler.handler, this, args);
+    return ReflectApply(eventHandler.handler, this, args);
   }
   eventHandler.handler = handler;
   return eventHandler;
 }
 
 function defineEventHandler(emitter, name) {
   // 8.1.5.1 Event handlers - basically `on[eventName]` attributes
-  Object.defineProperty(emitter, `on${name}`, {
+  ObjectDefineProperty(emitter, `on${name}`, {
+    __proto__: null,
     get() {
-      return this[kHandlers]?.get(name)?.handler ?? null;
+      if (this[kHandlers]) {
+        return MapPrototypeGet(this[kHandlers], name)?.handler ?? null;
+      }
+      return null;
     },
     set(value) {
       if (!this[kHandlers]) {
-        this[kHandlers] = new Map();
+        this[kHandlers] = new SafeMap();
       }
-      let wrappedHandler = this[kHandlers]?.get(name);
+      let wrappedHandler = MapPrototypeGet(this[kHandlers], name);
       if (wrappedHandler) {
         if (typeof wrappedHandler.handler === "function") {
-          this[kEvents].get(name).size--;
-          const size = this[kEvents].get(name).size;
+          MapPrototypeGet(this[kEvents], name).size--;
+          const size = MapPrototypeGet(this[kEvents], name).size;
           this[kRemoveListener](size, name, wrappedHandler.handler, false);
         }
         wrappedHandler.handler = value;
         if (typeof wrappedHandler.handler === "function") {
-          this[kEvents].get(name).size++;
-          const size = this[kEvents].get(name).size;
+          MapPrototypeGet(this[kEvents], name).size++;
+          const size = MapPrototypeGet(this[kEvents], name).size;
           this[kNewListener](size, name, value, false, false, false, false);
         }
       } else {
         wrappedHandler = makeEventHandler(value);
         this.addEventListener(name, wrappedHandler);
       }
-      this[kHandlers].set(name, wrappedHandler);
+      MapPrototypeSet(this[kHandlers], name, wrappedHandler);
     },
     configurable: true,
     enumerable: true,
@@ -1078,13 +1115,13 @@ function defineEventHandler(emitter, name) {
 const EventEmitterMixin = (Superclass) => {
   class MixedEventEmitter extends Superclass {
     constructor(...args) {
-      super(...args);
-      EventEmitter.call(this);
+      super(...new SafeArrayIterator(args));
+      FunctionPrototypeCall(EventEmitter, this);
     }
   }
-  const protoProps = Object.getOwnPropertyDescriptors(EventEmitter.prototype);
+  const protoProps = ObjectGetOwnPropertyDescriptors(EventEmitter.prototype);
   delete protoProps.constructor;
-  Object.defineProperties(MixedEventEmitter.prototype, protoProps);
+  ObjectDefineProperties(MixedEventEmitter.prototype, protoProps);
   return MixedEventEmitter;
 };