1#[cfg(not(any(target_os = "android", target_os = "ios")))]
8use crate::window::WindowId;
9use crate::{window::is_label_valid, Rect, Runtime, UserEvent};
10
11use http::Request;
12use tauri_utils::config::{
13 BackgroundThrottlingPolicy, Color, WebviewUrl, WindowConfig, WindowEffectsConfig,
14};
15use url::Url;
16
17use std::{
18 borrow::Cow,
19 collections::HashMap,
20 hash::{Hash, Hasher},
21 path::PathBuf,
22 sync::Arc,
23};
24
25type UriSchemeProtocol = dyn Fn(&str, http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)
26 + Send
27 + Sync
28 + 'static;
29
30type WebResourceRequestHandler =
31 dyn Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync;
32
33type NavigationHandler = dyn Fn(&Url) -> bool + Send;
34
35type NewWindowHandler = dyn Fn(Url, NewWindowFeatures) -> NewWindowResponse + Send + Sync;
36
37type OnPageLoadHandler = dyn Fn(Url, PageLoadEvent) + Send;
38
39type DocumentTitleChangedHandler = dyn Fn(String) + Send + 'static;
40
41type DownloadHandler = dyn Fn(DownloadEvent) -> bool + Send + Sync;
42
43#[cfg(target_os = "ios")]
44type InputAccessoryViewBuilderFn = dyn Fn(&objc2_ui_kit::UIView) -> Option<objc2::rc::Retained<objc2_ui_kit::UIView>>
45 + Send
46 + Sync
47 + 'static;
48
49pub enum DownloadEvent<'a> {
51 Requested {
53 url: Url,
55 destination: &'a mut PathBuf,
59 },
60 Finished {
62 url: Url,
64 path: Option<PathBuf>,
66 success: bool,
68 },
69}
70
71#[cfg(target_os = "android")]
72pub struct CreationContext<'a, 'b> {
73 pub env: &'a mut jni::JNIEnv<'b>,
74 pub activity: &'a jni::objects::JObject<'b>,
75 pub webview: &'a jni::objects::JObject<'b>,
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub enum PageLoadEvent {
81 Started,
83 Finished,
85}
86
87#[derive(Debug)]
89pub struct NewWindowOpener {
90 #[cfg(any(
94 target_os = "linux",
95 target_os = "dragonfly",
96 target_os = "freebsd",
97 target_os = "netbsd",
98 target_os = "openbsd",
99 ))]
100 pub webview: webkit2gtk::WebView,
101 #[cfg(windows)]
105 pub webview: webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2,
106 #[cfg(windows)]
107 pub environment: webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Environment,
108 #[cfg(target_os = "macos")]
110 pub webview: objc2::rc::Retained<objc2_web_kit::WKWebView>,
111 #[cfg(target_os = "macos")]
115 pub target_configuration: objc2::rc::Retained<objc2_web_kit::WKWebViewConfiguration>,
116}
117
118#[derive(Debug)]
120pub struct NewWindowFeatures {
121 pub(crate) size: Option<crate::dpi::LogicalSize<f64>>,
122 pub(crate) position: Option<crate::dpi::LogicalPosition<f64>>,
123 pub(crate) opener: NewWindowOpener,
124}
125
126impl NewWindowFeatures {
127 pub fn new(
128 size: Option<crate::dpi::LogicalSize<f64>>,
129 position: Option<crate::dpi::LogicalPosition<f64>>,
130 opener: NewWindowOpener,
131 ) -> Self {
132 Self {
133 size,
134 position,
135 opener,
136 }
137 }
138
139 pub fn size(&self) -> Option<crate::dpi::LogicalSize<f64>> {
142 self.size
143 }
144
145 pub fn position(&self) -> Option<crate::dpi::LogicalPosition<f64>> {
148 self.position
149 }
150
151 pub fn opener(&self) -> &NewWindowOpener {
153 &self.opener
154 }
155}
156
157pub enum NewWindowResponse {
159 Allow,
161 #[cfg(not(any(target_os = "android", target_os = "ios")))]
168 Create { window_id: WindowId },
169 Deny,
171}
172
173pub struct PendingWebview<T: UserEvent, R: Runtime<T>> {
175 pub label: String,
177
178 pub webview_attributes: WebviewAttributes,
180
181 pub uri_scheme_protocols: HashMap<String, Box<UriSchemeProtocol>>,
182
183 pub ipc_handler: Option<WebviewIpcHandler<T, R>>,
185
186 pub navigation_handler: Option<Box<NavigationHandler>>,
188
189 pub new_window_handler: Option<Box<NewWindowHandler>>,
190
191 pub document_title_changed_handler: Option<Box<DocumentTitleChangedHandler>>,
192
193 pub url: String,
195
196 #[cfg(target_os = "android")]
197 #[allow(clippy::type_complexity)]
198 pub on_webview_created:
199 Option<Box<dyn Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send>>,
200
201 pub web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,
202
203 pub on_page_load_handler: Option<Box<OnPageLoadHandler>>,
204
205 pub download_handler: Option<Arc<DownloadHandler>>,
206}
207
208impl<T: UserEvent, R: Runtime<T>> PendingWebview<T, R> {
209 pub fn new(
211 webview_attributes: WebviewAttributes,
212 label: impl Into<String>,
213 ) -> crate::Result<Self> {
214 let label = label.into();
215 if !is_label_valid(&label) {
216 Err(crate::Error::InvalidWindowLabel)
217 } else {
218 Ok(Self {
219 webview_attributes,
220 uri_scheme_protocols: Default::default(),
221 label,
222 ipc_handler: None,
223 navigation_handler: None,
224 new_window_handler: None,
225 document_title_changed_handler: None,
226 url: "tauri://localhost".to_string(),
227 #[cfg(target_os = "android")]
228 on_webview_created: None,
229 web_resource_request_handler: None,
230 on_page_load_handler: None,
231 download_handler: None,
232 })
233 }
234 }
235
236 pub fn register_uri_scheme_protocol<
237 N: Into<String>,
238 H: Fn(&str, http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)
239 + Send
240 + Sync
241 + 'static,
242 >(
243 &mut self,
244 uri_scheme: N,
245 protocol: H,
246 ) {
247 let uri_scheme = uri_scheme.into();
248 self
249 .uri_scheme_protocols
250 .insert(uri_scheme, Box::new(protocol));
251 }
252
253 #[cfg(target_os = "android")]
254 pub fn on_webview_created<
255 F: Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send + 'static,
256 >(
257 mut self,
258 f: F,
259 ) -> Self {
260 self.on_webview_created.replace(Box::new(f));
261 self
262 }
263}
264
265#[derive(Debug)]
267pub struct DetachedWebview<T: UserEvent, R: Runtime<T>> {
268 pub label: String,
270
271 pub dispatcher: R::WebviewDispatcher,
273}
274
275impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWebview<T, R> {
276 fn clone(&self) -> Self {
277 Self {
278 label: self.label.clone(),
279 dispatcher: self.dispatcher.clone(),
280 }
281 }
282}
283
284impl<T: UserEvent, R: Runtime<T>> Hash for DetachedWebview<T, R> {
285 fn hash<H: Hasher>(&self, state: &mut H) {
287 self.label.hash(state)
288 }
289}
290
291impl<T: UserEvent, R: Runtime<T>> Eq for DetachedWebview<T, R> {}
292impl<T: UserEvent, R: Runtime<T>> PartialEq for DetachedWebview<T, R> {
293 fn eq(&self, other: &Self) -> bool {
295 self.label.eq(&other.label)
296 }
297}
298
299#[derive(Debug)]
301pub struct WebviewAttributes {
302 pub url: WebviewUrl,
303 pub user_agent: Option<String>,
304 pub initialization_scripts: Vec<InitializationScript>,
318 pub data_directory: Option<PathBuf>,
319 pub drag_drop_handler_enabled: bool,
320 pub clipboard: bool,
321 pub accept_first_mouse: bool,
322 pub additional_browser_args: Option<String>,
323 pub window_effects: Option<WindowEffectsConfig>,
324 pub incognito: bool,
325 pub transparent: bool,
326 pub focus: bool,
327 pub bounds: Option<Rect>,
328 pub auto_resize: bool,
329 pub proxy_url: Option<Url>,
330 pub zoom_hotkeys_enabled: bool,
331 pub browser_extensions_enabled: bool,
332 pub extensions_path: Option<PathBuf>,
333 pub data_store_identifier: Option<[u8; 16]>,
334 pub use_https_scheme: bool,
335 pub devtools: Option<bool>,
336 pub background_color: Option<Color>,
337 pub traffic_light_position: Option<dpi::Position>,
338 pub background_throttling: Option<BackgroundThrottlingPolicy>,
339 pub javascript_disabled: bool,
340 pub allow_link_preview: bool,
343 #[cfg(target_os = "ios")]
355 pub input_accessory_view_builder: Option<InputAccessoryViewBuilder>,
356
357 #[cfg(windows)]
360 pub environment: Option<webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Environment>,
361
362 #[cfg(any(
365 target_os = "linux",
366 target_os = "dragonfly",
367 target_os = "freebsd",
368 target_os = "netbsd",
369 target_os = "openbsd",
370 ))]
371 pub related_view: Option<webkit2gtk::WebView>,
372
373 #[cfg(target_os = "macos")]
374 pub webview_configuration: Option<objc2::rc::Retained<objc2_web_kit::WKWebViewConfiguration>>,
375}
376
377unsafe impl Send for WebviewAttributes {}
378unsafe impl Sync for WebviewAttributes {}
379
380#[cfg(target_os = "ios")]
381#[non_exhaustive]
382pub struct InputAccessoryViewBuilder(pub Box<InputAccessoryViewBuilderFn>);
383
384#[cfg(target_os = "ios")]
385impl std::fmt::Debug for InputAccessoryViewBuilder {
386 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
387 f.debug_struct("InputAccessoryViewBuilder").finish()
388 }
389}
390
391#[cfg(target_os = "ios")]
392impl InputAccessoryViewBuilder {
393 pub fn new(builder: Box<InputAccessoryViewBuilderFn>) -> Self {
394 Self(builder)
395 }
396}
397
398impl From<&WindowConfig> for WebviewAttributes {
399 fn from(config: &WindowConfig) -> Self {
400 let mut builder = Self::new(config.url.clone())
401 .incognito(config.incognito)
402 .focused(config.focus)
403 .zoom_hotkeys_enabled(config.zoom_hotkeys_enabled)
404 .use_https_scheme(config.use_https_scheme)
405 .browser_extensions_enabled(config.browser_extensions_enabled)
406 .background_throttling(config.background_throttling.clone())
407 .devtools(config.devtools);
408
409 #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
410 {
411 builder = builder.transparent(config.transparent);
412 }
413 #[cfg(target_os = "macos")]
414 {
415 if let Some(position) = &config.traffic_light_position {
416 builder =
417 builder.traffic_light_position(dpi::LogicalPosition::new(position.x, position.y).into());
418 }
419 }
420 builder = builder.accept_first_mouse(config.accept_first_mouse);
421 if !config.drag_drop_enabled {
422 builder = builder.disable_drag_drop_handler();
423 }
424 if let Some(user_agent) = &config.user_agent {
425 builder = builder.user_agent(user_agent);
426 }
427 if let Some(additional_browser_args) = &config.additional_browser_args {
428 builder = builder.additional_browser_args(additional_browser_args);
429 }
430 if let Some(effects) = &config.window_effects {
431 builder = builder.window_effects(effects.clone());
432 }
433 if let Some(url) = &config.proxy_url {
434 builder = builder.proxy_url(url.to_owned());
435 }
436 if let Some(color) = config.background_color {
437 builder = builder.background_color(color);
438 }
439 builder.javascript_disabled = config.javascript_disabled;
440 builder.allow_link_preview = config.allow_link_preview;
441 #[cfg(target_os = "ios")]
442 if config.disable_input_accessory_view {
443 builder
444 .input_accessory_view_builder
445 .replace(InputAccessoryViewBuilder::new(Box::new(|_webview| None)));
446 }
447 builder
448 }
449}
450
451impl WebviewAttributes {
452 pub fn new(url: WebviewUrl) -> Self {
454 Self {
455 url,
456 user_agent: None,
457 initialization_scripts: Vec::new(),
458 data_directory: None,
459 drag_drop_handler_enabled: true,
460 clipboard: false,
461 accept_first_mouse: false,
462 additional_browser_args: None,
463 window_effects: None,
464 incognito: false,
465 transparent: false,
466 focus: true,
467 bounds: None,
468 auto_resize: false,
469 proxy_url: None,
470 zoom_hotkeys_enabled: false,
471 browser_extensions_enabled: false,
472 data_store_identifier: None,
473 extensions_path: None,
474 use_https_scheme: false,
475 devtools: None,
476 background_color: None,
477 traffic_light_position: None,
478 background_throttling: None,
479 javascript_disabled: false,
480 allow_link_preview: true,
481 #[cfg(target_os = "ios")]
482 input_accessory_view_builder: None,
483 #[cfg(windows)]
484 environment: None,
485 #[cfg(any(
486 target_os = "linux",
487 target_os = "dragonfly",
488 target_os = "freebsd",
489 target_os = "netbsd",
490 target_os = "openbsd",
491 ))]
492 related_view: None,
493 #[cfg(target_os = "macos")]
494 webview_configuration: None,
495 }
496 }
497
498 #[must_use]
500 pub fn user_agent(mut self, user_agent: &str) -> Self {
501 self.user_agent = Some(user_agent.to_string());
502 self
503 }
504
505 #[must_use]
523 pub fn initialization_script(mut self, script: impl Into<String>) -> Self {
524 self.initialization_scripts.push(InitializationScript {
525 script: script.into(),
526 for_main_frame_only: true,
527 });
528 self
529 }
530
531 #[must_use]
549 pub fn initialization_script_on_all_frames(mut self, script: impl Into<String>) -> Self {
550 self.initialization_scripts.push(InitializationScript {
551 script: script.into(),
552 for_main_frame_only: false,
553 });
554 self
555 }
556
557 #[must_use]
559 pub fn data_directory(mut self, data_directory: PathBuf) -> Self {
560 self.data_directory.replace(data_directory);
561 self
562 }
563
564 #[must_use]
566 pub fn disable_drag_drop_handler(mut self) -> Self {
567 self.drag_drop_handler_enabled = false;
568 self
569 }
570
571 #[must_use]
576 pub fn enable_clipboard_access(mut self) -> Self {
577 self.clipboard = true;
578 self
579 }
580
581 #[must_use]
583 pub fn accept_first_mouse(mut self, accept: bool) -> Self {
584 self.accept_first_mouse = accept;
585 self
586 }
587
588 #[must_use]
590 pub fn additional_browser_args(mut self, additional_args: &str) -> Self {
591 self.additional_browser_args = Some(additional_args.to_string());
592 self
593 }
594
595 #[must_use]
597 pub fn window_effects(mut self, effects: WindowEffectsConfig) -> Self {
598 self.window_effects = Some(effects);
599 self
600 }
601
602 #[must_use]
604 pub fn incognito(mut self, incognito: bool) -> Self {
605 self.incognito = incognito;
606 self
607 }
608
609 #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
611 #[must_use]
612 pub fn transparent(mut self, transparent: bool) -> Self {
613 self.transparent = transparent;
614 self
615 }
616
617 #[must_use]
619 pub fn focused(mut self, focus: bool) -> Self {
620 self.focus = focus;
621 self
622 }
623
624 #[must_use]
626 pub fn auto_resize(mut self) -> Self {
627 self.auto_resize = true;
628 self
629 }
630
631 #[must_use]
633 pub fn proxy_url(mut self, url: Url) -> Self {
634 self.proxy_url = Some(url);
635 self
636 }
637
638 #[must_use]
648 pub fn zoom_hotkeys_enabled(mut self, enabled: bool) -> Self {
649 self.zoom_hotkeys_enabled = enabled;
650 self
651 }
652
653 #[must_use]
660 pub fn browser_extensions_enabled(mut self, enabled: bool) -> Self {
661 self.browser_extensions_enabled = enabled;
662 self
663 }
664
665 #[must_use]
675 pub fn use_https_scheme(mut self, enabled: bool) -> Self {
676 self.use_https_scheme = enabled;
677 self
678 }
679
680 #[must_use]
690 pub fn devtools(mut self, enabled: Option<bool>) -> Self {
691 self.devtools = enabled;
692 self
693 }
694
695 #[must_use]
701 pub fn background_color(mut self, color: Color) -> Self {
702 self.background_color = Some(color);
703 self
704 }
705
706 #[must_use]
714 pub fn traffic_light_position(mut self, position: dpi::Position) -> Self {
715 self.traffic_light_position = Some(position);
716 self
717 }
718
719 #[must_use]
729 pub fn allow_link_preview(mut self, allow_link_preview: bool) -> Self {
730 self.allow_link_preview = allow_link_preview;
731 self
732 }
733
734 #[must_use]
749 pub fn background_throttling(mut self, policy: Option<BackgroundThrottlingPolicy>) -> Self {
750 self.background_throttling = policy;
751 self
752 }
753}
754
755pub type WebviewIpcHandler<T, R> = Box<dyn Fn(DetachedWebview<T, R>, Request<String>) + Send>;
757
758#[derive(Debug, Clone)]
760pub struct InitializationScript {
761 pub script: String,
763 pub for_main_frame_only: bool,
765}