diff --git a/components/layout/stylesheets/details.css b/components/layout/stylesheets/details.css index db3ccf3de47..d7ff253f501 100644 --- a/components/layout/stylesheets/details.css +++ b/components/layout/stylesheets/details.css @@ -8,11 +8,11 @@ slot[id="internal-contents-slot"] { display: block; } -summary[id="internal-fallback-summary"] { +:host summary:first-of-type { display: list-item; counter-increment: list-item 0; list-style: disclosure-closed inside; } -:host([open]) summary[id="internal-fallback-summary"] { +:host([open]) summary:first-of-type { list-style-type: disclosure-open; } diff --git a/components/script/dom/htmldetailselement.rs b/components/script/dom/htmldetailselement.rs index 9e1b18c3b5f..c57fec5ed6e 100644 --- a/components/script/dom/htmldetailselement.rs +++ b/components/script/dom/htmldetailselement.rs @@ -45,8 +45,9 @@ const DEFAULT_SUMMARY: &str = "Details"; #[derive(Clone, JSTraceable, MallocSizeOf)] #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] struct ShadowTree { - summary: Dom, - descendants: Dom, + summary_slot: Dom, + descendants_slot: Dom, + fallback_summary: Dom, } #[dom_struct] @@ -103,6 +104,7 @@ impl HTMLDetailsElement { .expect("UA shadow tree was not created") } + /// fn create_shadow_tree(&self, can_gc: CanGc) { let document = self.owner_document(); let root = self @@ -118,6 +120,50 @@ impl HTMLDetailsElement { ) .expect("Attaching UA shadow root failed"); + // > The details element is expected to have an internal shadow tree with three child elements: + // > 1. The first child element is a slot that is expected to take the details element's first summary + // > element child, if any. + let summary_slot = HTMLSlotElement::new(local_name!("slot"), None, &document, None, can_gc); + summary_slot.upcast::().set_attribute( + &local_name!("id"), + AttrValue::from_atomic("internal-summary-slot".to_owned()), + can_gc, + ); + root.upcast::() + .AppendChild(summary_slot.upcast::(), can_gc) + .unwrap(); + + // > This element has a single child summary element called the default summary which has + // > text content that is implementation-defined (and probably locale-specific). + let fallback_summary = + HTMLElement::new(local_name!("summary"), None, &document, None, can_gc); + fallback_summary.upcast::().set_attribute( + &local_name!("id"), + AttrValue::from_atomic("internal-fallback-summary".to_owned()), + can_gc, + ); + fallback_summary + .upcast::() + .SetTextContent(Some(DEFAULT_SUMMARY.into()), can_gc); + summary_slot + .upcast::() + .AppendChild(fallback_summary.upcast::(), can_gc) + .unwrap(); + + // > 2. The second child element is a slot that is expected to take the details element's + // > remaining descendants, if any. This element has no contents. + let descendants_slot = + HTMLSlotElement::new(local_name!("slot"), None, &document, None, can_gc); + descendants_slot.upcast::().set_attribute( + &local_name!("id"), + AttrValue::from_atomic("internal-contents-slot".to_owned()), + can_gc, + ); + root.upcast::() + .AppendChild(descendants_slot.upcast::(), can_gc) + .unwrap(); + + // > 3. The third child element is either a link or style element with the following styles for the default summary: let link_element = HTMLLinkElement::new( local_name!("link"), None, @@ -135,6 +181,8 @@ impl HTMLDetailsElement { .AppendChild(link_element.upcast::(), can_gc) .unwrap(); + // TODO(stevennovaryo): We need a mechanism for parsing and storing a stylesheet as + // we shouldn't reparse this each time we access the element. let details_stylesheet = parse_details_stylesheet( link_element .upcast::() @@ -143,45 +191,11 @@ impl HTMLDetailsElement { ); link_element.set_stylesheet(details_stylesheet.unwrap()); - let summary_slot = HTMLSlotElement::new(local_name!("slot"), None, &document, None, can_gc); - summary_slot.upcast::().set_attribute( - &local_name!("id"), - AttrValue::from_atomic("internal-summary-slot".to_owned()), - can_gc, - ); - root.upcast::() - .AppendChild(summary_slot.upcast::(), can_gc) - .unwrap(); - - let fallback_summary = - HTMLElement::new(local_name!("summary"), None, &document, None, can_gc); - fallback_summary.upcast::().set_attribute( - &local_name!("id"), - AttrValue::from_atomic("internal-fallback-summary".to_owned()), - can_gc, - ); - fallback_summary - .upcast::() - .SetTextContent(Some(DEFAULT_SUMMARY.into()), can_gc); - summary_slot - .upcast::() - .AppendChild(fallback_summary.upcast::(), can_gc) - .unwrap(); - - let descendants_slot = - HTMLSlotElement::new(local_name!("slot"), None, &document, None, can_gc); - descendants_slot.upcast::().set_attribute( - &local_name!("id"), - AttrValue::from_atomic("internal-contents-slot".to_owned()), - can_gc, - ); - root.upcast::() - .AppendChild(descendants_slot.upcast::(), can_gc) - .unwrap(); let _ = self.shadow_tree.borrow_mut().insert(ShadowTree { - summary: summary_slot.as_traced(), - descendants: descendants_slot.as_traced(), + summary_slot: summary_slot.as_traced(), + descendants_slot: descendants_slot.as_traced(), + fallback_summary: fallback_summary.as_traced(), }); self.upcast::() .dirty(crate::dom::node::NodeDamage::OtherNodeDamage); @@ -199,16 +213,18 @@ impl HTMLDetailsElement { fn update_shadow_tree_contents(&self, can_gc: CanGc) { let shadow_tree = self.shadow_tree(can_gc); - if let Some(summary) = self.find_corresponding_summary_element() { - shadow_tree - .summary - .Assign(vec![ElementOrText::Element(DomRoot::upcast(summary))]); - } - + let mut discovered_first_summary_element = false; let mut slottable_children = vec![]; + + // Assign all children to its corresponding slots. for child in self.upcast::().children() { if let Some(element) = child.downcast::() { - if element.local_name() == &local_name!("summary") { + // Assign the first summary element to the summary slot. + if element.local_name() == &local_name!("summary") && !discovered_first_summary_element { + shadow_tree + .summary_slot + .Assign(vec![ElementOrText::Element(DomRoot::from_ref(element))]); + discovered_first_summary_element = true; continue; } @@ -219,7 +235,16 @@ impl HTMLDetailsElement { slottable_children.push(ElementOrText::Text(DomRoot::from_ref(text))); } } - shadow_tree.descendants.Assign(slottable_children); + shadow_tree.descendants_slot.Assign(slottable_children); + + // If there are no summary element, assign fallback summary instead. + if !discovered_first_summary_element { + shadow_tree + .summary_slot + .Assign(vec![ElementOrText::Element(DomRoot::upcast( + shadow_tree.fallback_summary.as_rooted() + ))]); + } } }