use crate::{LogLevel, WebViewError, WebViewInputError, WebViewScriptError};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::future::Future;
use std::path::PathBuf;
use std::pin::Pin;
use std::sync::Arc;
#[derive(Debug)]
pub enum SchemeOutcome {
Handled(WebResourceResponse),
PassThrough,
}
pub(crate) type AsyncSchemeFuture = Pin<Box<dyn Future<Output = SchemeOutcome> + Send + 'static>>;
pub(crate) type AsyncSchemeHandler =
Arc<dyn Fn(http::Request<Vec<u8>>) -> AsyncSchemeFuture + Send + Sync>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NavigationPolicy {
Allow,
Cancel,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NewWindowPolicy {
LoadInSelf,
Cancel,
}
pub type NavigationHandler = Box<dyn Fn(&str) -> NavigationPolicy + Send + Sync>;
pub type NewWindowHandler = Box<dyn Fn(&str) -> NewWindowPolicy + Send + Sync>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DownloadRequest {
pub url: String,
pub user_agent: Option<String>,
pub content_disposition: Option<String>,
pub mime_type: Option<String>,
pub content_length: Option<u64>,
pub suggested_filename: Option<String>,
pub source_page_url: Option<String>,
pub cookie: Option<String>,
}
pub type DownloadHandler = Box<dyn Fn(DownloadRequest) + Send + Sync>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum WebViewCookieSameSite {
Lax,
Strict,
None,
}
impl WebViewCookieSameSite {
pub fn as_str(self) -> &'static str {
match self {
Self::Lax => "lax",
Self::Strict => "strict",
Self::None => "none",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WebViewCookie {
pub name: String,
pub value: String,
pub domain: String,
pub path: String,
#[serde(default, skip_serializing_if = "is_false")]
pub host_only: bool,
#[serde(default)]
pub secure: bool,
#[serde(default)]
pub http_only: bool,
#[serde(default)]
pub session: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_unix_ms: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub same_site: Option<WebViewCookieSameSite>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WebViewCookieSetRequest {
#[serde(default)]
pub url: String,
pub name: String,
pub value: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub domain: Option<String>,
#[serde(default = "default_cookie_path")]
pub path: String,
#[serde(default)]
pub secure: bool,
#[serde(default)]
pub http_only: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_unix_ms: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub same_site: Option<WebViewCookieSameSite>,
}
fn default_cookie_path() -> String {
"/".to_string()
}
fn is_false(value: &bool) -> bool {
!*value
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FileChooserRequest {
pub accept_types: Vec<String>,
pub allow_multiple: bool,
pub allow_directories: bool,
pub capture: bool,
pub source_page_url: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FileChooserFile {
pub path: Option<String>,
pub uri: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FileChooserResponse {
Cancel,
Error(String),
Files(Vec<FileChooserFile>),
}
#[derive(Debug)]
pub enum WebResourceBody {
Path(PathBuf),
Pipe(SystemPipeReader),
Bytes(Vec<u8>),
}
#[derive(Debug)]
pub struct SystemPipeReader {
#[cfg(unix)]
fd: std::os::fd::RawFd,
}
impl SystemPipeReader {
#[cfg(unix)]
pub fn into_raw_fd(self) -> std::os::fd::RawFd {
self.fd
}
#[cfg(unix)]
pub unsafe fn from_raw_fd(fd: std::os::fd::RawFd) -> Self {
Self { fd }
}
#[cfg(unix)]
pub fn into_file(self) -> std::fs::File {
use std::os::fd::FromRawFd;
unsafe { std::fs::File::from_raw_fd(self.into_raw_fd()) }
}
}
#[async_trait]
pub trait WebViewController: Send + Sync {
fn load_url(&self, url: &str) -> Result<(), WebViewError>;
fn load_data(&self, request: LoadDataRequest<'_>) -> Result<(), WebViewError>;
fn exec_js(&self, js: &str) -> Result<(), WebViewError>;
async fn eval_js(&self, js: &str) -> Result<serde_json::Value, WebViewScriptError>;
async fn current_url(&self) -> Result<Option<String>, WebViewError> {
Err(WebViewError::WebView(
"current_url is not implemented for this platform".to_string(),
))
}
fn post_message(&self, message: &str) -> Result<(), WebViewError>;
fn clear_browsing_data(&self) -> Result<(), WebViewError>;
fn set_user_agent(&self, ua: &str) -> Result<(), WebViewError>;
fn reload(&self) -> Result<(), WebViewError> {
Err(WebViewError::WebView(
"reload is not implemented for this platform".to_string(),
))
}
fn go_back(&self) -> Result<(), WebViewError> {
Err(WebViewError::WebView(
"go_back is not implemented for this platform".to_string(),
))
}
fn go_forward(&self) -> Result<(), WebViewError> {
Err(WebViewError::WebView(
"go_forward is not implemented for this platform".to_string(),
))
}
async fn list_cookies(&self) -> Result<Vec<WebViewCookie>, WebViewError> {
Err(WebViewError::WebView(
"cookie store is not implemented for this platform".to_string(),
))
}
async fn set_cookie(&self, _request: WebViewCookieSetRequest) -> Result<(), WebViewError> {
Err(WebViewError::WebView(
"cookie store is not implemented for this platform".to_string(),
))
}
async fn delete_cookie(
&self,
_name: &str,
_domain: &str,
_path: &str,
) -> Result<(), WebViewError> {
Err(WebViewError::WebView(
"cookie store is not implemented for this platform".to_string(),
))
}
async fn clear_cookies(&self) -> Result<(), WebViewError> {
Err(WebViewError::WebView(
"cookie store is not implemented for this platform".to_string(),
))
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ClickOptions {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub index: Option<usize>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TypeOptions {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub index: Option<usize>,
#[serde(default)]
pub replace: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FillOptions {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub index: Option<usize>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PressOptions;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ScrollOptions;
#[async_trait]
pub trait WebViewInputController: WebViewController {
async fn click(
&self,
_selector: &str,
_options: ClickOptions,
) -> Result<(), WebViewInputError> {
Err(WebViewInputError::Unsupported(
"input control is not implemented for this platform",
))
}
async fn type_text(
&self,
_selector: &str,
_text: &str,
_options: TypeOptions,
) -> Result<(), WebViewInputError> {
Err(WebViewInputError::Unsupported(
"input control is not implemented for this platform",
))
}
async fn fill(
&self,
_selector: &str,
_text: &str,
_options: FillOptions,
) -> Result<(), WebViewInputError> {
Err(WebViewInputError::Unsupported(
"input control is not implemented for this platform",
))
}
async fn press(&self, _key: &str, _options: PressOptions) -> Result<(), WebViewInputError> {
Err(WebViewInputError::Unsupported(
"input control is not implemented for this platform",
))
}
async fn scroll(
&self,
_dx: f64,
_dy: f64,
_options: ScrollOptions,
) -> Result<(), WebViewInputError> {
Err(WebViewInputError::Unsupported(
"input control is not implemented for this platform",
))
}
async fn scroll_to(
&self,
_selector: &str,
_options: ScrollOptions,
) -> Result<(), WebViewInputError> {
Err(WebViewInputError::Unsupported(
"input control is not implemented for this platform",
))
}
}
#[derive(Debug, Clone, Copy)]
pub struct LoadDataRequest<'a> {
pub data: &'a str,
pub base_url: &'a str,
pub history_url: Option<&'a str>,
}
impl<'a> LoadDataRequest<'a> {
pub fn new(data: &'a str, base_url: &'a str) -> Self {
Self {
data,
base_url,
history_url: None,
}
}
pub fn with_history_url(mut self, history_url: &'a str) -> Self {
self.history_url = Some(history_url);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoadErrorKind {
Dns,
Network,
Timeout,
Security,
Cancelled,
InvalidUrl,
NotFound,
Unknown,
}
#[derive(Debug, Clone)]
pub struct LoadError {
pub url: Option<String>,
pub kind: LoadErrorKind,
pub description: String,
}
pub trait WebViewDelegate: Send + Sync {
fn on_page_started(&self);
fn on_page_finished(&self);
fn on_load_error(&self, _error: &LoadError) {}
fn handle_post_message(&self, msg: String);
fn log(&self, level: LogLevel, message: &str);
}
#[derive(Debug)]
pub struct WebResourceResponse {
parts: http::response::Parts,
body: WebResourceBody,
}
impl From<Option<WebResourceResponse>> for SchemeOutcome {
fn from(value: Option<WebResourceResponse>) -> Self {
match value {
Some(response) => SchemeOutcome::Handled(response),
None => SchemeOutcome::PassThrough,
}
}
}
impl WebResourceResponse {
pub fn parts(&self) -> &http::response::Parts {
&self.parts
}
pub fn into_parts(self) -> (http::response::Parts, WebResourceBody) {
(self.parts, self.body)
}
}
impl From<(http::response::Parts, PathBuf)> for WebResourceResponse {
fn from(value: (http::response::Parts, PathBuf)) -> Self {
WebResourceResponse {
parts: value.0,
body: WebResourceBody::Path(value.1),
}
}
}
impl From<(http::response::Parts, SystemPipeReader)> for WebResourceResponse {
fn from(value: (http::response::Parts, SystemPipeReader)) -> Self {
WebResourceResponse {
parts: value.0,
body: WebResourceBody::Pipe(value.1),
}
}
}
impl From<(http::response::Parts, Vec<u8>)> for WebResourceResponse {
fn from(value: (http::response::Parts, Vec<u8>)) -> Self {
WebResourceResponse {
parts: value.0,
body: WebResourceBody::Bytes(value.1),
}
}
}
impl WebResourceResponse {
fn response_parts_with_status(status: u16) -> http::response::Parts {
let response = match http::Response::builder().status(status).body(()) {
Ok(response) => response,
Err(_) => http::Response::new(()),
};
let (parts, _) = response.into_parts();
parts
}
pub fn file(path: impl Into<PathBuf>) -> Self {
let path = path.into();
let content_length = std::fs::metadata(&path).ok().map(|m| m.len());
let mut parts = Self::response_parts_with_status(200);
if let Some(len) = content_length {
parts
.headers
.insert(http::header::CONTENT_LENGTH, http::HeaderValue::from(len));
}
Self {
parts,
body: WebResourceBody::Path(path),
}
}
pub fn bytes(data: impl Into<Vec<u8>>) -> Self {
let data = data.into();
let len = data.len();
let mut parts = Self::response_parts_with_status(200);
parts
.headers
.insert(http::header::CONTENT_LENGTH, http::HeaderValue::from(len));
Self {
parts,
body: WebResourceBody::Bytes(data),
}
}
pub fn stream(reader: SystemPipeReader) -> Self {
let parts = Self::response_parts_with_status(200);
Self {
parts,
body: WebResourceBody::Pipe(reader),
}
}
pub fn mime(mut self, content_type: &str) -> Self {
if let Ok(value) = http::HeaderValue::from_str(content_type) {
self.parts.headers.insert(http::header::CONTENT_TYPE, value);
}
self
}
pub fn status(mut self, code: u16) -> Self {
self.parts.status = http::StatusCode::from_u16(code).unwrap_or(self.parts.status);
self
}
pub fn header(mut self, name: &str, value: &str) -> Self {
if let (Ok(header_name), Ok(header_value)) = (
name.parse::<http::header::HeaderName>(),
http::HeaderValue::from_str(value),
) {
self.parts.headers.insert(header_name, header_value);
}
self
}
pub fn cors(self) -> Self {
self.header("access-control-allow-origin", "null")
}
}