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::{BindContext, Node, NodeDamage, NodeTraits, ShadowIncluding, UnbindContext};
32use crate::dom::virtualmethods::VirtualMethods;
33use crate::script_runtime::CanGc;
34
35#[dom_struct]
37pub(crate) struct HTMLSlotElement {
38 htmlelement: HTMLElement,
39
40 assigned_nodes: RefCell<Vec<Slottable>>,
42
43 manually_assigned_nodes: RefCell<Vec<Slottable>>,
45
46 is_in_agents_signal_slots: Cell<bool>,
50}
51
52impl HTMLSlotElementMethods<crate::DomTypeHolder> for HTMLSlotElement {
53 make_getter!(Name, "name");
55
56 make_atomic_setter!(SetName, "name");
58
59 fn AssignedNodes(&self, options: &AssignedNodesOptions) -> Vec<DomRoot<Node>> {
61 if !options.flatten {
63 return self
64 .assigned_nodes
65 .borrow()
66 .iter()
67 .map(|slottable| slottable.node())
68 .map(DomRoot::from_ref)
69 .collect();
70 }
71
72 rooted_vec!(let mut flattened_slottables);
74 self.find_flattened_slottables(&mut flattened_slottables);
75
76 flattened_slottables
77 .iter()
78 .map(|slottable| DomRoot::from_ref(slottable.node()))
79 .collect()
80 }
81
82 fn AssignedElements(&self, options: &AssignedNodesOptions) -> Vec<DomRoot<Element>> {
84 self.AssignedNodes(options)
85 .into_iter()
86 .flat_map(|node| node.downcast::<Element>().map(DomRoot::from_ref))
87 .collect()
88 }
89
90 fn Assign(&self, nodes: Vec<ElementOrText>) {
92 let cx = GlobalScope::get_cx();
93
94 for slottable in self.manually_assigned_nodes.borrow().iter() {
96 slottable.set_manual_slot_assignment(None);
97 }
98
99 rooted_vec!(let mut nodes_set);
101
102 for element_or_text in nodes.into_iter() {
104 rooted!(in(*cx) let node = match element_or_text {
105 ElementOrText::Element(element) => Slottable(Dom::from_ref(element.upcast())),
106 ElementOrText::Text(text) => Slottable(Dom::from_ref(text.upcast())),
107 });
108
109 if let Some(slot) = node.manual_slot_assignment() {
112 let mut manually_assigned_nodes = slot.manually_assigned_nodes.borrow_mut();
113 if let Some(position) = manually_assigned_nodes
114 .iter()
115 .position(|value| *value == *node)
116 {
117 manually_assigned_nodes.remove(position);
118 }
119 }
120
121 node.set_manual_slot_assignment(Some(self));
123
124 if !nodes_set.contains(&*node) {
126 nodes_set.push(node.clone());
127 }
128 }
129
130 *self.manually_assigned_nodes.borrow_mut() = nodes_set.iter().cloned().collect();
132
133 self.upcast::<Node>()
135 .GetRootNode(&GetRootNodeOptions::empty())
136 .assign_slottables_for_a_tree();
137 }
138}
139
140#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
149#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
150#[repr(transparent)]
151pub(crate) struct Slottable(pub Dom<Node>);
152#[derive(Default, JSTraceable, MallocSizeOf)]
158#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
159pub struct SlottableData {
160 pub(crate) assigned_slot: Option<Dom<HTMLSlotElement>>,
162
163 pub(crate) manual_slot_assignment: Option<Dom<HTMLSlotElement>>,
165}
166
167impl HTMLSlotElement {
168 fn new_inherited(
169 local_name: LocalName,
170 prefix: Option<Prefix>,
171 document: &Document,
172 ) -> HTMLSlotElement {
173 HTMLSlotElement {
174 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
175 assigned_nodes: Default::default(),
176 manually_assigned_nodes: Default::default(),
177 is_in_agents_signal_slots: Default::default(),
178 }
179 }
180
181 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
182 pub(crate) fn new(
183 local_name: LocalName,
184 prefix: Option<Prefix>,
185 document: &Document,
186 proto: Option<HandleObject>,
187 can_gc: CanGc,
188 ) -> DomRoot<HTMLSlotElement> {
189 Node::reflect_node_with_proto(
190 Box::new(HTMLSlotElement::new_inherited(local_name, prefix, document)),
191 document,
192 proto,
193 can_gc,
194 )
195 }
196
197 pub(crate) fn has_assigned_nodes(&self) -> bool {
198 !self.assigned_nodes.borrow().is_empty()
199 }
200
201 fn find_flattened_slottables(&self, result: &mut RootedVec<Slottable>) {
203 debug_assert!(result.is_empty());
205
206 if self.upcast::<Node>().containing_shadow_root().is_none() {
208 return;
209 };
210
211 rooted_vec!(let mut slottables);
213 self.find_slottables(&mut slottables);
214
215 if slottables.is_empty() {
218 for child in self.upcast::<Node>().children() {
219 let is_slottable = matches!(
220 child.type_id(),
221 NodeTypeId::Element(_) |
222 NodeTypeId::CharacterData(CharacterDataTypeId::Text(_))
223 );
224 if is_slottable {
225 slottables.push(Slottable(child.as_traced()));
226 }
227 }
228 }
229
230 for slottable in slottables.iter() {
232 match slottable.0.downcast::<HTMLSlotElement>() {
234 Some(slot_element)
235 if slot_element
236 .upcast::<Node>()
237 .containing_shadow_root()
238 .is_some() =>
239 {
240 rooted_vec!(let mut temporary_result);
242 slot_element.find_flattened_slottables(&mut temporary_result);
243
244 result.extend_from_slice(&temporary_result);
246 },
247 _ => {
249 result.push(slottable.clone());
250 },
251 };
252 }
253
254 }
256
257 fn find_slottables(&self, result: &mut RootedVec<Slottable>) {
262 let cx = GlobalScope::get_cx();
263
264 debug_assert!(result.is_empty());
266
267 let Some(root) = self.upcast::<Node>().containing_shadow_root() else {
270 return;
271 };
272
273 let host = root.Host();
275
276 if root.SlotAssignment() == SlotAssignmentMode::Manual {
278 for slottable in self.manually_assigned_nodes.borrow().iter() {
284 if slottable
285 .node()
286 .GetParentNode()
287 .is_some_and(|node| &*node == host.upcast::<Node>())
288 {
289 result.push(slottable.clone());
290 }
291 }
292 }
293 else {
295 for child in host.upcast::<Node>().children() {
296 let is_slottable = matches!(
297 child.type_id(),
298 NodeTypeId::Element(_) |
299 NodeTypeId::CharacterData(CharacterDataTypeId::Text(_))
300 );
301 if is_slottable {
302 rooted!(in(*cx) let slottable = Slottable(child.as_traced()));
303 let found_slot = slottable.find_a_slot(false);
305
306 if found_slot.is_some_and(|found_slot| &*found_slot == self) {
308 result.push(slottable.clone());
309 }
310 }
311 }
312 }
313
314 }
316
317 pub(crate) fn assign_slottables(&self) {
319 rooted_vec!(let mut slottables);
321 self.find_slottables(&mut slottables);
322
323 let slots_are_identical = self.assigned_nodes.borrow().iter().eq(slottables.iter());
326 if !slots_are_identical {
327 self.signal_a_slot_change();
328 }
329
330 for slottable in self.assigned_nodes().iter() {
334 slottable.set_assigned_slot(None);
335 }
336
337 *self.assigned_nodes.borrow_mut() = slottables.iter().cloned().collect();
339
340 for slottable in slottables.iter() {
342 slottable.set_assigned_slot(Some(self));
343 }
344 }
345
346 pub(crate) fn signal_a_slot_change(&self) {
348 self.upcast::<Node>().dirty(NodeDamage::ContentOrHeritage);
349
350 if self.is_in_agents_signal_slots.get() {
351 return;
352 }
353 self.is_in_agents_signal_slots.set(true);
354
355 let mutation_observers = ScriptThread::mutation_observers();
356 mutation_observers.add_signal_slot(self);
358
359 mutation_observers.queue_mutation_observer_microtask(ScriptThread::microtask_queue());
361 }
362
363 pub(crate) fn remove_from_signal_slots(&self) {
364 debug_assert!(self.is_in_agents_signal_slots.get());
365 self.is_in_agents_signal_slots.set(false);
366 }
367
368 pub(crate) fn assigned_nodes(&self) -> Ref<'_, [Slottable]> {
371 Ref::map(self.assigned_nodes.borrow(), Vec::as_slice)
372 }
373}
374
375impl Slottable {
376 pub(crate) fn find_a_slot(&self, open_flag: bool) -> Option<DomRoot<HTMLSlotElement>> {
378 let parent = self.node().GetParentNode()?;
380
381 let shadow_root = parent
384 .downcast::<Element>()
385 .and_then(Element::shadow_root)?;
386
387 if open_flag && shadow_root.Mode() != ShadowRootMode::Open {
389 return None;
390 }
391
392 if shadow_root.SlotAssignment() == SlotAssignmentMode::Manual {
395 for node in shadow_root
396 .upcast::<Node>()
397 .traverse_preorder(ShadowIncluding::No)
398 {
399 if let Some(slot) = node.downcast::<HTMLSlotElement>() {
400 if slot.manually_assigned_nodes.borrow().contains(self) {
401 return Some(DomRoot::from_ref(slot));
402 }
403 }
404 }
405 return None;
406 }
407
408 shadow_root.slot_for_name(&self.name())
411 }
412
413 pub(crate) fn assign_a_slot(&self) {
415 let slot = self.find_a_slot(false);
417
418 if let Some(slot) = slot {
420 slot.assign_slottables();
421 }
422 }
423
424 fn node(&self) -> &Node {
425 &self.0
426 }
427
428 pub(crate) fn assigned_slot(&self) -> Option<DomRoot<HTMLSlotElement>> {
429 self.node().assigned_slot()
430 }
431
432 pub(crate) fn set_assigned_slot(&self, assigned_slot: Option<&HTMLSlotElement>) {
433 self.node().set_assigned_slot(assigned_slot);
434 }
435
436 pub(crate) fn set_manual_slot_assignment(
437 &self,
438 manually_assigned_slot: Option<&HTMLSlotElement>,
439 ) {
440 self.node()
441 .set_manual_slot_assignment(manually_assigned_slot);
442 }
443
444 pub(crate) fn manual_slot_assignment(&self) -> Option<DomRoot<HTMLSlotElement>> {
445 self.node().manual_slot_assignment()
446 }
447
448 fn name(&self) -> DOMString {
449 let Some(element) = self.0.downcast::<Element>() else {
451 return DOMString::new();
452 };
453
454 element.get_string_attribute(&local_name!("slot"))
455 }
456}
457
458impl VirtualMethods for HTMLSlotElement {
459 fn super_type(&self) -> Option<&dyn VirtualMethods> {
460 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
461 }
462
463 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
465 self.super_type()
466 .unwrap()
467 .attribute_mutated(attr, mutation, can_gc);
468
469 if attr.local_name() == &local_name!("name") && attr.namespace() == &ns!() {
470 if let Some(shadow_root) = self.containing_shadow_root() {
471 let old_value = match mutation {
474 AttributeMutation::Set(old) => old
475 .map(|value| value.to_string().into())
476 .unwrap_or_default(),
477 AttributeMutation::Removed => attr.value().to_string().into(),
478 };
479
480 shadow_root.unregister_slot(old_value, self);
481 shadow_root.register_slot(self);
482 }
483
484 self.upcast::<Node>()
486 .GetRootNode(&GetRootNodeOptions::empty())
487 .assign_slottables_for_a_tree()
488 }
489 }
490
491 fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
492 if let Some(s) = self.super_type() {
493 s.bind_to_tree(context, can_gc);
494 }
495
496 if !context.tree_is_in_a_shadow_tree {
497 return;
498 }
499
500 self.containing_shadow_root()
501 .expect("not in a shadow tree")
502 .register_slot(self);
503 }
504
505 fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
506 if let Some(s) = self.super_type() {
507 s.unbind_from_tree(context, can_gc);
508 }
509
510 if let Some(shadow_root) = self.containing_shadow_root() {
511 shadow_root.unregister_slot(self.Name(), self);
512 }
513 }
514}
515
516impl js::gc::Rootable for Slottable {}
517
518impl js::gc::Initialize for Slottable {
519 #[allow(unsafe_code)]
520 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
521 unsafe fn initial() -> Option<Self> {
522 None
523 }
524}