servo/components/script/task.rs
Josh Matthews 9e2ee0029a
script: Reduce usage of Trusted in Node::insert. (#37762)
These changes introduce a new kind of task that uses a variation of the
`task!` syntax. Existing `task!` usages create task structs that have a
`Send` bound, which requires the use of `Trusted<T>` to reference a DOM
object T inside of the task closure. The new syntax replaces the `Send`
bound with a `JSTraceable` bound, which requires explicit capture
clauses with types. This looks like:
```rust
task!(ScriptPrepare: {script: DomRoot<HTMLScriptElement>} |script| {
    script.prepare(CanGc::note());
}),
```

The capture clauses must list every value that will be referenced from
the closure's environment—these values are moved into fields in the
generated task structure, which allows them to be traced by the GC as
part of a generated JSTraceable implementation. Since the closure itself
is not a `move` closure, any attempts to reference values not explicitly
captured will generate a borrow checker error since the closure requires
the `'static` lifetime.

Testing: Existing WPT tests exercise these code paths. I also attempted
to write incorrect tasks that capture references or use values not
explicitly captured, and the compiler correctly errors out.
Fixes: part of #35517

Signed-off-by: Josh Matthews <josh@joshmatthews.net>
2025-07-15 01:11:12 +00:00

158 lines
4.1 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Machinery for [tasks](https://html.spec.whatwg.org/multipage/#concept-task).
use std::fmt;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
macro_rules! task {
($name:ident: |$($field:ident: $field_type:ty$(,)*)*| $body:tt) => {{
#[allow(non_camel_case_types)]
struct $name<F> {
$($field: $field_type,)*
task: F,
}
#[allow(unsafe_code)]
unsafe impl<F> crate::JSTraceable for $name<F> {
#[allow(unsafe_code)]
unsafe fn trace(&self, tracer: *mut ::js::jsapi::JSTracer) {
$(self.$field.trace(tracer);)*
// We cannot trace the actual task closure. This is safe because
// all referenced values from within the closure are either borrowed
// or moved into fields in the struct (and therefore traced).
}
}
impl<F> crate::task::NonSendTaskOnce for $name<F>
where
F: ::std::ops::FnOnce($($field_type,)*),
{
fn run_once(self) {
(self.task)($(self.$field,)*);
}
}
$name {
$($field,)*
task: |$($field: $field_type,)*| $body,
}
}};
($name:ident: move || $body:tt) => {{
#[allow(non_camel_case_types)]
struct $name<F>(F);
impl<F> crate::task::TaskOnce for $name<F>
where
F: ::std::ops::FnOnce() + Send,
{
fn name(&self) -> &'static str {
stringify!($name)
}
fn run_once(self) {
(self.0)();
}
}
$name(move || $body)
}};
}
/// A task that can be sent between threads and run.
/// The name method is for profiling purposes.
pub(crate) trait TaskOnce: Send {
fn name(&self) -> &'static str {
::std::any::type_name::<Self>()
}
fn run_once(self);
}
/// A task that must be run on the same thread it originated in.
pub(crate) trait NonSendTaskOnce: crate::JSTraceable {
fn run_once(self);
}
/// A boxed version of `TaskOnce`.
pub(crate) trait TaskBox: Send {
fn name(&self) -> &'static str;
fn run_box(self: Box<Self>);
}
/// A boxed version of `NonSendTaskOnce`.
pub(crate) trait NonSendTaskBox: crate::JSTraceable {
fn run_box(self: Box<Self>);
}
impl<T> NonSendTaskBox for T
where
T: NonSendTaskOnce,
{
fn run_box(self: Box<Self>) {
self.run_once()
}
}
impl<T> TaskBox for T
where
T: TaskOnce,
{
fn name(&self) -> &'static str {
TaskOnce::name(self)
}
fn run_box(self: Box<Self>) {
self.run_once()
}
}
impl fmt::Debug for dyn TaskBox {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_tuple(self.name())
.field(&format_args!("..."))
.finish()
}
}
/// Encapsulated state required to create cancellable tasks from non-script threads.
#[derive(Clone, Default, JSTraceable, MallocSizeOf)]
pub(crate) struct TaskCanceller {
#[ignore_malloc_size_of = "This is difficult, because only one of them should be measured"]
pub(crate) cancelled: Arc<AtomicBool>,
}
impl TaskCanceller {
/// Returns a wrapped `task` that will be cancelled if the `TaskCanceller` says so.
pub(crate) fn wrap_task<T>(&self, task: T) -> impl TaskOnce + use<T>
where
T: TaskOnce,
{
CancellableTask {
canceller: self.clone(),
inner: task,
}
}
pub(crate) fn cancelled(&self) -> bool {
self.cancelled.load(Ordering::SeqCst)
}
}
/// A task that can be cancelled by toggling a shared flag.
pub(crate) struct CancellableTask<T: TaskOnce> {
canceller: TaskCanceller,
inner: T,
}
impl<T: TaskOnce> TaskOnce for CancellableTask<T> {
fn name(&self) -> &'static str {
self.inner.name()
}
fn run_once(self) {
if !self.canceller.cancelled() {
self.inner.run_once()
}
}
}