mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +01:00
auto merge of #1254 : pcwalton/servo/rule-hash, r=pcwalton
This is @pradeep90's PR with one little optimization.
This commit is contained in:
commit
76b367ceb6
3 changed files with 351 additions and 64 deletions
|
@ -3,6 +3,7 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use std::ascii::StrAsciiExt;
|
use std::ascii::StrAsciiExt;
|
||||||
|
use std::hashmap::HashMap;
|
||||||
use extra::arc::Arc;
|
use extra::arc::Arc;
|
||||||
use extra::sort::tim_sort;
|
use extra::sort::tim_sort;
|
||||||
|
|
||||||
|
@ -21,96 +22,285 @@ pub enum StylesheetOrigin {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub struct Stylist {
|
/// Map node attributes to Rules whose last simple selector starts with them.
|
||||||
priv ua_rules: PerOriginRules,
|
///
|
||||||
priv author_rules: PerOriginRules,
|
/// e.g.,
|
||||||
priv user_rules: PerOriginRules,
|
/// "p > img" would go into the set of Rules corresponding to the
|
||||||
|
/// element "img"
|
||||||
|
/// "a .foo .bar.baz" would go into the set of Rules corresponding to
|
||||||
|
/// the class "bar"
|
||||||
|
///
|
||||||
|
/// Because we match Rules right-to-left (i.e., moving up the tree
|
||||||
|
/// from a node), we need to compare the last simple selector in the
|
||||||
|
/// Rule with the node.
|
||||||
|
///
|
||||||
|
/// So, if a node has ID "id1" and classes "foo" and "bar", then all
|
||||||
|
/// the rules it matches will have their last simple selector starting
|
||||||
|
/// either with "#id1" or with ".foo" or with ".bar".
|
||||||
|
///
|
||||||
|
/// Hence, the union of the rules keyed on each of node's classes, ID,
|
||||||
|
/// element name, etc. will contain the Rules that actually match that
|
||||||
|
/// node.
|
||||||
|
pub struct SelectorMap {
|
||||||
|
// TODO: Tune the initial capacity of the HashMap
|
||||||
|
// FIXME: Use interned strings
|
||||||
|
priv id_hash: HashMap<~str, ~[Rule]>,
|
||||||
|
priv class_hash: HashMap<~str, ~[Rule]>,
|
||||||
|
priv element_hash: HashMap<~str, ~[Rule]>,
|
||||||
|
// For Rules that don't have ID, class, or element selectors.
|
||||||
|
priv universal_rules: ~[Rule],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SelectorMap {
|
||||||
|
fn new() -> SelectorMap {
|
||||||
|
SelectorMap {
|
||||||
|
id_hash: HashMap::new(),
|
||||||
|
class_hash: HashMap::new(),
|
||||||
|
element_hash: HashMap::new(),
|
||||||
|
universal_rules: ~[],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Append to `rule_list` all Rules in `self` that match node.
|
||||||
|
///
|
||||||
|
/// Extract matching rules as per node's ID, classes, tag name, etc..
|
||||||
|
/// Sort the Rules at the end to maintain cascading order.
|
||||||
|
fn get_all_matching_rules<N: TreeNode<T>, T: TreeNodeRefAsElement<N, E>, E: ElementLike>(
|
||||||
|
&self, node: &T,
|
||||||
|
pseudo_element: Option<PseudoElement>,
|
||||||
|
matching_rules_list: &mut [~[Rule]],
|
||||||
|
list_index: uint) {
|
||||||
|
|
||||||
|
let init_len = matching_rules_list[list_index].len();
|
||||||
|
static WHITESPACE: &'static [char] = &'static [' ', '\t', '\n', '\r', '\x0C'];
|
||||||
|
do node.with_imm_element_like |element: &E| {
|
||||||
|
match element.get_attr("id") {
|
||||||
|
Some(id) => SelectorMap::get_matching_rules_from_hash(
|
||||||
|
node, pseudo_element, &self.id_hash, id, &mut matching_rules_list[list_index]),
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
match element.get_attr("class") {
|
||||||
|
Some(ref class_attr) => {
|
||||||
|
for class in class_attr.split_iter(WHITESPACE) {
|
||||||
|
SelectorMap::get_matching_rules_from_hash(
|
||||||
|
node, pseudo_element, &self.class_hash, class, &mut matching_rules_list[list_index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectorMap::get_matching_rules_from_hash(
|
||||||
|
node, pseudo_element, &self.element_hash,
|
||||||
|
// HTML elements in HTML documents must be matched case-insensitively
|
||||||
|
// TODO: case-sensitivity depends on the document type
|
||||||
|
element.get_local_name().to_ascii_lower(),
|
||||||
|
&mut matching_rules_list[list_index]);
|
||||||
|
SelectorMap::get_matching_rules(
|
||||||
|
node, pseudo_element, self.universal_rules, &mut matching_rules_list[list_index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort only the rules we just added.
|
||||||
|
tim_sort(matching_rules_list[list_index].mut_slice_from(init_len));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_matching_rules_from_hash<N: TreeNode<T>, T: TreeNodeRefAsElement<N, E>, E: ElementLike>(
|
||||||
|
node: &T,
|
||||||
|
pseudo_element: Option<PseudoElement>,
|
||||||
|
hash: &HashMap<~str, ~[Rule]>,
|
||||||
|
key: &str,
|
||||||
|
matching_rules: &mut ~[Rule]) {
|
||||||
|
match hash.find(&key.to_str()) {
|
||||||
|
Some(rules) => SelectorMap::get_matching_rules(node, pseudo_element, *rules,
|
||||||
|
matching_rules),
|
||||||
|
None => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return rules in `rules` that match `node`.
|
||||||
|
fn get_matching_rules<N: TreeNode<T>, T: TreeNodeRefAsElement<N, E>, E: ElementLike>(
|
||||||
|
node: &T,
|
||||||
|
pseudo_element: Option<PseudoElement>,
|
||||||
|
rules: &[Rule],
|
||||||
|
matching_rules: &mut ~[Rule]) {
|
||||||
|
for rule in rules.iter() {
|
||||||
|
if matches_selector(&rule.selector, node, pseudo_element) {
|
||||||
|
// TODO: Is the cloning inefficient?
|
||||||
|
matching_rules.push(rule.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert rule into the correct hash.
|
||||||
|
/// Order in which to try: id_hash, class_hash, element_hash, universal_rules.
|
||||||
|
fn insert(&mut self, rule: Rule) {
|
||||||
|
// TODO: Can we avoid the repeated clones? (IMHO, the clone()
|
||||||
|
// is required because Arc makes Rule non-copyable)
|
||||||
|
match SelectorMap::get_id_name(rule.clone()) {
|
||||||
|
Some(id_name) => {
|
||||||
|
// TODO: Is this is a wasteful allocation of a list (~[rule])?
|
||||||
|
self.id_hash.insert_or_update_with(id_name,
|
||||||
|
~[rule.clone()],
|
||||||
|
|_, v| v.push(rule.clone()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
match SelectorMap::get_class_name(rule.clone()) {
|
||||||
|
Some(class_name) => {
|
||||||
|
self.class_hash.insert_or_update_with(class_name,
|
||||||
|
~[rule.clone()],
|
||||||
|
|_, v| v.push(rule.clone()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
match SelectorMap::get_element_name(rule.clone()) {
|
||||||
|
Some(element_name) => {
|
||||||
|
self.element_hash.insert_or_update_with(element_name,
|
||||||
|
~[rule.clone()],
|
||||||
|
|_, v| v.push(rule.clone()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.universal_rules.push(rule.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve the first ID name in Rule, or None otherwise.
|
||||||
|
fn get_id_name(rule: Rule) -> Option<~str> {
|
||||||
|
let selector = rule.selector;
|
||||||
|
let simple_selector_sequence = &selector.compound_selectors.simple_selectors;
|
||||||
|
for ss in simple_selector_sequence.iter() {
|
||||||
|
match *ss {
|
||||||
|
// TODO: Implement case-sensitivity based on the document type and quirks mode
|
||||||
|
IDSelector(ref id) => return Some(id.clone()),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve the FIRST class name in Rule, or None otherwise.
|
||||||
|
fn get_class_name(rule: Rule) -> Option<~str> {
|
||||||
|
let simple_selector_sequence = &rule.selector.compound_selectors.simple_selectors;
|
||||||
|
for ss in simple_selector_sequence.iter() {
|
||||||
|
match *ss {
|
||||||
|
// TODO: Implement case-sensitivity based on the document type and quirks mode
|
||||||
|
ClassSelector(ref class) => return Some(class.clone()),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve the name if it is a type selector, or None otherwise.
|
||||||
|
fn get_element_name(rule: Rule) -> Option<~str> {
|
||||||
|
let simple_selector_sequence = &rule.selector.compound_selectors.simple_selectors;
|
||||||
|
for ss in simple_selector_sequence.iter() {
|
||||||
|
match *ss {
|
||||||
|
// HTML elements in HTML documents must be matched case-insensitively
|
||||||
|
// TODO: case-sensitivity depends on the document type
|
||||||
|
LocalNameSelector(ref name) => return Some(name.to_ascii_lower()),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Stylist {
|
||||||
|
priv ua_rule_map: PerOriginSelectorMap,
|
||||||
|
priv author_rule_map: PerOriginSelectorMap,
|
||||||
|
priv user_rule_map: PerOriginSelectorMap,
|
||||||
|
priv stylesheet_index: uint,
|
||||||
|
}
|
||||||
|
|
||||||
impl Stylist {
|
impl Stylist {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new() -> Stylist {
|
pub fn new() -> Stylist {
|
||||||
Stylist {
|
Stylist {
|
||||||
ua_rules: PerOriginRules::new(),
|
ua_rule_map: PerOriginSelectorMap::new(),
|
||||||
author_rules: PerOriginRules::new(),
|
author_rule_map: PerOriginSelectorMap::new(),
|
||||||
user_rules: PerOriginRules::new(),
|
user_rule_map: PerOriginSelectorMap::new(),
|
||||||
|
stylesheet_index: 0u,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_stylesheet(&mut self, stylesheet: Stylesheet, origin: StylesheetOrigin) {
|
pub fn add_stylesheet(&mut self, stylesheet: Stylesheet, origin: StylesheetOrigin) {
|
||||||
let rules = match origin {
|
let rule_map = match origin {
|
||||||
UserAgentOrigin => &mut self.ua_rules,
|
UserAgentOrigin => &mut self.ua_rule_map,
|
||||||
AuthorOrigin => &mut self.author_rules,
|
AuthorOrigin => &mut self.author_rule_map,
|
||||||
UserOrigin => &mut self.user_rules,
|
UserOrigin => &mut self.user_rule_map,
|
||||||
};
|
};
|
||||||
let mut added_normal_declarations = false;
|
let mut added_normal_declarations = false;
|
||||||
let mut added_important_declarations = false;
|
let mut added_important_declarations = false;
|
||||||
|
let mut style_rule_index = 0u;
|
||||||
|
|
||||||
|
// Take apart the StyleRule into individual Rules and insert
|
||||||
|
// them into the SelectorMap of that priority.
|
||||||
macro_rules! append(
|
macro_rules! append(
|
||||||
($priority: ident, $flag: ident) => {
|
($priority: ident, $flag: ident) => {
|
||||||
if style_rule.declarations.$priority.len() > 0 {
|
if style_rule.declarations.$priority.len() > 0 {
|
||||||
$flag = true;
|
$flag = true;
|
||||||
for selector in style_rule.selectors.iter() {
|
for selector in style_rule.selectors.iter() {
|
||||||
// TODO: avoid copying?
|
// TODO: avoid copying?
|
||||||
rules.$priority.push(Rule {
|
rule_map.$priority.insert(Rule {
|
||||||
selector: selector.clone(),
|
selector: selector.clone(),
|
||||||
declarations: Arc::new(style_rule.declarations.$priority.clone()),
|
declarations: Arc::new(style_rule.declarations.$priority.clone()),
|
||||||
})
|
index: style_rule_index,
|
||||||
}
|
stylesheet_index: self.stylesheet_index,
|
||||||
}
|
});
|
||||||
};
|
|
||||||
)
|
|
||||||
|
|
||||||
let device = &Device { media_type: Screen }; // TODO, use Print when printing
|
|
||||||
do iter_style_rules(stylesheet.rules.as_slice(), device) |style_rule| {
|
|
||||||
append!(normal, added_normal_declarations);
|
|
||||||
append!(important, added_important_declarations);
|
|
||||||
}
|
|
||||||
|
|
||||||
// These sorts need to be stable
|
|
||||||
// Do not sort already-sorted unchanged vectors
|
|
||||||
if added_normal_declarations {
|
|
||||||
tim_sort(rules.normal)
|
|
||||||
}
|
|
||||||
if added_important_declarations {
|
|
||||||
tim_sort(rules.important)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_applicable_declarations<N: TreeNode<T>, T: TreeNodeRefAsElement<N, E>, E: ElementLike>(
|
|
||||||
&self, element: &T, style_attribute: Option<&PropertyDeclarationBlock>,
|
|
||||||
pseudo_element: Option<PseudoElement>) -> ~[Arc<~[PropertyDeclaration]>] {
|
|
||||||
assert!(element.is_element())
|
|
||||||
assert!(style_attribute.is_none() || pseudo_element.is_none(),
|
|
||||||
"Style attributes do not apply to pseudo-elements")
|
|
||||||
let mut applicable_declarations = ~[]; // TODO: use an iterator?
|
|
||||||
|
|
||||||
macro_rules! append(
|
|
||||||
($rules: expr) => {
|
|
||||||
for rule in $rules.iter() {
|
|
||||||
if matches_selector::<N, T, E>(&rule.selector, element, pseudo_element) {
|
|
||||||
applicable_declarations.push(rule.declarations.clone())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
);
|
);
|
||||||
|
|
||||||
// In cascading order
|
let device = &Device { media_type: Screen }; // TODO, use Print when printing
|
||||||
append!(self.ua_rules.normal);
|
do iter_style_rules(stylesheet.rules.as_slice(), device) |style_rule| {
|
||||||
append!(self.user_rules.normal);
|
append!(normal, added_normal_declarations);
|
||||||
|
append!(important, added_important_declarations);
|
||||||
|
style_rule_index += 1u;
|
||||||
|
}
|
||||||
|
self.stylesheet_index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_applicable_declarations<N: TreeNode<T>, T: TreeNodeRefAsElement<N, E>, E: ElementLike>(
|
||||||
|
&self, element: &T, style_attribute: Option<&PropertyDeclarationBlock>,
|
||||||
|
pseudo_element: Option<PseudoElement>) -> ~[Arc<~[PropertyDeclaration]>] {
|
||||||
|
assert!(element.is_element());
|
||||||
|
assert!(style_attribute.is_none() || pseudo_element.is_none(),
|
||||||
|
"Style attributes do not apply to pseudo-elements");
|
||||||
|
|
||||||
|
// In cascading order
|
||||||
|
let rule_map_list = [&self.ua_rule_map.normal,
|
||||||
|
&self.user_rule_map.normal,
|
||||||
|
&self.author_rule_map.normal,
|
||||||
|
&self.author_rule_map.important,
|
||||||
|
&self.user_rule_map.important,
|
||||||
|
&self.ua_rule_map.important];
|
||||||
|
|
||||||
|
// TODO: Make this a stack-allocated vector
|
||||||
|
let mut matching_rules_list: [~[Rule], ..6] = [~[], ~[], ~[], ~[], ~[], ~[]];
|
||||||
|
for (i, rule_map) in rule_map_list.iter().enumerate() {
|
||||||
|
rule_map.get_all_matching_rules(element, pseudo_element, matching_rules_list, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keeping this as a separate step because we will need it for further
|
||||||
|
// optimizations regarding grouping of Rules having the same Selector.
|
||||||
|
let declarations_list = matching_rules_list.map(
|
||||||
|
|rules| rules.map(|r| r.declarations.clone()));
|
||||||
|
|
||||||
|
let mut applicable_declarations = ~[];
|
||||||
|
applicable_declarations.push_all_move(declarations_list.slice(0, 3).concat_vec());
|
||||||
// Style attributes have author origin but higher specificity than style rules.
|
// Style attributes have author origin but higher specificity than style rules.
|
||||||
append!(self.author_rules.normal);
|
|
||||||
// TODO: avoid copying?
|
// TODO: avoid copying?
|
||||||
style_attribute.map(|sa| applicable_declarations.push(Arc::new(sa.normal.clone())));
|
style_attribute.map(|sa| applicable_declarations.push(Arc::new(sa.normal.clone())));
|
||||||
|
applicable_declarations.push_all_move(declarations_list.slice(3, 4).concat_vec());
|
||||||
append!(self.author_rules.important);
|
|
||||||
// TODO: avoid copying?
|
|
||||||
style_attribute.map(|sa| applicable_declarations.push(Arc::new(sa.important.clone())));
|
style_attribute.map(|sa| applicable_declarations.push(Arc::new(sa.important.clone())));
|
||||||
|
applicable_declarations.push_all_move(declarations_list.slice(4, 6).concat_vec());
|
||||||
append!(self.user_rules.important);
|
|
||||||
append!(self.ua_rules.important);
|
|
||||||
|
|
||||||
applicable_declarations
|
applicable_declarations
|
||||||
}
|
}
|
||||||
|
@ -129,17 +319,36 @@ impl PerOriginRules {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct PerOriginSelectorMap {
|
||||||
|
normal: SelectorMap,
|
||||||
|
important: SelectorMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PerOriginSelectorMap {
|
||||||
|
#[inline]
|
||||||
|
fn new() -> PerOriginSelectorMap {
|
||||||
|
PerOriginSelectorMap { normal: SelectorMap::new(), important:SelectorMap::new() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[deriving(Clone)]
|
#[deriving(Clone)]
|
||||||
struct Rule {
|
struct Rule {
|
||||||
selector: Selector,
|
selector: Selector,
|
||||||
declarations: Arc<~[PropertyDeclaration]>,
|
declarations: Arc<~[PropertyDeclaration]>,
|
||||||
|
// Index of the parent StyleRule in the parent Stylesheet (useful for
|
||||||
|
// breaking ties while cascading).
|
||||||
|
index: uint,
|
||||||
|
// Index of the parent stylesheet among all the stylesheets
|
||||||
|
stylesheet_index: uint,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Ord for Rule {
|
impl Ord for Rule {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn lt(&self, other: &Rule) -> bool {
|
fn lt(&self, other: &Rule) -> bool {
|
||||||
self.selector.specificity < other.selector.specificity
|
let this_rank = (self.selector.specificity, self.stylesheet_index, self.index);
|
||||||
|
let other_rank = (other.selector.specificity, other.stylesheet_index, other.index);
|
||||||
|
this_rank < other_rank
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +357,7 @@ impl Ord for Rule {
|
||||||
fn matches_selector<N: TreeNode<T>, T: TreeNodeRefAsElement<N, E>, E: ElementLike>(
|
fn matches_selector<N: TreeNode<T>, T: TreeNodeRefAsElement<N, E>, E: ElementLike>(
|
||||||
selector: &Selector, element: &T, pseudo_element: Option<PseudoElement>) -> bool {
|
selector: &Selector, element: &T, pseudo_element: Option<PseudoElement>) -> bool {
|
||||||
selector.pseudo_element == pseudo_element &&
|
selector.pseudo_element == pseudo_element &&
|
||||||
matches_compound_selector::<N, T, E>(&selector.compound_selectors, element)
|
matches_compound_selector::<N, T, E>(&selector.compound_selectors, element)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matches_compound_selector<N: TreeNode<T>, T: TreeNodeRefAsElement<N, E>, E: ElementLike>(
|
fn matches_compound_selector<N: TreeNode<T>, T: TreeNodeRefAsElement<N, E>, E: ElementLike>(
|
||||||
|
@ -312,3 +521,69 @@ fn match_attribute<N: TreeNode<T>, T: TreeNodeRefAsElement<N, E>, E: ElementLike
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn get_rules(css_string: &str) -> ~[~[Rule]] {
|
||||||
|
let device = &Device { media_type: Screen };
|
||||||
|
let sheet = Stylesheet::from_str(css_string);
|
||||||
|
let mut index = 0u;
|
||||||
|
let mut results = ~[];
|
||||||
|
do iter_style_rules(sheet.rules.as_slice(), device) |style_rule| {
|
||||||
|
results.push(style_rule.selectors.iter().map(|s| Rule {
|
||||||
|
selector: s.clone(),
|
||||||
|
declarations: Arc::new(style_rule.declarations.normal.clone()),
|
||||||
|
index: index,
|
||||||
|
stylesheet_index: 0u,
|
||||||
|
}).collect());
|
||||||
|
index += 1u;
|
||||||
|
}
|
||||||
|
results
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper method to get some Rules from selector strings.
|
||||||
|
/// Each sublist of the result contains the Rules for one StyleRule.
|
||||||
|
fn get_mock_rules(css_selectors: &[&str]) -> ~[~[Rule]] {
|
||||||
|
let css_string = css_selectors.map(|s| s + " { color: red; } ").concat();
|
||||||
|
get_rules(css_string)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rule_ordering_same_specificity(){
|
||||||
|
let rules_list = get_mock_rules(["a.intro", "img.sidebar"]);
|
||||||
|
let rule1 = rules_list[0][0].clone();
|
||||||
|
let rule2 = rules_list[1][0].clone();
|
||||||
|
assert!(rule1 < rule2, "The rule that comes later should win.");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_id_name(){
|
||||||
|
let rules_list = get_mock_rules([".intro", "#top"]);
|
||||||
|
assert_eq!(SelectorMap::get_id_name(rules_list[0][0].clone()), None);
|
||||||
|
assert_eq!(SelectorMap::get_id_name(rules_list[1][0].clone()), Some(~"top"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_class_name(){
|
||||||
|
let rules_list = get_mock_rules([".intro.foo", "#top"]);
|
||||||
|
assert_eq!(SelectorMap::get_class_name(rules_list[0][0].clone()), Some(~"intro"));
|
||||||
|
assert_eq!(SelectorMap::get_class_name(rules_list[1][0].clone()), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_element_name(){
|
||||||
|
let rules_list = get_mock_rules(["img.foo", "#top", "IMG", "ImG"]);
|
||||||
|
assert_eq!(SelectorMap::get_element_name(rules_list[0][0].clone()), Some(~"img"));
|
||||||
|
assert_eq!(SelectorMap::get_element_name(rules_list[1][0].clone()), None);
|
||||||
|
assert_eq!(SelectorMap::get_element_name(rules_list[2][0].clone()), Some(~"img"));
|
||||||
|
assert_eq!(SelectorMap::get_element_name(rules_list[3][0].clone()), Some(~"img"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_insert(){
|
||||||
|
let rules_list = get_mock_rules([".intro.foo", "#top"]);
|
||||||
|
let mut selector_map = SelectorMap::new();
|
||||||
|
selector_map.insert(rules_list[1][0].clone());
|
||||||
|
assert_eq!(1, selector_map.id_hash.find(&~"top").unwrap()[0].index);
|
||||||
|
selector_map.insert(rules_list[0][0].clone());
|
||||||
|
assert_eq!(0, selector_map.class_hash.find(&~"intro").unwrap()[0].index);
|
||||||
|
assert!(selector_map.class_hash.find(&~"foo").is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,7 +80,10 @@ pub struct AttrSelector {
|
||||||
type Iter = iter::Peekable<ComponentValue, vec::MoveIterator<ComponentValue>>;
|
type Iter = iter::Peekable<ComponentValue, vec::MoveIterator<ComponentValue>>;
|
||||||
|
|
||||||
|
|
||||||
// None means invalid selector
|
/// Parse a comma-separated list of Selectors.
|
||||||
|
/// aka Selector Group in http://www.w3.org/TR/css3-selectors/#grouping
|
||||||
|
///
|
||||||
|
/// Return the Selectors or None if there is an invalid selector.
|
||||||
pub fn parse_selector_list(input: ~[ComponentValue], namespaces: &NamespaceMap)
|
pub fn parse_selector_list(input: ~[ComponentValue], namespaces: &NamespaceMap)
|
||||||
-> Option<~[Selector]> {
|
-> Option<~[Selector]> {
|
||||||
let iter = &mut input.move_iter().peekable();
|
let iter = &mut input.move_iter().peekable();
|
||||||
|
@ -108,7 +111,10 @@ pub fn parse_selector_list(input: ~[ComponentValue], namespaces: &NamespaceMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// None means invalid selector
|
/// Build up a Selector.
|
||||||
|
/// selector : simple_selector_sequence [ combinator simple_selector_sequence ]* ;
|
||||||
|
///
|
||||||
|
/// None means invalid selector.
|
||||||
fn parse_selector(iter: &mut Iter, namespaces: &NamespaceMap)
|
fn parse_selector(iter: &mut Iter, namespaces: &NamespaceMap)
|
||||||
-> Option<Selector> {
|
-> Option<Selector> {
|
||||||
let (first, pseudo_element) = match parse_simple_selectors(iter, namespaces) {
|
let (first, pseudo_element) = match parse_simple_selectors(iter, namespaces) {
|
||||||
|
@ -201,7 +207,11 @@ fn compute_specificity(mut selector: &CompoundSelector,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// None means invalid selector
|
/// simple_selector_sequence
|
||||||
|
/// : [ type_selector | universal ] [ HASH | class | attrib | pseudo | negation ]*
|
||||||
|
/// | [ HASH | class | attrib | pseudo | negation ]+
|
||||||
|
///
|
||||||
|
/// None means invalid selector
|
||||||
fn parse_simple_selectors(iter: &mut Iter, namespaces: &NamespaceMap)
|
fn parse_simple_selectors(iter: &mut Iter, namespaces: &NamespaceMap)
|
||||||
-> Option<(~[SimpleSelector], Option<PseudoElement>)> {
|
-> Option<(~[SimpleSelector], Option<PseudoElement>)> {
|
||||||
let mut empty = true;
|
let mut empty = true;
|
||||||
|
|
|
@ -16,6 +16,8 @@ use media_queries;
|
||||||
|
|
||||||
|
|
||||||
pub struct Stylesheet {
|
pub struct Stylesheet {
|
||||||
|
/// List of rules in the order they were found (important for
|
||||||
|
/// cascading order)
|
||||||
rules: ~[CSSRule],
|
rules: ~[CSSRule],
|
||||||
namespaces: NamespaceMap,
|
namespaces: NamespaceMap,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue