Skip to main content

servo_dom_struct/
lib.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5#![recursion_limit = "128"]
6
7use proc_macro::TokenStream;
8use quote::quote;
9use syn::*;
10mod domobject;
11use crate::domobject::expand_dom_object;
12
13#[proc_macro_attribute]
14pub fn dom_struct(args: TokenStream, input: TokenStream) -> TokenStream {
15    let args2 = proc_macro2::TokenStream::from(args);
16    let input2 = proc_macro2::TokenStream::from(input);
17
18    TokenStream::from(dom_struct_impl(args2, input2))
19}
20
21fn dom_struct_impl(
22    args: proc_macro2::TokenStream,
23    input: proc_macro2::TokenStream,
24) -> proc_macro2::TokenStream {
25    let associated_memory = args.to_string().contains("associated_memory");
26    let no_has_parent = args.to_string().contains("no_has_parent");
27    if !associated_memory && !no_has_parent && !args.is_empty() {
28        panic!("#[dom_struct] only takes 'associated_memory' or 'no_has_parent' as an argument");
29    }
30    let attributes = quote! {
31        #[derive(deny_public_fields::DenyPublicFields, JSTraceable, MallocSizeOf)]
32        #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
33        #[repr(C)]
34    };
35
36    // Work around https://github.com/rust-lang/rust/issues/46489
37    let attributes: proc_macro2::TokenStream = attributes.to_string().parse().unwrap();
38
39    let output: proc_macro2::TokenStream = attributes.into_iter().chain(input).collect();
40
41    let item: Item = syn::parse2(output).unwrap();
42
43    if let Item::Struct(s) = item {
44        let expanded_dom_object = expand_dom_object(s.clone(), associated_memory);
45        let s2 = quote! { #s #expanded_dom_object };
46        if no_has_parent {
47            return s2;
48        }
49        if let Fields::Named(ref f) = s.fields {
50            let f = f.named.first().expect("Must have at least one field");
51            let ident = f.ident.as_ref().expect("Must have named fields");
52            let name = &s.ident;
53            let ty = &f.ty;
54
55            if !s.generics.params.is_empty() {
56                quote! (
57                    #s2
58
59                    impl<D: DomTypes> crate::HasParent for #name<D> {
60                        type Parent = #ty;
61                        /// This is used in a type assertion to ensure that
62                    /// the source and webidls agree as to what the parent type is
63                    fn as_parent(&self) -> &#ty {
64                        &self.#ident
65                    }
66                })
67            } else {
68                quote! (
69                    #s2
70
71                    impl crate::HasParent for #name {
72                        type Parent = #ty;
73                        /// This is used in a type assertion to ensure that
74                        /// the source and webidls agree as to what the parent type is
75                        fn as_parent(&self) -> &#ty {
76                            &self.#ident
77                        }
78                    }
79                )
80            }
81        } else {
82            panic!("#[dom_struct] only applies to structs with named fields");
83        }
84    } else {
85        panic!("#[dom_struct] only applies to structs");
86    }
87}
88
89#[test]
90fn test_valid_dom_struct_generation() {
91    let args = quote! { associated_memory };
92    let reflector_type: syn::Type = parse_quote!(Reflector);
93    let input = quote! {
94        struct DomElement {
95            reflector: #reflector_type,
96        }
97    };
98
99    let result = dom_struct_impl(args, input);
100
101    let output =
102        syn::parse2(result).expect("Macro output failed to parse into a valid Rust file structure");
103    let formatted_output = prettyplease::unparse(&output);
104
105    let expected_output = quote! {
106        #[derive(deny_public_fields::DenyPublicFields, JSTraceable, MallocSizeOf)]
107        #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
108        #[repr(C)]
109        struct DomElement {
110            reflector: Reflector,
111        }
112        #[expect(non_upper_case_globals)]
113        const _IMPL_DOMOBJECT_FOR_DomElement: () = {
114            trait NoDomObjectInDomObject<A> {
115                fn some_item() {}
116            }
117            impl<T: ?Sized> NoDomObjectInDomObject<()> for T {}
118            struct Invalid;
119            impl<T> NoDomObjectInDomObject<Invalid> for T
120            where
121                T: ?Sized + crate::DomObject,
122            {}
123        };
124        impl ::js::conversions::ToJSValConvertible for DomElement {
125            fn safe_to_jsval(
126                &self,
127                cx: &mut js::context::JSContext,
128                rval: js::rust::MutableHandleValue,
129            ) {
130                let object = crate::DomObject::reflector(self).get_jsobject();
131                object.safe_to_jsval(cx, rval);
132            }
133        }
134        impl crate::DomObject for DomElement {
135            type ReflectorType = crate::AssociatedMemory;
136            #[inline]
137            fn reflector(&self) -> &crate::Reflector<Self::ReflectorType> {
138                self.reflector.reflector()
139            }
140        }
141        impl crate::MutDomObject for DomElement {
142            unsafe fn init_reflector<Actual>(&self, obj: *mut js::jsapi::JSObject) {
143                self.reflector.init_reflector::<Actual>(obj);
144            }
145            unsafe fn init_reflector_without_associated_memory(
146                &self,
147                obj: *mut js::jsapi::JSObject,
148            ) {
149                self.reflector.init_reflector_without_associated_memory(obj);
150            }
151        }
152        impl Eq for DomElement {}
153        impl PartialEq for DomElement {
154            fn eq(&self, other: &Self) -> bool {
155                crate::DomObject::reflector(self) == crate::DomObject::reflector(other)
156            }
157        }
158        impl crate::HasParent for DomElement {
159            type Parent = Reflector;
160            /// This is used in a type assertion to ensure that
161            /// the source and webidls agree as to what the parent type is
162            fn as_parent(&self) -> &Reflector {
163                &self.reflector
164            }
165        }
166    };
167    let expected_output_parsed: syn::File = syn::parse2(expected_output)
168        .expect("Macro output failed to parse into a valid Rust file structure");
169    let expected_formatted_output = prettyplease::unparse(&expected_output_parsed);
170
171    assert_eq!(
172        formatted_output.to_string(),
173        expected_formatted_output.to_string()
174    )
175}
176
177#[test]
178#[should_panic(expected = "#[dom_struct] only takes 'associated_memory'")]
179fn test_invalid_arguments_panic() {
180    let args = quote! { invalid_flag_here };
181    let input = quote! { struct MockStruct { first_field: i32 } };
182
183    dom_struct_impl(args, input);
184}
185
186#[test]
187#[should_panic(expected = "#[dom_struct] should not be applied on empty structs")]
188fn test_empty_struct_panic() {
189    let args = quote! {};
190    let input = quote! { struct EmptyStruct{} };
191
192    dom_struct_impl(args, input);
193}