Skip to main content

nodejs/stdlib/
events.rs

1//! Node `events` module: `EventEmitter`. The emitter is an object tagged
2//! `@@native = "EventEmitter"` with hidden `@@on`/`@@once` maps (event name →
3//! listener array). `emit` collects listeners, releases the host borrow, then
4//! invokes each so callbacks can re-enter the host.
5
6use super::arg_str;
7use crate::host::{call_method, invoke, with_host, JsObj};
8use fusevm::Value;
9use indexmap::IndexMap;
10
11/// Construct a fresh `EventEmitter`.
12pub fn new_emitter() -> Value {
13    with_host(|h| {
14        let on = h.new_object(IndexMap::new());
15        let once = h.new_object(IndexMap::new());
16        let mut m = IndexMap::new();
17        m.insert("@@native".into(), h.new_str("EventEmitter"));
18        m.insert("@@on".into(), on);
19        m.insert("@@once".into(), once);
20        h.new_object(m)
21    })
22}
23
24/// The EventEmitter method names, exposed so `EventEmitter.prototype` can be
25/// enumerated / copied (express does `mixin(app, EventEmitter.prototype)` to make
26/// its `app` *function* an emitter).
27pub const METHODS: &[&str] = &[
28    "on",
29    "addListener",
30    "prependListener",
31    "once",
32    "prependOnceListener",
33    "emit",
34    "removeListener",
35    "off",
36    "removeAllListeners",
37    "listenerCount",
38    "listeners",
39    "rawListeners",
40    "eventNames",
41    "setMaxListeners",
42    "getMaxListeners",
43];
44
45/// The internal key an event name registers under.
46///
47/// `ToPropertyKey`, not `String(name)`: a SYMBOL event name is a distinct key
48/// (`@@sym:<id>`), so `on(sym, f)` and `on("Symbol(desc)", f)` are different
49/// events and `eventNames()` can hand the symbol itself back. Rendering it
50/// collapsed the two and made every symbol listener unremovable by its symbol.
51fn event_key(args: &[Value]) -> String {
52    with_host(|h| h.property_key(args.first().unwrap_or(&Value::Undef)))
53}
54
55pub fn instance_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
56    match method {
57        // Both the event-name coercion and the listener lookup re-enter the host,
58        // so they must resolve BEFORE the `with_host` that allocates the array —
59        // nesting them inside it panics with `RefCell already borrowed`. That was
60        // latent until `listeners` became reachable on a socket/request/stream.
61        // `rawListeners` returns the once-WRAPPERS in node; once-listeners are
62        // stored unwrapped here, so the two views coincide.
63        "listeners" | "rawListeners" => {
64            let items = listeners(recv, &event_key(&args));
65            Ok(with_host(|h| h.new_array(items)))
66        }
67        // The cap is not enforced (nothing here warns on listener count), but it
68        // must still read back what was set — `setMaxListeners` used to discard
69        // the value and `getMaxListeners` always answered the default 10.
70        "setMaxListeners" => {
71            let n = args
72                .first()
73                .map(|v| with_host(|h| h.to_number(v)))
74                .unwrap_or(10.0);
75            with_host(|h| {
76                let nv = Value::Float(n);
77                match h.get_mut(recv) {
78                    Some(JsObj::Object(p)) => {
79                        p.insert("@@maxListeners".into(), nv);
80                    }
81                    _ => h.set_fn_prop(recv, "@@maxListeners", nv),
82                }
83            });
84            Ok(recv.clone())
85        }
86        "getMaxListeners" => Ok(with_host(|h| match named_map(h, recv, "@@maxListeners") {
87            Some(v @ (Value::Float(_) | Value::Int(_))) => v,
88            _ => Value::Float(10.0),
89        })),
90        "on" | "addListener" | "prependListener" | "once" | "prependOnceListener" => {
91            let once = matches!(method, "once" | "prependOnceListener");
92            let prepend = method.starts_with("prepend");
93            let name = event_key(&args);
94            let f = args.get(1).cloned().unwrap_or(Value::Undef);
95            // `newListener` fires BEFORE the listener is added, so a handler for
96            // it sees the emitter without the new listener and can add its own
97            // ahead of it. It was never emitted at all.
98            if name != "newListener" && !listeners(recv, "newListener").is_empty() {
99                let nv = with_host(|h| h.new_str(name.clone()));
100                emit(recv, "newListener", &[nv, f.clone()])?;
101            }
102            add(
103                recv,
104                if once { "@@once" } else { "@@on" },
105                &name,
106                f,
107                prepend,
108            );
109            Ok(recv.clone())
110        }
111        "emit" => emit(
112            recv,
113            &event_key(&args),
114            &args.get(1..).map(|s| s.to_vec()).unwrap_or_default(),
115        ),
116        "removeListener" | "off" => {
117            let name = event_key(&args);
118            let f = args.get(1).cloned();
119            let had = f
120                .as_ref()
121                .is_some_and(|f| listeners(recv, &name).iter().any(|l| l == f));
122            remove(recv, &name, f.clone());
123            // `removeListener` fires AFTER the removal, and only when one
124            // actually happened. It was never emitted at all.
125            if had && name != "removeListener" && !listeners(recv, "removeListener").is_empty() {
126                let nv = with_host(|h| h.new_str(name.clone()));
127                emit(recv, "removeListener", &[nv, f.unwrap_or(Value::Undef)])?;
128            }
129            Ok(recv.clone())
130        }
131        "removeAllListeners" => {
132            let name = if args.is_empty() {
133                None
134            } else {
135                Some(event_key(&args))
136            };
137            remove_all(recv, name.as_deref());
138            Ok(recv.clone())
139        }
140        "listenerCount" => Ok(Value::Float(listeners(recv, &event_key(&args)).len() as f64)),
141        // A SYMBOL event name comes back as the symbol itself, not as its
142        // `Symbol(desc)` rendering — `emitter.on(sym, f)` then `eventNames()`
143        // has to hand back something `emitter.off(name, f)` accepts. Strings
144        // come first, then symbols, which is the own-key order node reports.
145        "eventNames" => Ok(with_host(|h| {
146            let mut keys: Vec<String> = Vec::new();
147            if let Some(JsObj::Object(p)) = named_map(h, recv, "@@on").and_then(|v| h.get(&v)) {
148                keys.extend(p.keys().cloned());
149            }
150            let (syms, strs): (Vec<String>, Vec<String>) = keys
151                .into_iter()
152                .partition(|k| crate::host::is_symbol_key(k));
153            let mut names: Vec<Value> = strs.into_iter().map(|k| h.new_str(k)).collect();
154            names.extend(
155                syms.iter()
156                    .filter_map(|k| h.symbol_of_key(k))
157                    .collect::<Vec<Value>>(),
158            );
159            h.new_array(names)
160        })),
161        _ => Err(crate::host::type_error(&format!(
162            "emitter.{method} is not a function"
163        ))),
164    }
165}
166
167/// Read a hidden emitter field (`@@on`/`@@once`). Works for a plain emitter
168/// object AND for a function/class receiver (express's `app` is a function whose
169/// emitter maps live in the fn-prop side table).
170fn named_map(h: &crate::host::JsHost, recv: &Value, which: &str) -> Option<Value> {
171    match h.get(recv) {
172        Some(JsObj::Object(p)) => p.get(which).cloned(),
173        Some(JsObj::Func(_)) | Some(JsObj::Class(_)) => h.fn_prop(recv, which),
174        _ => None,
175    }
176}
177
178/// Store a hidden emitter field, routing to props or the fn-prop table.
179fn set_named_map(h: &mut crate::host::JsHost, recv: &Value, which: &str, val: Value) {
180    match h.get(recv) {
181        Some(JsObj::Func(_)) | Some(JsObj::Class(_)) => h.set_fn_prop(recv, which, val),
182        _ => {
183            if let Some(JsObj::Object(p)) = h.get_mut(recv) {
184                p.insert(which.to_string(), val);
185            }
186        }
187    }
188}
189
190/// Register `f` for `name`.
191///
192/// Every listener — `once` included — goes into the single ordered `@@on` list,
193/// and `@@once` holds only a MARKER copy of the once-only ones. It used to be
194/// two parallel queues that `listeners` concatenated `@@on`-then-`@@once`, so a
195/// once-listener always fired last no matter when it was registered:
196/// `e.once('a', first); e.on('a', second)` ran `second` first, and
197/// `prependOnceListener` could not reach the front at all.
198fn add(recv: &Value, which: &str, name: &str, f: Value, prepend: bool) {
199    if which == "@@once" {
200        // The marker records once-ness; order within it is never observed.
201        add_to_list(recv, "@@once", name, f.clone(), false);
202    }
203    add_to_list(recv, "@@on", name, f, prepend);
204}
205
206fn add_to_list(recv: &Value, which: &str, name: &str, f: Value, prepend: bool) {
207    with_host(|h| {
208        // Lazily create the listener map (a mixed-in function emitter has none).
209        let map = match named_map(h, recv, which) {
210            Some(m) => m,
211            None => {
212                let m = h.new_object(IndexMap::new());
213                set_named_map(h, recv, which, m.clone());
214                m
215            }
216        };
217        // Ensure `map[name]` is an array, then push.
218        let arr = match h.get(&map) {
219            Some(JsObj::Object(p)) => p.get(name).cloned(),
220            _ => None,
221        };
222        let arr = match arr {
223            Some(a) if matches!(h.get(&a), Some(JsObj::Array(_))) => a,
224            _ => {
225                let a = h.new_array(Vec::new());
226                if let Some(JsObj::Object(p)) = h.get_mut(&map) {
227                    p.insert(name.to_string(), a.clone());
228                }
229                a
230            }
231        };
232        if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
233            // `prependListener` puts the handler FIRST; it was appending like
234            // `on`, so the two were indistinguishable.
235            if prepend {
236                items.insert(0, f);
237            } else {
238                items.push(f);
239            }
240        }
241    });
242}
243
244/// Drop the FIRST entry equal to `f` from one listener list, leaving any
245/// duplicate registrations of the same function in place.
246fn remove_first(recv: &Value, which: &str, name: &str, f: &Value) {
247    with_host(|h| {
248        let Some(map) = named_map(h, recv, which) else {
249            return;
250        };
251        let arr = match h.get(&map) {
252            Some(JsObj::Object(p)) => p.get(name).cloned(),
253            _ => None,
254        };
255        let mut emptied = false;
256        if let Some(JsObj::Array(items)) = arr.and_then(|a| h.get_mut(&a)) {
257            if let Some(i) = items.iter().position(|x| x == f) {
258                items.remove(i);
259            }
260            emptied = items.is_empty();
261        }
262        // An emptied list must take its KEY with it, or `eventNames()` keeps
263        // reporting an event nothing is listening for.
264        if emptied {
265            if let Some(JsObj::Object(p)) = h.get_mut(&map) {
266                p.shift_remove(name);
267            }
268        }
269    });
270}
271
272fn listeners(recv: &Value, name: &str) -> Vec<Value> {
273    with_host(|h| {
274        // `@@on` alone: it holds every listener in registration order, and
275        // `@@once` is only a marker copy of some of them.
276        let mut out = Vec::new();
277        if let Some(map) = named_map(h, recv, "@@on") {
278            if let Some(JsObj::Object(p)) = h.get(&map) {
279                if let Some(a) = p.get(name) {
280                    if let Some(JsObj::Array(items)) = h.get(a) {
281                        out.extend(items.iter().cloned());
282                    }
283                }
284            }
285        }
286        out
287    })
288}
289
290fn emit(recv: &Value, name: &str, args: &[Value]) -> Result<Value, String> {
291    let to_call = listeners(recv, name);
292    // An `error` event with no listener THROWS rather than being dropped. This
293    // is how node surfaces a failed socket, stream or request, and swallowing
294    // it turned every such failure into silence.
295    if name == "error" && to_call.is_empty() {
296        let err = args.first().cloned().unwrap_or(Value::Undef);
297        if matches!(err, Value::Undef) {
298            return Err(crate::host::plain_coded_error(
299                "Error",
300                "ERR_UNHANDLED_ERROR",
301                "Unhandled error.",
302            ));
303        }
304        let msg = with_host(|h| {
305            h.exc = Some(err.clone());
306            crate::builtins::error_string(h, &err)
307        });
308        return Err(msg);
309    }
310    // Once-listeners fire a single time. They live in BOTH lists now, so
311    // clearing the marker also has to drop one matching entry apiece from the
312    // ordered list — one, not all, so a function registered with `on` AND
313    // `once` keeps its `on` registration, as in node.
314    let expired = remove_all_of(recv, "@@once", Some(name));
315    for f in &expired {
316        remove_first(recv, "@@on", name, f);
317    }
318    let had = !to_call.is_empty();
319    for f in to_call {
320        invoke(&f, args.to_vec(), Some(recv.clone()))?;
321    }
322    // Settle any `events.once(emitter, name)` promise waiters for this event.
323    resolve_waiters(recv, name, args);
324    Ok(Value::Bool(had))
325}
326
327// ── `events.once` promise waiters ───────────────────────────────────────────
328//
329// `once(emitter, name)` returns a real Promise. We cannot register a Rust
330// closure as a JS listener (listeners must be callable Values), so instead a
331// pending promise is parked under the emitter's hidden `@@waiters` map keyed by
332// event name; `emit` (above) settles them. On `error`, waiters of every other
333// event reject with the error, mirroring Node's `once` semantics.
334
335/// Park `promise` to be resolved when `name` next fires on `recv`.
336fn add_waiter(recv: &Value, name: &str, promise: Value) {
337    with_host(|h| {
338        let map = match waiter_map(h, recv) {
339            Some(m) => m,
340            None => {
341                let m = h.new_object(IndexMap::new());
342                if let Some(JsObj::Object(p)) = h.get_mut(recv) {
343                    p.insert("@@waiters".into(), m.clone());
344                }
345                m
346            }
347        };
348        let existing = match h.get(&map) {
349            Some(JsObj::Object(p)) => p.get(name).cloned(),
350            _ => None,
351        };
352        let arr = match existing {
353            Some(a) if matches!(h.get(&a), Some(JsObj::Array(_))) => a,
354            _ => {
355                let a = h.new_array(Vec::new());
356                if let Some(JsObj::Object(p)) = h.get_mut(&map) {
357                    p.insert(name.to_string(), a.clone());
358                }
359                a
360            }
361        };
362        if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
363            items.push(promise);
364        }
365    });
366}
367
368fn waiter_map(h: &crate::host::JsHost, recv: &Value) -> Option<Value> {
369    match h.get(recv) {
370        Some(JsObj::Object(p)) => p.get("@@waiters").cloned(),
371        _ => None,
372    }
373}
374
375/// Remove and return the promises parked on `name`.
376fn take_waiters(recv: &Value, name: &str) -> Vec<Value> {
377    with_host(|h| {
378        let Some(map) = waiter_map(h, recv) else {
379            return Vec::new();
380        };
381        let arr = match h.get_mut(&map) {
382            Some(JsObj::Object(p)) => p.shift_remove(name),
383            _ => None,
384        };
385        let Some(arr) = arr else { return Vec::new() };
386        match h.get(&arr) {
387            Some(JsObj::Array(items)) => items.clone(),
388            _ => Vec::new(),
389        }
390    })
391}
392
393/// Remove and return every parked promise except those on `keep`.
394fn take_waiters_except(recv: &Value, keep: &str) -> Vec<Value> {
395    with_host(|h| {
396        let Some(map) = waiter_map(h, recv) else {
397            return Vec::new();
398        };
399        let keys: Vec<String> = match h.get(&map) {
400            Some(JsObj::Object(p)) => p.keys().filter(|k| k.as_str() != keep).cloned().collect(),
401            _ => Vec::new(),
402        };
403        let mut out = Vec::new();
404        for k in keys {
405            let arr = match h.get_mut(&map) {
406                Some(JsObj::Object(p)) => p.shift_remove(&k),
407                _ => None,
408            };
409            if let Some(arr) = arr {
410                if let Some(JsObj::Array(items)) = h.get(&arr) {
411                    out.extend(items.iter().cloned());
412                }
413            }
414        }
415        out
416    })
417}
418
419fn resolve_waiters(recv: &Value, name: &str, args: &[Value]) {
420    let waiting = take_waiters(recv, name);
421    if !waiting.is_empty() {
422        let arr = with_host(|h| h.new_array(args.to_vec()));
423        for p in &waiting {
424            if let Some(id) = with_host(|h| h.promise_id(p)) {
425                crate::host::resolve_promise_val(id, arr.clone());
426            }
427        }
428    }
429    if name == "error" {
430        let err = args.first().cloned().unwrap_or(Value::Undef);
431        for p in take_waiters_except(recv, "error") {
432            if let Some(id) = with_host(|h| h.promise_id(&p)) {
433                crate::host::reject_promise_val(id, err.clone());
434            }
435        }
436    }
437}
438
439// ── static module functions (`require('events').once`, `.listenerCount`, …) ──
440
441/// Static functions on the `events` module namespace. `EventEmitter` (the
442/// self-ref ctor) and `EventEmitterAsyncResource` are handled by the parent;
443/// `on` (async iterator) is deferred (see module docs / final report).
444pub const STATIC_METHODS: &[&str] = &[
445    "once",
446    "listenerCount",
447    "getEventListeners",
448    "getMaxListeners",
449    "setMaxListeners",
450    "addAbortListener",
451    "init",
452];
453
454/// Dispatch a static `events.<method>(...)`. Returns `None` for names this
455/// module does not own (e.g. `EventEmitter`) so the parent's specific arm wins.
456pub fn static_call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
457    let emitter = args.first().cloned().unwrap_or(Value::Undef);
458    Some(match method {
459        "once" => Ok(once_static(emitter, &arg_str(args, 1))),
460        "listenerCount" => Ok(Value::Float(
461            listeners(&emitter, &arg_str(args, 1)).len() as f64
462        )),
463        // `listeners` takes the host, so calling it INSIDE `with_host` borrowed
464        // the same RefCell twice and aborted the process with "RefCell already
465        // borrowed" — a Rust panic, not a throw, so no JS `try` caught it.
466        // Collect first, then borrow to build the array.
467        "getEventListeners" => {
468            let found = listeners(&emitter, &arg_str(args, 1));
469            Ok(with_host(|h| h.new_array(found)))
470        }
471        // No per-emitter cap is tracked; report Node's default and accept sets.
472        "getMaxListeners" => Ok(Value::Float(10.0)),
473        "setMaxListeners" => Ok(Value::Undef),
474        "addAbortListener" => Ok(add_abort_listener(args)),
475        "init" => Ok(init_emitter(emitter)),
476        _ => return None,
477    })
478}
479
480/// `events.once(emitter, name)` → a Promise resolving with the event args (or
481/// rejecting with the error if `error` fires first).
482fn once_static(emitter: Value, name: &str) -> Value {
483    let p = with_host(|h| h.new_promise());
484    add_waiter(&emitter, name, p.clone());
485    p
486}
487
488/// `EventEmitter.init(emitter)` — ensure the hidden emitter maps exist on
489/// `emitter` (used when mixing the emitter surface into a plain object).
490fn init_emitter(emitter: Value) -> Value {
491    with_host(|h| {
492        let has = matches!(h.get(&emitter), Some(JsObj::Object(p)) if p.contains_key("@@on"));
493        if !has {
494            let on = h.new_object(IndexMap::new());
495            let once = h.new_object(IndexMap::new());
496            let native = h.new_str("EventEmitter");
497            if let Some(JsObj::Object(p)) = h.get_mut(&emitter) {
498                p.entry("@@native".to_string()).or_insert(native);
499                p.insert("@@on".to_string(), on);
500                p.insert("@@once".to_string(), once);
501            }
502        }
503    });
504    emitter
505}
506
507/// `events.addAbortListener(signal, listener)` — best-effort: register a
508/// one-time `abort` listener if `signal` is emitter-like. `AbortSignal` is not
509/// modeled natively, so this is a no-op for plain signals. Returns a disposable
510/// placeholder object.
511fn add_abort_listener(args: &[Value]) -> Value {
512    let signal = args.first().cloned().unwrap_or(Value::Undef);
513    let listener = args.get(1).cloned().unwrap_or(Value::Undef);
514    let name = with_host(|h| h.new_str("abort"));
515    let _ = call_method(&signal, "once", vec![name, listener]);
516    with_host(|h| h.new_object(IndexMap::new()))
517}
518
519fn remove(recv: &Value, name: &str, f: Option<Value>) {
520    let Some(f) = f else { return };
521    with_host(|h| {
522        for which in ["@@on", "@@once"] {
523            if let Some(map) = named_map(h, recv, which) {
524                let arr = match h.get(&map) {
525                    Some(JsObj::Object(p)) => p.get(name).cloned(),
526                    _ => None,
527                };
528                if let Some(a) = arr {
529                    let now_empty = if let Some(JsObj::Array(items)) = h.get_mut(&a) {
530                        if let Some(pos) = items.iter().position(|x| x == &f) {
531                            items.remove(pos);
532                        }
533                        items.is_empty()
534                    } else {
535                        false
536                    };
537                    // Node drops an event key once its last listener is removed,
538                    // so `eventNames()` no longer lists it.
539                    if now_empty {
540                        if let Some(JsObj::Object(p)) = h.get_mut(&map) {
541                            p.shift_remove(name);
542                        }
543                    }
544                }
545            }
546        }
547    });
548}
549
550fn remove_all(recv: &Value, name: Option<&str>) {
551    remove_all_of(recv, "@@on", name);
552    remove_all_of(recv, "@@once", name);
553}
554
555/// Drop a whole listener list (or every list), returning what was in it so the
556/// caller can mirror the removal into the other map.
557fn remove_all_of(recv: &Value, which: &str, name: Option<&str>) -> Vec<Value> {
558    with_host(|h| {
559        let mut dropped = Vec::new();
560        if let Some(map) = named_map(h, recv, which) {
561            let arrays: Vec<Value> = match h.get(&map) {
562                Some(JsObj::Object(p)) => match name {
563                    Some(n) => p.get(n).cloned().into_iter().collect(),
564                    None => p.values().cloned().collect(),
565                },
566                _ => Vec::new(),
567            };
568            for a in arrays {
569                if let Some(JsObj::Array(items)) = h.get(&a) {
570                    dropped.extend(items.iter().cloned());
571                }
572            }
573            if let Some(JsObj::Object(p)) = h.get_mut(&map) {
574                match name {
575                    Some(n) => {
576                        p.shift_remove(n);
577                    }
578                    None => p.clear(),
579                }
580            }
581        }
582        dropped
583    })
584}