1use crate::{LogLevel, WebViewError, WebViewInputError, WebViewScriptError};
2use async_trait::async_trait;
3use serde::{Deserialize, Serialize};
4use std::future::Future;
5use std::path::PathBuf;
6use std::pin::Pin;
7use std::sync::Arc;
8
9#[derive(Debug)]
11pub enum SchemeOutcome {
12 Handled(WebResourceResponse),
14 PassThrough,
16}
17
18pub(crate) type AsyncSchemeFuture = Pin<Box<dyn Future<Output = SchemeOutcome> + Send + 'static>>;
20pub(crate) type AsyncSchemeHandler =
21 Arc<dyn Fn(http::Request<Vec<u8>>) -> AsyncSchemeFuture + Send + Sync>;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum NavigationPolicy {
26 Allow,
28 Cancel,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct NavigationRequest {
36 pub url: String,
37 pub has_user_gesture: bool,
38 pub is_main_frame: bool,
39}
40
41impl NavigationRequest {
42 pub fn new(url: impl Into<String>, has_user_gesture: bool, is_main_frame: bool) -> Self {
43 Self {
44 url: url.into(),
45 has_user_gesture,
46 is_main_frame,
47 }
48 }
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum NewWindowPolicy {
54 LoadInSelf,
56 Cancel,
58}
59
60pub type NavigationHandler = Box<dyn Fn(&NavigationRequest) -> NavigationPolicy + Send + Sync>;
61pub type NewWindowHandler = Box<dyn Fn(&str) -> NewWindowPolicy + Send + Sync>;
62
63#[derive(Debug, Clone, PartialEq, Eq)]
65pub enum UserAgentOverride {
66 Default,
68 Custom(String),
72}
73
74impl UserAgentOverride {
75 pub(crate) fn validate(&self) -> Result<(), WebViewError> {
76 if let Self::Custom(value) = self
77 && value.trim().is_empty()
78 {
79 return Err(WebViewError::WebView(
80 "custom user-agent override must not be empty".to_string(),
81 ));
82 }
83 Ok(())
84 }
85}
86
87#[cfg(test)]
88mod user_agent_override_tests {
89 use super::*;
90
91 #[test]
92 fn custom_user_agent_must_not_be_blank() {
93 assert!(UserAgentOverride::Custom(String::new()).validate().is_err());
94 assert!(UserAgentOverride::Custom(" ".into()).validate().is_err());
95 assert!(
96 UserAgentOverride::Custom("Mozilla/5.0 valid".into())
97 .validate()
98 .is_ok()
99 );
100 assert!(UserAgentOverride::Default.validate().is_ok());
101 }
102}
103
104#[derive(Debug, Clone, PartialEq, Eq)]
105pub struct DownloadRequest {
106 pub url: String,
108 pub user_agent: Option<String>,
110 pub content_disposition: Option<String>,
112 pub mime_type: Option<String>,
114 pub content_length: Option<u64>,
116 pub suggested_filename: Option<String>,
118 pub source_page_url: Option<String>,
120 pub cookie: Option<String>,
122}
123
124pub type DownloadHandler = Box<dyn Fn(DownloadRequest) + Send + Sync>;
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
131#[serde(rename_all = "lowercase")]
132pub enum WebViewCookieSameSite {
133 Lax,
134 Strict,
135 None,
136}
137
138impl WebViewCookieSameSite {
139 pub fn as_str(self) -> &'static str {
140 match self {
141 Self::Lax => "lax",
142 Self::Strict => "strict",
143 Self::None => "none",
144 }
145 }
146}
147
148#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
149pub struct WebViewCookie {
150 pub name: String,
151 pub value: String,
152 pub domain: String,
153 pub path: String,
154 #[serde(default, skip_serializing_if = "is_false")]
155 pub host_only: bool,
156 #[serde(default)]
157 pub secure: bool,
158 #[serde(default)]
159 pub http_only: bool,
160 #[serde(default)]
161 pub session: bool,
162 #[serde(default, skip_serializing_if = "Option::is_none")]
163 pub expires_unix_ms: Option<i64>,
164 #[serde(default, skip_serializing_if = "Option::is_none")]
165 pub same_site: Option<WebViewCookieSameSite>,
166}
167
168#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
169pub struct WebViewCookieSetRequest {
170 #[serde(default)]
171 pub url: String,
172 pub name: String,
173 pub value: String,
174 #[serde(default, skip_serializing_if = "Option::is_none")]
175 pub domain: Option<String>,
176 #[serde(default = "default_cookie_path")]
177 pub path: String,
178 #[serde(default)]
179 pub secure: bool,
180 #[serde(default)]
181 pub http_only: bool,
182 #[serde(default, skip_serializing_if = "Option::is_none")]
183 pub expires_unix_ms: Option<i64>,
184 #[serde(default, skip_serializing_if = "Option::is_none")]
185 pub same_site: Option<WebViewCookieSameSite>,
186}
187
188fn default_cookie_path() -> String {
189 "/".to_string()
190}
191
192fn is_false(value: &bool) -> bool {
193 !*value
194}
195
196#[derive(Debug, Clone, PartialEq, Eq)]
197pub struct FileChooserRequest {
198 pub accept_types: Vec<String>,
200 pub allow_multiple: bool,
202 pub allow_directories: bool,
204 pub capture: bool,
206 pub source_page_url: Option<String>,
208}
209
210#[derive(Debug, Clone, PartialEq, Eq)]
211pub struct FileChooserFile {
212 pub path: Option<String>,
213 pub uri: Option<String>,
214}
215
216#[derive(Debug, Clone, PartialEq, Eq)]
217pub enum FileChooserResponse {
218 Cancel,
219 Error(String),
220 Files(Vec<FileChooserFile>),
221}
222
223#[derive(Debug)]
225pub enum WebResourceBody {
226 Path(PathBuf),
228 Pipe(SystemPipeReader),
230 Bytes(Vec<u8>),
232}
233
234#[derive(Debug)]
236pub struct SystemPipeReader {
237 #[cfg(unix)]
238 fd: std::os::fd::RawFd,
239 #[cfg(windows)]
240 handle: std::os::windows::io::RawHandle,
241}
242
243impl SystemPipeReader {
244 #[cfg(unix)]
247 pub fn into_raw_fd(self) -> std::os::fd::RawFd {
248 self.fd
249 }
250
251 #[cfg(unix)]
257 pub unsafe fn from_raw_fd(fd: std::os::fd::RawFd) -> Self {
258 Self { fd }
259 }
260
261 #[cfg(unix)]
263 pub fn into_file(self) -> std::fs::File {
264 use std::os::fd::FromRawFd;
265 unsafe { std::fs::File::from_raw_fd(self.into_raw_fd()) }
266 }
267
268 #[cfg(windows)]
271 pub fn into_raw_handle(self) -> std::os::windows::io::RawHandle {
272 self.handle
273 }
274
275 #[cfg(windows)]
281 pub unsafe fn from_raw_handle(handle: std::os::windows::io::RawHandle) -> Self {
282 Self { handle }
283 }
284
285 #[cfg(windows)]
287 pub fn into_file(self) -> std::fs::File {
288 use std::os::windows::io::FromRawHandle;
289 unsafe { std::fs::File::from_raw_handle(self.into_raw_handle()) }
290 }
291}
292
293#[async_trait]
295pub trait WebViewController: Send + Sync {
296 fn load_url(&self, url: &str) -> Result<(), WebViewError>;
298
299 fn load_data(&self, request: LoadDataRequest<'_>) -> Result<(), WebViewError>;
301
302 fn exec_js(&self, js: &str) -> Result<(), WebViewError>;
304
305 async fn eval_js(&self, js: &str) -> Result<serde_json::Value, WebViewScriptError>;
315
316 async fn current_url(&self) -> Result<Option<String>, WebViewError> {
318 Err(WebViewError::WebView(
319 "current_url is not implemented for this platform".to_string(),
320 ))
321 }
322
323 fn post_message(&self, message: &str) -> Result<(), WebViewError>;
325
326 fn clear_browsing_data(&self) -> Result<(), WebViewError>;
328
329 fn set_user_agent_override(&self, user_agent: UserAgentOverride) -> Result<(), WebViewError>;
331
332 fn reload(&self) -> Result<(), WebViewError> {
334 Err(WebViewError::WebView(
335 "reload is not implemented for this platform".to_string(),
336 ))
337 }
338
339 fn go_back(&self) -> Result<(), WebViewError> {
341 Err(WebViewError::WebView(
342 "go_back is not implemented for this platform".to_string(),
343 ))
344 }
345
346 fn go_forward(&self) -> Result<(), WebViewError> {
348 Err(WebViewError::WebView(
349 "go_forward is not implemented for this platform".to_string(),
350 ))
351 }
352
353 async fn list_cookies(&self) -> Result<Vec<WebViewCookie>, WebViewError> {
355 Err(WebViewError::WebView(
356 "cookie store is not implemented for this platform".to_string(),
357 ))
358 }
359
360 async fn set_cookie(&self, _request: WebViewCookieSetRequest) -> Result<(), WebViewError> {
362 Err(WebViewError::WebView(
363 "cookie store is not implemented for this platform".to_string(),
364 ))
365 }
366
367 async fn delete_cookie(
369 &self,
370 _name: &str,
371 _domain: &str,
372 _path: &str,
373 ) -> Result<(), WebViewError> {
374 Err(WebViewError::WebView(
375 "cookie store is not implemented for this platform".to_string(),
376 ))
377 }
378
379 async fn clear_cookies(&self) -> Result<(), WebViewError> {
381 Err(WebViewError::WebView(
382 "cookie store is not implemented for this platform".to_string(),
383 ))
384 }
385
386 async fn clear_site_data(
390 &self,
391 _url: &str,
392 _options: ClearSiteDataOptions,
393 ) -> Result<ClearSiteDataResult, WebViewError> {
394 Err(WebViewError::WebView(
395 "site-scoped data clearing is not implemented for this platform".to_string(),
396 ))
397 }
398
399 async fn take_screenshot(&self) -> Result<Vec<u8>, WebViewError> {
402 Err(WebViewError::WebView(
403 "screenshot is not implemented for this platform".to_string(),
404 ))
405 }
406
407 async fn start_network_capture(&self) -> Result<(), WebViewError> {
412 Err(WebViewError::WebView(
413 "network capture is not implemented for this platform".to_string(),
414 ))
415 }
416
417 async fn stop_network_capture(&self) -> Result<(), WebViewError> {
420 Err(WebViewError::WebView(
421 "network capture is not implemented for this platform".to_string(),
422 ))
423 }
424
425 async fn network_entries(&self) -> Result<NetworkCaptureSnapshot, WebViewError> {
428 Err(WebViewError::WebView(
429 "network capture is not implemented for this platform".to_string(),
430 ))
431 }
432
433 async fn clear_network_capture(&self) -> Result<(), WebViewError> {
435 Err(WebViewError::WebView(
436 "network capture is not implemented for this platform".to_string(),
437 ))
438 }
439}
440
441#[derive(Debug, Clone, Copy)]
444pub struct ClearSiteDataOptions {
445 pub cache: bool,
446 pub site_data: bool,
447}
448
449#[derive(Debug, Clone, Copy)]
457pub struct ClearSiteDataResult {
458 pub cache_cleared: bool,
459 pub site_data_cleared: bool,
460}
461
462#[derive(Debug, Clone, Serialize, Deserialize)]
464pub struct NetworkEntry {
465 pub request_id: String,
467 pub url: String,
468 pub method: String,
469 pub resource_type: Option<String>,
472 pub request_headers: Vec<(String, String)>,
473 pub request_body: Option<String>,
475 pub status: Option<u16>,
476 pub response_headers: Vec<(String, String)>,
477 pub mime_type: Option<String>,
478 pub response_body: NetworkBody,
479 pub from_cache: bool,
480 pub failed: Option<String>,
483 pub wall_time: Option<f64>,
485 pub started: f64,
487 pub finished: Option<f64>,
488}
489
490impl NetworkEntry {
491 pub fn duration_ms(&self) -> Option<f64> {
493 self.finished
494 .filter(|finished| *finished >= self.started)
495 .map(|finished| (finished - self.started) * 1000.0)
496 }
497}
498
499#[derive(Debug, Clone, Default, Serialize, Deserialize)]
501#[serde(tag = "kind", rename_all = "snake_case")]
502pub enum NetworkBody {
503 #[default]
505 None,
506 Text { text: String },
508 Base64 { base64: String },
510 Skipped { reason: String },
513}
514
515#[derive(Debug, Clone, Default, Serialize, Deserialize)]
517pub struct NetworkCaptureSnapshot {
518 pub entries: Vec<NetworkEntry>,
519 pub dropped: u64,
522}
523
524#[derive(Debug, Clone, Default, Serialize, Deserialize)]
525pub struct ClickOptions {
526 #[serde(default, skip_serializing_if = "Option::is_none")]
527 pub index: Option<usize>,
528}
529
530#[derive(Debug, Clone, Default, Serialize, Deserialize)]
531pub struct TypeOptions {
532 #[serde(default, skip_serializing_if = "Option::is_none")]
533 pub index: Option<usize>,
534 #[serde(default)]
535 pub replace: bool,
536}
537
538#[derive(Debug, Clone, Default, Serialize, Deserialize)]
539pub struct FillOptions {
540 #[serde(default, skip_serializing_if = "Option::is_none")]
541 pub index: Option<usize>,
542}
543
544#[derive(Debug, Clone, Default, Serialize, Deserialize)]
545pub struct PressOptions {
546 #[serde(default, skip_serializing_if = "Option::is_none")]
547 pub selector: Option<String>,
548 #[serde(default, skip_serializing_if = "Option::is_none")]
549 pub index: Option<usize>,
550}
551
552#[derive(Debug, Clone, Default, Serialize, Deserialize)]
553pub struct ScrollOptions;
554
555#[async_trait]
556pub trait WebViewInputController: WebViewController {
557 async fn click(
558 &self,
559 _selector: &str,
560 _options: ClickOptions,
561 ) -> Result<(), WebViewInputError> {
562 Err(WebViewInputError::Unsupported(
563 "input control is not implemented for this platform",
564 ))
565 }
566
567 async fn type_text(
568 &self,
569 _selector: &str,
570 _text: &str,
571 _options: TypeOptions,
572 ) -> Result<(), WebViewInputError> {
573 Err(WebViewInputError::Unsupported(
574 "input control is not implemented for this platform",
575 ))
576 }
577
578 async fn fill(
579 &self,
580 _selector: &str,
581 _text: &str,
582 _options: FillOptions,
583 ) -> Result<(), WebViewInputError> {
584 Err(WebViewInputError::Unsupported(
585 "input control is not implemented for this platform",
586 ))
587 }
588
589 async fn press(&self, _key: &str, _options: PressOptions) -> Result<(), WebViewInputError> {
590 Err(WebViewInputError::Unsupported(
591 "input control is not implemented for this platform",
592 ))
593 }
594
595 async fn scroll(
596 &self,
597 _dx: f64,
598 _dy: f64,
599 _options: ScrollOptions,
600 ) -> Result<(), WebViewInputError> {
601 Err(WebViewInputError::Unsupported(
602 "input control is not implemented for this platform",
603 ))
604 }
605
606 async fn scroll_to(
607 &self,
608 _selector: &str,
609 _options: ScrollOptions,
610 ) -> Result<(), WebViewInputError> {
611 Err(WebViewInputError::Unsupported(
612 "input control is not implemented for this platform",
613 ))
614 }
615}
616
617#[derive(Debug, Clone, Copy)]
618pub struct LoadDataRequest<'a> {
619 pub data: &'a str,
620 pub base_url: &'a str,
621 pub history_url: Option<&'a str>,
622}
623
624impl<'a> LoadDataRequest<'a> {
625 pub fn new(data: &'a str, base_url: &'a str) -> Self {
626 Self {
627 data,
628 base_url,
629 history_url: None,
630 }
631 }
632
633 pub fn with_history_url(mut self, history_url: &'a str) -> Self {
634 self.history_url = Some(history_url);
635 self
636 }
637}
638
639#[derive(Debug, Clone, Copy, PartialEq, Eq)]
644pub enum LoadErrorKind {
645 Dns,
646 Network,
647 Timeout,
648 Security,
649 InvalidUrl,
650 NotFound,
651 Unknown,
652}
653
654#[derive(Debug, Clone, PartialEq, Eq)]
660pub struct LoadError {
661 pub failing_url: Option<String>,
663 pub kind: LoadErrorKind,
665 pub description: String,
667}
668
669pub trait WebViewDelegate: Send + Sync {
702 fn on_navigation_event(&self, event: crate::events::NavigationEvent);
707
708 fn on_webview_state_change(&self, _change: crate::events::WebViewStateChange) {}
712
713 fn handle_post_message(&self, msg: String);
715
716 fn handle_native_component_message(&self, _message_json: String) {}
722
723 fn log(&self, level: LogLevel, message: &str);
725}
726
727#[derive(Debug)]
729pub struct WebResourceResponse {
730 parts: http::response::Parts,
731 body: WebResourceBody,
732}
733
734impl From<Option<WebResourceResponse>> for SchemeOutcome {
735 fn from(value: Option<WebResourceResponse>) -> Self {
736 match value {
737 Some(response) => SchemeOutcome::Handled(response),
738 None => SchemeOutcome::PassThrough,
739 }
740 }
741}
742
743impl WebResourceResponse {
744 pub fn parts(&self) -> &http::response::Parts {
746 &self.parts
747 }
748
749 pub fn into_parts(self) -> (http::response::Parts, WebResourceBody) {
751 (self.parts, self.body)
752 }
753}
754
755impl From<(http::response::Parts, PathBuf)> for WebResourceResponse {
757 fn from(value: (http::response::Parts, PathBuf)) -> Self {
758 WebResourceResponse {
759 parts: value.0,
760 body: WebResourceBody::Path(value.1),
761 }
762 }
763}
764
765impl From<(http::response::Parts, SystemPipeReader)> for WebResourceResponse {
767 fn from(value: (http::response::Parts, SystemPipeReader)) -> Self {
768 WebResourceResponse {
769 parts: value.0,
770 body: WebResourceBody::Pipe(value.1),
771 }
772 }
773}
774
775impl From<(http::response::Parts, Vec<u8>)> for WebResourceResponse {
777 fn from(value: (http::response::Parts, Vec<u8>)) -> Self {
778 WebResourceResponse {
779 parts: value.0,
780 body: WebResourceBody::Bytes(value.1),
781 }
782 }
783}
784
785impl WebResourceResponse {
786 fn response_parts_with_status(status: u16) -> http::response::Parts {
787 let response = match http::Response::builder().status(status).body(()) {
788 Ok(response) => response,
789 Err(_) => http::Response::new(()),
790 };
791 let (parts, _) = response.into_parts();
792 parts
793 }
794
795 pub fn file(path: impl Into<PathBuf>) -> Self {
797 let path = path.into();
798 let content_length = std::fs::metadata(&path).ok().map(|m| m.len());
799 let mut parts = Self::response_parts_with_status(200);
800 if let Some(len) = content_length {
801 parts
802 .headers
803 .insert(http::header::CONTENT_LENGTH, http::HeaderValue::from(len));
804 }
805 Self {
806 parts,
807 body: WebResourceBody::Path(path),
808 }
809 }
810
811 pub fn bytes(data: impl Into<Vec<u8>>) -> Self {
813 let data = data.into();
814 let len = data.len();
815 let mut parts = Self::response_parts_with_status(200);
816 parts
817 .headers
818 .insert(http::header::CONTENT_LENGTH, http::HeaderValue::from(len));
819 Self {
820 parts,
821 body: WebResourceBody::Bytes(data),
822 }
823 }
824
825 pub fn stream(reader: SystemPipeReader) -> Self {
827 let parts = Self::response_parts_with_status(200);
828 Self {
829 parts,
830 body: WebResourceBody::Pipe(reader),
831 }
832 }
833
834 pub fn mime(mut self, content_type: &str) -> Self {
836 if let Ok(value) = http::HeaderValue::from_str(content_type) {
837 self.parts.headers.insert(http::header::CONTENT_TYPE, value);
838 }
839 self
840 }
841
842 pub fn status(mut self, code: u16) -> Self {
844 self.parts.status = http::StatusCode::from_u16(code).unwrap_or(self.parts.status);
845 self
846 }
847
848 pub fn header(mut self, name: &str, value: &str) -> Self {
850 if let (Ok(header_name), Ok(header_value)) = (
851 name.parse::<http::header::HeaderName>(),
852 http::HeaderValue::from_str(value),
853 ) {
854 self.parts.headers.insert(header_name, header_value);
855 }
856 self
857 }
858
859 pub fn cors(self) -> Self {
861 self.header("access-control-allow-origin", "null")
862 }
863}