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

Co-authored-by: chip reed <chip@chip.sh>
This commit is contained in:
Lucas Fernandes Nogueira 2021-04-27 11:52:12 -03:00 committed by GitHub
parent af6411d5f8
commit 181e132aee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 162 additions and 101 deletions

View File

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

View File

@ -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())
}

View File

@ -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()?,

View File

@ -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();

View File

@ -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()
}
}

View File

@ -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();

View File

@ -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<()> {
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_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<()> {
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()
}

View File

@ -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())?)
}
}

View File

@ -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)
}

View File

@ -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),

View File

@ -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");
});
})