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