123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353 |
- // 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<String>,
- }
- 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<String>, Option<String>),
- }
- /// Stored in [`Listeners`] to be called upon when the event that stored it is triggered.
- struct Handler {
- window: Option<String>,
- callback: Box<dyn Fn(Event) + Send>,
- }
- /// Holds event handlers and pending event handlers, along with the salts associating them.
- struct InnerListeners {
- handlers: Mutex<HashMap<String, HashMap<EventHandler, Handler>>>,
- pending: Mutex<Vec<Pending>>,
- function_name: Uuid,
- listeners_object_name: Uuid,
- }
- /// A self-contained event manager.
- pub(crate) struct Listeners {
- inner: Arc<InnerListeners>,
- }
- 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<F: Fn(Event) + Send + 'static>(
- &self,
- event: String,
- window: Option<String>,
- 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<F: FnOnce(Event) + Send + 'static>(
- &self,
- event: String,
- window: Option<String>,
- 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<String>, payload: Option<String>) {
- 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<String>,
- 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
- )
- }
|