Auto merge of #9586 - notriddle:details_ui, r=SimonSapin

Details ui

Requires a patch to rust-selectors, and doesn't currently recalculate the styles correctly (which is needed to make actual toggling work correctly).

Still trying to figure out what it takes to get style recalc to do what this needs.

<!-- Reviewable:start -->
[<img src="https://reviewable.io/review_button.svg" height="40" alt="Review on Reviewable"/>](https://reviewable.io/reviews/servo/servo/9586)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2016-03-20 13:27:59 +05:30
commit 090da52913
13 changed files with 294 additions and 27 deletions

View file

@ -676,7 +676,7 @@ impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode>
HTMLElementTypeId::HTMLInputElement))) || HTMLElementTypeId::HTMLInputElement))) ||
node.type_id() == Some(NodeTypeId::Element(ElementTypeId::HTMLElement( node.type_id() == Some(NodeTypeId::Element(ElementTypeId::HTMLElement(
HTMLElementTypeId::HTMLTextAreaElement))); 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 { node_is_input_or_text_area {
// A TextArea's text contents are displayed through the input text // A TextArea's text contents are displayed through the input text
// box, so don't construct them. // box, so don't construct them.
@ -1461,6 +1461,8 @@ impl<'a, ConcreteThreadSafeLayoutNode> PostorderNodeMutTraversal<ConcreteThreadS
PseudoElementType::Normal => display::T::inline, PseudoElementType::Normal => display::T::inline,
PseudoElementType::Before(display) => display, PseudoElementType::Before(display) => display,
PseudoElementType::After(display) => display, PseudoElementType::After(display) => display,
PseudoElementType::DetailsContent(display) => display,
PseudoElementType::DetailsSummary(display) => display,
}; };
(display, style.get_box().float, style.get_box().position) (display, style.get_box().float, style.get_box().position)
} }
@ -1646,6 +1648,8 @@ impl<ConcreteThreadSafeLayoutNode> NodeUtils for ConcreteThreadSafeLayoutNode
match self.get_pseudo_element_type() { match self.get_pseudo_element_type() {
PseudoElementType::Before(_) => &mut data.before_flow_construction_result, PseudoElementType::Before(_) => &mut data.before_flow_construction_result,
PseudoElementType::After (_) => &mut data.after_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, PseudoElementType::Normal => &mut data.flow_construction_result,
} }
} }

View file

@ -25,6 +25,10 @@ pub struct PrivateLayoutData {
pub after_flow_construction_result: ConstructionResult, pub after_flow_construction_result: ConstructionResult,
pub details_summary_flow_construction_result: ConstructionResult,
pub details_content_flow_construction_result: ConstructionResult,
/// Various flags. /// Various flags.
pub flags: LayoutDataFlags, pub flags: LayoutDataFlags,
} }
@ -38,6 +42,8 @@ impl PrivateLayoutData {
flow_construction_result: ConstructionResult::None, flow_construction_result: ConstructionResult::None,
before_flow_construction_result: ConstructionResult::None, before_flow_construction_result: ConstructionResult::None,
after_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(), flags: LayoutDataFlags::empty(),
} }
} }

View file

