// Copyright 2019-2021 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT use std::{ boxed::Box, cell::Cell, collections::HashMap, fmt, hash::Hash, sync::{Arc, Mutex}, }; use uuid::Uuid; /// Checks if an event name is valid. pub fn is_event_name_valid(event: &str) -> bool { event .chars() .all(|c| c.is_alphanumeric() || c == '-' || c == '/' || c == ':' || c == '_') } pub fn assert_event_name_is_valid(event: &str) { assert!( is_event_name_valid(event), "Event name must include only alphanumeric characters, `-`, `/`, `:` and `_`." ); } /// Represents an event handler. #[derive(Debug, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct EventHandler(Uuid); impl fmt::Display for EventHandler { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } /// An event that was triggered. #[derive(Debug, Clone)] pub struct Event { id: EventHandler, data: Option, } impl Event { /// The [`EventHandler`] that was triggered. pub fn id(&self) -> EventHandler { self.id } /// The event payload. pub fn payload(&self) -> Option<&str> { self.data.as_deref() } } /// What to do with the pending handler when resolving it? enum Pending { Unlisten(EventHandler), Listen(EventHandler, String, Handler), Trigger(String, Option, Option), } /// Stored in [`Listeners`] to be called upon when the event that stored it is triggered. struct Handler { window: Option, callback: Box, } /// Holds event handlers and pending event handlers, along with the salts associating them. struct InnerListeners { handlers: Mutex>>, pending: Mutex>, function_name: Uuid, listeners_object_name: Uuid, } /// A self-contained event manager. pub(crate) struct Listeners { inner: Arc, } impl Default for Listeners { fn default() -> Self { Self { inner: Arc::new(InnerListeners { handlers: Mutex::default(), pending: Mutex::default(), function_name: Uuid::new_v4(), listeners_object_name: Uuid::new_v4(), }), } } } impl Clone for Listeners { fn clone(&self) -> Self { Self { inner: self.inner.clone(), } } } impl Listeners { /// Randomly generated function name to represent the JavaScript event function. pub(crate) fn function_name(&self) -> String { self.inner.function_name.to_string() } /// Randomly generated listener object name to represent the JavaScript event listener object. pub(crate) fn listeners_object_name(&self) -> String { self.inner.listeners_object_name.to_string() } /// Insert a pending event action to the queue. fn insert_pending(&self, action: Pending) { self .inner .pending .lock() .expect("poisoned pending event queue") .push(action) } /// Finish all pending event actions. fn flush_pending(&self) { let pending = { let mut lock = self .inner .pending .lock() .expect("poisoned pending event queue"); std::mem::take(&mut *lock) }; for action in pending { match action { Pending::Unlisten(id) => self.unlisten(id), Pending::Listen(id, event, handler) => self.listen_(id, event, handler), Pending::Trigger(ref event, window, payload) => self.trigger(event, window, payload), } } } fn listen_(&self, id: EventHandler, event: String, handler: Handler) { match self.inner.handlers.try_lock() { Err(_) => self.insert_pending(Pending::Listen(id, event, handler)), Ok(mut lock) => { lock.entry(event).or_default().insert(id, handler); } } } /// Adds an event listener for JS events. pub(crate) fn listen( &self, event: String, window: Option, handler: F, ) -> EventHandler { let id = EventHandler(Uuid::new_v4()); let handler = Handler { window, callback: Box::new(handler), }; self.listen_(id, event, handler); id } /// Listen to a JS event and immediately unlisten. pub(crate) fn once( &self, event: String, window: Option, handler: F, ) -> EventHandler { let self_ = self.clone(); let handler = Cell::new(Some(handler)); self.listen(event, window, move |event| { self_.unlisten(event.id); let handler = handler .take() .expect("attempted to call handler more than once"); handler(event) }) } /// Removes an event listener. pub(crate) fn unlisten(&self, handler_id: EventHandler) { match self.inner.handlers.try_lock() { Err(_) => self.insert_pending(Pending::Unlisten(handler_id)), Ok(mut lock) => lock.values_mut().for_each(|handler| { handler.remove(&handler_id); }), } } /// Triggers the given global event with its payload. pub(crate) fn trigger(&self, event: &str, window: Option, payload: Option) { let mut maybe_pending = false; match self.inner.handlers.try_lock() { Err(_) => self.insert_pending(Pending::Trigger(event.to_owned(), window, payload)), Ok(lock) => { if let Some(handlers) = lock.get(event) { for (&id, handler) in handlers { if handler.window.is_none() || window == handler.window { maybe_pending = true; (handler.callback)(self::Event { id, data: payload.clone(), }) } } } } } if maybe_pending { self.flush_pending(); } } } #[cfg(test)] mod test { use super::*; use proptest::prelude::*; // dummy event handler function fn event_fn(s: Event) { println!("{:?}", s); } proptest! { #![proptest_config(ProptestConfig::with_cases(10000))] // check to see if listen() is properly passing keys into the LISTENERS map #[test] fn listeners_check_key(e in "[a-z]+") { let listeners: Listeners = Default::default(); // clone e as the key let key = e.clone(); // pass e and an dummy func into listen listeners.listen(e, None, event_fn); // lock mutex let l = listeners.inner.handlers.lock().unwrap(); // check if the generated key is in the map assert!(l.contains_key(&key)); } // check to see if listen inputs a handler function properly into the LISTENERS map. #[test] fn listeners_check_fn(e in "[a-z]+") { let listeners: Listeners = Default::default(); // clone e as the key let key = e.clone(); // pass e and an dummy func into listen listeners.listen(e, None, event_fn); // lock mutex let mut l = listeners.inner.handlers.lock().unwrap(); // check if l contains key if l.contains_key(&key) { // grab key if it exists let handler = l.get_mut(&key); // check to see if we get back a handler or not match handler { // pass on Some(handler) Some(_) => {}, // Fail on None None => panic!("handler is None") } } } // check to see if on_event properly grabs the stored function from listen. #[test] fn check_on_event(e in "[a-z]+", d in "[a-z]+") { let listeners: Listeners = Default::default(); // clone e as the key let key = e.clone(); // call listen with e and the event_fn dummy func listeners.listen(e.clone(), None, event_fn); // call on event with e and d. listeners.trigger(&e, None, Some(d)); // lock the mutex let l = listeners.inner.handlers.lock().unwrap(); // assert that the key is contained in the listeners map assert!(l.contains_key(&key)); } } } pub fn unlisten_js(listeners_object_name: String, event_name: String, event_id: u64) -> String { format!( " (function () {{ const listeners = (window['{listeners}'] || {{}})['{event_name}'] if (listeners) {{ const index = window['{listeners}']['{event_name}'].findIndex(e => e.id === {event_id}) if (index > -1) {{ window['{listeners}']['{event_name}'].splice(index, 1) }} }} }})() ", listeners = listeners_object_name, event_name = event_name, event_id = event_id, ) } pub fn listen_js( listeners_object_name: String, event: String, event_id: u64, window_label: Option, handler: String, ) -> String { format!( "if (window['{listeners}'] === void 0) {{ Object.defineProperty(window, '{listeners}', {{ value: Object.create(null) }}); }} if (window['{listeners}'][{event}] === void 0) {{ Object.defineProperty(window['{listeners}'], {event}, {{ value: [] }}); }} window['{listeners}'][{event}].push({{ id: {event_id}, windowLabel: {window_label}, handler: {handler} }}); ", listeners = listeners_object_name, event = event, event_id = event_id, window_label = if let Some(l) = window_label { crate::runtime::window::assert_label_is_valid(&l); format!("'{}'", l) } else { "null".to_owned() }, handler = handler ) }