Merge pull request #3078 from SimonSapin/style-cleanup

Various refactoring and fixes in the style crate
This commit is contained in:
Simon Sapin 2014-08-16 12:24:34 +01:00
commit 444ff84259
23 changed files with 861 additions and 887 deletions

View file

@ -348,7 +348,7 @@ RFLAGS_style = $(addprefix -L $(B)src/,$(DEPS_SUBMODULES)) -L $(B)src/components
MAKO_ZIP = $(S)src/components/style/Mako-0.9.1.zip
MAKO_style = $(S)src/components/style/properties/mod.rs
MAKO_SRC_style = $(MAKO_style).mako
SRC_style = $(call rwildcard,$(S)src/components/style/,*.rs) $(call rwildcard,$(S)src/compontents/style/properties/*.rs) $(MAKO_style)
SRC_style = $(call rwildcard,$(S)src/components/style/,*.rs) $(call rwildcard,$(S)src/compontents/style/properties/*.rs) $(MAKO_style) $(S)src/components/style/user-agent.css
CRATE_style = $(S)src/components/style/style.rs
DONE_style = $(B)src/components/style/libstyle.dummy
@ -364,7 +364,7 @@ DEPS_layout_traits = $(CRATE_layout_traits) $(SRC_layout_traits) $(DONE_script_t
RFLAGS_layout = $(addprefix -L $(B)src/,$(DEPS_SUBMODULES)) -L $(B)src/components/gfx -L $(B)src/components/util -L $(B)src/components/net -L $(B)src/components/script -L $(B)src/components/style -L $(B)src/components/msg -L$(B)src/components/macros -L$(B)src/components/layout_traits -L $(B)src/components/script_traits
SRC_layout = $(call rwildcard,$(S)src/components/layout/,*.rs) $(S)src/components/layout/css/user-agent.css
SRC_layout = $(call rwildcard,$(S)src/components/layout/,*.rs)
CRATE_layout = $(S)src/components/layout/layout.rs
DONE_layout = $(B)src/components/layout/liblayout.dummy

View file

@ -29,7 +29,7 @@ servo-test-$(1): $$(DEPS_$(1))
.PHONY: check-servo-$(1)
check-servo-$(1): servo-test-$(1)
@$$(call E, check: $(1))
$$(Q)./servo-test-$(1)
$$(Q)./servo-test-$(1) $(TESTNAME)
endef
$(foreach lib_crate,$(SERVO_LIB_CRATES),\

View file

@ -70,7 +70,7 @@ impl FontFamily {
/// Commands that the FontContext sends to the font cache task.
pub enum Command {
GetFontTemplate(String, FontTemplateDescriptor, Sender<Reply>),
AddWebFont(Vec<Url>, String, Sender<()>),
AddWebFont(String, Url, Sender<()>),
Exit(Sender<()>),
}
@ -105,21 +105,19 @@ impl FontCache {
result.send(GetFontTemplateReply(font_template));
}
AddWebFont(urls, family_name, result) => {
for url in urls.iter() {
let maybe_resource = load_whole_resource(&self.resource_task, url.clone());
match maybe_resource {
Ok((_, bytes)) => {
if !self.web_families.contains_key(&family_name) {
let family = FontFamily::new();
self.web_families.insert(family_name.clone(), family);
}
let family = self.web_families.get_mut(&family_name);
family.add_template(format!("{}", url).as_slice(), Some(bytes));
},
Err(msg) => {
fail!("{}: url={}", msg, url);
AddWebFont(family_name, url, result) => {
let maybe_resource = load_whole_resource(&self.resource_task, url.clone());
match maybe_resource {
Ok((_, bytes)) => {
if !self.web_families.contains_key(&family_name) {
let family = FontFamily::new();
self.web_families.insert(family_name.clone(), family);
}
let family = self.web_families.get_mut(&family_name);
family.add_template(format!("{}", url).as_slice(), Some(bytes));
},
Err(msg) => {
fail!("{}: url={}", msg, url);
}
}
result.send(());
@ -264,9 +262,9 @@ impl FontCacheTask {
}
}
pub fn add_web_font(&mut self, urls: Vec<Url>, family: &str) {
pub fn add_web_font(&mut self, family: String, url: Url) {
let (response_chan, response_port) = channel();
self.chan.send(AddWebFont(urls, family.to_string(), response_chan));
self.chan.send(AddWebFont(family, url, response_chan));
response_port.recv();
}

View file

@ -19,13 +19,13 @@ use servo_util::str::DOMString;
use std::mem;
use std::hash::{Hash, sip};
use std::slice::Items;
use style::{After, Before, ComputedValues, MatchedProperty, Stylist, TElement, TNode, cascade};
use style::{After, Before, ComputedValues, DeclarationBlock, Stylist, TElement, TNode, cascade};
use sync::Arc;
pub struct ApplicableDeclarations {
pub normal: SmallVec16<MatchedProperty>,
pub before: Vec<MatchedProperty>,
pub after: Vec<MatchedProperty>,
pub normal: SmallVec16<DeclarationBlock>,
pub before: Vec<DeclarationBlock>,
pub after: Vec<DeclarationBlock>,
/// Whether the `normal` declarations are shareable with other nodes.
pub normal_shareable: bool,
@ -51,11 +51,11 @@ impl ApplicableDeclarations {
#[deriving(Clone)]
pub struct ApplicableDeclarationsCacheEntry {
pub declarations: Vec<MatchedProperty>,
pub declarations: Vec<DeclarationBlock>,
}
impl ApplicableDeclarationsCacheEntry {
fn new(slice: &[MatchedProperty]) -> ApplicableDeclarationsCacheEntry {
fn new(slice: &[DeclarationBlock]) -> ApplicableDeclarationsCacheEntry {
let mut entry_declarations = Vec::new();
for declarations in slice.iter() {
entry_declarations.push(declarations.clone());
@ -81,11 +81,11 @@ impl Hash for ApplicableDeclarationsCacheEntry {
}
struct ApplicableDeclarationsCacheQuery<'a> {
declarations: &'a [MatchedProperty],
declarations: &'a [DeclarationBlock],
}
impl<'a> ApplicableDeclarationsCacheQuery<'a> {
fn new(declarations: &'a [MatchedProperty]) -> ApplicableDeclarationsCacheQuery<'a> {
fn new(declarations: &'a [DeclarationBlock]) -> ApplicableDeclarationsCacheQuery<'a> {
ApplicableDeclarationsCacheQuery {
declarations: declarations,
}
@ -141,14 +141,14 @@ impl ApplicableDeclarationsCache {
}
}
fn find(&self, declarations: &[MatchedProperty]) -> Option<Arc<ComputedValues>> {
fn find(&self, declarations: &[DeclarationBlock]) -> Option<Arc<ComputedValues>> {
match self.cache.find_equiv(&ApplicableDeclarationsCacheQuery::new(declarations)) {
None => None,
Some(ref values) => Some((*values).clone()),
}
}
fn insert(&mut self, declarations: &[MatchedProperty], style: Arc<ComputedValues>) {
fn insert(&mut self, declarations: &[DeclarationBlock], style: Arc<ComputedValues>) {
self.cache.insert(ApplicableDeclarationsCacheEntry::new(declarations), style)
}
}
@ -309,7 +309,7 @@ pub trait MatchMethods {
trait PrivateMatchMethods {
fn cascade_node_pseudo_element(&self,
parent_style: Option<&Arc<ComputedValues>>,
applicable_declarations: &[MatchedProperty],
applicable_declarations: &[DeclarationBlock],
style: &mut Option<Arc<ComputedValues>>,
applicable_declarations_cache: &mut
ApplicableDeclarationsCache,
@ -324,7 +324,7 @@ trait PrivateMatchMethods {
impl<'ln> PrivateMatchMethods for LayoutNode<'ln> {
fn cascade_node_pseudo_element(&self,
parent_style: Option<&Arc<ComputedValues>>,
applicable_declarations: &[MatchedProperty],
applicable_declarations: &[DeclarationBlock],
style: &mut Option<Arc<ComputedValues>>,
applicable_declarations_cache: &mut
ApplicableDeclarationsCache,

View file

@ -1,18 +0,0 @@
/* 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 style::{Stylesheet, Stylist, UserAgentOrigin, with_errors_silenced};
use url::Url;
pub fn new_stylist() -> Stylist {
let mut stylist = Stylist::new();
let ua_stylesheet = with_errors_silenced(|| Stylesheet::from_bytes(
include_bin!("user-agent.css"),
Url::parse("chrome:///user-agent.css").unwrap(),
None,
None));
stylist.add_stylesheet(ua_stylesheet, UserAgentOrigin);
stylist
}

View file

@ -62,7 +62,6 @@ pub mod extra;
pub mod css {
mod node_util;
pub mod select;
pub mod matching;
pub mod node_style;
}

View file

@ -6,7 +6,6 @@
//! rendered.
use css::matching::{ApplicableDeclarations, MatchMethods};
use css::select::new_stylist;
use css::node_style::StyledNode;
use construct::{FlowConstructionResult, NoConstructionResult};
use context::{LayoutContext, SharedLayoutContext};
@ -57,7 +56,7 @@ use std::comm::{channel, Sender, Receiver, Select};
use std::mem;
use std::ptr;
use style::{AuthorOrigin, Stylesheet, Stylist};
use style::CSSFontFaceRule;
use style::iter_font_face_rules;
use sync::{Arc, Mutex};
use url::Url;
@ -347,7 +346,7 @@ impl LayoutTask {
screen_size: screen_size,
display_list: None,
stylist: box new_stylist(),
stylist: box Stylist::new(),
parallel_traversal: parallel_traversal,
time_profiler_chan: time_profiler_chan,
opts: opts.clone(),
@ -491,22 +490,9 @@ impl LayoutTask {
fn handle_add_stylesheet(&mut self, sheet: Stylesheet) {
// Find all font-face rules and notify the font cache of them.
// GWTODO: Need to handle unloading web fonts (when we handle unloading stylesheets!)
// GWTODO: Need to handle font-face nested within media rules.
for rule in sheet.rules.iter() {
match rule {
&CSSFontFaceRule(ref font_face_rule) => {
let mut font_urls = vec!();
for source_line in font_face_rule.source_lines.iter() {
for source in source_line.sources.iter() {
font_urls.push(source.url.clone());
}
}
self.font_cache_task.add_web_font(font_urls, font_face_rule.family.as_slice());
},
_ => {}
}
}
iter_font_face_rules(&sheet, |family, url| {
self.font_cache_task.add_web_font(family.to_string(), url.clone());
});
self.stylist.add_stylesheet(sheet, AuthorOrigin);
}

View file

@ -265,13 +265,11 @@ impl<'ln> TNode<LayoutElement<'ln>> for LayoutNode<'ln> {
}
fn match_attr(&self, attr: &AttrSelector, test: |&str| -> bool) -> bool {
let name = unsafe {
let element: JS<Element> = self.node.transmute_copy();
if element.html_element_in_html_document_for_layout() {
attr.lower_name.as_slice()
} else {
attr.name.as_slice()
}
assert!(self.is_element())
let name = if self.is_html_element_in_html_document() {
attr.lower_name.as_slice()
} else {
attr.name.as_slice()
};
match attr.namespace {
SpecificNamespace(ref ns) => {
@ -283,6 +281,15 @@ impl<'ln> TNode<LayoutElement<'ln>> for LayoutNode<'ln> {
AnyNamespace => false,
}
}
fn is_html_element_in_html_document(&self) -> bool {
unsafe {
self.is_element() && {
let element: JS<Element> = self.node.transmute_copy();
element.html_element_in_html_document_for_layout()
}
}
}
}
pub struct LayoutNodeChildrenIterator<'a> {

View file

@ -4,7 +4,6 @@
//! Element nodes.
use cssparser::tokenize;
use dom::attr::{Attr, ReplacedAttr, FirstSetAttr, AttrHelpersForLayout};
use dom::attr::{AttrValue, StringAttrValue, UIntAttrValue, AtomAttrValue};
use dom::attrlist::AttrList;
@ -31,7 +30,7 @@ use dom::nodelist::NodeList;
use dom::virtualmethods::{VirtualMethods, vtable_for};
use layout_interface::ContentChangedDocumentDamage;
use layout_interface::MatchSelectorsDocumentDamage;
use style::{matches_compound_selector, NamespaceMap, parse_selector_list};
use style::{matches, parse_selector_list_from_str};
use style;
use servo_util::atom::Atom;
use servo_util::namespace;
@ -775,21 +774,13 @@ impl<'a> ElementMethods for JSRef<'a, Element> {
// http://dom.spec.whatwg.org/#dom-element-matches
fn Matches(&self, selectors: DOMString) -> Fallible<bool> {
let namespace = NamespaceMap::new();
match parse_selector_list(tokenize(selectors.as_slice()).map(|(token, _)| token).collect(),
&namespace) {
None => return Err(Syntax),
Some(ref selectors) => {
match parse_selector_list_from_str(selectors.as_slice()) {
Err(()) => Err(Syntax),
Ok(ref selectors) => {
let root: &JSRef<Node> = NodeCast::from_ref(self);
for selector in selectors.iter() {
let mut shareable = false;
if matches_compound_selector(&*selector.compound_selectors, root, &mut shareable) {
return Ok(true);
}
}
Ok(matches(selectors, root))
}
}
Ok(false)
}
}

View file

@ -4,7 +4,6 @@
//! The core DOM types. Defines the basic DOM hierarchy as well as all the HTML elements.
use cssparser::tokenize;
use dom::attr::Attr;
use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
use dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods;
@ -48,7 +47,7 @@ use layout_interface::{ContentBoxQuery, ContentBoxResponse, ContentBoxesQuery, C
LayoutChan, ReapLayoutDataMsg, TrustedNodeAddress, UntrustedNodeAddress};
use servo_util::geometry::Au;
use servo_util::str::{DOMString, null_str_as_empty};
use style::{parse_selector_list, matches_compound_selector, NamespaceMap};
use style::{parse_selector_list_from_str, matches};
use js::jsapi::{JSContext, JSObject, JSRuntime};
use js::jsfriendapi;
@ -611,21 +610,16 @@ impl<'m, 'n> NodeHelpers<'m, 'n> for JSRef<'n, Node> {
// http://dom.spec.whatwg.org/#dom-parentnode-queryselector
fn query_selector(&self, selectors: DOMString) -> Fallible<Option<Temporary<Element>>> {
// Step 1.
let namespace = NamespaceMap::new();
match parse_selector_list(tokenize(selectors.as_slice()).map(|(token, _)| token).collect(), &namespace) {
match parse_selector_list_from_str(selectors.as_slice()) {
// Step 2.
None => return Err(Syntax),
Err(()) => return Err(Syntax),
// Step 3.
Some(ref selectors) => {
Ok(ref selectors) => {
let root = self.ancestors().last().unwrap_or(self.clone());
for selector in selectors.iter() {
assert!(selector.pseudo_element.is_none());
for node in root.traverse_preorder().filter(|node| node.is_element()) {
let mut _shareable: bool = false;
if matches_compound_selector(selector.compound_selectors.deref(), &node, &mut _shareable) {
let elem: &JSRef<Element> = ElementCast::to_ref(&node).unwrap();
return Ok(Some(Temporary::from_rooted(elem)));
}
for node in root.traverse_preorder() {
if node.is_element() && matches(selectors, &node) {
let elem: &JSRef<Element> = ElementCast::to_ref(&node).unwrap();
return Ok(Some(Temporary::from_rooted(elem)));
}
}
}
@ -636,23 +630,15 @@ impl<'m, 'n> NodeHelpers<'m, 'n> for JSRef<'n, Node> {
// http://dom.spec.whatwg.org/#dom-parentnode-queryselectorall
fn query_selector_all(&self, selectors: DOMString) -> Fallible<Temporary<NodeList>> {
// Step 1.
let mut nodes = vec!();
let nodes;
let root = self.ancestors().last().unwrap_or(self.clone());
let namespace = NamespaceMap::new();
match parse_selector_list(tokenize(selectors.as_slice()).map(|(token, _)| token).collect(), &namespace) {
match parse_selector_list_from_str(selectors.as_slice()) {
// Step 2.
None => return Err(Syntax),
Err(()) => return Err(Syntax),
// Step 3.
Some(ref selectors) => {
for selector in selectors.iter() {
assert!(selector.pseudo_element.is_none());
for node in root.traverse_preorder().filter(|node| node.is_element()) {
let mut _shareable: bool = false;
if matches_compound_selector(selector.compound_selectors.deref(), &node, &mut _shareable) {
nodes.push(node.clone())
}
}
}
Ok(ref selectors) => {
nodes = root.traverse_preorder().filter(
|node| node.is_element() && matches(selectors, node)).collect()
}
}
let window = window_from_node(self).root();
@ -1997,29 +1983,32 @@ impl<'a> style::TNode<JSRef<'a, Element>> for JSRef<'a, Node> {
fn parent_node(&self) -> Option<JSRef<'a, Node>> {
(self as &NodeHelpers).parent_node().map(|node| *node.root())
}
fn prev_sibling(&self) -> Option<JSRef<'a, Node>> {
(self as &NodeHelpers).prev_sibling().map(|node| *node.root())
}
fn next_sibling(&self) -> Option<JSRef<'a, Node>> {
(self as &NodeHelpers).next_sibling().map(|node| *node.root())
}
fn is_document(&self) -> bool {
(self as &NodeHelpers).is_document()
}
fn is_element(&self) -> bool {
(self as &NodeHelpers).is_element()
}
fn as_element(&self) -> JSRef<'a, Element> {
let elem: Option<&JSRef<'a, Element>> = ElementCast::to_ref(self);
assert!(elem.is_some());
*elem.unwrap()
}
fn match_attr(&self, attr: &style::AttrSelector, test: |&str| -> bool) -> bool {
let name = {
let elem: Option<&JSRef<'a, Element>> = ElementCast::to_ref(self);
assert!(elem.is_some());
let elem: &ElementHelpers = elem.unwrap() as &ElementHelpers;
if elem.html_element_in_html_document() {
if self.is_html_element_in_html_document() {
attr.lower_name.as_slice()
} else {
attr.name.as_slice()
@ -2034,6 +2023,13 @@ impl<'a> style::TNode<JSRef<'a, Element>> for JSRef<'a, Node> {
style::AnyNamespace => false,
}
}
fn is_html_element_in_html_document(&self) -> bool {
let elem: Option<&JSRef<'a, Element>> = ElementCast::to_ref(self);
assert!(elem.is_some());
let elem: &ElementHelpers = elem.unwrap() as &ElementHelpers;
elem.html_element_in_html_document()
}
}
pub trait DisabledStateHelpers {

View file

@ -27,21 +27,6 @@ impl<T, I: Iterator<Result<T, SyntaxError>>> Iterator<T> for ErrorLoggerIterator
/// Set a `RUST_LOG=style::errors` environment variable
/// to log CSS parse errors to stderr.
pub fn log_css_error(location: SourceLocation, message: &str) {
// Check this first as its cheaper than local_data.
if log_enabled!(::log::INFO) {
if silence_errors.get().is_none() {
// TODO eventually this will got into a "web console" or something.
info!("{:u}:{:u} {:s}", location.line, location.column, message)
}
}
}
local_data_key!(silence_errors: ())
pub fn with_errors_silenced<T>(f: || -> T) -> T {
silence_errors.replace(Some(()));
let result = f();
silence_errors.replace(None);
result
// TODO eventually this will got into a "web console" or something.
info!("{:u}:{:u} {:s}", location.line, location.column, message)
}

View file

@ -6,37 +6,54 @@ use cssparser::ast::*;
use cssparser::parse_declaration_list;
use errors::{ErrorLoggerIterator, log_css_error};
use std::ascii::StrAsciiExt;
use parsing_utils::one_component_value;
use stylesheets::{CSSRule, CSSFontFaceRule};
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};
#[deriving(PartialEq)]
pub enum FontFaceFormat {
UnknownFormat,
WoffFormat,
TtfFormat,
SvgFormat,
EotFormat,
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)
}
}
},
}
}
}
pub struct FontFaceSource {
enum Source {
UrlSource(UrlSource),
LocalSource(String),
}
pub struct UrlSource {
pub url: Url,
pub format_hints: Vec<FontFaceFormat>,
}
pub struct FontFaceSourceLine {
pub sources: Vec<FontFaceSource>
pub format_hints: Vec<String>,
}
pub struct FontFaceRule {
pub family: String,
pub source_lines: Vec<FontFaceSourceLine>,
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) {
let mut maybe_family = None;
let mut source_lines = vec!();
if rule.prelude.as_slice().skip_whitespace().next().is_some() {
log_css_error(rule.location, "@font-face prelude contains unexpected characters");
return;
@ -50,144 +67,36 @@ pub fn parse_font_face_rule(rule: AtRule, parent_rules: &mut Vec<CSSRule>, base_
}
};
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: location, name: name, value: value, important: _}) => {
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" => {
// FIXME(#2802): Share code with the font-family parser.
match one_component_value(value.as_slice()) {
Some(&String(ref string_value)) => {
maybe_family = Some(string_value.clone());
let iter = &mut BufferedIter::new(value.as_slice().skip_whitespace());
match parse_one_family(iter) {
Ok(FamilyName(name)) => {
maybe_family = Some(name);
},
_ => {
log_css_error(location, format!("Unsupported font-family string {:s}", name).as_slice());
}
// This also includes generic family names:
_ => log_css_error(location, "Invalid font-family in @font-face"),
}
},
"src" => {
let mut iter = value.as_slice().skip_whitespace();
let mut sources = vec!();
let mut syntax_error = false;
'outer: loop {
// url() or local() should be next
let maybe_url = match iter.next() {
Some(&URL(ref string_value)) => {
let maybe_url = UrlParser::new().base_url(base_url).parse(string_value.as_slice());
let url = maybe_url.unwrap_or_else(|_| Url::parse("about:invalid").unwrap());
Some(url)
},
Some(&Function(ref string_value, ref _values)) => {
match string_value.as_slice() {
"local" => {
log_css_error(location, "local font face is not supported yet - skipping");
None
},
_ => {
log_css_error(location, format!("Unexpected token {}", string_value).as_slice());
syntax_error = true;
break;
}
}
},
_ => {
log_css_error(location, "Unsupported declaration type");
syntax_error = true;
break;
}
};
let mut next_token = iter.next();
match maybe_url {
Some(url) => {
let mut source = FontFaceSource {
url: url,
format_hints: vec!(),
};
// optional format, or comma to start loop again
match next_token {
Some(&Function(ref string_value, ref values)) => {
match string_value.as_slice() {
"format" => {
let mut format_iter = values.as_slice().skip_whitespace();
loop {
let fmt_token = format_iter.next();
match fmt_token {
Some(&String(ref format_hint)) => {
let hint = match format_hint.as_slice() {
"embedded-opentype" => EotFormat,
"woff" => WoffFormat,
"truetype" | "opentype" => TtfFormat,
"svg" => SvgFormat,
_ => UnknownFormat,
};
source.format_hints.push(hint);
},
_ => {
log_css_error(location, "Unexpected token");
syntax_error = true;
break 'outer;
}
}
let comma_token = format_iter.next();
match comma_token {
Some(&Comma) => {},
None => {
break;
}
_ => {
log_css_error(location, "Unexpected token");
syntax_error = true;
break 'outer;
}
}
}
},
_ => {
log_css_error(location,
format!("Unsupported token {}", string_value).as_slice());
syntax_error = true;
break;
}
}
next_token = iter.next();
},
_ => {}
}
sources.push(source);
},
None => {},
}
// after url or optional format, comes comma or end
match next_token {
Some(&Comma) => {},
None => break,
_ => {
log_css_error(location, "Unexpected token type");
syntax_error = true;
break;
}
}
}
if !syntax_error && sources.len() > 0 {
let source_line = FontFaceSourceLine {
sources: sources
};
source_lines.push(source_line);
}
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());
@ -197,11 +106,78 @@ pub fn parse_font_face_rule(rule: AtRule, parent_rules: &mut Vec<CSSRule>, base_
}
}
if maybe_family.is_some() && source_lines.len() > 0 {
let font_face_rule = FontFaceRule {
family: maybe_family.unwrap(),
source_lines: source_lines,
};
parent_rules.push(CSSFontFaceRule(font_face_rule));
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(())
}
}

View file

@ -18,6 +18,7 @@ pub trait TNode<E:TElement> : Clone {
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 {

View file

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

View file

@ -40,32 +40,32 @@ pub mod specified {
static AU_PER_PC: CSSFloat = AU_PER_PT * 12.;
impl Length {
#[inline]
fn parse_internal(input: &ComponentValue, negative_ok: bool) -> Option<Length> {
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. => Some(Au_(Au(0))),
_ => None
&Number(ref value) if value.value == 0. => Ok(Au_(Au(0))),
_ => Err(())
}
}
#[allow(dead_code)]
pub fn parse(input: &ComponentValue) -> Option<Length> {
pub fn parse(input: &ComponentValue) -> Result<Length, ()> {
Length::parse_internal(input, /* negative_ok = */ true)
}
pub fn parse_non_negative(input: &ComponentValue) -> Option<Length> {
pub fn parse_non_negative(input: &ComponentValue) -> Result<Length, ()> {
Length::parse_internal(input, /* negative_ok = */ false)
}
pub fn parse_dimension(value: CSSFloat, unit: &str) -> Option<Length> {
pub fn parse_dimension(value: CSSFloat, unit: &str) -> Result<Length, ()> {
match unit.to_ascii_lower().as_slice() {
"px" => Some(Length::from_px(value)),
"in" => Some(Au_(Au((value * AU_PER_IN) as i32))),
"cm" => Some(Au_(Au((value * AU_PER_CM) as i32))),
"mm" => Some(Au_(Au((value * AU_PER_MM) as i32))),
"pt" => Some(Au_(Au((value * AU_PER_PT) as i32))),
"pc" => Some(Au_(Au((value * AU_PER_PC) as i32))),
"em" => Some(Em(value)),
"ex" => Some(Ex(value)),
_ => None
"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]
@ -81,23 +81,23 @@ pub mod specified {
}
impl LengthOrPercentage {
fn parse_internal(input: &ComponentValue, negative_ok: bool)
-> Option<LengthOrPercentage> {
-> 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.
=> Some(LP_Percentage(value.value / 100.)),
&Number(ref value) if value.value == 0. => Some(LP_Length(Au_(Au(0)))),
_ => None
=> 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) -> Option<LengthOrPercentage> {
pub fn parse(input: &ComponentValue) -> Result<LengthOrPercentage, ()> {
LengthOrPercentage::parse_internal(input, /* negative_ok = */ true)
}
#[inline]
pub fn parse_non_negative(input: &ComponentValue) -> Option<LengthOrPercentage> {
pub fn parse_non_negative(input: &ComponentValue) -> Result<LengthOrPercentage, ()> {
LengthOrPercentage::parse_internal(input, /* negative_ok = */ false)
}
}
@ -110,23 +110,23 @@ pub mod specified {
}
impl LengthOrPercentageOrAuto {
fn parse_internal(input: &ComponentValue, negative_ok: bool)
-> Option<LengthOrPercentageOrAuto> {
-> 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.
=> Some(LPA_Percentage(value.value / 100.)),
&Number(ref value) if value.value == 0. => Some(LPA_Length(Au_(Au(0)))),
&Ident(ref value) if value.as_slice().eq_ignore_ascii_case("auto") => Some(LPA_Auto),
_ => None
=> 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) -> Option<LengthOrPercentageOrAuto> {
pub fn parse(input: &ComponentValue) -> Result<LengthOrPercentageOrAuto, ()> {
LengthOrPercentageOrAuto::parse_internal(input, /* negative_ok = */ true)
}
#[inline]
pub fn parse_non_negative(input: &ComponentValue) -> Option<LengthOrPercentageOrAuto> {
pub fn parse_non_negative(input: &ComponentValue) -> Result<LengthOrPercentageOrAuto, ()> {
LengthOrPercentageOrAuto::parse_internal(input, /* negative_ok = */ false)
}
}
@ -139,24 +139,24 @@ pub mod specified {
}
impl LengthOrPercentageOrNone {
fn parse_internal(input: &ComponentValue, negative_ok: bool)
-> Option<LengthOrPercentageOrNone> {
-> 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.
=> Some(LPN_Percentage(value.value / 100.)),
&Number(ref value) if value.value == 0. => Some(LPN_Length(Au_(Au(0)))),
&Ident(ref value) if value.as_slice().eq_ignore_ascii_case("none") => Some(LPN_None),
_ => None
=> 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) -> Option<LengthOrPercentageOrNone> {
pub fn parse(input: &ComponentValue) -> Result<LengthOrPercentageOrNone, ()> {
LengthOrPercentageOrNone::parse_internal(input, /* negative_ok = */ true)
}
#[inline]
pub fn parse_non_negative(input: &ComponentValue) -> Option<LengthOrPercentageOrNone> {
pub fn parse_non_negative(input: &ComponentValue) -> Result<LengthOrPercentageOrNone, ()> {
LengthOrPercentageOrNone::parse_internal(input, /* negative_ok = */ false)
}
}

View file

@ -18,7 +18,7 @@ pub use geom::SideOffsets2D;
use errors::{ErrorLoggerIterator, log_css_error};
pub use parsing_utils::*;
pub use self::common_types::*;
use selector_matching::MatchedProperty;
use selector_matching::DeclarationBlock;
pub use self::property_bit_field::PropertyBitField;
@ -118,12 +118,13 @@ pub mod longhands {
${caller.body()}
% if derived_from is None:
pub fn parse_declared(input: &[ComponentValue], base_url: &Url)
-> Option<DeclaredValue<SpecifiedValue>> {
-> Result<DeclaredValue<SpecifiedValue>, ()> {
match CSSWideKeyword::parse(input) {
Some(Some(keyword)) => Some(CSSWideKeyword(keyword)),
Some(None) => Some(CSSWideKeyword(${
"Inherit" if THIS_STYLE_STRUCT.inherited else "Initial"})),
None => parse_specified(input, base_url),
Ok(InheritKeyword) => Ok(Inherit),
Ok(InitialKeyword) => Ok(Initial),
Ok(UnsetKeyword) => Ok(${
"Inherit" if THIS_STYLE_STRUCT.inherited else "Initial"}),
Err(()) => parse_specified(input, base_url),
}
}
% endif
@ -136,7 +137,7 @@ pub mod longhands {
${caller.body()}
% if derived_from is None:
pub fn parse_specified(_input: &[ComponentValue], _base_url: &Url)
-> Option<DeclaredValue<SpecifiedValue>> {
-> Result<DeclaredValue<SpecifiedValue>, ()> {
parse(_input, _base_url).map(super::SpecifiedValue)
}
% endif
@ -147,7 +148,7 @@ pub mod longhands {
<%self:longhand name="${name}" derived_from="${derived_from}"
experimental="${experimental}">
${caller.body()}
pub fn parse(input: &[ComponentValue], base_url: &Url) -> Option<SpecifiedValue> {
pub fn parse(input: &[ComponentValue], base_url: &Url) -> Result<SpecifiedValue, ()> {
one_component_value(input).and_then(|c| from_component_value(c, base_url))
}
</%self:longhand>
@ -170,13 +171,13 @@ pub mod longhands {
${to_rust_ident(values.split()[0])}
}
pub fn from_component_value(v: &ComponentValue, _base_url: &Url)
-> Option<SpecifiedValue> {
-> Result<SpecifiedValue, ()> {
get_ident_lower(v).and_then(|keyword| {
match keyword.as_slice() {
% for value in values.split():
"${value}" => Some(${to_rust_ident(value)}),
"${value}" => Ok(${to_rust_ident(value)}),
% endfor
_ => None,
_ => Err(()),
}
})
}
@ -201,7 +202,7 @@ pub mod longhands {
}
#[inline] pub fn get_initial_value() -> computed_value::T { ${initial_value} }
#[inline] pub fn from_component_value(v: &ComponentValue, _base_url: &Url)
-> Option<SpecifiedValue> {
-> Result<SpecifiedValue, ()> {
specified::${type}::${parse_method}(v)
}
</%self:single_component_value>
@ -244,14 +245,14 @@ pub mod longhands {
% endfor
pub fn parse_border_width(component_value: &ComponentValue, _base_url: &Url)
-> Option<specified::Length> {
-> Result<specified::Length, ()> {
match component_value {
&Ident(ref value) => {
match value.as_slice().to_ascii_lower().as_slice() {
"thin" => Some(specified::Length::from_px(1.)),
"medium" => Some(specified::Length::from_px(3.)),
"thick" => Some(specified::Length::from_px(5.)),
_ => None
"thin" => Ok(specified::Length::from_px(1.)),
"medium" => Ok(specified::Length::from_px(3.)),
"thick" => Ok(specified::Length::from_px(5.)),
_ => Err(())
}
},
_ => specified::Length::parse_non_negative(component_value)
@ -267,7 +268,7 @@ pub mod longhands {
#[inline] pub fn get_initial_value() -> computed_value::T {
Au::from_px(3) // medium
}
pub fn parse(input: &[ComponentValue], base_url: &Url) -> Option<SpecifiedValue> {
pub fn parse(input: &[ComponentValue], base_url: &Url) -> Result<SpecifiedValue, ()> {
one_component_value(input).and_then(|c| parse_border_width(c, base_url))
}
#[inline]
@ -346,7 +347,7 @@ pub mod longhands {
pub fn get_initial_value() -> computed_value::T { computed::LPA_Auto }
#[inline]
pub fn from_component_value(v: &ComponentValue, _base_url: &Url)
-> Option<SpecifiedValue> {
-> Result<SpecifiedValue, ()> {
specified::LengthOrPercentageOrAuto::parse_non_negative(v)
}
pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
@ -387,18 +388,18 @@ pub mod longhands {
}
/// normal | <number> | <length> | <percentage>
pub fn from_component_value(input: &ComponentValue, _base_url: &Url)
-> Option<SpecifiedValue> {
-> Result<SpecifiedValue, ()> {
match input {
&ast::Number(ref value) if value.value >= 0.
=> Some(SpecifiedNumber(value.value)),
=> Ok(SpecifiedNumber(value.value)),
&ast::Percentage(ref value) if value.value >= 0.
=> Some(SpecifiedLength(specified::Em(value.value / 100.))),
=> Ok(SpecifiedLength(specified::Em(value.value / 100.))),
&Dimension(ref value, ref unit) if value.value >= 0.
=> specified::Length::parse_dimension(value.value, unit.as_slice())
.map(SpecifiedLength),
&Ident(ref value) if value.as_slice().eq_ignore_ascii_case("normal")
=> Some(SpecifiedNormal),
_ => None,
=> Ok(SpecifiedNormal),
_ => Err(()),
}
}
pub mod computed_value {
@ -474,14 +475,14 @@ pub mod longhands {
/// baseline | sub | super | top | text-top | middle | bottom | text-bottom
/// | <percentage> | <length>
pub fn from_component_value(input: &ComponentValue, _base_url: &Url)
-> Option<SpecifiedValue> {
-> Result<SpecifiedValue, ()> {
match input {
&Ident(ref value) => {
match value.as_slice().to_ascii_lower().as_slice() {
% for keyword in vertical_align_keywords:
"${keyword}" => Some(Specified_${to_rust_ident(keyword)}),
"${keyword}" => Ok(Specified_${to_rust_ident(keyword)}),
% endfor
_ => None,
_ => Err(()),
}
},
_ => specified::LengthOrPercentage::parse_non_negative(input)
@ -552,12 +553,12 @@ pub mod longhands {
// normal | none | [ <string> ]+
// TODO: <uri>, <counter>, attr(<identifier>), open-quote, close-quote, no-open-quote, no-close-quote
pub fn parse(input: &[ComponentValue], _base_url: &Url) -> Option<SpecifiedValue> {
pub fn parse(input: &[ComponentValue], _base_url: &Url) -> Result<SpecifiedValue, ()> {
match one_component_value(input) {
Some(&Ident(ref keyword)) => {
Ok(&Ident(ref keyword)) => {
match keyword.as_slice().to_ascii_lower().as_slice() {
"normal" => return Some(normal),
"none" => return Some(none),
"normal" => return Ok(normal),
"none" => return Ok(none),
_ => ()
}
},
@ -568,10 +569,10 @@ pub mod longhands {
match component_value {
&String(ref value)
=> content.push(StringContent(value.clone())),
_ => return None // invalid/unsupported value
_ => return Err(()) // invalid/unsupported value
}
}
Some(Content(content))
Ok(Content(content))
}
</%self:longhand>
// CSS 2.1, Section 13 - Paged media
@ -593,14 +594,16 @@ pub mod longhands {
#[inline] pub fn get_initial_value() -> SpecifiedValue {
None
}
pub fn from_component_value(component_value: &ComponentValue, base_url: &Url) -> Option<SpecifiedValue> {
pub fn from_component_value(component_value: &ComponentValue, base_url: &Url)
-> Result<SpecifiedValue, ()> {
match component_value {
&ast::URL(ref url) => {
let image_url = parse_url(url.as_slice(), base_url);
Some(Some(image_url))
Ok(Some(image_url))
},
&ast::Ident(ref value) if value.as_slice().eq_ignore_ascii_case("none") => Some(None),
_ => None,
&ast::Ident(ref value) if value.as_slice().eq_ignore_ascii_case("none")
=> Ok(None),
_ => Err(()),
}
}
</%self:single_component_value>
@ -643,36 +646,29 @@ pub mod longhands {
// FIXME(#1997, pcwalton): Support complete CSS2 syntax.
pub fn parse_horizontal_and_vertical(horiz: &ComponentValue, vert: &ComponentValue)
-> Option<SpecifiedValue> {
let horiz = match specified::LengthOrPercentage::parse_non_negative(horiz) {
None => return None,
Some(value) => value,
};
-> Result<SpecifiedValue, ()> {
let horiz = try!(specified::LengthOrPercentage::parse_non_negative(horiz));
let vert = try!(specified::LengthOrPercentage::parse_non_negative(vert));
let vert = match specified::LengthOrPercentage::parse_non_negative(vert) {
None => return None,
Some(value) => value,
};
Some(SpecifiedValue {
Ok(SpecifiedValue {
horizontal: horiz,
vertical: vert,
})
}
pub fn parse(input: &[ComponentValue], _: &Url) -> Option<SpecifiedValue> {
pub fn parse(input: &[ComponentValue], _: &Url) -> Result<SpecifiedValue, ()> {
let mut input_iter = input.skip_whitespace();
let horizontal = input_iter.next();
let vertical = input_iter.next();
if input_iter.next().is_some() {
return None
return Err(())
}
match (horizontal, vertical) {
(Some(horizontal), Some(vertical)) => {
parse_horizontal_and_vertical(horizontal, vertical)
}
_ => None
_ => Err(())
}
}
</%self:longhand>
@ -693,11 +689,11 @@ pub mod longhands {
RGBA { red: 0., green: 0., blue: 0., alpha: 1. } /* black */
}
pub fn parse_specified(input: &[ComponentValue], _base_url: &Url)
-> Option<DeclaredValue<SpecifiedValue>> {
-> Result<DeclaredValue<SpecifiedValue>, ()> {
match one_component_value(input).and_then(Color::parse) {
Some(RGBA(rgba)) => Some(SpecifiedValue(rgba)),
Some(CurrentColor) => Some(CSSWideKeyword(Inherit)),
None => None,
Ok(RGBA(rgba)) => Ok(SpecifiedValue(rgba)),
Ok(CurrentColor) => Ok(Inherit),
Err(()) => Err(()),
}
}
</%self:raw_longhand>
@ -722,61 +718,48 @@ pub mod longhands {
pub type T = Vec<FontFamily>;
}
pub type SpecifiedValue = computed_value::T;
#[inline] pub fn get_initial_value() -> computed_value::T { vec!(FamilyName("serif".to_string())) }
#[inline]
pub fn get_initial_value() -> computed_value::T {
vec![FamilyName("serif".to_string())]
}
/// <familiy-name>#
/// <familiy-name> = <string> | [ <ident>+ ]
/// TODO: <generic-familiy>
pub fn parse(input: &[ComponentValue], _base_url: &Url) -> Option<SpecifiedValue> {
from_iter(input.skip_whitespace())
pub fn parse(input: &[ComponentValue], _base_url: &Url) -> Result<SpecifiedValue, ()> {
parse_slice_comma_separated(input, parse_one_family)
}
pub fn from_iter<'a>(mut iter: SkipWhitespaceIterator<'a>) -> Option<SpecifiedValue> {
let mut result = vec!();
macro_rules! add(
($value: expr, $b: expr) => {
{
result.push($value);
match iter.next() {
Some(&Comma) => (),
None => $b,
_ => return None,
}
}
pub fn parse_one_family<'a>(iter: ParserIter) -> Result<FontFamily, ()> {
// TODO: avoid copying strings?
let mut idents = match iter.next() {
Some(&String(ref value)) => return Ok(FamilyName(value.clone())),
Some(&Ident(ref value)) => {
// match value.as_slice().to_ascii_lower().as_slice() {
// "serif" => return Ok(Serif),
// "sans-serif" => return Ok(SansSerif),
// "cursive" => return Ok(Cursive),
// "fantasy" => return Ok(Fantasy),
// "monospace" => return Ok(Monospace),
// _ => {
vec![value.as_slice()]
// }
// }
}
)
'outer: loop {
match iter.next() {
// TODO: avoid copying strings?
Some(&String(ref value)) => add!(FamilyName(value.clone()), break 'outer),
Some(&Ident(ref value)) => {
match value.as_slice().to_ascii_lower().as_slice() {
// "serif" => add!(Serif, break 'outer),
// "sans-serif" => add!(SansSerif, break 'outer),
// "cursive" => add!(Cursive, break 'outer),
// "fantasy" => add!(Fantasy, break 'outer),
// "monospace" => add!(Monospace, break 'outer),
_ => {
let mut idents = vec!(value.as_slice());
loop {
match iter.next() {
Some(&Ident(ref value)) => idents.push(value.as_slice()),
Some(&Comma) => {
result.push(FamilyName(idents.connect(" ")));
break
},
None => {
result.push(FamilyName(idents.connect(" ")));
break 'outer
},
_ => return None,
}
}
}
}
_ => return Err(())
};
for component_value in iter {
match component_value {
&Ident(ref value) => {
idents.push(value.as_slice());
iter.next();
},
_ => {
iter.push_back(component_value);
break
}
_ => return None,
}
}
Some(result)
Ok(FamilyName(idents.connect(" ")))
}
</%self:longhand>
@ -795,30 +778,30 @@ pub mod longhands {
}
/// normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
pub fn from_component_value(input: &ComponentValue, _base_url: &Url)
-> Option<SpecifiedValue> {
-> Result<SpecifiedValue, ()> {
match input {
&Ident(ref value) => {
match value.as_slice().to_ascii_lower().as_slice() {
"bold" => Some(SpecifiedWeight700),
"normal" => Some(SpecifiedWeight400),
"bolder" => Some(Bolder),
"lighter" => Some(Lighter),
_ => None,
"bold" => Ok(SpecifiedWeight700),
"normal" => Ok(SpecifiedWeight400),
"bolder" => Ok(Bolder),
"lighter" => Ok(Lighter),
_ => Err(()),
}
},
&Number(ref value) => match value.int_value {
Some(100) => Some(SpecifiedWeight100),
Some(200) => Some(SpecifiedWeight200),
Some(300) => Some(SpecifiedWeight300),
Some(400) => Some(SpecifiedWeight400),
Some(500) => Some(SpecifiedWeight500),
Some(600) => Some(SpecifiedWeight600),
Some(700) => Some(SpecifiedWeight700),
Some(800) => Some(SpecifiedWeight800),
Some(900) => Some(SpecifiedWeight900),
_ => None,
Some(100) => Ok(SpecifiedWeight100),
Some(200) => Ok(SpecifiedWeight200),
Some(300) => Ok(SpecifiedWeight300),
Some(400) => Ok(SpecifiedWeight400),
Some(500) => Ok(SpecifiedWeight500),
Some(600) => Ok(SpecifiedWeight600),
Some(700) => Ok(SpecifiedWeight700),
Some(800) => Ok(SpecifiedWeight800),
Some(900) => Ok(SpecifiedWeight900),
_ => Err(()),
},
_ => None
_ => Err(())
}
}
pub mod computed_value {
@ -890,7 +873,7 @@ pub mod longhands {
/// <length> | <percentage>
/// TODO: support <absolute-size> and <relative-size>
pub fn from_component_value(input: &ComponentValue, _base_url: &Url)
-> Option<SpecifiedValue> {
-> Result<SpecifiedValue, ()> {
specified::LengthOrPercentage::parse_non_negative(input).map(|value| {
match value {
specified::LP_Length(value) => value,
@ -927,30 +910,34 @@ pub mod longhands {
none
}
/// none | [ underline || overline || line-through || blink ]
pub fn parse(input: &[ComponentValue], _base_url: &Url) -> Option<SpecifiedValue> {
pub fn parse(input: &[ComponentValue], _base_url: &Url) -> Result<SpecifiedValue, ()> {
let mut result = SpecifiedValue {
underline: false, overline: false, line_through: false,
};
match one_component_value(input) {
Ok(&Ident(ref value))
if value.as_slice().eq_ignore_ascii_case("none") => return Ok(result),
_ => {}
}
let mut blink = false;
let mut empty = true;
for component_value in input.skip_whitespace() {
match get_ident_lower(component_value) {
None => return None,
Some(keyword) => match keyword.as_slice() {
"underline" => if result.underline { return None }
Err(()) => return Err(()),
Ok(keyword) => match keyword.as_slice() {
"underline" => if result.underline { return Err(()) }
else { empty = false; result.underline = true },
"overline" => if result.overline { return None }
"overline" => if result.overline { return Err(()) }
else { empty = false; result.overline = true },
"line-through" => if result.line_through { return None }
"line-through" => if result.line_through { return Err(()) }
else { empty = false; result.line_through = true },
"blink" => if blink { return None }
"blink" => if blink { return Err(()) }
else { empty = false; blink = true },
"none" => return if empty { Some(result) } else { None },
_ => return None,
_ => return Err(()),
}
}
}
if !empty { Some(result) } else { None }
if !empty { Ok(result) } else { Err(()) }
}
</%self:longhand>
@ -1070,7 +1057,7 @@ pub mod shorthands {
pub ${sub_property.ident}: Option<${sub_property.ident}::SpecifiedValue>,
% endfor
}
pub fn parse(input: &[ComponentValue], base_url: &Url) -> Option<Longhands> {
pub fn parse(input: &[ComponentValue], base_url: &Url) -> Result<Longhands, ()> {
${caller.body()}
}
}
@ -1080,7 +1067,7 @@ pub mod shorthands {
<%self:shorthand name="${name}" sub_properties="${
' '.join(sub_property_pattern % side
for side in ['top', 'right', 'bottom', 'left'])}">
let mut iter = input.skip_whitespace().map(|c| ${parser_function}(c, base_url));
let mut iter = input.skip_whitespace().map(|c| ${parser_function}(c, base_url).ok());
// zero or more than four values is invalid.
// one value sets them all
// two values set (top, bottom) and (left, right)
@ -1092,13 +1079,13 @@ pub mod shorthands {
let left = iter.next().unwrap_or(right);
if top.is_some() && right.is_some() && bottom.is_some() && left.is_some()
&& iter.next().is_none() {
Some(Longhands {
Ok(Longhands {
% for side in ["top", "right", "bottom", "left"]:
${to_rust_ident(sub_property_pattern % side)}: ${side},
% endfor
})
} else {
None
Err(())
}
</%self:shorthand>
</%def>
@ -1116,46 +1103,46 @@ pub mod shorthands {
for component_value in input.skip_whitespace() {
if color.is_none() {
match background_color::from_component_value(component_value, base_url) {
Some(v) => {
Ok(v) => {
color = Some(v);
any = true;
continue
},
None => ()
Err(()) => ()
}
}
if image.is_none() {
match background_image::from_component_value(component_value, base_url) {
Some(v) => {
Ok(v) => {
image = Some(v);
any = true;
continue
},
None => (),
Err(()) => (),
}
}
if repeat.is_none() {
match background_repeat::from_component_value(component_value, base_url) {
Some(v) => {
Ok(v) => {
repeat = Some(v);
any = true;
continue
},
None => ()
Err(()) => ()
}
}
if attachment.is_none() {
match background_attachment::from_component_value(component_value,
base_url) {
Some(v) => {
Ok(v) => {
attachment = Some(v);
any = true;
continue
},
None => ()
Err(()) => ()
}
}
@ -1165,17 +1152,17 @@ pub mod shorthands {
match background_position::parse_horizontal_and_vertical(
saved_component_value,
component_value) {
Some(v) => {
Ok(v) => {
position = Some(v);
any = true;
continue
},
None => (),
Err(()) => (),
}
}
// If we get here, parsing failed.
return None
return Err(())
}
None => {
// Save the component value.
@ -1185,7 +1172,7 @@ pub mod shorthands {
}
if any && last_component_value.is_none() {
Some(Longhands {
Ok(Longhands {
background_color: color,
background_image: image,
background_position: position,
@ -1193,14 +1180,14 @@ pub mod shorthands {
background_attachment: attachment,
})
} else {
None
Err(())
}
</%self:shorthand>
${four_sides_shorthand("margin", "margin-%s", "margin_top::from_component_value")}
${four_sides_shorthand("padding", "padding-%s", "padding_top::from_component_value")}
pub fn parse_color(value: &ComponentValue, _base_url: &Url) -> Option<specified::CSSColor> {
pub fn parse_color(value: &ComponentValue, _base_url: &Url) -> Result<specified::CSSColor, ()> {
specified::CSSColor::parse(value)
}
${four_sides_shorthand("border-color", "border-%s-color", "parse_color")}
@ -1209,9 +1196,9 @@ pub mod shorthands {
${four_sides_shorthand("border-width", "border-%s-width", "parse_border_width")}
pub fn parse_border(input: &[ComponentValue], base_url: &Url)
-> Option<(Option<specified::CSSColor>,
-> Result<(Option<specified::CSSColor>,
Option<border_top_style::SpecifiedValue>,
Option<specified::Length>)> {
Option<specified::Length>), ()> {
let mut color = None;
let mut style = None;
let mut width = None;
@ -1219,25 +1206,25 @@ pub mod shorthands {
for component_value in input.skip_whitespace() {
if color.is_none() {
match specified::CSSColor::parse(component_value) {
Some(c) => { color = Some(c); any = true; continue },
None => ()
Ok(c) => { color = Some(c); any = true; continue },
Err(()) => ()
}
}
if style.is_none() {
match border_top_style::from_component_value(component_value, base_url) {
Some(s) => { style = Some(s); any = true; continue },
None => ()
Ok(s) => { style = Some(s); any = true; continue },
Err(()) => ()
}
}
if width.is_none() {
match parse_border_width(component_value, base_url) {
Some(w) => { width = Some(w); any = true; continue },
None => ()
Ok(w) => { width = Some(w); any = true; continue },
Err(()) => ()
}
}
return None
return Err(())
}
if any { Some((color, style, width)) } else { None }
if any { Ok((color, style, width)) } else { Err(()) }
}
@ -1286,7 +1273,7 @@ pub mod shorthands {
// font-style, font-weight and font-variant.
// Leaves the values to None, 'normal' is the initial value for each of them.
match get_ident_lower(component_value) {
Some(ref ident) if ident.as_slice().eq_ignore_ascii_case("normal") => {
Ok(ref ident) if ident.as_slice().eq_ignore_ascii_case("normal") => {
nb_normals += 1;
continue;
}
@ -1294,25 +1281,25 @@ pub mod shorthands {
}
if style.is_none() {
match font_style::from_component_value(component_value, base_url) {
Some(s) => { style = Some(s); continue },
None => ()
Ok(s) => { style = Some(s); continue },
Err(()) => ()
}
}
if weight.is_none() {
match font_weight::from_component_value(component_value, base_url) {
Some(w) => { weight = Some(w); continue },
None => ()
Ok(w) => { weight = Some(w); continue },
Err(()) => ()
}
}
if variant.is_none() {
match font_variant::from_component_value(component_value, base_url) {
Some(v) => { variant = Some(v); continue },
None => ()
Ok(v) => { variant = Some(v); continue },
Err(()) => ()
}
}
match font_size::from_component_value(component_value, base_url) {
Some(s) => { size = Some(s); break },
None => return None
Ok(s) => { size = Some(s); break },
Err(()) => return Err(())
}
}
#[inline]
@ -1323,29 +1310,29 @@ pub mod shorthands {
}
}
if size.is_none() || (count(&style) + count(&weight) + count(&variant) + nb_normals) > 3 {
return None
return Err(())
}
let mut copied_iter = iter.clone();
match copied_iter.next() {
Some(&Delim('/')) => {
iter = copied_iter;
line_height = match iter.next() {
Some(v) => line_height::from_component_value(v, base_url),
_ => return None,
Some(v) => line_height::from_component_value(v, base_url).ok(),
_ => return Err(()),
};
if line_height.is_none() { return None }
if line_height.is_none() { return Err(()) }
}
_ => ()
}
let family = font_family::from_iter(iter);
if family.is_none() { return None }
Some(Longhands {
let family = try!(parse_comma_separated(
&mut BufferedIter::new(iter), font_family::parse_one_family));
Ok(Longhands {
font_style: style,
font_variant: variant,
font_weight: weight,
font_size: size,
line_height: line_height,
font_family: family
font_family: Some(family)
})
</%self:shorthand>
@ -1459,20 +1446,20 @@ pub fn parse_property_declaration_list<I: Iterator<Node>>(input: I, base_url: &U
}
#[deriving(Clone)]
pub enum CSSWideKeyword {
Initial,
Inherit,
InitialKeyword,
InheritKeyword,
UnsetKeyword,
}
impl CSSWideKeyword {
pub fn parse(input: &[ComponentValue]) -> Option<Option<CSSWideKeyword>> {
pub fn parse(input: &[ComponentValue]) -> Result<CSSWideKeyword, ()> {
one_component_value(input).and_then(get_ident_lower).and_then(|keyword| {
match keyword.as_slice() {
"initial" => Some(Some(Initial)),
"inherit" => Some(Some(Inherit)),
"unset" => Some(None),
_ => None
"initial" => Ok(InitialKeyword),
"inherit" => Ok(InheritKeyword),
"unset" => Ok(UnsetKeyword),
_ => Err(())
}
})
}
@ -1482,7 +1469,11 @@ impl CSSWideKeyword {
#[deriving(Clone)]
pub enum DeclaredValue<T> {
SpecifiedValue(T),
CSSWideKeyword(CSSWideKeyword),
Initial,
Inherit,
// There is no Unset variant here.
// The 'unset' keyword is represented as either Initial or Inherit,
// depending on whether the property is inherited.
}
#[deriving(Clone)]
@ -1521,12 +1512,12 @@ impl PropertyDeclaration {
return ValidOrIgnoredDeclaration
}
match longhands::${property.ident}::parse_declared(value, base_url) {
Some(value) => {
Ok(value) => {
seen.set_${property.ident}();
result_list.push(${property.camel_case}Declaration(value));
ValidOrIgnoredDeclaration
},
None => InvalidValue,
Err(()) => InvalidValue,
}
},
% else:
@ -1540,45 +1531,53 @@ impl PropertyDeclaration {
return ValidOrIgnoredDeclaration
}
match CSSWideKeyword::parse(value) {
Some(Some(keyword)) => {
Ok(InheritKeyword) => {
% for sub_property in shorthand.sub_properties:
if !seen.get_${sub_property.ident}() {
seen.set_${sub_property.ident}();
result_list.push(${sub_property.camel_case}Declaration(
CSSWideKeyword(keyword)));
result_list.push(
${sub_property.camel_case}Declaration(Inherit));
}
% endfor
ValidOrIgnoredDeclaration
},
Some(None) => {
Ok(InitialKeyword) => {
% for sub_property in shorthand.sub_properties:
if !seen.get_${sub_property.ident}() {
seen.set_${sub_property.ident}();
result_list.push(
${sub_property.camel_case}Declaration(Initial));
}
% endfor
ValidOrIgnoredDeclaration
},
Ok(UnsetKeyword) => {
% for sub_property in shorthand.sub_properties:
if !seen.get_${sub_property.ident}() {
seen.set_${sub_property.ident}();
result_list.push(${sub_property.camel_case}Declaration(
CSSWideKeyword(
${"Inherit" if sub_property.style_struct.inherited else "Initial"}
)
${"Inherit" if sub_property.style_struct.inherited else "Initial"}
));
}
% endfor
ValidOrIgnoredDeclaration
},
None => match shorthands::${shorthand.ident}::parse(value, base_url) {
Some(result) => {
Err(()) => match shorthands::${shorthand.ident}::parse(value, base_url) {
Ok(result) => {
% for sub_property in shorthand.sub_properties:
if !seen.get_${sub_property.ident}() {
seen.set_${sub_property.ident}();
result_list.push(${sub_property.camel_case}Declaration(
match result.${sub_property.ident} {
Some(value) => SpecifiedValue(value),
None => CSSWideKeyword(Initial),
None => Initial,
}
));
}
% endfor
ValidOrIgnoredDeclaration
},
None => InvalidValue,
Err(()) => InvalidValue,
}
}
},
@ -1788,7 +1787,7 @@ impl<T: Send + Share + Clone> ArcExperimental<T> for Arc<T> {
}
/// Fast path for the function below. Only computes new inherited styles.
fn cascade_with_cached_declarations(applicable_declarations: &[MatchedProperty],
fn cascade_with_cached_declarations(applicable_declarations: &[DeclarationBlock],
shareable: bool,
parent_style: &ComputedValues,
cached_style: &ComputedValues,
@ -1824,9 +1823,9 @@ fn cascade_with_cached_declarations(applicable_declarations: &[MatchedProperty],
(*specified_value).clone(),
context
),
CSSWideKeyword(Initial)
Initial
=> longhands::${property.ident}::get_initial_value(),
CSSWideKeyword(Inherit) => {
Inherit => {
// This is a bit slow, but this is rare so it shouldn't
// matter.
//
@ -1893,7 +1892,7 @@ fn cascade_with_cached_declarations(applicable_declarations: &[MatchedProperty],
/// this is ignored.
///
/// Returns the computed values and a boolean indicating whether the result is cacheable.
pub fn cascade(applicable_declarations: &[MatchedProperty],
pub fn cascade(applicable_declarations: &[DeclarationBlock],
shareable: bool,
parent_style: Option< &ComputedValues >,
cached_style: Option< &ComputedValues >)
@ -1934,8 +1933,8 @@ pub fn cascade(applicable_declarations: &[MatchedProperty],
($style_struct_getter: ident, $property: ident, $declared_value: expr) => {
match *$declared_value {
SpecifiedValue(specified_value) => specified_value,
CSSWideKeyword(Initial) => longhands::$property::get_initial_value(),
CSSWideKeyword(Inherit) => inherited_style.$style_struct_getter().$property.clone(),
Initial => longhands::$property::get_initial_value(),
Inherit => inherited_style.$style_struct_getter().$property.clone(),
}
};
)
@ -1950,8 +1949,8 @@ pub fn cascade(applicable_declarations: &[MatchedProperty],
context.font_size = match *value {
SpecifiedValue(specified_value) => computed::compute_Au_with_font_size(
specified_value, context.inherited_font_size),
CSSWideKeyword(Initial) => longhands::font_size::get_initial_value(),
CSSWideKeyword(Inherit) => context.inherited_font_size,
Initial => longhands::font_size::get_initial_value(),
Inherit => context.inherited_font_size,
}
}
ColorDeclaration(ref value) => {
@ -2031,9 +2030,9 @@ pub fn cascade(applicable_declarations: &[MatchedProperty],
(*specified_value).clone(),
&context
),
CSSWideKeyword(Initial)
Initial
=> longhands::${property.ident}::get_initial_value(),
CSSWideKeyword(Inherit) => {
Inherit => {
// This is a bit slow, but this is rare so it shouldn't
// matter.
//

View file

@ -3,10 +3,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use std::collections::hashmap::HashMap;
use std::ascii::StrAsciiExt;
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;
@ -16,7 +18,7 @@ use media_queries::{Device, Screen};
use node::{TElement, TNode};
use properties::{PropertyDeclaration, PropertyDeclarationBlock};
use selectors::*;
use stylesheets::{Stylesheet, iter_style_rules};
use stylesheets::{Stylesheet, iter_stylesheet_style_rules};
pub enum StylesheetOrigin {
UserAgentOrigin,
@ -50,7 +52,10 @@ struct SelectorMap {
// TODO: Tune the initial capacity of the HashMap
id_hash: HashMap<Atom, Vec<Rule>>,
class_hash: HashMap<Atom, Vec<Rule>>,
element_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.
@ -62,7 +67,8 @@ impl SelectorMap {
SelectorMap {
id_hash: HashMap::new(),
class_hash: HashMap::new(),
element_hash: HashMap::new(),
local_name_hash: HashMap::new(),
lower_local_name_hash: HashMap::new(),
universal_rules: vec!(),
empty: true,
}
@ -74,7 +80,7 @@ impl SelectorMap {
/// Sort the Rules at the end to maintain cascading order.
fn get_all_matching_rules<E:TElement,
N:TNode<E>,
V:VecLike<MatchedProperty>>(
V:VecLike<DeclarationBlock>>(
&self,
node: &N,
matching_rules_list: &mut V,
@ -111,13 +117,16 @@ impl SelectorMap {
None => {}
}
// HTML elements in HTML documents must be matched case-insensitively.
// TODO(pradeep): Case-sensitivity depends on the document type.
SelectorMap::get_matching_rules_from_hash_ignoring_case(node,
&self.element_hash,
element.get_local_name().as_slice(),
matching_rules_list,
shareable);
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(),
@ -125,12 +134,16 @@ impl SelectorMap {
shareable);
// Sort only the rules we just added.
sort::quicksort(matching_rules_list.vec_mut_slice_from(init_len));
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<MatchedProperty>>(
V:VecLike<DeclarationBlock>>(
node: &N,
hash: &HashMap<Atom, Vec<Rule>>,
key: &Atom,
@ -144,83 +157,45 @@ impl SelectorMap {
}
}
fn get_matching_rules_from_hash_ignoring_case<E:TElement,
N:TNode<E>,
V:VecLike<MatchedProperty>>(
node: &N,
hash: &HashMap<Atom, Vec<Rule>>,
key: &str,
matching_rules: &mut V,
shareable: &mut bool) {
// FIXME: Precache the lower case version as an atom.
match hash.find(&Atom::from_slice(key.to_ascii_lower().as_slice())) {
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<MatchedProperty>>(
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) {
// TODO(pradeep): Is the cloning inefficient?
matching_rules.vec_push(rule.property.clone());
matching_rules.vec_push(rule.declarations.clone());
}
}
}
/// Insert rule into the correct hash.
/// Order in which to try: id_hash, class_hash, element_hash, universal_rules.
/// 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) => {
match self.id_hash.find_mut(&id_name) {
Some(rules) => {
rules.push(rule);
return;
}
None => {}
}
self.id_hash.insert(id_name, vec!(rule));
self.id_hash.find_push(id_name, rule);
return;
}
None => {}
}
match SelectorMap::get_class_name(&rule) {
Some(class_name) => {
match self.class_hash.find_mut(&class_name) {
Some(rules) => {
rules.push(rule);
return;
}
None => {}
}
self.class_hash.insert(class_name, vec!(rule));
self.class_hash.find_push(class_name, rule);
return;
}
None => {}
}
match SelectorMap::get_element_name(&rule) {
Some(element_name) => {
match self.element_hash.find_mut(&element_name) {
Some(rules) => {
rules.push(rule);
return;
}
None => {}
}
self.element_hash.insert(element_name, vec!(rule));
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 => {}
@ -258,14 +233,12 @@ impl SelectorMap {
}
/// Retrieve the name if it is a type selector, or None otherwise.
fn get_element_name(rule: &Rule) -> Option<Atom> {
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 {
// HTML elements in HTML documents must be matched case-insensitively
// TODO: case-sensitivity depends on the document type
LocalNameSelector(ref name) => {
return Some(Atom::from_slice(name.as_slice().to_ascii_lower().as_slice()));
return Some(name.clone())
}
_ => {}
}
@ -284,12 +257,19 @@ pub struct Stylist {
impl Stylist {
#[inline]
pub fn new() -> Stylist {
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) {
@ -325,7 +305,7 @@ impl Stylist {
};
map.$priority.insert(Rule {
selector: selector.compound_selectors.clone(),
property: MatchedProperty {
declarations: DeclarationBlock {
specificity: selector.specificity,
declarations: $style_rule.declarations.$priority.clone(),
source_order: rules_source_order,
@ -337,7 +317,7 @@ impl Stylist {
);
let device = &Device { media_type: Screen }; // TODO, use Print when printing
iter_style_rules(stylesheet.rules.as_slice(), device, |style_rule| {
iter_stylesheet_style_rules(&stylesheet, device, |style_rule| {
append!(style_rule, normal);
append!(style_rule, important);
rules_source_order += 1;
@ -353,7 +333,7 @@ impl Stylist {
/// in `css::matching::PrivateMatchMethods::candidate_element_allows_for_style_sharing`.
pub fn push_applicable_declarations<E:TElement,
N:TNode<E>,
V:VecLike<MatchedProperty>>(
V:VecLike<DeclarationBlock>>(
&self,
element: &N,
style_attribute: Option<&PropertyDeclarationBlock>,
@ -382,7 +362,7 @@ impl Stylist {
// Step 2: Normal style attributes.
style_attribute.map(|sa| {
shareable = false;
applicable_declarations.vec_push(MatchedProperty::from_declarations(sa.normal.clone()))
applicable_declarations.vec_push(DeclarationBlock::from_declarations(sa.normal.clone()))
});
// Step 3: Author-supplied `!important` rules.
@ -393,7 +373,7 @@ impl Stylist {
// Step 4: `!important` style attributes.
style_attribute.map(|sa| {
shareable = false;
applicable_declarations.vec_push(MatchedProperty::from_declarations(sa.important.clone()))
applicable_declarations.vec_push(DeclarationBlock::from_declarations(sa.important.clone()))
});
// Step 5: User and UA `!important` rules.
@ -446,22 +426,22 @@ struct Rule {
// that it matches. Selector contains an owned vector (through
// CompoundSelector) and we want to avoid the allocation.
selector: Arc<CompoundSelector>,
property: MatchedProperty,
declarations: DeclarationBlock,
}
/// A property declaration together with its precedence among rules of equal specificity so that
/// we can sort them.
#[deriving(Clone)]
pub struct MatchedProperty {
pub struct DeclarationBlock {
pub declarations: Arc<Vec<PropertyDeclaration>>,
source_order: uint,
specificity: u32,
}
impl MatchedProperty {
impl DeclarationBlock {
#[inline]
pub fn from_declarations(declarations: Arc<Vec<PropertyDeclaration>>) -> MatchedProperty {
MatchedProperty {
pub fn from_declarations(declarations: Arc<Vec<PropertyDeclaration>>) -> DeclarationBlock {
DeclarationBlock {
declarations: declarations,
source_order: 0,
specificity: 0,
@ -469,34 +449,12 @@ impl MatchedProperty {
}
}
impl PartialEq for MatchedProperty {
#[inline]
fn eq(&self, other: &MatchedProperty) -> bool {
let this_rank = (self.specificity, self.source_order);
let other_rank = (other.specificity, other.source_order);
this_rank == other_rank
}
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))
}
impl Eq for MatchedProperty {}
impl PartialOrd for MatchedProperty {
#[inline]
fn partial_cmp(&self, other: &MatchedProperty) -> Option<Ordering> {
let this_rank = (self.specificity, self.source_order);
let other_rank = (other.specificity, other.source_order);
this_rank.partial_cmp(&other_rank)
}
}
impl Ord for MatchedProperty {
#[inline]
fn cmp(&self, other: &MatchedProperty) -> Ordering {
let this_rank = (self.specificity, self.source_order);
let other_rank = (other.specificity, other.source_order);
this_rank.cmp(&other_rank)
}
}
/// Determines whether the given element matches the given single or compound selector.
///
@ -504,7 +462,7 @@ impl Ord for MatchedProperty {
/// `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`.)
pub fn matches_compound_selector<E:TElement,
fn matches_compound_selector<E:TElement,
N:TNode<E>>(
selector: &CompoundSelector,
element: &N,
@ -645,11 +603,10 @@ fn matches_simple_selector<E:TElement,
shareable: &mut bool)
-> bool {
match *selector {
// TODO: case-sensitivity depends on the document type
// TODO: intern element names
LocalNameSelector(ref name) => {
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().as_slice().eq_ignore_ascii_case(name.as_slice())
element.get_local_name() == name
}
NamespaceSelector(ref namespace) => {
@ -934,11 +891,30 @@ fn matches_last_child<E:TElement,N:TNode<E>>(element: &N) -> bool {
}
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::{MatchedProperty, Rule, SelectorMap};
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.
@ -949,11 +925,11 @@ mod tests {
let namespaces = NamespaceMap::new();
css_selectors.iter().enumerate().map(|(i, selectors)| {
parse_selector_list(tokenize(*selectors).map(|(c, _)| c).collect(), &namespaces)
parse_selector_list(tokenize(*selectors).map(|(c, _)| c), &namespaces)
.unwrap().move_iter().map(|s| {
Rule {
selector: s.compound_selectors.clone(),
property: MatchedProperty {
declarations: DeclarationBlock {
specificity: s.specificity,
declarations: Arc::new(vec!()),
source_order: i,
@ -966,9 +942,10 @@ mod tests {
#[test]
fn test_rule_ordering_same_specificity(){
let rules_list = get_mock_rules(["a.intro", "img.sidebar"]);
let rule1 = rules_list[0][0].clone();
let rule2 = rules_list[1][0].clone();
assert!(rule1.property < rule2.property, "The rule that comes later should win.");
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]
@ -986,12 +963,18 @@ mod tests {
}
#[test]
fn test_get_element_name(){
fn test_get_local_name(){
let rules_list = get_mock_rules(["img.foo", "#top", "IMG", "ImG"]);
assert_eq!(SelectorMap::get_element_name(&rules_list[0][0]), Some(Atom::from_slice("img")));
assert_eq!(SelectorMap::get_element_name(&rules_list[1][0]), None);
assert_eq!(SelectorMap::get_element_name(&rules_list[2][0]), Some(Atom::from_slice("img")));
assert_eq!(SelectorMap::get_element_name(&rules_list[3][0]), Some(Atom::from_slice("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]
@ -999,9 +982,9 @@ mod tests {
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].property.source_order);
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].property.source_order);
assert_eq!(0, selector_map.class_hash.find(&Atom::from_slice("intro")).unwrap()[0].declarations.source_order);
assert!(selector_map.class_hash.find(&Atom::from_slice("foo")).is_none());
}
}

View file

@ -3,12 +3,11 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use std::{cmp, iter};
use std::ascii::StrAsciiExt;
use std::vec;
use std::ascii::{StrAsciiExt, OwnedStrAsciiExt};
use sync::Arc;
use cssparser::ast::*;
use cssparser::parse_nth;
use cssparser::{tokenize, parse_nth};
use servo_util::atom::Atom;
use servo_util::namespace::Namespace;
@ -59,7 +58,7 @@ pub enum Combinator {
pub enum SimpleSelector {
IDSelector(Atom),
ClassSelector(Atom),
LocalNameSelector(Atom),
LocalNameSelector(LocalNameSelector),
NamespaceSelector(Namespace),
// Attribute selectors
@ -93,6 +92,12 @@ pub enum SimpleSelector {
// ...
}
#[deriving(PartialEq, Clone)]
pub struct LocalNameSelector {
pub name: Atom,
pub lower_name: Atom,
}
#[deriving(PartialEq, Clone)]
pub struct AttrSelector {
pub name: String,
@ -107,21 +112,31 @@ pub enum NamespaceConstraint {
}
type Iter = iter::Peekable<ComponentValue, vec::MoveItems<ComponentValue>>;
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(input: Vec<ComponentValue>, namespaces: &NamespaceMap)
-> Option<Vec<Selector>> {
let iter = &mut input.move_iter().peekable();
let first = match parse_selector(iter, namespaces) {
None => return None,
Some(result) => result
};
let mut results = vec!(first);
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);
@ -130,29 +145,25 @@ pub fn parse_selector_list(input: Vec<ComponentValue>, namespaces: &NamespaceMap
Some(&Comma) => {
iter.next();
}
_ => return None,
}
match parse_selector(iter, namespaces) {
Some(selector) => results.push(selector),
None => return None,
_ => return Err(()),
}
results.push(try!(parse_selector(iter, namespaces)));
}
Some(results)
Ok(results)
}
type Iter<I> = iter::Peekable<ComponentValue, I>;
/// Build up a Selector.
/// selector : simple_selector_sequence [ combinator simple_selector_sequence ]* ;
///
/// None means invalid selector.
fn parse_selector(iter: &mut Iter, namespaces: &NamespaceMap)
-> Option<Selector> {
let (first, pseudo_element) = match parse_simple_selectors(iter, namespaces) {
None => return None,
Some(result) => result
};
/// `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 };
let mut pseudo_element = pseudo_element;
while pseudo_element.is_none() {
let any_whitespace = skip_whitespace(iter);
@ -164,21 +175,17 @@ fn parse_selector(iter: &mut Iter, namespaces: &NamespaceMap)
Some(&Delim('~')) => { iter.next(); LaterSibling },
Some(_) => {
if any_whitespace { Descendant }
else { return None }
else { return Err(()) }
}
};
match parse_simple_selectors(iter, namespaces) {
None => return None,
Some((simple_selectors, pseudo)) => {
compound = CompoundSelector {
simple_selectors: simple_selectors,
next: Some((box compound, combinator))
};
pseudo_element = pseudo;
}
}
let (simple_selectors, pseudo) = try!(parse_simple_selectors(iter, namespaces));
compound = CompoundSelector {
simple_selectors: simple_selectors,
next: Some((box compound, combinator))
};
pseudo_element = pseudo;
}
Some(Selector {
Ok(Selector {
specificity: compute_specificity(&compound, &pseudo_element),
compound_selectors: Arc::new(compound),
pseudo_element: pseudo_element,
@ -245,43 +252,39 @@ fn compute_specificity(mut selector: &CompoundSelector,
/// : [ type_selector | universal ] [ HASH | class | attrib | pseudo | negation ]*
/// | [ HASH | class | attrib | pseudo | negation ]+
///
/// None means invalid selector
fn parse_simple_selectors(iter: &mut Iter, namespaces: &NamespaceMap)
-> Option<(Vec<SimpleSelector>, Option<PseudoElement>)> {
/// `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 parse_type_selector(iter, namespaces) {
InvalidTypeSelector => return None,
NotATypeSelector => vec!(),
TypeSelector(s) => { empty = false; s }
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 parse_one_simple_selector(iter, namespaces, /* inside_negation = */ false) {
InvalidSimpleSelector => return None,
NotASimpleSelector => break,
SimpleSelectorResult(s) => { simple_selectors.push(s); empty = false },
PseudoElementResult(p) => { pseudo_element = Some(p); empty = false; break },
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 { None } // An empty selector is invalid
else { Some((simple_selectors, pseudo_element)) }
if empty { Err(()) } // An empty selector is invalid
else { Ok((simple_selectors, pseudo_element)) }
}
enum TypeSelectorParseResult {
InvalidTypeSelector,
NotATypeSelector,
TypeSelector(Vec<SimpleSelector>), // Length 0 (*|*), 1 (*|E or ns|*) or 2 (|E or ns|E)
}
fn parse_type_selector(iter: &mut Iter, namespaces: &NamespaceMap)
-> TypeSelectorParseResult {
/// * `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 parse_qualified_name(iter, /* in_attr_selector = */ false, namespaces) {
InvalidQualifiedName => InvalidTypeSelector,
NotAQualifiedName => NotATypeSelector,
QualifiedName(namespace, local_name) => {
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)),
@ -289,121 +292,115 @@ fn parse_type_selector(iter: &mut Iter, namespaces: &NamespaceMap)
}
match local_name {
Some(name) => {
let name_atom = Atom::from_slice(name.as_slice());
simple_selectors.push(LocalNameSelector(name_atom))
simple_selectors.push(LocalNameSelector(LocalNameSelector {
name: Atom::from_slice(name.as_slice()),
lower_name: Atom::from_slice(name.into_ascii_lower().as_slice())
}))
}
None => (),
}
TypeSelector(simple_selectors)
Ok(Some(simple_selectors))
}
}
}
enum SimpleSelectorParseResult {
InvalidSimpleSelector,
NotASimpleSelector,
SimpleSelectorResult(SimpleSelector),
PseudoElementResult(PseudoElement),
}
// Parse a simple selector other than a type selector
fn parse_one_simple_selector(iter: &mut Iter, namespaces: &NamespaceMap, inside_negation: bool)
-> SimpleSelectorParseResult {
/// 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)) => SimpleSelectorResult(IDSelector(Atom::from_slice(id.as_slice()))),
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)) => SimpleSelectorResult(ClassSelector(Atom::from_slice(class.as_slice()))),
_ => InvalidSimpleSelector,
Some(Ident(class)) => Ok(Some(SimpleSelectorResult(
ClassSelector(Atom::from_slice(class.as_slice()))))),
_ => Err(()),
}
}
Some(&SquareBracketBlock(_)) => match iter.next() {
Some(SquareBracketBlock(content))
=> match parse_attribute_selector(content, namespaces) {
None => InvalidSimpleSelector,
Some(simple_selector) => SimpleSelectorResult(simple_selector),
},
=> 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()) {
None => {
Err(()) => {
match name.as_slice().to_ascii_lower().as_slice() {
// Supported CSS 2.1 pseudo-elements only.
// ** Do not add to this list! **
"before" => PseudoElementResult(Before),
"after" => PseudoElementResult(After),
"before" => Ok(Some(PseudoElementResult(Before))),
"after" => Ok(Some(PseudoElementResult(After))),
// "first-line" => PseudoElementResult(FirstLine),
// "first-letter" => PseudoElementResult(FirstLetter),
_ => InvalidSimpleSelector
_ => Err(())
}
},
Some(result) => SimpleSelectorResult(result),
},
Some(Function(name, arguments)) => match parse_functional_pseudo_class(
name, arguments, namespaces, inside_negation) {
None => InvalidSimpleSelector,
Some(simple_selector) => SimpleSelectorResult(simple_selector),
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)) => match parse_pseudo_element(name) {
Some(pseudo_element) => PseudoElementResult(pseudo_element),
_ => InvalidSimpleSelector,
},
_ => InvalidSimpleSelector,
Some(Ident(name))
=> Ok(Some(PseudoElementResult(try!(parse_pseudo_element(name))))),
_ => Err(()),
}
}
_ => InvalidSimpleSelector,
_ => Err(()),
}
}
_ => NotASimpleSelector,
_ => Ok(None),
}
}
enum QualifiedNameParseResult {
InvalidQualifiedName,
NotAQualifiedName,
// Namespace URL, local name. None means '*'
QualifiedName(NamespaceConstraint, Option<String>)
}
fn parse_qualified_name(iter: &mut Iter, in_attr_selector: bool, namespaces: &NamespaceMap)
-> QualifiedNameParseResult {
#[inline]
fn default_namespace(namespaces: &NamespaceMap, local_name: Option<String>)
-> QualifiedNameParseResult {
QualifiedName(match namespaces.default {
/// * `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,
}, local_name)
}
};
Ok(Some((namespace, local_name)))
};
#[inline]
fn explicit_namespace(iter: &mut Iter, in_attr_selector: bool, namespace: NamespaceConstraint)
-> QualifiedNameParseResult {
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();
QualifiedName(namespace, None)
Ok(Some((namespace, None)))
},
Some(&Ident(_)) => {
let local_name = get_next_ident(iter);
QualifiedName(namespace, Some(local_name))
Ok(Some((namespace, Some(local_name))))
},
_ => InvalidQualifiedName,
_ => Err(()),
}
}
};
match iter.peek() {
Some(&Ident(_)) => {
@ -411,40 +408,39 @@ fn parse_qualified_name(iter: &mut Iter, in_attr_selector: bool, namespaces: &Na
match iter.peek() {
Some(&Delim('|')) => {
let namespace = match namespaces.prefix_map.find(&value) {
None => return InvalidQualifiedName, // Undeclared namespace prefix
None => return Err(()), // Undeclared namespace prefix
Some(ref ns) => (*ns).clone(),
};
explicit_namespace(iter, in_attr_selector, SpecificNamespace(namespace))
explicit_namespace(iter, SpecificNamespace(namespace))
},
_ if in_attr_selector => QualifiedName(
SpecificNamespace(namespace::Null), Some(value)),
_ => default_namespace(namespaces, Some(value)),
_ 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, in_attr_selector, AnyNamespace),
Some(&Delim('|')) => explicit_namespace(iter, AnyNamespace),
_ => {
if !in_attr_selector { default_namespace(namespaces, None) }
else { InvalidQualifiedName }
if !in_attr_selector { default_namespace(None) }
else { Err(()) }
},
}
},
Some(&Delim('|')) => explicit_namespace(
iter, in_attr_selector, SpecificNamespace(namespace::Null)),
_ => NotAQualifiedName,
Some(&Delim('|')) => explicit_namespace(iter, SpecificNamespace(namespace::Null)),
_ => Ok(None),
}
}
fn parse_attribute_selector(content: Vec<ComponentValue>, namespaces: &NamespaceMap)
-> Option<SimpleSelector> {
-> Result<SimpleSelector, ()> {
let iter = &mut content.move_iter().peekable();
let attr = match parse_qualified_name(iter, /* in_attr_selector = */ true, namespaces) {
InvalidQualifiedName | NotAQualifiedName => return None,
QualifiedName(_, None) => fail!("Implementation error, this should not happen."),
QualifiedName(namespace, Some(local_name)) => AttrSelector {
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,
@ -456,7 +452,7 @@ fn parse_attribute_selector(content: Vec<ComponentValue>, namespaces: &Namespace
skip_whitespace(iter);
match iter.next() {
Some(Ident(value)) | Some(String(value)) => value,
_ => return None,
_ => return Err(())
}
}};)
let result = match iter.next() {
@ -471,93 +467,92 @@ fn parse_attribute_selector(content: Vec<ComponentValue>, namespaces: &Namespace
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 None
_ => return Err(())
};
skip_whitespace(iter);
if iter.next().is_none() { Some(result) } else { None }
if iter.next().is_none() { Ok(result) } else { Err(()) }
}
fn parse_simple_pseudo_class(name: &str) -> Option<SimpleSelector> {
fn parse_simple_pseudo_class(name: &str) -> Result<SimpleSelector, ()> {
match name.to_ascii_lower().as_slice() {
"any-link" => Some(AnyLink),
"link" => Some(Link),
"visited" => Some(Visited),
"hover" => Some(Hover),
"disabled" => Some(Disabled),
"enabled" => Some(Enabled),
"first-child" => Some(FirstChild),
"last-child" => Some(LastChild),
"only-child" => Some(OnlyChild),
"root" => Some(Root),
"first-of-type" => Some(FirstOfType),
"last-of-type" => Some(LastOfType),
"only-of-type" => Some(OnlyOfType),
// "empty" => Some(Empty),
_ => None
"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)
-> Option<SimpleSelector> {
-> 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 { None } else { parse_negation(arguments, namespaces) },
_ => None
"not" => if inside_negation { Err(()) } else { parse_negation(arguments, namespaces) },
_ => Err(())
}
}
fn parse_pseudo_element(name: String) -> Option<PseudoElement> {
fn parse_pseudo_element(name: String) -> Result<PseudoElement, ()> {
match name.as_slice().to_ascii_lower().as_slice() {
// All supported pseudo-elements
"before" => Some(Before),
"after" => Some(After),
"before" => Ok(Before),
"after" => Ok(After),
// "first-line" => Some(FirstLine),
// "first-letter" => Some(FirstLetter),
_ => None
_ => Err(())
}
}
//fn parse_lang(arguments: vec!(ComponentValue)) -> Option<SimpleSelector> {
//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 { Some(Lang(value)) }
// else { Ok(Lang(value)) }
// },
// _ => None,
// _ => Err(()),
// }
//}
// Level 3: Parse ONE simple_selector
/// Level 3: Parse **one** simple_selector
fn parse_negation(arguments: Vec<ComponentValue>, namespaces: &NamespaceMap)
-> Option<SimpleSelector> {
-> Result<SimpleSelector, ()> {
let iter = &mut arguments.move_iter().peekable();
Some(Negation(match parse_type_selector(iter, namespaces) {
InvalidTypeSelector => return None,
TypeSelector(s) => s,
NotATypeSelector => {
match parse_one_simple_selector(iter, namespaces, /* inside_negation = */ true) {
SimpleSelectorResult(s) => vec!(s),
_ => return None
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(iter: &mut Iter) -> String {
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."),
@ -566,7 +561,7 @@ fn get_next_ident(iter: &mut Iter) -> String {
#[inline]
fn skip_whitespace(iter: &mut Iter) -> bool {
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 }
@ -585,14 +580,12 @@ mod tests {
use namespaces::NamespaceMap;
use super::*;
fn parse(input: &str) -> Option<Vec<Selector>> {
fn parse(input: &str) -> Result<Vec<Selector>, ()> {
parse_ns(input, &NamespaceMap::new())
}
fn parse_ns(input: &str, namespaces: &NamespaceMap) -> Option<Vec<Selector>> {
parse_selector_list(
cssparser::tokenize(input).map(|(v, _)| v).collect(),
namespaces)
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 {
@ -601,16 +594,18 @@ mod tests {
#[test]
fn test_parsing() {
assert!(parse("") == None)
assert!(parse("e") == Some(vec!(Selector{
assert!(parse("") == Err(()))
assert!(parse("EeÉ") == Ok(vec!(Selector {
compound_selectors: Arc::new(CompoundSelector {
simple_selectors: vec!(LocalNameSelector(Atom::from_slice("e"))),
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") == Some(vec!(Selector{
assert!(parse(".foo") == Ok(vec!(Selector {
compound_selectors: Arc::new(CompoundSelector {
simple_selectors: vec!(ClassSelector(Atom::from_slice("foo"))),
next: None,
@ -618,7 +613,7 @@ mod tests {
pseudo_element: None,
specificity: specificity(0, 1, 0),
})))
assert!(parse("#bar") == Some(vec!(Selector{
assert!(parse("#bar") == Ok(vec!(Selector {
compound_selectors: Arc::new(CompoundSelector {
simple_selectors: vec!(IDSelector(Atom::from_slice("bar"))),
next: None,
@ -626,9 +621,11 @@ mod tests {
pseudo_element: None,
specificity: specificity(1, 0, 0),
})))
assert!(parse("e.foo#bar") == Some(vec!(Selector{
assert!(parse("e.foo#bar") == Ok(vec!(Selector {
compound_selectors: Arc::new(CompoundSelector {
simple_selectors: vec!(LocalNameSelector(Atom::from_slice("e")),
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,
@ -636,11 +633,13 @@ mod tests {
pseudo_element: None,
specificity: specificity(1, 1, 1),
})))
assert!(parse("e.foo #bar") == Some(vec!(Selector{
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(Atom::from_slice("e")),
simple_selectors: vec!(LocalNameSelector(LocalNameSelector {
name: Atom::from_slice("e"),
lower_name: Atom::from_slice("e") }),
ClassSelector(Atom::from_slice("foo"))),
next: None,
}, Descendant)),
@ -651,7 +650,7 @@ mod tests {
// 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) == Some(vec!(Selector{
assert!(parse_ns("[Foo]", &namespaces) == Ok(vec!(Selector {
compound_selectors: Arc::new(CompoundSelector {
simple_selectors: vec!(AttrExists(AttrSelector {
name: "Foo".to_string(),
@ -666,7 +665,7 @@ mod tests {
// 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) == Some(vec!(Selector{
assert!(parse_ns("[Foo]", &namespaces) == Ok(vec!(Selector {
compound_selectors: Arc::new(CompoundSelector {
simple_selectors: vec!(AttrExists(AttrSelector {
name: "Foo".to_string(),
@ -679,11 +678,13 @@ mod tests {
specificity: specificity(0, 1, 0),
})))
// Default namespace does apply to type selectors
assert!(parse_ns("e", &namespaces) == Some(vec!(Selector{
assert!(parse_ns("e", &namespaces) == Ok(vec!(Selector {
compound_selectors: Arc::new(CompoundSelector {
simple_selectors: vec!(
NamespaceSelector(namespace::MathML),
LocalNameSelector(Atom::from_slice("e")),
LocalNameSelector(LocalNameSelector {
name: Atom::from_slice("e"),
lower_name: Atom::from_slice("e") }),
),
next: None,
}),
@ -691,7 +692,7 @@ mod tests {
specificity: specificity(0, 0, 1),
})))
// https://github.com/mozilla/servo/issues/1723
assert!(parse("::before") == Some(vec!(Selector{
assert!(parse("::before") == Ok(vec!(Selector {
compound_selectors: Arc::new(CompoundSelector {
simple_selectors: vec!(),
next: None,
@ -699,11 +700,13 @@ mod tests {
pseudo_element: Some(Before),
specificity: specificity(0, 0, 1),
})))
assert!(parse("div :after") == Some(vec!(Selector{
assert!(parse("div :after") == Ok(vec!(Selector {
compound_selectors: Arc::new(CompoundSelector {
simple_selectors: vec!(),
next: Some((box CompoundSelector {
simple_selectors: vec!(LocalNameSelector(Atom::from_slice("div"))),
simple_selectors: vec!(LocalNameSelector(LocalNameSelector {
name: Atom::from_slice("div"),
lower_name: Atom::from_slice("div") })),
next: None,
}, Descendant)),
}),

View file

@ -30,22 +30,17 @@ extern crate servo_util = "util";
// Public API
pub use stylesheets::{Stylesheet, CSSRule, StyleRule, CSSFontFaceRule};
pub use stylesheets::{Stylesheet, iter_font_face_rules};
pub use selector_matching::{Stylist, StylesheetOrigin, UserAgentOrigin, AuthorOrigin, UserOrigin};
pub use selector_matching::{MatchedProperty, matches_compound_selector};
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 errors::with_errors_silenced;
pub use node::{TElement, TNode};
pub use selectors::{PseudoElement, Before, After, AttrSelector, SpecificNamespace, AnyNamespace};
pub use selectors::{NamespaceConstraint, Selector, CompoundSelector, SimpleSelector, Combinator};
pub use selectors::{parse_selector_list};
pub use namespaces::NamespaceMap;
pub use media_queries::{MediaRule, MediaQueryList, MediaQuery, Device, MediaType, MediaQueryType};
pub use font_face::{FontFaceFormat, FontFaceRule, FontFaceSource,FontFaceSourceLine, TtfFormat};
pub use selectors::{PseudoElement, Before, After, SelectorList, parse_selector_list_from_str};
pub use selectors::{AttrSelector, NamespaceConstraint, SpecificNamespace, AnyNamespace};
mod stylesheets;
mod errors;

View file

@ -16,13 +16,13 @@ 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};
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)
pub rules: Vec<CSSRule>,
rules: Vec<CSSRule>,
}
@ -129,12 +129,12 @@ pub fn parse_style_rule(rule: QualifiedRule, parent_rules: &mut Vec<CSSRule>,
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, namespaces) {
Some(selectors) => parent_rules.push(CSSStyleRule(StyleRule{
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)
})),
None => log_css_error(location, format!(
Err(()) => log_css_error(location, format!(
"Invalid/unsupported selector: {}", serialized).as_slice()),
}
}
@ -164,3 +164,15 @@ pub fn iter_style_rules<'a>(rules: &[CSSRule], device: &media_queries::Device,
}
}
}
#[inline]
pub fn iter_stylesheet_style_rules(stylesheet: &Stylesheet, device: &media_queries::Device,
callback: |&StyleRule|) {
iter_style_rules(stylesheet.rules.as_slice(), device, callback)
}
#[inline]
pub fn iter_font_face_rules(stylesheet: &Stylesheet, callback: |family: &str, sources: &Url|) {
iter_font_face_rules_inner(stylesheet.rules.as_slice(), callback)
}

View file

@ -4,7 +4,7 @@
//! In-place sorting.
fn quicksort_helper<T:Ord + Eq + PartialOrd + PartialEq>(arr: &mut [T], left: int, right: int) {
fn quicksort_helper<T>(arr: &mut [T], left: int, right: int, compare: fn(&T, &T) -> Ordering) {
if right <= left {
return
}
@ -17,11 +17,11 @@ fn quicksort_helper<T:Ord + Eq + PartialOrd + PartialEq>(arr: &mut [T], left: in
let v: *mut T = &mut arr[right as uint];
loop {
i += 1;
while arr[i as uint] < (*v) {
while compare(&arr[i as uint], &*v) == Less {
i += 1
}
j -= 1;
while (*v) < arr[j as uint] {
while compare(&*v, &arr[j as uint]) == Less {
if j == left {
break
}
@ -31,11 +31,11 @@ fn quicksort_helper<T:Ord + Eq + PartialOrd + PartialEq>(arr: &mut [T], left: in
break
}
arr.swap(i as uint, j as uint);
if arr[i as uint] == (*v) {
if compare(&arr[i as uint], &*v) == Equal {
p += 1;
arr.swap(p as uint, i as uint)
}
if (*v) == arr[j as uint] {
if compare(&*v, &arr[j as uint]) == Equal {
q -= 1;
arr.swap(j as uint, q as uint)
}
@ -60,21 +60,21 @@ fn quicksort_helper<T:Ord + Eq + PartialOrd + PartialEq>(arr: &mut [T], left: in
assert!(k != 0);
}
quicksort_helper(arr, left, j);
quicksort_helper(arr, i, right);
quicksort_helper(arr, left, j, compare);
quicksort_helper(arr, i, right, compare);
}
/// An in-place quicksort.
///
/// The algorithm is from Sedgewick and Bentley, "Quicksort is Optimal":
/// http://www.cs.princeton.edu/~rs/talks/QuicksortIsOptimal.pdf
pub fn quicksort<T:Ord + Eq + PartialOrd + PartialEq>(arr: &mut [T]) {
pub fn quicksort_by<T>(arr: &mut [T], compare: fn(&T, &T) -> Ordering) {
if arr.len() <= 1 {
return
}
let len = arr.len();
quicksort_helper(arr, 0, (len - 1) as int);
quicksort_helper(arr, 0, (len - 1) as int, compare);
}
#[cfg(test)]
@ -90,7 +90,8 @@ pub mod test {
for _ in range(0u32, 50000u32) {
let len: uint = rng.gen();
let mut v: Vec<int> = rng.gen_iter::<int>().take((len % 32) + 1).collect();
sort::quicksort(v.as_mut_slice());
fn compare_ints(a: &int, b: &int) -> Ordering { a.cmp(b) }
sort::quicksort_by(v.as_mut_slice(), compare_ints);
for i in range(0, v.len() - 1) {
assert!(v.get(i) <= v.get(i + 1))
}

@ -1 +1 @@
Subproject commit 918bae60d4703e3db7f306b6c4a09de0670b9472
Subproject commit 48e517b40c8aad55b5f2b7072d092102b48797bc