Skip to main content

victauri_core/
snapshot.rs

1use serde::{Deserialize, Serialize};
2
3/// Current state of a Tauri window including geometry, visibility, and loaded URL.
4#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
5pub struct WindowState {
6    /// Tauri window label (e.g. "main", "notification").
7    pub label: String,
8    /// Window title bar text.
9    pub title: String,
10    /// URL currently loaded in the webview.
11    pub url: String,
12    /// Whether the window is visible on screen.
13    pub visible: bool,
14    /// Whether the window currently has input focus.
15    pub focused: bool,
16    /// Whether the window is maximized.
17    pub maximized: bool,
18    /// Whether the window is minimized.
19    pub minimized: bool,
20    /// Whether the window is in fullscreen mode.
21    pub fullscreen: bool,
22    /// Window position as (x, y) in screen coordinates.
23    pub position: (i32, i32),
24    /// Window dimensions as (width, height) in pixels.
25    pub size: (u32, u32),
26}
27
28/// A point-in-time snapshot of the DOM accessible tree from a specific webview.
29#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
30pub struct DomSnapshot {
31    /// Label of the webview this snapshot was taken from.
32    pub webview_label: String,
33    /// Top-level accessible elements in the DOM tree.
34    pub elements: Vec<DomElement>,
35    /// Maps ref IDs to CSS selectors for element lookup.
36    pub ref_map: std::collections::HashMap<String, String>,
37}
38
39/// A single element in the accessible DOM tree with semantic metadata and ref handle.
40#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
41pub struct DomElement {
42    /// Unique ref handle for this element (e.g. "e3"), used to target interactions.
43    pub ref_id: String,
44    /// HTML tag name (e.g. "div", "button").
45    pub tag: String,
46    /// ARIA role if present (e.g. "button", "navigation").
47    pub role: Option<String>,
48    /// Accessible name derived from aria-label, text content, or other heuristics.
49    pub name: Option<String>,
50    /// Visible text content of the element.
51    pub text: Option<String>,
52    /// Form input value, if applicable.
53    pub value: Option<String>,
54    /// Whether the element is interactive (not disabled).
55    pub enabled: bool,
56    /// Whether the element is visible in the viewport.
57    pub visible: bool,
58    /// Whether the element can receive keyboard focus.
59    pub focusable: bool,
60    /// Pixel-level bounding rectangle, if available.
61    pub bounds: Option<ElementBounds>,
62    /// Nested child elements forming the accessible subtree.
63    pub children: Vec<DomElement>,
64    /// Raw HTML attributes on the element.
65    pub attributes: std::collections::HashMap<String, String>,
66}
67
68/// Pixel-level bounding rectangle of a DOM element relative to the viewport.
69#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
70pub struct ElementBounds {
71    /// Left edge offset from viewport origin.
72    pub x: f64,
73    /// Top edge offset from viewport origin.
74    pub y: f64,
75    /// Element width in CSS pixels.
76    pub width: f64,
77    /// Element height in CSS pixels.
78    pub height: f64,
79}
80
81impl DomSnapshot {
82    /// Renders the snapshot as indented accessible text (roles, names, and ref handles).
83    pub fn to_accessible_text(&self, indent: usize) -> String {
84        let mut output = String::new();
85        for element in &self.elements {
86            Self::format_element(&mut output, element, indent);
87        }
88        output
89    }
90
91    fn format_element(output: &mut String, element: &DomElement, indent: usize) {
92        if !element.visible {
93            return;
94        }
95
96        let prefix = "  ".repeat(indent);
97
98        let role_str = element.role.as_deref().unwrap_or(&element.tag);
99        let name_str = element
100            .name
101            .as_ref()
102            .map(|n| format!(" \"{}\"", n))
103            .unwrap_or_default();
104        let ref_str = if element.focusable || element.tag == "button" || element.tag == "input" {
105            format!(" [ref={}]", element.ref_id)
106        } else {
107            String::new()
108        };
109
110        let line = format!("{}- {}{}{}\n", prefix, role_str, name_str, ref_str);
111        output.push_str(&line);
112
113        for child in &element.children {
114            Self::format_element(output, child, indent + 1);
115        }
116    }
117}