medea_jason/api/dart/
mod.rs

1//! External [`Jason`] API exposing functions that can be called via FFI and
2//! designed to be integrated into a [Flutter] plugin.
3//!
4//! [Flutter]: https://flutter.dev
5
6// TODO: Improve documentation in this module.
7#![expect(
8    clippy::as_conversions,
9    clippy::missing_safety_doc,
10    clippy::missing_panics_doc,
11    clippy::undocumented_unsafe_blocks,
12    missing_docs,
13    reason = "needs refactoring"
14)]
15
16pub mod api;
17pub mod err;
18
19use std::{
20    cell::Cell,
21    ffi::{CString, c_void},
22    marker::PhantomData,
23    panic, ptr,
24};
25
26use dart_sys::{_Dart_Handle, Dart_Handle};
27use derive_more::with_trait::Display;
28use flutter_rust_bridge::{
29    JoinHandle,
30    for_generated::{
31        BaseAsyncRuntime, NoOpErrorListener, SimpleExecutor, SimpleHandler,
32        SimpleThreadPool,
33    },
34};
35use libc::c_char;
36
37pub use self::{
38    api::{
39        ConnectionHandle, Jason, LocalMediaTrack, MediaManagerHandle,
40        ReconnectHandle, RemoteMediaTrack, RoomCloseReason, RoomHandle,
41    },
42    err::DartError as Error,
43};
44pub use crate::media::MediaDirection;
45use crate::{
46    api::{api::ForeignClass, dart::err::new_panic_error},
47    media::{
48        FacingMode, MediaDeviceKind, MediaKind, MediaSourceKind,
49        NoiseSuppressionLevel,
50    },
51    platform::utils::{
52        c_str_into_string, dart_api, free_dart_native_string,
53        handle::DartHandle, string_into_c_str,
54    },
55};
56
57thread_local! {
58    /// Used to create [`DartOpaque`]s on the Rust side.
59    pub static DART_HANDLER_PORT: Cell<Option<i64>> = Cell::default();
60}
61
62/// Wraps the provided function to catch all the Rust panics and propagate them
63/// to the Dart side.
64pub fn propagate_panic<T>(f: impl FnOnce() -> T) -> T {
65    panic::catch_unwind(panic::AssertUnwindSafe(f)).unwrap_or_else(|_| {
66        let exception = unsafe { new_panic_error() };
67        unsafe {
68            dart_api::propagate_error(exception);
69        }
70        unreachable!("`Dart_PropagateError` should do early return")
71    })
72}
73
74/// Marker indicating a C-style enum which can be converted from number
75/// primitives.
76pub trait PrimitiveEnum: TryFrom<i64> {}
77
78/// Owner of some allocated memory.
79#[derive(Clone, Copy, Debug)]
80#[repr(u8)]
81pub enum MemoryOwner {
82    /// Memory is allocated on Rust side.
83    Rust = 0,
84
85    /// Memory is allocated on Dart side.
86    Dart = 1,
87}
88
89/// Type-erased value that can be transferred via FFI boundaries to/from Dart.
90#[derive(Debug)]
91#[repr(C, u8)]
92pub enum DartValue {
93    /// No value. It can mean `()`, `void` or [`Option::None`] basing on the
94    /// contexts.
95    None,
96
97    /// Pointer to a [`Box`]ed Rust object.
98    Ptr(ptr::NonNull<c_void>),
99
100    /// Pointer to a [`Dart_Handle`] of some Dart object.
101    Handle(ptr::NonNull<Dart_Handle>),
102
103    /// Native string.
104    String(ptr::NonNull<c_char>, MemoryOwner),
105
106    /// Integer value.
107    ///
108    /// This can also be used to transfer boolean values and C-like enums.
109    Int(i64),
110
111    /// Float value.
112    Float(f64),
113
114    /// Boolean value.
115    Bool(bool),
116}
117
118impl Drop for DartValue {
119    fn drop(&mut self) {
120        match self {
121            Self::Float(_)
122            | Self::Bool(_)
123            | Self::Int(_)
124            | Self::Ptr(_)
125            | Self::Handle(_)
126            | Self::None => {}
127            Self::String(ptr, MemoryOwner::Dart) => unsafe {
128                free_dart_native_string(*ptr);
129            },
130            Self::String(ptr, MemoryOwner::Rust) => unsafe {
131                drop(CString::from_raw(ptr.as_ptr()));
132            },
133        }
134    }
135}
136
137impl From<()> for DartValue {
138    fn from((): ()) -> Self {
139        Self::None
140    }
141}
142
143impl<T: ForeignClass> From<T> for DartValue {
144    fn from(val: T) -> Self {
145        Self::Ptr(val.into_ptr().cast())
146    }
147}
148
149impl<T: ForeignClass> From<Option<T>> for DartValue {
150    fn from(val: Option<T>) -> Self {
151        val.map_or(Self::None, |t| Self::from(t))
152    }
153}
154
155impl From<String> for DartValue {
156    fn from(string: String) -> Self {
157        Self::String(string_into_c_str(string), MemoryOwner::Rust)
158    }
159}
160
161impl From<Option<String>> for DartValue {
162    fn from(val: Option<String>) -> Self {
163        val.map_or(Self::None, Self::from)
164    }
165}
166
167impl From<Option<i64>> for DartValue {
168    fn from(val: Option<i64>) -> Self {
169        val.map_or(Self::None, Self::from)
170    }
171}
172
173impl From<ptr::NonNull<Dart_Handle>> for DartValue {
174    fn from(handle: ptr::NonNull<*mut _Dart_Handle>) -> Self {
175        Self::Handle(handle)
176    }
177}
178
179impl From<Option<ptr::NonNull<Dart_Handle>>> for DartValue {
180    fn from(val: Option<ptr::NonNull<Dart_Handle>>) -> Self {
181        val.map_or(Self::None, Self::from)
182    }
183}
184
185impl From<Dart_Handle> for DartValue {
186    fn from(handle: Dart_Handle) -> Self {
187        Self::Handle(ptr::NonNull::from(Box::leak(Box::new(handle))))
188    }
189}
190
191impl From<Option<Dart_Handle>> for DartValue {
192    fn from(val: Option<Dart_Handle>) -> Self {
193        val.map_or(Self::None, Self::from)
194    }
195}
196
197impl From<Error> for DartValue {
198    fn from(err: Error) -> Self {
199        Self::Handle(err.into())
200    }
201}
202
203impl From<Option<Error>> for DartValue {
204    fn from(val: Option<Error>) -> Self {
205        val.map_or(Self::None, Self::from)
206    }
207}
208
209impl From<MediaDirection> for DartValue {
210    fn from(val: MediaDirection) -> Self {
211        Self::from(val as u8)
212    }
213}
214
215impl From<bool> for DartValue {
216    fn from(val: bool) -> Self {
217        Self::Bool(val)
218    }
219}
220
221impl From<f32> for DartValue {
222    fn from(val: f32) -> Self {
223        Self::Float(f64::from(val))
224    }
225}
226
227impl From<f64> for DartValue {
228    fn from(val: f64) -> Self {
229        Self::Float(val)
230    }
231}
232
233/// Implements [`From`] types that can by casted to `i64` for the [`DartValue`].
234/// Should be called for all the integer types fitting in `2^63`.
235macro_rules! impl_from_num_for_dart_value {
236    ($arg:ty) => {
237        impl From<$arg> for DartValue {
238            fn from(val: $arg) -> Self {
239                DartValue::Int(i64::from(val))
240            }
241        }
242    };
243}
244
245impl_from_num_for_dart_value!(i8);
246impl_from_num_for_dart_value!(i16);
247impl_from_num_for_dart_value!(i32);
248impl_from_num_for_dart_value!(i64);
249impl_from_num_for_dart_value!(u8);
250impl_from_num_for_dart_value!(u16);
251impl_from_num_for_dart_value!(u32);
252
253/// [`DartValue`] marked by a Rust type.
254///
255/// There are no type parameter specific functionality, it serves purely as a
256/// marker in type signatures.
257#[derive(Debug)]
258#[repr(transparent)]
259pub struct DartValueArg<T>(DartValue, PhantomData<*const T>);
260
261impl<F, T> From<F> for DartValueArg<T>
262where
263    DartValue: From<F>,
264{
265    fn from(from: F) -> Self {
266        Self(DartValue::from(from), PhantomData)
267    }
268}
269
270impl<T> TryFrom<DartValueArg<T>> for ptr::NonNull<c_void> {
271    type Error = DartValueCastError;
272
273    fn try_from(value: DartValueArg<T>) -> Result<Self, Self::Error> {
274        match value.0 {
275            DartValue::Ptr(ptr) => Ok(ptr),
276            DartValue::None
277            | DartValue::Handle(_)
278            | DartValue::String(_, _)
279            | DartValue::Int(_)
280            | DartValue::Bool(_)
281            | DartValue::Float(_) => Err(DartValueCastError {
282                expectation: "NonNull<c_void>",
283                value: value.0,
284            }),
285        }
286    }
287}
288
289impl<T> TryFrom<DartValueArg<T>> for Option<ptr::NonNull<c_void>> {
290    type Error = DartValueCastError;
291
292    fn try_from(value: DartValueArg<T>) -> Result<Self, Self::Error> {
293        match value.0 {
294            DartValue::None => Ok(None),
295            DartValue::Ptr(ptr) => Ok(Some(ptr)),
296            DartValue::Handle(_)
297            | DartValue::String(_, _)
298            | DartValue::Float(_)
299            | DartValue::Bool(_)
300            | DartValue::Int(_) => Err(DartValueCastError {
301                expectation: "Option<NonNull<c_void>>",
302                value: value.0,
303            }),
304        }
305    }
306}
307
308impl TryFrom<DartValueArg<Self>> for String {
309    type Error = DartValueCastError;
310
311    fn try_from(value: DartValueArg<Self>) -> Result<Self, Self::Error> {
312        match value.0 {
313            DartValue::String(c_str, _) => unsafe {
314                Ok(c_str_into_string(c_str))
315            },
316            DartValue::None
317            | DartValue::Ptr(_)
318            | DartValue::Handle(_)
319            | DartValue::Float(_)
320            | DartValue::Bool(_)
321            | DartValue::Int(_) => Err(DartValueCastError {
322                expectation: "String",
323                value: value.0,
324            }),
325        }
326    }
327}
328
329impl TryFrom<DartValueArg<()>> for () {
330    type Error = DartValueCastError;
331
332    fn try_from(value: DartValueArg<()>) -> Result<Self, Self::Error> {
333        match value.0 {
334            DartValue::None => Ok(()),
335            DartValue::Ptr(_)
336            | DartValue::Handle(_)
337            | DartValue::String(_, _)
338            | DartValue::Float(_)
339            | DartValue::Bool(_)
340            | DartValue::Int(_) => {
341                Err(DartValueCastError { expectation: "()", value: value.0 })
342            }
343        }
344    }
345}
346
347impl TryFrom<DartValueArg<Self>> for Option<DartHandle> {
348    type Error = DartValueCastError;
349
350    fn try_from(value: DartValueArg<Self>) -> Result<Self, Self::Error> {
351        match value.0 {
352            DartValue::None => Ok(None),
353            DartValue::Handle(handle) => {
354                let handle = unsafe { *handle.as_ptr() };
355                Ok(Some(unsafe { DartHandle::new(handle) }))
356            }
357            DartValue::Ptr(_)
358            | DartValue::Bool(_)
359            | DartValue::Float(_)
360            | DartValue::String(_, _)
361            | DartValue::Int(_) => Err(DartValueCastError {
362                expectation: "Option<DartHandle>",
363                value: value.0,
364            }),
365        }
366    }
367}
368
369impl TryFrom<DartValueArg<Self>> for Option<String> {
370    type Error = DartValueCastError;
371
372    fn try_from(value: DartValueArg<Self>) -> Result<Self, Self::Error> {
373        match value.0 {
374            DartValue::None => Ok(None),
375            DartValue::String(c_str, _) => unsafe {
376                Ok(Some(c_str_into_string(c_str)))
377            },
378            DartValue::Ptr(_)
379            | DartValue::Bool(_)
380            | DartValue::Float(_)
381            | DartValue::Handle(_)
382            | DartValue::Int(_) => Err(DartValueCastError {
383                expectation: "Option<String>",
384                value: value.0,
385            }),
386        }
387    }
388}
389
390impl<T> TryFrom<DartValueArg<T>> for Dart_Handle {
391    type Error = DartValueCastError;
392
393    fn try_from(value: DartValueArg<T>) -> Result<Self, Self::Error> {
394        match value.0 {
395            DartValue::Handle(c_ptr) => Ok(unsafe { unbox_dart_handle(c_ptr) }),
396            DartValue::None
397            | DartValue::Ptr(_)
398            | DartValue::String(_, _)
399            | DartValue::Float(_)
400            | DartValue::Bool(_)
401            | DartValue::Int(_) => Err(DartValueCastError {
402                expectation: "Dart_Handle",
403                value: value.0,
404            }),
405        }
406    }
407}
408
409impl TryFrom<DartValueArg<Self>> for DartHandle {
410    type Error = DartValueCastError;
411
412    fn try_from(value: DartValueArg<Self>) -> Result<Self, Self::Error> {
413        match value.0 {
414            DartValue::Handle(handle) => {
415                let handle = unsafe { unbox_dart_handle(handle) };
416                Ok(unsafe { Self::new(handle) })
417            }
418            DartValue::None
419            | DartValue::Ptr(_)
420            | DartValue::String(_, _)
421            | DartValue::Float(_)
422            | DartValue::Bool(_)
423            | DartValue::Int(_) => Err(DartValueCastError {
424                expectation: "DartHandle",
425                value: value.0,
426            }),
427        }
428    }
429}
430
431impl<T> TryFrom<DartValueArg<T>> for ptr::NonNull<Dart_Handle> {
432    type Error = DartValueCastError;
433
434    fn try_from(value: DartValueArg<T>) -> Result<Self, Self::Error> {
435        match value.0 {
436            DartValue::Handle(c_str) => Ok(c_str),
437            DartValue::None
438            | DartValue::Ptr(_)
439            | DartValue::String(_, _)
440            | DartValue::Float(_)
441            | DartValue::Bool(_)
442            | DartValue::Int(_) => Err(DartValueCastError {
443                expectation: "NonNull<Dart_Handle>",
444                value: value.0,
445            }),
446        }
447    }
448}
449
450impl<T> TryFrom<DartValueArg<T>> for Option<ptr::NonNull<Dart_Handle>> {
451    type Error = DartValueCastError;
452
453    fn try_from(value: DartValueArg<T>) -> Result<Self, Self::Error> {
454        match value.0 {
455            DartValue::None => Ok(None),
456            DartValue::Handle(c_str) => Ok(Some(c_str)),
457            DartValue::Ptr(_)
458            | DartValue::Bool(_)
459            | DartValue::Float(_)
460            | DartValue::String(_, _)
461            | DartValue::Int(_) => Err(DartValueCastError {
462                expectation: "Option<NonNull<Dart_Handle>>",
463                value: value.0,
464            }),
465        }
466    }
467}
468
469/// Helper macro implementing [`TryFrom`]`<`[`DartValueArg`]`>` for primitive
470/// types.
471macro_rules! impl_primitive_dart_value_try_from {
472    ($arg:ty) => {
473        impl TryFrom<DartValueArg<Self>> for $arg {
474            type Error = DartValueCastError;
475
476            fn try_from(
477                value: DartValueArg<Self>,
478            ) -> Result<Self, Self::Error> {
479                match value.0 {
480                    DartValue::Int(num) => {
481                        Ok(Self::try_from(num).map_err(
482                            |_| DartValueCastError {
483                                expectation: stringify!($arg),
484                                value: value.0,
485                            }
486                        )?)
487                    }
488                    _ => Err(DartValueCastError {
489                        expectation: stringify!($arg),
490                        value: value.0,
491                    }),
492                }
493            }
494        }
495
496        impl TryFrom<DartValueArg<Self>> for Option<$arg> {
497            type Error = DartValueCastError;
498
499            fn try_from(
500                value: DartValueArg<Self>
501            ) -> Result<Self, Self::Error> {
502                match value.0 {
503                    DartValue::None => Ok(None),
504                    DartValue::Int(num) => {
505                        Ok(Some(<$arg>::try_from(num).map_err(
506                            |_| DartValueCastError {
507                                expectation: concat!(
508                                    "Option<",
509                                    stringify!($arg),
510                                    ">"
511                                ),
512                                value: value.0,
513                            }
514                        )?))
515                    }
516                    _ => Err(DartValueCastError {
517                        expectation: concat!("Option<", stringify!($arg), ">"),
518                        value: value.0,
519                    }),
520                }
521            }
522        }
523    };
524    ($($arg:ty),+) => {
525        $(impl_primitive_dart_value_try_from!($arg);)+
526    }
527}
528
529impl_primitive_dart_value_try_from![i8, i16, i32, i64, u8, u16, u32];
530
531impl<T: PrimitiveEnum> TryFrom<DartValueArg<T>> for i64 {
532    type Error = DartValueCastError;
533
534    fn try_from(value: DartValueArg<T>) -> Result<Self, Self::Error> {
535        match value.0 {
536            DartValue::Int(num) => Ok(num),
537            DartValue::None
538            | DartValue::Ptr(_)
539            | DartValue::Handle(_)
540            | DartValue::Float(_)
541            | DartValue::Bool(_)
542            | DartValue::String(_, _) => {
543                Err(DartValueCastError { expectation: "i64", value: value.0 })
544            }
545        }
546    }
547}
548
549impl<T: PrimitiveEnum> TryFrom<DartValueArg<Self>> for Option<T> {
550    type Error = DartValueCastError;
551
552    fn try_from(value: DartValueArg<Self>) -> Result<Self, Self::Error> {
553        match value.0 {
554            DartValue::None => Ok(None),
555            DartValue::Int(num) => match T::try_from(num) {
556                Ok(variant) => Ok(Some(variant)),
557                Err(_) => Err(DartValueCastError {
558                    expectation: "Option<i64>",
559                    value: value.0,
560                }),
561            },
562            DartValue::Ptr(_)
563            | DartValue::Float(_)
564            | DartValue::Bool(_)
565            | DartValue::Handle(_)
566            | DartValue::String(_, _) => Err(DartValueCastError {
567                expectation: "Option<i64>",
568                value: value.0,
569            }),
570        }
571    }
572}
573
574impl TryFrom<DartValueArg<Self>> for Option<f64> {
575    type Error = DartValueCastError;
576
577    fn try_from(value: DartValueArg<Self>) -> Result<Self, Self::Error> {
578        match value.0 {
579            DartValue::None => Ok(None),
580            DartValue::Float(num) => Ok(Some(num)),
581            DartValue::Ptr(_)
582            | DartValue::Handle(_)
583            | DartValue::String(..)
584            | DartValue::Int(_)
585            | DartValue::Bool(_) => Err(DartValueCastError {
586                expectation: "Option<f64>",
587                value: value.0,
588            }),
589        }
590    }
591}
592
593impl TryFrom<DartValueArg<Self>> for Option<bool> {
594    type Error = DartValueCastError;
595
596    fn try_from(value: DartValueArg<Self>) -> Result<Self, Self::Error> {
597        match value.0 {
598            DartValue::None => Ok(None),
599            DartValue::Bool(num) => Ok(Some(num)),
600            DartValue::Ptr(_)
601            | DartValue::Handle(_)
602            | DartValue::String(..)
603            | DartValue::Int(_)
604            | DartValue::Float(_) => Err(DartValueCastError {
605                expectation: "Option<bool>",
606                value: value.0,
607            }),
608        }
609    }
610}
611
612impl TryFrom<DartValueArg<Self>> for bool {
613    type Error = DartValueCastError;
614
615    fn try_from(value: DartValueArg<Self>) -> Result<Self, Self::Error> {
616        match value.0 {
617            DartValue::Bool(num) => Ok(num),
618            DartValue::Ptr(..)
619            | DartValue::None
620            | DartValue::Handle(..)
621            | DartValue::String(..)
622            | DartValue::Int(..)
623            | DartValue::Float(..) => {
624                Err(DartValueCastError { expectation: "bool", value: value.0 })
625            }
626        }
627    }
628}
629
630/// Error of converting a [`DartValue`] to the concrete type.
631#[derive(Debug, Display)]
632#[display("expected `{expectation}`, but got: `{value:?}`")]
633pub struct DartValueCastError {
634    /// Expected type description. Like a [`String`] or an `Option<i64>`.
635    expectation: &'static str,
636
637    /// [`DartValue`] that cannot be casted.
638    value: DartValue,
639}
640
641impl PrimitiveEnum for MediaSourceKind {}
642impl PrimitiveEnum for FacingMode {}
643impl PrimitiveEnum for NoiseSuppressionLevel {}
644impl PrimitiveEnum for MediaDirection {}
645
646impl TryFrom<i64> for MediaSourceKind {
647    type Error = i64;
648
649    fn try_from(value: i64) -> Result<Self, Self::Error> {
650        match value {
651            0 => Ok(Self::Device),
652            1 => Ok(Self::Display),
653            _ => Err(value),
654        }
655    }
656}
657
658impl TryFrom<i64> for FacingMode {
659    type Error = i64;
660
661    fn try_from(value: i64) -> Result<Self, Self::Error> {
662        match value {
663            0 => Ok(Self::User),
664            1 => Ok(Self::Environment),
665            2 => Ok(Self::Left),
666            3 => Ok(Self::Right),
667            _ => Err(value),
668        }
669    }
670}
671
672impl TryFrom<i64> for NoiseSuppressionLevel {
673    type Error = i64;
674
675    fn try_from(value: i64) -> Result<Self, Self::Error> {
676        match value {
677            0 => Ok(Self::Low),
678            1 => Ok(Self::Moderate),
679            2 => Ok(Self::High),
680            3 => Ok(Self::VeryHigh),
681            _ => Err(value),
682        }
683    }
684}
685
686impl TryFrom<i64> for MediaKind {
687    type Error = i64;
688
689    fn try_from(value: i64) -> Result<Self, Self::Error> {
690        match value {
691            0 => Ok(Self::Audio),
692            1 => Ok(Self::Video),
693            _ => Err(value),
694        }
695    }
696}
697
698impl TryFrom<i64> for MediaDeviceKind {
699    type Error = i64;
700
701    fn try_from(value: i64) -> Result<Self, Self::Error> {
702        match value {
703            0 => Ok(Self::AudioInput),
704            1 => Ok(Self::VideoInput),
705            2 => Ok(Self::AudioOutput),
706            _ => Err(value),
707        }
708    }
709}
710
711impl TryFrom<i64> for MediaDirection {
712    type Error = i64;
713
714    fn try_from(value: i64) -> Result<Self, Self::Error> {
715        match value {
716            0 => Ok(Self::SendRecv),
717            1 => Ok(Self::SendOnly),
718            2 => Ok(Self::RecvOnly),
719            3 => Ok(Self::Inactive),
720            _ => Err(value),
721        }
722    }
723}
724
725/// Returns a [`Dart_Handle`] dereferenced from the provided pointer.
726#[unsafe(no_mangle)]
727pub unsafe extern "C" fn unbox_dart_handle(
728    val: ptr::NonNull<Dart_Handle>,
729) -> Dart_Handle {
730    unsafe { *val.as_ptr() }
731}
732
733/// Frees the provided [`ptr::NonNull`] pointer to a [`Dart_Handle`].
734#[unsafe(no_mangle)]
735pub unsafe extern "C" fn free_boxed_dart_handle(
736    val: ptr::NonNull<Dart_Handle>,
737) {
738    let handle = unsafe { Box::from_raw(val.as_ptr()) };
739    unsafe {
740        dart_api::delete_persistent_handle(*handle);
741    }
742}
743
744/// Returns a pointer to a boxed [`Dart_Handle`] created from the provided
745/// [`Dart_Handle`].
746#[unsafe(no_mangle)]
747pub unsafe extern "C" fn box_dart_handle(
748    val: Dart_Handle,
749) -> ptr::NonNull<Dart_Handle> {
750    let persisted = unsafe { dart_api::new_persistent_handle(val) };
751    ptr::NonNull::from(Box::leak(Box::new(persisted)))
752}
753
754/// Returns a boxed pointer to the provided [`DartValue`].
755#[unsafe(no_mangle)]
756pub unsafe extern "C" fn box_foreign_value(
757    val: DartValue,
758) -> ptr::NonNull<DartValue> {
759    ptr::NonNull::from(Box::leak(Box::new(val)))
760}
761
762/// [`SimpleHandler`] that uses [`NoOpErrorListener`],
763/// [`UnreachableAsyncRuntime`] and [`SimpleThreadPool`] with no threads.
764pub type FrbHandler = SimpleHandler<
765    SimpleExecutor<
766        NoOpErrorListener,
767        SimpleThreadPool,
768        UnreachableAsyncRuntime,
769    >,
770    NoOpErrorListener,
771>;
772
773/// Creates a new [`FrbHandler`].
774#[must_use]
775pub fn new_frb_handler() -> FrbHandler {
776    SimpleHandler::new(
777        SimpleExecutor::new(
778            NoOpErrorListener,
779            SimpleThreadPool,
780            UnreachableAsyncRuntime,
781        ),
782        NoOpErrorListener,
783    )
784}
785
786/// [`BaseAsyncRuntime`] that panics on use.
787#[derive(Debug, Copy, Clone)]
788pub struct UnreachableAsyncRuntime;
789
790impl BaseAsyncRuntime for UnreachableAsyncRuntime {
791    fn spawn<F>(&self, _: F) -> JoinHandle<F::Output>
792    where
793        F: Future<Output: Send + 'static> + Send + 'static,
794    {
795        // TODO: We don't need async runtime for `flutter_rust_bridge` but we
796        //       must keep "rust-async" Cargo feature enabled so we can use
797        //       "dart-opaque" Cargo feature. This should be fixed in frb, see:
798        //       https://github.com/fzyzcjy/flutter_rust_bridge/issues/2253
799        unreachable!("no async runtime available")
800    }
801}
802
803#[cfg(feature = "mockable")]
804#[expect(clippy::missing_docs_in_private_items, reason = "for testing only")]
805mod dart_value_extern_tests_helpers {
806    use super::propagate_panic;
807    use crate::platform::set_panic_hook;
808
809    #[unsafe(no_mangle)]
810    pub unsafe extern "C" fn fire_panic() {
811        set_panic_hook();
812        propagate_panic(|| panic!("Panicking"));
813    }
814}