1use std::cell::{Cell, Ref, RefCell};
6
7use dom_struct::dom_struct;
8use html5ever::{LocalName, Prefix, local_name, ns};
9use js::context::{JSContext, NoGC};
10use js::gc::RootedVec;
11use js::rust::HandleObject;
12use script_bindings::codegen::InheritTypes::{CharacterDataTypeId, NodeTypeId};
13
14use crate::dom::bindings::codegen::Bindings::HTMLSlotElementBinding::{
15 AssignedNodesOptions, HTMLSlotElementMethods,
16};
17use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods};
18use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods;
19use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{
20 ShadowRootMode, SlotAssignmentMode,
21};
22use crate::dom::bindings::codegen::UnionTypes::ElementOrText;
23use crate::dom::bindings::inheritance::Castable;
24use crate::dom::bindings::root::{Dom, DomRoot};
25use crate::dom::bindings::str::DOMString;
26use crate::dom::document::Document;
27use crate::dom::element::attributes::storage::AttrRef;
28use crate::dom::element::{AttributeMutation, Element};
29use crate::dom::html::htmlelement::HTMLElement;
30use crate::dom::node::virtualmethods::VirtualMethods;
31use crate::dom::node::{
32 BindContext, ForceSlottableNodeReconciliation, IsShadowTree, Node, NodeDamage, NodeTraits,
33 UnbindContext,
34};
35use crate::dom::{FlatTreeParent, NodeFlags};
36use crate::event_loop::script_thread::ScriptThread;
37
38#[dom_struct]
40pub(crate) struct HTMLSlotElement {
41 htmlelement: HTMLElement,
42
43 assigned_nodes: RefCell<Vec<Slottable>>,
45
46 manually_assigned_nodes: RefCell<Vec<Slottable>>,
48
49 is_in_agents_signal_slots: Cell<bool>,
53}
54
55impl HTMLSlotElementMethods<crate::DomTypeHolder> for HTMLSlotElement {
56 make_getter!(Name, "name");
58
59 make_atomic_setter!(SetName, "name");
61
62 fn AssignedNodes(&self, cx: &JSContext, options: &AssignedNodesOptions) -> Vec<DomRoot<Node>> {
64 if !options.flatten {
66 return self
67 .assigned_nodes
68 .borrow()
69 .iter()
70 .map(|slottable| slottable.node())
71 .map(DomRoot::from_ref)
72 .collect();
73 }
74
75 rooted_vec!(let mut flattened_slottables);
77 self.find_flattened_slottables(cx, &mut flattened_slottables);
78
79 flattened_slottables
80 .iter()
81 .map(|slottable| DomRoot::from_ref(slottable.node()))
82 .collect()
83 }
84
85 fn AssignedElements(
87 &self,
88 cx: &JSContext,
89 options: &AssignedNodesOptions,
90 ) -> Vec<DomRoot<Element>> {
91 self.AssignedNodes(cx, options)
92 .into_iter()
93 .flat_map(|node| node.downcast::<Element>().map(DomRoot::from_ref))
94 .collect()
95 }
96
97 fn Assign(&self, cx: &JSContext, nodes: Vec<ElementOrText>) {
99 for slottable in self.manually_assigned_nodes.borrow().iter() {
101 slottable.set_manual_slot_assignment(None);
102 }
103
104 rooted_vec!(let mut nodes_set);
106
107 for element_or_text in nodes.into_iter() {
109 rooted!(&in(cx) let node = match element_or_text {
110 ElementOrText::Element(element) => Slottable(Dom::from_ref(element.upcast())),
111 ElementOrText::Text(text) => Slottable(Dom::from_ref(text.upcast())),
112 });
113
114 if let Some(slot) = node.manual_slot_assignment() {
117 let mut manually_assigned_nodes = slot.manually_assigned_nodes.borrow_mut();
118 if let Some(position) = manually_assigned_nodes
119 .iter()
120 .position(|value| *value == *node)
121 {
122 manually_assigned_nodes.remove(position);
123 }
124 }
125
126 node.set_manual_slot_assignment(Some(self));
128
129 if !nodes_set.contains(&*node) {
131 nodes_set.push(node.clone());
132 }
133 }
134
135 *self.manually_assigned_nodes.borrow_mut() = nodes_set.iter().cloned().collect();
137
138 self.upcast::<Node>()
140 .GetRootNode(&GetRootNodeOptions::empty())
141 .assign_slottables_for_a_tree(cx, ForceSlottableNodeReconciliation::Force);
142 }
143}
144
145#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
154#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
155#[repr(transparent)]
156pub(crate) struct Slottable(pub Dom<Node>);
157#[derive(Default, JSTraceable, MallocSizeOf)]
163#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
164pub struct SlottableData {
165 pub(crate) assigned_slot: Option<Dom<HTMLSlotElement>>,
167
168 pub(crate) manual_slot_assignment: Option<Dom<HTMLSlotElement>>,
170}
171
172impl HTMLSlotElement {
173 fn new_inherited(
174 local_name: LocalName,
175 prefix: Option<Prefix>,
176 document: &Document,
177 ) -> HTMLSlotElement {
178 HTMLSlotElement {
179 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
180 assigned_nodes: Default::default(),
181 manually_assigned_nodes: Default::default(),
182 is_in_agents_signal_slots: Default::default(),
183 }
184 }
185
186 pub(crate) fn new(
187 cx: &mut JSContext,
188 local_name: LocalName,
189 prefix: Option<Prefix>,
190 document: &Document,
191 proto: Option<HandleObject>,
192 ) -> DomRoot<HTMLSlotElement> {
193 Node::reflect_node_with_proto(
194 cx,
195 Box::new(HTMLSlotElement::new_inherited(local_name, prefix, document)),
196 document,
197 proto,
198 )
199 }
200
201 pub(crate) fn has_assigned_nodes(&self) -> bool {
202 !self.assigned_nodes.borrow().is_empty()
203 }
204
205 fn find_flattened_slottables(&self, cx: &JSContext, result: &mut RootedVec<Slottable>) {
207 if !self.upcast::<Node>().is_in_a_shadow_tree() {
214 return;
215 };
216
217 rooted_vec!(let mut slottables);
219 self.find_slottables(cx, &mut slottables);
220
221 if slottables.is_empty() {
224 for child in self.upcast::<Node>().children_unrooted(cx) {
225 let is_slottable = matches!(
226 child.type_id(),
227 NodeTypeId::Element(_) |
228 NodeTypeId::CharacterData(CharacterDataTypeId::Text(_))
229 );
230 if is_slottable {
231 slottables.push(Slottable(Dom::from_ref(&*child)));
232 }
233 }
234 }
235
236 for slottable in slottables.iter() {
238 match slottable.0.downcast::<HTMLSlotElement>() {
240 Some(slot_element) if slot_element.upcast::<Node>().is_in_a_shadow_tree() => {
241 slot_element.find_flattened_slottables(cx, result);
244 },
245 _ => {
247 result.push(slottable.clone());
248 },
249 };
250 }
251
252 }
254
255 fn find_slottables(&self, cx: &JSContext, result: &mut RootedVec<Slottable>) {
260 debug_assert!(result.is_empty());
262
263 let Some(root) = self.upcast::<Node>().containing_shadow_root() else {
266 return;
267 };
268
269 let host = root.Host();
271
272 if root.SlotAssignment() == SlotAssignmentMode::Manual {
274 for slottable in self.manually_assigned_nodes.borrow().iter() {
280 if slottable
281 .node()
282 .GetParentNode()
283 .is_some_and(|node| &*node == host.upcast::<Node>())
284 {
285 result.push(slottable.clone());
286 }
287 }
288 }
289 else {
291 for child in host.upcast::<Node>().children_unrooted(cx) {
292 let is_slottable = matches!(
293 child.type_id(),
294 NodeTypeId::Element(_) |
295 NodeTypeId::CharacterData(CharacterDataTypeId::Text(_))
296 );
297 if is_slottable {
298 rooted!(&in(cx) let slottable = Slottable(Dom::from_ref(&*child)));
299 let found_slot = slottable.find_a_slot(cx.no_gc(), false);
301
302 if found_slot.is_some_and(|found_slot| &*found_slot == self) {
304 result.push(slottable.clone());
305 }
306 }
307 }
308 }
309
310 }
312
313 pub(crate) fn assign_slottables(&self, cx: &JSContext) {
315 rooted_vec!(let mut slottables);
317 self.find_slottables(cx, &mut slottables);
318
319 if self.assigned_nodes.borrow().iter().eq(slottables.iter()) {
324 return;
325 }
326
327 let document = self.owner_document();
330 let had_assigned_nodes = !self.assigned_nodes().is_empty();
331 for slottable in self.assigned_nodes().iter() {
332 document.remove_style_and_layout_data_from_subtree(cx.no_gc(), slottable.node());
333 slottable.node().dirty(cx.no_gc(), NodeDamage::Other);
334 }
335
336 self.signal_a_slot_change(cx);
337
338 for slottable in self.assigned_nodes().iter() {
345 if slottable
346 .assigned_slot()
347 .is_some_and(|assigned_slot| &*assigned_slot == self)
348 {
349 slottable.set_assigned_slot(None);
350 }
351 }
352
353 *self.assigned_nodes.borrow_mut() = slottables.iter().cloned().collect();
355
356 for slottable in slottables.iter() {
358 slottable.set_assigned_slot(Some(self));
359 }
360
361 if !had_assigned_nodes && !slottables.is_empty() {
364 let node = self.upcast::<Node>();
365 for child in node.children() {
366 document.remove_style_and_layout_data_from_subtree(cx.no_gc(), &child);
367 }
368 node.dirty(cx.no_gc(), NodeDamage::Other);
369 }
370
371 for slottable in slottables.iter() {
374 document.remove_style_and_layout_data_from_subtree(cx.no_gc(), slottable.node());
375 slottable.node().dirty(cx.no_gc(), NodeDamage::Other);
376 }
377
378 if let Some(selection) = self.owner_document().selection() &&
379 let FlatTreeParent::Parent(parent) =
380 self.upcast::<Node>().parent_in_flat_tree(cx.no_gc()) &&
381 parent.get_flag(NodeFlags::OVERLAPS_DOCUMENT_SELECTION)
382 {
383 selection.set_visible_selection_dirty();
384 }
385 }
386
387 pub(crate) fn signal_a_slot_change(&self, cx: &JSContext) {
389 self.upcast::<Node>()
390 .dirty(cx.no_gc(), NodeDamage::ContentOrHeritage);
391
392 if self.is_in_agents_signal_slots.get() {
393 return;
394 }
395 self.is_in_agents_signal_slots.set(true);
396
397 let mutation_observers = ScriptThread::mutation_observers();
398 mutation_observers.add_signal_slot(self);
400
401 mutation_observers.queue_mutation_observer_microtask(cx, ScriptThread::microtask_queue());
403 }
404
405 pub(crate) fn remove_from_signal_slots(&self) {
406 debug_assert!(self.is_in_agents_signal_slots.get());
407 self.is_in_agents_signal_slots.set(false);
408 }
409
410 pub(crate) fn assigned_nodes(&self) -> Ref<'_, [Slottable]> {
413 Ref::map(self.assigned_nodes.borrow(), Vec::as_slice)
414 }
415}
416
417impl Slottable {
418 pub(crate) fn find_a_slot(
420 &self,
421 no_gc: &NoGC,
422 open_flag: bool,
423 ) -> Option<DomRoot<HTMLSlotElement>> {
424 let parent = self.node().get_parent_node_unrooted(no_gc)?;
426
427 let shadow_root = parent
430 .downcast::<Element>()
431 .and_then(|element| element.shadow_root_unrooted(no_gc))?;
432
433 if open_flag && shadow_root.Mode() != ShadowRootMode::Open {
435 return None;
436 }
437
438 if shadow_root.SlotAssignment() == SlotAssignmentMode::Manual {
441 return self.assigned_slot();
442 }
443
444 shadow_root.slot_for_name(&self.name())
447 }
448
449 pub(crate) fn assign_a_slot(&self, cx: &JSContext) {
451 let slot = self.find_a_slot(cx.no_gc(), false);
453
454 if let Some(slot) = slot {
456 slot.assign_slottables(cx);
457 }
458 }
459
460 pub(crate) fn node(&self) -> &Node {
461 &self.0
462 }
463
464 pub(crate) fn assigned_slot(&self) -> Option<DomRoot<HTMLSlotElement>> {
465 self.node().assigned_slot()
466 }
467
468 pub(crate) fn set_assigned_slot(&self, assigned_slot: Option<&HTMLSlotElement>) {
469 self.node().set_assigned_slot(assigned_slot);
470 }
471
472 pub(crate) fn set_manual_slot_assignment(
473 &self,
474 manually_assigned_slot: Option<&HTMLSlotElement>,
475 ) {
476 self.node()
477 .set_manual_slot_assignment(manually_assigned_slot);
478 }
479
480 pub(crate) fn manual_slot_assignment(&self) -> Option<DomRoot<HTMLSlotElement>> {
481 self.node().manual_slot_assignment()
482 }
483
484 fn name(&self) -> DOMString {
485 let Some(element) = self.0.downcast::<Element>() else {
487 return DOMString::new();
488 };
489
490 element.get_string_attribute(&local_name!("slot"))
491 }
492}
493
494impl VirtualMethods for HTMLSlotElement {
495 fn super_type(&self) -> Option<&dyn VirtualMethods> {
496 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
497 }
498
499 fn attribute_mutated(
501 &self,
502 cx: &mut JSContext,
503 attr: AttrRef<'_>,
504 mutation: AttributeMutation,
505 ) {
506 self.super_type()
507 .unwrap()
508 .attribute_mutated(cx, attr, mutation);
509
510 if attr.local_name() == &local_name!("name") && attr.namespace() == &ns!() {
511 if let Some(shadow_root) = self.containing_shadow_root() {
512 let old_value = mutation
515 .old_value(attr)
516 .map(|value| value.into())
517 .unwrap_or_default();
518
519 shadow_root.unregister_slot(old_value, self);
520 shadow_root.register_slot(self);
521 }
522
523 self.upcast::<Node>()
525 .GetRootNode(&GetRootNodeOptions::empty())
526 .assign_slottables_for_a_tree(cx, ForceSlottableNodeReconciliation::Skip);
527 }
528 }
529
530 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
531 if let Some(s) = self.super_type() {
532 s.bind_to_tree(cx, context);
533 }
534
535 let was_already_in_shadow_tree = context.is_shadow_tree == IsShadowTree::Yes;
536 if !was_already_in_shadow_tree && let Some(shadow_root) = self.containing_shadow_root() {
537 shadow_root.register_slot(self);
538 }
539 }
540
541 fn unbind_from_tree(&self, cx: &mut JSContext, context: &UnbindContext) {
542 if let Some(s) = self.super_type() {
543 s.unbind_from_tree(cx, context);
544 }
545
546 if !self.upcast::<Node>().is_in_a_shadow_tree() &&
547 let Some(old_shadow_root) = self.containing_shadow_root()
548 {
549 old_shadow_root.unregister_slot(self.Name(), self);
551 }
552 }
553}
554
555impl js::gc::Rootable for Slottable {}
556
557impl js::gc::Initialize for Slottable {
558 #[expect(unsafe_code)]
559 unsafe fn initial() -> Option<Self> {
560 None
561 }
562}