1#![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 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 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 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 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}