Skip to content

Commit

Permalink
Implement :nth-child, :nth-last-child, :nth-of-type, :nth-last-of-typ…
Browse files Browse the repository at this point in the history
…e, :first-of-type, :last-of-type and :only-of-type Selectors Level 3 pseudo-classes
  • Loading branch information
therealglazou committed Nov 28, 2013
1 parent 238741f commit 991d4d8
Show file tree
Hide file tree
Showing 17 changed files with 1,207 additions and 6 deletions.
76 changes: 73 additions & 3 deletions src/components/style/selector_matching.rs
Expand Up @@ -489,14 +489,24 @@ fn matches_simple_selector<N: TreeNode<T>, T: TreeNodeRefAsElement<N, E>, E: Ele
}
}
}
FirstChild => matches_first_child(element),

FirstChild => matches_first_child(element),
LastChild => matches_last_child(element),

OnlyChild => matches_first_child(element) && matches_last_child(element),
OnlyChild => matches_first_child(element) &&
matches_last_child(element),

Root => matches_root(element),

NthChild(a, b) => matches_generic_nth_child(element, a, b, false, false),
NthLastChild(a, b) => matches_generic_nth_child(element, a, b, false, true),
NthOfType(a, b) => matches_generic_nth_child(element, a, b, true, false),
NthLastOfType(a, b) => matches_generic_nth_child(element, a, b, true, true),

FirstOfType => matches_generic_nth_child(element, 0, 1, true, false),
LastOfType => matches_generic_nth_child(element, 0, 1, true, true),
OnlyOfType => matches_generic_nth_child(element, 0, 1, true, false) &&
matches_generic_nth_child(element, 0, 1, true, true),

Negation(ref negated) => {
!negated.iter().all(|s| matches_simple_selector(s, element))
},
Expand All @@ -510,6 +520,66 @@ fn url_is_visited(_url: &str) -> bool {
false
}

#[inline]
fn matches_generic_nth_child<N: TreeNode<T>, T: TreeNodeRefAsElement<N, E>, E: ElementLike>(
element: &T, a: i32, b: i32, isOfType: bool, isFromEnd: bool) -> bool {
let mut node = element.clone();
// fail if we can't find a parent or if the node is the root element
// of the document (Cf. Selectors Level 3)
match node.node().parent_node() {
Some(parent) => if parent.is_document() {
return false;
},
None => return false
};

let mut local_name = "";
if isOfType {
// FIXME this is wrong
// TODO when the DOM supports namespaces on elements
do element.with_imm_element_like |element: &E| {
local_name = element.get_local_name();
}
}

let mut index = 1;
loop {
if isFromEnd {
match node.node().next_sibling() {
None => break,
Some(next_sibling) => node = next_sibling
}
} else {
match node.node().prev_sibling() {
None => break,
Some(prev_sibling) => node = prev_sibling
}
}

if node.is_element() {
if isOfType {
// FIXME this is wrong
// TODO when the DOM supports namespaces on elements
do node.with_imm_element_like |node: &E| {
if local_name == node.get_local_name() {
index += 1;
}
}
} else {
index += 1;
}
}

}

if a == 0 {
return b == index;
}

let n: i32 = (((index as f32) - (b as f32)) / (a as f32)) as i32;
n >= 0 && (a * n == index - b)
}

