123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889 |
- // Copyright 2019-2023 Tauri Programme within The Commons Conservancy
- // SPDX-License-Identifier: Apache-2.0
- // SPDX-License-Identifier: MIT
- use std::{
- borrow::Cow,
- collections::HashMap,
- fmt,
- sync::{Arc, Mutex, MutexGuard},
- };
- use serde::Serialize;
- use url::Url;
- use tauri_macros::default_runtime;
- use tauri_utils::debug_eprintln;
- use tauri_utils::{
- assets::{AssetKey, CspHash},
- config::{Csp, CspDirectiveSources},
- html::{SCRIPT_NONCE_TOKEN, STYLE_NONCE_TOKEN},
- };
- use crate::{
- app::{AppHandle, GlobalWebviewEventListener, GlobalWindowEventListener, OnPageLoad},
- event::{assert_event_name_is_valid, Event, EventId, EventTarget, Listeners},
- ipc::{Invoke, InvokeHandler, InvokeResponder, RuntimeAuthority},
- plugin::PluginStore,
- utils::{assets::Assets, config::Config, PackageInfo},
- Context, Pattern, Runtime, StateManager, Window,
- };
- use crate::{event::EmitArgs, resources::ResourceTable, Webview};
- #[cfg(desktop)]
- mod menu;
- #[cfg(all(desktop, feature = "tray-icon"))]
- mod tray;
- pub mod webview;
- pub mod window;
- #[derive(Default)]
- /// Spaced and quoted Content-Security-Policy hash values.
- struct CspHashStrings {
- script: Vec<String>,
- style: Vec<String>,
- }
- /// Sets the CSP value to the asset HTML if needed (on Linux).
- /// Returns the CSP string for access on the response header (on Windows and macOS).
- #[allow(clippy::borrowed_box)]
- fn set_csp<R: Runtime>(
- asset: &mut String,
- assets: &Box<dyn Assets>,
- asset_path: &AssetKey,
- manager: &AppManager<R>,
- csp: Csp,
- ) -> String {
- let mut csp = csp.into();
- let hash_strings =
- assets
- .csp_hashes(asset_path)
- .fold(CspHashStrings::default(), |mut acc, hash| {
- match hash {
- CspHash::Script(hash) => {
- acc.script.push(hash.into());
- }
- CspHash::Style(hash) => {
- acc.style.push(hash.into());
- }
- _csp_hash => {
- debug_eprintln!("Unknown CspHash variant encountered: {:?}", _csp_hash);
- }
- }
- acc
- });
- let dangerous_disable_asset_csp_modification = &manager
- .config()
- .app
- .security
- .dangerous_disable_asset_csp_modification;
- if dangerous_disable_asset_csp_modification.can_modify("script-src") {
- replace_csp_nonce(
- asset,
- SCRIPT_NONCE_TOKEN,
- &mut csp,
- "script-src",
- hash_strings.script,
- );
- }
- if dangerous_disable_asset_csp_modification.can_modify("style-src") {
- replace_csp_nonce(
- asset,
- STYLE_NONCE_TOKEN,
- &mut csp,
- "style-src",
- hash_strings.style,
- );
- }
- #[cfg(feature = "isolation")]
- if let Pattern::Isolation { schema, .. } = &*manager.pattern {
- let default_src = csp
- .entry("default-src".into())
- .or_insert_with(Default::default);
- default_src.push(crate::pattern::format_real_schema(schema));
- }
- Csp::DirectiveMap(csp).to_string()
- }
- // inspired by https://github.com/rust-lang/rust/blob/1be5c8f90912c446ecbdc405cbc4a89f9acd20fd/library/alloc/src/str.rs#L260-L297
- fn replace_with_callback<F: FnMut() -> String>(
- original: &str,
- pattern: &str,
- mut replacement: F,
- ) -> String {
- let mut result = String::new();
- let mut last_end = 0;
- for (start, part) in original.match_indices(pattern) {
- result.push_str(unsafe { original.get_unchecked(last_end..start) });
- result.push_str(&replacement());
- last_end = start + part.len();
- }
- result.push_str(unsafe { original.get_unchecked(last_end..original.len()) });
- result
- }
- fn replace_csp_nonce(
- asset: &mut String,
- token: &str,
- csp: &mut HashMap<String, CspDirectiveSources>,
- directive: &str,
- hashes: Vec<String>,
- ) {
- let mut nonces = Vec::new();
- *asset = replace_with_callback(asset, token, || {
- #[cfg(target_pointer_width = "64")]
- let mut raw = [0u8; 8];
- #[cfg(target_pointer_width = "32")]
- let mut raw = [0u8; 4];
- #[cfg(target_pointer_width = "16")]
- let mut raw = [0u8; 2];
- getrandom::getrandom(&mut raw).expect("failed to get random bytes");
- let nonce = usize::from_ne_bytes(raw);
- nonces.push(nonce);
- nonce.to_string()
- });
- if !(nonces.is_empty() && hashes.is_empty()) {
- let nonce_sources = nonces
- .into_iter()
- .map(|n| format!("'nonce-{n}'"))
- .collect::<Vec<String>>();
- let sources = csp.entry(directive.into()).or_default();
- let self_source = "'self'".to_string();
- if !sources.contains(&self_source) {
- sources.push(self_source);
- }
- sources.extend(nonce_sources);
- sources.extend(hashes);
- }
- }
- /// A resolved asset.
- pub struct Asset {
- /// The asset bytes.
- pub bytes: Vec<u8>,
- /// The asset's mime type.
- pub mime_type: String,
- /// The `Content-Security-Policy` header value.
- pub csp_header: Option<String>,
- }
- #[default_runtime(crate::Wry, wry)]
- pub struct AppManager<R: Runtime> {
- pub runtime_authority: RuntimeAuthority,
- pub window: window::WindowManager<R>,
- pub webview: webview::WebviewManager<R>,
- #[cfg(all(desktop, feature = "tray-icon"))]
- pub tray: tray::TrayManager<R>,
- #[cfg(desktop)]
- pub menu: menu::MenuManager<R>,
- pub(crate) plugins: Mutex<PluginStore<R>>,
- pub listeners: Listeners,
- pub state: Arc<StateManager>,
- pub config: Config,
- pub assets: Box<dyn Assets>,
- pub app_icon: Option<Vec<u8>>,
- pub package_info: PackageInfo,
- /// Application pattern.
- pub pattern: Arc<Pattern>,
- /// Application Resources Table
- pub(crate) resources_table: Arc<Mutex<ResourceTable>>,
- }
- impl<R: Runtime> fmt::Debug for AppManager<R> {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- let mut d = f.debug_struct("AppManager");
- d.field("window", &self.window)
- .field("plugins", &self.plugins)
- .field("state", &self.state)
- .field("config", &self.config)
- .field("app_icon", &self.app_icon)
- .field("package_info", &self.package_info)
- .field("pattern", &self.pattern);
- #[cfg(all(desktop, feature = "tray-icon"))]
- {
- d.field("tray", &self.tray);
- }
- d.finish()
- }
- }
- impl<R: Runtime> AppManager<R> {
- #[allow(clippy::too_many_arguments, clippy::type_complexity)]
- pub(crate) fn with_handlers(
- #[allow(unused_mut)] mut context: Context<impl Assets>,
- plugins: PluginStore<R>,
- invoke_handler: Box<InvokeHandler<R>>,
- on_page_load: Option<Arc<OnPageLoad<R>>>,
- uri_scheme_protocols: HashMap<String, Arc<webview::UriSchemeProtocol<R>>>,
- state: StateManager,
- window_event_listeners: Vec<GlobalWindowEventListener<R>>,
- webiew_event_listeners: Vec<GlobalWebviewEventListener<R>>,
- #[cfg(desktop)] window_menu_event_listeners: HashMap<
- String,
- crate::app::GlobalMenuEventListener<Window<R>>,
- >,
- (invoke_responder, invoke_initialization_script): (Option<Arc<InvokeResponder<R>>>, String),
- ) -> Self {
- // generate a random isolation key at runtime
- #[cfg(feature = "isolation")]
- if let Pattern::Isolation { ref mut key, .. } = &mut context.pattern {
- *key = uuid::Uuid::new_v4().to_string();
- }
- Self {
- runtime_authority: RuntimeAuthority::new(context.resolved_acl),
- window: window::WindowManager {
- windows: Mutex::default(),
- default_icon: context.default_window_icon,
- event_listeners: Arc::new(window_event_listeners),
- },
- webview: webview::WebviewManager {
- webviews: Mutex::default(),
- invoke_handler,
- on_page_load,
- uri_scheme_protocols: Mutex::new(uri_scheme_protocols),
- event_listeners: Arc::new(webiew_event_listeners),
- invoke_responder,
- invoke_initialization_script,
- },
- #[cfg(all(desktop, feature = "tray-icon"))]
- tray: tray::TrayManager {
- icon: context.tray_icon,
- icons: Default::default(),
- global_event_listeners: Default::default(),
- event_listeners: Default::default(),
- },
- #[cfg(desktop)]
- menu: menu::MenuManager {
- menus: Default::default(),
- menu: Default::default(),
- global_event_listeners: Default::default(),
- event_listeners: Mutex::new(window_menu_event_listeners),
- },
- plugins: Mutex::new(plugins),
- listeners: Listeners::default(),
- state: Arc::new(state),
- config: context.config,
- assets: context.assets,
- app_icon: context.app_icon,
- package_info: context.package_info,
- pattern: Arc::new(context.pattern),
- resources_table: Arc::default(),
- }
- }
- /// State managed by the application.
- pub(crate) fn state(&self) -> Arc<StateManager> {
- self.state.clone()
- }
- /// Get the base path to serve data from.
- ///
- /// * In dev mode, this will be based on the `devUrl` configuration value.
- /// * Otherwise, this will be based on the `frontendDist` configuration value.
- #[cfg(not(dev))]
- fn base_path(&self) -> Option<&Url> {
- use crate::utils::config::FrontendDist;
- match self.config.build.frontend_dist.as_ref() {
- Some(FrontendDist::Url(url)) => Some(url),
- _ => None,
- }
- }
- #[cfg(dev)]
- fn base_path(&self) -> Option<&Url> {
- self.config.build.dev_url.as_ref()
- }
- pub(crate) fn protocol_url(&self) -> Cow<'_, Url> {
- if cfg!(windows) || cfg!(target_os = "android") {
- Cow::Owned(Url::parse("http://tauri.localhost").unwrap())
- } else {
- Cow::Owned(Url::parse("tauri://localhost").unwrap())
- }
- }
- /// Get the base URL to use for webview requests.
- ///
- /// In dev mode, this will be based on the `devUrl` configuration value.
- pub(crate) fn get_url(&self) -> Cow<'_, Url> {
- match self.base_path() {
- Some(url) => Cow::Borrowed(url),
- _ => self.protocol_url(),
- }
- }
- fn csp(&self) -> Option<Csp> {
- if cfg!(feature = "custom-protocol") {
- self.config.app.security.csp.clone()
- } else {
- self
- .config
- .app
- .security
- .dev_csp
- .clone()
- .or_else(|| self.config.app.security.csp.clone())
- }
- }
- pub fn get_asset(&self, mut path: String) -> Result<Asset, Box<dyn std::error::Error>> {
- let assets = &self.assets;
- if path.ends_with('/') {
- path.pop();
- }
- path = percent_encoding::percent_decode(path.as_bytes())
- .decode_utf8_lossy()
- .to_string();
- let path = if path.is_empty() {
- // if the url is `tauri://localhost`, we should load `index.html`
- "index.html".to_string()
- } else {
- // skip leading `/`
- path.chars().skip(1).collect::<String>()
- };
- let mut asset_path = AssetKey::from(path.as_str());
- let asset_response = assets
- .get(&path.as_str().into())
- .or_else(|| {
- debug_eprintln!("Asset `{path}` not found; fallback to {path}.html");
- let fallback = format!("{}.html", path.as_str()).into();
- let asset = assets.get(&fallback);
- asset_path = fallback;
- asset
- })
- .or_else(|| {
- debug_eprintln!(
- "Asset `{}` not found; fallback to {}/index.html",
- path,
- path
- );
- let fallback = format!("{}/index.html", path.as_str()).into();
- let asset = assets.get(&fallback);
- asset_path = fallback;
- asset
- })
- .or_else(|| {
- debug_eprintln!("Asset `{}` not found; fallback to index.html", path);
- let fallback = AssetKey::from("index.html");
- let asset = assets.get(&fallback);
- asset_path = fallback;
- asset
- })
- .ok_or_else(|| crate::Error::AssetNotFound(path.clone()))
- .map(Cow::into_owned);
- let mut csp_header = None;
- let is_html = asset_path.as_ref().ends_with(".html");
- match asset_response {
- Ok(asset) => {
- let final_data = if is_html {
- let mut asset = String::from_utf8_lossy(&asset).into_owned();
- if let Some(csp) = self.csp() {
- csp_header.replace(set_csp(&mut asset, &self.assets, &asset_path, self, csp));
- }
- asset.as_bytes().to_vec()
- } else {
- asset
- };
- let mime_type = tauri_utils::mime_type::MimeType::parse(&final_data, &path);
- Ok(Asset {
- bytes: final_data.to_vec(),
- mime_type,
- csp_header,
- })
- }
- Err(e) => {
- debug_eprintln!("{:?}", e); // TODO log::error!
- Err(Box::new(e))
- }
- }
- }
- pub(crate) fn listeners(&self) -> &Listeners {
- &self.listeners
- }
- pub fn run_invoke_handler(&self, invoke: Invoke<R>) -> bool {
- (self.webview.invoke_handler)(invoke)
- }
- pub fn extend_api(&self, plugin: &str, invoke: Invoke<R>) -> bool {
- self
- .plugins
- .lock()
- .expect("poisoned plugin store")
- .extend_api(plugin, invoke)
- }
- pub fn initialize_plugins(&self, app: &AppHandle<R>) -> crate::Result<()> {
- self
- .plugins
- .lock()
- .expect("poisoned plugin store")
- .initialize_all(app, &self.config.plugins)
- }
- pub fn config(&self) -> &Config {
- &self.config
- }
- pub fn package_info(&self) -> &PackageInfo {
- &self.package_info
- }
- pub fn listen<F: Fn(Event) + Send + 'static>(
- &self,
- event: String,
- target: EventTarget,
- handler: F,
- ) -> EventId {
- assert_event_name_is_valid(&event);
- self.listeners().listen(event, target, handler)
- }
- pub fn unlisten(&self, id: EventId) {
- self.listeners().unlisten(id)
- }
- pub fn once<F: FnOnce(Event) + Send + 'static>(
- &self,
- event: String,
- target: EventTarget,
- handler: F,
- ) {
- assert_event_name_is_valid(&event);
- self.listeners().once(event, target, handler)
- }
- pub fn emit_filter<S, F>(&self, event: &str, payload: S, filter: F) -> crate::Result<()>
- where
- S: Serialize + Clone,
- F: Fn(&EventTarget) -> bool,
- {
- assert_event_name_is_valid(event);
- #[cfg(feature = "tracing")]
- let _span = tracing::debug_span!("emit::run").entered();
- let emit_args = EmitArgs::new(event, payload)?;
- let listeners = self.listeners();
- listeners.emit_js_filter(
- self.webview.webviews_lock().values(),
- event,
- &emit_args,
- Some(&filter),
- )?;
- listeners.emit_filter(emit_args, Some(filter))?;
- Ok(())
- }
- pub fn emit<S: Serialize + Clone>(&self, event: &str, payload: S) -> crate::Result<()> {
- assert_event_name_is_valid(event);
- #[cfg(feature = "tracing")]
- let _span = tracing::debug_span!("emit::run").entered();
- let emit_args = EmitArgs::new(event, payload)?;
- let listeners = self.listeners();
- listeners.emit_js(self.webview.webviews_lock().values(), event, &emit_args)?;
- listeners.emit(emit_args)?;
- Ok(())
- }
- pub fn get_window(&self, label: &str) -> Option<Window<R>> {
- self.window.windows_lock().get(label).cloned()
- }
- pub fn get_focused_window(&self) -> Option<Window<R>> {
- self
- .window
- .windows_lock()
- .iter()
- .find(|w| w.1.is_focused().unwrap_or(false))
- .map(|w| w.1.clone())
- }
- pub(crate) fn on_window_close(&self, label: &str) {
- if let Some(window) = self.window.windows_lock().remove(label) {
- for webview in window.webviews() {
- self.webview.webviews_lock().remove(webview.label());
- }
- }
- }
- pub(crate) fn on_webview_close(&self, label: &str) {
- self.webview.webviews_lock().remove(label);
- }
- pub fn windows(&self) -> HashMap<String, Window<R>> {
- self.window.windows_lock().clone()
- }
- pub fn get_webview(&self, label: &str) -> Option<Webview<R>> {
- self.webview.webviews_lock().get(label).cloned()
- }
- pub fn webviews(&self) -> HashMap<String, Webview<R>> {
- self.webview.webviews_lock().clone()
- }
- /// Resources table managed by the application.
- pub(crate) fn resources_table(&self) -> MutexGuard<'_, ResourceTable> {
- self
- .resources_table
- .lock()
- .expect("poisoned window manager")
- }
- }
- #[cfg(desktop)]
- impl<R: Runtime> AppManager<R> {
- pub fn remove_menu_from_stash_by_id(&self, id: Option<&crate::menu::MenuId>) {
- if let Some(id) = id {
- let is_used_by_a_window = self
- .window
- .windows_lock()
- .values()
- .any(|w| w.is_menu_in_use(id));
- if !(self.menu.is_menu_in_use(id) || is_used_by_a_window) {
- self.menu.menus_stash_lock().remove(id);
- }
- }
- }
- }
- #[cfg(test)]
- mod tests {
- use super::replace_with_callback;
- #[test]
- fn string_replace_with_callback() {
- let mut tauri_index = 0;
- #[allow(clippy::single_element_loop)]
- for (src, pattern, replacement, result) in [(
- "tauri is awesome, tauri is amazing",
- "tauri",
- || {
- tauri_index += 1;
- tauri_index.to_string()
- },
- "1 is awesome, 2 is amazing",
- )] {
- assert_eq!(replace_with_callback(src, pattern, replacement), result);
- }
- }
- }
- #[cfg(test)]
- mod test {
- use std::{
- sync::mpsc::{channel, Receiver, Sender},
- time::Duration,
- };
- use crate::{
- event::EventTarget,
- generate_context,
- plugin::PluginStore,
- test::{mock_app, MockRuntime},
- webview::WebviewBuilder,
- window::WindowBuilder,
- App, Manager, StateManager, Webview, WebviewWindow, WebviewWindowBuilder, Window, Wry,
- };
- use super::AppManager;
- const APP_LISTEN_ID: &str = "App::listen";
- const APP_LISTEN_ANY_ID: &str = "App::listen_any";
- const WINDOW_LISTEN_ID: &str = "Window::listen";
- const WINDOW_LISTEN_ANY_ID: &str = "Window::listen_any";
- const WEBVIEW_LISTEN_ID: &str = "Webview::listen";
- const WEBVIEW_LISTEN_ANY_ID: &str = "Webview::listen_any";
- const WEBVIEW_WINDOW_LISTEN_ID: &str = "WebviewWindow::listen";
- const WEBVIEW_WINDOW_LISTEN_ANY_ID: &str = "WebviewWindow::listen_any";
- const TEST_EVENT_NAME: &str = "event";
- #[test]
- fn check_get_url() {
- let context = generate_context!("test/fixture/src-tauri/tauri.conf.json", crate);
- let manager: AppManager<Wry> = AppManager::with_handlers(
- context,
- PluginStore::default(),
- Box::new(|_| false),
- None,
- Default::default(),
- StateManager::new(),
- Default::default(),
- 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/");
- }
- struct EventSetup {
- app: App<MockRuntime>,
- window: Window<MockRuntime>,
- webview: Webview<MockRuntime>,
- webview_window: WebviewWindow<MockRuntime>,
- tx: Sender<(&'static str, String)>,
- rx: Receiver<(&'static str, String)>,
- }
- fn setup_events(setup_any: bool) -> EventSetup {
- let app = mock_app();
- let window = WindowBuilder::new(&app, "main-window").build().unwrap();
- let webview = window
- .add_child(
- WebviewBuilder::new("main-webview", Default::default()),
- crate::LogicalPosition::new(0, 0),
- window.inner_size().unwrap(),
- )
- .unwrap();
- let webview_window = WebviewWindowBuilder::new(&app, "main-webview-window", Default::default())
- .build()
- .unwrap();
- let (tx, rx) = channel();
- macro_rules! setup_listener {
- ($type:ident, $id:ident, $any_id:ident) => {
- let tx_ = tx.clone();
- $type.listen(TEST_EVENT_NAME, move |evt| {
- tx_
- .send(($id, serde_json::from_str::<String>(evt.payload()).unwrap()))
- .unwrap();
- });
- if setup_any {
- let tx_ = tx.clone();
- $type.listen_any(TEST_EVENT_NAME, move |evt| {
- tx_
- .send((
- $any_id,
- serde_json::from_str::<String>(evt.payload()).unwrap(),
- ))
- .unwrap();
- });
- }
- };
- }
- setup_listener!(app, APP_LISTEN_ID, APP_LISTEN_ANY_ID);
- setup_listener!(window, WINDOW_LISTEN_ID, WINDOW_LISTEN_ANY_ID);
- setup_listener!(webview, WEBVIEW_LISTEN_ID, WEBVIEW_LISTEN_ANY_ID);
- setup_listener!(
- webview_window,
- WEBVIEW_WINDOW_LISTEN_ID,
- WEBVIEW_WINDOW_LISTEN_ANY_ID
- );
- EventSetup {
- app,
- window,
- webview,
- webview_window,
- tx,
- rx,
- }
- }
- fn assert_events(kind: &str, received: &[&str], expected: &[&str]) {
- for e in expected {
- assert!(received.contains(e), "{e} did not receive `{kind}` event");
- }
- assert_eq!(
- received.len(),
- expected.len(),
- "received {:?} `{kind}` events but expected {:?}",
- received,
- expected
- );
- }
- #[test]
- fn emit() {
- let EventSetup {
- app,
- window,
- webview,
- webview_window,
- tx: _,
- rx,
- } = setup_events(true);
- run_emit_test("emit (app)", app, &rx);
- run_emit_test("emit (window)", window, &rx);
- run_emit_test("emit (webview)", webview, &rx);
- run_emit_test("emit (webview_window)", webview_window, &rx);
- }
- fn run_emit_test<M: Manager<MockRuntime>>(kind: &str, m: M, rx: &Receiver<(&str, String)>) {
- let mut received = Vec::new();
- let payload = "global-payload";
- m.emit(TEST_EVENT_NAME, payload).unwrap();
- while let Ok((source, p)) = rx.recv_timeout(Duration::from_secs(1)) {
- assert_eq!(p, payload);
- received.push(source);
- }
- assert_events(
- kind,
- &received,
- &[
- APP_LISTEN_ID,
- APP_LISTEN_ANY_ID,
- WINDOW_LISTEN_ID,
- WINDOW_LISTEN_ANY_ID,
- WEBVIEW_LISTEN_ID,
- WEBVIEW_LISTEN_ANY_ID,
- WEBVIEW_WINDOW_LISTEN_ID,
- WEBVIEW_WINDOW_LISTEN_ANY_ID,
- ],
- );
- }
- #[test]
- fn emit_to() {
- let EventSetup {
- app,
- window,
- webview,
- webview_window,
- tx,
- rx,
- } = setup_events(false);
- run_emit_to_test(
- "emit_to (App)",
- &app,
- &window,
- &webview,
- &webview_window,
- tx.clone(),
- &rx,
- );
- run_emit_to_test(
- "emit_to (window)",
- &window,
- &window,
- &webview,
- &webview_window,
- tx.clone(),
- &rx,
- );
- run_emit_to_test(
- "emit_to (webview)",
- &webview,
- &window,
- &webview,
- &webview_window,
- tx.clone(),
- &rx,
- );
- run_emit_to_test(
- "emit_to (webview_window)",
- &webview_window,
- &window,
- &webview,
- &webview_window,
- tx.clone(),
- &rx,
- );
- }
- fn run_emit_to_test<M: Manager<MockRuntime>>(
- kind: &str,
- m: &M,
- window: &Window<MockRuntime>,
- webview: &Webview<MockRuntime>,
- webview_window: &WebviewWindow<MockRuntime>,
- tx: Sender<(&'static str, String)>,
- rx: &Receiver<(&'static str, String)>,
- ) {
- let mut received = Vec::new();
- let payload = "global-payload";
- macro_rules! test_target {
- ($target:expr, $id:ident) => {
- m.emit_to($target, TEST_EVENT_NAME, payload).unwrap();
- while let Ok((source, p)) = rx.recv_timeout(Duration::from_secs(1)) {
- assert_eq!(p, payload);
- received.push(source);
- }
- assert_events(kind, &received, &[$id]);
- received.clear();
- };
- }
- test_target!(EventTarget::App, APP_LISTEN_ID);
- test_target!(window.label(), WINDOW_LISTEN_ID);
- test_target!(webview.label(), WEBVIEW_LISTEN_ID);
- test_target!(webview_window.label(), WEBVIEW_WINDOW_LISTEN_ID);
- let other_webview_listen_id = "OtherWebview::listen";
- let other_webview = WebviewWindowBuilder::new(
- window,
- kind.replace(['(', ')', ' '], ""),
- Default::default(),
- )
- .build()
- .unwrap();
- other_webview.listen(TEST_EVENT_NAME, move |evt| {
- tx.send((
- other_webview_listen_id,
- serde_json::from_str::<String>(evt.payload()).unwrap(),
- ))
- .unwrap();
- });
- m.emit_to(other_webview.label(), TEST_EVENT_NAME, payload)
- .unwrap();
- while let Ok((source, p)) = rx.recv_timeout(Duration::from_secs(1)) {
- assert_eq!(p, payload);
- received.push(source);
- }
- assert_events("emit_to", &received, &[other_webview_listen_id]);
- }
- }
|