script/dom/html/
htmldetailselement.rs1use std::cell::{Cell, Ref};
6
7use dom_struct::dom_struct;
8use html5ever::{LocalName, Prefix, local_name};
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, Element};
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 = HTMLSlotElement::new(local_name!("slot"), None, &document, None, can_gc);
109 root.upcast::<Node>()
110 .AppendChild(summary.upcast::<Node>(), can_gc)
111 .unwrap();
112
113 let fallback_summary =
114 HTMLElement::new(local_name!("summary"), None, &document, None, can_gc);
115 fallback_summary
116 .upcast::<Node>()
117 .set_text_content_for_element(Some(DEFAULT_SUMMARY.into()), can_gc);
118 summary
119 .upcast::<Node>()
120 .AppendChild(fallback_summary.upcast::<Node>(), can_gc)
121 .unwrap();
122
123 let descendants = HTMLSlotElement::new(local_name!("slot"), None, &document, None, can_gc);
124 root.upcast::<Node>()
125 .AppendChild(descendants.upcast::<Node>(), can_gc)
126 .unwrap();
127
128 let _ = self.shadow_tree.borrow_mut().insert(ShadowTree {
129 summary: summary.as_traced(),
130 descendants: descendants.as_traced(),
131 implicit_summary: fallback_summary.as_traced(),
132 });
133 self.upcast::<Node>()
134 .dirty(crate::dom::node::NodeDamage::Other);
135 }
136
137 pub(crate) fn find_corresponding_summary_element(&self) -> Option<DomRoot<HTMLElement>> {
138 self.upcast::<Node>()
139 .children()
140 .filter_map(DomRoot::downcast::<HTMLElement>)
141 .find(|html_element| {
142 html_element.upcast::<Element>().local_name() == &local_name!("summary")
143 })
144 }
145
146 fn update_shadow_tree_contents(&self, can_gc: CanGc) {
147 let shadow_tree = self.shadow_tree(can_gc);
148
149 if let Some(summary) = self.find_corresponding_summary_element() {
150 shadow_tree
151 .summary
152 .Assign(vec![ElementOrText::Element(DomRoot::upcast(summary))]);
153 }
154
155 let mut slottable_children = vec![];
156 for child in self.upcast::<Node>().children() {
157 if let Some(element) = child.downcast::<Element>() {
158 if element.local_name() == &local_name!("summary") {
159 continue;
160 }
161
162 slottable_children.push(ElementOrText::Element(DomRoot::from_ref(element)));
163 }
164
165 if let Some(text) = child.downcast::<Text>() {
166 slottable_children.push(ElementOrText::Text(DomRoot::from_ref(text)));
167 }
168 }
169 shadow_tree.descendants.Assign(slottable_children);
170 }
171
172 fn update_shadow_tree_styles(&self, can_gc: CanGc) {
173 let shadow_tree = self.shadow_tree(can_gc);
174
175 let value = if self.Open() {
176 "display: block;"
177 } else {
178 "display: none;"
181 };
182 shadow_tree
183 .descendants
184 .upcast::<Element>()
185 .set_string_attribute(&local_name!("style"), value.into(), can_gc);
186
187 let implicit_summary_list_item_style = if self.Open() {
191 "disclosure-open"
192 } else {
193 "disclosure-closed"
194 };
195 let implicit_summary_style = format!(
196 "display: list-item;
197 counter-increment: list-item 0;
198 list-style: {implicit_summary_list_item_style} inside;"
199 );
200 shadow_tree
201 .implicit_summary
202 .upcast::<Element>()
203 .set_string_attribute(&local_name!("style"), implicit_summary_style.into(), can_gc);
204 }
205}
206
207impl HTMLDetailsElementMethods<crate::DomTypeHolder> for HTMLDetailsElement {
208 make_bool_getter!(Open, "open");
210
211 make_bool_setter!(SetOpen, "open");
213}
214
215impl VirtualMethods for HTMLDetailsElement {
216 fn super_type(&self) -> Option<&dyn VirtualMethods> {
217 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
218 }
219
220 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
221 self.super_type()
222 .unwrap()
223 .attribute_mutated(attr, mutation, can_gc);
224
225 if attr.local_name() == &local_name!("open") {
226 self.update_shadow_tree_styles(can_gc);
227
228 let counter = self.toggle_counter.get() + 1;
229 self.toggle_counter.set(counter);
230
231 let this = Trusted::new(self);
232 self.owner_global()
233 .task_manager()
234 .dom_manipulation_task_source()
235 .queue(task!(details_notification_task_steps: move || {
236 let this = this.root();
237 if counter == this.toggle_counter.get() {
238 this.upcast::<EventTarget>().fire_event(atom!("toggle"), CanGc::note());
239 }
240 }));
241 self.upcast::<Node>().dirty(NodeDamage::Other);
242 }
243 }
244
245 fn children_changed(&self, mutation: &ChildrenMutation) {
246 self.super_type().unwrap().children_changed(mutation);
247
248 self.update_shadow_tree_contents(CanGc::note());
249 }
250
251 fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
252 self.super_type().unwrap().bind_to_tree(context, can_gc);
253
254 self.update_shadow_tree_contents(CanGc::note());
255 self.update_shadow_tree_styles(CanGc::note());
256 }
257}