mirror of
https://github.com/servo/servo.git
synced 2025-06-23 16:44:33 +01:00
auto merge of #1001 : SimonSapin/servo/newnewcss, r=kmcallister
Add selector matching, style structs, cascading. The matching is quite naive and has many low hanging fruits for optimization. No pseudo-class (except `:not()`) is implemented yet, but those are easy to add as needed. Next step is to update the layout code to use this and drop netsurf-css. (Most of the +7066 lines stat is for bootstrap.css, added as a test.)
This commit is contained in:
commit
d6d2534b56
9 changed files with 7375 additions and 85 deletions
|
@ -131,7 +131,8 @@ impl<'self> Element {
|
||||||
pub fn get_attr(&'self self, name: &str) -> Option<&'self str> {
|
pub fn get_attr(&'self self, name: &str) -> Option<&'self str> {
|
||||||
// FIXME: Need an each() that links lifetimes in Rust.
|
// FIXME: Need an each() that links lifetimes in Rust.
|
||||||
for attr in self.attrs.iter() {
|
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;
|
let val: &str = attr.value;
|
||||||
return Some(val);
|
return Some(val);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,19 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* 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 stylesheets;
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod selectors;
|
pub mod selectors;
|
||||||
|
pub mod selector_matching;
|
||||||
pub mod properties;
|
pub mod properties;
|
||||||
pub mod namespaces;
|
pub mod namespaces;
|
||||||
pub mod media_queries;
|
pub mod media_queries;
|
||||||
pub mod parsing_utils;
|
pub mod parsing_utils;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
|
@ -12,6 +12,7 @@ pub mod specified {
|
||||||
use super::{Integer, Float};
|
use super::{Integer, Float};
|
||||||
pub use CSSColor = cssparser::Color;
|
pub use CSSColor = cssparser::Color;
|
||||||
|
|
||||||
|
#[deriving(Clone)]
|
||||||
pub enum Length {
|
pub enum Length {
|
||||||
Au(Integer), // application units
|
Au(Integer), // application units
|
||||||
Em(Float),
|
Em(Float),
|
||||||
|
@ -65,6 +66,7 @@ pub mod specified {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deriving(Clone)]
|
||||||
pub enum LengthOrPercentage {
|
pub enum LengthOrPercentage {
|
||||||
LP_Length(Length),
|
LP_Length(Length),
|
||||||
LP_Percentage(Float),
|
LP_Percentage(Float),
|
||||||
|
@ -91,6 +93,7 @@ pub mod specified {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deriving(Clone)]
|
||||||
pub enum LengthOrPercentageOrAuto {
|
pub enum LengthOrPercentageOrAuto {
|
||||||
LPA_Length(Length),
|
LPA_Length(Length),
|
||||||
LPA_Percentage(Float),
|
LPA_Percentage(Float),
|
||||||
|
@ -123,19 +126,23 @@ pub mod specified {
|
||||||
pub mod computed {
|
pub mod computed {
|
||||||
use cssparser;
|
use cssparser;
|
||||||
pub use CSSColor = cssparser::Color;
|
pub use CSSColor = cssparser::Color;
|
||||||
pub use compute_CSSColor = std::util::id;
|
pub use compute_CSSColor = super::super::longhands::computed_as_specified;
|
||||||
use super::*;
|
use super::*;
|
||||||
use super::super::longhands::font_weight;
|
use super::super::longhands;
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
current_color: cssparser::RGBA,
|
current_color: cssparser::RGBA,
|
||||||
|
font_size: Length,
|
||||||
|
font_weight: longhands::font_weight::ComputedValue,
|
||||||
|
position: longhands::position::SpecifiedValue,
|
||||||
|
float: longhands::float::SpecifiedValue,
|
||||||
|
is_root_element: bool,
|
||||||
has_border_top: bool,
|
has_border_top: bool,
|
||||||
has_border_right: bool,
|
has_border_right: bool,
|
||||||
has_border_bottom: bool,
|
has_border_bottom: bool,
|
||||||
has_border_left: bool,
|
has_border_left: bool,
|
||||||
font_size: Length,
|
|
||||||
font_weight: font_weight::ComputedValue,
|
|
||||||
// TODO, as needed: root font size, viewport size, etc.
|
// TODO, as needed: root font size, viewport size, etc.
|
||||||
}
|
}
|
||||||
|
#[deriving(Clone)]
|
||||||
pub struct Length(Integer); // in application units
|
pub struct Length(Integer); // in application units
|
||||||
impl Length {
|
impl Length {
|
||||||
pub fn times(self, factor: Float) -> Length {
|
pub fn times(self, factor: Float) -> Length {
|
||||||
|
@ -154,6 +161,7 @@ pub mod computed {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deriving(Clone)]
|
||||||
pub enum LengthOrPercentage {
|
pub enum LengthOrPercentage {
|
||||||
LP_Length(Length),
|
LP_Length(Length),
|
||||||
LP_Percentage(Float),
|
LP_Percentage(Float),
|
||||||
|
@ -166,6 +174,7 @@ pub mod computed {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deriving(Clone)]
|
||||||
pub enum LengthOrPercentageOrAuto {
|
pub enum LengthOrPercentageOrAuto {
|
||||||
LPA_Length(Length),
|
LPA_Length(Length),
|
||||||
LPA_Percentage(Float),
|
LPA_Percentage(Float),
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
// This file is a Mako template: http://www.makotemplates.org/
|
// This file is a Mako template: http://www.makotemplates.org/
|
||||||
|
|
||||||
use std::ascii::StrAsciiExt;
|
use std::ascii::StrAsciiExt;
|
||||||
|
use std::at_vec;
|
||||||
pub use std::iterator;
|
pub use std::iterator;
|
||||||
pub use cssparser::*;
|
pub use cssparser::*;
|
||||||
pub use style::errors::{ErrorLoggerIterator, log_css_error};
|
pub use style::errors::{ErrorLoggerIterator, log_css_error};
|
||||||
|
@ -23,20 +24,30 @@ def to_rust_ident(name):
|
||||||
return name
|
return name
|
||||||
|
|
||||||
class Longhand(object):
|
class Longhand(object):
|
||||||
def __init__(self, name):
|
def __init__(self, name, is_inherited):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.ident = to_rust_ident(name)
|
self.ident = to_rust_ident(name)
|
||||||
|
self.is_inherited = is_inherited
|
||||||
|
|
||||||
|
|
||||||
class Shorthand(object):
|
class Shorthand(object):
|
||||||
def __init__(self, name, sub_properties):
|
def __init__(self, name, sub_properties):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.ident = to_rust_ident(name)
|
self.ident = to_rust_ident(name)
|
||||||
self.sub_properties = [Longhand(s) for s in sub_properties]
|
self.sub_properties = [LONGHANDS_BY_NAME[s] for s in sub_properties]
|
||||||
|
|
||||||
|
LONGHANDS_PER_STYLE_STRUCT = []
|
||||||
|
THIS_STYLE_STRUCT_LONGHANDS = None
|
||||||
LONGHANDS = []
|
LONGHANDS = []
|
||||||
|
LONGHANDS_BY_NAME = {}
|
||||||
SHORTHANDS = []
|
SHORTHANDS = []
|
||||||
INHERITED = set()
|
|
||||||
|
def new_style_struct(name):
|
||||||
|
longhands = []
|
||||||
|
LONGHANDS_PER_STYLE_STRUCT.append((name, longhands))
|
||||||
|
global THIS_STYLE_STRUCT_LONGHANDS
|
||||||
|
THIS_STYLE_STRUCT_LONGHANDS = longhands
|
||||||
|
return ""
|
||||||
|
|
||||||
%>
|
%>
|
||||||
|
|
||||||
|
@ -44,21 +55,42 @@ pub mod longhands {
|
||||||
pub use super::*;
|
pub use super::*;
|
||||||
pub use std;
|
pub use std;
|
||||||
|
|
||||||
<%def name="longhand(name, inherited=False, no_super=False)">
|
pub fn computed_as_specified<T>(value: T, _context: &computed::Context) -> T { value }
|
||||||
|
|
||||||
|
<%def name="raw_longhand(name, inherited=False, no_super=False)">
|
||||||
<%
|
<%
|
||||||
property = Longhand(name)
|
property = Longhand(name, inherited)
|
||||||
|
THIS_STYLE_STRUCT_LONGHANDS.append(property)
|
||||||
LONGHANDS.append(property)
|
LONGHANDS.append(property)
|
||||||
if inherited:
|
LONGHANDS_BY_NAME[name] = property
|
||||||
INHERITED.add(name)
|
|
||||||
%>
|
%>
|
||||||
pub mod ${property.ident} {
|
pub mod ${property.ident} {
|
||||||
% if not no_super:
|
% if not no_super:
|
||||||
use super::*;
|
use super::*;
|
||||||
% endif
|
% endif
|
||||||
${caller.body()}
|
${caller.body()}
|
||||||
|
pub fn parse_declared(input: &[ComponentValue])
|
||||||
|
-> Option<DeclaredValue<SpecifiedValue>> {
|
||||||
|
match CSSWideKeyword::parse(input) {
|
||||||
|
Some(Left(keyword)) => Some(CSSWideKeyword(keyword)),
|
||||||
|
Some(Right(Unset)) => Some(CSSWideKeyword(${
|
||||||
|
"Inherit" if inherited else "Initial"})),
|
||||||
|
None => parse_specified(input),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
<%def name="longhand(name, inherited=False, no_super=False)">
|
||||||
|
<%self:raw_longhand name="${name}" inherited="${inherited}">
|
||||||
|
${caller.body()}
|
||||||
|
pub fn parse_specified(input: &[ComponentValue])
|
||||||
|
-> Option<DeclaredValue<SpecifiedValue>> {
|
||||||
|
parse(input).map_move(super::SpecifiedValue)
|
||||||
|
}
|
||||||
|
</%self:raw_longhand>
|
||||||
|
</%def>
|
||||||
|
|
||||||
<%def name="single_component_value(name, inherited=False)">
|
<%def name="single_component_value(name, inherited=False)">
|
||||||
<%self:longhand name="${name}" inherited="${inherited}">
|
<%self:longhand name="${name}" inherited="${inherited}">
|
||||||
${caller.body()}
|
${caller.body()}
|
||||||
|
@ -71,7 +103,8 @@ pub mod longhands {
|
||||||
<%def name="single_keyword(name, values, inherited=False)">
|
<%def name="single_keyword(name, values, inherited=False)">
|
||||||
<%self:single_component_value name="${name}" inherited="${inherited}">
|
<%self:single_component_value name="${name}" inherited="${inherited}">
|
||||||
// The computed value is the same as the specified value.
|
// The computed value is the same as the specified value.
|
||||||
pub use to_computed_value = std::util::id;
|
pub use to_computed_value = super::computed_as_specified;
|
||||||
|
#[deriving(Clone)]
|
||||||
pub enum SpecifiedValue {
|
pub enum SpecifiedValue {
|
||||||
% for value in values.split():
|
% for value in values.split():
|
||||||
${to_rust_ident(value)},
|
${to_rust_ident(value)},
|
||||||
|
@ -109,23 +142,29 @@ pub mod longhands {
|
||||||
|
|
||||||
// CSS 2.1, Section 8 - Box model
|
// CSS 2.1, Section 8 - Box model
|
||||||
|
|
||||||
|
${new_style_struct("Margin")}
|
||||||
|
|
||||||
% for side in ["top", "right", "bottom", "left"]:
|
% for side in ["top", "right", "bottom", "left"]:
|
||||||
${predefined_type("margin-" + side, "LengthOrPercentageOrAuto",
|
${predefined_type("margin-" + side, "LengthOrPercentageOrAuto",
|
||||||
"computed::LPA_Length(computed::Length(0))")}
|
"computed::LPA_Length(computed::Length(0))")}
|
||||||
% endfor
|
% endfor
|
||||||
|
|
||||||
|
${new_style_struct("Padding")}
|
||||||
|
|
||||||
% for side in ["top", "right", "bottom", "left"]:
|
% for side in ["top", "right", "bottom", "left"]:
|
||||||
${predefined_type("padding-" + side, "LengthOrPercentage",
|
${predefined_type("padding-" + side, "LengthOrPercentage",
|
||||||
"computed::LP_Length(computed::Length(0))",
|
"computed::LP_Length(computed::Length(0))",
|
||||||
"parse_non_negative")}
|
"parse_non_negative")}
|
||||||
% endfor
|
% endfor
|
||||||
|
|
||||||
|
${new_style_struct("Border")}
|
||||||
|
|
||||||
% for side in ["top", "right", "bottom", "left"]:
|
% for side in ["top", "right", "bottom", "left"]:
|
||||||
${predefined_type("border-%s-color" % side, "CSSColor", "CurrentColor")}
|
${predefined_type("border-%s-color" % side, "CSSColor", "CurrentColor")}
|
||||||
% endfor
|
% endfor
|
||||||
|
|
||||||
// dotted dashed double groove ridge insed outset hidden
|
// dotted dashed double groove ridge insed outset
|
||||||
${single_keyword("border-top-style", "none solid")}
|
${single_keyword("border-top-style", "none solid hidden")}
|
||||||
% for side in ["right", "bottom", "left"]:
|
% for side in ["right", "bottom", "left"]:
|
||||||
<%self:longhand name="border-${side}-style", no_super="True">
|
<%self:longhand name="border-${side}-style", no_super="True">
|
||||||
pub use super::border_top_style::*;
|
pub use super::border_top_style::*;
|
||||||
|
@ -165,6 +204,8 @@ pub mod longhands {
|
||||||
|
|
||||||
// CSS 2.1, Section 9 - Visual formatting model
|
// CSS 2.1, Section 9 - Visual formatting model
|
||||||
|
|
||||||
|
${new_style_struct("Box")}
|
||||||
|
|
||||||
// TODO: don't parse values we don't support
|
// TODO: don't parse values we don't support
|
||||||
${single_keyword("display",
|
${single_keyword("display",
|
||||||
"inline block list-item inline-block none "
|
"inline block list-item inline-block none "
|
||||||
|
@ -186,6 +227,7 @@ pub mod longhands {
|
||||||
"parse_non_negative")}
|
"parse_non_negative")}
|
||||||
|
|
||||||
<%self:single_component_value name="line-height">
|
<%self:single_component_value name="line-height">
|
||||||
|
#[deriving(Clone)]
|
||||||
pub enum SpecifiedValue {
|
pub enum SpecifiedValue {
|
||||||
SpecifiedNormal,
|
SpecifiedNormal,
|
||||||
SpecifiedLength(specified::Length),
|
SpecifiedLength(specified::Length),
|
||||||
|
@ -207,6 +249,7 @@ pub mod longhands {
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[deriving(Clone)]
|
||||||
pub enum ComputedValue {
|
pub enum ComputedValue {
|
||||||
Normal,
|
Normal,
|
||||||
Length(computed::Length),
|
Length(computed::Length),
|
||||||
|
@ -231,16 +274,37 @@ pub mod longhands {
|
||||||
|
|
||||||
// CSS 2.1, Section 14 - Colors and Backgrounds
|
// CSS 2.1, Section 14 - Colors and Backgrounds
|
||||||
|
|
||||||
|
${new_style_struct("Background")}
|
||||||
|
|
||||||
${predefined_type("background-color", "CSSColor",
|
${predefined_type("background-color", "CSSColor",
|
||||||
"RGBA(RGBA { red: 0., green: 0., blue: 0., alpha: 0. }) /* transparent */")}
|
"RGBA(RGBA { red: 0., green: 0., blue: 0., alpha: 0. }) /* transparent */")}
|
||||||
${predefined_type("color", "CSSColor",
|
|
||||||
"RGBA(RGBA { red: 0., green: 0., blue: 0., alpha: 1. }) /* black */",
|
|
||||||
inherited=True)}
|
${new_style_struct("Color")}
|
||||||
|
|
||||||
|
<%self:raw_longhand name="color" inherited="True">
|
||||||
|
pub use to_computed_value = super::computed_as_specified;
|
||||||
|
pub type SpecifiedValue = RGBA;
|
||||||
|
pub type ComputedValue = SpecifiedValue;
|
||||||
|
#[inline] pub fn get_initial_value() -> ComputedValue {
|
||||||
|
RGBA { red: 0., green: 0., blue: 0., alpha: 1. } /* black */
|
||||||
|
}
|
||||||
|
pub fn parse_specified(input: &[ComponentValue]) -> Option<DeclaredValue<SpecifiedValue>> {
|
||||||
|
match one_component_value(input).chain(Color::parse) {
|
||||||
|
Some(RGBA(rgba)) => Some(SpecifiedValue(rgba)),
|
||||||
|
Some(CurrentColor) => Some(CSSWideKeyword(Inherit)),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</%self:raw_longhand>
|
||||||
|
|
||||||
// CSS 2.1, Section 15 - Fonts
|
// CSS 2.1, Section 15 - Fonts
|
||||||
|
|
||||||
|
${new_style_struct("Font")}
|
||||||
|
|
||||||
<%self:longhand name="font-family" inherited="True">
|
<%self:longhand name="font-family" inherited="True">
|
||||||
pub use to_computed_value = std::util::id;
|
pub use to_computed_value = super::computed_as_specified;
|
||||||
|
#[deriving(Clone)]
|
||||||
enum FontFamily {
|
enum FontFamily {
|
||||||
FamilyName(~str),
|
FamilyName(~str),
|
||||||
// Generic
|
// Generic
|
||||||
|
@ -316,6 +380,7 @@ pub mod longhands {
|
||||||
${single_keyword("font-variant", "normal", inherited=True)} // Add small-caps when supported
|
${single_keyword("font-variant", "normal", inherited=True)} // Add small-caps when supported
|
||||||
|
|
||||||
<%self:single_component_value name="font-weight" inherited="True">
|
<%self:single_component_value name="font-weight" inherited="True">
|
||||||
|
#[deriving(Clone)]
|
||||||
pub enum SpecifiedValue {
|
pub enum SpecifiedValue {
|
||||||
Bolder,
|
Bolder,
|
||||||
Lighther,
|
Lighther,
|
||||||
|
@ -348,6 +413,7 @@ pub mod longhands {
|
||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[deriving(Clone)]
|
||||||
pub enum ComputedValue {
|
pub enum ComputedValue {
|
||||||
% for weight in range(100, 901, 100):
|
% for weight in range(100, 901, 100):
|
||||||
Weight${weight},
|
Weight${weight},
|
||||||
|
@ -407,11 +473,14 @@ pub mod longhands {
|
||||||
|
|
||||||
// CSS 2.1, Section 16 - Text
|
// CSS 2.1, Section 16 - Text
|
||||||
|
|
||||||
|
${new_style_struct("Text")}
|
||||||
|
|
||||||
// TODO: initial value should be 'start' (CSS Text Level 3, direction-dependent.)
|
// TODO: initial value should be 'start' (CSS Text Level 3, direction-dependent.)
|
||||||
${single_keyword("text-align", "left right center justify", inherited=True)}
|
${single_keyword("text-align", "left right center justify", inherited=True)}
|
||||||
|
|
||||||
<%self:longhand name="text-decoration">
|
<%self:longhand name="text-decoration">
|
||||||
pub use to_computed_value = std::util::id;
|
pub use to_computed_value = super::computed_as_specified;
|
||||||
|
#[deriving(Clone)]
|
||||||
pub struct SpecifiedValue {
|
pub struct SpecifiedValue {
|
||||||
underline: bool,
|
underline: bool,
|
||||||
overline: bool,
|
overline: bool,
|
||||||
|
@ -587,8 +656,8 @@ pub mod shorthands {
|
||||||
|
|
||||||
|
|
||||||
pub struct PropertyDeclarationBlock {
|
pub struct PropertyDeclarationBlock {
|
||||||
important: ~[PropertyDeclaration],
|
important: @[PropertyDeclaration],
|
||||||
normal: ~[PropertyDeclaration],
|
normal: @[PropertyDeclaration],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -600,6 +669,7 @@ pub fn parse_property_declaration_list(input: ~[Node]) -> PropertyDeclarationBlo
|
||||||
Decl_AtRule(rule) => log_css_error(
|
Decl_AtRule(rule) => log_css_error(
|
||||||
rule.location, fmt!("Unsupported at-rule in declaration list: @%s", rule.name)),
|
rule.location, fmt!("Unsupported at-rule in declaration list: @%s", rule.name)),
|
||||||
Declaration(Declaration{ location: l, name: n, value: v, important: i}) => {
|
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 };
|
let list = if i { &mut important } else { &mut normal };
|
||||||
if !PropertyDeclaration::parse(n, v, list) {
|
if !PropertyDeclaration::parse(n, v, list) {
|
||||||
log_css_error(l, "Invalid property declaration")
|
log_css_error(l, "Invalid property declaration")
|
||||||
|
@ -607,29 +677,37 @@ 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),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[deriving(Clone)]
|
||||||
pub enum CSSWideKeyword {
|
pub enum CSSWideKeyword {
|
||||||
Initial,
|
Initial,
|
||||||
Inherit,
|
Inherit,
|
||||||
Unset,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Unset;
|
||||||
|
|
||||||
impl CSSWideKeyword {
|
impl CSSWideKeyword {
|
||||||
pub fn parse(input: &[ComponentValue]) -> Option<CSSWideKeyword> {
|
pub fn parse(input: &[ComponentValue]) -> Option<Either<CSSWideKeyword, Unset>> {
|
||||||
do one_component_value(input).chain(get_ident_lower).chain |keyword| {
|
do one_component_value(input).chain(get_ident_lower).chain |keyword| {
|
||||||
match keyword.as_slice() {
|
match keyword.as_slice() {
|
||||||
"initial" => Some(Initial),
|
"initial" => Some(Left(Initial)),
|
||||||
"inherit" => Some(Inherit),
|
"inherit" => Some(Left(Inherit)),
|
||||||
"unset" => Some(Unset),
|
"unset" => Some(Right(Unset)),
|
||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[deriving(Clone)]
|
||||||
pub enum DeclaredValue<T> {
|
pub enum DeclaredValue<T> {
|
||||||
SpecifiedValue(T),
|
SpecifiedValue(T),
|
||||||
CSSWideKeyword(CSSWideKeyword),
|
CSSWideKeyword(CSSWideKeyword),
|
||||||
|
@ -647,24 +725,29 @@ impl PropertyDeclaration {
|
||||||
match name.to_ascii_lower().as_slice() {
|
match name.to_ascii_lower().as_slice() {
|
||||||
% for property in LONGHANDS:
|
% for property in LONGHANDS:
|
||||||
"${property.name}" => result_list.push(${property.ident}_declaration(
|
"${property.name}" => result_list.push(${property.ident}_declaration(
|
||||||
match CSSWideKeyword::parse(value) {
|
match longhands::${property.ident}::parse_declared(value) {
|
||||||
Some(keyword) => CSSWideKeyword(keyword),
|
Some(value) => value,
|
||||||
None => match longhands::${property.ident}::parse(value) {
|
None => return false,
|
||||||
Some(value) => SpecifiedValue(value),
|
|
||||||
None => return false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
% endfor
|
% endfor
|
||||||
% for shorthand in SHORTHANDS:
|
% for shorthand in SHORTHANDS:
|
||||||
"${shorthand.name}" => match CSSWideKeyword::parse(value) {
|
"${shorthand.name}" => match CSSWideKeyword::parse(value) {
|
||||||
Some(keyword) => {
|
Some(Left(keyword)) => {
|
||||||
% for sub_property in shorthand.sub_properties:
|
% for sub_property in shorthand.sub_properties:
|
||||||
result_list.push(${sub_property.ident}_declaration(
|
result_list.push(${sub_property.ident}_declaration(
|
||||||
CSSWideKeyword(keyword)
|
CSSWideKeyword(keyword)
|
||||||
));
|
));
|
||||||
% endfor
|
% endfor
|
||||||
},
|
},
|
||||||
|
Some(Right(Unset)) => {
|
||||||
|
% for sub_property in shorthand.sub_properties:
|
||||||
|
result_list.push(${sub_property.ident}_declaration(
|
||||||
|
CSSWideKeyword(${
|
||||||
|
"Inherit" if sub_property.is_inherited else "Initial"})
|
||||||
|
));
|
||||||
|
% endfor
|
||||||
|
},
|
||||||
None => match shorthands::${shorthand.ident}::parse(value) {
|
None => match shorthands::${shorthand.ident}::parse(value) {
|
||||||
Some(result) => {
|
Some(result) => {
|
||||||
% for sub_property in shorthand.sub_properties:
|
% for sub_property in shorthand.sub_properties:
|
||||||
|
@ -685,3 +768,125 @@ impl PropertyDeclaration {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub mod style_structs {
|
||||||
|
use super::longhands;
|
||||||
|
% for name, longhands in LONGHANDS_PER_STYLE_STRUCT:
|
||||||
|
pub struct ${name} {
|
||||||
|
% for longhand in longhands:
|
||||||
|
${longhand.ident}: longhands::${longhand.ident}::ComputedValue,
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ComputedValues {
|
||||||
|
% for name, longhands in LONGHANDS_PER_STYLE_STRUCT:
|
||||||
|
${name}: style_structs::${name},
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn get_initial_values() -> ComputedValues {
|
||||||
|
ComputedValues {
|
||||||
|
% for style_struct, longhands in LONGHANDS_PER_STYLE_STRUCT:
|
||||||
|
${style_struct}: style_structs::${style_struct} {
|
||||||
|
% for longhand in longhands:
|
||||||
|
${longhand.ident}: longhands::${longhand.ident}::get_initial_value(),
|
||||||
|
% endfor
|
||||||
|
},
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Most specific/important declarations last
|
||||||
|
pub fn cascade(applicable_declarations: &[@[PropertyDeclaration]],
|
||||||
|
parent_style: Option< &ComputedValues>)
|
||||||
|
-> ComputedValues {
|
||||||
|
let initial_keep_alive;
|
||||||
|
let (parent_style, is_root_element) = match parent_style {
|
||||||
|
Some(s) => (s, false),
|
||||||
|
None => {
|
||||||
|
initial_keep_alive = ~get_initial_values();
|
||||||
|
(&*initial_keep_alive, true)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
struct AllDeclaredValues {
|
||||||
|
% for property in LONGHANDS:
|
||||||
|
${property.ident}: DeclaredValue<longhands::${property.ident}::SpecifiedValue>,
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
let mut specified = AllDeclaredValues {
|
||||||
|
% for property in LONGHANDS:
|
||||||
|
${property.ident}: CSSWideKeyword(${
|
||||||
|
"Inherit" if property.is_inherited else "Initial"}),
|
||||||
|
% 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.
|
||||||
|
macro_rules! get_specified(
|
||||||
|
($style_struct: ident, $property: ident) => {
|
||||||
|
match specified.$property {
|
||||||
|
SpecifiedValue(value) => value,
|
||||||
|
CSSWideKeyword(Initial) => longhands::$property::get_initial_value(),
|
||||||
|
CSSWideKeyword(Inherit) => parent_style.$style_struct.$property.clone(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
)
|
||||||
|
macro_rules! has_border(
|
||||||
|
($property: ident) => {
|
||||||
|
match get_specified!(Border, $property) {
|
||||||
|
longhands::border_top_style::none
|
||||||
|
| longhands::border_top_style::hidden => false,
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
)
|
||||||
|
let context = &mut computed::Context {
|
||||||
|
current_color: get_specified!(Color, color),
|
||||||
|
font_size: parent_style.Font.font_size,
|
||||||
|
font_weight: parent_style.Font.font_weight,
|
||||||
|
position: get_specified!(Box, position),
|
||||||
|
float: get_specified!(Box, float),
|
||||||
|
is_root_element: is_root_element,
|
||||||
|
has_border_top: has_border!(border_top_style),
|
||||||
|
has_border_right: has_border!(border_right_style),
|
||||||
|
has_border_bottom: has_border!(border_bottom_style),
|
||||||
|
has_border_left: has_border!(border_left_style),
|
||||||
|
};
|
||||||
|
macro_rules! get_computed(
|
||||||
|
($style_struct: ident, $property: ident) => {
|
||||||
|
match specified.$property {
|
||||||
|
SpecifiedValue(ref value)
|
||||||
|
// TODO: avoid a copy?
|
||||||
|
=> longhands::$property::to_computed_value(value.clone(), context),
|
||||||
|
CSSWideKeyword(Initial) => longhands::$property::get_initial_value(),
|
||||||
|
CSSWideKeyword(Inherit) => parent_style.$style_struct.$property.clone(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
)
|
||||||
|
context.font_size = get_computed!(Font, font_size);
|
||||||
|
ComputedValues {
|
||||||
|
% for style_struct, longhands in LONGHANDS_PER_STYLE_STRUCT:
|
||||||
|
${style_struct}: style_structs::${style_struct} {
|
||||||
|
% for longhand in longhands:
|
||||||
|
${longhand.ident}: get_computed!(${style_struct}, ${longhand.ident}),
|
||||||
|
% endfor
|
||||||
|
},
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
245
src/components/script/style/selector_matching.rs
Normal file
245
src/components/script/style/selector_matching.rs
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
/* 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 added_normal_declarations = false;
|
||||||
|
let mut added_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, 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_computed_style(&self, element: AbstractNode<ScriptView>,
|
||||||
|
parent_style: Option<&ComputedValues>,
|
||||||
|
pseudo_element: Option<PseudoElement>)
|
||||||
|
-> 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<ScriptView>,
|
||||||
|
pseudo_element: Option<PseudoElement>) -> bool {
|
||||||
|
selector.pseudo_element == pseudo_element &&
|
||||||
|
matches_compound_selector(&selector.compound_selectors, element)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn matches_compound_selector(selector: &CompoundSelector,
|
||||||
|
element: AbstractNode<ScriptView>) -> 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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ pub struct Selector {
|
||||||
pub static STYLE_ATTRIBUTE_SPECIFICITY: u32 = 1 << 31;
|
pub static STYLE_ATTRIBUTE_SPECIFICITY: u32 = 1 << 31;
|
||||||
|
|
||||||
|
|
||||||
|
#[deriving(Eq)]
|
||||||
pub enum PseudoElement {
|
pub enum PseudoElement {
|
||||||
Before,
|
Before,
|
||||||
After,
|
After,
|
||||||
|
@ -40,30 +41,29 @@ pub enum Combinator {
|
||||||
pub enum SimpleSelector {
|
pub enum SimpleSelector {
|
||||||
IDSelector(~str),
|
IDSelector(~str),
|
||||||
ClassSelector(~str),
|
ClassSelector(~str),
|
||||||
LocalNameSelector{lowercase_name: ~str, cased_name: ~str},
|
LocalNameSelector(~str),
|
||||||
NamespaceSelector(~str),
|
NamespaceSelector(~str),
|
||||||
|
|
||||||
// Attribute selectors
|
// Attribute selectors
|
||||||
AttrExists(AttrSelector), // [foo]
|
AttrExists(AttrSelector), // [foo]
|
||||||
AttrEqual(AttrSelector, ~str), // [foo=bar]
|
AttrEqual(AttrSelector, ~str), // [foo=bar]
|
||||||
AttrIncludes(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]
|
AttrPrefixMatch(AttrSelector, ~str), // [foo^=bar]
|
||||||
AttrSubstringMatch(AttrSelector, ~str), // [foo*=bar]
|
AttrSubstringMatch(AttrSelector, ~str), // [foo*=bar]
|
||||||
AttrSuffixMatch(AttrSelector, ~str), // [foo$=bar]
|
AttrSuffixMatch(AttrSelector, ~str), // [foo$=bar]
|
||||||
|
|
||||||
// Pseudo-classes
|
// Pseudo-classes
|
||||||
Empty,
|
// Empty,
|
||||||
Root,
|
// Root,
|
||||||
Lang(~str),
|
// Lang(~str),
|
||||||
NthChild(i32, i32),
|
// NthChild(i32, i32),
|
||||||
Negation(~[SimpleSelector]),
|
Negation(~[SimpleSelector]),
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AttrSelector {
|
pub struct AttrSelector {
|
||||||
lowercase_name: ~str,
|
name: ~str,
|
||||||
cased_name: ~str,
|
|
||||||
namespace: Option<~str>,
|
namespace: Option<~str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ type Iter = iterator::Peekable<ComponentValue, vec::MoveIterator<ComponentValue>
|
||||||
|
|
||||||
// None means invalid selector
|
// None means 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();
|
||||||
let first = match parse_selector(iter, namespaces) {
|
let first = match parse_selector(iter, namespaces) {
|
||||||
None => return None,
|
None => return None,
|
||||||
|
@ -99,7 +99,7 @@ pub fn parse_selector_list(input: ~[ComponentValue], namespaces: &NamespaceMap)
|
||||||
|
|
||||||
// None means invalid selector
|
// 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) {
|
||||||
None => return None,
|
None => return None,
|
||||||
Some(result) => result
|
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),
|
specificity: compute_specificity(&compound, &pseudo_element),
|
||||||
compound_selectors: compound,
|
compound_selectors: compound,
|
||||||
pseudo_element: pseudo_element,
|
pseudo_element: pseudo_element,
|
||||||
};
|
})
|
||||||
Some(selector)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -168,12 +167,12 @@ fn compute_specificity(mut selector: &CompoundSelector,
|
||||||
specificity: &mut Specificity) {
|
specificity: &mut Specificity) {
|
||||||
for simple_selector in simple_selectors.iter() {
|
for simple_selector in simple_selectors.iter() {
|
||||||
match simple_selector {
|
match simple_selector {
|
||||||
&LocalNameSelector{_} => specificity.element_selectors += 1,
|
&LocalNameSelector(*) => specificity.element_selectors += 1,
|
||||||
&IDSelector(*) => specificity.id_selectors += 1,
|
&IDSelector(*) => specificity.id_selectors += 1,
|
||||||
&ClassSelector(*)
|
&ClassSelector(*)
|
||||||
| &AttrExists(*) | &AttrEqual(*) | &AttrIncludes(*) | &AttrDashMatch(*)
|
| &AttrExists(*) | &AttrEqual(*) | &AttrIncludes(*) | &AttrDashMatch(*)
|
||||||
| &AttrPrefixMatch(*) | &AttrSubstringMatch(*) | &AttrSuffixMatch(*)
|
| &AttrPrefixMatch(*) | &AttrSubstringMatch(*) | &AttrSuffixMatch(*)
|
||||||
| &Empty | &Root | &Lang(*) | &NthChild(*)
|
// | &Empty | &Root | &Lang(*) | &NthChild(*)
|
||||||
=> specificity.class_like_selectors += 1,
|
=> specificity.class_like_selectors += 1,
|
||||||
&NamespaceSelector(*) => (),
|
&NamespaceSelector(*) => (),
|
||||||
&Negation(ref negated)
|
&Negation(ref negated)
|
||||||
|
@ -214,8 +213,8 @@ fn parse_simple_selectors(iter: &mut Iter, namespaces: &NamespaceMap)
|
||||||
|
|
||||||
|
|
||||||
// None means invalid selector
|
// None means invalid selector
|
||||||
// Some(None) means no type selector
|
// Some(None) means no type selector.
|
||||||
// Some(Some([...])) is a type selector. Might be empty for *|*
|
// Some(Some(~[...])) is a type selector. Might be empty for *|*
|
||||||
fn parse_type_selector(iter: &mut Iter, namespaces: &NamespaceMap)
|
fn parse_type_selector(iter: &mut Iter, namespaces: &NamespaceMap)
|
||||||
-> Option<Option<~[SimpleSelector]>> {
|
-> Option<Option<~[SimpleSelector]>> {
|
||||||
skip_whitespace(iter);
|
skip_whitespace(iter);
|
||||||
|
@ -229,10 +228,7 @@ fn parse_type_selector(iter: &mut Iter, namespaces: &NamespaceMap)
|
||||||
None => (),
|
None => (),
|
||||||
}
|
}
|
||||||
match local_name {
|
match local_name {
|
||||||
Some(name) => simple_selectors.push(LocalNameSelector{
|
Some(name) => simple_selectors.push(LocalNameSelector(name)),
|
||||||
lowercase_name: name.to_ascii_lower(),
|
|
||||||
cased_name: name,
|
|
||||||
}),
|
|
||||||
None => (),
|
None => (),
|
||||||
}
|
}
|
||||||
Some(Some(simple_selectors))
|
Some(Some(simple_selectors))
|
||||||
|
@ -242,6 +238,10 @@ fn parse_type_selector(iter: &mut Iter, namespaces: &NamespaceMap)
|
||||||
|
|
||||||
|
|
||||||
// Parse a simple selector other than a type selector
|
// Parse a simple selector other than a type selector
|
||||||
|
// None means invalid selector
|
||||||
|
// Some(None) means not a simple name
|
||||||
|
// Some(Some(Left(s)) is a simple selector
|
||||||
|
// Some(Some(Right(p)) is a pseudo-element
|
||||||
fn parse_one_simple_selector(iter: &mut Iter, namespaces: &NamespaceMap, inside_negation: bool)
|
fn parse_one_simple_selector(iter: &mut Iter, namespaces: &NamespaceMap, inside_negation: bool)
|
||||||
-> Option<Option<Either<SimpleSelector, PseudoElement>>> {
|
-> Option<Option<Either<SimpleSelector, PseudoElement>>> {
|
||||||
match iter.peek() {
|
match iter.peek() {
|
||||||
|
@ -292,6 +292,7 @@ fn parse_one_simple_selector(iter: &mut Iter, namespaces: &NamespaceMap, inside_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// None means invalid selector
|
// None means invalid selector
|
||||||
// Some(None) means not a qualified name
|
// Some(None) means not a qualified name
|
||||||
// Some(Some((None, None)) means *|*
|
// Some(Some((None, None)) means *|*
|
||||||
|
@ -304,16 +305,14 @@ fn parse_qualified_name(iter: &mut Iter, allow_universal: bool, namespaces: &Nam
|
||||||
#[inline]
|
#[inline]
|
||||||
fn default_namespace(namespaces: &NamespaceMap, local_name: Option<~str>)
|
fn default_namespace(namespaces: &NamespaceMap, local_name: Option<~str>)
|
||||||
-> Option<Option<(Option<~str>, Option<~str>)>> {
|
-> Option<Option<(Option<~str>, Option<~str>)>> {
|
||||||
match namespaces.default {
|
Some(Some((namespaces.default.map(|url| url.to_owned()), local_name)))
|
||||||
None => Some(Some((None, local_name))),
|
|
||||||
Some(ref url) => Some(Some((Some(url.to_owned()), local_name))),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn explicit_namespace(iter: &mut Iter, allow_universal: bool, namespace_url: Option<~str>)
|
fn explicit_namespace(iter: &mut Iter, allow_universal: bool, namespace_url: Option<~str>)
|
||||||
-> Option<Option<(Option<~str>, Option<~str>)>> {
|
-> Option<Option<(Option<~str>, Option<~str>)>> {
|
||||||
assert!(iter.next() == Some(Delim('|')));
|
assert!(iter.next() == Some(Delim('|')),
|
||||||
|
"Implementation error, this should not happen.");
|
||||||
match iter.peek() {
|
match iter.peek() {
|
||||||
Some(&Delim('*')) if allow_universal => {
|
Some(&Delim('*')) if allow_universal => {
|
||||||
iter.next();
|
iter.next();
|
||||||
|
@ -331,24 +330,24 @@ fn parse_qualified_name(iter: &mut Iter, allow_universal: bool, namespaces: &Nam
|
||||||
Some(&Ident(_)) => {
|
Some(&Ident(_)) => {
|
||||||
let value = get_next_ident(iter);
|
let value = get_next_ident(iter);
|
||||||
match iter.peek() {
|
match iter.peek() {
|
||||||
Some(&Delim('|')) => default_namespace(namespaces, Some(value)),
|
Some(&Delim('|')) => {
|
||||||
_ => {
|
|
||||||
let namespace_url = match namespaces.prefix_map.find(&value) {
|
let namespace_url = match namespaces.prefix_map.find(&value) {
|
||||||
None => return None, // Undeclared namespace prefix: invalid selector
|
None => return None, // Undeclared namespace prefix: invalid selector
|
||||||
Some(ref url) => url.to_owned(),
|
Some(ref url) => url.to_owned(),
|
||||||
};
|
};
|
||||||
explicit_namespace(iter, allow_universal, Some(namespace_url))
|
explicit_namespace(iter, allow_universal, Some(namespace_url))
|
||||||
},
|
},
|
||||||
|
_ => default_namespace(namespaces, Some(value)),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(&Delim('*')) => {
|
Some(&Delim('*')) => {
|
||||||
iter.next(); // Consume '*'
|
iter.next(); // Consume '*'
|
||||||
match iter.peek() {
|
match iter.peek() {
|
||||||
Some(&Delim('|')) => {
|
Some(&Delim('|')) => explicit_namespace(iter, allow_universal, None),
|
||||||
|
_ => {
|
||||||
if allow_universal { default_namespace(namespaces, None) }
|
if allow_universal { default_namespace(namespaces, None) }
|
||||||
else { None }
|
else { None }
|
||||||
},
|
},
|
||||||
_ => explicit_namespace(iter, allow_universal, None),
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(&Delim('|')) => explicit_namespace(iter, allow_universal, Some(~"")),
|
Some(&Delim('|')) => explicit_namespace(iter, allow_universal, Some(~"")),
|
||||||
|
@ -366,11 +365,11 @@ fn parse_attribute_selector(content: ~[ComponentValue], namespaces: &NamespaceMa
|
||||||
Some(Some((_, None))) => fail!("Implementation error, this should not happen."),
|
Some(Some((_, None))) => fail!("Implementation error, this should not happen."),
|
||||||
Some(Some((namespace, Some(local_name)))) => AttrSelector {
|
Some(Some((namespace, Some(local_name)))) => AttrSelector {
|
||||||
namespace: namespace,
|
namespace: namespace,
|
||||||
lowercase_name: local_name.to_ascii_lower(),
|
name: local_name,
|
||||||
cased_name: local_name,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
skip_whitespace(iter);
|
skip_whitespace(iter);
|
||||||
|
// TODO: deal with empty value or value containing whitespace (see spec)
|
||||||
macro_rules! get_value( () => {{
|
macro_rules! get_value( () => {{
|
||||||
skip_whitespace(iter);
|
skip_whitespace(iter);
|
||||||
match iter.next() {
|
match iter.next() {
|
||||||
|
@ -382,7 +381,11 @@ fn parse_attribute_selector(content: ~[ComponentValue], namespaces: &NamespaceMa
|
||||||
None => AttrExists(attr), // [foo]
|
None => AttrExists(attr), // [foo]
|
||||||
Some(Delim('=')) => AttrEqual(attr, get_value!()), // [foo=bar]
|
Some(Delim('=')) => AttrEqual(attr, get_value!()), // [foo=bar]
|
||||||
Some(IncludeMatch) => AttrIncludes(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(PrefixMatch) => AttrPrefixMatch(attr, get_value!()), // [foo^=bar]
|
||||||
Some(SubstringMatch) => AttrSubstringMatch(attr, get_value!()), // [foo*=bar]
|
Some(SubstringMatch) => AttrSubstringMatch(attr, get_value!()), // [foo*=bar]
|
||||||
Some(SuffixMatch) => AttrSuffixMatch(attr, get_value!()), // [foo$=bar]
|
Some(SuffixMatch) => AttrSuffixMatch(attr, get_value!()), // [foo$=bar]
|
||||||
|
@ -395,8 +398,8 @@ fn parse_attribute_selector(content: ~[ComponentValue], namespaces: &NamespaceMa
|
||||||
|
|
||||||
fn parse_simple_pseudo_class(name: ~str) -> Option<Either<SimpleSelector, PseudoElement>> {
|
fn parse_simple_pseudo_class(name: ~str) -> Option<Either<SimpleSelector, PseudoElement>> {
|
||||||
match name.to_ascii_lower().as_slice() {
|
match name.to_ascii_lower().as_slice() {
|
||||||
"root" => Some(Left(Root)),
|
// "root" => Some(Left(Root)),
|
||||||
"empty" => Some(Left(Empty)),
|
// "empty" => Some(Left(Empty)),
|
||||||
|
|
||||||
// Supported CSS 2.1 pseudo-elements only.
|
// Supported CSS 2.1 pseudo-elements only.
|
||||||
"before" => Some(Right(Before)),
|
"before" => Some(Right(Before)),
|
||||||
|
@ -412,8 +415,8 @@ fn parse_functional_pseudo_class(name: ~str, arguments: ~[ComponentValue],
|
||||||
namespaces: &NamespaceMap, inside_negation: bool)
|
namespaces: &NamespaceMap, inside_negation: bool)
|
||||||
-> Option<SimpleSelector> {
|
-> Option<SimpleSelector> {
|
||||||
match name.to_ascii_lower().as_slice() {
|
match name.to_ascii_lower().as_slice() {
|
||||||
"lang" => parse_lang(arguments),
|
// "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)),
|
||||||
"not" => if inside_negation { None } else { parse_negation(arguments, namespaces) },
|
"not" => if inside_negation { None } else { parse_negation(arguments, namespaces) },
|
||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
|
@ -432,16 +435,16 @@ fn parse_pseudo_element(name: ~str) -> Option<PseudoElement> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn parse_lang(arguments: ~[ComponentValue]) -> Option<SimpleSelector> {
|
//fn parse_lang(arguments: ~[ComponentValue]) -> Option<SimpleSelector> {
|
||||||
let mut iter = arguments.move_skip_whitespace();
|
// let mut iter = arguments.move_skip_whitespace();
|
||||||
match iter.next() {
|
// match iter.next() {
|
||||||
Some(Ident(value)) => {
|
// Some(Ident(value)) => {
|
||||||
if "" == value || iter.next().is_some() { None }
|
// if "" == value || iter.next().is_some() { None }
|
||||||
else { Some(Lang(value)) }
|
// else { Some(Lang(value)) }
|
||||||
},
|
// },
|
||||||
_ => None,
|
// _ => None,
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
|
||||||
// Level 3: Parse ONE simple_selector
|
// Level 3: Parse ONE simple_selector
|
||||||
|
|
|
@ -26,12 +26,12 @@ pub enum CSSRule {
|
||||||
|
|
||||||
|
|
||||||
pub struct StyleRule {
|
pub struct StyleRule {
|
||||||
selectors: ~[selectors::Selector],
|
selectors: ~[@selectors::Selector],
|
||||||
declarations: properties::PropertyDeclarationBlock,
|
declarations: properties::PropertyDeclarationBlock,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn parse_stylesheet(css: &str) -> Stylesheet {
|
pub fn parse_stylesheet(css: &str) -> Stylesheet {
|
||||||
static STATE_CHARSET: uint = 1;
|
static STATE_CHARSET: uint = 1;
|
||||||
static STATE_IMPORTS: uint = 2;
|
static STATE_IMPORTS: uint = 2;
|
||||||
static STATE_NAMESPACES: uint = 3;
|
static STATE_NAMESPACES: uint = 3;
|
||||||
|
@ -117,7 +117,8 @@ pub fn parse_nested_at_rule(lower_name: &str, rule: AtRule,
|
||||||
|
|
||||||
|
|
||||||
impl Stylesheet {
|
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)] }
|
StyleRuleIterator { device: device, stack: ~[(self.rules.as_slice(), 0)] }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
6805
src/components/script/style/tests/bootstrap-v3.0.0.css
vendored
Normal file
6805
src/components/script/style/tests/bootstrap-v3.0.0.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
12
src/components/script/style/tests/mod.rs
Normal file
12
src/components/script/style/tests/mod.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
/* 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 super::stylesheets::parse_stylesheet;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bootstrap() {
|
||||||
|
// Test that parsing bootstrap does not trigger an assertion or otherwise fail.
|
||||||
|
let stylesheet = parse_stylesheet(include_str!("bootstrap-v3.0.0.css"));
|
||||||
|
assert!(stylesheet.rules.len() > 100); // This depends on whet selectors are supported.
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue