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]
391 pub fn initialization_script(mut self, script: impl Into<String>) -> Self {
392 self.initialization_scripts.push(InitializationScript {
393 script: script.into(),
394 for_main_frame_only: true,
395 });
396 self
397 }
398
399 #[must_use]
417 pub fn initialization_script_on_all_frames(mut self, script: impl Into<String>) -> Self {
418 self.initialization_scripts.push(InitializationScript {
419 script: script.into(),
420 for_main_frame_only: false,
421 });
422 self
423 }
424
425 #[must_use]
427 pub fn data_directory(mut self, data_directory: PathBuf) -> Self {
428 self.data_directory.replace(data_directory);
429 self
430 }
431
432 #[must_use]
434 pub fn disable_drag_drop_handler(mut self) -> Self {
435 self.drag_drop_handler_enabled = false;
436 self
437 }
438
439 #[must_use]
444 pub fn enable_clipboard_access(mut self) -> Self {
445 self.clipboard = true;
446 self
447 }
448
449 #[must_use]
451 pub fn accept_first_mouse(mut self, accept: bool) -> Self {
452 self.accept_first_mouse = accept;
453 self
454 }
455
456 #[must_use]
458 pub fn additional_browser_args(mut self, additional_args: &str) -> Self {
459 self.additional_browser_args = Some(additional_args.to_string());
460 self
461 }
462
463 #[must_use]
465 pub fn window_effects(mut self, effects: WindowEffectsConfig) -> Self {
466 self.window_effects = Some(effects);
467 self
468 }
469
470 #[must_use]
472 pub fn incognito(mut self, incognito: bool) -> Self {
473 self.incognito = incognito;
474 self
475 }
476
477 #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
479 #[must_use]
480 pub fn transparent(mut self, transparent: bool) -> Self {
481 self.transparent = transparent;
482 self
483 }
484
485 #[must_use]
487 pub fn focused(mut self, focus: bool) -> Self {
488 self.focus = focus;
489 self
490 }
491
492 #[must_use]
494 pub fn auto_resize(mut self) -> Self {
495 self.auto_resize = true;
496 self
497 }
498
499 #[must_use]
501 pub fn proxy_url(mut self, url: Url) -> Self {
502 self.proxy_url = Some(url);
503 self
504 }
505
506 #[must_use]
516 pub fn zoom_hotkeys_enabled(mut self, enabled: bool) -> Self {
517 self.zoom_hotkeys_enabled = enabled;
518 self
519 }
520
521 #[must_use]
528 pub fn browser_extensions_enabled(mut self, enabled: bool) -> Self {
529 self.browser_extensions_enabled = enabled;
530 self
531 }
532
533 #[must_use]
543 pub fn use_https_scheme(mut self, enabled: bool) -> Self {
544 self.use_https_scheme = enabled;
545 self
546 }
547
548 #[must_use]
558 pub fn devtools(mut self, enabled: Option<bool>) -> Self {
559 self.devtools = enabled;
560 self
561 }
562
563 #[must_use]
569 pub fn background_color(mut self, color: Color) -> Self {
570 self.background_color = Some(color);
571 self
572 }
573
574 #[must_use]
582 pub fn traffic_light_position(mut self, position: dpi::Position) -> Self {
583 self.traffic_light_position = Some(position);
584 self
585 }
586
587 #[must_use]
597 pub fn allow_link_preview(mut self, allow_link_preview: bool) -> Self {
598 self.allow_link_preview = allow_link_preview;
599 self
600 }
601
602 #[must_use]
617 pub fn background_throttling(mut self, policy: Option<BackgroundThrottlingPolicy>) -> Self {
618 self.background_throttling = policy;
619 self
620 }
621}
622
623pub type WebviewIpcHandler<T, R> = Box<dyn Fn(DetachedWebview<T, R>, Request<String>) + Send>;
625
626#[derive(Debug, Clone)]
628pub struct InitializationScript {
629 pub script: String,
631 pub for_main_frame_only: bool,
633}