From c15b285d0842e68a5033067161e25a3cfc2438a5 Mon Sep 17 00:00:00 2001 From: c Date: Mon, 13 Jan 2025 10:47:56 +0100 Subject: [PATCH 01/29] refactor: move fn assert_event_name_is_valid() to the only module using it --- crates/tauri/src/event/mod.rs | 7 ------- crates/tauri/src/manager/mod.rs | 9 ++++++++- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/tauri/src/event/mod.rs b/crates/tauri/src/event/mod.rs index 5338bfeb32fb..1dc2b94e325a 100644 --- a/crates/tauri/src/event/mod.rs +++ b/crates/tauri/src/event/mod.rs @@ -16,13 +16,6 @@ pub fn is_event_name_valid(event: &str) -> bool { .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 `_`." - ); -} - /// Unique id of an event. pub type EventId = u32; diff --git a/crates/tauri/src/manager/mod.rs b/crates/tauri/src/manager/mod.rs index 440ed3a28170..3db36992a377 100644 --- a/crates/tauri/src/manager/mod.rs +++ b/crates/tauri/src/manager/mod.rs @@ -24,7 +24,7 @@ use crate::{ AppHandle, ChannelInterceptor, GlobalWebviewEventListener, GlobalWindowEventListener, OnPageLoad, }, - event::{assert_event_name_is_valid, Event, EventId, EventTarget, Listeners}, + event::{is_event_name_valid, Event, EventId, EventTarget, Listeners}, ipc::{Invoke, InvokeHandler, RuntimeAuthority}, plugin::PluginStore, utils::{config::Config, PackageInfo}, @@ -39,6 +39,13 @@ mod tray; pub mod webview; pub mod window; +fn assert_event_name_is_valid(event: &str) { + assert!( + is_event_name_valid(event), + "Event name must include only alphanumeric characters, `-`, `/`, `:` and `_`." + ); +} + #[derive(Default)] /// Spaced and quoted Content-Security-Policy hash values. struct CspHashStrings { From 93e27d7a4ed34129e274b7e5bd69b5b1009fb560 Mon Sep 17 00:00:00 2001 From: c Date: Mon, 13 Jan 2025 11:20:18 +0100 Subject: [PATCH 02/29] fix: use macro so that panic points to the precise source location --- crates/tauri/src/manager/mod.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/crates/tauri/src/manager/mod.rs b/crates/tauri/src/manager/mod.rs index 3db36992a377..774e77431987 100644 --- a/crates/tauri/src/manager/mod.rs +++ b/crates/tauri/src/manager/mod.rs @@ -39,11 +39,13 @@ mod tray; pub mod webview; pub mod window; -fn assert_event_name_is_valid(event: &str) { - assert!( - is_event_name_valid(event), - "Event name must include only alphanumeric characters, `-`, `/`, `:` and `_`." - ); +macro_rules! assert_event_name_is_valid { + {$event:expr} => { + assert!( + is_event_name_valid($event), + "Event name must include only alphanumeric characters, `-`, `/`, `:` and `_`." + ); + } } #[derive(Default)] @@ -520,7 +522,7 @@ impl AppManager { target: EventTarget, handler: F, ) -> EventId { - assert_event_name_is_valid(&event); + assert_event_name_is_valid!(&event); self.listeners().listen(event, target, handler) } @@ -530,7 +532,7 @@ impl AppManager { target: EventTarget, handler: F, ) -> EventId { - assert_event_name_is_valid(&event); + assert_event_name_is_valid!(&event); self.listeners().once(event, target, handler) } @@ -543,7 +545,7 @@ impl AppManager { tracing::instrument("app::emit", skip(self, payload)) )] pub fn emit(&self, event: &str, payload: S) -> crate::Result<()> { - assert_event_name_is_valid(event); + assert_event_name_is_valid!(event); #[cfg(feature = "tracing")] let _span = tracing::debug_span!("emit::run").entered(); @@ -572,7 +574,7 @@ impl AppManager { S: Serialize + Clone, F: Fn(&EventTarget) -> bool, { - assert_event_name_is_valid(event); + assert_event_name_is_valid!(event); #[cfg(feature = "tracing")] let _span = tracing::debug_span!("emit::run").entered(); From 76236b397f13ad2c81292e9d8989de75fce57b08 Mon Sep 17 00:00:00 2001 From: c Date: Sun, 19 Jan 2025 13:22:46 +0100 Subject: [PATCH 03/29] refactor: default trait methods on Emitter trait to reduce boilerplate --- crates/tauri/src/app.rs | 79 +------------------ crates/tauri/src/lib.rs | 14 +++- crates/tauri/src/webview/mod.rs | 89 +--------------------- crates/tauri/src/webview/webview_window.rs | 75 +----------------- crates/tauri/src/window/mod.rs | 89 +--------------------- 5 files changed, 17 insertions(+), 329 deletions(-) diff --git a/crates/tauri/src/app.rs b/crates/tauri/src/app.rs index 3881e336dc82..ab64e4e206f9 100644 --- a/crates/tauri/src/app.rs +++ b/crates/tauri/src/app.rs @@ -18,8 +18,8 @@ use crate::{ sealed::{ManagerBase, RuntimeOrDispatch}, utils::{config::Config, Env}, webview::PageLoadPayload, - Context, DeviceEventFilter, Emitter, EventLoopMessage, Listener, Manager, Monitor, Result, - Runtime, Scopes, StateManager, Theme, Webview, WebviewWindowBuilder, Window, + Context, DeviceEventFilter, Emitter, EventLoopMessage, Listener, Manager, Monitor, Runtime, + Scopes, StateManager, Theme, Webview, WebviewWindowBuilder, Window, }; #[cfg(desktop)] @@ -38,7 +38,6 @@ use tauri_runtime::{ }; use tauri_utils::{assets::AssetsIter, PackageInfo}; -use serde::Serialize; use std::{ borrow::Cow, collections::HashMap, @@ -966,79 +965,7 @@ macro_rules! shared_app_impl { } } - impl Emitter for $app { - /// Emits an event to all [targets](EventTarget). - /// - /// # Examples - /// ``` - /// use tauri::Emitter; - /// - /// #[tauri::command] - /// fn synchronize(app: tauri::AppHandle) { - /// // emits the synchronized event to all webviews - /// app.emit("synchronized", ()); - /// } - /// ``` - fn emit(&self, event: &str, payload: S) -> Result<()> { - self.manager.emit(event, payload) - } - - /// Emits an event to all [targets](EventTarget) matching the given target. - /// - /// # Examples - /// ``` - /// use tauri::{Emitter, EventTarget}; - /// - /// #[tauri::command] - /// fn download(app: tauri::AppHandle) { - /// for i in 1..100 { - /// std::thread::sleep(std::time::Duration::from_millis(150)); - /// // emit a download progress event to all listeners - /// app.emit_to(EventTarget::any(), "download-progress", i); - /// // emit an event to listeners that used App::listen or AppHandle::listen - /// app.emit_to(EventTarget::app(), "download-progress", i); - /// // emit an event to any webview/window/webviewWindow matching the given label - /// app.emit_to("updater", "download-progress", i); // similar to using EventTarget::labeled - /// app.emit_to(EventTarget::labeled("updater"), "download-progress", i); - /// // emit an event to listeners that used WebviewWindow::listen - /// app.emit_to(EventTarget::webview_window("updater"), "download-progress", i); - /// } - /// } - /// ``` - fn emit_to(&self, target: I, event: &str, payload: S) -> Result<()> - where - I: Into, - S: Serialize + Clone, - { - self.manager.emit_to(target, event, payload) - } - - /// Emits an event to all [targets](EventTarget) based on the given filter. - /// - /// # Examples - /// ``` - /// use tauri::{Emitter, EventTarget}; - /// - /// #[tauri::command] - /// fn download(app: tauri::AppHandle) { - /// for i in 1..100 { - /// std::thread::sleep(std::time::Duration::from_millis(150)); - /// // emit a download progress event to the updater window - /// app.emit_filter("download-progress", i, |t| match t { - /// EventTarget::WebviewWindow { label } => label == "main", - /// _ => false, - /// }); - /// } - /// } - /// ``` - fn emit_filter(&self, event: &str, payload: S, filter: F) -> Result<()> - where - S: Serialize + Clone, - F: Fn(&EventTarget) -> bool, - { - self.manager.emit_filter(event, payload, filter) - } - } + impl Emitter for $app {} }; } diff --git a/crates/tauri/src/lib.rs b/crates/tauri/src/lib.rs index cbbf07bd55d3..9a7c8945708b 100644 --- a/crates/tauri/src/lib.rs +++ b/crates/tauri/src/lib.rs @@ -929,7 +929,9 @@ pub trait Emitter: sealed::ManagerBase { /// app.emit("synchronized", ()); /// } /// ``` - fn emit(&self, event: &str, payload: S) -> Result<()>; + fn emit(&self, event: &str, payload: S) -> Result<()> { + self.manager().emit(event, payload) + } /// Emits an event to all [targets](EventTarget) matching the given target. /// @@ -956,7 +958,10 @@ pub trait Emitter: sealed::ManagerBase { fn emit_to(&self, target: I, event: &str, payload: S) -> Result<()> where I: Into, - S: Serialize + Clone; + S: Serialize + Clone, + { + self.manager().emit_to(target, event, payload) + } /// Emits an event to all [targets](EventTarget) based on the given filter. /// @@ -979,7 +984,10 @@ pub trait Emitter: sealed::ManagerBase { fn emit_filter(&self, event: &str, payload: S, filter: F) -> Result<()> where S: Serialize + Clone, - F: Fn(&EventTarget) -> bool; + F: Fn(&EventTarget) -> bool, + { + self.manager().emit_filter(event, payload, filter) + } } /// Prevent implementation details from leaking out of the [`Manager`] trait. diff --git a/crates/tauri/src/webview/mod.rs b/crates/tauri/src/webview/mod.rs index cd27e2eb259a..82af31b1e653 100644 --- a/crates/tauri/src/webview/mod.rs +++ b/crates/tauri/src/webview/mod.rs @@ -1770,94 +1770,7 @@ tauri::Builder::default() } } -impl Emitter for Webview { - /// Emits an event to all [targets](EventTarget). - /// - /// # Examples - #[cfg_attr( - feature = "unstable", - doc = r####" -``` -use tauri::Emitter; - -#[tauri::command] -fn synchronize(webview: tauri::Webview) { - // emits the synchronized event to all webviews - webview.emit("synchronized", ()); -} - ``` - "#### - )] - fn emit(&self, event: &str, payload: S) -> crate::Result<()> { - self.manager.emit(event, payload) - } - - /// Emits an event to all [targets](EventTarget) matching the given target. - /// - /// # Examples - #[cfg_attr( - feature = "unstable", - doc = r####" -``` -use tauri::{Emitter, EventTarget}; - -#[tauri::command] -fn download(webview: tauri::Webview) { - for i in 1..100 { - std::thread::sleep(std::time::Duration::from_millis(150)); - // emit a download progress event to all listeners - webview.emit_to(EventTarget::any(), "download-progress", i); - // emit an event to listeners that used App::listen or AppHandle::listen - webview.emit_to(EventTarget::app(), "download-progress", i); - // emit an event to any webview/window/webviewWindow matching the given label - webview.emit_to("updater", "download-progress", i); // similar to using EventTarget::labeled - webview.emit_to(EventTarget::labeled("updater"), "download-progress", i); - // emit an event to listeners that used WebviewWindow::listen - webview.emit_to(EventTarget::webview_window("updater"), "download-progress", i); - } -} -``` -"#### - )] - fn emit_to(&self, target: I, event: &str, payload: S) -> crate::Result<()> - where - I: Into, - S: Serialize + Clone, - { - self.manager.emit_to(target, event, payload) - } - - /// Emits an event to all [targets](EventTarget) based on the given filter. - /// - /// # Examples - #[cfg_attr( - feature = "unstable", - doc = r####" -``` -use tauri::{Emitter, EventTarget}; - -#[tauri::command] -fn download(webview: tauri::Webview) { - for i in 1..100 { - std::thread::sleep(std::time::Duration::from_millis(150)); - // emit a download progress event to the updater window - webview.emit_filter("download-progress", i, |t| match t { - EventTarget::WebviewWindow { label } => label == "main", - _ => false, - }); - } -} - ``` - "#### - )] - fn emit_filter(&self, event: &str, payload: S, filter: F) -> crate::Result<()> - where - S: Serialize + Clone, - F: Fn(&EventTarget) -> bool, - { - self.manager.emit_filter(event, payload, filter) - } -} +impl Emitter for Webview {} impl Manager for Webview { fn resources_table(&self) -> MutexGuard<'_, ResourceTable> { diff --git a/crates/tauri/src/webview/webview_window.rs b/crates/tauri/src/webview/webview_window.rs index 901ffb1d9a47..aed2d3d1e9f6 100644 --- a/crates/tauri/src/webview/webview_window.rs +++ b/crates/tauri/src/webview/webview_window.rs @@ -27,7 +27,6 @@ use crate::{ UserAttentionType, }, }; -use serde::Serialize; use tauri_utils::{ config::{Color, WebviewUrl, WindowConfig}, Theme, @@ -2064,79 +2063,7 @@ impl Listener for WebviewWindow { } } -impl Emitter for WebviewWindow { - /// Emits an event to all [targets](EventTarget). - /// - /// # Examples - /// ``` - /// use tauri::Emitter; - /// - /// #[tauri::command] - /// fn synchronize(window: tauri::WebviewWindow) { - /// // emits the synchronized event to all webviews - /// window.emit("synchronized", ()); - /// } - /// ``` - fn emit(&self, event: &str, payload: S) -> crate::Result<()> { - self.manager().emit(event, payload) - } - - /// Emits an event to all [targets](EventTarget) matching the given target. - /// - /// # Examples - /// ``` - /// use tauri::{Emitter, EventTarget}; - /// - /// #[tauri::command] - /// fn download(window: tauri::WebviewWindow) { - /// for i in 1..100 { - /// std::thread::sleep(std::time::Duration::from_millis(150)); - /// // emit a download progress event to all listeners - /// window.emit_to(EventTarget::any(), "download-progress", i); - /// // emit an event to listeners that used App::listen or AppHandle::listen - /// window.emit_to(EventTarget::app(), "download-progress", i); - /// // emit an event to any webview/window/webviewWindow matching the given label - /// window.emit_to("updater", "download-progress", i); // similar to using EventTarget::labeled - /// window.emit_to(EventTarget::labeled("updater"), "download-progress", i); - /// // emit an event to listeners that used WebviewWindow::listen - /// window.emit_to(EventTarget::webview_window("updater"), "download-progress", i); - /// } - /// } - /// ``` - fn emit_to(&self, target: I, event: &str, payload: S) -> crate::Result<()> - where - I: Into, - S: Serialize + Clone, - { - self.manager().emit_to(target, event, payload) - } - - /// Emits an event to all [targets](EventTarget) based on the given filter. - /// - /// # Examples - /// ``` - /// use tauri::{Emitter, EventTarget}; - /// - /// #[tauri::command] - /// fn download(window: tauri::WebviewWindow) { - /// for i in 1..100 { - /// std::thread::sleep(std::time::Duration::from_millis(150)); - /// // emit a download progress event to the updater window - /// window.emit_filter("download-progress", i, |t| match t { - /// EventTarget::WebviewWindow { label } => label == "main", - /// _ => false, - /// }); - /// } - /// } - /// ``` - fn emit_filter(&self, event: &str, payload: S, filter: F) -> crate::Result<()> - where - S: Serialize + Clone, - F: Fn(&EventTarget) -> bool, - { - self.manager().emit_filter(event, payload, filter) - } -} +impl Emitter for WebviewWindow {} impl Manager for WebviewWindow { fn resources_table(&self) -> MutexGuard<'_, ResourceTable> { diff --git a/crates/tauri/src/window/mod.rs b/crates/tauri/src/window/mod.rs index 8330d6aaa733..0a5bcefdef0f 100644 --- a/crates/tauri/src/window/mod.rs +++ b/crates/tauri/src/window/mod.rs @@ -2209,94 +2209,7 @@ tauri::Builder::default() } } -impl Emitter for Window { - /// Emits an event to all [targets](EventTarget). - /// - /// # Examples - #[cfg_attr( - feature = "unstable", - doc = r####" -``` -use tauri::Emitter; - -#[tauri::command] -fn synchronize(window: tauri::Window) { - // emits the synchronized event to all webviews - window.emit("synchronized", ()); -} - ``` - "#### - )] - fn emit(&self, event: &str, payload: S) -> crate::Result<()> { - self.manager.emit(event, payload) - } - - /// Emits an event to all [targets](EventTarget) matching the given target. - /// - /// # Examples - #[cfg_attr( - feature = "unstable", - doc = r####" -``` -use tauri::{Emitter, EventTarget}; - -#[tauri::command] -fn download(window: tauri::Window) { - for i in 1..100 { - std::thread::sleep(std::time::Duration::from_millis(150)); - // emit a download progress event to all listeners - window.emit_to(EventTarget::any(), "download-progress", i); - // emit an event to listeners that used App::listen or AppHandle::listen - window.emit_to(EventTarget::app(), "download-progress", i); - // emit an event to any webview/window/webviewWindow matching the given label - window.emit_to("updater", "download-progress", i); // similar to using EventTarget::labeled - window.emit_to(EventTarget::labeled("updater"), "download-progress", i); - // emit an event to listeners that used WebviewWindow::listen - window.emit_to(EventTarget::webview_window("updater"), "download-progress", i); - } -} -``` -"#### - )] - fn emit_to(&self, target: I, event: &str, payload: S) -> crate::Result<()> - where - I: Into, - S: Serialize + Clone, - { - self.manager.emit_to(target, event, payload) - } - - /// Emits an event to all [targets](EventTarget) based on the given filter. - /// - /// # Examples - #[cfg_attr( - feature = "unstable", - doc = r####" -``` -use tauri::{Emitter, EventTarget}; - -#[tauri::command] -fn download(window: tauri::Window) { - for i in 1..100 { - std::thread::sleep(std::time::Duration::from_millis(150)); - // emit a download progress event to the updater window - window.emit_filter("download-progress", i, |t| match t { - EventTarget::WebviewWindow { label } => label == "main", - _ => false, - }); - } -} - ``` - "#### - )] - fn emit_filter(&self, event: &str, payload: S, filter: F) -> crate::Result<()> - where - S: Serialize + Clone, - F: Fn(&EventTarget) -> bool, - { - self.manager.emit_filter(event, payload, filter) - } -} +impl Emitter for Window {} /// The [`WindowEffectsConfig`] object builder #[derive(Default)] From 13a13cb65999fe5ac0415fb752bebb73e645da7c Mon Sep 17 00:00:00 2001 From: c Date: Mon, 13 Jan 2025 11:23:45 +0100 Subject: [PATCH 04/29] docs: document requirements for event names --- crates/tauri/src/lib.rs | 14 ++++++++++++++ crates/tauri/src/manager/mod.rs | 10 ++++++++++ 2 files changed, 24 insertions(+) diff --git a/crates/tauri/src/lib.rs b/crates/tauri/src/lib.rs index 9a7c8945708b..2b8b6015184c 100644 --- a/crates/tauri/src/lib.rs +++ b/crates/tauri/src/lib.rs @@ -837,6 +837,8 @@ pub trait Listener: sealed::ManagerBase { /// }) /// .invoke_handler(tauri::generate_handler![synchronize]); /// ``` + /// # Panics + /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_` fn listen(&self, event: impl Into, handler: F) -> EventId where F: Fn(Event) + Send + 'static; @@ -844,6 +846,8 @@ pub trait Listener: sealed::ManagerBase { /// Listen to an event on this manager only once. /// /// See [`Self::listen`] for more information. + /// # Panics + /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_` fn once(&self, event: impl Into, handler: F) -> EventId where F: FnOnce(Event) + Send + 'static; @@ -895,6 +899,8 @@ pub trait Listener: sealed::ManagerBase { /// }) /// .invoke_handler(tauri::generate_handler![synchronize]); /// ``` + /// # Panics + /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_` fn listen_any(&self, event: impl Into, handler: F) -> EventId where F: Fn(Event) + Send + 'static, @@ -907,6 +913,8 @@ pub trait Listener: sealed::ManagerBase { /// Listens once to an emitted event to any [target](EventTarget) . /// /// See [`Self::listen_any`] for more information. + /// # Panics + /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_` fn once_any(&self, event: impl Into, handler: F) -> EventId where F: FnOnce(Event) + Send + 'static, @@ -929,6 +937,8 @@ pub trait Emitter: sealed::ManagerBase { /// app.emit("synchronized", ()); /// } /// ``` + /// # Panics + /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_` fn emit(&self, event: &str, payload: S) -> Result<()> { self.manager().emit(event, payload) } @@ -955,6 +965,8 @@ pub trait Emitter: sealed::ManagerBase { /// } /// } /// ``` + /// # Panics + /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_` fn emit_to(&self, target: I, event: &str, payload: S) -> Result<()> where I: Into, @@ -981,6 +993,8 @@ pub trait Emitter: sealed::ManagerBase { /// } /// } /// ``` + /// # Panics + /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_` fn emit_filter(&self, event: &str, payload: S, filter: F) -> Result<()> where S: Serialize + Clone, diff --git a/crates/tauri/src/manager/mod.rs b/crates/tauri/src/manager/mod.rs index 774e77431987..ca0eef61a6f6 100644 --- a/crates/tauri/src/manager/mod.rs +++ b/crates/tauri/src/manager/mod.rs @@ -516,6 +516,8 @@ impl AppManager { &self.package_info } + /// # Panics + /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_` pub fn listen( &self, event: String, @@ -526,6 +528,8 @@ impl AppManager { self.listeners().listen(event, target, handler) } + /// # Panics + /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_` pub fn once( &self, event: String, @@ -544,6 +548,8 @@ impl AppManager { feature = "tracing", tracing::instrument("app::emit", skip(self, payload)) )] + /// # Panics + /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_` pub fn emit(&self, event: &str, payload: S) -> crate::Result<()> { assert_event_name_is_valid!(event); @@ -569,6 +575,8 @@ impl AppManager { feature = "tracing", tracing::instrument("app::emit::filter", skip(self, payload, filter)) )] + /// # Panics + /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_` pub fn emit_filter(&self, event: &str, payload: S, filter: F) -> crate::Result<()> where S: Serialize + Clone, @@ -598,6 +606,8 @@ impl AppManager { feature = "tracing", tracing::instrument("app::emit::to", skip(self, target, payload), fields(target)) )] + /// # Panics + /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_` pub fn emit_to(&self, target: I, event: &str, payload: S) -> crate::Result<()> where I: Into, From 1153ff6be9550490594889c9d263d32b952817bd Mon Sep 17 00:00:00 2001 From: c Date: Sun, 19 Jan 2025 14:01:55 +0100 Subject: [PATCH 05/29] refactor: move panic closer to user-facing code --- crates/tauri/src/lib.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/tauri/src/lib.rs b/crates/tauri/src/lib.rs index 2b8b6015184c..33654180724d 100644 --- a/crates/tauri/src/lib.rs +++ b/crates/tauri/src/lib.rs @@ -940,6 +940,10 @@ pub trait Emitter: sealed::ManagerBase { /// # Panics /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_` fn emit(&self, event: &str, payload: S) -> Result<()> { + assert!( + event::is_event_name_valid(event), + "Event name must include only alphanumeric characters, `-`, `/`, `:` and `_`." + ); self.manager().emit(event, payload) } @@ -972,6 +976,10 @@ pub trait Emitter: sealed::ManagerBase { I: Into, S: Serialize + Clone, { + assert!( + event::is_event_name_valid(event), + "Event name must include only alphanumeric characters, `-`, `/`, `:` and `_`." + ); self.manager().emit_to(target, event, payload) } @@ -1000,6 +1008,10 @@ pub trait Emitter: sealed::ManagerBase { S: Serialize + Clone, F: Fn(&EventTarget) -> bool, { + assert!( + event::is_event_name_valid(event), + "Event name must include only alphanumeric characters, `-`, `/`, `:` and `_`." + ); self.manager().emit_filter(event, payload, filter) } } From b26986f0dabab54dd775b63ed55ba37d9bb74ebb Mon Sep 17 00:00:00 2001 From: c Date: Mon, 20 Jan 2025 11:04:27 +0100 Subject: [PATCH 06/29] add .changes file --- .changes/document-event-name-requirements.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changes/document-event-name-requirements.md diff --git a/.changes/document-event-name-requirements.md b/.changes/document-event-name-requirements.md new file mode 100644 index 000000000000..9b10a2d0011e --- /dev/null +++ b/.changes/document-event-name-requirements.md @@ -0,0 +1,5 @@ +--- +"tauri": patch:enhance +--- + +Document requirements on event names and panics that result if requirements are not met. From a86fcc8042a123b70ed3fb31612bfc9422cc171e Mon Sep 17 00:00:00 2001 From: c Date: Tue, 21 Jan 2025 12:15:26 +0100 Subject: [PATCH 07/29] chore: cleanup imports --- crates/tauri/src/manager/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/tauri/src/manager/mod.rs b/crates/tauri/src/manager/mod.rs index ca0eef61a6f6..e9e8dfabaefb 100644 --- a/crates/tauri/src/manager/mod.rs +++ b/crates/tauri/src/manager/mod.rs @@ -19,18 +19,18 @@ use tauri_utils::{ html::{SCRIPT_NONCE_TOKEN, STYLE_NONCE_TOKEN}, }; +use crate::resources::ResourceTable; use crate::{ app::{ AppHandle, ChannelInterceptor, GlobalWebviewEventListener, GlobalWindowEventListener, OnPageLoad, }, - event::{is_event_name_valid, Event, EventId, EventTarget, Listeners}, + event::{is_event_name_valid, EmitArgs, Event, EventId, EventTarget, Listeners}, ipc::{Invoke, InvokeHandler, RuntimeAuthority}, plugin::PluginStore, utils::{config::Config, PackageInfo}, - Assets, Context, Pattern, Runtime, StateManager, Window, + Assets, Context, Pattern, Runtime, StateManager, Webview, Window, }; -use crate::{event::EmitArgs, resources::ResourceTable, Webview}; #[cfg(desktop)] mod menu; From d10ae608883a8f933905048730a22f8410a735b8 Mon Sep 17 00:00:00 2001 From: c Date: Wed, 22 Jan 2025 11:27:49 +0100 Subject: [PATCH 08/29] feat: extend Error enum by new IllegalEventName variant --- crates/tauri/src/error.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/tauri/src/error.rs b/crates/tauri/src/error.rs index 0943c390893f..8a4d3a982c70 100644 --- a/crates/tauri/src/error.rs +++ b/crates/tauri/src/error.rs @@ -160,6 +160,9 @@ pub enum Error { /// Bad `__TAURI_INVOKE_KEY__` value received in ipc message. #[error("bad __TAURI_INVOKE_KEY__ value received in ipc message")] InvokeKey, + /// Illegal event name. + #[error("only alphanumeric, '-', '/', ':', '_' permitted for event names: {0:?}")] + IllegalEventName(String), } impl From for Error { From 8e9b31e5fcfd3354928b063e4a8cf8a40df596e1 Mon Sep 17 00:00:00 2001 From: c Date: Tue, 21 Jan 2025 14:02:27 +0100 Subject: [PATCH 09/29] refactor: make EventName take a generic --- crates/tauri/src/event/mod.rs | 1 + crates/tauri/src/event/plugin.rs | 46 +++++++++++++++++++++++++++++--- crates/tauri/src/lib.rs | 1 + 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/crates/tauri/src/event/mod.rs b/crates/tauri/src/event/mod.rs index 1dc2b94e325a..2d8b944c5621 100644 --- a/crates/tauri/src/event/mod.rs +++ b/crates/tauri/src/event/mod.rs @@ -7,6 +7,7 @@ pub(crate) mod plugin; use std::{convert::Infallible, str::FromStr}; pub(crate) use listener::Listeners; +pub(crate) use plugin::EventName; use serde::{Deserialize, Serialize}; /// Checks if an event name is valid. diff --git a/crates/tauri/src/event/plugin.rs b/crates/tauri/src/event/plugin.rs index 20029dbb73bc..50ee57ca5981 100644 --- a/crates/tauri/src/event/plugin.rs +++ b/crates/tauri/src/event/plugin.rs @@ -14,13 +14,41 @@ use crate::{AppHandle, Emitter, Webview}; use super::{is_event_name_valid, EventTarget}; -pub struct EventName(String); +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct EventName(S); -impl Deref for EventName { +impl> Deref for EventName { type Target = str; fn deref(&self) -> &Self::Target { - &self.0 + self.0.as_ref() + } +} + +impl> EventName { + pub(crate) fn new(s: S) -> Result> { + if !is_event_name_valid(s.as_ref()) { + return Err(crate::Error::IllegalEventName(s.as_ref().to_string())); + } + Ok(EventName(s)) + } + + pub(crate) fn into_inner(self) -> S { + self.0 + } +} + +impl EventName<&'static str> { + // this convenience method is for using in const contexts to discharge the preconditions + // &'static prevents using this function accidentally with dynamically built string slices + pub(crate) const fn from_str(s: &'static str) -> EventName<&'static str> { + EventName(s) + } +} + +impl EventName<&str> { + pub fn into_owned(&self) -> EventName { + EventName(self.0.to_string()) } } @@ -108,3 +136,15 @@ pub(crate) fn init() -> TauriPlugin { .invoke_handler(crate::generate_handler![listen, unlisten, emit, emit_to]) .build() } + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_illegal_event_name() { + let s = EventName::new("some\r illegal event name") + .unwrap_err() + .to_string(); + assert_eq!("only alphanumeric, '-', '/', ':', '_' permitted for event names: \"some\\r illegal event name\"", s); + } +} diff --git a/crates/tauri/src/lib.rs b/crates/tauri/src/lib.rs index 33654180724d..33b5e738472a 100644 --- a/crates/tauri/src/lib.rs +++ b/crates/tauri/src/lib.rs @@ -208,6 +208,7 @@ pub use runtime::ActivationPolicy; #[cfg(target_os = "macos")] pub use self::utils::TitleBarStyle; +use self::event::EventName; pub use self::event::{Event, EventId, EventTarget}; pub use { self::app::{ From ed80b00b4db2ff71fac8c9f678b335ef0bca6df8 Mon Sep 17 00:00:00 2001 From: c Date: Tue, 21 Jan 2025 14:45:48 +0100 Subject: [PATCH 10/29] refactor: replace assert with unwrapping checked constructor --- crates/tauri/src/event/mod.rs | 7 ------- crates/tauri/src/event/plugin.rs | 9 +++++++- crates/tauri/src/lib.rs | 27 ++++++------------------ crates/tauri/src/manager/mod.rs | 35 +++++++++----------------------- 4 files changed, 24 insertions(+), 54 deletions(-) diff --git a/crates/tauri/src/event/mod.rs b/crates/tauri/src/event/mod.rs index 2d8b944c5621..31c324fdd844 100644 --- a/crates/tauri/src/event/mod.rs +++ b/crates/tauri/src/event/mod.rs @@ -10,13 +10,6 @@ pub(crate) use listener::Listeners; pub(crate) use plugin::EventName; use serde::{Deserialize, Serialize}; -/// 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 == '_') -} - /// Unique id of an event. pub type EventId = u32; diff --git a/crates/tauri/src/event/plugin.rs b/crates/tauri/src/event/plugin.rs index 50ee57ca5981..6e127b94c179 100644 --- a/crates/tauri/src/event/plugin.rs +++ b/crates/tauri/src/event/plugin.rs @@ -12,7 +12,14 @@ use crate::plugin::{Builder, TauriPlugin}; use crate::{command, ipc::CallbackFn, EventId, Result, Runtime}; use crate::{AppHandle, Emitter, Webview}; -use super::{is_event_name_valid, EventTarget}; +use super::EventTarget; + +/// Checks if an event name is valid. +fn is_event_name_valid(event: &str) -> bool { + event + .chars() + .all(|c| c.is_alphanumeric() || c == '-' || c == '/' || c == ':' || c == '_') +} #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct EventName(S); diff --git a/crates/tauri/src/lib.rs b/crates/tauri/src/lib.rs index 33b5e738472a..77de8490d8b0 100644 --- a/crates/tauri/src/lib.rs +++ b/crates/tauri/src/lib.rs @@ -938,14 +938,9 @@ pub trait Emitter: sealed::ManagerBase { /// app.emit("synchronized", ()); /// } /// ``` - /// # Panics - /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_` fn emit(&self, event: &str, payload: S) -> Result<()> { - assert!( - event::is_event_name_valid(event), - "Event name must include only alphanumeric characters, `-`, `/`, `:` and `_`." - ); - self.manager().emit(event, payload) + let event = EventName::new(event)?; + self.manager().emit(&*event, payload) } /// Emits an event to all [targets](EventTarget) matching the given target. @@ -970,18 +965,13 @@ pub trait Emitter: sealed::ManagerBase { /// } /// } /// ``` - /// # Panics - /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_` fn emit_to(&self, target: I, event: &str, payload: S) -> Result<()> where I: Into, S: Serialize + Clone, { - assert!( - event::is_event_name_valid(event), - "Event name must include only alphanumeric characters, `-`, `/`, `:` and `_`." - ); - self.manager().emit_to(target, event, payload) + let event = EventName::new(event)?; + self.manager().emit_to(target, &*event, payload) } /// Emits an event to all [targets](EventTarget) based on the given filter. @@ -1002,18 +992,13 @@ pub trait Emitter: sealed::ManagerBase { /// } /// } /// ``` - /// # Panics - /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_` fn emit_filter(&self, event: &str, payload: S, filter: F) -> Result<()> where S: Serialize + Clone, F: Fn(&EventTarget) -> bool, { - assert!( - event::is_event_name_valid(event), - "Event name must include only alphanumeric characters, `-`, `/`, `:` and `_`." - ); - self.manager().emit_filter(event, payload, filter) + let event = EventName::new(event)?; + self.manager().emit_filter(&*event, payload, filter) } } diff --git a/crates/tauri/src/manager/mod.rs b/crates/tauri/src/manager/mod.rs index e9e8dfabaefb..81b656c23eca 100644 --- a/crates/tauri/src/manager/mod.rs +++ b/crates/tauri/src/manager/mod.rs @@ -25,11 +25,11 @@ use crate::{ AppHandle, ChannelInterceptor, GlobalWebviewEventListener, GlobalWindowEventListener, OnPageLoad, }, - event::{is_event_name_valid, EmitArgs, Event, EventId, EventTarget, Listeners}, + event::{EmitArgs, Event, EventId, EventTarget, Listeners}, ipc::{Invoke, InvokeHandler, RuntimeAuthority}, plugin::PluginStore, utils::{config::Config, PackageInfo}, - Assets, Context, Pattern, Runtime, StateManager, Webview, Window, + Assets, Context, EventName, Pattern, Runtime, StateManager, Webview, Window, }; #[cfg(desktop)] @@ -39,15 +39,6 @@ mod tray; pub mod webview; pub mod window; -macro_rules! assert_event_name_is_valid { - {$event:expr} => { - assert!( - is_event_name_valid($event), - "Event name must include only alphanumeric characters, `-`, `/`, `:` and `_`." - ); - } -} - #[derive(Default)] /// Spaced and quoted Content-Security-Policy hash values. struct CspHashStrings { @@ -524,8 +515,8 @@ impl AppManager { target: EventTarget, handler: F, ) -> EventId { - assert_event_name_is_valid!(&event); - self.listeners().listen(event, target, handler) + let event = EventName::new(event).unwrap(); + self.listeners().listen(event.into_inner(), target, handler) } /// # Panics @@ -536,8 +527,8 @@ impl AppManager { target: EventTarget, handler: F, ) -> EventId { - assert_event_name_is_valid!(&event); - self.listeners().once(event, target, handler) + let event = EventName::new(event).unwrap(); + self.listeners().once(event.into_inner(), target, handler) } pub fn unlisten(&self, id: EventId) { @@ -548,10 +539,8 @@ impl AppManager { feature = "tracing", tracing::instrument("app::emit", skip(self, payload)) )] - /// # Panics - /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_` pub fn emit(&self, event: &str, payload: S) -> crate::Result<()> { - assert_event_name_is_valid!(event); + let event = EventName::new(event)?; #[cfg(feature = "tracing")] let _span = tracing::debug_span!("emit::run").entered(); @@ -565,7 +554,7 @@ impl AppManager { .cloned() .collect::>(); - listeners.emit_js(webviews.iter(), event, &emit_args)?; + listeners.emit_js(webviews.iter(), &*emit_args.event, &emit_args)?; listeners.emit(emit_args)?; Ok(()) @@ -575,14 +564,12 @@ impl AppManager { feature = "tracing", tracing::instrument("app::emit::filter", skip(self, payload, filter)) )] - /// # Panics - /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_` pub fn emit_filter(&self, event: &str, payload: S, filter: F) -> crate::Result<()> where S: Serialize + Clone, F: Fn(&EventTarget) -> bool, { - assert_event_name_is_valid!(event); + let event = EventName::new(event)?; #[cfg(feature = "tracing")] let _span = tracing::debug_span!("emit::run").entered(); @@ -592,7 +579,7 @@ impl AppManager { listeners.emit_js_filter( self.webview.webviews_lock().values(), - event, + &*emit_args.event, &emit_args, Some(&filter), )?; @@ -606,8 +593,6 @@ impl AppManager { feature = "tracing", tracing::instrument("app::emit::to", skip(self, target, payload), fields(target)) )] - /// # Panics - /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_` pub fn emit_to(&self, target: I, event: &str, payload: S) -> crate::Result<()> where I: Into, From 9129ce86eaa276053b7a3b5165604e8950179458 Mon Sep 17 00:00:00 2001 From: c Date: Tue, 21 Jan 2025 15:15:46 +0100 Subject: [PATCH 11/29] refactor: make EmitArgs constructor take EventName --- crates/tauri/src/event/listener.rs | 4 ++-- crates/tauri/src/event/mod.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/tauri/src/event/listener.rs b/crates/tauri/src/event/listener.rs index a02c2df86070..b89ac5156ef9 100644 --- a/crates/tauri/src/event/listener.rs +++ b/crates/tauri/src/event/listener.rs @@ -199,7 +199,7 @@ impl Listeners { match self.inner.handlers.try_lock() { Err(_) => self.insert_pending(Pending::Emit(emit_args)), Ok(lock) => { - if let Some(handlers) = lock.get(&emit_args.event_name) { + if let Some(handlers) = lock.get(&*emit_args.event_name) { let handlers = handlers.iter(); let handlers = handlers.filter(|(_, h)| match_any_or_filter(&h.target, &filter)); for (&id, Handler { callback, .. }) in handlers { @@ -384,7 +384,7 @@ mod test { listeners.listen(key.clone(), EventTarget::Any, event_fn); // call on event with key and d. listeners.emit(EmitArgs { - event_name: key.clone(), + event_name: crate::EventName::new(key.clone()).unwrap(), event: serde_json::to_string(&key).unwrap(), payload: serde_json::to_string(&d).unwrap() })?; diff --git a/crates/tauri/src/event/mod.rs b/crates/tauri/src/event/mod.rs index 31c324fdd844..b4b468ea68aa 100644 --- a/crates/tauri/src/event/mod.rs +++ b/crates/tauri/src/event/mod.rs @@ -111,7 +111,7 @@ impl FromStr for EventTarget { #[derive(Clone)] pub struct EmitArgs { /// Raw event name. - pub event_name: String, + pub event_name: EventName, /// Serialized event name. pub event: String, /// Serialized payload. @@ -119,12 +119,12 @@ pub struct EmitArgs { } impl EmitArgs { - pub fn new(event: &str, payload: S) -> crate::Result { + pub fn new(event: EventName<&str>, payload: S) -> crate::Result { #[cfg(feature = "tracing")] let _span = tracing::debug_span!("window::emit::serialize").entered(); Ok(EmitArgs { - event_name: event.into(), - event: serde_json::to_string(event)?, + event: serde_json::to_string(&*event)?, + event_name: event.into_owned(), payload: serde_json::to_string(&payload)?, }) } From 82b0fb8802aeaf7ddb18b90e6866b4825373c234 Mon Sep 17 00:00:00 2001 From: c Date: Tue, 21 Jan 2025 15:27:36 +0100 Subject: [PATCH 12/29] refactor: delete superfluous function argument --- crates/tauri/src/event/listener.rs | 16 +++------------- crates/tauri/src/manager/mod.rs | 3 +-- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/crates/tauri/src/event/listener.rs b/crates/tauri/src/event/listener.rs index b89ac5156ef9..05bcafc42866 100644 --- a/crates/tauri/src/event/listener.rs +++ b/crates/tauri/src/event/listener.rs @@ -269,7 +269,6 @@ impl Listeners { pub(crate) fn emit_js_filter<'a, R, I, F>( &self, mut webviews: I, - event: &str, emit_args: &EmitArgs, filter: Option, ) -> crate::Result<()> @@ -280,6 +279,7 @@ impl Listeners { { let js_listeners = self.inner.js_event_listeners.lock().unwrap(); webviews.try_for_each(|webview| { + let event = &emit_args.event; if let Some(handlers) = js_listeners.get(webview.label()).and_then(|s| s.get(event)) { let ids = handlers .iter() @@ -293,22 +293,12 @@ impl Listeners { }) } - pub(crate) fn emit_js<'a, R, I>( - &self, - webviews: I, - event: &str, - emit_args: &EmitArgs, - ) -> crate::Result<()> + pub(crate) fn emit_js<'a, R, I>(&self, webviews: I, emit_args: &EmitArgs) -> crate::Result<()> where R: Runtime, I: Iterator>, { - self.emit_js_filter( - webviews, - event, - emit_args, - None::<&dyn Fn(&EventTarget) -> bool>, - ) + self.emit_js_filter(webviews, emit_args, None::<&dyn Fn(&EventTarget) -> bool>) } } diff --git a/crates/tauri/src/manager/mod.rs b/crates/tauri/src/manager/mod.rs index 81b656c23eca..8ef6ca7b6efb 100644 --- a/crates/tauri/src/manager/mod.rs +++ b/crates/tauri/src/manager/mod.rs @@ -554,7 +554,7 @@ impl AppManager { .cloned() .collect::>(); - listeners.emit_js(webviews.iter(), &*emit_args.event, &emit_args)?; + listeners.emit_js(webviews.iter(), &emit_args)?; listeners.emit(emit_args)?; Ok(()) @@ -579,7 +579,6 @@ impl AppManager { listeners.emit_js_filter( self.webview.webviews_lock().values(), - &*emit_args.event, &emit_args, Some(&filter), )?; From e2bfa93181dbc72a9870585b2abc2284d3eada8f Mon Sep 17 00:00:00 2001 From: c Date: Tue, 21 Jan 2025 16:31:33 +0100 Subject: [PATCH 13/29] refactor: finish threading EventName through function calls --- crates/tauri/src/event/listener.rs | 51 +++++++++++++++-------------- crates/tauri/src/event/mod.rs | 4 +-- crates/tauri/src/event/plugin.rs | 18 ++++++++-- crates/tauri/src/lib.rs | 6 ++-- crates/tauri/src/manager/mod.rs | 23 ++++++++----- crates/tauri/src/manager/webview.rs | 10 +++--- crates/tauri/src/manager/window.rs | 46 +++++++++++++++----------- crates/tauri/src/webview/mod.rs | 8 ++--- crates/tauri/src/window/mod.rs | 2 +- 9 files changed, 97 insertions(+), 71 deletions(-) diff --git a/crates/tauri/src/event/listener.rs b/crates/tauri/src/event/listener.rs index 05bcafc42866..673a295c6890 100644 --- a/crates/tauri/src/event/listener.rs +++ b/crates/tauri/src/event/listener.rs @@ -21,7 +21,7 @@ enum Pending { Unlisten(EventId), Listen { id: EventId, - event: String, + event: crate::EventName, handler: Handler, }, Emit(EmitArgs), @@ -137,11 +137,14 @@ impl Listeners { Ok(()) } - fn listen_with_id(&self, id: EventId, event: String, handler: Handler) { + fn listen_with_id(&self, id: EventId, event: crate::EventName, 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); + lock + .entry(event.into_inner()) + .or_default() + .insert(id, handler); } } } @@ -149,7 +152,7 @@ impl Listeners { /// Adds an event listener. pub(crate) fn listen( &self, - event: String, + event: crate::EventName, target: EventTarget, handler: F, ) -> EventId { @@ -162,7 +165,7 @@ impl Listeners { /// Listen to an event and immediately unlisten. pub(crate) fn once( &self, - event: String, + event: crate::EventName, target: EventTarget, handler: F, ) -> EventId { @@ -224,7 +227,7 @@ impl Listeners { pub(crate) fn listen_js( &self, - event: &str, + event: crate::EventName<&str>, source_webview_label: &str, target: EventTarget, id: EventId, @@ -238,15 +241,15 @@ impl Listeners { .insert(JsHandler::new(target, id)); } - pub(crate) fn unlisten_js(&self, event: &str, id: EventId) { + pub(crate) fn unlisten_js(&self, event: crate::EventName<&str>, id: EventId) { let mut js_listeners = self.inner.js_event_listeners.lock().unwrap(); let js_listeners = js_listeners.values_mut(); for js_listeners in js_listeners { - if let Some(handlers) = js_listeners.get_mut(event) { + if let Some(handlers) = js_listeners.get_mut(&*event) { handlers.retain(|h| h.id != id); if handlers.is_empty() { - js_listeners.remove(event); + js_listeners.remove(&*event); } } } @@ -254,13 +257,13 @@ impl Listeners { pub(crate) fn has_js_listener bool>( &self, - event: &str, + event: crate::EventName<&str>, filter: F, ) -> bool { let js_listeners = self.inner.js_event_listeners.lock().unwrap(); js_listeners.values().any(|events| { events - .get(event) + .get(&*event) .map(|handlers| handlers.iter().any(|handler| filter(&handler.target))) .unwrap_or(false) }) @@ -329,33 +332,32 @@ mod test { fn listeners_check_key(e in "[a-z]+") { let listeners: Listeners = Default::default(); // clone e as the key - let key = e.clone(); + let key = crate::EventName::new(e.clone()).unwrap(); // pass e and an dummy func into listen - listeners.listen(e, EventTarget::Any, event_fn); + listeners.listen(key, EventTarget::Any, 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)); + assert!(l.contains_key(&e)); } // 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(); + let listeners: Listeners = Default::default(); + let key = crate::EventName::new(e.clone()).unwrap(); // pass e and an dummy func into listen - listeners.listen(e, EventTarget::Any, event_fn); + listeners.listen(key, EventTarget::Any, event_fn); // lock mutex let mut l = listeners.inner.handlers.lock().unwrap(); // check if l contains key - if l.contains_key(&key) { + if l.contains_key(&e) { // grab key if it exists - let handler = l.get_mut(&key); + let handler = l.get_mut(&e); // check to see if we get back a handler or not match handler { // pass on Some(handler) @@ -368,14 +370,15 @@ mod test { // check to see if on_event properly grabs the stored function from listen. #[test] - fn check_on_event(key in "[a-z]+", d in "[a-z]+") { + fn check_on_event(e in "[a-z]+", d in "[a-z]+") { let listeners: Listeners = Default::default(); + let key = crate::EventName::new(e.clone()).unwrap(); // call listen with key and the event_fn dummy func listeners.listen(key.clone(), EventTarget::Any, event_fn); // call on event with key and d. listeners.emit(EmitArgs { - event_name: crate::EventName::new(key.clone()).unwrap(), - event: serde_json::to_string(&key).unwrap(), + event_name: key.clone(), + event: serde_json::to_string(&e).unwrap(), payload: serde_json::to_string(&d).unwrap() })?; @@ -383,7 +386,7 @@ mod test { let l = listeners.inner.handlers.lock().unwrap(); // assert that the key is contained in the listeners map - assert!(l.contains_key(&key)); + assert!(l.contains_key(&e)); } } } diff --git a/crates/tauri/src/event/mod.rs b/crates/tauri/src/event/mod.rs index b4b468ea68aa..d2315e395723 100644 --- a/crates/tauri/src/event/mod.rs +++ b/crates/tauri/src/event/mod.rs @@ -156,7 +156,7 @@ impl Event { pub fn listen_js_script( listeners_object_name: &str, serialized_target: &str, - event: &str, + event: EventName<&str>, event_id: EventId, handler: &str, ) -> String { @@ -195,7 +195,7 @@ pub fn emit_js_script( pub fn unlisten_js_script( listeners_object_name: &str, - event_name: &str, + event_name: EventName<&str>, event_id: EventId, ) -> String { format!( diff --git a/crates/tauri/src/event/plugin.rs b/crates/tauri/src/event/plugin.rs index 6e127b94c179..6b1082879f73 100644 --- a/crates/tauri/src/event/plugin.rs +++ b/crates/tauri/src/event/plugin.rs @@ -24,6 +24,8 @@ fn is_event_name_valid(event: &str) -> bool { #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct EventName(S); +impl Copy for EventName<&str> {} + impl> Deref for EventName { type Target = str; @@ -43,6 +45,16 @@ impl> EventName { pub(crate) fn into_inner(self) -> S { self.0 } + + pub(crate) fn as_str_event(&self) -> EventName<&str> { + EventName(self.0.as_ref()) + } +} + +impl std::fmt::Display for EventName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } } impl EventName<&'static str> { @@ -54,7 +66,7 @@ impl EventName<&'static str> { } impl EventName<&str> { - pub fn into_owned(&self) -> EventName { + pub fn into_owned(self) -> EventName { EventName(self.0.to_string()) } } @@ -106,7 +118,7 @@ pub async fn listen( target: EventTarget, handler: CallbackFn, ) -> Result { - webview.listen_js(&event, target, handler) + webview.listen_js(event.as_str_event(), target, handler) } #[command(root = "crate")] @@ -115,7 +127,7 @@ pub async fn unlisten( event: EventName, event_id: EventId, ) -> Result<()> { - webview.unlisten_js(&event, event_id) + webview.unlisten_js(event.as_str_event(), event_id) } #[command(root = "crate")] diff --git a/crates/tauri/src/lib.rs b/crates/tauri/src/lib.rs index 77de8490d8b0..1328ab7a11a9 100644 --- a/crates/tauri/src/lib.rs +++ b/crates/tauri/src/lib.rs @@ -940,7 +940,7 @@ pub trait Emitter: sealed::ManagerBase { /// ``` fn emit(&self, event: &str, payload: S) -> Result<()> { let event = EventName::new(event)?; - self.manager().emit(&*event, payload) + self.manager().emit(event, payload) } /// Emits an event to all [targets](EventTarget) matching the given target. @@ -971,7 +971,7 @@ pub trait Emitter: sealed::ManagerBase { S: Serialize + Clone, { let event = EventName::new(event)?; - self.manager().emit_to(target, &*event, payload) + self.manager().emit_to(target, event, payload) } /// Emits an event to all [targets](EventTarget) based on the given filter. @@ -998,7 +998,7 @@ pub trait Emitter: sealed::ManagerBase { F: Fn(&EventTarget) -> bool, { let event = EventName::new(event)?; - self.manager().emit_filter(&*event, payload, filter) + self.manager().emit_filter(event, payload, filter) } } diff --git a/crates/tauri/src/manager/mod.rs b/crates/tauri/src/manager/mod.rs index 8ef6ca7b6efb..35ac622460ef 100644 --- a/crates/tauri/src/manager/mod.rs +++ b/crates/tauri/src/manager/mod.rs @@ -516,7 +516,7 @@ impl AppManager { handler: F, ) -> EventId { let event = EventName::new(event).unwrap(); - self.listeners().listen(event.into_inner(), target, handler) + self.listeners().listen(event, target, handler) } /// # Panics @@ -528,7 +528,7 @@ impl AppManager { handler: F, ) -> EventId { let event = EventName::new(event).unwrap(); - self.listeners().once(event.into_inner(), target, handler) + self.listeners().once(event, target, handler) } pub fn unlisten(&self, id: EventId) { @@ -539,9 +539,11 @@ impl AppManager { feature = "tracing", tracing::instrument("app::emit", skip(self, payload)) )] - pub fn emit(&self, event: &str, payload: S) -> crate::Result<()> { - let event = EventName::new(event)?; - + pub fn emit( + &self, + event: EventName<&str>, + payload: S, + ) -> crate::Result<()> { #[cfg(feature = "tracing")] let _span = tracing::debug_span!("emit::run").entered(); let emit_args = EmitArgs::new(event, payload)?; @@ -564,13 +566,16 @@ impl AppManager { feature = "tracing", tracing::instrument("app::emit::filter", skip(self, payload, filter)) )] - pub fn emit_filter(&self, event: &str, payload: S, filter: F) -> crate::Result<()> + pub fn emit_filter( + &self, + event: EventName<&str>, + payload: S, + filter: F, + ) -> crate::Result<()> where S: Serialize + Clone, F: Fn(&EventTarget) -> bool, { - let event = EventName::new(event)?; - #[cfg(feature = "tracing")] let _span = tracing::debug_span!("emit::run").entered(); let emit_args = EmitArgs::new(event, payload)?; @@ -592,7 +597,7 @@ impl AppManager { feature = "tracing", tracing::instrument("app::emit::to", skip(self, target, payload), fields(target)) )] - pub fn emit_to(&self, target: I, event: &str, payload: S) -> crate::Result<()> + pub fn emit_to(&self, target: I, event: EventName<&str>, payload: S) -> crate::Result<()> where I: Into, S: Serialize + Clone, diff --git a/crates/tauri/src/manager/webview.rs b/crates/tauri/src/manager/webview.rs index 99522442a4d7..5fa6e67cf924 100644 --- a/crates/tauri/src/manager/webview.rs +++ b/crates/tauri/src/manager/webview.rs @@ -633,7 +633,7 @@ impl WebviewManager { } let _ = webview.manager.emit( - "tauri://webview-created", + crate::EventName::from_str("tauri://webview-created"), Some(crate::webview::CreatedEvent { label: webview.label().into(), }), @@ -680,14 +680,14 @@ fn on_webview_event(webview: &Webview, event: &WebviewEvent) -> c paths: Some(paths), position, }; - webview.emit_to_webview(DRAG_ENTER_EVENT, payload)? + webview.emit_to_webview(&DRAG_ENTER_EVENT, payload)? } DragDropEvent::Over { position } => { let payload = DragDropPayload { position, paths: None, }; - webview.emit_to_webview(DRAG_OVER_EVENT, payload)? + webview.emit_to_webview(&DRAG_OVER_EVENT, payload)? } DragDropEvent::Drop { paths, position } => { let scopes = webview.state::(); @@ -702,9 +702,9 @@ fn on_webview_event(webview: &Webview, event: &WebviewEvent) -> c paths: Some(paths), position, }; - webview.emit_to_webview(DRAG_DROP_EVENT, payload)? + webview.emit_to_webview(&DRAG_DROP_EVENT, payload)? } - DragDropEvent::Leave => webview.emit_to_webview(DRAG_LEAVE_EVENT, ())?, + DragDropEvent::Leave => webview.emit_to_webview(&DRAG_LEAVE_EVENT, ())?, _ => unimplemented!(), }, } diff --git a/crates/tauri/src/manager/window.rs b/crates/tauri/src/manager/window.rs index 72645a2f9a61..2ecaa2a8b49f 100644 --- a/crates/tauri/src/manager/window.rs +++ b/crates/tauri/src/manager/window.rs @@ -18,21 +18,23 @@ use tauri_runtime::{ use crate::{ app::GlobalWindowEventListener, image::Image, sealed::ManagerBase, AppHandle, Emitter, - EventLoopMessage, EventTarget, Manager, Runtime, Scopes, Window, WindowEvent, + EventLoopMessage, EventName, EventTarget, Manager, Runtime, Scopes, Window, WindowEvent, }; -const WINDOW_RESIZED_EVENT: &str = "tauri://resize"; -const WINDOW_MOVED_EVENT: &str = "tauri://move"; -const WINDOW_CLOSE_REQUESTED_EVENT: &str = "tauri://close-requested"; -const WINDOW_DESTROYED_EVENT: &str = "tauri://destroyed"; -const WINDOW_FOCUS_EVENT: &str = "tauri://focus"; -const WINDOW_BLUR_EVENT: &str = "tauri://blur"; -const WINDOW_SCALE_FACTOR_CHANGED_EVENT: &str = "tauri://scale-change"; -const WINDOW_THEME_CHANGED: &str = "tauri://theme-changed"; -pub(crate) const DRAG_ENTER_EVENT: &str = "tauri://drag-enter"; -pub(crate) const DRAG_OVER_EVENT: &str = "tauri://drag-over"; -pub(crate) const DRAG_DROP_EVENT: &str = "tauri://drag-drop"; -pub(crate) const DRAG_LEAVE_EVENT: &str = "tauri://drag-leave"; +const WINDOW_RESIZED_EVENT: EventName<&str> = EventName::from_str("tauri://resize"); +const WINDOW_MOVED_EVENT: EventName<&str> = EventName::from_str("tauri://move"); +const WINDOW_CLOSE_REQUESTED_EVENT: EventName<&str> = + EventName::from_str("tauri://close-requested"); +const WINDOW_DESTROYED_EVENT: EventName<&str> = EventName::from_str("tauri://destroyed"); +const WINDOW_FOCUS_EVENT: EventName<&str> = EventName::from_str("tauri://focus"); +const WINDOW_BLUR_EVENT: EventName<&str> = EventName::from_str("tauri://blur"); +const WINDOW_SCALE_FACTOR_CHANGED_EVENT: EventName<&str> = + EventName::from_str("tauri://scale-change"); +const WINDOW_THEME_CHANGED: EventName<&str> = EventName::from_str("tauri://theme-changed"); +pub(crate) const DRAG_ENTER_EVENT: EventName<&str> = EventName::from_str("tauri://drag-enter"); +pub(crate) const DRAG_OVER_EVENT: EventName<&str> = EventName::from_str("tauri://drag-over"); +pub(crate) const DRAG_DROP_EVENT: EventName<&str> = EventName::from_str("tauri://drag-drop"); +pub(crate) const DRAG_LEAVE_EVENT: EventName<&str> = EventName::from_str("tauri://drag-leave"); pub struct WindowManager { pub windows: Mutex>>, @@ -124,16 +126,20 @@ impl WindowManager { impl Window { /// Emits event to [`EventTarget::Window`] and [`EventTarget::WebviewWindow`] - fn emit_to_window(&self, event: &str, payload: S) -> crate::Result<()> { + fn emit_to_window( + &self, + event: EventName<&str>, + payload: S, + ) -> crate::Result<()> { let window_label = self.label(); - self.emit_filter(event, payload, |target| match target { + self.emit_filter(&event, payload, |target| match target { EventTarget::Window { label } | EventTarget::WebviewWindow { label } => label == window_label, _ => false, }) } /// Checks whether has js listener for [`EventTarget::Window`] or [`EventTarget::WebviewWindow`] - fn has_js_listener(&self, event: &str) -> bool { + fn has_js_listener(&self, event: EventName<&str>) -> bool { let window_label = self.label(); let listeners = self.manager().listeners(); listeners.has_js_listener(event, |target| match target { @@ -192,7 +198,7 @@ fn on_window_event(window: &Window, event: &WindowEvent) -> crate if window.is_webview_window() { window.emit_to( EventTarget::labeled(window.label()), - DRAG_ENTER_EVENT, + &DRAG_ENTER_EVENT, payload, )? } else { @@ -207,7 +213,7 @@ fn on_window_event(window: &Window, event: &WindowEvent) -> crate if window.is_webview_window() { window.emit_to( EventTarget::labeled(window.label()), - DRAG_OVER_EVENT, + &DRAG_OVER_EVENT, payload, )? } else { @@ -231,7 +237,7 @@ fn on_window_event(window: &Window, event: &WindowEvent) -> crate if window.is_webview_window() { window.emit_to( EventTarget::labeled(window.label()), - DRAG_DROP_EVENT, + &DRAG_DROP_EVENT, payload, )? } else { @@ -240,7 +246,7 @@ fn on_window_event(window: &Window, event: &WindowEvent) -> crate } DragDropEvent::Leave => { if window.is_webview_window() { - window.emit_to(EventTarget::labeled(window.label()), DRAG_LEAVE_EVENT, ())? + window.emit_to(EventTarget::labeled(window.label()), &DRAG_LEAVE_EVENT, ())? } else { window.emit_to_window(DRAG_LEAVE_EVENT, ())? } diff --git a/crates/tauri/src/webview/mod.rs b/crates/tauri/src/webview/mod.rs index 82af31b1e653..6bf1371c01a4 100644 --- a/crates/tauri/src/webview/mod.rs +++ b/crates/tauri/src/webview/mod.rs @@ -35,8 +35,8 @@ use crate::{ }, manager::AppManager, sealed::{ManagerBase, RuntimeOrDispatch}, - AppHandle, Emitter, Event, EventId, EventLoopMessage, Listener, Manager, ResourceTable, Runtime, - Window, + AppHandle, Emitter, Event, EventId, EventLoopMessage, EventName, Listener, Manager, + ResourceTable, Runtime, Window, }; use std::{ @@ -1491,7 +1491,7 @@ fn main() { /// Register a JS event listener and return its identifier. pub(crate) fn listen_js( &self, - event: &str, + event: EventName<&str>, target: EventTarget, handler: CallbackFn, ) -> crate::Result { @@ -1513,7 +1513,7 @@ fn main() { } /// Unregister a JS event listener. - pub(crate) fn unlisten_js(&self, event: &str, id: EventId) -> crate::Result<()> { + pub(crate) fn unlisten_js(&self, event: EventName<&str>, id: EventId) -> crate::Result<()> { let listeners = self.manager().listeners(); self.eval(&crate::event::unlisten_js_script( diff --git a/crates/tauri/src/window/mod.rs b/crates/tauri/src/window/mod.rs index 0a5bcefdef0f..4e9c89d77b90 100644 --- a/crates/tauri/src/window/mod.rs +++ b/crates/tauri/src/window/mod.rs @@ -405,7 +405,7 @@ tauri::Builder::default() // run on the main thread to fix a deadlock on webview.eval if the tracing feature is enabled let _ = window.run_on_main_thread(move || { let _ = app_manager.emit( - "tauri://window-created", + crate::EventName::from_str("tauri://window-created"), Some(crate::webview::CreatedEvent { label: window_label, }), From 1b26b87e9832468f23f939231c02526878c52642 Mon Sep 17 00:00:00 2001 From: c Date: Tue, 21 Jan 2025 17:06:42 +0100 Subject: [PATCH 14/29] refactor: functions need not be pub --- crates/tauri/src/event/plugin.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/tauri/src/event/plugin.rs b/crates/tauri/src/event/plugin.rs index 6b1082879f73..2c613a1dd574 100644 --- a/crates/tauri/src/event/plugin.rs +++ b/crates/tauri/src/event/plugin.rs @@ -112,7 +112,7 @@ impl<'de> Deserialize<'de> for WebviewLabel { } #[command(root = "crate")] -pub async fn listen( +async fn listen( webview: Webview, event: EventName, target: EventTarget, @@ -122,7 +122,7 @@ pub async fn listen( } #[command(root = "crate")] -pub async fn unlisten( +async fn unlisten( webview: Webview, event: EventName, event_id: EventId, @@ -131,7 +131,7 @@ pub async fn unlisten( } #[command(root = "crate")] -pub async fn emit( +async fn emit( app: AppHandle, event: EventName, payload: Option, @@ -140,7 +140,7 @@ pub async fn emit( } #[command(root = "crate")] -pub async fn emit_to( +async fn emit_to( app: AppHandle, target: EventTarget, event: EventName, From 4f057d1626f138dbe5aba0a34decdba9ee182d8f Mon Sep 17 00:00:00 2001 From: c Date: Tue, 21 Jan 2025 19:00:16 +0100 Subject: [PATCH 15/29] refactor: move EventName checks as far up the callchain as possible --- crates/tauri/src/app.rs | 10 ++++++---- crates/tauri/src/lib.rs | 8 ++++---- crates/tauri/src/manager/mod.rs | 6 ++---- crates/tauri/src/webview/mod.rs | 6 ++++-- crates/tauri/src/webview/webview_window.rs | 8 +++++--- crates/tauri/src/window/mod.rs | 8 +++++--- 6 files changed, 26 insertions(+), 20 deletions(-) diff --git a/crates/tauri/src/app.rs b/crates/tauri/src/app.rs index ab64e4e206f9..57fc7a00ac56 100644 --- a/crates/tauri/src/app.rs +++ b/crates/tauri/src/app.rs @@ -18,8 +18,8 @@ use crate::{ sealed::{ManagerBase, RuntimeOrDispatch}, utils::{config::Config, Env}, webview::PageLoadPayload, - Context, DeviceEventFilter, Emitter, EventLoopMessage, Listener, Manager, Monitor, Runtime, - Scopes, StateManager, Theme, Webview, WebviewWindowBuilder, Window, + Context, DeviceEventFilter, Emitter, EventLoopMessage, EventName, Listener, Manager, Monitor, + Runtime, Scopes, StateManager, Theme, Webview, WebviewWindowBuilder, Window, }; #[cfg(desktop)] @@ -928,7 +928,8 @@ macro_rules! shared_app_impl { where F: Fn(Event) + Send + 'static, { - self.manager.listen(event.into(), EventTarget::App, handler) + let event = EventName::new(event.into()).unwrap(); + self.manager.listen(event, EventTarget::App, handler) } /// Listen to an event on this app only once. @@ -938,7 +939,8 @@ macro_rules! shared_app_impl { where F: FnOnce(Event) + Send + 'static, { - self.manager.once(event.into(), EventTarget::App, handler) + let event = EventName::new(event.into()).unwrap(); + self.manager.once(event, EventTarget::App, handler) } /// Unlisten to an event on this app. diff --git a/crates/tauri/src/lib.rs b/crates/tauri/src/lib.rs index 1328ab7a11a9..7378694168cd 100644 --- a/crates/tauri/src/lib.rs +++ b/crates/tauri/src/lib.rs @@ -906,9 +906,8 @@ pub trait Listener: sealed::ManagerBase { where F: Fn(Event) + Send + 'static, { - self - .manager() - .listen(event.into(), EventTarget::Any, handler) + let event = EventName::new(event.into()).unwrap(); + self.manager().listen(event, EventTarget::Any, handler) } /// Listens once to an emitted event to any [target](EventTarget) . @@ -920,7 +919,8 @@ pub trait Listener: sealed::ManagerBase { where F: FnOnce(Event) + Send + 'static, { - self.manager().once(event.into(), EventTarget::Any, handler) + let event = EventName::new(event.into()).unwrap(); + self.manager().once(event, EventTarget::Any, handler) } } diff --git a/crates/tauri/src/manager/mod.rs b/crates/tauri/src/manager/mod.rs index 35ac622460ef..6ad2b797f220 100644 --- a/crates/tauri/src/manager/mod.rs +++ b/crates/tauri/src/manager/mod.rs @@ -511,11 +511,10 @@ impl AppManager { /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_` pub fn listen( &self, - event: String, + event: EventName, target: EventTarget, handler: F, ) -> EventId { - let event = EventName::new(event).unwrap(); self.listeners().listen(event, target, handler) } @@ -523,11 +522,10 @@ impl AppManager { /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_` pub fn once( &self, - event: String, + event: EventName, target: EventTarget, handler: F, ) -> EventId { - let event = EventName::new(event).unwrap(); self.listeners().once(event, target, handler) } diff --git a/crates/tauri/src/webview/mod.rs b/crates/tauri/src/webview/mod.rs index 6bf1371c01a4..be95435a8390 100644 --- a/crates/tauri/src/webview/mod.rs +++ b/crates/tauri/src/webview/mod.rs @@ -1711,8 +1711,9 @@ tauri::Builder::default() where F: Fn(Event) + Send + 'static, { + let event = EventName::new(event.into()).unwrap(); self.manager.listen( - event.into(), + event, EventTarget::Webview { label: self.label().to_string(), }, @@ -1727,8 +1728,9 @@ tauri::Builder::default() where F: FnOnce(Event) + Send + 'static, { + let event = EventName::new(event.into()).unwrap(); self.manager.once( - event.into(), + event, EventTarget::Webview { label: self.label().to_string(), }, diff --git a/crates/tauri/src/webview/webview_window.rs b/crates/tauri/src/webview/webview_window.rs index aed2d3d1e9f6..c25b0b255e5e 100644 --- a/crates/tauri/src/webview/webview_window.rs +++ b/crates/tauri/src/webview/webview_window.rs @@ -15,7 +15,7 @@ use crate::{ ipc::ScopeObject, runtime::dpi::{PhysicalPosition, PhysicalSize}, window::Monitor, - Emitter, Listener, ResourceTable, Window, + Emitter, EventName, Listener, ResourceTable, Window, }; #[cfg(desktop)] use crate::{ @@ -2009,8 +2009,9 @@ impl Listener for WebviewWindow { where F: Fn(Event) + Send + 'static, { + let event = EventName::new(event.into()).unwrap(); self.manager().listen( - event.into(), + event, EventTarget::WebviewWindow { label: self.label().to_string(), }, @@ -2025,8 +2026,9 @@ impl Listener for WebviewWindow { where F: FnOnce(Event) + Send + 'static, { + let event = EventName::new(event.into()).unwrap(); self.manager().once( - event.into(), + event, EventTarget::WebviewWindow { label: self.label().to_string(), }, diff --git a/crates/tauri/src/window/mod.rs b/crates/tauri/src/window/mod.rs index 4e9c89d77b90..c16b45ca48a3 100644 --- a/crates/tauri/src/window/mod.rs +++ b/crates/tauri/src/window/mod.rs @@ -28,7 +28,7 @@ use crate::{ sealed::{ManagerBase, RuntimeOrDispatch}, utils::config::{WindowConfig, WindowEffectsConfig}, webview::WebviewBuilder, - Emitter, EventLoopMessage, Listener, Manager, ResourceTable, Runtime, Theme, Webview, + Emitter, EventLoopMessage, EventName, Listener, Manager, ResourceTable, Runtime, Theme, Webview, WindowEvent, }; #[cfg(desktop)] @@ -2150,8 +2150,9 @@ tauri::Builder::default() where F: Fn(Event) + Send + 'static, { + let event = EventName::new(event.into()).unwrap(); self.manager.listen( - event.into(), + event, EventTarget::Window { label: self.label().to_string(), }, @@ -2166,8 +2167,9 @@ tauri::Builder::default() where F: FnOnce(Event) + Send + 'static, { + let event = EventName::new(event.into()).unwrap(); self.manager.once( - event.into(), + event, EventTarget::Window { label: self.label().to_string(), }, From 246ff4efd05a7cbcf29bfcb20174d11dbf84eaff Mon Sep 17 00:00:00 2001 From: c Date: Wed, 22 Jan 2025 12:34:52 +0100 Subject: [PATCH 16/29] refactor: move EventName to the event module --- crates/tauri/src/event/mod.rs | 76 ++++++++++++++++++++++++++++++- crates/tauri/src/event/plugin.rs | 77 +------------------------------- 2 files changed, 75 insertions(+), 78 deletions(-) diff --git a/crates/tauri/src/event/mod.rs b/crates/tauri/src/event/mod.rs index d2315e395723..a552fa841439 100644 --- a/crates/tauri/src/event/mod.rs +++ b/crates/tauri/src/event/mod.rs @@ -7,8 +7,80 @@ pub(crate) mod plugin; use std::{convert::Infallible, str::FromStr}; pub(crate) use listener::Listeners; -pub(crate) use plugin::EventName; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; + +/// Checks if an event name is valid. +fn is_event_name_valid(event: &str) -> bool { + event + .chars() + .all(|c| c.is_alphanumeric() || c == '-' || c == '/' || c == ':' || c == '_') +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct EventName(S); + +impl Copy for EventName<&str> {} + +impl> std::ops::Deref for EventName { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.0.as_ref() + } +} + +impl> EventName { + pub(crate) fn new(s: S) -> crate::Result> { + if !is_event_name_valid(s.as_ref()) { + return Err(crate::Error::IllegalEventName(s.as_ref().to_string())); + } + Ok(EventName(s)) + } + + pub(crate) fn into_inner(self) -> S { + self.0 + } + + pub(crate) fn as_str_event(&self) -> EventName<&str> { + EventName(self.0.as_ref()) + } +} + +impl std::fmt::Display for EventName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl EventName<&'static str> { + // this convenience method is for using in const contexts to discharge the preconditions + // &'static prevents using this function accidentally with dynamically built string slices + pub(crate) const fn from_str(s: &'static str) -> EventName<&'static str> { + EventName(s) + } +} + +impl EventName<&str> { + pub fn into_owned(self) -> EventName { + EventName(self.0.to_string()) + } +} + +impl<'de> Deserialize<'de> for EventName { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + let event_id = String::deserialize(deserializer)?; + if is_event_name_valid(&event_id) { + Ok(EventName(event_id)) + } else { + Err(serde::de::Error::custom( + "Event name must include only alphanumeric characters, `-`, `/`, `:` and `_`.", + )) + } + } +} /// Unique id of an event. pub type EventId = u32; diff --git a/crates/tauri/src/event/plugin.rs b/crates/tauri/src/event/plugin.rs index 2c613a1dd574..968ac0623477 100644 --- a/crates/tauri/src/event/plugin.rs +++ b/crates/tauri/src/event/plugin.rs @@ -1,9 +1,6 @@ // Copyright 2019-2024 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT - -use std::ops::Deref; - use serde::{Deserialize, Deserializer}; use serde_json::Value as JsonValue; use tauri_runtime::window::is_label_valid; @@ -12,81 +9,9 @@ use crate::plugin::{Builder, TauriPlugin}; use crate::{command, ipc::CallbackFn, EventId, Result, Runtime}; use crate::{AppHandle, Emitter, Webview}; +use super::EventName; use super::EventTarget; -/// Checks if an event name is valid. -fn is_event_name_valid(event: &str) -> bool { - event - .chars() - .all(|c| c.is_alphanumeric() || c == '-' || c == '/' || c == ':' || c == '_') -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct EventName(S); - -impl Copy for EventName<&str> {} - -impl> Deref for EventName { - type Target = str; - - fn deref(&self) -> &Self::Target { - self.0.as_ref() - } -} - -impl> EventName { - pub(crate) fn new(s: S) -> Result> { - if !is_event_name_valid(s.as_ref()) { - return Err(crate::Error::IllegalEventName(s.as_ref().to_string())); - } - Ok(EventName(s)) - } - - pub(crate) fn into_inner(self) -> S { - self.0 - } - - pub(crate) fn as_str_event(&self) -> EventName<&str> { - EventName(self.0.as_ref()) - } -} - -impl std::fmt::Display for EventName { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } -} - -impl EventName<&'static str> { - // this convenience method is for using in const contexts to discharge the preconditions - // &'static prevents using this function accidentally with dynamically built string slices - pub(crate) const fn from_str(s: &'static str) -> EventName<&'static str> { - EventName(s) - } -} - -impl EventName<&str> { - pub fn into_owned(self) -> EventName { - EventName(self.0.to_string()) - } -} - -impl<'de> Deserialize<'de> for EventName { - fn deserialize(deserializer: D) -> std::result::Result - where - D: Deserializer<'de>, - { - let event_id = String::deserialize(deserializer)?; - if is_event_name_valid(&event_id) { - Ok(EventName(event_id)) - } else { - Err(serde::de::Error::custom( - "Event name must include only alphanumeric characters, `-`, `/`, `:` and `_`.", - )) - } - } -} - pub struct WebviewLabel(String); impl AsRef for WebviewLabel { From 92d4830e7b94c4cf0f1c2e5e4bab2c3b13a06639 Mon Sep 17 00:00:00 2001 From: c Date: Wed, 22 Jan 2025 12:38:55 +0100 Subject: [PATCH 17/29] refactor: expand functions covered by EventName to fn emit_to_webview() --- crates/tauri/src/manager/webview.rs | 31 +++++++++++++++++------------ 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/crates/tauri/src/manager/webview.rs b/crates/tauri/src/manager/webview.rs index 5fa6e67cf924..5b2dbd52ae26 100644 --- a/crates/tauri/src/manager/webview.rs +++ b/crates/tauri/src/manager/webview.rs @@ -25,8 +25,7 @@ use crate::{ pattern::PatternJavascript, sealed::ManagerBase, webview::PageLoadPayload, - Emitter, EventLoopMessage, EventTarget, Manager, Runtime, Scopes, UriSchemeContext, Webview, - Window, + EventLoopMessage, EventTarget, Manager, Runtime, Scopes, UriSchemeContext, Webview, Window, }; use super::{ @@ -661,14 +660,20 @@ impl WebviewManager { impl Webview { /// Emits event to [`EventTarget::Window`] and [`EventTarget::WebviewWindow`] - fn emit_to_webview(&self, event: &str, payload: S) -> crate::Result<()> { + fn emit_to_webview( + &self, + event: crate::EventName<&str>, + payload: S, + ) -> crate::Result<()> { let window_label = self.label(); - self.emit_filter(event, payload, |target| match target { - EventTarget::Webview { label } | EventTarget::WebviewWindow { label } => { - label == window_label - } - _ => false, - }) + self + .manager() + .emit_filter(event, payload, |target| match target { + EventTarget::Webview { label } | EventTarget::WebviewWindow { label } => { + label == window_label + } + _ => false, + }) } } @@ -680,14 +685,14 @@ fn on_webview_event(webview: &Webview, event: &WebviewEvent) -> c paths: Some(paths), position, }; - webview.emit_to_webview(&DRAG_ENTER_EVENT, payload)? + webview.emit_to_webview(DRAG_ENTER_EVENT, payload)? } DragDropEvent::Over { position } => { let payload = DragDropPayload { position, paths: None, }; - webview.emit_to_webview(&DRAG_OVER_EVENT, payload)? + webview.emit_to_webview(DRAG_OVER_EVENT, payload)? } DragDropEvent::Drop { paths, position } => { let scopes = webview.state::(); @@ -702,9 +707,9 @@ fn on_webview_event(webview: &Webview, event: &WebviewEvent) -> c paths: Some(paths), position, }; - webview.emit_to_webview(&DRAG_DROP_EVENT, payload)? + webview.emit_to_webview(DRAG_DROP_EVENT, payload)? } - DragDropEvent::Leave => webview.emit_to_webview(&DRAG_LEAVE_EVENT, ())?, + DragDropEvent::Leave => webview.emit_to_webview(DRAG_LEAVE_EVENT, ())?, _ => unimplemented!(), }, } From e84c81e15ff5781486dacf7e45890a6934c477b9 Mon Sep 17 00:00:00 2001 From: c Date: Wed, 22 Jan 2025 14:21:04 +0100 Subject: [PATCH 18/29] refactor: replace Deref use with direct method --- crates/tauri/src/event/listener.rs | 8 +++---- crates/tauri/src/event/mod.rs | 14 +++++------- crates/tauri/src/event/plugin.rs | 4 ++-- crates/tauri/src/manager/window.rs | 36 +++++++++++++++++++----------- 4 files changed, 34 insertions(+), 28 deletions(-) diff --git a/crates/tauri/src/event/listener.rs b/crates/tauri/src/event/listener.rs index 673a295c6890..4a10e0c55d55 100644 --- a/crates/tauri/src/event/listener.rs +++ b/crates/tauri/src/event/listener.rs @@ -202,7 +202,7 @@ impl Listeners { match self.inner.handlers.try_lock() { Err(_) => self.insert_pending(Pending::Emit(emit_args)), Ok(lock) => { - if let Some(handlers) = lock.get(&*emit_args.event_name) { + if let Some(handlers) = lock.get(emit_args.event_name.as_str()) { let handlers = handlers.iter(); let handlers = handlers.filter(|(_, h)| match_any_or_filter(&h.target, &filter)); for (&id, Handler { callback, .. }) in handlers { @@ -245,11 +245,11 @@ impl Listeners { let mut js_listeners = self.inner.js_event_listeners.lock().unwrap(); let js_listeners = js_listeners.values_mut(); for js_listeners in js_listeners { - if let Some(handlers) = js_listeners.get_mut(&*event) { + if let Some(handlers) = js_listeners.get_mut(event.as_str()) { handlers.retain(|h| h.id != id); if handlers.is_empty() { - js_listeners.remove(&*event); + js_listeners.remove(event.as_str()); } } } @@ -263,7 +263,7 @@ impl Listeners { let js_listeners = self.inner.js_event_listeners.lock().unwrap(); js_listeners.values().any(|events| { events - .get(&*event) + .get(event.as_str()) .map(|handlers| handlers.iter().any(|handler| filter(&handler.target))) .unwrap_or(false) }) diff --git a/crates/tauri/src/event/mod.rs b/crates/tauri/src/event/mod.rs index a552fa841439..d34c21707ff4 100644 --- a/crates/tauri/src/event/mod.rs +++ b/crates/tauri/src/event/mod.rs @@ -21,14 +21,6 @@ pub struct EventName(S); impl Copy for EventName<&str> {} -impl> std::ops::Deref for EventName { - type Target = str; - - fn deref(&self) -> &Self::Target { - self.0.as_ref() - } -} - impl> EventName { pub(crate) fn new(s: S) -> crate::Result> { if !is_event_name_valid(s.as_ref()) { @@ -44,6 +36,10 @@ impl> EventName { pub(crate) fn as_str_event(&self) -> EventName<&str> { EventName(self.0.as_ref()) } + + pub(crate) fn as_str(&self) -> &str { + self.0.as_ref() + } } impl std::fmt::Display for EventName { @@ -195,7 +191,7 @@ impl EmitArgs { #[cfg(feature = "tracing")] let _span = tracing::debug_span!("window::emit::serialize").entered(); Ok(EmitArgs { - event: serde_json::to_string(&*event)?, + event: serde_json::to_string(event.as_str())?, event_name: event.into_owned(), payload: serde_json::to_string(&payload)?, }) diff --git a/crates/tauri/src/event/plugin.rs b/crates/tauri/src/event/plugin.rs index 968ac0623477..bd7e2da6dc8a 100644 --- a/crates/tauri/src/event/plugin.rs +++ b/crates/tauri/src/event/plugin.rs @@ -61,7 +61,7 @@ async fn emit( event: EventName, payload: Option, ) -> Result<()> { - app.emit(&event, payload) + app.emit(event.as_str(), payload) } #[command(root = "crate")] @@ -71,7 +71,7 @@ async fn emit_to( event: EventName, payload: Option, ) -> Result<()> { - app.emit_to(target, &event, payload) + app.emit_to(target, event.as_str(), payload) } /// Initializes the event plugin. diff --git a/crates/tauri/src/manager/window.rs b/crates/tauri/src/manager/window.rs index 2ecaa2a8b49f..f678552a29d4 100644 --- a/crates/tauri/src/manager/window.rs +++ b/crates/tauri/src/manager/window.rs @@ -17,8 +17,8 @@ use tauri_runtime::{ }; use crate::{ - app::GlobalWindowEventListener, image::Image, sealed::ManagerBase, AppHandle, Emitter, - EventLoopMessage, EventName, EventTarget, Manager, Runtime, Scopes, Window, WindowEvent, + app::GlobalWindowEventListener, image::Image, sealed::ManagerBase, AppHandle, EventLoopMessage, + EventName, EventTarget, Manager, Runtime, Scopes, Window, WindowEvent, }; const WINDOW_RESIZED_EVENT: EventName<&str> = EventName::from_str("tauri://resize"); @@ -132,10 +132,14 @@ impl Window { payload: S, ) -> crate::Result<()> { let window_label = self.label(); - self.emit_filter(&event, payload, |target| match target { - EventTarget::Window { label } | EventTarget::WebviewWindow { label } => label == window_label, - _ => false, - }) + self + .manager() + .emit_filter(event, payload, |target| match target { + EventTarget::Window { label } | EventTarget::WebviewWindow { label } => { + label == window_label + } + _ => false, + }) } /// Checks whether has js listener for [`EventTarget::Window`] or [`EventTarget::WebviewWindow`] @@ -196,9 +200,10 @@ fn on_window_event(window: &Window, event: &WindowEvent) -> crate }; if window.is_webview_window() { - window.emit_to( + // use underlying manager, otherwise have to recheck EventName + window.manager().emit_to( EventTarget::labeled(window.label()), - &DRAG_ENTER_EVENT, + DRAG_ENTER_EVENT, payload, )? } else { @@ -211,9 +216,10 @@ fn on_window_event(window: &Window, event: &WindowEvent) -> crate paths: None, }; if window.is_webview_window() { - window.emit_to( + // use underlying manager, otherwise have to recheck EventName + window.manager().emit_to( EventTarget::labeled(window.label()), - &DRAG_OVER_EVENT, + DRAG_OVER_EVENT, payload, )? } else { @@ -235,9 +241,10 @@ fn on_window_event(window: &Window, event: &WindowEvent) -> crate }; if window.is_webview_window() { - window.emit_to( + // use underlying manager, otherwise have to recheck EventName + window.manager().emit_to( EventTarget::labeled(window.label()), - &DRAG_DROP_EVENT, + DRAG_DROP_EVENT, payload, )? } else { @@ -246,7 +253,10 @@ fn on_window_event(window: &Window, event: &WindowEvent) -> crate } DragDropEvent::Leave => { if window.is_webview_window() { - window.emit_to(EventTarget::labeled(window.label()), &DRAG_LEAVE_EVENT, ())? + // use underlying manager, otherwise have to recheck EventName + window + .manager() + .emit_to(EventTarget::labeled(window.label()), DRAG_LEAVE_EVENT, ())? } else { window.emit_to_window(DRAG_LEAVE_EVENT, ())? } From 62a226297eb9fdb68f01d2ff26b831eccec11a1e Mon Sep 17 00:00:00 2001 From: c Date: Wed, 22 Jan 2025 14:30:13 +0100 Subject: [PATCH 19/29] refactor: pull event name out of EmitArgs --- crates/tauri/src/event/listener.rs | 24 ++++++++++++++---------- crates/tauri/src/event/mod.rs | 3 --- crates/tauri/src/manager/mod.rs | 4 ++-- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/crates/tauri/src/event/listener.rs b/crates/tauri/src/event/listener.rs index 4a10e0c55d55..8f718c65f45d 100644 --- a/crates/tauri/src/event/listener.rs +++ b/crates/tauri/src/event/listener.rs @@ -24,7 +24,7 @@ enum Pending { event: crate::EventName, handler: Handler, }, - Emit(EmitArgs), + Emit(crate::EventName, EmitArgs), } /// Stored in [`Listeners`] to be called upon, when the event that stored it, is triggered. @@ -128,8 +128,8 @@ impl Listeners { match action { Pending::Unlisten(id) => self.unlisten(id), Pending::Listen { id, event, handler } => self.listen_with_id(id, event, handler), - Pending::Emit(args) => { - self.emit(args)?; + Pending::Emit(event, args) => { + self.emit(event, args)?; } } } @@ -193,16 +193,21 @@ impl Listeners { } /// Emits the given event with its payload based on a filter. - pub(crate) fn emit_filter(&self, emit_args: EmitArgs, filter: Option) -> crate::Result<()> + pub(crate) fn emit_filter( + &self, + event: crate::EventName, + emit_args: EmitArgs, + filter: Option, + ) -> crate::Result<()> where F: Fn(&EventTarget) -> bool, { let mut maybe_pending = false; match self.inner.handlers.try_lock() { - Err(_) => self.insert_pending(Pending::Emit(emit_args)), + Err(_) => self.insert_pending(Pending::Emit(event, emit_args)), Ok(lock) => { - if let Some(handlers) = lock.get(emit_args.event_name.as_str()) { + if let Some(handlers) = lock.get(event.as_str()) { let handlers = handlers.iter(); let handlers = handlers.filter(|(_, h)| match_any_or_filter(&h.target, &filter)); for (&id, Handler { callback, .. }) in handlers { @@ -221,8 +226,8 @@ impl Listeners { } /// Emits the given event with its payload. - pub(crate) fn emit(&self, emit_args: EmitArgs) -> crate::Result<()> { - self.emit_filter(emit_args, None::<&dyn Fn(&EventTarget) -> bool>) + pub(crate) fn emit(&self, event: crate::EventName, emit_args: EmitArgs) -> crate::Result<()> { + self.emit_filter(event, emit_args, None::<&dyn Fn(&EventTarget) -> bool>) } pub(crate) fn listen_js( @@ -376,8 +381,7 @@ mod test { // call listen with key and the event_fn dummy func listeners.listen(key.clone(), EventTarget::Any, event_fn); // call on event with key and d. - listeners.emit(EmitArgs { - event_name: key.clone(), + listeners.emit(key.clone(), EmitArgs { event: serde_json::to_string(&e).unwrap(), payload: serde_json::to_string(&d).unwrap() })?; diff --git a/crates/tauri/src/event/mod.rs b/crates/tauri/src/event/mod.rs index d34c21707ff4..0350b5c242b3 100644 --- a/crates/tauri/src/event/mod.rs +++ b/crates/tauri/src/event/mod.rs @@ -178,8 +178,6 @@ impl FromStr for EventTarget { /// Serialized emit arguments. #[derive(Clone)] pub struct EmitArgs { - /// Raw event name. - pub event_name: EventName, /// Serialized event name. pub event: String, /// Serialized payload. @@ -192,7 +190,6 @@ impl EmitArgs { let _span = tracing::debug_span!("window::emit::serialize").entered(); Ok(EmitArgs { event: serde_json::to_string(event.as_str())?, - event_name: event.into_owned(), payload: serde_json::to_string(&payload)?, }) } diff --git a/crates/tauri/src/manager/mod.rs b/crates/tauri/src/manager/mod.rs index 6ad2b797f220..36a9a1f068fc 100644 --- a/crates/tauri/src/manager/mod.rs +++ b/crates/tauri/src/manager/mod.rs @@ -555,7 +555,7 @@ impl AppManager { .collect::>(); listeners.emit_js(webviews.iter(), &emit_args)?; - listeners.emit(emit_args)?; + listeners.emit(event.into_owned(), emit_args)?; Ok(()) } @@ -586,7 +586,7 @@ impl AppManager { Some(&filter), )?; - listeners.emit_filter(emit_args, Some(filter))?; + listeners.emit_filter(event.into_owned(), emit_args, Some(filter))?; Ok(()) } From fb6f2df232577ef510878c3f284271e9d7a07f8e Mon Sep 17 00:00:00 2001 From: c Date: Thu, 23 Jan 2025 12:52:20 +0100 Subject: [PATCH 20/29] fixup! refactor: move EventName to the event module --- crates/tauri/src/event/mod.rs | 12 ++++++++++++ crates/tauri/src/event/plugin.rs | 12 ------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/tauri/src/event/mod.rs b/crates/tauri/src/event/mod.rs index 0350b5c242b3..f8e01337b665 100644 --- a/crates/tauri/src/event/mod.rs +++ b/crates/tauri/src/event/mod.rs @@ -291,3 +291,15 @@ pub fn event_initialization_script(function: &str, listeners: &str) -> String { " ) } + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_illegal_event_name() { + let s = EventName::new("some\r illegal event name") + .unwrap_err() + .to_string(); + assert_eq!("only alphanumeric, '-', '/', ':', '_' permitted for event names: \"some\\r illegal event name\"", s); + } +} diff --git a/crates/tauri/src/event/plugin.rs b/crates/tauri/src/event/plugin.rs index bd7e2da6dc8a..6948c0de0932 100644 --- a/crates/tauri/src/event/plugin.rs +++ b/crates/tauri/src/event/plugin.rs @@ -80,15 +80,3 @@ pub(crate) fn init() -> TauriPlugin { .invoke_handler(crate::generate_handler![listen, unlisten, emit, emit_to]) .build() } - -#[cfg(test)] -mod tests { - use super::*; - #[test] - fn test_illegal_event_name() { - let s = EventName::new("some\r illegal event name") - .unwrap_err() - .to_string(); - assert_eq!("only alphanumeric, '-', '/', ':', '_' permitted for event names: \"some\\r illegal event name\"", s); - } -} From 5760c3ba72f9f60df32b007171a3fc9876369b6d Mon Sep 17 00:00:00 2001 From: c Date: Thu, 23 Jan 2025 12:57:11 +0100 Subject: [PATCH 21/29] refactor: remove unnecessary serialization call --- crates/tauri/src/event/listener.rs | 5 +---- crates/tauri/src/event/mod.rs | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/tauri/src/event/listener.rs b/crates/tauri/src/event/listener.rs index 8f718c65f45d..387ebe08f4f2 100644 --- a/crates/tauri/src/event/listener.rs +++ b/crates/tauri/src/event/listener.rs @@ -381,10 +381,7 @@ mod test { // call listen with key and the event_fn dummy func listeners.listen(key.clone(), EventTarget::Any, event_fn); // call on event with key and d. - listeners.emit(key.clone(), EmitArgs { - event: serde_json::to_string(&e).unwrap(), - payload: serde_json::to_string(&d).unwrap() - })?; + listeners.emit(key.clone(), EmitArgs::new(key.as_str_event(), &d)?)?; // lock the mutex let l = listeners.inner.handlers.lock().unwrap(); diff --git a/crates/tauri/src/event/mod.rs b/crates/tauri/src/event/mod.rs index f8e01337b665..3f44f728526a 100644 --- a/crates/tauri/src/event/mod.rs +++ b/crates/tauri/src/event/mod.rs @@ -189,7 +189,7 @@ impl EmitArgs { #[cfg(feature = "tracing")] let _span = tracing::debug_span!("window::emit::serialize").entered(); Ok(EmitArgs { - event: serde_json::to_string(event.as_str())?, + event: event.to_string(), payload: serde_json::to_string(&payload)?, }) } @@ -250,7 +250,7 @@ pub fn emit_js_script( serialized_ids: &str, ) -> crate::Result { Ok(format!( - "(function () {{ const fn = window['{}']; fn && fn({{event: {}, payload: {}}}, {ids}) }})()", + "(function () {{ const fn = window['{}']; fn && fn({{event: \"{}\", payload: {}}}, {ids}) }})()", event_emit_function_name, emit_args.event, emit_args.payload, From d36be9f214198595ba86e83359157de5d0282908 Mon Sep 17 00:00:00 2001 From: c Date: Thu, 23 Jan 2025 13:21:59 +0100 Subject: [PATCH 22/29] refactor: do not need Clone bounds and ownership of payload to serialize --- crates/tauri/src/event/mod.rs | 4 ++-- crates/tauri/src/lib.rs | 6 +++--- crates/tauri/src/manager/mod.rs | 16 ++++++--------- crates/tauri/src/manager/webview.rs | 2 +- crates/tauri/src/manager/window.rs | 32 +++++++++++++---------------- crates/tauri/src/window/mod.rs | 2 +- 6 files changed, 27 insertions(+), 35 deletions(-) diff --git a/crates/tauri/src/event/mod.rs b/crates/tauri/src/event/mod.rs index 3f44f728526a..c9b54da653c7 100644 --- a/crates/tauri/src/event/mod.rs +++ b/crates/tauri/src/event/mod.rs @@ -185,12 +185,12 @@ pub struct EmitArgs { } impl EmitArgs { - pub fn new(event: EventName<&str>, payload: S) -> crate::Result { + pub fn new(event: EventName<&str>, payload: &S) -> crate::Result { #[cfg(feature = "tracing")] let _span = tracing::debug_span!("window::emit::serialize").entered(); Ok(EmitArgs { event: event.to_string(), - payload: serde_json::to_string(&payload)?, + payload: serde_json::to_string(payload)?, }) } } diff --git a/crates/tauri/src/lib.rs b/crates/tauri/src/lib.rs index 7378694168cd..7d05f7f14c46 100644 --- a/crates/tauri/src/lib.rs +++ b/crates/tauri/src/lib.rs @@ -940,7 +940,7 @@ pub trait Emitter: sealed::ManagerBase { /// ``` fn emit(&self, event: &str, payload: S) -> Result<()> { let event = EventName::new(event)?; - self.manager().emit(event, payload) + self.manager().emit(event, &payload) } /// Emits an event to all [targets](EventTarget) matching the given target. @@ -971,7 +971,7 @@ pub trait Emitter: sealed::ManagerBase { S: Serialize + Clone, { let event = EventName::new(event)?; - self.manager().emit_to(target, event, payload) + self.manager().emit_to(target, event, &payload) } /// Emits an event to all [targets](EventTarget) based on the given filter. @@ -998,7 +998,7 @@ pub trait Emitter: sealed::ManagerBase { F: Fn(&EventTarget) -> bool, { let event = EventName::new(event)?; - self.manager().emit_filter(event, payload, filter) + self.manager().emit_filter(event, &payload, filter) } } diff --git a/crates/tauri/src/manager/mod.rs b/crates/tauri/src/manager/mod.rs index 36a9a1f068fc..287e73cec0c2 100644 --- a/crates/tauri/src/manager/mod.rs +++ b/crates/tauri/src/manager/mod.rs @@ -537,14 +537,10 @@ impl AppManager { feature = "tracing", tracing::instrument("app::emit", skip(self, payload)) )] - pub fn emit( - &self, - event: EventName<&str>, - payload: S, - ) -> crate::Result<()> { + pub fn emit(&self, event: EventName<&str>, payload: &S) -> crate::Result<()> { #[cfg(feature = "tracing")] let _span = tracing::debug_span!("emit::run").entered(); - let emit_args = EmitArgs::new(event, payload)?; + let emit_args = EmitArgs::new(event, &payload)?; let listeners = self.listeners(); let webviews = self @@ -571,12 +567,12 @@ impl AppManager { filter: F, ) -> crate::Result<()> where - S: Serialize + Clone, + S: Serialize, F: Fn(&EventTarget) -> bool, { #[cfg(feature = "tracing")] let _span = tracing::debug_span!("emit::run").entered(); - let emit_args = EmitArgs::new(event, payload)?; + let emit_args = EmitArgs::new(event, &payload)?; let listeners = self.listeners(); @@ -595,10 +591,10 @@ impl AppManager { feature = "tracing", tracing::instrument("app::emit::to", skip(self, target, payload), fields(target)) )] - pub fn emit_to(&self, target: I, event: EventName<&str>, payload: S) -> crate::Result<()> + pub fn emit_to(&self, target: I, event: EventName<&str>, payload: &S) -> crate::Result<()> where I: Into, - S: Serialize + Clone, + S: Serialize, { let target = target.into(); #[cfg(feature = "tracing")] diff --git a/crates/tauri/src/manager/webview.rs b/crates/tauri/src/manager/webview.rs index 5b2dbd52ae26..f6b2798d2bab 100644 --- a/crates/tauri/src/manager/webview.rs +++ b/crates/tauri/src/manager/webview.rs @@ -633,7 +633,7 @@ impl WebviewManager { let _ = webview.manager.emit( crate::EventName::from_str("tauri://webview-created"), - Some(crate::webview::CreatedEvent { + &Some(crate::webview::CreatedEvent { label: webview.label().into(), }), ); diff --git a/crates/tauri/src/manager/window.rs b/crates/tauri/src/manager/window.rs index f678552a29d4..57266a12ed6f 100644 --- a/crates/tauri/src/manager/window.rs +++ b/crates/tauri/src/manager/window.rs @@ -126,11 +126,7 @@ impl WindowManager { impl Window { /// Emits event to [`EventTarget::Window`] and [`EventTarget::WebviewWindow`] - fn emit_to_window( - &self, - event: EventName<&str>, - payload: S, - ) -> crate::Result<()> { + fn emit_to_window(&self, event: EventName<&str>, payload: &S) -> crate::Result<()> { let window_label = self.label(); self .manager() @@ -168,10 +164,10 @@ fn on_window_event(window: &Window, event: &WindowEvent) -> crate if window.has_js_listener(WINDOW_CLOSE_REQUESTED_EVENT) { api.prevent_close(); } - window.emit_to_window(WINDOW_CLOSE_REQUESTED_EVENT, ())?; + window.emit_to_window(WINDOW_CLOSE_REQUESTED_EVENT, &())?; } WindowEvent::Destroyed => { - window.emit_to_window(WINDOW_DESTROYED_EVENT, ())?; + window.emit_to_window(WINDOW_DESTROYED_EVENT, &())?; } WindowEvent::Focused(focused) => window.emit_to_window( if *focused { @@ -179,7 +175,7 @@ fn on_window_event(window: &Window, event: &WindowEvent) -> crate } else { WINDOW_BLUR_EVENT }, - (), + &(), )?, WindowEvent::ScaleFactorChanged { scale_factor, @@ -187,7 +183,7 @@ fn on_window_event(window: &Window, event: &WindowEvent) -> crate .. } => window.emit_to_window( WINDOW_SCALE_FACTOR_CHANGED_EVENT, - ScaleFactorChanged { + &ScaleFactorChanged { scale_factor: *scale_factor, size: *new_inner_size, }, @@ -204,10 +200,10 @@ fn on_window_event(window: &Window, event: &WindowEvent) -> crate window.manager().emit_to( EventTarget::labeled(window.label()), DRAG_ENTER_EVENT, - payload, + &payload, )? } else { - window.emit_to_window(DRAG_ENTER_EVENT, payload)? + window.emit_to_window(DRAG_ENTER_EVENT, &payload)? } } DragDropEvent::Over { position } => { @@ -220,10 +216,10 @@ fn on_window_event(window: &Window, event: &WindowEvent) -> crate window.manager().emit_to( EventTarget::labeled(window.label()), DRAG_OVER_EVENT, - payload, + &payload, )? } else { - window.emit_to_window(DRAG_OVER_EVENT, payload)? + window.emit_to_window(DRAG_OVER_EVENT, &payload)? } } DragDropEvent::Drop { paths, position } => { @@ -245,10 +241,10 @@ fn on_window_event(window: &Window, event: &WindowEvent) -> crate window.manager().emit_to( EventTarget::labeled(window.label()), DRAG_DROP_EVENT, - payload, + &payload, )? } else { - window.emit_to_window(DRAG_DROP_EVENT, payload)? + window.emit_to_window(DRAG_DROP_EVENT, &payload)? } } DragDropEvent::Leave => { @@ -256,15 +252,15 @@ fn on_window_event(window: &Window, event: &WindowEvent) -> crate // use underlying manager, otherwise have to recheck EventName window .manager() - .emit_to(EventTarget::labeled(window.label()), DRAG_LEAVE_EVENT, ())? + .emit_to(EventTarget::labeled(window.label()), DRAG_LEAVE_EVENT, &())? } else { - window.emit_to_window(DRAG_LEAVE_EVENT, ())? + window.emit_to_window(DRAG_LEAVE_EVENT, &())? } } _ => unimplemented!(), }, WindowEvent::ThemeChanged(theme) => { - window.emit_to_window(WINDOW_THEME_CHANGED, theme.to_string())? + window.emit_to_window(WINDOW_THEME_CHANGED, &theme.to_string())? } } Ok(()) diff --git a/crates/tauri/src/window/mod.rs b/crates/tauri/src/window/mod.rs index c16b45ca48a3..5479d4e730e4 100644 --- a/crates/tauri/src/window/mod.rs +++ b/crates/tauri/src/window/mod.rs @@ -406,7 +406,7 @@ tauri::Builder::default() let _ = window.run_on_main_thread(move || { let _ = app_manager.emit( crate::EventName::from_str("tauri://window-created"), - Some(crate::webview::CreatedEvent { + &Some(crate::webview::CreatedEvent { label: window_label, }), ); From 0b9147a77202462a3ad15f9b9efe5e63393bb572 Mon Sep 17 00:00:00 2001 From: c Date: Thu, 23 Jan 2025 13:27:13 +0100 Subject: [PATCH 23/29] refactor: remove unnecessary to_string() before serializing --- crates/tauri/src/manager/window.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/tauri/src/manager/window.rs b/crates/tauri/src/manager/window.rs index 57266a12ed6f..0f99f950e0e0 100644 --- a/crates/tauri/src/manager/window.rs +++ b/crates/tauri/src/manager/window.rs @@ -259,9 +259,7 @@ fn on_window_event(window: &Window, event: &WindowEvent) -> crate } _ => unimplemented!(), }, - WindowEvent::ThemeChanged(theme) => { - window.emit_to_window(WINDOW_THEME_CHANGED, &theme.to_string())? - } + WindowEvent::ThemeChanged(theme) => window.emit_to_window(WINDOW_THEME_CHANGED, &theme)?, } Ok(()) } From 389c129d5c7bdfe2c303c44a933908d5a386e3cb Mon Sep 17 00:00:00 2001 From: c Date: Thu, 23 Jan 2025 13:51:34 +0100 Subject: [PATCH 24/29] refactor: make fields private --- crates/tauri/src/event/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/tauri/src/event/mod.rs b/crates/tauri/src/event/mod.rs index c9b54da653c7..6956876ca00e 100644 --- a/crates/tauri/src/event/mod.rs +++ b/crates/tauri/src/event/mod.rs @@ -179,9 +179,9 @@ impl FromStr for EventTarget { #[derive(Clone)] pub struct EmitArgs { /// Serialized event name. - pub event: String, + event: String, /// Serialized payload. - pub payload: String, + payload: String, } impl EmitArgs { From bdb733a405fd4b17a22f31abc800e2585c79a203 Mon Sep 17 00:00:00 2001 From: c Date: Thu, 23 Jan 2025 13:59:45 +0100 Subject: [PATCH 25/29] refactor: EmitArgs should keep EventName invariants --- crates/tauri/src/event/listener.rs | 2 +- crates/tauri/src/event/mod.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/tauri/src/event/listener.rs b/crates/tauri/src/event/listener.rs index 387ebe08f4f2..1ea2e9237034 100644 --- a/crates/tauri/src/event/listener.rs +++ b/crates/tauri/src/event/listener.rs @@ -287,7 +287,7 @@ impl Listeners { { let js_listeners = self.inner.js_event_listeners.lock().unwrap(); webviews.try_for_each(|webview| { - let event = &emit_args.event; + let event = emit_args.event.as_str(); if let Some(handlers) = js_listeners.get(webview.label()).and_then(|s| s.get(event)) { let ids = handlers .iter() diff --git a/crates/tauri/src/event/mod.rs b/crates/tauri/src/event/mod.rs index 6956876ca00e..4460c1e163f6 100644 --- a/crates/tauri/src/event/mod.rs +++ b/crates/tauri/src/event/mod.rs @@ -179,7 +179,7 @@ impl FromStr for EventTarget { #[derive(Clone)] pub struct EmitArgs { /// Serialized event name. - event: String, + event: EventName, /// Serialized payload. payload: String, } @@ -189,7 +189,7 @@ impl EmitArgs { #[cfg(feature = "tracing")] let _span = tracing::debug_span!("window::emit::serialize").entered(); Ok(EmitArgs { - event: event.to_string(), + event: event.into_owned(), payload: serde_json::to_string(payload)?, }) } From 7294a93fb72743d2dc2a1d7947bf11724910248e Mon Sep 17 00:00:00 2001 From: amrbashir Date: Fri, 24 Jan 2025 03:51:50 +0200 Subject: [PATCH 26/29] remove unnecessary arg in internal emit/emit_filter --- crates/tauri/src/event/listener.rs | 23 ++++++++--------------- crates/tauri/src/manager/mod.rs | 4 ++-- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/crates/tauri/src/event/listener.rs b/crates/tauri/src/event/listener.rs index 1ea2e9237034..e3ce7a68ea1d 100644 --- a/crates/tauri/src/event/listener.rs +++ b/crates/tauri/src/event/listener.rs @@ -24,7 +24,7 @@ enum Pending { event: crate::EventName, handler: Handler, }, - Emit(crate::EventName, EmitArgs), + Emit(EmitArgs), } /// Stored in [`Listeners`] to be called upon, when the event that stored it, is triggered. @@ -128,9 +128,7 @@ impl Listeners { match action { Pending::Unlisten(id) => self.unlisten(id), Pending::Listen { id, event, handler } => self.listen_with_id(id, event, handler), - Pending::Emit(event, args) => { - self.emit(event, args)?; - } + Pending::Emit(args) => self.emit(args)?, } } @@ -193,21 +191,16 @@ impl Listeners { } /// Emits the given event with its payload based on a filter. - pub(crate) fn emit_filter( - &self, - event: crate::EventName, - emit_args: EmitArgs, - filter: Option, - ) -> crate::Result<()> + pub(crate) fn emit_filter(&self, emit_args: EmitArgs, filter: Option) -> crate::Result<()> where F: Fn(&EventTarget) -> bool, { let mut maybe_pending = false; match self.inner.handlers.try_lock() { - Err(_) => self.insert_pending(Pending::Emit(event, emit_args)), + Err(_) => self.insert_pending(Pending::Emit(emit_args)), Ok(lock) => { - if let Some(handlers) = lock.get(event.as_str()) { + if let Some(handlers) = lock.get(emit_args.event.as_str()) { let handlers = handlers.iter(); let handlers = handlers.filter(|(_, h)| match_any_or_filter(&h.target, &filter)); for (&id, Handler { callback, .. }) in handlers { @@ -226,8 +219,8 @@ impl Listeners { } /// Emits the given event with its payload. - pub(crate) fn emit(&self, event: crate::EventName, emit_args: EmitArgs) -> crate::Result<()> { - self.emit_filter(event, emit_args, None::<&dyn Fn(&EventTarget) -> bool>) + pub(crate) fn emit(&self, emit_args: EmitArgs) -> crate::Result<()> { + self.emit_filter(emit_args, None::<&dyn Fn(&EventTarget) -> bool>) } pub(crate) fn listen_js( @@ -381,7 +374,7 @@ mod test { // call listen with key and the event_fn dummy func listeners.listen(key.clone(), EventTarget::Any, event_fn); // call on event with key and d. - listeners.emit(key.clone(), EmitArgs::new(key.as_str_event(), &d)?)?; + listeners.emit(EmitArgs::new(key.as_str_event(), &d)?)?; // lock the mutex let l = listeners.inner.handlers.lock().unwrap(); diff --git a/crates/tauri/src/manager/mod.rs b/crates/tauri/src/manager/mod.rs index 287e73cec0c2..2e8d59a8b019 100644 --- a/crates/tauri/src/manager/mod.rs +++ b/crates/tauri/src/manager/mod.rs @@ -551,7 +551,7 @@ impl AppManager { .collect::>(); listeners.emit_js(webviews.iter(), &emit_args)?; - listeners.emit(event.into_owned(), emit_args)?; + listeners.emit(emit_args)?; Ok(()) } @@ -582,7 +582,7 @@ impl AppManager { Some(&filter), )?; - listeners.emit_filter(event.into_owned(), emit_args, Some(filter))?; + listeners.emit_filter(emit_args, Some(filter))?; Ok(()) } From d32da91bd2dfadb7f46b6f6799ba8aa58365c0de Mon Sep 17 00:00:00 2001 From: amrbashir Date: Fri, 24 Jan 2025 04:06:12 +0200 Subject: [PATCH 27/29] Resue EventName in internal listeners instead of EventName type alias, move to its own file --- crates/tauri-cli/src/helpers/flock.rs | 8 +-- crates/tauri/src/event/event_name.rs | 66 +++++++++++++++++++++++ crates/tauri/src/event/listener.rs | 37 +++++++------ crates/tauri/src/event/mod.rs | 75 ++------------------------- crates/tauri/src/manager/mod.rs | 19 +++++-- crates/tauri/src/manager/window.rs | 4 +- 6 files changed, 107 insertions(+), 102 deletions(-) create mode 100644 crates/tauri/src/event/event_name.rs diff --git a/crates/tauri-cli/src/helpers/flock.rs b/crates/tauri-cli/src/helpers/flock.rs index 84ead6ae9170..e55af96afe13 100644 --- a/crates/tauri-cli/src/helpers/flock.rs +++ b/crates/tauri-cli/src/helpers/flock.rs @@ -327,15 +327,11 @@ mod sys { } pub(super) fn error_contended(err: &Error) -> bool { - err - .raw_os_error() - .map_or(false, |x| x == ERROR_LOCK_VIOLATION as i32) + err.raw_os_error() == Some(ERROR_LOCK_VIOLATION as i32) } pub(super) fn error_unsupported(err: &Error) -> bool { - err - .raw_os_error() - .map_or(false, |x| x == ERROR_INVALID_FUNCTION as i32) + err.raw_os_error() == Some(ERROR_INVALID_FUNCTION as i32) } pub(super) fn unlock(file: &File) -> Result<()> { diff --git a/crates/tauri/src/event/event_name.rs b/crates/tauri/src/event/event_name.rs new file mode 100644 index 000000000000..54a6f8eb829c --- /dev/null +++ b/crates/tauri/src/event/event_name.rs @@ -0,0 +1,66 @@ +use serde::{Deserialize, Deserializer}; + +/// Checks if an event name is valid. +fn is_event_name_valid(event: &str) -> bool { + event + .chars() + .all(|c| c.is_alphanumeric() || c == '-' || c == '/' || c == ':' || c == '_') +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub(crate) struct EventName(S); + +impl Copy for EventName<&str> {} + +impl> EventName { + pub(crate) fn new(s: S) -> crate::Result> { + if !is_event_name_valid(s.as_ref()) { + return Err(crate::Error::IllegalEventName(s.as_ref().to_string())); + } + Ok(EventName(s)) + } + + pub(crate) fn as_str_event(&self) -> EventName<&str> { + EventName(self.0.as_ref()) + } + + pub(crate) fn as_str(&self) -> &str { + self.0.as_ref() + } +} + +impl std::fmt::Display for EventName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl EventName<&'static str> { + // this convenience method is for using in const contexts to discharge the preconditions + // &'static prevents using this function accidentally with dynamically built string slices + pub(crate) const fn from_str(s: &'static str) -> EventName<&'static str> { + EventName(s) + } +} + +impl EventName<&str> { + pub fn into_owned(self) -> EventName { + EventName(self.0.to_string()) + } +} + +impl<'de> Deserialize<'de> for EventName { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + let event_id = String::deserialize(deserializer)?; + if is_event_name_valid(&event_id) { + Ok(EventName(event_id)) + } else { + Err(serde::de::Error::custom( + "Event name must include only alphanumeric characters, `-`, `/`, `:` and `_`.", + )) + } + } +} diff --git a/crates/tauri/src/event/listener.rs b/crates/tauri/src/event/listener.rs index e3ce7a68ea1d..f4f4350b9516 100644 --- a/crates/tauri/src/event/listener.rs +++ b/crates/tauri/src/event/listener.rs @@ -55,13 +55,12 @@ impl JsHandler { } type WebviewLabel = String; -type EventName = String; /// Holds event handlers and pending event handlers, along with the salts associating them. struct InnerListeners { pending: Mutex>, - handlers: Mutex>>, - js_event_listeners: Mutex>>>, + handlers: Mutex>>, + js_event_listeners: Mutex>>>, function_name: &'static str, listeners_object_name: &'static str, next_event_id: Arc, @@ -139,10 +138,7 @@ impl Listeners { match self.inner.handlers.try_lock() { Err(_) => self.insert_pending(Pending::Listen { id, event, handler }), Ok(mut lock) => { - lock - .entry(event.into_inner()) - .or_default() - .insert(id, handler); + lock.entry(event).or_default().insert(id, handler); } } } @@ -200,7 +196,7 @@ impl Listeners { match self.inner.handlers.try_lock() { Err(_) => self.insert_pending(Pending::Emit(emit_args)), Ok(lock) => { - if let Some(handlers) = lock.get(emit_args.event.as_str()) { + if let Some(handlers) = lock.get(&emit_args.event) { let handlers = handlers.iter(); let handlers = handlers.filter(|(_, h)| match_any_or_filter(&h.target, &filter)); for (&id, Handler { callback, .. }) in handlers { @@ -230,24 +226,26 @@ impl Listeners { target: EventTarget, id: EventId, ) { + let event = event.into_owned(); let mut listeners = self.inner.js_event_listeners.lock().unwrap(); listeners .entry(source_webview_label.to_string()) .or_default() - .entry(event.to_string()) + .entry(event) .or_default() .insert(JsHandler::new(target, id)); } pub(crate) fn unlisten_js(&self, event: crate::EventName<&str>, id: EventId) { + let event = event.into_owned(); let mut js_listeners = self.inner.js_event_listeners.lock().unwrap(); let js_listeners = js_listeners.values_mut(); for js_listeners in js_listeners { - if let Some(handlers) = js_listeners.get_mut(event.as_str()) { + if let Some(handlers) = js_listeners.get_mut(&event) { handlers.retain(|h| h.id != id); if handlers.is_empty() { - js_listeners.remove(event.as_str()); + js_listeners.remove(&event); } } } @@ -258,10 +256,11 @@ impl Listeners { event: crate::EventName<&str>, filter: F, ) -> bool { + let event = event.into_owned(); let js_listeners = self.inner.js_event_listeners.lock().unwrap(); js_listeners.values().any(|events| { events - .get(event.as_str()) + .get(&event) .map(|handlers| handlers.iter().any(|handler| filter(&handler.target))) .unwrap_or(false) }) @@ -278,9 +277,9 @@ impl Listeners { I: Iterator>, F: Fn(&EventTarget) -> bool, { + let event = &emit_args.event; let js_listeners = self.inner.js_event_listeners.lock().unwrap(); webviews.try_for_each(|webview| { - let event = emit_args.event.as_str(); if let Some(handlers) = js_listeners.get(webview.label()).and_then(|s| s.get(event)) { let ids = handlers .iter() @@ -332,13 +331,13 @@ mod test { // clone e as the key let key = crate::EventName::new(e.clone()).unwrap(); // pass e and an dummy func into listen - listeners.listen(key, EventTarget::Any, event_fn); + listeners.listen(key.clone(), EventTarget::Any, event_fn); // lock mutex let l = listeners.inner.handlers.lock().unwrap(); // check if the generated key is in the map - assert!(l.contains_key(&e)); + assert!(l.contains_key(&key)); } // check to see if listen inputs a handler function properly into the LISTENERS map. @@ -347,15 +346,15 @@ mod test { let listeners: Listeners = Default::default(); let key = crate::EventName::new(e.clone()).unwrap(); // pass e and an dummy func into listen - listeners.listen(key, EventTarget::Any, event_fn); + listeners.listen(key.clone(), EventTarget::Any, event_fn); // lock mutex let mut l = listeners.inner.handlers.lock().unwrap(); // check if l contains key - if l.contains_key(&e) { + if l.contains_key(&key) { // grab key if it exists - let handler = l.get_mut(&e); + let handler = l.get_mut(&key); // check to see if we get back a handler or not match handler { // pass on Some(handler) @@ -380,7 +379,7 @@ mod test { let l = listeners.inner.handlers.lock().unwrap(); // assert that the key is contained in the listeners map - assert!(l.contains_key(&e)); + assert!(l.contains_key(&key)); } } } diff --git a/crates/tauri/src/event/mod.rs b/crates/tauri/src/event/mod.rs index 4460c1e163f6..b9b881e23c61 100644 --- a/crates/tauri/src/event/mod.rs +++ b/crates/tauri/src/event/mod.rs @@ -7,76 +7,11 @@ pub(crate) mod plugin; use std::{convert::Infallible, str::FromStr}; pub(crate) use listener::Listeners; -use serde::{Deserialize, Deserializer, Serialize}; +use serde::{Deserialize, Serialize}; -/// Checks if an event name is valid. -fn is_event_name_valid(event: &str) -> bool { - event - .chars() - .all(|c| c.is_alphanumeric() || c == '-' || c == '/' || c == ':' || c == '_') -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct EventName(S); - -impl Copy for EventName<&str> {} - -impl> EventName { - pub(crate) fn new(s: S) -> crate::Result> { - if !is_event_name_valid(s.as_ref()) { - return Err(crate::Error::IllegalEventName(s.as_ref().to_string())); - } - Ok(EventName(s)) - } - - pub(crate) fn into_inner(self) -> S { - self.0 - } - - pub(crate) fn as_str_event(&self) -> EventName<&str> { - EventName(self.0.as_ref()) - } - - pub(crate) fn as_str(&self) -> &str { - self.0.as_ref() - } -} +mod event_name; -impl std::fmt::Display for EventName { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } -} - -impl EventName<&'static str> { - // this convenience method is for using in const contexts to discharge the preconditions - // &'static prevents using this function accidentally with dynamically built string slices - pub(crate) const fn from_str(s: &'static str) -> EventName<&'static str> { - EventName(s) - } -} - -impl EventName<&str> { - pub fn into_owned(self) -> EventName { - EventName(self.0.to_string()) - } -} - -impl<'de> Deserialize<'de> for EventName { - fn deserialize(deserializer: D) -> std::result::Result - where - D: Deserializer<'de>, - { - let event_id = String::deserialize(deserializer)?; - if is_event_name_valid(&event_id) { - Ok(EventName(event_id)) - } else { - Err(serde::de::Error::custom( - "Event name must include only alphanumeric characters, `-`, `/`, `:` and `_`.", - )) - } - } -} +pub(crate) use event_name::EventName; /// Unique id of an event. pub type EventId = u32; @@ -178,7 +113,7 @@ impl FromStr for EventTarget { /// Serialized emit arguments. #[derive(Clone)] pub struct EmitArgs { - /// Serialized event name. + /// event name. event: EventName, /// Serialized payload. payload: String, @@ -250,7 +185,7 @@ pub fn emit_js_script( serialized_ids: &str, ) -> crate::Result { Ok(format!( - "(function () {{ const fn = window['{}']; fn && fn({{event: \"{}\", payload: {}}}, {ids}) }})()", + "(function () {{ const fn = window['{}']; fn && fn({{event: '{}', payload: {}}}, {ids}) }})()", event_emit_function_name, emit_args.event, emit_args.payload, diff --git a/crates/tauri/src/manager/mod.rs b/crates/tauri/src/manager/mod.rs index 2e8d59a8b019..3919968aec82 100644 --- a/crates/tauri/src/manager/mod.rs +++ b/crates/tauri/src/manager/mod.rs @@ -509,7 +509,7 @@ impl AppManager { /// # Panics /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_` - pub fn listen( + pub(crate) fn listen( &self, event: EventName, target: EventTarget, @@ -520,7 +520,7 @@ impl AppManager { /// # Panics /// Will panic if `event` contains characters other than alphanumeric, `-`, `/`, `:` and `_` - pub fn once( + pub(crate) fn once( &self, event: EventName, target: EventTarget, @@ -537,7 +537,11 @@ impl AppManager { feature = "tracing", tracing::instrument("app::emit", skip(self, payload)) )] - pub fn emit(&self, event: EventName<&str>, payload: &S) -> crate::Result<()> { + pub(crate) fn emit( + &self, + event: EventName<&str>, + payload: &S, + ) -> crate::Result<()> { #[cfg(feature = "tracing")] let _span = tracing::debug_span!("emit::run").entered(); let emit_args = EmitArgs::new(event, &payload)?; @@ -560,7 +564,7 @@ impl AppManager { feature = "tracing", tracing::instrument("app::emit::filter", skip(self, payload, filter)) )] - pub fn emit_filter( + pub(crate) fn emit_filter( &self, event: EventName<&str>, payload: S, @@ -591,7 +595,12 @@ impl AppManager { feature = "tracing", tracing::instrument("app::emit::to", skip(self, target, payload), fields(target)) )] - pub fn emit_to(&self, target: I, event: EventName<&str>, payload: &S) -> crate::Result<()> + pub(crate) fn emit_to( + &self, + target: I, + event: EventName<&str>, + payload: &S, + ) -> crate::Result<()> where I: Into, S: Serialize, diff --git a/crates/tauri/src/manager/window.rs b/crates/tauri/src/manager/window.rs index 0f99f950e0e0..60709f1448ed 100644 --- a/crates/tauri/src/manager/window.rs +++ b/crates/tauri/src/manager/window.rs @@ -17,8 +17,8 @@ use tauri_runtime::{ }; use crate::{ - app::GlobalWindowEventListener, image::Image, sealed::ManagerBase, AppHandle, EventLoopMessage, - EventName, EventTarget, Manager, Runtime, Scopes, Window, WindowEvent, + app::GlobalWindowEventListener, event::EventName, image::Image, sealed::ManagerBase, AppHandle, + EventLoopMessage, EventTarget, Manager, Runtime, Scopes, Window, WindowEvent, }; const WINDOW_RESIZED_EVENT: EventName<&str> = EventName::from_str("tauri://resize"); From 94343f9a9a9c7466c03b498831fdbb88516f4049 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Fri, 24 Jan 2025 04:07:26 +0200 Subject: [PATCH 28/29] unneeded change file --- .changes/document-event-name-requirements.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .changes/document-event-name-requirements.md diff --git a/.changes/document-event-name-requirements.md b/.changes/document-event-name-requirements.md deleted file mode 100644 index 9b10a2d0011e..000000000000 --- a/.changes/document-event-name-requirements.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch:enhance ---- - -Document requirements on event names and panics that result if requirements are not met. From 51269668309bba6722fdfa8848c9f8ebd6d5bb11 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Fri, 24 Jan 2025 04:08:25 +0200 Subject: [PATCH 29/29] licenses header --- crates/tauri/src/event/event_name.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/tauri/src/event/event_name.rs b/crates/tauri/src/event/event_name.rs index 54a6f8eb829c..3b553b622776 100644 --- a/crates/tauri/src/event/event_name.rs +++ b/crates/tauri/src/event/event_name.rs @@ -1,3 +1,7 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + use serde::{Deserialize, Deserializer}; /// Checks if an event name is valid.