Skip to main content

lingxia_browser/
types.rs

1//! Public data types for the browser runtime: address-bar requests/responses,
2//! navigation policy types, tab info, automation element/wait types, and errors.
3
4use 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    /// Session-history availability of the tab's webview; drives smart
135    /// back/forward affordances in shell chrome.
136    #[serde(default)]
137    pub can_go_back: bool,
138    #[serde(default)]
139    pub can_go_forward: bool,
140}
141
142/// Opaque identity of a newly requested trusted control-page navigation.
143///
144/// Document authority is intentionally absent: only the native navigation
145/// commit can mint and activate it.
146#[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}