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::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::attributes::storage::AttrRef;
29use crate::dom::element::{AttributeMutation, Element};
30use crate::dom::html::htmlelement::HTMLElement;
31use crate::dom::node::virtualmethods::VirtualMethods;
32use crate::dom::node::{
33 BindContext, ForceSlottableNodeReconciliation, IsShadowTree, Node, NodeDamage, NodeTraits,
34 UnbindContext,
35};
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, cx: &JSContext, 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(cx, &mut flattened_slottables);
77
78 flattened_slottables
79 .iter()
80 .map(|slottable| DomRoot::from_ref(slottable.node()))
81 .collect()
82 }
83
84 fn AssignedElements(
86 &self,
87 cx: &JSContext,
88 options: &AssignedNodesOptions,
89 ) -> Vec<DomRoot<Element>> {
90 self.AssignedNodes(cx, options)
91 .into_iter()
92 .flat_map(|node| node.downcast::<Element>().map(DomRoot::from_ref))
93 .collect()
94 }
95
96 fn Assign(&self, cx: &JSContext, nodes: Vec<ElementOrText>) {
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(cx, 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 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, cx: &JSContext, result: &mut RootedVec<Slottable>) {
206 if !self.upcast::<Node>().is_in_a_shadow_tree() {
213 return;
214 };
215
216 rooted_vec!(let mut slottables);
218 self.find_slottables(cx, &mut slottables);
219
220 if slottables.is_empty() {
223 for child in self.upcast::<Node>().children_unrooted(cx) {
224 let is_slottable = matches!(
225 child.type_id(),
226 NodeTypeId::Element(_) |
227 NodeTypeId::CharacterData(CharacterDataTypeId::Text(_))
228 );
229 if is_slottable {
230 slottables.push(Slottable(Dom::from_ref(&*child)));
231 }
232 }
233 }
234
235 for slottable in slottables.iter() {
237 match slottable.0.downcast::<HTMLSlotElement>() {
239 Some(slot_element) if slot_element.upcast::<Node>().is_in_a_shadow_tree() => {
240 slot_element.find_flattened_slottables(cx, result);
243 },
244 _ => {
246 result.push(slottable.clone());
247 },
248 };
249 }
250
251 }
253
254 fn find_slottables(&self, cx: &JSContext, result: &mut RootedVec<Slottable>) {
259 debug_assert!(result.is_empty());
261
262 let Some(root) = self.upcast::<Node>().containing_shadow_root() else {
265 return;
266 };
267
268 let host = root.Host();
270
271 if root.SlotAssignment() == SlotAssignmentMode::Manual {
273 for slottable in self.manually_assigned_nodes.borrow().iter() {
279 if slottable
280 .node()
281 .GetParentNode()
282 .is_some_and(|node| &*node == host.upcast::<Node>())
283 {
284 result.push(slottable.clone());
285 }
286 }
287 }
288 else {
290 for child in host.upcast::<Node>().children_unrooted(cx) {
291 let is_slottable = matches!(
292 child.type_id(),
293 NodeTypeId::Element(_) |
294 NodeTypeId::CharacterData(CharacterDataTypeId::Text(_))
295 );
296 if is_slottable {
297 rooted!(&in(cx) let slottable = Slottable(Dom::from_ref(&*child)));
298 let found_slot = slottable.find_a_slot(false);
300
301 if found_slot.is_some_and(|found_slot| &*found_slot == self) {
303 result.push(slottable.clone());
304 }
305 }
306 }
307 }
308
309 }
311
312 pub(crate) fn assign_slottables(&self, cx: &JSContext) {
314 rooted_vec!(let mut slottables);
316 self.find_slottables(cx, &mut slottables);
317
318 if self.assigned_nodes.borrow().iter().eq(slottables.iter()) {
323 return;
324 }
325
326 for slottable in self.assigned_nodes().iter() {
329 slottable
330 .node()
331 .remove_style_and_layout_data_from_subtree(cx);
332 slottable.node().dirty(NodeDamage::Other);
333 }
334
335 self.signal_a_slot_change(cx);
336
337 for slottable in self.assigned_nodes().iter() {
344 if slottable
345 .assigned_slot()
346 .is_some_and(|assigned_slot| &*assigned_slot == self)
347 {
348 slottable.set_assigned_slot(None);
349 }
350 }
351
352 *self.assigned_nodes.borrow_mut() = slottables.iter().cloned().collect();
354
355 for slottable in slottables.iter() {
357 slottable.set_assigned_slot(Some(self));
358 }
359
360 for slottable in slottables.iter() {
363 slottable
364 .node()
365 .remove_style_and_layout_data_from_subtree(cx);
366 slottable.node().dirty(NodeDamage::Other);
367 }
368 }
369
370 pub(crate) fn signal_a_slot_change(&self, cx: &JSContext) {
372 self.upcast::<Node>().dirty(NodeDamage::ContentOrHeritage);
373
374 if self.is_in_agents_signal_slots.get() {
375 return;
376 }
377 self.is_in_agents_signal_slots.set(true);
378
379 let mutation_observers = ScriptThread::mutation_observers();
380 mutation_observers.add_signal_slot(self);
382
383 mutation_observers.queue_mutation_observer_microtask(cx, ScriptThread::microtask_queue());
385 }
386
387 pub(crate) fn remove_from_signal_slots(&self) {
388 debug_assert!(self.is_in_agents_signal_slots.get());
389 self.is_in_agents_signal_slots.set(false);
390 }
391
392 pub(crate) fn assigned_nodes(&self) -> Ref<'_, [Slottable]> {
395 Ref::map(self.assigned_nodes.borrow(), Vec::as_slice)
396 }
397}
398
399impl Slottable {
400 pub(crate) fn find_a_slot(&self, open_flag: bool) -> Option<DomRoot<HTMLSlotElement>> {
402 let parent = self.node().GetParentNode()?;
404
405 let shadow_root = parent
408 .downcast::<Element>()
409 .and_then(Element::shadow_root)?;
410
411 if open_flag && shadow_root.Mode() != ShadowRootMode::Open {
413 return None;
414 }
415
416 if shadow_root.SlotAssignment() == SlotAssignmentMode::Manual {
419 return self.assigned_slot();
420 }
421
422 shadow_root.slot_for_name(&self.name())
425 }
426
427 pub(crate) fn assign_a_slot(&self, cx: &JSContext) {
429 let slot = self.find_a_slot(false);
431
432 if let Some(slot) = slot {
434 slot.assign_slottables(cx);
435 }
436 }
437
438 pub(crate) fn node(&self) -> &Node {
439 &self.0
440 }
441
442 pub(crate) fn assigned_slot(&self) -> Option<DomRoot<HTMLSlotElement>> {
443 self.node().assigned_slot()
444 }
445
446 pub(crate) fn set_assigned_slot(&self, assigned_slot: Option<&HTMLSlotElement>) {
447 self.node().set_assigned_slot(assigned_slot);
448 }
449
450 pub(crate) fn set_manual_slot_assignment(
451 &self,
452 manually_assigned_slot: Option<&HTMLSlotElement>,
453 ) {
454 self.node()
455 .set_manual_slot_assignment(manually_assigned_slot);
456 }
457
458 pub(crate) fn manual_slot_assignment(&self) -> Option<DomRoot<HTMLSlotElement>> {
459 self.node().manual_slot_assignment()
460 }
461
462 fn name(&self) -> DOMString {
463 let Some(element) = self.0.downcast::<Element>() else {
465 return DOMString::new();
466 };
467
468 element.get_string_attribute(&local_name!("slot"))
469 }
470}
471
472impl VirtualMethods for HTMLSlotElement {
473 fn super_type(&self) -> Option<&dyn VirtualMethods> {
474 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
475 }
476
477 fn attribute_mutated(
479 &self,
480 cx: &mut JSContext,
481 attr: AttrRef<'_>,
482 mutation: AttributeMutation,
483 ) {
484 self.super_type()
485 .unwrap()
486 .attribute_mutated(cx, attr, mutation);
487
488 if attr.local_name() == &local_name!("name") && attr.namespace() == &ns!() {
489 if let Some(shadow_root) = self.containing_shadow_root() {
490 let old_value = match mutation {
493 AttributeMutation::Set(old, _) => old
494 .map(|value| value.to_string().into())
495 .unwrap_or_default(),
496 AttributeMutation::Removed => attr.value().to_string().into(),
497 };
498
499 shadow_root.unregister_slot(old_value, self);
500 shadow_root.register_slot(self);
501 }
502
503 self.upcast::<Node>()
505 .GetRootNode(&GetRootNodeOptions::empty())
506 .assign_slottables_for_a_tree(cx, ForceSlottableNodeReconciliation::Skip);
507 }
508 }
509
510 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
511 if let Some(s) = self.super_type() {
512 s.bind_to_tree(cx, context);
513 }
514
515 let was_already_in_shadow_tree = context.is_shadow_tree == IsShadowTree::Yes;
516 if !was_already_in_shadow_tree && let Some(shadow_root) = self.containing_shadow_root() {
517 shadow_root.register_slot(self);
518 }
519 }
520
521 fn unbind_from_tree(&self, cx: &mut JSContext, context: &UnbindContext) {
522 if let Some(s) = self.super_type() {
523 s.unbind_from_tree(cx, context);
524 }
525
526 if !self.upcast::<Node>().is_in_a_shadow_tree() &&
527 let Some(old_shadow_root) = self.containing_shadow_root()
528 {
529 old_shadow_root.unregister_slot(self.Name(), self);
531 }
532 }
533}
534
535impl js::gc::Rootable for Slottable {}
536
537impl js::gc::Initialize for Slottable {
538 #[expect(unsafe_code)]
539 unsafe fn initial() -> Option<Self> {
540 None
541 }
542}