use std::collections::HashMap;
#[derive(Debug, Clone)]
pub enum WebViewCommand {
Navigate(String),
GoBack,
GoForward,
Reload,
Stop,
LoadHtml {
html: String,
base_url: Option<String>,
},
ExecuteScript(String),
SetZoom(f64),
SetJavaScriptEnabled(bool),
ClearData(ClearDataOptions),
SetUserAgent(String),
InjectCss(String),
PostMessage(String),
Find {
text: String,
forward: bool,
case_sensitive: bool,
},
ClearFind,
Print,
Screenshot,
}
#[derive(Debug, Clone, Default)]
pub struct ClearDataOptions {
pub cache: bool,
pub cookies: bool,
pub local_storage: bool,
pub session_storage: bool,
pub indexed_db: bool,
}
impl ClearDataOptions {
pub fn all() -> Self {
Self {
cache: true,
cookies: true,
local_storage: true,
session_storage: true,
indexed_db: true,
}
}
pub fn cache_only() -> Self {
Self {
cache: true,
..Default::default()
}
}
}
#[derive(Debug, Clone, Default)]
pub struct WebViewState {
pub url: Option<String>,
pub title: Option<String>,
pub loading: bool,
pub progress: f32,
pub can_go_back: bool,
pub can_go_forward: bool,
pub zoom: f64,
pub javascript_enabled: bool,
pub focused: bool,
pub error: Option<String>,
pub security: SecurityInfo,
}
impl WebViewState {
pub fn new() -> Self {
Self {
zoom: 1.0,
javascript_enabled: true,
security: SecurityInfo::default(),
..Default::default()
}
}
pub fn is_secure(&self) -> bool {
self.url
.as_ref()
.map(|u| u.starts_with("https://"))
.unwrap_or(false)
}
}
#[derive(Debug, Clone, Default)]
pub struct SecurityInfo {
pub secure: bool,
pub certificate: Option<CertificateInfo>,
}
#[derive(Debug, Clone)]
pub struct CertificateInfo {
pub issuer: String,
pub subject: String,
pub valid_from: String,
pub valid_until: String,
}
#[derive(Debug, Clone)]
pub enum WebViewEvent {
NavigationStarted(String),
NavigationCompleted(String),
NavigationFailed { url: String, error: String },
TitleChanged(String),
ProgressChanged(f32),
MessageReceived(String),
ScriptResult { id: String, result: String },
NewWindowRequested(String),
DownloadRequested {
url: String,
suggested_filename: String,
},
PermissionRequested(Permission),
FindResult {
current_match: u32,
total_matches: u32,
},
ScreenshotCaptured(Vec<u8>),
Ready,
FocusChanged(bool),
Error(String),
}
#[derive(Debug, Clone)]
pub enum Permission {
Geolocation,
Microphone,
Camera,
Notifications,
Clipboard,
Other(String),
}
#[derive(Debug, Clone)]
pub struct WebViewConfig {
pub url: Option<String>,
pub html: Option<String>,
pub javascript_enabled: bool,
pub devtools_enabled: bool,
pub user_agent: Option<String>,
pub background_color: Option<(u8, u8, u8, u8)>,
pub allow_file_access: bool,
pub custom_headers: HashMap<String, String>,
pub hardware_acceleration: bool,
pub autoplay_enabled: bool,
}
impl Default for WebViewConfig {
fn default() -> Self {
Self {
url: None,
html: None,
javascript_enabled: true,
devtools_enabled: false,
user_agent: None,
background_color: None,
allow_file_access: false,
custom_headers: HashMap::new(),
hardware_acceleration: true,
autoplay_enabled: false,
}
}
}
impl WebViewConfig {
pub fn with_url(url: impl Into<String>) -> Self {
Self {
url: Some(url.into()),
..Default::default()
}
}
pub fn with_html(html: impl Into<String>) -> Self {
Self {
html: Some(html.into()),
..Default::default()
}
}
pub fn devtools(mut self, enabled: bool) -> Self {
self.devtools_enabled = enabled;
self
}
pub fn user_agent(mut self, agent: impl Into<String>) -> Self {
self.user_agent = Some(agent.into());
self
}
pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.custom_headers.insert(name.into(), value.into());
self
}
}
pub trait WebViewBackend: Send {
fn send(&self, command: WebViewCommand) -> Result<(), WebViewError>;
fn state(&self) -> WebViewState;
fn resize(&self, width: u32, height: u32);
fn handle_permission(&self, permission: Permission, allow: bool);
}
#[derive(Debug, Clone)]
pub enum WebViewError {
InvalidUrl(String),
NavigationFailed(String),
ScriptError(String),
PermissionDenied,
BackendUnavailable,
NotSupported,
Other(String),
}
impl std::fmt::Display for WebViewError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidUrl(url) => write!(f, "Invalid URL: {}", url),
Self::NavigationFailed(msg) => write!(f, "Navigation failed: {}", msg),
Self::ScriptError(msg) => write!(f, "Script error: {}", msg),
Self::PermissionDenied => write!(f, "Permission denied"),
Self::BackendUnavailable => write!(f, "WebView backend unavailable"),
Self::NotSupported => write!(f, "WebView not supported on this platform"),
Self::Other(msg) => write!(f, "WebView error: {}", msg),
}
}
}
impl std::error::Error for WebViewError {}
#[derive(Debug, Default)]
pub struct NoOpWebView {
state: WebViewState,
}
impl NoOpWebView {
pub fn new() -> Self {
Self {
state: WebViewState::new(),
}
}
}
impl WebViewBackend for NoOpWebView {
fn send(&self, _command: WebViewCommand) -> Result<(), WebViewError> {
Ok(())
}
fn state(&self) -> WebViewState {
self.state.clone()
}
fn resize(&self, _width: u32, _height: u32) {}
fn handle_permission(&self, _permission: Permission, _allow: bool) {}
}
pub mod urls {
pub fn is_http(url: &str) -> bool {
url.starts_with("http://") || url.starts_with("https://")
}
pub fn is_file(url: &str) -> bool {
url.starts_with("file://")
}
pub fn is_data(url: &str) -> bool {
url.starts_with("data:")
}
pub fn is_blob(url: &str) -> bool {
url.starts_with("blob:")
}
pub fn domain(url: &str) -> Option<&str> {
let url = url.strip_prefix("https://").or_else(|| url.strip_prefix("http://"))?;
url.split('/').next()
}
}