Skip to main content

wry_bindgen/
closure.rs

1//! Closure compatibility types and traits.
2
3use alloc::boxed::Box;
4
5use crate::JsValue;
6use crate::encode::{BinaryEncode, CallbackKey, EncodeTypeDef};
7use crate::function::RustCallback;
8use crate::ipc::{DecodeError, DecodedData, EncodedData};
9use crate::object_store::insert_object;
10
11/// Borrowed or owned closure handle for passing Rust closures to JavaScript.
12pub struct ScopedClosure<'a, T: ?Sized> {
13    // careful: must be Box<T> not just T because unsized PhantomData
14    // seems to have weird interaction with Pin<>
15    pub(crate) _phantom: core::marker::PhantomData<(&'a (), crate::alloc::boxed::Box<T>)>,
16    pub(crate) callback: CallbackOwnership,
17    pub(crate) value: crate::JsValue,
18}
19
20/// Who is responsible for disposing the JS-side `RustFunction` wrapper that
21/// backs a `ScopedClosure`. Encoding an owned `Closure` by value detaches it
22/// because JavaScript takes ownership; borrowed encodes keep Rust ownership.
23/// Destructors only dispose in the `Owned` state.
24#[derive(Clone, Copy)]
25pub(crate) enum CallbackOwnership {
26    /// No Rust callback backing this closure (e.g., wrapping a raw JS function).
27    None,
28    /// Rust owns the callback; `ScopedClosure::drop` disposes it.
29    Owned,
30    /// Ownership has been handed off. No dispose on drop, but encoders still
31    /// flush so JS receives the callable before the call that needs it.
32    Detached,
33}
34
35impl CallbackOwnership {
36    /// Encoding this closure to JS requires an immediate flush so the JS
37    /// side has the callable ready.
38    pub(crate) fn needs_flush(&self) -> bool {
39        !matches!(self, Self::None)
40    }
41
42    /// Transition `Owned` → `Detached`. No-op for `None` / `Detached`.
43    pub(crate) fn detach(&mut self) {
44        if matches!(self, Self::Owned) {
45            *self = Self::Detached;
46        }
47    }
48}
49
50/// Owned closure handle. This follows upstream wasm-bindgen's alias direction.
51pub type Closure<T> = ScopedClosure<'static, T>;
52
53use crate::__rt::WasmWord;
54
55/// Drop hook exported for compatibility with wasm-bindgen-generated code.
56///
57/// # Safety
58///
59/// This is an ABI entry point called by generated glue code. The arguments
60/// must be the encoded closure metadata expected by that glue.
61#[unsafe(no_mangle)]
62pub unsafe extern "C" fn __wbindgen_destroy_closure(a: WasmWord, b: WasmWord) {
63    let _ = (a, b);
64}
65
66impl<T: ?Sized> ScopedClosure<'static, T> {
67    /// Wrap a raw closure. Only for use by generated code.
68    pub(crate) fn wrap_encode_decode<FnPtr>(
69        encode_decode: impl Fn(&mut DecodedData, &mut EncodedData) -> Result<(), DecodeError> + 'static,
70    ) -> Self
71    where
72        CallbackKey<FnPtr>: BinaryEncode + EncodeTypeDef,
73    {
74        let key = insert_object(RustCallback::new_fn(encode_decode));
75        let value =
76            crate::__rt::wbg_cast::<CallbackKey<FnPtr>, crate::JsValue>(CallbackKey::new(key));
77        Self {
78            _phantom: core::marker::PhantomData,
79            callback: crate::closure::CallbackOwnership::Owned,
80            value,
81        }
82    }
83
84    /// Wrap a raw closure. Only for use by generated code.
85    pub(crate) fn wrap_encode_decode_mut<FnPtr>(
86        encode_decode: impl FnMut(&mut DecodedData, &mut EncodedData) -> Result<(), DecodeError>
87        + 'static,
88    ) -> Self
89    where
90        CallbackKey<FnPtr>: BinaryEncode + EncodeTypeDef,
91    {
92        let key = insert_object(RustCallback::new_fn_mut(encode_decode));
93        let value =
94            crate::__rt::wbg_cast::<CallbackKey<FnPtr>, crate::JsValue>(CallbackKey::new(key));
95        Self {
96            _phantom: core::marker::PhantomData,
97            callback: crate::closure::CallbackOwnership::Owned,
98            value,
99        }
100    }
101
102    /// Wrap a raw one-shot closure. Only for use by generated code.
103    pub(crate) fn wrap_once_encode_decode_mut<FnPtr>(
104        mut encode_decode: impl FnMut(&mut DecodedData, &mut EncodedData) -> Result<(), DecodeError>
105        + 'static,
106    ) -> Self
107    where
108        CallbackKey<FnPtr>: BinaryEncode + EncodeTypeDef,
109    {
110        let handle_cell = alloc::rc::Rc::new(core::cell::Cell::new(None));
111        let handle_for_callback = handle_cell.clone();
112        let key = insert_object(RustCallback::new_fn_mut(move |decoder, encoder| {
113            let result = encode_decode(decoder, encoder);
114            // Dispose the one-shot callback whether or not decoding succeeded.
115            if let Some(handle) = handle_for_callback.take() {
116                crate::batch::queue_rust_object_drop(handle);
117            }
118            result
119        }));
120        handle_cell.set(Some(key));
121        let value = crate::__rt::wbg_cast::<CallbackKey<FnPtr>, crate::JsValue>(
122            CallbackKey::new_with_policy(key, crate::encode::CallbackPolicy::JsOwnedOnce),
123        );
124        // Once-cells dispose themselves after the first call, but they still
125        // need Rust-side ownership while a `Closure` handle exists. Promise
126        // adapters store resolve/reject once-closures and rely on dropping the
127        // pair after the first completion to dispose the unused callback.
128        Self {
129            _phantom: core::marker::PhantomData,
130            callback: crate::closure::CallbackOwnership::Owned,
131            value,
132        }
133    }
134}
135
136impl<'a, T> ScopedClosure<'a, T>
137where
138    T: ?Sized + WasmClosure,
139{
140    /// Returns the JavaScript function value for this closure.
141    pub fn as_js_value(&self) -> &JsValue {
142        &self.value
143    }
144}
145
146impl<T: ?Sized> Drop for ScopedClosure<'_, T> {
147    fn drop(&mut self) {
148        if let crate::closure::CallbackOwnership::Owned = self.callback {
149            crate::batch::queue_js_dispose_rust_function(self.value.id());
150        }
151        // JsValue::drop runs after this (via field drop glue) and queues
152        // the heap-ref release.
153    }
154}
155
156impl<T: ?Sized> Unpin for ScopedClosure<'_, T> {}
157
158/// Marker for closures that are safe to invoke through panic-catching glue.
159#[doc(hidden)]
160pub trait MaybeUnwindSafe {}
161
162impl<T: ?Sized> MaybeUnwindSafe for T {}
163
164/// A trait for converting a Rust closure into the JS closure type `T`.
165#[doc(hidden)]
166pub trait IntoWasmClosure<T: ?Sized> {
167    fn into_closure(self) -> Closure<T>
168    where
169        Self: Sized,
170    {
171        unreachable!("unsized closure objects must be converted from Box<Self>")
172    }
173
174    fn into_closure_box(self: Box<Self>) -> Closure<T>;
175}
176
177/// A trait for converting a shared borrowed Rust closure into the JS closure type `T`.
178#[doc(hidden)]
179pub trait IntoWasmClosureRef<T: ?Sized> {
180    fn into_scoped_closure_ref<'a>(t: &'a Self) -> ScopedClosure<'a, T::Static>
181    where
182        T: WasmClosure;
183}
184
185/// A trait for converting a mutably borrowed Rust closure into the JS closure type `T`.
186#[doc(hidden)]
187pub trait IntoWasmClosureRefMut<T: ?Sized> {
188    fn into_scoped_closure_ref_mut<'a>(t: &'a mut Self) -> ScopedClosure<'a, T::Static>
189    where
190        T: WasmClosure;
191}
192
193/// A trait for converting an `FnOnce(A...) -> R` into a `Closure<dyn FnMut(A...) -> R>`.
194#[doc(hidden)]
195pub trait WasmClosureFnOnce<T: ?Sized, A, R>: Sized + 'static {
196    fn into_closure(self) -> Closure<T>;
197}
198
199/// A trait for converting an aborting `FnOnce(A...) -> R` into a closure.
200#[doc(hidden)]
201pub trait WasmClosureFnOnceAbort<T: ?Sized, A, R>: Sized + 'static {
202    fn into_closure(self) -> Closure<T>;
203}
204
205impl<T: ?Sized> AsRef<JsValue> for ScopedClosure<'_, T> {
206    fn as_ref(&self) -> &JsValue {
207        &self.value
208    }
209}
210
211impl<T> core::fmt::Debug for ScopedClosure<'_, T>
212where
213    T: ?Sized,
214{
215    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
216        f.debug_struct("Closure")
217            .field("value", &self.value)
218            .finish()
219    }
220}
221
222/// Upstream-compatible marker trait for closure signatures.
223#[doc(hidden)]
224pub trait WasmClosure {
225    /// The `'static` version of this closure type.
226    type Static: ?Sized;
227    /// The mutable version of this closure type.
228    type AsMut: ?Sized;
229}
230
231/// Internal trait for closure types that can be wrapped and passed to JavaScript.
232#[doc(hidden)]
233pub trait WryWasmClosure<M> {
234    /// Create a Closure from a boxed closure.
235    fn into_js_closure(boxed: Box<Self>) -> Closure<Self>;
236}
237
238impl<T> ScopedClosure<'static, T>
239where
240    T: ?Sized + WasmClosure,
241{
242    pub fn new<F>(t: F) -> Self
243    where
244        F: IntoWasmClosure<T> + MaybeUnwindSafe + 'static,
245    {
246        Self::own(t)
247    }
248
249    pub fn own<F>(t: F) -> Self
250    where
251        F: IntoWasmClosure<T> + MaybeUnwindSafe + 'static,
252    {
253        <F as IntoWasmClosure<T>>::into_closure(t)
254    }
255
256    pub fn own_aborting<F>(t: F) -> Self
257    where
258        F: IntoWasmClosure<T> + 'static,
259    {
260        <F as IntoWasmClosure<T>>::into_closure(t)
261    }
262
263    pub fn own_assert_unwind_safe<F>(t: F) -> Self
264    where
265        F: IntoWasmClosure<T> + 'static,
266    {
267        <F as IntoWasmClosure<T>>::into_closure(t)
268    }
269
270    pub fn wrap<F>(data: Box<F>) -> Self
271    where
272        F: IntoWasmClosure<T> + ?Sized + MaybeUnwindSafe,
273    {
274        <F as IntoWasmClosure<T>>::into_closure_box(data)
275    }
276
277    pub fn wrap_aborting<F>(data: Box<F>) -> Self
278    where
279        F: IntoWasmClosure<T> + ?Sized,
280    {
281        <F as IntoWasmClosure<T>>::into_closure_box(data)
282    }
283
284    pub fn wrap_assert_unwind_safe<F>(data: Box<F>) -> Self
285    where
286        F: IntoWasmClosure<T> + ?Sized,
287    {
288        <F as IntoWasmClosure<T>>::into_closure_box(data)
289    }
290
291    pub fn borrow<'a, F>(t: &'a F) -> ScopedClosure<'a, T::Static>
292    where
293        F: IntoWasmClosureRef<T> + MaybeUnwindSafe + ?Sized,
294    {
295        F::into_scoped_closure_ref(t)
296    }
297
298    pub fn borrow_aborting<'a, F>(t: &'a F) -> ScopedClosure<'a, T::Static>
299    where
300        F: IntoWasmClosureRef<T> + ?Sized,
301    {
302        F::into_scoped_closure_ref(t)
303    }
304
305    pub fn borrow_assert_unwind_safe<'a, F>(t: &'a F) -> ScopedClosure<'a, T::Static>
306    where
307        F: IntoWasmClosureRef<T> + ?Sized,
308    {
309        F::into_scoped_closure_ref(t)
310    }
311
312    pub fn borrow_mut<'a, F>(t: &'a mut F) -> ScopedClosure<'a, T::Static>
313    where
314        F: IntoWasmClosureRefMut<T> + MaybeUnwindSafe + ?Sized,
315    {
316        F::into_scoped_closure_ref_mut(t)
317    }
318
319    pub fn borrow_mut_aborting<'a, F>(t: &'a mut F) -> ScopedClosure<'a, T::Static>
320    where
321        F: IntoWasmClosureRefMut<T> + ?Sized,
322    {
323        F::into_scoped_closure_ref_mut(t)
324    }
325
326    pub fn borrow_mut_assert_unwind_safe<'a, F>(t: &'a mut F) -> ScopedClosure<'a, T::Static>
327    where
328        F: IntoWasmClosureRefMut<T> + ?Sized,
329    {
330        F::into_scoped_closure_ref_mut(t)
331    }
332
333    /// Create a `Closure` from a function that can only be called once.
334    ///
335    /// Since we have no way of enforcing that JS cannot attempt to call this
336    /// `FnOnce` more than once, this produces a `Closure<dyn FnMut(A...) -> R>`
337    /// that will panic if called more than once.
338    pub fn once<F, A, R>(fn_once: F) -> Self
339    where
340        F: WasmClosureFnOnce<T, A, R> + MaybeUnwindSafe,
341    {
342        <F as WasmClosureFnOnce<T, A, R>>::into_closure(fn_once)
343    }
344
345    pub fn once_aborting<F, A, R>(fn_once: F) -> Self
346    where
347        F: WasmClosureFnOnceAbort<T, A, R>,
348    {
349        <F as WasmClosureFnOnceAbort<T, A, R>>::into_closure(fn_once)
350    }
351
352    pub fn once_assert_unwind_safe<F, A, R>(fn_once: F) -> Self
353    where
354        F: WasmClosureFnOnceAbort<T, A, R>,
355    {
356        Self::once_aborting(fn_once)
357    }
358
359    /// Forgets the closure, leaking it.
360    pub fn forget(self) {
361        core::mem::forget(self);
362    }
363
364    /// Converts the `Closure` into a `JsValue`.
365    pub fn into_js_value(self) -> JsValue {
366        let value = core::mem::ManuallyDrop::new(self);
367        // Clone the value to get ownership without triggering drop
368        value.value.clone()
369    }
370
371    /// Create a `Closure` from a function that can only be called once,
372    /// and return the underlying `JsValue` directly.
373    ///
374    /// This is a convenience method that combines `once` and `into_js_value`.
375    pub fn once_into_js<F, A, R>(fn_once: F) -> JsValue
376    where
377        F: WasmClosureFnOnce<T, A, R> + MaybeUnwindSafe,
378    {
379        Self::once(fn_once).into_js_value()
380    }
381}