javascriptcore_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3
4/// Transforms a Rust function into a C function for being used as a JavaScript callback.
5///
6/// This `function_callback` procedural macro transforms a Rust function of type:
7///
8/// ```rust,ignore
9/// fn(
10///     context: &JSContext,
11///     function: Option<&JSObject>,
12///     this_object: Option<&JSObject>,
13///     arguments: &[JSValue]
14/// ) -> Result<JSValue, JSException>
15/// ```
16///
17/// into a `javascriptcore_sys::JSObjectCallAsFunctionCallback` function.
18///
19/// Check the documentation of `javascriptcore::JSValue::new_function` to learn more.
20#[proc_macro_attribute]
21pub fn function_callback(_attributes: TokenStream, item: TokenStream) -> TokenStream {
22    let function = syn::parse::<syn::ItemFn>(item)
23        .expect("#[function_callback] must apply on a valid function");
24    let function_visibility = &function.vis;
25    let function_name = &function.sig.ident;
26    let function_generics = &function.sig.generics.params;
27    let function_where_clause = &function.sig.generics.where_clause;
28
29    quote! {
30        #function_visibility unsafe extern "C" fn #function_name < #function_generics > (
31            raw_ctx: javascriptcore::sys::JSContextRef,
32            function: javascriptcore::sys::JSObjectRef,
33            this_object: javascriptcore::sys::JSObjectRef,
34            argument_count: usize,
35            arguments: *const javascriptcore::sys::JSValueRef,
36            exception: *mut javascriptcore::sys::JSValueRef,
37        ) -> *const javascriptcore::sys::OpaqueJSValue
38        #function_where_clause
39        {
40            use ::core::{mem::ManuallyDrop, option::Option, ops::Not, ptr, result::Result, slice};
41            use ::std::vec::Vec;
42            use javascriptcore::{sys::JSValueRef, JSContext, JSObject, JSValue};
43
44            // This should never happen, it's simply a paranoid precaution.
45            assert!(raw_ctx.is_null().not(), "`JSContextRef` is null");
46
47            // First off, let's prepare the arguments. The goal is to transform the raw C pointers
48            // into Rust types.
49
50            // Let's not drop `ctx`, otherwise it will close the context.
51            let ctx = ManuallyDrop::new(JSContext::from_raw(raw_ctx as *mut _));
52            let function = JSObject::from_raw(raw_ctx, function);
53            let this_object = JSObject::from_raw(raw_ctx, this_object);
54
55            let function = if function.is_null() {
56                None
57            } else {
58                Some(&function)
59            };
60
61            let this_object = if this_object.is_null() {
62                None
63            } else {
64                Some(&this_object)
65            };
66
67            let arguments = if argument_count == 0 || arguments.is_null() {
68                Vec::new()
69            } else {
70                unsafe { slice::from_raw_parts(arguments, argument_count) }
71                    .iter()
72                    .map(|value| JSValue::from_raw(raw_ctx, *value))
73                    .collect::<Vec<_>>()
74            };
75
76            // Isolate the `#function` inside its own block to avoid collisions with variables.
77            // Let's use also this as an opportunity to type check the function being annotated by
78            // `function_callback`.
79            let func: fn(
80                &JSContext,
81                Option<&JSObject>,
82                Option<&JSObject>,
83                &[JSValue],
84            ) -> Result<JSValue, JSException> = {
85                #function
86
87                #function_name ::< #function_generics >
88            };
89
90            // Second, call the original function.
91            let result = func(&ctx, function, this_object, arguments.as_slice());
92
93            // Finally, let's handle the result, including the exception.
94            match result {
95                Ok(value) => {
96                    // Ensure `exception` contains a null pointer.
97                    *exception = ptr::null_mut();
98
99                    // Return the result.
100                    value.into()
101                }
102                Err(exc) => {
103                    // Fill the exception.
104                    *exception = JSValueRef::from(exc) as *mut _;
105
106                    // Return a null pointer for the result.
107                    ptr::null()
108                }
109            }
110        }
111    }
112    .into()
113}
114
115/// Transforms a Rust function into a C function for being used as a JavaScript
116/// constructor callback.
117///
118/// This `constructor_callback` procedural macro transforms a Rust function of type:
119///
120/// ```rust,ignore
121/// fn(
122///     context: &JSContext,
123///     constructor: Option<&JSObject>,
124///     arguments: &[JSValue]
125/// ) -> Result<JSValue, JSException>
126/// ```
127///
128/// into a `javascriptcore_sys::JSObjectCallAsConstructorCallback` function.
129///
130/// Check the documentation of `javascriptcore::JSClass::new` to learn more.
131#[proc_macro_attribute]
132pub fn constructor_callback(_attributes: TokenStream, item: TokenStream) -> TokenStream {
133    let constructor = syn::parse::<syn::ItemFn>(item)
134        .expect("#[constructor_callback] must apply on a valid function");
135    let constructor_visibility = &constructor.vis;
136    let constructor_name = &constructor.sig.ident;
137    let constructor_generics = &constructor.sig.generics.params;
138    let constructor_where_clause = &constructor.sig.generics.where_clause;
139
140    quote! {
141        #constructor_visibility unsafe extern "C" fn #constructor_name < #constructor_generics >(
142            raw_ctx: javascriptcore::sys::JSContextRef,
143            constructor: javascriptcore::sys::JSObjectRef,
144            argument_count: usize,
145            arguments: *const javascriptcore::sys::JSValueRef,
146            exception: *mut javascriptcore::sys::JSValueRef,
147        ) -> *mut javascriptcore::sys::OpaqueJSValue
148        #constructor_where_clause
149        {
150            use ::core::{mem::ManuallyDrop, option::Option, ops::Not, ptr, result::Result, slice};
151            use ::std::vec::Vec;
152            use javascriptcore::{sys::JSValueRef, JSContext, JSObject, JSValue};
153
154            // This should never happen, it's simply a paranoid precaution.
155            assert!(raw_ctx.is_null().not(), "`JSContextRef` is null");
156
157            // First off, let's prepare the arguments. The goal is to transform the raw C pointers
158            // into Rust types.
159
160            // Let's not drop `ctx`, otherwise it will close the context.
161            let ctx = ManuallyDrop::new(JSContext::from_raw(raw_ctx as *mut _));
162            let constructor = JSObject::from_raw(raw_ctx, constructor);
163
164            let arguments = if argument_count == 0 || arguments.is_null() {
165                Vec::new()
166            } else {
167                unsafe { slice::from_raw_parts(arguments, argument_count) }
168                    .iter()
169                    .map(|value| JSValue::from_raw(raw_ctx, *value))
170                    .collect::<Vec<_>>()
171            };
172
173            // Isolate the `#constructor` inside its own block to avoid collisions with variables.
174            // Let's use also this as an opportunity to type check the constructor being annotated by
175            // `constructor_callback`.
176            let ctor: fn(
177                &JSContext,
178                &JSObject,
179                &[JSValue],
180            ) -> Result<JSValue, JSException> = {
181                #constructor
182
183                #constructor_name ::< #constructor_generics >
184            };
185
186            // Second, call the original constructor.
187            let result = ctor(&ctx, &constructor, arguments.as_slice());
188
189            // Finally, let's handle the result, including the exception.
190            match result {
191                Ok(value) => {
192                    // Ensure `exception` contains a null pointer.
193                    *exception = ptr::null_mut();
194
195                    // Return the result.
196                    value.into()
197                }
198                Err(exc) => {
199                    // Fill the exception.
200                    *exception = JSValueRef::from(exc) as *mut _;
201
202                    // Return a null pointer for the result.
203                    ptr::null_mut()
204                }
205            }
206        }
207    }
208    .into()
209}