1use std::cell::{Cell, Ref, RefCell};
6
7use dom_struct::dom_struct;
8use html5ever::{LocalName, Prefix, local_name, ns};
9use js::gc::RootedVec;
10use js::rust::HandleObject;
11use script_bindings::codegen::InheritTypes::{CharacterDataTypeId, NodeTypeId};
12
13use crate::ScriptThread;
14use crate::dom::attr::Attr;
15use crate::dom::bindings::codegen::Bindings::HTMLSlotElementBinding::{
16 AssignedNodesOptions, HTMLSlotElementMethods,
17};
18use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods};
19use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods;
20use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{
21 ShadowRootMode, SlotAssignmentMode,
22};
23use crate::dom::bindings::codegen::UnionTypes::ElementOrText;
24use crate::dom::bindings::inheritance::Castable;
25use crate::dom::bindings::root::{Dom, DomRoot};
26use crate::dom::bindings::str::DOMString;
27use crate::dom::document::Document;
28use crate::dom::element::{AttributeMutation, Element};
29use crate::dom::globalscope::GlobalScope;
30use crate::dom::html::htmlelement::HTMLElement;
31use crate::dom::node::{
32 BindContext, IsShadowTree, Node, NodeDamage, NodeTraits, ShadowIncluding, UnbindContext,
33};
34use crate::dom::virtualmethods::VirtualMethods;
35use crate::script_runtime::CanGc;
36
37#[dom_struct]
39pub(crate) struct HTMLSlotElement {
40 htmlelement: HTMLElement,
41
42 assigned_nodes: RefCell<Vec<Slottable>>,
44
45 manually_assigned_nodes: RefCell<Vec<Slottable>>,
47
48 is_in_agents_signal_slots: Cell<bool>,
52}
53
54impl HTMLSlotElementMethods<crate::DomTypeHolder> for HTMLSlotElement {
55 make_getter!(Name, "name");
57
58 make_atomic_setter!(SetName, "name");
60
61 fn AssignedNodes(&self, options: &AssignedNodesOptions) -> Vec<DomRoot<Node>> {
63 if !options.flatten {
65 return self
66 .assigned_nodes
67 .borrow()
68 .iter()
69 .map(|slottable| slottable.node())
70 .map(DomRoot::from_ref)
71 .collect();
72 }
73
74 rooted_vec!(let mut flattened_slottables);
76 self.find_flattened_slottables(&mut flattened_slottables);
77
78 flattened_slottables
79 .iter()
80 .map(|slottable| DomRoot::from_ref(slottable.node()))
81 .collect()
82 }
83
84 fn AssignedElements(&self, options: &AssignedNodesOptions) -> Vec<DomRoot<Element>> {
86 self.AssignedNodes(options)
87 .into_iter()
88 .flat_map(|node| node.downcast::<Element>().map(DomRoot::from_ref))
89 .collect()
90 }
91
92 fn Assign(&self, nodes: Vec<ElementOrText>) {
94 let cx = GlobalScope::get_cx();
95
96 for slottable in self.manually_assigned_nodes.borrow().iter() {
98 slottable.set_manual_slot_assignment(None);
99 }
100
101 rooted_vec!(let mut nodes_set);
103
104 for element_or_text in nodes.into_iter() {
106 rooted!(in(*cx) let node = match element_or_text {
107 ElementOrText::Element(element) => Slottable(Dom::from_ref(element.upcast())),
108 ElementOrText::Text(text) => Slottable(Dom::from_ref(text.upcast())),
109 });
110
111 if let Some(slot) = node.manual_slot_assignment() {
114 let mut manually_assigned_nodes = slot.manually_assigned_nodes.borrow_mut();
115 if let Some(position) = manually_assigned_nodes
116 .iter()
117 .position(|value| *value == *node)
118 {
119 manually_assigned_nodes.remove(position);
120 }
121 }
122
123 node.set_manual_slot_assignment(Some(self));
125
126 if !nodes_set.contains(&*node) {
128 nodes_set.push(node.clone());
129 }
130 }
131
132 *self.manually_assigned_nodes.borrow_mut() = nodes_set.iter().cloned().collect();
134
135 self.upcast::<Node>()
137 .GetRootNode(&GetRootNodeOptions::empty())
138 .assign_slottables_for_a_tree();
139 }
140}
141
142#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
151#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
152#[repr(transparent)]
153pub(crate) struct Slottable(pub Dom<Node>);
154#[derive(Default, JSTraceable, MallocSizeOf)]
160#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
161pub struct SlottableData {
162 pub(crate) assigned_slot: Option<Dom<HTMLSlotElement>>,
164
165 pub(crate) manual_slot_assignment: Option<Dom<HTMLSlotElement>>,
167}
168
169impl HTMLSlotElement {
170 fn new_inherited(
171 local_name: LocalName,
172 prefix: Option<Prefix>,
173 document: &Document,
174 ) -> HTMLSlotElement {
175 HTMLSlotElement {
176 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
177 assigned_nodes: Default::default(),
178 manually_assigned_nodes: Default::default(),
179 is_in_agents_signal_slots: Default::default(),
180 }
181 }
182
183 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
184 pub(crate) fn new(
185 local_name: LocalName,
186 prefix: Option<Prefix>,
187 document: &Document,
188 proto: Option<HandleObject>,
189 can_gc: CanGc,
190 ) -> DomRoot<HTMLSlotElement> {
191 Node::reflect_node_with_proto(
192 Box::new(HTMLSlotElement::new_inherited(local_name, prefix, document)),
193 document,
194 proto,
195 can_gc,
196 )
197 }
198
199 pub(crate) fn has_assigned_nodes(&self) -> bool {
200 !self.assigned_nodes.borrow().is_empty()
201 }
202
203 fn find_flattened_slottables(&self, result: &mut RootedVec<Slottable>) {
205 debug_assert!(result.is_empty());
207
208 if self.upcast::<Node>().containing_shadow_root().is_none() {
210 return;
211 };
212
213 rooted_vec!(let mut slottables);
215 self.find_slottables(&mut slottables);
216
217 if slottables.is_empty() {
220 for child in self.upcast::<Node>().children() {
221 let is_slottable = matches!(
222 child.type_id(),
223 NodeTypeId::Element(_) |
224 NodeTypeId::CharacterData(CharacterDataTypeId::Text(_))
225 );
226 if is_slottable {
227 slottables.push(Slottable(child.as_traced()));
228 }
229 }
230 }
231
232 for slottable in slottables.iter() {
234 match slottable.0.downcast::<HTMLSlotElement>() {
236 Some(slot_element)
237 if slot_element
238 .upcast::<Node>()
239 .containing_shadow_root()
240 .is_some() =>
241 {
242 rooted_vec!(let mut temporary_result);
244 slot_element.find_flattened_slottables(&mut temporary_result);
245
246 result.extend_from_slice(&temporary_result);
248 },
249 _ => {
251 result.push(slottable.clone());
252 },
253 };
254 }
255
256 }
258
259 fn find_slottables(&self, result: &mut RootedVec<Slottable>) {
264 let cx = GlobalScope::get_cx();
265
266 debug_assert!(result.is_empty());
268
269 let Some(root) = self.upcast::<Node>().containing_shadow_root() else {
272 return;
273 };
274
275 let host = root.Host();
277
278 if root.SlotAssignment() == SlotAssignmentMode::Manual {
280 for slottable in self.manually_assigned_nodes.borrow().iter() {
286 if slottable
287 .node()
288 .GetParentNode()
289 .is_some_and(|node| &*node == host.upcast::<Node>())
290 {
291 result.push(slottable.clone());
292 }
293 }
294 }
295 else {
297 for child in host.upcast::<Node>().children() {
298 let is_slottable = matches!(
299 child.type_id(),
300 NodeTypeId::Element(_) |
301 NodeTypeId::CharacterData(CharacterDataTypeId::Text(_))
302 );
303 if is_slottable {
304 rooted!(in(*cx) let slottable = Slottable(child.as_traced()));
305 let found_slot = slottable.find_a_slot(false);
307
308 if found_slot.is_some_and(|found_slot| &*found_slot == self) {
310 result.push(slottable.clone());
311 }
312 }
313 }
314 }
315
316 }
318
319 pub(crate) fn assign_slottables(&self) {
321 rooted_vec!(let mut slottables);
323 self.find_slottables(&mut slottables);
324
325 if self.assigned_nodes.borrow().iter().eq(slottables.iter()) {
330 return;
331 }
332 self.signal_a_slot_change();
333
334 for slottable in self.assigned_nodes().iter() {
338 slottable.set_assigned_slot(None);
339 }
340
341 *self.assigned_nodes.borrow_mut() = slottables.iter().cloned().collect();
343
344 for slottable in slottables.iter() {
346 slottable.set_assigned_slot(Some(self));
347 }
348 }
349
350 pub(crate) fn signal_a_slot_change(&self) {
352 self.upcast::<Node>().dirty(NodeDamage::ContentOrHeritage);
353
354 if self.is_in_agents_signal_slots.get() {
355 return;
356 }
357 self.is_in_agents_signal_slots.set(true);
358
359 let mutation_observers = ScriptThread::mutation_observers();
360 mutation_observers.add_signal_slot(self);
362
363 mutation_observers.queue_mutation_observer_microtask(ScriptThread::microtask_queue());
365 }
366
367 pub(crate) fn remove_from_signal_slots(&self) {
368 debug_assert!(self.is_in_agents_signal_slots.get());
369 self.is_in_agents_signal_slots.set(false);
370 }
371
372 pub(crate) fn assigned_nodes(&self) -> Ref<'_, [Slottable]> {
375 Ref::map(self.assigned_nodes.borrow(), Vec::as_slice)
376 }
377}
378
379impl Slottable {
380 pub(crate) fn find_a_slot(&self, open_flag: bool) -> Option<DomRoot<HTMLSlotElement>> {
382 let parent = self.node().GetParentNode()?;
384
385 let shadow_root = parent
388 .downcast::<Element>()
389 .and_then(Element::shadow_root)?;
390
391 if open_flag && shadow_root.Mode() != ShadowRootMode::Open {
393 return None;
394 }
395
396 if shadow_root.SlotAssignment() == SlotAssignmentMode::Manual {
399 for node in shadow_root
400 .upcast::<Node>()
401 .traverse_preorder(ShadowIncluding::No)
402 {
403 if let Some(slot) = node.downcast::<HTMLSlotElement>() {
404 if slot.manually_assigned_nodes.borrow().contains(self) {
405 return Some(DomRoot::from_ref(slot));
406 }
407 }
408 }
409 return None;
410 }
411
412 shadow_root.slot_for_name(&self.name())
415 }
416
417 pub(crate) fn assign_a_slot(&self) {
419 let slot = self.find_a_slot(false);
421
422 if let Some(slot) = slot {
424 slot.assign_slottables();
425 }
426 }
427
428 fn node(&self) -> &Node {
429 &self.0
430 }
431
432 pub(crate) fn assigned_slot(&self) -> Option<DomRoot<HTMLSlotElement>> {
433 self.node().assigned_slot()
434 }
435
436 pub(crate) fn set_assigned_slot(&self, assigned_slot: Option<&HTMLSlotElement>) {
437 self.node().set_assigned_slot(assigned_slot);
438 }
439
440 pub(crate) fn set_manual_slot_assignment(
441 &self,
442 manually_assigned_slot: Option<&HTMLSlotElement>,
443 ) {
444 self.node()
445 .set_manual_slot_assignment(manually_assigned_slot);
446 }
447
448 pub(crate) fn manual_slot_assignment(&self) -> Option<DomRoot<HTMLSlotElement>> {
449 self.node().manual_slot_assignment()
450 }
451
452 fn name(&self) -> DOMString {
453 let Some(element) = self.0.downcast::<Element>() else {
455 return DOMString::new();
456 };
457
458 element.get_string_attribute(&local_name!("slot"))
459 }
460}
461
462impl VirtualMethods for HTMLSlotElement {
463 fn super_type(&self) -> Option<&dyn VirtualMethods> {
464 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
465 }
466
467 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
469 self.super_type()
470 .unwrap()
471 .attribute_mutated(attr, mutation, can_gc);
472
473 if attr.local_name() == &local_name!("name") && attr.namespace() == &ns!() {
474 if let Some(shadow_root) = self.containing_shadow_root() {
475 let old_value = match mutation {
478 AttributeMutation::Set(old, _) => old
479 .map(|value| value.to_string().into())
480 .unwrap_or_default(),
481 AttributeMutation::Removed => attr.value().to_string().into(),
482 };
483
484 shadow_root.unregister_slot(old_value, self);
485 shadow_root.register_slot(self);
486 }
487
488 self.upcast::<Node>()
490 .GetRootNode(&GetRootNodeOptions::empty())
491 .assign_slottables_for_a_tree()
492 }
493 }
494
495 fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
496 if let Some(s) = self.super_type() {
497 s.bind_to_tree(context, can_gc);
498 }
499
500 let was_already_in_shadow_tree = context.is_shadow_tree == IsShadowTree::Yes;
501 if !was_already_in_shadow_tree {
502 if let Some(shadow_root) = self.containing_shadow_root() {
503 shadow_root.register_slot(self);
504 }
505 }
506 }
507
508 fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
509 if let Some(s) = self.super_type() {
510 s.unbind_from_tree(context, can_gc);
511 }
512
513 if !self.upcast::<Node>().is_in_a_shadow_tree() {
514 if let Some(old_shadow_root) = self.containing_shadow_root() {
515 old_shadow_root.unregister_slot(self.Name(), self);
517 }
518 }
519 }
520}
521
522impl js::gc::Rootable for Slottable {}
523
524impl js::gc::Initialize for Slottable {
525 #[expect(unsafe_code)]
526 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
527 unsafe fn initial() -> Option<Self> {
528 None
529 }
530}