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))) ||
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<ConcreteThreadS
PseudoElementType::Normal => 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<ConcreteThreadSafeLayoutNode> 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,
}
}

View file

@ -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(),
}
}

View file

@ -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)
}

View file

@ -536,6 +536,8 @@ pub fn process_resolved_style_request<N: LayoutNode>(
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)
};

View file

@ -615,6 +615,8 @@ pub enum PseudoElementType<T> {
Normal,
Before(T),
After(T),
DetailsSummary(T),
DetailsContent(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<()> {
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<T> PseudoElementType<T> {
/// 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<ConcreteThreadSafeLayoutNode = Self>;
type ChildrenIterator: Iterator<Item = Self> + 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<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.
///
/// 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<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> {
unsafe fn dangerous_first_child(&self) -> Option<Self> {
self.get_jsmanaged().first_child_ref()
.map(|node| self.new_with_this_lifetime(&node))
@ -1041,10 +1110,13 @@ impl<ConcreteNode> ThreadSafeLayoutNodeChildrenIterator<ConcreteNode>
pub fn new(parent: ConcreteNode) -> Self {
let first_child: Option<ConcreteNode> = 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,12 +1130,53 @@ impl<ConcreteNode> Iterator for ThreadSafeLayoutNodeChildrenIterator<ConcreteNod
where ConcreteNode: DangerousThreadSafeLayoutNode {
type Item = ConcreteNode;
fn next(&mut self) -> Option<ConcreteNode> {
let node = self.current_node.clone();
match self.parent_node.get_pseudo_element_type() {
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)
}
});
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(_) => {
match unsafe { self.parent_node.dangerous_first_child() } {
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(),
}
@ -1074,14 +1187,18 @@ impl<ConcreteNode> Iterator for ThreadSafeLayoutNodeChildrenIterator<ConcreteNod
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
}
}
}
}
/// A wrapper around elements that ensures layout can only ever access safe properties and cannot
@ -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 {

View file

@ -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<PseudoElement, ()> {
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]

View file

@ -164,7 +164,6 @@ impl<Impl: SelectorImplExt> Stylist<Impl> {
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

View file

@ -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;
}

View file

@ -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",

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>