From 9d9c5398a84c11fdabf991e20362f8225dd1ea02 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Mon, 25 Jan 2016 08:51:44 -0700 Subject: [PATCH] Get the fundamentals of the HTMLDetailsElement rendering stuff working. Still need to implement the style invalidation. Part of #9395 --- components/layout/construct.rs | 6 +- components/layout/data.rs | 6 + components/layout/fragment.rs | 8 +- components/layout/query.rs | 2 + components/layout/wrapper.rs | 171 +++++++++++++++--- components/style/selector_impl.rs | 16 +- components/style/selector_matching.rs | 1 - resources/servo.css | 21 +++ tests/wpt/mozilla/meta/MANIFEST.json | 48 +++++ .../tests/mozilla/details_ui_closed.html | 9 + .../tests/mozilla/details_ui_closed_ref.html | 12 ++ .../tests/mozilla/details_ui_opened.html | 9 + .../tests/mozilla/details_ui_opened_ref.html | 12 ++ 13 files changed, 294 insertions(+), 27 deletions(-) create mode 100644 tests/wpt/mozilla/tests/mozilla/details_ui_closed.html create mode 100644 tests/wpt/mozilla/tests/mozilla/details_ui_closed_ref.html create mode 100644 tests/wpt/mozilla/tests/mozilla/details_ui_opened.html create mode 100644 tests/wpt/mozilla/tests/mozilla/details_ui_opened_ref.html diff --git a/components/layout/construct.rs b/components/layout/construct.rs index 3f91bdad3d5..25988b7eb11 100644 --- a/components/layout/construct.rs +++ b/components/layout/construct.rs @@ -676,7 +676,7 @@ impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode> HTMLElementTypeId::HTMLInputElement))) || node.type_id() == Some(NodeTypeId::Element(ElementTypeId::HTMLElement( HTMLElementTypeId::HTMLTextAreaElement))); - if node.get_pseudo_element_type() != PseudoElementType::Normal || + if node.get_pseudo_element_type().is_before_or_after() || node_is_input_or_text_area { // A TextArea's text contents are displayed through the input text // box, so don't construct them. @@ -1461,6 +1461,8 @@ impl<'a, ConcreteThreadSafeLayoutNode> PostorderNodeMutTraversal display::T::inline, PseudoElementType::Before(display) => display, PseudoElementType::After(display) => display, + PseudoElementType::DetailsContent(display) => display, + PseudoElementType::DetailsSummary(display) => display, }; (display, style.get_box().float, style.get_box().position) } @@ -1646,6 +1648,8 @@ impl NodeUtils for ConcreteThreadSafeLayoutNode match self.get_pseudo_element_type() { PseudoElementType::Before(_) => &mut data.before_flow_construction_result, PseudoElementType::After (_) => &mut data.after_flow_construction_result, + PseudoElementType::DetailsSummary(_) => &mut data.details_summary_flow_construction_result, + PseudoElementType::DetailsContent(_) => &mut data.details_content_flow_construction_result, PseudoElementType::Normal => &mut data.flow_construction_result, } } diff --git a/components/layout/data.rs b/components/layout/data.rs index b11e4ebfd05..f582ec71ffb 100644 --- a/components/layout/data.rs +++ b/components/layout/data.rs @@ -25,6 +25,10 @@ pub struct PrivateLayoutData { pub after_flow_construction_result: ConstructionResult, + pub details_summary_flow_construction_result: ConstructionResult, + + pub details_content_flow_construction_result: ConstructionResult, + /// Various flags. pub flags: LayoutDataFlags, } @@ -38,6 +42,8 @@ impl PrivateLayoutData { flow_construction_result: ConstructionResult::None, before_flow_construction_result: ConstructionResult::None, after_flow_construction_result: ConstructionResult::None, + details_summary_flow_construction_result: ConstructionResult::None, + details_content_flow_construction_result: ConstructionResult::None, flags: LayoutDataFlags::empty(), } } diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index 0a873e90835..4ab2858496c 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -2445,7 +2445,9 @@ impl Fragment { match self.pseudo { PseudoElementType::Normal => FragmentType::FragmentBody, PseudoElementType::Before(_) => FragmentType::BeforePseudoContent, - PseudoElementType::After(_) => FragmentType::AfterPseudoContent + PseudoElementType::After(_) => FragmentType::AfterPseudoContent, + PseudoElementType::DetailsSummary(_) => FragmentType::FragmentBody, + PseudoElementType::DetailsContent(_) => FragmentType::FragmentBody, } } @@ -2453,7 +2455,9 @@ impl Fragment { let layer_type = match self.pseudo { PseudoElementType::Normal => LayerType::FragmentBody, PseudoElementType::Before(_) => LayerType::BeforePseudoContent, - PseudoElementType::After(_) => LayerType::AfterPseudoContent + PseudoElementType::After(_) => LayerType::AfterPseudoContent, + PseudoElementType::DetailsSummary(_) => LayerType::FragmentBody, + PseudoElementType::DetailsContent(_) => LayerType::FragmentBody, }; LayerId::new_of_type(layer_type, self.node.id() as usize) } diff --git a/components/layout/query.rs b/components/layout/query.rs index 9bb62d5915a..9d1b9ed1db3 100644 --- a/components/layout/query.rs +++ b/components/layout/query.rs @@ -536,6 +536,8 @@ pub fn process_resolved_style_request( let layout_node = match pseudo { &Some(PseudoElement::Before) => layout_node.get_before_pseudo(), &Some(PseudoElement::After) => layout_node.get_after_pseudo(), + &Some(PseudoElement::DetailsSummary) => layout_node.get_details_summary_pseudo(), + &Some(PseudoElement::DetailsContent) => layout_node.get_details_content_pseudo(), _ => Some(layout_node) }; diff --git a/components/layout/wrapper.rs b/components/layout/wrapper.rs index 92b46cb41ec..30ca65f6e39 100644 --- a/components/layout/wrapper.rs +++ b/components/layout/wrapper.rs @@ -615,6 +615,8 @@ pub enum PseudoElementType { Normal, Before(T), After(T), + DetailsSummary(T), + DetailsContent(T), } impl PseudoElementType { @@ -625,11 +627,20 @@ impl PseudoElementType { } } + pub fn is_before_or_after(&self) -> bool { + match *self { + PseudoElementType::Before(_) | PseudoElementType::After(_) => true, + _ => false, + } + } + pub fn strip(&self) -> PseudoElementType<()> { match *self { PseudoElementType::Normal => PseudoElementType::Normal, PseudoElementType::Before(_) => PseudoElementType::Before(()), PseudoElementType::After(_) => PseudoElementType::After(()), + PseudoElementType::DetailsSummary(_) => PseudoElementType::DetailsSummary(()), + PseudoElementType::DetailsContent(_) => PseudoElementType::DetailsContent(()), } } } @@ -637,7 +648,7 @@ impl PseudoElementType { /// A thread-safe version of `LayoutNode`, used during flow construction. This type of layout /// node does not allow any parents or siblings of nodes to be accessed, to avoid races. -pub trait ThreadSafeLayoutNode : Clone + Copy + Sized { +pub trait ThreadSafeLayoutNode : Clone + Copy + Sized + PartialEq { type ConcreteThreadSafeLayoutElement: ThreadSafeLayoutElement; type ChildrenIterator: Iterator + Sized; @@ -659,6 +670,9 @@ pub trait ThreadSafeLayoutNode : Clone + Copy + Sized { /// Returns an iterator over this node's children. fn children(&self) -> Self::ChildrenIterator; + #[inline] + fn is_element(&self) -> bool { if let Some(NodeTypeId::Element(_)) = self.type_id() { true } else { false } } + /// If this is an element, accesses the element data. Fails if this is not an element node. #[inline] fn as_element(&self) -> Self::ConcreteThreadSafeLayoutElement; @@ -686,6 +700,38 @@ pub trait ThreadSafeLayoutNode : Clone + Copy + Sized { }) } + #[inline] + fn get_details_summary_pseudo(&self) -> Option { + if self.is_element() && + self.as_element().get_local_name() == &atom!("details") && + self.as_element().get_namespace() == &ns!(html) { + self.borrow_layout_data().unwrap() + .style_data.per_pseudo + .get(&PseudoElement::DetailsSummary) + .map(|style| { + self.with_pseudo(PseudoElementType::DetailsSummary(style.get_box().display)) + }) + } else { + None + } + } + + #[inline] + fn get_details_content_pseudo(&self) -> Option { + if self.is_element() && + self.as_element().get_local_name() == &atom!("details") && + self.as_element().get_namespace() == &ns!(html) { + self.borrow_layout_data().unwrap() + .style_data.per_pseudo + .get(&PseudoElement::DetailsContent) + .map(|style| { + self.with_pseudo(PseudoElementType::DetailsContent(style.get_box().display)) + }) + } else { + None + } + } + /// Borrows the layout data immutably. Fails on a conflicting borrow. /// /// TODO(pcwalton): Make this private. It will let us avoid borrow flag checks in some cases. @@ -708,6 +754,8 @@ pub trait ThreadSafeLayoutNode : Clone + Copy + Sized { let style = match self.get_pseudo_element_type() { PseudoElementType::Before(_) => data.style_data.per_pseudo.get(&PseudoElement::Before), PseudoElementType::After(_) => data.style_data.per_pseudo.get(&PseudoElement::After), + PseudoElementType::DetailsSummary(_) => data.style_data.per_pseudo.get(&PseudoElement::DetailsSummary), + PseudoElementType::DetailsContent(_) => data.style_data.per_pseudo.get(&PseudoElement::DetailsContent), PseudoElementType::Normal => data.style_data.style.as_ref(), }; style.unwrap() @@ -727,6 +775,13 @@ pub trait ThreadSafeLayoutNode : Clone + Copy + Sized { PseudoElementType::After(_) => { data.style_data.per_pseudo.remove(&PseudoElement::After); } + PseudoElementType::DetailsSummary(_) => { + data.style_data.per_pseudo.remove(&PseudoElement::DetailsSummary); + } + PseudoElementType::DetailsContent(_) => { + data.style_data.per_pseudo.remove(&PseudoElement::DetailsContent); + } + PseudoElementType::Normal => { data.style_data.style = None; } @@ -798,6 +853,12 @@ pub trait ThreadSafeLayoutElement: Clone + Copy + Sized { #[inline] fn get_attr<'a>(&'a self, namespace: &Namespace, name: &Atom) -> Option<&'a str>; + + #[inline] + fn get_local_name(&self) -> &Atom; + + #[inline] + fn get_namespace(&self) -> &Namespace; } #[derive(Copy, Clone)] @@ -808,7 +869,15 @@ pub struct ServoThreadSafeLayoutNode<'ln> { pseudo: PseudoElementType, } +impl<'a> PartialEq for ServoThreadSafeLayoutNode<'a> { + #[inline] + fn eq(&self, other: &ServoThreadSafeLayoutNode<'a>) -> bool { + self.node == other.node + } +} + impl<'ln> DangerousThreadSafeLayoutNode for ServoThreadSafeLayoutNode<'ln> { + unsafe fn dangerous_first_child(&self) -> Option { self.get_jsmanaged().first_child_ref() .map(|node| self.new_with_this_lifetime(&node)) @@ -1041,10 +1110,13 @@ impl ThreadSafeLayoutNodeChildrenIterator pub fn new(parent: ConcreteNode) -> Self { let first_child: Option = match parent.get_pseudo_element_type() { PseudoElementType::Normal => { - parent.get_before_pseudo().or_else(|| { + parent.get_before_pseudo().or_else(|| parent.get_details_summary_pseudo()).or_else(|| { unsafe { parent.dangerous_first_child() } }) }, + PseudoElementType::DetailsContent(_) | PseudoElementType::DetailsSummary(_) => { + unsafe { parent.dangerous_first_child() } + }, _ => None, }; ThreadSafeLayoutNodeChildrenIterator { @@ -1058,29 +1130,74 @@ impl Iterator for ThreadSafeLayoutNodeChildrenIterator Option { - let node = self.current_node.clone(); + match self.parent_node.get_pseudo_element_type() { - if let Some(ref node) = node { - self.current_node = match node.get_pseudo_element_type() { - PseudoElementType::Before(_) => { - match unsafe { self.parent_node.dangerous_first_child() } { - Some(first) => Some(first), - None => self.parent_node.get_after_pseudo(), + PseudoElementType::Before(_) | PseudoElementType::After(_) => None, + + PseudoElementType::DetailsSummary(_) => { + let mut current_node = self.current_node.clone(); + loop { + let next_node = if let Some(ref node) = current_node { + if node.is_element() && + node.as_element().get_local_name() == &atom!("summary") && + node.as_element().get_namespace() == &ns!(html) { + self.current_node = None; + return Some(node.clone()); + } + unsafe { node.dangerous_next_sibling() } + } else { + self.current_node = None; + return None + }; + current_node = next_node; + } + } + + PseudoElementType::DetailsContent(_) => { + let node = self.current_node.clone(); + let node = node.and_then(|node| { + if node.is_element() && + node.as_element().get_local_name() == &atom!("summary") && + node.as_element().get_namespace() == &ns!(html) { + unsafe { node.dangerous_next_sibling() } + } else { + Some(node) } - }, - PseudoElementType::Normal => { - match unsafe { node.dangerous_next_sibling() } { - Some(next) => Some(next), - None => self.parent_node.get_after_pseudo(), - } - }, - PseudoElementType::After(_) => { - None - }, - }; + }); + self.current_node = node.and_then(|node| unsafe { node.dangerous_next_sibling() }); + node + } + + PseudoElementType::Normal => { + let node = self.current_node.clone(); + if let Some(ref node) = node { + self.current_node = match node.get_pseudo_element_type() { + PseudoElementType::Before(_) => { + let first = self.parent_node.get_details_summary_pseudo().or_else(|| unsafe { + self.parent_node.dangerous_first_child() + }); + match first { + Some(first) => Some(first), + None => self.parent_node.get_after_pseudo(), + } + }, + PseudoElementType::Normal => { + match unsafe { node.dangerous_next_sibling() } { + Some(next) => Some(next), + None => self.parent_node.get_after_pseudo(), + } + }, + PseudoElementType::DetailsSummary(_) => self.parent_node.get_details_content_pseudo(), + PseudoElementType::DetailsContent(_) => self.parent_node.get_after_pseudo(), + PseudoElementType::After(_) => { + None + }, + }; + } + node + } + } - - node } } @@ -1099,6 +1216,16 @@ impl<'le> ThreadSafeLayoutElement for ServoThreadSafeLayoutElement<'le> { self.element.get_attr_val_for_layout(namespace, name) } } + + #[inline] + fn get_local_name(&self) -> &Atom { + self.element.local_name() + } + + #[inline] + fn get_namespace(&self) -> &Namespace { + self.element.namespace() + } } pub enum TextContent { diff --git a/components/style/selector_impl.rs b/components/style/selector_impl.rs index 77aa23ca808..7fcdab1cc6b 100644 --- a/components/style/selector_impl.rs +++ b/components/style/selector_impl.rs @@ -26,6 +26,8 @@ pub trait SelectorImplExt : SelectorImpl + Sized { pub enum PseudoElement { Before, After, + DetailsSummary, + DetailsContent, } #[derive(Clone, Debug, PartialEq, Eq, HeapSizeOf, Hash)] @@ -97,12 +99,22 @@ impl SelectorImpl for ServoSelectorImpl { Ok(pseudo_class) } - fn parse_pseudo_element(_context: &ParserContext, + fn parse_pseudo_element(context: &ParserContext, name: &str) -> Result { use self::PseudoElement::*; let pseudo_element = match_ignore_ascii_case! { name, "before" => Before, "after" => After, + "-servo-details-summary" => if context.in_user_agent_stylesheet { + DetailsSummary + } else { + return Err(()) + }, + "-servo-details-content" => if context.in_user_agent_stylesheet { + DetailsContent + } else { + return Err(()) + }, _ => return Err(()) }; @@ -122,6 +134,8 @@ impl SelectorImplExt for ServoSelectorImpl { where F: FnMut(PseudoElement) { fun(PseudoElement::Before); fun(PseudoElement::After); + fun(PseudoElement::DetailsContent); + fun(PseudoElement::DetailsSummary); } #[inline] diff --git a/components/style/selector_matching.rs b/components/style/selector_matching.rs index abcfd65dfb8..b2ac93636f5 100644 --- a/components/style/selector_matching.rs +++ b/components/style/selector_matching.rs @@ -164,7 +164,6 @@ impl Stylist { if !stylesheet.is_effective_for_device(device) { return; } - let mut rules_source_order = self.rules_source_order; // Take apart the StyleRule into individual Rules and insert diff --git a/resources/servo.css b/resources/servo.css index dbd3f30e72a..5f0bcdf695f 100644 --- a/resources/servo.css +++ b/resources/servo.css @@ -37,3 +37,24 @@ textarea { -servo-overflow-clip-box: content-box; } +// https://html.spec.whatwg.org/multipage/rendering.html#the-details-and-summary-elements +details { + display: block; +} +details::-servo-details-summary { + margin-left: 40px; + display: list-item; + list-style: disclosure-closed; +} +details[open]::-servo-details-summary { + list-style: disclosure-open; +} +details::-servo-details-content { + margin-left: 40px; + overflow: hidden; + display: none; +} +details[open]::-servo-details-content { + display: block; +} + diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index deee3a3eeb2..34f6556c9b8 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -5416,6 +5416,30 @@ "url": "/_mozilla/mozilla/canvas/drawimage_html_image_9.html" } ], + "mozilla/details_ui_closed.html": [ + { + "path": "mozilla/details_ui_closed.html", + "references": [ + [ + "/_mozilla/mozilla/details_ui_closed_ref.html", + "==" + ] + ], + "url": "/_mozilla/mozilla/details_ui_closed.html" + } + ], + "mozilla/details_ui_opened.html": [ + { + "path": "mozilla/details_ui_opened.html", + "references": [ + [ + "/_mozilla/mozilla/details_ui_opened_ref.html", + "==" + ] + ], + "url": "/_mozilla/mozilla/details_ui_opened.html" + } + ], "mozilla/iframe/resize_after_load.html": [ { "path": "mozilla/iframe/resize_after_load.html", @@ -11652,6 +11676,30 @@ "url": "/_mozilla/mozilla/canvas/drawimage_html_image_9.html" } ], + "mozilla/details_ui_closed.html": [ + { + "path": "mozilla/details_ui_closed.html", + "references": [ + [ + "/_mozilla/mozilla/details_ui_closed_ref.html", + "==" + ] + ], + "url": "/_mozilla/mozilla/details_ui_closed.html" + } + ], + "mozilla/details_ui_opened.html": [ + { + "path": "mozilla/details_ui_opened.html", + "references": [ + [ + "/_mozilla/mozilla/details_ui_opened_ref.html", + "==" + ] + ], + "url": "/_mozilla/mozilla/details_ui_opened.html" + } + ], "mozilla/iframe/resize_after_load.html": [ { "path": "mozilla/iframe/resize_after_load.html", diff --git a/tests/wpt/mozilla/tests/mozilla/details_ui_closed.html b/tests/wpt/mozilla/tests/mozilla/details_ui_closed.html new file mode 100644 index 00000000000..cf5c046a983 --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/details_ui_closed.html @@ -0,0 +1,9 @@ + + + + +
+Test +Contents +
+ diff --git a/tests/wpt/mozilla/tests/mozilla/details_ui_closed_ref.html b/tests/wpt/mozilla/tests/mozilla/details_ui_closed_ref.html new file mode 100644 index 00000000000..b7db1ce810c --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/details_ui_closed_ref.html @@ -0,0 +1,12 @@ + + + + +
+
Test
+
Contents
+
+ diff --git a/tests/wpt/mozilla/tests/mozilla/details_ui_opened.html b/tests/wpt/mozilla/tests/mozilla/details_ui_opened.html new file mode 100644 index 00000000000..10e65e98d81 --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/details_ui_opened.html @@ -0,0 +1,9 @@ + + + + +
+Test +Contents +
+ diff --git a/tests/wpt/mozilla/tests/mozilla/details_ui_opened_ref.html b/tests/wpt/mozilla/tests/mozilla/details_ui_opened_ref.html new file mode 100644 index 00000000000..57ae10d082a --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/details_ui_opened_ref.html @@ -0,0 +1,12 @@ + + + + +
+
Test
+
Contents
+
+