diff --git a/.changes/tauri-runtime-uuid.md b/.changes/tauri-runtime-uuid.md new file mode 100644 index 000000000000..65d6b126848e --- /dev/null +++ b/.changes/tauri-runtime-uuid.md @@ -0,0 +1,5 @@ +--- +'tauri-runtime': 'patch:breaking' +--- + +Added `WindowEventId` type and Changed `Dispatch::on_window_event` return type from `Uuid` to `WindowEventId` diff --git a/.changes/tauri-runtime-wry-uuid.md b/.changes/tauri-runtime-wry-uuid.md new file mode 100644 index 000000000000..427b293ae4de --- /dev/null +++ b/.changes/tauri-runtime-wry-uuid.md @@ -0,0 +1,5 @@ +--- +'tauri-runtime-wry': 'patch:breaking' +--- + +Changed `WebviewId` to be an alias for `u32` instead of `u64` diff --git a/.changes/tauri-uuid-rand.md b/.changes/tauri-uuid-rand.md new file mode 100644 index 000000000000..af1a51533dcf --- /dev/null +++ b/.changes/tauri-uuid-rand.md @@ -0,0 +1,15 @@ +--- +'tauri': 'patch:breaking' +--- + +This release contains a number of breaking changes to improve the consistency of tauri internals and the public facing APIs +and simplifying the types where applicable: + +- Removed `EventHandler` type. +- Added `EventId` type +- Changed `Manager::listen_global` and `Window::listen` to return the new `EventId` type instead of `EventHandler`. +- Removed the return type of `Manager::once_global` and `Window::once` +- Changed `Manager::unlisten` and `Window::unlisten` to take he new `EventId` type. +- Added `tauri::scope::ScopeEventId` +- Changed `FsScope::listen` to return the new `ScopeEventId` instead of `Uuid`. +- Added `FsScope::unlisten` diff --git a/core/tauri-runtime-wry/Cargo.toml b/core/tauri-runtime-wry/Cargo.toml index 20a4256b3338..519a8878ec0b 100644 --- a/core/tauri-runtime-wry/Cargo.toml +++ b/core/tauri-runtime-wry/Cargo.toml @@ -19,8 +19,6 @@ features = [ "dox" ] wry = { version = "0.33", default-features = false, features = [ "tao", "file-drop", "protocol" ] } tauri-runtime = { version = "1.0.0-alpha.2", path = "../tauri-runtime" } tauri-utils = { version = "2.0.0-alpha.8", path = "../tauri-utils" } -uuid = { version = "1", features = [ "v4" ] } -rand = "0.8" raw-window-handle = "0.5" http = "0.2" diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index d1e36a5508da..e805e23b7190 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -21,6 +21,7 @@ use tauri_runtime::{ }, DeviceEventFilter, Dispatch, Error, EventLoopProxy, ExitRequestedEventAction, Icon, Result, RunEvent, RunIteration, Runtime, RuntimeHandle, RuntimeInitArgs, UserAttentionType, UserEvent, + WindowEventId, }; #[cfg(windows)] @@ -41,7 +42,6 @@ use wry::webview::WebViewBuilderExtWindows; #[cfg(target_os = "macos")] use tauri_utils::TitleBarStyle; use tauri_utils::{config::WindowConfig, debug_eprintln, Theme}; -use uuid::Uuid; use wry::{ application::{ dpi::{ @@ -93,13 +93,14 @@ use std::{ path::PathBuf, rc::Rc, sync::{ + atomic::{AtomicU32, Ordering}, mpsc::{channel, Sender}, Arc, Mutex, Weak, }, thread::{current as current_thread, ThreadId}, }; -pub type WebviewId = u64; +pub type WebviewId = u32; type IpcHandler = dyn Fn(&Window, String) + 'static; type FileDropHandler = dyn Fn(&Window, WryFileDropEvent) -> bool + 'static; @@ -109,7 +110,7 @@ pub use webview::Webview; pub type WebContextStore = Arc, WebContext>>>; // window pub type WindowEventHandler = Box; -pub type WindowEventListeners = Arc>>; +pub type WindowEventListeners = Arc>>; #[derive(Debug, Clone, Default)] pub struct WebviewIdStore(Arc>>); @@ -171,6 +172,9 @@ pub struct Context { pub proxy: WryEventLoopProxy>, main_thread: DispatcherMainThreadContext, plugins: Arc + Send>>>>, + next_window_id: Arc, + next_window_event_id: Arc, + next_webcontext_id: Arc, } impl Context { @@ -184,6 +188,18 @@ impl Context { None }) } + + fn next_window_id(&self) -> WebviewId { + self.next_window_id.fetch_add(1, Ordering::Relaxed) + } + + fn next_window_event_id(&self) -> WebviewId { + self.next_window_event_id.fetch_add(1, Ordering::Relaxed) + } + + fn next_webcontext_id(&self) -> WebviewId { + self.next_webcontext_id.fetch_add(1, Ordering::Relaxed) + } } impl Context { @@ -194,7 +210,7 @@ impl Context { ) -> Result>> { let label = pending.label.clone(); let context = self.clone(); - let window_id = rand::random(); + let window_id = self.next_window_id(); send_user_message( self, @@ -909,7 +925,7 @@ pub enum ApplicationMessage { pub enum WindowMessage { WithWebview(Box), - AddEventListener(Uuid, Box), + AddEventListener(WindowEventId, Box), // Devtools #[cfg(any(debug_assertions, feature = "devtools"))] OpenDevTools, @@ -1056,8 +1072,8 @@ impl Dispatch for WryDispatcher { send_user_message(&self.context, Message::Task(Box::new(f))) } - fn on_window_event(&self, f: F) -> Uuid { - let id = Uuid::new_v4(); + fn on_window_event(&self, f: F) -> WindowEventId { + let id = self.context.next_window_event_id(); let _ = self.context.proxy.send_event(Message::Window( self.window_id, WindowMessage::AddEventListener(id, Box::new(f)), @@ -1640,11 +1656,9 @@ impl WryHandle { &self, f: F, ) -> Result> { + let id = self.context.next_window_id(); let (tx, rx) = channel(); - send_user_message( - &self.context, - Message::CreateWindow(rand::random(), Box::new(f), tx), - )?; + send_user_message(&self.context, Message::CreateWindow(id, Box::new(f), tx))?; rx.recv().unwrap() } @@ -1794,6 +1808,9 @@ impl Wry { windows, }, plugins: Default::default(), + next_window_id: Default::default(), + next_window_event_id: Default::default(), + next_webcontext_id: Default::default(), }; Ok(Self { @@ -1850,7 +1867,7 @@ impl Runtime for Wry { before_webview_creation: Option, ) -> Result> { let label = pending.label.clone(); - let window_id = rand::random(); + let window_id = self.context.next_window_id(); let webview = create_webview( window_id, @@ -2661,7 +2678,7 @@ fn create_webview( if let Some(handler) = ipc_handler { webview_builder = - webview_builder.with_ipc_handler(create_ipc_handler(context, label.clone(), handler)); + webview_builder.with_ipc_handler(create_ipc_handler(context.clone(), label.clone(), handler)); } for (scheme, protocol) in uri_scheme_protocols { @@ -2686,8 +2703,9 @@ fn create_webview( if automation_enabled { webview_attributes.data_directory.clone() } else { - // random unique key - Some(Uuid::new_v4().as_hyphenated().to_string().into()) + // unique key + let key = context.next_webcontext_id().to_string().into(); + Some(key) }; let entry = web_context.entry(web_context_key.clone()); let web_context = match entry { diff --git a/core/tauri-runtime/Cargo.toml b/core/tauri-runtime/Cargo.toml index e4ceee1265c9..c09cb29edb44 100644 --- a/core/tauri-runtime/Cargo.toml +++ b/core/tauri-runtime/Cargo.toml @@ -27,7 +27,6 @@ serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0" thiserror = "1.0" tauri-utils = { version = "2.0.0-alpha.8", path = "../tauri-utils" } -uuid = { version = "1", features = [ "v4" ] } http = "0.2.4" raw-window-handle = "0.5" url = { version = "2" } diff --git a/core/tauri-runtime/src/lib.rs b/core/tauri-runtime/src/lib.rs index 52158ccff121..8f4385df250e 100644 --- a/core/tauri-runtime/src/lib.rs +++ b/core/tauri-runtime/src/lib.rs @@ -17,7 +17,6 @@ use serde::Deserialize; use std::{fmt::Debug, sync::mpsc::Sender}; use tauri_utils::Theme; use url::Url; -use uuid::Uuid; /// Types useful for interacting with a user's monitors. pub mod monitor; @@ -37,6 +36,8 @@ use http::{ status::InvalidStatusCode, }; +pub type WindowEventId = u32; + /// Type of user attention requested on a window. #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] #[serde(tag = "type")] @@ -327,7 +328,7 @@ pub trait Dispatch: Debug + Clone + Send + Sync + Sized + 'static fn run_on_main_thread(&self, f: F) -> Result<()>; /// Registers a window event handler. - fn on_window_event(&self, f: F) -> Uuid; + fn on_window_event(&self, f: F) -> WindowEventId; /// Runs a closure with the platform webview object as argument. fn with_webview) + Send + 'static>(&self, f: F) -> Result<()>; diff --git a/core/tauri/Cargo.toml b/core/tauri/Cargo.toml index 965a8cb35b28..85ee4485f404 100644 --- a/core/tauri/Cargo.toml +++ b/core/tauri/Cargo.toml @@ -43,7 +43,7 @@ serde_json = { version = "1.0", features = [ "raw_value" ] } serde = { version = "1.0", features = [ "derive" ] } tokio = { version = "1", features = [ "rt", "rt-multi-thread", "sync", "fs", "io-util" ] } futures-util = "0.3" -uuid = { version = "1", features = [ "v4" ] } +uuid = { version = "1", features = [ "v4" ], optional = true } url = { version = "2.4" } anyhow = "1.0" thiserror = "1.0" @@ -52,7 +52,7 @@ tauri-runtime = { version = "1.0.0-alpha.2", path = "../tauri-runtime" } tauri-macros = { version = "2.0.0-alpha.8", path = "../tauri-macros" } tauri-utils = { version = "2.0.0-alpha.8", features = [ "resources" ], path = "../tauri-utils" } tauri-runtime-wry = { version = "1.0.0-alpha.3", path = "../tauri-runtime-wry", optional = true } -rand = "0.8" +getrandom = "0.2" serde_repr = "0.1" state = "0.6" http = "0.2" @@ -135,7 +135,7 @@ wry = [ "tauri-runtime-wry" ] objc-exception = [ "tauri-runtime-wry/objc-exception" ] linux-ipc-protocol = [ "tauri-runtime-wry/linux-protocol-body", "webkit2gtk/v2_40" ] linux-libxdo = [ "tray-icon/libxdo", "muda/libxdo" ] -isolation = [ "tauri-utils/isolation", "tauri-macros/isolation" ] +isolation = [ "tauri-utils/isolation", "tauri-macros/isolation", "uuid" ] custom-protocol = [ "tauri-macros/custom-protocol" ] native-tls = [ "reqwest/native-tls" ] native-tls-vendored = [ "reqwest/native-tls-vendored" ] diff --git a/core/tauri/scripts/init.js b/core/tauri/scripts/init.js index 0a242919bb88..c5f1490e5120 100644 --- a/core/tauri/scripts/init.js +++ b/core/tauri/scripts/init.js @@ -12,8 +12,6 @@ __RAW_bundle_script__ })() - __RAW_listen_function__ - __RAW_core_script__ __RAW_event_initialization_script__ diff --git a/core/tauri/src/event/commands.rs b/core/tauri/src/event/commands.rs index d710a84a0279..42d79bb0e892 100644 --- a/core/tauri/src/event/commands.rs +++ b/core/tauri/src/event/commands.rs @@ -2,23 +2,29 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::{command, ipc::CallbackFn, Manager, Result, Runtime, Window}; +use crate::{command, ipc::CallbackFn, EventId, Manager, Result, Runtime, Window}; use serde::{Deserialize, Deserializer}; use serde_json::Value as JsonValue; use tauri_runtime::window::is_label_valid; use super::is_event_name_valid; -pub struct EventId(String); +pub struct EventName(String); -impl<'de> Deserialize<'de> for EventId { +impl AsRef for EventName { + fn as_ref(&self) -> &str { + &self.0 + } +} + +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(EventId(event_id)) + Ok(EventName(event_id)) } else { Err(serde::de::Error::custom( "Event name must include only alphanumeric characters, `-`, `/`, `:` and `_`.", @@ -48,22 +54,22 @@ impl<'de> Deserialize<'de> for WindowLabel { #[command(root = "crate")] pub fn listen( window: Window, - event: EventId, + event: EventName, window_label: Option, handler: CallbackFn, -) -> Result { +) -> Result { window.listen_js(window_label.map(|l| l.0), event.0, handler) } #[command(root = "crate")] -pub fn unlisten(window: Window, event: EventId, event_id: usize) -> Result<()> { - window.unlisten_js(event.0, event_id) +pub fn unlisten(window: Window, event: EventName, event_id: EventId) -> Result<()> { + window.unlisten_js(event.as_ref(), event_id) } #[command(root = "crate")] pub fn emit( window: Window, - event: EventId, + event: EventName, window_label: Option, payload: Option, ) -> Result<()> { diff --git a/core/tauri/src/event/listener.rs b/core/tauri/src/event/listener.rs index db1263cf3da8..6b9fa2f9075b 100644 --- a/core/tauri/src/event/listener.rs +++ b/core/tauri/src/event/listener.rs @@ -2,20 +2,22 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use super::{Event, EventHandler}; +use super::{Event, EventId}; use std::{ boxed::Box, cell::Cell, collections::HashMap, - sync::{Arc, Mutex}, + sync::{ + atomic::{AtomicU32, Ordering}, + Arc, Mutex, + }, }; -use uuid::Uuid; /// What to do with the pending handler when resolving it? enum Pending { - Unlisten(EventHandler), - Listen(EventHandler, String, Handler), + Unlisten(EventId), + Listen(EventId, String, Handler), Trigger(String, Option, Option), } @@ -27,10 +29,11 @@ struct Handler { /// Holds event handlers and pending event handlers, along with the salts associating them. struct InnerListeners { - handlers: Mutex>>, + handlers: Mutex>>, pending: Mutex>, - function_name: Uuid, - listeners_object_name: Uuid, + function_name: &'static str, + listeners_object_name: &'static str, + next_event_id: Arc, } /// A self-contained event manager. @@ -44,8 +47,9 @@ impl Default for Listeners { inner: Arc::new(InnerListeners { handlers: Mutex::default(), pending: Mutex::default(), - function_name: Uuid::new_v4(), - listeners_object_name: Uuid::new_v4(), + function_name: "__internal_unstable_listeners_function_id__", + listeners_object_name: "__internal_unstable_listeners_object_id__", + next_event_id: Default::default(), }), } } @@ -60,14 +64,18 @@ impl Clone for Listeners { } impl Listeners { - /// Randomly generated function name to represent the JavaScript event function. - pub(crate) fn function_name(&self) -> String { - self.inner.function_name.to_string() + pub(crate) fn next_event_id(&self) -> EventId { + self.inner.next_event_id.fetch_add(1, Ordering::Relaxed) } - /// Randomly generated listener object name to represent the JavaScript event listener object. - pub(crate) fn listeners_object_name(&self) -> String { - self.inner.listeners_object_name.to_string() + /// Function name to represent the JavaScript event function. + pub(crate) fn function_name(&self) -> &str { + self.inner.function_name + } + + /// Listener object name to represent the JavaScript event listener object. + pub(crate) fn listeners_object_name(&self) -> &str { + self.inner.listeners_object_name } /// Insert a pending event action to the queue. @@ -100,7 +108,7 @@ impl Listeners { } } - fn listen_(&self, id: EventHandler, event: String, handler: Handler) { + fn listen_(&self, id: EventId, event: String, handler: Handler) { match self.inner.handlers.try_lock() { Err(_) => self.insert_pending(Pending::Listen(id, event, handler)), Ok(mut lock) => { @@ -115,8 +123,8 @@ impl Listeners { event: String, window: Option, handler: F, - ) -> EventHandler { - let id = EventHandler(Uuid::new_v4()); + ) -> EventId { + let id = self.next_event_id(); let handler = Handler { window, callback: Box::new(handler), @@ -133,7 +141,7 @@ impl Listeners { event: String, window: Option, handler: F, - ) -> EventHandler { + ) { let self_ = self.clone(); let handler = Cell::new(Some(handler)); @@ -143,15 +151,15 @@ impl Listeners { .take() .expect("attempted to call handler more than once"); handler(event) - }) + }); } /// Removes an event listener. - pub(crate) fn unlisten(&self, handler_id: EventHandler) { + pub(crate) fn unlisten(&self, id: EventId) { match self.inner.handlers.try_lock() { - Err(_) => self.insert_pending(Pending::Unlisten(handler_id)), + Err(_) => self.insert_pending(Pending::Unlisten(id)), Ok(mut lock) => lock.values_mut().for_each(|handler| { - handler.remove(&handler_id); + handler.remove(&id); }), } } diff --git a/core/tauri/src/event/mod.rs b/core/tauri/src/event/mod.rs index ddc351d54867..7df0f7f25c4e 100644 --- a/core/tauri/src/event/mod.rs +++ b/core/tauri/src/event/mod.rs @@ -2,9 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use std::{fmt, hash::Hash}; -use uuid::Uuid; - mod commands; mod listener; pub(crate) use listener::Listeners; @@ -28,26 +25,19 @@ pub fn assert_event_name_is_valid(event: &str) { ); } -/// Represents an event handler. -#[derive(Debug, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct EventHandler(Uuid); - -impl fmt::Display for EventHandler { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} +/// Unique id of an event. +pub type EventId = u32; /// An event that was triggered. #[derive(Debug, Clone)] pub struct Event { - id: EventHandler, + id: EventId, data: Option, } impl Event { - /// The [`EventHandler`] that was triggered. - pub fn id(&self) -> EventHandler { + /// The [`EventId`] of the handler that was triggered. + pub fn id(&self) -> EventId { self.id } @@ -68,7 +58,7 @@ pub(crate) fn init() -> TauriPlugin { .build() } -pub fn unlisten_js(listeners_object_name: String, event_name: String, event_id: usize) -> String { +pub fn unlisten_js(listeners_object_name: &str, event_name: &str, event_id: EventId) -> String { format!( " (function () {{ @@ -85,11 +75,11 @@ pub fn unlisten_js(listeners_object_name: String, event_name: String, event_id: } pub fn listen_js( - listeners_object_name: String, - event: String, - event_id: usize, - window_label: Option, - handler: String, + listeners_object_name: &str, + event: &str, + event_id: EventId, + window_label: Option<&str>, + handler: &str, ) -> String { format!( " @@ -111,7 +101,7 @@ pub fn listen_js( ", listeners = listeners_object_name, window_label = if let Some(l) = window_label { - crate::runtime::window::assert_label_is_valid(&l); + crate::runtime::window::assert_label_is_valid(l); format!("'{l}'") } else { "null".to_owned() diff --git a/core/tauri/src/ipc/channel.rs b/core/tauri/src/ipc/channel.rs index 76a9101cebaf..827e7297fd25 100644 --- a/core/tauri/src/ipc/channel.rs +++ b/core/tauri/src/ipc/channel.rs @@ -4,7 +4,10 @@ use std::{ collections::HashMap, - sync::{Arc, Mutex}, + sync::{ + atomic::{AtomicU32, Ordering}, + Arc, Mutex, + }, }; use serde::{Deserialize, Serialize, Serializer}; @@ -24,6 +27,9 @@ pub const CHANNEL_PLUGIN_NAME: &str = "__TAURI_CHANNEL__"; pub const FETCH_CHANNEL_DATA_COMMAND: &str = "plugin:__TAURI_CHANNEL__|fetch"; pub(crate) const CHANNEL_ID_HEADER_NAME: &str = "Tauri-Channel-Id"; +static CHANNEL_COUNTER: AtomicU32 = AtomicU32::new(0); +static CHANNEL_DATA_COUNTER: AtomicU32 = AtomicU32::new(0); + /// Maps a channel id to a pending data that must be send to the JavaScript side via the IPC. #[derive(Default, Clone)] pub struct ChannelDataIpcQueue(pub(crate) Arc>>); @@ -49,10 +55,10 @@ impl Channel { pub fn new crate::Result<()> + Send + Sync + 'static>( on_message: F, ) -> Self { - Self::_new(rand::random(), on_message) + Self::new_with_id(CHANNEL_COUNTER.fetch_add(1, Ordering::Relaxed), on_message) } - pub(crate) fn _new crate::Result<()> + Send + Sync + 'static>( + pub(crate) fn new_with_id crate::Result<()> + Send + Sync + 'static>( id: u32, on_message: F, ) -> Self { @@ -69,8 +75,8 @@ impl Channel { } pub(crate) fn from_ipc(window: Window, callback: CallbackFn) -> Self { - Channel::_new(callback.0, move |body| { - let data_id = rand::random(); + Channel::new_with_id(callback.0, move |body| { + let data_id = CHANNEL_DATA_COUNTER.fetch_add(1, Ordering::Relaxed); window .state::() .0 diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index e7902fcd61b6..e6304ef046dd 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -185,7 +185,7 @@ pub use runtime::ActivationPolicy; #[cfg(target_os = "macos")] pub use self::utils::TitleBarStyle; -pub use self::event::{Event, EventHandler}; +pub use self::event::{Event, EventId}; pub use { self::app::{ App, AppHandle, AssetResolver, Builder, CloseRequestApi, GlobalWindowEvent, RunEvent, @@ -613,7 +613,7 @@ pub trait Manager: sealed::ManagerBase { /// }) /// .invoke_handler(tauri::generate_handler![synchronize]); /// ``` - fn listen_global(&self, event: impl Into, handler: F) -> EventHandler + fn listen_global(&self, event: impl Into, handler: F) -> EventId where F: Fn(Event) + Send + 'static, { @@ -623,7 +623,7 @@ pub trait Manager: sealed::ManagerBase { /// Listen to a global event only once. /// /// See [`Self::listen_global`] for more information. - fn once_global(&self, event: impl Into, handler: F) -> EventHandler + fn once_global(&self, event: impl Into, handler: F) where F: FnOnce(Event) + Send + 'static, { @@ -678,8 +678,8 @@ pub trait Manager: sealed::ManagerBase { /// Ok(()) /// }); /// ``` - fn unlisten(&self, handler_id: EventHandler) { - self.manager().unlisten(handler_id) + fn unlisten(&self, id: EventId) { + self.manager().unlisten(id) } /// Fetch a single window from the manager. diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index bd5320191840..2a97ee7414a4 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -32,7 +32,7 @@ use crate::{ AppHandle, GlobalWindowEvent, GlobalWindowEventListener, OnPageLoad, PageLoadPayload, UriSchemeResponder, }, - event::{assert_event_name_is_valid, Event, EventHandler, Listeners}, + event::{assert_event_name_is_valid, Event, EventId, Listeners}, ipc::{Invoke, InvokeHandler, InvokeResponder}, pattern::PatternJavascript, plugin::PluginStore, @@ -193,7 +193,9 @@ fn replace_csp_nonce( ) { let mut nonces = Vec::new(); *asset = replace_with_callback(asset, token, || { - let nonce = rand::random::(); + let mut raw = [0u8; 8]; + getrandom::getrandom(&mut raw).expect("failed to get random bytes"); + let nonce = usize::from_ne_bytes(raw); nonces.push(nonce); nonce.to_string() }); @@ -772,9 +774,6 @@ impl WindowManager { ipc_script: &'a str, #[raw] bundle_script: &'a str, - // A function to immediately listen to an event. - #[raw] - listen_function: &'a str, #[raw] core_script: &'a str, #[raw] @@ -807,16 +806,6 @@ impl WindowManager { pattern_script, ipc_script, bundle_script, - listen_function: &format!( - "function listen(eventName, cb) {{ {} }}", - crate::event::listen_js( - self.event_listeners_object_name(), - "eventName".into(), - 0, - None, - "window['_' + window.__TAURI__.transformCallback(cb) ]".into() - ) - ), core_script: &CoreJavascript { os_name: std::env::consts::OS, } @@ -848,47 +837,13 @@ impl WindowManager { }} }}); ", - function = self.event_emit_function_name(), - listeners = self.event_listeners_object_name() + function = self.listeners().function_name(), + listeners = self.listeners().listeners_object_name() ) } -} - -#[cfg(test)] -mod test { - use crate::{generate_context, plugin::PluginStore, StateManager, Wry}; - - use super::WindowManager; - - #[test] - fn check_get_url() { - let context = generate_context!("test/fixture/src-tauri/tauri.conf.json", crate); - let manager: WindowManager = WindowManager::with_handlers( - context, - PluginStore::default(), - Box::new(|_| false), - Box::new(|_, _| ()), - Default::default(), - StateManager::new(), - Default::default(), - Default::default(), - (None, "".into()), - ); - - #[cfg(custom_protocol)] - { - assert_eq!( - manager.get_url().to_string(), - if cfg!(windows) || cfg!(target_os = "android") { - "http://tauri.localhost/" - } else { - "tauri://localhost" - } - ); - } - #[cfg(dev)] - assert_eq!(manager.get_url().to_string(), "http://localhost:4000/"); + pub(crate) fn listeners(&self) -> &Listeners { + &self.inner.listeners } } @@ -1194,13 +1149,9 @@ impl WindowManager { &self.inner.package_info } - pub fn unlisten(&self, handler_id: EventHandler) { - self.inner.listeners.unlisten(handler_id) - } - pub fn trigger(&self, event: &str, window: Option, data: Option) { assert_event_name_is_valid(event); - self.inner.listeners.trigger(event, window, data) + self.listeners().trigger(event, window, data) } pub fn listen( @@ -1208,9 +1159,9 @@ impl WindowManager { event: String, window: Option, handler: F, - ) -> EventHandler { + ) -> EventId { assert_event_name_is_valid(&event); - self.inner.listeners.listen(event, window, handler) + self.listeners().listen(event, window, handler) } pub fn once( @@ -1218,17 +1169,13 @@ impl WindowManager { event: String, window: Option, handler: F, - ) -> EventHandler { + ) { assert_event_name_is_valid(&event); - self.inner.listeners.once(event, window, handler) + self.listeners().once(event, window, handler) } - pub fn event_listeners_object_name(&self) -> String { - self.inner.listeners.listeners_object_name() - } - - pub fn event_emit_function_name(&self) -> String { - self.inner.listeners.function_name() + pub fn unlisten(&self, id: EventId) { + self.listeners().unlisten(id) } pub fn get_window(&self, label: &str) -> Option> { @@ -1351,3 +1298,41 @@ mod tests { } } } + +#[cfg(test)] +mod test { + use crate::{generate_context, plugin::PluginStore, StateManager, Wry}; + + use super::WindowManager; + + #[test] + fn check_get_url() { + let context = generate_context!("test/fixture/src-tauri/tauri.conf.json", crate); + let manager: WindowManager = WindowManager::with_handlers( + context, + PluginStore::default(), + Box::new(|_| false), + Box::new(|_, _| ()), + Default::default(), + StateManager::new(), + Default::default(), + Default::default(), + (None, "".into()), + ); + + #[cfg(custom_protocol)] + { + assert_eq!( + manager.get_url().to_string(), + if cfg!(windows) || cfg!(target_os = "android") { + "http://tauri.localhost/" + } else { + "tauri://localhost" + } + ); + } + + #[cfg(dev)] + assert_eq!(manager.get_url().to_string(), "http://localhost:4000/"); + } +} diff --git a/core/tauri/src/plugin/mobile.rs b/core/tauri/src/plugin/mobile.rs index 4aa726e3609b..4e7317db5256 100644 --- a/core/tauri/src/plugin/mobile.rs +++ b/core/tauri/src/plugin/mobile.rs @@ -11,6 +11,9 @@ use crate::{ sealed::{ManagerBase, RuntimeOrDispatch}, }; +#[cfg(mobile)] +use std::sync::atomic::{AtomicI32, Ordering}; + use once_cell::sync::OnceCell; use serde::{de::DeserializeOwned, Serialize}; @@ -24,6 +27,8 @@ type PluginResponse = Result; type PendingPluginCallHandler = Box; +#[cfg(mobile)] +static PENDING_PLUGIN_CALLS_ID: AtomicI32 = AtomicI32::new(0); static PENDING_PLUGIN_CALLS: OnceCell>> = OnceCell::new(); static CHANNELS: OnceCell>> = OnceCell::new(); @@ -315,7 +320,7 @@ pub(crate) fn run_command, F: FnOnce(PluginResponse) + os::raw::{c_char, c_int, c_ulonglong}, }; - let id: i32 = rand::random(); + let id: i32 = PENDING_PLUGIN_CALLS_ID.fetch_add(1, Ordering::Relaxed); PENDING_PLUGIN_CALLS .get_or_init(Default::default) .lock() @@ -434,7 +439,7 @@ pub(crate) fn run_command< _ => unreachable!(), }; - let id: i32 = rand::random(); + let id: i32 = PENDING_PLUGIN_CALLS_ID.fetch_add(1, Ordering::Relaxed); let plugin_name = name.to_string(); let command = command.as_ref().to_string(); let handle_ = handle.clone(); diff --git a/core/tauri/src/protocol/asset.rs b/core/tauri/src/protocol/asset.rs index 449a8a03e5cf..afd5b02c914d 100644 --- a/core/tauri/src/protocol/asset.rs +++ b/core/tauri/src/protocol/asset.rs @@ -5,7 +5,6 @@ use crate::{path::SafePathBuf, scope, window::UriSchemeProtocolHandler}; use http::{header::*, status::StatusCode, Request, Response}; use http_range::HttpRange; -use rand::RngCore; use std::{borrow::Cow, io::SeekFrom}; use tauri_utils::debug_eprintln; use tauri_utils::mime_type::MimeType; @@ -227,7 +226,7 @@ fn get_response( fn random_boundary() -> String { let mut x = [0_u8; 30]; - rand::thread_rng().fill_bytes(&mut x); + getrandom::getrandom(&mut x).expect("failed to get random bytes"); (x[..]) .iter() .map(|&x| format!("{x:x}")) diff --git a/core/tauri/src/scope/fs.rs b/core/tauri/src/scope/fs.rs index 3d1f57b0e96a..498c9b73c2ed 100644 --- a/core/tauri/src/scope/fs.rs +++ b/core/tauri/src/scope/fs.rs @@ -6,11 +6,15 @@ use std::{ collections::{HashMap, HashSet}, fmt, path::{Path, PathBuf, MAIN_SEPARATOR}, - sync::{Arc, Mutex}, + sync::{ + atomic::{AtomicU32, Ordering}, + Arc, Mutex, + }, }; use tauri_utils::config::FsScope; -use uuid::Uuid; + +use crate::ScopeEventId; pub use glob::Pattern; @@ -30,8 +34,15 @@ type EventListener = Box; pub struct Scope { allowed_patterns: Arc>>, forbidden_patterns: Arc>>, - event_listeners: Arc>>, + event_listeners: Arc>>, match_options: glob::MatchOptions, + next_event_id: Arc, +} + +impl Scope { + fn next_event_id(&self) -> u32 { + self.next_event_id.fetch_add(1, Ordering::Relaxed) + } } impl fmt::Debug for Scope { @@ -118,6 +129,7 @@ impl Scope { allowed_patterns: Arc::new(Mutex::new(allowed_patterns)), forbidden_patterns: Arc::new(Mutex::new(forbidden_patterns)), event_listeners: Default::default(), + next_event_id: Default::default(), match_options: glob::MatchOptions { // this is needed so `/dir/*` doesn't match files within subdirectories such as `/dir/subdir/file.txt` // see: https://github.com/tauri-apps/tauri/security/advisories/GHSA-6mv3-wm7j-h4w5 @@ -139,12 +151,35 @@ impl Scope { } /// Listen to an event on this scope. - pub fn listen(&self, f: F) -> Uuid { - let id = Uuid::new_v4(); - self.event_listeners.lock().unwrap().insert(id, Box::new(f)); + pub fn listen(&self, f: F) -> ScopeEventId { + let id = self.next_event_id(); + self.listen_with_id(id, f); id } + fn listen_with_id(&self, id: ScopeEventId, f: F) { + self.event_listeners.lock().unwrap().insert(id, Box::new(f)); + } + + /// Listen to an event on this scope and immediately unlisten. + pub fn once(&self, f: F) { + let listerners = self.event_listeners.clone(); + let handler = std::cell::Cell::new(Some(f)); + let id = self.next_event_id(); + self.listen_with_id(id, move |event| { + listerners.lock().unwrap().remove(&id); + let handler = handler + .take() + .expect("attempted to call handler more than once"); + handler(event) + }); + } + + /// Removes an event listener on this scope. + pub fn unlisten(&self, id: ScopeEventId) { + self.event_listeners.lock().unwrap().remove(&id); + } + fn trigger(&self, event: Event) { let listeners = self.event_listeners.lock().unwrap(); let handlers = listeners.values(); @@ -276,6 +311,7 @@ mod tests { allowed_patterns: Default::default(), forbidden_patterns: Default::default(), event_listeners: Default::default(), + next_event_id: Default::default(), match_options: glob::MatchOptions { // this is needed so `/dir/*` doesn't match files within subdirectories such as `/dir/subdir/file.txt` // see: https://github.com/tauri-apps/tauri/security/advisories/GHSA-6mv3-wm7j-h4w5 diff --git a/core/tauri/src/scope/mod.rs b/core/tauri/src/scope/mod.rs index d69fbc07abde..fbb886fb4b5e 100644 --- a/core/tauri/src/scope/mod.rs +++ b/core/tauri/src/scope/mod.rs @@ -9,6 +9,9 @@ pub mod ipc; use std::path::Path; +/// Unique id of a scope event. +pub type ScopeEventId = u32; + /// Managed state for all the core scopes in a tauri application. pub struct Scopes { pub(crate) ipc: ipc::Scope, diff --git a/core/tauri/src/test/mock_runtime.rs b/core/tauri/src/test/mock_runtime.rs index b466de798ec0..df2cbc60d052 100644 --- a/core/tauri/src/test/mock_runtime.rs +++ b/core/tauri/src/test/mock_runtime.rs @@ -13,14 +13,13 @@ use tauri_runtime::{ CursorIcon, DetachedWindow, PendingWindow, RawWindow, WindowEvent, }, DeviceEventFilter, Dispatch, Error, EventLoopProxy, ExitRequestedEventAction, Icon, Result, - RunEvent, Runtime, RuntimeHandle, RuntimeInitArgs, UserAttentionType, UserEvent, + RunEvent, Runtime, RuntimeHandle, RuntimeInitArgs, UserAttentionType, UserEvent, WindowEventId, }; #[cfg(target_os = "macos")] use tauri_utils::TitleBarStyle; use tauri_utils::{config::WindowConfig, Theme}; use url::Url; -use uuid::Uuid; #[cfg(windows)] use windows::Win32::Foundation::HWND; @@ -30,14 +29,14 @@ use std::{ collections::HashMap, fmt, sync::{ - atomic::{AtomicBool, Ordering}, + atomic::{AtomicBool, AtomicU32, Ordering}, mpsc::{channel, sync_channel, Receiver, SyncSender}, Arc, Mutex, }, }; type ShortcutMap = HashMap>; -type WindowId = usize; +type WindowId = u32; enum Message { Task(Box), @@ -52,6 +51,8 @@ pub struct RuntimeContext { windows: Arc>>, shortcuts: Arc>, run_tx: SyncSender, + next_window_id: Arc, + next_window_event_id: Arc, } // SAFETY: we ensure this type is only used on the main thread. @@ -79,6 +80,14 @@ impl RuntimeContext { Ok(()) } } + + fn next_window_id(&self) -> WindowId { + self.next_window_id.fetch_add(1, Ordering::Relaxed) + } + + fn next_window_event_id(&self) -> WindowEventId { + self.next_window_event_id.fetch_add(1, Ordering::Relaxed) + } } impl fmt::Debug for RuntimeContext { @@ -105,7 +114,7 @@ impl RuntimeHandle for MockRuntimeHandle { pending: PendingWindow, _before_webview_creation: Option, ) -> Result> { - let id = rand::random(); + let id = self.context.next_window_id(); self.context.windows.borrow_mut().insert(id, Window); Ok(DetachedWindow { label: pending.label, @@ -346,8 +355,8 @@ impl Dispatch for MockDispatcher { self.context.send_message(Message::Task(Box::new(f))) } - fn on_window_event(&self, f: F) -> Uuid { - Uuid::new_v4() + fn on_window_event(&self, f: F) -> WindowEventId { + self.context.next_window_event_id() } fn with_webview) + Send + 'static>(&self, f: F) -> Result<()> { @@ -516,7 +525,7 @@ impl Dispatch for MockDispatcher { pending: PendingWindow, _before_webview_creation: Option, ) -> Result> { - let id = rand::random(); + let id = self.context.next_window_id(); self.context.windows.borrow_mut().insert(id, Window); Ok(DetachedWindow { label: pending.label, @@ -694,6 +703,8 @@ impl MockRuntime { windows: Default::default(), shortcuts: Default::default(), run_tx: tx, + next_window_id: Default::default(), + next_window_event_id: Default::default(), }; Self { is_running, @@ -732,7 +743,7 @@ impl Runtime for MockRuntime { pending: PendingWindow, _before_webview_creation: Option, ) -> Result> { - let id = rand::random(); + let id = self.context.next_window_id(); self.context.windows.borrow_mut().insert(id, Window); Ok(DetachedWindow { label: pending.label, diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index e0c35bcb9ad2..6673dbbae0ad 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -13,7 +13,7 @@ use crate::TitleBarStyle; use crate::{ app::{AppHandle, UriSchemeResponder}, command::{CommandArg, CommandItem}, - event::{Event, EventHandler}, + event::{Event, EventId}, ipc::{ CallbackFn, Invoke, InvokeBody, InvokeError, InvokeMessage, InvokeResolver, OwnedInvokeResponder, @@ -895,7 +895,7 @@ pub struct Window { /// The manager to associate this webview window with. pub(crate) manager: WindowManager, pub(crate) app_handle: AppHandle, - js_event_listeners: Arc>>>, + js_event_listeners: Arc>>>, // The menu set for this window #[cfg(desktop)] pub(crate) menu: Arc>>>, @@ -2228,15 +2228,15 @@ impl Window { window_label: Option, event: String, handler: CallbackFn, - ) -> crate::Result { - let event_id = rand::random(); + ) -> crate::Result { + let event_id = self.manager.listeners().next_event_id(); self.eval(&crate::event::listen_js( - self.manager().event_listeners_object_name(), - format!("'{}'", event), + self.manager().listeners().listeners_object_name(), + &format!("'{}'", event), event_id, - window_label.clone(), - format!("window['_{}']", handler.0), + window_label.as_deref(), + &format!("window['_{}']", handler.0), ))?; self @@ -2254,9 +2254,9 @@ impl Window { } /// Unregister a JS event listener. - pub(crate) fn unlisten_js(&self, event: String, id: usize) -> crate::Result<()> { + pub(crate) fn unlisten_js(&self, event: &str, id: EventId) -> crate::Result<()> { self.eval(&crate::event::unlisten_js( - self.manager().event_listeners_object_name(), + self.manager().listeners().listeners_object_name(), event, id, ))?; @@ -2425,7 +2425,7 @@ impl Window { ) -> crate::Result<()> { self.eval(&format!( "(function () {{ const fn = window['{}']; fn && fn({{event: {}, windowLabel: {}, payload: {}}}) }})()", - self.manager.event_emit_function_name(), + self.manager.listeners().function_name(), serde_json::to_string(event)?, serde_json::to_string(&source_window_label)?, serde_json::to_value(payload)?, @@ -2477,7 +2477,7 @@ impl Window { /// Ok(()) /// }); /// ``` - pub fn listen(&self, event: impl Into, handler: F) -> EventHandler + pub fn listen(&self, event: impl Into, handler: F) -> EventId where F: Fn(Event) + Send + 'static, { @@ -2510,14 +2510,14 @@ impl Window { /// Ok(()) /// }); /// ``` - pub fn unlisten(&self, handler_id: EventHandler) { - self.manager.unlisten(handler_id) + pub fn unlisten(&self, id: EventId) { + self.manager.unlisten(id) } /// Listen to an event on this window a single time. /// /// See [`Self::listen`] for more information. - pub fn once(&self, event: impl Into, handler: F) -> EventHandler + pub fn once(&self, event: impl Into, handler: F) where F: FnOnce(Event) + Send + 'static, { diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index d5d2857f07e2..b6a7bc8271b1 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -3399,7 +3399,7 @@ checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" [[package]] name = "tauri" -version = "2.0.0-alpha.14" +version = "2.0.0-alpha.15" dependencies = [ "anyhow", "bytes", @@ -3407,6 +3407,7 @@ dependencies = [ "dirs-next", "embed_plist", "futures-util", + "getrandom 0.2.10", "glib", "glob", "gtk", @@ -3424,7 +3425,6 @@ dependencies = [ "once_cell", "percent-encoding", "png", - "rand 0.8.5", "raw-window-handle", "reqwest", "serde", @@ -3450,7 +3450,7 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.0.0-alpha.8" +version = "2.0.0-alpha.9" dependencies = [ "anyhow", "cargo_toml", @@ -3470,7 +3470,7 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.0.0-alpha.7" +version = "2.0.0-alpha.8" dependencies = [ "base64", "brotli", @@ -3494,7 +3494,7 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.0.0-alpha.7" +version = "2.0.0-alpha.8" dependencies = [ "heck", "proc-macro2", @@ -3550,7 +3550,7 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "1.0.0-alpha.1" +version = "1.0.0-alpha.2" dependencies = [ "gtk", "http", @@ -3561,24 +3561,21 @@ dependencies = [ "tauri-utils", "thiserror", "url", - "uuid", "windows", ] [[package]] name = "tauri-runtime-wry" -version = "1.0.0-alpha.2" +version = "1.0.0-alpha.3" dependencies = [ "cocoa 0.24.1", "gtk", "http", "jni", "percent-encoding", - "rand 0.8.5", "raw-window-handle", "tauri-runtime", "tauri-utils", - "uuid", "webkit2gtk", "webview2-com", "windows", @@ -3587,7 +3584,7 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "2.0.0-alpha.7" +version = "2.0.0-alpha.8" dependencies = [ "aes-gcm", "brotli",