Cargoify servo

This commit is contained in:
Jack Moffitt 2014-08-28 09:34:23 -06:00
parent db2f642c32
commit c6ab60dbfc
1761 changed files with 8423 additions and 2294 deletions

2
components/style/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
properties/mod.rs
properties/mod.rs.tmp

View file

@ -0,0 +1,31 @@
[package]
name = "style"
version = "0.0.1"
authors = ["The Servo Project Developers"]
build = "make -f makefile.cargo"
[lib]
name = "style"
path = "lib.rs"
doc = false
[dependencies.macros]
path = "../macros"
[dependencies.util]
path = "../util"
[dependencies.geom]
git = "https://github.com/servo/rust-geom"
[dependencies.url]
git = "https://github.com/servo/rust-url"
[dependencies.cssparser]
git = "https://github.com/servo/rust-cssparser"
[dependencies.encoding]
git = "https://github.com/lifthrasiir/rust-encoding"

Binary file not shown.

View file

@ -0,0 +1,6 @@
servo-style
===========
Prototype replacement style system for Servo,
based on [rust-cssparser](https://github.com/mozilla-servo/rust-cssparser)
instead of [NetSurfs libcss](https://github.com/mozilla-servo/libcss).

View file

@ -0,0 +1,32 @@
/* 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 cssparser::ast::{SyntaxError, SourceLocation};
pub struct ErrorLoggerIterator<I>(pub I);
impl<T, I: Iterator<Result<T, SyntaxError>>> Iterator<T> for ErrorLoggerIterator<I> {
fn next(&mut self) -> Option<T> {
let ErrorLoggerIterator(ref mut this) = *self;
loop {
match this.next() {
Some(Ok(v)) => return Some(v),
Some(Err(error)) => log_css_error(error.location,
format!("{:?}", error.reason).as_slice()),
None => return None,
}
}
}
}
/// Defaults to a no-op.
/// Set a `RUST_LOG=style::errors` environment variable
/// to log CSS parse errors to stderr.
pub fn log_css_error(location: SourceLocation, message: &str) {
// TODO eventually this will got into a "web console" or something.
info!("{:u}:{:u} {:s}", location.line, location.column, message)
}

View file

@ -0,0 +1,183 @@
/* 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 cssparser::ast::*;
use cssparser::parse_declaration_list;
use errors::{ErrorLoggerIterator, log_css_error};
use std::ascii::StrAsciiExt;
use parsing_utils::{BufferedIter, ParserIter, parse_slice_comma_separated};
use properties::longhands::font_family::parse_one_family;
use properties::computed_values::font_family::FamilyName;
use stylesheets::{CSSRule, CSSFontFaceRule, CSSStyleRule, CSSMediaRule};
use media_queries::{Device, Screen};
use url::{Url, UrlParser};
static SUPPORTED_FORMATS: &'static [&'static str] = &["truetype", "opentype"];
pub fn iter_font_face_rules_inner(rules: &[CSSRule], callback: |family: &str, source: &Url|) {
let device = &Device { media_type: Screen }; // TODO, use Print when printing
for rule in rules.iter() {
match *rule {
CSSStyleRule(_) => {},
CSSMediaRule(ref rule) => if rule.media_queries.evaluate(device) {
iter_font_face_rules_inner(rule.rules.as_slice(), |f, s| callback(f, s))
},
CSSFontFaceRule(ref rule) => {
for source in rule.sources.iter() {
if source.format_hints.is_empty() || source.format_hints.iter().any(
|f| SUPPORTED_FORMATS.iter().any(
|s| f.as_slice().eq_ignore_ascii_case(*s))) {
callback(rule.family.as_slice(), &source.url)
}
}
},
}
}
}
enum Source {
UrlSource(UrlSource),
LocalSource(String),
}
pub struct UrlSource {
pub url: Url,
pub format_hints: Vec<String>,
}
pub struct FontFaceRule {
pub family: String,
pub sources: Vec<UrlSource>, // local() is not supported yet
}
pub fn parse_font_face_rule(rule: AtRule, parent_rules: &mut Vec<CSSRule>, base_url: &Url) {
if rule.prelude.as_slice().skip_whitespace().next().is_some() {
log_css_error(rule.location, "@font-face prelude contains unexpected characters");
return;
}
let block = match rule.block {
Some(block) => block,
None => {
log_css_error(rule.location, "Invalid @font-face rule");
return
}
};
let mut maybe_family = None;
let mut maybe_sources = None;
for item in ErrorLoggerIterator(parse_declaration_list(block.move_iter())) {
match item {
DeclAtRule(rule) => log_css_error(
rule.location, format!("Unsupported at-rule in declaration list: @{:s}", rule.name).as_slice()),
Declaration(Declaration{ location, name, value, important }) => {
if important {
log_css_error(location, "!important is not allowed on @font-face descriptors");
continue
}
let name_lower = name.as_slice().to_ascii_lower();
match name_lower.as_slice() {
"font-family" => {
let iter = &mut BufferedIter::new(value.as_slice().skip_whitespace());
match parse_one_family(iter) {
Ok(FamilyName(name)) => {
maybe_family = Some(name);
},
// This also includes generic family names:
_ => log_css_error(location, "Invalid font-family in @font-face"),
}
},
"src" => {
match parse_slice_comma_separated(
value.as_slice(), |iter| parse_one_url_src(iter, base_url)) {
Ok(sources) => maybe_sources = Some(sources),
Err(()) => log_css_error(location, "Invalid src in @font-face"),
};
},
_ => {
log_css_error(location, format!("Unsupported declaration {:s}", name).as_slice());
}
}
}
}
}
match (maybe_family, maybe_sources) {
(Some(family), Some(sources)) => parent_rules.push(CSSFontFaceRule(FontFaceRule {
family: family,
sources: sources,
})),
(None, _) => log_css_error(rule.location, "@font-face without a font-family descriptor"),
_ => log_css_error(rule.location, "@font-face without an src descriptor"),
}
}
/// local() is not supported yet
fn parse_one_url_src(iter: ParserIter, base_url: &Url) -> Result<UrlSource, ()> {
match parse_one_src(iter, base_url) {
Ok(UrlSource(source)) => Ok(source),
_ => Err(())
}
}
fn parse_one_src(iter: ParserIter, base_url: &Url) -> Result<Source, ()> {
let url = match iter.next() {
// Parsing url()
Some(&URL(ref url)) => {
UrlParser::new().base_url(base_url).parse(url.as_slice()).unwrap_or_else(
|_error| Url::parse("about:invalid").unwrap())
},
// Parsing local() with early return()
Some(&Function(ref name, ref arguments)) => {
if name.as_slice().eq_ignore_ascii_case("local") {
let iter = &mut BufferedIter::new(arguments.as_slice().skip_whitespace());
match parse_one_family(iter) {
Ok(FamilyName(name)) => return Ok(LocalSource(name)),
_ => return Err(())
}
}
return Err(())
},
_ => return Err(())
};
// Parsing optional format()
let format_hints = match iter.next() {
Some(&Function(ref name, ref arguments)) => {
if !name.as_slice().eq_ignore_ascii_case("format") {
return Err(())
}
try!(parse_slice_comma_separated(arguments.as_slice(), parse_one_format))
}
Some(component_value) => {
iter.push_back(component_value);
vec![]
}
None => vec![],
};
Ok(UrlSource(UrlSource {
url: url,
format_hints: format_hints,
}))
}
fn parse_one_format(iter: ParserIter) -> Result<String, ()> {
match iter.next() {
Some(&String(ref value)) => {
if iter.next().is_none() {
Ok(value.clone())
} else {
Err(())
}
}
_ => Err(())
}
}

52
components/style/lib.rs Normal file
View file

@ -0,0 +1,52 @@
/* 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/. */
#![comment = "The Servo Parallel Browser Project"]
#![license = "MPL"]
#![feature(globs, macro_rules)]
#![feature(phase)]
#[phase(plugin, link)] extern crate log;
extern crate debug;
extern crate collections;
extern crate geom;
extern crate num;
extern crate serialize;
extern crate sync;
extern crate url;
extern crate cssparser;
extern crate encoding;
#[phase(plugin)]
extern crate servo_macros = "macros";
extern crate servo_util = "util";
// Public API
pub use stylesheets::{Stylesheet, iter_font_face_rules};
pub use selector_matching::{Stylist, StylesheetOrigin, UserAgentOrigin, AuthorOrigin, UserOrigin};
pub use selector_matching::{DeclarationBlock, matches};
pub use properties::{cascade, cascade_anonymous};
pub use properties::{PropertyDeclaration, ComputedValues, computed_values, style_structs};
pub use properties::{PropertyDeclarationBlock, parse_style_attribute}; // Style attributes
pub use properties::{CSSFloat, DeclaredValue, PropertyDeclarationParseResult};
pub use properties::longhands;
pub use node::{TElement, TNode};
pub use selectors::{PseudoElement, Before, After, SelectorList, parse_selector_list_from_str};
pub use selectors::{AttrSelector, NamespaceConstraint, SpecificNamespace, AnyNamespace};
pub use cssparser::{Color, RGBA};
mod stylesheets;
mod errors;
mod selectors;
mod selector_matching;
mod properties;
mod namespaces;
mod node;
mod media_queries;
mod parsing_utils;
mod font_face;

View file

@ -0,0 +1,8 @@
MAKO_ZIP = Mako-0.9.1.zip
PYTHON = $(shell which python2.7 2>/dev/null || echo python)
all: properties/mod.rs
properties/mod.rs: properties/mod.rs.mako
PYTHONPATH=$(MAKO_ZIP) $(PYTHON) -c "from mako.template import Template; print(Template(filename='$<').render())" > $@.tmp
mv $@.tmp $@

View file

@ -0,0 +1,131 @@
/* 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 cssparser::parse_rule_list;
use cssparser::ast::*;
use errors::{ErrorLoggerIterator, log_css_error};
use stylesheets::{CSSRule, CSSMediaRule, parse_style_rule, parse_nested_at_rule};
use namespaces::NamespaceMap;
use url::Url;
pub struct MediaRule {
pub media_queries: MediaQueryList,
pub rules: Vec<CSSRule>,
}
pub struct MediaQueryList {
// "not all" is omitted from the list.
// An empty list never matches.
media_queries: Vec<MediaQuery>
}
// For now, this is a "Level 2 MQ", ie. a media type.
pub struct MediaQuery {
media_type: MediaQueryType,
// TODO: Level 3 MQ expressions
}
pub enum MediaQueryType {
All, // Always true
MediaType(MediaType),
}
#[deriving(PartialEq)]
pub enum MediaType {
Screen,
Print,
}
pub struct Device {
pub media_type: MediaType,
// TODO: Level 3 MQ data: viewport size, etc.
}
pub fn parse_media_rule(rule: AtRule, parent_rules: &mut Vec<CSSRule>,
namespaces: &NamespaceMap, base_url: &Url) {
let media_queries = parse_media_query_list(rule.prelude.as_slice());
let block = match rule.block {
Some(block) => block,
None => {
log_css_error(rule.location, "Invalid @media rule");
return
}
};
let mut rules = vec!();
for rule in ErrorLoggerIterator(parse_rule_list(block.move_iter())) {
match rule {
QualifiedRule(rule) => parse_style_rule(rule, &mut rules, namespaces, base_url),
AtRule(rule) => parse_nested_at_rule(
rule.name.as_slice().to_ascii_lower().as_slice(), rule, &mut rules, namespaces, base_url),
}
}
parent_rules.push(CSSMediaRule(MediaRule {
media_queries: media_queries,
rules: rules,
}))
}
pub fn parse_media_query_list(input: &[ComponentValue]) -> MediaQueryList {
let iter = &mut input.skip_whitespace();
let mut next = iter.next();
if next.is_none() {
return MediaQueryList{ media_queries: vec!(MediaQuery{media_type: All}) }
}
let mut queries = vec!();
loop {
let mq = match next {
Some(&Ident(ref value)) => {
match value.as_slice().to_ascii_lower().as_slice() {
"screen" => Some(MediaQuery{ media_type: MediaType(Screen) }),
"print" => Some(MediaQuery{ media_type: MediaType(Print) }),
"all" => Some(MediaQuery{ media_type: All }),
_ => None
}
},
_ => None
};
match iter.next() {
None => {
for mq in mq.move_iter() {
queries.push(mq);
}
return MediaQueryList{ media_queries: queries }
},
Some(&Comma) => {
for mq in mq.move_iter() {
queries.push(mq);
}
},
// Ingnore this comma-separated part
_ => loop {
match iter.next() {
Some(&Comma) => break,
None => return MediaQueryList{ media_queries: queries },
_ => (),
}
},
}
next = iter.next();
}
}
impl MediaQueryList {
pub fn evaluate(&self, device: &Device) -> bool {
self.media_queries.iter().any(|mq| {
match mq.media_type {
MediaType(media_type) => media_type == device.media_type,
All => true,
}
// TODO: match Level 3 expressions
})
}
}

View file

@ -0,0 +1,64 @@
/* 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 cssparser::ast::*;
use std::collections::hashmap::HashMap;
use servo_util::namespace::Namespace;
use errors::log_css_error;
pub struct NamespaceMap {
pub default: Option<Namespace>,
pub prefix_map: HashMap<String, Namespace>,
}
impl NamespaceMap {
pub fn new() -> NamespaceMap {
NamespaceMap { default: None, prefix_map: HashMap::new() }
}
}
pub fn parse_namespace_rule(rule: AtRule, namespaces: &mut NamespaceMap) {
let location = rule.location;
macro_rules! syntax_error(
() => {{
log_css_error(location, "Invalid @namespace rule");
return
}};
);
if rule.block.is_some() { syntax_error!() }
let mut prefix: Option<String> = None;
let mut ns: Option<Namespace> = None;
let mut iter = rule.prelude.move_skip_whitespace();
for component_value in iter {
match component_value {
Ident(value) => {
if prefix.is_some() { syntax_error!() }
prefix = Some(value.into_string());
},
URL(value) | String(value) => {
if ns.is_some() { syntax_error!() }
ns = Some(Namespace::from_str(value.as_slice()));
break
},
_ => syntax_error!(),
}
}
if iter.next().is_some() { syntax_error!() }
match (prefix, ns) {
(Some(prefix), Some(ns)) => {
if namespaces.prefix_map.swap(prefix, ns).is_some() {
log_css_error(location, "Duplicate @namespace rule");
}
},
(None, Some(ns)) => {
if namespaces.default.is_some() {
log_css_error(location, "Duplicate @namespace rule");
}
namespaces.default = Some(ns);
},
_ => syntax_error!()
}
}

34
components/style/node.rs Normal file
View file

@ -0,0 +1,34 @@
/* 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/. */
//! Traits that nodes must implement. Breaks the otherwise-cyclic dependency between layout and
//! style.
use selectors::AttrSelector;
use servo_util::atom::Atom;
use servo_util::namespace::Namespace;
pub trait TNode<E:TElement> : Clone {
fn parent_node(&self) -> Option<Self>;
fn prev_sibling(&self) -> Option<Self>;
fn next_sibling(&self) -> Option<Self>;
fn is_document(&self) -> bool;
fn is_element(&self) -> bool;
fn as_element(&self) -> E;
fn match_attr(&self, attr: &AttrSelector, test: |&str| -> bool) -> bool;
fn is_html_element_in_html_document(&self) -> bool;
}
pub trait TElement {
fn get_attr(&self, namespace: &Namespace, attr: &str) -> Option<&'static str>;
fn get_link(&self) -> Option<&'static str>;
fn get_local_name<'a>(&'a self) -> &'a Atom;
fn get_namespace<'a>(&'a self) -> &'a Namespace;
fn get_hover_state(&self) -> bool;
fn get_id(&self) -> Option<Atom>;
fn get_disabled_state(&self) -> bool;
fn get_enabled_state(&self) -> bool;
}

