mirror of
https://github.com/servo/servo.git
synced 2025-08-06 22:15:33 +01:00
Cargoify servo
This commit is contained in:
parent
db2f642c32
commit
c6ab60dbfc
1761 changed files with 8423 additions and 2294 deletions
2
components/style/.gitignore
vendored
Normal file
2
components/style/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
properties/mod.rs
|
||||
properties/mod.rs.tmp
|
31
components/style/Cargo.toml
Normal file
31
components/style/Cargo.toml
Normal 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"
|
||||
|
BIN
components/style/Mako-0.9.1.zip
Normal file
BIN
components/style/Mako-0.9.1.zip
Normal file
Binary file not shown.
6
components/style/README.md
Normal file
6
components/style/README.md
Normal 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 [NetSurf’s libcss](https://github.com/mozilla-servo/libcss).
|
32
components/style/errors.rs
Normal file
32
components/style/errors.rs
Normal 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)
|
||||
}
|
183
components/style/font_face.rs
Normal file
183
components/style/font_face.rs
Normal 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
52
components/style/lib.rs
Normal 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;
|
8
components/style/makefile.cargo
Normal file
8
components/style/makefile.cargo
Normal 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 $@
|
131
components/style/media_queries.rs
Normal file
131
components/style/media_queries.rs
Normal 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
|
||||
})
|
||||
}
|
||||
}
|
64
components/style/namespaces.rs
Normal file
64
components/style/namespaces.rs
Normal 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
34
components/style/node.rs
Normal 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;
|
||||
}
|
||||
|
81
components/style/parsing_utils.rs
Normal file
81
components/style/parsing_utils.rs
Normal 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)
|
||||
}
|
262
components/style/properties/common_types.rs
Normal file
262
components/style/properties/common_types.rs
Normal 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())
|
||||
}
|
2143
components/style/properties/mod.rs.mako
Normal file
2143
components/style/properties/mod.rs.mako
Normal file
File diff suppressed because it is too large
Load diff
990
components/style/selector_matching.rs
Normal file
990
components/style/selector_matching.rs
Normal 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());
|
||||
}
|
||||
}
|
717
components/style/selectors.rs
Normal file
717
components/style/selectors.rs
Normal 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),
|
||||
})))
|
||||
}
|
||||
}
|
178
components/style/stylesheets.rs
Normal file
178
components/style/stylesheets.rs
Normal 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)
|
||||
}
|
118
components/style/user-agent.css
Normal file
118
components/style/user-agent.css
Normal 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 }
|
Loading…
Add table
Add a link
Reference in a new issue