#![allow(clippy::arc_with_non_send_sync)]
#![allow(clippy::too_many_arguments)]
use std::{
collections::HashMap,
fmt,
fs::create_dir_all,
path::PathBuf,
sync::{
Arc, Mutex,
atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering},
mpsc::{self, Receiver, Sender},
},
time::Duration,
};
use cef::*;
use raw_window_handle::{DisplayHandle, HasDisplayHandle};
use tauri_runtime::{
DeviceEventFilter, Error, EventLoopProxy, ExitRequestedEventAction, Result, RunEvent, Runtime,
RuntimeHandle, RuntimeInitArgs, UserEvent,
dpi::PhysicalPosition,
monitor::Monitor,
webview::{DetachedWebview, PendingWebview},
window::{
DetachedWindow, DragDropEvent, PendingWindow, RawWindow, WebviewEvent, WindowEvent, WindowId,
},
};
use tauri_utils::Theme;
use winit::{
application::ApplicationHandler,
data_transfer::{DataTransferId, TypeHint},
event::{StartCause, WindowEvent as WinitWindowEvent},
event_loop::{
ActiveEventLoop, DndAction, EventLoop, EventLoopBuilder, EventLoopProxy as WinitEventLoopProxy,
},
window::WindowId as WinitWindowId,
};
use crate::DebugEnvironment;
use crate::external_message_pump::CefExternalPump;
use crate::platform::EventLoopExt;
use crate::{
cef_impl::{client as browser_client, ipc, request_handler},
macros::wrap_with_args,
webview::{
self, AppWebview, CefWebviewAttributes, CefWebviewDispatcher, Webview, WebviewMessage,
create_webview_detached,
},
window::{
AppWindow, CefWindowDispatcher, WindowMessage, create_window_detached,
winit_monitor_to_tauri_monitor, winit_theme_to_tauri_theme,
},
window_handle::SendRawDisplayHandle,
};
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
use winit::platform::gtk4::EventLoopBuilderExtGtk4;
#[cfg(target_os = "macos")]
use winit::platform::macos::EventLoopBuilderExtMacOS;
#[cfg(windows)]
use winit::platform::windows::EventLoopBuilderExtWindows;
type SettingsCallback = dyn FnOnce(&mut cef::Settings) + Send + Sync;
pub use cef;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum SecretStorage {
#[default]
Auto,
Mock,
System,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum SandboxPolicy {
#[default]
Auto,
Required,
Disabled,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum RemoteDebugging {
#[default]
Disabled,
Port {
port: u16,
allowed_origins: Vec<String>,
},
Pipe,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum DevToolsPolicy {
#[default]
Auto,
Allowed,
Disallowed,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum CertificateErrorPolicy {
#[default]
ChromeInterstitial,
Cancel,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ProxyConfig {
#[default]
System,
Direct,
AutoDetect,
PacScript {
url: String,
},
FixedServers {
server: String,
bypass_list: Option<String>,
},
}
impl ProxyConfig {
fn to_preference(&self) -> serde_json::Value {
match self {
Self::System => serde_json::json!({ "mode": "system" }),
Self::Direct => serde_json::json!({ "mode": "direct" }),
Self::AutoDetect => serde_json::json!({ "mode": "auto_detect" }),
Self::PacScript { url } => serde_json::json!({ "mode": "pac_script", "pac_url": url }),
Self::FixedServers {
server,
bypass_list,
} => {
let mut value = serde_json::json!({ "mode": "fixed_servers", "server": server });
if let Some(bypass_list) = bypass_list
&& let Some(object) = value.as_object_mut()
{
object.insert(
"bypass_list".to_string(),
serde_json::Value::String(bypass_list.clone()),
);
}
value
}
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum AutoplayPolicy {
#[default]
Default,
NoUserGestureRequired,
UserGestureRequired,
DocumentUserActivationRequired,
}
impl AutoplayPolicy {
fn as_switch_value(self) -> Option<&'static str> {
match self {
Self::Default => None,
Self::NoUserGestureRequired => Some("no-user-gesture-required"),
Self::UserGestureRequired => Some("user-gesture-required"),
Self::DocumentUserActivationRequired => Some("document-user-activation-required"),
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum WebRtcIpHandling {
#[default]
Default,
DefaultPublicAndPrivateInterfaces,
DefaultPublicInterfaceOnly,
DisableNonProxiedUdp,
}
impl WebRtcIpHandling {
fn as_switch_value(self) -> Option<&'static str> {
match self {
Self::Default => None,
Self::DefaultPublicAndPrivateInterfaces => Some("default_public_and_private_interfaces"),
Self::DefaultPublicInterfaceOnly => Some("default_public_interface_only"),
Self::DisableNonProxiedUdp => Some("disable_non_proxied_udp"),
}
}
}
#[derive(Default)]
pub struct Cef {
command_line_args: Vec<(String, Option<String>)>,
disabled_features: Vec<String>,
enabled_features: Vec<String>,
deep_link_schemes: Vec<String>,
cache_path: Option<PathBuf>,
api_version: Option<i32>,
secret_storage: SecretStorage,
profile_preferences: Vec<(String, serde_json::Value)>,
global_preferences: Vec<(String, serde_json::Value)>,
content_settings: Vec<(cef::ContentSettingTypes, cef::ContentSettingValues)>,
allow_chromium_command_line_args: bool,
log_file: Option<PathBuf>,
log_severity: Option<LogSeverity>,
log_items: Option<LogItems>,
locale: Option<String>,
accept_language_list: Option<String>,
user_agent: Option<String>,
user_agent_product: Option<String>,
javascript_flags: Option<String>,
chrome_policy_id: Option<String>,
persist_session_cookies: bool,
remote_debugging: RemoteDebugging,
devtools: DevToolsPolicy,
debug_environment: DebugEnvironment,
certificate_errors: CertificateErrorPolicy,
sandbox: SandboxPolicy,
settings_callback: Option<Box<SettingsCallback>>,
}
impl fmt::Debug for Cef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Cef")
.field("command_line_args", &self.command_line_args)
.field("disabled_features", &self.disabled_features)
.field("enabled_features", &self.enabled_features)
.field("deep_link_schemes", &self.deep_link_schemes)
.field("cache_path", &self.cache_path)
.field("api_version", &self.api_version)
.field("secret_storage", &self.secret_storage)
.field("profile_preferences", &self.profile_preferences)
.field("global_preferences", &self.global_preferences)
.field("content_settings", &self.content_settings)
.field(
"allow_chromium_command_line_args",
&self.allow_chromium_command_line_args,
)
.field("log_file", &self.log_file)
.field("log_severity", &self.log_severity)
.field("log_items", &self.log_items)
.field("locale", &self.locale)
.field("accept_language_list", &self.accept_language_list)
.field("user_agent", &self.user_agent)
.field("user_agent_product", &self.user_agent_product)
.field("javascript_flags", &self.javascript_flags)
.field("chrome_policy_id", &self.chrome_policy_id)
.field("persist_session_cookies", &self.persist_session_cookies)
.field("remote_debugging", &self.remote_debugging)
.field("devtools", &self.devtools)
.field("debug_environment", &self.debug_environment)
.field("certificate_errors", &self.certificate_errors)
.field("sandbox", &self.sandbox)
.field("settings_callback", &self.settings_callback.is_some())
.finish()
}
}
impl Cef {
#[must_use]
pub fn with_settings<F>(mut self, callback: F) -> Self
where
F: FnOnce(&mut cef::Settings) + Send + Sync + 'static,
{
self.settings_callback = Some(Box::new(callback));
self
}
#[must_use]
pub fn command_line_arg<K: Into<String>, V: Into<String>>(
mut self,
key: K,
value: Option<V>,
) -> Self {
self
.command_line_args
.push((key.into(), value.map(Into::into)));
self
}
#[must_use]
pub fn command_line_args<K: Into<String>, V: Into<String>>(
mut self,
args: impl IntoIterator<Item = (K, Option<V>)>,
) -> Self {
self
.command_line_args
.extend(args.into_iter().map(|(k, v)| (k.into(), v.map(Into::into))));
self
}
#[must_use]
pub fn deep_link_schemes<S: Into<String>>(
mut self,
schemes: impl IntoIterator<Item = S>,
) -> Self {
self
.deep_link_schemes
.extend(schemes.into_iter().map(Into::into));
self
}
#[must_use]
pub fn root_cache_path<P: AsRef<std::path::Path>>(mut self, path: P) -> Self {
self.cache_path = Some(path.as_ref().to_path_buf());
self
}
#[must_use]
pub fn cef_api_version(mut self, version: i32) -> Self {
self.api_version = Some(version);
self
}
#[must_use]
pub fn secret_storage(mut self, storage: SecretStorage) -> Self {
self.secret_storage = storage;
self
}
#[must_use]
pub fn profile_preference<K: Into<String>>(mut self, name: K, enabled: bool) -> Self {
self
.profile_preferences
.push((name.into(), serde_json::Value::Bool(enabled)));
self
}
#[must_use]
pub fn profile_preference_value<K: Into<String>, V: Into<serde_json::Value>>(
mut self,
name: K,
value: V,
) -> Self {
self.profile_preferences.push((name.into(), value.into()));
self
}
#[must_use]
pub fn global_preference<K: Into<String>, V: Into<serde_json::Value>>(
mut self,
name: K,
value: V,
) -> Self {
self.global_preferences.push((name.into(), value.into()));
self
}
#[must_use]
pub fn default_content_setting(
mut self,
content_type: cef::ContentSettingTypes,
value: cef::ContentSettingValues,
) -> Self {
self.content_settings.push((content_type, value));
self
}
#[must_use]
pub fn allow_chromium_command_line_args(mut self, allow: bool) -> Self {
self.allow_chromium_command_line_args = allow;
self
}
#[must_use]
pub fn log_file<P: AsRef<std::path::Path>>(mut self, path: P) -> Self {
self.log_file = Some(path.as_ref().to_path_buf());
self
}
#[must_use]
pub fn log_severity(mut self, severity: LogSeverity) -> Self {
self.log_severity = Some(severity);
self
}
#[must_use]
pub fn locale<S: Into<String>>(mut self, locale: S) -> Self {
self.locale = Some(locale.into());
self
}
#[must_use]
pub fn accept_language_list<S: Into<String>>(mut self, languages: S) -> Self {
self.accept_language_list = Some(languages.into());
self
}
#[must_use]
pub fn sandbox(mut self, policy: SandboxPolicy) -> Self {
self.sandbox = policy;
self
}
#[must_use]
pub fn disable_features<S: Into<String>>(
mut self,
features: impl IntoIterator<Item = S>,
) -> Self {
self
.disabled_features
.extend(features.into_iter().map(Into::into));
self
}
#[must_use]
pub fn enable_features<S: Into<String>>(mut self, features: impl IntoIterator<Item = S>) -> Self {
self
.enabled_features
.extend(features.into_iter().map(Into::into));
self
}
#[must_use]
pub fn debug_environment(mut self, policy: DebugEnvironment) -> Self {
self.debug_environment = policy;
self
}
#[must_use]
pub fn remote_debugging(mut self, remote_debugging: RemoteDebugging) -> Self {
self.remote_debugging = remote_debugging;
self
}
#[must_use]
pub fn devtools(mut self, policy: DevToolsPolicy) -> Self {
self.devtools = policy;
self
}
#[must_use]
pub fn certificate_errors(mut self, policy: CertificateErrorPolicy) -> Self {
self.certificate_errors = policy;
self
}
#[must_use]
pub fn proxy(mut self, proxy: ProxyConfig) -> Self {
self
.profile_preferences
.push(("proxy".to_string(), proxy.to_preference()));
self
}
#[must_use]
pub fn autoplay(mut self, policy: AutoplayPolicy) -> Self {
if let Some(value) = policy.as_switch_value() {
self
.command_line_args
.push(("--autoplay-policy".to_string(), Some(value.to_string())));
}
self
}
#[must_use]
pub fn webrtc_ip_handling(mut self, policy: WebRtcIpHandling) -> Self {
if let Some(value) = policy.as_switch_value() {
self.command_line_args.push((
"--webrtc-ip-handling-policy".to_string(),
Some(value.to_string()),
));
}
self
}
#[must_use]
pub fn spell_checking(mut self, enabled: bool) -> Self {
self.profile_preferences.push((
"browser.enable_spellchecking".to_string(),
serde_json::Value::Bool(enabled),
));
self
}
#[must_use]
pub fn safe_browsing(mut self, enabled: bool) -> Self {
self.profile_preferences.push((
"safebrowsing.enabled".to_string(),
serde_json::Value::Bool(enabled),
));
self
}
#[must_use]
pub fn component_updates(mut self, enabled: bool) -> Self {
if !enabled {
self
.command_line_args
.push(("--disable-component-update".to_string(), None));
}
self
}
#[must_use]
pub fn user_agent<S: Into<String>>(mut self, user_agent: S) -> Self {
self.user_agent = Some(user_agent.into());
self
}
#[must_use]
pub fn user_agent_product<S: Into<String>>(mut self, product: S) -> Self {
self.user_agent_product = Some(product.into());
self
}
#[must_use]
pub fn persist_session_cookies(mut self, persist: bool) -> Self {
self.persist_session_cookies = persist;
self
}
#[must_use]
pub fn javascript_flags<S: Into<String>>(mut self, flags: S) -> Self {
self.javascript_flags = Some(flags.into());
self
}
#[must_use]
pub fn chrome_policy_id<S: Into<String>>(mut self, policy_id: S) -> Self {
self.chrome_policy_id = Some(policy_id.into());
self
}
#[must_use]
pub fn log_items(mut self, items: LogItems) -> Self {
self.log_items = Some(items);
self
}
}
impl<T: UserEvent> tauri_runtime::RuntimeInitAttrs<T> for Cef {
type Runtime = CefRuntime<T>;
fn apply_config(&mut self, config: &tauri_utils::config::Config) -> Result<()> {
if let Some(plugin_config) = config
.plugins
.0
.get("deep-link")
.and_then(|config| config.get("desktop").cloned())
{
#[derive(serde::Deserialize)]
#[serde(untagged)]
enum DesktopDeepLinks {
One(tauri_utils::config::DeepLinkProtocol),
List(Vec<tauri_utils::config::DeepLinkProtocol>),
}
let protocols: DesktopDeepLinks =
serde_json::from_value(plugin_config).map_err(tauri_runtime::Error::Json)?;
let schemes = match protocols {
DesktopDeepLinks::One(protocol) => protocol.schemes,
DesktopDeepLinks::List(protocols) => protocols
.into_iter()
.flat_map(|protocol| protocol.schemes)
.collect(),
};
self.deep_link_schemes.extend(schemes);
}
Ok(())
}
}
impl<T: UserEvent> From<Cef> for tauri_runtime::dynamic::DynRuntimeInitAttrs<T> {
fn from(attrs: Cef) -> Self {
Self::new(attrs)
}
}
pub struct NewWindowOpener {
source_url: Option<url::Url>,
}
impl NewWindowOpener {
pub(crate) fn new(source_url: Option<url::Url>) -> Self {
Self { source_url }
}
pub fn source_url(&self) -> Option<&url::Url> {
self.source_url.as_ref()
}
}
impl std::fmt::Debug for NewWindowOpener {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("NewWindowOpener")
.field("source_url_observed", &self.source_url.is_some())
.finish()
}
}
#[derive(Clone, Debug)]
pub struct EventProxy<T: UserEvent> {
context: RuntimeContext<T>,
}
impl<T: UserEvent> EventLoopProxy<T> for EventProxy<T> {
fn send_event(&self, event: T) -> Result<()> {
self.context.send_message(Message::UserEvent(event))
}
}
#[derive(Clone)]
pub(crate) struct RuntimeContext<T: UserEvent> {
pub(crate) sender: Sender<Message<T>>,
pub(crate) proxy: WinitEventLoopProxy,
main_thread_id: std::thread::ThreadId,
next_window_id: Arc<AtomicU32>,
next_webview_id: Arc<AtomicU32>,
next_window_event_id: Arc<AtomicU32>,
next_webview_event_id: Arc<AtomicU32>,
current_dispatch: Arc<MainThreadDispatchSlot<T>>,
pub(crate) app_wide_theme: Arc<Mutex<Option<Theme>>>,
pub(crate) cef_pump: CefExternalPump,
pub(crate) cache_path: Arc<PathBuf>,
pub(crate) profile_preferences: Arc<Vec<(String, serde_json::Value)>>,
pub(crate) content_settings: Arc<Vec<(cef::ContentSettingTypes, cef::ContentSettingValues)>>,
pub(crate) certificate_errors: CertificateErrorPolicy,
pub(crate) devtools_allowed: bool,
}
#[derive(Clone, Copy)]
struct MainThreadDispatch<T: UserEvent> {
app: *mut WinitCefApp<T>,
event_loop: *const dyn ActiveEventLoop,
}
struct MainThreadDispatchSlot<T: UserEvent> {
current: AtomicPtr<MainThreadDispatch<T>>,
}
impl<T: UserEvent> MainThreadDispatchSlot<T> {
fn install(&self, dispatch: &mut MainThreadDispatch<T>) -> *mut MainThreadDispatch<T> {
self.current.swap(dispatch, Ordering::AcqRel)
}
fn restore(&self, current: *mut MainThreadDispatch<T>, previous: *mut MainThreadDispatch<T>) {
let installed = self.current.swap(previous, Ordering::AcqRel);
debug_assert_eq!(installed, current);
}
fn current(&self) -> Option<&MainThreadDispatch<T>> {
let current = self.current.load(Ordering::Acquire);
if current.is_null() {
None
} else {
Some(unsafe { &*current })
}
}
}
impl<T: UserEvent> Default for MainThreadDispatchSlot<T> {
fn default() -> Self {
Self {
current: AtomicPtr::new(std::ptr::null_mut()),
}
}
}
struct MainThreadDispatchGuard<T: UserEvent> {
context: RuntimeContext<T>,
dispatch: Box<MainThreadDispatch<T>>,
previous: *mut MainThreadDispatch<T>,
}
impl<T: UserEvent> Drop for MainThreadDispatchGuard<T> {
fn drop(&mut self) {
self
.context
.current_dispatch
.restore(self.dispatch.as_mut(), self.previous);
}
}
fn dispatches_run_event<T: UserEvent>(message: &Message<T>) -> bool {
match message {
Message::UserEvent(_) | Message::Opened(_) => true,
#[cfg(target_os = "macos")]
Message::Reopen { .. } => true,
_ => false,
}
}
#[allow(clippy::result_large_err)]
fn handle_main_thread_message<T: UserEvent>(
context: &RuntimeContext<T>,
message: Message<T>,
) -> std::result::Result<(), Message<T>> {
if dispatches_run_event(&message) {
return Err(message);
}
let Some(dispatch) = context.current_dispatch.current() else {
return Err(message);
};
let app = unsafe { &mut *dispatch.app };
let event_loop = unsafe { &*dispatch.event_loop };
app.handle_message(event_loop, message);
Ok(())
}
impl<T: UserEvent> fmt::Debug for RuntimeContext<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RuntimeContext").finish()
}
}
impl<T: UserEvent> RuntimeContext<T> {
pub(crate) fn send_message(&self, message: Message<T>) -> Result<()> {
let message = if self.is_main_thread() {
match handle_main_thread_message(self, message) {
Ok(()) => return Ok(()),
Err(message) => message,
}
} else {
message
};
self
.sender
.send(message)
.map_err(|_| Error::FailedToSendMessage)?;
self.proxy.wake_up();
Ok(())
}
pub(crate) fn is_main_thread(&self) -> bool {
std::thread::current().id() == self.main_thread_id
}
pub(crate) fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()> {
if self.is_main_thread() {
f();
Ok(())
} else {
self.send_message(Message::Task(Box::new(f)))
}
}
pub(crate) fn next_window_id(&self) -> WindowId {
self.next_window_id.fetch_add(1, Ordering::Relaxed).into()
}
pub(crate) fn next_webview_id(&self) -> u32 {
self.next_webview_id.fetch_add(1, Ordering::Relaxed)
}
pub(crate) fn next_window_event_id(&self) -> u32 {
self.next_window_event_id.fetch_add(1, Ordering::Relaxed)
}
pub(crate) fn next_webview_event_id(&self) -> u32 {
self.next_webview_event_id.fetch_add(1, Ordering::Relaxed)
}
}
pub(crate) type AfterWindowCreationCallback = Box<dyn for<'a> Fn(RawWindow<'a>) + Send>;
pub(crate) enum Message<T: UserEvent> {
EventLoop(EventLoopMessage),
BrowserClosed(WindowId, u32),
PopupPending(crate::popup::PopupRequest, Arc<crate::popup::PopupFamily>),
PopupCreated(
crate::popup::PopupRequest,
i32,
Arc<crate::popup::PopupFamily>,
),
PopupAborted(crate::popup::PopupRequest),
PopupClosed(i32),
#[cfg(any(target_os = "macos", windows))]
DestroyWebviewHostWindow(u32),
Opened(Vec<url::Url>),
#[cfg(target_os = "macos")]
Reopen {
has_visible_windows: bool,
},
#[cfg(target_os = "macos")]
AccessibilityChanged {
enabled: bool,
},
CreateWindow {
window_id: WindowId,
webview_id: Option<u32>,
pending: Box<PendingWindow<T, CefRuntime<T>>>,
after_window_creation: Option<AfterWindowCreationCallback>,
result_tx: Sender<Result<()>>,
},
CreateWebview {
window_id: WindowId,
webview_id: u32,
pending: Box<PendingWebview<T, CefRuntime<T>>>,
result_tx: Sender<Result<()>>,
},
Window {
window_id: WindowId,
message: WindowMessage,
},
Webview {
window_id: WindowId,
webview_id: u32,
message: WebviewMessage,
},
NavigateFirstWebview {
window_id: WindowId,
url: String,
},
DragDropScriptEvent {
window_id: WindowId,
webview_id: u32,
target: browser_client::DragDropEventTarget,
drag_drop_state: Arc<Mutex<browser_client::DragDropState>>,
event: browser_client::DragDropScriptEvent,
},
Task(Box<dyn FnOnce() + Send>),
RequestExit(i32),
UserEvent(T),
}
fn device_event_filter_to_winit(filter: DeviceEventFilter) -> winit::event_loop::DeviceEvents {
match filter {
DeviceEventFilter::Always => winit::event_loop::DeviceEvents::Never,
DeviceEventFilter::Unfocused => winit::event_loop::DeviceEvents::WhenFocused,
DeviceEventFilter::Never => winit::event_loop::DeviceEvents::Always,
}
}
pub(crate) enum EventLoopMessage {
SetTheme(Option<Theme>),
SetDeviceEventFilter(DeviceEventFilter),
PrimaryMonitor(Sender<Result<Option<Monitor>>>),
MonitorFromPoint(Sender<Result<Option<Monitor>>>, f64, f64),
AvailableMonitors(Sender<Result<Vec<Monitor>>>),
CursorPosition(Sender<Result<PhysicalPosition<f64>>>),
DisplayHandle(Sender<std::result::Result<SendRawDisplayHandle, raw_window_handle::HandleError>>),
#[cfg(target_os = "macos")]
SetActivationPolicy(tauri_runtime::ActivationPolicy),
#[cfg(target_os = "macos")]
SetDockVisibility(bool),
#[cfg(target_os = "macos")]
ShowApplication,
#[cfg(target_os = "macos")]
HideApplication,
}
#[derive(Debug)]
pub(crate) struct WinitDragDropState {
id: DataTransferId,
paths: Option<Vec<PathBuf>>,
paths_requested: bool,
enter_position: Option<PhysicalPosition<f64>>,
latest_position: Option<PhysicalPosition<f64>>,
enter_emitted: bool,
drop_pending: bool,
}
impl WinitDragDropState {
fn position(&self) -> PhysicalPosition<f64> {
self
.latest_position
.or(self.enter_position)
.unwrap_or_default()
}
}
fn pending_native_drag_enter(
native_drag_drop: &mut Option<WinitDragDropState>,
) -> Option<DragDropEvent> {
native_drag_drop.as_mut().and_then(|state| {
if state.enter_emitted {
return None;
}
let paths = state.paths.clone()?;
let position = state.position();
state.enter_emitted = true;
Some(DragDropEvent::Enter { paths, position })
})
}
fn pending_native_drag_drop(
native_drag_drop: &mut Option<WinitDragDropState>,
) -> Option<DragDropEvent> {
native_drag_drop.as_mut().and_then(|state| {
if !state.drop_pending || !state.enter_emitted {
return None;
}
let paths = state.paths.clone()?;
let position = state.position();
Some(DragDropEvent::Drop { paths, position })
})
}
fn request_native_drag_paths(
event_loop: &dyn ActiveEventLoop,
native_drag_drop: &mut Option<WinitDragDropState>,
) {
let Some(state) = native_drag_drop else {
return;
};
let id = state.id;
if state.paths.is_some() || state.paths_requested {
return;
}
if event_loop
.fetch_data_transfer(id, &TypeHint::UriList)
.is_err()
{
*native_drag_drop = None;
let _ = event_loop.set_valid_dnd_actions(id, &[]);
} else if let Some(state) = native_drag_drop {
state.paths_requested = true;
}
}
macro_rules! event_loop_getter {
($self:ident, $variant:ident) => {{
let (tx, rx) = mpsc::channel();
match $self
.context
.send_message(Message::EventLoop(EventLoopMessage::$variant(tx)))
{
Ok(()) => rx.recv().map_err(|_| Error::FailedToReceiveMessage),
Err(error) => Err(error),
}
}};
}
fn find_monitor_from_point(
monitors: impl Iterator<Item = winit::monitor::MonitorHandle>,
x: f64,
y: f64,
) -> Option<winit::monitor::MonitorHandle> {
monitors.into_iter().find(|monitor| {
let pos = monitor.position().unwrap_or_default();
let size = monitor
.current_video_mode()
.map(|mode| mode.size())
.unwrap_or_default();
x >= pos.x as f64
&& x <= pos.x as f64 + size.width as f64
&& y >= pos.y as f64
&& y <= pos.y as f64 + size.height as f64
})
}
#[cfg(target_os = "macos")]
fn is_cef_helper_process() -> bool {
const HELPER_SUFFIXES: &[&str] = &[
" Helper (GPU)",
" Helper (Renderer)",
" Helper (Plugin)",
" Helper (Alerts)",
" Helper",
];
std::env::current_exe()
.ok()
.and_then(|path| {
path
.file_name()
.and_then(|name| name.to_str())
.map(|name| HELPER_SUFFIXES.iter().any(|suffix| name.ends_with(suffix)))
})
.unwrap_or_default()
}
pub(crate) struct AppState<T: UserEvent> {
pub(crate) windows: HashMap<WindowId, AppWindow>,
pub(crate) closing_windows: Vec<AppWindow>,
pub(crate) winid_id_to_window_id_map: HashMap<WinitWindowId, WindowId>,
pub(crate) callback: Box<dyn FnMut(RunEvent<T>)>,
pub(crate) live_browsers: usize,
live_popups: HashMap<i32, Arc<crate::popup::PopupFamily>>,
pending_popups: Vec<(crate::popup::PopupRequest, Arc<crate::popup::PopupFamily>)>,
pub(crate) exiting: bool,
}
pub(crate) struct WinitCefApp<T: UserEvent> {
pub(crate) context: RuntimeContext<T>,
receiver: Receiver<Message<T>>,
pub(crate) state: AppState<T>,
pub(crate) scheme_registry: request_handler::SchemeRegistry,
}
impl<T: UserEvent> WinitCefApp<T> {
fn new(
context: RuntimeContext<T>,
receiver: Receiver<Message<T>>,
callback: Box<dyn FnMut(RunEvent<T>)>,
scheme_registry: request_handler::SchemeRegistry,
) -> Self {
Self {
context,
receiver,
state: AppState {
windows: HashMap::new(),
closing_windows: Vec::new(),
winid_id_to_window_id_map: HashMap::new(),
callback,
live_browsers: 0,
live_popups: HashMap::new(),
pending_popups: Vec::new(),
exiting: false,
},
scheme_registry,
}
}
fn run_callback(&mut self, event: RunEvent<T>) {
(self.state.callback)(event);
}
fn install_current_dispatch(
&mut self,
event_loop: &dyn ActiveEventLoop,
) -> MainThreadDispatchGuard<T> {
let mut dispatch = Box::new(MainThreadDispatch {
app: self as *mut _,
event_loop: event_loop as *const _,
});
let previous = self.context.current_dispatch.install(dispatch.as_mut());
MainThreadDispatchGuard {
context: self.context.clone(),
dispatch,
previous,
}
}
fn drain_messages(&mut self, event_loop: &dyn ActiveEventLoop) {
while let Ok(message) = self.receiver.try_recv() {
self.handle_message(event_loop, message);
}
}
fn handle_message(&mut self, event_loop: &dyn ActiveEventLoop, message: Message<T>) {
match message {
Message::EventLoop(message) => self.handle_event_loop_message(event_loop, message),
Message::PopupPending(request, family) => {
self.state.pending_popups.push((request, family));
}
Message::PopupCreated(request, id, family) => {
self
.state
.pending_popups
.retain(|(pending, _)| !pending.is_same(&request));
self.state.live_popups.insert(id, family);
}
Message::PopupAborted(request) => {
self
.state
.pending_popups
.retain(|(pending, _)| !pending.is_same(&request));
self.exit_if_done(event_loop);
}
Message::PopupClosed(id) => {
self.state.live_popups.remove(&id);
self.exit_if_done(event_loop);
}
Message::BrowserClosed(_window_id, webview_id) => {
let closed = self.state.windows.iter_mut().find_map(|(id, appwindow)| {
appwindow
.children
.iter()
.position(|child| child.webview_id == webview_id)
.map(|index| {
let child = appwindow.children.remove(index);
(*id, child, appwindow.children.is_empty())
})
});
let mut emptied_window = None;
if let Some((window_id, child, was_last)) = closed {
self.remove_scheme_handler_entries(&child);
if was_last {
emptied_window = Some(window_id);
}
} else {
for appwindow in &mut self.state.closing_windows {
appwindow
.children
.retain(|child| child.webview_id != webview_id);
}
self
.state
.closing_windows
.retain(|appwindow| !appwindow.children.is_empty());
}
self.state.live_browsers = self.state.live_browsers.saturating_sub(1);
if let Some(window_id) = emptied_window {
self.request_window_close(window_id, event_loop);
} else {
self.exit_if_done(event_loop);
}
}
#[cfg(any(target_os = "macos", windows))]
Message::DestroyWebviewHostWindow(webview_id) => {
if let Some(child) = self
.state
.windows
.values()
.chain(self.state.closing_windows.iter())
.flat_map(|appwindow| appwindow.children.iter())
.find(|child| child.webview_id == webview_id)
{
child.destroy_host_window();
}
}
Message::CreateWindow {
window_id,
webview_id,
pending,
after_window_creation,
result_tx,
} => {
let result = self.create_window(
event_loop,
window_id,
webview_id,
pending,
after_window_creation,
);
let _ = result_tx.send(result);
}
Message::CreateWebview {
window_id,
webview_id,
pending,
result_tx,
} => {
let _ = result_tx.send(self.create_webview(window_id, webview_id, *pending));
}
Message::Window { window_id, message } => {
self.handle_window_message(event_loop, window_id, message)
}
Message::Webview {
window_id,
webview_id,
message,
} => self.handle_webview_message(window_id, webview_id, message),
Message::NavigateFirstWebview { window_id, url } => {
self.navigate_first_webview(window_id, &url)
}
Message::DragDropScriptEvent {
window_id,
webview_id,
target,
drag_drop_state,
event,
} => {
if let Some(event) = browser_client::event_from_script_event(&drag_drop_state, event) {
self.emit_drag_drop_event(window_id, webview_id, target, event);
}
}
Message::Task(task) => task(),
Message::RequestExit(code) => {
if self.request_exit(Some(code)) {
self.close_all_browsers();
self.exit_if_done(event_loop);
}
}
Message::Opened(urls) => self.run_callback(RunEvent::Opened { urls }),
#[cfg(target_os = "macos")]
Message::Reopen {
has_visible_windows,
} => self.run_callback(RunEvent::Reopen {
has_visible_windows,
}),
#[cfg(target_os = "macos")]
Message::AccessibilityChanged { enabled } => self.set_browsers_accessibility_state(enabled),
Message::UserEvent(event) => self.run_callback(RunEvent::UserEvent(event)),
}
}
fn handle_event_loop_message(
&mut self,
event_loop: &dyn ActiveEventLoop,
message: EventLoopMessage,
) {
match message {
EventLoopMessage::SetTheme(theme) => {
*self.context.app_wide_theme.lock().unwrap() = theme;
for appwindow in self.state.windows.values_mut() {
appwindow.set_theme(theme);
}
}
EventLoopMessage::PrimaryMonitor(tx) => {
let monitor = event_loop
.primary_monitor()
.map(|monitor| winit_monitor_to_tauri_monitor(&monitor));
let _ = tx.send(Ok(monitor));
}
EventLoopMessage::MonitorFromPoint(tx, x, y) => {
let monitor = find_monitor_from_point(event_loop.available_monitors(), x, y)
.map(|monitor| winit_monitor_to_tauri_monitor(&monitor));
let _ = tx.send(Ok(monitor));
}
EventLoopMessage::AvailableMonitors(tx) => {
let monitors = event_loop
.available_monitors()
.map(|monitor| winit_monitor_to_tauri_monitor(&monitor))
.collect();
let _ = tx.send(Ok(monitors));
}
EventLoopMessage::SetDeviceEventFilter(filter) => {
event_loop.listen_device_events(device_event_filter_to_winit(filter));
}
EventLoopMessage::CursorPosition(tx) => {
let _ = tx.send(event_loop.cursor_position());
}
EventLoopMessage::DisplayHandle(tx) => {
let handle = event_loop
.display_handle()
.map(|handle| SendRawDisplayHandle(handle.as_raw()));
let _ = tx.send(handle);
}
#[cfg(target_os = "macos")]
EventLoopMessage::SetActivationPolicy(activation_policy) => {
event_loop.set_activation_policy(activation_policy)
}
#[cfg(target_os = "macos")]
EventLoopMessage::SetDockVisibility(visible) => event_loop.set_dock_visibility(visible),
#[cfg(target_os = "macos")]
EventLoopMessage::ShowApplication => event_loop.show_application(),
#[cfg(target_os = "macos")]
EventLoopMessage::HideApplication => event_loop.hide_application(),
}
}
fn remove_scheme_handler_entries(&self, child: &AppWebview) {
let mut registry = self.scheme_registry.lock().unwrap();
for scheme in child.uri_scheme_protocols.keys() {
registry.remove(&(child.browser_id, scheme.clone()));
}
}
fn emit_drag_drop_event(
&mut self,
window_id: WindowId,
webview_id: u32,
target: browser_client::DragDropEventTarget,
event: DragDropEvent,
) {
match target {
browser_client::DragDropEventTarget::Window => {
self.emit_window_event(window_id, WindowEvent::DragDrop(event));
}
browser_client::DragDropEventTarget::Webview => {
self.emit_webview_event(window_id, webview_id, WebviewEvent::DragDrop(event));
}
}
}
fn emit_window_event(&mut self, window_id: WindowId, event: WindowEvent) {
let Some(appwindow) = self.state.windows.get(&window_id) else {
return;
};
let label = appwindow.label.clone();
let listeners = appwindow.listeners.clone();
self.run_callback(RunEvent::WindowEvent {
label,
event: event.clone(),
});
{
let listeners = listeners.lock().unwrap();
for handler in listeners.values() {
handler(&event);
}
}
}
fn emit_webview_event(&mut self, window_id: WindowId, webview_id: u32, event: WebviewEvent) {
let Some(appwindow) = self.state.windows.get(&window_id) else {
return;
};
let Some(child) = appwindow
.children
.iter()
.find(|child| child.webview_id == webview_id)
else {
return;
};
let label = child.label.clone();
let listeners = child.listeners.clone();
self.run_callback(RunEvent::WebviewEvent {
label,
event: event.clone(),
});
{
let listeners = listeners.lock().unwrap();
for handler in listeners.values() {
handler(&event);
}
}
}
fn request_exit(&mut self, code: Option<i32>) -> bool {
if self.state.exiting {
return false;
}
let (tx, rx) = mpsc::channel();
self.run_callback(RunEvent::ExitRequested { code, tx });
if matches!(rx.try_recv(), Ok(ExitRequestedEventAction::Prevent)) {
false
} else {
self.state.exiting = true;
true
}
}
pub(crate) fn close_window(&mut self, window_id: WindowId, event_loop: &dyn ActiveEventLoop) {
if !self.state.windows.contains_key(&window_id) {
return;
}
if !self.state.exiting {
self.emit_window_event(window_id, WindowEvent::Destroyed);
}
let Some(appwindow) = self.state.windows.remove(&window_id) else {
return;
};
self
.state
.winid_id_to_window_id_map
.remove(&appwindow.window.id());
for child in &appwindow.children {
self.remove_scheme_handler_entries(child);
child.popup_family.close_all();
child.host.close_dev_tools();
child.host.close_browser(1);
}
if appwindow.children.is_empty() {
drop(appwindow);
} else {
appwindow.window.set_visible(false);
self.state.closing_windows.push(appwindow);
}
self.exit_if_done(event_loop);
}
pub(crate) fn request_window_close(
&mut self,
window_id: WindowId,
event_loop: &dyn ActiveEventLoop,
) {
if self.state.exiting {
self.close_window(window_id, event_loop);
return;
}
let (tx, rx) = mpsc::channel();
let Some(appwindow) = self.state.windows.get(&window_id) else {
return;
};
let label = appwindow.label.clone();
let listeners = appwindow.listeners.clone();
{
let listeners = listeners.lock().unwrap();
for handler in listeners.values() {
handler(&WindowEvent::CloseRequested {
signal_tx: tx.clone(),
});
}
}
self.run_callback(RunEvent::WindowEvent {
label,
event: WindowEvent::CloseRequested { signal_tx: tx },
});
if !matches!(rx.try_recv(), Ok(true)) {
self.close_window(window_id, event_loop);
}
}
fn navigate_first_webview(&self, window_id: WindowId, url: &str) {
let Some(frame) = self
.state
.windows
.get(&window_id)
.and_then(|window| window.children.first())
.and_then(|webview| webview.browser.main_frame())
else {
return;
};
frame.load_url(Some(&CefString::from(url)));
}
fn close_all_browsers(&mut self) {
for appwindow in self.state.windows.values() {
for child in &appwindow.children {
child.popup_family.close_all();
child.host.close_dev_tools();
child.host.close_browser(1);
}
}
}
#[cfg(target_os = "macos")]
fn set_browsers_accessibility_state(&self, enabled: bool) {
let state = if enabled {
State::ENABLED
} else {
State::DISABLED
};
for appwindow in self.state.windows.values() {
for child in &appwindow.children {
child.host.set_accessibility_state(state);
}
}
}
fn exit_if_done(&mut self, event_loop: &dyn ActiveEventLoop) {
self
.state
.pending_popups
.retain(|(_, family)| !family.is_revoked());
if self.state.live_browsers != 0
|| !self.state.live_popups.is_empty()
|| !self.state.pending_popups.is_empty()
{
return;
}
if self.state.exiting || (self.state.windows.is_empty() && self.request_exit(None)) {
self.run_callback(RunEvent::Exit);
event_loop.exit();
}
}
}
impl<T: UserEvent> ApplicationHandler for WinitCefApp<T> {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let _guard = self.install_current_dispatch(event_loop);
self.drain_messages(event_loop);
}
fn new_events(&mut self, event_loop: &dyn ActiveEventLoop, cause: StartCause) {
let _guard = self.install_current_dispatch(event_loop);
match cause {
StartCause::Init => {
self.run_callback(RunEvent::Ready);
self.context.cef_pump.do_work();
}
StartCause::Poll => self.run_callback(RunEvent::Resumed),
_ => {}
}
}
fn proxy_wake_up(&mut self, event_loop: &dyn ActiveEventLoop) {
let _guard = self.install_current_dispatch(event_loop);
self.drain_messages(event_loop);
}
fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) {
let _guard = self.install_current_dispatch(event_loop);
self.apply_pending_activations();
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
self.apply_pending_host_layouts();
self.run_callback(RunEvent::MainEventsCleared);
}
fn window_event(
&mut self,
event_loop: &dyn ActiveEventLoop,
winit_id: WinitWindowId,
event: WinitWindowEvent,
) {
let _guard = self.install_current_dispatch(event_loop);
let Some(window_id) = self.state.winid_id_to_window_id_map.get(&winit_id).copied() else {
return;
};
let Some(appwindow) = self.state.windows.get_mut(&window_id) else {
return;
};
match event {
WinitWindowEvent::CloseRequested => self.request_window_close(window_id, event_loop),
WinitWindowEvent::Destroyed => self.close_window(window_id, event_loop),
WinitWindowEvent::SurfaceResized(size) => {
webview::layout_app_window(appwindow);
self.emit_window_event(window_id, WindowEvent::Resized(size));
}
WinitWindowEvent::ScaleFactorChanged {
scale_factor,
surface_size_writer,
} => {
let new_inner_size = surface_size_writer
.surface_size()
.unwrap_or_else(|_| appwindow.window.surface_size());
webview::layout_app_window(appwindow);
self.emit_window_event(
window_id,
WindowEvent::ScaleFactorChanged {
scale_factor,
new_inner_size,
},
);
}
WinitWindowEvent::Moved(pos) => {
self.emit_window_event(
window_id,
WindowEvent::Moved(PhysicalPosition::new(pos.x, pos.y)),
);
}
WinitWindowEvent::Focused(focused) => {
self.emit_window_event(window_id, WindowEvent::Focused(focused));
}
WinitWindowEvent::ThemeChanged(theme) => {
let system_theme = winit_theme_to_tauri_theme(theme);
if let Some(explicit_theme) = appwindow.preferred_theme() {
appwindow.set_theme(Some(explicit_theme));
} else {
#[cfg(target_os = "macos")]
appwindow.reapply_traffic_light_position_after_appearance_change();
}
self.emit_window_event(window_id, WindowEvent::ThemeChanged(system_theme));
}
#[cfg(windows)]
WinitWindowEvent::RedrawRequested => {
appwindow.draw_background_surface();
}
WinitWindowEvent::DragEntered { id, position } => {
let has_file_paths = event_loop
.data_transfer(id)
.map(|data_transfer| data_transfer.has_type(&TypeHint::UriList))
.unwrap_or(false);
if has_file_paths {
appwindow.native_drag_drop = Some(WinitDragDropState {
id,
paths: None,
paths_requested: false,
enter_position: position,
latest_position: position,
enter_emitted: false,
drop_pending: false,
});
let _ = event_loop.set_valid_dnd_actions(id, &[DndAction::Copy]);
request_native_drag_paths(event_loop, &mut appwindow.native_drag_drop);
} else {
appwindow.native_drag_drop = None;
let _ = event_loop.set_valid_dnd_actions(id, &[]);
}
}
WinitWindowEvent::DragPosition { id, position, .. } => {
if let Some(state) = appwindow
.native_drag_drop
.as_mut()
.filter(|state| state.id == id)
{
state.latest_position = Some(position);
state.enter_position.get_or_insert(position);
}
let enter_event = pending_native_drag_enter(&mut appwindow.native_drag_drop);
let over_event = appwindow
.native_drag_drop
.as_ref()
.filter(|state| state.id == id && state.enter_emitted)
.map(|_| DragDropEvent::Over { position });
if let Some(event) = enter_event {
self.emit_window_event(window_id, WindowEvent::DragDrop(event));
}
if let Some(event) = over_event {
self.emit_window_event(window_id, WindowEvent::DragDrop(event));
}
}
WinitWindowEvent::DragDropped { id, .. } => {
if let Some(state) = appwindow
.native_drag_drop
.as_mut()
.filter(|state| state.id == id)
{
state.drop_pending = true;
}
request_native_drag_paths(event_loop, &mut appwindow.native_drag_drop);
let enter_event = pending_native_drag_enter(&mut appwindow.native_drag_drop);
let drop_event = pending_native_drag_drop(&mut appwindow.native_drag_drop);
let drop_emitted = drop_event.is_some();
if drop_emitted {
appwindow.native_drag_drop = None;
}
if let Some(event) = enter_event {
self.emit_window_event(window_id, WindowEvent::DragDrop(event));
}
if let Some(event) = drop_event {
self.emit_window_event(window_id, WindowEvent::DragDrop(event));
}
}
WinitWindowEvent::DragLeft { id } => {
let entered = appwindow
.native_drag_drop
.as_ref()
.is_some_and(|state| state.id == id && state.enter_emitted);
appwindow.native_drag_drop = None;
if entered {
self.emit_window_event(window_id, WindowEvent::DragDrop(DragDropEvent::Leave));
}
}
WinitWindowEvent::DataTransferReceived { id, value, .. } => {
let mut reject_drag = false;
if let Some(state) = appwindow
.native_drag_drop
.as_mut()
.filter(|state| state.id == id)
{
match value.try_as_file_paths() {
Ok(paths) if !paths.is_empty() => state.paths = Some(paths),
Ok(_) => reject_drag = state.drop_pending,
Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {}
Err(_) => reject_drag = state.drop_pending,
}
}
if reject_drag {
appwindow.native_drag_drop = None;
let _ = event_loop.set_valid_dnd_actions(id, &[]);
return;
}
let enter_event = pending_native_drag_enter(&mut appwindow.native_drag_drop);
let drop_event = pending_native_drag_drop(&mut appwindow.native_drag_drop);
let drop_emitted = drop_event.is_some();
if drop_emitted {
appwindow.native_drag_drop = None;
}
if let Some(event) = enter_event {
self.emit_window_event(window_id, WindowEvent::DragDrop(event));
}
if let Some(event) = drop_event {
self.emit_window_event(window_id, WindowEvent::DragDrop(event));
}
}
_ => {}
}
}
}
fn deep_link_arguments<I>(args: I, schemes: &[String]) -> Vec<String>
where
I: IntoIterator<Item = String>,
{
args
.into_iter()
.filter(|arg| {
url::Url::parse(arg).is_ok_and(|url| schemes.iter().any(|scheme| scheme == url.scheme()))
})
.collect()
}
fn append_command_line_args(command_line: &mut CommandLine, args: &[(String, Option<String>)]) {
for (arg, value) in args {
if let Some(value) = value {
command_line.append_switch_with_value(
Some(&CefString::from(arg.as_str())),
Some(&CefString::from(value.as_str())),
);
} else if arg.starts_with("-") {
command_line.append_switch(Some(&CefString::from(arg.as_str())));
} else {
command_line.append_argument(Some(&CefString::from(arg.as_str())));
}
}
}
wrap_with_args! {
wrap_app => TauriCefAppArgs;
struct TauriCefApp<T: UserEvent> {
context: RuntimeContext<T>,
context_initialized: Arc<AtomicBool>,
deep_link_schemes: Vec<String>,
restore_deep_link_arguments: bool,
internal_command_line_args: Vec<(String, Option<String>)>,
browser_command_line_args: Vec<(String, Option<String>)>,
disabled_features: Vec<String>,
enabled_features: Vec<String>,
}
impl App {
fn render_process_handler(&self) -> Option<RenderProcessHandler> {
Some(ipc::TauriRenderProcessHandler::new())
}
fn browser_process_handler(&self) -> Option<BrowserProcessHandler> {
Some(browser_client::TauriCefBrowserProcessHandler::new(
self.context.clone(),
self.context_initialized.clone(),
self.deep_link_schemes.clone(),
))
}
fn on_before_command_line_processing(
&self,
process_type: Option<&CefString>,
command_line: Option<&mut CommandLine>,
) {
let Some(command_line) = command_line else {
return;
};
append_command_line_args(command_line, &self.internal_command_line_args);
let is_browser_process = process_type.is_none_or(|ty| ty.to_string().is_empty());
if is_browser_process {
if self.restore_deep_link_arguments {
for deep_link in deep_link_arguments(std::env::args().skip(1), &self.deep_link_schemes) {
command_line.append_argument(Some(&CefString::from(deep_link.as_str())));
}
}
append_command_line_args(command_line, &self.browser_command_line_args);
crate::switches::append_merged_switch(
command_line,
"disable-features",
&self.disabled_features,
);
crate::switches::append_merged_switch(
command_line,
"enable-features",
&self.enabled_features,
);
}
}
}
}
pub fn run_cef_helper_process() {
let args = cef::args::Args::new();
#[cfg(target_os = "macos")]
let _sandbox = (!crate::sandbox::launched_without_sandbox()).then(|| {
let mut sandbox = cef::sandbox::Sandbox::new();
sandbox.initialize(args.as_main_args());
sandbox
});
#[cfg(target_os = "macos")]
let _loader = {
let loader = cef::library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), true);
assert!(loader.load());
loader
};
let _ = cef::api_hash(sys::CEF_API_VERSION_LAST, 0);
let mut app = TauriCefHelperApp::new();
let _ = cef::execute_process(
Some(args.as_main_args()),
Some(&mut app),
std::ptr::null_mut(),
);
}
wrap_app! {
struct TauriCefHelperApp;
impl App {
fn render_process_handler(&self) -> Option<RenderProcessHandler> {
Some(ipc::TauriRenderProcessHandler::new())
}
}
}
#[derive(Debug, Clone)]
pub struct CefRuntimeHandle<T: UserEvent> {
context: RuntimeContext<T>,
}
impl<T: UserEvent> RuntimeHandle<T> for CefRuntimeHandle<T> {
type Runtime = CefRuntime<T>;
fn create_proxy(&self) -> <Self::Runtime as Runtime<T>>::EventLoopProxy {
EventProxy {
context: self.context.clone(),
}
}
#[cfg(target_os = "macos")]
fn set_activation_policy(
&self,
activation_policy: tauri_runtime::ActivationPolicy,
) -> Result<()> {
let message = Message::EventLoop(EventLoopMessage::SetActivationPolicy(activation_policy));
self.context.send_message(message)
}
#[cfg(target_os = "macos")]
fn set_dock_visibility(&self, visible: bool) -> Result<()> {
let message = Message::EventLoop(EventLoopMessage::SetDockVisibility(visible));
self.context.send_message(message)
}
fn request_exit(&self, code: i32) -> Result<()> {
self.context.send_message(Message::RequestExit(code))
}
fn custom_scheme_url(&self, scheme: &str, https: bool) -> String {
format!(
"{}://{scheme}.localhost",
if https { "https" } else { "http" }
)
}
fn webview_version(&self) -> Result<String> {
crate::webview_version()
}
fn create_window<F: Fn(RawWindow<'_>) + Send + 'static>(
&self,
pending: PendingWindow<T, Self::Runtime>,
after_window_creation: Option<F>,
) -> Result<DetachedWindow<T, Self::Runtime>> {
create_window_detached(&self.context, pending, after_window_creation)
}
fn create_webview(
&self,
window_id: WindowId,
pending: PendingWebview<T, Self::Runtime>,
) -> Result<DetachedWebview<T, Self::Runtime>> {
create_webview_detached(&self.context, window_id, pending)
}
fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()> {
self.context.run_on_main_thread(f)
}
fn display_handle(
&self,
) -> std::result::Result<DisplayHandle<'_>, raw_window_handle::HandleError> {
let raw = event_loop_getter!(self, DisplayHandle)
.map_err(|_| raw_window_handle::HandleError::Unavailable)??;
Ok(unsafe { DisplayHandle::borrow_raw(raw.0) })
}
fn primary_monitor(&self) -> Result<Option<Monitor>> {
event_loop_getter!(self, PrimaryMonitor)?
}
fn monitor_from_point(&self, x: f64, y: f64) -> Result<Option<Monitor>> {
let (tx, rx) = mpsc::channel();
self
.context
.send_message(Message::EventLoop(EventLoopMessage::MonitorFromPoint(
tx, x, y,
)))?;
rx.recv().map_err(|_| Error::FailedToReceiveMessage)?
}
fn available_monitors(&self) -> Result<Vec<Monitor>> {
event_loop_getter!(self, AvailableMonitors)?
}
fn cursor_position(&self) -> Result<PhysicalPosition<f64>> {
event_loop_getter!(self, CursorPosition)?
}
fn set_theme(&self, theme: Option<Theme>) {
let message = Message::EventLoop(EventLoopMessage::SetTheme(theme));
let _ = self.context.send_message(message);
}
#[cfg(target_os = "macos")]
fn show(&self) -> Result<()> {
let message = Message::EventLoop(EventLoopMessage::ShowApplication);
self.context.send_message(message)
}
#[cfg(target_os = "macos")]
fn hide(&self) -> Result<()> {
let message = Message::EventLoop(EventLoopMessage::HideApplication);
self.context.send_message(message)
}
fn set_device_event_filter(&self, filter: DeviceEventFilter) {
let message = Message::EventLoop(EventLoopMessage::SetDeviceEventFilter(filter));
let _ = self.context.send_message(message);
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
fn fetch_data_store_identifiers<F: FnOnce(Vec<[u8; 16]>) + Send + 'static>(
&self,
cb: F,
) -> Result<()> {
cb(Vec::new());
Ok(())
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
fn remove_data_store<F: FnOnce(Result<()>) + Send + 'static>(
&self,
_uuid: [u8; 16],
cb: F,
) -> Result<()> {
cb(Ok(()));
Ok(())
}
}
pub struct CefRuntime<T: UserEvent = tauri::EventLoopMessage> {
event_loop: EventLoop,
receiver: Receiver<Message<T>>,
context: RuntimeContext<T>,
scheme_registry: request_handler::SchemeRegistry,
#[cfg(target_os = "macos")]
_app_delegate: Option<objc2::rc::Retained<crate::platform::macos::AppDelegate>>,
}
impl<T: UserEvent> fmt::Debug for CefRuntime<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CefRuntime").finish()
}
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd"
))]
struct TerminationSignals {
sigint: Option<libc::sigaction>,
sigterm: Option<libc::sigaction>,
sighup: Option<libc::sigaction>,
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd"
))]
impl TerminationSignals {
fn capture() -> Self {
Self {
sigint: Self::capture_one(libc::SIGINT),
sigterm: Self::capture_one(libc::SIGTERM),
sighup: Self::capture_one(libc::SIGHUP),
}
}
fn restore(&self) {
Self::restore_one(libc::SIGINT, self.sigint);
Self::restore_one(libc::SIGTERM, self.sigterm);
Self::restore_one(libc::SIGHUP, self.sighup);
}
fn capture_one(sig: libc::c_int) -> Option<libc::sigaction> {
let mut action = std::mem::MaybeUninit::<libc::sigaction>::uninit();
if unsafe { libc::sigaction(sig, std::ptr::null(), action.as_mut_ptr()) } == 0 {
Some(unsafe { action.assume_init() })
} else {
None
}
}
fn restore_one(sig: libc::c_int, previous: Option<libc::sigaction>) {
let Some(previous) = previous else {
return;
};
unsafe { libc::sigaction(sig, &previous, std::ptr::null_mut()) };
}
}
impl<T: UserEvent> CefRuntime<T> {
fn init(
mut event_loop_builder: EventLoopBuilder,
runtime_args: RuntimeInitArgs<Cef>,
) -> Result<Self> {
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd"
))]
let pre_cef_signals = TerminationSignals::capture();
let args = cef::args::Args::new();
#[cfg(target_os = "macos")]
let is_helper = is_cef_helper_process();
#[cfg(target_os = "macos")]
let (_sandbox, _loader) = {
let sandbox = if is_helper && !crate::sandbox::launched_without_sandbox() {
let mut sandbox = cef::sandbox::Sandbox::new();
sandbox.initialize(args.as_main_args());
Some(sandbox)
} else {
None
};
let loader =
cef::library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), is_helper);
assert!(loader.load());
(sandbox, loader)
};
#[cfg(target_os = "macos")]
if !is_helper {
crate::platform::macos::setup_application();
}
let version = runtime_args
.runtime_init_attrs
.api_version
.unwrap_or(sys::CEF_API_VERSION_LAST);
let _ = cef::api_hash(version, 0);
let is_browser_process = args
.as_cmd_line()
.map(|cmd| cmd.has_switch(Some(&CefString::from("type"))) != 1)
.unwrap_or(true);
if !is_browser_process {
let mut helper_app = TauriCefHelperApp::new();
let ret = cef::execute_process(
Some(args.as_main_args()),
Some(&mut helper_app),
std::ptr::null_mut(),
);
std::process::exit(ret.max(0));
}
let Cef {
command_line_args,
disabled_features,
enabled_features,
deep_link_schemes,
cache_path: cache_path_override,
secret_storage,
mut profile_preferences,
mut global_preferences,
content_settings,
allow_chromium_command_line_args,
log_file,
log_severity,
log_items,
locale,
accept_language_list,
user_agent,
user_agent_product,
javascript_flags,
chrome_policy_id,
persist_session_cookies,
remote_debugging,
devtools: devtools_policy,
debug_environment,
certificate_errors,
sandbox: sandbox_policy,
settings_callback,
api_version: _,
} = runtime_args.runtime_init_attrs;
crate::environment::remove_crash_reporter_overrides(debug_environment, tauri::is_dev());
crate::switches::warn_about_dangerous_switches(&command_line_args);
crate::switches::warn_about_replacing_switches(&command_line_args);
#[allow(unused_mut)]
let mut internal_command_line_args: Vec<(String, Option<String>)> = Vec::new();
#[allow(unused_mut)]
let mut browser_command_line_args: Vec<(String, Option<String>)> = Vec::new();
#[cfg(target_os = "macos")]
{
let mock_keychain = match secret_storage {
SecretStorage::Auto => tauri::is_dev(),
SecretStorage::Mock => true,
SecretStorage::System => false,
};
if mock_keychain {
browser_command_line_args.push(("--use-mock-keychain".to_string(), None));
}
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
{
let basic_password_store = match secret_storage {
SecretStorage::Auto => tauri::is_dev(),
SecretStorage::Mock => true,
SecretStorage::System => false,
};
if basic_password_store {
browser_command_line_args.push(("--password-store".to_string(), Some("basic".to_string())));
}
}
let no_sandbox = {
let decision = crate::sandbox::resolve_sandbox_decision(sandbox_policy);
match decision {
crate::sandbox::SandboxDecision::Keep => false,
crate::sandbox::SandboxDecision::Disable(reason) => {
log::warn!(
"running Chromium without a sandbox: {}. A compromised renderer process runs with the full privileges of the current user.",
reason.message()
);
true
}
crate::sandbox::SandboxDecision::Refuse(reason) => {
log::error!(
"refusing to start: SandboxPolicy::Required asked for a Chromium sandbox, but {}.",
reason.message()
);
return Err(Error::CreateWebview(
format!(
"SandboxPolicy::Required cannot be honored: {}",
reason.message()
)
.into(),
));
}
}
};
#[cfg(windows)]
let _ = secret_storage;
let devtools_allowed = match devtools_policy {
DevToolsPolicy::Auto => cfg!(debug_assertions) || cfg!(feature = "devtools"),
DevToolsPolicy::Allowed => true,
DevToolsPolicy::Disallowed => false,
};
profile_preferences.insert(
0,
(
crate::cef_impl::preferences::DEVTOOLS_AVAILABILITY.to_string(),
crate::cef_impl::preferences::DEVTOOLS_ALLOWED.into(),
),
);
let remote_debugging_enabled = match &remote_debugging {
RemoteDebugging::Disabled => false,
RemoteDebugging::Pipe => {
browser_command_line_args.push(("--remote-debugging-pipe".to_string(), None));
true
}
RemoteDebugging::Port {
port,
allowed_origins,
} => {
if *port < 1024 {
log::warn!(
"ignoring RemoteDebugging::Port {{ port: {port} }}: only ports between 1024 and 65535 are accepted"
);
false
} else {
browser_command_line_args.push((
"--remote-debugging-port".to_string(),
Some(port.to_string()),
));
if !allowed_origins.is_empty() {
browser_command_line_args.push((
"--remote-allow-origins".to_string(),
Some(allowed_origins.join(",")),
));
}
log::warn!(
"the Chrome DevTools protocol is listening on port {port}. Anything that can \
reach it can read and rewrite every page this application shows."
);
true
}
}
};
if crate::environment::neutralizes_tls_key_log(debug_environment, tauri::is_dev()) {
browser_command_line_args.push(("--ssl-key-log-file".to_string(), Some(String::new())));
}
if !remote_debugging_enabled {
global_preferences.insert(
0,
(
crate::cef_impl::preferences::REMOTE_DEBUGGING_ALLOWED.to_string(),
serde_json::Value::Bool(false),
),
);
}
let cache_path = cache_path_override.unwrap_or_else(|| {
let cache_base = dirs::cache_dir().unwrap_or_else(std::env::temp_dir);
cache_base.join(&runtime_args.identifier).join("cef")
});
let _ = create_dir_all(&cache_path);
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
{
internal_command_line_args.push(("--ozone-platform".to_string(), Some("x11".to_string())));
unsafe { std::env::set_var("GDK_BACKEND", "x11") };
gtk::gdk::set_allowed_backends("x11");
event_loop_builder.with_gtk4();
tauri_runtime::gtk::declare_version(tauri_runtime::gtk::Version::V4);
}
#[cfg(windows)]
if let Some(hook) = runtime_args.msg_hook {
use winit::platform::windows::EventLoopBuilderExtWindows;
event_loop_builder.with_msg_hook(hook);
}
#[cfg(target_os = "macos")]
event_loop_builder.with_default_menu(false);
let event_loop = event_loop_builder
.build()
.map_err(|_| Error::CreateWindow)?;
let proxy = event_loop.create_proxy();
let (sender, receiver) = mpsc::channel();
let context_initialized = Arc::new(AtomicBool::new(false));
let cef_pump = CefExternalPump::new();
let context = RuntimeContext {
sender: sender.clone(),
proxy: proxy.clone(),
main_thread_id: std::thread::current().id(),
next_window_id: Default::default(),
next_webview_id: Default::default(),
next_window_event_id: Default::default(),
next_webview_event_id: Default::default(),
current_dispatch: Default::default(),
app_wide_theme: Default::default(),
cef_pump,
cache_path: Arc::new(cache_path.clone()),
profile_preferences: Arc::new(profile_preferences),
content_settings: Arc::new(content_settings),
certificate_errors,
devtools_allowed,
};
internal_command_line_args.push(("--no-first-run".to_string(), None));
browser_command_line_args.extend(command_line_args);
let command_line_args_disabled = !(allow_chromium_command_line_args || tauri::is_dev());
let mut app = TauriCefApp::build(TauriCefAppArgs {
context: context.clone(),
context_initialized: context_initialized.clone(),
deep_link_schemes,
restore_deep_link_arguments: command_line_args_disabled,
internal_command_line_args,
browser_command_line_args,
disabled_features,
enabled_features,
});
let ret = cef::execute_process(
Some(args.as_main_args()),
Some(&mut app),
std::ptr::null_mut(),
);
assert_eq!(
ret, -1,
"CEF browser process unexpectedly returned from execute_process"
);
let log_file = log_file.unwrap_or_else(|| cache_path.join("cef.log"));
let log_severity = log_severity.unwrap_or(if tauri::is_dev() {
LogSeverity::DEFAULT
} else {
LogSeverity::WARNING
});
let mut settings = cef::Settings {
no_sandbox: no_sandbox as std::os::raw::c_int,
cache_path: cache_path.to_string_lossy().to_string().as_str().into(),
command_line_args_disabled: command_line_args_disabled as std::os::raw::c_int,
log_file: log_file.to_string_lossy().to_string().as_str().into(),
log_severity,
persist_session_cookies: persist_session_cookies as std::os::raw::c_int,
external_message_pump: 1,
..Default::default()
};
if let Some(log_items) = log_items {
settings.log_items = log_items;
}
if let Some(locale) = locale {
settings.locale = locale.as_str().into();
}
let accept_language_list =
accept_language_list.or_else(crate::locale::system_accept_language_list);
if let Some(accept_language_list) = accept_language_list {
settings.accept_language_list = accept_language_list.as_str().into();
}
if let Some(user_agent) = &user_agent {
settings.user_agent = user_agent.as_str().into();
if user_agent_product.is_some() {
log::warn!(
"ignoring the CEF user agent product: Cef::user_agent replaces the whole User-Agent string, including the product token"
);
}
} else if let Some(user_agent_product) = &user_agent_product {
settings.user_agent_product = user_agent_product.as_str().into();
}
if let Some(javascript_flags) = &javascript_flags {
settings.javascript_flags = javascript_flags.as_str().into();
}
if let Some(chrome_policy_id) = &chrome_policy_id {
settings.chrome_policy_id = chrome_policy_id.as_str().into();
}
if let Some(callback) = settings_callback {
callback(&mut settings);
}
if cef::initialize(
Some(args.as_main_args()),
Some(&settings),
Some(&mut app),
std::ptr::null_mut(),
) != 1
{
return Err(Error::WebviewRuntimeNotInstalled);
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd"
))]
pre_cef_signals.restore();
#[cfg(target_os = "macos")]
let app_delegate = if !is_helper {
use crate::platform::macos::AppDelegateEvent;
let context_ = context.clone();
let handler = Box::new(move |event| match event {
AppDelegateEvent::TryTerminate => {
let _ = context_.send_message(Message::RequestExit(0));
}
AppDelegateEvent::Reopen {
has_visible_windows,
} => {
let _ = context_.send_message(Message::Reopen {
has_visible_windows,
});
}
AppDelegateEvent::AccessibilityChanged { enabled } => {
let _ = context_.send_message(Message::AccessibilityChanged { enabled });
}
AppDelegateEvent::OpenURLs { urls } => {
let _ = context_.send_message(Message::Opened(urls));
}
});
let app_delegate = crate::platform::macos::set_application_event_handler(handler);
Some(app_delegate)
} else {
None
};
while !context_initialized.load(Ordering::SeqCst) {
context.cef_pump.do_work();
std::thread::sleep(Duration::from_millis(1));
}
crate::cef_impl::preferences::apply_global_preferences(&global_preferences);
Ok(Self {
event_loop,
receiver,
context,
scheme_registry: Default::default(),
#[cfg(target_os = "macos")]
_app_delegate: app_delegate,
})
}
}
impl<T: UserEvent> Runtime<T> for CefRuntime<T> {
type WindowDispatcher = CefWindowDispatcher<T>;
type WebviewDispatcher = CefWebviewDispatcher<T>;
type Handle = CefRuntimeHandle<T>;
type EventLoopProxy = EventProxy<T>;
type RuntimeWebviewAttributes = CefWebviewAttributes;
type Webview = Webview;
type RuntimeInitAttrs = Cef;
type WindowOpener = NewWindowOpener;
fn new(args: RuntimeInitArgs<Self::RuntimeInitAttrs>) -> Result<Self> {
Self::init(EventLoopBuilder::default(), args)
}
#[cfg(any(
windows,
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
fn new_any_thread(args: RuntimeInitArgs<Self::RuntimeInitAttrs>) -> Result<Self> {
let mut event_loop_builder = EventLoopBuilder::default();
event_loop_builder.with_any_thread(true);
Self::init(event_loop_builder, args)
}
fn create_proxy(&self) -> Self::EventLoopProxy {
EventProxy {
context: self.context.clone(),
}
}
fn handle(&self) -> Self::Handle {
CefRuntimeHandle {
context: self.context.clone(),
}
}
fn create_window<F: Fn(RawWindow<'_>) + Send + 'static>(
&self,
pending: PendingWindow<T, Self>,
after_window_creation: Option<F>,
) -> Result<DetachedWindow<T, Self>> {
create_window_detached(&self.context, pending, after_window_creation)
}
fn create_webview(
&self,
window_id: WindowId,
pending: PendingWebview<T, Self>,
) -> Result<DetachedWebview<T, Self>> {
create_webview_detached(&self.context, window_id, pending)
}
fn primary_monitor(&self) -> Option<Monitor> {
event_loop_getter!(self, PrimaryMonitor)
.flatten()
.ok()
.unwrap_or_default()
}
fn monitor_from_point(&self, x: f64, y: f64) -> Option<Monitor> {
let (tx, rx) = mpsc::channel();
self
.context
.send_message(Message::EventLoop(EventLoopMessage::MonitorFromPoint(
tx, x, y,
)))
.and_then(|_| rx.recv().map_err(|_| Error::FailedToReceiveMessage))
.ok()?
.ok()
.unwrap_or_default()
}
fn available_monitors(&self) -> Vec<Monitor> {
event_loop_getter!(self, AvailableMonitors)
.flatten()
.ok()
.unwrap_or_default()
}
fn cursor_position(&self) -> Result<PhysicalPosition<f64>> {
event_loop_getter!(self, CursorPosition)?
}
fn set_theme(&self, theme: Option<Theme>) {
let message = Message::EventLoop(EventLoopMessage::SetTheme(theme));
let _ = self.context.send_message(message);
}
#[cfg(target_os = "macos")]
fn set_activation_policy(&mut self, activation_policy: tauri_runtime::ActivationPolicy) {
let message = Message::EventLoop(EventLoopMessage::SetActivationPolicy(activation_policy));
let _ = self.context.send_message(message);
}
#[cfg(target_os = "macos")]
fn set_dock_visibility(&mut self, visible: bool) {
let message = Message::EventLoop(EventLoopMessage::SetDockVisibility(visible));
let _ = self.context.send_message(message);
}
#[cfg(target_os = "macos")]
fn show(&self) {
let message = Message::EventLoop(EventLoopMessage::ShowApplication);
let _ = self.context.send_message(message);
}
#[cfg(target_os = "macos")]
fn hide(&self) {
let message = Message::EventLoop(EventLoopMessage::HideApplication);
let _ = self.context.send_message(message);
}
fn set_device_event_filter(&mut self, filter: DeviceEventFilter) {
self
.event_loop
.listen_device_events(device_event_filter_to_winit(filter));
}
fn run_iteration<F: FnMut(RunEvent<T>) + 'static>(&mut self, mut callback: F) {
while let Ok(message) = self.receiver.try_recv() {
if let Message::UserEvent(event) = message {
callback(RunEvent::UserEvent(event));
}
}
self.context.cef_pump.do_work();
callback(RunEvent::MainEventsCleared);
}
fn run_return<F: FnMut(RunEvent<T>) + 'static>(self, callback: F) -> i32 {
self.run(callback);
0
}
fn run<F: FnMut(RunEvent<T>) + 'static>(self, callback: F) {
let app = WinitCefApp::new(
self.context,
self.receiver,
Box::new(callback),
self.scheme_registry,
);
let _ = self.event_loop.run_app(app);
cef::shutdown();
}
}
#[cfg(test)]
mod configuration_tests {
use super::*;
#[test]
fn a_fixed_proxy_is_spelled_the_way_chromium_spells_it() {
let preference = ProxyConfig::FixedServers {
server: "socks5://127.0.0.1:9050".to_string(),
bypass_list: Some("*.internal".to_string()),
}
.to_preference();
assert_eq!(
preference,
serde_json::json!({
"mode": "fixed_servers",
"server": "socks5://127.0.0.1:9050",
"bypass_list": "*.internal",
})
);
}
#[test]
fn a_fixed_proxy_without_a_bypass_list_omits_the_key() {
let preference = ProxyConfig::FixedServers {
server: "http://proxy:8080".to_string(),
bypass_list: None,
}
.to_preference();
assert_eq!(
preference,
serde_json::json!({ "mode": "fixed_servers", "server": "http://proxy:8080" })
);
}
#[test]
fn the_modeless_proxy_configurations_carry_only_a_mode() {
for (config, mode) in [
(ProxyConfig::System, "system"),
(ProxyConfig::Direct, "direct"),
(ProxyConfig::AutoDetect, "auto_detect"),
] {
assert_eq!(config.to_preference(), serde_json::json!({ "mode": mode }));
}
assert_eq!(
ProxyConfig::PacScript {
url: "http://wpad/proxy.pac".to_string()
}
.to_preference(),
serde_json::json!({ "mode": "pac_script", "pac_url": "http://wpad/proxy.pac" })
);
}
#[test]
fn the_default_policies_append_no_switch_at_all() {
assert_eq!(AutoplayPolicy::default().as_switch_value(), None);
assert_eq!(WebRtcIpHandling::default().as_switch_value(), None);
}
#[test]
fn the_named_policies_use_chromiums_own_spelling() {
assert_eq!(
AutoplayPolicy::NoUserGestureRequired.as_switch_value(),
Some("no-user-gesture-required"),
"autoplay values are hyphenated"
);
assert_eq!(
WebRtcIpHandling::DisableNonProxiedUdp.as_switch_value(),
Some("disable_non_proxied_udp"),
"WebRTC values are underscored"
);
}
#[test]
fn remote_debugging_is_off_by_default() {
assert_eq!(RemoteDebugging::default(), RemoteDebugging::Disabled);
}
#[test]
fn the_defaults_are_the_conservative_ones() {
let cef = Cef::default();
assert_eq!(cef.devtools, DevToolsPolicy::Auto);
assert_eq!(cef.debug_environment, DebugEnvironment::Auto);
assert_eq!(cef.sandbox, SandboxPolicy::Auto);
assert!(
!cef.allow_chromium_command_line_args,
"a shipped application must ignore Chromium switches on its command line"
);
assert!(
!cef.persist_session_cookies,
"a session cookie is dropped on exit, as it is in a browser"
);
assert_eq!(
cef.certificate_errors,
CertificateErrorPolicy::ChromeInterstitial
);
}
#[test]
fn a_typed_option_is_just_a_preference() {
let cef = Cef::default().safe_browsing(false).spell_checking(false);
assert!(
cef
.profile_preferences
.iter()
.any(|(name, value)| name == "safebrowsing.enabled" && value == &serde_json::json!(false))
);
assert!(
cef
.profile_preferences
.iter()
.any(|(name, value)| name == "browser.enable_spellchecking"
&& value == &serde_json::json!(false))
);
}
#[test]
fn a_later_preference_wins_over_an_earlier_one() {
let cef = Cef::default()
.safe_browsing(false)
.profile_preference("safebrowsing.enabled", true);
let values: Vec<_> = cef
.profile_preferences
.iter()
.filter(|(name, _)| name == "safebrowsing.enabled")
.map(|(_, value)| value.clone())
.collect();
assert_eq!(
values,
[serde_json::json!(false), serde_json::json!(true)],
"both are kept, in call order, so the application's last word wins"
);
}
}
#[cfg(test)]
mod deep_link_argument_tests {
use super::deep_link_arguments;
fn schemes() -> Vec<String> {
vec!["myapp".to_string(), "my-other-app".to_string()]
}
fn filter(args: &[&str]) -> Vec<String> {
deep_link_arguments(args.iter().map(|arg| (*arg).to_string()), &schemes())
}
#[test]
fn keeps_configured_deep_links_in_order() {
assert_eq!(
filter(&["myapp://open/one", "my-other-app://open/two"]),
vec![
"myapp://open/one".to_string(),
"my-other-app://open/two".to_string(),
]
);
}
#[test]
fn drops_everything_that_is_not_a_configured_deep_link() {
assert!(
filter(&[
"--remote-debugging-port=9222",
"--disable-web-security",
"/home/user/document.txt",
"not a url",
"",
"https://example.com",
"otherapp://open",
])
.is_empty()
);
}
#[test]
fn an_empty_scheme_list_keeps_nothing() {
assert!(deep_link_arguments(["myapp://open".to_string()], &[]).is_empty());
}
#[test]
fn scheme_matching_is_exact() {
assert_eq!(filter(&["MYAPP://open"]), vec!["MYAPP://open".to_string()]);
assert!(filter(&["myapp2://open", "myap://open"]).is_empty());
}
}