Skip to main content

tauri_runtime_wry/
lib.rs

1// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5//! The [`wry`] Tauri [`Runtime`].
6//!
7//! None of the exposed API of this crate is stable, and it may break semver
8//! compatibility in the future. The major version only signifies the intended Tauri version.
9
10#![doc(
11  html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png",
12  html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png"
13)]
14
15use self::monitor::MonitorExt;
16use http::Request;
17#[cfg(target_os = "macos")]
18use objc2::ClassType;
19use raw_window_handle::{DisplayHandle, HasDisplayHandle, HasWindowHandle};
20
21#[cfg(windows)]
22use tauri_runtime::webview::ScrollBarStyle;
23use tauri_runtime::{
24  Cookie, DeviceEventFilter, Error, EventLoopProxy, ExitRequestedEventAction, Icon,
25  ProgressBarState, ProgressBarStatus, Result, RunEvent, Runtime, RuntimeHandle, RuntimeInitArgs,
26  UserAttentionType, UserEvent, WebviewDispatch, WebviewEventId, WindowDispatch, WindowEventId,
27  dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
28  monitor::Monitor,
29  webview::{DetachedWebview, DownloadEvent, PendingWebview, WebviewIpcHandler},
30  window::{
31    CursorIcon, DetachedWindow, DetachedWindowWebview, DragDropEvent, PendingWindow, RawWindow,
32    WebviewEvent, WindowBuilder, WindowBuilderBase, WindowEvent, WindowId, WindowSizeConstraints,
33  },
34};
35
36#[cfg(target_vendor = "apple")]
37use objc2::rc::Retained;
38#[cfg(target_os = "android")]
39use tao::platform::android::{WindowBuilderExtAndroid, WindowExtAndroid};
40#[cfg(target_os = "macos")]
41use tao::platform::macos::{EventLoopWindowTargetExtMacOS, WindowBuilderExtMacOS};
42#[cfg(any(
43  target_os = "linux",
44  target_os = "dragonfly",
45  target_os = "freebsd",
46  target_os = "netbsd",
47  target_os = "openbsd"
48))]
49use tao::platform::unix::{WindowBuilderExtUnix, WindowExtUnix};
50#[cfg(windows)]
51use tao::platform::windows::{WindowBuilderExtWindows, WindowExtWindows};
52#[cfg(windows)]
53use webview2_com::{
54  ContainsFullScreenElementChangedEventHandler, FocusChangedEventHandler,
55  Microsoft::Web::WebView2::Win32::ICoreWebView2Controller,
56};
57#[cfg(windows)]
58use windows::Win32::Foundation::HWND;
59#[cfg(target_os = "ios")]
60use wry::WebViewBuilderExtIos;
61#[cfg(target_os = "macos")]
62use wry::WebViewBuilderExtMacos;
63#[cfg(windows)]
64use wry::WebViewBuilderExtWindows;
65#[cfg(target_vendor = "apple")]
66use wry::{WebViewBuilderExtDarwin, WebViewExtDarwin};
67
68use tao::{
69  event::{Event, StartCause, WindowEvent as TaoWindowEvent},
70  event_loop::{
71    ControlFlow, DeviceEventFilter as TaoDeviceEventFilter, EventLoop, EventLoopBuilder,
72    EventLoopProxy as TaoEventLoopProxy, EventLoopWindowTarget,
73  },
74  monitor::MonitorHandle,
75  window::{
76    CursorIcon as TaoCursorIcon, Fullscreen, Icon as TaoWindowIcon,
77    ProgressBarState as TaoProgressBarState, ProgressState as TaoProgressState, Theme as TaoTheme,
78    UserAttentionType as TaoUserAttentionType,
79  },
80};
81#[cfg(target_os = "macos")]
82use tauri_utils::TitleBarStyle;
83use tauri_utils::config::PreventOverflowConfig;
84use tauri_utils::{
85  Theme,
86  config::{Color, WindowConfig},
87};
88use url::Url;
89#[cfg(windows)]
90use wry::ScrollBarStyle as WryScrollBarStyle;
91use wry::{
92  DragDropEvent as WryDragDropEvent, ProxyConfig, ProxyEndpoint, WebContext as WryWebContext,
93  WebView, WebViewBuilder,
94};
95
96pub use tao;
97pub use tao::window::{Window, WindowBuilder as TaoWindowBuilder, WindowId as TaoWindowId};
98pub use wry;
99pub use wry::webview_version;
100
101#[cfg(windows)]
102use wry::WebViewExtWindows;
103#[cfg(target_os = "android")]
104use wry::{
105  WebViewBuilderExtAndroid, WebViewExtAndroid,
106  prelude::{dispatch, find_class},
107};
108#[cfg(not(any(
109  target_os = "windows",
110  target_os = "macos",
111  target_os = "ios",
112  target_os = "android"
113)))]
114use wry::{WebViewBuilderExtUnix, WebViewExtUnix};
115
116#[cfg(target_os = "ios")]
117pub use tao::platform::ios::{WindowBuilderExtIOS, WindowExtIOS};
118#[cfg(target_os = "macos")]
119pub use tao::platform::macos::{
120  ActivationPolicy as TaoActivationPolicy, EventLoopExtMacOS, WindowExtMacOS,
121};
122#[cfg(target_os = "macos")]
123use tauri_runtime::ActivationPolicy;
124
125use std::{
126  cell::RefCell,
127  collections::{
128    BTreeMap, HashMap, HashSet,
129    hash_map::Entry::{Occupied, Vacant},
130  },
131  fmt,
132  ops::Deref,
133  path::PathBuf,
134  rc::Rc,
135  sync::{
136    Arc, Mutex, Weak,
137    atomic::{AtomicBool, AtomicU32, Ordering},
138    mpsc::{Sender, channel},
139  },
140  thread::{ThreadId, current as current_thread},
141};
142
143pub type WebviewId = u32;
144type IpcHandler = dyn Fn(Request<String>) + 'static;
145
146#[cfg(not(debug_assertions))]
147mod dialog;
148mod monitor;
149#[cfg(any(
150  windows,
151  target_os = "linux",
152  target_os = "dragonfly",
153  target_os = "freebsd",
154  target_os = "netbsd",
155  target_os = "openbsd"
156))]
157mod undecorated_resizing;
158mod util;
159mod webview;
160mod webview_permissions;
161mod window;
162
163pub use webview::Webview;
164use window::WindowExt as _;
165
166#[derive(Debug)]
167pub struct WebContext {
168  pub inner: WryWebContext,
169  pub referenced_by_webviews: HashSet<String>,
170  // on Linux the custom protocols are associated with the context
171  // and you cannot register a URI scheme more than once
172  pub registered_custom_protocols: HashSet<String>,
173}
174
175pub type WebContextStore = Arc<Mutex<HashMap<Option<PathBuf>, WebContext>>>;
176// window
177pub type WindowEventHandler = Box<dyn Fn(&WindowEvent) + Send>;
178pub type WindowEventListeners = Arc<Mutex<HashMap<WindowEventId, WindowEventHandler>>>;
179pub type WebviewEventHandler = Box<dyn Fn(&WebviewEvent) + Send>;
180pub type WebviewEventListeners = Arc<Mutex<HashMap<WebviewEventId, WebviewEventHandler>>>;
181
182#[derive(Debug, Clone, Default)]
183pub struct WindowIdStore(Arc<Mutex<HashMap<TaoWindowId, WindowId>>>);
184
185impl WindowIdStore {
186  pub fn insert(&self, w: TaoWindowId, id: WindowId) {
187    self.0.lock().unwrap().insert(w, id);
188  }
189
190  pub fn get(&self, w: &TaoWindowId) -> Option<WindowId> {
191    self.0.lock().unwrap().get(w).copied()
192  }
193}
194
195#[macro_export]
196macro_rules! getter {
197  ($self: ident, $rx: expr, $message: expr) => {{
198    $self.context.send_user_message($message)?;
199    $rx
200      .recv()
201      .map_err(|_| $crate::Error::FailedToReceiveMessage)
202  }};
203}
204
205macro_rules! window_getter {
206  ($self: ident, $message: expr) => {{
207    let (tx, rx) = channel();
208    getter!($self, rx, Message::Window($self.window_id, $message(tx)))
209  }};
210}
211
212macro_rules! event_loop_window_getter {
213  ($self: ident, $message: expr) => {{
214    let (tx, rx) = channel();
215    getter!($self, rx, Message::EventLoopWindowTarget($message(tx)))
216  }};
217}
218
219macro_rules! webview_getter {
220  ($self: ident, $message: expr) => {{
221    let (tx, rx) = channel();
222    getter!(
223      $self,
224      rx,
225      Message::Webview(
226        *$self.window_id.lock().unwrap(),
227        $self.webview_id,
228        $message(tx)
229      )
230    )
231  }};
232}
233
234#[derive(Clone)]
235pub struct Context<T: UserEvent> {
236  pub window_id_map: WindowIdStore,
237  main_thread_id: ThreadId,
238  pub proxy: TaoEventLoopProxy<Message<T>>,
239  main_thread: DispatcherMainThreadContext<T>,
240  plugins: Arc<Mutex<Vec<Box<dyn Plugin<T> + Send>>>>,
241  next_window_id: Arc<AtomicU32>,
242  next_webview_id: Arc<AtomicU32>,
243  next_window_event_id: Arc<AtomicU32>,
244  next_webview_event_id: Arc<AtomicU32>,
245  webview_runtime_installed: bool,
246}
247
248unsafe impl<T: UserEvent> Send for Context<T> {}
249unsafe impl<T: UserEvent> Sync for Context<T> {}
250
251impl<T: UserEvent> Context<T> {
252  pub fn run_threaded<R, F>(&self, f: F) -> R
253  where
254    F: FnOnce(Option<&DispatcherMainThreadContext<T>>) -> R,
255  {
256    f(if current_thread().id() == self.main_thread_id {
257      Some(&self.main_thread)
258    } else {
259      None
260    })
261  }
262
263  fn send_user_message(&self, message: Message<T>) -> Result<()> {
264    if current_thread().id() == self.main_thread_id {
265      handle_user_message(
266        &self.main_thread.window_target,
267        message,
268        UserMessageContext {
269          window_id_map: &self.window_id_map,
270          windows: &self.main_thread.windows,
271        },
272      );
273      Ok(())
274    } else {
275      self
276        .proxy
277        .send_event(message)
278        .map_err(|_| Error::FailedToSendMessage)
279    }
280  }
281
282  fn next_window_id(&self) -> WindowId {
283    self.next_window_id.fetch_add(1, Ordering::Relaxed).into()
284  }
285
286  fn next_webview_id(&self) -> WebviewId {
287    self.next_webview_id.fetch_add(1, Ordering::Relaxed)
288  }
289
290  fn next_window_event_id(&self) -> u32 {
291    self.next_window_event_id.fetch_add(1, Ordering::Relaxed)
292  }
293
294  fn next_webview_event_id(&self) -> u32 {
295    self.next_webview_event_id.fetch_add(1, Ordering::Relaxed)
296  }
297}
298
299impl<T: UserEvent> Context<T> {
300  fn create_window<F: Fn(RawWindow) + Send + 'static>(
301    &self,
302    pending: PendingWindow<T, Wry<T>>,
303    after_window_creation: Option<F>,
304  ) -> Result<DetachedWindow<T, Wry<T>>> {
305    let label = pending.label.clone();
306    let context = self.clone();
307    let window_id = self.next_window_id();
308    let (webview_id, use_https_scheme) = pending
309      .webview
310      .as_ref()
311      .map(|w| {
312        (
313          Some(context.next_webview_id()),
314          w.webview_attributes.use_https_scheme,
315        )
316      })
317      .unwrap_or((None, false));
318
319    let (tx, rx) = channel();
320    self.send_user_message(Message::CreateWindow(
321      window_id,
322      Box::new(move |event_loop| {
323        create_window(
324          window_id,
325          webview_id.unwrap_or_default(),
326          event_loop,
327          &context,
328          pending,
329          after_window_creation,
330        )
331      }),
332      tx,
333    ))?;
334    rx.recv()
335      .map_err(|_| crate::Error::FailedToReceiveMessage)??;
336
337    let dispatcher = WryWindowDispatcher {
338      window_id,
339      context: self.clone(),
340    };
341
342    let detached_webview = webview_id.map(|id| {
343      let webview = DetachedWebview {
344        label: label.clone(),
345        dispatcher: WryWebviewDispatcher {
346          window_id: Arc::new(Mutex::new(window_id)),
347          webview_id: id,
348          context: self.clone(),
349        },
350      };
351      DetachedWindowWebview {
352        webview,
353        use_https_scheme,
354      }
355    });
356
357    Ok(DetachedWindow {
358      id: window_id,
359      label,
360      dispatcher,
361      webview: detached_webview,
362    })
363  }
364
365  fn create_webview(
366    &self,
367    window_id: WindowId,
368    pending: PendingWebview<T, Wry<T>>,
369  ) -> Result<DetachedWebview<T, Wry<T>>> {
370    let label = pending.label.clone();
371    let context = self.clone();
372
373    let webview_id = self.next_webview_id();
374
375    let window_id_wrapper = Arc::new(Mutex::new(window_id));
376    let window_id_wrapper_ = window_id_wrapper.clone();
377
378    let (tx, rx) = channel();
379    self.send_user_message(Message::CreateWebview(
380      window_id,
381      Box::new(move |window, _options| {
382        create_webview(
383          WebviewKind::WindowChild,
384          window,
385          window_id_wrapper_,
386          webview_id,
387          &context,
388          pending,
389          #[cfg(windows)]
390          _options.focused_webview,
391        )
392      }),
393      tx,
394    ))?;
395    rx.recv()
396      .map_err(|_| crate::Error::FailedToReceiveMessage)??;
397
398    let dispatcher = WryWebviewDispatcher {
399      window_id: window_id_wrapper,
400      webview_id,
401      context: self.clone(),
402    };
403
404    Ok(DetachedWebview { label, dispatcher })
405  }
406}
407
408#[cfg(feature = "tracing")]
409#[derive(Clone, Default)]
410pub struct ActiveTraceSpanStore(Rc<RefCell<Vec<ActiveTracingSpan>>>);
411
412// Deliberately does not borrow the inner `RefCell`: formatting can happen re-entrantly
413// while the store is already borrowed (e.g. from an event loop callback),
414// which would panic with "already borrowed".
415#[cfg(feature = "tracing")]
416impl fmt::Debug for ActiveTraceSpanStore {
417  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
418    f.debug_struct("ActiveTraceSpanStore")
419      .finish_non_exhaustive()
420  }
421}
422
423#[cfg(feature = "tracing")]
424impl ActiveTraceSpanStore {
425  pub fn remove_window_draw(&self) {
426    self
427      .0
428      .borrow_mut()
429      .retain(|t| !matches!(t, ActiveTracingSpan::WindowDraw { id: _, span: _ }));
430  }
431}
432
433#[cfg(feature = "tracing")]
434#[derive(Debug)]
435pub enum ActiveTracingSpan {
436  WindowDraw {
437    id: TaoWindowId,
438    span: tracing::span::EnteredSpan,
439  },
440}
441
442pub struct WindowsStore(pub RefCell<BTreeMap<WindowId, WindowWrapper>>);
443
444// Deliberately does not borrow the inner `RefCell`: formatting can happen re-entrantly
445// while the store is already borrowed (e.g. from an event loop callback),
446// which would panic with "already borrowed".
447impl fmt::Debug for WindowsStore {
448  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
449    f.debug_struct("WindowsStore").finish_non_exhaustive()
450  }
451}
452
453#[derive(Debug, Clone)]
454pub struct DispatcherMainThreadContext<T: UserEvent> {
455  pub window_target: EventLoopWindowTarget<Message<T>>,
456  pub web_context: WebContextStore,
457  // changing this to an Rc will cause frequent app crashes.
458  pub windows: Arc<WindowsStore>,
459  #[cfg(feature = "tracing")]
460  pub active_tracing_spans: ActiveTraceSpanStore,
461}
462
463impl<T: UserEvent> fmt::Debug for Context<T> {
464  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
465    f.debug_struct("Context")
466      .field("main_thread_id", &self.main_thread_id)
467      .field("proxy", &self.proxy)
468      .field("main_thread", &self.main_thread)
469      .finish()
470  }
471}
472
473pub struct DeviceEventFilterWrapper(pub TaoDeviceEventFilter);
474
475impl From<DeviceEventFilter> for DeviceEventFilterWrapper {
476  fn from(item: DeviceEventFilter) -> Self {
477    match item {
478      DeviceEventFilter::Always => Self(TaoDeviceEventFilter::Always),
479      DeviceEventFilter::Never => Self(TaoDeviceEventFilter::Never),
480      DeviceEventFilter::Unfocused => Self(TaoDeviceEventFilter::Unfocused),
481    }
482  }
483}
484
485pub struct RectWrapper(pub wry::Rect);
486impl From<tauri_runtime::dpi::Rect> for RectWrapper {
487  fn from(value: tauri_runtime::dpi::Rect) -> Self {
488    RectWrapper(wry::Rect {
489      position: value.position,
490      size: value.size,
491    })
492  }
493}
494
495/// Wrapper around a [`tao::window::Icon`] that can be created from an [`Icon`].
496pub struct TaoIcon(pub TaoWindowIcon);
497
498impl TryFrom<Icon<'_>> for TaoIcon {
499  type Error = Error;
500  fn try_from(icon: Icon<'_>) -> std::result::Result<Self, Self::Error> {
501    TaoWindowIcon::from_rgba(icon.rgba.to_vec(), icon.width, icon.height)
502      .map(Self)
503      .map_err(|e| Error::InvalidIcon(Box::new(e)))
504  }
505}
506
507pub struct WindowEventWrapper(pub Option<WindowEvent>);
508
509impl WindowEventWrapper {
510  fn map_from_tao(event: &TaoWindowEvent<'_>, #[cfg(windows)] window: &WindowWrapper) -> Self {
511    let event = match event {
512      TaoWindowEvent::Resized(size) => WindowEvent::Resized(*size),
513      TaoWindowEvent::Moved(position) => WindowEvent::Moved(*position),
514      TaoWindowEvent::Destroyed => WindowEvent::Destroyed,
515      TaoWindowEvent::ScaleFactorChanged {
516        scale_factor,
517        new_inner_size,
518      } => WindowEvent::ScaleFactorChanged {
519        scale_factor: *scale_factor,
520        new_inner_size: **new_inner_size,
521      },
522      TaoWindowEvent::Focused(focused) => {
523        #[cfg(not(windows))]
524        return Self(Some(WindowEvent::Focused(*focused)));
525        // on multiwebview mode, if there's no focused webview, it means we're receiving a direct window focus change
526        // (without receiving a webview focus, such as when clicking the taskbar app icon or using Alt + Tab)
527        // in this case we must send the focus change event here
528        #[cfg(windows)]
529        if window.has_children.load(Ordering::Relaxed) {
530          if !*focused {
531            // Blur events are handled in the webview side (add_LostFocus)
532            return Self(None);
533          }
534
535          let mut focused_webview = window.focused_webview.lock().unwrap();
536          if let FocusState::Blured {
537            last_focused_webview_label,
538          } = &*focused_webview
539          {
540            let should_focus_webview =
541              last_focused_webview_label
542                .as_deref()
543                .and_then(|last_focused_webview_label| {
544                  window
545                    .webviews
546                    .iter()
547                    .find(|w| w.label == last_focused_webview_label)
548                });
549            *focused_webview = FocusState::WindowFocused;
550            if let Some(should_focus_webview) = should_focus_webview {
551              drop(focused_webview);
552              let _ = should_focus_webview.focus();
553            }
554            WindowEvent::Focused(true)
555          } else {
556            // Already focused
557            return Self(None);
558          }
559        } else if window.webviews.is_empty() {
560          // Raw tao window without webviews, forward the event
561          WindowEvent::Focused(*focused)
562        } else {
563          // when not on multiwebview mode, wry will set focus to the webview,
564          // and we will handle focus change events on the webview (add_GotFocus and add_LostFocus)
565          return Self(None);
566        }
567      }
568      TaoWindowEvent::ThemeChanged(theme) => WindowEvent::ThemeChanged(map_theme(theme)),
569      #[cfg(mobile)]
570      TaoWindowEvent::Suspended => WindowEvent::Suspended,
571      #[cfg(mobile)]
572      TaoWindowEvent::Resumed => WindowEvent::Resumed,
573      _ => return Self(None),
574    };
575    Self(Some(event))
576  }
577
578  fn parse(window: &WindowWrapper, event: &TaoWindowEvent<'_>) -> Self {
579    match event {
580      // resized event from tao doesn't include a reliable size on macOS
581      // because wry replaces the NSView
582      TaoWindowEvent::Resized(_) => {
583        if let Some(w) = &window.inner {
584          let size = inner_size(
585            w,
586            &window.webviews,
587            window.has_children.load(Ordering::Relaxed),
588          );
589          Self(Some(WindowEvent::Resized(size)))
590        } else {
591          Self(None)
592        }
593      }
594      e => Self::map_from_tao(
595        e,
596        #[cfg(windows)]
597        window,
598      ),
599    }
600  }
601}
602
603pub fn map_theme(theme: &TaoTheme) -> Theme {
604  match theme {
605    TaoTheme::Light => Theme::Light,
606    TaoTheme::Dark => Theme::Dark,
607    _ => Theme::Light,
608  }
609}
610
611#[cfg(target_os = "macos")]
612fn tao_activation_policy(activation_policy: ActivationPolicy) -> TaoActivationPolicy {
613  match activation_policy {
614    ActivationPolicy::Regular => TaoActivationPolicy::Regular,
615    ActivationPolicy::Accessory => TaoActivationPolicy::Accessory,
616    ActivationPolicy::Prohibited => TaoActivationPolicy::Prohibited,
617    _ => unimplemented!(),
618  }
619}
620
621pub struct MonitorHandleWrapper(pub MonitorHandle);
622
623impl From<MonitorHandleWrapper> for Monitor {
624  fn from(monitor: MonitorHandleWrapper) -> Monitor {
625    Self {
626      name: monitor.0.name(),
627      position: monitor.0.position(),
628      size: monitor.0.size(),
629      work_area: monitor.0.work_area(),
630      scale_factor: monitor.0.scale_factor(),
631    }
632  }
633}
634
635fn find_monitor_for_position(
636  monitors: impl Iterator<Item = MonitorHandle>,
637  window_position: Position,
638) -> Option<MonitorHandle> {
639  monitors.into_iter().find(|m| {
640    let monitor_pos = m.position();
641    let monitor_size = m.size();
642
643    // type annotations required for 32bit targets.
644    let window_position = window_position.to_physical::<i32>(m.scale_factor());
645
646    monitor_pos.x <= window_position.x
647      && window_position.x < monitor_pos.x + monitor_size.width as i32
648      && monitor_pos.y <= window_position.y
649      && window_position.y < monitor_pos.y + monitor_size.height as i32
650  })
651}
652
653#[derive(Debug, Clone)]
654pub struct UserAttentionTypeWrapper(pub TaoUserAttentionType);
655
656impl From<UserAttentionType> for UserAttentionTypeWrapper {
657  fn from(request_type: UserAttentionType) -> Self {
658    let o = match request_type {
659      UserAttentionType::Critical => TaoUserAttentionType::Critical,
660      UserAttentionType::Informational => TaoUserAttentionType::Informational,
661    };
662    Self(o)
663  }
664}
665
666#[derive(Debug)]
667pub struct CursorIconWrapper(pub TaoCursorIcon);
668
669impl From<CursorIcon> for CursorIconWrapper {
670  fn from(icon: CursorIcon) -> Self {
671    use CursorIcon::*;
672    let i = match icon {
673      Default => TaoCursorIcon::Default,
674      Crosshair => TaoCursorIcon::Crosshair,
675      Hand => TaoCursorIcon::Hand,
676      Arrow => TaoCursorIcon::Arrow,
677      Move => TaoCursorIcon::Move,
678      Text => TaoCursorIcon::Text,
679      Wait => TaoCursorIcon::Wait,
680      Help => TaoCursorIcon::Help,
681      Progress => TaoCursorIcon::Progress,
682      NotAllowed => TaoCursorIcon::NotAllowed,
683      ContextMenu => TaoCursorIcon::ContextMenu,
684      Cell => TaoCursorIcon::Cell,
685      VerticalText => TaoCursorIcon::VerticalText,
686      Alias => TaoCursorIcon::Alias,
687      Copy => TaoCursorIcon::Copy,
688      NoDrop => TaoCursorIcon::NoDrop,
689      Grab => TaoCursorIcon::Grab,
690      Grabbing => TaoCursorIcon::Grabbing,
691      AllScroll => TaoCursorIcon::AllScroll,
692      ZoomIn => TaoCursorIcon::ZoomIn,
693      ZoomOut => TaoCursorIcon::ZoomOut,
694      EResize => TaoCursorIcon::EResize,
695      NResize => TaoCursorIcon::NResize,
696      NeResize => TaoCursorIcon::NeResize,
697      NwResize => TaoCursorIcon::NwResize,
698      SResize => TaoCursorIcon::SResize,
699      SeResize => TaoCursorIcon::SeResize,
700      SwResize => TaoCursorIcon::SwResize,
701      WResize => TaoCursorIcon::WResize,
702      EwResize => TaoCursorIcon::EwResize,
703      NsResize => TaoCursorIcon::NsResize,
704      NeswResize => TaoCursorIcon::NeswResize,
705      NwseResize => TaoCursorIcon::NwseResize,
706      ColResize => TaoCursorIcon::ColResize,
707      RowResize => TaoCursorIcon::RowResize,
708      _ => TaoCursorIcon::Default,
709    };
710    Self(i)
711  }
712}
713
714pub struct ProgressStateWrapper(pub TaoProgressState);
715
716impl From<ProgressBarStatus> for ProgressStateWrapper {
717  fn from(status: ProgressBarStatus) -> Self {
718    let state = match status {
719      ProgressBarStatus::None => TaoProgressState::None,
720      ProgressBarStatus::Normal => TaoProgressState::Normal,
721      ProgressBarStatus::Indeterminate => TaoProgressState::Indeterminate,
722      ProgressBarStatus::Paused => TaoProgressState::Paused,
723      ProgressBarStatus::Error => TaoProgressState::Error,
724    };
725    Self(state)
726  }
727}
728
729pub struct ProgressBarStateWrapper(pub TaoProgressBarState);
730
731impl From<ProgressBarState> for ProgressBarStateWrapper {
732  fn from(progress_state: ProgressBarState) -> Self {
733    Self(TaoProgressBarState {
734      progress: progress_state.progress,
735      state: progress_state
736        .status
737        .map(|state| ProgressStateWrapper::from(state).0),
738      desktop_filename: progress_state.desktop_filename,
739    })
740  }
741}
742
743#[derive(Clone, Default)]
744pub struct WindowBuilderWrapper {
745  inner: TaoWindowBuilder,
746  center: bool,
747  prevent_overflow: Option<Size>,
748  #[cfg(target_os = "macos")]
749  tabbing_identifier: Option<String>,
750}
751
752impl std::fmt::Debug for WindowBuilderWrapper {
753  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
754    let mut s = f.debug_struct("WindowBuilderWrapper");
755    s.field("inner", &self.inner)
756      .field("center", &self.center)
757      .field("prevent_overflow", &self.prevent_overflow);
758    #[cfg(target_os = "macos")]
759    {
760      s.field("tabbing_identifier", &self.tabbing_identifier);
761    }
762    s.finish()
763  }
764}
765
766// SAFETY: this type is `Send` since `menu_items` are read only here
767#[allow(clippy::non_send_fields_in_send_ty)]
768unsafe impl Send for WindowBuilderWrapper {}
769
770impl WindowBuilderBase for WindowBuilderWrapper {}
771impl WindowBuilder for WindowBuilderWrapper {
772  fn new() -> Self {
773    #[allow(unused_mut)]
774    let mut builder = Self::default().focused(true);
775
776    #[cfg(target_os = "macos")]
777    {
778      // TODO: find a proper way to prevent webview being pushed out of the window.
779      // Workaround for issue: https://github.com/tauri-apps/tauri/issues/10225
780      // The window requires `NSFullSizeContentViewWindowMask` flag to prevent devtools
781      // pushing the content view out of the window.
782      // By setting the default style to `TitleBarStyle::Visible` should fix the issue for most of the users.
783      builder = builder.title_bar_style(TitleBarStyle::Visible);
784    }
785
786    builder = builder.title("Tauri App");
787
788    #[cfg(windows)]
789    {
790      builder = builder.window_classname("Tauri Window");
791    }
792
793    builder
794  }
795
796  fn with_config(config: &WindowConfig) -> Self {
797    let mut window = WindowBuilderWrapper::new();
798
799    #[cfg(target_os = "macos")]
800    {
801      window = window
802        .hidden_title(config.hidden_title)
803        .title_bar_style(config.title_bar_style);
804      if let Some(identifier) = &config.tabbing_identifier {
805        window = window.tabbing_identifier(identifier);
806      }
807      if let Some(position) = &config.traffic_light_position {
808        window = window.traffic_light_position(tauri_runtime::dpi::LogicalPosition::new(
809          position.x, position.y,
810        ));
811      }
812    }
813
814    #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
815    {
816      window = window.transparent(config.transparent);
817    }
818    #[cfg(all(
819      target_os = "macos",
820      not(feature = "macos-private-api"),
821      debug_assertions
822    ))]
823    if config.transparent {
824      eprintln!(
825        "The window is set to be transparent but the `macos-private-api` is not enabled.
826        This can be enabled via the `tauri.macOSPrivateApi` configuration property <https://v2.tauri.app/reference/config/#macosprivateapi>
827      ");
828    }
829
830    #[cfg(any(
831      target_os = "linux",
832      target_os = "dragonfly",
833      target_os = "freebsd",
834      target_os = "netbsd",
835      target_os = "openbsd"
836    ))]
837    {
838      // Mouse event is disabled on Linux since sudden event bursts could block event loop.
839      window.inner = window.inner.with_cursor_moved_event(false);
840    }
841
842    #[cfg(target_os = "android")]
843    {
844      if let Some(activity_name) = &config.activity_name {
845        window.inner = window.inner.with_activity_name(activity_name.clone());
846      }
847      if let Some(activity_name) = &config.created_by_activity_name {
848        window.inner = window
849          .inner
850          .with_created_by_activity_name(activity_name.clone());
851      }
852    }
853
854    #[cfg(target_os = "ios")]
855    {
856      if let Some(scene_identifier) = &config.requested_by_scene_identifier {
857        window.inner = window
858          .inner
859          .with_requesting_scene_identifier(scene_identifier.clone());
860      }
861    }
862
863    // ignore size from config for mobile for backward compatibility
864    #[cfg(not(any(target_os = "ios", target_os = "android")))]
865    {
866      window = window.inner_size(config.width, config.height);
867    }
868
869    window = window
870      .title(config.title.to_string())
871      .focused(config.focus)
872      .focusable(config.focusable)
873      .visible(config.visible)
874      .resizable(config.resizable)
875      .fullscreen(config.fullscreen)
876      .decorations(config.decorations)
877      .maximized(config.maximized)
878      .always_on_bottom(config.always_on_bottom)
879      .always_on_top(config.always_on_top)
880      .visible_on_all_workspaces(config.visible_on_all_workspaces)
881      .content_protected(config.content_protected)
882      .skip_taskbar(config.skip_taskbar)
883      .theme(config.theme)
884      .no_redirection_bitmap(config.no_redirection_bitmap)
885      .closable(config.closable)
886      .maximizable(config.maximizable)
887      .minimizable(config.minimizable)
888      .shadow(config.shadow);
889
890    let mut constraints = WindowSizeConstraints::default();
891
892    if let Some(min_width) = config.min_width {
893      constraints.min_width = Some(tao::dpi::LogicalUnit::new(min_width).into());
894    }
895    if let Some(min_height) = config.min_height {
896      constraints.min_height = Some(tao::dpi::LogicalUnit::new(min_height).into());
897    }
898    if let Some(max_width) = config.max_width {
899      constraints.max_width = Some(tao::dpi::LogicalUnit::new(max_width).into());
900    }
901    if let Some(max_height) = config.max_height {
902      constraints.max_height = Some(tao::dpi::LogicalUnit::new(max_height).into());
903    }
904    if let Some(color) = config.background_color {
905      window = window.background_color(color);
906    }
907    window = window.inner_size_constraints(constraints);
908
909    if let (Some(x), Some(y)) = (config.x, config.y) {
910      window = window.position(x, y);
911    }
912
913    if config.center {
914      window = window.center();
915    }
916
917    if let Some(window_classname) = &config.window_classname {
918      window = window.window_classname(window_classname);
919    }
920
921    if let Some(prevent_overflow) = &config.prevent_overflow {
922      window = match prevent_overflow {
923        PreventOverflowConfig::Enable(true) => window.prevent_overflow(),
924        PreventOverflowConfig::Margin(margin) => {
925          window.prevent_overflow_with_margin(PhysicalSize::new(margin.width, margin.height).into())
926        }
927        _ => window,
928      };
929    }
930
931    window
932  }
933
934  fn center(mut self) -> Self {
935    self.center = true;
936    self
937  }
938
939  fn position(mut self, x: f64, y: f64) -> Self {
940    self.inner = self.inner.with_position(LogicalPosition::new(x, y));
941    self
942  }
943
944  fn inner_size(mut self, width: f64, height: f64) -> Self {
945    self.inner = self.inner.with_inner_size(LogicalSize::new(width, height));
946    self
947  }
948
949  fn min_inner_size(mut self, min_width: f64, min_height: f64) -> Self {
950    self.inner = self
951      .inner
952      .with_min_inner_size(LogicalSize::new(min_width, min_height));
953    self
954  }
955
956  fn max_inner_size(mut self, max_width: f64, max_height: f64) -> Self {
957    self.inner = self
958      .inner
959      .with_max_inner_size(LogicalSize::new(max_width, max_height));
960    self
961  }
962
963  fn inner_size_constraints(mut self, constraints: WindowSizeConstraints) -> Self {
964    self.inner.window.inner_size_constraints = tao::window::WindowSizeConstraints {
965      min_width: constraints.min_width,
966      min_height: constraints.min_height,
967      max_width: constraints.max_width,
968      max_height: constraints.max_height,
969    };
970    self
971  }
972
973  /// Prevent the window from overflowing the working area (e.g. monitor size - taskbar size) on creation
974  ///
975  /// ## Platform-specific
976  ///
977  /// - **iOS / Android:** Unsupported.
978  fn prevent_overflow(mut self) -> Self {
979    self
980      .prevent_overflow
981      .replace(PhysicalSize::new(0, 0).into());
982    self
983  }
984
985  /// Prevent the window from overflowing the working area (e.g. monitor size - taskbar size)
986  /// on creation with a margin
987  ///
988  /// ## Platform-specific
989  ///
990  /// - **iOS / Android:** Unsupported.
991  fn prevent_overflow_with_margin(mut self, margin: Size) -> Self {
992    self.prevent_overflow.replace(margin);
993    self
994  }
995
996  fn resizable(mut self, resizable: bool) -> Self {
997    self.inner = self.inner.with_resizable(resizable);
998    self
999  }
1000
1001  fn maximizable(mut self, maximizable: bool) -> Self {
1002    self.inner = self.inner.with_maximizable(maximizable);
1003    self
1004  }
1005
1006  fn minimizable(mut self, minimizable: bool) -> Self {
1007    self.inner = self.inner.with_minimizable(minimizable);
1008    self
1009  }
1010
1011  fn closable(mut self, closable: bool) -> Self {
1012    self.inner = self.inner.with_closable(closable);
1013    self
1014  }
1015
1016  fn title<S: Into<String>>(mut self, title: S) -> Self {
1017    self.inner = self.inner.with_title(title.into());
1018    self
1019  }
1020
1021  fn fullscreen(mut self, fullscreen: bool) -> Self {
1022    self.inner = if fullscreen {
1023      self
1024        .inner
1025        .with_fullscreen(Some(Fullscreen::Borderless(None)))
1026    } else {
1027      self.inner.with_fullscreen(None)
1028    };
1029    self
1030  }
1031
1032  fn focused(mut self, focused: bool) -> Self {
1033    self.inner = self.inner.with_focused(focused);
1034    self
1035  }
1036
1037  fn focusable(mut self, focusable: bool) -> Self {
1038    self.inner = self.inner.with_focusable(focusable);
1039    self
1040  }
1041
1042  fn maximized(mut self, maximized: bool) -> Self {
1043    self.inner = self.inner.with_maximized(maximized);
1044    self
1045  }
1046
1047  fn visible(mut self, visible: bool) -> Self {
1048    self.inner = self.inner.with_visible(visible);
1049    self
1050  }
1051
1052  #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
1053  fn transparent(mut self, transparent: bool) -> Self {
1054    self.inner = self.inner.with_transparent(transparent);
1055    self
1056  }
1057
1058  fn decorations(mut self, decorations: bool) -> Self {
1059    self.inner = self.inner.with_decorations(decorations);
1060    self
1061  }
1062
1063  fn always_on_bottom(mut self, always_on_bottom: bool) -> Self {
1064    self.inner = self.inner.with_always_on_bottom(always_on_bottom);
1065    self
1066  }
1067
1068  fn always_on_top(mut self, always_on_top: bool) -> Self {
1069    self.inner = self.inner.with_always_on_top(always_on_top);
1070    self
1071  }
1072
1073  fn visible_on_all_workspaces(mut self, visible_on_all_workspaces: bool) -> Self {
1074    self.inner = self
1075      .inner
1076      .with_visible_on_all_workspaces(visible_on_all_workspaces);
1077    self
1078  }
1079
1080  fn content_protected(mut self, protected: bool) -> Self {
1081    self.inner = self.inner.with_content_protection(protected);
1082    self
1083  }
1084
1085  fn shadow(#[allow(unused_mut)] mut self, _enable: bool) -> Self {
1086    #[cfg(windows)]
1087    {
1088      self.inner = self.inner.with_undecorated_shadow(_enable);
1089    }
1090    #[cfg(target_os = "macos")]
1091    {
1092      self.inner = self.inner.with_has_shadow(_enable);
1093    }
1094    self
1095  }
1096
1097  #[cfg(windows)]
1098  fn owner(mut self, owner: HWND) -> Self {
1099    self.inner = self.inner.with_owner_window(owner.0 as _);
1100    self
1101  }
1102
1103  #[cfg(windows)]
1104  fn parent(mut self, parent: HWND) -> Self {
1105    self.inner = self.inner.with_parent_window(parent.0 as _);
1106    self
1107  }
1108
1109  #[cfg(target_os = "macos")]
1110  fn parent(mut self, parent: *mut std::ffi::c_void) -> Self {
1111    self.inner = self.inner.with_parent_window(parent);
1112    self
1113  }
1114
1115  #[cfg(any(
1116    target_os = "linux",
1117    target_os = "dragonfly",
1118    target_os = "freebsd",
1119    target_os = "netbsd",
1120    target_os = "openbsd"
1121  ))]
1122  fn transient_for(mut self, parent: &impl gtk::glib::IsA<gtk::Window>) -> Self {
1123    self.inner = self.inner.with_transient_for(parent);
1124    self
1125  }
1126
1127  #[cfg(windows)]
1128  fn drag_and_drop(mut self, enabled: bool) -> Self {
1129    self.inner = self.inner.with_drag_and_drop(enabled);
1130    self
1131  }
1132
1133  #[cfg(target_os = "macos")]
1134  fn title_bar_style(mut self, style: TitleBarStyle) -> Self {
1135    match style {
1136      TitleBarStyle::Visible => {
1137        self.inner = self.inner.with_titlebar_transparent(false);
1138        // Fixes rendering issue when resizing window with devtools open (https://github.com/tauri-apps/tauri/issues/3914)
1139        self.inner = self.inner.with_fullsize_content_view(true);
1140      }
1141      TitleBarStyle::Transparent => {
1142        self.inner = self.inner.with_titlebar_transparent(true);
1143        self.inner = self.inner.with_fullsize_content_view(false);
1144      }
1145      TitleBarStyle::Overlay => {
1146        self.inner = self.inner.with_titlebar_transparent(true);
1147        self.inner = self.inner.with_fullsize_content_view(true);
1148      }
1149      unknown => {
1150        #[cfg(feature = "tracing")]
1151        tracing::warn!("unknown title bar style applied: {unknown}");
1152
1153        #[cfg(not(feature = "tracing"))]
1154        eprintln!("unknown title bar style applied: {unknown}");
1155      }
1156    }
1157    self
1158  }
1159
1160  #[cfg(target_os = "macos")]
1161  fn traffic_light_position<P: Into<Position>>(mut self, position: P) -> Self {
1162    self.inner = self.inner.with_traffic_light_inset(position.into());
1163    self
1164  }
1165
1166  #[cfg(target_os = "macos")]
1167  fn hidden_title(mut self, hidden: bool) -> Self {
1168    self.inner = self.inner.with_title_hidden(hidden);
1169    self
1170  }
1171
1172  #[cfg(target_os = "macos")]
1173  fn tabbing_identifier(mut self, identifier: &str) -> Self {
1174    self.inner = self.inner.with_tabbing_identifier(identifier);
1175    self.tabbing_identifier.replace(identifier.into());
1176    self
1177  }
1178
1179  fn icon(mut self, icon: Icon) -> Result<Self> {
1180    self.inner = self
1181      .inner
1182      .with_window_icon(Some(TaoIcon::try_from(icon)?.0));
1183    Ok(self)
1184  }
1185
1186  fn background_color(mut self, color: Color) -> Self {
1187    self.inner = self.inner.with_background_color(color.into());
1188    self
1189  }
1190
1191  #[cfg(any(
1192    windows,
1193    target_os = "linux",
1194    target_os = "dragonfly",
1195    target_os = "freebsd",
1196    target_os = "netbsd",
1197    target_os = "openbsd"
1198  ))]
1199  fn skip_taskbar(mut self, skip: bool) -> Self {
1200    self.inner = self.inner.with_skip_taskbar(skip);
1201    self
1202  }
1203
1204  #[cfg(any(target_os = "macos", target_os = "ios", target_os = "android"))]
1205  fn skip_taskbar(self, _skip: bool) -> Self {
1206    self
1207  }
1208
1209  fn theme(mut self, theme: Option<Theme>) -> Self {
1210    self.inner = self.inner.with_theme(if let Some(t) = theme {
1211      match t {
1212        Theme::Dark => Some(TaoTheme::Dark),
1213        _ => Some(TaoTheme::Light),
1214      }
1215    } else {
1216      None
1217    });
1218
1219    self
1220  }
1221
1222  fn has_icon(&self) -> bool {
1223    self.inner.window.window_icon.is_some()
1224  }
1225
1226  fn get_theme(&self) -> Option<Theme> {
1227    self.inner.window.preferred_theme.map(|theme| match theme {
1228      TaoTheme::Dark => Theme::Dark,
1229      _ => Theme::Light,
1230    })
1231  }
1232
1233  #[cfg(windows)]
1234  fn window_classname<S: Into<String>>(mut self, window_classname: S) -> Self {
1235    self.inner = self.inner.with_window_classname(window_classname);
1236    self
1237  }
1238  #[cfg(not(windows))]
1239  fn window_classname<S: Into<String>>(self, _window_classname: S) -> Self {
1240    self
1241  }
1242
1243  fn no_redirection_bitmap(#[allow(unused_mut)] mut self, _enable: bool) -> Self {
1244    #[cfg(windows)]
1245    {
1246      self.inner = self.inner.with_no_redirection_bitmap(_enable);
1247    }
1248    self
1249  }
1250
1251  #[cfg(target_os = "android")]
1252  fn activity_name<S: Into<String>>(mut self, class_name: S) -> Self {
1253    self.inner = self.inner.with_activity_name(class_name.into());
1254    self
1255  }
1256
1257  #[cfg(target_os = "android")]
1258  fn created_by_activity_name<S: Into<String>>(mut self, class_name: S) -> Self {
1259    self.inner = self.inner.with_created_by_activity_name(class_name.into());
1260    self
1261  }
1262
1263  #[cfg(target_os = "ios")]
1264  fn requested_by_scene_identifier<S: Into<String>>(mut self, identifier: S) -> Self {
1265    self.inner = self
1266      .inner
1267      .with_requesting_scene_identifier(identifier.into());
1268    self
1269  }
1270}
1271
1272#[cfg(any(
1273  target_os = "linux",
1274  target_os = "dragonfly",
1275  target_os = "freebsd",
1276  target_os = "netbsd",
1277  target_os = "openbsd"
1278))]
1279pub struct GtkWindow(pub gtk::ApplicationWindow);
1280#[cfg(any(
1281  target_os = "linux",
1282  target_os = "dragonfly",
1283  target_os = "freebsd",
1284  target_os = "netbsd",
1285  target_os = "openbsd"
1286))]
1287#[allow(clippy::non_send_fields_in_send_ty)]
1288unsafe impl Send for GtkWindow {}
1289
1290#[cfg(any(
1291  target_os = "linux",
1292  target_os = "dragonfly",
1293  target_os = "freebsd",
1294  target_os = "netbsd",
1295  target_os = "openbsd"
1296))]
1297pub struct GtkBox(pub gtk::Box);
1298#[cfg(any(
1299  target_os = "linux",
1300  target_os = "dragonfly",
1301  target_os = "freebsd",
1302  target_os = "netbsd",
1303  target_os = "openbsd"
1304))]
1305#[allow(clippy::non_send_fields_in_send_ty)]
1306unsafe impl Send for GtkBox {}
1307
1308pub struct SendRawWindowHandle(pub raw_window_handle::RawWindowHandle);
1309unsafe impl Send for SendRawWindowHandle {}
1310
1311pub enum ApplicationMessage {
1312  #[cfg(target_os = "macos")]
1313  Show,
1314  #[cfg(target_os = "macos")]
1315  Hide,
1316  #[cfg(any(target_os = "macos", target_os = "ios"))]
1317  FetchDataStoreIdentifiers(Box<dyn FnOnce(Vec<[u8; 16]>) + Send + 'static>),
1318  #[cfg(any(target_os = "macos", target_os = "ios"))]
1319  RemoveDataStore([u8; 16], Box<dyn FnOnce(Result<()>) + Send + 'static>),
1320}
1321
1322pub enum WindowMessage {
1323  AddEventListener(WindowEventId, Box<dyn Fn(&WindowEvent) + Send>),
1324  // Getters
1325  ScaleFactor(Sender<f64>),
1326  InnerPosition(Sender<Result<PhysicalPosition<i32>>>),
1327  OuterPosition(Sender<Result<PhysicalPosition<i32>>>),
1328  InnerSize(Sender<PhysicalSize<u32>>),
1329  OuterSize(Sender<PhysicalSize<u32>>),
1330  IsFullscreen(Sender<bool>),
1331  IsMinimized(Sender<bool>),
1332  IsMaximized(Sender<bool>),
1333  IsFocused(Sender<bool>),
1334  IsDecorated(Sender<bool>),
1335  IsResizable(Sender<bool>),
1336  IsMaximizable(Sender<bool>),
1337  IsMinimizable(Sender<bool>),
1338  IsClosable(Sender<bool>),
1339  IsVisible(Sender<bool>),
1340  Title(Sender<String>),
1341  // Monitors are converted on the main thread: on iOS tao's `MonitorHandle`
1342  // can only be queried (and dropped) there.
1343  CurrentMonitor(Sender<Option<Monitor>>),
1344  PrimaryMonitor(Sender<Option<Monitor>>),
1345  MonitorFromPoint(Sender<Option<Monitor>>, (f64, f64)),
1346  AvailableMonitors(Sender<Vec<Monitor>>),
1347  #[cfg(any(
1348    target_os = "linux",
1349    target_os = "dragonfly",
1350    target_os = "freebsd",
1351    target_os = "netbsd",
1352    target_os = "openbsd"
1353  ))]
1354  GtkWindow(Sender<GtkWindow>),
1355  #[cfg(any(
1356    target_os = "linux",
1357    target_os = "dragonfly",
1358    target_os = "freebsd",
1359    target_os = "netbsd",
1360    target_os = "openbsd"
1361  ))]
1362  GtkBox(Sender<GtkBox>),
1363  #[cfg(target_os = "android")]
1364  ActivityName(Sender<String>),
1365  #[cfg(target_os = "ios")]
1366  SceneIdentifier(Sender<String>),
1367  RawWindowHandle(Sender<std::result::Result<SendRawWindowHandle, raw_window_handle::HandleError>>),
1368  Theme(Sender<Theme>),
1369  IsEnabled(Sender<bool>),
1370  IsAlwaysOnTop(Sender<bool>),
1371  // Setters
1372  Center,
1373  RequestUserAttention(Option<UserAttentionTypeWrapper>),
1374  SetEnabled(bool),
1375  SetResizable(bool),
1376  SetMaximizable(bool),
1377  SetMinimizable(bool),
1378  SetClosable(bool),
1379  SetTitle(String),
1380  Maximize,
1381  Unmaximize,
1382  Minimize,
1383  Unminimize,
1384  Show,
1385  Hide,
1386  Close,
1387  Destroy,
1388  SetDecorations(bool),
1389  SetShadow(bool),
1390  SetAlwaysOnBottom(bool),
1391  SetAlwaysOnTop(bool),
1392  SetVisibleOnAllWorkspaces(bool),
1393  SetContentProtected(bool),
1394  SetSize(Size),
1395  SetMinSize(Option<Size>),
1396  SetMaxSize(Option<Size>),
1397  SetSizeConstraints(WindowSizeConstraints),
1398  SetPosition(Position),
1399  SetFullscreen(bool),
1400  SetFullscreenOnMonitor(PhysicalPosition<f64>),
1401  #[cfg(target_os = "macos")]
1402  SetSimpleFullscreen(bool),
1403  SetFocus,
1404  SetFocusable(bool),
1405  SetIcon(TaoWindowIcon),
1406  SetSkipTaskbar(bool),
1407  SetCursorGrab(bool),
1408  SetCursorVisible(bool),
1409  SetCursorIcon(CursorIcon),
1410  SetCursorPosition(Position),
1411  SetIgnoreCursorEvents(bool),
1412  SetBadgeCount(Option<i64>, Option<String>),
1413  SetBadgeLabel(Option<String>),
1414  SetOverlayIcon(Option<TaoIcon>),
1415  SetProgressBar(ProgressBarState),
1416  SetTitleBarStyle(tauri_utils::TitleBarStyle),
1417  SetTrafficLightPosition(Position),
1418  SetTheme(Option<Theme>),
1419  SetBackgroundColor(Option<Color>),
1420  DragWindow,
1421  ResizeDragWindow(tauri_runtime::ResizeDirection),
1422  RequestRedraw,
1423}
1424
1425#[derive(Debug, Clone)]
1426pub enum SynthesizedWindowEvent {
1427  Focused(bool),
1428  DragDrop(DragDropEvent),
1429}
1430
1431impl From<SynthesizedWindowEvent> for WindowEventWrapper {
1432  fn from(event: SynthesizedWindowEvent) -> Self {
1433    let event = match event {
1434      SynthesizedWindowEvent::Focused(focused) => WindowEvent::Focused(focused),
1435      SynthesizedWindowEvent::DragDrop(event) => WindowEvent::DragDrop(event),
1436    };
1437    Self(Some(event))
1438  }
1439}
1440
1441pub enum WebviewMessage {
1442  AddEventListener(WebviewEventId, Box<dyn Fn(&WebviewEvent) + Send>),
1443  #[cfg(not(all(feature = "tracing", not(target_os = "android"))))]
1444  EvaluateScript(String),
1445  #[cfg(all(feature = "tracing", not(target_os = "android")))]
1446  EvaluateScript(String, Sender<()>, tracing::Span),
1447  #[cfg(not(all(feature = "tracing", not(target_os = "android"))))]
1448  EvaluateScriptWithCallback(String, Box<dyn Fn(String) + Send + 'static>),
1449  #[cfg(all(feature = "tracing", not(target_os = "android")))]
1450  EvaluateScriptWithCallback(
1451    String,
1452    Box<dyn Fn(String) + Send + 'static>,
1453    Sender<()>,
1454    tracing::Span,
1455  ),
1456  CookiesForUrl(Url, Sender<Result<Vec<tauri_runtime::Cookie<'static>>>>),
1457  Cookies(Sender<Result<Vec<tauri_runtime::Cookie<'static>>>>),
1458  SetCookie(tauri_runtime::Cookie<'static>),
1459  DeleteCookie(tauri_runtime::Cookie<'static>),
1460  WebviewEvent(WebviewEvent),
1461  SynthesizedWindowEvent(SynthesizedWindowEvent),
1462  Navigate(Url),
1463  Reload,
1464  Print,
1465  Close,
1466  Show,
1467  Hide,
1468  SetPosition(Position),
1469  SetSize(Size),
1470  SetBounds(tauri_runtime::dpi::Rect),
1471  SetFocus,
1472  Reparent(WindowId, Sender<Result<()>>),
1473  SetAutoResize(bool),
1474  SetZoom(f64),
1475  SetBackgroundColor(Option<Color>),
1476  ClearAllBrowsingData,
1477  // Getters
1478  Url(Sender<Result<String>>),
1479  Bounds(Sender<Result<tauri_runtime::dpi::Rect>>),
1480  Position(Sender<Result<PhysicalPosition<i32>>>),
1481  Size(Sender<Result<PhysicalSize<u32>>>),
1482  WithWebview(Box<dyn FnOnce(Webview) + Send>),
1483  // Devtools
1484  #[cfg(any(debug_assertions, feature = "devtools"))]
1485  OpenDevTools,
1486  #[cfg(any(debug_assertions, feature = "devtools"))]
1487  CloseDevTools,
1488  #[cfg(any(debug_assertions, feature = "devtools"))]
1489  IsDevToolsOpen(Sender<bool>),
1490}
1491
1492pub enum EventLoopWindowTargetMessage {
1493  CursorPosition(Sender<Result<PhysicalPosition<f64>>>),
1494  // See `WindowMessage::CurrentMonitor`.
1495  PrimaryMonitor(Sender<Option<Monitor>>),
1496  MonitorFromPoint(Sender<Option<Monitor>>, (f64, f64)),
1497  AvailableMonitors(Sender<Vec<Monitor>>),
1498  SetTheme(Option<Theme>),
1499  SetDeviceEventFilter(DeviceEventFilter),
1500}
1501
1502pub type CreateWindowClosure<T> =
1503  Box<dyn FnOnce(&EventLoopWindowTarget<Message<T>>) -> Result<WindowWrapper> + Send>;
1504
1505pub type CreateWebviewClosure =
1506  Box<dyn FnOnce(&Window, CreateWebviewOptions) -> Result<WebviewWrapper> + Send>;
1507
1508pub struct CreateWebviewOptions {
1509  #[cfg(windows)]
1510  pub focused_webview: Arc<Mutex<FocusState>>,
1511}
1512
1513pub enum Message<T: 'static> {
1514  Task(Box<dyn FnOnce() + Send>),
1515  #[cfg(target_os = "macos")]
1516  SetActivationPolicy(ActivationPolicy),
1517  #[cfg(target_os = "macos")]
1518  SetDockVisibility(bool),
1519  RequestExit(i32),
1520  Application(ApplicationMessage),
1521  Window(WindowId, WindowMessage),
1522  Webview(WindowId, WebviewId, WebviewMessage),
1523  EventLoopWindowTarget(EventLoopWindowTargetMessage),
1524  CreateWebview(WindowId, CreateWebviewClosure, Sender<Result<()>>),
1525  CreateWindow(WindowId, CreateWindowClosure<T>, Sender<Result<()>>),
1526  CreateRawWindow(
1527    WindowId,
1528    Box<dyn FnOnce() -> (String, TaoWindowBuilder) + Send>,
1529    Sender<Result<Weak<Window>>>,
1530  ),
1531  UserEvent(T),
1532}
1533
1534impl<T: UserEvent> Clone for Message<T> {
1535  fn clone(&self) -> Self {
1536    match self {
1537      Self::UserEvent(t) => Self::UserEvent(t.clone()),
1538      _ => unimplemented!(),
1539    }
1540  }
1541}
1542
1543/// The Tauri [`WebviewDispatch`] for [`Wry`].
1544#[derive(Debug, Clone)]
1545pub struct WryWebviewDispatcher<T: UserEvent> {
1546  window_id: Arc<Mutex<WindowId>>,
1547  webview_id: WebviewId,
1548  context: Context<T>,
1549}
1550
1551impl<T: UserEvent> WebviewDispatch<T> for WryWebviewDispatcher<T> {
1552  type Runtime = Wry<T>;
1553
1554  fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()> {
1555    self.context.send_user_message(Message::Task(Box::new(f)))
1556  }
1557
1558  fn on_webview_event<F: Fn(&WebviewEvent) + Send + 'static>(&self, f: F) -> WindowEventId {
1559    let id = self.context.next_webview_event_id();
1560    let _ = self.context.proxy.send_event(Message::Webview(
1561      *self.window_id.lock().unwrap(),
1562      self.webview_id,
1563      WebviewMessage::AddEventListener(id, Box::new(f)),
1564    ));
1565    id
1566  }
1567
1568  fn with_webview<F: FnOnce(Box<dyn std::any::Any>) + Send + 'static>(&self, f: F) -> Result<()> {
1569    self.context.send_user_message(Message::Webview(
1570      *self.window_id.lock().unwrap(),
1571      self.webview_id,
1572      WebviewMessage::WithWebview(Box::new(move |webview| f(Box::new(webview)))),
1573    ))
1574  }
1575
1576  #[cfg(any(debug_assertions, feature = "devtools"))]
1577  fn open_devtools(&self) {
1578    let _ = self.context.send_user_message(Message::Webview(
1579      *self.window_id.lock().unwrap(),
1580      self.webview_id,
1581      WebviewMessage::OpenDevTools,
1582    ));
1583  }
1584
1585  #[cfg(any(debug_assertions, feature = "devtools"))]
1586  fn close_devtools(&self) {
1587    let _ = self.context.send_user_message(Message::Webview(
1588      *self.window_id.lock().unwrap(),
1589      self.webview_id,
1590      WebviewMessage::CloseDevTools,
1591    ));
1592  }
1593
1594  /// Gets the devtools window's current open state.
1595  #[cfg(any(debug_assertions, feature = "devtools"))]
1596  fn is_devtools_open(&self) -> Result<bool> {
1597    webview_getter!(self, WebviewMessage::IsDevToolsOpen)
1598  }
1599
1600  // Getters
1601
1602  fn url(&self) -> Result<String> {
1603    webview_getter!(self, WebviewMessage::Url)?
1604  }
1605
1606  fn bounds(&self) -> Result<tauri_runtime::dpi::Rect> {
1607    webview_getter!(self, WebviewMessage::Bounds)?
1608  }
1609
1610  fn position(&self) -> Result<PhysicalPosition<i32>> {
1611    webview_getter!(self, WebviewMessage::Position)?
1612  }
1613
1614  fn size(&self) -> Result<PhysicalSize<u32>> {
1615    webview_getter!(self, WebviewMessage::Size)?
1616  }
1617
1618  // Setters
1619
1620  fn navigate(&self, url: Url) -> Result<()> {
1621    self.context.send_user_message(Message::Webview(
1622      *self.window_id.lock().unwrap(),
1623      self.webview_id,
1624      WebviewMessage::Navigate(url),
1625    ))
1626  }
1627
1628  fn reload(&self) -> Result<()> {
1629    self.context.send_user_message(Message::Webview(
1630      *self.window_id.lock().unwrap(),
1631      self.webview_id,
1632      WebviewMessage::Reload,
1633    ))
1634  }
1635
1636  fn print(&self) -> Result<()> {
1637    self.context.send_user_message(Message::Webview(
1638      *self.window_id.lock().unwrap(),
1639      self.webview_id,
1640      WebviewMessage::Print,
1641    ))
1642  }
1643
1644  fn close(&self) -> Result<()> {
1645    self.context.send_user_message(Message::Webview(
1646      *self.window_id.lock().unwrap(),
1647      self.webview_id,
1648      WebviewMessage::Close,
1649    ))
1650  }
1651
1652  fn set_bounds(&self, bounds: tauri_runtime::dpi::Rect) -> Result<()> {
1653    self.context.send_user_message(Message::Webview(
1654      *self.window_id.lock().unwrap(),
1655      self.webview_id,
1656      WebviewMessage::SetBounds(bounds),
1657    ))
1658  }
1659
1660  fn set_size(&self, size: Size) -> Result<()> {
1661    self.context.send_user_message(Message::Webview(
1662      *self.window_id.lock().unwrap(),
1663      self.webview_id,
1664      WebviewMessage::SetSize(size),
1665    ))
1666  }
1667
1668  fn set_position(&self, position: Position) -> Result<()> {
1669    self.context.send_user_message(Message::Webview(
1670      *self.window_id.lock().unwrap(),
1671      self.webview_id,
1672      WebviewMessage::SetPosition(position),
1673    ))
1674  }
1675
1676  fn set_focus(&self) -> Result<()> {
1677    self.context.send_user_message(Message::Webview(
1678      *self.window_id.lock().unwrap(),
1679      self.webview_id,
1680      WebviewMessage::SetFocus,
1681    ))
1682  }
1683
1684  fn reparent(&self, window_id: WindowId) -> Result<()> {
1685    let mut current_window_id = self.window_id.lock().unwrap();
1686    let (tx, rx) = channel();
1687    self.context.send_user_message(Message::Webview(
1688      *current_window_id,
1689      self.webview_id,
1690      WebviewMessage::Reparent(window_id, tx),
1691    ))?;
1692
1693    rx.recv().unwrap()?;
1694
1695    *current_window_id = window_id;
1696    Ok(())
1697  }
1698
1699  fn cookies_for_url(&self, url: Url) -> Result<Vec<Cookie<'static>>> {
1700    let (tx, rx) = channel();
1701    self.context.send_user_message(Message::Webview(
1702      *self.window_id.lock().unwrap(),
1703      self.webview_id,
1704      WebviewMessage::CookiesForUrl(url, tx),
1705    ))?;
1706
1707    rx.recv().unwrap()
1708  }
1709
1710  fn cookies(&self) -> Result<Vec<Cookie<'static>>> {
1711    webview_getter!(self, WebviewMessage::Cookies)?
1712  }
1713
1714  fn set_cookie(&self, cookie: Cookie<'_>) -> Result<()> {
1715    self.context.send_user_message(Message::Webview(
1716      *self.window_id.lock().unwrap(),
1717      self.webview_id,
1718      WebviewMessage::SetCookie(cookie.into_owned()),
1719    ))?;
1720    Ok(())
1721  }
1722
1723  fn delete_cookie(&self, cookie: Cookie<'_>) -> Result<()> {
1724    self.context.send_user_message(Message::Webview(
1725      *self.window_id.lock().unwrap(),
1726      self.webview_id,
1727      WebviewMessage::DeleteCookie(cookie.into_owned()),
1728    ))?;
1729    Ok(())
1730  }
1731
1732  fn set_auto_resize(&self, auto_resize: bool) -> Result<()> {
1733    self.context.send_user_message(Message::Webview(
1734      *self.window_id.lock().unwrap(),
1735      self.webview_id,
1736      WebviewMessage::SetAutoResize(auto_resize),
1737    ))
1738  }
1739
1740  #[cfg(all(feature = "tracing", not(target_os = "android")))]
1741  fn eval_script<S: Into<String>>(&self, script: S) -> Result<()> {
1742    // use a channel so the EvaluateScript task uses the current span as parent
1743    let (tx, rx) = channel();
1744    getter!(
1745      self,
1746      rx,
1747      Message::Webview(
1748        *self.window_id.lock().unwrap(),
1749        self.webview_id,
1750        WebviewMessage::EvaluateScript(script.into(), tx, tracing::Span::current()),
1751      )
1752    )
1753  }
1754
1755  #[cfg(not(all(feature = "tracing", not(target_os = "android"))))]
1756  fn eval_script<S: Into<String>>(&self, script: S) -> Result<()> {
1757    self.context.send_user_message(Message::Webview(
1758      *self.window_id.lock().unwrap(),
1759      self.webview_id,
1760      WebviewMessage::EvaluateScript(script.into()),
1761    ))
1762  }
1763
1764  #[cfg(all(feature = "tracing", not(target_os = "android")))]
1765  fn eval_script_with_callback<S: Into<String>>(
1766    &self,
1767    script: S,
1768    callback: impl Fn(String) + Send + 'static,
1769  ) -> Result<()> {
1770    // use a channel so the EvaluateScript task uses the current span as parent
1771    let (tx, rx) = channel();
1772    getter!(
1773      self,
1774      rx,
1775      Message::Webview(
1776        *self.window_id.lock().unwrap(),
1777        self.webview_id,
1778        WebviewMessage::EvaluateScriptWithCallback(
1779          script.into(),
1780          Box::new(callback),
1781          tx,
1782          tracing::Span::current(),
1783        ),
1784      )
1785    )
1786  }
1787
1788  #[cfg(not(all(feature = "tracing", not(target_os = "android"))))]
1789  fn eval_script_with_callback<S: Into<String>>(
1790    &self,
1791    script: S,
1792    callback: impl Fn(String) + Send + 'static,
1793  ) -> Result<()> {
1794    self.context.send_user_message(Message::Webview(
1795      *self.window_id.lock().unwrap(),
1796      self.webview_id,
1797      WebviewMessage::EvaluateScriptWithCallback(script.into(), Box::new(callback)),
1798    ))
1799  }
1800
1801  fn set_zoom(&self, scale_factor: f64) -> Result<()> {
1802    self.context.send_user_message(Message::Webview(
1803      *self.window_id.lock().unwrap(),
1804      self.webview_id,
1805      WebviewMessage::SetZoom(scale_factor),
1806    ))
1807  }
1808
1809  fn clear_all_browsing_data(&self) -> Result<()> {
1810    self.context.send_user_message(Message::Webview(
1811      *self.window_id.lock().unwrap(),
1812      self.webview_id,
1813      WebviewMessage::ClearAllBrowsingData,
1814    ))
1815  }
1816
1817  fn hide(&self) -> Result<()> {
1818    self.context.send_user_message(Message::Webview(
1819      *self.window_id.lock().unwrap(),
1820      self.webview_id,
1821      WebviewMessage::Hide,
1822    ))
1823  }
1824
1825  fn show(&self) -> Result<()> {
1826    self.context.send_user_message(Message::Webview(
1827      *self.window_id.lock().unwrap(),
1828      self.webview_id,
1829      WebviewMessage::Show,
1830    ))
1831  }
1832
1833  fn set_background_color(&self, color: Option<Color>) -> Result<()> {
1834    self.context.send_user_message(Message::Webview(
1835      *self.window_id.lock().unwrap(),
1836      self.webview_id,
1837      WebviewMessage::SetBackgroundColor(color),
1838    ))
1839  }
1840}
1841
1842/// The Tauri [`WindowDispatch`] for [`Wry`].
1843#[derive(Debug, Clone)]
1844pub struct WryWindowDispatcher<T: UserEvent> {
1845  window_id: WindowId,
1846  context: Context<T>,
1847}
1848
1849// SAFETY: this is safe since the `Context` usage is guarded by `Context::send_user_message`.
1850#[allow(clippy::non_send_fields_in_send_ty)]
1851unsafe impl<T: UserEvent> Sync for WryWindowDispatcher<T> {}
1852
1853fn get_raw_window_handle<T: UserEvent>(
1854  dispatcher: &WryWindowDispatcher<T>,
1855) -> Result<std::result::Result<SendRawWindowHandle, raw_window_handle::HandleError>> {
1856  window_getter!(dispatcher, WindowMessage::RawWindowHandle)
1857}
1858
1859impl<T: UserEvent> WindowDispatch<T> for WryWindowDispatcher<T> {
1860  type Runtime = Wry<T>;
1861  type WindowBuilder = WindowBuilderWrapper;
1862
1863  fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()> {
1864    self.context.send_user_message(Message::Task(Box::new(f)))
1865  }
1866
1867  fn on_window_event<F: Fn(&WindowEvent) + Send + 'static>(&self, f: F) -> WindowEventId {
1868    let id = self.context.next_window_event_id();
1869    let _ = self.context.proxy.send_event(Message::Window(
1870      self.window_id,
1871      WindowMessage::AddEventListener(id, Box::new(f)),
1872    ));
1873    id
1874  }
1875
1876  // Getters
1877
1878  fn scale_factor(&self) -> Result<f64> {
1879    window_getter!(self, WindowMessage::ScaleFactor)
1880  }
1881
1882  fn inner_position(&self) -> Result<PhysicalPosition<i32>> {
1883    window_getter!(self, WindowMessage::InnerPosition)?
1884  }
1885
1886  fn outer_position(&self) -> Result<PhysicalPosition<i32>> {
1887    window_getter!(self, WindowMessage::OuterPosition)?
1888  }
1889
1890  fn inner_size(&self) -> Result<PhysicalSize<u32>> {
1891    window_getter!(self, WindowMessage::InnerSize)
1892  }
1893
1894  fn outer_size(&self) -> Result<PhysicalSize<u32>> {
1895    window_getter!(self, WindowMessage::OuterSize)
1896  }
1897
1898  fn is_fullscreen(&self) -> Result<bool> {
1899    window_getter!(self, WindowMessage::IsFullscreen)
1900  }
1901
1902  fn is_minimized(&self) -> Result<bool> {
1903    window_getter!(self, WindowMessage::IsMinimized)
1904  }
1905
1906  fn is_maximized(&self) -> Result<bool> {
1907    window_getter!(self, WindowMessage::IsMaximized)
1908  }
1909
1910  fn is_focused(&self) -> Result<bool> {
1911    window_getter!(self, WindowMessage::IsFocused)
1912  }
1913
1914  /// Gets the window's current decoration state.
1915  fn is_decorated(&self) -> Result<bool> {
1916    window_getter!(self, WindowMessage::IsDecorated)
1917  }
1918
1919  /// Gets the window's current resizable state.
1920  fn is_resizable(&self) -> Result<bool> {
1921    window_getter!(self, WindowMessage::IsResizable)
1922  }
1923
1924  /// Gets the current native window's maximize button state
1925  fn is_maximizable(&self) -> Result<bool> {
1926    window_getter!(self, WindowMessage::IsMaximizable)
1927  }
1928
1929  /// Gets the current native window's minimize button state
1930  fn is_minimizable(&self) -> Result<bool> {
1931    window_getter!(self, WindowMessage::IsMinimizable)
1932  }
1933
1934  /// Gets the current native window's close button state
1935  fn is_closable(&self) -> Result<bool> {
1936    window_getter!(self, WindowMessage::IsClosable)
1937  }
1938
1939  fn is_visible(&self) -> Result<bool> {
1940    window_getter!(self, WindowMessage::IsVisible)
1941  }
1942
1943  fn title(&self) -> Result<String> {
1944    window_getter!(self, WindowMessage::Title)
1945  }
1946
1947  fn current_monitor(&self) -> Result<Option<Monitor>> {
1948    window_getter!(self, WindowMessage::CurrentMonitor)
1949  }
1950
1951  fn primary_monitor(&self) -> Result<Option<Monitor>> {
1952    window_getter!(self, WindowMessage::PrimaryMonitor)
1953  }
1954
1955  fn monitor_from_point(&self, x: f64, y: f64) -> Result<Option<Monitor>> {
1956    let (tx, rx) = channel();
1957
1958    let _ = self.context.send_user_message(Message::Window(
1959      self.window_id,
1960      WindowMessage::MonitorFromPoint(tx, (x, y)),
1961    ));
1962
1963    rx.recv().map_err(|_| crate::Error::FailedToReceiveMessage)
1964  }
1965
1966  fn available_monitors(&self) -> Result<Vec<Monitor>> {
1967    window_getter!(self, WindowMessage::AvailableMonitors)
1968  }
1969
1970  fn theme(&self) -> Result<Theme> {
1971    window_getter!(self, WindowMessage::Theme)
1972  }
1973
1974  fn is_enabled(&self) -> Result<bool> {
1975    window_getter!(self, WindowMessage::IsEnabled)
1976  }
1977
1978  fn is_always_on_top(&self) -> Result<bool> {
1979    window_getter!(self, WindowMessage::IsAlwaysOnTop)
1980  }
1981
1982  #[cfg(any(
1983    target_os = "linux",
1984    target_os = "dragonfly",
1985    target_os = "freebsd",
1986    target_os = "netbsd",
1987    target_os = "openbsd"
1988  ))]
1989  fn gtk_window(&self) -> Result<gtk::ApplicationWindow> {
1990    window_getter!(self, WindowMessage::GtkWindow).map(|w| w.0)
1991  }
1992
1993  #[cfg(any(
1994    target_os = "linux",
1995    target_os = "dragonfly",
1996    target_os = "freebsd",
1997    target_os = "netbsd",
1998    target_os = "openbsd"
1999  ))]
2000  fn default_vbox(&self) -> Result<gtk::Box> {
2001    window_getter!(self, WindowMessage::GtkBox).map(|w| w.0)
2002  }
2003
2004  /// Returns the name of the Android activity associated with this window.
2005  #[cfg(target_os = "android")]
2006  fn activity_name(&self) -> Result<String> {
2007    window_getter!(self, WindowMessage::ActivityName)
2008  }
2009
2010  /// Returns the identifier of the UIScene tied to this UIWindow.
2011  #[cfg(target_os = "ios")]
2012  fn scene_identifier(&self) -> Result<String> {
2013    window_getter!(self, WindowMessage::SceneIdentifier)
2014  }
2015
2016  fn window_handle(
2017    &self,
2018  ) -> std::result::Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
2019    get_raw_window_handle(self)
2020      .map_err(|_| raw_window_handle::HandleError::Unavailable)
2021      .and_then(|r| r.map(|h| unsafe { raw_window_handle::WindowHandle::borrow_raw(h.0) }))
2022  }
2023
2024  // Setters
2025
2026  fn center(&self) -> Result<()> {
2027    self
2028      .context
2029      .send_user_message(Message::Window(self.window_id, WindowMessage::Center))
2030  }
2031
2032  fn request_user_attention(&self, request_type: Option<UserAttentionType>) -> Result<()> {
2033    self.context.send_user_message(Message::Window(
2034      self.window_id,
2035      WindowMessage::RequestUserAttention(request_type.map(Into::into)),
2036    ))
2037  }
2038
2039  // Creates a window by dispatching a message to the event loop.
2040  // Note that this must be called from a separate thread, otherwise the channel will introduce a deadlock.
2041  fn create_window<F: Fn(RawWindow) + Send + 'static>(
2042    &mut self,
2043    pending: PendingWindow<T, Self::Runtime>,
2044    after_window_creation: Option<F>,
2045  ) -> Result<DetachedWindow<T, Self::Runtime>> {
2046    self.context.create_window(pending, after_window_creation)
2047  }
2048
2049  // Creates a webview by dispatching a message to the event loop.
2050  // Note that this must be called from a separate thread, otherwise the channel will introduce a deadlock.
2051  fn create_webview(
2052    &mut self,
2053    pending: PendingWebview<T, Self::Runtime>,
2054  ) -> Result<DetachedWebview<T, Self::Runtime>> {
2055    self.context.create_webview(self.window_id, pending)
2056  }
2057
2058  fn set_resizable(&self, resizable: bool) -> Result<()> {
2059    self.context.send_user_message(Message::Window(
2060      self.window_id,
2061      WindowMessage::SetResizable(resizable),
2062    ))
2063  }
2064
2065  fn set_enabled(&self, enabled: bool) -> Result<()> {
2066    self.context.send_user_message(Message::Window(
2067      self.window_id,
2068      WindowMessage::SetEnabled(enabled),
2069    ))
2070  }
2071
2072  fn set_maximizable(&self, maximizable: bool) -> Result<()> {
2073    self.context.send_user_message(Message::Window(
2074      self.window_id,
2075      WindowMessage::SetMaximizable(maximizable),
2076    ))
2077  }
2078
2079  fn set_minimizable(&self, minimizable: bool) -> Result<()> {
2080    self.context.send_user_message(Message::Window(
2081      self.window_id,
2082      WindowMessage::SetMinimizable(minimizable),
2083    ))
2084  }
2085
2086  fn set_closable(&self, closable: bool) -> Result<()> {
2087    self.context.send_user_message(Message::Window(
2088      self.window_id,
2089      WindowMessage::SetClosable(closable),
2090    ))
2091  }
2092
2093  fn set_title<S: Into<String>>(&self, title: S) -> Result<()> {
2094    self.context.send_user_message(Message::Window(
2095      self.window_id,
2096      WindowMessage::SetTitle(title.into()),
2097    ))
2098  }
2099
2100  fn maximize(&self) -> Result<()> {
2101    self
2102      .context
2103      .send_user_message(Message::Window(self.window_id, WindowMessage::Maximize))
2104  }
2105
2106  fn unmaximize(&self) -> Result<()> {
2107    self
2108      .context
2109      .send_user_message(Message::Window(self.window_id, WindowMessage::Unmaximize))
2110  }
2111
2112  fn minimize(&self) -> Result<()> {
2113    self
2114      .context
2115      .send_user_message(Message::Window(self.window_id, WindowMessage::Minimize))
2116  }
2117
2118  fn unminimize(&self) -> Result<()> {
2119    self
2120      .context
2121      .send_user_message(Message::Window(self.window_id, WindowMessage::Unminimize))
2122  }
2123
2124  fn show(&self) -> Result<()> {
2125    self
2126      .context
2127      .send_user_message(Message::Window(self.window_id, WindowMessage::Show))
2128  }
2129
2130  fn hide(&self) -> Result<()> {
2131    self
2132      .context
2133      .send_user_message(Message::Window(self.window_id, WindowMessage::Hide))
2134  }
2135
2136  fn close(&self) -> Result<()> {
2137    // NOTE: close cannot use `Context::send_user_message` because it accesses the event loop callback
2138    self
2139      .context
2140      .proxy
2141      .send_event(Message::Window(self.window_id, WindowMessage::Close))
2142      .map_err(|_| Error::FailedToSendMessage)
2143  }
2144
2145  fn destroy(&self) -> Result<()> {
2146    // NOTE: destroy cannot use `Context::send_user_message` because it accesses the event loop callback
2147    self
2148      .context
2149      .proxy
2150      .send_event(Message::Window(self.window_id, WindowMessage::Destroy))
2151      .map_err(|_| Error::FailedToSendMessage)
2152  }
2153
2154  fn set_decorations(&self, decorations: bool) -> Result<()> {
2155    self.context.send_user_message(Message::Window(
2156      self.window_id,
2157      WindowMessage::SetDecorations(decorations),
2158    ))
2159  }
2160
2161  fn set_shadow(&self, enable: bool) -> Result<()> {
2162    self.context.send_user_message(Message::Window(
2163      self.window_id,
2164      WindowMessage::SetShadow(enable),
2165    ))
2166  }
2167
2168  fn set_always_on_bottom(&self, always_on_bottom: bool) -> Result<()> {
2169    self.context.send_user_message(Message::Window(
2170      self.window_id,
2171      WindowMessage::SetAlwaysOnBottom(always_on_bottom),
2172    ))
2173  }
2174
2175  fn set_always_on_top(&self, always_on_top: bool) -> Result<()> {
2176    self.context.send_user_message(Message::Window(
2177      self.window_id,
2178      WindowMessage::SetAlwaysOnTop(always_on_top),
2179    ))
2180  }
2181
2182  fn set_visible_on_all_workspaces(&self, visible_on_all_workspaces: bool) -> Result<()> {
2183    self.context.send_user_message(Message::Window(
2184      self.window_id,
2185      WindowMessage::SetVisibleOnAllWorkspaces(visible_on_all_workspaces),
2186    ))
2187  }
2188
2189  fn set_content_protected(&self, protected: bool) -> Result<()> {
2190    self.context.send_user_message(Message::Window(
2191      self.window_id,
2192      WindowMessage::SetContentProtected(protected),
2193    ))
2194  }
2195
2196  fn set_size(&self, size: Size) -> Result<()> {
2197    self.context.send_user_message(Message::Window(
2198      self.window_id,
2199      WindowMessage::SetSize(size),
2200    ))
2201  }
2202
2203  fn set_min_size(&self, size: Option<Size>) -> Result<()> {
2204    self.context.send_user_message(Message::Window(
2205      self.window_id,
2206      WindowMessage::SetMinSize(size),
2207    ))
2208  }
2209
2210  fn set_max_size(&self, size: Option<Size>) -> Result<()> {
2211    self.context.send_user_message(Message::Window(
2212      self.window_id,
2213      WindowMessage::SetMaxSize(size),
2214    ))
2215  }
2216
2217  fn set_size_constraints(&self, constraints: WindowSizeConstraints) -> Result<()> {
2218    self.context.send_user_message(Message::Window(
2219      self.window_id,
2220      WindowMessage::SetSizeConstraints(constraints),
2221    ))
2222  }
2223
2224  fn set_position(&self, position: Position) -> Result<()> {
2225    self.context.send_user_message(Message::Window(
2226      self.window_id,
2227      WindowMessage::SetPosition(position),
2228    ))
2229  }
2230
2231  fn set_fullscreen_on_monitor(&self, position: PhysicalPosition<f64>) -> Result<()> {
2232    self.context.send_user_message(Message::Window(
2233      self.window_id,
2234      WindowMessage::SetFullscreenOnMonitor(position),
2235    ))
2236  }
2237
2238  fn set_fullscreen(&self, fullscreen: bool) -> Result<()> {
2239    self.context.send_user_message(Message::Window(
2240      self.window_id,
2241      WindowMessage::SetFullscreen(fullscreen),
2242    ))
2243  }
2244
2245  #[cfg(target_os = "macos")]
2246  fn set_simple_fullscreen(&self, enable: bool) -> Result<()> {
2247    self.context.send_user_message(Message::Window(
2248      self.window_id,
2249      WindowMessage::SetSimpleFullscreen(enable),
2250    ))
2251  }
2252
2253  fn set_focus(&self) -> Result<()> {
2254    self
2255      .context
2256      .send_user_message(Message::Window(self.window_id, WindowMessage::SetFocus))
2257  }
2258
2259  fn set_focusable(&self, focusable: bool) -> Result<()> {
2260    self.context.send_user_message(Message::Window(
2261      self.window_id,
2262      WindowMessage::SetFocusable(focusable),
2263    ))
2264  }
2265
2266  fn set_icon(&self, icon: Icon) -> Result<()> {
2267    self.context.send_user_message(Message::Window(
2268      self.window_id,
2269      WindowMessage::SetIcon(TaoIcon::try_from(icon)?.0),
2270    ))
2271  }
2272
2273  fn set_skip_taskbar(&self, skip: bool) -> Result<()> {
2274    self.context.send_user_message(Message::Window(
2275      self.window_id,
2276      WindowMessage::SetSkipTaskbar(skip),
2277    ))
2278  }
2279
2280  fn set_cursor_grab(&self, grab: bool) -> crate::Result<()> {
2281    self.context.send_user_message(Message::Window(
2282      self.window_id,
2283      WindowMessage::SetCursorGrab(grab),
2284    ))
2285  }
2286
2287  fn set_cursor_visible(&self, visible: bool) -> crate::Result<()> {
2288    self.context.send_user_message(Message::Window(
2289      self.window_id,
2290      WindowMessage::SetCursorVisible(visible),
2291    ))
2292  }
2293
2294  fn set_cursor_icon(&self, icon: CursorIcon) -> crate::Result<()> {
2295    self.context.send_user_message(Message::Window(
2296      self.window_id,
2297      WindowMessage::SetCursorIcon(icon),
2298    ))
2299  }
2300
2301  fn set_cursor_position<Pos: Into<Position>>(&self, position: Pos) -> crate::Result<()> {
2302    self.context.send_user_message(Message::Window(
2303      self.window_id,
2304      WindowMessage::SetCursorPosition(position.into()),
2305    ))
2306  }
2307
2308  fn set_ignore_cursor_events(&self, ignore: bool) -> crate::Result<()> {
2309    self.context.send_user_message(Message::Window(
2310      self.window_id,
2311      WindowMessage::SetIgnoreCursorEvents(ignore),
2312    ))
2313  }
2314
2315  fn start_dragging(&self) -> Result<()> {
2316    self
2317      .context
2318      .send_user_message(Message::Window(self.window_id, WindowMessage::DragWindow))
2319  }
2320
2321  fn start_resize_dragging(&self, direction: tauri_runtime::ResizeDirection) -> Result<()> {
2322    self.context.send_user_message(Message::Window(
2323      self.window_id,
2324      WindowMessage::ResizeDragWindow(direction),
2325    ))
2326  }
2327
2328  fn set_badge_count(&self, count: Option<i64>, desktop_filename: Option<String>) -> Result<()> {
2329    self.context.send_user_message(Message::Window(
2330      self.window_id,
2331      WindowMessage::SetBadgeCount(count, desktop_filename),
2332    ))
2333  }
2334
2335  fn set_badge_label(&self, label: Option<String>) -> Result<()> {
2336    self.context.send_user_message(Message::Window(
2337      self.window_id,
2338      WindowMessage::SetBadgeLabel(label),
2339    ))
2340  }
2341
2342  fn set_overlay_icon(&self, icon: Option<Icon>) -> Result<()> {
2343    let icon: Result<Option<TaoIcon>> = icon.map_or(Ok(None), |x| Ok(Some(TaoIcon::try_from(x)?)));
2344
2345    self.context.send_user_message(Message::Window(
2346      self.window_id,
2347      WindowMessage::SetOverlayIcon(icon?),
2348    ))
2349  }
2350
2351  fn set_progress_bar(&self, progress_state: ProgressBarState) -> Result<()> {
2352    self.context.send_user_message(Message::Window(
2353      self.window_id,
2354      WindowMessage::SetProgressBar(progress_state),
2355    ))
2356  }
2357
2358  fn set_title_bar_style(&self, style: tauri_utils::TitleBarStyle) -> Result<()> {
2359    self.context.send_user_message(Message::Window(
2360      self.window_id,
2361      WindowMessage::SetTitleBarStyle(style),
2362    ))
2363  }
2364
2365  fn set_traffic_light_position(&self, position: Position) -> Result<()> {
2366    self.context.send_user_message(Message::Window(
2367      self.window_id,
2368      WindowMessage::SetTrafficLightPosition(position),
2369    ))
2370  }
2371
2372  fn set_theme(&self, theme: Option<Theme>) -> Result<()> {
2373    self.context.send_user_message(Message::Window(
2374      self.window_id,
2375      WindowMessage::SetTheme(theme),
2376    ))
2377  }
2378
2379  fn set_background_color(&self, color: Option<Color>) -> Result<()> {
2380    self.context.send_user_message(Message::Window(
2381      self.window_id,
2382      WindowMessage::SetBackgroundColor(color),
2383    ))
2384  }
2385}
2386
2387#[derive(Clone)]
2388pub struct WebviewWrapper {
2389  label: String,
2390  id: WebviewId,
2391  inner: Rc<WebView>,
2392  context_store: WebContextStore,
2393  webview_event_listeners: WebviewEventListeners,
2394  // the key of the WebContext if it's not shared
2395  context_key: Option<PathBuf>,
2396  bounds: Arc<Mutex<Option<WebviewBounds>>>,
2397}
2398
2399impl Deref for WebviewWrapper {
2400  type Target = WebView;
2401
2402  #[inline(always)]
2403  fn deref(&self) -> &Self::Target {
2404    &self.inner
2405  }
2406}
2407
2408impl Drop for WebviewWrapper {
2409  fn drop(&mut self) {
2410    if Rc::get_mut(&mut self.inner).is_some() {
2411      let mut context_store = self.context_store.lock().unwrap();
2412
2413      if let Some(web_context) = context_store.get_mut(&self.context_key) {
2414        web_context.referenced_by_webviews.remove(&self.label);
2415
2416        // https://github.com/tauri-apps/tauri/issues/14626
2417        // Because WebKit does not close its network process even when no webviews are running,
2418        // we need to ensure to re-use the existing process on Linux by keeping the WebContext
2419        // alive for the lifetime of the app.
2420        // WebKit on macOS handles this itself.
2421        #[cfg(not(any(
2422          target_os = "linux",
2423          target_os = "dragonfly",
2424          target_os = "freebsd",
2425          target_os = "netbsd",
2426          target_os = "openbsd"
2427        )))]
2428        if web_context.referenced_by_webviews.is_empty() {
2429          context_store.remove(&self.context_key);
2430        }
2431      }
2432    }
2433  }
2434}
2435
2436#[cfg(windows)]
2437#[derive(Debug)]
2438pub enum FocusState {
2439  WindowFocused,
2440  WebviewFocused {
2441    webview_label: String,
2442  },
2443  Blured {
2444    last_focused_webview_label: Option<String>,
2445  },
2446}
2447
2448#[cfg(windows)]
2449impl Default for FocusState {
2450  fn default() -> Self {
2451    Self::Blured {
2452      last_focused_webview_label: None,
2453    }
2454  }
2455}
2456
2457pub struct WindowWrapper {
2458  label: String,
2459  inner: Option<Arc<Window>>,
2460  // whether this window has child webviews
2461  // or it's just a container for a single webview
2462  has_children: AtomicBool,
2463  webviews: Vec<WebviewWrapper>,
2464  window_event_listeners: WindowEventListeners,
2465  #[cfg(windows)]
2466  background_color: Option<tao::window::RGBA>,
2467  #[cfg(windows)]
2468  is_window_transparent: bool,
2469  #[cfg(windows)]
2470  surface: Option<softbuffer::Surface<Arc<Window>, Arc<Window>>>,
2471  #[cfg(windows)]
2472  focused_webview: Arc<Mutex<FocusState>>,
2473}
2474
2475impl WindowWrapper {
2476  pub fn label(&self) -> &str {
2477    &self.label
2478  }
2479}
2480
2481impl fmt::Debug for WindowWrapper {
2482  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2483    f.debug_struct("WindowWrapper")
2484      .field("label", &self.label)
2485      .field("inner", &self.inner)
2486      .finish()
2487  }
2488}
2489
2490#[derive(Debug, Clone)]
2491pub struct EventProxy<T: UserEvent>(TaoEventLoopProxy<Message<T>>);
2492
2493#[cfg(target_os = "ios")]
2494#[allow(clippy::non_send_fields_in_send_ty)]
2495unsafe impl<T: UserEvent> Sync for EventProxy<T> {}
2496
2497impl<T: UserEvent> EventLoopProxy<T> for EventProxy<T> {
2498  fn send_event(&self, event: T) -> Result<()> {
2499    self
2500      .0
2501      .send_event(Message::UserEvent(event))
2502      .map_err(|_| Error::EventLoopClosed)
2503  }
2504}
2505
2506pub trait PluginBuilder<T: UserEvent> {
2507  type Plugin: Plugin<T>;
2508  fn build(self, context: Context<T>) -> Self::Plugin;
2509}
2510
2511pub trait Plugin<T: UserEvent> {
2512  fn on_event(
2513    &mut self,
2514    event: &Event<Message<T>>,
2515    event_loop: &EventLoopWindowTarget<Message<T>>,
2516    proxy: &TaoEventLoopProxy<Message<T>>,
2517    control_flow: &mut ControlFlow,
2518    context: EventLoopIterationContext<'_, T>,
2519    web_context: &WebContextStore,
2520  ) -> bool;
2521}
2522
2523/// A Tauri [`Runtime`] wrapper around wry.
2524pub struct Wry<T: UserEvent> {
2525  context: Context<T>,
2526  event_loop: EventLoop<Message<T>>,
2527}
2528
2529impl<T: UserEvent> fmt::Debug for Wry<T> {
2530  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2531    f.debug_struct("Wry")
2532      .field("main_thread_id", &self.context.main_thread_id)
2533      .field("event_loop", &self.event_loop)
2534      .field("windows", &self.context.main_thread.windows)
2535      .field("web_context", &self.context.main_thread.web_context)
2536      .finish()
2537  }
2538}
2539
2540/// A handle to the Wry runtime.
2541#[derive(Debug, Clone)]
2542pub struct WryHandle<T: UserEvent> {
2543  context: Context<T>,
2544}
2545
2546// SAFETY: this is safe since the `Context` usage is guarded by `Context::send_user_message`.
2547#[allow(clippy::non_send_fields_in_send_ty)]
2548unsafe impl<T: UserEvent> Sync for WryHandle<T> {}
2549
2550impl<T: UserEvent> WryHandle<T> {
2551  /// Creates a new tao window using a callback, and returns its window id.
2552  pub fn create_tao_window<F: FnOnce() -> (String, TaoWindowBuilder) + Send + 'static>(
2553    &self,
2554    f: F,
2555  ) -> Result<Weak<Window>> {
2556    let id = self.context.next_window_id();
2557    let (tx, rx) = channel();
2558    self
2559      .context
2560      .send_user_message(Message::CreateRawWindow(id, Box::new(f), tx))?;
2561    rx.recv().unwrap()
2562  }
2563
2564  /// Gets the [`WindowId`] associated with the given [`TaoWindowId`].
2565  pub fn window_id(&self, window_id: TaoWindowId) -> WindowId {
2566    self.context.window_id_map.get(&window_id).unwrap()
2567  }
2568
2569  /// Send a message to the event loop.
2570  pub fn send_event(&self, message: Message<T>) -> Result<()> {
2571    self
2572      .context
2573      .proxy
2574      .send_event(message)
2575      .map_err(|_| Error::FailedToSendMessage)?;
2576    Ok(())
2577  }
2578
2579  pub fn plugin<P: PluginBuilder<T> + 'static>(&mut self, plugin: P)
2580  where
2581    <P as PluginBuilder<T>>::Plugin: Send,
2582  {
2583    self
2584      .context
2585      .plugins
2586      .lock()
2587      .unwrap()
2588      .push(Box::new(plugin.build(self.context.clone())));
2589  }
2590}
2591
2592impl<T: UserEvent> RuntimeHandle<T> for WryHandle<T> {
2593  type Runtime = Wry<T>;
2594
2595  fn create_proxy(&self) -> EventProxy<T> {
2596    EventProxy(self.context.proxy.clone())
2597  }
2598
2599  #[cfg(target_os = "macos")]
2600  fn set_activation_policy(&self, activation_policy: ActivationPolicy) -> Result<()> {
2601    self
2602      .context
2603      .send_user_message(Message::SetActivationPolicy(activation_policy))
2604  }
2605
2606  #[cfg(target_os = "macos")]
2607  fn set_dock_visibility(&self, visible: bool) -> Result<()> {
2608    self
2609      .context
2610      .send_user_message(Message::SetDockVisibility(visible))
2611  }
2612
2613  fn request_exit(&self, code: i32) -> Result<()> {
2614    // NOTE: request_exit cannot use `Context::send_user_message` because it accesses the event loop callback
2615    self
2616      .context
2617      .proxy
2618      .send_event(Message::RequestExit(code))
2619      .map_err(|_| Error::FailedToSendMessage)
2620  }
2621
2622  // Creates a window by dispatching a message to the event loop.
2623  // Note that this must be called from a separate thread, otherwise the channel will introduce a deadlock.
2624  fn create_window<F: Fn(RawWindow) + Send + 'static>(
2625    &self,
2626    pending: PendingWindow<T, Self::Runtime>,
2627    after_window_creation: Option<F>,
2628  ) -> Result<DetachedWindow<T, Self::Runtime>> {
2629    self.context.create_window(pending, after_window_creation)
2630  }
2631
2632  // Creates a webview by dispatching a message to the event loop.
2633  // Note that this must be called from a separate thread, otherwise the channel will introduce a deadlock.
2634  fn create_webview(
2635    &self,
2636    window_id: WindowId,
2637    pending: PendingWebview<T, Self::Runtime>,
2638  ) -> Result<DetachedWebview<T, Self::Runtime>> {
2639    self.context.create_webview(window_id, pending)
2640  }
2641
2642  fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()> {
2643    self.context.send_user_message(Message::Task(Box::new(f)))
2644  }
2645
2646  fn display_handle(
2647    &self,
2648  ) -> std::result::Result<DisplayHandle<'_>, raw_window_handle::HandleError> {
2649    self.context.main_thread.window_target.display_handle()
2650  }
2651
2652  fn primary_monitor(&self) -> Result<Option<Monitor>> {
2653    event_loop_window_getter!(self, EventLoopWindowTargetMessage::PrimaryMonitor)
2654  }
2655
2656  fn monitor_from_point(&self, x: f64, y: f64) -> Result<Option<Monitor>> {
2657    let (tx, rx) = channel();
2658    self
2659      .context
2660      .send_user_message(Message::EventLoopWindowTarget(
2661        EventLoopWindowTargetMessage::MonitorFromPoint(tx, (x, y)),
2662      ))?;
2663    Ok(rx.recv().unwrap())
2664  }
2665
2666  fn available_monitors(&self) -> Result<Vec<Monitor>> {
2667    event_loop_window_getter!(self, EventLoopWindowTargetMessage::AvailableMonitors)
2668  }
2669
2670  fn cursor_position(&self) -> Result<PhysicalPosition<f64>> {
2671    event_loop_window_getter!(self, EventLoopWindowTargetMessage::CursorPosition)?
2672      .map_err(|_| Error::FailedToGetCursorPosition)
2673  }
2674
2675  fn set_theme(&self, theme: Option<Theme>) {
2676    let _ = self
2677      .context
2678      .send_user_message(Message::EventLoopWindowTarget(
2679        EventLoopWindowTargetMessage::SetTheme(theme),
2680      ));
2681  }
2682
2683  #[cfg(target_os = "macos")]
2684  fn show(&self) -> tauri_runtime::Result<()> {
2685    self
2686      .context
2687      .send_user_message(Message::Application(ApplicationMessage::Show))
2688  }
2689
2690  #[cfg(target_os = "macos")]
2691  fn hide(&self) -> tauri_runtime::Result<()> {
2692    self
2693      .context
2694      .send_user_message(Message::Application(ApplicationMessage::Hide))
2695  }
2696
2697  fn set_device_event_filter(&self, filter: DeviceEventFilter) {
2698    let _ = self
2699      .context
2700      .send_user_message(Message::EventLoopWindowTarget(
2701        EventLoopWindowTargetMessage::SetDeviceEventFilter(filter),
2702      ));
2703  }
2704
2705  #[cfg(target_os = "android")]
2706  fn find_class<'a>(
2707    &self,
2708    env: &mut jni::JNIEnv<'a>,
2709    activity: &jni::objects::JObject<'_>,
2710    name: impl Into<String>,
2711  ) -> std::result::Result<jni::objects::JClass<'a>, jni::errors::Error> {
2712    find_class(env, activity, name.into())
2713  }
2714
2715  #[cfg(target_os = "android")]
2716  fn run_on_android_context<F>(&self, f: F)
2717  where
2718    F: FnOnce(&mut jni::JNIEnv<'_>, &jni::objects::JObject<'_>, &jni::objects::JObject<'_>)
2719      + Send
2720      + 'static,
2721  {
2722    dispatch(f)
2723  }
2724
2725  #[cfg(any(target_os = "macos", target_os = "ios"))]
2726  fn fetch_data_store_identifiers<F: FnOnce(Vec<[u8; 16]>) + Send + 'static>(
2727    &self,
2728    cb: F,
2729  ) -> Result<()> {
2730    self.context.send_user_message(Message::Application(
2731      ApplicationMessage::FetchDataStoreIdentifiers(Box::new(cb)),
2732    ))
2733  }
2734
2735  #[cfg(any(target_os = "macos", target_os = "ios"))]
2736  fn remove_data_store<F: FnOnce(Result<()>) + Send + 'static>(
2737    &self,
2738    uuid: [u8; 16],
2739    cb: F,
2740  ) -> Result<()> {
2741    self
2742      .context
2743      .send_user_message(Message::Application(ApplicationMessage::RemoveDataStore(
2744        uuid,
2745        Box::new(cb),
2746      )))
2747  }
2748}
2749
2750impl<T: UserEvent> Wry<T> {
2751  fn init_with_builder(
2752    mut event_loop_builder: EventLoopBuilder<Message<T>>,
2753    #[allow(unused_variables)] args: RuntimeInitArgs,
2754  ) -> Result<Self> {
2755    #[cfg(windows)]
2756    if let Some(hook) = args.msg_hook {
2757      use tao::platform::windows::EventLoopBuilderExtWindows;
2758      event_loop_builder.with_msg_hook(hook);
2759    }
2760
2761    #[cfg(any(
2762      target_os = "linux",
2763      target_os = "dragonfly",
2764      target_os = "freebsd",
2765      target_os = "netbsd",
2766      target_os = "openbsd"
2767    ))]
2768    if let Some(app_id) = args.app_id {
2769      use tao::platform::unix::EventLoopBuilderExtUnix;
2770      event_loop_builder.with_app_id(app_id);
2771    }
2772    Self::init(event_loop_builder.build())
2773  }
2774
2775  fn init(event_loop: EventLoop<Message<T>>) -> Result<Self> {
2776    let main_thread_id = current_thread().id();
2777    let web_context = WebContextStore::default();
2778
2779    #[allow(clippy::arc_with_non_send_sync)]
2780    let windows = Arc::new(WindowsStore(RefCell::new(BTreeMap::default())));
2781    let window_id_map = WindowIdStore::default();
2782
2783    let context = Context {
2784      window_id_map,
2785      main_thread_id,
2786      proxy: event_loop.create_proxy(),
2787      main_thread: DispatcherMainThreadContext {
2788        window_target: event_loop.deref().clone(),
2789        web_context,
2790        windows,
2791        #[cfg(feature = "tracing")]
2792        active_tracing_spans: Default::default(),
2793      },
2794      plugins: Default::default(),
2795      next_window_id: Default::default(),
2796      next_webview_id: Default::default(),
2797      next_window_event_id: Default::default(),
2798      next_webview_event_id: Default::default(),
2799      webview_runtime_installed: wry::webview_version().is_ok(),
2800    };
2801
2802    Ok(Self {
2803      context,
2804      event_loop,
2805    })
2806  }
2807}
2808
2809impl<T: UserEvent> Runtime<T> for Wry<T> {
2810  type WindowDispatcher = WryWindowDispatcher<T>;
2811  type WebviewDispatcher = WryWebviewDispatcher<T>;
2812  type Handle = WryHandle<T>;
2813
2814  type EventLoopProxy = EventProxy<T>;
2815
2816  fn new(args: RuntimeInitArgs) -> Result<Self> {
2817    Self::init_with_builder(EventLoopBuilder::<Message<T>>::with_user_event(), args)
2818  }
2819  #[cfg(any(
2820    target_os = "linux",
2821    target_os = "dragonfly",
2822    target_os = "freebsd",
2823    target_os = "netbsd",
2824    target_os = "openbsd"
2825  ))]
2826  fn new_any_thread(args: RuntimeInitArgs) -> Result<Self> {
2827    use tao::platform::unix::EventLoopBuilderExtUnix;
2828    let mut event_loop_builder = EventLoopBuilder::<Message<T>>::with_user_event();
2829    event_loop_builder.with_any_thread(true);
2830    Self::init_with_builder(event_loop_builder, args)
2831  }
2832
2833  #[cfg(windows)]
2834  fn new_any_thread(args: RuntimeInitArgs) -> Result<Self> {
2835    use tao::platform::windows::EventLoopBuilderExtWindows;
2836    let mut event_loop_builder = EventLoopBuilder::<Message<T>>::with_user_event();
2837    event_loop_builder.with_any_thread(true);
2838    Self::init_with_builder(event_loop_builder, args)
2839  }
2840
2841  fn create_proxy(&self) -> EventProxy<T> {
2842    EventProxy(self.event_loop.create_proxy())
2843  }
2844
2845  fn handle(&self) -> Self::Handle {
2846    WryHandle {
2847      context: self.context.clone(),
2848    }
2849  }
2850
2851  fn create_window<F: Fn(RawWindow) + Send + 'static>(
2852    &self,
2853    pending: PendingWindow<T, Self>,
2854    after_window_creation: Option<F>,
2855  ) -> Result<DetachedWindow<T, Self>> {
2856    let label = pending.label.clone();
2857    let window_id = self.context.next_window_id();
2858    let (webview_id, use_https_scheme) = pending
2859      .webview
2860      .as_ref()
2861      .map(|w| {
2862        (
2863          Some(self.context.next_webview_id()),
2864          w.webview_attributes.use_https_scheme,
2865        )
2866      })
2867      .unwrap_or((None, false));
2868
2869    let window = create_window(
2870      window_id,
2871      webview_id.unwrap_or_default(),
2872      &self.event_loop,
2873      &self.context,
2874      pending,
2875      after_window_creation,
2876    )?;
2877
2878    let dispatcher = WryWindowDispatcher {
2879      window_id,
2880      context: self.context.clone(),
2881    };
2882
2883    self
2884      .context
2885      .main_thread
2886      .windows
2887      .0
2888      .borrow_mut()
2889      .insert(window_id, window);
2890
2891    let detached_webview = webview_id.map(|id| {
2892      let webview = DetachedWebview {
2893        label: label.clone(),
2894        dispatcher: WryWebviewDispatcher {
2895          window_id: Arc::new(Mutex::new(window_id)),
2896          webview_id: id,
2897          context: self.context.clone(),
2898        },
2899      };
2900      DetachedWindowWebview {
2901        webview,
2902        use_https_scheme,
2903      }
2904    });
2905
2906    Ok(DetachedWindow {
2907      id: window_id,
2908      label,
2909      dispatcher,
2910      webview: detached_webview,
2911    })
2912  }
2913
2914  fn create_webview(
2915    &self,
2916    window_id: WindowId,
2917    pending: PendingWebview<T, Self>,
2918  ) -> Result<DetachedWebview<T, Self>> {
2919    let label = pending.label.clone();
2920
2921    let window = self
2922      .context
2923      .main_thread
2924      .windows
2925      .0
2926      .borrow()
2927      .get(&window_id)
2928      .map(|w| {
2929        (
2930          w.inner.clone(),
2931          CreateWebviewOptions {
2932            #[cfg(windows)]
2933            focused_webview: w.focused_webview.clone(),
2934          },
2935        )
2936      });
2937    if let Some((Some(window), _options)) = window {
2938      let window_id_wrapper = Arc::new(Mutex::new(window_id));
2939
2940      let webview_id = self.context.next_webview_id();
2941
2942      let webview = create_webview(
2943        WebviewKind::WindowChild,
2944        &window,
2945        window_id_wrapper.clone(),
2946        webview_id,
2947        &self.context,
2948        pending,
2949        #[cfg(windows)]
2950        _options.focused_webview,
2951      )?;
2952
2953      if let Some(w) = self
2954        .context
2955        .main_thread
2956        .windows
2957        .0
2958        .borrow_mut()
2959        .get_mut(&window_id)
2960      {
2961        w.webviews.push(webview);
2962        w.has_children.store(true, Ordering::Relaxed);
2963      }
2964
2965      let dispatcher = WryWebviewDispatcher {
2966        window_id: window_id_wrapper,
2967        webview_id,
2968        context: self.context.clone(),
2969      };
2970
2971      Ok(DetachedWebview { label, dispatcher })
2972    } else {
2973      Err(Error::WindowNotFound)
2974    }
2975  }
2976
2977  fn primary_monitor(&self) -> Option<Monitor> {
2978    self
2979      .context
2980      .main_thread
2981      .window_target
2982      .primary_monitor()
2983      .map(|m| MonitorHandleWrapper(m).into())
2984  }
2985
2986  fn monitor_from_point(&self, x: f64, y: f64) -> Option<Monitor> {
2987    self
2988      .context
2989      .main_thread
2990      .window_target
2991      .monitor_from_point(x, y)
2992      .map(|m| MonitorHandleWrapper(m).into())
2993  }
2994
2995  fn available_monitors(&self) -> Vec<Monitor> {
2996    self
2997      .context
2998      .main_thread
2999      .window_target
3000      .available_monitors()
3001      .map(|m| MonitorHandleWrapper(m).into())
3002      .collect()
3003  }
3004
3005  fn cursor_position(&self) -> Result<PhysicalPosition<f64>> {
3006    self
3007      .context
3008      .main_thread
3009      .window_target
3010      .cursor_position()
3011      .map_err(|_| Error::FailedToGetCursorPosition)
3012  }
3013
3014  fn set_theme(&self, theme: Option<Theme>) {
3015    self.event_loop.set_theme(to_tao_theme(theme));
3016  }
3017
3018  #[cfg(target_os = "macos")]
3019  fn set_activation_policy(&mut self, activation_policy: ActivationPolicy) {
3020    self
3021      .event_loop
3022      .set_activation_policy(tao_activation_policy(activation_policy));
3023  }
3024
3025  #[cfg(target_os = "macos")]
3026  fn set_activate_ignoring_other_apps(&mut self, ignore: bool) {
3027    self.event_loop.set_activate_ignoring_other_apps(ignore);
3028  }
3029
3030  #[cfg(target_os = "macos")]
3031  fn set_dock_visibility(&mut self, visible: bool) {
3032    self.event_loop.set_dock_visibility(visible);
3033  }
3034
3035  #[cfg(target_os = "macos")]
3036  fn show(&self) {
3037    self.event_loop.show_application();
3038  }
3039
3040  #[cfg(target_os = "macos")]
3041  fn hide(&self) {
3042    self.event_loop.hide_application();
3043  }
3044
3045  fn set_device_event_filter(&mut self, filter: DeviceEventFilter) {
3046    self
3047      .event_loop
3048      .set_device_event_filter(DeviceEventFilterWrapper::from(filter).0);
3049  }
3050
3051  #[cfg(desktop)]
3052  fn run_iteration<F: FnMut(RunEvent<T>) + 'static>(&mut self, mut callback: F) {
3053    use tao::platform::run_return::EventLoopExtRunReturn;
3054    let windows = &self.context.main_thread.windows;
3055    let window_id_map = &self.context.window_id_map;
3056    let web_context = &self.context.main_thread.web_context;
3057    let plugins = &self.context.plugins;
3058
3059    #[cfg(feature = "tracing")]
3060    let active_tracing_spans = &self.context.main_thread.active_tracing_spans;
3061
3062    let proxy = self.event_loop.create_proxy();
3063
3064    self
3065      .event_loop
3066      .run_return(|event, event_loop, control_flow| {
3067        *control_flow = ControlFlow::Wait;
3068        if let Event::MainEventsCleared = &event {
3069          *control_flow = ControlFlow::Exit;
3070        }
3071
3072        for p in plugins.lock().unwrap().iter_mut() {
3073          let prevent_default = p.on_event(
3074            &event,
3075            event_loop,
3076            &proxy,
3077            control_flow,
3078            EventLoopIterationContext {
3079              callback: &mut callback,
3080              window_id_map,
3081              windows,
3082              #[cfg(feature = "tracing")]
3083              active_tracing_spans,
3084            },
3085            web_context,
3086          );
3087          if prevent_default {
3088            return;
3089          }
3090        }
3091
3092        handle_event_loop(
3093          event,
3094          event_loop,
3095          control_flow,
3096          EventLoopIterationContext {
3097            callback: &mut callback,
3098            windows,
3099            window_id_map,
3100            #[cfg(feature = "tracing")]
3101            active_tracing_spans,
3102          },
3103        );
3104      });
3105  }
3106
3107  fn run<F: FnMut(RunEvent<T>) + 'static>(self, callback: F) {
3108    let event_handler = make_event_handler(self.context, callback);
3109    self.event_loop.run(event_handler)
3110  }
3111
3112  #[cfg(not(target_os = "ios"))]
3113  fn run_return<F: FnMut(RunEvent<T>) + 'static>(mut self, callback: F) -> i32 {
3114    use tao::platform::run_return::EventLoopExtRunReturn;
3115
3116    let event_handler = make_event_handler(self.context, callback);
3117    self.event_loop.run_return(event_handler)
3118  }
3119
3120  #[cfg(target_os = "ios")]
3121  fn run_return<F: FnMut(RunEvent<T>) + 'static>(self, callback: F) -> i32 {
3122    self.run(callback);
3123    0
3124  }
3125}
3126
3127fn make_event_handler<T: UserEvent, F: FnMut(RunEvent<T>) + 'static>(
3128  context: Context<T>,
3129  mut callback: F,
3130) -> impl FnMut(Event<'_, Message<T>>, &EventLoopWindowTarget<Message<T>>, &mut ControlFlow) {
3131  let windows = context.main_thread.windows;
3132  let window_id_map = context.window_id_map;
3133  let web_context = context.main_thread.web_context;
3134  let plugins = context.plugins;
3135
3136  #[cfg(feature = "tracing")]
3137  let active_tracing_spans = context.main_thread.active_tracing_spans;
3138  let proxy = context.proxy;
3139
3140  move |event, event_loop, control_flow| {
3141    for p in plugins.lock().unwrap().iter_mut() {
3142      let prevent_default = p.on_event(
3143        &event,
3144        event_loop,
3145        &proxy,
3146        control_flow,
3147        EventLoopIterationContext {
3148          callback: &mut callback,
3149          window_id_map: &window_id_map,
3150          windows: &windows,
3151          #[cfg(feature = "tracing")]
3152          active_tracing_spans: &active_tracing_spans,
3153        },
3154        &web_context,
3155      );
3156      if prevent_default {
3157        return;
3158      }
3159    }
3160    handle_event_loop(
3161      event,
3162      event_loop,
3163      control_flow,
3164      EventLoopIterationContext {
3165        callback: &mut callback,
3166        window_id_map: &window_id_map,
3167        windows: &windows,
3168        #[cfg(feature = "tracing")]
3169        active_tracing_spans: &active_tracing_spans,
3170      },
3171    );
3172  }
3173}
3174
3175pub struct EventLoopIterationContext<'a, T: UserEvent> {
3176  pub callback: &'a mut (dyn FnMut(RunEvent<T>) + 'static),
3177  pub window_id_map: &'a WindowIdStore,
3178  pub windows: &'a WindowsStore,
3179  #[cfg(feature = "tracing")]
3180  pub active_tracing_spans: &'a ActiveTraceSpanStore,
3181}
3182
3183struct UserMessageContext<'a> {
3184  windows: &'a WindowsStore,
3185  window_id_map: &'a WindowIdStore,
3186}
3187
3188fn handle_user_message<T: UserEvent>(
3189  event_loop: &EventLoopWindowTarget<Message<T>>,
3190  message: Message<T>,
3191  context: UserMessageContext,
3192) {
3193  let UserMessageContext {
3194    window_id_map,
3195    windows,
3196  } = context;
3197  match message {
3198    Message::Task(task) => task(),
3199    #[cfg(target_os = "macos")]
3200    Message::SetActivationPolicy(activation_policy) => {
3201      event_loop.set_activation_policy_at_runtime(tao_activation_policy(activation_policy))
3202    }
3203    #[cfg(target_os = "macos")]
3204    Message::SetDockVisibility(visible) => event_loop.set_dock_visibility(visible),
3205    Message::RequestExit(_code) => panic!("cannot handle RequestExit on the main thread"),
3206    Message::Application(application_message) => match application_message {
3207      #[cfg(target_os = "macos")]
3208      ApplicationMessage::Show => {
3209        event_loop.show_application();
3210      }
3211      #[cfg(target_os = "macos")]
3212      ApplicationMessage::Hide => {
3213        event_loop.hide_application();
3214      }
3215      #[cfg(any(target_os = "macos", target_os = "ios"))]
3216      ApplicationMessage::FetchDataStoreIdentifiers(cb) => {
3217        if let Err(e) = WebView::fetch_data_store_identifiers(cb) {
3218          // this shouldn't ever happen because we're running on the main thread
3219          // but let's be safe and warn here
3220          log::error!("failed to fetch data store identifiers: {e}");
3221        }
3222      }
3223      #[cfg(any(target_os = "macos", target_os = "ios"))]
3224      ApplicationMessage::RemoveDataStore(uuid, cb) => {
3225        WebView::remove_data_store(&uuid, move |res| {
3226          cb(res.map_err(|_| Error::FailedToRemoveDataStore))
3227        })
3228      }
3229    },
3230    Message::Window(id, window_message) => {
3231      let w = windows.0.borrow().get(&id).map(|w| {
3232        #[cfg(windows)]
3233        let focused_webview = w.focused_webview.clone();
3234        #[cfg(not(windows))]
3235        let focused_webview = ();
3236        (
3237          w.inner.clone(),
3238          w.webviews.clone(),
3239          w.has_children.load(Ordering::Relaxed),
3240          w.window_event_listeners.clone(),
3241          focused_webview,
3242        )
3243      });
3244      if let Some((
3245        Some(window),
3246        webviews,
3247        has_children,
3248        window_event_listeners,
3249        _focused_webview,
3250      )) = w
3251      {
3252        match window_message {
3253          WindowMessage::AddEventListener(id, listener) => {
3254            window_event_listeners.lock().unwrap().insert(id, listener);
3255          }
3256
3257          // Getters
3258          WindowMessage::ScaleFactor(tx) => tx.send(window.scale_factor()).unwrap(),
3259          WindowMessage::InnerPosition(tx) => tx
3260            .send(
3261              window
3262                .inner_position()
3263                .map_err(|_| Error::FailedToSendMessage),
3264            )
3265            .unwrap(),
3266          WindowMessage::OuterPosition(tx) => tx
3267            .send(
3268              window
3269                .outer_position()
3270                .map_err(|_| Error::FailedToSendMessage),
3271            )
3272            .unwrap(),
3273          WindowMessage::InnerSize(tx) => tx
3274            .send(inner_size(&window, &webviews, has_children))
3275            .unwrap(),
3276          WindowMessage::OuterSize(tx) => tx.send(window.outer_size()).unwrap(),
3277          WindowMessage::IsFullscreen(tx) => tx.send(window.fullscreen().is_some()).unwrap(),
3278          WindowMessage::IsMinimized(tx) => tx.send(window.is_minimized()).unwrap(),
3279          WindowMessage::IsMaximized(tx) => tx.send(window.is_maximized()).unwrap(),
3280          #[cfg(not(windows))]
3281          WindowMessage::IsFocused(tx) => tx.send(window.is_focused()).unwrap(),
3282          #[cfg(windows)]
3283          WindowMessage::IsFocused(tx) => {
3284            let focused = if has_children {
3285              // on multiwebview mode, get the focused state from cache,
3286              // as the window might not have direct focus
3287              matches!(
3288                *_focused_webview.lock().unwrap(),
3289                FocusState::WindowFocused | FocusState::WebviewFocused { .. }
3290              )
3291            } else {
3292              window.is_focused()
3293            };
3294            tx.send(focused).unwrap()
3295          }
3296          WindowMessage::IsDecorated(tx) => tx.send(window.is_decorated()).unwrap(),
3297          WindowMessage::IsResizable(tx) => tx.send(window.is_resizable()).unwrap(),
3298          WindowMessage::IsMaximizable(tx) => tx.send(window.is_maximizable()).unwrap(),
3299          WindowMessage::IsMinimizable(tx) => tx.send(window.is_minimizable()).unwrap(),
3300          WindowMessage::IsClosable(tx) => tx.send(window.is_closable()).unwrap(),
3301          WindowMessage::IsVisible(tx) => tx.send(window.is_visible()).unwrap(),
3302          WindowMessage::Title(tx) => tx.send(window.title()).unwrap(),
3303          WindowMessage::CurrentMonitor(tx) => tx
3304            .send(
3305              window
3306                .current_monitor()
3307                .map(|m| MonitorHandleWrapper(m).into()),
3308            )
3309            .unwrap(),
3310          WindowMessage::PrimaryMonitor(tx) => tx
3311            .send(
3312              window
3313                .primary_monitor()
3314                .map(|m| MonitorHandleWrapper(m).into()),
3315            )
3316            .unwrap(),
3317          WindowMessage::MonitorFromPoint(tx, (x, y)) => tx
3318            .send(
3319              window
3320                .monitor_from_point(x, y)
3321                .map(|m| MonitorHandleWrapper(m).into()),
3322            )
3323            .unwrap(),
3324          WindowMessage::AvailableMonitors(tx) => tx
3325            .send(
3326              window
3327                .available_monitors()
3328                .map(|m| MonitorHandleWrapper(m).into())
3329                .collect(),
3330            )
3331            .unwrap(),
3332          #[cfg(any(
3333            target_os = "linux",
3334            target_os = "dragonfly",
3335            target_os = "freebsd",
3336            target_os = "netbsd",
3337            target_os = "openbsd"
3338          ))]
3339          WindowMessage::GtkWindow(tx) => tx.send(GtkWindow(window.gtk_window().clone())).unwrap(),
3340          #[cfg(any(
3341            target_os = "linux",
3342            target_os = "dragonfly",
3343            target_os = "freebsd",
3344            target_os = "netbsd",
3345            target_os = "openbsd"
3346          ))]
3347          WindowMessage::GtkBox(tx) => tx
3348            .send(GtkBox(window.default_vbox().unwrap().clone()))
3349            .unwrap(),
3350          #[cfg(target_os = "android")]
3351          WindowMessage::ActivityName(tx) => {
3352            tx.send(window.activity_name()).unwrap();
3353          }
3354          #[cfg(target_os = "ios")]
3355          WindowMessage::SceneIdentifier(tx) => {
3356            tx.send(window.scene_identifier()).unwrap();
3357          }
3358          WindowMessage::RawWindowHandle(tx) => tx
3359            .send(
3360              window
3361                .window_handle()
3362                .map(|h| SendRawWindowHandle(h.as_raw())),
3363            )
3364            .unwrap(),
3365          WindowMessage::Theme(tx) => {
3366            tx.send(map_theme(&window.theme())).unwrap();
3367          }
3368          WindowMessage::IsEnabled(tx) => tx.send(window.is_enabled()).unwrap(),
3369          WindowMessage::IsAlwaysOnTop(tx) => tx.send(window.is_always_on_top()).unwrap(),
3370          // Setters
3371          WindowMessage::Center => window.center(),
3372          WindowMessage::RequestUserAttention(request_type) => {
3373            window.request_user_attention(request_type.map(|r| r.0));
3374          }
3375          WindowMessage::SetResizable(resizable) => {
3376            window.set_resizable(resizable);
3377            #[cfg(windows)]
3378            if !resizable {
3379              undecorated_resizing::detach_resize_handler(window.hwnd());
3380            } else if !window.is_decorated() {
3381              undecorated_resizing::attach_resize_handler(
3382                window.hwnd(),
3383                window.has_undecorated_shadow(),
3384              );
3385            }
3386          }
3387          WindowMessage::SetMaximizable(maximizable) => window.set_maximizable(maximizable),
3388          WindowMessage::SetMinimizable(minimizable) => window.set_minimizable(minimizable),
3389          WindowMessage::SetClosable(closable) => window.set_closable(closable),
3390          WindowMessage::SetTitle(title) => window.set_title(&title),
3391          WindowMessage::Maximize => window.set_maximized(true),
3392          WindowMessage::Unmaximize => window.set_maximized(false),
3393          WindowMessage::Minimize => window.set_minimized(true),
3394          WindowMessage::Unminimize => window.set_minimized(false),
3395          WindowMessage::SetEnabled(enabled) => window.set_enabled(enabled),
3396          WindowMessage::Show => window.set_visible(true),
3397          WindowMessage::Hide => window.set_visible(false),
3398          WindowMessage::Close => {
3399            panic!("cannot handle `WindowMessage::Close` on the main thread")
3400          }
3401          WindowMessage::Destroy => {
3402            panic!("cannot handle `WindowMessage::Destroy` on the main thread")
3403          }
3404          WindowMessage::SetDecorations(decorations) => {
3405            window.set_decorations(decorations);
3406            #[cfg(windows)]
3407            if decorations {
3408              undecorated_resizing::detach_resize_handler(window.hwnd());
3409            } else if window.is_resizable() {
3410              undecorated_resizing::attach_resize_handler(
3411                window.hwnd(),
3412                window.has_undecorated_shadow(),
3413              );
3414            }
3415          }
3416          WindowMessage::SetShadow(_enable) => {
3417            #[cfg(windows)]
3418            {
3419              window.set_undecorated_shadow(_enable);
3420              undecorated_resizing::update_drag_hwnd_rgn_for_undecorated(window.hwnd(), _enable);
3421            }
3422            #[cfg(target_os = "macos")]
3423            window.set_has_shadow(_enable);
3424          }
3425          WindowMessage::SetAlwaysOnBottom(always_on_bottom) => {
3426            window.set_always_on_bottom(always_on_bottom)
3427          }
3428          WindowMessage::SetAlwaysOnTop(always_on_top) => window.set_always_on_top(always_on_top),
3429          WindowMessage::SetVisibleOnAllWorkspaces(visible_on_all_workspaces) => {
3430            window.set_visible_on_all_workspaces(visible_on_all_workspaces)
3431          }
3432          WindowMessage::SetContentProtected(protected) => window.set_content_protection(protected),
3433          WindowMessage::SetSize(size) => {
3434            window.set_inner_size(size);
3435          }
3436          WindowMessage::SetMinSize(size) => {
3437            window.set_min_inner_size(size);
3438          }
3439          WindowMessage::SetMaxSize(size) => {
3440            window.set_max_inner_size(size);
3441          }
3442          WindowMessage::SetSizeConstraints(constraints) => {
3443            window.set_inner_size_constraints(tao::window::WindowSizeConstraints {
3444              min_width: constraints.min_width,
3445              min_height: constraints.min_height,
3446              max_width: constraints.max_width,
3447              max_height: constraints.max_height,
3448            });
3449          }
3450          WindowMessage::SetPosition(position) => window.set_outer_position(position),
3451          WindowMessage::SetFullscreen(fullscreen) => {
3452            if fullscreen {
3453              window.set_fullscreen(Some(Fullscreen::Borderless(None)))
3454            } else {
3455              window.set_fullscreen(None)
3456            }
3457          }
3458          WindowMessage::SetFullscreenOnMonitor(position) => {
3459            // Not `Window::monitor_from_point`: on macOS and Linux (GTK) it takes logical
3460            // coordinates, while callers pass physical ones (e.g. `Monitor::position`).
3461            if let Some(monitor) =
3462              find_monitor_for_position(window.available_monitors(), position.into())
3463            {
3464              window.set_fullscreen(Some(Fullscreen::Borderless(Some(monitor))))
3465            }
3466          }
3467
3468          #[cfg(target_os = "macos")]
3469          WindowMessage::SetSimpleFullscreen(enable) => {
3470            window.set_simple_fullscreen(enable);
3471          }
3472
3473          WindowMessage::SetFocus => {
3474            window.set_focus();
3475          }
3476          WindowMessage::SetFocusable(focusable) => {
3477            window.set_focusable(focusable);
3478          }
3479          WindowMessage::SetIcon(icon) => {
3480            window.set_window_icon(Some(icon));
3481          }
3482          #[allow(unused_variables)]
3483          WindowMessage::SetSkipTaskbar(skip) => {
3484            #[cfg(any(
3485              windows,
3486              target_os = "linux",
3487              target_os = "dragonfly",
3488              target_os = "freebsd",
3489              target_os = "netbsd",
3490              target_os = "openbsd"
3491            ))]
3492            let _ = window.set_skip_taskbar(skip);
3493          }
3494          WindowMessage::SetCursorGrab(grab) => {
3495            let _ = window.set_cursor_grab(grab);
3496          }
3497          WindowMessage::SetCursorVisible(visible) => {
3498            window.set_cursor_visible(visible);
3499          }
3500          WindowMessage::SetCursorIcon(icon) => {
3501            window.set_cursor_icon(CursorIconWrapper::from(icon).0);
3502          }
3503          WindowMessage::SetCursorPosition(position) => {
3504            let _ = window.set_cursor_position(position);
3505          }
3506          WindowMessage::SetIgnoreCursorEvents(ignore) => {
3507            let _ = window.set_ignore_cursor_events(ignore);
3508          }
3509          WindowMessage::DragWindow => {
3510            let _ = window.drag_window();
3511          }
3512          WindowMessage::ResizeDragWindow(direction) => {
3513            let _ = window.drag_resize_window(match direction {
3514              tauri_runtime::ResizeDirection::East => tao::window::ResizeDirection::East,
3515              tauri_runtime::ResizeDirection::North => tao::window::ResizeDirection::North,
3516              tauri_runtime::ResizeDirection::NorthEast => tao::window::ResizeDirection::NorthEast,
3517              tauri_runtime::ResizeDirection::NorthWest => tao::window::ResizeDirection::NorthWest,
3518              tauri_runtime::ResizeDirection::South => tao::window::ResizeDirection::South,
3519              tauri_runtime::ResizeDirection::SouthEast => tao::window::ResizeDirection::SouthEast,
3520              tauri_runtime::ResizeDirection::SouthWest => tao::window::ResizeDirection::SouthWest,
3521              tauri_runtime::ResizeDirection::West => tao::window::ResizeDirection::West,
3522            });
3523          }
3524          WindowMessage::RequestRedraw => {
3525            window.request_redraw();
3526          }
3527          WindowMessage::SetBadgeCount(_count, _desktop_filename) => {
3528            #[cfg(target_os = "ios")]
3529            window.set_badge_count(
3530              _count.map_or(0, |x| x.clamp(i32::MIN as i64, i32::MAX as i64) as i32),
3531            );
3532
3533            #[cfg(target_os = "macos")]
3534            window.set_badge_label(_count.map(|x| x.to_string()));
3535
3536            #[cfg(any(
3537              target_os = "linux",
3538              target_os = "dragonfly",
3539              target_os = "freebsd",
3540              target_os = "netbsd",
3541              target_os = "openbsd"
3542            ))]
3543            window.set_badge_count(_count, _desktop_filename);
3544          }
3545          WindowMessage::SetBadgeLabel(_label) => {
3546            #[cfg(target_os = "macos")]
3547            window.set_badge_label(_label);
3548          }
3549          WindowMessage::SetOverlayIcon(_icon) => {
3550            #[cfg(windows)]
3551            window.set_overlay_icon(_icon.map(|x| x.0).as_ref());
3552          }
3553          WindowMessage::SetProgressBar(progress_state) => {
3554            window.set_progress_bar(ProgressBarStateWrapper::from(progress_state).0);
3555          }
3556          WindowMessage::SetTitleBarStyle(_style) => {
3557            #[cfg(target_os = "macos")]
3558            match _style {
3559              TitleBarStyle::Visible => {
3560                window.set_titlebar_transparent(false);
3561                window.set_fullsize_content_view(true);
3562              }
3563              TitleBarStyle::Transparent => {
3564                window.set_titlebar_transparent(true);
3565                window.set_fullsize_content_view(false);
3566              }
3567              TitleBarStyle::Overlay => {
3568                window.set_titlebar_transparent(true);
3569                window.set_fullsize_content_view(true);
3570              }
3571              unknown => {
3572                #[cfg(feature = "tracing")]
3573                tracing::warn!("unknown title bar style applied: {unknown}");
3574
3575                #[cfg(not(feature = "tracing"))]
3576                eprintln!("unknown title bar style applied: {unknown}");
3577              }
3578            };
3579          }
3580          WindowMessage::SetTrafficLightPosition(_position) => {
3581            #[cfg(target_os = "macos")]
3582            window.set_traffic_light_inset(_position);
3583          }
3584          WindowMessage::SetTheme(theme) => {
3585            window.set_theme(to_tao_theme(theme));
3586          }
3587          WindowMessage::SetBackgroundColor(color) => {
3588            window.set_background_color(color.map(Into::into))
3589          }
3590        }
3591      }
3592    }
3593    Message::Webview(window_id, webview_id, webview_message) => {
3594      #[cfg(any(
3595        target_os = "macos",
3596        windows,
3597        target_os = "linux",
3598        target_os = "dragonfly",
3599        target_os = "freebsd",
3600        target_os = "netbsd",
3601        target_os = "openbsd"
3602      ))]
3603      if let WebviewMessage::Reparent(new_parent_window_id, tx) = webview_message {
3604        let webview_handle = windows.0.borrow_mut().get_mut(&window_id).and_then(|w| {
3605          w.webviews
3606            .iter()
3607            .position(|w| w.id == webview_id)
3608            .map(|webview_index| w.webviews.remove(webview_index))
3609        });
3610
3611        if let Some(webview) = webview_handle {
3612          if let Some((Some(new_parent_window), new_parent_window_webviews)) = windows
3613            .0
3614            .borrow_mut()
3615            .get_mut(&new_parent_window_id)
3616            .map(|w| (w.inner.clone(), &mut w.webviews))
3617          {
3618            #[cfg(target_os = "macos")]
3619            let reparent_result = {
3620              use wry::WebViewExtMacOS;
3621              webview.inner.reparent(new_parent_window.ns_window() as _)
3622            };
3623            #[cfg(windows)]
3624            let reparent_result = { webview.inner.reparent(new_parent_window.hwnd()) };
3625
3626            #[cfg(any(
3627              target_os = "linux",
3628              target_os = "dragonfly",
3629              target_os = "freebsd",
3630              target_os = "netbsd",
3631              target_os = "openbsd"
3632            ))]
3633            let reparent_result = {
3634              if let Some(container) = new_parent_window.default_vbox() {
3635                webview.inner.reparent(container)
3636              } else {
3637                Err(wry::Error::MessageSender)
3638              }
3639            };
3640
3641            match reparent_result {
3642              Ok(_) => {
3643                new_parent_window_webviews.push(webview);
3644                tx.send(Ok(())).unwrap();
3645              }
3646              Err(e) => {
3647                log::error!("failed to reparent webview: {e}");
3648                tx.send(Err(Error::FailedToSendMessage)).unwrap();
3649              }
3650            }
3651          }
3652        } else {
3653          tx.send(Err(Error::FailedToSendMessage)).unwrap();
3654        }
3655
3656        return;
3657      }
3658
3659      let webview_handle = windows.0.borrow().get(&window_id).map(|w| {
3660        (
3661          w.inner.clone(),
3662          w.webviews.iter().find(|w| w.id == webview_id).cloned(),
3663        )
3664      });
3665      if let Some((Some(window), Some(webview))) = webview_handle {
3666        match webview_message {
3667          WebviewMessage::WebviewEvent(_) => { /* already handled */ }
3668          WebviewMessage::SynthesizedWindowEvent(_) => { /* already handled */ }
3669          WebviewMessage::Reparent(_window_id, _tx) => { /* already handled */ }
3670          WebviewMessage::AddEventListener(id, listener) => {
3671            webview
3672              .webview_event_listeners
3673              .lock()
3674              .unwrap()
3675              .insert(id, listener);
3676          }
3677
3678          #[cfg(all(feature = "tracing", not(target_os = "android")))]
3679          WebviewMessage::EvaluateScript(script, tx, span) => {
3680            let _span = span.entered();
3681            if let Err(e) = webview.evaluate_script(&script) {
3682              log::error!("{e}");
3683            }
3684            tx.send(()).unwrap();
3685          }
3686          #[cfg(not(all(feature = "tracing", not(target_os = "android"))))]
3687          WebviewMessage::EvaluateScript(script) => {
3688            if let Err(e) = webview.evaluate_script(&script) {
3689              log::error!("{e}");
3690            }
3691          }
3692          #[cfg(all(feature = "tracing", not(target_os = "android")))]
3693          WebviewMessage::EvaluateScriptWithCallback(script, callback, tx, span) => {
3694            let _span = span.entered();
3695            if let Err(e) = webview.evaluate_script_with_callback(&script, callback) {
3696              log::error!("{e}");
3697            }
3698            tx.send(()).unwrap();
3699          }
3700          #[cfg(not(all(feature = "tracing", not(target_os = "android"))))]
3701          WebviewMessage::EvaluateScriptWithCallback(script, callback) => {
3702            if let Err(e) = webview.evaluate_script_with_callback(&script, callback) {
3703              log::error!("{e}");
3704            }
3705          }
3706          WebviewMessage::Navigate(url) => {
3707            if let Err(e) = webview.load_url(url.as_str()) {
3708              log::error!("failed to navigate to url {}: {}", url, e);
3709            }
3710          }
3711          WebviewMessage::Reload => {
3712            if let Err(e) = webview.reload() {
3713              log::error!("failed to reload: {e}");
3714            }
3715          }
3716          WebviewMessage::Show => {
3717            if let Err(e) = webview.set_visible(true) {
3718              log::error!("failed to change webview visibility: {e}");
3719            }
3720          }
3721          WebviewMessage::Hide => {
3722            if let Err(e) = webview.set_visible(false) {
3723              log::error!("failed to change webview visibility: {e}");
3724            }
3725          }
3726          WebviewMessage::Print => {
3727            let _ = webview.print();
3728          }
3729          WebviewMessage::Close => {
3730            #[allow(unknown_lints, clippy::manual_inspect)]
3731            windows.0.borrow_mut().get_mut(&window_id).map(|window| {
3732              if let Some(i) = window.webviews.iter().position(|w| w.id == webview.id) {
3733                window.webviews.remove(i);
3734              }
3735              window
3736            });
3737          }
3738          WebviewMessage::SetBounds(bounds) => {
3739            let bounds: RectWrapper = bounds.into();
3740            let bounds = bounds.0;
3741
3742            if let Some(b) = &mut *webview.bounds.lock().unwrap() {
3743              let scale_factor = window.scale_factor();
3744              let size = bounds.size.to_logical::<f32>(scale_factor);
3745              let position = bounds.position.to_logical::<f32>(scale_factor);
3746              let window_size = window.inner_size().to_logical::<f32>(scale_factor);
3747              b.width_rate = size.width / window_size.width;
3748              b.height_rate = size.height / window_size.height;
3749              b.x_rate = position.x / window_size.width;
3750              b.y_rate = position.y / window_size.height;
3751            }
3752
3753            if let Err(e) = webview.set_bounds(bounds) {
3754              log::error!("failed to set webview size: {e}");
3755            }
3756          }
3757          WebviewMessage::SetSize(size) => match webview.bounds() {
3758            Ok(mut bounds) => {
3759              bounds.size = size;
3760
3761              let scale_factor = window.scale_factor();
3762              let size = size.to_logical::<f32>(scale_factor);
3763
3764              if let Some(b) = &mut *webview.bounds.lock().unwrap() {
3765                let window_size = window.inner_size().to_logical::<f32>(scale_factor);
3766                b.width_rate = size.width / window_size.width;
3767                b.height_rate = size.height / window_size.height;
3768              }
3769
3770              if let Err(e) = webview.set_bounds(bounds) {
3771                log::error!("failed to set webview size: {e}");
3772              }
3773            }
3774            Err(e) => {
3775              log::error!("failed to get webview bounds: {e}");
3776            }
3777          },
3778          WebviewMessage::SetPosition(position) => match webview.bounds() {
3779            Ok(mut bounds) => {
3780              bounds.position = position;
3781
3782              let scale_factor = window.scale_factor();
3783              let position = position.to_logical::<f32>(scale_factor);
3784
3785              if let Some(b) = &mut *webview.bounds.lock().unwrap() {
3786                let window_size = window.inner_size().to_logical::<f32>(scale_factor);
3787                b.x_rate = position.x / window_size.width;
3788                b.y_rate = position.y / window_size.height;
3789              }
3790
3791              if let Err(e) = webview.set_bounds(bounds) {
3792                log::error!("failed to set webview position: {e}");
3793              }
3794            }
3795            Err(e) => {
3796              log::error!("failed to get webview bounds: {e}");
3797            }
3798          },
3799          WebviewMessage::SetZoom(scale_factor) => {
3800            if let Err(e) = webview.zoom(scale_factor) {
3801              log::error!("failed to set webview zoom: {e}");
3802            }
3803          }
3804          WebviewMessage::SetBackgroundColor(color) => {
3805            if let Err(e) =
3806              webview.set_background_color(color.map(Into::into).unwrap_or((255, 255, 255, 255)))
3807            {
3808              log::error!("failed to set webview background color: {e}");
3809            }
3810          }
3811          WebviewMessage::ClearAllBrowsingData => {
3812            if let Err(e) = webview.clear_all_browsing_data() {
3813              log::error!("failed to clear webview browsing data: {e}");
3814            }
3815          }
3816          // Getters
3817          WebviewMessage::Url(tx) => {
3818            tx.send(
3819              webview
3820                .url()
3821                .map(|u| u.parse().expect("invalid webview URL"))
3822                .map_err(|_| Error::FailedToSendMessage),
3823            )
3824            .unwrap();
3825          }
3826
3827          WebviewMessage::Cookies(tx) => {
3828            tx.send(webview.cookies().map_err(|_| Error::FailedToSendMessage))
3829              .unwrap();
3830          }
3831
3832          WebviewMessage::SetCookie(cookie) => {
3833            if let Err(e) = webview.set_cookie(&cookie) {
3834              log::error!("failed to set webview cookie: {e}");
3835            }
3836          }
3837
3838          WebviewMessage::DeleteCookie(cookie) => {
3839            if let Err(e) = webview.delete_cookie(&cookie) {
3840              log::error!("failed to delete webview cookie: {e}");
3841            }
3842          }
3843
3844          WebviewMessage::CookiesForUrl(url, tx) => {
3845            let webview_cookies = webview
3846              .cookies_for_url(url.as_str())
3847              .map_err(|_| Error::FailedToSendMessage);
3848            tx.send(webview_cookies).unwrap();
3849          }
3850
3851          WebviewMessage::Bounds(tx) => {
3852            tx.send(
3853              webview
3854                .bounds()
3855                .map(|bounds| tauri_runtime::dpi::Rect {
3856                  size: bounds.size,
3857                  position: bounds.position,
3858                })
3859                .map_err(|_| Error::FailedToSendMessage),
3860            )
3861            .unwrap();
3862          }
3863          WebviewMessage::Position(tx) => {
3864            tx.send(
3865              webview
3866                .bounds()
3867                .map(|bounds| bounds.position.to_physical(window.scale_factor()))
3868                .map_err(|_| Error::FailedToSendMessage),
3869            )
3870            .unwrap();
3871          }
3872          WebviewMessage::Size(tx) => {
3873            tx.send(
3874              webview
3875                .bounds()
3876                .map(|bounds| bounds.size.to_physical(window.scale_factor()))
3877                .map_err(|_| Error::FailedToSendMessage),
3878            )
3879            .unwrap();
3880          }
3881          WebviewMessage::SetFocus => {
3882            if let Err(e) = webview.focus() {
3883              log::error!("failed to focus webview: {e}");
3884            }
3885          }
3886          WebviewMessage::SetAutoResize(auto_resize) => match webview.bounds() {
3887            Ok(bounds) => {
3888              let scale_factor = window.scale_factor();
3889              let window_size = window.inner_size().to_logical::<f32>(scale_factor);
3890              *webview.bounds.lock().unwrap() = if auto_resize {
3891                let size = bounds.size.to_logical::<f32>(scale_factor);
3892                let position = bounds.position.to_logical::<f32>(scale_factor);
3893                Some(WebviewBounds {
3894                  x_rate: position.x / window_size.width,
3895                  y_rate: position.y / window_size.height,
3896                  width_rate: size.width / window_size.width,
3897                  height_rate: size.height / window_size.height,
3898                })
3899              } else {
3900                None
3901              };
3902            }
3903            Err(e) => {
3904              log::error!("failed to get webview bounds: {e}");
3905            }
3906          },
3907          WebviewMessage::WithWebview(f) => {
3908            #[cfg(any(
3909              target_os = "linux",
3910              target_os = "dragonfly",
3911              target_os = "freebsd",
3912              target_os = "netbsd",
3913              target_os = "openbsd"
3914            ))]
3915            {
3916              f(webview.webview());
3917            }
3918            #[cfg(target_os = "macos")]
3919            {
3920              use wry::WebViewExtMacOS;
3921              let platform_webview = webview.webview();
3922              let manager = webview.manager();
3923              let ns_window = webview.ns_window();
3924              f(Webview {
3925                webview: Retained::as_ptr(&platform_webview).cast_mut() as *mut std::ffi::c_void,
3926                manager: Retained::as_ptr(&manager).cast_mut() as *mut std::ffi::c_void,
3927                ns_window: Retained::as_ptr(&ns_window).cast_mut() as *mut std::ffi::c_void,
3928              });
3929            }
3930            #[cfg(target_os = "ios")]
3931            {
3932              use wry::WebViewExtIOS;
3933              let platform_webview = webview.inner.webview();
3934              let manager = webview.inner.manager();
3935
3936              f(Webview {
3937                webview: Retained::as_ptr(&platform_webview).cast_mut() as *mut std::ffi::c_void,
3938                manager: Retained::as_ptr(&manager).cast_mut() as *mut std::ffi::c_void,
3939                view_controller: window.ui_view_controller(),
3940              });
3941            }
3942            #[cfg(windows)]
3943            {
3944              f(Webview {
3945                controller: webview.controller(),
3946                environment: webview.environment(),
3947              });
3948            }
3949            #[cfg(target_os = "android")]
3950            {
3951              f(webview.handle())
3952            }
3953          }
3954          #[cfg(any(debug_assertions, feature = "devtools"))]
3955          WebviewMessage::OpenDevTools => {
3956            webview.open_devtools();
3957          }
3958          #[cfg(any(debug_assertions, feature = "devtools"))]
3959          WebviewMessage::CloseDevTools => {
3960            webview.close_devtools();
3961          }
3962          #[cfg(any(debug_assertions, feature = "devtools"))]
3963          WebviewMessage::IsDevToolsOpen(tx) => {
3964            tx.send(webview.is_devtools_open()).unwrap();
3965          }
3966        }
3967      }
3968    }
3969    Message::CreateWebview(window_id, handler, sender) => {
3970      let window = windows.0.borrow().get(&window_id).map(|w| {
3971        (
3972          w.inner.clone(),
3973          CreateWebviewOptions {
3974            #[cfg(windows)]
3975            focused_webview: w.focused_webview.clone(),
3976          },
3977        )
3978      });
3979      if let Some((Some(window), options)) = window {
3980        match handler(&window, options) {
3981          Ok(webview) => {
3982            if let Some(w) = windows.0.borrow_mut().get_mut(&window_id) {
3983              w.webviews.push(webview);
3984              w.has_children.store(true, Ordering::Relaxed);
3985            }
3986            // SAFETY: The caller calls blocking `rx.recv()` so the receiver will never be dropped before this
3987            sender.send(Ok(())).unwrap();
3988          }
3989          Err(e) => {
3990            // SAFETY: The caller calls blocking `rx.recv()` so the receiver will never be dropped before this
3991            sender.send(Err(e)).unwrap();
3992          }
3993        }
3994      }
3995    }
3996    Message::CreateWindow(window_id, handler, sender) => match handler(event_loop) {
3997      Ok(webview) => {
3998        windows.0.borrow_mut().insert(window_id, webview);
3999        // SAFETY: The caller calls blocking `rx.recv()` so the receiver will never be dropped before this
4000        sender.send(Ok(())).unwrap();
4001      }
4002      Err(e) => {
4003        // SAFETY: The caller calls blocking `rx.recv()` so the receiver will never be dropped before this
4004        sender.send(Err(e)).unwrap();
4005      }
4006    },
4007    Message::CreateRawWindow(window_id, handler, sender) => {
4008      let (label, builder) = handler();
4009
4010      #[cfg(windows)]
4011      let background_color = builder.window.background_color;
4012      #[cfg(windows)]
4013      let is_window_transparent = builder.window.transparent;
4014
4015      if let Ok(window) = builder.build(event_loop) {
4016        window_id_map.insert(window.id(), window_id);
4017
4018        let window = Arc::new(window);
4019
4020        #[cfg(windows)]
4021        let surface = if is_window_transparent {
4022          if let Ok(context) = softbuffer::Context::new(window.clone()) {
4023            if let Ok(mut surface) = softbuffer::Surface::new(&context, window.clone()) {
4024              window.draw_surface(&mut surface, background_color);
4025              Some(surface)
4026            } else {
4027              None
4028            }
4029          } else {
4030            None
4031          }
4032        } else {
4033          None
4034        };
4035
4036        windows.0.borrow_mut().insert(
4037          window_id,
4038          WindowWrapper {
4039            label,
4040            has_children: AtomicBool::new(false),
4041            inner: Some(window.clone()),
4042            window_event_listeners: Default::default(),
4043            webviews: Vec::new(),
4044            #[cfg(windows)]
4045            background_color,
4046            #[cfg(windows)]
4047            is_window_transparent,
4048            #[cfg(windows)]
4049            surface,
4050            #[cfg(windows)]
4051            focused_webview: Default::default(),
4052          },
4053        );
4054        sender.send(Ok(Arc::downgrade(&window))).unwrap();
4055      } else {
4056        sender.send(Err(Error::CreateWindow)).unwrap();
4057      }
4058    }
4059
4060    Message::UserEvent(_) => (),
4061    Message::EventLoopWindowTarget(message) => match message {
4062      EventLoopWindowTargetMessage::CursorPosition(sender) => {
4063        let pos = event_loop
4064          .cursor_position()
4065          .map_err(|_| Error::FailedToSendMessage);
4066        sender.send(pos).unwrap();
4067      }
4068      EventLoopWindowTargetMessage::PrimaryMonitor(sender) => {
4069        sender
4070          .send(
4071            event_loop
4072              .primary_monitor()
4073              .map(|m| MonitorHandleWrapper(m).into()),
4074          )
4075          .unwrap();
4076      }
4077      EventLoopWindowTargetMessage::MonitorFromPoint(sender, (x, y)) => {
4078        sender
4079          .send(
4080            event_loop
4081              .monitor_from_point(x, y)
4082              .map(|m| MonitorHandleWrapper(m).into()),
4083          )
4084          .unwrap();
4085      }
4086      EventLoopWindowTargetMessage::AvailableMonitors(sender) => {
4087        sender
4088          .send(
4089            event_loop
4090              .available_monitors()
4091              .map(|m| MonitorHandleWrapper(m).into())
4092              .collect(),
4093          )
4094          .unwrap();
4095      }
4096      EventLoopWindowTargetMessage::SetTheme(theme) => {
4097        event_loop.set_theme(to_tao_theme(theme));
4098        // On macOS tao caches each window's theme and only refreshes it from the
4099        // system-wide appearance change notification, which the app-level
4100        // `NSApp.setAppearance` call above never posts, so `Window::theme()`
4101        // would keep reporting the previous value. tao's window-level setter
4102        // does update the cache, so push the theme through it as well.
4103        #[cfg(target_os = "macos")]
4104        for window in windows.0.borrow().values() {
4105          if let Some(inner) = &window.inner {
4106            inner.set_theme(to_tao_theme(theme));
4107          }
4108        }
4109      }
4110      EventLoopWindowTargetMessage::SetDeviceEventFilter(filter) => {
4111        event_loop.set_device_event_filter(DeviceEventFilterWrapper::from(filter).0);
4112      }
4113    },
4114  }
4115}
4116
4117fn handle_event_loop<T: UserEvent>(
4118  event: Event<'_, Message<T>>,
4119  event_loop: &EventLoopWindowTarget<Message<T>>,
4120  control_flow: &mut ControlFlow,
4121  context: EventLoopIterationContext<'_, T>,
4122) {
4123  let EventLoopIterationContext {
4124    callback,
4125    window_id_map,
4126    windows,
4127    #[cfg(feature = "tracing")]
4128    active_tracing_spans,
4129  } = context;
4130  if *control_flow != ControlFlow::Exit {
4131    *control_flow = ControlFlow::Wait;
4132  }
4133
4134  match event {
4135    Event::NewEvents(StartCause::Init) => {
4136      callback(RunEvent::Ready);
4137    }
4138
4139    Event::NewEvents(StartCause::Poll) => {
4140      callback(RunEvent::Resumed);
4141    }
4142
4143    Event::MainEventsCleared => {
4144      callback(RunEvent::MainEventsCleared);
4145    }
4146
4147    Event::LoopDestroyed => {
4148      callback(RunEvent::Exit);
4149    }
4150
4151    #[cfg(windows)]
4152    Event::RedrawRequested(id) => {
4153      if let Some(window_id) = window_id_map.get(&id) {
4154        let mut windows_ref = windows.0.borrow_mut();
4155        if let Some(window) = windows_ref.get_mut(&window_id) {
4156          if window.is_window_transparent {
4157            let background_color = window.background_color;
4158            if let Some(surface) = &mut window.surface {
4159              if let Some(window) = &window.inner {
4160                window.draw_surface(surface, background_color);
4161              }
4162            }
4163          }
4164        }
4165      }
4166    }
4167
4168    #[cfg(feature = "tracing")]
4169    Event::RedrawEventsCleared => {
4170      active_tracing_spans.remove_window_draw();
4171    }
4172
4173    Event::UserEvent(Message::Webview(
4174      window_id,
4175      webview_id,
4176      WebviewMessage::WebviewEvent(event),
4177    )) => {
4178      let windows_ref = windows.0.borrow();
4179      if let Some(window) = windows_ref.get(&window_id) {
4180        if let Some(webview) = window.webviews.iter().find(|w| w.id == webview_id) {
4181          let label = webview.label.clone();
4182          let webview_event_listeners = webview.webview_event_listeners.clone();
4183
4184          drop(windows_ref);
4185
4186          callback(RunEvent::WebviewEvent {
4187            label,
4188            event: event.clone(),
4189          });
4190          let listeners = webview_event_listeners.lock().unwrap();
4191          let handlers = listeners.values();
4192          for handler in handlers {
4193            handler(&event);
4194          }
4195        }
4196      }
4197    }
4198
4199    Event::UserEvent(Message::Webview(
4200      window_id,
4201      _webview_id,
4202      WebviewMessage::SynthesizedWindowEvent(event),
4203    )) => {
4204      if let Some(event) = WindowEventWrapper::from(event).0 {
4205        let windows_ref = windows.0.borrow();
4206        let window = windows_ref.get(&window_id);
4207        if let Some(window) = window {
4208          let label = window.label.clone();
4209          let window_event_listeners = window.window_event_listeners.clone();
4210
4211          drop(windows_ref);
4212
4213          callback(RunEvent::WindowEvent {
4214            label,
4215            event: event.clone(),
4216          });
4217
4218          let listeners = window_event_listeners.lock().unwrap();
4219          let handlers = listeners.values();
4220          for handler in handlers {
4221            handler(&event);
4222          }
4223        }
4224      }
4225    }
4226
4227    Event::WindowEvent {
4228      event, window_id, ..
4229    } => {
4230      if let Some(window_id) = window_id_map.get(&window_id) {
4231        {
4232          let windows_ref = windows.0.borrow();
4233          if let Some(window) = windows_ref.get(&window_id) {
4234            if let Some(event) = WindowEventWrapper::parse(window, &event).0 {
4235              let label = window.label.clone();
4236              let window_event_listeners = window.window_event_listeners.clone();
4237
4238              drop(windows_ref);
4239
4240              callback(RunEvent::WindowEvent {
4241                label,
4242                event: event.clone(),
4243              });
4244              let listeners = window_event_listeners.lock().unwrap();
4245              let handlers = listeners.values();
4246              for handler in handlers {
4247                handler(&event);
4248              }
4249            }
4250          }
4251        }
4252
4253        match event {
4254          #[cfg(windows)]
4255          TaoWindowEvent::ThemeChanged(theme) => {
4256            if let Some(window) = windows.0.borrow().get(&window_id) {
4257              for webview in &window.webviews {
4258                let theme = match theme {
4259                  TaoTheme::Dark => wry::Theme::Dark,
4260                  TaoTheme::Light => wry::Theme::Light,
4261                  _ => wry::Theme::Light,
4262                };
4263                if let Err(e) = webview.set_theme(theme) {
4264                  log::error!("failed to set theme: {e}");
4265                }
4266              }
4267            }
4268          }
4269          TaoWindowEvent::CloseRequested => {
4270            on_close_requested(callback, window_id, windows);
4271          }
4272          TaoWindowEvent::Destroyed => {
4273            let removed = windows.0.borrow_mut().remove(&window_id).is_some();
4274            if removed {
4275              let is_empty = windows.0.borrow().is_empty();
4276              if is_empty {
4277                let (tx, rx) = channel();
4278                callback(RunEvent::ExitRequested { code: None, tx });
4279
4280                let recv = rx.try_recv();
4281                let should_prevent = matches!(recv, Ok(ExitRequestedEventAction::Prevent));
4282
4283                if !should_prevent {
4284                  *control_flow = ControlFlow::Exit;
4285                }
4286              }
4287            }
4288          }
4289          TaoWindowEvent::Resized(size) => {
4290            if let Some((Some(window), webviews)) = windows
4291              .0
4292              .borrow()
4293              .get(&window_id)
4294              .map(|w| (w.inner.clone(), w.webviews.clone()))
4295            {
4296              let size = size.to_logical::<f32>(window.scale_factor());
4297              for webview in webviews {
4298                if let Some(b) = &*webview.bounds.lock().unwrap() {
4299                  if let Err(e) = webview.set_bounds(wry::Rect {
4300                    position: LogicalPosition::new(size.width * b.x_rate, size.height * b.y_rate)
4301                      .into(),
4302                    size: LogicalSize::new(size.width * b.width_rate, size.height * b.height_rate)
4303                      .into(),
4304                  }) {
4305                    log::error!("failed to autoresize webview: {e}");
4306                  }
4307                }
4308              }
4309            }
4310          }
4311          _ => {}
4312        }
4313      }
4314    }
4315    Event::UserEvent(message) => match message {
4316      Message::RequestExit(code) => {
4317        let (tx, rx) = channel();
4318        callback(RunEvent::ExitRequested {
4319          code: Some(code),
4320          tx,
4321        });
4322
4323        let recv = rx.try_recv();
4324        let should_prevent = matches!(recv, Ok(ExitRequestedEventAction::Prevent));
4325
4326        if !should_prevent {
4327          *control_flow = ControlFlow::ExitWithCode(code);
4328        }
4329      }
4330      Message::Window(id, WindowMessage::Close) => {
4331        on_close_requested(callback, id, windows);
4332      }
4333      Message::Window(id, WindowMessage::Destroy) => {
4334        on_window_close(id, windows);
4335      }
4336      Message::UserEvent(t) => callback(RunEvent::UserEvent(t)),
4337      message => {
4338        handle_user_message(
4339          event_loop,
4340          message,
4341          UserMessageContext {
4342            window_id_map,
4343            windows,
4344          },
4345        );
4346      }
4347    },
4348    #[cfg(any(target_os = "macos", target_os = "ios", target_os = "android"))]
4349    Event::Opened { urls } => {
4350      callback(RunEvent::Opened { urls });
4351    }
4352    #[cfg(target_os = "macos")]
4353    Event::Reopen {
4354      has_visible_windows,
4355      ..
4356    } => callback(RunEvent::Reopen {
4357      has_visible_windows,
4358    }),
4359    #[cfg(target_os = "ios")]
4360    Event::SceneRequested { scene, options } => {
4361      callback(RunEvent::SceneRequested { scene, options });
4362    }
4363    _ => (),
4364  }
4365}
4366
4367fn on_close_requested<'a, T: UserEvent>(
4368  callback: &'a mut (dyn FnMut(RunEvent<T>) + 'static),
4369  window_id: WindowId,
4370  windows: &WindowsStore,
4371) {
4372  let (tx, rx) = channel();
4373  let windows_ref = windows.0.borrow();
4374  if let Some(w) = windows_ref.get(&window_id) {
4375    let label = w.label.clone();
4376    let window_event_listeners = w.window_event_listeners.clone();
4377
4378    drop(windows_ref);
4379
4380    let listeners = window_event_listeners.lock().unwrap();
4381    let handlers = listeners.values();
4382    for handler in handlers {
4383      handler(&WindowEvent::CloseRequested {
4384        signal_tx: tx.clone(),
4385      });
4386    }
4387    callback(RunEvent::WindowEvent {
4388      label,
4389      event: WindowEvent::CloseRequested { signal_tx: tx },
4390    });
4391    if let Ok(true) = rx.try_recv() {
4392    } else {
4393      on_window_close(window_id, windows);
4394    }
4395  }
4396}
4397
4398fn on_window_close(window_id: WindowId, windows: &WindowsStore) {
4399  if let Some(window_wrapper) = windows.0.borrow_mut().get_mut(&window_id) {
4400    window_wrapper.inner = None;
4401    #[cfg(windows)]
4402    window_wrapper.surface.take();
4403  }
4404}
4405
4406fn parse_proxy_url(url: &Url) -> Result<ProxyConfig> {
4407  let host = url.host().map(|h| h.to_string()).unwrap_or_default();
4408  let port = url.port().map(|p| p.to_string()).unwrap_or_default();
4409
4410  if url.scheme() == "http" {
4411    let config = ProxyConfig::Http(ProxyEndpoint { host, port });
4412
4413    Ok(config)
4414  } else if url.scheme() == "socks5" {
4415    let config = ProxyConfig::Socks5(ProxyEndpoint { host, port });
4416
4417    Ok(config)
4418  } else {
4419    Err(Error::InvalidProxyUrl)
4420  }
4421}
4422
4423fn create_window<T: UserEvent, F: Fn(RawWindow) + Send + 'static>(
4424  window_id: WindowId,
4425  webview_id: u32,
4426  event_loop: &EventLoopWindowTarget<Message<T>>,
4427  context: &Context<T>,
4428  pending: PendingWindow<T, Wry<T>>,
4429  after_window_creation: Option<F>,
4430) -> Result<WindowWrapper> {
4431  #[allow(unused_mut)]
4432  let PendingWindow {
4433    mut window_builder,
4434    label,
4435    webview,
4436  } = pending;
4437
4438  #[cfg(feature = "tracing")]
4439  let _webview_create_span = tracing::debug_span!("wry::webview::create").entered();
4440  #[cfg(feature = "tracing")]
4441  let window_draw_span = tracing::debug_span!("wry::window::draw").entered();
4442  #[cfg(feature = "tracing")]
4443  let window_create_span =
4444    tracing::debug_span!(parent: &window_draw_span, "wry::window::create").entered();
4445
4446  let window_event_listeners = WindowEventListeners::default();
4447
4448  #[cfg(windows)]
4449  let background_color = window_builder.inner.window.background_color;
4450  #[cfg(windows)]
4451  let is_window_transparent = window_builder.inner.window.transparent;
4452
4453  #[cfg(target_os = "macos")]
4454  {
4455    if window_builder.tabbing_identifier.is_none()
4456      || window_builder.inner.window.transparent
4457      || !window_builder.inner.window.decorations
4458    {
4459      window_builder.inner = window_builder.inner.with_automatic_window_tabbing(false);
4460    }
4461  }
4462
4463  #[cfg(desktop)]
4464  if window_builder.prevent_overflow.is_some() || window_builder.center {
4465    let monitor = if let Some(window_position) = &window_builder.inner.window.position {
4466      find_monitor_for_position(event_loop.available_monitors(), *window_position)
4467    } else {
4468      event_loop.primary_monitor()
4469    };
4470    if let Some(monitor) = monitor {
4471      let scale_factor = monitor.scale_factor();
4472      let desired_size = window_builder
4473        .inner
4474        .window
4475        .inner_size
4476        .unwrap_or_else(|| PhysicalSize::new(800, 600).into());
4477      let mut inner_size = window_builder
4478        .inner
4479        .window
4480        .inner_size_constraints
4481        .clamp(desired_size, scale_factor)
4482        .to_physical::<u32>(scale_factor);
4483      let mut window_size = inner_size;
4484      #[allow(unused_mut)]
4485      // Left and right window shadow counts as part of the window on Windows
4486      // We need to include it when calculating positions, but not size
4487      let mut shadow_width = 0;
4488      #[cfg(windows)]
4489      if window_builder.inner.window.decorations {
4490        use windows::Win32::UI::WindowsAndMessaging::{AdjustWindowRect, WS_OVERLAPPEDWINDOW};
4491        let mut rect = windows::Win32::Foundation::RECT::default();
4492        let result = unsafe { AdjustWindowRect(&mut rect, WS_OVERLAPPEDWINDOW, false) };
4493        if result.is_ok() {
4494          shadow_width = (rect.right - rect.left) as u32;
4495          // rect.bottom is made out of shadow, and we don't care about it
4496          window_size.height += -rect.top as u32;
4497        }
4498      }
4499
4500      if let Some(margin) = window_builder.prevent_overflow {
4501        let work_area = monitor.work_area();
4502        let margin = margin.to_physical::<u32>(scale_factor);
4503        let constraint = PhysicalSize::new(
4504          work_area.size.width - margin.width,
4505          work_area.size.height - margin.height,
4506        );
4507        if window_size.width > constraint.width || window_size.height > constraint.height {
4508          if window_size.width > constraint.width {
4509            inner_size.width = inner_size
4510              .width
4511              .saturating_sub(window_size.width - constraint.width);
4512            window_size.width = constraint.width;
4513          }
4514          if window_size.height > constraint.height {
4515            inner_size.height = inner_size
4516              .height
4517              .saturating_sub(window_size.height - constraint.height);
4518            window_size.height = constraint.height;
4519          }
4520          window_builder.inner.window.inner_size = Some(inner_size.into());
4521        }
4522      }
4523
4524      if window_builder.center {
4525        window_size.width += shadow_width;
4526        let position = window::calculate_window_center_position(window_size, monitor);
4527        let logical_position = position.to_logical::<f64>(scale_factor);
4528        window_builder = window_builder.position(logical_position.x, logical_position.y);
4529      }
4530    }
4531  };
4532
4533  #[cfg(any(target_os = "macos", target_os = "linux"))]
4534  let (initial_position, is_fullscreen) = (
4535    window_builder.inner.window.position,
4536    window_builder.inner.window.fullscreen.is_some(),
4537  );
4538
4539  // If fullscreen is requested with an explicit position, resolve the target
4540  // monitor up front so the window is created fullscreen on that display.
4541  #[cfg(any(target_os = "macos", target_os = "linux"))]
4542  if let (true, Some(position)) = (is_fullscreen, initial_position) {
4543    if let Some(target_monitor) =
4544      find_monitor_for_position(event_loop.available_monitors(), position)
4545    {
4546      window_builder.inner.window.fullscreen = Some(Fullscreen::Borderless(Some(target_monitor)));
4547    }
4548  }
4549
4550  let window = window_builder
4551    .inner
4552    .build(event_loop)
4553    .inspect_err(|e| log::error!("Error creating window: {e:?}"))
4554    .map_err(|_| Error::CreateWindow)?;
4555
4556  // On macOS, `with_position` uses the content origin; the title bar is added
4557  // above it. `set_outer_position` is needed for precise window placement.
4558  #[cfg(target_os = "macos")]
4559  if !is_fullscreen {
4560    if let Some(position) = initial_position {
4561      window.set_outer_position(position);
4562    }
4563  }
4564
4565  #[cfg(feature = "tracing")]
4566  {
4567    drop(window_create_span);
4568
4569    context
4570      .main_thread
4571      .active_tracing_spans
4572      .0
4573      .borrow_mut()
4574      .push(ActiveTracingSpan::WindowDraw {
4575        id: window.id(),
4576        span: window_draw_span,
4577      });
4578  }
4579
4580  context.window_id_map.insert(window.id(), window_id);
4581
4582  if let Some(handler) = after_window_creation {
4583    let raw = RawWindow {
4584      #[cfg(windows)]
4585      hwnd: window.hwnd(),
4586      #[cfg(any(
4587        target_os = "linux",
4588        target_os = "dragonfly",
4589        target_os = "freebsd",
4590        target_os = "netbsd",
4591        target_os = "openbsd"
4592      ))]
4593      gtk_window: window.gtk_window(),
4594      #[cfg(any(
4595        target_os = "linux",
4596        target_os = "dragonfly",
4597        target_os = "freebsd",
4598        target_os = "netbsd",
4599        target_os = "openbsd"
4600      ))]
4601      default_vbox: window.default_vbox(),
4602      _marker: &std::marker::PhantomData,
4603    };
4604    handler(raw);
4605  }
4606
4607  let mut webviews = Vec::new();
4608
4609  #[cfg(windows)]
4610  let focused_webview = Arc::new(Mutex::new(FocusState::default()));
4611
4612  #[cfg(feature = "unstable")]
4613  let has_children = webview.is_some();
4614  #[cfg(not(feature = "unstable"))]
4615  let has_children = false;
4616
4617  if let Some(webview) = webview {
4618    webviews.push(create_webview(
4619      #[cfg(feature = "unstable")]
4620      WebviewKind::WindowChild,
4621      #[cfg(not(feature = "unstable"))]
4622      WebviewKind::WindowContent,
4623      &window,
4624      Arc::new(Mutex::new(window_id)),
4625      webview_id,
4626      context,
4627      webview,
4628      #[cfg(windows)]
4629      focused_webview.clone(),
4630    )?);
4631  }
4632
4633  let window = Arc::new(window);
4634
4635  #[cfg(windows)]
4636  let surface = if is_window_transparent {
4637    if let Ok(context) = softbuffer::Context::new(window.clone()) {
4638      if let Ok(mut surface) = softbuffer::Surface::new(&context, window.clone()) {
4639        window.draw_surface(&mut surface, background_color);
4640        Some(surface)
4641      } else {
4642        None
4643      }
4644    } else {
4645      None
4646    }
4647  } else {
4648    None
4649  };
4650
4651  Ok(WindowWrapper {
4652    label,
4653    has_children: AtomicBool::new(has_children),
4654    inner: Some(window),
4655    webviews,
4656    window_event_listeners,
4657    #[cfg(windows)]
4658    background_color,
4659    #[cfg(windows)]
4660    is_window_transparent,
4661    #[cfg(windows)]
4662    surface,
4663    #[cfg(windows)]
4664    focused_webview,
4665  })
4666}
4667
4668/// the kind of the webview
4669#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
4670enum WebviewKind {
4671  /// webview is the entire window content
4672  WindowContent,
4673  /// webview is a child of the window, which can contain other webviews too
4674  WindowChild,
4675}
4676
4677#[derive(Debug, Clone)]
4678struct WebviewBounds {
4679  x_rate: f32,
4680  y_rate: f32,
4681  width_rate: f32,
4682  height_rate: f32,
4683}
4684
4685fn create_webview<T: UserEvent>(
4686  kind: WebviewKind,
4687  window: &Window,
4688  window_id: Arc<Mutex<WindowId>>,
4689  id: WebviewId,
4690  context: &Context<T>,
4691  pending: PendingWebview<T, Wry<T>>,
4692  #[cfg(windows)] focused_webview: Arc<Mutex<FocusState>>,
4693) -> Result<WebviewWrapper> {
4694  if !context.webview_runtime_installed {
4695    #[cfg(all(not(debug_assertions), windows))]
4696    dialog::error(
4697      r#"Could not find the WebView2 Runtime.
4698
4699Make sure it is installed or download it from <A href="https://developer.microsoft.com/en-us/microsoft-edge/webview2">https://developer.microsoft.com/en-us/microsoft-edge/webview2</A>
4700
4701You may have it installed on another user account, but it is not available for this one.
4702"#,
4703    );
4704
4705    if cfg!(target_os = "macos") {
4706      log::warn!("WebKit webview runtime not found, attempting to create webview anyway.");
4707    } else {
4708      return Err(Error::WebviewRuntimeNotInstalled);
4709    }
4710  }
4711
4712  #[allow(unused_mut)]
4713  let PendingWebview {
4714    webview_attributes,
4715    uri_scheme_protocols,
4716    label,
4717    ipc_handler,
4718    url,
4719    ..
4720  } = pending;
4721
4722  let mut web_context = context
4723    .main_thread
4724    .web_context
4725    .lock()
4726    .expect("poisoned WebContext store");
4727  let is_first_context = web_context.is_empty();
4728  // the context must be stored on the HashMap because it must outlive the WebView on macOS
4729  let automation_enabled = std::env::var("TAURI_WEBVIEW_AUTOMATION").as_deref() == Ok("true");
4730  let web_context_key = webview_attributes.data_directory;
4731  let entry = web_context.entry(web_context_key.clone());
4732  let web_context = match entry {
4733    Occupied(occupied) => {
4734      let occupied = occupied.into_mut();
4735      occupied.referenced_by_webviews.insert(label.clone());
4736      occupied
4737    }
4738    Vacant(vacant) => {
4739      let mut web_context = WryWebContext::new(web_context_key.clone());
4740      web_context.set_allows_automation(if automation_enabled {
4741        is_first_context
4742      } else {
4743        false
4744      });
4745      vacant.insert(WebContext {
4746        inner: web_context,
4747        referenced_by_webviews: [label.clone()].into(),
4748        registered_custom_protocols: HashSet::new(),
4749      })
4750    }
4751  };
4752
4753  let mut webview_builder = WebViewBuilder::new_with_web_context(&mut web_context.inner)
4754    .with_id(&label)
4755    .with_focused(webview_attributes.focus)
4756    .with_transparent(webview_attributes.transparent)
4757    .with_accept_first_mouse(webview_attributes.accept_first_mouse)
4758    .with_incognito(webview_attributes.incognito)
4759    .with_clipboard(webview_attributes.clipboard)
4760    .with_hotkeys_zoom(webview_attributes.zoom_hotkeys_enabled)
4761    .with_general_autofill_enabled(webview_attributes.general_autofill_enabled);
4762
4763  if url != "about:blank" {
4764    webview_builder = webview_builder.with_url(&url);
4765  }
4766
4767  #[cfg(target_os = "macos")]
4768  if let Some(webview_configuration) = webview_attributes.webview_configuration {
4769    webview_builder = webview_builder.with_webview_configuration(webview_configuration);
4770  }
4771
4772  #[cfg(any(target_os = "windows", target_os = "android"))]
4773  {
4774    webview_builder = webview_builder.with_https_scheme(webview_attributes.use_https_scheme);
4775  }
4776
4777  if let Some(background_throttling) = webview_attributes.background_throttling {
4778    webview_builder = webview_builder.with_background_throttling(match background_throttling {
4779      tauri_utils::config::BackgroundThrottlingPolicy::Disabled => {
4780        wry::BackgroundThrottlingPolicy::Disabled
4781      }
4782      tauri_utils::config::BackgroundThrottlingPolicy::Suspend => {
4783        wry::BackgroundThrottlingPolicy::Suspend
4784      }
4785      tauri_utils::config::BackgroundThrottlingPolicy::Throttle => {
4786        wry::BackgroundThrottlingPolicy::Throttle
4787      }
4788    });
4789  }
4790
4791  if webview_attributes.javascript_disabled {
4792    webview_builder = webview_builder.with_javascript_disabled();
4793  }
4794
4795  if let Some(color) = webview_attributes.background_color {
4796    webview_builder = webview_builder.with_background_color(color.into());
4797  }
4798
4799  if webview_attributes.drag_drop_handler_enabled {
4800    let proxy = context.proxy.clone();
4801    let window_id_ = window_id.clone();
4802    webview_builder = webview_builder.with_drag_drop_handler(move |event| {
4803      let event = match event {
4804        WryDragDropEvent::Enter {
4805          paths,
4806          position: (x, y),
4807        } => DragDropEvent::Enter {
4808          paths,
4809          position: PhysicalPosition::new(x as _, y as _),
4810        },
4811        WryDragDropEvent::Over { position: (x, y) } => DragDropEvent::Over {
4812          position: PhysicalPosition::new(x as _, y as _),
4813        },
4814        WryDragDropEvent::Drop {
4815          paths,
4816          position: (x, y),
4817        } => DragDropEvent::Drop {
4818          paths,
4819          position: PhysicalPosition::new(x as _, y as _),
4820        },
4821        WryDragDropEvent::Leave => DragDropEvent::Leave,
4822        _ => unimplemented!(),
4823      };
4824
4825      let message = if kind == WebviewKind::WindowContent {
4826        WebviewMessage::SynthesizedWindowEvent(SynthesizedWindowEvent::DragDrop(event))
4827      } else {
4828        WebviewMessage::WebviewEvent(WebviewEvent::DragDrop(event))
4829      };
4830
4831      let _ = proxy.send_event(Message::Webview(*window_id_.lock().unwrap(), id, message));
4832      true
4833    });
4834  }
4835
4836  if let Some(navigation_handler) = pending.navigation_handler {
4837    webview_builder = webview_builder.with_navigation_handler(move |url| {
4838      url
4839        .parse()
4840        .map(|url| navigation_handler(&url))
4841        .unwrap_or(true)
4842    });
4843  }
4844
4845  if let Some(new_window_handler) = pending.new_window_handler {
4846    #[cfg(desktop)]
4847    let context = context.clone();
4848    webview_builder = webview_builder.with_new_window_req_handler(move |url, features| {
4849      let Ok(url) = url.parse() else {
4850        return wry::NewWindowResponse::Deny;
4851      };
4852      let response = new_window_handler(
4853        url,
4854        tauri_runtime::webview::NewWindowFeatures::new(
4855          features.size,
4856          features.position,
4857          tauri_runtime::webview::NewWindowOpener {
4858            #[cfg(desktop)]
4859            webview: features.opener.webview,
4860            #[cfg(windows)]
4861            environment: features.opener.environment,
4862            #[cfg(target_os = "macos")]
4863            target_configuration: features.opener.target_configuration,
4864          },
4865        ),
4866      );
4867      match response {
4868        tauri_runtime::webview::NewWindowResponse::Allow => wry::NewWindowResponse::Allow,
4869        #[cfg(desktop)]
4870        tauri_runtime::webview::NewWindowResponse::Create { window_id } => {
4871          let windows = &context.main_thread.windows.0;
4872          let webview = windows
4873            .borrow()
4874            .get(&window_id)
4875            .unwrap()
4876            .webviews
4877            .first()
4878            .unwrap()
4879            .clone();
4880
4881          #[cfg(desktop)]
4882          wry::NewWindowResponse::Create {
4883            #[cfg(target_os = "macos")]
4884            webview: wry::WebViewExtMacOS::webview(&*webview).as_super().into(),
4885            #[cfg(any(
4886              target_os = "linux",
4887              target_os = "dragonfly",
4888              target_os = "freebsd",
4889              target_os = "netbsd",
4890              target_os = "openbsd",
4891            ))]
4892            webview: webview.webview(),
4893            #[cfg(windows)]
4894            webview: webview.webview(),
4895          }
4896        }
4897        tauri_runtime::webview::NewWindowResponse::Deny => wry::NewWindowResponse::Deny,
4898      }
4899    });
4900  }
4901
4902  if let Some(document_title_changed_handler) = pending.document_title_changed_handler {
4903    webview_builder =
4904      webview_builder.with_document_title_changed_handler(document_title_changed_handler)
4905  }
4906
4907  if let Some(permission_request_handler) = pending.permission_request_handler {
4908    webview_builder = webview_builder.with_permission_handler(move |kind| {
4909      let kind = webview_permissions::from_wry_permission_kind(kind);
4910      let response = permission_request_handler(kind);
4911      webview_permissions::to_wry_permission_response(response)
4912    });
4913  }
4914
4915  let webview_bounds = if let Some(bounds) = webview_attributes.bounds {
4916    let bounds: RectWrapper = bounds.into();
4917    let bounds = bounds.0;
4918
4919    let scale_factor = window.scale_factor();
4920    let position = bounds.position.to_logical::<f32>(scale_factor);
4921    let size = bounds.size.to_logical::<f32>(scale_factor);
4922
4923    webview_builder = webview_builder.with_bounds(bounds);
4924
4925    let window_size = window.inner_size().to_logical::<f32>(scale_factor);
4926
4927    if webview_attributes.auto_resize {
4928      Some(WebviewBounds {
4929        x_rate: position.x / window_size.width,
4930        y_rate: position.y / window_size.height,
4931        width_rate: size.width / window_size.width,
4932        height_rate: size.height / window_size.height,
4933      })
4934    } else {
4935      None
4936    }
4937  } else {
4938    #[cfg(feature = "unstable")]
4939    {
4940      webview_builder = webview_builder.with_bounds(wry::Rect {
4941        position: LogicalPosition::new(0, 0).into(),
4942        size: window.inner_size().into(),
4943      });
4944      Some(WebviewBounds {
4945        x_rate: 0.,
4946        y_rate: 0.,
4947        width_rate: 1.,
4948        height_rate: 1.,
4949      })
4950    }
4951    #[cfg(not(feature = "unstable"))]
4952    None
4953  };
4954
4955  if let Some(download_handler) = pending.download_handler {
4956    let download_handler_ = download_handler.clone();
4957    webview_builder = webview_builder.with_download_started_handler(move |url, path| {
4958      if let Ok(url) = url.parse() {
4959        download_handler_(DownloadEvent::Requested {
4960          url,
4961          destination: path,
4962        })
4963      } else {
4964        false
4965      }
4966    });
4967    webview_builder = webview_builder.with_download_completed_handler(move |url, path, success| {
4968      if let Ok(url) = url.parse() {
4969        download_handler(DownloadEvent::Finished { url, path, success });
4970      }
4971    });
4972  }
4973
4974  if let Some(page_load_handler) = pending.on_page_load_handler {
4975    webview_builder = webview_builder.with_on_page_load_handler(move |event, url| {
4976      if let Ok(url) = url.parse() {
4977        page_load_handler(
4978          url,
4979          match event {
4980            wry::PageLoadEvent::Started => tauri_runtime::webview::PageLoadEvent::Started,
4981            wry::PageLoadEvent::Finished => tauri_runtime::webview::PageLoadEvent::Finished,
4982          },
4983        )
4984      };
4985    });
4986  }
4987
4988  if let Some(user_agent) = webview_attributes.user_agent {
4989    webview_builder = webview_builder.with_user_agent(&user_agent);
4990  }
4991
4992  if let Some(proxy_url) = webview_attributes.proxy_url {
4993    let config = parse_proxy_url(&proxy_url)?;
4994
4995    webview_builder = webview_builder.with_proxy_config(config);
4996  }
4997
4998  #[cfg(windows)]
4999  {
5000    if let Some(additional_browser_args) = webview_attributes.additional_browser_args {
5001      webview_builder = webview_builder.with_additional_browser_args(&additional_browser_args);
5002    }
5003
5004    if let Some(environment) = webview_attributes.environment {
5005      webview_builder = webview_builder.with_environment(environment);
5006    }
5007
5008    webview_builder = webview_builder.with_theme(match window.theme() {
5009      TaoTheme::Dark => wry::Theme::Dark,
5010      TaoTheme::Light => wry::Theme::Light,
5011      _ => wry::Theme::Light,
5012    });
5013
5014    webview_builder =
5015      webview_builder.with_scroll_bar_style(match webview_attributes.scroll_bar_style {
5016        ScrollBarStyle::Default => WryScrollBarStyle::Default,
5017        ScrollBarStyle::FluentOverlay => WryScrollBarStyle::FluentOverlay,
5018        _ => unreachable!(),
5019      });
5020  }
5021
5022  #[cfg(windows)]
5023  {
5024    webview_builder = webview_builder
5025      .with_browser_extensions_enabled(webview_attributes.browser_extensions_enabled);
5026  }
5027
5028  #[cfg(any(
5029    windows,
5030    target_os = "linux",
5031    target_os = "dragonfly",
5032    target_os = "freebsd",
5033    target_os = "netbsd",
5034    target_os = "openbsd"
5035  ))]
5036  {
5037    if let Some(path) = &webview_attributes.extensions_path {
5038      webview_builder = webview_builder.with_extensions_path(path);
5039    }
5040  }
5041
5042  #[cfg(any(
5043    target_os = "linux",
5044    target_os = "dragonfly",
5045    target_os = "freebsd",
5046    target_os = "netbsd",
5047    target_os = "openbsd"
5048  ))]
5049  {
5050    if let Some(related_view) = webview_attributes.related_view {
5051      webview_builder = webview_builder.with_related_view(related_view);
5052    }
5053  }
5054
5055  #[cfg(any(target_os = "macos", target_os = "ios"))]
5056  {
5057    if let Some(data_store_identifier) = &webview_attributes.data_store_identifier {
5058      webview_builder = webview_builder.with_data_store_identifier(*data_store_identifier);
5059    }
5060
5061    webview_builder =
5062      webview_builder.with_allow_link_preview(webview_attributes.allow_link_preview);
5063
5064    if let Some(on_web_content_process_terminate_handler) =
5065      pending.on_web_content_process_terminate_handler
5066    {
5067      webview_builder = webview_builder
5068        .with_on_web_content_process_terminate_handler(on_web_content_process_terminate_handler);
5069    } else {
5070      log::debug!("web content process terminated");
5071      let context_ = context.clone();
5072      let window_id_ = window_id.clone();
5073      webview_builder = webview_builder.with_on_web_content_process_terminate_handler(move || {
5074        if let Ok(windows) = &context_.main_thread.windows.0.try_borrow() {
5075          if let Some(window) = windows.get(&*window_id_.lock().unwrap()) {
5076            if let Some(webview) = window.webviews.iter().find(|w| w.id == id) {
5077              match webview.reload() {
5078                Ok(_) => log::debug!("webview reloaded"),
5079                Err(e) => log::error!("failed to reload webview: {e}"),
5080              }
5081            } else {
5082              log::error!("failed to find webview")
5083            }
5084          } else {
5085            log::error!("failed to get window")
5086          }
5087        } else {
5088          log::error!("failed to borrow windows")
5089        }
5090      });
5091    }
5092  }
5093
5094  #[cfg(target_os = "ios")]
5095  {
5096    webview_builder = webview_builder.with_limit_navigations_to_app_bound_domains(
5097      webview_attributes.limit_navigations_to_app_bound_domains,
5098    );
5099
5100    if let Some(input_accessory_view_builder) = webview_attributes.input_accessory_view_builder {
5101      webview_builder = webview_builder
5102        .with_input_accessory_view_builder(move |webview| input_accessory_view_builder.0(webview));
5103    }
5104  }
5105
5106  #[cfg(target_os = "macos")]
5107  {
5108    if let Some(position) = &webview_attributes.traffic_light_position {
5109      webview_builder = webview_builder.with_traffic_light_inset(*position);
5110    }
5111  }
5112
5113  #[cfg(windows)]
5114  let window_id_for_ipc = window_id.clone();
5115  #[cfg(not(windows))]
5116  let window_id_for_ipc = window_id;
5117  webview_builder = webview_builder.with_ipc_handler(create_ipc_handler(
5118    window_id_for_ipc,
5119    id,
5120    context.clone(),
5121    label.clone(),
5122    ipc_handler,
5123  ));
5124
5125  for script in webview_attributes.initialization_scripts {
5126    webview_builder = webview_builder
5127      .with_initialization_script_for_main_only(script.script, script.for_main_frame_only);
5128  }
5129
5130  for (scheme, protocol) in uri_scheme_protocols {
5131    // on Linux the custom protocols are associated with the web context
5132    // and you cannot register a scheme more than once
5133    #[cfg(any(
5134      target_os = "linux",
5135      target_os = "dragonfly",
5136      target_os = "freebsd",
5137      target_os = "netbsd",
5138      target_os = "openbsd"
5139    ))]
5140    {
5141      if web_context.registered_custom_protocols.contains(&scheme) {
5142        continue;
5143      }
5144
5145      web_context
5146        .registered_custom_protocols
5147        .insert(scheme.clone());
5148    }
5149
5150    webview_builder = webview_builder.with_asynchronous_custom_protocol(
5151      scheme,
5152      move |webview_id, request, responder| {
5153        protocol(
5154          webview_id,
5155          request,
5156          Box::new(move |response| responder.respond(response)),
5157        )
5158      },
5159    );
5160  }
5161
5162  #[cfg(any(debug_assertions, feature = "devtools"))]
5163  {
5164    webview_builder = webview_builder.with_devtools(webview_attributes.devtools.unwrap_or(true));
5165  }
5166
5167  #[cfg(target_os = "android")]
5168  {
5169    if let Some(on_webview_created) = pending.on_webview_created {
5170      webview_builder = webview_builder.on_webview_created(move |ctx| {
5171        on_webview_created(tauri_runtime::webview::CreationContext {
5172          env: ctx.env,
5173          activity: ctx.activity,
5174          webview: ctx.webview,
5175        })
5176      });
5177    }
5178  }
5179
5180  let webview = match kind {
5181    #[cfg(not(any(
5182      target_os = "windows",
5183      target_os = "macos",
5184      target_os = "ios",
5185      target_os = "android"
5186    )))]
5187    WebviewKind::WindowChild => {
5188      // only way to account for menu bar height, and also works for multiwebviews :)
5189      let vbox = window.default_vbox().unwrap();
5190      webview_builder.build_gtk(vbox)
5191    }
5192    #[cfg(any(
5193      target_os = "windows",
5194      target_os = "macos",
5195      target_os = "ios",
5196      target_os = "android"
5197    ))]
5198    WebviewKind::WindowChild => webview_builder.build_as_child(&window),
5199    WebviewKind::WindowContent => {
5200      #[cfg(any(
5201        target_os = "windows",
5202        target_os = "macos",
5203        target_os = "ios",
5204        target_os = "android"
5205      ))]
5206      let builder = webview_builder.build(&window);
5207      #[cfg(not(any(
5208        target_os = "windows",
5209        target_os = "macos",
5210        target_os = "ios",
5211        target_os = "android"
5212      )))]
5213      let builder = {
5214        let vbox = window.default_vbox().unwrap();
5215        webview_builder.build_gtk(vbox)
5216      };
5217      builder
5218    }
5219  }
5220  .map_err(|e| Error::CreateWebview(Box::new(e)))?;
5221
5222  if kind == WebviewKind::WindowContent {
5223    #[cfg(any(
5224      target_os = "linux",
5225      target_os = "dragonfly",
5226      target_os = "freebsd",
5227      target_os = "netbsd",
5228      target_os = "openbsd"
5229    ))]
5230    undecorated_resizing::attach_resize_handler(&webview);
5231    #[cfg(windows)]
5232    if window.is_resizable() && !window.is_decorated() {
5233      undecorated_resizing::attach_resize_handler(window.hwnd(), window.has_undecorated_shadow());
5234    }
5235  }
5236
5237  #[cfg(windows)]
5238  {
5239    let controller = webview.controller();
5240    let mut token = 0;
5241
5242    add_focus_change_listeners(
5243      window_id.clone(),
5244      id,
5245      context.proxy.clone(),
5246      focused_webview,
5247      label.clone(),
5248      &controller,
5249      &mut token,
5250    );
5251
5252    if let Ok(webview) = unsafe { controller.CoreWebView2() } {
5253      let proxy_clone = context.proxy.clone();
5254      unsafe {
5255        let _ = webview.add_ContainsFullScreenElementChanged(
5256          &ContainsFullScreenElementChangedEventHandler::create(Box::new(move |sender, _| {
5257            let mut contains_fullscreen_element = windows::core::BOOL::default();
5258            sender
5259              .ok_or_else(windows::core::Error::empty)?
5260              .ContainsFullScreenElement(&mut contains_fullscreen_element)?;
5261            let _ = proxy_clone.send_event(Message::Window(
5262              *window_id.lock().unwrap(),
5263              WindowMessage::SetFullscreen(contains_fullscreen_element.as_bool()),
5264            ));
5265            Ok(())
5266          })),
5267          &mut token,
5268        );
5269      }
5270    }
5271  }
5272
5273  Ok(WebviewWrapper {
5274    label,
5275    id,
5276    inner: Rc::new(webview),
5277    context_store: context.main_thread.web_context.clone(),
5278    webview_event_listeners: Default::default(),
5279    context_key: if automation_enabled {
5280      None
5281    } else {
5282      web_context_key
5283    },
5284    bounds: Arc::new(Mutex::new(webview_bounds)),
5285  })
5286}
5287
5288/// Create a wry ipc handler from a tauri ipc handler.
5289fn create_ipc_handler<T: UserEvent>(
5290  window_id: Arc<Mutex<WindowId>>,
5291  webview_id: WebviewId,
5292  context: Context<T>,
5293  label: String,
5294  ipc_handler: Option<WebviewIpcHandler<T, Wry<T>>>,
5295) -> Box<IpcHandler> {
5296  Box::new(move |request| {
5297    if let Some(handler) = &ipc_handler {
5298      handler(
5299        DetachedWebview {
5300          label: label.clone(),
5301          dispatcher: WryWebviewDispatcher {
5302            window_id: window_id.clone(),
5303            webview_id,
5304            context: context.clone(),
5305          },
5306        },
5307        request,
5308      );
5309    }
5310  })
5311}
5312
5313#[cfg(target_os = "macos")]
5314fn inner_size(
5315  window: &Window,
5316  webviews: &[WebviewWrapper],
5317  has_children: bool,
5318) -> PhysicalSize<u32> {
5319  if !has_children && !webviews.is_empty() {
5320    use wry::WebViewExtMacOS;
5321    let webview = webviews.first().unwrap();
5322    let view = unsafe { Retained::cast_unchecked::<objc2_app_kit::NSView>(webview.webview()) };
5323    let view_frame = view.frame();
5324    let logical: LogicalSize<f64> = (view_frame.size.width, view_frame.size.height).into();
5325    return logical.to_physical(window.scale_factor());
5326  }
5327
5328  window.inner_size()
5329}
5330
5331#[cfg(not(target_os = "macos"))]
5332#[allow(unused_variables)]
5333fn inner_size(
5334  window: &Window,
5335  webviews: &[WebviewWrapper],
5336  has_children: bool,
5337) -> PhysicalSize<u32> {
5338  window.inner_size()
5339}
5340
5341fn to_tao_theme(theme: Option<Theme>) -> Option<TaoTheme> {
5342  match theme {
5343    Some(Theme::Light) => Some(TaoTheme::Light),
5344    Some(Theme::Dark) => Some(TaoTheme::Dark),
5345    _ => None,
5346  }
5347}
5348
5349/// Used to prevent duplicated [`WindowEvent::Focused`] events,
5350/// and to track last focused webview in multi-webview mode for us to restore webview focuses
5351#[cfg(windows)]
5352fn add_focus_change_listeners<T: UserEvent>(
5353  window_id: Arc<Mutex<WindowId>>,
5354  id: u32,
5355  proxy: TaoEventLoopProxy<Message<T>>,
5356  focused_webview: Arc<Mutex<FocusState>>,
5357  label: String,
5358  controller: &ICoreWebView2Controller,
5359  token: &mut i64,
5360) {
5361  let label_ = label.clone();
5362  let window_id_ = window_id.clone();
5363  let proxy_clone = proxy.clone();
5364  let focused_webview_ = focused_webview.clone();
5365  if let Err(error) = unsafe {
5366    controller.add_GotFocus(
5367      &FocusChangedEventHandler::create(Box::new(move |_, _| {
5368        let mut focused_webview = focused_webview_.lock().unwrap();
5369        // when using multiwebview mode, we should check if the focus change is actually a "webview focus change"
5370        // instead of a window focus change (here we're patching window events, so we only care about the actual window changing focus)
5371        let already_focused = matches!(
5372          *focused_webview,
5373          FocusState::WindowFocused | FocusState::WebviewFocused { .. }
5374        );
5375        *focused_webview = FocusState::WebviewFocused {
5376          webview_label: label_.clone(),
5377        };
5378
5379        if !already_focused {
5380          let _ = proxy_clone.send_event(Message::Webview(
5381            *window_id_.lock().unwrap(),
5382            id,
5383            WebviewMessage::SynthesizedWindowEvent(SynthesizedWindowEvent::Focused(true)),
5384          ));
5385        }
5386        Ok(())
5387      })),
5388      token,
5389    )
5390  } {
5391    log::error!(
5392      "Failed to attach WebView2 `add_GotFocus` handler, `WindowEvent::Focused` will not be sent: {error}"
5393    );
5394    return;
5395  }
5396
5397  if let Err(error) = unsafe {
5398    controller.add_LostFocus(
5399      &FocusChangedEventHandler::create(Box::new(move |_, _| {
5400        let mut focused_webview = focused_webview.lock().unwrap();
5401        // when using multiwebview mode, we should handle webview focus changes
5402        // so we check is the currently focused webview matches this webview's
5403        // (in this case, it means we lost the window focus)
5404        //
5405        // on multiwebview mode if we change focus to a different webview
5406        // we get the gotFocus event of the other webview before the lostFocus
5407        // so this check makes sense
5408        if let FocusState::WebviewFocused { ref webview_label } = *focused_webview {
5409          let lost_window_focus = webview_label == &label;
5410          if lost_window_focus {
5411            // only reset when we lost window focus - otherwise some other webview is focused
5412            *focused_webview = FocusState::Blured {
5413              last_focused_webview_label: Some(label.clone()),
5414            };
5415            let _ = proxy.send_event(Message::Webview(
5416              *window_id.lock().unwrap(),
5417              id,
5418              WebviewMessage::SynthesizedWindowEvent(SynthesizedWindowEvent::Focused(false)),
5419            ));
5420          }
5421        }
5422
5423        Ok(())
5424      })),
5425      token,
5426    )
5427  } {
5428    log::error!(
5429      "Failed to attach WebView2 `add_LostFocus` handler, `WindowEvent::Focused` will not be sent: {error}"
5430    );
5431  }
5432}