use lingxia_webview::{WebViewError, WebViewInputError, WebViewScriptError};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum BrowserAddressInputTrigger {
Edit,
#[default]
Submit,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum BrowserAddressAction {
Navigate,
Suggest,
Reject,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum BrowserAddressValueKind {
Empty,
Url,
SearchQuery,
Invalid,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum BrowserNavigationTarget {
CurrentTab,
NewTab,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum BrowserNavigationPolicyDecision {
InWebview,
OpenExternal,
Deny,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BrowserNavigationPolicyRequest {
pub raw_url: String,
#[serde(default)]
pub has_user_gesture: bool,
#[serde(default = "default_true")]
pub is_main_frame: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BrowserNavigationPolicyResponse {
pub decision: BrowserNavigationPolicyDecision,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct BrowserAddressInputContext {
#[serde(default)]
pub preferred_scheme: Option<String>,
#[serde(default)]
pub current_url: Option<String>,
#[serde(default)]
pub tab_id: Option<String>,
#[serde(default)]
pub allow_search_fallback: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BrowserAddressInputRequest {
pub raw_input: String,
#[serde(default)]
pub trigger: BrowserAddressInputTrigger,
#[serde(default)]
pub context: BrowserAddressInputContext,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BrowserAddressState {
pub raw_input: String,
pub normalized_input: String,
pub display_text: String,
pub value_kind: BrowserAddressValueKind,
pub canonical_url: Option<String>,
pub inferred_scheme: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BrowserAddressNavigation {
pub url: String,
pub target: BrowserNavigationTarget,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BrowserAddressSuggestion {
pub kind: String,
pub title: String,
pub subtitle: Option<String>,
pub fill_text: String,
pub navigation: Option<BrowserAddressNavigation>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BrowserAddressInputError {
pub code: String,
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BrowserAddressInputResponse {
pub action: BrowserAddressAction,
pub state: BrowserAddressState,
pub navigation: Option<BrowserAddressNavigation>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub suggestions: Option<Vec<BrowserAddressSuggestion>>,
pub error: Option<BrowserAddressInputError>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BrowserTabInfo {
pub tab_id: String,
pub path: String,
pub session_id: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub current_url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BrowserRect {
pub left: f64,
pub top: f64,
pub width: f64,
pub height: f64,
pub right: f64,
pub bottom: f64,
pub center_x: f64,
pub center_y: f64,
pub viewport_width: f64,
pub viewport_height: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BrowserElementInfo {
pub exists: bool,
pub visible: bool,
pub enabled: bool,
pub editable: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(default, skip_serializing_if = "is_false")]
pub text_truncated: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub value: Option<String>,
#[serde(default, skip_serializing_if = "is_false")]
pub value_truncated: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rect: Option<BrowserRect>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum BrowserWaitCondition {
Loaded,
SelectorExists {
selector: String,
},
SelectorVisible {
selector: String,
},
SelectorHidden {
selector: String,
},
SelectorEditable {
selector: String,
},
JsTrue {
js: String,
},
UrlEquals {
url: String,
},
UrlContains {
text: String,
},
Navigation {
#[serde(default, skip_serializing_if = "Option::is_none")]
initial_url: Option<String>,
#[serde(default)]
wait_until_complete: bool,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BrowserWaitResult {
pub elapsed_ms: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub current_url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub element: Option<BrowserElementInfo>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub value: Option<serde_json::Value>,
}
pub trait BrowserNativeInputHost: Send + Sync {
fn prepare_for_input(&self, tab_id: &str) -> Result<(), String>;
}
#[derive(Debug, thiserror::Error)]
pub enum BrowserAutomationError {
#[error("browser tab not found: {0}")]
TabNotFound(String),
#[error("browser tab webview not found: {0}")]
WebViewNotFound(String),
#[error(transparent)]
Script(#[from] WebViewScriptError),
#[error(transparent)]
Input(#[from] WebViewInputError),
#[error(transparent)]
WebView(#[from] WebViewError),
#[error("native input host is not registered")]
NativeInputHostMissing,
#[error("native input error: {0}")]
NativeInput(String),
#[error("timed out after {timeout_ms}ms waiting for {condition}")]
WaitTimeout { condition: String, timeout_ms: u64 },
}
fn default_true() -> bool {
true
}
fn is_false(value: &bool) -> bool {
!*value
}