Skip to main content

ferrijs_std/events/
mod.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3#![allow(
4    clippy::mutable_key_type,
5    clippy::for_kv_map,
6    clippy::new_without_default
7)]
8use std::{
9    rc::Rc,
10    sync::{Arc, RwLock},
11};
12
13use crate::utils::{
14    error::ErrorExtensions, module::ModuleInfo, object::ObjectExt, result::ResultExt,
15};
16use rquickjs::{
17    class::{JsClass, Trace, Tracer},
18    module::{Declarations, Exports, ModuleDef},
19    prelude::{Func, Opt, Rest, This},
20    CatchResultExt, Class, Ctx, Function, JsLifetime, Object, Result, String as JsString, Symbol,
21    Value,
22};
23use tracing::trace;
24
25use self::{custom_event::CustomEvent, event::Event, event_target::EventTarget};
26
27pub mod custom_event;
28pub mod event;
29pub mod event_target;
30
31#[derive(Clone, Debug)]
32pub enum EventKey<'js> {
33    Symbol(Symbol<'js>),
34    String(Rc<str>),
35}
36
37impl<'js> EventKey<'js> {
38    fn from_value(ctx: &Ctx, value: Value<'js>) -> Result<Self> {
39        if value.is_string() {
40            let key: String = value.get()?;
41            Ok(EventKey::String(key.into()))
42        } else {
43            let sym = value.into_symbol().ok_or("Not a symbol").or_throw(ctx)?;
44            Ok(EventKey::Symbol(sym))
45        }
46    }
47}
48
49impl Eq for EventKey<'_> {}
50
51impl PartialEq for EventKey<'_> {
52    fn eq(&self, other: &Self) -> bool {
53        match (self, other) {
54            (EventKey::Symbol(symbol1), EventKey::Symbol(symbol2)) => symbol1 == symbol2,
55            (EventKey::String(str1), EventKey::String(str2)) => str1 == str2,
56            _ => false,
57        }
58    }
59}
60
61pub struct EventItem<'js> {
62    callback: Function<'js>,
63    once: bool,
64}
65
66pub type EventList<'js> = Vec<(EventKey<'js>, Vec<EventItem<'js>>)>;
67pub type Events<'js> = Arc<RwLock<EventList<'js>>>;
68
69/// Get the hidden symbol used to store the event list on JS objects.
70fn events_symbol<'js>(ctx: &Ctx<'js>) -> Result<Symbol<'js>> {
71    Symbol::new_global(ctx.clone(), "__ee")
72}
73
74/// Convert a Class into an Object for use with Emitter methods.
75fn class_to_obj<'js, C: JsClass<'js>>(class: Class<'js, C>) -> Result<Object<'js>> {
76    Object::from_value(class.into_value())
77}
78
79/// Resolve the event list from a JS object. For native Emitter classes,
80/// reads from the native struct. For plain JS objects (e.g. stream.js Readable),
81/// lazily creates and stores a native EventEmitter as a hidden property.
82#[allow(clippy::arc_with_non_send_sync)]
83pub fn resolve_events<'js>(ctx: &Ctx<'js>, obj: &Object<'js>) -> Result<Events<'js>> {
84    // Try native EventEmitter first
85    if let Some(class) = Class::<EventEmitter>::from_object(obj) {
86        return Ok(class.borrow().events.clone());
87    }
88    let sym = events_symbol(ctx)?;
89    // Check for hidden property
90    if let Some(ee) = obj.get::<_, Option<Class<'js, EventEmitter<'js>>>>(sym.clone())? {
91        return Ok(ee.borrow().events.clone());
92    }
93    // Create and store a new one
94    let events: Events<'js> = Arc::new(RwLock::new(Vec::new()));
95    let ee = Class::instance(
96        ctx.clone(),
97        EventEmitter {
98            events: events.clone(),
99        },
100    )?;
101    obj.set(sym, ee)?;
102    Ok(events)
103}
104
105#[rquickjs::class]
106#[derive(Clone)]
107pub struct EventEmitter<'js> {
108    pub events: Events<'js>,
109}
110
111unsafe impl<'js> JsLifetime<'js> for EventEmitter<'js> {
112    type Changed<'to> = EventEmitter<'to>;
113}
114
115impl<'js> Emitter<'js> for EventEmitter<'js> {
116    fn get_event_list(&self) -> Arc<RwLock<EventList<'js>>> {
117        self.events.clone()
118    }
119}
120
121impl<'js> Trace<'js> for EventEmitter<'js> {
122    fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
123        self.trace_event_emitter(tracer);
124    }
125}
126
127#[rquickjs::methods]
128impl<'js> EventEmitter<'js> {
129    #[qjs(constructor)]
130    pub fn new() -> Self {
131        Self {
132            #[allow(clippy::arc_with_non_send_sync)]
133            events: Arc::new(RwLock::new(Vec::new())),
134        }
135    }
136}
137
138pub trait EmitError<'js> {
139    fn emit_error<C>(self, id: &'static str, ctx: &Ctx<'js>, this: Class<'js, C>) -> Result<bool>
140    where
141        C: Emitter<'js>;
142}
143
144impl<'js, T> EmitError<'js> for Result<T> {
145    fn emit_error<C>(self, id: &'static str, ctx: &Ctx<'js>, this: Class<'js, C>) -> Result<bool>
146    where
147        C: Emitter<'js>,
148    {
149        if let Err(err) = self.catch(ctx) {
150            trace!("Error caught in: {}", id);
151            if this.borrow().has_listener_str("error") {
152                let error_value = err.into_value(ctx)?;
153                C::emit_str(this, ctx, "error", vec![error_value], false)?;
154                return Ok(true);
155            }
156            return Err(err.throw(ctx));
157        }
158        Ok(false)
159    }
160}
161
162pub trait Emitter<'js>
163where
164    Self: JsClass<'js> + Sized + 'js,
165{
166    fn get_event_list(&self) -> Arc<RwLock<EventList<'js>>>;
167
168    fn on_event_changed(&mut self, _event: EventKey<'js>, _added: bool) -> Result<()> {
169        Ok(())
170    }
171
172    /// Resolve the event list from a `this` object. For native classes,
173    /// extracts from the class data. For plain JS objects, uses the hidden property.
174    fn resolve_events_from(ctx: &Ctx<'js>, this: &Object<'js>) -> Result<Events<'js>> {
175        if let Some(class) = Class::<Self>::from_object(this) {
176            return Ok(class.borrow().get_event_list());
177        }
178        resolve_events(ctx, this)
179    }
180
181    fn add_event_emitter_prototype(ctx: &Ctx<'js>) -> Result<Object<'js>> {
182        let proto = Class::<Self>::prototype(ctx)?
183            .or_throw_msg(ctx, "Prototype for EventEmitter not found")?;
184
185        let on = Function::new(ctx.clone(), Self::on)?;
186        let off = Function::new(ctx.clone(), Self::remove_event_listener)?;
187
188        proto.set("once", Func::from(Self::once))?;
189        proto.set("on", on.clone())?;
190        proto.set("emit", Func::from(Self::emit))?;
191        proto.set("prependListener", Func::from(Self::prepend_listener))?;
192        proto.set(
193            "prependOnceListener",
194            Func::from(Self::prepend_once_listener),
195        )?;
196        proto.set("off", off.clone())?;
197        proto.set("eventNames", Func::from(Self::event_names))?;
198        proto.set("addListener", on)?;
199        proto.set("removeListener", off)?;
200        proto.set("listenerCount", Func::from(Self::listener_count))?;
201        proto.set("removeAllListeners", Func::from(Self::remove_all_listeners))?;
202
203        Ok(proto)
204    }
205
206    fn add_event_target_prototype(ctx: &Ctx<'js>) -> Result<Object<'js>> {
207        let proto = Class::<Self>::prototype(ctx)?
208            .or_throw_msg(ctx, "Prototype for EventTarget not found")?;
209
210        let on = Function::new(ctx.clone(), Self::evt_add_event_listener)?;
211        let off = Function::new(ctx.clone(), Self::remove_event_listener)?;
212
213        proto.set("dispatchEvent", Func::from(Self::evt_dispatch_event))?;
214        proto.set("addEventListener", on)?;
215        proto.set("removeEventListener", off)?;
216
217        Ok(proto)
218    }
219
220    fn trace_event_emitter<'a>(&self, tracer: Tracer<'a, 'js>) {
221        let events = self.get_event_list();
222        let events = events.read().unwrap();
223        for (key, items) in events.iter() {
224            if let EventKey::Symbol(sym) = &key {
225                tracer.mark(sym);
226            }
227
228            for item in items {
229                tracer.mark(&item.callback);
230            }
231        }
232    }
233
234    fn remove_event_listener_str(
235        this: Class<'js, Self>,
236        ctx: &Ctx<'js>,
237        event: &str,
238        listener: Function<'js>,
239    ) -> Result<Object<'js>> {
240        let event = to_event(ctx, event)?;
241        Self::remove_event_listener(This(class_to_obj(this)?), ctx.clone(), event, listener)
242    }
243
244    fn remove_event_listener(
245        this: This<Object<'js>>,
246        ctx: Ctx<'js>,
247        event: Value<'js>,
248        listener: Function<'js>,
249    ) -> Result<Object<'js>> {
250        let events = Self::resolve_events_from(&ctx, &this)?;
251        let mut events = events.write().or_throw(&ctx)?;
252
253        let key = EventKey::from_value(&ctx, event)?;
254        if let Some(index) = events.iter_mut().position(|(k, _)| k == &key) {
255            let items = &mut events[index].1;
256            if let Some(pos) = items.iter().position(|item| item.callback == listener) {
257                items.remove(pos);
258                if items.is_empty() {
259                    events.remove(index);
260                }
261            }
262        };
263
264        Ok(this.0)
265    }
266
267    fn add_event_listener_str(
268        this: Class<'js, Self>,
269        ctx: &Ctx<'js>,
270        event: &str,
271        listener: Function<'js>,
272        prepend: bool,
273        once: bool,
274    ) -> Result<Object<'js>> {
275        let event = to_event(ctx, event)?;
276        Self::add_event_listener(
277            This(class_to_obj(this)?),
278            ctx.clone(),
279            event,
280            listener,
281            prepend,
282            once,
283        )
284    }
285
286    fn once(
287        this: This<Object<'js>>,
288        ctx: Ctx<'js>,
289        event: Value<'js>,
290        listener: Function<'js>,
291    ) -> Result<Object<'js>> {
292        Self::add_event_listener(this, ctx, event, listener, false, true)
293    }
294
295    fn on(
296        this: This<Object<'js>>,
297        ctx: Ctx<'js>,
298        event: Value<'js>,
299        listener: Function<'js>,
300    ) -> Result<Object<'js>> {
301        Self::add_event_listener(this, ctx, event, listener, false, false)
302    }
303
304    fn prepend_listener(
305        this: This<Object<'js>>,
306        ctx: Ctx<'js>,
307        event: Value<'js>,
308        listener: Function<'js>,
309    ) -> Result<Object<'js>> {
310        Self::add_event_listener(this, ctx, event, listener, true, false)
311    }
312
313    fn prepend_once_listener(
314        this: This<Object<'js>>,
315        ctx: Ctx<'js>,
316        event: Value<'js>,
317        listener: Function<'js>,
318    ) -> Result<Object<'js>> {
319        Self::add_event_listener(this, ctx, event, listener, true, true)
320    }
321
322    fn evt_add_event_listener(
323        this: This<Object<'js>>,
324        ctx: Ctx<'js>,
325        event: Value<'js>,
326        listener: Function<'js>,
327        options: Opt<Object<'js>>,
328    ) -> Result<Object<'js>> {
329        let mut once = false;
330        if let Some(opt) = options.0 {
331            if let Some(once_opt) = opt.get("once")? {
332                once = once_opt;
333            }
334        }
335        Self::add_event_listener(this, ctx, event, listener, false, once)
336    }
337
338    fn add_event_listener(
339        this: This<Object<'js>>,
340        ctx: Ctx<'js>,
341        event: Value<'js>,
342        listener: Function<'js>,
343        prepend: bool,
344        once: bool,
345    ) -> Result<Object<'js>> {
346        let events = Self::resolve_events_from(&ctx, &this)?;
347        let mut events = events.write().or_throw(&ctx)?;
348        let key = EventKey::from_value(&ctx, event)?;
349        let mut is_new = false;
350
351        let items = match events.iter_mut().find(|(k, _)| k == &key) {
352            Some((_, entry_items)) => entry_items,
353            None => {
354                is_new = true;
355                events.push((key.clone(), Vec::new()));
356                &mut events.last_mut().unwrap().1
357            },
358        };
359
360        let item = EventItem {
361            callback: listener,
362            once,
363        };
364        if !prepend {
365            items.push(item);
366        } else {
367            items.insert(0, item);
368        }
369        if is_new {
370            if let Some(class) = Class::<Self>::from_object(&this.0) {
371                class.borrow_mut().on_event_changed(key, true)?;
372            }
373        }
374        Ok(this.0)
375    }
376
377    fn has_listener_str(&self, event: &str) -> bool {
378        let key = EventKey::String(event.into());
379        has_key(self.get_event_list(), key)
380    }
381
382    #[allow(dead_code)]
383    fn has_listener(&self, ctx: Ctx<'js>, event: Value<'js>) -> Result<bool> {
384        let key = EventKey::from_value(&ctx, event)?;
385        Ok(has_key(self.get_event_list(), key))
386    }
387
388    #[allow(dead_code)]
389    fn get_listeners(&self, ctx: &Ctx<'js>, event: Value<'js>) -> Result<Vec<Function<'js>>> {
390        let key = EventKey::from_value(ctx, event)?;
391        Ok(find_all_listeners(self.get_event_list(), key))
392    }
393
394    fn get_listeners_str(&self, event: &str) -> Vec<Function<'js>> {
395        let key = EventKey::String(event.into());
396        find_all_listeners(self.get_event_list(), key)
397    }
398
399    fn do_emit(
400        event: Value<'js>,
401        this: This<Object<'js>>,
402        ctx: &Ctx<'js>,
403        args: Rest<Value<'js>>,
404        defer: bool,
405    ) -> Result<bool> {
406        let events = Self::resolve_events_from(ctx, &this)?;
407        let mut events = events.write().or_throw(ctx)?;
408        let key = EventKey::from_value(ctx, event)?;
409
410        if let Some(index) = events.iter_mut().position(|(k, _)| k == &key) {
411            let items = &mut events[index].1;
412            let mut callbacks = Vec::with_capacity(items.len());
413            items.retain(|item: &EventItem<'_>| {
414                callbacks.push(item.callback.clone());
415                !item.once
416            });
417            if items.is_empty() {
418                events.remove(index);
419                if let Some(class) = Class::<Self>::from_object(&this.0) {
420                    class.borrow_mut().on_event_changed(key, false)?;
421                }
422            }
423            drop(events);
424            for callback in callbacks {
425                let args: Vec<Value<'js>> = args.iter().map(|arg| arg.to_owned()).collect();
426                let args = Rest(args);
427                let this_val = This(this.0.clone().into_value());
428                if defer {
429                    callback.defer((this_val, args))?;
430                } else {
431                    callback.call::<_, ()>((this_val, args))?;
432                }
433            }
434            Ok(true)
435        } else {
436            Ok(false)
437        }
438    }
439
440    fn emit_str(
441        this: Class<'js, Self>,
442        ctx: &Ctx<'js>,
443        event: &str,
444        args: Vec<Value<'js>>,
445        defer: bool,
446    ) -> Result<()> {
447        let event = to_event(ctx, event)?;
448        Self::do_emit(event, This(class_to_obj(this)?), ctx, args.into(), defer)?;
449        Ok(())
450    }
451
452    fn emit(
453        this: This<Object<'js>>,
454        ctx: Ctx<'js>,
455        event: Value<'js>,
456        args: Rest<Value<'js>>,
457    ) -> Result<bool> {
458        Self::do_emit(event, this, &ctx, args, false)
459    }
460
461    fn evt_dispatch_event(
462        this: This<Object<'js>>,
463        ctx: Ctx<'js>,
464        event: Value<'js>,
465    ) -> Result<bool> {
466        let event_type = event.get_optional("type")?.unwrap();
467        Self::do_emit(event_type, this, &ctx, Rest(vec![event]), false)
468    }
469
470    fn event_names(this: This<Object<'js>>, ctx: Ctx<'js>) -> Result<Vec<Value<'js>>> {
471        let events = Self::resolve_events_from(&ctx, &this)?;
472        let events = events.read().or_throw(&ctx)?;
473
474        let mut names = Vec::with_capacity(events.len());
475        for (key, _entry) in events.iter() {
476            let value = match key {
477                EventKey::Symbol(symbol) => symbol.clone().into_value(),
478                EventKey::String(str) => JsString::from_str(ctx.clone(), str)?.into(),
479            };
480
481            names.push(value)
482        }
483
484        Ok(names)
485    }
486
487    fn listener_count(this: This<Object<'js>>, ctx: Ctx<'js>, event: Value<'js>) -> Result<usize> {
488        let events = Self::resolve_events_from(&ctx, &this)?;
489        let key = EventKey::from_value(&ctx, event)?;
490        let events = events.read().or_throw(&ctx)?;
491        Ok(events
492            .iter()
493            .find(|(k, _)| k == &key)
494            .map_or(0, |(_, items)| items.len()))
495    }
496
497    fn remove_all_listeners(
498        this: This<Object<'js>>,
499        ctx: Ctx<'js>,
500        event: Opt<Value<'js>>,
501    ) -> Result<Object<'js>> {
502        let events = Self::resolve_events_from(&ctx, &this)?;
503        let mut events = events.write().or_throw(&ctx)?;
504        match event.0 {
505            Some(event) if !event.is_undefined() => {
506                let key = EventKey::from_value(&ctx, event)?;
507                events.retain(|(k, _)| k != &key);
508            },
509            _ => events.clear(),
510        }
511        Ok(this.0)
512    }
513}
514
515fn find_all_listeners<'js>(
516    events: Arc<RwLock<EventList<'js>>>,
517    key: EventKey<'js>,
518) -> Vec<Function<'js>> {
519    let events = events.read().unwrap();
520    let items = events.iter().find(|(k, _)| k == &key);
521    if let Some((_, callbacks)) = items {
522        callbacks.iter().map(|item| item.callback.clone()).collect()
523    } else {
524        vec![]
525    }
526}
527
528fn has_key<'js>(event_list: Arc<RwLock<EventList<'js>>>, key: EventKey<'js>) -> bool {
529    event_list.read().unwrap().iter().any(|(k, _)| k == &key)
530}
531
532fn to_event<'js>(ctx: &Ctx<'js>, event: &str) -> Result<Value<'js>> {
533    let event = JsString::from_str(ctx.clone(), event)?;
534    Ok(event.into_value())
535}
536
537pub struct EventsModule;
538
539impl ModuleDef for EventsModule {
540    fn declare(declare: &Declarations) -> Result<()> {
541        declare.declare(stringify!(EventEmitter))?;
542        declare.declare("default")?;
543
544        Ok(())
545    }
546
547    fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> {
548        let ctor = Class::<EventEmitter>::create_constructor(ctx)?
549            .expect("Can't create EventEmitter constructor");
550        ctor.set(stringify!(EventEmitter), ctor.clone())?;
551        exports.export(stringify!(EventEmitter), ctor.clone())?;
552        exports.export("default", ctor)?;
553
554        EventEmitter::add_event_emitter_prototype(ctx)?;
555
556        Ok(())
557    }
558}
559
560impl From<EventsModule> for ModuleInfo<EventsModule> {
561    fn from(val: EventsModule) -> Self {
562        ModuleInfo {
563            name: "events",
564            module: val,
565        }
566    }
567}
568
569pub fn init(ctx: &Ctx<'_>) -> Result<()> {
570    let globals = ctx.globals();
571
572    Class::<EventTarget>::define(&globals)?;
573    Class::<CustomEvent>::define(&globals)?;
574    Class::<Event>::define(&globals)?;
575
576    EventTarget::add_event_target_prototype(ctx)?;
577
578    Ok(())
579}