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 if !associated_memory && !args.is_empty() {
27 panic!("#[dom_struct] only takes 'associated_memory' as an argument");
28 }
29 let attributes = quote! {
30 #[derive(deny_public_fields::DenyPublicFields, JSTraceable, MallocSizeOf)]
31 #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
32 #[repr(C)]
33 };
34
35 let attributes: proc_macro2::TokenStream = attributes.to_string().parse().unwrap();
37
38 let output: proc_macro2::TokenStream = attributes.into_iter().chain(input).collect();
39
40 let item: Item = syn::parse2(output).unwrap();
41
42 if let Item::Struct(s) = item {
43 let expanded_dom_object = expand_dom_object(s.clone(), associated_memory);
44 let s2 = quote! { #s #expanded_dom_object };
45 if !s.generics.params.is_empty() {
46 return s2;
47 }
48 if let Fields::Named(ref f) = s.fields {
49 let f = f.named.first().expect("Must have at least one field");
50 let ident = f.ident.as_ref().expect("Must have named fields");
51 let name = &s.ident;
52 let ty = &f.ty;
53
54 quote! (
55 #s2
56
57 impl crate::HasParent for #name {
58 type Parent = #ty;
59 fn as_parent(&self) -> &#ty {
62 &self.#ident
63 }
64 }
65 )
66 } else {
67 panic!("#[dom_struct] only applies to structs with named fields");
68 }
69 } else {
70 panic!("#[dom_struct] only applies to structs");
71 }
72}
73
74#[test]
75fn test_valid_dom_struct_generation() {
76 let args = quote! { associated_memory };
77 let reflector_type: syn::Type = parse_quote!(Reflector);
78 let input = quote! {
79 struct DomElement {
80 reflector: #reflector_type,
81 }
82 };
83
84 let result = dom_struct_impl(args, input);
85
86 let output =
87 syn::parse2(result).expect("Macro output failed to parse into a valid Rust file structure");
88 let formatted_output = prettyplease::unparse(&output);
89
90 let expected_output = quote! {
91 #[derive(deny_public_fields::DenyPublicFields, JSTraceable, MallocSizeOf)]
92 #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
93 #[repr(C)]
94 struct DomElement {
95 reflector: Reflector,
96 }
97 #[expect(non_upper_case_globals)]
98 const _IMPL_DOMOBJECT_FOR_DomElement: () = {
99 trait NoDomObjectInDomObject<A> {
100 fn some_item() {}
101 }
102 impl<T: ?Sized> NoDomObjectInDomObject<()> for T {}
103 #[expect(dead_code)]
104 struct Invalid;
105 impl<T> NoDomObjectInDomObject<Invalid> for T
106 where
107 T: ?Sized + crate::DomObject,
108 {}
109 };
110 impl ::js::conversions::ToJSValConvertible for DomElement {
111 #[expect(unsafe_code)]
112 unsafe fn to_jsval(
113 &self,
114 cx: *mut js::jsapi::JSContext,
115 rval: js::rust::MutableHandleValue,
116 ) {
117 let object = crate::DomObject::reflector(self).get_jsobject();
118 object.to_jsval(cx, rval)
119 }
120 }
121 impl crate::DomObject for DomElement {
122 type ReflectorType = crate::AssociatedMemory;
123 #[inline]
124 fn reflector(&self) -> &crate::Reflector<Self::ReflectorType> {
125 self.reflector.reflector()
126 }
127 }
128 impl crate::MutDomObject for DomElement {
129 unsafe fn init_reflector<Actual>(&self, obj: *mut js::jsapi::JSObject) {
130 self.reflector.init_reflector::<Actual>(obj);
131 }
132 unsafe fn init_reflector_without_associated_memory(
133 &self,
134 obj: *mut js::jsapi::JSObject,
135 ) {
136 self.reflector.init_reflector_without_associated_memory(obj);
137 }
138 }
139 impl Eq for DomElement {}
140 impl PartialEq for DomElement {
141 fn eq(&self, other: &Self) -> bool {
142 crate::DomObject::reflector(self) == crate::DomObject::reflector(other)
143 }
144 }
145 impl crate::HasParent for DomElement {
146 type Parent = Reflector;
147 fn as_parent(&self) -> &Reflector {
150 &self.reflector
151 }
152 }
153 };
154 let expected_output_parsed: syn::File = syn::parse2(expected_output)
155 .expect("Macro output failed to parse into a valid Rust file structure");
156 let expected_formatted_output = prettyplease::unparse(&expected_output_parsed);
157
158 assert_eq!(
159 formatted_output.to_string(),
160 expected_formatted_output.to_string()
161 )
162}
163
164#[test]
165#[should_panic(expected = "#[dom_struct] only takes 'associated_memory'")]
166fn test_invalid_arguments_panic() {
167 let args = quote! { invalid_flag_here };
168 let input = quote! { struct MockStruct { first_field: i32 } };
169
170 dom_struct_impl(args, input);
171}
172
173#[test]
174#[should_panic(expected = "#[dom_struct] should not be applied on empty structs")]
175fn test_empty_struct_panic() {
176 let args = quote! {};
177 let input = quote! { struct EmptyStruct{} };
178
179 dom_struct_impl(args, input);
180}