Skip to main content

wry_bindgen/
function.rs

1//! JavaScript function references and Rust callback management.
2//!
3//! This module provides types for calling JavaScript functions from Rust
4//! and for registering Rust callbacks that can be called from JavaScript.
5
6// Allow clippy lints for macro-generated code and internal types
7#![allow(clippy::too_many_arguments)]
8#![allow(clippy::type_complexity)]
9
10use alloc::vec::Vec;
11use core::cell::RefCell;
12use core::marker::PhantomData;
13
14use crate::batch::{force_flush, run_js_sync, with_runtime};
15use crate::encode::{BatchableResult, BinaryEncode, EncodeTypeDef, TYPE_CACHED, TYPE_FULL};
16use crate::ipc::DecodeError;
17use crate::ipc::DecodedData;
18use crate::ipc::EncodedData;
19
20/// Reserved function ID for dropping native Rust refs when JS objects are GC'd.
21/// JS sends this when a FinalizationRegistry callback fires for a RustFunction.
22pub const DROP_NATIVE_REF_FN_ID: u32 = 0xFFFFFFFF;
23
24/// Reserved function ID for calling exported Rust struct methods from JS.
25/// JS sends this with the export name to call the appropriate handler.
26pub const CALL_EXPORT_FN_ID: u32 = 0xFFFFFFFE;
27
28/// Encode type definitions for a function call.
29///
30/// On first encounter, emits `TYPE_FULL` + id + inline definition and records
31/// the id on the current encoder's pending-ack list. Once JS has acked the
32/// `TYPE_FULL` (signalled by the matching JS Respond popping the type-cache
33/// frame), subsequent calls emit `TYPE_CACHED` + id.
34fn encode_function_types(encoder: &mut EncodedData, encode_types: impl FnOnce(&mut Vec<u8>)) {
35    let mut type_buf = Vec::new();
36    encode_types(&mut type_buf);
37
38    with_runtime(|state| {
39        let (id, can_use_cached) = state.get_or_create_type_id(&type_buf);
40        if can_use_cached {
41            encoder.push_u8(TYPE_CACHED);
42            encoder.push_u32(id);
43        } else {
44            encoder.push_u8(TYPE_FULL);
45            encoder.push_u32(id);
46            encoder.register_pending_type_id(id);
47            for byte in type_buf {
48                encoder.push_u8(byte);
49            }
50        }
51    });
52}
53
54/// A reference to a JavaScript function that can be called from Rust.
55///
56/// The type parameter encodes the function signature.
57/// Arguments and return values are serialized using the binary protocol.
58pub struct JSFunction<T> {
59    id: u32,
60    function: PhantomData<T>,
61}
62
63impl<T> JSFunction<T> {
64    pub const fn new(id: u32) -> Self {
65        Self {
66            id,
67            function: PhantomData,
68        }
69    }
70
71    /// Get the function ID.
72    pub fn id(&self) -> u32 {
73        self.id
74    }
75}
76
77macro_rules! impl_js_function_call {
78    // Base case: zero arguments
79    (0,) => {
80        impl<R: BatchableResult + EncodeTypeDef> JSFunction<fn() -> R> {
81            pub fn call(&self) -> R {
82                run_js_sync::<R>(self.id, |encoder| {
83                    encode_function_types(encoder, |buf| {
84                        buf.push(0);
85                        R::encode_type_def(buf);
86                    });
87                })
88            }
89        }
90    };
91    // Recursive case: N arguments
92    ($n:expr, $($T:ident $arg:ident),+) => {
93        impl<$($T: EncodeTypeDef,)+ R: BatchableResult + EncodeTypeDef>
94            JSFunction<fn($($T),+) -> R>
95        {
96            pub fn call(&self, $($arg: $T),+) -> R
97            where
98                $($T: BinaryEncode,)+
99            {
100                run_js_sync::<R>(self.id, |encoder| {
101                    encode_function_types(encoder, |buf| {
102                        buf.push($n);
103                        $($T::encode_type_def(buf);)+
104                        R::encode_type_def(buf);
105                    });
106                    $($arg.encode(encoder);)+
107                })
108            }
109        }
110    };
111}
112
113impl_js_function_call!(0,);
114impl_js_function_call!(1, T1 arg1);
115impl_js_function_call!(2, T1 arg1, T2 arg2);
116impl_js_function_call!(3, T1 arg1, T2 arg2, T3 arg3);
117impl_js_function_call!(4, T1 arg1, T2 arg2, T3 arg3, T4 arg4);
118impl_js_function_call!(5, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5);
119impl_js_function_call!(6, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6);
120impl_js_function_call!(7, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7);
121impl_js_function_call!(8, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8);
122impl_js_function_call!(9, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9);
123impl_js_function_call!(10, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10);
124impl_js_function_call!(11, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11);
125impl_js_function_call!(12, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12);
126impl_js_function_call!(13, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13);
127impl_js_function_call!(14, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14);
128impl_js_function_call!(15, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15);
129impl_js_function_call!(16, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16);
130impl_js_function_call!(17, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16, T17 arg17);
131impl_js_function_call!(18, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16, T17 arg17, T18 arg18);
132impl_js_function_call!(19, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16, T17 arg17, T18 arg18, T19 arg19);
133impl_js_function_call!(20, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16, T17 arg17, T18 arg18, T19 arg19, T20 arg20);
134impl_js_function_call!(21, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16, T17 arg17, T18 arg18, T19 arg19, T20 arg20, T21 arg21);
135impl_js_function_call!(22, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16, T17 arg17, T18 arg18, T19 arg19, T20 arg20, T21 arg21, T22 arg22);
136impl_js_function_call!(23, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16, T17 arg17, T18 arg18, T19 arg19, T20 arg20, T21 arg21, T22 arg22, T23 arg23);
137impl_js_function_call!(24, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16, T17 arg17, T18 arg18, T19 arg19, T20 arg20, T21 arg21, T22 arg22, T23 arg23, T24 arg24);
138impl_js_function_call!(25, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16, T17 arg17, T18 arg18, T19 arg19, T20 arg20, T21 arg21, T22 arg22, T23 arg23, T24 arg24, T25 arg25);
139impl_js_function_call!(26, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16, T17 arg17, T18 arg18, T19 arg19, T20 arg20, T21 arg21, T22 arg22, T23 arg23, T24 arg24, T25 arg25, T26 arg26);
140impl_js_function_call!(27, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16, T17 arg17, T18 arg18, T19 arg19, T20 arg20, T21 arg21, T22 arg22, T23 arg23, T24 arg24, T25 arg25, T26 arg26, T27 arg27);
141impl_js_function_call!(28, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16, T17 arg17, T18 arg18, T19 arg19, T20 arg20, T21 arg21, T22 arg22, T23 arg23, T24 arg24, T25 arg25, T26 arg26, T27 arg27, T28 arg28);
142impl_js_function_call!(29, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16, T17 arg17, T18 arg18, T19 arg19, T20 arg20, T21 arg21, T22 arg22, T23 arg23, T24 arg24, T25 arg25, T26 arg26, T27 arg27, T28 arg28, T29 arg29);
143impl_js_function_call!(30, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16, T17 arg17, T18 arg18, T19 arg19, T20 arg20, T21 arg21, T22 arg22, T23 arg23, T24 arg24, T25 arg25, T26 arg26, T27 arg27, T28 arg28, T29 arg29, T30 arg30);
144impl_js_function_call!(31, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16, T17 arg17, T18 arg18, T19 arg19, T20 arg20, T21 arg21, T22 arg22, T23 arg23, T24 arg24, T25 arg25, T26 arg26, T27 arg27, T28 arg28, T29 arg29, T30 arg30, T31 arg31);
145impl_js_function_call!(32, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16, T17 arg17, T18 arg18, T19 arg19, T20 arg20, T21 arg21, T22 arg22, T23 arg23, T24 arg24, T25 arg25, T26 arg26, T27 arg27, T28 arg28, T29 arg29, T30 arg30, T31 arg31, T32 arg32);
146
147/// Internal type for storing Rust callback functions.
148/// Always stores as `Rc<dyn Fn(...)>` for uniform handling.
149/// - For `Fn` closures: stored directly, supports reentrant calls
150/// - For `FnMut` closures: wrapped in RefCell internally, panics on reentrant calls
151type CallbackFn = dyn Fn(&mut DecodedData, &mut EncodedData) -> Result<(), DecodeError>;
152
153pub(crate) struct RustCallback {
154    f: alloc::rc::Rc<CallbackFn>,
155}
156
157impl RustCallback {
158    /// Create a callback from an `Fn` closure (supports reentrant calls).
159    ///
160    /// The closure returns `Err` if an argument from JS fails to decode; the
161    /// caller surfaces that instead of letting an `unwrap` panic propagate.
162    pub fn new_fn<F>(f: F) -> Self
163    where
164        F: Fn(&mut DecodedData, &mut EncodedData) -> Result<(), DecodeError> + 'static,
165    {
166        Self {
167            f: alloc::rc::Rc::new(move |data: &mut DecodedData, encoder: &mut EncodedData| {
168                let result = f(data, encoder);
169                force_flush();
170                result
171            }),
172        }
173    }
174
175    /// Create a callback from an `FnMut` closure (panics on reentrant calls)
176    pub fn new_fn_mut<F>(f: F) -> Self
177    where
178        F: FnMut(&mut DecodedData, &mut EncodedData) -> Result<(), DecodeError> + 'static,
179    {
180        // Wrap the FnMut in a RefCell, then create an Fn wrapper
181        let cell = RefCell::new(f);
182        Self {
183            f: alloc::rc::Rc::new(move |data: &mut DecodedData, encoder: &mut EncodedData| {
184                let result = {
185                    let mut f = cell.borrow_mut();
186                    f(data, encoder)
187                };
188                force_flush();
189                result
190            }),
191        }
192    }
193
194    /// Get a cloned Rc to the callback
195    pub fn clone_rc(&self) -> alloc::rc::Rc<CallbackFn> {
196        self.f.clone()
197    }
198}