Skip to main content

windows_reactor/
reference.rs

1use std::cell::{Cell, RefCell};
2use std::collections::VecDeque;
3use std::fmt;
4use std::marker::PhantomData;
5use std::rc::{Rc, Weak};
6use std::sync::atomic::{AtomicU64, Ordering};
7
8use crate::core::{NativeWork, NodeId, RuntimeError, WindowToken};
9use crate::element::{Callback, View};
10
11const IMPERATIVE_QUEUE_CAPACITY: usize = 4_096;
12static NEXT_OBSERVATION_ID: AtomicU64 = AtomicU64::new(1);
13
14#[derive(Debug)]
15pub(crate) enum HostRequest {
16    CloseWindow { identity: WindowToken },
17    OpenWindow { identity: WindowToken, root: View },
18}
19
20struct WindowRequestState {
21    active: Option<ActiveWindowRequests>,
22    lifecycle: WindowRequestLifecycle,
23    staged_close: bool,
24    staged_opens: Vec<View>,
25}
26
27#[derive(Default)]
28struct ActiveWindowRequests {
29    close: bool,
30    opens: Vec<View>,
31}
32
33#[derive(Clone, Copy, Eq, PartialEq)]
34enum WindowRequestLifecycle {
35    Open,
36    CloseCommitted,
37    Closed,
38    ClosedCommitted,
39}
40
41#[derive(Clone)]
42pub(crate) struct WindowEndpoint {
43    identity: WindowToken,
44    state: Rc<RefCell<WindowRequestState>>,
45}
46
47impl WindowEndpoint {
48    pub(crate) fn new(identity: WindowToken) -> Self {
49        Self {
50            identity,
51            state: Rc::new(RefCell::new(WindowRequestState {
52                active: None,
53                lifecycle: WindowRequestLifecycle::Open,
54                staged_close: false,
55                staged_opens: Vec::new(),
56            })),
57        }
58    }
59
60    pub(crate) fn begin(&self) {
61        let mut state = self.state.borrow_mut();
62        assert!(
63            state.active.is_none(),
64            "component lifecycle invocation reentered"
65        );
66        state.active = Some(ActiveWindowRequests::default());
67    }
68
69    pub(crate) fn finish(&self) {
70        let mut state = self.state.borrow_mut();
71        let active = state
72            .active
73            .take()
74            .expect("component lifecycle invocation was not active");
75        state.staged_close |= active.close;
76        state.staged_opens.extend(active.opens);
77    }
78
79    pub(crate) fn take_requests(&self) -> Vec<HostRequest> {
80        let mut state = self.state.borrow_mut();
81        let mut requests = state
82            .staged_opens
83            .drain(..)
84            .map(|root| HostRequest::OpenWindow {
85                identity: self.identity,
86                root,
87            })
88            .collect::<Vec<_>>();
89        if state.staged_close {
90            state.staged_close = false;
91            requests.push(HostRequest::CloseWindow {
92                identity: self.identity,
93            });
94        }
95        requests
96    }
97
98    pub(crate) fn close(&self) {
99        let mut state = self.state.borrow_mut();
100        state.lifecycle = match state.lifecycle {
101            WindowRequestLifecycle::Open | WindowRequestLifecycle::Closed => {
102                WindowRequestLifecycle::Closed
103            }
104            WindowRequestLifecycle::CloseCommitted | WindowRequestLifecycle::ClosedCommitted => {
105                WindowRequestLifecycle::ClosedCommitted
106            }
107        };
108        state.active = None;
109        state.staged_close = false;
110        state.staged_opens.clear();
111    }
112
113    pub(crate) fn commit_close(&self) {
114        let mut state = self.state.borrow_mut();
115        state.lifecycle = match state.lifecycle {
116            WindowRequestLifecycle::Open | WindowRequestLifecycle::CloseCommitted => {
117                WindowRequestLifecycle::CloseCommitted
118            }
119            WindowRequestLifecycle::Closed | WindowRequestLifecycle::ClosedCommitted => {
120                WindowRequestLifecycle::ClosedCommitted
121            }
122        };
123    }
124
125    pub(crate) fn reference(&self) -> WindowRef {
126        WindowRef {
127            endpoint: self.clone(),
128        }
129    }
130
131    fn request_open(&self, root: View) -> bool {
132        let mut state = self.state.borrow_mut();
133        if state.lifecycle != WindowRequestLifecycle::Open {
134            return false;
135        }
136        let Some(active) = state.active.as_mut() else {
137            return false;
138        };
139        active.opens.push(root);
140        true
141    }
142}
143
144/// A token-bound capability for a component's owning window.
145///
146/// Requests are accepted only during `create`, `changed`, or `update` on the owning window. The
147/// Pump commits an accepted request after the resulting candidate publishes.
148#[derive(Clone)]
149pub struct WindowRef {
150    endpoint: WindowEndpoint,
151}
152
153impl WindowRef {
154    /// Requests that the owning window close after the current component turn publishes.
155    #[must_use = "false means there is no active component publication"]
156    pub fn request_close(&self) -> bool {
157        let mut state = self.endpoint.state.borrow_mut();
158        if state.lifecycle != WindowRequestLifecycle::Open {
159            return false;
160        }
161        let Some(active) = state.active.as_mut() else {
162            return false;
163        };
164        active.close = true;
165        true
166    }
167
168    #[cfg(test)]
169    pub(crate) fn close_committed(&self) -> bool {
170        matches!(
171            self.endpoint.state.borrow().lifecycle,
172            WindowRequestLifecycle::CloseCommitted | WindowRequestLifecycle::ClosedCommitted
173        )
174    }
175
176    pub(crate) fn request_open(&self, root: View) -> bool {
177        self.endpoint.request_open(root)
178    }
179}
180
181impl fmt::Debug for WindowRef {
182    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
183        let state = self.endpoint.state.borrow();
184        formatter
185            .debug_struct("WindowRef")
186            .field("active", &state.active.is_some())
187            .field(
188                "close_committed",
189                &matches!(
190                    state.lifecycle,
191                    WindowRequestLifecycle::CloseCommitted
192                        | WindowRequestLifecycle::ClosedCommitted
193                ),
194            )
195            .field(
196                "open",
197                &matches!(
198                    state.lifecycle,
199                    WindowRequestLifecycle::Open | WindowRequestLifecycle::CloseCommitted
200                ),
201            )
202            .finish()
203    }
204}
205
206pub(crate) mod sealed {
207    pub trait Sealed {}
208}
209
210/// A native control that can be bound to an [`ElementRef`].
211///
212/// This trait is sealed and implemented only for controls whose generated schema supports native
213/// references.
214pub trait ReferenceControl: sealed::Sealed + 'static {}
215
216/// A stable typed reference to a published native element.
217///
218/// Components normally store references as fields. A reference is unbound before its element is
219/// published and after removal, shutdown, or window close. Imperative methods only enqueue work;
220/// they do not expose or invoke the native WinUI object.
221///
222/// An accepted one-shot request completes exactly once. If its binding is retired before native
223/// processing, its completion receives the typed `Unavailable` error.
224///
225/// References are control-specific:
226///
227/// ```compile_fail
228/// use windows_reactor::{Button, ElementRef, TextBox};
229///
230/// let button = ElementRef::<Button>::new();
231/// let _ = TextBox::new().element_ref(&button);
232/// ```
233///
234/// ```compile_fail
235/// use windows_reactor::ElementRef;
236///
237/// let _ = ElementRef::<u32>::new();
238/// ```
239pub struct ElementRef<T> {
240    target: Rc<RefCell<ReferenceTarget>>,
241    marker: PhantomData<fn() -> T>,
242}
243
244impl<T: ReferenceControl> ElementRef<T> {
245    pub fn new() -> Self {
246        Self {
247            target: Rc::new(RefCell::new(ReferenceTarget::default())),
248            marker: PhantomData,
249        }
250    }
251
252    pub(crate) fn binding(&self) -> NativeElementRef {
253        NativeElementRef(Rc::clone(&self.target))
254    }
255}
256
257impl<T: FocusControl> ElementRef<T> {
258    /// Enqueues programmatic focus for the currently published element.
259    ///
260    /// `true` means the request was accepted into the host queue. WinUI may later complete the
261    /// request with `false` when focus does not move; that is not a host error.
262    #[must_use = "false means the reference is currently unbound"]
263    pub fn request_focus(&self) -> bool {
264        self.request_focus_result(|_| {})
265    }
266
267    /// Enqueues programmatic focus and reports the native result asynchronously.
268    #[must_use = "false means the reference is currently unbound"]
269    pub fn request_focus_result(
270        &self,
271        completion: impl Fn(Result<bool, FocusError>) + 'static,
272    ) -> bool {
273        let Some(binding) = self.target.borrow().binding.clone() else {
274            return false;
275        };
276        binding.endpoint.enqueue(NativeWork {
277            identity: binding.identity,
278            work: ImperativeRequest::Focus {
279                node: binding.node,
280                completion: Callback::new(move |result: Result<bool, RuntimeError>| {
281                    completion(result.map_err(FocusError::from_runtime));
282                }),
283            },
284        })
285    }
286}
287
288impl ElementRef<crate::WebView2> {
289    /// Requests creation of the CoreWebView2 object for the currently published control.
290    ///
291    /// The returned COM object is the application-facing WebView2 core, not the XAML control
292    /// owned by Reactor.
293    #[must_use = "false means the reference is currently unbound"]
294    pub fn request_core_web_view2(
295        &self,
296        completion: impl Fn(Result<windows_core::IUnknown, WebView2Error>) + 'static,
297    ) -> bool {
298        let Some(binding) = self.target.borrow().binding.clone() else {
299            return false;
300        };
301        binding.endpoint.enqueue(NativeWork {
302            identity: binding.identity,
303            work: ImperativeRequest::InitializeWebView2 {
304                node: binding.node,
305                completion: Callback::new(
306                    move |result: Result<windows_core::IUnknown, RuntimeError>| {
307                        completion(result.map_err(WebView2Error::from_runtime));
308                    },
309                ),
310            },
311        })
312    }
313}
314
315impl ElementRef<crate::SwapChainPanel> {
316    /// Attaches an application-owned DXGI swap chain to the published panel.
317    #[must_use = "false means the reference is currently unbound"]
318    pub fn request_set_swap_chain(
319        &self,
320        swap_chain: windows_core::IUnknown,
321        completion: impl Fn(Result<(), SwapChainPanelError>) + 'static,
322    ) -> bool {
323        self.request_swap_chain(Some(swap_chain), completion)
324    }
325
326    /// Detaches the current DXGI swap chain from the published panel.
327    #[must_use = "false means the reference is currently unbound"]
328    pub fn request_clear_swap_chain(
329        &self,
330        completion: impl Fn(Result<(), SwapChainPanelError>) + 'static,
331    ) -> bool {
332        self.request_swap_chain(None, completion)
333    }
334
335    /// Observes panel metrics and frame rendering across published panel bindings.
336    ///
337    /// Registration is accepted before publication. Each new binding creates a native
338    /// subscription for that panel, and removal retires the old subscription.
339    #[must_use = "the observation stops when the handle is dropped"]
340    pub fn observe_surface(
341        &self,
342        callback: impl Fn(SwapChainPanelEvent) + 'static,
343    ) -> ElementObservation {
344        self.register_observation(ReferenceObservation::SwapChainPanel(Callback::new(
345            callback,
346        )))
347    }
348
349    fn request_swap_chain(
350        &self,
351        swap_chain: Option<windows_core::IUnknown>,
352        completion: impl Fn(Result<(), SwapChainPanelError>) + 'static,
353    ) -> bool {
354        let Some(binding) = self.target.borrow().binding.clone() else {
355            return false;
356        };
357        binding.endpoint.enqueue(NativeWork {
358            identity: binding.identity,
359            work: ImperativeRequest::SetSwapChain {
360                node: binding.node,
361                swap_chain,
362                completion: Callback::new(move |result: Result<(), RuntimeError>| {
363                    completion(result.map_err(SwapChainPanelError::from_runtime));
364                }),
365            },
366        })
367    }
368}
369
370impl ElementRef<crate::Image> {
371    /// Assigns an application-owned native ImageSource to the published image.
372    #[must_use = "false means the reference is currently unbound"]
373    pub fn request_set_native_source(
374        &self,
375        source: Option<windows_core::IUnknown>,
376        completion: impl Fn(Result<(), ImageSourceError>) + 'static,
377    ) -> bool {
378        let Some(binding) = self.target.borrow().binding.clone() else {
379            return false;
380        };
381        binding.endpoint.enqueue(NativeWork {
382            identity: binding.identity,
383            work: ImperativeRequest::SetNativeImageSource {
384                node: binding.node,
385                source,
386                completion: Callback::new(move |result: Result<(), RuntimeError>| {
387                    completion(result.map_err(ImageSourceError::from_runtime));
388                }),
389            },
390        })
391    }
392
393    /// Observes rasterization scale across the image's published bindings.
394    ///
395    /// Registration is accepted before publication. Each new binding creates a native
396    /// subscription after the image enters a live XAML tree.
397    #[must_use = "the observation stops when the handle is dropped"]
398    pub fn observe_rasterization_scale(
399        &self,
400        callback: impl Fn(f64) + 'static,
401    ) -> ElementObservation {
402        self.register_observation(ReferenceObservation::ImageScale(Callback::new(callback)))
403    }
404}
405
406impl ElementRef<crate::Grid> {
407    /// Observes an application-owned lifted Composition host across published bindings.
408    ///
409    /// Registration is accepted before publication. Each new binding creates a native
410    /// subscription for that host, and removal retires the old subscription.
411    #[must_use = "the observation stops when the handle is dropped"]
412    pub fn observe_composition_host(
413        &self,
414        callback: impl Fn(CompositionHostEvent) + 'static,
415    ) -> ElementObservation {
416        self.register_observation(ReferenceObservation::CompositionHost(Callback::new(
417            callback,
418        )))
419    }
420
421    /// Assigns an application-owned lifted Composition visual to the host element.
422    #[must_use = "false means the reference is currently unbound"]
423    pub fn request_set_child_visual(
424        &self,
425        visual: Option<windows_core::IUnknown>,
426        completion: impl Fn(Result<(), CompositionHostError>) + 'static,
427    ) -> bool {
428        let Some(binding) = self.target.borrow().binding.clone() else {
429            return false;
430        };
431        binding.endpoint.enqueue(NativeWork {
432            identity: binding.identity,
433            work: ImperativeRequest::SetCompositionChildVisual {
434                node: binding.node,
435                visual,
436                completion: Callback::new(move |result: Result<(), RuntimeError>| {
437                    completion(result.map_err(CompositionHostError::from_runtime));
438                }),
439            },
440        })
441    }
442}
443
444/// Layout and compositor events from a composition host.
445///
446/// Width and height are in device-independent pixels (DIPs). Scale converts DIPs to pixels.
447#[derive(Clone, Debug, PartialEq)]
448pub enum CompositionHostEvent {
449    /// Provides the application-safe compositor capability for a newly bound host.
450    Ready {
451        compositor: windows_core::IUnknown,
452        width: f64,
453        height: f64,
454        scale: f64,
455    },
456    /// Reports updated layout metrics for the current host.
457    Metrics { width: f64, height: f64, scale: f64 },
458}
459
460/// Failure reported by an asynchronous native integration request.
461#[derive(Clone, Copy, Debug, Eq, PartialEq)]
462pub enum IntegrationError {
463    /// A native operation failed with the given HRESULT value.
464    Native(i32),
465    /// The target is not currently bound or cannot accept the request.
466    Unavailable,
467}
468
469impl IntegrationError {
470    fn from_runtime(error: RuntimeError) -> Self {
471        match error {
472            RuntimeError::Native(code) => Self::Native(code),
473            _ => Self::Unavailable,
474        }
475    }
476}
477
478/// Errors from composition-host requests.
479pub type CompositionHostError = IntegrationError;
480/// Errors from focus requests.
481pub type FocusError = IntegrationError;
482/// Errors from image-source requests.
483pub type ImageSourceError = IntegrationError;
484/// Errors from swap-chain panel requests.
485pub type SwapChainPanelError = IntegrationError;
486/// Errors from WebView2 integration requests.
487pub type WebView2Error = IntegrationError;
488/// Rendering and layout events from a swap-chain panel.
489#[derive(Clone, Copy, Debug, PartialEq)]
490pub enum SwapChainPanelEvent {
491    /// Reports panel dimensions in DIPs and the pixel scale for each axis.
492    Metrics {
493        width: f64,
494        height: f64,
495        scale_x: f32,
496        scale_y: f32,
497    },
498    /// Requests a frame during the composition rendering pass.
499    Rendering,
500}
501
502impl<T> Clone for ElementRef<T> {
503    fn clone(&self) -> Self {
504        Self {
505            target: Rc::clone(&self.target),
506            marker: PhantomData,
507        }
508    }
509}
510
511impl<T: ReferenceControl> Default for ElementRef<T> {
512    fn default() -> Self {
513        Self::new()
514    }
515}
516
517impl<T> fmt::Debug for ElementRef<T> {
518    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
519        formatter
520            .debug_struct("ElementRef")
521            .field("bound", &self.target.borrow().binding.is_some())
522            .finish()
523    }
524}
525
526impl<T> PartialEq for ElementRef<T> {
527    fn eq(&self, other: &Self) -> bool {
528        Rc::ptr_eq(&self.target, &other.target)
529    }
530}
531
532impl<T> Eq for ElementRef<T> {}
533
534/// Marks generated controls that accept programmatic focus.
535///
536/// This trait is sealed and implemented only for controls whose generated schema declares focus
537/// support.
538pub trait FocusControl: ReferenceControl {}
539
540#[derive(Clone)]
541pub(crate) struct NativeElementRef(Rc<RefCell<ReferenceTarget>>);
542
543impl NativeElementRef {
544    pub(crate) fn identity(&self) -> usize {
545        Rc::as_ptr(&self.0) as usize
546    }
547
548    pub(crate) fn bind(&self, endpoint: ImperativeEndpoint, identity: WindowToken, node: NodeId) {
549        let reference = self.identity();
550        let previous = self.0.borrow().binding.clone();
551        let observations = {
552            let mut target = self.0.borrow_mut();
553            target.observations.retain(|observation| {
554                observation
555                    .upgrade()
556                    .is_some_and(|observation| observation.active.get())
557            });
558            target
559                .observations
560                .iter()
561                .filter_map(Weak::upgrade)
562                .collect::<Vec<_>>()
563        };
564        if let Some(previous) = previous {
565            previous.endpoint.retire_observations(reference);
566            for observation in &observations {
567                previous.endpoint.enqueue_observation_revocation(
568                    previous.identity,
569                    previous.node,
570                    observation.id,
571                );
572            }
573        }
574        let binding = ReferenceBinding {
575            endpoint,
576            identity,
577            node,
578        };
579        self.0.borrow_mut().binding = Some(binding.clone());
580        let requests = observations
581            .iter()
582            .map(|observation| {
583                (
584                    observation.id,
585                    ObservationRegistration::request(observation, &self.0, &binding),
586                )
587            })
588            .collect();
589        binding.endpoint.replace_observations(reference, requests);
590    }
591
592    pub(crate) fn unbind(&self, identity: WindowToken, node: NodeId) {
593        let binding = self.0.borrow().binding.clone();
594        if let Some(binding) = binding
595            && binding.identity == identity
596            && binding.node == node
597        {
598            let reference = self.identity();
599            binding.endpoint.retire_observations(reference);
600            for observation in self
601                .0
602                .borrow()
603                .observations
604                .iter()
605                .filter_map(Weak::upgrade)
606                .filter(|observation| observation.active.get())
607            {
608                binding.endpoint.enqueue_observation_revocation(
609                    binding.identity,
610                    binding.node,
611                    observation.id,
612                );
613            }
614            self.0.borrow_mut().binding = None;
615        }
616    }
617
618    pub(crate) fn binding_target(&self) -> Option<(WindowToken, NodeId)> {
619        self.0
620            .borrow()
621            .binding
622            .as_ref()
623            .map(|binding| (binding.identity, binding.node))
624    }
625}
626
627impl fmt::Debug for NativeElementRef {
628    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
629        formatter.write_str("NativeElementRef")
630    }
631}
632
633impl PartialEq for NativeElementRef {
634    fn eq(&self, other: &Self) -> bool {
635        Rc::ptr_eq(&self.0, &other.0)
636    }
637}
638
639impl Eq for NativeElementRef {}
640
641#[derive(Clone)]
642struct ReferenceBinding {
643    endpoint: ImperativeEndpoint,
644    identity: WindowToken,
645    node: NodeId,
646}
647
648#[derive(Default)]
649struct ReferenceTarget {
650    binding: Option<ReferenceBinding>,
651    observations: Vec<Weak<ObservationRegistration>>,
652}
653
654#[derive(Clone)]
655enum ReferenceObservation {
656    SwapChainPanel(Callback<SwapChainPanelEvent>),
657    ImageScale(Callback<f64>),
658    CompositionHost(Callback<CompositionHostEvent>),
659}
660
661struct ObservationRegistration {
662    active: Cell<bool>,
663    id: u64,
664    observation: ReferenceObservation,
665}
666
667impl ObservationRegistration {
668    fn request(
669        this: &Rc<Self>,
670        target: &Rc<RefCell<ReferenceTarget>>,
671        binding: &ReferenceBinding,
672    ) -> NativeWork<ImperativeRequest> {
673        let identity = binding.identity;
674        let node = binding.node;
675        let target = Rc::downgrade(target);
676        let registration = Rc::downgrade(this);
677        let work = match &this.observation {
678            ReferenceObservation::SwapChainPanel(callback) => {
679                ImperativeRequest::ObserveSwapChainPanel {
680                    node,
681                    observation: this.id,
682                    callback: current_binding_callback(
683                        target,
684                        registration,
685                        identity,
686                        node,
687                        callback.clone(),
688                    ),
689                }
690            }
691            ReferenceObservation::ImageScale(callback) => ImperativeRequest::ObserveImageScale {
692                node,
693                observation: this.id,
694                callback: current_binding_callback(
695                    target,
696                    registration,
697                    identity,
698                    node,
699                    callback.clone(),
700                ),
701            },
702            ReferenceObservation::CompositionHost(callback) => {
703                ImperativeRequest::ObserveCompositionHost {
704                    node,
705                    observation: this.id,
706                    callback: current_binding_callback(
707                        target,
708                        registration,
709                        identity,
710                        node,
711                        callback.clone(),
712                    ),
713                }
714            }
715        };
716        NativeWork { identity, work }
717    }
718}
719
720fn current_binding_callback<T: 'static>(
721    target: Weak<RefCell<ReferenceTarget>>,
722    registration: Weak<ObservationRegistration>,
723    identity: WindowToken,
724    node: NodeId,
725    callback: Callback<T>,
726) -> Callback<T> {
727    Callback::new_with_acceptance(move |value| {
728        let active = registration
729            .upgrade()
730            .is_some_and(|registration| registration.active.get());
731        let current = active
732            && target.upgrade().is_some_and(|target| {
733                target
734                    .borrow()
735                    .binding
736                    .as_ref()
737                    .is_some_and(|binding| binding.identity == identity && binding.node == node)
738            });
739        current && callback.call(value)
740    })
741}
742
743impl<T> ElementRef<T> {
744    fn register_observation(&self, observation: ReferenceObservation) -> ElementObservation {
745        let id = NEXT_OBSERVATION_ID
746            .try_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
747            .unwrap_or_else(|_| panic!("element observation identity exhausted"));
748        let registration = Rc::new(ObservationRegistration {
749            active: Cell::new(true),
750            id,
751            observation,
752        });
753        let mut target = self.target.borrow_mut();
754        target.observations.push(Rc::downgrade(&registration));
755        if let Some(binding) = &target.binding {
756            let request = ObservationRegistration::request(&registration, &self.target, binding);
757            binding
758                .endpoint
759                .enqueue_observation(self.target_identity(), registration.id, request);
760        }
761        ElementObservation {
762            reference: self.target_identity(),
763            registration,
764            target: Rc::downgrade(&self.target),
765        }
766    }
767
768    fn target_identity(&self) -> usize {
769        Rc::as_ptr(&self.target) as usize
770    }
771}
772
773/// Keeps an `ElementRef` observation registered.
774///
775/// Dropping the handle stops callback delivery and prevents the observation from following later
776/// bindings. The observed element reference may be captured by the callback without creating an
777/// ownership cycle.
778pub struct ElementObservation {
779    reference: usize,
780    registration: Rc<ObservationRegistration>,
781    target: Weak<RefCell<ReferenceTarget>>,
782}
783
784impl Drop for ElementObservation {
785    fn drop(&mut self) {
786        self.registration.active.set(false);
787        let Some(target) = self.target.upgrade() else {
788            return;
789        };
790        let registration = self.registration.id;
791        let mut target = target.borrow_mut();
792        target.observations.retain(|observation| {
793            observation
794                .upgrade()
795                .is_some_and(|observation| observation.id != registration)
796        });
797        if let Some(binding) = &target.binding {
798            binding.endpoint.retire_observation(
799                self.reference,
800                registration,
801                binding.identity,
802                binding.node,
803            );
804        }
805    }
806}
807
808#[derive(Clone, Debug, PartialEq)]
809pub(crate) enum ImperativeRequest {
810    Focus {
811        node: NodeId,
812        completion: Callback<Result<bool, RuntimeError>>,
813    },
814    InitializeWebView2 {
815        node: NodeId,
816        completion: Callback<Result<windows_core::IUnknown, RuntimeError>>,
817    },
818    ObserveSwapChainPanel {
819        node: NodeId,
820        observation: u64,
821        callback: Callback<SwapChainPanelEvent>,
822    },
823    SetSwapChain {
824        node: NodeId,
825        swap_chain: Option<windows_core::IUnknown>,
826        completion: Callback<Result<(), RuntimeError>>,
827    },
828    SetNativeImageSource {
829        node: NodeId,
830        source: Option<windows_core::IUnknown>,
831        completion: Callback<Result<(), RuntimeError>>,
832    },
833    ObserveImageScale {
834        node: NodeId,
835        observation: u64,
836        callback: Callback<f64>,
837    },
838    ObserveCompositionHost {
839        node: NodeId,
840        observation: u64,
841        callback: Callback<CompositionHostEvent>,
842    },
843    RevokeObservation {
844        node: NodeId,
845        observation: u64,
846    },
847    SetCompositionChildVisual {
848        node: NodeId,
849        visual: Option<windows_core::IUnknown>,
850        completion: Callback<Result<(), RuntimeError>>,
851    },
852}
853
854impl ImperativeRequest {
855    pub(crate) fn complete_unavailable(self) {
856        match self {
857            Self::Focus { node, completion } => {
858                _ = completion.call(Err(RuntimeError::MissingNode(node)));
859            }
860            Self::InitializeWebView2 { node, completion } => {
861                _ = completion.call(Err(RuntimeError::MissingNode(node)));
862            }
863            Self::SetSwapChain {
864                node, completion, ..
865            }
866            | Self::SetNativeImageSource {
867                node, completion, ..
868            }
869            | Self::SetCompositionChildVisual {
870                node, completion, ..
871            } => {
872                _ = completion.call(Err(RuntimeError::MissingNode(node)));
873            }
874            Self::ObserveSwapChainPanel { .. }
875            | Self::ObserveImageScale { .. }
876            | Self::ObserveCompositionHost { .. }
877            | Self::RevokeObservation { .. } => {}
878        }
879    }
880}
881
882#[derive(Clone)]
883pub(crate) struct ImperativeEndpoint {
884    queue: Rc<RefCell<VecDeque<QueuedImperative>>>,
885    wake: Option<Rc<dyn Fn()>>,
886}
887
888enum QueuedImperative {
889    OneShot(NativeWork<ImperativeRequest>),
890    Observation {
891        reference: usize,
892        registration: u64,
893        request: NativeWork<ImperativeRequest>,
894    },
895}
896
897impl ImperativeEndpoint {
898    pub(crate) fn new(wake: Option<Rc<dyn Fn()>>) -> Self {
899        Self {
900            queue: Rc::new(RefCell::new(VecDeque::new())),
901            wake,
902        }
903    }
904
905    fn enqueue(&self, request: NativeWork<ImperativeRequest>) -> bool {
906        let mut queue = self.queue.borrow_mut();
907        if queue.len() >= IMPERATIVE_QUEUE_CAPACITY {
908            return false;
909        }
910        queue.push_back(QueuedImperative::OneShot(request));
911        drop(queue);
912        self.wake();
913        true
914    }
915
916    fn enqueue_observation(
917        &self,
918        reference: usize,
919        registration: u64,
920        request: NativeWork<ImperativeRequest>,
921    ) {
922        self.queue
923            .borrow_mut()
924            .push_back(QueuedImperative::Observation {
925                reference,
926                registration,
927                request,
928            });
929        self.wake();
930    }
931
932    fn replace_observations(
933        &self,
934        reference: usize,
935        requests: Vec<(u64, NativeWork<ImperativeRequest>)>,
936    ) {
937        let mut queue = self.queue.borrow_mut();
938        queue.retain(|queued| {
939            !matches!(
940                queued,
941                QueuedImperative::Observation {
942                    reference: queued,
943                    ..
944                } if *queued == reference
945            )
946        });
947        queue.extend(requests.into_iter().map(|(registration, request)| {
948            QueuedImperative::Observation {
949                reference,
950                registration,
951                request,
952            }
953        }));
954        drop(queue);
955        self.wake();
956    }
957
958    fn retire_observations(&self, reference: usize) {
959        self.queue.borrow_mut().retain(|queued| {
960            !matches!(
961                queued,
962                QueuedImperative::Observation {
963                    reference: queued,
964                    ..
965                } if *queued == reference
966            )
967        });
968    }
969
970    fn retire_observation(
971        &self,
972        reference: usize,
973        registration: u64,
974        identity: WindowToken,
975        node: NodeId,
976    ) {
977        self.queue.borrow_mut().retain(|queued| {
978            !matches!(
979                queued,
980                QueuedImperative::Observation {
981                    reference: queued_reference,
982                    registration: queued_registration,
983                    ..
984                } if *queued_reference == reference && *queued_registration == registration
985            )
986        });
987        self.enqueue_observation_revocation(identity, node, registration);
988    }
989
990    fn enqueue_observation_revocation(
991        &self,
992        identity: WindowToken,
993        node: NodeId,
994        observation: u64,
995    ) {
996        self.queue
997            .borrow_mut()
998            .push_back(QueuedImperative::OneShot(NativeWork {
999                identity,
1000                work: ImperativeRequest::RevokeObservation { node, observation },
1001            }));
1002        self.wake();
1003    }
1004
1005    pub(crate) fn pop_front(&self) -> Option<NativeWork<ImperativeRequest>> {
1006        self.queue
1007            .borrow_mut()
1008            .pop_front()
1009            .map(|queued| match queued {
1010                QueuedImperative::OneShot(request)
1011                | QueuedImperative::Observation { request, .. } => request,
1012            })
1013    }
1014
1015    pub(crate) fn is_empty(&self) -> bool {
1016        self.queue.borrow().is_empty()
1017    }
1018
1019    pub(crate) fn clear(&self) {
1020        self.queue.borrow_mut().clear();
1021    }
1022
1023    pub(crate) fn complete_unavailable(&self) {
1024        let queued = std::mem::take(&mut *self.queue.borrow_mut());
1025        for request in queued {
1026            match request {
1027                QueuedImperative::OneShot(request)
1028                | QueuedImperative::Observation { request, .. } => {
1029                    request.work.complete_unavailable();
1030                }
1031            }
1032        }
1033    }
1034
1035    fn wake(&self) {
1036        if let Some(wake) = &self.wake {
1037            wake();
1038        }
1039    }
1040}