View file

@ -0,0 +1,81 @@
/* 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 cssparser::ast::{ComponentValue, Ident, Comma, SkipWhitespaceIterable, SkipWhitespaceIterator};
pub fn one_component_value<'a>(input: &'a [ComponentValue]) -> Result<&'a ComponentValue, ()> {
let mut iter = input.skip_whitespace();
match iter.next() {
Some(value) => if iter.next().is_none() { Ok(value) } else { Err(()) },
None => Err(())
}
}
pub fn get_ident_lower(component_value: &ComponentValue) -> Result<String, ()> {
match component_value {
&Ident(ref value) => Ok(value.as_slice().to_ascii_lower()),
_ => Err(()),
}
}
pub struct BufferedIter<E, I> {
iter: I,
buffer: Option<E>,
}
impl<E, I: Iterator<E>> BufferedIter<E, I> {
pub fn new(iter: I) -> BufferedIter<E, I> {
BufferedIter {
iter: iter,
buffer: None,
}
}
#[inline]
pub fn push_back(&mut self, value: E) {
assert!(self.buffer.is_none());
self.buffer = Some(value);
}
}
impl<E, I: Iterator<E>> Iterator<E> for BufferedIter<E, I> {
#[inline]
fn next(&mut self) -> Option<E> {
if self.buffer.is_some() {
self.buffer.take()
}
else {
self.iter.next()
}
}
}
pub type ParserIter<'a, 'b> = &'a mut BufferedIter<&'b ComponentValue, SkipWhitespaceIterator<'b>>;
#[inline]
pub fn parse_slice_comma_separated<T>(input: &[ComponentValue],
parse_one: |ParserIter| -> Result<T, ()>)
-> Result<Vec<T>, ()> {
parse_comma_separated(&mut BufferedIter::new(input.skip_whitespace()), parse_one)
}
#[inline]
pub fn parse_comma_separated<T>(iter: ParserIter,
parse_one: |ParserIter| -> Result<T, ()>)
-> Result<Vec<T>, ()> {
let mut values = vec![try!(parse_one(iter))];
for component_value in iter {
match component_value {
&Comma => values.push(try!(parse_one(iter))),
_ => return Err(())
}
}
Ok(values)
}

View file

@ -0,0 +1,262 @@
/* 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/. */
#![allow(non_camel_case_types)]
use url::{Url, UrlParser};
pub use servo_util::geometry::Au;
pub type CSSFloat = f64;
pub static DEFAULT_LINE_HEIGHT: CSSFloat = 1.14;
pub mod specified {
use std::ascii::StrAsciiExt;
use cssparser::ast;
use cssparser::ast::*;
use super::{Au, CSSFloat};
pub use CSSColor = cssparser::Color;
#[deriving(Clone)]
pub enum Length {
Au_(Au), // application units
Em(CSSFloat),
Ex(CSSFloat),
// XXX uncomment when supported:
// Ch(CSSFloat),
// Rem(CSSFloat),
// Vw(CSSFloat),
// Vh(CSSFloat),
// Vmin(CSSFloat),
// Vmax(CSSFloat),
}
static AU_PER_PX: CSSFloat = 60.;
static AU_PER_IN: CSSFloat = AU_PER_PX * 96.;
static AU_PER_CM: CSSFloat = AU_PER_IN / 2.54;
static AU_PER_MM: CSSFloat = AU_PER_IN / 25.4;
static AU_PER_PT: CSSFloat = AU_PER_IN / 72.;
static AU_PER_PC: CSSFloat = AU_PER_PT * 12.;
impl Length {
#[inline]
fn parse_internal(input: &ComponentValue, negative_ok: bool) -> Result<Length, ()> {
match input {
&Dimension(ref value, ref unit) if negative_ok || value.value >= 0.
=> Length::parse_dimension(value.value, unit.as_slice()),
&Number(ref value) if value.value == 0. => Ok(Au_(Au(0))),
_ => Err(())
}
}
#[allow(dead_code)]
pub fn parse(input: &ComponentValue) -> Result<Length, ()> {
Length::parse_internal(input, /* negative_ok = */ true)
}
pub fn parse_non_negative(input: &ComponentValue) -> Result<Length, ()> {
Length::parse_internal(input, /* negative_ok = */ false)
}
pub fn parse_dimension(value: CSSFloat, unit: &str) -> Result<Length, ()> {
match unit.to_ascii_lower().as_slice() {
"px" => Ok(Length::from_px(value)),
"in" => Ok(Au_(Au((value * AU_PER_IN) as i32))),
"cm" => Ok(Au_(Au((value * AU_PER_CM) as i32))),
"mm" => Ok(Au_(Au((value * AU_PER_MM) as i32))),
"pt" => Ok(Au_(Au((value * AU_PER_PT) as i32))),
"pc" => Ok(Au_(Au((value * AU_PER_PC) as i32))),
"em" => Ok(Em(value)),
"ex" => Ok(Ex(value)),
_ => Err(())
}
}
#[inline]
pub fn from_px(px_value: CSSFloat) -> Length {
Au_(Au((px_value * AU_PER_PX) as i32))
}
}
#[deriving(Clone)]
pub enum LengthOrPercentage {
LP_Length(Length),
LP_Percentage(CSSFloat), // [0 .. 100%] maps to [0.0 .. 1.0]
}
impl LengthOrPercentage {
fn parse_internal(input: &ComponentValue, negative_ok: bool)
-> Result<LengthOrPercentage, ()> {
match input {
&Dimension(ref value, ref unit) if negative_ok || value.value >= 0.
=> Length::parse_dimension(value.value, unit.as_slice()).map(LP_Length),
&ast::Percentage(ref value) if negative_ok || value.value >= 0.
=> Ok(LP_Percentage(value.value / 100.)),
&Number(ref value) if value.value == 0. => Ok(LP_Length(Au_(Au(0)))),
_ => Err(())
}
}
#[allow(dead_code)]
#[inline]
pub fn parse(input: &ComponentValue) -> Result<LengthOrPercentage, ()> {
LengthOrPercentage::parse_internal(input, /* negative_ok = */ true)
}
#[inline]
pub fn parse_non_negative(input: &ComponentValue) -> Result<LengthOrPercentage, ()> {
LengthOrPercentage::parse_internal(input, /* negative_ok = */ false)
}
}
#[deriving(Clone)]
pub enum LengthOrPercentageOrAuto {
LPA_Length(Length),
LPA_Percentage(CSSFloat), // [0 .. 100%] maps to [0.0 .. 1.0]
LPA_Auto,
}
impl LengthOrPercentageOrAuto {
fn parse_internal(input: &ComponentValue, negative_ok: bool)
-> Result<LengthOrPercentageOrAuto, ()> {
match input {
&Dimension(ref value, ref unit) if negative_ok || value.value >= 0.
=> Length::parse_dimension(value.value, unit.as_slice()).map(LPA_Length),
&ast::Percentage(ref value) if negative_ok || value.value >= 0.
=> Ok(LPA_Percentage(value.value / 100.)),
&Number(ref value) if value.value == 0. => Ok(LPA_Length(Au_(Au(0)))),
&Ident(ref value) if value.as_slice().eq_ignore_ascii_case("auto") => Ok(LPA_Auto),
_ => Err(())
}
}
#[inline]
pub fn parse(input: &ComponentValue) -> Result<LengthOrPercentageOrAuto, ()> {
LengthOrPercentageOrAuto::parse_internal(input, /* negative_ok = */ true)
}
#[inline]
pub fn parse_non_negative(input: &ComponentValue) -> Result<LengthOrPercentageOrAuto, ()> {
LengthOrPercentageOrAuto::parse_internal(input, /* negative_ok = */ false)
}
}
#[deriving(Clone)]
pub enum LengthOrPercentageOrNone {
LPN_Length(Length),
LPN_Percentage(CSSFloat), // [0 .. 100%] maps to [0.0 .. 1.0]
LPN_None,
}
impl LengthOrPercentageOrNone {
fn parse_internal(input: &ComponentValue, negative_ok: bool)
-> Result<LengthOrPercentageOrNone, ()> {
match input {
&Dimension(ref value, ref unit) if negative_ok || value.value >= 0.
=> Length::parse_dimension(value.value, unit.as_slice()).map(LPN_Length),
&ast::Percentage(ref value) if negative_ok || value.value >= 0.
=> Ok(LPN_Percentage(value.value / 100.)),
&Number(ref value) if value.value == 0. => Ok(LPN_Length(Au_(Au(0)))),
&Ident(ref value) if value.as_slice().eq_ignore_ascii_case("none") => Ok(LPN_None),
_ => Err(())
}
}
#[allow(dead_code)]
#[inline]
pub fn parse(input: &ComponentValue) -> Result<LengthOrPercentageOrNone, ()> {
LengthOrPercentageOrNone::parse_internal(input, /* negative_ok = */ true)
}
#[inline]
pub fn parse_non_negative(input: &ComponentValue) -> Result<LengthOrPercentageOrNone, ()> {
LengthOrPercentageOrNone::parse_internal(input, /* negative_ok = */ false)
}
}
}
pub mod computed {
pub use CSSColor = cssparser::Color;
pub use compute_CSSColor = super::super::longhands::computed_as_specified;
use super::*;
use super::super::longhands;
pub use servo_util::geometry::Au;
pub struct Context {
pub inherited_font_weight: longhands::font_weight::computed_value::T,
pub inherited_font_size: longhands::font_size::computed_value::T,
pub inherited_minimum_line_height: longhands::_servo_minimum_line_height::T,
pub inherited_text_decorations_in_effect: longhands::_servo_text_decorations_in_effect::T,
pub inherited_height: longhands::height::T,
pub color: longhands::color::computed_value::T,
pub text_decoration: longhands::text_decoration::computed_value::T,
pub font_size: longhands::font_size::computed_value::T,
pub display: longhands::display::computed_value::T,
pub positioned: bool,
pub floated: bool,
pub border_top_present: bool,
pub border_right_present: bool,
pub border_bottom_present: bool,
pub border_left_present: bool,
pub is_root_element: bool,
// TODO, as needed: root font size, viewport size, etc.
}
#[allow(non_snake_case_functions)]
#[inline]
pub fn compute_Au(value: specified::Length, context: &Context) -> Au {
compute_Au_with_font_size(value, context.font_size)
}
/// A special version of `compute_Au` used for `font-size`.
#[allow(non_snake_case_functions)]
#[inline]
pub fn compute_Au_with_font_size(value: specified::Length, reference_font_size: Au) -> Au {
match value {
specified::Au_(value) => value,
specified::Em(value) => reference_font_size.scale_by(value),
specified::Ex(value) => {
let x_height = 0.5; // TODO: find that from the font
reference_font_size.scale_by(value * x_height)
},
}
}
#[deriving(PartialEq, Clone)]
pub enum LengthOrPercentage {
LP_Length(Au),
LP_Percentage(CSSFloat),
}
#[allow(non_snake_case_functions)]
pub fn compute_LengthOrPercentage(value: specified::LengthOrPercentage, context: &Context)
-> LengthOrPercentage {
match value {
specified::LP_Length(value) => LP_Length(compute_Au(value, context)),
specified::LP_Percentage(value) => LP_Percentage(value),
}
}
#[deriving(PartialEq, Clone)]
pub enum LengthOrPercentageOrAuto {
LPA_Length(Au),
LPA_Percentage(CSSFloat),
LPA_Auto,
}
#[allow(non_snake_case_functions)]
pub fn compute_LengthOrPercentageOrAuto(value: specified::LengthOrPercentageOrAuto,
context: &Context) -> LengthOrPercentageOrAuto {
match value {
specified::LPA_Length(value) => LPA_Length(compute_Au(value, context)),
specified::LPA_Percentage(value) => LPA_Percentage(value),
specified::LPA_Auto => LPA_Auto,
}
}
#[deriving(PartialEq, Clone)]
pub enum LengthOrPercentageOrNone {
LPN_Length(Au),
LPN_Percentage(CSSFloat),
LPN_None,
}
#[allow(non_snake_case_functions)]
pub fn compute_LengthOrPercentageOrNone(value: specified::LengthOrPercentageOrNone,
context: &Context) -> LengthOrPercentageOrNone {
match value {
specified::LPN_Length(value) => LPN_Length(compute_Au(value, context)),
specified::LPN_Percentage(value) => LPN_Percentage(value),
specified::LPN_None => LPN_None,
}
}
}
pub fn parse_url(input: &str, base_url: &Url) -> Url {
UrlParser::new().base_url(base_url).parse(input)
.unwrap_or_else(|_| Url::parse("about:invalid").unwrap())
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,990 @@
/* 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::collections::hashmap::HashMap;
use std::hash::Hash;
use std::num::div_rem;
use sync::Arc;
use url::Url;
use servo_util::atom::Atom;
use servo_util::namespace;
use servo_util::smallvec::VecLike;
use servo_util::sort;
use media_queries::{Device, Screen};
use node::{TElement, TNode};
use properties::{PropertyDeclaration, PropertyDeclarationBlock};
use selectors::*;
use stylesheets::{Stylesheet, iter_stylesheet_style_rules};
pub enum StylesheetOrigin {
UserAgentOrigin,
AuthorOrigin,
UserOrigin,
}
/// The definition of whitespace per CSS Selectors Level 3 § 4.
static SELECTOR_WHITESPACE: &'static [char] = &[' ', '\t', '\n', '\r', '\x0C'];
/// Map node attributes to Rules whose last simple selector starts with them.
///
/// e.g.,
/// "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.
struct SelectorMap {
// TODO: Tune the initial capacity of the HashMap
id_hash: HashMap<Atom, Vec<Rule>>,
class_hash: HashMap<Atom, Vec<Rule>>,
local_name_hash: HashMap<Atom, Vec<Rule>>,
/// Same as local_name_hash, but keys are lower-cased.
/// For HTML elements in HTML documents.
lower_local_name_hash: HashMap<Atom, Vec<Rule>>,
// For Rules that don't have ID, class, or element selectors.
universal_rules: Vec<Rule>,
/// Whether this hash is empty.
empty: bool,
}
impl SelectorMap {
fn new() -> SelectorMap {
SelectorMap {
id_hash: HashMap::new(),
class_hash: HashMap::new(),
local_name_hash: HashMap::new(),
lower_local_name_hash: HashMap::new(),
universal_rules: vec!(),
empty: true,
}
}
/// 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<E:TElement,
N:TNode<E>,
V:VecLike<DeclarationBlock>>(
&self,
node: &N,
matching_rules_list: &mut V,
shareable: &mut bool) {
if self.empty {
return
}
// At the end, we're going to sort the rules that we added, so remember where we began.
let init_len = matching_rules_list.vec_len();
let element = node.as_element();
match element.get_id() {
Some(id) => {
SelectorMap::get_matching_rules_from_hash(node,
&self.id_hash,
&id,
matching_rules_list,
shareable)
}
None => {}
}
match element.get_attr(&namespace::Null, "class") {
Some(ref class_attr) => {
// FIXME: Store classes pre-split as atoms to make the loop below faster.
for class in class_attr.split(SELECTOR_WHITESPACE) {
SelectorMap::get_matching_rules_from_hash(node,
&self.class_hash,
&Atom::from_slice(class),
matching_rules_list,
shareable);
}
}
None => {}
}
let local_name_hash = if node.is_html_element_in_html_document() {
&self.lower_local_name_hash
} else {
&self.local_name_hash
};
SelectorMap::get_matching_rules_from_hash(node,
local_name_hash,
element.get_local_name(),
matching_rules_list,
shareable);
SelectorMap::get_matching_rules(node,
self.universal_rules.as_slice(),
matching_rules_list,
shareable);
// Sort only the rules we just added.
sort::quicksort_by(matching_rules_list.vec_mut_slice_from(init_len), compare);
fn compare(a: &DeclarationBlock, b: &DeclarationBlock) -> Ordering {
(a.specificity, a.source_order).cmp(&(b.specificity, b.source_order))
}
}
fn get_matching_rules_from_hash<E:TElement,
N:TNode<E>,
V:VecLike<DeclarationBlock>>(
node: &N,
hash: &HashMap<Atom, Vec<Rule>>,
key: &Atom,
matching_rules: &mut V,
shareable: &mut bool) {
match hash.find(key) {
Some(rules) => {
SelectorMap::get_matching_rules(node, rules.as_slice(), matching_rules, shareable)
}
None => {}
}
}
/// Adds rules in `rules` that match `node` to the `matching_rules` list.
fn get_matching_rules<E:TElement,
N:TNode<E>,
V:VecLike<DeclarationBlock>>(
node: &N,
rules: &[Rule],
matching_rules: &mut V,
shareable: &mut bool) {
for rule in rules.iter() {
if matches_compound_selector(&*rule.selector, node, shareable) {
matching_rules.vec_push(rule.declarations.clone());
}
}
}
/// Insert rule into the correct hash.
/// Order in which to try: id_hash, class_hash, local_name_hash, universal_rules.
fn insert(&mut self, rule: Rule) {
self.empty = false;
match SelectorMap::get_id_name(&rule) {
Some(id_name) => {
self.id_hash.find_push(id_name, rule);
return;
}
None => {}
}
match SelectorMap::get_class_name(&rule) {
Some(class_name) => {
self.class_hash.find_push(class_name, rule);
return;
}
None => {}
}
match SelectorMap::get_local_name(&rule) {
Some(LocalNameSelector { name, lower_name }) => {
self.local_name_hash.find_push(name, rule.clone());
self.lower_local_name_hash.find_push(lower_name, rule);
return;
}
None => {}
}
self.universal_rules.push(rule);
}
/// Retrieve the first ID name in Rule, or None otherwise.
fn get_id_name(rule: &Rule) -> Option<Atom> {
let simple_selector_sequence = &rule.selector.simple_selectors;
for ss in simple_selector_sequence.iter() {
match *ss {
// TODO(pradeep): 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<Atom> {
let simple_selector_sequence = &rule.selector.simple_selectors;
for ss in simple_selector_sequence.iter() {
match *ss {
// TODO(pradeep): 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_local_name(rule: &Rule) -> Option<LocalNameSelector> {
let simple_selector_sequence = &rule.selector.simple_selectors;
for ss in simple_selector_sequence.iter() {
match *ss {
LocalNameSelector(ref name) => {
return Some(name.clone())
}
_ => {}
}
}
return None
}
}
pub struct Stylist {
element_map: PerPseudoElementSelectorMap,
before_map: PerPseudoElementSelectorMap,
after_map: PerPseudoElementSelectorMap,
rules_source_order: uint,
}
impl Stylist {
#[inline]
pub fn new() -> Stylist {
let mut stylist = Stylist {
element_map: PerPseudoElementSelectorMap::new(),
before_map: PerPseudoElementSelectorMap::new(),
after_map: PerPseudoElementSelectorMap::new(),
rules_source_order: 0u,
};
let ua_stylesheet = Stylesheet::from_bytes(
include_bin!("user-agent.css"),
Url::parse("chrome:///user-agent.css").unwrap(),
None,
None);
stylist.add_stylesheet(ua_stylesheet, UserAgentOrigin);
stylist
}
pub fn add_stylesheet(&mut self, stylesheet: Stylesheet, origin: StylesheetOrigin) {
let (mut element_map, mut before_map, mut after_map) = match origin {
UserAgentOrigin => (
&mut self.element_map.user_agent,
&mut self.before_map.user_agent,
&mut self.after_map.user_agent,
),
AuthorOrigin => (
&mut self.element_map.author,
&mut self.before_map.author,
&mut self.after_map.author,
),
UserOrigin => (
&mut self.element_map.user,
&mut self.before_map.user,
&mut self.after_map.user,
),
};
let mut rules_source_order = self.rules_source_order;
// Take apart the StyleRule into individual Rules and insert
// them into the SelectorMap of that priority.
macro_rules! append(
($style_rule: ident, $priority: ident) => {
if $style_rule.declarations.$priority.len() > 0 {
for selector in $style_rule.selectors.iter() {
let map = match selector.pseudo_element {
None => &mut element_map,
Some(Before) => &mut before_map,
Some(After) => &mut after_map,
};
map.$priority.insert(Rule {
selector: selector.compound_selectors.clone(),
declarations: DeclarationBlock {
specificity: selector.specificity,
declarations: $style_rule.declarations.$priority.clone(),
source_order: rules_source_order,
},
});
}
}
};
);
let device = &Device { media_type: Screen }; // TODO, use Print when printing
iter_stylesheet_style_rules(&stylesheet, device, |style_rule| {
append!(style_rule, normal);
append!(style_rule, important);
rules_source_order += 1;
});
self.rules_source_order = rules_source_order;
}
/// Returns the applicable CSS declarations for the given element. This corresponds to
/// `ElementRuleCollector` in WebKit.
///
/// The returned boolean indicates whether the style is *shareable*; that is, whether the
/// matched selectors are simple enough to allow the matching logic to be reduced to the logic
/// in `css::matching::PrivateMatchMethods::candidate_element_allows_for_style_sharing`.
pub fn push_applicable_declarations<E:TElement,
N:TNode<E>,
V:VecLike<DeclarationBlock>>(
&self,
element: &N,
style_attribute: Option<&PropertyDeclarationBlock>,
pseudo_element: Option<PseudoElement>,
applicable_declarations: &mut V)
-> bool {
assert!(element.is_element());
assert!(style_attribute.is_none() || pseudo_element.is_none(),
"Style attributes do not apply to pseudo-elements");
let map = match pseudo_element {
None => &self.element_map,
Some(Before) => &self.before_map,
Some(After) => &self.after_map,
};
let mut shareable = true;
// Step 1: Normal rules.
map.user_agent.normal.get_all_matching_rules(element,
applicable_declarations,
&mut shareable);
map.user.normal.get_all_matching_rules(element, applicable_declarations, &mut shareable);
map.author.normal.get_all_matching_rules(element, applicable_declarations, &mut shareable);
// Step 2: Normal style attributes.
style_attribute.map(|sa| {
shareable = false;
applicable_declarations.vec_push(DeclarationBlock::from_declarations(sa.normal.clone()))
});
// Step 3: Author-supplied `!important` rules.
map.author.important.get_all_matching_rules(element,
applicable_declarations,
&mut shareable);
// Step 4: `!important` style attributes.
style_attribute.map(|sa| {
shareable = false;
applicable_declarations.vec_push(DeclarationBlock::from_declarations(sa.important.clone()))
});
// Step 5: User and UA `!important` rules.
map.user.important.get_all_matching_rules(element,
applicable_declarations,
&mut shareable);
map.user_agent.important.get_all_matching_rules(element,
applicable_declarations,
&mut shareable);
shareable
}
}
struct PerOriginSelectorMap {
normal: SelectorMap,
important: SelectorMap,
}
impl PerOriginSelectorMap {
#[inline]
fn new() -> PerOriginSelectorMap {
PerOriginSelectorMap {
normal: SelectorMap::new(),
important: SelectorMap::new(),
}
}
}
struct PerPseudoElementSelectorMap {
user_agent: PerOriginSelectorMap,
author: PerOriginSelectorMap,
user: PerOriginSelectorMap,
}
impl PerPseudoElementSelectorMap {
#[inline]
fn new() -> PerPseudoElementSelectorMap {
PerPseudoElementSelectorMap {
user_agent: PerOriginSelectorMap::new(),
author: PerOriginSelectorMap::new(),
user: PerOriginSelectorMap::new(),
}
}
}
#[deriving(Clone)]
struct Rule {
// This is an Arc because Rule will essentially be cloned for every node
// that it matches. Selector contains an owned vector (through
// CompoundSelector) and we want to avoid the allocation.
selector: Arc<CompoundSelector>,
declarations: DeclarationBlock,
}
/// A property declaration together with its precedence among rules of equal specificity so that
/// we can sort them.
#[deriving(Clone)]
pub struct DeclarationBlock {
pub declarations: Arc<Vec<PropertyDeclaration>>,
source_order: uint,
specificity: u32,
}
impl DeclarationBlock {
#[inline]
pub fn from_declarations(declarations: Arc<Vec<PropertyDeclaration>>) -> DeclarationBlock {
DeclarationBlock {
declarations: declarations,
source_order: 0,
specificity: 0,
}
}
}
pub fn matches<E:TElement, N:TNode<E>>(selector_list: &SelectorList, element: &N) -> bool {
get_selector_list_selectors(selector_list).iter().any(|selector|
selector.pseudo_element.is_none() &&
matches_compound_selector(&*selector.compound_selectors, element, &mut false))
}
/// Determines whether the given element matches the given single or compound selector.
///
/// NB: If you add support for any new kinds of selectors to this routine, be sure to set
/// `shareable` to false unless you are willing to update the style sharing logic. Otherwise things
/// will almost certainly break as nodes will start mistakenly sharing styles. (See the code in
/// `main/css/matching.rs`.)
fn matches_compound_selector<E:TElement,
N:TNode<E>>(
selector: &CompoundSelector,
element: &N,
shareable: &mut bool)
-> bool {
match matches_compound_selector_internal(selector, element, shareable) {
Matched => true,
_ => false
}
}
/// A result of selector matching, includes 3 failure types,
///
/// NotMatchedAndRestartFromClosestLaterSibling
/// NotMatchedAndRestartFromClosestDescendant
/// NotMatchedGlobally
///
/// When NotMatchedGlobally appears, stop selector matching completely since
/// the succeeding selectors never matches.
/// It is raised when
/// Child combinator cannot find the candidate element.
/// Descendant combinator cannot find the candidate element.
///
/// When NotMatchedAndRestartFromClosestDescendant appears, the selector
/// matching does backtracking and restarts from the closest Descendant
/// combinator.
/// It is raised when
/// NextSibling combinator cannot find the candidate element.
/// LaterSibling combinator cannot find the candidate element.
/// Child combinator doesn't match on the found element.
///
/// When NotMatchedAndRestartFromClosestLaterSibling appears, the selector
/// matching does backtracking and restarts from the closest LaterSibling
/// combinator.
/// It is raised when
/// NextSibling combinator doesn't match on the found element.
///
/// For example, when the selector "d1 d2 a" is provided and we cannot *find*
/// an appropriate ancestor node for "d1", this selector matching raises
/// NotMatchedGlobally since even if "d2" is moved to more upper node, the
/// candidates for "d1" becomes less than before and d1 .
///
/// The next example is siblings. When the selector "b1 + b2 ~ d1 a" is
/// providied and we cannot *find* an appropriate brother node for b1,
/// the selector matching raises NotMatchedAndRestartFromClosestDescendant.
/// The selectors ("b1 + b2 ~") doesn't match and matching restart from "d1".
///
/// The additional example is child and sibling. When the selector
/// "b1 + c1 > b2 ~ d1 a" is provided and the selector "b1" doesn't match on
/// the element, this "b1" raises NotMatchedAndRestartFromClosestLaterSibling.
/// However since the selector "c1" raises
/// NotMatchedAndRestartFromClosestDescendant. So the selector
/// "b1 + c1 > b2 ~ " doesn't match and restart matching from "d1".
enum SelectorMatchingResult {
Matched,
NotMatchedAndRestartFromClosestLaterSibling,
NotMatchedAndRestartFromClosestDescendant,
NotMatchedGlobally,
}
fn matches_compound_selector_internal<E:TElement,
N:TNode<E>>(
selector: &CompoundSelector,
element: &N,
shareable: &mut bool)
-> SelectorMatchingResult {
if !selector.simple_selectors.iter().all(|simple_selector| {
matches_simple_selector(simple_selector, element, shareable)
}) {
return NotMatchedAndRestartFromClosestLaterSibling
}
match selector.next {
None => Matched,
Some((ref next_selector, combinator)) => {
let (siblings, candidate_not_found) = match combinator {
Child => (false, NotMatchedGlobally),
Descendant => (false, NotMatchedGlobally),
NextSibling => (true, NotMatchedAndRestartFromClosestDescendant),
LaterSibling => (true, NotMatchedAndRestartFromClosestDescendant),
};
let mut node = (*element).clone();
loop {
let next_node = if siblings {
node.prev_sibling()
} else {
node.parent_node()
};
match next_node {
None => return candidate_not_found,
Some(next_node) => node = next_node,
}
if node.is_element() {
let result = matches_compound_selector_internal(&**next_selector,
&node,
shareable);
match (result, combinator) {
// Return the status immediately.
(Matched, _) => return result,
(NotMatchedGlobally, _) => return result,
// Upgrade the failure status to
// NotMatchedAndRestartFromClosestDescendant.
(_, Child) => return NotMatchedAndRestartFromClosestDescendant,
// Return the status directly.
(_, NextSibling) => return result,
// If the failure status is NotMatchedAndRestartFromClosestDescendant
// and combinator is LaterSibling, give up this LaterSibling matching
// and restart from the closest descendant combinator.
(NotMatchedAndRestartFromClosestDescendant, LaterSibling) => return result,
// The Descendant combinator and the status is
// NotMatchedAndRestartFromClosestLaterSibling or
// NotMatchedAndRestartFromClosestDescendant,
// or the LaterSibling combinator and the status is
// NotMatchedAndRestartFromClosestDescendant
// can continue to matching on the next candidate element.
_ => {},
}
}
}
}
}
}
/// Determines whether the given element matches the given single selector.
///
/// NB: If you add support for any new kinds of selectors to this routine, be sure to set
/// `shareable` to false unless you are willing to update the style sharing logic. Otherwise things
/// will almost certainly break as nodes will start mistakenly sharing styles. (See the code in
/// `main/css/matching.rs`.)
#[inline]
fn matches_simple_selector<E:TElement,
N:TNode<E>>(
selector: &SimpleSelector,
element: &N,
shareable: &mut bool)
-> bool {
match *selector {
LocalNameSelector(LocalNameSelector { ref name, ref lower_name }) => {
let name = if element.is_html_element_in_html_document() { lower_name } else { name };
let element = element.as_element();
element.get_local_name() == name
}
NamespaceSelector(ref namespace) => {
*shareable = false;
let element = element.as_element();
element.get_namespace() == namespace
}
// TODO: case-sensitivity depends on the document type and quirks mode
// TODO: cache and intern IDs on elements.
IDSelector(ref id) => {
*shareable = false;
let element = element.as_element();
element.get_id().map_or(false, |attr| {
attr == *id
})
}
// TODO: cache and intern class names on elements.
ClassSelector(ref class) => {
let element = element.as_element();
element.get_attr(&namespace::Null, "class")
.map_or(false, |attr| {
// TODO: case-sensitivity depends on the document type and quirks mode
attr.split(SELECTOR_WHITESPACE).any(|c| c == class.as_slice())
})
}
AttrExists(ref attr) => {
*shareable = false;
element.match_attr(attr, |_| true)
}
AttrEqual(ref attr, ref value) => {
if value.as_slice() != "DIR" {
// FIXME(pcwalton): Remove once we start actually supporting RTL text. This is in
// here because the UA style otherwise disables all style sharing completely.
*shareable = false
}
element.match_attr(attr, |attr_value| {
attr_value == value.as_slice()
})
}
AttrIncludes(ref attr, ref value) => {
*shareable = false;
element.match_attr(attr, |attr_value| {
attr_value.split(SELECTOR_WHITESPACE).any(|v| v == value.as_slice())
})
}
AttrDashMatch(ref attr, ref value, ref dashing_value) => {
*shareable = false;
element.match_attr(attr, |attr_value| {
attr_value == value.as_slice() ||
attr_value.starts_with(dashing_value.as_slice())
})
}
AttrPrefixMatch(ref attr, ref value) => {
*shareable = false;
element.match_attr(attr, |attr_value| {
attr_value.starts_with(value.as_slice())
})
}
AttrSubstringMatch(ref attr, ref value) => {
*shareable = false;
element.match_attr(attr, |attr_value| {
attr_value.contains(value.as_slice())
})
}
AttrSuffixMatch(ref attr, ref value) => {
*shareable = false;
element.match_attr(attr, |attr_value| {
attr_value.ends_with(value.as_slice())
})
}
AnyLink => {
*shareable = false;
let element = element.as_element();
element.get_link().is_some()
}
Link => {
*shareable = false;
let elem = element.as_element();
match elem.get_link() {
Some(url) => !url_is_visited(url),
None => false,
}
}
Visited => {
*shareable = false;
let elem = element.as_element();
match elem.get_link() {
Some(url) => url_is_visited(url),
None => false,
}
}
Hover => {
*shareable = false;
let elem = element.as_element();
elem.get_hover_state()
},
// http://www.whatwg.org/html/#selector-disabled
Disabled => {
*shareable = false;
let elem = element.as_element();
elem.get_disabled_state()
},
// http://www.whatwg.org/html/#selector-enabled
Enabled => {
*shareable = false;
let elem = element.as_element();
elem.get_enabled_state()
},
FirstChild => {
*shareable = false;
matches_first_child(element)
}
LastChild => {
*shareable = false;
matches_last_child(element)
}
OnlyChild => {
*shareable = false;
matches_first_child(element) && matches_last_child(element)
}
Root => {
*shareable = false;
matches_root(element)
}
NthChild(a, b) => {
*shareable = false;
matches_generic_nth_child(element, a, b, false, false)
}
NthLastChild(a, b) => {
*shareable = false;
matches_generic_nth_child(element, a, b, false, true)
}
NthOfType(a, b) => {
*shareable = false;
matches_generic_nth_child(element, a, b, true, false)
}
NthLastOfType(a, b) => {
*shareable = false;
matches_generic_nth_child(element, a, b, true, true)
}
FirstOfType => {
*shareable = false;
matches_generic_nth_child(element, 0, 1, true, false)
}
LastOfType => {
*shareable = false;
matches_generic_nth_child(element, 0, 1, true, true)
}
OnlyOfType => {
*shareable = false;
matches_generic_nth_child(element, 0, 1, true, false) &&
matches_generic_nth_child(element, 0, 1, true, true)
}
Negation(ref negated) => {
*shareable = false;
!negated.iter().all(|s| matches_simple_selector(s, element, shareable))
},
}
}
fn url_is_visited(_url: &str) -> bool {
// FIXME: implement this.
// This function will probably need to take a "session"
// or something containing browsing history as an additional parameter.
false
}
#[inline]
fn matches_generic_nth_child<'a,
E:TElement,
N:TNode<E>>(
element: &N,
a: i32,
b: i32,
is_of_type: bool,
is_from_end: bool)
-> bool {
let mut node = element.clone();
// fail if we can't find a parent or if the node is the root element
// of the document (Cf. Selectors Level 3)
match node.parent_node() {
Some(parent) => if parent.is_document() {
return false;
},
None => return false
};
let mut index = 1;
loop {
if is_from_end {
match node.next_sibling() {
None => break,
Some(next_sibling) => node = next_sibling
}
} else {
match node.prev_sibling() {
None => break,
Some(prev_sibling) => node = prev_sibling
}
}
if node.is_element() {
if is_of_type {
let element = element.as_element();
let node = node.as_element();
if element.get_local_name() == node.get_local_name() &&
element.get_namespace() == node.get_namespace() {
index += 1;
}
} else {
index += 1;
}
}
}
if a == 0 {
return b == index;
}
let (n, r) = div_rem(index - b, a);
n >= 0 && r == 0
}
#[inline]
fn matches_root<E:TElement,N:TNode<E>>(element: &N) -> bool {
match element.parent_node() {
Some(parent) => parent.is_document(),
None => false
}
}
#[inline]
fn matches_first_child<E:TElement,N:TNode<E>>(element: &N) -> bool {
let mut node = element.clone();
loop {
match node.prev_sibling() {
Some(prev_sibling) => {
node = prev_sibling;
if node.is_element() {
return false
}
},
None => match node.parent_node() {
// Selectors level 3 says :first-child does not match the
// root of the document; Warning, level 4 says, for the time
// being, the contrary...
Some(parent) => return !parent.is_document(),
None => return false
}
}
}
}
#[inline]
fn matches_last_child<E:TElement,N:TNode<E>>(element: &N) -> bool {
let mut node = element.clone();
loop {
match node.next_sibling() {
Some(next_sibling) => {
node = next_sibling;
if node.is_element() {
return false
}
},
None => match node.parent_node() {
// Selectors level 3 says :last-child does not match the
// root of the document; Warning, level 4 says, for the time
// being, the contrary...
Some(parent) => return !parent.is_document(),
None => return false
}
}
}
}
trait FindPush<K, V> {
fn find_push(&mut self, key: K, value: V);
}
impl<K: Eq + Hash, V> FindPush<K, V> for HashMap<K, Vec<V>> {
fn find_push(&mut self, key: K, value: V) {
match self.find_mut(&key) {
Some(vec) => {
vec.push(value);
return
}
None => {}
}
self.insert(key, vec![value]);
}
}
#[cfg(test)]
mod tests {
use servo_util::atom::Atom;
use sync::Arc;
use super::{DeclarationBlock, Rule, SelectorMap};
use selectors::LocalNameSelector;
/// 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]) -> Vec<Vec<Rule>> {
use namespaces::NamespaceMap;
use selectors::parse_selector_list;
use cssparser::tokenize;
let namespaces = NamespaceMap::new();
css_selectors.iter().enumerate().map(|(i, selectors)| {
parse_selector_list(tokenize(*selectors).map(|(c, _)| c), &namespaces)
.unwrap().move_iter().map(|s| {
Rule {
selector: s.compound_selectors.clone(),
declarations: DeclarationBlock {
specificity: s.specificity,
declarations: Arc::new(vec!()),
source_order: i,
}
}
}).collect()
}).collect()
}
#[test]
fn test_rule_ordering_same_specificity(){
let rules_list = get_mock_rules(["a.intro", "img.sidebar"]);
let a = &rules_list[0][0].declarations;
let b = &rules_list[1][0].declarations;
assert!((a.specificity, a.source_order).cmp(&(b.specificity, b.source_order)) == Less,
"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]), None);
assert_eq!(SelectorMap::get_id_name(&rules_list[1][0]), Some(Atom::from_slice("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]), Some(Atom::from_slice("intro")));
assert_eq!(SelectorMap::get_class_name(rules_list.get(1).get(0)), None);
}
#[test]
fn test_get_local_name(){
let rules_list = get_mock_rules(["img.foo", "#top", "IMG", "ImG"]);
let check = |i, names: Option<(&str, &str)>| {
assert!(SelectorMap::get_local_name(&rules_list[i][0])
== names.map(|(name, lower_name)| LocalNameSelector {
name: Atom::from_slice(name),
lower_name: Atom::from_slice(lower_name) }))
};
check(0, Some(("img", "img")));
check(1, None);
check(2, Some(("IMG", "img")));
check(3, Some(("ImG", "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(&Atom::from_slice("top")).unwrap()[0].declarations.source_order);
selector_map.insert(rules_list[0][0].clone());
assert_eq!(0, selector_map.class_hash.find(&Atom::from_slice("intro")).unwrap()[0].declarations.source_order);
assert!(selector_map.class_hash.find(&Atom::from_slice("foo")).is_none());
}
}

View file

@ -0,0 +1,717 @@
/* 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::{cmp, iter};
use std::ascii::{StrAsciiExt, OwnedStrAsciiExt};
use sync::Arc;
use cssparser::ast::*;
use cssparser::{tokenize, parse_nth};
use servo_util::atom::Atom;
use servo_util::namespace::Namespace;
use servo_util::namespace;
use namespaces::NamespaceMap;
// Only used in tests
impl PartialEq for Arc<CompoundSelector> {
fn eq(&self, other: &Arc<CompoundSelector>) -> bool {
**self == **other
}
}
#[deriving(PartialEq, Clone)]
pub struct Selector {
pub compound_selectors: Arc<CompoundSelector>,
pub pseudo_element: Option<PseudoElement>,
pub specificity: u32,
}
#[deriving(PartialEq, Clone)]
pub enum PseudoElement {
Before,
After,
// FirstLine,
// FirstLetter,
}
#[deriving(PartialEq, Clone)]
pub struct CompoundSelector {
pub simple_selectors: Vec<SimpleSelector>,
pub next: Option<(Box<CompoundSelector>, Combinator)>, // c.next is left of c
}
#[deriving(PartialEq, Clone)]
pub enum Combinator {
Child, // >
Descendant, // space
NextSibling, // +
LaterSibling, // ~
}
#[deriving(PartialEq, Clone)]
pub enum SimpleSelector {
IDSelector(Atom),
ClassSelector(Atom),
LocalNameSelector(LocalNameSelector),
NamespaceSelector(Namespace),
// Attribute selectors
AttrExists(AttrSelector), // [foo]
AttrEqual(AttrSelector, String), // [foo=bar]
AttrIncludes(AttrSelector, String), // [foo~=bar]
AttrDashMatch(AttrSelector, String, String), // [foo|=bar] Second string is the first + "-"
AttrPrefixMatch(AttrSelector, String), // [foo^=bar]
AttrSubstringMatch(AttrSelector, String), // [foo*=bar]
AttrSuffixMatch(AttrSelector, String), // [foo$=bar]
// Pseudo-classes
Negation(Vec<SimpleSelector>),
AnyLink,
Link,
Visited,
Hover,
Disabled,
Enabled,
FirstChild, LastChild, OnlyChild,
// Empty,
Root,
// Lang(String),
NthChild(i32, i32),
NthLastChild(i32, i32),
NthOfType(i32, i32),
NthLastOfType(i32, i32),
FirstOfType,
LastOfType,
OnlyOfType
// ...
}
#[deriving(PartialEq, Clone)]
pub struct LocalNameSelector {
pub name: Atom,
pub lower_name: Atom,
}
#[deriving(PartialEq, Clone)]
pub struct AttrSelector {
pub name: String,
pub lower_name: String,
pub namespace: NamespaceConstraint,
}
#[deriving(PartialEq, Clone)]
pub enum NamespaceConstraint {
AnyNamespace,
SpecificNamespace(Namespace),
}
pub fn parse_selector_list_from_str(input: &str) -> Result<SelectorList, ()> {
let namespaces = NamespaceMap::new();
let iter = tokenize(input).map(|(token, _)| token);
parse_selector_list(iter, &namespaces).map(|s| SelectorList { selectors: s })
}
/// Re-exported to script, but opaque.
pub struct SelectorList {
selectors: Vec<Selector>
}
/// Public to the style crate, but not re-exported to script
pub fn get_selector_list_selectors<'a>(selector_list: &'a SelectorList) -> &'a [Selector] {
selector_list.selectors.as_slice()
}
/// 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<I: Iterator<ComponentValue>>(
iter: I, namespaces: &NamespaceMap)
-> Result<Vec<Selector>, ()> {
let iter = &mut iter.peekable();
let mut results = vec![try!(parse_selector(iter, namespaces))];
loop {
skip_whitespace(iter);
match iter.peek() {
None => break, // EOF
Some(&Comma) => {
iter.next();
}
_ => return Err(()),
}
results.push(try!(parse_selector(iter, namespaces)));
}
Ok(results)
}
type Iter<I> = iter::Peekable<ComponentValue, I>;
/// Build up a Selector.
/// selector : simple_selector_sequence [ combinator simple_selector_sequence ]* ;
///
/// `Err` means invalid selector.
fn parse_selector<I: Iterator<ComponentValue>>(
iter: &mut Iter<I>, namespaces: &NamespaceMap)
-> Result<Selector, ()> {
let (first, mut pseudo_element) = try!(parse_simple_selectors(iter, namespaces));
let mut compound = CompoundSelector{ simple_selectors: first, next: None };
while pseudo_element.is_none() {
let any_whitespace = skip_whitespace(iter);
let combinator = match iter.peek() {
None => break, // EOF
Some(&Comma) => break,
Some(&Delim('>')) => { iter.next(); Child },
Some(&Delim('+')) => { iter.next(); NextSibling },
Some(&Delim('~')) => { iter.next(); LaterSibling },
Some(_) => {
if any_whitespace { Descendant }
else { return Err(()) }
}
};
let (simple_selectors, pseudo) = try!(parse_simple_selectors(iter, namespaces));
compound = CompoundSelector {
simple_selectors: simple_selectors,
next: Some((box compound, combinator))
};
pseudo_element = pseudo;
}
Ok(Selector {
specificity: compute_specificity(&compound, &pseudo_element),
compound_selectors: Arc::new(compound),
pseudo_element: pseudo_element,
})
}
fn compute_specificity(mut selector: &CompoundSelector,
pseudo_element: &Option<PseudoElement>) -> u32 {
struct Specificity {
id_selectors: u32,
class_like_selectors: u32,
element_selectors: u32,
}
let mut specificity = Specificity {
id_selectors: 0,
class_like_selectors: 0,
element_selectors: 0,
};
if pseudo_element.is_some() { specificity.element_selectors += 1 }
simple_selectors_specificity(selector.simple_selectors.as_slice(), &mut specificity);
loop {
match selector.next {
None => break,
Some((ref next_selector, _)) => {
selector = &**next_selector;
simple_selectors_specificity(selector.simple_selectors.as_slice(), &mut specificity)
}
}
}
fn simple_selectors_specificity(simple_selectors: &[SimpleSelector],
specificity: &mut Specificity) {
for simple_selector in simple_selectors.iter() {
match simple_selector {
&LocalNameSelector(..) => specificity.element_selectors += 1,
&IDSelector(..) => specificity.id_selectors += 1,
&ClassSelector(..)
| &AttrExists(..) | &AttrEqual(..) | &AttrIncludes(..) | &AttrDashMatch(..)
| &AttrPrefixMatch(..) | &AttrSubstringMatch(..) | &AttrSuffixMatch(..)
| &AnyLink | &Link | &Visited | &Hover | &Disabled | &Enabled
| &FirstChild | &LastChild | &OnlyChild | &Root
// | &Empty | &Lang(*)
| &NthChild(..) | &NthLastChild(..)
| &NthOfType(..) | &NthLastOfType(..)
| &FirstOfType | &LastOfType | &OnlyOfType
=> specificity.class_like_selectors += 1,
&NamespaceSelector(..) => (),
&Negation(ref negated)
=> simple_selectors_specificity(negated.as_slice(), specificity),
}
}
}
static MAX_10BIT: u32 = (1u32 << 10) - 1;
cmp::min(specificity.id_selectors, MAX_10BIT) << 20
| cmp::min(specificity.class_like_selectors, MAX_10BIT) << 10
| cmp::min(specificity.element_selectors, MAX_10BIT)
}
/// simple_selector_sequence
/// : [ type_selector | universal ] [ HASH | class | attrib | pseudo | negation ]*
/// | [ HASH | class | attrib | pseudo | negation ]+
///
/// `Err(())` means invalid selector
fn parse_simple_selectors<I: Iterator<ComponentValue>>(
iter: &mut Iter<I>, namespaces: &NamespaceMap)
-> Result<(Vec<SimpleSelector>, Option<PseudoElement>), ()> {
let mut empty = true;
let mut simple_selectors = match try!(parse_type_selector(iter, namespaces)) {
None => vec![],
Some(s) => { empty = false; s }
};
let mut pseudo_element = None;
loop {
match try!(parse_one_simple_selector(iter, namespaces, /* inside_negation = */ false)) {
None => break,
Some(SimpleSelectorResult(s)) => { simple_selectors.push(s); empty = false },
Some(PseudoElementResult(p)) => { pseudo_element = Some(p); empty = false; break },
}
}
if empty { Err(()) } // An empty selector is invalid
else { Ok((simple_selectors, pseudo_element)) }
}
/// * `Err(())`: Invalid selector, abort
/// * `Ok(None)`: Not a type selector, could be something else. `iter` was not consumed.
/// * `Ok(Some(vec))`: Length 0 (`*|*`), 1 (`*|E` or `ns|*`) or 2 (`|E` or `ns|E`)
fn parse_type_selector<I: Iterator<ComponentValue>>(
iter: &mut Iter<I>, namespaces: &NamespaceMap)
-> Result<Option<Vec<SimpleSelector>>, ()> {
skip_whitespace(iter);
match try!(parse_qualified_name(iter, /* in_attr_selector = */ false, namespaces)) {
None => Ok(None),
Some((namespace, local_name)) => {
let mut simple_selectors = vec!();
match namespace {
SpecificNamespace(ns) => simple_selectors.push(NamespaceSelector(ns)),
AnyNamespace => (),
}
match local_name {
Some(name) => {
simple_selectors.push(LocalNameSelector(LocalNameSelector {
name: Atom::from_slice(name.as_slice()),
lower_name: Atom::from_slice(name.into_ascii_lower().as_slice())
}))
}
None => (),
}
Ok(Some(simple_selectors))
}
}
}
enum SimpleSelectorParseResult {
SimpleSelectorResult(SimpleSelector),
PseudoElementResult(PseudoElement),
}
/// Parse a simple selector other than a type selector.
///
/// * `Err(())`: Invalid selector, abort
/// * `Ok(None)`: Not a simple selector, could be something else. `iter` was not consumed.
/// * `Ok(Some(_))`: Parsed a simple selector or pseudo-element
fn parse_one_simple_selector<I: Iterator<ComponentValue>>(
iter: &mut Iter<I>, namespaces: &NamespaceMap, inside_negation: bool)
-> Result<Option<SimpleSelectorParseResult>, ()> {
match iter.peek() {
Some(&IDHash(_)) => match iter.next() {
Some(IDHash(id)) => Ok(Some(SimpleSelectorResult(
IDSelector(Atom::from_slice(id.as_slice()))))),
_ => fail!("Implementation error, this should not happen."),
},
Some(&Delim('.')) => {
iter.next();
match iter.next() {
Some(Ident(class)) => Ok(Some(SimpleSelectorResult(
ClassSelector(Atom::from_slice(class.as_slice()))))),
_ => Err(()),
}
}
Some(&SquareBracketBlock(_)) => match iter.next() {
Some(SquareBracketBlock(content))
=> Ok(Some(SimpleSelectorResult(try!(parse_attribute_selector(content, namespaces))))),
_ => fail!("Implementation error, this should not happen."),
},
Some(&Colon) => {
iter.next();
match iter.next() {
Some(Ident(name)) => match parse_simple_pseudo_class(name.as_slice()) {
Err(()) => {
match name.as_slice().to_ascii_lower().as_slice() {
// Supported CSS 2.1 pseudo-elements only.
// ** Do not add to this list! **
"before" => Ok(Some(PseudoElementResult(Before))),
"after" => Ok(Some(PseudoElementResult(After))),
// "first-line" => PseudoElementResult(FirstLine),
// "first-letter" => PseudoElementResult(FirstLetter),
_ => Err(())
}
},
Ok(result) => Ok(Some(SimpleSelectorResult(result))),
},
Some(Function(name, arguments))
=> Ok(Some(SimpleSelectorResult(try!(parse_functional_pseudo_class(
name, arguments, namespaces, inside_negation))))),
Some(Colon) => {
match iter.next() {
Some(Ident(name))
=> Ok(Some(PseudoElementResult(try!(parse_pseudo_element(name))))),
_ => Err(()),
}
}
_ => Err(()),
}
}
_ => Ok(None),
}
}
/// * `Err(())`: Invalid selector, abort
/// * `Ok(None)`: Not a simple selector, could be something else. `iter` was not consumed.
/// * `Ok(Some((namespace, local_name)))`: `None` for the local name means a `*` universal selector
fn parse_qualified_name<I: Iterator<ComponentValue>>(
iter: &mut Iter<I>, in_attr_selector: bool, namespaces: &NamespaceMap)
-> Result<Option<(NamespaceConstraint, Option<String>)>, ()> {
let default_namespace = |local_name| {
let namespace = match namespaces.default {
Some(ref ns) => SpecificNamespace(ns.clone()),
None => AnyNamespace,
};
Ok(Some((namespace, local_name)))
};
let explicit_namespace = |iter: &mut Iter<I>, namespace| {
assert!(iter.next() == Some(Delim('|')),
"Implementation error, this should not happen.");
match iter.peek() {
Some(&Delim('*')) if !in_attr_selector => {
iter.next();
Ok(Some((namespace, None)))
},
Some(&Ident(_)) => {
let local_name = get_next_ident(iter);
Ok(Some((namespace, Some(local_name))))
},
_ => Err(()),
}
};
match iter.peek() {
Some(&Ident(_)) => {
let value = get_next_ident(iter);
match iter.peek() {
Some(&Delim('|')) => {
let namespace = match namespaces.prefix_map.find(&value) {
None => return Err(()), // Undeclared namespace prefix
Some(ref ns) => (*ns).clone(),
};
explicit_namespace(iter, SpecificNamespace(namespace))
},
_ if in_attr_selector => Ok(Some(
(SpecificNamespace(namespace::Null), Some(value)))),
_ => default_namespace(Some(value)),
}
},
Some(&Delim('*')) => {
iter.next(); // Consume '*'
match iter.peek() {
Some(&Delim('|')) => explicit_namespace(iter, AnyNamespace),
_ => {
if !in_attr_selector { default_namespace(None) }
else { Err(()) }
},
}
},
Some(&Delim('|')) => explicit_namespace(iter, SpecificNamespace(namespace::Null)),
_ => Ok(None),
}
}
fn parse_attribute_selector(content: Vec<ComponentValue>, namespaces: &NamespaceMap)
-> Result<SimpleSelector, ()> {
let iter = &mut content.move_iter().peekable();
let attr = match try!(parse_qualified_name(iter, /* in_attr_selector = */ true, namespaces)) {
None => return Err(()),
Some((_, None)) => fail!("Implementation error, this should not happen."),
Some((namespace, Some(local_name))) => AttrSelector {
namespace: namespace,
lower_name: local_name.as_slice().to_ascii_lower(),
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() {
Some(Ident(value)) | Some(String(value)) => value,
_ => return Err(())
}
}};)
let result = match iter.next() {
None => AttrExists(attr), // [foo]
Some(Delim('=')) => AttrEqual(attr, (get_value!())), // [foo=bar]
Some(IncludeMatch) => AttrIncludes(attr, (get_value!())), // [foo~=bar]
Some(DashMatch) => {
let value = get_value!();
let dashing_value = format!("{}-", 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]
_ => return Err(())
};
skip_whitespace(iter);
if iter.next().is_none() { Ok(result) } else { Err(()) }
}
fn parse_simple_pseudo_class(name: &str) -> Result<SimpleSelector, ()> {
match name.to_ascii_lower().as_slice() {
"any-link" => Ok(AnyLink),
"link" => Ok(Link),
"visited" => Ok(Visited),
"hover" => Ok(Hover),
"disabled" => Ok(Disabled),
"enabled" => Ok(Enabled),
"first-child" => Ok(FirstChild),
"last-child" => Ok(LastChild),
"only-child" => Ok(OnlyChild),
"root" => Ok(Root),
"first-of-type" => Ok(FirstOfType),
"last-of-type" => Ok(LastOfType),
"only-of-type" => Ok(OnlyOfType),
// "empty" => Ok(Empty),
_ => Err(())
}
}
fn parse_functional_pseudo_class(name: String, arguments: Vec<ComponentValue>,
namespaces: &NamespaceMap, inside_negation: bool)
-> Result<SimpleSelector, ()> {
match name.as_slice().to_ascii_lower().as_slice() {
// "lang" => parse_lang(arguments),
"nth-child" => parse_nth(arguments.as_slice()).map(|(a, b)| NthChild(a, b)),
"nth-last-child" => parse_nth(arguments.as_slice()).map(|(a, b)| NthLastChild(a, b)),
"nth-of-type" => parse_nth(arguments.as_slice()).map(|(a, b)| NthOfType(a, b)),
"nth-last-of-type" => parse_nth(arguments.as_slice()).map(|(a, b)| NthLastOfType(a, b)),
"not" => if inside_negation { Err(()) } else { parse_negation(arguments, namespaces) },
_ => Err(())
}
}
fn parse_pseudo_element(name: String) -> Result<PseudoElement, ()> {
match name.as_slice().to_ascii_lower().as_slice() {
// All supported pseudo-elements
"before" => Ok(Before),
"after" => Ok(After),
// "first-line" => Some(FirstLine),
// "first-letter" => Some(FirstLetter),
_ => Err(())
}
}
//fn parse_lang(arguments: vec!(ComponentValue)) -> Result<SimpleSelector, ()> {
// let mut iter = arguments.move_skip_whitespace();
// match iter.next() {
// Some(Ident(value)) => {
// if "" == value || iter.next().is_some() { None }
// else { Ok(Lang(value)) }
// },
// _ => Err(()),
// }
//}
/// Level 3: Parse **one** simple_selector
fn parse_negation(arguments: Vec<ComponentValue>, namespaces: &NamespaceMap)
-> Result<SimpleSelector, ()> {
let iter = &mut arguments.move_iter().peekable();
match try!(parse_type_selector(iter, namespaces)) {
Some(type_selector) => Ok(Negation(type_selector)),
None => {
match try!(parse_one_simple_selector(iter, namespaces, /* inside_negation = */ true)) {
Some(SimpleSelectorResult(simple_selector)) => Ok(Negation(vec![simple_selector])),
_ => Err(())
}
},
}
}
/// Assuming the next token is an ident, consume it and return its value
#[inline]
fn get_next_ident<I: Iterator<ComponentValue>>(iter: &mut Iter<I>) -> String {
match iter.next() {
Some(Ident(value)) => value,
_ => fail!("Implementation error, this should not happen."),
}
}
#[inline]
fn skip_whitespace<I: Iterator<ComponentValue>>(iter: &mut Iter<I>) -> bool {
let mut any_whitespace = false;
loop {
if iter.peek() != Some(&WhiteSpace) { return any_whitespace }
any_whitespace = true;
iter.next();
}
}
#[cfg(test)]
mod tests {
use sync::Arc;
use cssparser;
use servo_util::atom::Atom;
use servo_util::namespace;
use namespaces::NamespaceMap;
use super::*;
fn parse(input: &str) -> Result<Vec<Selector>, ()> {
parse_ns(input, &NamespaceMap::new())
}
fn parse_ns(input: &str, namespaces: &NamespaceMap) -> Result<Vec<Selector>, ()> {
parse_selector_list(cssparser::tokenize(input).map(|(v, _)| v), namespaces)
}
fn specificity(a: u32, b: u32, c: u32) -> u32 {
a << 20 | b << 10 | c
}
#[test]
fn test_parsing() {
assert!(parse("") == Err(()))
assert!(parse("EeÉ") == Ok(vec!(Selector {
compound_selectors: Arc::new(CompoundSelector {
simple_selectors: vec!(LocalNameSelector(LocalNameSelector {
name: Atom::from_slice("EeÉ"),
lower_name: Atom::from_slice("eeÉ") })),
next: None,
}),
pseudo_element: None,
specificity: specificity(0, 0, 1),
})))
assert!(parse(".foo") == Ok(vec!(Selector {
compound_selectors: Arc::new(CompoundSelector {
simple_selectors: vec!(ClassSelector(Atom::from_slice("foo"))),
next: None,
}),
pseudo_element: None,
specificity: specificity(0, 1, 0),
})))
assert!(parse("#bar") == Ok(vec!(Selector {
compound_selectors: Arc::new(CompoundSelector {
simple_selectors: vec!(IDSelector(Atom::from_slice("bar"))),
next: None,
}),
pseudo_element: None,
specificity: specificity(1, 0, 0),
})))
assert!(parse("e.foo#bar") == Ok(vec!(Selector {
compound_selectors: Arc::new(CompoundSelector {
simple_selectors: vec!(LocalNameSelector(LocalNameSelector {
name: Atom::from_slice("e"),
lower_name: Atom::from_slice("e") }),
ClassSelector(Atom::from_slice("foo")),
IDSelector(Atom::from_slice("bar"))),
next: None,
}),
pseudo_element: None,
specificity: specificity(1, 1, 1),
})))
assert!(parse("e.foo #bar") == Ok(vec!(Selector {
compound_selectors: Arc::new(CompoundSelector {
simple_selectors: vec!(IDSelector(Atom::from_slice("bar"))),
next: Some((box CompoundSelector {
simple_selectors: vec!(LocalNameSelector(LocalNameSelector {
name: Atom::from_slice("e"),
lower_name: Atom::from_slice("e") }),
ClassSelector(Atom::from_slice("foo"))),
next: None,
}, Descendant)),
}),
pseudo_element: None,
specificity: specificity(1, 1, 1),
})))
// Default namespace does not apply to attribute selectors
// https://github.com/mozilla/servo/pull/1652
let mut namespaces = NamespaceMap::new();
assert!(parse_ns("[Foo]", &namespaces) == Ok(vec!(Selector {
compound_selectors: Arc::new(CompoundSelector {
simple_selectors: vec!(AttrExists(AttrSelector {
name: "Foo".to_string(),
lower_name: "foo".to_string(),
namespace: SpecificNamespace(namespace::Null),
})),
next: None,
}),
pseudo_element: None,
specificity: specificity(0, 1, 0),
})))
// Default namespace does not apply to attribute selectors
// https://github.com/mozilla/servo/pull/1652
namespaces.default = Some(namespace::MathML);
assert!(parse_ns("[Foo]", &namespaces) == Ok(vec!(Selector {
compound_selectors: Arc::new(CompoundSelector {
simple_selectors: vec!(AttrExists(AttrSelector {
name: "Foo".to_string(),
lower_name: "foo".to_string(),
namespace: SpecificNamespace(namespace::Null),
})),
next: None,
}),
pseudo_element: None,
specificity: specificity(0, 1, 0),
})))
// Default namespace does apply to type selectors
assert!(parse_ns("e", &namespaces) == Ok(vec!(Selector {
compound_selectors: Arc::new(CompoundSelector {
simple_selectors: vec!(
NamespaceSelector(namespace::MathML),
LocalNameSelector(LocalNameSelector {
name: Atom::from_slice("e"),
lower_name: Atom::from_slice("e") }),
),
next: None,
}),
pseudo_element: None,
specificity: specificity(0, 0, 1),
})))
// https://github.com/mozilla/servo/issues/1723
assert!(parse("::before") == Ok(vec!(Selector {
compound_selectors: Arc::new(CompoundSelector {
simple_selectors: vec!(),
next: None,
}),
pseudo_element: Some(Before),
specificity: specificity(0, 0, 1),
})))
assert!(parse("div :after") == Ok(vec!(Selector {
compound_selectors: Arc::new(CompoundSelector {
simple_selectors: vec!(),
next: Some((box CompoundSelector {
simple_selectors: vec!(LocalNameSelector(LocalNameSelector {
name: Atom::from_slice("div"),
lower_name: Atom::from_slice("div") })),
next: None,
}, Descendant)),
}),
pseudo_element: Some(After),
specificity: specificity(0, 0, 2),
})))
}
}

