Skip to main content

windows_webview/
reactor.rs

1use super::*;
2use std::cell::RefCell;
3use std::rc::Rc;
4use windows_reactor::{
5    Callback, Component, ComponentContext, ElementRef, IntegrationError, IntoPayloadCallback, View,
6    ViewContext, WebView2,
7};
8
9/// Hosts a WebView2 in a [`windows-reactor`](windows_reactor) UI tree.
10///
11/// Calls `on_ready` on the UI thread after the XAML control initializes and fails fast if
12/// initialization fails.
13pub fn webview(on_ready: impl IntoPayloadCallback<WebView>) -> View {
14    let on_ready = on_ready.into_payload_callback();
15    webview_result(move |result| match result {
16        Ok(webview) => {
17            _ = on_ready.call(webview);
18        }
19        Err(IntegrationError::Native(code)) => {
20            panic!("windows-webview Reactor integration failed: HRESULT({code:#010X})");
21        }
22        Err(IntegrationError::Unavailable) => {}
23    })
24}
25
26/// Hosts a WebView2 and reports initialization success or failure on the UI thread.
27pub fn webview_result(
28    on_ready: impl IntoPayloadCallback<std::result::Result<WebView, IntegrationError>>,
29) -> View {
30    View::component::<WebViewHost>(on_ready.into_payload_callback())
31}
32
33struct WebViewHost {
34    control: ElementRef<WebView2>,
35    on_ready: Rc<RefCell<Callback<std::result::Result<WebView, IntegrationError>>>>,
36}
37
38impl Component for WebViewHost {
39    type Input = Callback<std::result::Result<WebView, IntegrationError>>;
40    type Message = ();
41
42    fn create(input: &Self::Input, _context: &ComponentContext<Self>) -> Self {
43        Self {
44            control: ElementRef::new(),
45            on_ready: Rc::new(RefCell::new(input.clone())),
46        }
47    }
48
49    fn input_changed(&mut self, input: &Self::Input, _context: &ComponentContext<Self>) {
50        *self.on_ready.borrow_mut() = input.clone();
51    }
52
53    fn view(&self, _input: &Self::Input, context: &mut ViewContext<Self>) -> View {
54        let control = self.control.clone();
55        let on_ready = Rc::clone(&self.on_ready);
56        context.use_effect("initialize", (), move || {
57            let callback = Rc::clone(&on_ready);
58            let accepted = control.request_core_web_view2(move |result| {
59                let result = result.and_then(|core| bridge(&core));
60                let callback = callback.borrow().clone();
61                _ = callback.call(result);
62            });
63            if !accepted {
64                let callback = on_ready.borrow().clone();
65                _ = callback.call(Err(IntegrationError::Unavailable));
66            }
67            None
68        });
69        WebView2::new().element_ref(&self.control).into()
70    }
71}
72
73fn bridge(core: &IUnknown) -> std::result::Result<WebView, IntegrationError> {
74    let interop: ICoreWebView2Interop2 = core.cast().map_err(integration_error)?;
75    let com_core: ICoreWebView2 =
76        unsafe { interop.GetComICoreWebView2() }.map_err(integration_error)?;
77    Ok(WebView::from_core(com_core))
78}
79
80fn integration_error(error: Error) -> IntegrationError {
81    IntegrationError::Native(error.code().0)
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn bridge_errors_preserve_the_hresult() {
90        let code = HRESULT(0x8000_4005_u32 as i32);
91        assert_eq!(
92            integration_error(Error::from_hresult(code)),
93            IntegrationError::Native(code.0)
94        );
95    }
96}