pytauri_core/ext_mod_impl/
webview.rs

1use std::{borrow::Cow, path::PathBuf};
2
3use pyo3::{
4    prelude::*,
5    pybacked::PyBackedStr,
6    types::{PyDict, PyString},
7};
8use pyo3_utils::{
9    from_py_dict::{derive_from_py_dict, FromPyDict as _, NotRequired},
10    py_wrapper::{PyWrapper, PyWrapperT0},
11    serde::PySerde,
12    ungil::UnsafeUngilExt,
13};
14use tauri::webview::{
15    self,
16    cookie::{self, time::OffsetDateTime},
17};
18
19use crate::{
20    ext_mod::{
21        image::Image,
22        manager_method_impl,
23        menu::{context_menu_impl, ImplContextMenu, Menu, MenuEvent},
24        window::{Effects, Monitor, ProgressBarState, TitleBarStyle, Window},
25        CursorIcon, ImplManager, PhysicalPositionF64, PhysicalPositionI32, PhysicalSizeU32,
26        Position, Size, Theme, Url, UserAttentionType, WebviewEvent, WebviewUrl, WindowEvent,
27    },
28    tauri_runtime::Runtime,
29    utils::{cfg_impl, delegate_inner, PyResultExt as _, TauriError},
30};
31
32pub(crate) type TauriWebviewWindow = webview::WebviewWindow<Runtime>;
33type TauriWebview = webview::Webview<Runtime>;
34
35/// See also: [tauri::utils::config::WindowConfig]
36// TODO, PERF: use `&Config` to avoid clone,
37// see [<PySerde as FromPyObject>::extract_bound] comment.
38pub(crate) type WindowConfigFrom = PySerde<tauri::utils::config::WindowConfig>;
39/// See also: [tauri::utils::config::WindowConfig]
40#[expect(dead_code)] // TODO
41pub(crate) type WindowConfigInto<'a> = PySerde<Cow<'a, tauri::utils::config::WindowConfig>>;
42
43/// See also: [tauri::webview::WebviewWindow]
44#[pyclass(frozen)]
45#[non_exhaustive]
46pub struct WebviewWindow(pub PyWrapper<PyWrapperT0<TauriWebviewWindow>>);
47
48impl WebviewWindow {
49    pub(crate) fn new(webview_window: TauriWebviewWindow) -> Self {
50        Self(PyWrapper::new0(webview_window))
51    }
52}
53
54#[pymethods]
55impl WebviewWindow {
56    // equivalent to `tauri::WebviewWindow::builder`
57    #[new]
58    #[pyo3(signature = (manager, label, url, /, **kwargs))]
59    fn __new__(
60        py: Python<'_>,
61        manager: ImplManager,
62        label: String,
63        url: &Bound<'_, WebviewUrl>,
64        kwargs: Option<&Bound<'_, PyDict>>,
65    ) -> PyResult<Self> {
66        WebviewWindowBuilder::build(py, manager, label, url, kwargs)
67    }
68
69    fn run_on_main_thread(&self, py: Python<'_>, handler: PyObject) -> PyResult<()> {
70        py.allow_threads(|| {
71            delegate_inner!(self, run_on_main_thread, move || {
72                Python::with_gil(|py| {
73                    let handler = handler.bind(py);
74                    let result = handler.call0();
75                    result.unwrap_unraisable_py_result(py, Some(handler), || {
76                        "Python exception occurred in `WebviewWindow::run_on_main_thread`"
77                    });
78                })
79            })
80        })
81    }
82
83    fn label<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
84        let webview_window = self.0.inner_ref();
85        // if `label` is immutable, we can intern it to save memory.
86        PyString::intern(py, webview_window.label())
87    }
88
89    fn on_window_event(&self, py: Python<'_>, handler: PyObject) {
90        py.allow_threads(|| {
91            self.0.inner_ref().on_window_event(move |window_event| {
92                Python::with_gil(|py| {
93                    let window_event: WindowEvent = WindowEvent::from_tauri(py, window_event)
94                        // TODO: maybe we should only `write_unraisable` and log it instead of `panic` here?
95                        .expect("Failed to convert `WindowEvent` to pyobject");
96
97                    let handler = handler.bind(py);
98                    let result = handler.call1((window_event,));
99                    result.unwrap_unraisable_py_result(py, Some(handler), || {
100                        "Python exception occurred in `WebviewWindow::on_window_event` handler"
101                    });
102                })
103            })
104        })
105    }
106
107    fn on_webview_event(&self, py: Python<'_>, handler: PyObject) {
108        py.allow_threads(|| {
109            self.0.inner_ref().on_webview_event(move |webview_event| {
110                Python::with_gil(|py| {
111                    let webview_event: WebviewEvent = WebviewEvent::from_tauri(py, webview_event)
112                        // TODO: maybe we should only `write_unraisable` and log it instead of `panic` here?
113                        .expect("Failed to convert `WebviewEvent` to pyobject");
114
115                    let handler = handler.bind(py);
116                    let result = handler.call1((webview_event,));
117                    result.unwrap_unraisable_py_result(py, Some(handler), || {
118                        "Python exception occurred in `WebviewWindow::on_webview_event` handler"
119                    });
120                })
121            })
122        })
123    }
124
125    fn on_menu_event(slf: Py<Self>, py: Python<'_>, handler: PyObject) {
126        let moved_slf = slf.clone_ref(py);
127        py.allow_threads(|| {
128            slf.get()
129                .0
130                .inner_ref()
131                .on_menu_event(move |_window, menu_event| {
132                    Python::with_gil(|py| {
133                        // See: <https://github.com/tauri-apps/tauri/blob/8e9339e8807338597132ffd8688fb9da00f4102b/crates/tauri/src/app.rs#L2168-L2184>,
134                        // The `window` argument is always the `WebviewWindow` instance that calls this method,
135                        // so we can directly use the same PyObject.
136                        let window: &Py<Self> = &moved_slf; // TODO, XXX, FIXME: return `Window` instead of `WebviewWindow`?
137                        debug_assert_eq!(
138                            &*window.get().0.inner_ref().as_ref().window_ref(),
139                            _window
140                        );
141                        let menu_event: Bound<'_, MenuEvent> =
142                            MenuEvent::intern(py, &menu_event.id.0);
143
144                        let handler = handler.bind(py);
145                        let result = handler.call1((window, menu_event));
146                        result.unwrap_unraisable_py_result(py, Some(handler), || {
147                            "Python exception occurred in `WebviewWindow::on_menu_event` handler"
148                        });
149                    })
150                })
151        })
152    }
153
154    fn menu(&self, py: Python<'_>) -> Option<Menu> {
155        py.allow_threads(|| self.0.inner_ref().menu().map(Menu::new))
156    }
157
158    fn set_menu(&self, py: Python<'_>, menu: Py<Menu>) -> PyResult<Option<Menu>> {
159        py.allow_threads(|| {
160            let menu = menu.get().0.inner_ref().clone();
161            let returned_menu = delegate_inner!(self, set_menu, menu)?;
162            PyResult::Ok(returned_menu.map(Menu::new))
163        })
164    }
165
166    fn remove_menu(&self, py: Python<'_>) -> PyResult<Option<Menu>> {
167        py.allow_threads(|| {
168            let returned_menu = delegate_inner!(self, remove_menu,)?;
169            PyResult::Ok(returned_menu.map(Menu::new))
170        })
171    }
172
173    fn hide_menu(&self, py: Python<'_>) -> PyResult<()> {
174        py.allow_threads(|| delegate_inner!(self, hide_menu,))
175    }
176
177    fn show_menu(&self, py: Python<'_>) -> PyResult<()> {
178        py.allow_threads(|| delegate_inner!(self, show_menu,))
179    }
180
181    fn is_menu_visible(&self, py: Python<'_>) -> PyResult<bool> {
182        py.allow_threads(|| delegate_inner!(self, is_menu_visible,))
183    }
184
185    fn popup_menu(&self, py: Python<'_>, menu: ImplContextMenu) -> PyResult<()> {
186        py.allow_threads(|| {
187            context_menu_impl!(&menu, |menu| delegate_inner!(self, popup_menu, menu))
188        })
189    }
190
191    fn popup_menu_at(
192        &self,
193        py: Python<'_>,
194        menu: ImplContextMenu,
195        position: Py<Position>,
196    ) -> PyResult<()> {
197        let position = position.get().to_tauri(py)?;
198        py.allow_threads(|| {
199            context_menu_impl!(&menu, |menu| delegate_inner!(
200                self,
201                popup_menu_at,
202                menu,
203                position
204            ))
205        })
206    }
207
208    fn scale_factor(&self, py: Python<'_>) -> PyResult<f64> {
209        py.allow_threads(|| delegate_inner!(self, scale_factor,))
210    }
211
212    fn inner_position(&self, py: Python<'_>) -> PyResult<PhysicalPositionI32> {
213        let position = py.allow_threads(|| delegate_inner!(self, inner_position,))?;
214        PhysicalPositionI32::from_tauri(py, position)
215    }
216
217    fn outer_position(&self, py: Python<'_>) -> PyResult<PhysicalPositionI32> {
218        let position = py.allow_threads(|| delegate_inner!(self, outer_position,))?;
219        PhysicalPositionI32::from_tauri(py, position)
220    }
221
222    fn inner_size(&self, py: Python<'_>) -> PyResult<PhysicalSizeU32> {
223        let size = py.allow_threads(|| delegate_inner!(self, inner_size,))?;
224        PhysicalSizeU32::from_tauri(py, size)
225    }
226
227    fn outer_size(&self, py: Python<'_>) -> PyResult<PhysicalSizeU32> {
228        let size = py.allow_threads(|| delegate_inner!(self, outer_size,))?;
229        PhysicalSizeU32::from_tauri(py, size)
230    }
231
232    fn is_fullscreen(&self, py: Python<'_>) -> PyResult<bool> {
233        py.allow_threads(|| delegate_inner!(self, is_fullscreen,))
234    }
235
236    fn is_minimized(&self, py: Python<'_>) -> PyResult<bool> {
237        py.allow_threads(|| delegate_inner!(self, is_minimized,))
238    }
239
240    fn is_maximized(&self, py: Python<'_>) -> PyResult<bool> {
241        py.allow_threads(|| delegate_inner!(self, is_maximized,))
242    }
243
244    fn is_focused(&self, py: Python<'_>) -> PyResult<bool> {
245        py.allow_threads(|| delegate_inner!(self, is_focused,))
246    }
247
248    fn is_decorated(&self, py: Python<'_>) -> PyResult<bool> {
249        py.allow_threads(|| delegate_inner!(self, is_decorated,))
250    }
251
252    fn is_resizable(&self, py: Python<'_>) -> PyResult<bool> {
253        py.allow_threads(|| delegate_inner!(self, is_resizable,))
254    }
255
256    fn is_enabled(&self, py: Python<'_>) -> PyResult<bool> {
257        py.allow_threads(|| delegate_inner!(self, is_enabled,))
258    }
259
260    fn is_always_on_top(&self, py: Python<'_>) -> PyResult<bool> {
261        py.allow_threads(|| delegate_inner!(self, is_always_on_top,))
262    }
263
264    fn is_maximizable(&self, py: Python<'_>) -> PyResult<bool> {
265        py.allow_threads(|| delegate_inner!(self, is_maximizable,))
266    }
267
268    fn is_minimizable(&self, py: Python<'_>) -> PyResult<bool> {
269        py.allow_threads(|| delegate_inner!(self, is_minimizable,))
270    }
271
272    fn is_closable(&self, py: Python<'_>) -> PyResult<bool> {
273        py.allow_threads(|| delegate_inner!(self, is_closable,))
274    }
275
276    fn is_visible(&self, py: Python<'_>) -> PyResult<bool> {
277        py.allow_threads(|| delegate_inner!(self, is_visible,))
278    }
279
280    fn title(&self, py: Python<'_>) -> PyResult<String> {
281        py.allow_threads(|| delegate_inner!(self, title,))
282    }
283
284    fn current_monitor(&self, py: Python<'_>) -> PyResult<Option<Monitor>> {
285        let monitor = py.allow_threads(|| delegate_inner!(self, current_monitor,))?;
286        let monitor = monitor.map(|m| Monitor::from_tauri(py, m)).transpose()?;
287        Ok(monitor)
288    }
289
290    fn primary_monitor(&self, py: Python<'_>) -> PyResult<Option<Monitor>> {
291        let monitor = py.allow_threads(|| delegate_inner!(self, primary_monitor,))?;
292        let monitor = monitor.map(|m| Monitor::from_tauri(py, m)).transpose()?;
293        Ok(monitor)
294    }
295
296    fn monitor_from_point(&self, py: Python<'_>, x: f64, y: f64) -> PyResult<Option<Monitor>> {
297        let monitor = py.allow_threads(|| delegate_inner!(self, monitor_from_point, x, y))?;
298        let monitor = monitor.map(|m| Monitor::from_tauri(py, m)).transpose()?;
299        Ok(monitor)
300    }
301
302    fn available_monitors(&self, py: Python<'_>) -> PyResult<Vec<Monitor>> {
303        let monitors = py.allow_threads(|| delegate_inner!(self, available_monitors,))?;
304        let monitors = monitors
305            .into_iter()
306            .map(|m| Monitor::from_tauri(py, m))
307            .collect::<PyResult<Vec<_>>>()?;
308        Ok(monitors)
309    }
310
311    fn theme(&self, py: Python<'_>) -> PyResult<Theme> {
312        py.allow_threads(|| delegate_inner!(self, theme,).map(Into::into))
313    }
314
315    fn cursor_position(&self, py: Python<'_>) -> PyResult<PhysicalPositionF64> {
316        let position = py.allow_threads(|| delegate_inner!(self, cursor_position,))?;
317        PhysicalPositionF64::from_tauri(py, position)
318    }
319
320    fn center(&self, py: Python<'_>) -> PyResult<()> {
321        py.allow_threads(|| delegate_inner!(self, center,))
322    }
323
324    fn request_user_attention(
325        &self,
326        py: Python<'_>,
327        attention_type: Option<UserAttentionType>,
328    ) -> PyResult<()> {
329        py.allow_threads(|| {
330            delegate_inner!(self, request_user_attention, attention_type.map(Into::into))
331        })
332    }
333
334    fn set_resizable(&self, py: Python<'_>, resizable: bool) -> PyResult<()> {
335        py.allow_threads(|| delegate_inner!(self, set_resizable, resizable))
336    }
337
338    fn set_enabled(&self, py: Python<'_>, enabled: bool) -> PyResult<()> {
339        py.allow_threads(|| delegate_inner!(self, set_enabled, enabled))
340    }
341
342    fn set_maximizable(&self, py: Python<'_>, maximizable: bool) -> PyResult<()> {
343        py.allow_threads(|| delegate_inner!(self, set_maximizable, maximizable))
344    }
345
346    fn set_minimizable(&self, py: Python<'_>, minimizable: bool) -> PyResult<()> {
347        py.allow_threads(|| delegate_inner!(self, set_minimizable, minimizable))
348    }
349
350    fn set_closable(&self, py: Python<'_>, closable: bool) -> PyResult<()> {
351        py.allow_threads(|| delegate_inner!(self, set_closable, closable))
352    }
353
354    fn set_title(&self, py: Python<'_>, title: &str) -> PyResult<()> {
355        py.allow_threads(|| delegate_inner!(self, set_title, title))
356    }
357
358    fn maximize(&self, py: Python<'_>) -> PyResult<()> {
359        py.allow_threads(|| delegate_inner!(self, maximize,))
360    }
361
362    fn unmaximize(&self, py: Python<'_>) -> PyResult<()> {
363        py.allow_threads(|| delegate_inner!(self, unmaximize,))
364    }
365
366    fn minimize(&self, py: Python<'_>) -> PyResult<()> {
367        py.allow_threads(|| delegate_inner!(self, minimize,))
368    }
369
370    fn unminimize(&self, py: Python<'_>) -> PyResult<()> {
371        py.allow_threads(|| delegate_inner!(self, unminimize,))
372    }
373
374    fn show(&self, py: Python<'_>) -> PyResult<()> {
375        py.allow_threads(|| delegate_inner!(self, show,))
376    }
377
378    fn hide(&self, py: Python<'_>) -> PyResult<()> {
379        py.allow_threads(|| delegate_inner!(self, hide,))
380    }
381
382    fn close(&self, py: Python<'_>) -> PyResult<()> {
383        py.allow_threads(|| delegate_inner!(self, close,))
384    }
385
386    fn destroy(&self, py: Python<'_>) -> PyResult<()> {
387        py.allow_threads(|| delegate_inner!(self, destroy,))
388    }
389
390    fn set_decorations(&self, py: Python<'_>, decorations: bool) -> PyResult<()> {
391        py.allow_threads(|| delegate_inner!(self, set_decorations, decorations))
392    }
393
394    fn set_shadow(&self, py: Python<'_>, shadow: bool) -> PyResult<()> {
395        py.allow_threads(|| delegate_inner!(self, set_shadow, shadow))
396    }
397
398    fn set_effects(&self, py: Python<'_>, effects: Option<Effects>) -> PyResult<()> {
399        py.allow_threads(|| {
400            let effects = effects.map(|e| e.into_tauri().build());
401            delegate_inner!(self, set_effects, effects)
402        })
403    }
404
405    fn set_always_on_bottom(&self, py: Python<'_>, always_on_bottom: bool) -> PyResult<()> {
406        py.allow_threads(|| delegate_inner!(self, set_always_on_bottom, always_on_bottom))
407    }
408
409    fn set_always_on_top(&self, py: Python<'_>, always_on_top: bool) -> PyResult<()> {
410        py.allow_threads(|| delegate_inner!(self, set_always_on_top, always_on_top))
411    }
412
413    fn set_visible_on_all_workspaces(
414        &self,
415        py: Python<'_>,
416        visible_on_all_workspaces: bool,
417    ) -> PyResult<()> {
418        py.allow_threads(|| {
419            delegate_inner!(
420                self,
421                set_visible_on_all_workspaces,
422                visible_on_all_workspaces
423            )
424        })
425    }
426
427    fn set_content_protected(&self, py: Python<'_>, protected: bool) -> PyResult<()> {
428        py.allow_threads(|| delegate_inner!(self, set_content_protected, protected))
429    }
430
431    fn set_size(&self, py: Python<'_>, size: Py<Size>) -> PyResult<()> {
432        let size = size.get().to_tauri(py)?;
433        py.allow_threads(|| delegate_inner!(self, set_size, size))
434    }
435
436    fn set_min_size(&self, py: Python<'_>, size: Option<Py<Size>>) -> PyResult<()> {
437        let size = size.map(|s| s.get().to_tauri(py)).transpose()?;
438        py.allow_threads(|| delegate_inner!(self, set_min_size, size))
439    }
440
441    fn set_max_size(&self, py: Python<'_>, size: Option<Py<Size>>) -> PyResult<()> {
442        let size = size.map(|s| s.get().to_tauri(py)).transpose()?;
443        py.allow_threads(|| delegate_inner!(self, set_max_size, size))
444    }
445
446    // TODO: `set_size_constraints`, we need wait for tauri to expose `dpi::PixelUnit` first.
447    // PR: <https://github.com/tauri-apps/tauri/pull/14009>
448
449    fn set_position(&self, py: Python<'_>, position: Py<Position>) -> PyResult<()> {
450        let position = position.get().to_tauri(py)?;
451        py.allow_threads(|| delegate_inner!(self, set_position, position))
452    }
453
454    fn set_fullscreen(&self, py: Python<'_>, fullscreen: bool) -> PyResult<()> {
455        py.allow_threads(|| delegate_inner!(self, set_fullscreen, fullscreen))
456    }
457
458    fn set_focus(&self, py: Python<'_>) -> PyResult<()> {
459        py.allow_threads(|| delegate_inner!(self, set_focus,))
460    }
461
462    fn set_icon(&self, py: Python<'_>, icon: Py<Image>) -> PyResult<()> {
463        let icon = icon.get().to_tauri(py);
464        py.allow_threads(|| delegate_inner!(self, set_icon, icon))
465    }
466
467    fn set_background_color(&self, py: Python<'_>, color: Option<Color>) -> PyResult<()> {
468        let color = color.map(|c| c.0);
469        py.allow_threads(|| delegate_inner!(self, set_background_color, color))
470    }
471
472    fn set_skip_taskbar(&self, py: Python<'_>, skip: bool) -> PyResult<()> {
473        py.allow_threads(|| delegate_inner!(self, set_skip_taskbar, skip))
474    }
475
476    fn set_cursor_grab(&self, py: Python<'_>, grab: bool) -> PyResult<()> {
477        py.allow_threads(|| delegate_inner!(self, set_cursor_grab, grab))
478    }
479
480    fn set_cursor_visible(&self, py: Python<'_>, visible: bool) -> PyResult<()> {
481        py.allow_threads(|| delegate_inner!(self, set_cursor_visible, visible))
482    }
483
484    fn set_cursor_icon(&self, py: Python<'_>, icon: CursorIcon) -> PyResult<()> {
485        py.allow_threads(|| delegate_inner!(self, set_cursor_icon, icon.into()))
486    }
487
488    fn set_cursor_position(&self, py: Python<'_>, position: Py<Position>) -> PyResult<()> {
489        let position = position.get().to_tauri(py)?;
490        py.allow_threads(|| delegate_inner!(self, set_cursor_position, position))
491    }
492
493    fn set_ignore_cursor_events(&self, py: Python<'_>, ignore: bool) -> PyResult<()> {
494        py.allow_threads(|| delegate_inner!(self, set_ignore_cursor_events, ignore))
495    }
496
497    fn start_dragging(&self, py: Python<'_>) -> PyResult<()> {
498        py.allow_threads(|| delegate_inner!(self, start_dragging,))
499    }
500
501    #[cfg(windows)]
502    fn set_overlay_icon(&self, py: Python<'_>, icon: Option<Py<Image>>) -> PyResult<()> {
503        let icon = icon.as_ref().map(|i| i.get().to_tauri(py));
504        py.allow_threads(|| delegate_inner!(self, set_overlay_icon, icon))
505    }
506
507    fn set_badge_count(&self, py: Python<'_>, count: Option<i64>) -> PyResult<()> {
508        py.allow_threads(|| delegate_inner!(self, set_badge_count, count))
509    }
510
511    #[cfg(target_os = "macos")]
512    fn set_badge_label(&self, py: Python<'_>, label: Option<String>) -> PyResult<()> {
513        py.allow_threads(|| delegate_inner!(self, set_badge_label, label))
514    }
515
516    fn set_progress_bar(&self, py: Python<'_>, progress_state: ProgressBarState) -> PyResult<()> {
517        py.allow_threads(|| delegate_inner!(self, set_progress_bar, progress_state.into_tauri()))
518    }
519
520    fn set_title_bar_style(&self, py: Python<'_>, style: TitleBarStyle) -> PyResult<()> {
521        py.allow_threads(|| delegate_inner!(self, set_title_bar_style, style.into()))
522    }
523
524    fn set_theme(&self, py: Python<'_>, theme: Option<Theme>) -> PyResult<()> {
525        py.allow_threads(|| delegate_inner!(self, set_theme, theme.map(Into::into)))
526    }
527
528    fn print(&self, py: Python<'_>) -> PyResult<()> {
529        py.allow_threads(|| delegate_inner!(self, print,))
530    }
531
532    fn url(&self, py: Python<'_>) -> PyResult<Url<'_>> {
533        let url = py.allow_threads(|| delegate_inner!(self, url,))?;
534        Ok(url.into())
535    }
536
537    fn navigate(&self, py: Python<'_>, url: Url<'_>) -> PyResult<()> {
538        py.allow_threads(|| delegate_inner!(self, navigate, url.into()))
539    }
540
541    fn reload(&self, py: Python<'_>) -> PyResult<()> {
542        py.allow_threads(|| delegate_inner!(self, reload,))
543    }
544
545    fn eval(&self, py: Python<'_>, js: &str) -> PyResult<()> {
546        py.allow_threads(|| delegate_inner!(self, eval, js))
547    }
548
549    fn open_devtools(&self, py: Python<'_>) -> PyResult<()> {
550        cfg_impl!(|any(debug_assertions, feature = "tauri-devtools")| -> () {
551            py.allow_threads(|| {
552                self.0.inner_ref().open_devtools();
553                Ok(())
554            })
555        })
556    }
557
558    fn close_devtools(&self, py: Python<'_>) -> PyResult<()> {
559        cfg_impl!(|any(debug_assertions, feature = "tauri-devtools")| -> () {
560            py.allow_threads(|| {
561                self.0.inner_ref().close_devtools();
562                Ok(())
563            })
564        })
565    }
566
567    fn is_devtools_open(&self, py: Python<'_>) -> PyResult<bool> {
568        cfg_impl!(|any(debug_assertions, feature = "tauri-devtools")| -> bool {
569            py.allow_threads(|| {
570                Ok(self.0.inner_ref().is_devtools_open())
571            })
572        })
573    }
574
575    fn set_zoom(&self, py: Python<'_>, scale_factor: f64) -> PyResult<()> {
576        py.allow_threads(|| delegate_inner!(self, set_zoom, scale_factor))
577    }
578
579    fn clear_all_browsing_data(&self, py: Python<'_>) -> PyResult<()> {
580        py.allow_threads(|| delegate_inner!(self, clear_all_browsing_data,))
581    }
582
583    fn cookies_for_url(&self, py: Python<'_>, url: Url<'_>) -> PyResult<Vec<Cookie>> {
584        let cookies = py.allow_threads(|| delegate_inner!(self, cookies_for_url, url.into()))?;
585        let cookies = cookies
586            .into_iter()
587            .map(|c| Cookie::from_tauri(py, &c))
588            .collect::<Vec<_>>();
589        Ok(cookies)
590    }
591
592    fn cookies(&self, py: Python<'_>) -> PyResult<Vec<Cookie>> {
593        let cookies = py.allow_threads(|| delegate_inner!(self, cookies,))?;
594        let cookies = cookies
595            .into_iter()
596            .map(|c| Cookie::from_tauri(py, &c))
597            .collect::<Vec<_>>();
598        Ok(cookies)
599    }
600
601    fn set_cookie(&self, py: Python<'_>, cookie: Cookie) -> PyResult<()> {
602        let cookie = cookie.to_tauri(py)?;
603        py.allow_threads(|| delegate_inner!(self, set_cookie, cookie))
604    }
605
606    fn delete_cookie(&self, py: Python<'_>, cookie: Cookie) -> PyResult<()> {
607        let cookie = cookie.to_tauri(py)?;
608        py.allow_threads(|| delegate_inner!(self, delete_cookie, cookie))
609    }
610
611    /// See also: [tauri::webview::WebviewWindow::as_ref]
612    fn as_ref_webview(&self) -> Webview {
613        let webview = self.0.inner_ref().as_ref().clone();
614        Webview::new(webview)
615    }
616
617    // TODO: `as_ref_windows`, see <https://github.com/tauri-apps/tauri/pull/14012>
618}
619
620/// See also: [tauri::webview::Webview]
621#[pyclass(frozen)]
622#[non_exhaustive]
623pub struct Webview(pub PyWrapper<PyWrapperT0<TauriWebview>>);
624
625impl Webview {
626    pub(crate) fn new(webview: TauriWebview) -> Self {
627        Self(PyWrapper::new0(webview))
628    }
629}
630
631#[pymethods]
632impl Webview {
633    fn window(&self) -> Window {
634        let window = self.0.inner_ref().window();
635        Window::new(window)
636    }
637}
638
639/// See also: [tauri::webview::Color]
640///
641/// `(r, g, b, a)`
642pub struct Color(pub(crate) webview::Color);
643
644impl<'py> FromPyObject<'py> for Color {
645    fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
646        let (r, g, b, a): (u8, u8, u8, u8) = ob.extract()?;
647        Ok(Self(webview::Color(r, g, b, a)))
648    }
649}
650
651macro_rules! same_site_impl {
652    ($ident:ident => : $( $(#[$meta:meta])* $variant:ident ),*) => {
653        /// See also: [cookie::SameSite]
654        #[pyclass(frozen, eq, eq_int)]
655        #[derive(PartialEq, Clone, Copy)]
656        #[non_exhaustive]
657        pub enum $ident {
658            $(
659                $(#[$meta])*
660                $variant,
661            )*
662        }
663
664        impl From<cookie::SameSite> for $ident {
665            fn from(val: cookie::SameSite) -> Self {
666                match val {
667                    $(cookie::SameSite::$variant => $ident::$variant,)*
668                }
669            }
670        }
671
672        impl From<$ident> for cookie::SameSite {
673            fn from(val: $ident) -> Self {
674                match val {
675                    $($ident::$variant => cookie::SameSite::$variant,)*
676                }
677            }
678        }
679    };
680}
681
682same_site_impl!(SameSite => : Strict, Lax, #[pyo3(name = "None_")] None);
683
684// NOTE: we need to implement this manually
685// because [NotRequired::into_py_with_none] requires `'&T: IntoPyObject`
686impl<'py> IntoPyObject<'py> for &SameSite {
687    type Error = <SameSite as IntoPyObject<'py>>::Error;
688    type Output = <SameSite as IntoPyObject<'py>>::Output;
689    type Target = <SameSite as IntoPyObject<'py>>::Target;
690
691    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
692        <SameSite as IntoPyObject<'py>>::into_pyobject(*self, py)
693    }
694}
695
696/// See also: [tauri::webview::Cookie]
697// ref:
698// - <https://github.com/encode/starlette/blob/6ee94f2cac955eeae68d2898a8dec8cf17b48736/starlette/responses.py#L91-L103>
699// - <https://docs.python.org/3.14/library/http.cookies.html#http.cookies.Morsel>
700//
701// TODO: [IntoPyObject] does not use `pyo3::intern`, we should file an issue to pyo3
702// TODO: Submit a feature request to pyo3 to add `#[pyo3(skip_if)]` for skipping certain fields
703#[derive(IntoPyObject, IntoPyObjectRef)]
704pub struct Cookie {
705    key: Py<PyString>,
706    value: Py<PyString>,
707    max_age: Option<i64>,
708    expires: Option<OffsetDateTime>,
709    path: Option<Py<PyString>>,
710    domain: Option<Py<PyString>>,
711    secure: Option<bool>,
712    httponly: Option<bool>,
713    samesite: Option<SameSite>,
714    partitioned: Option<bool>,
715}
716
717derive_from_py_dict!(Cookie {
718    key,
719    value,
720    #[pyo3(default)]
721    max_age,
722    #[pyo3(default)]
723    expires,
724    #[pyo3(default)]
725    path,
726    #[pyo3(default)]
727    domain,
728    #[pyo3(default)]
729    secure,
730    #[pyo3(default)]
731    httponly,
732    #[pyo3(default)]
733    samesite,
734    #[pyo3(default)]
735    partitioned,
736});
737
738impl<'py> FromPyObject<'py> for Cookie {
739    fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
740        let dict = ob.downcast::<PyDict>()?;
741        Self::from_py_dict(dict)
742    }
743}
744
745impl Cookie {
746    pub(crate) fn from_tauri(py: Python<'_>, cookie: &webview::Cookie<'_>) -> Self {
747        let key = PyString::new(py, cookie.name()).unbind();
748        let value = PyString::new(py, cookie.value()).unbind();
749        let max_age = cookie.max_age().map(|d| d.whole_seconds());
750        let expires = cookie.expires_datetime();
751        let path = cookie.path().map(|p| PyString::new(py, p).unbind());
752        let domain = cookie.domain().map(|d| PyString::new(py, d).unbind());
753        let secure = cookie.secure();
754        let httponly = cookie.http_only();
755        let samesite = cookie.same_site().map(|s| s.into());
756        let partitioned = cookie.partitioned();
757        Self {
758            key,
759            value,
760            max_age,
761            expires,
762            path,
763            domain,
764            secure,
765            httponly,
766            samesite,
767            partitioned,
768        }
769    }
770
771    pub(crate) fn to_tauri(&self, py: Python<'_>) -> PyResult<webview::Cookie<'_>> {
772        let Self {
773            key,
774            value,
775            max_age,
776            expires,
777            path,
778            domain,
779            secure,
780            httponly,
781            samesite,
782            partitioned,
783        } = self;
784
785        // TODO, PERF: once we drop py39 support, we can use [PyStringMethods::to_str] directly.
786        let key = key.to_cow(py)?;
787        let value = value.to_cow(py)?;
788        let mut cookie_builder = cookie::CookieBuilder::new(key, value);
789
790        if let Some(max_age) = max_age {
791            cookie_builder = cookie_builder.max_age(cookie::time::Duration::seconds(*max_age));
792        }
793        if let Some(expires) = expires {
794            cookie_builder = cookie_builder.expires(*expires);
795        }
796        if let Some(path) = path {
797            cookie_builder = cookie_builder.path(path.to_cow(py)?);
798        }
799        if let Some(domain) = domain {
800            cookie_builder = cookie_builder.domain(domain.to_cow(py)?);
801        }
802        if let Some(secure) = secure {
803            cookie_builder = cookie_builder.secure(*secure);
804        }
805        if let Some(httponly) = httponly {
806            cookie_builder = cookie_builder.http_only(*httponly);
807        }
808        if let Some(samesite) = samesite {
809            cookie_builder = cookie_builder.same_site((*samesite).into());
810        }
811        if let Some(partitioned) = partitioned {
812            cookie_builder = cookie_builder.partitioned(*partitioned);
813        }
814        Ok(cookie_builder.build())
815    }
816}
817
818/// See also: [tauri::webview::WebviewWindowBuilder]
819#[non_exhaustive]
820pub struct WebviewWindowBuilderArgs {
821    // TODO, FIXME: on_menu_event: NotRequired<PyObject>,
822    // `on_menu_event` passes `Window` instead of `WebviewWindow`,
823    // we need to submit a PR to tauri to modify the signature,
824    // or add a method to get `WebviewWindow` from `Window`
825
826    // TODO: on_web_resource_request
827    on_navigation: NotRequired<PyObject>,
828    // TODO: on_new_window
829    on_document_title_changed: NotRequired<PyObject>,
830    // TODO: on_download, on_page_load
831    menu: NotRequired<Py<Menu>>,
832    center: NotRequired<bool>,
833    position: NotRequired<(f64, f64)>,
834    inner_size: NotRequired<(f64, f64)>,
835    min_inner_size: NotRequired<(f64, f64)>,
836    max_inner_size: NotRequired<(f64, f64)>,
837    // TODO: inner_size_constraints
838    prevent_overflow: NotRequired<bool>,
839    prevent_overflow_with_margin: NotRequired<Py<Size>>,
840    resizable: NotRequired<bool>,
841    maximizable: NotRequired<bool>,
842    minimizable: NotRequired<bool>,
843    closable: NotRequired<bool>,
844    title: NotRequired<String>,
845    fullscreen: NotRequired<bool>,
846    focusable: NotRequired<bool>,
847    focused: NotRequired<bool>,
848    maximized: NotRequired<bool>,
849    visible: NotRequired<bool>,
850    theme: NotRequired<Option<Theme>>,
851    decorations: NotRequired<bool>,
852    always_on_bottom: NotRequired<bool>,
853    always_on_top: NotRequired<bool>,
854    visible_on_all_workspaces: NotRequired<bool>,
855    content_protected: NotRequired<bool>,
856    icon: NotRequired<Py<Image>>,
857    skip_taskbar: NotRequired<bool>,
858    window_classname: NotRequired<String>,
859    shadow: NotRequired<bool>,
860    parent: NotRequired<Py<WebviewWindow>>,
861    #[cfg(windows)]
862    owner: NotRequired<Py<WebviewWindow>>,
863    #[cfg(any(
864        target_os = "linux",
865        target_os = "dragonfly",
866        target_os = "freebsd",
867        target_os = "netbsd",
868        target_os = "openbsd"
869    ))]
870    transient_for: NotRequired<Py<WebviewWindow>>,
871    #[cfg(windows)]
872    drag_and_drop: NotRequired<bool>,
873    #[cfg(target_os = "macos")]
874    title_bar_style: NotRequired<TitleBarStyle>,
875    #[cfg(target_os = "macos")]
876    traffic_light_position: NotRequired<Py<Position>>,
877    #[cfg(target_os = "macos")]
878    allow_link_preview: NotRequired<bool>,
879    #[cfg(target_os = "macos")]
880    hidden_title: NotRequired<bool>,
881    #[cfg(target_os = "macos")]
882    tabbing_identifier: NotRequired<PyBackedStr>,
883    effects: NotRequired<Effects>,
884    accept_first_mouse: NotRequired<bool>,
885    initialization_script: NotRequired<String>,
886    initialization_script_for_all_frames: NotRequired<String>,
887    user_agent: NotRequired<PyBackedStr>,
888    additional_browser_args: NotRequired<PyBackedStr>,
889    data_directory: NotRequired<PathBuf>,
890    disable_drag_drop_handler: NotRequired<bool>,
891    enable_clipboard_access: NotRequired<bool>,
892    incognito: NotRequired<bool>,
893    auto_resize: NotRequired<bool>,
894    // TODO, PERF: remove `'static` bound
895    proxy_url: NotRequired<Url<'static>>,
896    transparent: NotRequired<bool>,
897    zoom_hotkeys_enabled: NotRequired<bool>,
898    browser_extensions_enabled: NotRequired<bool>,
899    extensions_path: NotRequired<PathBuf>,
900    // TODO: data_store_identifier
901    use_https_scheme: NotRequired<bool>,
902    devtools: NotRequired<bool>,
903    background_color: NotRequired<Color>,
904    // TODO: BackgroundThrottlingPolicy
905    disable_javascript: NotRequired<bool>,
906    // TODO: window_features
907}
908
909derive_from_py_dict!(WebviewWindowBuilderArgs {
910    #[pyo3(default)]
911    on_navigation,
912    #[pyo3(default)]
913    on_document_title_changed,
914    #[pyo3(default)]
915    menu,
916    #[pyo3(default)]
917    center,
918    #[pyo3(default)]
919    position,
920    #[pyo3(default)]
921    inner_size,
922    #[pyo3(default)]
923    min_inner_size,
924    #[pyo3(default)]
925    max_inner_size,
926    #[pyo3(default)]
927    prevent_overflow,
928    #[pyo3(default)]
929    prevent_overflow_with_margin,
930    #[pyo3(default)]
931    resizable,
932    #[pyo3(default)]
933    maximizable,
934    #[pyo3(default)]
935    minimizable,
936    #[pyo3(default)]
937    closable,
938    #[pyo3(default)]
939    title,
940    #[pyo3(default)]
941    fullscreen,
942    #[pyo3(default)]
943    focusable,
944    #[pyo3(default)]
945    focused,
946    #[pyo3(default)]
947    maximized,
948    #[pyo3(default)]
949    visible,
950    #[pyo3(default)]
951    theme,
952    #[pyo3(default)]
953    decorations,
954    #[pyo3(default)]
955    always_on_bottom,
956    #[pyo3(default)]
957    always_on_top,
958    #[pyo3(default)]
959    visible_on_all_workspaces,
960    #[pyo3(default)]
961    content_protected,
962    #[pyo3(default)]
963    icon,
964    #[pyo3(default)]
965    skip_taskbar,
966    #[pyo3(default)]
967    window_classname,
968    #[pyo3(default)]
969    shadow,
970    #[pyo3(default)]
971    parent,
972    #[cfg(windows)]
973    #[pyo3(default)]
974    owner,
975    #[cfg(any(
976        target_os = "linux",
977        target_os = "dragonfly",
978        target_os = "freebsd",
979        target_os = "netbsd",
980        target_os = "openbsd"
981    ))]
982    #[pyo3(default)]
983    transient_for,
984    #[cfg(windows)]
985    #[pyo3(default)]
986    drag_and_drop,
987    #[cfg(target_os = "macos")]
988    #[pyo3(default)]
989    title_bar_style,
990    #[cfg(target_os = "macos")]
991    #[pyo3(default)]
992    traffic_light_position,
993    #[cfg(target_os = "macos")]
994    #[pyo3(default)]
995    allow_link_preview,
996    #[cfg(target_os = "macos")]
997    #[pyo3(default)]
998    hidden_title,
999    #[cfg(target_os = "macos")]
1000    #[pyo3(default)]
1001    tabbing_identifier,
1002    #[pyo3(default)]
1003    effects,
1004    #[pyo3(default)]
1005    accept_first_mouse,
1006    #[pyo3(default)]
1007    initialization_script,
1008    #[pyo3(default)]
1009    initialization_script_for_all_frames,
1010    #[pyo3(default)]
1011    user_agent,
1012    #[pyo3(default)]
1013    additional_browser_args,
1014    #[pyo3(default)]
1015    data_directory,
1016    #[pyo3(default)]
1017    disable_drag_drop_handler,
1018    #[pyo3(default)]
1019    enable_clipboard_access,
1020    #[pyo3(default)]
1021    incognito,
1022    #[pyo3(default)]
1023    auto_resize,
1024    #[pyo3(default)]
1025    proxy_url,
1026    #[pyo3(default)]
1027    transparent,
1028    #[pyo3(default)]
1029    zoom_hotkeys_enabled,
1030    #[pyo3(default)]
1031    browser_extensions_enabled,
1032    #[pyo3(default)]
1033    extensions_path,
1034    #[pyo3(default)]
1035    use_https_scheme,
1036    #[pyo3(default)]
1037    devtools,
1038    #[pyo3(default)]
1039    background_color,
1040    #[pyo3(default)]
1041    disable_javascript,
1042});
1043
1044impl WebviewWindowBuilderArgs {
1045    fn from_kwargs(kwargs: Option<&Bound<'_, PyDict>>) -> PyResult<Option<Self>> {
1046        kwargs.map(Self::from_py_dict).transpose()
1047    }
1048
1049    fn apply_to_builder<'a, M>(
1050        self,
1051        py: Python<'_>,
1052        mut builder: webview::WebviewWindowBuilder<'a, Runtime, M>,
1053    ) -> PyResult<webview::WebviewWindowBuilder<'a, Runtime, M>>
1054    where
1055        M: tauri::Manager<Runtime>,
1056    {
1057        let Self {
1058            on_navigation,
1059            on_document_title_changed,
1060            menu,
1061            center,
1062            position,
1063            inner_size,
1064            min_inner_size,
1065            max_inner_size,
1066            prevent_overflow,
1067            prevent_overflow_with_margin,
1068            resizable,
1069            maximizable,
1070            minimizable,
1071            closable,
1072            title,
1073            fullscreen,
1074            focusable,
1075            focused,
1076            maximized,
1077            visible,
1078            theme,
1079            decorations,
1080            always_on_bottom,
1081            always_on_top,
1082            visible_on_all_workspaces,
1083            content_protected,
1084            icon,
1085            skip_taskbar,
1086            window_classname,
1087            shadow,
1088            parent,
1089            #[cfg(windows)]
1090            owner,
1091            #[cfg(any(
1092                target_os = "linux",
1093                target_os = "dragonfly",
1094                target_os = "freebsd",
1095                target_os = "netbsd",
1096                target_os = "openbsd"
1097            ))]
1098            transient_for,
1099            #[cfg(windows)]
1100            drag_and_drop,
1101            #[cfg(target_os = "macos")]
1102            title_bar_style,
1103            #[cfg(target_os = "macos")]
1104            traffic_light_position,
1105            #[cfg(target_os = "macos")]
1106            allow_link_preview,
1107            #[cfg(target_os = "macos")]
1108            hidden_title,
1109            #[cfg(target_os = "macos")]
1110            tabbing_identifier,
1111            effects,
1112            accept_first_mouse,
1113            initialization_script,
1114            initialization_script_for_all_frames,
1115            user_agent,
1116            additional_browser_args,
1117            data_directory,
1118            disable_drag_drop_handler,
1119            enable_clipboard_access,
1120            incognito,
1121            auto_resize,
1122            proxy_url,
1123            transparent,
1124            zoom_hotkeys_enabled,
1125            browser_extensions_enabled,
1126            extensions_path,
1127            use_https_scheme,
1128            devtools,
1129            background_color,
1130            disable_javascript,
1131        } = self;
1132
1133        if let Some(on_navigation) = on_navigation.0 {
1134            builder = builder.on_navigation(move |url| {
1135                Python::with_gil(|py| {
1136                    let url = Url::from(url);
1137
1138                    let handler = on_navigation.bind(py);
1139                    let ret = handler
1140                        .call1((url,))
1141                        .unwrap_unraisable_py_result(py, Some(handler), || {
1142                            "Python exception occurred in `WebviewWindowBuilder::on_navigation` handler"
1143                        });
1144                    ret.extract::<bool>()
1145                        .unwrap_unraisable_py_result(py, Some(&ret), || {
1146                            "`WebviewWindowBuilder::on_navigation` return non-bool value"
1147                        })
1148                })
1149            });
1150        };
1151        if let Some(on_document_title_changed) = on_document_title_changed.0 {
1152            builder = builder.on_document_title_changed(move |webview_window, title| {
1153                Python::with_gil(|py| {
1154                    let webview_window = WebviewWindow::new(webview_window);
1155
1156                    let handler = on_document_title_changed.bind(py);
1157                    handler
1158                        .call1((webview_window, title))
1159                        .unwrap_unraisable_py_result(py, Some(handler), || {
1160                            "Python exception occurred in `WebviewWindowBuilder::on_document_title_changed` handler"
1161                        });
1162                })
1163            });
1164        };
1165        if let Some(menu) = menu.0 {
1166            let menu = menu.get().0.inner_ref().clone();
1167            builder = builder.menu(menu);
1168        }
1169        if let Some(true) = center.0 {
1170            builder = builder.center();
1171        }
1172        if let Some((x, y)) = position.0 {
1173            builder = builder.position(x, y);
1174        }
1175        if let Some((width, height)) = inner_size.0 {
1176            builder = builder.inner_size(width, height);
1177        }
1178        if let Some((min_width, min_height)) = min_inner_size.0 {
1179            builder = builder.min_inner_size(min_width, min_height);
1180        }
1181        if let Some((max_width, max_height)) = max_inner_size.0 {
1182            builder = builder.max_inner_size(max_width, max_height);
1183        }
1184        if let Some(true) = prevent_overflow.0 {
1185            builder = builder.prevent_overflow();
1186        }
1187        if let Some(margin) = prevent_overflow_with_margin.0 {
1188            let margin = margin.get().to_tauri(py)?;
1189            builder = builder.prevent_overflow_with_margin(margin);
1190        }
1191        if let Some(resizable) = resizable.0 {
1192            builder = builder.resizable(resizable);
1193        }
1194        if let Some(maximizable) = maximizable.0 {
1195            builder = builder.maximizable(maximizable);
1196        }
1197        if let Some(minimizable) = minimizable.0 {
1198            builder = builder.minimizable(minimizable);
1199        }
1200        if let Some(closable) = closable.0 {
1201            builder = builder.closable(closable);
1202        }
1203        if let Some(title) = title.0 {
1204            builder = builder.title(title);
1205        }
1206        if let Some(fullscreen) = fullscreen.0 {
1207            builder = builder.fullscreen(fullscreen);
1208        }
1209        if let Some(focusable) = focusable.0 {
1210            builder = builder.focusable(focusable);
1211        }
1212        if let Some(focused) = focused.0 {
1213            builder = builder.focused(focused);
1214        }
1215        if let Some(maximized) = maximized.0 {
1216            builder = builder.maximized(maximized);
1217        }
1218        if let Some(visible) = visible.0 {
1219            builder = builder.visible(visible);
1220        }
1221        if let Some(theme) = theme.0 {
1222            builder = builder.theme(theme.map(Into::into));
1223        }
1224        if let Some(decorations) = decorations.0 {
1225            builder = builder.decorations(decorations);
1226        }
1227        if let Some(always_on_bottom) = always_on_bottom.0 {
1228            builder = builder.always_on_bottom(always_on_bottom);
1229        }
1230        if let Some(always_on_top) = always_on_top.0 {
1231            builder = builder.always_on_top(always_on_top);
1232        }
1233        if let Some(visible_on_all_workspaces) = visible_on_all_workspaces.0 {
1234            builder = builder.visible_on_all_workspaces(visible_on_all_workspaces);
1235        }
1236        if let Some(content_protected) = content_protected.0 {
1237            builder = builder.content_protected(content_protected);
1238        }
1239        if let Some(icon) = icon.0 {
1240            // TODO, PERF, FIXME: avoid clone.
1241            // This is a signature error of tauri.
1242            // The signature should have been `icon: Image<'_>`,
1243            // but instead it is `icon: Image<'a>`. We need to submit a PR.
1244            let icon = icon.get().to_tauri(py).to_owned();
1245            builder = builder.icon(icon).map_err(TauriError::from)?;
1246        }
1247        if let Some(skip_taskbar) = skip_taskbar.0 {
1248            builder = builder.skip_taskbar(skip_taskbar);
1249        }
1250        if let Some(window_classname) = window_classname.0 {
1251            builder = builder.window_classname(window_classname);
1252        }
1253        if let Some(shadow) = shadow.0 {
1254            builder = builder.shadow(shadow);
1255        }
1256        if let Some(parent) = parent.0 {
1257            let parent = parent.get().0.inner_ref();
1258            builder = builder.parent(&parent).map_err(TauriError::from)?;
1259        }
1260        #[cfg(windows)]
1261        if let Some(owner) = owner.0 {
1262            let owner = owner.get().0.inner_ref();
1263            builder = builder.owner(&owner).map_err(TauriError::from)?;
1264        }
1265        #[cfg(any(
1266            target_os = "linux",
1267            target_os = "dragonfly",
1268            target_os = "freebsd",
1269            target_os = "netbsd",
1270            target_os = "openbsd"
1271        ))]
1272        if let Some(transient_for) = transient_for.0 {
1273            let transient_for = transient_for.get().0.inner_ref();
1274            builder = builder
1275                .transient_for(&transient_for)
1276                .map_err(TauriError::from)?;
1277        }
1278        #[cfg(windows)]
1279        if let Some(drag_and_drop) = drag_and_drop.0 {
1280            builder = builder.drag_and_drop(drag_and_drop);
1281        }
1282        #[cfg(target_os = "macos")]
1283        if let Some(title_bar_style) = title_bar_style.0 {
1284            builder = builder.title_bar_style(title_bar_style.into());
1285        }
1286        #[cfg(target_os = "macos")]
1287        if let Some(traffic_light_position) = traffic_light_position.0 {
1288            let traffic_light_position = traffic_light_position.get().to_tauri(py)?;
1289            builder = builder.traffic_light_position(traffic_light_position);
1290        }
1291        #[cfg(target_os = "macos")]
1292        if let Some(allow_link_preview) = allow_link_preview.0 {
1293            builder = builder.allow_link_preview(allow_link_preview);
1294        }
1295        #[cfg(target_os = "macos")]
1296        if let Some(hidden_title) = hidden_title.0 {
1297            builder = builder.hidden_title(hidden_title);
1298        }
1299        #[cfg(target_os = "macos")]
1300        if let Some(tabbing_identifier) = tabbing_identifier.0 {
1301            builder = builder.tabbing_identifier(&tabbing_identifier);
1302        }
1303        if let Some(effects) = effects.0 {
1304            let effects = effects.into_tauri().build();
1305            builder = builder.effects(effects);
1306        }
1307        if let Some(accept_first_mouse) = accept_first_mouse.0 {
1308            builder = builder.accept_first_mouse(accept_first_mouse);
1309        }
1310        if let Some(initialization_script) = initialization_script.0 {
1311            builder = builder.initialization_script(initialization_script);
1312        }
1313        if let Some(initialization_script_for_all_frames) = initialization_script_for_all_frames.0 {
1314            builder =
1315                builder.initialization_script_for_all_frames(initialization_script_for_all_frames);
1316        }
1317        if let Some(user_agent) = user_agent.0 {
1318            builder = builder.user_agent(&user_agent);
1319        }
1320        if let Some(additional_browser_args) = additional_browser_args.0 {
1321            builder = builder.additional_browser_args(&additional_browser_args);
1322        }
1323        if let Some(data_directory) = data_directory.0 {
1324            builder = builder.data_directory(data_directory);
1325        }
1326        if let Some(true) = disable_drag_drop_handler.0 {
1327            builder = builder.disable_drag_drop_handler();
1328        }
1329        if let Some(true) = enable_clipboard_access.0 {
1330            builder = builder.enable_clipboard_access();
1331        }
1332        if let Some(incognito) = incognito.0 {
1333            builder = builder.incognito(incognito);
1334        }
1335        if let Some(true) = auto_resize.0 {
1336            builder = builder.auto_resize();
1337        }
1338        if let Some(proxy_url) = proxy_url.0 {
1339            builder = builder.proxy_url(proxy_url.into());
1340        }
1341        builder = cfg_impl!(
1342            |any(not(target_os = "macos"), feature = "tauri-macos-private-api")| -> _ {
1343                if let Some(transparent) = transparent.0 {
1344                    builder = builder.transparent(transparent);
1345                }
1346                Ok(builder)
1347            }
1348        )?;
1349        if let Some(zoom_hotkeys_enabled) = zoom_hotkeys_enabled.0 {
1350            builder = builder.zoom_hotkeys_enabled(zoom_hotkeys_enabled);
1351        }
1352        if let Some(browser_extensions_enabled) = browser_extensions_enabled.0 {
1353            builder = builder.browser_extensions_enabled(browser_extensions_enabled);
1354        }
1355        if let Some(extensions_path) = extensions_path.0 {
1356            builder = builder.extensions_path(extensions_path);
1357        }
1358        if let Some(use_https_scheme) = use_https_scheme.0 {
1359            builder = builder.use_https_scheme(use_https_scheme);
1360        }
1361        if let Some(devtools) = devtools.0 {
1362            builder = builder.devtools(devtools);
1363        }
1364        if let Some(background_color) = background_color.0 {
1365            builder = builder.background_color(background_color.0);
1366        }
1367        if let Some(true) = disable_javascript.0 {
1368            builder = builder.disable_javascript();
1369        }
1370
1371        Ok(builder)
1372    }
1373}
1374
1375/// See also: [tauri::webview::WebviewWindowBuilder]
1376#[pyclass(frozen)]
1377#[non_exhaustive]
1378pub struct WebviewWindowBuilder;
1379
1380#[pymethods]
1381impl WebviewWindowBuilder {
1382    #[staticmethod]
1383    #[pyo3(signature = (manager, label, url, /, **kwargs))]
1384    fn build(
1385        py: Python<'_>,
1386        manager: ImplManager,
1387        label: String,
1388        url: &Bound<'_, WebviewUrl>,
1389        kwargs: Option<&Bound<'_, PyDict>>,
1390    ) -> PyResult<WebviewWindow> {
1391        let url = url.get().to_tauri()?;
1392        let args = WebviewWindowBuilderArgs::from_kwargs(kwargs)?;
1393        manager_method_impl!(py, &manager, move |py, manager| {
1394            let mut builder = webview::WebviewWindowBuilder::new(manager, label, url);
1395            if let Some(args) = args {
1396                builder = args.apply_to_builder(py, builder)?;
1397            }
1398
1399            let webview_window = unsafe {
1400                // Safety: `WebviewWindowBuilder` does not hold the GIL, so this is safe
1401                py.allow_threads_unsend(builder, |builder| builder.build())
1402            }
1403            .map_err(TauriError::from)?;
1404
1405            PyResult::Ok(WebviewWindow::new(webview_window))
1406        })?
1407    }
1408
1409    #[staticmethod]
1410    #[pyo3(signature = (manager, config, /, **kwargs))]
1411    fn from_config(
1412        py: Python<'_>,
1413        manager: ImplManager,
1414        config: WindowConfigFrom,
1415        kwargs: Option<&Bound<'_, PyDict>>,
1416    ) -> PyResult<WebviewWindow> {
1417        let config = config.into_inner();
1418        let args = WebviewWindowBuilderArgs::from_kwargs(kwargs)?;
1419        manager_method_impl!(py, &manager, move |py, manager| {
1420            let mut builder = webview::WebviewWindowBuilder::from_config(manager, &config)
1421                .map_err(TauriError::from)?;
1422            if let Some(args) = args {
1423                builder = args.apply_to_builder(py, builder)?;
1424            }
1425
1426            let webview_window = unsafe {
1427                // Safety: `WebviewWindowBuilder` does not hold the GIL, so this is safe
1428                py.allow_threads_unsend(builder, |builder| builder.build())
1429            }
1430            .map_err(TauriError::from)?;
1431
1432            PyResult::Ok(WebviewWindow::new(webview_window))
1433        })?
1434    }
1435}