@ -2445,7 +2445,9 @@ impl Fragment {
match self.pseudo { match self.pseudo {
PseudoElementType::Normal => FragmentType::FragmentBody, PseudoElementType::Normal => FragmentType::FragmentBody,
PseudoElementType::Before(_) => FragmentType::BeforePseudoContent, 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 { let layer_type = match self.pseudo {
PseudoElementType::Normal => LayerType::FragmentBody, PseudoElementType::Normal => LayerType::FragmentBody,
PseudoElementType::Before(_) => LayerType::BeforePseudoContent, 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) LayerId::new_of_type(layer_type, self.node.id() as usize)
} }

View file

@ -536,6 +536,8 @@ pub fn process_resolved_style_request<N: LayoutNode>(
let layout_node = match pseudo { let layout_node = match pseudo {
&Some(PseudoElement::Before) => layout_node.get_before_pseudo(), &Some(PseudoElement::Before) => layout_node.get_before_pseudo(),
&Some(PseudoElement::After) => layout_node.get_after_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) _ => Some(layout_node)
}; };

View file

@ -615,6 +615,8 @@ pub enum PseudoElementType<T> {
Normal, Normal,
Before(T), Before(T),
After(T), After(T),
DetailsSummary(T),
DetailsContent(T),
} }
impl<T> PseudoElementType<T> { impl<T> PseudoElementType<T> {
@ -625,11 +627,20 @@ impl<T> PseudoElementType<T> {
} }
} }
pub fn is_before_or_after(&self) -> bool {
match *self {
PseudoElementType::Before(_) | PseudoElementType::After(_) => true,
_ => false,
}
}
pub fn strip(&self) -> PseudoElementType<()> { pub fn strip(&self) -> PseudoElementType<()> {
match *self { match *self {
PseudoElementType::Normal => PseudoElementType::Normal, PseudoElementType::Normal => PseudoElementType::Normal,
PseudoElementType::Before(_) => PseudoElementType::Before(()), PseudoElementType::Before(_) => PseudoElementType::Before(()),
PseudoElementType::After(_) => PseudoElementType::After(()), PseudoElementType::After(_) => PseudoElementType::After(()),
PseudoElementType::DetailsSummary(_) => PseudoElementType::DetailsSummary(()),
PseudoElementType::DetailsContent(_) => PseudoElementType::DetailsContent(()),
} }
} }
} }
@ -637,7 +648,7 @@ impl<T> PseudoElementType<T> {
/// A thread-safe version of `LayoutNode`, used during flow construction. This type of layout /// 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. /// 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<ConcreteThreadSafeLayoutNode = Self>; type ConcreteThreadSafeLayoutElement: ThreadSafeLayoutElement<ConcreteThreadSafeLayoutNode = Self>;
type ChildrenIterator: Iterator<Item = Self> + Sized; type ChildrenIterator: Iterator<Item = Self> + Sized;
@ -659,6 +670,9 @@ pub trait ThreadSafeLayoutNode : Clone + Copy + Sized {
/// Returns an iterator over this node's children. /// Returns an iterator over this node's children.
fn children(&self) -> Self::ChildrenIterator; 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. /// If this is an element, accesses the element data. Fails if this is not an element node.
#[inline] #[inline]
fn as_element(&self) -> Self::ConcreteThreadSafeLayoutElement; fn as_element(&self) -> Self::ConcreteThreadSafeLayoutElement;
@ -686,6 +700,38 @@ pub trait ThreadSafeLayoutNode : Clone + Copy + Sized {
}) })
} }
#[inline]
fn get_details_summary_pseudo(&self) -> Option<Self> {
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<Self> {
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. /// 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. /// 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() { let style = match self.get_pseudo_element_type() {
PseudoElementType::Before(_) => data.style_data.per_pseudo.get(&PseudoElement::Before), PseudoElementType::Before(_) => data.style_data.per_pseudo.get(&PseudoElement::Before),
PseudoElementType::After(_) => data.style_data.per_pseudo.get(&PseudoElement::After), 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(), PseudoElementType::Normal => data.style_data.style.as_ref(),
}; };
style.unwrap() style.unwrap()
@ -727,6 +775,13 @@ pub trait ThreadSafeLayoutNode : Clone + Copy + Sized {
PseudoElementType::After(_) => { PseudoElementType::After(_) => {
data.style_data.per_pseudo.remove(&PseudoElement::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 => { PseudoElementType::Normal => {
data.style_data.style = None; data.style_data.style = None;
} }
@ -798,6 +853,12 @@ pub trait ThreadSafeLayoutElement: Clone + Copy + Sized {
#[inline] #[inline]
fn get_attr<'a>(&'a self, namespace: &Namespace, name: &Atom) -> Option<&'a str>; 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)] #[derive(Copy, Clone)]
@ -808,7 +869,15 @@ pub struct ServoThreadSafeLayoutNode<'ln> {
pseudo: PseudoElementType<display::T>, pseudo: PseudoElementType<display::T>,
} }
impl<'a> PartialEq for ServoThreadSafeLayoutNode<'a> {
#[inline]
fn eq(&self, other: &ServoThreadSafeLayoutNode<'a>) -> bool {
self.node == other.node
}
}
impl<'ln> DangerousThreadSafeLayoutNode for ServoThreadSafeLayoutNode<'ln> { impl<'ln> DangerousThreadSafeLayoutNode for ServoThreadSafeLayoutNode<'ln> {
unsafe fn dangerous_first_child(&self) -> Option<Self> { unsafe fn dangerous_first_child(&self) -> Option<Self> {
self.get_jsmanaged().first_child_ref() self.get_jsmanaged().first_child_ref()
.map(|node| self.new_with_this_lifetime(&node)) .map(|node| self.new_with_this_lifetime(&node))
@ -1041,10 +1110,13 @@ impl<ConcreteNode> ThreadSafeLayoutNodeChildrenIterator<ConcreteNode>
pub fn new(parent: ConcreteNode) -> Self { pub fn new(parent: ConcreteNode) -> Self {
let first_child: Option<ConcreteNode> = match parent.get_pseudo_element_type() { let first_child: Option<ConcreteNode> = match parent.get_pseudo_element_type() {
PseudoElementType::Normal => { 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() } unsafe { parent.dangerous_first_child() }
}) })
}, },
PseudoElementType::DetailsContent(_) | PseudoElementType::DetailsSummary(_) => {
unsafe { parent.dangerous_first_child() }
},
_ => None, _ => None,
}; };
ThreadSafeLayoutNodeChildrenIterator { ThreadSafeLayoutNodeChildrenIterator {
@ -1058,29 +1130,74 @@ impl<ConcreteNode> Iterator for ThreadSafeLayoutNodeChildrenIterator<ConcreteNod
where ConcreteNode: DangerousThreadSafeLayoutNode { where ConcreteNode: DangerousThreadSafeLayoutNode {
type Item = ConcreteNode; type Item = ConcreteNode;
fn next(&mut self) -> Option<ConcreteNode> { fn next(&mut self) -> Option<ConcreteNode> {
let node = self.current_node.clone(); match self.parent_node.get_pseudo_element_type() {
if let Some(ref node) = node { PseudoElementType::Before(_) | PseudoElementType::After(_) => None,
self.current_node = match node.get_pseudo_element_type() {
PseudoElementType::Before(_) => { PseudoElementType::DetailsSummary(_) => {
match unsafe { self.parent_node.dangerous_first_child() } { let mut current_node = self.current_node.clone();
Some(first) => Some(first), loop {
None => self.parent_node.get_after_pseudo(), 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 => { self.current_node = node.and_then(|node| unsafe { node.dangerous_next_sibling() });
match unsafe { node.dangerous_next_sibling() } { node
Some(next) => Some(next), }
None => self.parent_node.get_after_pseudo(),
} PseudoElementType::Normal => {
}, let node = self.current_node.clone();
PseudoElementType::After(_) => { if let Some(ref node) = node {
None 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) 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 { pub enum TextContent {

View file

@ -26,6 +26,8 @@ pub trait SelectorImplExt : SelectorImpl + Sized {
pub enum PseudoElement { pub enum PseudoElement {
Before, Before,
After, After,
DetailsSummary,
DetailsContent,
} }
#[derive(Clone, Debug, PartialEq, Eq, HeapSizeOf, Hash)] #[derive(Clone, Debug, PartialEq, Eq, HeapSizeOf, Hash)]
@ -97,12 +99,22 @@ impl SelectorImpl for ServoSelectorImpl {
Ok(pseudo_class) Ok(pseudo_class)
} }
fn parse_pseudo_element(_context: &ParserContext, fn parse_pseudo_element(context: &ParserContext,
name: &str) -> Result<PseudoElement, ()> { name: &str) -> Result<PseudoElement, ()> {
use self::PseudoElement::*; use self::PseudoElement::*;
let pseudo_element = match_ignore_ascii_case! { name, let pseudo_element = match_ignore_ascii_case! { name,
"before" => Before, "before" => Before,
"after" => After, "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(()) _ => return Err(())
}; };
@ -122,6 +134,8 @@ impl SelectorImplExt for ServoSelectorImpl {
where F: FnMut(PseudoElement) { where F: FnMut(PseudoElement) {
fun(PseudoElement::Before); fun(PseudoElement::Before);
fun(PseudoElement::After); fun(PseudoElement::After);
fun(PseudoElement::DetailsContent);
fun(PseudoElement::DetailsSummary);
} }
#[inline] #[inline]

View file

@ -164,7 +164,6 @@ impl<Impl: SelectorImplExt> Stylist<Impl> {
if !stylesheet.is_effective_for_device(device) { if !stylesheet.is_effective_for_device(device) {
return; return;
} }
let mut rules_source_order = self.rules_source_order; let mut rules_source_order = self.rules_source_order;
// Take apart the StyleRule into individual Rules and insert // Take apart the StyleRule into individual Rules and insert

View file

@ -37,3 +37,24 @@ textarea {
-servo-overflow-clip-box: content-box; -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;
}

View file

@ -5416,6 +5416,30 @@
"url": "/_mozilla/mozilla/canvas/drawimage_html_image_9.html" "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": [ "mozilla/iframe/resize_after_load.html": [
{ {
"path": "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" "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": [ "mozilla/iframe/resize_after_load.html": [
{ {
"path": "mozilla/iframe/resize_after_load.html", "path": "mozilla/iframe/resize_after_load.html",

View file

@ -0,0 +1,9 @@
<!doctype html>
<meta charset="utf-8">
<title></title>
<link rel="match" href="details_ui_closed_ref.html">
<details>
<summary>Test</summary>
Contents
</details>

View file

@ -0,0 +1,12 @@
<!doctype html>
<meta charset="utf-8">
<title></title>
<style>
#s { display: list-item; list-style: disclosure-closed; margin-left: 40px }
#c { display: none }
</style>
<div id=d>
<div id=s>Test</div>
<div id=c>Contents</div>
</div>

View file

@ -0,0 +1,9 @@
<!doctype html>
<meta charset="utf-8">
<title></title>
<link rel="match" href="details_ui_opened_ref.html">
<details open>
<summary>Test</summary>
Contents
</details>

View file

@ -0,0 +1,12 @@
<!doctype html>
<meta charset="utf-8">
<title></title>
<style>
#s { display: list-item; list-style: disclosure-open; margin-left: 40px }
#c { margin-left: 40px }
</style>
<div id=d>
<div id=s>Test</div>
<div id=c>Contents</div>
</div>