mod listener;
pub(crate) mod plugin;
use std::{convert::Infallible, str::FromStr};
pub(crate) use listener::Listeners;
use serde::{Deserialize, Serialize};
mod event_name;
pub(crate) use event_name::EventName;
use crate::ipc::CallbackFn;
pub type EventId = u32;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
#[serde(tag = "kind")]
#[non_exhaustive]
pub enum EventTarget {
Any,
AnyLabel {
label: String,
},
App,
Window {
label: String,
},
Webview {
label: String,
},
WebviewWindow {
label: String,
},
}
impl EventTarget {
pub fn any() -> Self {
Self::Any
}
pub fn app() -> Self {
Self::App
}
pub fn labeled(label: impl Into<String>) -> Self {
Self::AnyLabel {
label: label.into(),
}
}
pub fn window(label: impl Into<String>) -> Self {
Self::Window {
label: label.into(),
}
}
pub fn webview(label: impl Into<String>) -> Self {
Self::Webview {
label: label.into(),
}
}
pub fn webview_window(label: impl Into<String>) -> Self {
Self::WebviewWindow {
label: label.into(),
}
}
}
impl<T: AsRef<str>> From<T> for EventTarget {
fn from(value: T) -> Self {
Self::AnyLabel {
label: value.as_ref().to_string(),
}
}
}
impl FromStr for EventTarget {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::AnyLabel {
label: s.to_string(),
})
}
}
#[derive(Clone)]
pub struct EmitArgs {
event: EventName,
payload: String,
}
impl EmitArgs {
pub fn new<S: Serialize>(event: EventName<&str>, payload: &S) -> crate::Result<Self> {
#[cfg(feature = "tracing")]
let _span = tracing::debug_span!("window::emit::serialize").entered();
Ok(EmitArgs {
event: event.into_owned(),
payload: serde_json::to_string(payload)?,
})
}
pub fn new_str(event: EventName<&str>, payload: String) -> crate::Result<Self> {
#[cfg(feature = "tracing")]
let _span = tracing::debug_span!("window::emit::json").entered();
Ok(EmitArgs {
event: event.into_owned(),
payload,
})
}
}
#[derive(Debug, Clone)]
pub struct Event {
id: EventId,
data: String,
}
impl Event {
fn new(id: EventId, data: String) -> Self {
Self { id, data }
}
pub fn id(&self) -> EventId {
self.id
}
pub fn payload(&self) -> &str {
&self.data
}
}
pub(crate) fn listen_js_script(
listeners_object_name: &str,
serialized_target: &str,
event: EventName<&str>,
event_id: EventId,
handler: CallbackFn,
) -> String {
let handler_id = handler.0;
format!(
"(function () {{
if (window['{listeners_object_name}'] === void 0) {{
Object.defineProperty(window, '{listeners_object_name}', {{ value: Object.create(null) }});
}}
if (window['{listeners_object_name}']['{event}'] === void 0) {{
Object.defineProperty(window['{listeners_object_name}'], '{event}', {{ value: Object.create(null) }});
}}
const eventListeners = window['{listeners_object_name}']['{event}']
const listener = {{
target: {serialized_target},
handlerId: {handler_id}
}};
Object.defineProperty(eventListeners, '{event_id}', {{ value: listener, configurable: true }});
}})()
",
)
}
pub(crate) fn emit_js_script(
event_emit_function_name: &str,
emit_args: &EmitArgs,
serialized_ids: &str,
) -> crate::Result<String> {
Ok(format!(
"(function () {{ const fn = window['{}']; fn && fn({{event: '{}', payload: {}}}, {ids}) }})()",
event_emit_function_name,
emit_args.event,
emit_args.payload,
ids = serialized_ids,
))
}
pub(crate) fn unlisten_js_script(
listeners_object_name: &str,
event_arg: &str,
event_id_arg: &str,
) -> String {
format!(
"(function () {{
const listeners = (window['{listeners_object_name}'] || {{}})[{event_arg}]
if (listeners) {{
window.__TAURI_INTERNALS__.unregisterCallback(listeners[{event_id_arg}].handlerId)
}}
}})()
",
)
}
pub(crate) fn event_initialization_script(function_name: &str, listeners: &str) -> String {
format!(
"Object.defineProperty(window, '{function_name}', {{
value: function (eventData, ids) {{
const listeners = (window['{listeners}'] && window['{listeners}'][eventData.event]) || []
for (const id of ids) {{
const listener = listeners[id]
if (listener) {{
eventData.id = id
window.__TAURI_INTERNALS__.runCallback(listener.handlerId, eventData)
}}
}}
}}
}});
"
)
}
#[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);
}
}