#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::window::WindowId;
use crate::{window::is_label_valid, Rect, Runtime, UserEvent};
use http::Request;
use tauri_utils::config::{
BackgroundThrottlingPolicy, Color, ScrollBarStyle as ConfigScrollBarStyle, WebviewUrl,
WindowConfig, WindowEffectsConfig,
};
use url::Url;
use std::{
borrow::Cow,
collections::HashMap,
hash::{Hash, Hasher},
path::PathBuf,
sync::Arc,
};
type UriSchemeProtocolHandler = dyn Fn(&str, http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)
+ Send
+ Sync
+ 'static;
type WebResourceRequestHandler =
dyn Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync;
type NavigationHandler = dyn Fn(&Url) -> bool + Send;
type NewWindowHandler = dyn Fn(Url, NewWindowFeatures) -> NewWindowResponse + Send + Sync;
type OnPageLoadHandler = dyn Fn(Url, PageLoadEvent) + Send;
type DocumentTitleChangedHandler = dyn Fn(String) + Send + 'static;
type DownloadHandler = dyn Fn(DownloadEvent) -> bool + Send + Sync;
#[cfg(target_os = "ios")]
type InputAccessoryViewBuilderFn = dyn Fn(&objc2_ui_kit::UIView) -> Option<objc2::rc::Retained<objc2_ui_kit::UIView>>
+ Send
+ Sync
+ 'static;
pub enum DownloadEvent<'a> {
Requested {
url: Url,
destination: &'a mut PathBuf,
},
Finished {
url: Url,
path: Option<PathBuf>,
success: bool,
},
}
#[cfg(target_os = "android")]
pub struct CreationContext<'a, 'b> {
pub env: &'a mut jni::JNIEnv<'b>,
pub activity: &'a jni::objects::JObject<'b>,
pub webview: &'a jni::objects::JObject<'b>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PageLoadEvent {
Started,
Finished,
}
#[derive(Debug)]
pub struct NewWindowOpener {
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
))]
pub webview: webkit2gtk::WebView,
#[cfg(windows)]
pub webview: webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2,
#[cfg(windows)]
pub environment: webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Environment,
#[cfg(target_os = "macos")]
pub webview: objc2::rc::Retained<objc2_web_kit::WKWebView>,
#[cfg(target_os = "macos")]
pub target_configuration: objc2::rc::Retained<objc2_web_kit::WKWebViewConfiguration>,
}
#[derive(Debug)]
pub struct NewWindowFeatures {
pub(crate) size: Option<crate::dpi::LogicalSize<f64>>,
pub(crate) position: Option<crate::dpi::LogicalPosition<f64>>,
pub(crate) opener: NewWindowOpener,
}
impl NewWindowFeatures {
pub fn new(
size: Option<crate::dpi::LogicalSize<f64>>,
position: Option<crate::dpi::LogicalPosition<f64>>,
opener: NewWindowOpener,
) -> Self {
Self {
size,
position,
opener,
}
}
pub fn size(&self) -> Option<crate::dpi::LogicalSize<f64>> {
self.size
}
pub fn position(&self) -> Option<crate::dpi::LogicalPosition<f64>> {
self.position
}
pub fn opener(&self) -> &NewWindowOpener {
&self.opener
}
}
pub enum NewWindowResponse {
Allow,
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Create { window_id: WindowId },
Deny,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Default)]
pub enum ScrollBarStyle {
#[default]
Default,
#[cfg(windows)]
FluentOverlay,
}
pub struct PendingWebview<T: UserEvent, R: Runtime<T>> {
pub label: String,
pub webview_attributes: WebviewAttributes,
pub uri_scheme_protocols: HashMap<String, Box<UriSchemeProtocolHandler>>,
pub ipc_handler: Option<WebviewIpcHandler<T, R>>,
pub navigation_handler: Option<Box<NavigationHandler>>,
pub new_window_handler: Option<Box<NewWindowHandler>>,
pub document_title_changed_handler: Option<Box<DocumentTitleChangedHandler>>,
pub url: String,
#[cfg(target_os = "android")]
#[allow(clippy::type_complexity)]
pub on_webview_created:
Option<Box<dyn Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send>>,
pub web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,
pub on_page_load_handler: Option<Box<OnPageLoadHandler>>,
pub download_handler: Option<Arc<DownloadHandler>>,
}
impl<T: UserEvent, R: Runtime<T>> PendingWebview<T, R> {
pub fn new(
webview_attributes: WebviewAttributes,
label: impl Into<String>,
) -> crate::Result<Self> {
let label = label.into();
if !is_label_valid(&label) {
Err(crate::Error::InvalidWindowLabel)
} else {
Ok(Self {
webview_attributes,
uri_scheme_protocols: Default::default(),
label,
ipc_handler: None,
navigation_handler: None,
new_window_handler: None,
document_title_changed_handler: None,
url: "tauri://localhost".to_string(),
#[cfg(target_os = "android")]
on_webview_created: None,
web_resource_request_handler: None,
on_page_load_handler: None,
download_handler: None,
})
}
}
pub fn register_uri_scheme_protocol<
N: Into<String>,
H: Fn(&str, http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)
+ Send
+ Sync
+ 'static,
>(
&mut self,
uri_scheme: N,
protocol_handler: H,
) {
let uri_scheme = uri_scheme.into();
self
.uri_scheme_protocols
.insert(uri_scheme, Box::new(protocol_handler));
}
#[cfg(target_os = "android")]
pub fn on_webview_created<
F: Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send + 'static,
>(
mut self,
f: F,
) -> Self {
self.on_webview_created.replace(Box::new(f));
self
}
}
#[derive(Debug)]
pub struct DetachedWebview<T: UserEvent, R: Runtime<T>> {
pub label: String,
pub dispatcher: R::WebviewDispatcher,
}
impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWebview<T, R> {
fn clone(&self) -> Self {
Self {
label: self.label.clone(),
dispatcher: self.dispatcher.clone(),
}
}
}
impl<T: UserEvent, R: Runtime<T>> Hash for DetachedWebview<T, R> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.label.hash(state)
}
}
impl<T: UserEvent, R: Runtime<T>> Eq for DetachedWebview<T, R> {}
impl<T: UserEvent, R: Runtime<T>> PartialEq for DetachedWebview<T, R> {
fn eq(&self, other: &Self) -> bool {
self.label.eq(&other.label)
}
}
#[derive(Debug)]
pub struct WebviewAttributes {
pub url: WebviewUrl,
pub user_agent: Option<String>,
pub initialization_scripts: Vec<InitializationScript>,
pub data_directory: Option<PathBuf>,
pub drag_drop_handler_enabled: bool,
pub clipboard: bool,
pub accept_first_mouse: bool,
pub additional_browser_args: Option<String>,
pub window_effects: Option<WindowEffectsConfig>,
pub incognito: bool,
pub transparent: bool,
pub focus: bool,
pub bounds: Option<Rect>,
pub auto_resize: bool,
pub proxy_url: Option<Url>,
pub zoom_hotkeys_enabled: bool,
pub browser_extensions_enabled: bool,
pub extensions_path: Option<PathBuf>,
pub data_store_identifier: Option<[u8; 16]>,
pub use_https_scheme: bool,
pub devtools: Option<bool>,
pub background_color: Option<Color>,
pub traffic_light_position: Option<dpi::Position>,
pub background_throttling: Option<BackgroundThrottlingPolicy>,
pub javascript_disabled: bool,
pub allow_link_preview: bool,
pub scroll_bar_style: ScrollBarStyle,
#[cfg(target_os = "ios")]
pub input_accessory_view_builder: Option<InputAccessoryViewBuilder>,
#[cfg(windows)]
pub environment: Option<webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Environment>,
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
))]
pub related_view: Option<webkit2gtk::WebView>,
#[cfg(target_os = "macos")]
pub webview_configuration: Option<objc2::rc::Retained<objc2_web_kit::WKWebViewConfiguration>>,
}
unsafe impl Send for WebviewAttributes {}
unsafe impl Sync for WebviewAttributes {}
#[cfg(target_os = "ios")]
#[non_exhaustive]
pub struct InputAccessoryViewBuilder(pub Box<InputAccessoryViewBuilderFn>);
#[cfg(target_os = "ios")]
impl std::fmt::Debug for InputAccessoryViewBuilder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("InputAccessoryViewBuilder").finish()
}
}
#[cfg(target_os = "ios")]
impl InputAccessoryViewBuilder {
pub fn new(builder: Box<InputAccessoryViewBuilderFn>) -> Self {
Self(builder)
}
}
impl From<&WindowConfig> for WebviewAttributes {
fn from(config: &WindowConfig) -> Self {
let mut builder = Self::new(config.url.clone())
.incognito(config.incognito)
.focused(config.focus)
.zoom_hotkeys_enabled(config.zoom_hotkeys_enabled)
.use_https_scheme(config.use_https_scheme)
.browser_extensions_enabled(config.browser_extensions_enabled)
.background_throttling(config.background_throttling.clone())
.devtools(config.devtools)
.scroll_bar_style(match config.scroll_bar_style {
ConfigScrollBarStyle::Default => ScrollBarStyle::Default,
#[cfg(windows)]
ConfigScrollBarStyle::FluentOverlay => ScrollBarStyle::FluentOverlay,
_ => ScrollBarStyle::Default,
});
#[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
{
builder = builder.transparent(config.transparent);
}
#[cfg(target_os = "macos")]
{
if let Some(position) = &config.traffic_light_position {
builder =
builder.traffic_light_position(dpi::LogicalPosition::new(position.x, position.y).into());
}
}
builder = builder.accept_first_mouse(config.accept_first_mouse);
if !config.drag_drop_enabled {
builder = builder.disable_drag_drop_handler();
}
if let Some(user_agent) = &config.user_agent {
builder = builder.user_agent(user_agent);
}
if let Some(additional_browser_args) = &config.additional_browser_args {
builder = builder.additional_browser_args(additional_browser_args);
}
if let Some(effects) = &config.window_effects {
builder = builder.window_effects(effects.clone());
}
if let Some(url) = &config.proxy_url {
builder = builder.proxy_url(url.to_owned());
}
if let Some(color) = config.background_color {
builder = builder.background_color(color);
}
builder.javascript_disabled = config.javascript_disabled;
builder.allow_link_preview = config.allow_link_preview;
#[cfg(target_os = "ios")]
if config.disable_input_accessory_view {
builder
.input_accessory_view_builder
.replace(InputAccessoryViewBuilder::new(Box::new(|_webview| None)));
}
builder
}
}
impl WebviewAttributes {
pub fn new(url: WebviewUrl) -> Self {
Self {
url,
user_agent: None,
initialization_scripts: Vec::new(),
data_directory: None,
drag_drop_handler_enabled: true,
clipboard: false,
accept_first_mouse: false,
additional_browser_args: None,
window_effects: None,
incognito: false,
transparent: false,
focus: true,
bounds: None,
auto_resize: false,
proxy_url: None,
zoom_hotkeys_enabled: false,
browser_extensions_enabled: false,
data_store_identifier: None,
extensions_path: None,
use_https_scheme: false,
devtools: None,
background_color: None,
traffic_light_position: None,
background_throttling: None,
javascript_disabled: false,
allow_link_preview: true,
scroll_bar_style: ScrollBarStyle::Default,
#[cfg(target_os = "ios")]
input_accessory_view_builder: None,
#[cfg(windows)]
environment: None,
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
))]
related_view: None,
#[cfg(target_os = "macos")]
webview_configuration: None,
}
}
#[must_use]
pub fn user_agent(mut self, user_agent: &str) -> Self {
self.user_agent = Some(user_agent.to_string());
self
}
#[must_use]
pub fn initialization_script(mut self, script: impl Into<String>) -> Self {
self.initialization_scripts.push(InitializationScript {
script: script.into(),
for_main_frame_only: true,
});
self
}
#[must_use]
pub fn initialization_script_on_all_frames(mut self, script: impl Into<String>) -> Self {
self.initialization_scripts.push(InitializationScript {
script: script.into(),
for_main_frame_only: false,
});
self
}
#[must_use]
pub fn data_directory(mut self, data_directory: PathBuf) -> Self {
self.data_directory.replace(data_directory);
self
}
#[must_use]
pub fn disable_drag_drop_handler(mut self) -> Self {
self.drag_drop_handler_enabled = false;
self
}
#[must_use]
pub fn enable_clipboard_access(mut self) -> Self {
self.clipboard = true;
self
}
#[must_use]
pub fn accept_first_mouse(mut self, accept: bool) -> Self {
self.accept_first_mouse = accept;
self
}
#[must_use]
pub fn additional_browser_args(mut self, additional_args: &str) -> Self {
self.additional_browser_args = Some(additional_args.to_string());
self
}
#[must_use]
pub fn window_effects(mut self, effects: WindowEffectsConfig) -> Self {
self.window_effects = Some(effects);
self
}
#[must_use]
pub fn incognito(mut self, incognito: bool) -> Self {
self.incognito = incognito;
self
}
#[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
#[must_use]
pub fn transparent(mut self, transparent: bool) -> Self {
self.transparent = transparent;
self
}
#[must_use]
pub fn focused(mut self, focus: bool) -> Self {
self.focus = focus;
self
}
#[must_use]
pub fn auto_resize(mut self) -> Self {
self.auto_resize = true;
self
}
#[must_use]
pub fn proxy_url(mut self, url: Url) -> Self {
self.proxy_url = Some(url);
self
}
#[must_use]
pub fn zoom_hotkeys_enabled(mut self, enabled: bool) -> Self {
self.zoom_hotkeys_enabled = enabled;
self
}
#[must_use]
pub fn browser_extensions_enabled(mut self, enabled: bool) -> Self {
self.browser_extensions_enabled = enabled;
self
}
#[must_use]
pub fn use_https_scheme(mut self, enabled: bool) -> Self {
self.use_https_scheme = enabled;
self
}
#[must_use]
pub fn devtools(mut self, enabled: Option<bool>) -> Self {
self.devtools = enabled;
self
}
#[must_use]
pub fn background_color(mut self, color: Color) -> Self {
self.background_color = Some(color);
self
}
#[must_use]
pub fn traffic_light_position(mut self, position: dpi::Position) -> Self {
self.traffic_light_position = Some(position);
self
}
#[must_use]
pub fn allow_link_preview(mut self, allow_link_preview: bool) -> Self {
self.allow_link_preview = allow_link_preview;
self
}
#[must_use]
pub fn background_throttling(mut self, policy: Option<BackgroundThrottlingPolicy>) -> Self {
self.background_throttling = policy;
self
}
#[must_use]
pub fn scroll_bar_style(mut self, style: ScrollBarStyle) -> Self {
self.scroll_bar_style = style;
self
}
}
pub type WebviewIpcHandler<T, R> = Box<dyn Fn(DetachedWebview<T, R>, Request<String>) + Send>;
#[derive(Debug, Clone)]
pub struct InitializationScript {
pub script: String,
pub for_main_frame_only: bool,
}