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>
This commit is contained in:
Josh Matthews 2025-07-14 21:11:12 -04:00 committed by GitHub
parent f5b0165e54
commit 9e2ee0029a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 74 additions and 27 deletions

View file

@ -82,7 +82,6 @@ use crate::dom::bindings::inheritance::{
Castable, CharacterDataTypeId, ElementTypeId, EventTargetTypeId, HTMLElementTypeId, NodeTypeId,
SVGElementTypeId, SVGGraphicsElementTypeId, TextTypeId,
};
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::reflector::{DomObject, DomObjectWrap, reflect_dom_object_with_proto};
use crate::dom::bindings::root::{Dom, DomRoot, DomSlice, LayoutDom, MutNullableDom, ToLayout};
use crate::dom::bindings::str::{DOMString, USVString};
@ -2563,10 +2562,7 @@ impl Node {
for node in new_nodes {
// Step 11.1 For each shadow-including inclusive descendant inclusiveDescendant of node,
// in shadow-including tree order, append inclusiveDescendant to staticNodeList.
static_node_list.extend(
node.traverse_preorder(ShadowIncluding::Yes)
.map(|n| Trusted::new(&*n)),
);
static_node_list.extend(node.traverse_preorder(ShadowIncluding::Yes));
}
// We use a delayed task for this step to work around an awkward interaction between
@ -2579,13 +2575,15 @@ impl Node {
// 2) post_connection_steps from Node::insert,
// we use a delayed task that will run as soon as Node::insert removes its
// script/layout blocker.
parent_document.add_delayed_task(task!(PostConnectionSteps: move || {
// Step 12. For each node of staticNodeList, if node is connected, then run the
// post-connection steps with node.
for node in static_node_list.iter().map(Trusted::root).filter(|n| n.is_connected()) {
vtable_for(&node).post_connection_steps();
}
}));
parent_document.add_delayed_task(
task!(PostConnectionSteps: |static_node_list: Vec<DomRoot<Node>>| {
// Step 12. For each node of staticNodeList, if node is connected, then run the
// post-connection steps with node.
for node in static_node_list.iter().filter(|n| n.is_connected()) {
vtable_for(node).post_connection_steps();
}
}),
);
parent_document.remove_script_and_layout_blocker();
from_document.remove_script_and_layout_blocker();