1use std::cell::{Cell, Ref};
6
7use dom_struct::dom_struct;
8use html5ever::{LocalName, Prefix, QualName, local_name, ns};
9use js::rust::HandleObject;
10
11use crate::dom::attr::Attr;
12use crate::dom::bindings::cell::DomRefCell;
13use crate::dom::bindings::codegen::Bindings::HTMLDetailsElementBinding::HTMLDetailsElementMethods;
14use crate::dom::bindings::codegen::Bindings::HTMLSlotElementBinding::HTMLSlotElement_Binding::HTMLSlotElementMethods;
15use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
16use crate::dom::bindings::codegen::UnionTypes::ElementOrText;
17use crate::dom::bindings::inheritance::Castable;
18use crate::dom::bindings::refcounted::Trusted;
19use crate::dom::bindings::root::{Dom, DomRoot};
20use crate::dom::document::Document;
21use crate::dom::element::{AttributeMutation, CustomElementCreationMode, Element, ElementCreator};
22use crate::dom::eventtarget::EventTarget;
23use crate::dom::html::htmlelement::HTMLElement;
24use crate::dom::html::htmlslotelement::HTMLSlotElement;
25use crate::dom::node::{BindContext, ChildrenMutation, Node, NodeDamage, NodeTraits};
26use crate::dom::text::Text;
27use crate::dom::virtualmethods::VirtualMethods;
28use crate::script_runtime::CanGc;
29
30const DEFAULT_SUMMARY: &str = "Details";
32
33#[derive(Clone, JSTraceable, MallocSizeOf)]
38#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
39struct ShadowTree {
40 summary: Dom<HTMLSlotElement>,
41 descendants: Dom<HTMLSlotElement>,
42 implicit_summary: Dom<HTMLElement>,
44}
45
46#[dom_struct]
47pub(crate) struct HTMLDetailsElement {
48 htmlelement: HTMLElement,
49 toggle_counter: Cell<u32>,
50
51 shadow_tree: DomRefCell<Option<ShadowTree>>,
53}
54
55impl HTMLDetailsElement {
56 fn new_inherited(
57 local_name: LocalName,
58 prefix: Option<Prefix>,
59 document: &Document,
60 ) -> HTMLDetailsElement {
61 HTMLDetailsElement {
62 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
63 toggle_counter: Cell::new(0),
64 shadow_tree: Default::default(),
65 }
66 }
67
68 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
69 pub(crate) fn new(
70 local_name: LocalName,
71 prefix: Option<Prefix>,
72 document: &Document,
73 proto: Option<HandleObject>,
74 can_gc: CanGc,
75 ) -> DomRoot<HTMLDetailsElement> {
76 Node::reflect_node_with_proto(
77 Box::new(HTMLDetailsElement::new_inherited(
78 local_name, prefix, document,
79 )),
80 document,
81 proto,
82 can_gc,
83 )
84 }
85
86 pub(crate) fn toggle(&self) {
87 self.SetOpen(!self.Open());
88 }
89
90 fn shadow_tree(&self, can_gc: CanGc) -> Ref<'_, ShadowTree> {
91 if !self.upcast::<Element>().is_shadow_host() {
92 self.create_shadow_tree(can_gc);
93 }
94
95 Ref::filter_map(self.shadow_tree.borrow(), Option::as_ref)
96 .ok()
97 .expect("UA shadow tree was not created")
98 }
99
100 fn create_shadow_tree(&self, can_gc: CanGc) {
101 let document = self.owner_document();
102 let root = self
105 .upcast::<Element>()
106 .attach_ua_shadow_root(false, can_gc);
107
108 let summary = Element::create(
109 QualName::new(None, ns!(html), local_name!("slot")),
110 None,
111 &document,
112 ElementCreator::ScriptCreated,
113 CustomElementCreationMode::Asynchronous,
114 None,
115 can_gc,
116 );
117 let summary = DomRoot::downcast::<HTMLSlotElement>(summary).unwrap();
118 root.upcast::<Node>()
119 .AppendChild(summary.upcast::<Node>(), can_gc)
120 .unwrap();
121
122 let fallback_summary = Element::create(
123 QualName::new(None, ns!(html), local_name!("summary")),
124 None,
125 &document,
126 ElementCreator::ScriptCreated,
127 CustomElementCreationMode::Asynchronous,
128 None,
129 can_gc,
130 );
131 let fallback_summary = DomRoot::downcast::<HTMLElement>(fallback_summary).unwrap();
132 fallback_summary
133 .upcast::<Node>()
134 .set_text_content_for_element(Some(DEFAULT_SUMMARY.into()), can_gc);
135 summary
136 .upcast::<Node>()
137 .AppendChild(fallback_summary.upcast::<Node>(), can_gc)
138 .unwrap();
139
140 let descendants = Element::create(
141 QualName::new(None, ns!(html), local_name!("slot")),
142 None,
143 &document,
144 ElementCreator::ScriptCreated,
145 CustomElementCreationMode::Asynchronous,
146 None,
147 can_gc,
148 );
149 let descendants = DomRoot::downcast::<HTMLSlotElement>(descendants).unwrap();
150 root.upcast::<Node>()
151 .AppendChild(descendants.upcast::<Node>(), can_gc)
152 .unwrap();
153
154 let _ = self.shadow_tree.borrow_mut().insert(ShadowTree {
155 summary: summary.as_traced(),
156 descendants: descendants.as_traced(),
157 implicit_summary: fallback_summary.as_traced(),
158 });
159 self.upcast::<Node>()
160 .dirty(crate::dom::node::NodeDamage::Other);
161 }
162
163 pub(crate) fn find_corresponding_summary_element(&self) -> Option<DomRoot<HTMLElement>> {
164 self.upcast::<Node>()
165 .children()
166 .filter_map(DomRoot::downcast::<HTMLElement>)
167 .find(|html_element| {
168 html_element.upcast::<Element>().local_name() == &local_name!("summary")
169 })
170 }
171
172 fn update_shadow_tree_contents(&self, can_gc: CanGc) {
173 let shadow_tree = self.shadow_tree(can_gc);
174
175 if let Some(summary) = self.find_corresponding_summary_element() {
176 shadow_tree
177 .summary
178 .Assign(vec![ElementOrText::Element(DomRoot::upcast(summary))]);
179 }
180
181 let mut slottable_children = vec![];
182 for child in self.upcast::<Node>().children() {
183 if let Some(element) = child.downcast::<Element>() {
184 if element.local_name() == &local_name!("summary") {
185 continue;
186 }
187
188 slottable_children.push(ElementOrText::Element(DomRoot::from_ref(element)));
189 }
190
191 if let Some(text) = child.downcast::<Text>() {
192 slottable_children.push(ElementOrText::Text(DomRoot::from_ref(text)));
193 }
194 }
195 shadow_tree.descendants.Assign(slottable_children);
196 }
197
198 fn update_shadow_tree_styles(&self, can_gc: CanGc) {
199 let shadow_tree = self.shadow_tree(can_gc);
200
201 let value = if self.Open() {
202 "display: block;"
203 } else {
204 "display: none;"
207 };
208 shadow_tree
209 .descendants
210 .upcast::<Element>()
211 .set_string_attribute(&local_name!("style"), value.into(), can_gc);
212
213 let implicit_summary_list_item_style = if self.Open() {
217 "disclosure-open"
218 } else {
219 "disclosure-closed"
220 };
221 let implicit_summary_style = format!(
222 "display: list-item;
223 counter-increment: list-item 0;
224 list-style: {implicit_summary_list_item_style} inside;"
225 );
226 shadow_tree
227 .implicit_summary
228 .upcast::<Element>()
229 .set_string_attribute(&local_name!("style"), implicit_summary_style.into(), can_gc);
230 }
231}
232
233impl HTMLDetailsElementMethods<crate::DomTypeHolder> for HTMLDetailsElement {
234 make_bool_getter!(Open, "open");
236
237 make_bool_setter!(SetOpen, "open");
239}
240
241impl VirtualMethods for HTMLDetailsElement {
242 fn super_type(&self) -> Option<&dyn VirtualMethods> {
243 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
244 }
245
246 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
247 self.super_type()
248 .unwrap()
249 .attribute_mutated(attr, mutation, can_gc);
250
251 if attr.local_name() == &local_name!("open") {
252 self.update_shadow_tree_styles(can_gc);
253
254 let counter = self.toggle_counter.get() + 1;
255 self.toggle_counter.set(counter);
256
257 let this = Trusted::new(self);
258 self.owner_global()
259 .task_manager()
260 .dom_manipulation_task_source()
261 .queue(task!(details_notification_task_steps: move || {
262 let this = this.root();
263 if counter == this.toggle_counter.get() {
264 this.upcast::<EventTarget>().fire_event(atom!("toggle"), CanGc::note());
265 }
266 }));
267 self.upcast::<Node>().dirty(NodeDamage::Other);
268 }
269 }
270
271 fn children_changed(&self, mutation: &ChildrenMutation) {
272 self.super_type().unwrap().children_changed(mutation);
273
274 self.update_shadow_tree_contents(CanGc::note());
275 }
276
277 fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
278 self.super_type().unwrap().bind_to_tree(context, can_gc);
279
280 self.update_shadow_tree_contents(CanGc::note());
281 self.update_shadow_tree_styles(CanGc::note());
282 }
283}