#[inline]
fn matches_root<N: TreeNode<T>, T: TreeNodeRefAsElement<N, E>, E: ElementLike>(
element: &T) -> bool {
Expand Down
22 changes: 19 additions & 3 deletions src/components/style/selectors.rs
Expand Up @@ -5,6 +5,7 @@
use std::{vec, iter};
use std::ascii::StrAsciiExt;
use cssparser::ast::*;
use cssparser::parse_nth;
use namespaces::NamespaceMap;


Expand Down Expand Up @@ -66,7 +67,13 @@ pub enum SimpleSelector {
// Empty,
Root,
// Lang(~str),
// NthChild(i32, i32),
NthChild(i32, i32),
NthLastChild(i32, i32),
NthOfType(i32, i32),
NthLastOfType(i32, i32),
FirstOfType,
LastOfType,
OnlyOfType
// ...
}

Expand Down Expand Up @@ -192,7 +199,10 @@ fn compute_specificity(mut selector: &CompoundSelector,
| &AttrPrefixMatch(*) | &AttrSubstringMatch(*) | &AttrSuffixMatch(*)
| &AnyLink | &Link | &Visited
| &FirstChild | &LastChild | &OnlyChild | &Root
// | &Empty | &Lang(*) | &NthChild(*)
// | &Empty | &Lang(*)
| &NthChild(*) | &NthLastChild(*)
| &NthOfType(*) | &NthLastOfType(*)
| &FirstOfType | &LastOfType | &OnlyOfType
=> specificity.class_like_selectors += 1,
&NamespaceSelector(*) => (),
&Negation(ref negated)
Expand Down Expand Up @@ -441,6 +451,9 @@ fn parse_simple_pseudo_class(name: &str) -> Option<SimpleSelector> {
"last-child" => Some(LastChild),
"only-child" => Some(OnlyChild),
"root" => Some(Root),
"first-of-type" => Some(FirstOfType),
"last-of-type" => Some(LastOfType),
"only-of-type" => Some(OnlyOfType),
// "empty" => Some(Empty),
_ => None
}
Expand All @@ -452,7 +465,10 @@ fn parse_functional_pseudo_class(name: ~str, arguments: ~[ComponentValue],
-> Option<SimpleSelector> {
match name.to_ascii_lower().as_slice() {
// "lang" => parse_lang(arguments),
// "nth-child" => parse_nth(arguments).map(|&(a, b)| NthChild(a, b)),
"nth-child" => parse_nth(arguments).map(|(a, b)| NthChild(a, b)),
"nth-last-child" => parse_nth(arguments).map(|(a, b)| NthLastChild(a, b)),
"nth-of-type" => parse_nth(arguments).map(|(a, b)| NthOfType(a, b)),
"nth-last-of-type" => parse_nth(arguments).map(|(a, b)| NthLastOfType(a, b)),
"not" => if inside_negation { None } else { parse_negation(arguments, namespaces) },
_ => None
}
Expand Down
7 changes: 7 additions & 0 deletions src/test/ref/basic.list
Expand Up @@ -5,3 +5,10 @@
== first_child_pseudo_a.html first_child_pseudo_b.html
== last_child_pseudo_a.html last_child_pseudo_b.html
== only_child_pseudo_a.html only_child_pseudo_b.html
== nth_child_pseudo_a.html nth_child_pseudo_b.html
== nth_last_child_pseudo_a.html nth_last_child_pseudo_b.html
== nth_of_type_pseudo_a.html nth_of_type_pseudo_b.html
== nth_last_of_type_pseudo_a.html nth_last_of_type_pseudo_b.html
== first_of_type_pseudo_a.html first_of_type_pseudo_b.html
== last_of_type_pseudo_a.html last_of_type_pseudo_b.html
== only_of_type_pseudo_a.html only_of_type_pseudo_b.html
52 changes: 52 additions & 0 deletions src/test/ref/first_of_type_pseudo_a.html
@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html>
<head>
<title>:first-of-type test</title>
<style type="text/css">
/* should not match according to Selectors 3 because html has no parent element */
html:first-of-type { background: red; }

div > p,
div > div,
div > address {
float: left;
width: 20px;
height: 20px;
margin: 0px;
margin-right: 10px;
padding: 0px;
}
div > p {
background: white;
}
div > div,
div > address {
background: black;
}
body > div { clear: both; margin-bottom: 10px; }

#d1 > .ok { background: red; }
#d1 > *:first-of-type { background: green }

</style>
</head>
<body>
<div id="d1">
<p class="ok"> </p>
<div class="ok"> </div>
<div> </div>
<p> </p>
<address class="ok"> </address>
<p> </p>
<div> </div>
<p> </p>
<p> </p>
<address> </address>
<address> </address>
<p> </p>
<div> </div>
<p> </p>
<p> </p>
</div>
</body>
</html>
48 changes: 48 additions & 0 deletions src/test/ref/first_of_type_pseudo_b.html
@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html>
<head>
<title>:first-of-type test</title>
<style type="text/css">
div > p,
div > div,
div > address {
float: left;
width: 20px;
height: 20px;
margin: 0px;
margin-right: 10px;
padding: 0px;
}
div > p {
background: white;
}
div > div,
div > address {
background: black;
}
body > div { clear: both; margin-bottom: 10px; }

#d1 > .ok { background: green; }

</style>
</head>
<body>
<div id="d1">
<p class="ok"> </p>
<div class="ok"> </div>
<div> </div>
<p> </p>
<address class="ok"> </address>
<p> </p>
<div> </div>
<p> </p>
<p> </p>
<address> </address>
<address> </address>
<p> </p>
<div> </div>
<p> </p>
<p> </p>
</div>
</body>
</html>
52 changes: 52 additions & 0 deletions src/test/ref/last_of_type_pseudo_a.html
@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html>
<head>
<title>:last-of-type test</title>
<style type="text/css">
/* should not match according to Selectors 3 because html has no parent element */
html:last-of-type { background: red; }

div > p,
div > div,
div > address {
float: left;
width: 20px;
height: 20px;
margin: 0px;
margin-right: 10px;
padding: 0px;
}
div > p {
background: white;
}
div > div,
div > address {
background: black;
}
body > div { clear: both; margin-bottom: 10px; }

#d1 > .ok { background: red; }
#d1 > *:last-of-type { background: green }

</style>
</head>
<body>
<div id="d1">
<p> </p>
<div> </div>
<div> </div>
<p> </p>
<address> </address>
<p> </p>
<div> </div>
<p> </p>
<p> </p>
<address> </address>
<address class="ok"> </address>
<p> </p>
<div class="ok"> </div>
<p> </p>
<p class="ok"> </p>
</div>
</body>
</html>
48 changes: 48 additions & 0 deletions src/test/ref/last_of_type_pseudo_b.html
@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html>
<head>
<title>:last-of-type test</title>
<style type="text/css">
div > p,
div > div,
div > address {
float: left;
width: 20px;
height: 20px;
margin: 0px;
margin-right: 10px;
padding: 0px;
}
div > p {
background: white;
}
div > div,
div > address {
background: black;
}
body > div { clear: both; margin-bottom: 10px; }

#d1 > .ok { background: green; }

</style>
</head>
<body>
<div id="d1">
<p> </p>
<div> </div>
<div> </div>
<p> </p>
<address> </address>
<p> </p>
<div> </div>
<p> </p>
<p> </p>
<address> </address>
<address class="ok"> </address>
<p> </p>
<div class="ok"> </div>
<p> </p>
<p class="ok"> </p>
</div>
</body>
</html>

0 comments on commit 991d4d8

Please sign in to comment.