View file

@ -0,0 +1,178 @@
/* 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::iter::Iterator;
use std::ascii::StrAsciiExt;
use url::Url;
use encoding::EncodingRef;
use cssparser::{decode_stylesheet_bytes, tokenize, parse_stylesheet_rules, ToCss};
use cssparser::ast::*;
use selectors;
use properties;
use errors::{ErrorLoggerIterator, log_css_error};
use namespaces::{NamespaceMap, parse_namespace_rule};
use media_queries::{MediaRule, parse_media_rule};
use media_queries;
use font_face::{FontFaceRule, parse_font_face_rule, iter_font_face_rules_inner};
pub struct Stylesheet {
/// List of rules in the order they were found (important for
/// cascading order)
rules: Vec<CSSRule>,
}
pub enum CSSRule {
CSSStyleRule(StyleRule),
CSSMediaRule(MediaRule),
CSSFontFaceRule(FontFaceRule),
}
pub struct StyleRule {
pub selectors: Vec<selectors::Selector>,
pub declarations: properties::PropertyDeclarationBlock,
}
impl Stylesheet {
pub fn from_bytes_iter<I: Iterator<Vec<u8>>>(
mut input: I, base_url: Url, protocol_encoding_label: Option<&str>,
environment_encoding: Option<EncodingRef>) -> Stylesheet {
let mut bytes = vec!();
// TODO: incremental decoding and tokinization/parsing
for chunk in input {
bytes.push_all(chunk.as_slice())
}
Stylesheet::from_bytes(bytes.as_slice(), base_url, protocol_encoding_label, environment_encoding)
}
pub fn from_bytes(
bytes: &[u8], base_url: Url, protocol_encoding_label: Option<&str>,
environment_encoding: Option<EncodingRef>) -> Stylesheet {
// TODO: bytes.as_slice could be bytes.container_as_bytes()
let (string, _) = decode_stylesheet_bytes(
bytes.as_slice(), protocol_encoding_label, environment_encoding);
Stylesheet::from_str(string.as_slice(), base_url)
}
pub fn from_str(css: &str, base_url: Url) -> Stylesheet {
static STATE_CHARSET: uint = 1;
static STATE_IMPORTS: uint = 2;
static STATE_NAMESPACES: uint = 3;
static STATE_BODY: uint = 4;
let mut state: uint = STATE_CHARSET;
let mut rules = vec!();
let mut namespaces = NamespaceMap::new();
for rule in ErrorLoggerIterator(parse_stylesheet_rules(tokenize(css))) {
let next_state; // Unitialized to force each branch to set it.
match rule {
QualifiedRule(rule) => {
next_state = STATE_BODY;
parse_style_rule(rule, &mut rules, &namespaces, &base_url)
},
AtRule(rule) => {
let lower_name = rule.name.as_slice().to_ascii_lower();
match lower_name.as_slice() {
"charset" => {
if state > STATE_CHARSET {
log_css_error(rule.location, "@charset must be the first rule")
}
// Valid @charset rules are just ignored
next_state = STATE_IMPORTS;
},
"import" => {
if state > STATE_IMPORTS {
next_state = state;
log_css_error(rule.location,
"@import must be before any rule but @charset")
} else {
next_state = STATE_IMPORTS;
// TODO: support @import
log_css_error(rule.location, "@import is not supported yet")
}
},
"namespace" => {
if state > STATE_NAMESPACES {
next_state = state;
log_css_error(
rule.location,
"@namespace must be before any rule but @charset and @import"
)
} else {
next_state = STATE_NAMESPACES;
parse_namespace_rule(rule, &mut namespaces)
}
},
_ => {
next_state = STATE_BODY;
parse_nested_at_rule(lower_name.as_slice(), rule, &mut rules, &namespaces, &base_url)
},
}
},
}
state = next_state;
}
Stylesheet{ rules: rules }
}
}
pub fn parse_style_rule(rule: QualifiedRule, parent_rules: &mut Vec<CSSRule>,
namespaces: &NamespaceMap, base_url: &Url) {
let QualifiedRule{location: location, prelude: prelude, block: block} = rule;
// FIXME: avoid doing this for valid selectors
let serialized = prelude.iter().to_css();
match selectors::parse_selector_list(prelude.move_iter(), namespaces) {
Ok(selectors) => parent_rules.push(CSSStyleRule(StyleRule{
selectors: selectors,
declarations: properties::parse_property_declaration_list(block.move_iter(), base_url)
})),
Err(()) => log_css_error(location, format!(
"Invalid/unsupported selector: {}", serialized).as_slice()),
}
}
// lower_name is passed explicitly to avoid computing it twice.
pub fn parse_nested_at_rule(lower_name: &str, rule: AtRule,
parent_rules: &mut Vec<CSSRule>, namespaces: &NamespaceMap, base_url: &Url) {
match lower_name {
"media" => parse_media_rule(rule, parent_rules, namespaces, base_url),
"font-face" => parse_font_face_rule(rule, parent_rules, base_url),
_ => log_css_error(rule.location,
format!("Unsupported at-rule: @{:s}", lower_name).as_slice())
}
}
pub fn iter_style_rules<'a>(rules: &[CSSRule], device: &media_queries::Device,
callback: |&StyleRule|) {
for rule in rules.iter() {
match *rule {
CSSStyleRule(ref rule) => callback(rule),
CSSMediaRule(ref rule) => if rule.media_queries.evaluate(device) {
iter_style_rules(rule.rules.as_slice(), device, |s| callback(s))
},
CSSFontFaceRule(_) => {},
}
}
}
#[inline]
pub fn iter_stylesheet_style_rules(stylesheet: &Stylesheet, device: &media_queries::Device,
callback: |&StyleRule|) {
iter_style_rules(stylesheet.rules.as_slice(), device, callback)
}
#[inline]
pub fn iter_font_face_rules(stylesheet: &Stylesheet, callback: |family: &str, sources: &Url|) {
iter_font_face_rules_inner(stylesheet.rules.as_slice(), callback)
}

View file

@ -0,0 +1,118 @@
html, address,
blockquote,
body, div,
dt, fieldset, form,
frame, frameset,
h1, h2, h3, h4,
h5, h6, noframes,
center, dir,
hr, menu, pre { display: block; unicode-bidi: embed }
head, noscript { display: none }
table { display: table }
tr { display: table-row }
thead { display: table-header-group }
tbody { display: table-row-group }
tfoot { display: table-footer-group }
col { display: table-column }
colgroup { display: table-column-group }
td, th { display: table-cell }
caption { display: table-caption }
th { font-weight: bolder; text-align: center }
caption { text-align: center }
body { margin: 8px }
h1 { font-size: 2em; margin: .67em 0 }
h2 { font-size: 1.5em; margin: .75em 0 }
h3 { font-size: 1.17em; margin: .83em 0 }
h4,
blockquote,
fieldset, form,
dir, menu { margin: 1.12em 0 }
h5 { font-size: .83em; margin: 1.5em 0 }
h6 { font-size: .75em; margin: 1.67em 0 }
h1, h2, h3, h4,
h5, h6, b,
strong { font-weight: bolder }
blockquote { margin-left: 40px; margin-right: 40px }
i, cite, em,
var, address { font-style: italic }
pre, tt, code,
kbd, samp { font-family: monospace }
pre { white-space: pre }
button, textarea,
input, select { display: inline-block }
big { font-size: 1.17em }
small, sub, sup { font-size: .83em }
sub { vertical-align: sub }
sup { vertical-align: super }
table { border-spacing: 2px; }
thead, tbody,
tfoot { vertical-align: middle }
td, th, tr { vertical-align: inherit }
s, strike, del { text-decoration: line-through }
hr { border: 1px inset }
/* lists */
dd { display: block; margin-left: 40px }
p, dl, multicol { display: block; margin: 1em 0 }
ul { display: block; list-style-type: disc;
margin: 1em 0; padding-left: 40px }
ol { display: block; list-style-type: decimal;
margin: 1em 0; padding-left: 40px }
li { display: list-item }
/* nested lists have no top/bottom margins */
ul ul, ul ol, ul dl,
ol ul, ol ol, ol dl,
dl ul, dl ol, dl dl { margin-top: 0; margin-bottom: 0 }
/* 2 deep unordered lists use a circle */
ol ul, ul ul { list-style-type: circle; }
/* 3 deep (or more) unordered lists use a square */
ol ol ul, ol ul ul,
ul ol ul, ul ul ul { list-style-type: square; }
/* The type attribute on ol and ul elements */
ul[type="disc"] { list-style-type: disc; }
ul[type="circle"] { list-style-type: circle; }
ul[type="square"] { list-style-type: square; }
ol[type="1"] { list-style-type: decimal; }
ol[type="a"] { list-style-type: lower-alpha; }
ol[type="A"] { list-style-type: upper-alpha; }
ol[type="i"] { list-style-type: lower-roman; }
ol[type="I"] { list-style-type: upper-roman; }
u, ins { text-decoration: underline }
br:before { content: "\A"; white-space: pre }
center { text-align: center }
a:link,
a:visited,
area:link,
area:visited,
link:link,
link:visited { text-decoration: underline }
:focus { outline: thin dotted invert }
/* Begin bidirectionality settings (do not change) */
BDO[DIR="ltr"] { direction: ltr; unicode-bidi: bidi-override }
BDO[DIR="rtl"] { direction: rtl; unicode-bidi: bidi-override }
*[DIR="ltr"] { direction: ltr; unicode-bidi: embed }
*[DIR="rtl"] { direction: rtl; unicode-bidi: embed }
@media print {
h1 { page-break-before: always }
h1, h2, h3,
h4, h5, h6 { page-break-after: avoid }
ul, ol, dl { page-break-before: avoid }
}
/* Servo additions */
a:link,
area:link,
link:link { color: blue }
script { display: none }
style { display: none }