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