Skip to main content

lingxia_windows_contract/
lib.rs

1//! Windows host UI contract shared by the Rust Windows SDK pieces.
2//!
3//! This crate intentionally contains no Win32 window implementation. The
4//! implementation belongs to `lingxia-windows-sdk`.
5//!
6//! The crate is Windows-only; off-Windows it compiles to nothing so a
7//! `cargo *(--workspace)` on other hosts neither pulls the `windows` crate
8//! nor lints Win32 contracts that can't exist there.
9#![cfg(windows)]
10
11use std::any::Any;
12use std::collections::HashMap;
13use std::sync::{Arc, Mutex, OnceLock};
14
15use lingxia_webview::{WebTag, WebViewError};
16use windows::Win32::Foundation::{HWND, RECT};
17use windows::Win32::Graphics::Gdi::HDC;
18
19type StdResult<T, E = WebViewError> = std::result::Result<T, E>;
20
21pub type HostWindowCreatedHandler = Arc<dyn Fn(isize) + Send + Sync>;
22pub type CloseHandler = Arc<dyn Fn() + Send + Sync>;
23pub type ChromeEventHandler = Arc<dyn Fn(WindowsChromeCommand) + Send + Sync>;
24pub type WebViewVisibilityHandler = Arc<dyn Fn(&WebTag, bool) + Send + Sync>;
25pub type WindowsHostPanelInputHandler = Arc<dyn Fn(WindowsHostPanelKeyEvent) -> bool + Send + Sync>;
26
27static DEFAULT_WINDOW_SIZE: OnceLock<(i32, i32)> = OnceLock::new();
28static BACKEND: OnceLock<Arc<dyn WindowsHostBackend>> = OnceLock::new();
29static CLOSE_HANDLERS: OnceLock<Mutex<HashMap<String, CloseHandler>>> = OnceLock::new();
30static CHROME_HANDLERS: OnceLock<Mutex<HashMap<String, ChromeEventHandler>>> = OnceLock::new();
31static VISIBILITY_HANDLER: OnceLock<Mutex<Option<WebViewVisibilityHandler>>> = OnceLock::new();
32static HOST_WINDOW_CREATED_HANDLERS: OnceLock<Mutex<Vec<HostWindowCreatedHandler>>> =
33    OnceLock::new();
34static HOST_PANEL_INPUT_HANDLERS: OnceLock<Mutex<HashMap<String, WindowsHostPanelInputHandler>>> =
35    OnceLock::new();
36static WINDOW_LAYOUTS: OnceLock<Mutex<HashMap<String, WindowsWindowLayout>>> = OnceLock::new();
37static WINDOWS_CHROME_RENDERER: OnceLock<Mutex<Option<Arc<dyn WindowsChromeRenderer>>>> =
38    OnceLock::new();
39static ASIDE_PANEL_TABS: OnceLock<Mutex<HashMap<String, Vec<WindowsAsidePanelTab>>>> =
40    OnceLock::new();
41static ASIDE_PANEL_EVENT_HANDLER: OnceLock<Mutex<Option<WindowsAsidePanelEventHandler>>> =
42    OnceLock::new();
43
44/// One tab in a docked aside slot.
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct WindowsAsidePanelTab {
47    pub surface_id: String,
48    pub title: String,
49    pub active: bool,
50}
51
52/// Chrome events from a docked aside slot, routed back to the owner of the
53/// addressed browser, lxapp, or native slot.
54#[derive(Debug, Clone)]
55pub enum WindowsAsidePanelEvent {
56    TabClick {
57        panel_id: String,
58        surface_id: String,
59    },
60    TabClose {
61        panel_id: String,
62        surface_id: String,
63    },
64    CloseAll {
65        panel_id: String,
66    },
67    NavBack {
68        panel_id: String,
69    },
70    NavForward {
71        panel_id: String,
72    },
73    NavReload {
74        panel_id: String,
75    },
76}
77
78pub type WindowsAsidePanelEventHandler = Arc<dyn Fn(WindowsAsidePanelEvent) + Send + Sync>;
79
80/// Stable panel id of the shared aside browser panel (one per window).
81pub const ASIDE_BROWSER_PANEL_ID: &str = "lx.aside-browser";
82/// Stable panel id of the shared lxapp aside slot (one per window).
83pub const ASIDE_LXAPP_PANEL_ID: &str = "lx.aside-lxapp";
84
85/// Publishes the tab strip of an aside browser panel; an empty list removes
86/// it (the panel then falls back to non-tabbed chrome).
87pub fn set_aside_panel_tabs(panel_id: &str, tabs: Vec<WindowsAsidePanelTab>) {
88    let registry = ASIDE_PANEL_TABS.get_or_init(|| Mutex::new(HashMap::new()));
89    if let Ok(mut registry) = registry.lock() {
90        if tabs.is_empty() {
91            registry.remove(panel_id);
92        } else {
93            registry.insert(panel_id.to_string(), tabs);
94        }
95    }
96}
97
98pub fn aside_panel_tabs(panel_id: &str) -> Vec<WindowsAsidePanelTab> {
99    ASIDE_PANEL_TABS
100        .get()
101        .and_then(|registry| registry.lock().ok())
102        .and_then(|registry| registry.get(panel_id).cloned())
103        .unwrap_or_default()
104}
105
106pub fn set_windows_aside_panel_event_handler(handler: WindowsAsidePanelEventHandler) {
107    let slot = ASIDE_PANEL_EVENT_HANDLER.get_or_init(|| Mutex::new(None));
108    if let Ok(mut slot) = slot.lock() {
109        *slot = Some(handler);
110    }
111}
112
113/// Routes a chrome event to the aside-panel handler; `false` when none is
114/// installed.
115pub fn dispatch_windows_aside_panel_event(event: WindowsAsidePanelEvent) -> bool {
116    let handler = ASIDE_PANEL_EVENT_HANDLER
117        .get()
118        .and_then(|slot| slot.lock().ok())
119        .and_then(|slot| slot.clone());
120    let Some(handler) = handler else {
121        return false;
122    };
123    handler(event);
124    true
125}
126
127fn unsupported_operation<T>(operation: &str) -> StdResult<T> {
128    Err(WebViewError::WebView(format!(
129        "Windows host backend does not support {operation}"
130    )))
131}
132
133/// Host callbacks implemented by the window owner.
134///
135/// Every hook has a conservative default so a custom host can opt into only the
136/// capabilities it actually orchestrates. For example, a host that wants the
137/// SDK-managed native view components usually starts with
138/// `find_webview_content_window` and `post_to_window_thread`, then adds panel or
139/// shell integration as needed.
140pub trait WindowsHostBackend: Send + Sync {
141    fn show_webview_as_panel(
142        &self,
143        _webtag: &WebTag,
144        _title: &str,
145        _panel_id: &str,
146    ) -> StdResult<()> {
147        unsupported_operation("show_webview_as_panel")
148    }
149
150    fn show_webview_as_adaptive_panel(
151        &self,
152        _webtag: &WebTag,
153        _title: &str,
154        _panel_id: &str,
155        _position: WindowsPanelPosition,
156        _preferred_size: Option<i32>,
157    ) -> StdResult<()> {
158        unsupported_operation("show_webview_as_adaptive_panel")
159    }
160
161    fn present_webview_in_active_group(&self, _webtag: &WebTag) -> StdResult<()> {
162        unsupported_operation("present_webview_in_active_group")
163    }
164
165    fn active_host_window_is_device_framed(&self) -> bool {
166        false
167    }
168
169    fn active_host_window_webtag_key(&self) -> Option<String> {
170        None
171    }
172
173    fn present_webview_as_group_main(&self, _webtag: &WebTag, _group_key: String) -> StdResult<()> {
174        unsupported_operation("present_webview_as_group_main")
175    }
176
177    fn present_webview_as_overlay(
178        &self,
179        _webtag: &WebTag,
180        _width: f64,
181        _height: f64,
182        _width_ratio: f64,
183        _height_ratio: f64,
184        _position: u8,
185    ) -> StdResult<()> {
186        unsupported_operation("present_webview_as_overlay")
187    }
188
189    fn configure_webview_surface_interaction(
190        &self,
191        _webtag: &WebTag,
192        _close_button: bool,
193        _dismiss_on_outside: bool,
194        _modal: bool,
195    ) -> StdResult<()> {
196        unsupported_operation("configure_webview_surface_interaction")
197    }
198
199    fn resize_host_window_content(
200        &self,
201        _webtag: &WebTag,
202        _width: i32,
203        _height: i32,
204    ) -> StdResult<()> {
205        unsupported_operation("resize_host_window_content")
206    }
207
208    fn restore_presented_group_main(&self) -> StdResult<()> {
209        unsupported_operation("restore_presented_group_main")
210    }
211
212    fn show_interactive_host_panel(
213        &self,
214        _panel_id: &str,
215        _title: &str,
216        _body: &str,
217        _position: WindowsPanelPosition,
218    ) -> StdResult<()> {
219        unsupported_operation("show_interactive_host_panel")
220    }
221
222    fn hide_host_panel(&self, _panel_id: &str) -> StdResult<()> {
223        unsupported_operation("hide_host_panel")
224    }
225
226    fn update_host_panel_body(&self, _panel_id: &str, _body: &str) -> StdResult<()> {
227        unsupported_operation("update_host_panel_body")
228    }
229
230    fn set_host_panel_tabs(&self, _panel_id: &str, _tabs: Vec<WindowsHostPanelTab>) -> bool {
231        false
232    }
233
234    fn set_host_panel_maximized(&self, _panel_id: &str, _maximized: bool) -> bool {
235        false
236    }
237
238    fn invalidate_host_panel(&self, _panel_id: &str) -> bool {
239        false
240    }
241
242    fn is_panel_visible(&self, _panel_id: &str) -> bool {
243        false
244    }
245
246    fn find_webview_content_window(&self, _webtag: &WebTag) -> Option<WindowsWebViewContentWindow> {
247        None
248    }
249
250    fn webview_window_snapshot(&self, _webtag: &WebTag) -> StdResult<WindowsWebViewWindowSnapshot> {
251        unsupported_operation("webview_window_snapshot")
252    }
253
254    fn show_webview_window(
255        &self,
256        _webtag: &WebTag,
257        _title: &str,
258        _activate: bool,
259    ) -> StdResult<()> {
260        unsupported_operation("show_webview_window")
261    }
262
263    fn show_webview_window_with_content_size(
264        &self,
265        _webtag: &WebTag,
266        _title: &str,
267        _activate: bool,
268        _width: Option<i32>,
269        _height: Option<i32>,
270    ) -> StdResult<()> {
271        unsupported_operation("show_webview_window_with_content_size")
272    }
273
274    fn navigate_webview_window(
275        &self,
276        _webtag: &WebTag,
277        _title: &str,
278        _activate: bool,
279        _animation: WindowsNavAnimation,
280    ) -> StdResult<()> {
281        unsupported_operation("navigate_webview_window")
282    }
283
284    fn hide_webview_window(&self, _webtag: &WebTag) -> StdResult<()> {
285        unsupported_operation("hide_webview_window")
286    }
287
288    fn request_host_window_layout(&self, _window: WindowsHostWindow) -> bool {
289        false
290    }
291
292    fn active_content_screen_rect(&self) -> Option<WindowsContentRect> {
293        None
294    }
295
296    fn post_to_window_thread(&self, _window: isize, _callback: Box<dyn FnOnce() + Send>) -> bool {
297        false
298    }
299
300    fn sync_webview_window_layout(&self, _webtag: &WebTag) {}
301
302    /// Repaints an aside panel's chrome after a tab-strip change that leaves
303    /// the attached layout untouched (e.g. an inactive tab closed).
304    fn refresh_aside_panel(&self, _panel_id: &str) {}
305}
306
307pub fn refresh_aside_panel(panel_id: &str) {
308    if let Ok(backend) = backend() {
309        backend.refresh_aside_panel(panel_id);
310    }
311}
312
313pub fn set_windows_host_backend(backend: Arc<dyn WindowsHostBackend>) {
314    if BACKEND.set(backend).is_err() {
315        log::warn!("Windows host backend is already installed; ignoring replacement");
316    }
317}
318
319fn backend() -> StdResult<&'static Arc<dyn WindowsHostBackend>> {
320    BACKEND
321        .get()
322        .ok_or_else(|| WebViewError::WebView("Windows host backend is not installed".to_string()))
323}
324
325#[derive(Debug, Clone, PartialEq, Eq)]
326pub struct WindowsWebViewWindowSnapshot {
327    pub window_id: usize,
328    pub webtag_key: String,
329    pub visible: bool,
330    pub window_left: i32,
331    pub window_top: i32,
332    pub window_width: i32,
333    pub window_height: i32,
334    pub content_left: i32,
335    pub content_top: i32,
336    pub content_width: u32,
337    pub content_height: u32,
338    /// Composition-clip corner radii `[tl, tr, br, bl]` of the live surface
339    /// (zeros for windowed hosting), so screenshot compositing can reproduce
340    /// the on-screen rounding.
341    pub content_corner_radii: [i32; 4],
342}
343
344#[derive(Debug, Clone, Copy, PartialEq)]
345pub struct WindowsWebViewContentWindow {
346    pub window: isize,
347    pub content_left: i32,
348    pub content_top: i32,
349    pub content_width: i32,
350    pub content_height: i32,
351    pub scale: f64,
352}
353
354#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
355pub enum WindowsPanelPosition {
356    Left,
357    #[default]
358    Right,
359    Top,
360    Bottom,
361}
362
363/// The page-transition animation a `navigate` should play, mirroring the
364/// `AnimationType` the core computes from the JS navigation verb (forward slide
365/// for `navigateTo`, backward slide for `navigateBack`, none for
366/// `redirectTo`/`switchTab`/`reLaunch`). Kept as a contract-local enum so this
367/// crate needs no dependency on `lingxia-platform`.
368#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
369pub enum WindowsNavAnimation {
370    #[default]
371    None,
372    Forward,
373    Backward,
374}
375
376#[derive(Clone, Default)]
377pub struct WindowsWindowLayout {
378    payload: Option<Arc<dyn Any + Send + Sync>>,
379}
380
381impl std::fmt::Debug for WindowsWindowLayout {
382    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
383        f.debug_struct("WindowsWindowLayout")
384            .field("has_payload", &self.payload.is_some())
385            .finish()
386    }
387}
388
389impl WindowsWindowLayout {
390    pub fn new<T>(payload: T) -> Self
391    where
392        T: Any + Send + Sync + 'static,
393    {
394        Self {
395            payload: Some(Arc::new(payload)),
396        }
397    }
398
399    pub fn empty() -> Self {
400        Self::default()
401    }
402
403    pub fn is_empty(&self) -> bool {
404        self.payload.is_none()
405    }
406
407    pub fn downcast_ref<T>(&self) -> Option<&T>
408    where
409        T: Any + 'static,
410    {
411        self.payload.as_deref()?.downcast_ref::<T>()
412    }
413}
414
415#[derive(Debug, Clone, PartialEq, Eq)]
416pub struct WindowsHostPanelTab {
417    pub id: u64,
418    pub title: String,
419    pub active: bool,
420}
421
422#[derive(Debug, Clone, PartialEq, Eq)]
423pub struct WindowsHostPanelContent {
424    pub title: Option<String>,
425    pub body: Option<String>,
426    pub tabs: Vec<WindowsHostPanelTab>,
427    pub maximized: bool,
428}
429
430#[derive(Debug, Clone, PartialEq)]
431pub struct WindowsChromePanel {
432    pub panel_id: String,
433    pub webtag_key: String,
434    pub title: String,
435    pub rect: RECT,
436    /// Top-band slice (aligned with the main navbar baseline) where a browser
437    /// aside paints its address bar; `None` for panels with no band header.
438    pub header_rect: Option<RECT>,
439    /// Gutter between this panel and the neighboring workspace region. It is
440    /// both the resize hit target and the exposed first-layer shell surface.
441    pub resize_handle: Option<RECT>,
442    pub host_content: Option<WindowsHostPanelContent>,
443    pub docked: bool,
444}
445
446#[derive(Debug, Clone, PartialEq, Eq)]
447pub struct WindowsChromePanelLayoutInput {
448    pub panel_id: String,
449    pub webtag_key: String,
450    pub position: WindowsPanelPosition,
451    pub requested_size: Option<i32>,
452    pub docked: bool,
453    pub maximized: bool,
454}
455
456#[derive(Debug, Clone, PartialEq)]
457pub struct WindowsChromePanelLayout {
458    pub panel_id: String,
459    pub webtag_key: String,
460    pub rect: RECT,
461    /// Top-band slice for a browser aside's address bar (see
462    /// [`WindowsChromePanel::header_rect`]); `None` when the panel has none.
463    pub header_rect: Option<RECT>,
464    pub resize_handle: Option<RECT>,
465}
466
467#[derive(Debug, Clone, PartialEq)]
468pub struct WindowsChromeAttachedLayout {
469    /// Full main region after aside arbitration, including main-owned chrome.
470    pub main_region: RECT,
471    /// Main WebView viewport after reserving its navigation bar.
472    pub main: RECT,
473    pub panels: Vec<WindowsChromePanelLayout>,
474}
475
476#[derive(Debug, Clone, PartialEq)]
477pub struct WindowsChromeAttachedState {
478    pub main_region: RECT,
479    pub main: RECT,
480    pub panels: Vec<WindowsChromePanel>,
481}
482
483#[derive(Debug, Clone)]
484pub struct WindowsChromeState {
485    pub hwnd: HWND,
486    pub client: RECT,
487    pub layout: WindowsWindowLayout,
488    pub attached: Option<WindowsChromeAttachedState>,
489    pub frame_button_hover: Option<WindowsFrameButton>,
490    pub frame_button_pressed: Option<WindowsFrameButton>,
491    /// Client-space cursor position while over this window's chrome; drives
492    /// hover feedback (frame buttons keep their dedicated state above).
493    pub cursor: Option<(i32, i32)>,
494}
495
496#[derive(Debug, Clone, Copy, PartialEq, Eq)]
497pub enum WindowsFrameButton {
498    Minimize,
499    Maximize,
500    Close,
501}
502
503#[derive(Debug, Clone, PartialEq)]
504pub struct WindowsChromeCommand {
505    pub id: String,
506    pub payload: serde_json::Value,
507    pub focus: Option<String>,
508    pub double_click: Option<Box<WindowsChromeCommand>>,
509    pub include_screen_position: bool,
510}
511
512impl WindowsChromeCommand {
513    pub fn new(id: impl Into<String>) -> Self {
514        Self {
515            id: id.into(),
516            payload: serde_json::Value::Null,
517            focus: None,
518            double_click: None,
519            include_screen_position: false,
520        }
521    }
522
523    pub fn with_payload(mut self, payload: serde_json::Value) -> Self {
524        self.payload = payload;
525        self
526    }
527
528    pub fn with_focus(mut self, surface_id: impl Into<String>) -> Self {
529        self.focus = Some(surface_id.into());
530        self
531    }
532
533    pub fn with_double_click(mut self, command: WindowsChromeCommand) -> Self {
534        self.double_click = Some(Box::new(command));
535        self
536    }
537
538    pub fn with_screen_position(mut self) -> Self {
539        self.include_screen_position = true;
540        self
541    }
542}
543
544#[derive(Debug, Clone, PartialEq)]
545pub enum WindowsChromeHit {
546    Caption,
547    FrameButton(WindowsFrameButton),
548    Focusable {
549        id: String,
550        context_menu: Option<WindowsChromeCommand>,
551        /// Optional command invoked on left-button-down in addition to
552        /// focusing the surface (e.g. focusing the terminal pane under the
553        /// cursor). Carries the click's screen position when requested.
554        click_command: Option<WindowsChromeCommand>,
555    },
556    Command(WindowsChromeCommand),
557    CommandWithContext {
558        command: WindowsChromeCommand,
559        context_menu: WindowsChromeCommand,
560    },
561    Chrome,
562}
563
564pub trait WindowsChromeRenderer: Send + Sync {
565    fn content_rect(&self, client: RECT, layout: &WindowsWindowLayout) -> RECT;
566    fn panel_corner_radius(&self) -> i32;
567
568    fn attached_layout(
569        &self,
570        client: RECT,
571        layout: &WindowsWindowLayout,
572        panels: &[WindowsChromePanelLayoutInput],
573    ) -> Option<WindowsChromeAttachedLayout> {
574        let _ = (client, layout, panels);
575        None
576    }
577
578    fn paint(&self, hdc: HDC, state: &WindowsChromeState);
579
580    fn paint_region(&self, hdc: HDC, state: &WindowsChromeState, invalid: RECT) {
581        let _ = invalid;
582        self.paint(hdc, state);
583    }
584
585    fn hit_test(&self, state: &WindowsChromeState, point: (i32, i32)) -> Option<WindowsChromeHit>;
586
587    fn frame_button_rect(
588        &self,
589        state: &WindowsChromeState,
590        button: WindowsFrameButton,
591    ) -> Option<RECT> {
592        let _ = (state, button);
593        None
594    }
595
596    /// Bounding rect of the hover-highlightable element under `point`; the
597    /// host invalidates the rects the cursor enters/leaves so hover feedback
598    /// repaints exactly the affected element.
599    fn hover_rect(&self, state: &WindowsChromeState, point: (i32, i32)) -> Option<RECT> {
600        let _ = (state, point);
601        None
602    }
603
604    /// Translate a wheel gesture over owner-drawn chrome into a runtime
605    /// command. Returning `None` lets the host forward the gesture normally.
606    fn mouse_wheel(
607        &self,
608        state: &WindowsChromeState,
609        point: (i32, i32),
610        delta: i16,
611    ) -> Option<WindowsChromeCommand> {
612        let _ = (state, point, delta);
613        None
614    }
615}
616
617#[derive(Debug, Clone, Copy, PartialEq, Eq)]
618pub struct WindowsHostPanelKeyEvent {
619    pub vk: u32,
620    pub ctrl: bool,
621    pub shift: bool,
622    pub alt: bool,
623    pub character: Option<char>,
624}
625
626#[derive(Debug, Clone, Copy, PartialEq, Eq)]
627pub struct WindowsHostWindow {
628    pub window: isize,
629}
630
631#[derive(Debug, Clone, Copy, PartialEq, Eq)]
632pub struct WindowsContentRect {
633    pub host_window: isize,
634    pub left: i32,
635    pub top: i32,
636    pub width: i32,
637    pub height: i32,
638    pub dpi: u32,
639}
640
641pub fn set_default_window_size(width: i32, height: i32) {
642    if width > 0 && height > 0 {
643        let _ = DEFAULT_WINDOW_SIZE.set((width, height));
644    }
645}
646
647pub fn default_window_size() -> (i32, i32) {
648    DEFAULT_WINDOW_SIZE.get().copied().unwrap_or((1024, 768))
649}
650
651pub fn set_windows_chrome_renderer(renderer: Arc<dyn WindowsChromeRenderer>) {
652    let slot = WINDOWS_CHROME_RENDERER.get_or_init(|| Mutex::new(None));
653    if let Ok(mut slot) = slot.lock() {
654        *slot = Some(renderer);
655    }
656}
657
658pub fn windows_chrome_renderer() -> Option<Arc<dyn WindowsChromeRenderer>> {
659    WINDOWS_CHROME_RENDERER
660        .get()
661        .and_then(|renderer| renderer.lock().ok())
662        .and_then(|renderer| renderer.clone())
663}
664
665pub fn set_webview_close_handler(webtag: &WebTag, handler: CloseHandler) {
666    let handlers = CLOSE_HANDLERS.get_or_init(|| Mutex::new(HashMap::new()));
667    if let Ok(mut handlers) = handlers.lock() {
668        handlers.insert(webtag.key().to_string(), handler);
669    }
670}
671
672pub fn webview_close_handler(webtag_key: &str) -> Option<CloseHandler> {
673    CLOSE_HANDLERS
674        .get()
675        .and_then(|handlers| handlers.lock().ok())
676        .and_then(|handlers| handlers.get(webtag_key).cloned())
677}
678
679pub fn set_webview_visibility_handler(handler: WebViewVisibilityHandler) {
680    let slot = VISIBILITY_HANDLER.get_or_init(|| Mutex::new(None));
681    if let Ok(mut slot) = slot.lock() {
682        *slot = Some(handler);
683    }
684}
685
686pub fn webview_visibility_handler() -> Option<WebViewVisibilityHandler> {
687    VISIBILITY_HANDLER
688        .get()
689        .and_then(|slot| slot.lock().ok())
690        .and_then(|slot| slot.clone())
691}
692
693pub fn set_webview_chrome_event_handler(webtag: &WebTag, handler: ChromeEventHandler) {
694    let handlers = CHROME_HANDLERS.get_or_init(|| Mutex::new(HashMap::new()));
695    if let Ok(mut handlers) = handlers.lock() {
696        handlers.insert(webtag.key().to_string(), handler);
697    }
698}
699
700pub fn webview_chrome_event_handler(webtag_key: &str) -> Option<ChromeEventHandler> {
701    CHROME_HANDLERS
702        .get()
703        .and_then(|handlers| handlers.lock().ok())
704        .and_then(|handlers| handlers.get(webtag_key).cloned())
705}
706
707pub fn add_host_window_created_handler(handler: HostWindowCreatedHandler) {
708    let handlers = HOST_WINDOW_CREATED_HANDLERS.get_or_init(|| Mutex::new(Vec::new()));
709    if let Ok(mut handlers) = handlers.lock() {
710        handlers.push(handler);
711    }
712}
713
714pub fn host_window_created_handlers() -> Vec<HostWindowCreatedHandler> {
715    HOST_WINDOW_CREATED_HANDLERS
716        .get()
717        .and_then(|state| state.lock().ok())
718        .map(|state| state.clone())
719        .unwrap_or_default()
720}
721
722pub fn set_host_panel_input_handler(panel_id: &str, handler: WindowsHostPanelInputHandler) {
723    let handlers = HOST_PANEL_INPUT_HANDLERS.get_or_init(|| Mutex::new(HashMap::new()));
724    if let Ok(mut handlers) = handlers.lock() {
725        handlers.insert(panel_id.to_string(), handler);
726    }
727}
728
729pub fn clear_host_panel_input_handler(panel_id: &str) {
730    if let Some(handlers) = HOST_PANEL_INPUT_HANDLERS.get()
731        && let Ok(mut handlers) = handlers.lock()
732    {
733        handlers.remove(panel_id);
734    }
735}
736
737pub fn host_panel_input_handler(panel_id: &str) -> Option<WindowsHostPanelInputHandler> {
738    HOST_PANEL_INPUT_HANDLERS
739        .get()
740        .and_then(|handlers| handlers.lock().ok())
741        .and_then(|handlers| handlers.get(panel_id).cloned())
742}
743
744pub fn set_webview_window_layout(webtag: &WebTag, layout: WindowsWindowLayout) -> StdResult<()> {
745    let layouts = WINDOW_LAYOUTS.get_or_init(|| Mutex::new(HashMap::new()));
746    if let Ok(mut layouts) = layouts.lock() {
747        layouts.insert(webtag.key().to_string(), layout);
748    }
749    if let Ok(backend) = backend() {
750        backend.sync_webview_window_layout(webtag);
751    }
752    Ok(())
753}
754
755pub fn current_window_layout(webtag_key: &str) -> WindowsWindowLayout {
756    WINDOW_LAYOUTS
757        .get()
758        .and_then(|layouts| layouts.lock().ok())
759        .and_then(|layouts| layouts.get(webtag_key).cloned())
760        .unwrap_or_default()
761}
762
763pub fn cleanup_webview_state(webtag_key: &str) {
764    if let Some(handlers) = CLOSE_HANDLERS.get()
765        && let Ok(mut handlers) = handlers.lock()
766    {
767        handlers.remove(webtag_key);
768    }
769    if let Some(handlers) = CHROME_HANDLERS.get()
770        && let Ok(mut handlers) = handlers.lock()
771    {
772        handlers.remove(webtag_key);
773    }
774    if let Some(layouts) = WINDOW_LAYOUTS.get()
775        && let Ok(mut layouts) = layouts.lock()
776    {
777        layouts.remove(webtag_key);
778    }
779}
780
781pub fn show_webview_as_panel(webtag: &WebTag, title: &str, panel_id: &str) -> StdResult<()> {
782    backend()?.show_webview_as_panel(webtag, title, panel_id)
783}
784
785pub fn show_webview_as_adaptive_panel(
786    webtag: &WebTag,
787    title: &str,
788    panel_id: &str,
789    position: WindowsPanelPosition,
790    preferred_size: Option<i32>,
791) -> StdResult<()> {
792    backend()?.show_webview_as_adaptive_panel(webtag, title, panel_id, position, preferred_size)
793}
794
795pub fn present_webview_in_active_group(webtag: &WebTag) -> StdResult<()> {
796    backend()?.present_webview_in_active_group(webtag)
797}
798
799pub fn active_host_window_is_device_framed() -> bool {
800    backend()
801        .map(|backend| backend.active_host_window_is_device_framed())
802        .unwrap_or(false)
803}
804
805pub fn active_host_window_webtag_key() -> Option<String> {
806    backend()
807        .ok()
808        .and_then(|backend| backend.active_host_window_webtag_key())
809}
810
811pub fn present_webview_as_group_main(webtag: &WebTag, group_key: String) -> StdResult<()> {
812    backend()?.present_webview_as_group_main(webtag, group_key)
813}
814
815pub fn present_webview_as_overlay(
816    webtag: &WebTag,
817    width: f64,
818    height: f64,
819    width_ratio: f64,
820    height_ratio: f64,
821    position: u8,
822) -> StdResult<()> {
823    backend()?.present_webview_as_overlay(
824        webtag,
825        width,
826        height,
827        width_ratio,
828        height_ratio,
829        position,
830    )
831}
832
833pub fn configure_webview_surface_interaction(
834    webtag: &WebTag,
835    close_button: bool,
836    dismiss_on_outside: bool,
837    modal: bool,
838) -> StdResult<()> {
839    backend()?.configure_webview_surface_interaction(
840        webtag,
841        close_button,
842        dismiss_on_outside,
843        modal,
844    )
845}
846
847pub fn resize_host_window_content(webtag: &WebTag, width: i32, height: i32) -> StdResult<()> {
848    backend()?.resize_host_window_content(webtag, width, height)
849}
850
851pub fn restore_presented_group_main() -> StdResult<()> {
852    backend()?.restore_presented_group_main()
853}
854
855pub fn show_interactive_host_panel(
856    panel_id: &str,
857    title: &str,
858    body: &str,
859    position: WindowsPanelPosition,
860) -> StdResult<()> {
861    backend()?.show_interactive_host_panel(panel_id, title, body, position)
862}
863
864pub fn hide_host_panel(panel_id: &str) -> StdResult<()> {
865    backend()?.hide_host_panel(panel_id)
866}
867
868pub fn update_host_panel_body(panel_id: &str, body: &str) -> StdResult<()> {
869    backend()?.update_host_panel_body(panel_id, body)
870}
871
872pub fn set_host_panel_tabs(panel_id: &str, tabs: Vec<WindowsHostPanelTab>) -> bool {
873    backend()
874        .map(|backend| backend.set_host_panel_tabs(panel_id, tabs))
875        .unwrap_or(false)
876}
877
878pub fn set_host_panel_maximized(panel_id: &str, maximized: bool) -> bool {
879    backend()
880        .map(|backend| backend.set_host_panel_maximized(panel_id, maximized))
881        .unwrap_or(false)
882}
883
884pub fn invalidate_host_panel(panel_id: &str) -> bool {
885    backend()
886        .map(|backend| backend.invalidate_host_panel(panel_id))
887        .unwrap_or(false)
888}
889
890pub fn is_panel_visible(panel_id: &str) -> bool {
891    backend()
892        .map(|backend| backend.is_panel_visible(panel_id))
893        .unwrap_or(false)
894}
895
896pub fn find_host_window_for_webview(webtag: &WebTag) -> StdResult<WindowsHostWindow> {
897    let content = find_webview_content_window(webtag).ok_or_else(|| {
898        WebViewError::WebView(format!("no window registered for {}", webtag.key()))
899    })?;
900    Ok(WindowsHostWindow {
901        window: content.window,
902    })
903}
904
905pub fn request_host_window_layout(window: WindowsHostWindow) -> bool {
906    backend()
907        .map(|backend| backend.request_host_window_layout(window))
908        .unwrap_or(false)
909}
910
911pub fn active_content_screen_rect() -> Option<WindowsContentRect> {
912    backend()
913        .ok()
914        .and_then(|backend| backend.active_content_screen_rect())
915}
916
917pub fn find_webview_content_window(webtag: &WebTag) -> Option<WindowsWebViewContentWindow> {
918    backend()
919        .ok()
920        .and_then(|backend| backend.find_webview_content_window(webtag))
921}
922
923pub fn webview_window_snapshot(webtag: &WebTag) -> StdResult<WindowsWebViewWindowSnapshot> {
924    backend()?.webview_window_snapshot(webtag)
925}
926
927pub fn show_webview_window(webtag: &WebTag, title: &str, activate: bool) -> StdResult<()> {
928    backend()?.show_webview_window(webtag, title, activate)
929}
930
931pub fn show_webview_window_with_content_size(
932    webtag: &WebTag,
933    title: &str,
934    activate: bool,
935    width: Option<i32>,
936    height: Option<i32>,
937) -> StdResult<()> {
938    backend()?.show_webview_window_with_content_size(webtag, title, activate, width, height)
939}
940
941pub fn navigate_webview_window(
942    webtag: &WebTag,
943    title: &str,
944    activate: bool,
945    animation: WindowsNavAnimation,
946) -> StdResult<()> {
947    backend()?.navigate_webview_window(webtag, title, activate, animation)
948}
949
950pub fn hide_webview_window(webtag: &WebTag) -> StdResult<()> {
951    backend()?.hide_webview_window(webtag)
952}
953
954pub fn post_to_window_thread(window: isize, callback: Box<dyn FnOnce() + Send>) -> bool {
955    backend()
956        .map(|backend| backend.post_to_window_thread(window, callback))
957        .unwrap_or(false)
958}