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, Copy, PartialEq, Eq)]
35pub enum NewWindowPolicy {
36 LoadInSelf,
38 Cancel,
40}
41
42pub type NavigationHandler = Box<dyn Fn(&str) -> NavigationPolicy + Send + Sync>;
43pub type NewWindowHandler = Box<dyn Fn(&str) -> NewWindowPolicy + Send + Sync>;
44
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct DownloadRequest {
47 pub url: String,
49 pub user_agent: Option<String>,
51 pub content_disposition: Option<String>,
53 pub mime_type: Option<String>,
55 pub content_length: Option<u64>,
57 pub suggested_filename: Option<String>,
59 pub source_page_url: Option<String>,
61 pub cookie: Option<String>,
63}
64
65pub type DownloadHandler = Box<dyn Fn(DownloadRequest) + Send + Sync>;
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
72#[serde(rename_all = "lowercase")]
73pub enum WebViewCookieSameSite {
74 Lax,
75 Strict,
76 None,
77}
78
79impl WebViewCookieSameSite {
80 pub fn as_str(self) -> &'static str {
81 match self {
82 Self::Lax => "lax",
83 Self::Strict => "strict",
84 Self::None => "none",
85 }
86 }
87}
88
89#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
90pub struct WebViewCookie {
91 pub name: String,
92 pub value: String,
93 pub domain: String,
94 pub path: String,
95 #[serde(default, skip_serializing_if = "is_false")]
96 pub host_only: bool,
97 #[serde(default)]
98 pub secure: bool,
99 #[serde(default)]
100 pub http_only: bool,
101 #[serde(default)]
102 pub session: bool,
103 #[serde(default, skip_serializing_if = "Option::is_none")]
104 pub expires_unix_ms: Option<i64>,
105 #[serde(default, skip_serializing_if = "Option::is_none")]
106 pub same_site: Option<WebViewCookieSameSite>,
107}
108
109#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
110pub struct WebViewCookieSetRequest {
111 #[serde(default)]
112 pub url: String,
113 pub name: String,
114 pub value: String,
115 #[serde(default, skip_serializing_if = "Option::is_none")]
116 pub domain: Option<String>,
117 #[serde(default = "default_cookie_path")]
118 pub path: String,
119 #[serde(default)]
120 pub secure: bool,
121 #[serde(default)]
122 pub http_only: bool,
123 #[serde(default, skip_serializing_if = "Option::is_none")]
124 pub expires_unix_ms: Option<i64>,
125 #[serde(default, skip_serializing_if = "Option::is_none")]
126 pub same_site: Option<WebViewCookieSameSite>,
127}
128
129fn default_cookie_path() -> String {
130 "/".to_string()
131}
132
133fn is_false(value: &bool) -> bool {
134 !*value
135}
136
137#[derive(Debug, Clone, PartialEq, Eq)]
138pub struct FileChooserRequest {
139 pub accept_types: Vec<String>,
141 pub allow_multiple: bool,
143 pub allow_directories: bool,
145 pub capture: bool,
147 pub source_page_url: Option<String>,
149}
150
151#[derive(Debug, Clone, PartialEq, Eq)]
152pub struct FileChooserFile {
153 pub path: Option<String>,
154 pub uri: Option<String>,
155}
156
157#[derive(Debug, Clone, PartialEq, Eq)]
158pub enum FileChooserResponse {
159 Cancel,
160 Error(String),
161 Files(Vec<FileChooserFile>),
162}
163
164#[derive(Debug)]
166pub enum WebResourceBody {
167 Path(PathBuf),
169 Pipe(SystemPipeReader),
171 Bytes(Vec<u8>),
173}
174
175#[derive(Debug)]
177pub struct SystemPipeReader {
178 #[cfg(unix)]
179 fd: std::os::fd::RawFd,
180 #[cfg(windows)]
181 handle: std::os::windows::io::RawHandle,
182}
183
184impl SystemPipeReader {
185 #[cfg(unix)]
188 pub fn into_raw_fd(self) -> std::os::fd::RawFd {
189 self.fd
190 }
191
192 #[cfg(unix)]
198 pub unsafe fn from_raw_fd(fd: std::os::fd::RawFd) -> Self {
199 Self { fd }
200 }
201
202 #[cfg(unix)]
204 pub fn into_file(self) -> std::fs::File {
205 use std::os::fd::FromRawFd;
206 unsafe { std::fs::File::from_raw_fd(self.into_raw_fd()) }
207 }
208
209 #[cfg(windows)]
212 pub fn into_raw_handle(self) -> std::os::windows::io::RawHandle {
213 self.handle
214 }
215
216 #[cfg(windows)]
222 pub unsafe fn from_raw_handle(handle: std::os::windows::io::RawHandle) -> Self {
223 Self { handle }
224 }
225
226 #[cfg(windows)]
228 pub fn into_file(self) -> std::fs::File {
229 use std::os::windows::io::FromRawHandle;
230 unsafe { std::fs::File::from_raw_handle(self.into_raw_handle()) }
231 }
232}
233
234#[async_trait]
236pub trait WebViewController: Send + Sync {
237 fn load_url(&self, url: &str) -> Result<(), WebViewError>;
239
240 fn load_data(&self, request: LoadDataRequest<'_>) -> Result<(), WebViewError>;
242
243 fn exec_js(&self, js: &str) -> Result<(), WebViewError>;
245
246 async fn eval_js(&self, js: &str) -> Result<serde_json::Value, WebViewScriptError>;
256
257 async fn current_url(&self) -> Result<Option<String>, WebViewError> {
259 Err(WebViewError::WebView(
260 "current_url is not implemented for this platform".to_string(),
261 ))
262 }
263
264 fn post_message(&self, message: &str) -> Result<(), WebViewError>;
266
267 fn clear_browsing_data(&self) -> Result<(), WebViewError>;
269
270 fn set_user_agent(&self, ua: &str) -> Result<(), WebViewError>;
272
273 fn reload(&self) -> Result<(), WebViewError> {
275 Err(WebViewError::WebView(
276 "reload is not implemented for this platform".to_string(),
277 ))
278 }
279
280 fn go_back(&self) -> Result<(), WebViewError> {
282 Err(WebViewError::WebView(
283 "go_back is not implemented for this platform".to_string(),
284 ))
285 }
286
287 fn go_forward(&self) -> Result<(), WebViewError> {
289 Err(WebViewError::WebView(
290 "go_forward is not implemented for this platform".to_string(),
291 ))
292 }
293
294 async fn list_cookies(&self) -> Result<Vec<WebViewCookie>, WebViewError> {
296 Err(WebViewError::WebView(
297 "cookie store is not implemented for this platform".to_string(),
298 ))
299 }
300
301 async fn set_cookie(&self, _request: WebViewCookieSetRequest) -> Result<(), WebViewError> {
303 Err(WebViewError::WebView(
304 "cookie store is not implemented for this platform".to_string(),
305 ))
306 }
307
308 async fn delete_cookie(
310 &self,
311 _name: &str,
312 _domain: &str,
313 _path: &str,
314 ) -> Result<(), WebViewError> {
315 Err(WebViewError::WebView(
316 "cookie store is not implemented for this platform".to_string(),
317 ))
318 }
319
320 async fn clear_cookies(&self) -> Result<(), WebViewError> {
322 Err(WebViewError::WebView(
323 "cookie store is not implemented for this platform".to_string(),
324 ))
325 }
326
327 async fn take_screenshot(&self) -> Result<Vec<u8>, WebViewError> {
330 Err(WebViewError::WebView(
331 "screenshot is not implemented for this platform".to_string(),
332 ))
333 }
334}
335
336#[derive(Debug, Clone, Default, Serialize, Deserialize)]
337pub struct ClickOptions {
338 #[serde(default, skip_serializing_if = "Option::is_none")]
339 pub index: Option<usize>,
340}
341
342#[derive(Debug, Clone, Default, Serialize, Deserialize)]
343pub struct TypeOptions {
344 #[serde(default, skip_serializing_if = "Option::is_none")]
345 pub index: Option<usize>,
346 #[serde(default)]
347 pub replace: bool,
348}
349
350#[derive(Debug, Clone, Default, Serialize, Deserialize)]
351pub struct FillOptions {
352 #[serde(default, skip_serializing_if = "Option::is_none")]
353 pub index: Option<usize>,
354}
355
356#[derive(Debug, Clone, Default, Serialize, Deserialize)]
357pub struct PressOptions;
358
359#[derive(Debug, Clone, Default, Serialize, Deserialize)]
360pub struct ScrollOptions;
361
362#[async_trait]
363pub trait WebViewInputController: WebViewController {
364 async fn click(
365 &self,
366 _selector: &str,
367 _options: ClickOptions,
368 ) -> Result<(), WebViewInputError> {
369 Err(WebViewInputError::Unsupported(
370 "input control is not implemented for this platform",
371 ))
372 }
373
374 async fn type_text(
375 &self,
376 _selector: &str,
377 _text: &str,
378 _options: TypeOptions,
379 ) -> Result<(), WebViewInputError> {
380 Err(WebViewInputError::Unsupported(
381 "input control is not implemented for this platform",
382 ))
383 }
384
385 async fn fill(
386 &self,
387 _selector: &str,
388 _text: &str,
389 _options: FillOptions,
390 ) -> Result<(), WebViewInputError> {
391 Err(WebViewInputError::Unsupported(
392 "input control is not implemented for this platform",
393 ))
394 }
395
396 async fn press(&self, _key: &str, _options: PressOptions) -> Result<(), WebViewInputError> {
397 Err(WebViewInputError::Unsupported(
398 "input control is not implemented for this platform",
399 ))
400 }
401
402 async fn scroll(
403 &self,
404 _dx: f64,
405 _dy: f64,
406 _options: ScrollOptions,
407 ) -> Result<(), WebViewInputError> {
408 Err(WebViewInputError::Unsupported(
409 "input control is not implemented for this platform",
410 ))
411 }
412
413 async fn scroll_to(
414 &self,
415 _selector: &str,
416 _options: ScrollOptions,
417 ) -> Result<(), WebViewInputError> {
418 Err(WebViewInputError::Unsupported(
419 "input control is not implemented for this platform",
420 ))
421 }
422}
423
424#[derive(Debug, Clone, Copy)]
425pub struct LoadDataRequest<'a> {
426 pub data: &'a str,
427 pub base_url: &'a str,
428 pub history_url: Option<&'a str>,
429}
430
431impl<'a> LoadDataRequest<'a> {
432 pub fn new(data: &'a str, base_url: &'a str) -> Self {
433 Self {
434 data,
435 base_url,
436 history_url: None,
437 }
438 }
439
440 pub fn with_history_url(mut self, history_url: &'a str) -> Self {
441 self.history_url = Some(history_url);
442 self
443 }
444}
445
446#[derive(Debug, Clone, Copy, PartialEq, Eq)]
448pub enum LoadErrorKind {
449 Dns,
450 Network,
451 Timeout,
452 Security,
453 Cancelled,
454 InvalidUrl,
455 NotFound,
456 Unknown,
457}
458
459#[derive(Debug, Clone)]
464pub struct LoadError {
465 pub url: Option<String>,
467 pub kind: LoadErrorKind,
469 pub description: String,
471}
472
473pub trait WebViewDelegate: Send + Sync {
475 fn on_page_started(&self);
477
478 fn on_page_finished(&self);
480
481 fn on_load_error(&self, _error: &LoadError) {}
486
487 fn on_title_changed(&self, _title: &str) {}
491
492 fn on_favicon_changed(&self, _png_bytes: Vec<u8>) {}
497
498 fn handle_post_message(&self, msg: String);
500
501 fn handle_native_component_message(&self, message_json: &str) {
508 let _ = message_json;
509 }
510
511 fn log(&self, level: LogLevel, message: &str);
513}
514
515#[derive(Debug)]
517pub struct WebResourceResponse {
518 parts: http::response::Parts,
519 body: WebResourceBody,
520}
521
522impl From<Option<WebResourceResponse>> for SchemeOutcome {
523 fn from(value: Option<WebResourceResponse>) -> Self {
524 match value {
525 Some(response) => SchemeOutcome::Handled(response),
526 None => SchemeOutcome::PassThrough,
527 }
528 }
529}
530
531impl WebResourceResponse {
532 pub fn parts(&self) -> &http::response::Parts {
534 &self.parts
535 }
536
537 pub fn into_parts(self) -> (http::response::Parts, WebResourceBody) {
539 (self.parts, self.body)
540 }
541}
542
543impl From<(http::response::Parts, PathBuf)> for WebResourceResponse {
545 fn from(value: (http::response::Parts, PathBuf)) -> Self {
546 WebResourceResponse {
547 parts: value.0,
548 body: WebResourceBody::Path(value.1),
549 }
550 }
551}
552
553impl From<(http::response::Parts, SystemPipeReader)> for WebResourceResponse {
555 fn from(value: (http::response::Parts, SystemPipeReader)) -> Self {
556 WebResourceResponse {
557 parts: value.0,
558 body: WebResourceBody::Pipe(value.1),
559 }
560 }
561}
562
563impl From<(http::response::Parts, Vec<u8>)> for WebResourceResponse {
565 fn from(value: (http::response::Parts, Vec<u8>)) -> Self {
566 WebResourceResponse {
567 parts: value.0,
568 body: WebResourceBody::Bytes(value.1),
569 }
570 }
571}
572
573impl WebResourceResponse {
574 fn response_parts_with_status(status: u16) -> http::response::Parts {
575 let response = match http::Response::builder().status(status).body(()) {
576 Ok(response) => response,
577 Err(_) => http::Response::new(()),
578 };
579 let (parts, _) = response.into_parts();
580 parts
581 }
582
583 pub fn file(path: impl Into<PathBuf>) -> Self {
585 let path = path.into();
586 let content_length = std::fs::metadata(&path).ok().map(|m| m.len());
587 let mut parts = Self::response_parts_with_status(200);
588 if let Some(len) = content_length {
589 parts
590 .headers
591 .insert(http::header::CONTENT_LENGTH, http::HeaderValue::from(len));
592 }
593 Self {
594 parts,
595 body: WebResourceBody::Path(path),
596 }
597 }
598
599 pub fn bytes(data: impl Into<Vec<u8>>) -> Self {
601 let data = data.into();
602 let len = data.len();
603 let mut parts = Self::response_parts_with_status(200);
604 parts
605 .headers
606 .insert(http::header::CONTENT_LENGTH, http::HeaderValue::from(len));
607 Self {
608 parts,
609 body: WebResourceBody::Bytes(data),
610 }
611 }
612
613 pub fn stream(reader: SystemPipeReader) -> Self {
615 let parts = Self::response_parts_with_status(200);
616 Self {
617 parts,
618 body: WebResourceBody::Pipe(reader),
619 }
620 }
621
622 pub fn mime(mut self, content_type: &str) -> Self {
624 if let Ok(value) = http::HeaderValue::from_str(content_type) {
625 self.parts.headers.insert(http::header::CONTENT_TYPE, value);
626 }
627 self
628 }
629
630 pub fn status(mut self, code: u16) -> Self {
632 self.parts.status = http::StatusCode::from_u16(code).unwrap_or(self.parts.status);
633 self
634 }
635
636 pub fn header(mut self, name: &str, value: &str) -> Self {
638 if let (Ok(header_name), Ok(header_value)) = (
639 name.parse::<http::header::HeaderName>(),
640 http::HeaderValue::from_str(value),
641 ) {
642 self.parts.headers.insert(header_name, header_value);
643 }
644 self
645 }
646
647 pub fn cors(self) -> Self {
649 self.header("access-control-allow-origin", "null")
650 }
651}