1use crate::{window::is_label_valid, Rect, Runtime, UserEvent};
8
9use http::Request;
10use tauri_utils::config::{
11 BackgroundThrottlingPolicy, Color, WebviewUrl, WindowConfig, WindowEffectsConfig,
12};
13use url::Url;
14
15use std::{
16 borrow::Cow,
17 collections::HashMap,
18 hash::{Hash, Hasher},
19 path::PathBuf,
20 sync::Arc,
21};
22
23type UriSchemeProtocol = dyn Fn(&str, http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)
24 + Send
25 + Sync
26 + 'static;
27
28type WebResourceRequestHandler =
29 dyn Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync;
30
31type NavigationHandler = dyn Fn(&Url) -> bool + Send;
32
33type OnPageLoadHandler = dyn Fn(Url, PageLoadEvent) + Send;
34
35type DownloadHandler = dyn Fn(DownloadEvent) -> bool + Send + Sync;
36
37#[cfg(target_os = "ios")]
38type InputAccessoryViewBuilderFn = dyn Fn(&objc2_ui_kit::UIView) -> Option<objc2::rc::Retained<objc2_ui_kit::UIView>>
39 + Send
40 + Sync
41 + 'static;
42
43pub enum DownloadEvent<'a> {
45 Requested {
47 url: Url,
49 destination: &'a mut PathBuf,
53 },
54 Finished {
56 url: Url,
58 path: Option<PathBuf>,
60 success: bool,
62 },
63}
64
65#[cfg(target_os = "android")]
66pub struct CreationContext<'a, 'b> {
67 pub env: &'a mut jni::JNIEnv<'b>,
68 pub activity: &'a jni::objects::JObject<'b>,
69 pub webview: &'a jni::objects::JObject<'b>,
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum PageLoadEvent {
75 Started,
77 Finished,
79}
80
81pub struct PendingWebview<T: UserEvent, R: Runtime<T>> {
83 pub label: String,
85
86 pub webview_attributes: WebviewAttributes,
88
89 pub uri_scheme_protocols: HashMap<String, Box<UriSchemeProtocol>>,
90
91 pub ipc_handler: Option<WebviewIpcHandler<T, R>>,
93
94 pub navigation_handler: Option<Box<NavigationHandler>>,
96
97 pub url: String,
99
100 #[cfg(target_os = "android")]
101 #[allow(clippy::type_complexity)]
102 pub on_webview_created:
103 Option<Box<dyn Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send>>,
104
105 pub web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,
106
107 pub on_page_load_handler: Option<Box<OnPageLoadHandler>>,
108
109 pub download_handler: Option<Arc<DownloadHandler>>,
110}
111
112impl<T: UserEvent, R: Runtime<T>> PendingWebview<T, R> {
113 pub fn new(
115 webview_attributes: WebviewAttributes,
116 label: impl Into<String>,
117 ) -> crate::Result<Self> {
118 let label = label.into();
119 if !is_label_valid(&label) {
120 Err(crate::Error::InvalidWindowLabel)
121 } else {
122 Ok(Self {
123 webview_attributes,
124 uri_scheme_protocols: Default::default(),
125 label,
126 ipc_handler: None,
127 navigation_handler: None,
128 url: "tauri://localhost".to_string(),
129 #[cfg(target_os = "android")]
130 on_webview_created: None,
131 web_resource_request_handler: None,
132 on_page_load_handler: None,
133 download_handler: None,
134 })
135 }
136 }
137
138 pub fn register_uri_scheme_protocol<
139 N: Into<String>,
140 H: Fn(&str, http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)
141 + Send
142 + Sync
143 + 'static,
144 >(
145 &mut self,
146 uri_scheme: N,
147 protocol: H,
148 ) {
149 let uri_scheme = uri_scheme.into();
150 self
151 .uri_scheme_protocols
152 .insert(uri_scheme, Box::new(protocol));
153 }
154
155 #[cfg(target_os = "android")]
156 pub fn on_webview_created<
157 F: Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send + 'static,
158 >(
159 mut self,
160 f: F,
161 ) -> Self {
162 self.on_webview_created.replace(Box::new(f));
163 self
164 }
165}
166
167#[derive(Debug)]
169pub struct DetachedWebview<T: UserEvent, R: Runtime<T>> {
170 pub label: String,
172
173 pub dispatcher: R::WebviewDispatcher,
175}
176
177impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWebview<T, R> {
178 fn clone(&self) -> Self {
179 Self {
180 label: self.label.clone(),
181 dispatcher: self.dispatcher.clone(),
182 }
183 }
184}
185
186impl<T: UserEvent, R: Runtime<T>> Hash for DetachedWebview<T, R> {
187 fn hash<H: Hasher>(&self, state: &mut H) {
189 self.label.hash(state)
190 }
191}
192
193impl<T: UserEvent, R: Runtime<T>> Eq for DetachedWebview<T, R> {}
194impl<T: UserEvent, R: Runtime<T>> PartialEq for DetachedWebview<T, R> {
195 fn eq(&self, other: &Self) -> bool {
197 self.label.eq(&other.label)
198 }
199}
200
201#[derive(Debug)]
203pub struct WebviewAttributes {
204 pub url: WebviewUrl,
205 pub user_agent: Option<String>,
206 pub initialization_scripts: Vec<InitializationScript>,
220 pub data_directory: Option<PathBuf>,
221 pub drag_drop_handler_enabled: bool,
222 pub clipboard: bool,
223 pub accept_first_mouse: bool,
224 pub additional_browser_args: Option<String>,
225 pub window_effects: Option<WindowEffectsConfig>,
226 pub incognito: bool,
227 pub transparent: bool,
228 pub focus: bool,
229 pub bounds: Option<Rect>,
230 pub auto_resize: bool,
231 pub proxy_url: Option<Url>,
232 pub zoom_hotkeys_enabled: bool,
233 pub browser_extensions_enabled: bool,
234 pub extensions_path: Option<PathBuf>,
235 pub data_store_identifier: Option<[u8; 16]>,
236 pub use_https_scheme: bool,
237 pub devtools: Option<bool>,
238 pub background_color: Option<Color>,
239 pub traffic_light_position: Option<dpi::Position>,
240 pub background_throttling: Option<BackgroundThrottlingPolicy>,
241 pub javascript_disabled: bool,
242 pub allow_link_preview: bool,
245 #[cfg(target_os = "ios")]
257 pub input_accessory_view_builder: Option<InputAccessoryViewBuilder>,
258}
259
260#[cfg(target_os = "ios")]
261#[non_exhaustive]
262pub struct InputAccessoryViewBuilder(pub Box<InputAccessoryViewBuilderFn>);
263
264#[cfg(target_os = "ios")]
265impl std::fmt::Debug for InputAccessoryViewBuilder {
266 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
267 f.debug_struct("InputAccessoryViewBuilder").finish()
268 }
269}
270
271#[cfg(target_os = "ios")]
272impl InputAccessoryViewBuilder {
273 pub fn new(builder: Box<InputAccessoryViewBuilderFn>) -> Self {
274 Self(builder)
275 }
276}
277
278impl From<&WindowConfig> for WebviewAttributes {
279 fn from(config: &WindowConfig) -> Self {
280 let mut builder = Self::new(config.url.clone())
281 .incognito(config.incognito)
282 .focused(config.focus)
283 .zoom_hotkeys_enabled(config.zoom_hotkeys_enabled)
284 .use_https_scheme(config.use_https_scheme)
285 .browser_extensions_enabled(config.browser_extensions_enabled)
286 .background_throttling(config.background_throttling.clone())
287 .devtools(config.devtools);
288
289 #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
290 {
291 builder = builder.transparent(config.transparent);
292 }
293 #[cfg(target_os = "macos")]
294 {
295 if let Some(position) = &config.traffic_light_position {
296 builder =
297 builder.traffic_light_position(dpi::LogicalPosition::new(position.x, position.y).into());
298 }
299 }
300 builder = builder.accept_first_mouse(config.accept_first_mouse);
301 if !config.drag_drop_enabled {
302 builder = builder.disable_drag_drop_handler();
303 }
304 if let Some(user_agent) = &config.user_agent {
305 builder = builder.user_agent(user_agent);
306 }
307 if let Some(additional_browser_args) = &config.additional_browser_args {
308 builder = builder.additional_browser_args(additional_browser_args);
309 }
310 if let Some(effects) = &config.window_effects {
311 builder = builder.window_effects(effects.clone());
312 }
313 if let Some(url) = &config.proxy_url {
314 builder = builder.proxy_url(url.to_owned());
315 }
316 if let Some(color) = config.background_color {
317 builder = builder.background_color(color);
318 }
319 builder.javascript_disabled = config.javascript_disabled;
320 builder.allow_link_preview = config.allow_link_preview;
321 #[cfg(target_os = "ios")]
322 if config.disable_input_accessory_view {
323 builder
324 .input_accessory_view_builder
325 .replace(InputAccessoryViewBuilder::new(Box::new(|_webview| None)));
326 }
327 builder
328 }
329}
330
331impl WebviewAttributes {
332 pub fn new(url: WebviewUrl) -> Self {
334 Self {
335 url,
336 user_agent: None,
337 initialization_scripts: Vec::new(),
338 data_directory: None,
339 drag_drop_handler_enabled: true,
340 clipboard: false,
341 accept_first_mouse: false,
342 additional_browser_args: None,
343 window_effects: None,
344 incognito: false,
345 transparent: false,
346 focus: true,
347 bounds: None,
348 auto_resize: false,
349 proxy_url: None,
350 zoom_hotkeys_enabled: false,
351 browser_extensions_enabled: false,
352 data_store_identifier: None,
353 extensions_path: None,
354 use_https_scheme: false,
355 devtools: None,
356 background_color: None,
357 traffic_light_position: None,
358 background_throttling: None,
359 javascript_disabled: false,
360 allow_link_preview: true,
361 #[cfg(target_os = "ios")]
362 input_accessory_view_builder: None,
363 }
364 }
365
366 #[must_use]
368 pub fn user_agent(mut self, user_agent: &str) -> Self {
369 self.user_agent = Some(user_agent.to_string());
370 self
371 }
372
373 #[must_use]
390 pub fn initialization_script(mut self, script: impl Into<String>) -> Self {
391 self.initialization_scripts.push(InitializationScript {
392 script: script.into(),
393 for_main_frame_only: true,
394 });
395 self
396 }
397
398 #[must_use]
415 pub fn initialization_script_on_all_frames(mut self, script: impl Into<String>) -> Self {
416 self.initialization_scripts.push(InitializationScript {
417 script: script.into(),
418 for_main_frame_only: false,
419 });
420 self
421 }
422
423 #[must_use]
425 pub fn data_directory(mut self, data_directory: PathBuf) -> Self {
426 self.data_directory.replace(data_directory);
427 self
428 }
429
430 #[must_use]
432 pub fn disable_drag_drop_handler(mut self) -> Self {
433 self.drag_drop_handler_enabled = false;
434 self
435 }
436
437 #[must_use]
442 pub fn enable_clipboard_access(mut self) -> Self {
443 self.clipboard = true;
444 self
445 }
446
447 #[must_use]
449 pub fn accept_first_mouse(mut self, accept: bool) -> Self {
450 self.accept_first_mouse = accept;
451 self
452 }
453
454 #[must_use]
456 pub fn additional_browser_args(mut self, additional_args: &str) -> Self {
457 self.additional_browser_args = Some(additional_args.to_string());
458 self
459 }
460
461 #[must_use]
463 pub fn window_effects(mut self, effects: WindowEffectsConfig) -> Self {
464 self.window_effects = Some(effects);
465 self
466 }
467
468 #[must_use]
470 pub fn incognito(mut self, incognito: bool) -> Self {
471 self.incognito = incognito;
472 self
473 }
474
475 #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
477 #[must_use]
478 pub fn transparent(mut self, transparent: bool) -> Self {
479 self.transparent = transparent;
480 self
481 }
482
483 #[must_use]
485 pub fn focused(mut self, focus: bool) -> Self {
486 self.focus = focus;
487 self
488 }
489
490 #[must_use]
492 pub fn auto_resize(mut self) -> Self {
493 self.auto_resize = true;
494 self
495 }
496
497 #[must_use]
499 pub fn proxy_url(mut self, url: Url) -> Self {
500 self.proxy_url = Some(url);
501 self
502 }
503
504 #[must_use]
514 pub fn zoom_hotkeys_enabled(mut self, enabled: bool) -> Self {
515 self.zoom_hotkeys_enabled = enabled;
516 self
517 }
518
519 #[must_use]
526 pub fn browser_extensions_enabled(mut self, enabled: bool) -> Self {
527 self.browser_extensions_enabled = enabled;
528 self
529 }
530
531 #[must_use]
541 pub fn use_https_scheme(mut self, enabled: bool) -> Self {
542 self.use_https_scheme = enabled;
543 self
544 }
545
546 #[must_use]
556 pub fn devtools(mut self, enabled: Option<bool>) -> Self {
557 self.devtools = enabled;
558 self
559 }
560
561 #[must_use]
567 pub fn background_color(mut self, color: Color) -> Self {
568 self.background_color = Some(color);
569 self
570 }
571
572 #[must_use]
580 pub fn traffic_light_position(mut self, position: dpi::Position) -> Self {
581 self.traffic_light_position = Some(position);
582 self
583 }
584
585 #[must_use]
595 pub fn allow_link_preview(mut self, allow_link_preview: bool) -> Self {
596 self.allow_link_preview = allow_link_preview;
597 self
598 }
599
600 #[must_use]
615 pub fn background_throttling(mut self, policy: Option<BackgroundThrottlingPolicy>) -> Self {
616 self.background_throttling = policy;
617 self
618 }
619}
620
621pub type WebviewIpcHandler<T, R> = Box<dyn Fn(DetachedWebview<T, R>, Request<String>) + Send>;
623
624#[derive(Debug, Clone)]
626pub struct InitializationScript {
627 pub script: String,
629 pub for_main_frame_only: bool,
631}