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}
181
182impl SystemPipeReader {
183 #[cfg(unix)]
186 pub fn into_raw_fd(self) -> std::os::fd::RawFd {
187 self.fd
188 }
189
190 #[cfg(unix)]
196 pub unsafe fn from_raw_fd(fd: std::os::fd::RawFd) -> Self {
197 Self { fd }
198 }
199
200 #[cfg(unix)]
202 pub fn into_file(self) -> std::fs::File {
203 use std::os::fd::FromRawFd;
204 unsafe { std::fs::File::from_raw_fd(self.into_raw_fd()) }
205 }
206}
207
208#[async_trait]
210pub trait WebViewController: Send + Sync {
211 fn load_url(&self, url: &str) -> Result<(), WebViewError>;
213
214 fn load_data(&self, request: LoadDataRequest<'_>) -> Result<(), WebViewError>;
216
217 fn exec_js(&self, js: &str) -> Result<(), WebViewError>;
219
220 async fn eval_js(&self, js: &str) -> Result<serde_json::Value, WebViewScriptError>;
230
231 async fn current_url(&self) -> Result<Option<String>, WebViewError> {
233 Err(WebViewError::WebView(
234 "current_url is not implemented for this platform".to_string(),
235 ))
236 }
237
238 fn post_message(&self, message: &str) -> Result<(), WebViewError>;
240
241 fn clear_browsing_data(&self) -> Result<(), WebViewError>;
243
244 fn set_user_agent(&self, ua: &str) -> Result<(), WebViewError>;
246
247 fn reload(&self) -> Result<(), WebViewError> {
249 Err(WebViewError::WebView(
250 "reload is not implemented for this platform".to_string(),
251 ))
252 }
253
254 fn go_back(&self) -> Result<(), WebViewError> {
256 Err(WebViewError::WebView(
257 "go_back is not implemented for this platform".to_string(),
258 ))
259 }
260
261 fn go_forward(&self) -> Result<(), WebViewError> {
263 Err(WebViewError::WebView(
264 "go_forward is not implemented for this platform".to_string(),
265 ))
266 }
267
268 async fn list_cookies(&self) -> Result<Vec<WebViewCookie>, WebViewError> {
270 Err(WebViewError::WebView(
271 "cookie store is not implemented for this platform".to_string(),
272 ))
273 }
274
275 async fn set_cookie(&self, _request: WebViewCookieSetRequest) -> Result<(), WebViewError> {
277 Err(WebViewError::WebView(
278 "cookie store is not implemented for this platform".to_string(),
279 ))
280 }
281
282 async fn delete_cookie(
284 &self,
285 _name: &str,
286 _domain: &str,
287 _path: &str,
288 ) -> Result<(), WebViewError> {
289 Err(WebViewError::WebView(
290 "cookie store is not implemented for this platform".to_string(),
291 ))
292 }
293
294 async fn clear_cookies(&self) -> Result<(), WebViewError> {
296 Err(WebViewError::WebView(
297 "cookie store is not implemented for this platform".to_string(),
298 ))
299 }
300
301 async fn take_screenshot(&self) -> Result<Vec<u8>, WebViewError> {
304 Err(WebViewError::WebView(
305 "screenshot is not implemented for this platform".to_string(),
306 ))
307 }
308}
309
310#[derive(Debug, Clone, Default, Serialize, Deserialize)]
311pub struct ClickOptions {
312 #[serde(default, skip_serializing_if = "Option::is_none")]
313 pub index: Option<usize>,
314}
315
316#[derive(Debug, Clone, Default, Serialize, Deserialize)]
317pub struct TypeOptions {
318 #[serde(default, skip_serializing_if = "Option::is_none")]
319 pub index: Option<usize>,
320 #[serde(default)]
321 pub replace: bool,
322}
323
324#[derive(Debug, Clone, Default, Serialize, Deserialize)]
325pub struct FillOptions {
326 #[serde(default, skip_serializing_if = "Option::is_none")]
327 pub index: Option<usize>,
328}
329
330#[derive(Debug, Clone, Default, Serialize, Deserialize)]
331pub struct PressOptions;
332
333#[derive(Debug, Clone, Default, Serialize, Deserialize)]
334pub struct ScrollOptions;
335
336#[async_trait]
337pub trait WebViewInputController: WebViewController {
338 async fn click(
339 &self,
340 _selector: &str,
341 _options: ClickOptions,
342 ) -> Result<(), WebViewInputError> {
343 Err(WebViewInputError::Unsupported(
344 "input control is not implemented for this platform",
345 ))
346 }
347
348 async fn type_text(
349 &self,
350 _selector: &str,
351 _text: &str,
352 _options: TypeOptions,
353 ) -> Result<(), WebViewInputError> {
354 Err(WebViewInputError::Unsupported(
355 "input control is not implemented for this platform",
356 ))
357 }
358
359 async fn fill(
360 &self,
361 _selector: &str,
362 _text: &str,
363 _options: FillOptions,
364 ) -> Result<(), WebViewInputError> {
365 Err(WebViewInputError::Unsupported(
366 "input control is not implemented for this platform",
367 ))
368 }
369
370 async fn press(&self, _key: &str, _options: PressOptions) -> Result<(), WebViewInputError> {
371 Err(WebViewInputError::Unsupported(
372 "input control is not implemented for this platform",
373 ))
374 }
375
376 async fn scroll(
377 &self,
378 _dx: f64,
379 _dy: f64,
380 _options: ScrollOptions,
381 ) -> Result<(), WebViewInputError> {
382 Err(WebViewInputError::Unsupported(
383 "input control is not implemented for this platform",
384 ))
385 }
386
387 async fn scroll_to(
388 &self,
389 _selector: &str,
390 _options: ScrollOptions,
391 ) -> Result<(), WebViewInputError> {
392 Err(WebViewInputError::Unsupported(
393 "input control is not implemented for this platform",
394 ))
395 }
396}
397
398#[derive(Debug, Clone, Copy)]
399pub struct LoadDataRequest<'a> {
400 pub data: &'a str,
401 pub base_url: &'a str,
402 pub history_url: Option<&'a str>,
403}
404
405impl<'a> LoadDataRequest<'a> {
406 pub fn new(data: &'a str, base_url: &'a str) -> Self {
407 Self {
408 data,
409 base_url,
410 history_url: None,
411 }
412 }
413
414 pub fn with_history_url(mut self, history_url: &'a str) -> Self {
415 self.history_url = Some(history_url);
416 self
417 }
418}
419
420#[derive(Debug, Clone, Copy, PartialEq, Eq)]
422pub enum LoadErrorKind {
423 Dns,
424 Network,
425 Timeout,
426 Security,
427 Cancelled,
428 InvalidUrl,
429 NotFound,
430 Unknown,
431}
432
433#[derive(Debug, Clone)]
438pub struct LoadError {
439 pub url: Option<String>,
441 pub kind: LoadErrorKind,
443 pub description: String,
445}
446
447pub trait WebViewDelegate: Send + Sync {
449 fn on_page_started(&self);
451
452 fn on_page_finished(&self);
454
455 fn on_load_error(&self, _error: &LoadError) {}
460
461 fn handle_post_message(&self, msg: String);
463
464 fn log(&self, level: LogLevel, message: &str);
466}
467
468#[derive(Debug)]
470pub struct WebResourceResponse {
471 parts: http::response::Parts,
472 body: WebResourceBody,
473}
474
475impl From<Option<WebResourceResponse>> for SchemeOutcome {
476 fn from(value: Option<WebResourceResponse>) -> Self {
477 match value {
478 Some(response) => SchemeOutcome::Handled(response),
479 None => SchemeOutcome::PassThrough,
480 }
481 }
482}
483
484impl WebResourceResponse {
485 pub fn parts(&self) -> &http::response::Parts {
487 &self.parts
488 }
489
490 pub fn into_parts(self) -> (http::response::Parts, WebResourceBody) {
492 (self.parts, self.body)
493 }
494}
495
496impl From<(http::response::Parts, PathBuf)> for WebResourceResponse {
498 fn from(value: (http::response::Parts, PathBuf)) -> Self {
499 WebResourceResponse {
500 parts: value.0,
501 body: WebResourceBody::Path(value.1),
502 }
503 }
504}
505
506impl From<(http::response::Parts, SystemPipeReader)> for WebResourceResponse {
508 fn from(value: (http::response::Parts, SystemPipeReader)) -> Self {
509 WebResourceResponse {
510 parts: value.0,
511 body: WebResourceBody::Pipe(value.1),
512 }
513 }
514}
515
516impl From<(http::response::Parts, Vec<u8>)> for WebResourceResponse {
518 fn from(value: (http::response::Parts, Vec<u8>)) -> Self {
519 WebResourceResponse {
520 parts: value.0,
521 body: WebResourceBody::Bytes(value.1),
522 }
523 }
524}
525
526impl WebResourceResponse {
527 fn response_parts_with_status(status: u16) -> http::response::Parts {
528 let response = match http::Response::builder().status(status).body(()) {
529 Ok(response) => response,
530 Err(_) => http::Response::new(()),
531 };
532 let (parts, _) = response.into_parts();
533 parts
534 }
535
536 pub fn file(path: impl Into<PathBuf>) -> Self {
538 let path = path.into();
539 let content_length = std::fs::metadata(&path).ok().map(|m| m.len());
540 let mut parts = Self::response_parts_with_status(200);
541 if let Some(len) = content_length {
542 parts
543 .headers
544 .insert(http::header::CONTENT_LENGTH, http::HeaderValue::from(len));
545 }
546 Self {
547 parts,
548 body: WebResourceBody::Path(path),
549 }
550 }
551
552 pub fn bytes(data: impl Into<Vec<u8>>) -> Self {
554 let data = data.into();
555 let len = data.len();
556 let mut parts = Self::response_parts_with_status(200);
557 parts
558 .headers
559 .insert(http::header::CONTENT_LENGTH, http::HeaderValue::from(len));
560 Self {
561 parts,
562 body: WebResourceBody::Bytes(data),
563 }
564 }
565
566 pub fn stream(reader: SystemPipeReader) -> Self {
568 let parts = Self::response_parts_with_status(200);
569 Self {
570 parts,
571 body: WebResourceBody::Pipe(reader),
572 }
573 }
574
575 pub fn mime(mut self, content_type: &str) -> Self {
577 if let Ok(value) = http::HeaderValue::from_str(content_type) {
578 self.parts.headers.insert(http::header::CONTENT_TYPE, value);
579 }
580 self
581 }
582
583 pub fn status(mut self, code: u16) -> Self {
585 self.parts.status = http::StatusCode::from_u16(code).unwrap_or(self.parts.status);
586 self
587 }
588
589 pub fn header(mut self, name: &str, value: &str) -> Self {
591 if let (Ok(header_name), Ok(header_value)) = (
592 name.parse::<http::header::HeaderName>(),
593 http::HeaderValue::from_str(value),
594 ) {
595 self.parts.headers.insert(header_name, header_value);
596 }
597 self
598 }
599
600 pub fn cors(self) -> Self {
602 self.header("access-control-allow-origin", "null")
603 }
604}