pytauri_core/ext_mod_impl/
webview.rs

1use pyo3::{prelude::*, types::PyString};
2use pyo3_utils::py_wrapper::{PyWrapper, PyWrapperT0};
3use tauri::webview;
4
5use crate::{
6    ext_mod::{
7        image::Image,
8        menu::{context_menu_impl, ImplContextMenu, Menu, MenuEvent},
9        window::Window,
10        Position, Theme, Url, WebviewEvent, WindowEvent,
11    },
12    tauri_runtime::Runtime,
13    utils::{delegate_inner, PyResultExt as _},
14};
15
16pub(crate) type TauriWebviewWindow = webview::WebviewWindow<Runtime>;
17type TauriWebview = webview::Webview<Runtime>;
18
19/// see also: [tauri::webview::WebviewWindow]
20#[pyclass(frozen)]
21#[non_exhaustive]
22pub struct WebviewWindow(pub PyWrapper<PyWrapperT0<TauriWebviewWindow>>);
23
24impl WebviewWindow {
25    pub(crate) fn new(webview_window: TauriWebviewWindow) -> Self {
26        Self(PyWrapper::new0(webview_window))
27    }
28}
29
30#[pymethods]
31impl WebviewWindow {
32    fn run_on_main_thread(&self, py: Python<'_>, handler: PyObject) -> PyResult<()> {
33        py.allow_threads(|| {
34            delegate_inner!(self, run_on_main_thread, move || {
35                Python::with_gil(|py| {
36                    let handler = handler.bind(py);
37                    let result = handler.call0();
38                    result.unwrap_unraisable_py_result(py, Some(handler), || {
39                        "Python exception occurred in `WebviewWindow::run_on_main_thread`"
40                    });
41                })
42            })
43        })
44    }
45
46    fn label<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
47        let webview_window = self.0.inner_ref();
48        // if `label` is immutable, we can intern it to save memory.
49        PyString::intern(py, webview_window.label())
50    }
51
52    fn on_window_event(&self, py: Python<'_>, handler: PyObject) {
53        py.allow_threads(|| {
54            self.0.inner_ref().on_window_event(move |window_event| {
55                Python::with_gil(|py| {
56                    let window_event: WindowEvent = WindowEvent::from_tauri(py, window_event)
57                        // TODO: maybe we should only `write_unraisable` and log it instead of `panic` here?
58                        .expect("Failed to convert `WindowEvent` to pyobject");
59
60                    let handler = handler.bind(py);
61                    let result = handler.call1((window_event,));
62                    result.unwrap_unraisable_py_result(py, Some(handler), || {
63                        "Python exception occurred in `WebviewWindow::on_window_event` handler"
64                    });
65                })
66            })
67        })
68    }
69
70    fn on_menu_event(slf: Py<Self>, py: Python<'_>, handler: PyObject) {
71        let moved_slf = slf.clone_ref(py);
72        py.allow_threads(|| {
73            slf.get()
74                .0
75                .inner_ref()
76                .on_menu_event(move |_window, menu_event| {
77                    Python::with_gil(|py| {
78                        // See: <https://github.com/tauri-apps/tauri/blob/8e9339e8807338597132ffd8688fb9da00f4102b/crates/tauri/src/app.rs#L2168-L2184>,
79                        // The `window` argument is always the `WebviewWindow` instance that calls this method,
80                        // so we can directly use the same PyObject.
81                        let window: &Py<Self> = &moved_slf; // TODO, XXX, FIXME: return `Window` instead of `WebviewWindow`?
82                        debug_assert_eq!(
83                            &*window.get().0.inner_ref().as_ref().window_ref(),
84                            _window
85                        );
86                        let menu_event: Bound<'_, MenuEvent> =
87                            MenuEvent::intern(py, &menu_event.id.0);
88
89                        let handler = handler.bind(py);
90                        let result = handler.call1((window, menu_event));
91                        result.unwrap_unraisable_py_result(py, Some(handler), || {
92                            "Python exception occurred in `WebviewWindow::on_menu_event` handler"
93                        });
94                    })
95                })
96        })
97    }
98
99    fn menu(&self, py: Python<'_>) -> Option<Menu> {
100        py.allow_threads(|| self.0.inner_ref().menu().map(Menu::new))
101    }
102
103    fn set_menu(&self, py: Python<'_>, menu: Py<Menu>) -> PyResult<Option<Menu>> {
104        py.allow_threads(|| {
105            let menu = menu.get().0.inner_ref().clone();
106            let returned_menu = delegate_inner!(self, set_menu, menu)?;
107            PyResult::Ok(returned_menu.map(Menu::new))
108        })
109    }
110
111    fn remove_menu(&self, py: Python<'_>) -> PyResult<Option<Menu>> {
112        py.allow_threads(|| {
113            let returned_menu = delegate_inner!(self, remove_menu,)?;
114            PyResult::Ok(returned_menu.map(Menu::new))
115        })
116    }
117
118    fn hide_menu(&self, py: Python<'_>) -> PyResult<()> {
119        py.allow_threads(|| delegate_inner!(self, hide_menu,))
120    }
121
122    fn show_menu(&self, py: Python<'_>) -> PyResult<()> {
123        py.allow_threads(|| delegate_inner!(self, show_menu,))
124    }
125
126    fn is_menu_visible(&self, py: Python<'_>) -> PyResult<bool> {
127        py.allow_threads(|| delegate_inner!(self, is_menu_visible,))
128    }
129
130    fn popup_menu(&self, py: Python<'_>, menu: ImplContextMenu) -> PyResult<()> {
131        py.allow_threads(|| {
132            context_menu_impl!(&menu, |menu| delegate_inner!(self, popup_menu, menu))
133        })
134    }
135
136    fn popup_menu_at(
137        &self,
138        py: Python<'_>,
139        menu: ImplContextMenu,
140        position: Py<Position>,
141    ) -> PyResult<()> {
142        let position = position.get().to_tauri(py)?;
143        py.allow_threads(|| {
144            context_menu_impl!(&menu, |menu| delegate_inner!(
145                self,
146                popup_menu_at,
147                menu,
148                position
149            ))
150        })
151    }
152
153    fn is_fullscreen(&self, py: Python<'_>) -> PyResult<bool> {
154        py.allow_threads(|| delegate_inner!(self, is_fullscreen,))
155    }
156
157    fn is_minimized(&self, py: Python<'_>) -> PyResult<bool> {
158        py.allow_threads(|| delegate_inner!(self, is_minimized,))
159    }
160
161    fn is_maximized(&self, py: Python<'_>) -> PyResult<bool> {
162        py.allow_threads(|| delegate_inner!(self, is_maximized,))
163    }
164
165    fn is_focused(&self, py: Python<'_>) -> PyResult<bool> {
166        py.allow_threads(|| delegate_inner!(self, is_focused,))
167    }
168
169    fn is_decorated(&self, py: Python<'_>) -> PyResult<bool> {
170        py.allow_threads(|| delegate_inner!(self, is_decorated,))
171    }
172
173    fn is_resizable(&self, py: Python<'_>) -> PyResult<bool> {
174        py.allow_threads(|| delegate_inner!(self, is_resizable,))
175    }
176
177    fn is_enabled(&self, py: Python<'_>) -> PyResult<bool> {
178        py.allow_threads(|| delegate_inner!(self, is_enabled,))
179    }
180
181    fn is_maximizable(&self, py: Python<'_>) -> PyResult<bool> {
182        py.allow_threads(|| delegate_inner!(self, is_maximizable,))
183    }
184
185    fn is_minimizable(&self, py: Python<'_>) -> PyResult<bool> {
186        py.allow_threads(|| delegate_inner!(self, is_minimizable,))
187    }
188
189    fn is_closable(&self, py: Python<'_>) -> PyResult<bool> {
190        py.allow_threads(|| delegate_inner!(self, is_closable,))
191    }
192
193    fn is_visible(&self, py: Python<'_>) -> PyResult<bool> {
194        py.allow_threads(|| delegate_inner!(self, is_visible,))
195    }
196
197    fn title(&self, py: Python<'_>) -> PyResult<String> {
198        py.allow_threads(|| delegate_inner!(self, title,))
199    }
200
201    fn theme(&self, py: Python<'_>) -> PyResult<Theme> {
202        py.allow_threads(|| delegate_inner!(self, theme,).map(Into::into))
203    }
204
205    fn center(&self, py: Python<'_>) -> PyResult<()> {
206        py.allow_threads(|| delegate_inner!(self, center,))
207    }
208
209    fn set_resizable(&self, py: Python<'_>, resizable: bool) -> PyResult<()> {
210        py.allow_threads(|| delegate_inner!(self, set_resizable, resizable))
211    }
212
213    fn set_enabled(&self, py: Python<'_>, enabled: bool) -> PyResult<()> {
214        py.allow_threads(|| delegate_inner!(self, set_enabled, enabled))
215    }
216
217    fn set_maximizable(&self, py: Python<'_>, maximizable: bool) -> PyResult<()> {
218        py.allow_threads(|| delegate_inner!(self, set_maximizable, maximizable))
219    }
220
221    fn set_minimizable(&self, py: Python<'_>, minimizable: bool) -> PyResult<()> {
222        py.allow_threads(|| delegate_inner!(self, set_minimizable, minimizable))
223    }
224
225    fn set_closable(&self, py: Python<'_>, closable: bool) -> PyResult<()> {
226        py.allow_threads(|| delegate_inner!(self, set_closable, closable))
227    }
228
229    fn set_title(&self, py: Python<'_>, title: &str) -> PyResult<()> {
230        py.allow_threads(|| delegate_inner!(self, set_title, title))
231    }
232
233    fn maximize(&self, py: Python<'_>) -> PyResult<()> {
234        py.allow_threads(|| delegate_inner!(self, maximize,))
235    }
236
237    fn unmaximize(&self, py: Python<'_>) -> PyResult<()> {
238        py.allow_threads(|| delegate_inner!(self, unmaximize,))
239    }
240
241    fn minimize(&self, py: Python<'_>) -> PyResult<()> {
242        py.allow_threads(|| delegate_inner!(self, minimize,))
243    }
244
245    fn unminimize(&self, py: Python<'_>) -> PyResult<()> {
246        py.allow_threads(|| delegate_inner!(self, unminimize,))
247    }
248
249    fn show(&self, py: Python<'_>) -> PyResult<()> {
250        py.allow_threads(|| delegate_inner!(self, show,))
251    }
252
253    fn hide(&self, py: Python<'_>) -> PyResult<()> {
254        py.allow_threads(|| delegate_inner!(self, hide,))
255    }
256
257    fn close(&self, py: Python<'_>) -> PyResult<()> {
258        py.allow_threads(|| delegate_inner!(self, close,))
259    }
260
261    fn destroy(&self, py: Python<'_>) -> PyResult<()> {
262        py.allow_threads(|| delegate_inner!(self, destroy,))
263    }
264
265    fn set_decorations(&self, py: Python<'_>, decorations: bool) -> PyResult<()> {
266        py.allow_threads(|| delegate_inner!(self, set_decorations, decorations))
267    }
268
269    fn set_shadow(&self, py: Python<'_>, shadow: bool) -> PyResult<()> {
270        py.allow_threads(|| delegate_inner!(self, set_shadow, shadow))
271    }
272
273    fn set_always_on_bottom(&self, py: Python<'_>, always_on_bottom: bool) -> PyResult<()> {
274        py.allow_threads(|| delegate_inner!(self, set_always_on_bottom, always_on_bottom))
275    }
276
277    fn set_always_on_top(&self, py: Python<'_>, always_on_top: bool) -> PyResult<()> {
278        py.allow_threads(|| delegate_inner!(self, set_always_on_top, always_on_top))
279    }
280
281    fn set_visible_on_all_workspaces(
282        &self,
283        py: Python<'_>,
284        visible_on_all_workspaces: bool,
285    ) -> PyResult<()> {
286        py.allow_threads(|| {
287            delegate_inner!(
288                self,
289                set_visible_on_all_workspaces,
290                visible_on_all_workspaces
291            )
292        })
293    }
294
295    fn set_content_protected(&self, py: Python<'_>, protected: bool) -> PyResult<()> {
296        py.allow_threads(|| delegate_inner!(self, set_content_protected, protected))
297    }
298
299    fn set_fullscreen(&self, py: Python<'_>, fullscreen: bool) -> PyResult<()> {
300        py.allow_threads(|| delegate_inner!(self, set_fullscreen, fullscreen))
301    }
302
303    fn set_focus(&self, py: Python<'_>) -> PyResult<()> {
304        py.allow_threads(|| delegate_inner!(self, set_focus,))
305    }
306
307    fn set_icon(&self, py: Python<'_>, icon: Py<Image>) -> PyResult<()> {
308        let icon = icon.get().to_tauri(py);
309        py.allow_threads(|| delegate_inner!(self, set_icon, icon))
310    }
311
312    fn set_skip_taskbar(&self, py: Python<'_>, skip: bool) -> PyResult<()> {
313        py.allow_threads(|| delegate_inner!(self, set_skip_taskbar, skip))
314    }
315
316    fn set_cursor_grab(&self, py: Python<'_>, grab: bool) -> PyResult<()> {
317        py.allow_threads(|| delegate_inner!(self, set_cursor_grab, grab))
318    }
319
320    fn set_cursor_visible(&self, py: Python<'_>, visible: bool) -> PyResult<()> {
321        py.allow_threads(|| delegate_inner!(self, set_cursor_visible, visible))
322    }
323
324    fn set_ignore_cursor_events(&self, py: Python<'_>, ignore: bool) -> PyResult<()> {
325        py.allow_threads(|| delegate_inner!(self, set_ignore_cursor_events, ignore))
326    }
327
328    fn start_dragging(&self, py: Python<'_>) -> PyResult<()> {
329        py.allow_threads(|| delegate_inner!(self, start_dragging,))
330    }
331
332    #[pyo3(signature = (count))]
333    fn set_badge_count(&self, py: Python<'_>, count: Option<i64>) -> PyResult<()> {
334        py.allow_threads(|| delegate_inner!(self, set_badge_count, count))
335    }
336
337    fn set_theme(&self, py: Python<'_>, theme: Option<Theme>) -> PyResult<()> {
338        py.allow_threads(|| delegate_inner!(self, set_theme, theme.map(Into::into)))
339    }
340
341    fn print(&self, py: Python<'_>) -> PyResult<()> {
342        py.allow_threads(|| delegate_inner!(self, print,))
343    }
344
345    fn url(&self, py: Python<'_>) -> PyResult<Url> {
346        let url = py.allow_threads(|| delegate_inner!(self, url,))?;
347        Ok(url.into())
348    }
349
350    fn navigate(&self, py: Python<'_>, url: Url<'_>) -> PyResult<()> {
351        py.allow_threads(|| delegate_inner!(self, navigate, url.into()))
352    }
353
354    fn eval(&self, py: Python<'_>, js: &str) -> PyResult<()> {
355        py.allow_threads(|| delegate_inner!(self, eval, js))
356    }
357
358    fn set_zoom(&self, py: Python<'_>, scale_factor: f64) -> PyResult<()> {
359        py.allow_threads(|| delegate_inner!(self, set_zoom, scale_factor))
360    }
361
362    fn clear_all_browsing_data(&self, py: Python<'_>) -> PyResult<()> {
363        py.allow_threads(|| delegate_inner!(self, clear_all_browsing_data,))
364    }
365
366    /// see also: [tauri::webview::WebviewWindow::as_ref]
367    fn as_ref_webview(&self) -> Webview {
368        let webview = self.0.inner_ref().as_ref().clone();
369        Webview::new(webview)
370    }
371
372    /// TODO: This method only exists in [tauri::webview::Webview],
373    /// we should submit a PR to synchronize it to [tauri::webview::WebviewWindow].
374    fn on_webview_event(&self, py: Python<'_>, handler: PyObject) {
375        py.allow_threads(|| {
376            self.0
377                .inner_ref()
378                .as_ref()
379                .on_webview_event(move |webview_event| {
380                    Python::with_gil(|py| {
381                        let webview_event: WebviewEvent =
382                            WebviewEvent::from_tauri(py, webview_event)
383                                // TODO: maybe we should only `write_unraisable` and log it instead of `panic` here?
384                                .expect("Failed to convert `WebviewEvent` to pyobject");
385
386                        let handler = handler.bind(py);
387                        let result = handler.call1((webview_event,));
388                        result.unwrap_unraisable_py_result(py, Some(handler), || {
389                            "Python exception occurred in `WebviewWindow::on_webview_event` handler"
390                        });
391                    })
392                })
393        })
394    }
395}
396
397/// see also: [tauri::webview::Webview]
398#[pyclass(frozen)]
399#[non_exhaustive]
400pub struct Webview(pub PyWrapper<PyWrapperT0<TauriWebview>>);
401
402impl Webview {
403    pub(crate) fn new(webview: TauriWebview) -> Self {
404        Self(PyWrapper::new0(webview))
405    }
406}
407
408#[pymethods]
409impl Webview {
410    fn window(&self) -> Window {
411        let window = self.0.inner_ref().window();
412        Window::new(window)
413    }
414}