1use crate::{window::is_label_valid, Rect, Runtime, UserEvent};
8
9use http::Request;
10use tauri_utils::config::{Color, WebviewUrl, WindowConfig, WindowEffectsConfig};
11use url::Url;
12
13use std::{
14 borrow::Cow,
15 collections::HashMap,
16 hash::{Hash, Hasher},
17 path::PathBuf,
18 sync::Arc,
19};
20
21type UriSchemeProtocol = dyn Fn(&str, http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)
22 + Send
23 + Sync
24 + 'static;
25
26type WebResourceRequestHandler =
27 dyn Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync;
28
29type NavigationHandler = dyn Fn(&Url) -> bool + Send;
30
31type OnPageLoadHandler = dyn Fn(Url, PageLoadEvent) + Send;
32
33type DownloadHandler = dyn Fn(DownloadEvent) -> bool + Send + Sync;
34
35pub enum DownloadEvent<'a> {
37 Requested {
39 url: Url,
41 destination: &'a mut PathBuf,
45 },
46 Finished {
48 url: Url,
50 path: Option<PathBuf>,
52 success: bool,
54 },
55}
56
57#[cfg(target_os = "android")]
58pub struct CreationContext<'a, 'b> {
59 pub env: &'a mut jni::JNIEnv<'b>,
60 pub activity: &'a jni::objects::JObject<'b>,
61 pub webview: &'a jni::objects::JObject<'b>,
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum PageLoadEvent {
67 Started,
69 Finished,
71}
72
73pub struct PendingWebview<T: UserEvent, R: Runtime<T>> {
75 pub label: String,
77
78 pub webview_attributes: WebviewAttributes,
80
81 pub uri_scheme_protocols: HashMap<String, Box<UriSchemeProtocol>>,
82
83 pub ipc_handler: Option<WebviewIpcHandler<T, R>>,
85
86 pub navigation_handler: Option<Box<NavigationHandler>>,
88
89 pub url: String,
91
92 #[cfg(target_os = "android")]
93 #[allow(clippy::type_complexity)]
94 pub on_webview_created:
95 Option<Box<dyn Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send>>,
96
97 pub web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,
98
99 pub on_page_load_handler: Option<Box<OnPageLoadHandler>>,
100
101 pub download_handler: Option<Arc<DownloadHandler>>,
102}
103
104impl<T: UserEvent, R: Runtime<T>> PendingWebview<T, R> {
105 pub fn new(
107 webview_attributes: WebviewAttributes,
108 label: impl Into<String>,
109 ) -> crate::Result<Self> {
110 let label = label.into();
111 if !is_label_valid(&label) {
112 Err(crate::Error::InvalidWindowLabel)
113 } else {
114 Ok(Self {
115 webview_attributes,
116 uri_scheme_protocols: Default::default(),
117 label,
118 ipc_handler: None,
119 navigation_handler: None,
120 url: "tauri://localhost".to_string(),
121 #[cfg(target_os = "android")]
122 on_webview_created: None,
123 web_resource_request_handler: None,
124 on_page_load_handler: None,
125 download_handler: None,
126 })
127 }
128 }
129
130 pub fn register_uri_scheme_protocol<
131 N: Into<String>,
132 H: Fn(&str, http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)
133 + Send
134 + Sync
135 + 'static,
136 >(
137 &mut self,
138 uri_scheme: N,
139 protocol: H,
140 ) {
141 let uri_scheme = uri_scheme.into();
142 self
143 .uri_scheme_protocols
144 .insert(uri_scheme, Box::new(protocol));
145 }
146
147 #[cfg(target_os = "android")]
148 pub fn on_webview_created<
149 F: Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send + 'static,
150 >(
151 mut self,
152 f: F,
153 ) -> Self {
154 self.on_webview_created.replace(Box::new(f));
155 self
156 }
157}
158
159#[derive(Debug)]
161pub struct DetachedWebview<T: UserEvent, R: Runtime<T>> {
162 pub label: String,
164
165 pub dispatcher: R::WebviewDispatcher,
167}
168
169impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWebview<T, R> {
170 fn clone(&self) -> Self {
171 Self {
172 label: self.label.clone(),
173 dispatcher: self.dispatcher.clone(),
174 }
175 }
176}
177
178impl<T: UserEvent, R: Runtime<T>> Hash for DetachedWebview<T, R> {
179 fn hash<H: Hasher>(&self, state: &mut H) {
181 self.label.hash(state)
182 }
183}
184
185impl<T: UserEvent, R: Runtime<T>> Eq for DetachedWebview<T, R> {}
186impl<T: UserEvent, R: Runtime<T>> PartialEq for DetachedWebview<T, R> {
187 fn eq(&self, other: &Self) -> bool {
189 self.label.eq(&other.label)
190 }
191}
192
193#[derive(Debug, Clone)]
195pub struct WebviewAttributes {
196 pub url: WebviewUrl,
197 pub user_agent: Option<String>,
198 pub initialization_scripts: Vec<String>,
199 pub data_directory: Option<PathBuf>,
200 pub drag_drop_handler_enabled: bool,
201 pub clipboard: bool,
202 pub accept_first_mouse: bool,
203 pub additional_browser_args: Option<String>,
204 pub window_effects: Option<WindowEffectsConfig>,
205 pub incognito: bool,
206 pub transparent: bool,
207 pub focus: bool,
208 pub bounds: Option<Rect>,
209 pub auto_resize: bool,
210 pub proxy_url: Option<Url>,
211 pub zoom_hotkeys_enabled: bool,
212 pub browser_extensions_enabled: bool,
213 pub extensions_path: Option<PathBuf>,
214 pub data_store_identifier: Option<[u8; 16]>,
215 pub use_https_scheme: bool,
216 pub devtools: Option<bool>,
217 pub background_color: Option<Color>,
218}
219
220impl From<&WindowConfig> for WebviewAttributes {
221 fn from(config: &WindowConfig) -> Self {
222 let mut builder = Self::new(config.url.clone())
223 .incognito(config.incognito)
224 .focused(config.focus)
225 .zoom_hotkeys_enabled(config.zoom_hotkeys_enabled)
226 .use_https_scheme(config.use_https_scheme)
227 .browser_extensions_enabled(config.browser_extensions_enabled)
228 .devtools(config.devtools);
229 #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
230 {
231 builder = builder.transparent(config.transparent);
232 }
233 builder = builder.accept_first_mouse(config.accept_first_mouse);
234 if !config.drag_drop_enabled {
235 builder = builder.disable_drag_drop_handler();
236 }
237 if let Some(user_agent) = &config.user_agent {
238 builder = builder.user_agent(user_agent);
239 }
240 if let Some(additional_browser_args) = &config.additional_browser_args {
241 builder = builder.additional_browser_args(additional_browser_args);
242 }
243 if let Some(effects) = &config.window_effects {
244 builder = builder.window_effects(effects.clone());
245 }
246 if let Some(url) = &config.proxy_url {
247 builder = builder.proxy_url(url.to_owned());
248 }
249 if let Some(color) = config.background_color {
250 builder = builder.background_color(color);
251 }
252 builder
253 }
254}
255
256impl WebviewAttributes {
257 pub fn new(url: WebviewUrl) -> Self {
259 Self {
260 url,
261 user_agent: None,
262 initialization_scripts: Vec::new(),
263 data_directory: None,
264 drag_drop_handler_enabled: true,
265 clipboard: false,
266 accept_first_mouse: false,
267 additional_browser_args: None,
268 window_effects: None,
269 incognito: false,
270 transparent: false,
271 focus: true,
272 bounds: None,
273 auto_resize: false,
274 proxy_url: None,
275 zoom_hotkeys_enabled: false,
276 browser_extensions_enabled: false,
277 data_store_identifier: None,
278 extensions_path: None,
279 use_https_scheme: false,
280 devtools: None,
281 background_color: None,
282 }
283 }
284
285 #[must_use]
287 pub fn user_agent(mut self, user_agent: &str) -> Self {
288 self.user_agent = Some(user_agent.to_string());
289 self
290 }
291
292 #[must_use]
294 pub fn initialization_script(mut self, script: &str) -> Self {
295 self.initialization_scripts.push(script.to_string());
296 self
297 }
298
299 #[must_use]
301 pub fn data_directory(mut self, data_directory: PathBuf) -> Self {
302 self.data_directory.replace(data_directory);
303 self
304 }
305
306 #[must_use]
308 pub fn disable_drag_drop_handler(mut self) -> Self {
309 self.drag_drop_handler_enabled = false;
310 self
311 }
312
313 #[must_use]
318 pub fn enable_clipboard_access(mut self) -> Self {
319 self.clipboard = true;
320 self
321 }
322
323 #[must_use]
325 pub fn accept_first_mouse(mut self, accept: bool) -> Self {
326 self.accept_first_mouse = accept;
327 self
328 }
329
330 #[must_use]
332 pub fn additional_browser_args(mut self, additional_args: &str) -> Self {
333 self.additional_browser_args = Some(additional_args.to_string());
334 self
335 }
336
337 #[must_use]
339 pub fn window_effects(mut self, effects: WindowEffectsConfig) -> Self {
340 self.window_effects = Some(effects);
341 self
342 }
343
344 #[must_use]
346 pub fn incognito(mut self, incognito: bool) -> Self {
347 self.incognito = incognito;
348 self
349 }
350
351 #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
353 #[must_use]
354 pub fn transparent(mut self, transparent: bool) -> Self {
355 self.transparent = transparent;
356 self
357 }
358
359 #[must_use]
361 pub fn focused(mut self, focus: bool) -> Self {
362 self.focus = focus;
363 self
364 }
365
366 #[must_use]
368 pub fn auto_resize(mut self) -> Self {
369 self.auto_resize = true;
370 self
371 }
372
373 #[must_use]
375 pub fn proxy_url(mut self, url: Url) -> Self {
376 self.proxy_url = Some(url);
377 self
378 }
379
380 #[must_use]
390 pub fn zoom_hotkeys_enabled(mut self, enabled: bool) -> Self {
391 self.zoom_hotkeys_enabled = enabled;
392 self
393 }
394
395 #[must_use]
402 pub fn browser_extensions_enabled(mut self, enabled: bool) -> Self {
403 self.browser_extensions_enabled = enabled;
404 self
405 }
406
407 #[must_use]
417 pub fn use_https_scheme(mut self, enabled: bool) -> Self {
418 self.use_https_scheme = enabled;
419 self
420 }
421
422 #[must_use]
432 pub fn devtools(mut self, enabled: Option<bool>) -> Self {
433 self.devtools = enabled;
434 self
435 }
436
437 #[must_use]
443 pub fn background_color(mut self, color: Color) -> Self {
444 self.background_color = Some(color);
445 self
446 }
447}
448
449pub type WebviewIpcHandler<T, R> = Box<dyn Fn(DetachedWebview<T, R>, Request<String>) + Send>;