Skip to main content

script_bindings/
tasks.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Machinery for [tasks](https://html.spec.whatwg.org/multipage/#concept-task).
6
7use std::fmt;
8use std::sync::Arc;
9use std::sync::atomic::{AtomicBool, Ordering};
10
11#[macro_export]
12macro_rules! task {
13    ($name:ident: |$($field:ident: $field_type:ty$(,)*)*| $body:tt) => {{
14        #[allow(non_camel_case_types)]
15        struct $name<F> {
16            $($field: $field_type,)*
17            task: F,
18        }
19        #[expect(unsafe_code)]
20        unsafe impl<F> js::gc::Traceable for $name<F> {
21            #[expect(unsafe_code)]
22            unsafe fn trace(&self, tracer: *mut ::js::jsapi::JSTracer) {
23                unsafe { $(self.$field.trace(tracer);)* }
24                // We cannot trace the actual task closure. This is safe because
25                // all referenced values from within the closure are either borrowed
26                // or moved into fields in the struct (and therefore traced).
27            }
28        }
29        impl<F> $crate::task::NonSendTaskOnce for $name<F>
30        where
31            F: ::std::ops::FnOnce($($field_type,)*),
32        {
33            fn run_once(self, _cx: &mut js::context::JSContext) {
34                (self.task)($(self.$field,)*);
35            }
36        }
37        $name {
38            $($field,)*
39            task: |$($field: $field_type,)*| $body,
40        }
41    }};
42
43    ($name:ident: move || $body:tt) => {{
44        #[allow(non_camel_case_types)]
45        struct $name<F>(F);
46        impl<F> $crate::tasks::TaskOnce for $name<F>
47        where
48            F: ::std::ops::FnOnce() + Send,
49        {
50            fn name(&self) -> &'static str {
51                stringify!($name)
52            }
53
54            fn run_once(self, _cx: &mut js::context::JSContext) {
55                (self.0)();
56            }
57        }
58        $name(move || $body)
59    }};
60
61    ($name:ident: |$cx: ident $(, $field:ident: $field_type:ty)*| $body:tt) => {{
62        #[allow(non_camel_case_types)]
63        struct $name<F> {
64            $($field: $field_type,)*
65            task: F,
66        }
67        #[expect(unsafe_code)]
68        unsafe impl<F> js::gc::Traceable for $name<F> {
69            #[expect(unsafe_code)]
70            unsafe fn trace(&self, tracer: *mut ::js::jsapi::JSTracer) {
71                unsafe { $(self.$field.trace(tracer);)* }
72                // We cannot trace the actual task closure. This is safe because
73                // all referenced values from within the closure are either borrowed
74                // or moved into fields in the struct (and therefore traced).
75            }
76        }
77        impl<F> $crate::tasks::NonSendTaskOnce for $name<F>
78        where
79            F: ::std::ops::FnOnce(&mut js::context::JSContext, $($field_type,)*),
80        {
81            fn run_once(self, cx: &mut js::context::JSContext) {
82                (self.task)(cx, $(self.$field,)*);
83            }
84        }
85        $name {
86            $($field,)*
87            task: |$cx: &mut js::context::JSContext, $($field: $field_type,)*| $body,
88        }
89    }};
90
91    ($name:ident: move |$cx: ident| $body:tt) => {{
92        #[allow(non_camel_case_types)]
93        struct $name<F>(F);
94        impl<F> $crate::tasks::TaskOnce for $name<F>
95        where
96            F: ::std::ops::FnOnce(&mut js::context::JSContext) + Send,
97        {
98            fn name(&self) -> &'static str {
99                stringify!($name)
100            }
101
102            fn run_once(self, cx: &mut js::context::JSContext) {
103                (self.0)(cx);
104            }
105        }
106        $name(move |$cx: &mut js::context::JSContext| $body)
107    }};
108}
109
110/// A task that can be sent between threads and run.
111/// The name method is for profiling purposes.
112pub trait TaskOnce: Send {
113    fn name(&self) -> &'static str {
114        ::std::any::type_name::<Self>()
115    }
116
117    fn run_once(self, cx: &mut js::context::JSContext);
118}
119
120/// A task that must be run on the same thread it originated in.
121pub trait NonSendTaskOnce: crate::JSTraceable {
122    fn run_once(self, cx: &mut js::context::JSContext);
123}
124
125/// A boxed version of `TaskOnce`.
126pub trait TaskBox: Send {
127    fn name(&self) -> &'static str;
128
129    fn run_box(self: Box<Self>, cx: &mut js::context::JSContext);
130}
131
132/// A boxed version of `NonSendTaskOnce`.
133pub trait NonSendTaskBox: crate::JSTraceable {
134    fn run_box(self: Box<Self>, cx: &mut js::context::JSContext);
135}
136
137impl<T> NonSendTaskBox for T
138where
139    T: NonSendTaskOnce,
140{
141    fn run_box(self: Box<Self>, cx: &mut js::context::JSContext) {
142        self.run_once(cx)
143    }
144}
145
146impl<T> TaskBox for T
147where
148    T: TaskOnce,
149{
150    fn name(&self) -> &'static str {
151        TaskOnce::name(self)
152    }
153
154    fn run_box(self: Box<Self>, cx: &mut js::context::JSContext) {
155        self.run_once(cx)
156    }
157}
158
159impl fmt::Debug for dyn TaskBox {
160    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
161        fmt.debug_tuple(self.name())
162            .field(&format_args!("..."))
163            .finish()
164    }
165}
166
167/// Encapsulated state required to create cancellable tasks from non-script threads.
168#[derive(Clone, Default, JSTraceable, MallocSizeOf)]
169pub struct TaskCanceller {
170    #[conditional_malloc_size_of]
171    pub cancelled: Arc<AtomicBool>,
172}
173
174impl TaskCanceller {
175    /// Returns a wrapped `task` that will be cancelled if the `TaskCanceller` says so.
176    pub fn wrap_task<T>(&self, task: T) -> impl TaskOnce + use<T>
177    where
178        T: TaskOnce,
179    {
180        CancellableTask {
181            canceller: self.clone(),
182            inner: task,
183        }
184    }
185
186    pub fn cancelled(&self) -> bool {
187        self.cancelled.load(Ordering::SeqCst)
188    }
189}
190
191/// A task that can be cancelled by toggling a shared flag.
192pub(crate) struct CancellableTask<T: TaskOnce> {
193    canceller: TaskCanceller,
194    inner: T,
195}
196
197impl<T: TaskOnce> TaskOnce for CancellableTask<T> {
198    fn name(&self) -> &'static str {
199        self.inner.name()
200    }
201
202    fn run_once(self, cx: &mut js::context::JSContext) {
203        if !self.canceller.cancelled() {
204            self.inner.run_once(cx)
205        }
206    }
207}