1use lingxia_webview::{WebViewError, WebViewInputError, WebViewScriptError};
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
8#[serde(rename_all = "snake_case")]
9pub enum BrowserAddressInputTrigger {
10 Edit,
11 #[default]
12 Submit,
13}
14
15#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
16#[serde(rename_all = "snake_case")]
17pub enum BrowserAddressAction {
18 Navigate,
19 Suggest,
20 Reject,
21}
22
23#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
24#[serde(rename_all = "snake_case")]
25pub enum BrowserAddressValueKind {
26 Empty,
27 Url,
28 SearchQuery,
29 Invalid,
30}
31
32#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
33#[serde(rename_all = "snake_case")]
34pub enum BrowserNavigationTarget {
35 CurrentTab,
36 NewTab,
37}
38
39#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
40#[serde(rename_all = "snake_case")]
41pub enum BrowserNavigationPolicyDecision {
42 InWebview,
43 OpenExternal,
44 Deny,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct BrowserNavigationPolicyRequest {
49 pub raw_url: String,
50 #[serde(default)]
51 pub has_user_gesture: bool,
52 #[serde(default = "default_true")]
53 pub is_main_frame: bool,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct BrowserNavigationPolicyResponse {
58 pub decision: BrowserNavigationPolicyDecision,
59 #[serde(default, skip_serializing_if = "Option::is_none")]
60 pub reason: Option<String>,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize, Default)]
64pub struct BrowserAddressInputContext {
65 #[serde(default)]
66 pub preferred_scheme: Option<String>,
67 #[serde(default)]
68 pub current_url: Option<String>,
69 #[serde(default)]
70 pub tab_id: Option<String>,
71 #[serde(default)]
72 pub allow_search_fallback: bool,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct BrowserAddressInputRequest {
77 pub raw_input: String,
78 #[serde(default)]
79 pub trigger: BrowserAddressInputTrigger,
80 #[serde(default)]
81 pub context: BrowserAddressInputContext,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct BrowserAddressState {
86 pub raw_input: String,
87 pub normalized_input: String,
88 pub display_text: String,
89 pub value_kind: BrowserAddressValueKind,
90 pub canonical_url: Option<String>,
91 pub inferred_scheme: Option<String>,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct BrowserAddressNavigation {
96 pub url: String,
97 pub target: BrowserNavigationTarget,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct BrowserAddressSuggestion {
102 pub kind: String,
103 pub title: String,
104 pub subtitle: Option<String>,
105 pub fill_text: String,
106 pub navigation: Option<BrowserAddressNavigation>,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct BrowserAddressInputError {
111 pub code: String,
112 pub message: String,
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct BrowserAddressInputResponse {
117 pub action: BrowserAddressAction,
118 pub state: BrowserAddressState,
119 pub navigation: Option<BrowserAddressNavigation>,
120 #[serde(default, skip_serializing_if = "Option::is_none")]
121 pub suggestions: Option<Vec<BrowserAddressSuggestion>>,
122 pub error: Option<BrowserAddressInputError>,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct BrowserTabInfo {
127 pub tab_id: String,
128 pub path: String,
129 pub session_id: u64,
130 #[serde(default, skip_serializing_if = "Option::is_none")]
131 pub current_url: Option<String>,
132 #[serde(default, skip_serializing_if = "Option::is_none")]
133 pub title: Option<String>,
134 #[serde(default)]
137 pub can_go_back: bool,
138 #[serde(default)]
139 pub can_go_forward: bool,
140}
141
142#[derive(Debug, Clone, PartialEq, Eq)]
147pub struct TrustedControlPageNavigation {
148 pub tab_id: String,
149 pub browser_session_id: u64,
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct BrowserRect {
154 pub left: f64,
155 pub top: f64,
156 pub width: f64,
157 pub height: f64,
158 pub right: f64,
159 pub bottom: f64,
160 pub center_x: f64,
161 pub center_y: f64,
162 pub viewport_width: f64,
163 pub viewport_height: f64,
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct BrowserElementInfo {
168 pub exists: bool,
169 pub visible: bool,
170 pub enabled: bool,
171 pub editable: bool,
172 #[serde(default, skip_serializing_if = "Option::is_none")]
173 pub text: Option<String>,
174 #[serde(default, skip_serializing_if = "is_false")]
175 pub text_truncated: bool,
176 #[serde(default, skip_serializing_if = "Option::is_none")]
177 pub value: Option<String>,
178 #[serde(default, skip_serializing_if = "is_false")]
179 pub value_truncated: bool,
180 #[serde(default, skip_serializing_if = "Option::is_none")]
181 pub rect: Option<BrowserRect>,
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize)]
185#[serde(tag = "kind", rename_all = "snake_case")]
186pub enum BrowserWaitCondition {
187 Loaded,
188 SelectorExists {
189 selector: String,
190 },
191 SelectorVisible {
192 selector: String,
193 },
194 SelectorHidden {
195 selector: String,
196 },
197 SelectorEditable {
198 selector: String,
199 },
200 JsTrue {
201 js: String,
202 },
203 UrlEquals {
204 url: String,
205 },
206 UrlContains {
207 text: String,
208 },
209 Navigation {
210 #[serde(default, skip_serializing_if = "Option::is_none")]
211 initial_url: Option<String>,
212 #[serde(default)]
213 wait_until_complete: bool,
214 },
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct BrowserWaitResult {
219 pub elapsed_ms: u64,
220 #[serde(default, skip_serializing_if = "Option::is_none")]
221 pub current_url: Option<String>,
222 #[serde(default, skip_serializing_if = "Option::is_none")]
223 pub element: Option<BrowserElementInfo>,
224 #[serde(default, skip_serializing_if = "Option::is_none")]
225 pub value: Option<serde_json::Value>,
226}
227
228pub trait BrowserNativeInputHost: Send + Sync {
229 fn prepare_for_input(&self, tab_id: &str) -> Result<(), String>;
230}
231
232#[derive(Debug, thiserror::Error)]
233pub enum BrowserAutomationError {
234 #[error("browser tab not found: {0}")]
235 TabNotFound(String),
236 #[error("browser tab webview not found: {0}")]
237 WebViewNotFound(String),
238 #[error(transparent)]
239 Script(#[from] WebViewScriptError),
240 #[error(transparent)]
241 Input(#[from] WebViewInputError),
242 #[error(transparent)]
243 WebView(#[from] WebViewError),
244 #[error("native input host is not registered")]
245 NativeInputHostMissing,
246 #[error("native input error: {0}")]
247 NativeInput(String),
248 #[error("timed out after {timeout_ms}ms waiting for {condition}")]
249 WaitTimeout { condition: String, timeout_ms: u64 },
250}
251
252fn default_true() -> bool {
253 true
254}
255
256fn is_false(value: &bool) -> bool {
257 !*value
258}