Sfoglia il codice sorgente

refactor(core): simplify usage of app event and window label types (#1623)

Co-authored-by: chip reed <chip@chip.sh>
Lucas Fernandes Nogueira 4 anni fa
parent
commit
181e132aee

+ 5 - 0
.changes/simplify-tag-label-usage.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+Simplify usage of app event and window label types.

+ 3 - 3
core/tauri/src/endpoints/event.rs

@@ -52,12 +52,12 @@ impl Cmd {
         });
 
         // dispatch the event to Rust listeners
-        window.trigger(e.clone(), payload.clone());
+        window.trigger(&e, payload.clone());
 
         if let Some(target) = window_label {
-          window.emit_to(&target, e, payload)?;
+          window.emit_to(&target, &e, payload)?;
         } else {
-          window.emit_all(e, payload)?;
+          window.emit_all(&e, payload)?;
         }
         Ok(().into())
       }

+ 6 - 4
core/tauri/src/endpoints/window.rs

@@ -99,7 +99,7 @@ struct WindowCreatedEvent {
 
 impl Cmd {
   #[allow(dead_code)]
-  pub async fn run<M: Params>(self, window: Window<M>) -> crate::Result<InvokeResponse> {
+  pub async fn run<P: Params>(self, window: Window<P>) -> crate::Result<InvokeResponse> {
     if cfg!(not(window_all)) {
       Err(crate::Error::ApiNotAllowlisted("window > all".to_string()))
     } else {
@@ -114,7 +114,7 @@ impl Cmd {
         Self::CreateWebview { options } => {
           let mut window = window;
           // Panic if the user's `Tag` type decided to return an error while parsing.
-          let label: M::Label = options.label.parse().unwrap_or_else(|_| {
+          let label: P::Label = options.label.parse().unwrap_or_else(|_| {
             panic!(
               "Window module received unknown window label: {}",
               options.label
@@ -124,13 +124,15 @@ impl Cmd {
           let url = options.url.clone();
           let pending =
             crate::runtime::window::PendingWindow::with_config(options, label.clone(), url);
-          window.create_window(pending)?.emit_others_internal(
-            "tauri://window-created".to_string(),
+
+          window.create_window(pending)?.emit_others(
+            &crate::runtime::manager::tauri_event::<P::Event>("tauri://window-created"),
             Some(WindowCreatedEvent {
               label: label.to_string(),
             }),
           )?;
         }
+
         Self::SetResizable { resizable } => window.set_resizable(resizable)?,
         Self::SetTitle { title } => window.set_title(&title)?,
         Self::Maximize => window.maximize()?,

+ 11 - 6
core/tauri/src/event.rs

@@ -2,8 +2,9 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use crate::runtime::tag::Tag;
+use crate::runtime::tag::{Tag, TagRef};
 use std::{
+  borrow::Borrow,
   boxed::Box,
   collections::HashMap,
   fmt,
@@ -134,7 +135,7 @@ impl<Event: Tag, Window: Tag> Listeners<Event, Window> {
       match action {
         Pending::Unlisten(id) => self.unlisten(id),
         Pending::Listen(id, event, handler) => self.listen_(id, event, handler),
-        Pending::Trigger(event, window, payload) => self.trigger(event, window, payload),
+        Pending::Trigger(ref event, window, payload) => self.trigger(event, window, payload),
       }
     }
   }
@@ -191,12 +192,16 @@ impl<Event: Tag, Window: Tag> Listeners<Event, Window> {
   }
 
   /// Triggers the given global event with its payload.
-  pub(crate) fn trigger(&self, event: Event, window: Option<Window>, payload: Option<String>) {
+  pub(crate) fn trigger<E>(&self, event: &E, window: Option<Window>, payload: Option<String>)
+  where
+    E: TagRef<Event> + ?Sized,
+    Event: Borrow<E>,
+  {
     let mut maybe_pending = false;
     match self.inner.handlers.try_lock() {
-      Err(_) => self.insert_pending(Pending::Trigger(event, window, payload)),
+      Err(_) => self.insert_pending(Pending::Trigger(event.to_owned(), window, payload)),
       Ok(lock) => {
-        if let Some(handlers) = lock.get(&event) {
+        if let Some(handlers) = lock.get(event) {
           for (&id, handler) in handlers {
             if window.is_none() || window == handler.window {
               maybe_pending = true;
@@ -280,7 +285,7 @@ mod test {
       // call listen with e and the event_fn dummy func
       listeners.listen(e.clone(), None, event_fn);
       // call on event with e and d.
-      listeners.trigger(e, None, Some(d));
+      listeners.trigger(&e, None, Some(d));
 
       // lock the mutex
       let l = listeners.inner.handlers.lock().unwrap();

+ 34 - 16
core/tauri/src/lib.rs

@@ -39,7 +39,7 @@ pub type SyncTask = Box<dyn FnOnce() + Send>;
 use crate::api::assets::Assets;
 use crate::api::config::Config;
 use crate::event::{Event, EventHandler};
-use crate::runtime::tag::Tag;
+use crate::runtime::tag::{Tag, TagRef};
 use crate::runtime::window::PendingWindow;
 use crate::runtime::{Dispatch, Runtime};
 use serde::Serialize;
@@ -51,10 +51,12 @@ pub use {
   api::config::WindowUrl,
   hooks::{InvokeHandler, InvokeMessage, OnPageLoad, PageLoadPayload, SetupHook},
   runtime::app::{App, Builder},
+  runtime::flavors::wry::Wry,
   runtime::webview::Attributes,
   runtime::window::export::Window,
 };
 
+use std::borrow::Borrow;
 /// Reads the config file at compile time and generates a [`Context`] based on its content.
 ///
 /// The default config file path is a `tauri.conf.json` file inside the Cargo manifest directory of
@@ -130,31 +132,39 @@ pub trait Params: sealed::ParamsBase {
 /// Manages a running application.
 ///
 /// TODO: expand these docs
-pub trait Manager<M: Params>: sealed::ManagerBase<M> {
+pub trait Manager<P: Params>: sealed::ManagerBase<P> {
   /// The [`Config`] the manager was created with.
   fn config(&self) -> &Config {
     self.manager().config()
   }
 
   /// Emits a event to all windows.
-  fn emit_all<S: Serialize + Clone>(&self, event: M::Event, payload: Option<S>) -> Result<()> {
+  fn emit_all<E, S>(&self, event: &E, payload: Option<S>) -> Result<()>
+  where
+    E: TagRef<P::Event> + ?Sized,
+    S: Serialize + Clone,
+  {
     self.manager().emit_filter(event, payload, |_| true)
   }
 
   /// Emits an event to a window with the specified label.
-  fn emit_to<S: Serialize + Clone>(
+  fn emit_to<E, L, S: Serialize + Clone>(
     &self,
-    label: &M::Label,
-    event: M::Event,
+    label: &L,
+    event: &E,
     payload: Option<S>,
-  ) -> Result<()> {
+  ) -> Result<()>
+  where
+    L: TagRef<P::Label> + ?Sized,
+    E: TagRef<P::Event> + ?Sized,
+  {
     self
       .manager()
-      .emit_filter(event, payload, |w| w.label() == label)
+      .emit_filter(event, payload, |w| label == w.label())
   }
 
   /// Creates a new [`Window`] on the [`Runtime`] and attaches it to the [`Manager`].
-  fn create_window(&mut self, pending: PendingWindow<M>) -> Result<Window<M>> {
+  fn create_window(&mut self, pending: PendingWindow<P>) -> Result<Window<P>> {
     use sealed::RuntimeOrDispatch::*;
 
     let labels = self.manager().labels().into_iter().collect::<Vec<_>>();
@@ -167,23 +177,27 @@ pub trait Manager<M: Params>: sealed::ManagerBase<M> {
   }
 
   /// Listen to a global event.
-  fn listen_global<F>(&self, event: M::Event, handler: F) -> EventHandler
+  fn listen_global<E: Into<P::Event>, F>(&self, event: E, handler: F) -> EventHandler
   where
     F: Fn(Event) + Send + 'static,
   {
-    self.manager().listen(event, None, handler)
+    self.manager().listen(event.into(), None, handler)
   }
 
   /// Listen to a global event only once.
-  fn once_global<F>(&self, event: M::Event, handler: F) -> EventHandler
+  fn once_global<E: Into<P::Event>, F>(&self, event: E, handler: F) -> EventHandler
   where
     F: Fn(Event) + Send + 'static,
   {
-    self.manager().once(event, None, handler)
+    self.manager().once(event.into(), None, handler)
   }
 
   /// Trigger a global event.
-  fn trigger_global(&self, event: M::Event, data: Option<String>) {
+  fn trigger_global<E>(&self, event: &E, data: Option<String>)
+  where
+    E: TagRef<P::Event> + ?Sized,
+    P::Event: Borrow<E>,
+  {
     self.manager().trigger(event, None, data)
   }
 
@@ -193,12 +207,16 @@ pub trait Manager<M: Params>: sealed::ManagerBase<M> {
   }
 
   /// Fetch a single window from the manager.
-  fn get_window(&self, label: &M::Label) -> Option<Window<M>> {
+  fn get_window<L>(&self, label: &L) -> Option<Window<P>>
+  where
+    L: TagRef<P::Label> + ?Sized,
+    P::Label: Borrow<L>,
+  {
     self.manager().get_window(label)
   }
 
   /// Fetch all managed windows.
-  fn windows(&self) -> HashMap<M::Label, Window<M>> {
+  fn windows(&self) -> HashMap<P::Label, Window<P>> {
     self.manager().windows()
   }
 }

+ 1 - 1
core/tauri/src/runtime/app.rs

@@ -74,7 +74,7 @@ impl<M: Params> App<M> {
         // invoke the Event `tauri://update` from JS or rust side.
         main_window.listen(
           updater::EVENT_CHECK_UPDATE
-            .parse()
+            .parse::<M::Event>()
             .unwrap_or_else(|_| panic!("bad label")),
           move |_msg| {
             let window = event_window.clone();

+ 50 - 30
core/tauri/src/runtime/manager.rs

@@ -2,6 +2,7 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
+use crate::runtime::tag::TagRef;
 use crate::{
   api::{
     assets::Assets,
@@ -13,7 +14,7 @@ use crate::{
   hooks::{InvokeHandler, InvokeMessage, InvokePayload, OnPageLoad, PageLoadPayload},
   plugin::PluginStore,
   runtime::{
-    tag::{tags_to_javascript_array, Tag, ToJavascript},
+    tag::{tags_to_javascript_array, Tag, ToJsString},
     webview::{Attributes, CustomProtocol, FileDropEvent, FileDropHandler, WebviewRpcHandler},
     window::{DetachedWindow, PendingWindow},
     Dispatch, Icon, Runtime,
@@ -23,6 +24,7 @@ use crate::{
 };
 use serde::Serialize;
 use serde_json::Value as JsonValue;
+use std::borrow::Borrow;
 use std::marker::PhantomData;
 use std::{
   borrow::Cow,
@@ -33,6 +35,20 @@ use std::{
 };
 use uuid::Uuid;
 
+/// Parse a string representing an internal tauri event into [`Params::Event`]
+///
+/// # Panics
+///
+/// This will panic if the `FromStr` implementation of [`Params::Event`] returns an error.
+pub(crate) fn tauri_event<Event: Tag>(tauri_event: &str) -> Event {
+  tauri_event.parse().unwrap_or_else(|_| {
+    panic!(
+      "failed to parse internal tauri event into Params::Event: {}",
+      tauri_event
+    )
+  })
+}
+
 pub struct InnerWindowManager<P: Params> {
   windows: Mutex<HashMap<P::Label, Window<P>>>,
   plugins: Mutex<PluginStore<P>>,
@@ -168,7 +184,7 @@ impl<P: Params> WindowManager<P> {
               window.__TAURI__.__currentWindow = {{ label: {current_window_label} }}
             "#,
         window_labels_array = tags_to_javascript_array(pending_labels)?,
-        current_window_label = label.to_javascript()?,
+        current_window_label = label.to_js_string()?,
       ));
 
     if !attributes.has_icon() {
@@ -281,14 +297,16 @@ impl<P: Params> WindowManager<P> {
         let window = manager.attach_window(window);
         let _ = match event {
           FileDropEvent::Hovered(paths) => {
-            window.emit_internal("tauri://file-drop".to_string(), Some(paths))
-          }
-          FileDropEvent::Dropped(paths) => {
-            window.emit_internal("tauri://file-drop-hover".to_string(), Some(paths))
-          }
-          FileDropEvent::Cancelled => {
-            window.emit_internal("tauri://file-drop-cancelled".to_string(), Some(()))
+            window.emit_internal(&tauri_event::<P::Event>("tauri://file-drop"), Some(paths))
           }
+          FileDropEvent::Dropped(paths) => window.emit_internal(
+            &tauri_event::<P::Event>("tauri://file-drop-hover"),
+            Some(paths),
+          ),
+          FileDropEvent::Cancelled => window.emit_internal(
+            &tauri_event::<P::Event>("tauri://file-drop-cancelled"),
+            Some(()),
+          ),
         };
       });
       true
@@ -474,30 +492,20 @@ impl<P: Params> WindowManager<P> {
 
     window
   }
-  pub fn emit_filter_internal<S: Serialize + Clone, F: Fn(&Window<P>) -> bool>(
-    &self,
-    event: String,
-    payload: Option<S>,
-    filter: F,
-  ) -> crate::Result<()> {
-    self
-      .windows_lock()
-      .values()
-      .filter(|&w| filter(w))
-      .try_for_each(|window| window.emit_internal(event.clone(), payload.clone()))
-  }
-  pub fn emit_filter<S: Serialize + Clone, F: Fn(&Window<P>) -> bool>(
-    &self,
-    event: P::Event,
-    payload: Option<S>,
-    filter: F,
-  ) -> crate::Result<()> {
+
+  pub fn emit_filter<E, S, F>(&self, event: &E, payload: Option<S>, filter: F) -> crate::Result<()>
+  where
+    E: TagRef<P::Event> + ?Sized,
+    S: Serialize + Clone,
+    F: Fn(&Window<P>) -> bool,
+  {
     self
       .windows_lock()
       .values()
       .filter(|&w| filter(w))
-      .try_for_each(|window| window.emit(&event, payload.clone()))
+      .try_for_each(|window| window.emit(event, payload.clone()))
   }
+
   pub fn labels(&self) -> HashSet<P::Label> {
     self.windows_lock().keys().cloned().collect()
   }
@@ -510,9 +518,15 @@ impl<P: Params> WindowManager<P> {
   pub fn unlisten(&self, handler_id: EventHandler) {
     self.inner.listeners.unlisten(handler_id)
   }
-  pub fn trigger(&self, event: P::Event, window: Option<P::Label>, data: Option<String>) {
+
+  pub fn trigger<E>(&self, event: &E, window: Option<P::Label>, data: Option<String>)
+  where
+    E: TagRef<P::Event> + ?Sized,
+    P::Event: Borrow<E>,
+  {
     self.inner.listeners.trigger(event, window, data)
   }
+
   pub fn listen<F: Fn(Event) + Send + 'static>(
     &self,
     event: P::Event,
@@ -563,9 +577,15 @@ impl<P: Params> WindowManager<P> {
       .expect("poisoned salt mutex")
       .remove(&uuid)
   }
-  pub fn get_window(&self, label: &P::Label) -> Option<Window<P>> {
+
+  pub fn get_window<L>(&self, label: &L) -> Option<Window<P>>
+  where
+    L: TagRef<P::Label> + ?Sized,
+    P::Label: Borrow<L>,
+  {
     self.windows_lock().get(label).cloned()
   }
+
   pub fn windows(&self) -> HashMap<P::Label, Window<P>> {
     self.windows_lock().clone()
   }

+ 18 - 4
core/tauri/src/runtime/tag.rs

@@ -85,6 +85,20 @@ impl<T, E: Debug> Tag for T where
 {
 }
 
+/// A reference to a [`Tag`].
+///
+/// * [`Display`] so that we can still convert this tag to a JavaScript string.
+/// * [`ToOwned`] to make sure we can clone it into the owned tag in specific cases.
+/// * [`PartialEq`] so that we can compare refs to the owned tags easily.
+/// * [`Hash`] + [`Eq`] because we want to be able to use a ref as a key to internal hashmaps.
+pub trait TagRef<T: Tag>: Display + ToOwned<Owned = T> + PartialEq<T> + Eq + Hash {}
+
+/// Automatically implement [`TagRef`] for all types that fit the requirements.
+impl<T: Tag, R> TagRef<T> for R where
+  R: Display + ToOwned<Owned = T> + PartialEq<T> + Eq + Hash + ?Sized
+{
+}
+
 /// Private helper to turn [`Tag`] related things into JavaScript, safely.
 ///
 /// The main concern is string escaping, so we rely on [`serde_json`] to handle all serialization
@@ -93,13 +107,13 @@ impl<T, E: Debug> Tag for T where
 ///
 /// We don't want downstream users to implement this trait so that [`Tag`]s cannot be turned into
 /// invalid JavaScript - regardless of their content.
-pub(crate) trait ToJavascript {
-  fn to_javascript(&self) -> crate::Result<String>;
+pub(crate) trait ToJsString {
+  fn to_js_string(&self) -> crate::Result<String>;
 }
 
-impl<T: Tag> ToJavascript for T {
+impl<D: Display> ToJsString for D {
   /// Turn any [`Tag`] into the JavaScript representation of a string.
-  fn to_javascript(&self) -> crate::Result<String> {
+  fn to_js_string(&self) -> crate::Result<String> {
     Ok(serde_json::to_string(&self.to_string())?)
   }
 }

+ 26 - 28
core/tauri/src/runtime/window.rs

@@ -9,7 +9,7 @@ use crate::{
   event::{Event, EventHandler},
   hooks::{InvokeMessage, InvokePayload, PageLoadPayload},
   runtime::{
-    tag::ToJavascript,
+    tag::ToJsString,
     webview::{FileDropHandler, WebviewRpcHandler},
     Dispatch, Runtime,
   },
@@ -107,7 +107,8 @@ impl<M: Params> PartialEq for DetachedWindow<M> {
 /// We want to export the runtime related window at the crate root, but not look like a re-export.
 pub(crate) mod export {
   use super::*;
-  use crate::runtime::manager::WindowManager;
+  use crate::runtime::{manager::WindowManager, tag::TagRef};
+  use std::borrow::Borrow;
 
   /// A webview window managed by Tauri.
   ///
@@ -195,11 +196,11 @@ pub(crate) mod export {
       &self.window.label
     }
 
-    pub(crate) fn emit_internal<E: ToJavascript, S: Serialize>(
-      &self,
-      event: E,
-      payload: Option<S>,
-    ) -> crate::Result<()> {
+    pub(crate) fn emit_internal<E, S>(&self, event: &E, payload: Option<S>) -> crate::Result<()>
+    where
+      E: TagRef<P::Event> + ?Sized,
+      S: Serialize,
+    {
       let js_payload = match payload {
         Some(payload_value) => serde_json::to_value(payload_value)?,
         None => JsonValue::Null,
@@ -208,7 +209,7 @@ pub(crate) mod export {
       self.eval(&format!(
         "window['{}']({{event: {}, payload: {}}}, '{}')",
         self.manager.event_emit_function_name(),
-        event.to_javascript()?,
+        event.to_js_string()?,
         js_payload,
         self.manager.generate_salt(),
       ))?;
@@ -217,50 +218,47 @@ pub(crate) mod export {
     }
 
     /// Emits an event to the current window.
-    pub fn emit<S: Serialize>(&self, event: &P::Event, payload: Option<S>) -> crate::Result<()> {
-      self.emit_internal(event.clone(), payload)
-    }
-
-    #[allow(dead_code)]
-    pub(crate) fn emit_others_internal<S: Serialize + Clone>(
-      &self,
-      event: String,
-      payload: Option<S>,
-    ) -> crate::Result<()> {
-      self
-        .manager
-        .emit_filter_internal(event, payload, |w| w != self)
+    pub fn emit<E, S>(&self, event: &E, payload: Option<S>) -> crate::Result<()>
+    where
+      E: TagRef<P::Event> + ?Sized,
+      S: Serialize,
+    {
+      self.emit_internal(event, payload)
     }
 
     /// Emits an event on all windows except this one.
-    pub fn emit_others<S: Serialize + Clone>(
+    pub fn emit_others<E: TagRef<P::Event> + ?Sized, S: Serialize + Clone>(
       &self,
-      event: P::Event,
+      event: &E,
       payload: Option<S>,
     ) -> crate::Result<()> {
       self.manager.emit_filter(event, payload, |w| w != self)
     }
 
     /// Listen to an event on this window.
-    pub fn listen<F>(&self, event: P::Event, handler: F) -> EventHandler
+    pub fn listen<E: Into<P::Event>, F>(&self, event: E, handler: F) -> EventHandler
     where
       F: Fn(Event) + Send + 'static,
     {
       let label = self.window.label.clone();
-      self.manager.listen(event, Some(label), handler)
+      self.manager.listen(event.into(), Some(label), handler)
     }
 
     /// Listen to a an event on this window a single time.
-    pub fn once<F>(&self, event: P::Event, handler: F) -> EventHandler
+    pub fn once<E: Into<P::Event>, F>(&self, event: E, handler: F) -> EventHandler
     where
       F: Fn(Event) + Send + 'static,
     {
       let label = self.window.label.clone();
-      self.manager.once(event, Some(label), handler)
+      self.manager.once(event.into(), Some(label), handler)
     }
 
     /// Triggers an event on this window.
-    pub fn trigger(&self, event: P::Event, data: Option<String>) {
+    pub fn trigger<E>(&self, event: &E, data: Option<String>)
+    where
+      E: TagRef<P::Event> + ?Sized,
+      P::Event: Borrow<E>,
+    {
       let label = self.window.label.clone();
       self.manager.trigger(event, Some(label), data)
     }

+ 6 - 7
core/tauri/src/updater/mod.rs

@@ -339,6 +339,7 @@ mod error;
 
 pub use self::error::Error;
 
+use crate::runtime::manager::tauri_event;
 use crate::{
   api::{
     app::restart_application,
@@ -434,7 +435,7 @@ pub(crate) fn listener<M: Params>(
   // Wait to receive the event `"tauri://update"`
   window.listen(
     EVENT_CHECK_UPDATE
-      .parse()
+      .parse::<M::Event>()
       .unwrap_or_else(|_| panic!("bad label")),
     move |_msg| {
       let window = isolated_window.clone();
@@ -468,9 +469,7 @@ pub(crate) fn listener<M: Params>(
 
               // Emit `tauri://update-available`
               let _ = window.emit(
-                &EVENT_UPDATE_AVAILABLE
-                  .parse()
-                  .unwrap_or_else(|_| panic!("bad label")),
+                &tauri_event::<M::Event>(EVENT_UPDATE_AVAILABLE),
                 Some(UpdateManifest {
                   body,
                   date: updater.date.clone(),
@@ -481,7 +480,7 @@ pub(crate) fn listener<M: Params>(
               // Listen for `tauri://update-install`
               window.once(
                 EVENT_INSTALL_UPDATE
-                  .parse()
+                  .parse::<M::Event>()
                   .unwrap_or_else(|_| panic!("bad label")),
                 move |_msg| {
                   let window = window_isolation.clone();
@@ -524,8 +523,8 @@ pub(crate) fn listener<M: Params>(
 
 // Send a status update via `tauri://update-status` event.
 fn send_status_update<M: Params>(window: Window<M>, status: &str, error: Option<String>) {
-  let _ = window.emit_internal(
-    EVENT_STATUS_UPDATE.to_string(),
+  let _ = window.emit(
+    &tauri_event::<M::Event>(EVENT_STATUS_UPDATE),
     Some(StatusEvent {
       error,
       status: String::from(status),

+ 2 - 2
examples/api/src-tauri/src/main.rs

@@ -20,14 +20,14 @@ fn main() {
   tauri::Builder::default()
     .on_page_load(|window, _| {
       let window_ = window.clone();
-      window.listen("js-event".into(), move |event| {
+      window.listen("js-event", move |event| {
         println!("got js-event with message '{:?}'", event.payload());
         let reply = Reply {
           data: "something else".to_string(),
         };
 
         window_
-          .emit(&"rust-event".into(), Some(reply))
+          .emit("rust-event", Some(reply))
           .expect("failed to emit");
       });
     })