Skip to main content

tauri_plugin_cef_webview/
registry.rs

1use std::{
2    collections::{HashMap, HashSet},
3    sync::{
4        atomic::{AtomicI32, AtomicU64, Ordering},
5        Arc, Mutex,
6    },
7};
8
9use tauri::{webview::Webview, LogicalPosition, LogicalSize, Rect, Runtime, Url};
10
11use crate::{scripts, Config};
12
13/// Mutable observation of a child webview, updated from Tauri and guest events.
14#[derive(Clone, Debug)]
15pub struct WebviewState {
16    pub url: String,
17    pub title: String,
18    pub loading: bool,
19    pub waiting_for_response: bool,
20    pub can_go_back: bool,
21    pub can_go_forward: bool,
22    pub history_index: i32,
23    pub crashed: bool,
24    pub audio_muted: bool,
25    pub audible: bool,
26    pub user_agent: String,
27    pub zoom_factor: f64,
28    pub zoom_level: f64,
29    pub web_contents_id: i32,
30    pub devtools_focused: bool,
31    pub last_finished_url: String,
32}
33
34impl Default for WebviewState {
35    fn default() -> Self {
36        Self {
37            url: String::new(),
38            title: String::new(),
39            loading: false,
40            waiting_for_response: false,
41            can_go_back: false,
42            can_go_forward: false,
43            history_index: 0,
44            crashed: false,
45            audio_muted: false,
46            audible: false,
47            user_agent: String::new(),
48            zoom_factor: 1.0,
49            zoom_level: 0.0,
50            web_contents_id: 0,
51            devtools_focused: false,
52            last_finished_url: String::new(),
53        }
54    }
55}
56
57/// Shared handle to a webview state, usable from builder callbacks.
58#[derive(Clone, Default)]
59pub(crate) struct SharedState(Arc<Mutex<WebviewState>>);
60
61impl SharedState {
62    pub(crate) fn update(&self, update: impl FnOnce(&mut WebviewState)) {
63        let mut state = self.0.lock().unwrap_or_else(|error| error.into_inner());
64        update(&mut state);
65    }
66
67    pub(crate) fn snapshot(&self) -> WebviewState {
68        self.0
69            .lock()
70            .unwrap_or_else(|error| error.into_inner())
71            .clone()
72    }
73}
74
75/// State managed by the plugin. Holds every child webview created through it.
76pub struct Registry<R: Runtime> {
77    webviews: Mutex<HashMap<String, Webview<R>>>,
78    states: Mutex<HashMap<String, SharedState>>,
79    pending: Mutex<HashSet<String>>,
80    css_sequence: Arc<AtomicU64>,
81    contents_sequence: Arc<AtomicI32>,
82    config: Config,
83}
84
85impl<R: Runtime> Registry<R> {
86    pub(crate) fn new(config: Config) -> Self {
87        Self {
88            webviews: Mutex::new(HashMap::new()),
89            states: Mutex::new(HashMap::new()),
90            pending: Mutex::new(HashSet::new()),
91            css_sequence: Arc::new(AtomicU64::new(0)),
92            contents_sequence: Arc::new(AtomicI32::new(0)),
93            config,
94        }
95    }
96
97    pub(crate) fn config(&self) -> &Config {
98        &self.config
99    }
100
101    pub(crate) fn state(&self, label: &str) -> SharedState {
102        self.states
103            .lock()
104            .unwrap_or_else(|error| error.into_inner())
105            .entry(label.to_string())
106            .or_default()
107            .clone()
108    }
109
110    pub(crate) fn remove_state(&self, label: &str) -> Option<SharedState> {
111        self.states
112            .lock()
113            .unwrap_or_else(|error| error.into_inner())
114            .remove(label)
115    }
116
117    pub(crate) fn next_contents_id(&self) -> i32 {
118        self.contents_sequence.fetch_add(1, Ordering::Relaxed) + 1
119    }
120
121    /// Marks a label as owned by the plugin while its native webview is created.
122    pub(crate) fn mark_child(&self, label: &str) {
123        self.pending
124            .lock()
125            .unwrap_or_else(|error| error.into_inner())
126            .insert(label.to_string());
127    }
128
129    pub(crate) fn unmark_child(&self, label: &str) {
130        self.pending
131            .lock()
132            .unwrap_or_else(|error| error.into_inner())
133            .remove(label);
134    }
135
136    /// Whether `label` belongs to a webview embedded by this plugin.
137    pub fn is_child(&self, label: &str) -> bool {
138        self.pending
139            .lock()
140            .unwrap_or_else(|error| error.into_inner())
141            .contains(label)
142            || self
143                .webviews
144                .lock()
145                .unwrap_or_else(|error| error.into_inner())
146                .contains_key(label)
147    }
148
149    pub(crate) fn insert(&self, label: &str, webview: Webview<R>) {
150        self.webviews
151            .lock()
152            .unwrap_or_else(|error| error.into_inner())
153            .insert(label.to_string(), webview);
154    }
155
156    pub(crate) fn remove(&self, label: &str) -> Option<Webview<R>> {
157        self.webviews
158            .lock()
159            .unwrap_or_else(|error| error.into_inner())
160            .remove(label)
161    }
162
163    /// Returns a handle for the child webview registered with `label`.
164    pub fn get(&self, label: &str) -> Option<CefWebview<R>> {
165        self.webviews
166            .lock()
167            .unwrap_or_else(|error| error.into_inner())
168            .get(label)
169            .cloned()
170            .map(|webview| CefWebview {
171                webview,
172                css_sequence: Arc::clone(&self.css_sequence),
173            })
174    }
175
176    /// Labels of every child webview currently managed by the plugin.
177    pub fn labels(&self) -> Vec<String> {
178        self.webviews
179            .lock()
180            .unwrap_or_else(|error| error.into_inner())
181            .keys()
182            .cloned()
183            .collect()
184    }
185}
186
187/// Handle to a child webview created by the plugin.
188#[derive(Clone)]
189pub struct CefWebview<R: Runtime> {
190    pub(crate) webview: Webview<R>,
191    css_sequence: Arc<AtomicU64>,
192}
193
194impl<R: Runtime> CefWebview<R> {
195    /// Label of the child webview.
196    pub fn label(&self) -> &str {
197        self.webview.label()
198    }
199
200    /// Access the underlying Tauri webview.
201    pub fn inner(&self) -> &Webview<R> {
202        &self.webview
203    }
204
205    /// Evaluates JavaScript in the child webview.
206    pub fn eval(&self, js: impl Into<String>) -> tauri::Result<()> {
207        self.webview.eval(js)
208    }
209
210    /// Injects a stylesheet into the child webview and returns its key.
211    pub fn insert_css(&self, css: &str) -> tauri::Result<String> {
212        let key = format!(
213            "cwv-css-{}",
214            self.css_sequence.fetch_add(1, Ordering::Relaxed)
215        );
216        self.webview.eval(scripts::insert_style(&key, css))?;
217        Ok(key)
218    }
219
220    /// Removes a stylesheet previously inserted with [`Self::insert_css`].
221    pub fn remove_inserted_css(&self, key: &str) -> tauri::Result<()> {
222        self.webview.eval(scripts::remove_style(key))
223    }
224
225    /// Navigates the child webview to `url`.
226    pub fn navigate(&self, url: &str) -> Result<(), String> {
227        let url = Url::parse(url).map_err(|error| error.to_string())?;
228        self.webview
229            .navigate(url)
230            .map_err(|error| error.to_string())
231    }
232
233    /// Reloads the child webview.
234    pub fn reload(&self) -> tauri::Result<()> {
235        self.webview.reload()
236    }
237
238    /// Opens the child webview devtools (debug builds only).
239    #[cfg(any(debug_assertions, feature = "devtools"))]
240    pub fn open_devtools(&self) {
241        self.webview.open_devtools();
242    }
243
244    /// No-op when devtools support is not compiled in.
245    #[cfg(not(any(debug_assertions, feature = "devtools")))]
246    pub fn open_devtools(&self) {}
247
248    /// Moves and resizes the child webview. Values are logical pixels.
249    pub fn set_rect(&self, x: f64, y: f64, width: f64, height: f64) -> tauri::Result<()> {
250        self.webview.set_bounds(Rect {
251            position: LogicalPosition::new(x, y).into(),
252            size: LogicalSize::new(width, height).into(),
253        })
254    }
255
256    /// Shows or hides the child webview.
257    pub fn set_visible(&self, visible: bool) -> tauri::Result<()> {
258        if visible {
259            self.webview.show()
260        } else {
261            self.webview.hide()
262        }
263    }
264
265    /// Closes the child webview.
266    pub fn close(&self) -> tauri::Result<()> {
267        self.webview.close()
268    }
269}