diff --git a/src/components/script/dom/element.rs b/src/components/script/dom/element.rs index 32b5e8e9207..3b29913fe2e 100644 --- a/src/components/script/dom/element.rs +++ b/src/components/script/dom/element.rs @@ -131,7 +131,8 @@ impl<'self> Element { pub fn get_attr(&'self self, name: &str) -> Option<&'self str> { // FIXME: Need an each() that links lifetimes in Rust. for attr in self.attrs.iter() { - if eq_slice(attr.name, name) { + // FIXME: only case-insensitive in the HTML namespace (as opposed to SVG, etc.) + if attr.name.eq_ignore_ascii_case(name) { let val: &str = attr.value; return Some(val); } diff --git a/src/components/script/style/mod.rs b/src/components/script/style/mod.rs index 0b7e8c1613e..d8d719fabdc 100644 --- a/src/components/script/style/mod.rs +++ b/src/components/script/style/mod.rs @@ -2,9 +2,15 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +// The "real" public API +pub use self::selector_matching::{Stylist, StylesheetOrigin}; + + +// Things that need to be public to make the compiler happy pub mod stylesheets; pub mod errors; pub mod selectors; +pub mod selector_matching; pub mod properties; pub mod namespaces; pub mod media_queries; diff --git a/src/components/script/style/properties/mod.rs.mako b/src/components/script/style/properties/mod.rs.mako index 0759a9b241f..fe0926e6830 100644 --- a/src/components/script/style/properties/mod.rs.mako +++ b/src/components/script/style/properties/mod.rs.mako @@ -5,6 +5,7 @@ // This file is a Mako template: http://www.makotemplates.org/ use std::ascii::StrAsciiExt; +use std::at_vec; pub use std::iterator; pub use cssparser::*; pub use style::errors::{ErrorLoggerIterator, log_css_error}; @@ -655,8 +656,8 @@ pub mod shorthands { pub struct PropertyDeclarationBlock { - important: ~[PropertyDeclaration], - normal: ~[PropertyDeclaration], + important: @[PropertyDeclaration], + normal: @[PropertyDeclaration], } @@ -668,6 +669,7 @@ pub fn parse_property_declaration_list(input: ~[Node]) -> PropertyDeclarationBlo Decl_AtRule(rule) => log_css_error( rule.location, fmt!("Unsupported at-rule in declaration list: @%s", rule.name)), Declaration(Declaration{ location: l, name: n, value: v, important: i}) => { + // TODO: only keep the last valid declaration for a given name. let list = if i { &mut important } else { &mut normal }; if !PropertyDeclaration::parse(n, v, list) { log_css_error(l, "Invalid property declaration") @@ -675,7 +677,11 @@ pub fn parse_property_declaration_list(input: ~[Node]) -> PropertyDeclarationBlo } } } - PropertyDeclarationBlock { important: important, normal: normal } + PropertyDeclarationBlock { + // TODO avoid copying? + important: at_vec::to_managed_move(important), + normal: at_vec::to_managed_move(normal), + } } @@ -795,7 +801,8 @@ fn get_initial_values() -> ComputedValues { } -pub fn cascade(applicable_declarations: &[PropertyDeclaration], +// Most specific/important declarations last +pub fn cascade(applicable_declarations: &[@[PropertyDeclaration]], parent_style: Option< &ComputedValues>) -> ComputedValues { let initial_keep_alive; @@ -817,14 +824,17 @@ pub fn cascade(applicable_declarations: &[PropertyDeclaration], "Inherit" if property.is_inherited else "Initial"}), % endfor }; - for declaration in applicable_declarations.iter() { - match declaration { - % for property in LONGHANDS: - &${property.ident}_declaration(ref value) => { - // Overwrite earlier declarations. - specified.${property.ident} = (*value).clone() // TODO: can we avoid a copy? - } - % endfor + for sub_list in applicable_declarations.iter() { + for declaration in sub_list.iter() { + match declaration { + % for property in LONGHANDS: + &${property.ident}_declaration(ref value) => { + // Overwrite earlier declarations. + // TODO: can we avoid a copy? + specified.${property.ident} = (*value).clone() + } + % endfor + } } } // This assumes that the computed and specified values have the same Rust type. diff --git a/src/components/script/style/selector_matching.rs b/src/components/script/style/selector_matching.rs new file mode 100644 index 00000000000..f365bdc894a --- /dev/null +++ b/src/components/script/style/selector_matching.rs @@ -0,0 +1,243 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::ascii::StrAsciiExt; +use extra::sort::tim_sort; + +use style::selectors::*; +use style::stylesheets::parse_stylesheet; +use style::media_queries::{Device, Screen}; +use style::properties::{ComputedValues, cascade, PropertyDeclaration}; +use dom::node::{AbstractNode, ScriptView}; +use dom::element::Element; + + +pub enum StylesheetOrigin { + UserAgentOrigin, + AuthorOrigin, + UserOrigin, +} + + +pub struct Stylist { + priv ua_rules: PerOriginRules, + priv author_rules: PerOriginRules, + priv user_rules: PerOriginRules, +} + + +impl Stylist { + #[inline] + pub fn new() -> Stylist { + Stylist { + ua_rules: PerOriginRules::new(), + author_rules: PerOriginRules::new(), + user_rules: PerOriginRules::new(), + } + } + + pub fn add_stylesheet(&mut self, css_source: &str, origin: StylesheetOrigin) { + let stylesheet = parse_stylesheet(css_source); + let rules = match origin { + UserAgentOrigin => &mut self.ua_rules, + AuthorOrigin => &mut self.author_rules, + UserOrigin => &mut self.user_rules, + }; + let mut has_normal_declarations = false; + let mut has_important_declarations = false; + + macro_rules! append( + ($priority: ident, $flag: ident) => { + if style_rule.declarations.$priority.len() > 0 { + $flag = true; + for selector in style_rule.selectors.iter() { + rules.$priority.push(Rule { + selector: *selector, + declarations: style_rule.declarations.$priority, + }) + } + } + }; + ) + + let device = &Device { media_type: Screen }; // TODO, use Print when printing + for style_rule in stylesheet.iter_style_rules(device) { + append!(normal, has_normal_declarations); + append!(important, has_important_declarations); + } + + if has_normal_declarations { + tim_sort(rules.normal) + } + if has_important_declarations { + tim_sort(rules.important) + } + } + + pub fn get_computed_style(&self, element: AbstractNode, + parent_style: Option<&ComputedValues>, + pseudo_element: Option) + -> ComputedValues { + assert!(element.is_element()) + // Only the root does not inherit. + // The root has no parent or a non-element parent. + assert_eq!( + parent_style.is_none(), + match element.parent_node() { + None => true, + Some(ref node) => !node.is_element() + } + ); + let mut applicable_declarations = ~[]; // TODO: use an iterator? + + macro_rules! append( + ($rules: expr) => { + for rule in $rules.iter() { + if matches_selector(rule.selector, element, pseudo_element) { + applicable_declarations.push(rule.declarations) + } + } + }; + ); + + // In cascading order + append!(self.ua_rules.normal); + append!(self.user_rules.normal); + append!(self.author_rules.normal); + // TODO add style attribute + append!(self.author_rules.important); + append!(self.user_rules.important); + append!(self.ua_rules.important); + + cascade(applicable_declarations, parent_style) + } +} + + +struct PerOriginRules { + normal: ~[Rule], + important: ~[Rule], +} + +impl PerOriginRules { + #[inline] + fn new() -> PerOriginRules { + PerOriginRules { normal: ~[], important: ~[] } + } +} + +#[deriving(Clone)] +struct Rule { + selector: @Selector, + declarations: @[PropertyDeclaration], +} + + +impl Ord for Rule { + #[inline] + fn lt(&self, other: &Rule) -> bool { + self.selector.specificity < other.selector.specificity + } +} + + +#[inline] +fn matches_selector(selector: &Selector, element: AbstractNode, + pseudo_element: Option) -> bool { + selector.pseudo_element == pseudo_element && + matches_compound_selector(&selector.compound_selectors, element) +} + + +fn matches_compound_selector(selector: &CompoundSelector, + element: AbstractNode) -> bool { + if do element.with_imm_element |element| { + !do selector.simple_selectors.iter().all |simple_selector| { + matches_simple_selector(simple_selector, element) + } + } { + return false + } + match selector.next { + None => true, + Some((ref next_selector, combinator)) => { + let (siblings, just_one) = match combinator { + Child => (false, true), + Descendant => (false, false), + NextSibling => (true, true), + LaterSibling => (true, false), + }; + let mut node = element; + loop { + match if siblings { node.prev_sibling() } else { node.parent_node() } { + None => return false, + Some(next_node) => node = next_node, + } + if node.is_element() { + if matches_compound_selector(&**next_selector, node) { + return true + } else if just_one { + return false + } + } + } + } + } +} + +#[inline] +fn matches_simple_selector(selector: &SimpleSelector, element: &Element) -> bool { + static WHITESPACE: &'static [char] = &'static [' ', '\t', '\n', '\r', '\x0C']; + + match *selector { + // TODO: case-sensitivity depends on the document type + LocalNameSelector(ref name) => element.tag_name.eq_ignore_ascii_case(name.as_slice()), + NamespaceSelector(_) => false, // TODO, when the DOM supports namespaces on elements. + // TODO: case-sensitivity depends on the document type and quirks mode + // TODO: cache and intern IDs on elements. + IDSelector(ref id) => element.get_attr("id") == Some(id.as_slice()), + // TODO: cache and intern classe names on elements. + ClassSelector(ref class) => match element.get_attr("class") { + None => false, + // TODO: case-sensitivity depends on the document type and quirks mode + Some(ref class_attr) + => class_attr.split_iter(WHITESPACE).any(|c| c == class.as_slice()), + }, + + AttrExists(ref attr) => match_attribute(attr, element, |_| true), + AttrEqual(ref attr, ref value) => match_attribute(attr, element, |v| v == value.as_slice()), + AttrIncludes(ref attr, ref value) => do match_attribute(attr, element) |attr_value| { + attr_value.split_iter(WHITESPACE).any(|v| v == value.as_slice()) + }, + AttrDashMatch(ref attr, ref value, ref dashing_value) + => do match_attribute(attr, element) |attr_value| { + attr_value == value.as_slice() || attr_value.starts_with(dashing_value.as_slice()) + }, + AttrPrefixMatch(ref attr, ref value) => do match_attribute(attr, element) |attr_value| { + attr_value.starts_with(value.as_slice()) + }, + AttrSubstringMatch(ref attr, ref value) => do match_attribute(attr, element) |attr_value| { + attr_value.contains(value.as_slice()) + }, + AttrSuffixMatch(ref attr, ref value) => do match_attribute(attr, element) |attr_value| { + attr_value.ends_with(value.as_slice()) + }, + + Negation(ref negated) => { + !negated.iter().all(|s| matches_simple_selector(s, element)) + }, + } +} + + +#[inline] +fn match_attribute(attr: &AttrSelector, element: &Element, f: &fn(&str)-> bool) -> bool { + match attr.namespace { + Some(_) => false, // TODO, when the DOM supports namespaces on attributes + None => match element.get_attr(attr.name) { + None => false, + Some(ref value) => f(value.as_slice()) + } + } +} diff --git a/src/components/script/style/selectors.rs b/src/components/script/style/selectors.rs index 6d95bd7fd74..7d3c35ad3e8 100644 --- a/src/components/script/style/selectors.rs +++ b/src/components/script/style/selectors.rs @@ -17,6 +17,7 @@ pub struct Selector { pub static STYLE_ATTRIBUTE_SPECIFICITY: u32 = 1 << 31; +#[deriving(Eq)] pub enum PseudoElement { Before, After, @@ -40,30 +41,29 @@ pub enum Combinator { pub enum SimpleSelector { IDSelector(~str), ClassSelector(~str), - LocalNameSelector{lowercase_name: ~str, cased_name: ~str}, + LocalNameSelector(~str), NamespaceSelector(~str), // Attribute selectors AttrExists(AttrSelector), // [foo] AttrEqual(AttrSelector, ~str), // [foo=bar] AttrIncludes(AttrSelector, ~str), // [foo~=bar] - AttrDashMatch(AttrSelector, ~str), // [foo|=bar] + AttrDashMatch(AttrSelector, ~str, ~str), // [foo|=bar] Second string is the first + "-" AttrPrefixMatch(AttrSelector, ~str), // [foo^=bar] AttrSubstringMatch(AttrSelector, ~str), // [foo*=bar] AttrSuffixMatch(AttrSelector, ~str), // [foo$=bar] // Pseudo-classes - Empty, - Root, - Lang(~str), - NthChild(i32, i32), +// Empty, +// Root, +// Lang(~str), +// NthChild(i32, i32), Negation(~[SimpleSelector]), // ... } pub struct AttrSelector { - lowercase_name: ~str, - cased_name: ~str, + name: ~str, namespace: Option<~str>, } @@ -73,7 +73,7 @@ type Iter = iterator::Peekable // None means invalid selector pub fn parse_selector_list(input: ~[ComponentValue], namespaces: &NamespaceMap) - -> Option<~[Selector]> { + -> Option<~[@Selector]> { let iter = &mut input.move_iter().peekable(); let first = match parse_selector(iter, namespaces) { None => return None, @@ -99,7 +99,7 @@ pub fn parse_selector_list(input: ~[ComponentValue], namespaces: &NamespaceMap) // None means invalid selector fn parse_selector(iter: &mut Iter, namespaces: &NamespaceMap) - -> Option { + -> Option<@Selector> { let (first, pseudo_element) = match parse_simple_selectors(iter, namespaces) { None => return None, Some(result) => result @@ -130,12 +130,11 @@ fn parse_selector(iter: &mut Iter, namespaces: &NamespaceMap) } } } - let selector = Selector{ + Some(@Selector { specificity: compute_specificity(&compound, &pseudo_element), compound_selectors: compound, pseudo_element: pseudo_element, - }; - Some(selector) + }) } @@ -168,12 +167,12 @@ fn compute_specificity(mut selector: &CompoundSelector, specificity: &mut Specificity) { for simple_selector in simple_selectors.iter() { match simple_selector { - &LocalNameSelector{_} => specificity.element_selectors += 1, + &LocalNameSelector(*) => specificity.element_selectors += 1, &IDSelector(*) => specificity.id_selectors += 1, &ClassSelector(*) | &AttrExists(*) | &AttrEqual(*) | &AttrIncludes(*) | &AttrDashMatch(*) | &AttrPrefixMatch(*) | &AttrSubstringMatch(*) | &AttrSuffixMatch(*) - | &Empty | &Root | &Lang(*) | &NthChild(*) +// | &Empty | &Root | &Lang(*) | &NthChild(*) => specificity.class_like_selectors += 1, &NamespaceSelector(*) => (), &Negation(ref negated) @@ -229,10 +228,7 @@ fn parse_type_selector(iter: &mut Iter, namespaces: &NamespaceMap) None => (), } match local_name { - Some(name) => simple_selectors.push(LocalNameSelector{ - lowercase_name: name.to_ascii_lower(), - cased_name: name, - }), + Some(name) => simple_selectors.push(LocalNameSelector(name)), None => (), } Some(Some(simple_selectors)) @@ -369,11 +365,11 @@ fn parse_attribute_selector(content: ~[ComponentValue], namespaces: &NamespaceMa Some(Some((_, None))) => fail!("Implementation error, this should not happen."), Some(Some((namespace, Some(local_name)))) => AttrSelector { namespace: namespace, - lowercase_name: local_name.to_ascii_lower(), - cased_name: local_name, + name: local_name, }, }; skip_whitespace(iter); + // TODO: deal with empty value or value containing whitespace (see spec) macro_rules! get_value( () => {{ skip_whitespace(iter); match iter.next() { @@ -385,7 +381,11 @@ fn parse_attribute_selector(content: ~[ComponentValue], namespaces: &NamespaceMa None => AttrExists(attr), // [foo] Some(Delim('=')) => AttrEqual(attr, get_value!()), // [foo=bar] Some(IncludeMatch) => AttrIncludes(attr, get_value!()), // [foo~=bar] - Some(DashMatch) => AttrDashMatch(attr, get_value!()), // [foo|=bar] + Some(DashMatch) => { + let value = get_value!(); + let dashing_value = value + "-"; + AttrDashMatch(attr, value, dashing_value) // [foo|=bar] + }, Some(PrefixMatch) => AttrPrefixMatch(attr, get_value!()), // [foo^=bar] Some(SubstringMatch) => AttrSubstringMatch(attr, get_value!()), // [foo*=bar] Some(SuffixMatch) => AttrSuffixMatch(attr, get_value!()), // [foo$=bar] @@ -398,8 +398,8 @@ fn parse_attribute_selector(content: ~[ComponentValue], namespaces: &NamespaceMa fn parse_simple_pseudo_class(name: ~str) -> Option> { match name.to_ascii_lower().as_slice() { - "root" => Some(Left(Root)), - "empty" => Some(Left(Empty)), +// "root" => Some(Left(Root)), +// "empty" => Some(Left(Empty)), // Supported CSS 2.1 pseudo-elements only. "before" => Some(Right(Before)), @@ -415,8 +415,8 @@ fn parse_functional_pseudo_class(name: ~str, arguments: ~[ComponentValue], namespaces: &NamespaceMap, inside_negation: bool) -> Option { match name.to_ascii_lower().as_slice() { - "lang" => parse_lang(arguments), - "nth-child" => parse_nth(arguments).map(|&(a, b)| NthChild(a, b)), +// "lang" => parse_lang(arguments), +// "nth-child" => parse_nth(arguments).map(|&(a, b)| NthChild(a, b)), "not" => if inside_negation { None } else { parse_negation(arguments, namespaces) }, _ => None } @@ -435,16 +435,16 @@ fn parse_pseudo_element(name: ~str) -> Option { } -fn parse_lang(arguments: ~[ComponentValue]) -> Option { - let mut iter = arguments.move_skip_whitespace(); - match iter.next() { - Some(Ident(value)) => { - if "" == value || iter.next().is_some() { None } - else { Some(Lang(value)) } - }, - _ => None, - } -} +//fn parse_lang(arguments: ~[ComponentValue]) -> Option { +// let mut iter = arguments.move_skip_whitespace(); +// match iter.next() { +// Some(Ident(value)) => { +// if "" == value || iter.next().is_some() { None } +// else { Some(Lang(value)) } +// }, +// _ => None, +// } +//} // Level 3: Parse ONE simple_selector diff --git a/src/components/script/style/stylesheets.rs b/src/components/script/style/stylesheets.rs index 966e19e1ab6..7d7e343748d 100644 --- a/src/components/script/style/stylesheets.rs +++ b/src/components/script/style/stylesheets.rs @@ -26,7 +26,7 @@ pub enum CSSRule { pub struct StyleRule { - selectors: ~[selectors::Selector], + selectors: ~[@selectors::Selector], declarations: properties::PropertyDeclarationBlock, } @@ -117,7 +117,8 @@ pub fn parse_nested_at_rule(lower_name: &str, rule: AtRule, impl Stylesheet { - fn iter_style_rules<'a>(&'a self, device: &'a media_queries::Device) -> StyleRuleIterator<'a> { + pub fn iter_style_rules<'a>(&'a self, device: &'a media_queries::Device) + -> StyleRuleIterator<'a> { StyleRuleIterator { device: device, stack: ~[(self.rules.as_slice(), 0)] } } }