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