1#![allow(clippy::arc_with_non_send_sync)]
6#![allow(clippy::too_many_arguments)]
7
8use std::{
9 collections::HashMap,
10 fmt,
11 fs::create_dir_all,
12 path::PathBuf,
13 sync::{
14 Arc, Mutex,
15 atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering},
16 mpsc::{self, Receiver, Sender},
17 },
18 time::Duration,
19};
20
21use cef::*;
22use raw_window_handle::{DisplayHandle, HasDisplayHandle};
23use tauri_runtime::{
24 DeviceEventFilter, Error, EventLoopProxy, ExitRequestedEventAction, Result, RunEvent, Runtime,
25 RuntimeHandle, RuntimeInitArgs, UserEvent,
26 dpi::PhysicalPosition,
27 monitor::Monitor,
28 webview::{DetachedWebview, PendingWebview},
29 window::{
30 DetachedWindow, DragDropEvent, PendingWindow, RawWindow, WebviewEvent, WindowEvent, WindowId,
31 },
32};
33use tauri_utils::Theme;
34use winit::{
35 application::ApplicationHandler,
36 data_transfer::{DataTransferId, TypeHint},
37 event::{StartCause, WindowEvent as WinitWindowEvent},
38 event_loop::{
39 ActiveEventLoop, DndAction, EventLoop, EventLoopBuilder, EventLoopProxy as WinitEventLoopProxy,
40 },
41 window::WindowId as WinitWindowId,
42};
43
44use crate::DebugEnvironment;
45use crate::external_message_pump::CefExternalPump;
46use crate::platform::EventLoopExt;
47use crate::{
48 cef_impl::{client as browser_client, ipc, request_handler},
49 macros::wrap_with_args,
50 webview::{
51 self, AppWebview, CefWebviewAttributes, CefWebviewDispatcher, Webview, WebviewMessage,
52 create_webview_detached,
53 },
54 window::{
55 AppWindow, CefWindowDispatcher, WindowMessage, create_window_detached,
56 winit_monitor_to_tauri_monitor, winit_theme_to_tauri_theme,
57 },
58 window_handle::SendRawDisplayHandle,
59};
60#[cfg(any(
61 target_os = "linux",
62 target_os = "dragonfly",
63 target_os = "freebsd",
64 target_os = "netbsd",
65 target_os = "openbsd"
66))]
67use winit::platform::gtk4::EventLoopBuilderExtGtk4;
68#[cfg(target_os = "macos")]
69use winit::platform::macos::EventLoopBuilderExtMacOS;
70#[cfg(windows)]
71use winit::platform::windows::EventLoopBuilderExtWindows;
72
73pub type NativeWindowEventHandler = dyn Fn(&str, &dyn ActiveEventLoop, Arc<dyn winit::window::Window>, &WinitWindowEvent)
76 + Send
77 + Sync;
78
79type SettingsCallback = dyn FnOnce(&mut cef::Settings) + Send + Sync;
81
82pub use cef;
90
91#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
131#[non_exhaustive]
132pub enum SecretStorage {
133 #[default]
137 Auto,
138 Mock,
144 System,
147}
148
149#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
163#[non_exhaustive]
164pub enum SandboxPolicy {
165 #[default]
176 Auto,
177 Required,
184 Disabled,
190}
191
192#[derive(Debug, Default, Clone, PartialEq, Eq)]
198#[non_exhaustive]
199pub enum RemoteDebugging {
200 #[default]
204 Disabled,
205 Port {
217 port: u16,
219 allowed_origins: Vec<String>,
221 },
222 Pipe,
228}
229
230#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
257#[non_exhaustive]
258pub enum DevToolsPolicy {
259 #[default]
262 Auto,
263 Allowed,
268 Disallowed,
270}
271
272#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
274#[non_exhaustive]
275pub enum CertificateErrorPolicy {
276 #[default]
282 ChromeInterstitial,
283 Cancel,
289}
290
291#[derive(Debug, Default, Clone, PartialEq, Eq)]
297#[non_exhaustive]
298pub enum ProxyConfig {
299 #[default]
304 System,
305 Direct,
307 AutoDetect,
309 PacScript {
311 url: String,
313 },
314 FixedServers {
316 server: String,
319 bypass_list: Option<String>,
322 },
323}
324
325impl ProxyConfig {
326 fn to_preference(&self) -> serde_json::Value {
328 match self {
329 Self::System => serde_json::json!({ "mode": "system" }),
330 Self::Direct => serde_json::json!({ "mode": "direct" }),
331 Self::AutoDetect => serde_json::json!({ "mode": "auto_detect" }),
332 Self::PacScript { url } => serde_json::json!({ "mode": "pac_script", "pac_url": url }),
333 Self::FixedServers {
334 server,
335 bypass_list,
336 } => {
337 let mut value = serde_json::json!({ "mode": "fixed_servers", "server": server });
338 if let Some(bypass_list) = bypass_list
339 && let Some(object) = value.as_object_mut()
340 {
341 object.insert(
342 "bypass_list".to_string(),
343 serde_json::Value::String(bypass_list.clone()),
344 );
345 }
346 value
347 }
348 }
349 }
350}
351
352#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
356#[non_exhaustive]
357pub enum AutoplayPolicy {
358 #[default]
361 Default,
362 NoUserGestureRequired,
367 UserGestureRequired,
369 DocumentUserActivationRequired,
371}
372
373impl AutoplayPolicy {
374 fn as_switch_value(self) -> Option<&'static str> {
376 match self {
377 Self::Default => None,
378 Self::NoUserGestureRequired => Some("no-user-gesture-required"),
379 Self::UserGestureRequired => Some("user-gesture-required"),
380 Self::DocumentUserActivationRequired => Some("document-user-activation-required"),
381 }
382 }
383}
384
385#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
391#[non_exhaustive]
392pub enum WebRtcIpHandling {
393 #[default]
395 Default,
396 DefaultPublicAndPrivateInterfaces,
398 DefaultPublicInterfaceOnly,
400 DisableNonProxiedUdp,
403}
404
405impl WebRtcIpHandling {
406 fn as_switch_value(self) -> Option<&'static str> {
408 match self {
409 Self::Default => None,
410 Self::DefaultPublicAndPrivateInterfaces => Some("default_public_and_private_interfaces"),
411 Self::DefaultPublicInterfaceOnly => Some("default_public_interface_only"),
412 Self::DisableNonProxiedUdp => Some("disable_non_proxied_udp"),
413 }
414 }
415}
416
417#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
441#[non_exhaustive]
442pub enum DowngradePolicy {
443 #[default]
450 KeepProfile,
451 ResetProfile,
460}
461
462#[derive(Default)]
472pub struct Cef {
473 command_line_args: Vec<(String, Option<String>)>,
474 disabled_features: Vec<String>,
475 enabled_features: Vec<String>,
476 deep_link_schemes: Vec<String>,
477 cache_path: Option<PathBuf>,
478 api_version: Option<i32>,
479 secret_storage: SecretStorage,
480 profile_preferences: Vec<(String, serde_json::Value)>,
481 global_preferences: Vec<(String, serde_json::Value)>,
482 content_settings: Vec<(cef::ContentSettingTypes, cef::ContentSettingValues)>,
483 allow_chromium_command_line_args: bool,
484 log_file: Option<PathBuf>,
485 log_severity: Option<LogSeverity>,
486 log_items: Option<LogItems>,
487 locale: Option<String>,
488 accept_language_list: Option<String>,
489 user_agent: Option<String>,
490 user_agent_product: Option<String>,
491 javascript_flags: Option<String>,
492 chrome_policy_id: Option<String>,
493 persist_session_cookies: bool,
494 remote_debugging: RemoteDebugging,
495 devtools: DevToolsPolicy,
496 debug_environment: DebugEnvironment,
497 certificate_errors: CertificateErrorPolicy,
498 sandbox: SandboxPolicy,
499 downgrade: DowngradePolicy,
500 settings_callback: Option<Box<SettingsCallback>>,
501 window_event_handler: Option<Arc<NativeWindowEventHandler>>,
502}
503
504impl fmt::Debug for Cef {
505 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
506 f.debug_struct("Cef")
507 .field("command_line_args", &self.command_line_args)
508 .field("disabled_features", &self.disabled_features)
509 .field("enabled_features", &self.enabled_features)
510 .field("deep_link_schemes", &self.deep_link_schemes)
511 .field("cache_path", &self.cache_path)
512 .field("api_version", &self.api_version)
513 .field("secret_storage", &self.secret_storage)
514 .field("profile_preferences", &self.profile_preferences)
515 .field("global_preferences", &self.global_preferences)
516 .field("content_settings", &self.content_settings)
517 .field(
518 "allow_chromium_command_line_args",
519 &self.allow_chromium_command_line_args,
520 )
521 .field("log_file", &self.log_file)
522 .field("log_severity", &self.log_severity)
523 .field("log_items", &self.log_items)
524 .field("locale", &self.locale)
525 .field("accept_language_list", &self.accept_language_list)
526 .field("user_agent", &self.user_agent)
529 .field("user_agent_product", &self.user_agent_product)
530 .field("javascript_flags", &self.javascript_flags)
531 .field("chrome_policy_id", &self.chrome_policy_id)
532 .field("persist_session_cookies", &self.persist_session_cookies)
533 .field("remote_debugging", &self.remote_debugging)
534 .field("devtools", &self.devtools)
535 .field("debug_environment", &self.debug_environment)
536 .field("certificate_errors", &self.certificate_errors)
537 .field("sandbox", &self.sandbox)
538 .field("downgrade", &self.downgrade)
539 .field("settings_callback", &self.settings_callback.is_some())
540 .field("window_event_handler", &self.window_event_handler.is_some())
541 .finish()
542 }
543}
544
545impl Cef {
546 #[must_use]
553 pub fn on_window_event<F>(mut self, handler: F) -> Self
554 where
555 F: Fn(&str, &dyn ActiveEventLoop, Arc<dyn winit::window::Window>, &WinitWindowEvent)
556 + Send
557 + Sync
558 + 'static,
559 {
560 self.window_event_handler = Some(Arc::new(handler));
561 self
562 }
563
564 #[must_use]
568 pub fn with_settings<F>(mut self, callback: F) -> Self
569 where
570 F: FnOnce(&mut cef::Settings) + Send + Sync + 'static,
571 {
572 self.settings_callback = Some(Box::new(callback));
573 self
574 }
575
576 #[must_use]
583 pub fn command_line_arg<K: Into<String>, V: Into<String>>(
584 mut self,
585 key: K,
586 value: Option<V>,
587 ) -> Self {
588 self
589 .command_line_args
590 .push((key.into(), value.map(Into::into)));
591 self
592 }
593
594 #[must_use]
598 pub fn command_line_args<K: Into<String>, V: Into<String>>(
599 mut self,
600 args: impl IntoIterator<Item = (K, Option<V>)>,
601 ) -> Self {
602 self
603 .command_line_args
604 .extend(args.into_iter().map(|(k, v)| (k.into(), v.map(Into::into))));
605 self
606 }
607
608 #[must_use]
612 pub fn deep_link_schemes<S: Into<String>>(
613 mut self,
614 schemes: impl IntoIterator<Item = S>,
615 ) -> Self {
616 self
617 .deep_link_schemes
618 .extend(schemes.into_iter().map(Into::into));
619 self
620 }
621
622 #[must_use]
631 pub fn root_cache_path<P: AsRef<std::path::Path>>(mut self, path: P) -> Self {
632 self.cache_path = Some(path.as_ref().to_path_buf());
633 self
634 }
635
636 #[must_use]
643 pub fn downgrade(mut self, policy: DowngradePolicy) -> Self {
644 self.downgrade = policy;
645 self
646 }
647
648 #[must_use]
655 pub fn cef_api_version(mut self, version: i32) -> Self {
656 self.api_version = Some(version);
657 self
658 }
659
660 #[must_use]
673 pub fn secret_storage(mut self, storage: SecretStorage) -> Self {
674 self.secret_storage = storage;
675 self
676 }
677
678 #[must_use]
705 pub fn profile_preference<K: Into<String>>(mut self, name: K, enabled: bool) -> Self {
706 self
707 .profile_preferences
708 .push((name.into(), serde_json::Value::Bool(enabled)));
709 self
710 }
711
712 #[must_use]
741 pub fn profile_preference_value<K: Into<String>, V: Into<serde_json::Value>>(
742 mut self,
743 name: K,
744 value: V,
745 ) -> Self {
746 self.profile_preferences.push((name.into(), value.into()));
747 self
748 }
749
750 #[must_use]
757 pub fn global_preference<K: Into<String>, V: Into<serde_json::Value>>(
758 mut self,
759 name: K,
760 value: V,
761 ) -> Self {
762 self.global_preferences.push((name.into(), value.into()));
763 self
764 }
765
766 #[must_use]
803 pub fn default_content_setting(
804 mut self,
805 content_type: cef::ContentSettingTypes,
806 value: cef::ContentSettingValues,
807 ) -> Self {
808 self.content_settings.push((content_type, value));
809 self
810 }
811
812 #[must_use]
833 pub fn allow_chromium_command_line_args(mut self, allow: bool) -> Self {
834 self.allow_chromium_command_line_args = allow;
835 self
836 }
837
838 #[must_use]
848 pub fn log_file<P: AsRef<std::path::Path>>(mut self, path: P) -> Self {
849 self.log_file = Some(path.as_ref().to_path_buf());
850 self
851 }
852
853 #[must_use]
864 pub fn log_severity(mut self, severity: LogSeverity) -> Self {
865 self.log_severity = Some(severity);
866 self
867 }
868
869 #[must_use]
879 pub fn locale<S: Into<String>>(mut self, locale: S) -> Self {
880 self.locale = Some(locale.into());
881 self
882 }
883
884 #[must_use]
890 pub fn accept_language_list<S: Into<String>>(mut self, languages: S) -> Self {
891 self.accept_language_list = Some(languages.into());
892 self
893 }
894
895 #[must_use]
906 pub fn sandbox(mut self, policy: SandboxPolicy) -> Self {
907 self.sandbox = policy;
908 self
909 }
910
911 #[must_use]
919 pub fn disable_features<S: Into<String>>(
920 mut self,
921 features: impl IntoIterator<Item = S>,
922 ) -> Self {
923 self
924 .disabled_features
925 .extend(features.into_iter().map(Into::into));
926 self
927 }
928
929 #[must_use]
933 pub fn enable_features<S: Into<String>>(mut self, features: impl IntoIterator<Item = S>) -> Self {
934 self
935 .enabled_features
936 .extend(features.into_iter().map(Into::into));
937 self
938 }
939
940 #[must_use]
952 pub fn debug_environment(mut self, policy: DebugEnvironment) -> Self {
953 self.debug_environment = policy;
954 self
955 }
956
957 #[must_use]
965 pub fn remote_debugging(mut self, remote_debugging: RemoteDebugging) -> Self {
966 self.remote_debugging = remote_debugging;
967 self
968 }
969
970 #[must_use]
976 pub fn devtools(mut self, policy: DevToolsPolicy) -> Self {
977 self.devtools = policy;
978 self
979 }
980
981 #[must_use]
986 pub fn certificate_errors(mut self, policy: CertificateErrorPolicy) -> Self {
987 self.certificate_errors = policy;
988 self
989 }
990
991 #[must_use]
995 pub fn proxy(mut self, proxy: ProxyConfig) -> Self {
996 self
997 .profile_preferences
998 .push(("proxy".to_string(), proxy.to_preference()));
999 self
1000 }
1001
1002 #[must_use]
1004 pub fn autoplay(mut self, policy: AutoplayPolicy) -> Self {
1005 if let Some(value) = policy.as_switch_value() {
1006 self
1007 .command_line_args
1008 .push(("--autoplay-policy".to_string(), Some(value.to_string())));
1009 }
1010 self
1011 }
1012
1013 #[must_use]
1015 pub fn webrtc_ip_handling(mut self, policy: WebRtcIpHandling) -> Self {
1016 if let Some(value) = policy.as_switch_value() {
1017 self.command_line_args.push((
1018 "--webrtc-ip-handling-policy".to_string(),
1019 Some(value.to_string()),
1020 ));
1021 }
1022 self
1023 }
1024
1025 #[must_use]
1032 pub fn spell_checking(mut self, enabled: bool) -> Self {
1033 self.profile_preferences.push((
1034 "browser.enable_spellchecking".to_string(),
1035 serde_json::Value::Bool(enabled),
1036 ));
1037 self
1038 }
1039
1040 #[must_use]
1048 pub fn safe_browsing(mut self, enabled: bool) -> Self {
1049 self.profile_preferences.push((
1050 "safebrowsing.enabled".to_string(),
1051 serde_json::Value::Bool(enabled),
1052 ));
1053 self
1054 }
1055
1056 #[must_use]
1063 pub fn component_updates(mut self, enabled: bool) -> Self {
1064 if !enabled {
1065 self
1066 .command_line_args
1067 .push(("--disable-component-update".to_string(), None));
1068 }
1069 self
1070 }
1071
1072 #[must_use]
1079 pub fn user_agent<S: Into<String>>(mut self, user_agent: S) -> Self {
1080 self.user_agent = Some(user_agent.into());
1081 self
1082 }
1083
1084 #[must_use]
1089 pub fn user_agent_product<S: Into<String>>(mut self, product: S) -> Self {
1090 self.user_agent_product = Some(product.into());
1091 self
1092 }
1093
1094 #[must_use]
1101 pub fn persist_session_cookies(mut self, persist: bool) -> Self {
1102 self.persist_session_cookies = persist;
1103 self
1104 }
1105
1106 #[must_use]
1112 pub fn javascript_flags<S: Into<String>>(mut self, flags: S) -> Self {
1113 self.javascript_flags = Some(flags.into());
1114 self
1115 }
1116
1117 #[must_use]
1126 pub fn chrome_policy_id<S: Into<String>>(mut self, policy_id: S) -> Self {
1127 self.chrome_policy_id = Some(policy_id.into());
1128 self
1129 }
1130
1131 #[must_use]
1135 pub fn log_items(mut self, items: LogItems) -> Self {
1136 self.log_items = Some(items);
1137 self
1138 }
1139}
1140
1141impl<T: UserEvent> tauri_runtime::RuntimeInitAttrs<T> for Cef {
1142 type Runtime = CefRuntime<T>;
1143
1144 fn apply_config(&mut self, config: &tauri_utils::config::Config) -> Result<()> {
1145 if let Some(plugin_config) = config
1146 .plugins
1147 .0
1148 .get("deep-link")
1149 .and_then(|config| config.get("desktop").cloned())
1150 {
1151 #[derive(serde::Deserialize)]
1152 #[serde(untagged)]
1153 enum DesktopDeepLinks {
1154 One(tauri_utils::config::DeepLinkProtocol),
1155 List(Vec<tauri_utils::config::DeepLinkProtocol>),
1156 }
1157
1158 let protocols: DesktopDeepLinks =
1159 serde_json::from_value(plugin_config).map_err(tauri_runtime::Error::Json)?;
1160 let schemes = match protocols {
1161 DesktopDeepLinks::One(protocol) => protocol.schemes,
1162 DesktopDeepLinks::List(protocols) => protocols
1163 .into_iter()
1164 .flat_map(|protocol| protocol.schemes)
1165 .collect(),
1166 };
1167
1168 self.deep_link_schemes.extend(schemes);
1169 }
1170 Ok(())
1171 }
1172}
1173
1174impl<T: UserEvent> From<Cef> for tauri_runtime::dynamic::DynRuntimeInitAttrs<T> {
1175 fn from(attrs: Cef) -> Self {
1176 Self::new(attrs)
1177 }
1178}
1179
1180pub struct NewWindowOpener {
1182 source_url: Option<url::Url>,
1183}
1184
1185impl NewWindowOpener {
1186 pub(crate) fn new(source_url: Option<url::Url>) -> Self {
1187 Self { source_url }
1188 }
1189
1190 pub fn source_url(&self) -> Option<&url::Url> {
1196 self.source_url.as_ref()
1197 }
1198}
1199
1200impl std::fmt::Debug for NewWindowOpener {
1201 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1202 formatter
1204 .debug_struct("NewWindowOpener")
1205 .field("source_url_observed", &self.source_url.is_some())
1206 .finish()
1207 }
1208}
1209
1210#[derive(Clone, Debug)]
1211pub struct EventProxy<T: UserEvent> {
1212 context: RuntimeContext<T>,
1213}
1214
1215impl<T: UserEvent> EventLoopProxy<T> for EventProxy<T> {
1216 fn send_event(&self, event: T) -> Result<()> {
1217 self.context.send_message(Message::UserEvent(event))
1218 }
1219}
1220
1221#[derive(Clone)]
1222pub(crate) struct RuntimeContext<T: UserEvent> {
1223 pub(crate) sender: Sender<Message<T>>,
1224 #[cfg(not(target_os = "macos"))]
1226 proxy: WinitEventLoopProxy,
1227 #[cfg(target_os = "macos")]
1231 wake: crate::platform::macos::MainThreadWake,
1232 main_thread_id: std::thread::ThreadId,
1233 next_window_id: Arc<AtomicU32>,
1234 next_webview_id: Arc<AtomicU32>,
1235 next_window_event_id: Arc<AtomicU32>,
1236 next_webview_event_id: Arc<AtomicU32>,
1237 current_dispatch: Arc<MainThreadDispatchSlot<T>>,
1238 pub(crate) app_wide_theme: Arc<Mutex<Option<Theme>>>,
1239 pub(crate) cef_pump: CefExternalPump,
1240 pub(crate) cache_path: Arc<PathBuf>,
1244 pub(crate) profile_preferences: Arc<Vec<(String, serde_json::Value)>>,
1248 pub(crate) content_settings: Arc<Vec<(cef::ContentSettingTypes, cef::ContentSettingValues)>>,
1251 pub(crate) certificate_errors: CertificateErrorPolicy,
1254 pub(crate) devtools_allowed: bool,
1257 window_event_handler: Option<Arc<NativeWindowEventHandler>>,
1258}
1259
1260#[derive(Clone, Copy)]
1270struct MainThreadDispatch<T: UserEvent> {
1271 app: *mut WinitCefApp<T>,
1272 event_loop: *const dyn ActiveEventLoop,
1273}
1274
1275struct MainThreadDispatchSlot<T: UserEvent> {
1276 current: AtomicPtr<MainThreadDispatch<T>>,
1277 #[cfg(target_os = "macos")]
1279 wake_pending: AtomicBool,
1280}
1281
1282impl<T: UserEvent> MainThreadDispatchSlot<T> {
1283 fn install(&self, dispatch: &mut MainThreadDispatch<T>) -> *mut MainThreadDispatch<T> {
1284 self.current.swap(dispatch, Ordering::AcqRel)
1285 }
1286
1287 #[cfg(target_os = "macos")]
1289 fn is_active(&self) -> bool {
1290 !self.current.load(Ordering::Acquire).is_null()
1291 }
1292
1293 #[cfg(target_os = "macos")]
1297 fn deliver_wake(&self, proxy: &WinitEventLoopProxy) {
1298 if self.is_active() || !self.wake_pending.load(Ordering::Acquire) {
1299 return;
1300 }
1301 self.wake_pending.swap(false, Ordering::AcqRel);
1305 proxy.wake_up();
1306 }
1307
1308 fn restore(&self, current: *mut MainThreadDispatch<T>, previous: *mut MainThreadDispatch<T>) {
1309 let installed = self.current.swap(previous, Ordering::AcqRel);
1310 debug_assert_eq!(installed, current);
1311 }
1312
1313 fn current(&self) -> Option<&MainThreadDispatch<T>> {
1314 let current = self.current.load(Ordering::Acquire);
1315 if current.is_null() {
1316 None
1317 } else {
1318 Some(unsafe { &*current })
1324 }
1325 }
1326}
1327
1328impl<T: UserEvent> Default for MainThreadDispatchSlot<T> {
1329 fn default() -> Self {
1330 Self {
1331 current: AtomicPtr::new(std::ptr::null_mut()),
1332 #[cfg(target_os = "macos")]
1333 wake_pending: AtomicBool::new(false),
1334 }
1335 }
1336}
1337
1338struct MainThreadDispatchGuard<T: UserEvent> {
1339 context: RuntimeContext<T>,
1340 dispatch: Box<MainThreadDispatch<T>>,
1341 previous: *mut MainThreadDispatch<T>,
1342}
1343
1344impl<T: UserEvent> Drop for MainThreadDispatchGuard<T> {
1345 fn drop(&mut self) {
1346 self
1347 .context
1348 .current_dispatch
1349 .restore(self.dispatch.as_mut(), self.previous);
1350 }
1351}
1352
1353fn dispatches_run_event<T: UserEvent>(message: &Message<T>) -> bool {
1358 match message {
1359 Message::UserEvent(_) | Message::Opened(_) => true,
1360 #[cfg(target_os = "macos")]
1361 Message::Reopen { .. } => true,
1362 _ => false,
1363 }
1364}
1365
1366#[allow(clippy::result_large_err)]
1367fn handle_main_thread_message<T: UserEvent>(
1368 context: &RuntimeContext<T>,
1369 message: Message<T>,
1370) -> std::result::Result<(), Message<T>> {
1371 if dispatches_run_event(&message) {
1382 return Err(message);
1383 }
1384
1385 let Some(dispatch) = context.current_dispatch.current() else {
1386 return Err(message);
1387 };
1388
1389 let app = unsafe { &mut *dispatch.app };
1393 let event_loop = unsafe { &*dispatch.event_loop };
1394
1395 app.handle_message(event_loop, message);
1396
1397 Ok(())
1398}
1399
1400impl<T: UserEvent> fmt::Debug for RuntimeContext<T> {
1401 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1402 f.debug_struct("RuntimeContext").finish()
1403 }
1404}
1405
1406impl<T: UserEvent> RuntimeContext<T> {
1407 pub(crate) fn send_message(&self, message: Message<T>) -> Result<()> {
1408 let message = if self.is_main_thread() {
1409 match handle_main_thread_message(self, message) {
1410 Ok(()) => return Ok(()),
1411 Err(message) => message,
1412 }
1413 } else {
1414 message
1415 };
1416
1417 self
1418 .sender
1419 .send(message)
1420 .map_err(|_| Error::FailedToSendMessage)?;
1421 self.wake_event_loop();
1422 Ok(())
1423 }
1424
1425 pub(crate) fn wake_event_loop(&self) {
1430 #[cfg(not(target_os = "macos"))]
1431 self.proxy.wake_up();
1432
1433 #[cfg(target_os = "macos")]
1447 {
1448 self
1449 .current_dispatch
1450 .wake_pending
1451 .store(true, Ordering::Release);
1452 self.wake.wake_up();
1453 }
1454 }
1455
1456 pub(crate) fn is_main_thread(&self) -> bool {
1457 std::thread::current().id() == self.main_thread_id
1458 }
1459
1460 pub(crate) fn wait_for_creation(&self, result_rx: Receiver<Result<()>>) -> Result<()> {
1468 if self.is_main_thread() {
1469 return match result_rx.try_recv() {
1470 Ok(result) => result,
1471 Err(mpsc::TryRecvError::Empty) => Ok(()),
1472 Err(mpsc::TryRecvError::Disconnected) => Err(Error::FailedToReceiveMessage),
1473 };
1474 }
1475 result_rx
1476 .recv()
1477 .map_err(|_| Error::FailedToReceiveMessage)?
1478 }
1479
1480 pub(crate) fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()> {
1490 if self.is_main_thread() {
1491 f();
1492 Ok(())
1493 } else {
1494 self.send_message(Message::Task(Box::new(f)))
1495 }
1496 }
1497
1498 pub(crate) fn next_window_id(&self) -> WindowId {
1499 self.next_window_id.fetch_add(1, Ordering::Relaxed).into()
1500 }
1501
1502 pub(crate) fn next_webview_id(&self) -> u32 {
1503 self.next_webview_id.fetch_add(1, Ordering::Relaxed)
1504 }
1505
1506 pub(crate) fn next_window_event_id(&self) -> u32 {
1507 self.next_window_event_id.fetch_add(1, Ordering::Relaxed)
1508 }
1509
1510 pub(crate) fn next_webview_event_id(&self) -> u32 {
1511 self.next_webview_event_id.fetch_add(1, Ordering::Relaxed)
1512 }
1513}
1514
1515pub(crate) type AfterWindowCreationCallback = Box<dyn for<'a> Fn(RawWindow<'a>) + Send>;
1516
1517pub(crate) enum Message<T: UserEvent> {
1518 EventLoop(EventLoopMessage),
1519 BrowserClosed(WindowId, u32),
1520 PopupPending(crate::popup::PopupRequest, Arc<crate::popup::PopupFamily>),
1521 PopupCreated(
1522 crate::popup::PopupRequest,
1523 i32,
1524 Arc<crate::popup::PopupFamily>,
1525 ),
1526 PopupAborted(crate::popup::PopupRequest),
1527 PopupClosed(i32),
1528 #[cfg(any(target_os = "macos", windows))]
1531 DestroyWebviewHostWindow(u32),
1532 Opened(Vec<url::Url>),
1533 #[cfg(target_os = "macos")]
1534 Reopen {
1535 has_visible_windows: bool,
1536 },
1537 #[cfg(target_os = "macos")]
1538 AccessibilityChanged {
1539 enabled: bool,
1540 },
1541 CreateWindow {
1542 window_id: WindowId,
1543 webview_id: Option<u32>,
1544 pending: Box<PendingWindow<T, CefRuntime<T>>>,
1545 after_window_creation: Option<AfterWindowCreationCallback>,
1546 result_tx: Sender<Result<()>>,
1547 },
1548 CreateWebview {
1549 window_id: WindowId,
1550 webview_id: u32,
1551 pending: Box<PendingWebview<T, CefRuntime<T>>>,
1552 result_tx: Sender<Result<()>>,
1553 },
1554 Window {
1555 window_id: WindowId,
1556 message: WindowMessage,
1557 },
1558 Webview {
1559 window_id: WindowId,
1560 webview_id: u32,
1561 message: WebviewMessage,
1562 },
1563 NavigateFirstWebview {
1564 window_id: WindowId,
1565 url: String,
1566 },
1567 DragDropScriptEvent {
1568 window_id: WindowId,
1569 webview_id: u32,
1570 target: browser_client::DragDropEventTarget,
1571 drag_drop_state: Arc<Mutex<browser_client::DragDropState>>,
1572 event: browser_client::DragDropScriptEvent,
1573 },
1574 Task(Box<dyn FnOnce() + Send>),
1575 RequestExit(i32),
1576 UserEvent(T),
1577}
1578
1579fn device_event_filter_to_winit(filter: DeviceEventFilter) -> winit::event_loop::DeviceEvents {
1580 match filter {
1581 DeviceEventFilter::Always => winit::event_loop::DeviceEvents::Never,
1582 DeviceEventFilter::Unfocused => winit::event_loop::DeviceEvents::WhenFocused,
1583 DeviceEventFilter::Never => winit::event_loop::DeviceEvents::Always,
1584 }
1585}
1586
1587pub(crate) enum EventLoopMessage {
1588 SetTheme(Option<Theme>),
1589 SetDeviceEventFilter(DeviceEventFilter),
1590 PrimaryMonitor(Sender<Result<Option<Monitor>>>),
1591 MonitorFromPoint(Sender<Result<Option<Monitor>>>, f64, f64),
1592 AvailableMonitors(Sender<Result<Vec<Monitor>>>),
1593 CursorPosition(Sender<Result<PhysicalPosition<f64>>>),
1594 DisplayHandle(Sender<std::result::Result<SendRawDisplayHandle, raw_window_handle::HandleError>>),
1595 #[cfg(target_os = "macos")]
1596 SetActivationPolicy(tauri_runtime::ActivationPolicy),
1597 #[cfg(target_os = "macos")]
1598 SetDockVisibility(bool),
1599 #[cfg(target_os = "macos")]
1600 ShowApplication,
1601 #[cfg(target_os = "macos")]
1602 HideApplication,
1603}
1604
1605#[derive(Debug)]
1606pub(crate) struct WinitDragDropState {
1607 id: DataTransferId,
1608 paths: Option<Vec<PathBuf>>,
1609 paths_requested: bool,
1610 enter_position: Option<PhysicalPosition<f64>>,
1611 latest_position: Option<PhysicalPosition<f64>>,
1612 enter_emitted: bool,
1613 drop_pending: bool,
1614}
1615
1616impl WinitDragDropState {
1617 fn position(&self) -> PhysicalPosition<f64> {
1618 self
1619 .latest_position
1620 .or(self.enter_position)
1621 .unwrap_or_default()
1622 }
1623}
1624
1625fn pending_native_drag_enter(
1626 native_drag_drop: &mut Option<WinitDragDropState>,
1627) -> Option<DragDropEvent> {
1628 native_drag_drop.as_mut().and_then(|state| {
1629 if state.enter_emitted {
1630 return None;
1631 }
1632
1633 let paths = state.paths.clone()?;
1634 let position = state.position();
1635
1636 state.enter_emitted = true;
1637 Some(DragDropEvent::Enter { paths, position })
1638 })
1639}
1640
1641fn pending_native_drag_drop(
1642 native_drag_drop: &mut Option<WinitDragDropState>,
1643) -> Option<DragDropEvent> {
1644 native_drag_drop.as_mut().and_then(|state| {
1645 if !state.drop_pending || !state.enter_emitted {
1646 return None;
1647 }
1648
1649 let paths = state.paths.clone()?;
1650 let position = state.position();
1651
1652 Some(DragDropEvent::Drop { paths, position })
1653 })
1654}
1655
1656fn request_native_drag_paths(
1657 event_loop: &dyn ActiveEventLoop,
1658 native_drag_drop: &mut Option<WinitDragDropState>,
1659) {
1660 let Some(state) = native_drag_drop else {
1661 return;
1662 };
1663 let id = state.id;
1664 if state.paths.is_some() || state.paths_requested {
1665 return;
1666 }
1667
1668 if event_loop
1669 .fetch_data_transfer(id, &TypeHint::UriList)
1670 .is_err()
1671 {
1672 *native_drag_drop = None;
1673 let _ = event_loop.set_valid_dnd_actions(id, &[]);
1674 } else if let Some(state) = native_drag_drop {
1675 state.paths_requested = true;
1676 }
1677}
1678
1679macro_rules! event_loop_getter {
1680 ($self:ident, $variant:ident) => {{
1681 let (tx, rx) = mpsc::channel();
1682 match $self
1683 .context
1684 .send_message(Message::EventLoop(EventLoopMessage::$variant(tx)))
1685 {
1686 Ok(()) => rx.recv().map_err(|_| Error::FailedToReceiveMessage),
1687 Err(error) => Err(error),
1688 }
1689 }};
1690}
1691
1692fn find_monitor_from_point(
1693 monitors: impl Iterator<Item = winit::monitor::MonitorHandle>,
1694 x: f64,
1695 y: f64,
1696) -> Option<winit::monitor::MonitorHandle> {
1697 monitors.into_iter().find(|monitor| {
1698 let pos = monitor.position().unwrap_or_default();
1699 let size = monitor
1700 .current_video_mode()
1701 .map(|mode| mode.size())
1702 .unwrap_or_default();
1703 x >= pos.x as f64
1704 && x <= pos.x as f64 + size.width as f64
1705 && y >= pos.y as f64
1706 && y <= pos.y as f64 + size.height as f64
1707 })
1708}
1709
1710#[cfg(target_os = "macos")]
1711fn is_cef_helper_process() -> bool {
1712 const HELPER_SUFFIXES: &[&str] = &[
1713 " Helper (GPU)",
1714 " Helper (Renderer)",
1715 " Helper (Plugin)",
1716 " Helper (Alerts)",
1717 " Helper",
1718 ];
1719
1720 std::env::current_exe()
1721 .ok()
1722 .and_then(|path| {
1723 path
1724 .file_name()
1725 .and_then(|name| name.to_str())
1726 .map(|name| HELPER_SUFFIXES.iter().any(|suffix| name.ends_with(suffix)))
1727 })
1728 .unwrap_or_default()
1729}
1730
1731pub(crate) struct AppState<T: UserEvent> {
1732 pub(crate) windows: HashMap<WindowId, AppWindow>,
1733 pub(crate) closing_windows: Vec<AppWindow>,
1737 pub(crate) winid_id_to_window_id_map: HashMap<WinitWindowId, WindowId>,
1738 pub(crate) callback: Box<dyn FnMut(RunEvent<T>)>,
1739 pub(crate) live_browsers: usize,
1740 live_popups: HashMap<i32, Arc<crate::popup::PopupFamily>>,
1741 pending_popups: Vec<(crate::popup::PopupRequest, Arc<crate::popup::PopupFamily>)>,
1742 pub(crate) exiting: bool,
1743}
1744
1745pub(crate) struct WinitCefApp<T: UserEvent> {
1746 pub(crate) context: RuntimeContext<T>,
1747 receiver: Receiver<Message<T>>,
1748 pub(crate) state: AppState<T>,
1749 pub(crate) scheme_registry: request_handler::SchemeRegistry,
1750}
1751
1752impl<T: UserEvent> WinitCefApp<T> {
1753 fn new(
1754 context: RuntimeContext<T>,
1755 receiver: Receiver<Message<T>>,
1756 callback: Box<dyn FnMut(RunEvent<T>)>,
1757 scheme_registry: request_handler::SchemeRegistry,
1758 ) -> Self {
1759 Self {
1760 context,
1761 receiver,
1762 state: AppState {
1763 windows: HashMap::new(),
1764 closing_windows: Vec::new(),
1765 winid_id_to_window_id_map: HashMap::new(),
1766 callback,
1767 live_browsers: 0,
1768 live_popups: HashMap::new(),
1769 pending_popups: Vec::new(),
1770 exiting: false,
1771 },
1772 scheme_registry,
1773 }
1774 }
1775
1776 fn run_callback(&mut self, event: RunEvent<T>) {
1777 (self.state.callback)(event);
1778 }
1779
1780 fn install_current_dispatch(
1781 &mut self,
1782 event_loop: &dyn ActiveEventLoop,
1783 ) -> MainThreadDispatchGuard<T> {
1784 let mut dispatch = Box::new(MainThreadDispatch {
1785 app: self as *mut _,
1786 event_loop: event_loop as *const _,
1787 });
1788
1789 let previous = self.context.current_dispatch.install(dispatch.as_mut());
1790
1791 MainThreadDispatchGuard {
1792 context: self.context.clone(),
1793 dispatch,
1794 previous,
1795 }
1796 }
1797
1798 fn drain_messages(&mut self, event_loop: &dyn ActiveEventLoop) {
1799 while let Ok(message) = self.receiver.try_recv() {
1800 self.handle_message(event_loop, message);
1801 }
1802 }
1803
1804 fn handle_message(&mut self, event_loop: &dyn ActiveEventLoop, message: Message<T>) {
1805 match message {
1806 Message::EventLoop(message) => self.handle_event_loop_message(event_loop, message),
1807 Message::PopupPending(request, family) => {
1808 self.state.pending_popups.push((request, family));
1809 }
1810 Message::PopupCreated(request, id, family) => {
1811 self
1812 .state
1813 .pending_popups
1814 .retain(|(pending, _)| !pending.is_same(&request));
1815 self.state.live_popups.insert(id, family);
1816 }
1817 Message::PopupAborted(request) => {
1818 self
1819 .state
1820 .pending_popups
1821 .retain(|(pending, _)| !pending.is_same(&request));
1822 self.exit_if_done(event_loop);
1823 }
1824 Message::PopupClosed(id) => {
1825 self.state.live_popups.remove(&id);
1826 self.exit_if_done(event_loop);
1827 }
1828 Message::BrowserClosed(_window_id, webview_id) => {
1829 let closed = self.state.windows.iter_mut().find_map(|(id, appwindow)| {
1840 appwindow
1841 .children
1842 .iter()
1843 .position(|child| child.webview_id == webview_id)
1844 .map(|index| {
1845 let child = appwindow.children.remove(index);
1846 if let Some(view) = &child.offscreen {
1847 view.visible.store(false, Ordering::Relaxed);
1848 view.request_redraw();
1849 }
1850 (*id, child, appwindow.children.is_empty())
1851 })
1852 });
1853
1854 let mut emptied_window = None;
1855 if let Some((window_id, child, was_last)) = closed {
1856 self.remove_scheme_handler_entries(&child);
1857 if was_last {
1858 emptied_window = Some(window_id);
1859 }
1860 } else {
1861 for appwindow in &mut self.state.closing_windows {
1866 appwindow
1867 .children
1868 .retain(|child| child.webview_id != webview_id);
1869 }
1870 self
1871 .state
1872 .closing_windows
1873 .retain(|appwindow| !appwindow.children.is_empty());
1874 }
1875
1876 self.state.live_browsers = self.state.live_browsers.saturating_sub(1);
1877
1878 if let Some(window_id) = emptied_window {
1883 self.request_window_close(window_id, event_loop);
1884 } else {
1885 self.exit_if_done(event_loop);
1886 }
1887 }
1888 #[cfg(any(target_os = "macos", windows))]
1889 Message::DestroyWebviewHostWindow(webview_id) => {
1890 if let Some(child) = self
1898 .state
1899 .windows
1900 .values()
1901 .chain(self.state.closing_windows.iter())
1902 .flat_map(|appwindow| appwindow.children.iter())
1903 .find(|child| child.webview_id == webview_id)
1904 {
1905 child.destroy_host_window();
1906 }
1907 }
1908 Message::CreateWindow {
1909 window_id,
1910 webview_id,
1911 pending,
1912 after_window_creation,
1913 result_tx,
1914 } => {
1915 let result = self.create_window(
1916 event_loop,
1917 window_id,
1918 webview_id,
1919 pending,
1920 after_window_creation,
1921 );
1922 if let Err(mpsc::SendError(Err(error))) = result_tx.send(result) {
1924 log::error!("failed to create window: {error}");
1925 }
1926 }
1927 Message::CreateWebview {
1928 window_id,
1929 webview_id,
1930 pending,
1931 result_tx,
1932 } => {
1933 let result = self.create_webview(window_id, webview_id, *pending);
1934 if let Err(mpsc::SendError(Err(error))) = result_tx.send(result) {
1935 log::error!("failed to create webview: {error}");
1936 }
1937 }
1938 Message::Window { window_id, message } => {
1939 self.handle_window_message(event_loop, window_id, message)
1940 }
1941 Message::Webview {
1942 window_id,
1943 webview_id,
1944 message,
1945 } => self.handle_webview_message(window_id, webview_id, message),
1946 Message::NavigateFirstWebview { window_id, url } => {
1947 self.navigate_first_webview(window_id, &url)
1948 }
1949 Message::DragDropScriptEvent {
1950 window_id,
1951 webview_id,
1952 target,
1953 drag_drop_state,
1954 event,
1955 } => {
1956 if let Some(event) = browser_client::event_from_script_event(&drag_drop_state, event) {
1957 self.emit_drag_drop_event(window_id, webview_id, target, event);
1958 }
1959 }
1960 Message::Task(task) => task(),
1961 Message::RequestExit(code) => {
1962 if self.request_exit(Some(code)) {
1963 self.close_all_browsers();
1964 self.exit_if_done(event_loop);
1965 }
1966 }
1967 Message::Opened(urls) => self.run_callback(RunEvent::Opened { urls }),
1968 #[cfg(target_os = "macos")]
1969 Message::Reopen {
1970 has_visible_windows,
1971 } => self.run_callback(RunEvent::Reopen {
1972 has_visible_windows,
1973 }),
1974 #[cfg(target_os = "macos")]
1975 Message::AccessibilityChanged { enabled } => self.set_browsers_accessibility_state(enabled),
1976 Message::UserEvent(event) => self.run_callback(RunEvent::UserEvent(event)),
1977 }
1978 }
1979
1980 fn handle_event_loop_message(
1981 &mut self,
1982 event_loop: &dyn ActiveEventLoop,
1983 message: EventLoopMessage,
1984 ) {
1985 match message {
1986 EventLoopMessage::SetTheme(theme) => {
1987 *self.context.app_wide_theme.lock().unwrap() = theme;
1988 for appwindow in self.state.windows.values_mut() {
1989 appwindow.set_theme(theme);
1990 }
1991 }
1992 EventLoopMessage::PrimaryMonitor(tx) => {
1993 let monitor = event_loop
1994 .primary_monitor()
1995 .map(|monitor| winit_monitor_to_tauri_monitor(&monitor));
1996 let _ = tx.send(Ok(monitor));
1997 }
1998 EventLoopMessage::MonitorFromPoint(tx, x, y) => {
1999 let monitor = find_monitor_from_point(event_loop.available_monitors(), x, y)
2000 .map(|monitor| winit_monitor_to_tauri_monitor(&monitor));
2001 let _ = tx.send(Ok(monitor));
2002 }
2003 EventLoopMessage::AvailableMonitors(tx) => {
2004 let monitors = event_loop
2005 .available_monitors()
2006 .map(|monitor| winit_monitor_to_tauri_monitor(&monitor))
2007 .collect();
2008 let _ = tx.send(Ok(monitors));
2009 }
2010 EventLoopMessage::SetDeviceEventFilter(filter) => {
2011 event_loop.listen_device_events(device_event_filter_to_winit(filter));
2012 }
2013 EventLoopMessage::CursorPosition(tx) => {
2014 let _ = tx.send(event_loop.cursor_position());
2015 }
2016 EventLoopMessage::DisplayHandle(tx) => {
2017 let handle = event_loop
2018 .display_handle()
2019 .map(|handle| SendRawDisplayHandle(handle.as_raw()));
2020 let _ = tx.send(handle);
2021 }
2022 #[cfg(target_os = "macos")]
2023 EventLoopMessage::SetActivationPolicy(activation_policy) => {
2024 event_loop.set_activation_policy(activation_policy)
2025 }
2026 #[cfg(target_os = "macos")]
2027 EventLoopMessage::SetDockVisibility(visible) => event_loop.set_dock_visibility(visible),
2028 #[cfg(target_os = "macos")]
2029 EventLoopMessage::ShowApplication => event_loop.show_application(),
2030 #[cfg(target_os = "macos")]
2031 EventLoopMessage::HideApplication => event_loop.hide_application(),
2032 }
2033 }
2034
2035 fn remove_scheme_handler_entries(&self, child: &AppWebview) {
2037 let mut registry = self.scheme_registry.lock().unwrap();
2038 for scheme in child.uri_scheme_protocols.keys() {
2039 registry.remove(&(child.browser_id, scheme.clone()));
2040 }
2041 }
2042
2043 fn emit_drag_drop_event(
2044 &mut self,
2045 window_id: WindowId,
2046 webview_id: u32,
2047 target: browser_client::DragDropEventTarget,
2048 event: DragDropEvent,
2049 ) {
2050 match target {
2051 browser_client::DragDropEventTarget::Window => {
2052 self.emit_window_event(window_id, WindowEvent::DragDrop(event));
2053 }
2054 browser_client::DragDropEventTarget::Webview => {
2055 self.emit_webview_event(window_id, webview_id, WebviewEvent::DragDrop(event));
2056 }
2057 }
2058 }
2059
2060 fn emit_window_event(&mut self, window_id: WindowId, event: WindowEvent) {
2061 let Some(appwindow) = self.state.windows.get(&window_id) else {
2062 return;
2063 };
2064 let label = appwindow.label.clone();
2065 let listeners = appwindow.listeners.clone();
2066
2067 self.run_callback(RunEvent::WindowEvent {
2068 label,
2069 event: event.clone(),
2070 });
2071
2072 {
2073 let listeners = listeners.lock().unwrap();
2074 for handler in listeners.values() {
2075 handler(&event);
2076 }
2077 }
2078 }
2079
2080 fn emit_webview_event(&mut self, window_id: WindowId, webview_id: u32, event: WebviewEvent) {
2081 let Some(appwindow) = self.state.windows.get(&window_id) else {
2082 return;
2083 };
2084 let Some(child) = appwindow
2085 .children
2086 .iter()
2087 .find(|child| child.webview_id == webview_id)
2088 else {
2089 return;
2090 };
2091 let label = child.label.clone();
2092 let listeners = child.listeners.clone();
2093
2094 self.run_callback(RunEvent::WebviewEvent {
2095 label,
2096 event: event.clone(),
2097 });
2098
2099 {
2100 let listeners = listeners.lock().unwrap();
2101 for handler in listeners.values() {
2102 handler(&event);
2103 }
2104 }
2105 }
2106
2107 fn request_exit(&mut self, code: Option<i32>) -> bool {
2108 if self.state.exiting {
2110 return false;
2111 }
2112
2113 let (tx, rx) = mpsc::channel();
2114 self.run_callback(RunEvent::ExitRequested { code, tx });
2115
2116 if matches!(rx.try_recv(), Ok(ExitRequestedEventAction::Prevent)) {
2117 false
2118 } else {
2119 self.state.exiting = true;
2120 true
2121 }
2122 }
2123
2124 pub(crate) fn close_window(&mut self, window_id: WindowId, event_loop: &dyn ActiveEventLoop) {
2125 if !self.state.windows.contains_key(&window_id) {
2126 return;
2127 }
2128 if !self.state.exiting {
2137 self.emit_window_event(window_id, WindowEvent::Destroyed);
2138 }
2139 let Some(appwindow) = self.state.windows.remove(&window_id) else {
2140 return;
2141 };
2142 for child in &appwindow.children {
2143 if let Some(view) = &child.offscreen {
2144 view.visible.store(false, Ordering::Relaxed);
2145 }
2146 }
2147 if let Some(handler) = self.context.window_event_handler.clone() {
2148 handler(
2149 &appwindow.label,
2150 event_loop,
2151 appwindow.window.clone(),
2152 &WinitWindowEvent::Destroyed,
2153 );
2154 }
2155 self
2156 .state
2157 .winid_id_to_window_id_map
2158 .remove(&appwindow.window.id());
2159 for child in &appwindow.children {
2163 self.remove_scheme_handler_entries(child);
2164 child.popup_family.close_all();
2165 child.host.close_dev_tools();
2170 child.host.close_browser(1);
2171 }
2172
2173 if appwindow.children.is_empty() {
2174 drop(appwindow);
2176 } else {
2177 appwindow.window.set_visible(false);
2187 self.state.closing_windows.push(appwindow);
2188 }
2189
2190 self.exit_if_done(event_loop);
2193 }
2194
2195 pub(crate) fn request_window_close(
2196 &mut self,
2197 window_id: WindowId,
2198 event_loop: &dyn ActiveEventLoop,
2199 ) {
2200 if self.state.exiting {
2202 self.close_window(window_id, event_loop);
2203 return;
2204 }
2205
2206 let (tx, rx) = mpsc::channel();
2207 let Some(appwindow) = self.state.windows.get(&window_id) else {
2208 return;
2209 };
2210 let label = appwindow.label.clone();
2211 let listeners = appwindow.listeners.clone();
2212
2213 {
2214 let listeners = listeners.lock().unwrap();
2215 for handler in listeners.values() {
2216 handler(&WindowEvent::CloseRequested {
2217 signal_tx: tx.clone(),
2218 });
2219 }
2220 }
2221
2222 self.run_callback(RunEvent::WindowEvent {
2223 label,
2224 event: WindowEvent::CloseRequested { signal_tx: tx },
2225 });
2226
2227 if !matches!(rx.try_recv(), Ok(true)) {
2228 self.close_window(window_id, event_loop);
2229 }
2230 }
2231
2232 fn navigate_first_webview(&self, window_id: WindowId, url: &str) {
2233 let Some(frame) = self
2234 .state
2235 .windows
2236 .get(&window_id)
2237 .and_then(|window| window.children.first())
2238 .and_then(|webview| webview.browser.main_frame())
2239 else {
2240 return;
2241 };
2242
2243 frame.load_url(Some(&CefString::from(url)));
2244 }
2245
2246 fn close_all_browsers(&mut self) {
2247 for appwindow in self.state.windows.values() {
2252 for child in &appwindow.children {
2253 child.popup_family.close_all();
2254 child.host.close_dev_tools();
2255 child.host.close_browser(1);
2256 }
2257 }
2258 }
2259
2260 #[cfg(target_os = "macos")]
2261 fn set_browsers_accessibility_state(&self, enabled: bool) {
2262 let state = if enabled {
2263 State::ENABLED
2264 } else {
2265 State::DISABLED
2266 };
2267 for appwindow in self.state.windows.values() {
2268 for child in &appwindow.children {
2269 child.host.set_accessibility_state(state);
2270 }
2271 }
2272 }
2273
2274 fn exit_if_done(&mut self, event_loop: &dyn ActiveEventLoop) {
2275 self
2284 .state
2285 .pending_popups
2286 .retain(|(_, family)| !family.is_revoked());
2287
2288 if self.state.live_browsers != 0
2289 || !self.state.live_popups.is_empty()
2290 || !self.state.pending_popups.is_empty()
2291 {
2292 return;
2293 }
2294
2295 if self.state.exiting || (self.state.windows.is_empty() && self.request_exit(None)) {
2296 self.run_callback(RunEvent::Exit);
2297 event_loop.exit();
2298 }
2299 }
2300}
2301
2302impl<T: UserEvent> ApplicationHandler for WinitCefApp<T> {
2306 fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
2307 let _guard = self.install_current_dispatch(event_loop);
2308 self.drain_messages(event_loop);
2309 }
2310
2311 fn new_events(&mut self, event_loop: &dyn ActiveEventLoop, cause: StartCause) {
2312 let _guard = self.install_current_dispatch(event_loop);
2313 match cause {
2314 StartCause::Init => {
2315 self.run_callback(RunEvent::Ready);
2316 self.context.cef_pump.do_work();
2317 }
2318 StartCause::Poll => self.run_callback(RunEvent::Resumed),
2320 _ => {}
2321 }
2322 }
2323
2324 fn proxy_wake_up(&mut self, event_loop: &dyn ActiveEventLoop) {
2325 let _guard = self.install_current_dispatch(event_loop);
2326 self.drain_messages(event_loop);
2327 }
2328
2329 fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) {
2330 let _guard = self.install_current_dispatch(event_loop);
2331 self.apply_pending_activations();
2332 #[cfg(any(
2333 target_os = "linux",
2334 target_os = "dragonfly",
2335 target_os = "freebsd",
2336 target_os = "netbsd",
2337 target_os = "openbsd"
2338 ))]
2339 self.apply_pending_host_layouts();
2340 self.run_callback(RunEvent::MainEventsCleared);
2341 }
2342
2343 fn window_event(
2344 &mut self,
2345 event_loop: &dyn ActiveEventLoop,
2346 winit_id: WinitWindowId,
2347 event: WinitWindowEvent,
2348 ) {
2349 let _guard = self.install_current_dispatch(event_loop);
2350 let Some(window_id) = self.state.winid_id_to_window_id_map.get(&winit_id).copied() else {
2351 return;
2352 };
2353 if !matches!(event, WinitWindowEvent::Destroyed)
2354 && let (Some(handler), Some((label, window))) = (
2355 self.context.window_event_handler.clone(),
2356 self
2357 .state
2358 .windows
2359 .get(&window_id)
2360 .map(|window| (window.label.clone(), window.window.clone())),
2361 )
2362 {
2363 handler(&label, event_loop, window, &event);
2364 }
2365 let Some(appwindow) = self.state.windows.get_mut(&window_id) else {
2366 return;
2367 };
2368
2369 match event {
2370 WinitWindowEvent::CloseRequested => self.request_window_close(window_id, event_loop),
2371
2372 WinitWindowEvent::Destroyed => self.close_window(window_id, event_loop),
2376 WinitWindowEvent::SurfaceResized(size) => {
2377 webview::layout_app_window(appwindow);
2378 self.emit_window_event(window_id, WindowEvent::Resized(size));
2379 }
2380 WinitWindowEvent::ScaleFactorChanged {
2381 scale_factor,
2382 surface_size_writer,
2383 } => {
2384 let new_inner_size = surface_size_writer
2385 .surface_size()
2386 .unwrap_or_else(|_| appwindow.window.surface_size());
2387 webview::layout_app_window(appwindow);
2388 for child in appwindow.children.iter().filter(|c| c.offscreen.is_some()) {
2391 child.host.notify_screen_info_changed();
2392 child.host.was_resized();
2393 }
2394 self.emit_window_event(
2395 window_id,
2396 WindowEvent::ScaleFactorChanged {
2397 scale_factor,
2398 new_inner_size,
2399 },
2400 );
2401 }
2402 WinitWindowEvent::Moved(pos) => {
2403 self.emit_window_event(
2404 window_id,
2405 WindowEvent::Moved(PhysicalPosition::new(pos.x, pos.y)),
2406 );
2407 }
2408 WinitWindowEvent::Focused(focused) => {
2409 self.emit_window_event(window_id, WindowEvent::Focused(focused));
2410 }
2411 WinitWindowEvent::ThemeChanged(theme) => {
2412 let system_theme = winit_theme_to_tauri_theme(theme);
2413 if let Some(explicit_theme) = appwindow.preferred_theme() {
2414 appwindow.set_theme(Some(explicit_theme));
2415 } else {
2416 #[cfg(target_os = "macos")]
2419 appwindow.reapply_traffic_light_position_after_appearance_change();
2420 }
2421 self.emit_window_event(window_id, WindowEvent::ThemeChanged(system_theme));
2422 }
2423 #[cfg(windows)]
2424 WinitWindowEvent::RedrawRequested => {
2425 appwindow.draw_background_surface();
2426 }
2427 WinitWindowEvent::DragEntered { id, position } => {
2428 let has_file_paths = event_loop
2429 .data_transfer(id)
2430 .map(|data_transfer| data_transfer.has_type(&TypeHint::UriList))
2431 .unwrap_or(false);
2432
2433 if has_file_paths {
2434 appwindow.native_drag_drop = Some(WinitDragDropState {
2435 id,
2436 paths: None,
2437 paths_requested: false,
2438 enter_position: position,
2439 latest_position: position,
2440 enter_emitted: false,
2441 drop_pending: false,
2442 });
2443 let _ = event_loop.set_valid_dnd_actions(id, &[DndAction::Copy]);
2444 request_native_drag_paths(event_loop, &mut appwindow.native_drag_drop);
2445 } else {
2446 appwindow.native_drag_drop = None;
2447 let _ = event_loop.set_valid_dnd_actions(id, &[]);
2448 }
2449 }
2450 WinitWindowEvent::DragPosition { id, position, .. } => {
2451 if let Some(state) = appwindow
2452 .native_drag_drop
2453 .as_mut()
2454 .filter(|state| state.id == id)
2455 {
2456 state.latest_position = Some(position);
2457 state.enter_position.get_or_insert(position);
2458 }
2459
2460 let enter_event = pending_native_drag_enter(&mut appwindow.native_drag_drop);
2461
2462 let over_event = appwindow
2463 .native_drag_drop
2464 .as_ref()
2465 .filter(|state| state.id == id && state.enter_emitted)
2466 .map(|_| DragDropEvent::Over { position });
2467
2468 if let Some(event) = enter_event {
2469 self.emit_window_event(window_id, WindowEvent::DragDrop(event));
2470 }
2471 if let Some(event) = over_event {
2472 self.emit_window_event(window_id, WindowEvent::DragDrop(event));
2473 }
2474 }
2475 WinitWindowEvent::DragDropped { id, .. } => {
2476 if let Some(state) = appwindow
2477 .native_drag_drop
2478 .as_mut()
2479 .filter(|state| state.id == id)
2480 {
2481 state.drop_pending = true;
2482 }
2483
2484 request_native_drag_paths(event_loop, &mut appwindow.native_drag_drop);
2485
2486 let enter_event = pending_native_drag_enter(&mut appwindow.native_drag_drop);
2487 let drop_event = pending_native_drag_drop(&mut appwindow.native_drag_drop);
2488
2489 let drop_emitted = drop_event.is_some();
2490 if drop_emitted {
2491 appwindow.native_drag_drop = None;
2492 }
2493
2494 if let Some(event) = enter_event {
2495 self.emit_window_event(window_id, WindowEvent::DragDrop(event));
2496 }
2497 if let Some(event) = drop_event {
2498 self.emit_window_event(window_id, WindowEvent::DragDrop(event));
2499 }
2500 }
2501 WinitWindowEvent::DragLeft { id } => {
2502 let entered = appwindow
2503 .native_drag_drop
2504 .as_ref()
2505 .is_some_and(|state| state.id == id && state.enter_emitted);
2506
2507 appwindow.native_drag_drop = None;
2508
2509 if entered {
2510 self.emit_window_event(window_id, WindowEvent::DragDrop(DragDropEvent::Leave));
2511 }
2512 }
2513 WinitWindowEvent::DataTransferReceived { id, value, .. } => {
2514 let mut reject_drag = false;
2515
2516 if let Some(state) = appwindow
2517 .native_drag_drop
2518 .as_mut()
2519 .filter(|state| state.id == id)
2520 {
2521 match value.try_as_file_paths() {
2522 Ok(paths) if !paths.is_empty() => state.paths = Some(paths),
2523 Ok(_) => reject_drag = state.drop_pending,
2524 Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {}
2525 Err(_) => reject_drag = state.drop_pending,
2526 }
2527 }
2528
2529 if reject_drag {
2530 appwindow.native_drag_drop = None;
2531 let _ = event_loop.set_valid_dnd_actions(id, &[]);
2532 return;
2533 }
2534
2535 let enter_event = pending_native_drag_enter(&mut appwindow.native_drag_drop);
2536 let drop_event = pending_native_drag_drop(&mut appwindow.native_drag_drop);
2537
2538 let drop_emitted = drop_event.is_some();
2539 if drop_emitted {
2540 appwindow.native_drag_drop = None;
2541 }
2542
2543 if let Some(event) = enter_event {
2544 self.emit_window_event(window_id, WindowEvent::DragDrop(event));
2545 }
2546 if let Some(event) = drop_event {
2547 self.emit_window_event(window_id, WindowEvent::DragDrop(event));
2548 }
2549 }
2550 _ => {}
2551 }
2552 }
2553}
2554
2555fn deep_link_arguments<I>(args: I, schemes: &[String]) -> Vec<String>
2563where
2564 I: IntoIterator<Item = String>,
2565{
2566 args
2567 .into_iter()
2568 .filter(|arg| {
2569 url::Url::parse(arg).is_ok_and(|url| schemes.iter().any(|scheme| scheme == url.scheme()))
2570 })
2571 .collect()
2572}
2573
2574fn append_command_line_args(command_line: &mut CommandLine, args: &[(String, Option<String>)]) {
2582 for (arg, value) in args {
2583 if let Some(value) = value {
2584 command_line.append_switch_with_value(
2585 Some(&CefString::from(arg.as_str())),
2586 Some(&CefString::from(value.as_str())),
2587 );
2588 } else if arg.starts_with("-") {
2589 command_line.append_switch(Some(&CefString::from(arg.as_str())));
2590 } else {
2591 command_line.append_argument(Some(&CefString::from(arg.as_str())));
2592 }
2593 }
2594}
2595
2596wrap_with_args! {
2597 wrap_app => TauriCefAppArgs;
2598
2599 struct TauriCefApp<T: UserEvent> {
2600 context: RuntimeContext<T>,
2601 context_initialized: Arc<AtomicBool>,
2602 deep_link_schemes: Vec<String>,
2603 restore_deep_link_arguments: bool,
2606 internal_command_line_args: Vec<(String, Option<String>)>,
2613 browser_command_line_args: Vec<(String, Option<String>)>,
2620 api_version: Option<i32>,
2623 disabled_features: Vec<String>,
2631 enabled_features: Vec<String>,
2632 }
2633
2634 impl App {
2635 fn render_process_handler(&self) -> Option<RenderProcessHandler> {
2636 Some(ipc::TauriRenderProcessHandler::new())
2637 }
2638
2639 fn browser_process_handler(&self) -> Option<BrowserProcessHandler> {
2640 Some(browser_client::TauriCefBrowserProcessHandler::new(
2641 self.context.clone(),
2642 self.context_initialized.clone(),
2643 self.deep_link_schemes.clone(),
2644 self.api_version,
2645 ))
2646 }
2647
2648 fn on_before_command_line_processing(
2649 &self,
2650 process_type: Option<&CefString>,
2651 command_line: Option<&mut CommandLine>,
2652 ) {
2653 let Some(command_line) = command_line else {
2654 return;
2655 };
2656
2657 append_command_line_args(command_line, &self.internal_command_line_args);
2658
2659 let is_browser_process = process_type.is_none_or(|ty| ty.to_string().is_empty());
2662 if is_browser_process {
2663 if self.restore_deep_link_arguments {
2670 for deep_link in deep_link_arguments(std::env::args().skip(1), &self.deep_link_schemes) {
2671 command_line.append_argument(Some(&CefString::from(deep_link.as_str())));
2672 }
2673 }
2674
2675 append_command_line_args(command_line, &self.browser_command_line_args);
2676
2677 crate::switches::append_merged_switch(
2681 command_line,
2682 "disable-features",
2683 &self.disabled_features,
2684 );
2685 crate::switches::append_merged_switch(
2686 command_line,
2687 "enable-features",
2688 &self.enabled_features,
2689 );
2690 }
2691 }
2692 }
2693}
2694
2695pub(crate) const API_VERSION_SWITCH: &str = "tauri-cef-api-version";
2716
2717fn child_process_api_version<I>(args: I) -> Option<i32>
2724where
2725 I: IntoIterator<Item = std::ffi::OsString>,
2726{
2727 let prefix = format!("--{API_VERSION_SWITCH}=");
2728 let mut is_child_process = false;
2729 let mut version = None;
2730 for arg in args {
2732 let Some(arg) = arg.to_str() else {
2733 continue;
2734 };
2735 if arg.starts_with("--type=") {
2736 is_child_process = true;
2737 } else if let Some(value) = arg.strip_prefix(&prefix) {
2738 version = value.parse().ok();
2739 }
2740 }
2741 version.filter(|_| is_child_process)
2742}
2743
2744pub fn run_cef_helper_process() {
2745 let args = cef::args::Args::new();
2746
2747 #[cfg(target_os = "macos")]
2751 let _sandbox = (!crate::sandbox::launched_without_sandbox()).then(|| {
2752 let mut sandbox = cef::sandbox::Sandbox::new();
2753 sandbox.initialize(args.as_main_args());
2754 sandbox
2755 });
2756
2757 #[cfg(target_os = "macos")]
2758 let _loader = {
2759 let loader = cef::library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), true);
2760 assert!(loader.load());
2761 loader
2762 };
2763
2764 let version = child_process_api_version(std::env::args_os()).unwrap_or(sys::CEF_API_VERSION_LAST);
2766 if cef::api_hash(version, 0).is_null() {
2769 panic!("the loaded CEF library does not support CEF API version {version}");
2770 }
2771 let mut app = TauriCefHelperApp::new();
2772 let _ = cef::execute_process(
2773 Some(args.as_main_args()),
2774 Some(&mut app),
2775 std::ptr::null_mut(),
2776 );
2777}
2778
2779wrap_app! {
2780 struct TauriCefHelperApp;
2781
2782 impl App {
2783 fn render_process_handler(&self) -> Option<RenderProcessHandler> {
2784 Some(ipc::TauriRenderProcessHandler::new())
2785 }
2786 }
2787}
2788
2789#[derive(Debug, Clone)]
2790pub struct CefRuntimeHandle<T: UserEvent> {
2791 context: RuntimeContext<T>,
2792}
2793
2794impl<T: UserEvent> RuntimeHandle<T> for CefRuntimeHandle<T> {
2795 type Runtime = CefRuntime<T>;
2796
2797 fn create_proxy(&self) -> <Self::Runtime as Runtime<T>>::EventLoopProxy {
2798 EventProxy {
2799 context: self.context.clone(),
2800 }
2801 }
2802
2803 #[cfg(target_os = "macos")]
2804 fn set_activation_policy(
2805 &self,
2806 activation_policy: tauri_runtime::ActivationPolicy,
2807 ) -> Result<()> {
2808 let message = Message::EventLoop(EventLoopMessage::SetActivationPolicy(activation_policy));
2809 self.context.send_message(message)
2810 }
2811
2812 #[cfg(target_os = "macos")]
2813 fn set_dock_visibility(&self, visible: bool) -> Result<()> {
2814 let message = Message::EventLoop(EventLoopMessage::SetDockVisibility(visible));
2815 self.context.send_message(message)
2816 }
2817
2818 fn request_exit(&self, code: i32) -> Result<()> {
2819 self.context.send_message(Message::RequestExit(code))
2820 }
2821
2822 fn custom_scheme_url(&self, scheme: &str, https: bool) -> String {
2826 format!(
2827 "{}://{scheme}.localhost",
2828 if https { "https" } else { "http" }
2829 )
2830 }
2831
2832 fn webview_version(&self) -> Result<String> {
2833 crate::webview_version()
2834 }
2835
2836 fn create_window<F: Fn(RawWindow<'_>) + Send + 'static>(
2837 &self,
2838 pending: PendingWindow<T, Self::Runtime>,
2839 after_window_creation: Option<F>,
2840 ) -> Result<DetachedWindow<T, Self::Runtime>> {
2841 create_window_detached(&self.context, pending, after_window_creation)
2842 }
2843
2844 fn create_webview(
2845 &self,
2846 window_id: WindowId,
2847 pending: PendingWebview<T, Self::Runtime>,
2848 ) -> Result<DetachedWebview<T, Self::Runtime>> {
2849 create_webview_detached(&self.context, window_id, pending)
2850 }
2851
2852 fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()> {
2853 self.context.run_on_main_thread(f)
2854 }
2855
2856 fn display_handle(
2857 &self,
2858 ) -> std::result::Result<DisplayHandle<'_>, raw_window_handle::HandleError> {
2859 let raw = event_loop_getter!(self, DisplayHandle)
2860 .map_err(|_| raw_window_handle::HandleError::Unavailable)??;
2861 Ok(unsafe { DisplayHandle::borrow_raw(raw.0) })
2864 }
2865
2866 fn primary_monitor(&self) -> Result<Option<Monitor>> {
2867 event_loop_getter!(self, PrimaryMonitor)?
2868 }
2869
2870 fn monitor_from_point(&self, x: f64, y: f64) -> Result<Option<Monitor>> {
2871 let (tx, rx) = mpsc::channel();
2872 self
2873 .context
2874 .send_message(Message::EventLoop(EventLoopMessage::MonitorFromPoint(
2875 tx, x, y,
2876 )))?;
2877 rx.recv().map_err(|_| Error::FailedToReceiveMessage)?
2878 }
2879
2880 fn available_monitors(&self) -> Result<Vec<Monitor>> {
2881 event_loop_getter!(self, AvailableMonitors)?
2882 }
2883
2884 fn cursor_position(&self) -> Result<PhysicalPosition<f64>> {
2885 event_loop_getter!(self, CursorPosition)?
2886 }
2887
2888 fn set_theme(&self, theme: Option<Theme>) {
2889 let message = Message::EventLoop(EventLoopMessage::SetTheme(theme));
2890 let _ = self.context.send_message(message);
2891 }
2892
2893 #[cfg(target_os = "macos")]
2894 fn show(&self) -> Result<()> {
2895 let message = Message::EventLoop(EventLoopMessage::ShowApplication);
2896 self.context.send_message(message)
2897 }
2898
2899 #[cfg(target_os = "macos")]
2900 fn hide(&self) -> Result<()> {
2901 let message = Message::EventLoop(EventLoopMessage::HideApplication);
2902 self.context.send_message(message)
2903 }
2904
2905 fn set_device_event_filter(&self, filter: DeviceEventFilter) {
2906 let message = Message::EventLoop(EventLoopMessage::SetDeviceEventFilter(filter));
2907 let _ = self.context.send_message(message);
2908 }
2909
2910 #[cfg(any(target_os = "macos", target_os = "ios"))]
2911 fn fetch_data_store_identifiers<F: FnOnce(Vec<[u8; 16]>) + Send + 'static>(
2912 &self,
2913 cb: F,
2914 ) -> Result<()> {
2915 cb(Vec::new());
2916 Ok(())
2917 }
2918
2919 #[cfg(any(target_os = "macos", target_os = "ios"))]
2920 fn remove_data_store<F: FnOnce(Result<()>) + Send + 'static>(
2921 &self,
2922 _uuid: [u8; 16],
2923 cb: F,
2924 ) -> Result<()> {
2925 cb(Ok(()));
2926 Ok(())
2927 }
2928}
2929
2930pub struct CefRuntime<T: UserEvent = tauri::EventLoopMessage> {
2931 event_loop: EventLoop,
2932 receiver: Receiver<Message<T>>,
2933 context: RuntimeContext<T>,
2934 scheme_registry: request_handler::SchemeRegistry,
2935 #[cfg(target_os = "macos")]
2936 _app_delegate: Option<objc2::rc::Retained<crate::platform::macos::AppDelegate>>,
2937}
2938
2939impl<T: UserEvent> fmt::Debug for CefRuntime<T> {
2940 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2941 f.debug_struct("CefRuntime").finish()
2942 }
2943}
2944
2945#[cfg(any(
2955 target_os = "linux",
2956 target_os = "dragonfly",
2957 target_os = "freebsd",
2958 target_os = "openbsd",
2959 target_os = "netbsd"
2960))]
2961struct TerminationSignals {
2962 sigint: Option<libc::sigaction>,
2963 sigterm: Option<libc::sigaction>,
2964 sighup: Option<libc::sigaction>,
2965}
2966
2967#[cfg(any(
2968 target_os = "linux",
2969 target_os = "dragonfly",
2970 target_os = "freebsd",
2971 target_os = "openbsd",
2972 target_os = "netbsd"
2973))]
2974impl TerminationSignals {
2975 fn capture() -> Self {
2976 Self {
2977 sigint: Self::capture_one(libc::SIGINT),
2978 sigterm: Self::capture_one(libc::SIGTERM),
2979 sighup: Self::capture_one(libc::SIGHUP),
2980 }
2981 }
2982
2983 fn restore(&self) {
2986 Self::restore_one(libc::SIGINT, self.sigint);
2987 Self::restore_one(libc::SIGTERM, self.sigterm);
2988 Self::restore_one(libc::SIGHUP, self.sighup);
2989 }
2990
2991 fn capture_one(sig: libc::c_int) -> Option<libc::sigaction> {
2992 let mut action = std::mem::MaybeUninit::<libc::sigaction>::uninit();
2993 if unsafe { libc::sigaction(sig, std::ptr::null(), action.as_mut_ptr()) } == 0 {
2994 Some(unsafe { action.assume_init() })
2995 } else {
2996 None
2997 }
2998 }
2999
3000 fn restore_one(sig: libc::c_int, previous: Option<libc::sigaction>) {
3001 let Some(previous) = previous else {
3002 return;
3003 };
3004
3005 unsafe { libc::sigaction(sig, &previous, std::ptr::null_mut()) };
3006 }
3007}
3008
3009impl<T: UserEvent> CefRuntime<T> {
3010 fn init(
3011 mut event_loop_builder: EventLoopBuilder,
3012 runtime_args: RuntimeInitArgs<Cef>,
3013 ) -> Result<Self> {
3014 #[cfg(any(
3017 target_os = "linux",
3018 target_os = "dragonfly",
3019 target_os = "freebsd",
3020 target_os = "openbsd",
3021 target_os = "netbsd"
3022 ))]
3023 let pre_cef_signals = TerminationSignals::capture();
3024
3025 let args = cef::args::Args::new();
3026
3027 #[cfg(target_os = "macos")]
3028 let is_helper = is_cef_helper_process();
3029
3030 #[cfg(target_os = "macos")]
3031 let (_sandbox, _loader) = {
3032 let sandbox = if is_helper && !crate::sandbox::launched_without_sandbox() {
3035 let mut sandbox = cef::sandbox::Sandbox::new();
3036 sandbox.initialize(args.as_main_args());
3037 Some(sandbox)
3038 } else {
3039 None
3040 };
3041
3042 let loader =
3043 cef::library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), is_helper);
3044 assert!(loader.load());
3045
3046 (sandbox, loader)
3047 };
3048
3049 #[cfg(target_os = "macos")]
3050 if !is_helper {
3051 crate::platform::macos::setup_application();
3052 }
3053
3054 let api_version = runtime_args.runtime_init_attrs.api_version;
3063 let version = child_process_api_version(std::env::args_os())
3064 .or(api_version)
3065 .unwrap_or(sys::CEF_API_VERSION_LAST);
3066 if cef::api_hash(version, 0).is_null() {
3069 log::error!("the loaded CEF library does not support CEF API version {version}");
3070 return Err(Error::WebviewRuntimeNotInstalled);
3071 }
3072
3073 let is_browser_process = args
3078 .as_cmd_line()
3079 .map(|cmd| cmd.has_switch(Some(&CefString::from("type"))) != 1)
3080 .unwrap_or(true);
3081
3082 if !is_browser_process {
3083 let mut helper_app = TauriCefHelperApp::new();
3084 let ret = cef::execute_process(
3085 Some(args.as_main_args()),
3086 Some(&mut helper_app),
3087 std::ptr::null_mut(),
3088 );
3089 std::process::exit(ret.max(0));
3092 }
3093
3094 let Cef {
3095 command_line_args,
3096 disabled_features,
3097 enabled_features,
3098 deep_link_schemes,
3099 cache_path: cache_path_override,
3100 secret_storage,
3101 mut profile_preferences,
3102 mut global_preferences,
3103 content_settings,
3104 allow_chromium_command_line_args,
3105 log_file,
3106 log_severity,
3107 log_items,
3108 locale,
3109 accept_language_list,
3110 user_agent,
3111 user_agent_product,
3112 javascript_flags,
3113 chrome_policy_id,
3114 persist_session_cookies,
3115 remote_debugging,
3116 devtools: devtools_policy,
3117 debug_environment,
3118 certificate_errors,
3119 sandbox: sandbox_policy,
3120 downgrade: downgrade_policy,
3121 settings_callback,
3122 window_event_handler,
3123 api_version: _,
3125 } = runtime_args.runtime_init_attrs;
3126
3127 crate::environment::remove_crash_reporter_overrides(debug_environment, tauri::is_dev());
3131
3132 crate::switches::warn_about_dangerous_switches(&command_line_args);
3136 crate::switches::warn_about_replacing_switches(&command_line_args);
3137
3138 #[allow(unused_mut)]
3141 let mut internal_command_line_args: Vec<(String, Option<String>)> = Vec::new();
3142 #[allow(unused_mut)]
3143 let mut browser_command_line_args: Vec<(String, Option<String>)> = Vec::new();
3144
3145 #[cfg(target_os = "macos")]
3148 {
3149 let mock_keychain = match secret_storage {
3150 SecretStorage::Auto => tauri::is_dev(),
3151 SecretStorage::Mock => true,
3152 SecretStorage::System => false,
3153 };
3154 if mock_keychain {
3155 browser_command_line_args.push(("--use-mock-keychain".to_string(), None));
3156 }
3157 }
3158 #[cfg(any(
3159 target_os = "linux",
3160 target_os = "dragonfly",
3161 target_os = "freebsd",
3162 target_os = "netbsd",
3163 target_os = "openbsd"
3164 ))]
3165 {
3166 let basic_password_store = match secret_storage {
3170 SecretStorage::Auto => tauri::is_dev(),
3171 SecretStorage::Mock => true,
3172 SecretStorage::System => false,
3173 };
3174 if basic_password_store {
3175 browser_command_line_args.push(("--password-store".to_string(), Some("basic".to_string())));
3176 }
3177 }
3178
3179 let no_sandbox = {
3186 let decision = crate::sandbox::resolve_sandbox_decision(sandbox_policy);
3187 match decision {
3188 crate::sandbox::SandboxDecision::Keep => false,
3189 crate::sandbox::SandboxDecision::Disable(reason) => {
3190 log::warn!(
3191 "running Chromium without a sandbox: {}. A compromised renderer process runs with the full privileges of the current user.",
3192 reason.message()
3193 );
3194 true
3195 }
3196 crate::sandbox::SandboxDecision::Refuse(reason) => {
3197 log::error!(
3198 "refusing to start: SandboxPolicy::Required asked for a Chromium sandbox, but {}.",
3199 reason.message()
3200 );
3201 return Err(Error::CreateWebview(
3202 format!(
3203 "SandboxPolicy::Required cannot be honored: {}",
3204 reason.message()
3205 )
3206 .into(),
3207 ));
3208 }
3209 }
3210 };
3211 #[cfg(windows)]
3213 let _ = secret_storage;
3214
3215 let devtools_allowed = match devtools_policy {
3219 DevToolsPolicy::Auto => cfg!(debug_assertions) || cfg!(feature = "devtools"),
3220 DevToolsPolicy::Allowed => true,
3221 DevToolsPolicy::Disallowed => false,
3222 };
3223
3224 profile_preferences.insert(
3237 0,
3238 (
3239 crate::cef_impl::preferences::DEVTOOLS_AVAILABILITY.to_string(),
3240 crate::cef_impl::preferences::DEVTOOLS_ALLOWED.into(),
3241 ),
3242 );
3243
3244 let remote_debugging_enabled = match &remote_debugging {
3249 RemoteDebugging::Disabled => false,
3250 RemoteDebugging::Pipe => {
3251 browser_command_line_args.push(("--remote-debugging-pipe".to_string(), None));
3252 true
3253 }
3254 RemoteDebugging::Port {
3255 port,
3256 allowed_origins,
3257 } => {
3258 if *port < 1024 {
3261 log::warn!(
3262 "ignoring RemoteDebugging::Port {{ port: {port} }}: only ports between 1024 and 65535 are accepted"
3263 );
3264 false
3265 } else {
3266 browser_command_line_args.push((
3267 "--remote-debugging-port".to_string(),
3268 Some(port.to_string()),
3269 ));
3270 if !allowed_origins.is_empty() {
3271 browser_command_line_args.push((
3272 "--remote-allow-origins".to_string(),
3273 Some(allowed_origins.join(",")),
3274 ));
3275 }
3276 log::warn!(
3277 "the Chrome DevTools protocol is listening on port {port}. Anything that can \
3278 reach it can read and rewrite every page this application shows."
3279 );
3280 true
3281 }
3282 }
3283 };
3284 if crate::environment::neutralizes_tls_key_log(debug_environment, tauri::is_dev()) {
3289 browser_command_line_args.push(("--ssl-key-log-file".to_string(), Some(String::new())));
3290 }
3291
3292 if !remote_debugging_enabled {
3296 global_preferences.insert(
3297 0,
3298 (
3299 crate::cef_impl::preferences::REMOTE_DEBUGGING_ALLOWED.to_string(),
3300 serde_json::Value::Bool(false),
3301 ),
3302 );
3303 }
3304
3305 let cache_path = cache_path_override.unwrap_or_else(|| {
3306 let cache_base = dirs::cache_dir().unwrap_or_else(std::env::temp_dir);
3307 cache_base.join(&runtime_args.identifier).join("cef")
3308 });
3309 let _ = create_dir_all(&cache_path);
3310 crate::downgrade::prepare_root_cache_path(&cache_path, downgrade_policy);
3315
3316 #[cfg(any(
3322 target_os = "linux",
3323 target_os = "dragonfly",
3324 target_os = "freebsd",
3325 target_os = "netbsd",
3326 target_os = "openbsd"
3327 ))]
3328 {
3329 internal_command_line_args.push(("--ozone-platform".to_string(), Some("x11".to_string())));
3330 unsafe { std::env::set_var("GDK_BACKEND", "x11") };
3340 gtk::gdk::set_allowed_backends("x11");
3341 event_loop_builder.with_gtk4();
3342
3343 tauri_runtime::gtk::declare_version(tauri_runtime::gtk::Version::V4);
3346 }
3347
3348 #[cfg(windows)]
3349 if let Some(hook) = runtime_args.msg_hook {
3350 use winit::platform::windows::EventLoopBuilderExtWindows;
3351 event_loop_builder.with_msg_hook(hook);
3352 }
3353
3354 #[cfg(target_os = "macos")]
3355 event_loop_builder.with_default_menu(false);
3356
3357 let event_loop = event_loop_builder
3358 .build()
3359 .map_err(|e| Error::CreateWindow(Box::new(e)))?;
3360 let proxy = event_loop.create_proxy();
3361 let (sender, receiver) = mpsc::channel();
3362 let context_initialized = Arc::new(AtomicBool::new(false));
3363 let cef_pump = CefExternalPump::new();
3364 let current_dispatch: Arc<MainThreadDispatchSlot<T>> = Default::default();
3365 #[cfg(target_os = "macos")]
3366 let wake = {
3367 let slot = current_dispatch.clone();
3368 crate::platform::macos::MainThreadWake::new(move || slot.deliver_wake(&proxy))
3369 };
3370 let context = RuntimeContext {
3371 window_event_handler,
3372 sender: sender.clone(),
3373 #[cfg(not(target_os = "macos"))]
3374 proxy,
3375 #[cfg(target_os = "macos")]
3376 wake,
3377 main_thread_id: std::thread::current().id(),
3378 next_window_id: Default::default(),
3379 next_webview_id: Default::default(),
3380 next_window_event_id: Default::default(),
3381 next_webview_event_id: Default::default(),
3382 current_dispatch,
3383 app_wide_theme: Default::default(),
3384 cef_pump,
3385 cache_path: Arc::new(cache_path.clone()),
3386 profile_preferences: Arc::new(profile_preferences),
3387 content_settings: Arc::new(content_settings),
3388 certificate_errors,
3389 devtools_allowed,
3390 };
3391
3392 internal_command_line_args.push(("--no-first-run".to_string(), None));
3393
3394 browser_command_line_args.extend(command_line_args);
3397
3398 let command_line_args_disabled = !(allow_chromium_command_line_args || tauri::is_dev());
3409
3410 let mut app = TauriCefApp::build(TauriCefAppArgs {
3411 context: context.clone(),
3412 context_initialized: context_initialized.clone(),
3413 deep_link_schemes,
3414 restore_deep_link_arguments: command_line_args_disabled,
3415 internal_command_line_args,
3416 browser_command_line_args,
3417 api_version,
3418 disabled_features,
3419 enabled_features,
3420 });
3421
3422 let ret = cef::execute_process(
3425 Some(args.as_main_args()),
3426 Some(&mut app),
3427 std::ptr::null_mut(),
3428 );
3429 assert_eq!(
3430 ret, -1,
3431 "CEF browser process unexpectedly returned from execute_process"
3432 );
3433
3434 let log_file = log_file.unwrap_or_else(|| cache_path.join("cef.log"));
3438 let log_severity = log_severity.unwrap_or(if tauri::is_dev() {
3440 LogSeverity::DEFAULT
3441 } else {
3442 LogSeverity::WARNING
3443 });
3444
3445 let mut settings = cef::Settings {
3446 no_sandbox: no_sandbox as std::os::raw::c_int,
3450 cache_path: cache_path.to_string_lossy().to_string().as_str().into(),
3451 command_line_args_disabled: command_line_args_disabled as std::os::raw::c_int,
3452 log_file: log_file.to_string_lossy().to_string().as_str().into(),
3453 log_severity,
3454 persist_session_cookies: persist_session_cookies as std::os::raw::c_int,
3455 external_message_pump: 1,
3456 ..Default::default()
3457 };
3458
3459 if let Some(log_items) = log_items {
3460 settings.log_items = log_items;
3461 }
3462
3463 if let Some(locale) = locale {
3467 settings.locale = locale.as_str().into();
3468 }
3469
3470 let accept_language_list =
3476 accept_language_list.or_else(crate::locale::system_accept_language_list);
3477 if let Some(accept_language_list) = accept_language_list {
3478 settings.accept_language_list = accept_language_list.as_str().into();
3479 }
3480
3481 if let Some(user_agent) = &user_agent {
3484 settings.user_agent = user_agent.as_str().into();
3485 if user_agent_product.is_some() {
3486 log::warn!(
3487 "ignoring the CEF user agent product: Cef::user_agent replaces the whole User-Agent string, including the product token"
3488 );
3489 }
3490 } else if let Some(user_agent_product) = &user_agent_product {
3491 settings.user_agent_product = user_agent_product.as_str().into();
3492 }
3493
3494 if let Some(javascript_flags) = &javascript_flags {
3495 settings.javascript_flags = javascript_flags.as_str().into();
3496 }
3497 if let Some(chrome_policy_id) = &chrome_policy_id {
3498 settings.chrome_policy_id = chrome_policy_id.as_str().into();
3499 }
3500
3501 if let Some(callback) = settings_callback {
3502 callback(&mut settings);
3503 }
3504 if cef::initialize(
3505 Some(args.as_main_args()),
3506 Some(&settings),
3507 Some(&mut app),
3508 std::ptr::null_mut(),
3509 ) != 1
3510 {
3511 return Err(Error::WebviewRuntimeNotInstalled);
3512 }
3513
3514 #[cfg(any(
3515 target_os = "linux",
3516 target_os = "dragonfly",
3517 target_os = "freebsd",
3518 target_os = "openbsd",
3519 target_os = "netbsd"
3520 ))]
3521 pre_cef_signals.restore();
3522
3523 #[cfg(target_os = "macos")]
3524 let app_delegate = if !is_helper {
3525 use crate::platform::macos::AppDelegateEvent;
3526
3527 let context_ = context.clone();
3528 let handler = Box::new(move |event| match event {
3529 AppDelegateEvent::TryTerminate => {
3530 let _ = context_.send_message(Message::RequestExit(0));
3531 }
3532 AppDelegateEvent::Reopen {
3533 has_visible_windows,
3534 } => {
3535 let _ = context_.send_message(Message::Reopen {
3536 has_visible_windows,
3537 });
3538 }
3539 AppDelegateEvent::AccessibilityChanged { enabled } => {
3540 let _ = context_.send_message(Message::AccessibilityChanged { enabled });
3541 }
3542 AppDelegateEvent::OpenURLs { urls } => {
3543 let _ = context_.send_message(Message::Opened(urls));
3544 }
3545 });
3546 let app_delegate = crate::platform::macos::set_application_event_handler(handler);
3547 Some(app_delegate)
3548 } else {
3549 None
3550 };
3551
3552 while !context_initialized.load(Ordering::SeqCst) {
3554 context.cef_pump.do_work();
3555 std::thread::sleep(Duration::from_millis(1));
3556 }
3557
3558 crate::cef_impl::preferences::apply_global_preferences(&global_preferences);
3563
3564 Ok(Self {
3565 event_loop,
3566 receiver,
3567 context,
3568 scheme_registry: Default::default(),
3569 #[cfg(target_os = "macos")]
3570 _app_delegate: app_delegate,
3571 })
3572 }
3573}
3574
3575impl<T: UserEvent> Runtime<T> for CefRuntime<T> {
3576 type WindowDispatcher = CefWindowDispatcher<T>;
3577 type WebviewDispatcher = CefWebviewDispatcher<T>;
3578 type Handle = CefRuntimeHandle<T>;
3579 type EventLoopProxy = EventProxy<T>;
3580 type RuntimeWebviewAttributes = CefWebviewAttributes;
3581 type Webview = Webview;
3582 type RuntimeInitAttrs = Cef;
3583 type WindowOpener = NewWindowOpener;
3584
3585 fn new(args: RuntimeInitArgs<Self::RuntimeInitAttrs>) -> Result<Self> {
3586 Self::init(EventLoopBuilder::default(), args)
3587 }
3588
3589 #[cfg(any(
3590 windows,
3591 target_os = "linux",
3592 target_os = "dragonfly",
3593 target_os = "freebsd",
3594 target_os = "netbsd",
3595 target_os = "openbsd"
3596 ))]
3597 fn new_any_thread(args: RuntimeInitArgs<Self::RuntimeInitAttrs>) -> Result<Self> {
3598 let mut event_loop_builder = EventLoopBuilder::default();
3599 event_loop_builder.with_any_thread(true);
3600 Self::init(event_loop_builder, args)
3601 }
3602
3603 fn create_proxy(&self) -> Self::EventLoopProxy {
3604 EventProxy {
3605 context: self.context.clone(),
3606 }
3607 }
3608
3609 fn handle(&self) -> Self::Handle {
3610 CefRuntimeHandle {
3611 context: self.context.clone(),
3612 }
3613 }
3614
3615 fn create_window<F: Fn(RawWindow<'_>) + Send + 'static>(
3616 &self,
3617 pending: PendingWindow<T, Self>,
3618 after_window_creation: Option<F>,
3619 ) -> Result<DetachedWindow<T, Self>> {
3620 create_window_detached(&self.context, pending, after_window_creation)
3621 }
3622
3623 fn create_webview(
3624 &self,
3625 window_id: WindowId,
3626 pending: PendingWebview<T, Self>,
3627 ) -> Result<DetachedWebview<T, Self>> {
3628 create_webview_detached(&self.context, window_id, pending)
3629 }
3630
3631 fn primary_monitor(&self) -> Option<Monitor> {
3632 event_loop_getter!(self, PrimaryMonitor)
3633 .flatten()
3634 .ok()
3635 .unwrap_or_default()
3636 }
3637
3638 fn monitor_from_point(&self, x: f64, y: f64) -> Option<Monitor> {
3639 let (tx, rx) = mpsc::channel();
3640 self
3641 .context
3642 .send_message(Message::EventLoop(EventLoopMessage::MonitorFromPoint(
3643 tx, x, y,
3644 )))
3645 .and_then(|_| rx.recv().map_err(|_| Error::FailedToReceiveMessage))
3646 .ok()?
3647 .ok()
3648 .unwrap_or_default()
3649 }
3650
3651 fn available_monitors(&self) -> Vec<Monitor> {
3652 event_loop_getter!(self, AvailableMonitors)
3653 .flatten()
3654 .ok()
3655 .unwrap_or_default()
3656 }
3657
3658 fn cursor_position(&self) -> Result<PhysicalPosition<f64>> {
3659 event_loop_getter!(self, CursorPosition)?
3660 }
3661
3662 fn set_theme(&self, theme: Option<Theme>) {
3663 let message = Message::EventLoop(EventLoopMessage::SetTheme(theme));
3664 let _ = self.context.send_message(message);
3665 }
3666
3667 #[cfg(target_os = "macos")]
3668 fn set_activation_policy(&mut self, activation_policy: tauri_runtime::ActivationPolicy) {
3669 let message = Message::EventLoop(EventLoopMessage::SetActivationPolicy(activation_policy));
3670 let _ = self.context.send_message(message);
3671 }
3672
3673 #[cfg(target_os = "macos")]
3674 fn set_activate_ignoring_other_apps(&mut self, ignore: bool) {
3675 if !ignore {
3679 log::warn!(
3680 "`Builder::activate_ignoring_other_apps(false)` is not supported by the CEF runtime yet"
3681 );
3682 }
3683 }
3684
3685 #[cfg(target_os = "macos")]
3686 fn set_dock_visibility(&mut self, visible: bool) {
3687 let message = Message::EventLoop(EventLoopMessage::SetDockVisibility(visible));
3688 let _ = self.context.send_message(message);
3689 }
3690
3691 #[cfg(target_os = "macos")]
3692 fn show(&self) {
3693 let message = Message::EventLoop(EventLoopMessage::ShowApplication);
3694 let _ = self.context.send_message(message);
3695 }
3696
3697 #[cfg(target_os = "macos")]
3698 fn hide(&self) {
3699 let message = Message::EventLoop(EventLoopMessage::HideApplication);
3700 let _ = self.context.send_message(message);
3701 }
3702
3703 fn set_device_event_filter(&mut self, filter: DeviceEventFilter) {
3704 self
3705 .event_loop
3706 .listen_device_events(device_event_filter_to_winit(filter));
3707 }
3708
3709 fn run_iteration<F: FnMut(RunEvent<T>) + 'static>(&mut self, mut callback: F) {
3710 while let Ok(message) = self.receiver.try_recv() {
3711 if let Message::UserEvent(event) = message {
3712 callback(RunEvent::UserEvent(event));
3713 }
3714 }
3715 self.context.cef_pump.do_work();
3716 callback(RunEvent::MainEventsCleared);
3717 }
3718
3719 fn run_return<F: FnMut(RunEvent<T>) + 'static>(self, callback: F) -> i32 {
3720 self.run(callback);
3721 0
3723 }
3724
3725 fn run<F: FnMut(RunEvent<T>) + 'static>(self, callback: F) {
3726 let app = WinitCefApp::new(
3727 self.context,
3728 self.receiver,
3729 Box::new(callback),
3730 self.scheme_registry,
3731 );
3732 let _ = self.event_loop.run_app(app);
3733 cef::shutdown();
3734 }
3735}
3736
3737#[cfg(test)]
3738mod configuration_tests {
3739 use super::*;
3740
3741 #[test]
3742 fn a_fixed_proxy_is_spelled_the_way_chromium_spells_it() {
3743 let preference = ProxyConfig::FixedServers {
3744 server: "socks5://127.0.0.1:9050".to_string(),
3745 bypass_list: Some("*.internal".to_string()),
3746 }
3747 .to_preference();
3748
3749 assert_eq!(
3750 preference,
3751 serde_json::json!({
3752 "mode": "fixed_servers",
3753 "server": "socks5://127.0.0.1:9050",
3754 "bypass_list": "*.internal",
3755 })
3756 );
3757 }
3758
3759 #[test]
3760 fn a_fixed_proxy_without_a_bypass_list_omits_the_key() {
3761 let preference = ProxyConfig::FixedServers {
3764 server: "http://proxy:8080".to_string(),
3765 bypass_list: None,
3766 }
3767 .to_preference();
3768
3769 assert_eq!(
3770 preference,
3771 serde_json::json!({ "mode": "fixed_servers", "server": "http://proxy:8080" })
3772 );
3773 }
3774
3775 #[test]
3776 fn the_modeless_proxy_configurations_carry_only_a_mode() {
3777 for (config, mode) in [
3778 (ProxyConfig::System, "system"),
3779 (ProxyConfig::Direct, "direct"),
3780 (ProxyConfig::AutoDetect, "auto_detect"),
3781 ] {
3782 assert_eq!(config.to_preference(), serde_json::json!({ "mode": mode }));
3783 }
3784 assert_eq!(
3785 ProxyConfig::PacScript {
3786 url: "http://wpad/proxy.pac".to_string()
3787 }
3788 .to_preference(),
3789 serde_json::json!({ "mode": "pac_script", "pac_url": "http://wpad/proxy.pac" })
3790 );
3791 }
3792
3793 #[test]
3794 fn the_default_policies_append_no_switch_at_all() {
3795 assert_eq!(AutoplayPolicy::default().as_switch_value(), None);
3798 assert_eq!(WebRtcIpHandling::default().as_switch_value(), None);
3799 }
3800
3801 #[test]
3802 fn the_named_policies_use_chromiums_own_spelling() {
3803 assert_eq!(
3804 AutoplayPolicy::NoUserGestureRequired.as_switch_value(),
3805 Some("no-user-gesture-required"),
3806 "autoplay values are hyphenated"
3807 );
3808 assert_eq!(
3809 WebRtcIpHandling::DisableNonProxiedUdp.as_switch_value(),
3810 Some("disable_non_proxied_udp"),
3811 "WebRTC values are underscored"
3812 );
3813 }
3814
3815 #[test]
3816 fn remote_debugging_is_off_by_default() {
3817 assert_eq!(RemoteDebugging::default(), RemoteDebugging::Disabled);
3818 }
3819
3820 #[test]
3821 fn the_defaults_are_the_conservative_ones() {
3822 let cef = Cef::default();
3823 assert_eq!(cef.devtools, DevToolsPolicy::Auto);
3824 assert_eq!(cef.debug_environment, DebugEnvironment::Auto);
3825 assert_eq!(cef.sandbox, SandboxPolicy::Auto);
3826 assert_eq!(
3827 cef.downgrade,
3828 DowngradePolicy::KeepProfile,
3829 "a rollback keeps the user's data on disk, as Chrome does; wiping it is opted into"
3830 );
3831 assert!(
3832 !cef.allow_chromium_command_line_args,
3833 "a shipped application must ignore Chromium switches on its command line"
3834 );
3835 assert!(
3836 !cef.persist_session_cookies,
3837 "a session cookie is dropped on exit, as it is in a browser"
3838 );
3839 assert_eq!(
3840 cef.certificate_errors,
3841 CertificateErrorPolicy::ChromeInterstitial
3842 );
3843 }
3844
3845 #[test]
3846 fn a_typed_option_is_just_a_preference() {
3847 let cef = Cef::default().safe_browsing(false).spell_checking(false);
3850 assert!(
3851 cef
3852 .profile_preferences
3853 .iter()
3854 .any(|(name, value)| name == "safebrowsing.enabled" && value == &serde_json::json!(false))
3855 );
3856 assert!(
3857 cef
3858 .profile_preferences
3859 .iter()
3860 .any(|(name, value)| name == "browser.enable_spellchecking"
3861 && value == &serde_json::json!(false))
3862 );
3863 }
3864
3865 #[test]
3866 fn a_later_preference_wins_over_an_earlier_one() {
3867 let cef = Cef::default()
3869 .safe_browsing(false)
3870 .profile_preference("safebrowsing.enabled", true);
3871 let values: Vec<_> = cef
3872 .profile_preferences
3873 .iter()
3874 .filter(|(name, _)| name == "safebrowsing.enabled")
3875 .map(|(_, value)| value.clone())
3876 .collect();
3877 assert_eq!(
3878 values,
3879 [serde_json::json!(false), serde_json::json!(true)],
3880 "both are kept, in call order, so the application's last word wins"
3881 );
3882 }
3883}
3884
3885#[cfg(test)]
3886mod child_process_api_version_tests {
3887 use super::{API_VERSION_SWITCH, child_process_api_version};
3888
3889 fn version(args: &[&str]) -> Option<i32> {
3890 child_process_api_version(args.iter().map(std::ffi::OsString::from))
3891 }
3892
3893 #[test]
3894 fn reads_the_version_the_browser_process_passed() {
3895 assert_eq!(
3896 version(&[
3897 "app",
3898 "--type=renderer",
3899 "--tauri-cef-api-version=15101",
3900 "--lang=en-US"
3901 ]),
3902 Some(15101)
3903 );
3904 assert_eq!(
3906 version(&["app", "--tauri-cef-api-version=15101", "--type=zygote"]),
3907 Some(15101)
3908 );
3909 }
3910
3911 #[test]
3912 fn the_switch_is_spelled_the_way_it_is_appended() {
3913 let arg = format!("--{API_VERSION_SWITCH}=15101");
3914 assert_eq!(version(&["app", "--type=gpu-process", &arg]), Some(15101));
3915 }
3916
3917 #[test]
3918 fn absent_means_the_default() {
3919 assert_eq!(version(&[]), None);
3920 assert_eq!(version(&["app", "--type=renderer"]), None);
3921 assert_eq!(
3923 version(&["app", "--type=renderer", "--tauri-cef-api-version-x=15101"]),
3924 None
3925 );
3926 }
3927
3928 #[test]
3929 fn garbage_means_the_default() {
3930 for value in ["", "latest", "15101x", "1.5", "99999999999", " 15101"] {
3931 let arg = format!("--tauri-cef-api-version={value}");
3932 assert_eq!(
3933 version(&["app", "--type=renderer", &arg]),
3934 None,
3935 "{value:?}"
3936 );
3937 }
3938 assert_eq!(
3940 version(&["app", "--type=renderer", "--tauri-cef-api-version"]),
3941 None
3942 );
3943 }
3944
3945 #[test]
3946 fn the_last_occurrence_wins() {
3947 assert_eq!(
3948 version(&[
3949 "app",
3950 "--type=renderer",
3951 "--tauri-cef-api-version=15000",
3952 "--tauri-cef-api-version=15101"
3953 ]),
3954 Some(15101)
3955 );
3956 assert_eq!(
3957 version(&[
3958 "app",
3959 "--type=renderer",
3960 "--tauri-cef-api-version=15101",
3961 "--tauri-cef-api-version=latest"
3962 ]),
3963 None
3964 );
3965 }
3966
3967 #[test]
3968 fn a_browser_process_ignores_the_switch() {
3969 assert_eq!(version(&["app", "--tauri-cef-api-version=15101"]), None);
3972 }
3973
3974 #[cfg(unix)]
3975 #[test]
3976 fn an_argument_that_is_not_unicode_is_skipped() {
3977 use std::os::unix::ffi::OsStringExt;
3978
3979 let args = vec![
3980 std::ffi::OsString::from("app"),
3981 std::ffi::OsString::from_vec(vec![0xff, 0xfe]),
3982 std::ffi::OsString::from("--type=renderer"),
3983 std::ffi::OsString::from("--tauri-cef-api-version=15101"),
3984 ];
3985 assert_eq!(child_process_api_version(args), Some(15101));
3986 }
3987}
3988
3989#[cfg(test)]
3990mod deep_link_argument_tests {
3991 use super::deep_link_arguments;
3992
3993 fn schemes() -> Vec<String> {
3994 vec!["myapp".to_string(), "my-other-app".to_string()]
3995 }
3996
3997 fn filter(args: &[&str]) -> Vec<String> {
3998 deep_link_arguments(args.iter().map(|arg| (*arg).to_string()), &schemes())
3999 }
4000
4001 #[test]
4002 fn keeps_configured_deep_links_in_order() {
4003 assert_eq!(
4004 filter(&["myapp://open/one", "my-other-app://open/two"]),
4005 vec![
4006 "myapp://open/one".to_string(),
4007 "my-other-app://open/two".to_string(),
4008 ]
4009 );
4010 }
4011
4012 #[test]
4013 fn drops_everything_that_is_not_a_configured_deep_link() {
4014 assert!(
4017 filter(&[
4018 "--remote-debugging-port=9222",
4019 "--disable-web-security",
4020 "/home/user/document.txt",
4021 "not a url",
4022 "",
4023 "https://example.com",
4024 "otherapp://open",
4025 ])
4026 .is_empty()
4027 );
4028 }
4029
4030 #[test]
4031 fn an_empty_scheme_list_keeps_nothing() {
4032 assert!(deep_link_arguments(["myapp://open".to_string()], &[]).is_empty());
4033 }
4034
4035 #[test]
4036 fn scheme_matching_is_exact() {
4037 assert_eq!(filter(&["MYAPP://open"]), vec!["MYAPP://open".to_string()]);
4042 assert!(filter(&["myapp2://open", "myap://open"]).is_empty());
4043 }
4044}