webview2_com_callback_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::{
4    parenthesized,
5    parse::{Parse, ParseStream},
6    parse_macro_input,
7    punctuated::Punctuated,
8    Ident, Result, Token, TypePath, Visibility,
9};
10
11struct CallbackTypes {
12    pub interface: TypePath,
13    pub arg_1: TypePath,
14    pub arg_2: Option<TypePath>,
15}
16
17impl Parse for CallbackTypes {
18    fn parse(input: ParseStream) -> Result<Self> {
19        let content;
20        parenthesized!(content in input);
21        let args: Punctuated<TypePath, Token![,]> = content.parse_terminated(TypePath::parse)?;
22        input.parse::<Token![;]>()?;
23        let mut args = args.into_iter();
24        if let (Some(interface), Some(arg_1), arg_2) = (args.next(), args.next(), args.next()) {
25            Ok(CallbackTypes {
26                interface,
27                arg_1,
28                arg_2,
29            })
30        } else {
31            Err(content.error("expected (interface, arg_1, arg_2)"))
32        }
33    }
34}
35
36struct CallbackStruct {
37    pub vis: Visibility,
38    _struct_token: Token![struct],
39    pub ident: Ident,
40    pub args: CallbackTypes,
41}
42
43impl Parse for CallbackStruct {
44    fn parse(input: ParseStream) -> Result<Self> {
45        Ok(CallbackStruct {
46            vis: input.parse()?,
47            _struct_token: input.parse()?,
48            ident: input.parse()?,
49            args: input.parse()?,
50        })
51    }
52}
53
54/// Implement a `CompletedCallback` using the types specified as tuple struct fields.
55#[proc_macro_attribute]
56pub fn completed_callback(_attr: TokenStream, input: TokenStream) -> TokenStream {
57    let ast = parse_macro_input!(input as CallbackStruct);
58    impl_completed_callback(&ast)
59}
60
61fn impl_completed_callback(ast: &CallbackStruct) -> TokenStream {
62    let vis = &ast.vis;
63
64    let name = &ast.ident;
65    let closure = get_closure(name);
66    let interface = &ast.args.interface;
67
68    let arg_1 = &ast.args.arg_1;
69    let arg_2 = &ast.args.arg_2;
70
71    let msg = match interface.path.segments.last() {
72        Some(interface) => format!("Implementation of [`{}`].", interface.ident),
73        None => String::from("Implementation of unknown [`completed_callback`] interface."),
74    };
75
76    let gen = match arg_2 {
77        Some(arg_2) => quote! {
78            type #closure = CompletedClosure<#arg_1, #arg_2>;
79
80            #[doc = #msg]
81            #[implement(Microsoft::Web::WebView2::Win32::#interface)]
82            #vis struct #name(Option<#closure>);
83
84            #[allow(non_snake_case)]
85            impl #name {
86                pub fn create(
87                    closure: #closure,
88                ) -> #interface {
89                    Self(Some(closure)).into()
90                }
91
92                pub fn wait_for_async_operation(
93                    closure: Box<
94                        dyn FnOnce(#interface) -> crate::Result<()>,
95                    >,
96                    completed: #closure,
97                ) -> crate::Result<()> {
98                    let (tx, rx) = mpsc::channel();
99                    let completed: #closure =
100                        Box::new(move |arg_1, arg_2| -> ::windows::Result<()> {
101                            let result = completed(arg_1, arg_2).map_err(crate::Error::WindowsError);
102                            tx.send(result).expect("send over mpsc channel");
103                            Ok(())
104                        });
105                    let callback = Self::create(completed);
106
107                    closure(callback)?;
108                    crate::wait_with_pump(rx)?
109                }
110
111                fn Invoke<'a>(
112                    &mut self,
113                    arg_1: <#arg_1 as InvokeArg<'a>>::Input,
114                    arg_2: <#arg_2 as InvokeArg<'a>>::Input,
115                ) -> ::windows::Result<()> {
116                    match self.0.take() {
117                        Some(completed) => completed(
118                            <#arg_1 as InvokeArg<'a>>::convert(arg_1),
119                            <#arg_2 as InvokeArg<'a>>::convert(arg_2),
120                        ),
121                        None => Ok(()),
122                    }
123                }
124            }
125        },
126        None => quote! {
127            type #closure = Box<dyn FnOnce(<#arg_1 as ClosureArg>::Output) -> ::windows::Result<()>>;
128
129            #[doc = #msg]
130            #[implement(Microsoft::Web::WebView2::Win32::#interface)]
131            #vis struct #name(Option<#closure>);
132
133            #[allow(non_snake_case)]
134            impl #name {
135                pub fn create(
136                    closure: #closure,
137                ) -> #interface {
138                    Self(Some(closure)).into()
139                }
140
141                pub fn wait_for_async_operation(
142                    closure: Box<
143                        dyn FnOnce(#interface) -> crate::Result<()>,
144                    >,
145                    completed: #closure,
146                ) -> crate::Result<()> {
147                    let (tx, rx) = mpsc::channel();
148                    let completed: #closure =
149                        Box::new(move |arg_1| -> ::windows::Result<()> {
150                            let result = completed(arg_1).map_err(crate::Error::WindowsError);
151                            tx.send(result).expect("send over mpsc channel");
152                            Ok(())
153                        });
154                    let callback = Self::create(completed);
155
156                    closure(callback)?;
157                    crate::wait_with_pump(rx)?
158                }
159
160                fn Invoke<'a>(
161                    &mut self,
162                    arg_1: <#arg_1 as InvokeArg<'a>>::Input,
163                ) -> ::windows::Result<()> {
164                    match self.0.take() {
165                        Some(completed) => completed(
166                            <#arg_1 as InvokeArg<'a>>::convert(arg_1),
167                        ),
168                        None => Ok(()),
169                    }
170                }
171            }
172        },
173    };
174
175    gen.into()
176}
177
178/// Implement an `EventCallback` using the types specified as tuple struct fields.
179#[proc_macro_attribute]
180pub fn event_callback(_attr: TokenStream, input: TokenStream) -> TokenStream {
181    let ast = parse_macro_input!(input as CallbackStruct);
182    impl_event_callback(&ast)
183}
184
185fn impl_event_callback(ast: &CallbackStruct) -> TokenStream {
186    let vis = &ast.vis;
187
188    let name = &ast.ident;
189    let closure = get_closure(name);
190
191    let interface = &ast.args.interface;
192
193    let arg_1 = &ast.args.arg_1;
194    let arg_2 = &ast
195        .args
196        .arg_2
197        .as_ref()
198        .expect("event_callback should always have 2 arguments");
199
200    let msg = match interface.path.segments.last() {
201        Some(interface) => format!("Implementation of [`{}`].", interface.ident),
202        None => String::from("Implementation of unknown [`event_callback`] interface."),
203    };
204
205    let gen = quote! {
206        type #closure = EventClosure<#arg_1, #arg_2>;
207
208        #[doc = #msg]
209        #[implement(Microsoft::Web::WebView2::Win32::#interface)]
210        #vis struct #name(#closure);
211
212        #[allow(non_snake_case)]
213        impl #name {
214            pub fn create(
215                closure: #closure,
216            ) -> #interface {
217                Self(closure).into()
218            }
219
220            fn Invoke<'a>(
221                &mut self,
222                arg_1: <#arg_1 as InvokeArg<'a>>::Input,
223                arg_2: <#arg_2 as InvokeArg<'a>>::Input,
224            ) -> ::windows::Result<()> {
225                self.0(
226                    <#arg_1 as InvokeArg<'a>>::convert(arg_1),
227                    <#arg_2 as InvokeArg<'a>>::convert(arg_2),
228                )
229            }
230        }
231    };
232
233    gen.into()
234}
235
236fn get_closure(name: &Ident) -> Ident {
237    format_ident!("{}Closure", name)
238}