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    "eventNames",
40    "setMaxListeners",
41    "getMaxListeners",
42];
43
44pub fn instance_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
45    match method {
46        "listeners" => Ok(with_host(|h| {
47            h.new_array(listeners(recv, &arg_str(&args, 0)))
48        })),
49        // A no-op accessor pair kept for API completeness; the emitter has no cap.
50        "setMaxListeners" => Ok(recv.clone()),
51        "getMaxListeners" => Ok(Value::Float(10.0)),
52        "on" | "addListener" | "prependListener" => {
53            add(
54                recv,
55                "@@on",
56                &arg_str(&args, 0),
57                args.get(1).cloned().unwrap_or(Value::Undef),
58            );
59            Ok(recv.clone())
60        }
61        "once" | "prependOnceListener" => {
62            add(
63                recv,
64                "@@once",
65                &arg_str(&args, 0),
66                args.get(1).cloned().unwrap_or(Value::Undef),
67            );
68            Ok(recv.clone())
69        }
70        "emit" => emit(
71            recv,
72            &arg_str(&args, 0),
73            &args.get(1..).map(|s| s.to_vec()).unwrap_or_default(),
74        ),
75        "removeListener" | "off" => {
76            remove(recv, &arg_str(&args, 0), args.get(1).cloned());
77            Ok(recv.clone())
78        }
79        "removeAllListeners" => {
80            let name = if args.is_empty() {
81                None
82            } else {
83                Some(arg_str(&args, 0))
84            };
85            remove_all(recv, name.as_deref());
86            Ok(recv.clone())
87        }
88        "listenerCount" => Ok(Value::Float(
89            listeners(recv, &arg_str(&args, 0)).len() as f64
90        )),
91        "eventNames" => Ok(with_host(|h| {
92            let mut keys: Vec<String> = Vec::new();
93            for map in ["@@on", "@@once"] {
94                if let Some(JsObj::Object(p)) = named_map(h, recv, map).and_then(|v| h.get(&v)) {
95                    keys.extend(p.keys().cloned());
96                }
97            }
98            let names: Vec<Value> = keys.into_iter().map(|k| h.new_str(k)).collect();
99            h.new_array(names)
100        })),
101        _ => Err(crate::host::type_error(&format!(
102            "emitter.{method} is not a function"
103        ))),
104    }
105}
106
107/// Read a hidden emitter field (`@@on`/`@@once`). Works for a plain emitter
108/// object AND for a function/class receiver (express's `app` is a function whose
109/// emitter maps live in the fn-prop side table).
110fn named_map(h: &crate::host::JsHost, recv: &Value, which: &str) -> Option<Value> {
111    match h.get(recv) {
112        Some(JsObj::Object(p)) => p.get(which).cloned(),
113        Some(JsObj::Func(_)) | Some(JsObj::Class(_)) => h.fn_prop(recv, which),
114        _ => None,
115    }
116}
117
118/// Store a hidden emitter field, routing to props or the fn-prop table.
119fn set_named_map(h: &mut crate::host::JsHost, recv: &Value, which: &str, val: Value) {
120    match h.get(recv) {
121        Some(JsObj::Func(_)) | Some(JsObj::Class(_)) => h.set_fn_prop(recv, which, val),
122        _ => {
123            if let Some(JsObj::Object(p)) = h.get_mut(recv) {
124                p.insert(which.to_string(), val);
125            }
126        }
127    }
128}
129
130fn add(recv: &Value, which: &str, name: &str, f: Value) {
131    with_host(|h| {
132        // Lazily create the listener map (a mixed-in function emitter has none).
133        let map = match named_map(h, recv, which) {
134            Some(m) => m,
135            None => {
136                let m = h.new_object(IndexMap::new());
137                set_named_map(h, recv, which, m.clone());
138                m
139            }
140        };
141        // Ensure `map[name]` is an array, then push.
142        let arr = match h.get(&map) {
143            Some(JsObj::Object(p)) => p.get(name).cloned(),
144            _ => None,
145        };
146        let arr = match arr {
147            Some(a) if matches!(h.get(&a), Some(JsObj::Array(_))) => a,
148            _ => {
149                let a = h.new_array(Vec::new());
150                if let Some(JsObj::Object(p)) = h.get_mut(&map) {
151                    p.insert(name.to_string(), a.clone());
152                }
153                a
154            }
155        };
156        if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
157            items.push(f);
158        }
159    });
160}
161
162fn listeners(recv: &Value, name: &str) -> Vec<Value> {
163    with_host(|h| {
164        let mut out = Vec::new();
165        for which in ["@@on", "@@once"] {
166            if let Some(map) = named_map(h, recv, which) {
167                if let Some(JsObj::Object(p)) = h.get(&map) {
168                    if let Some(a) = p.get(name) {
169                        if let Some(JsObj::Array(items)) = h.get(a) {
170                            out.extend(items.iter().cloned());
171                        }
172                    }
173                }
174            }
175        }
176        out
177    })
178}
179
180fn emit(recv: &Value, name: &str, args: &[Value]) -> Result<Value, String> {
181    let to_call = listeners(recv, name);
182    // Once-listeners fire a single time: clear them before invoking.
183    remove_all_of(recv, "@@once", Some(name));
184    let had = !to_call.is_empty();
185    for f in to_call {
186        invoke(&f, args.to_vec(), Some(recv.clone()))?;
187    }
188    // Settle any `events.once(emitter, name)` promise waiters for this event.
189    resolve_waiters(recv, name, args);
190    Ok(Value::Bool(had))
191}
192
193// ── `events.once` promise waiters ───────────────────────────────────────────
194//
195// `once(emitter, name)` returns a real Promise. We cannot register a Rust
196// closure as a JS listener (listeners must be callable Values), so instead a
197// pending promise is parked under the emitter's hidden `@@waiters` map keyed by
198// event name; `emit` (above) settles them. On `error`, waiters of every other
199// event reject with the error, mirroring Node's `once` semantics.
200
201/// Park `promise` to be resolved when `name` next fires on `recv`.
202fn add_waiter(recv: &Value, name: &str, promise: Value) {
203    with_host(|h| {
204        let map = match waiter_map(h, recv) {
205            Some(m) => m,
206            None => {
207                let m = h.new_object(IndexMap::new());
208                if let Some(JsObj::Object(p)) = h.get_mut(recv) {
209                    p.insert("@@waiters".into(), m.clone());
210                }
211                m
212            }
213        };
214        let existing = match h.get(&map) {
215            Some(JsObj::Object(p)) => p.get(name).cloned(),
216            _ => None,
217        };
218        let arr = match existing {
219            Some(a) if matches!(h.get(&a), Some(JsObj::Array(_))) => a,
220            _ => {
221                let a = h.new_array(Vec::new());
222                if let Some(JsObj::Object(p)) = h.get_mut(&map) {
223                    p.insert(name.to_string(), a.clone());
224                }
225                a
226            }
227        };
228        if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
229            items.push(promise);
230        }
231    });
232}
233
234fn waiter_map(h: &crate::host::JsHost, recv: &Value) -> Option<Value> {
235    match h.get(recv) {
236        Some(JsObj::Object(p)) => p.get("@@waiters").cloned(),
237        _ => None,
238    }
239}
240
241/// Remove and return the promises parked on `name`.
242fn take_waiters(recv: &Value, name: &str) -> Vec<Value> {
243    with_host(|h| {
244        let Some(map) = waiter_map(h, recv) else {
245            return Vec::new();
246        };
247        let arr = match h.get_mut(&map) {
248            Some(JsObj::Object(p)) => p.shift_remove(name),
249            _ => None,
250        };
251        let Some(arr) = arr else { return Vec::new() };
252        match h.get(&arr) {
253            Some(JsObj::Array(items)) => items.clone(),
254            _ => Vec::new(),
255        }
256    })
257}
258
259/// Remove and return every parked promise except those on `keep`.
260fn take_waiters_except(recv: &Value, keep: &str) -> Vec<Value> {
261    with_host(|h| {
262        let Some(map) = waiter_map(h, recv) else {
263            return Vec::new();
264        };
265        let keys: Vec<String> = match h.get(&map) {
266            Some(JsObj::Object(p)) => p.keys().filter(|k| k.as_str() != keep).cloned().collect(),
267            _ => Vec::new(),
268        };
269        let mut out = Vec::new();
270        for k in keys {
271            let arr = match h.get_mut(&map) {
272                Some(JsObj::Object(p)) => p.shift_remove(&k),
273                _ => None,
274            };
275            if let Some(arr) = arr {
276                if let Some(JsObj::Array(items)) = h.get(&arr) {
277                    out.extend(items.iter().cloned());
278                }
279            }
280        }
281        out
282    })
283}
284
285fn resolve_waiters(recv: &Value, name: &str, args: &[Value]) {
286    let waiting = take_waiters(recv, name);
287    if !waiting.is_empty() {
288        let arr = with_host(|h| h.new_array(args.to_vec()));
289        for p in &waiting {
290            if let Some(id) = with_host(|h| h.promise_id(p)) {
291                crate::host::resolve_promise_val(id, arr.clone());
292            }
293        }
294    }
295    if name == "error" {
296        let err = args.first().cloned().unwrap_or(Value::Undef);
297        for p in take_waiters_except(recv, "error") {
298            if let Some(id) = with_host(|h| h.promise_id(&p)) {
299                crate::host::reject_promise_val(id, err.clone());
300            }
301        }
302    }
303}
304
305// ── static module functions (`require('events').once`, `.listenerCount`, …) ──
306
307/// Static functions on the `events` module namespace. `EventEmitter` (the
308/// self-ref ctor) and `EventEmitterAsyncResource` are handled by the parent;
309/// `on` (async iterator) is deferred (see module docs / final report).
310pub const STATIC_METHODS: &[&str] = &[
311    "once",
312    "listenerCount",
313    "getEventListeners",
314    "getMaxListeners",
315    "setMaxListeners",
316    "addAbortListener",
317    "init",
318];
319
320/// Dispatch a static `events.<method>(...)`. Returns `None` for names this
321/// module does not own (e.g. `EventEmitter`) so the parent's specific arm wins.
322pub fn static_call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
323    let emitter = args.first().cloned().unwrap_or(Value::Undef);
324    Some(match method {
325        "once" => Ok(once_static(emitter, &arg_str(args, 1))),
326        "listenerCount" => Ok(Value::Float(
327            listeners(&emitter, &arg_str(args, 1)).len() as f64
328        )),
329        "getEventListeners" => Ok(with_host(|h| {
330            h.new_array(listeners(&emitter, &arg_str(args, 1)))
331        })),
332        // No per-emitter cap is tracked; report Node's default and accept sets.
333        "getMaxListeners" => Ok(Value::Float(10.0)),
334        "setMaxListeners" => Ok(Value::Undef),
335        "addAbortListener" => Ok(add_abort_listener(args)),
336        "init" => Ok(init_emitter(emitter)),
337        _ => return None,
338    })
339}
340
341/// `events.once(emitter, name)` → a Promise resolving with the event args (or
342/// rejecting with the error if `error` fires first).
343fn once_static(emitter: Value, name: &str) -> Value {
344    let p = with_host(|h| h.new_promise());
345    add_waiter(&emitter, name, p.clone());
346    p
347}
348
349/// `EventEmitter.init(emitter)` — ensure the hidden emitter maps exist on
350/// `emitter` (used when mixing the emitter surface into a plain object).
351fn init_emitter(emitter: Value) -> Value {
352    with_host(|h| {
353        let has = matches!(h.get(&emitter), Some(JsObj::Object(p)) if p.contains_key("@@on"));
354        if !has {
355            let on = h.new_object(IndexMap::new());
356            let once = h.new_object(IndexMap::new());
357            let native = h.new_str("EventEmitter");
358            if let Some(JsObj::Object(p)) = h.get_mut(&emitter) {
359                p.entry("@@native".to_string()).or_insert(native);
360                p.insert("@@on".to_string(), on);
361                p.insert("@@once".to_string(), once);
362            }
363        }
364    });
365    emitter
366}
367
368/// `events.addAbortListener(signal, listener)` — best-effort: register a
369/// one-time `abort` listener if `signal` is emitter-like. `AbortSignal` is not
370/// modeled natively, so this is a no-op for plain signals. Returns a disposable
371/// placeholder object.
372fn add_abort_listener(args: &[Value]) -> Value {
373    let signal = args.first().cloned().unwrap_or(Value::Undef);
374    let listener = args.get(1).cloned().unwrap_or(Value::Undef);
375    let name = with_host(|h| h.new_str("abort"));
376    let _ = call_method(&signal, "once", vec![name, listener]);
377    with_host(|h| h.new_object(IndexMap::new()))
378}
379
380fn remove(recv: &Value, name: &str, f: Option<Value>) {
381    let Some(f) = f else { return };
382    with_host(|h| {
383        for which in ["@@on", "@@once"] {
384            if let Some(map) = named_map(h, recv, which) {
385                let arr = match h.get(&map) {
386                    Some(JsObj::Object(p)) => p.get(name).cloned(),
387                    _ => None,
388                };
389                if let Some(a) = arr {
390                    let now_empty = if let Some(JsObj::Array(items)) = h.get_mut(&a) {
391                        if let Some(pos) = items.iter().position(|x| x == &f) {
392                            items.remove(pos);
393                        }
394                        items.is_empty()
395                    } else {
396                        false
397                    };
398                    // Node drops an event key once its last listener is removed,
399                    // so `eventNames()` no longer lists it.
400                    if now_empty {
401                        if let Some(JsObj::Object(p)) = h.get_mut(&map) {
402                            p.shift_remove(name);
403                        }
404                    }
405                }
406            }
407        }
408    });
409}
410
411fn remove_all(recv: &Value, name: Option<&str>) {
412    remove_all_of(recv, "@@on", name);
413    remove_all_of(recv, "@@once", name);
414}
415
416fn remove_all_of(recv: &Value, which: &str, name: Option<&str>) {
417    with_host(|h| {
418        if let Some(map) = named_map(h, recv, which) {
419            if let Some(JsObj::Object(p)) = h.get_mut(&map) {
420                match name {
421                    Some(n) => {
422                        p.shift_remove(n);
423                    }
424                    None => p.clear(),
425                }
426            }
427        }
428    });
429}