style: Split stylesheets.rs

This file has become quite bloated lately. This commit deletes that file in
favor of a set of submodules.

The only noticeable change apart from code move, is converting deep_clone_foo
methods into a trait.

It also unifies logic related to different style rules in the same place.

There's some missing work, specially related to font-face and counter-style, but
I think this is worth landing in the meantime.
This commit is contained in:
Emilio Cobos Álvarez 2017-06-04 21:46:46 +02:00
parent 942fce3a0b
commit 58fd80e282
No known key found for this signature in database
GPG key ID: 056B727BB9C1027C
36 changed files with 2298 additions and 1995 deletions

View file

@ -0,0 +1,520 @@
/* 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/. */
//! Parsing of the stylesheet contents.
use {Namespace, Prefix};
use counter_style::{parse_counter_style_body, parse_counter_style_name};
use cssparser::{AtRuleParser, AtRuleType, Parser, QualifiedRuleParser, RuleListParser, SourceLocation};
use font_face::parse_font_face_block;
use media_queries::{parse_media_query_list, MediaList};
use parking_lot::RwLock;
use parser::{Parse, ParserContext, log_css_error};
use properties::parse_property_declaration_list;
use selector_parser::{SelectorImpl, SelectorParser};
use selectors::SelectorList;
use shared_lock::{Locked, SharedRwLock};
use std::sync::atomic::AtomicBool;
use str::starts_with_ignore_ascii_case;
use stylearc::Arc;
use stylesheets::{CssRule, CssRules, CssRuleType, Origin, StylesheetLoader};
use stylesheets::{DocumentRule, ImportRule, KeyframesRule, MediaRule, NamespaceRule, PageRule};
use stylesheets::{StyleRule, SupportsRule, ViewportRule};
use stylesheets::document_rule::DocumentCondition;
use stylesheets::keyframes_rule::parse_keyframe_list;
use stylesheets::loader::NoOpLoader;
use stylesheets::stylesheet::{Namespaces, Stylesheet};
use stylesheets::supports_rule::SupportsCondition;
use values::CustomIdent;
use values::KeyframesName;
use values::specified::url::SpecifiedUrl;
/// The parser for the top-level rules in a stylesheet.
pub struct TopLevelRuleParser<'a> {
/// The origin of the stylesheet we're parsing.
pub stylesheet_origin: Origin,
/// A reference to the lock we need to use to create rules.
pub shared_lock: &'a SharedRwLock,
/// A reference to a stylesheet loader if applicable, for `@import` rules.
pub loader: Option<&'a StylesheetLoader>,
/// The parser context. This initially won't contain any namespaces, but
/// will be populated after parsing namespace rules, if any.
pub context: ParserContext<'a>,
/// The current state of the parser.
pub state: State,
/// The namespace map we use for parsing. Needs to start as `Some()`, and
/// will be taken out after parsing namespace rules, and that reference will
/// be moved to `ParserContext`.
pub namespaces: Option<&'a mut Namespaces>,
}
impl<'b> TopLevelRuleParser<'b> {
fn nested<'a: 'b>(&'a self) -> NestedRuleParser<'a, 'b> {
NestedRuleParser {
stylesheet_origin: self.stylesheet_origin,
shared_lock: self.shared_lock,
context: &self.context,
}
}
/// Returns the associated parser context with this rule parser.
pub fn context(&self) -> &ParserContext {
&self.context
}
/// Returns the current state of the parser.
pub fn state(&self) -> State {
self.state
}
}
/// The current state of the parser.
#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
pub enum State {
/// We haven't started parsing rules.
Start = 1,
/// We're parsing `@import` rules.
Imports = 2,
/// We're parsing `@namespace` rules.
Namespaces = 3,
/// We're parsing the main body of the stylesheet.
Body = 4,
/// We've found an invalid state (as, a namespace rule after style rules),
/// and the rest of the stylesheet should be ignored.
Invalid = 5,
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
/// Vendor prefix.
pub enum VendorPrefix {
/// -moz prefix.
Moz,
/// -webkit prefix.
WebKit,
}
/// A rule prelude for a given at-rule.
pub enum AtRulePrelude {
/// A @font-face rule prelude.
FontFace(SourceLocation),
/// A @counter-style rule prelude, with its counter style name.
CounterStyle(CustomIdent),
/// A @media rule prelude, with its media queries.
Media(Arc<Locked<MediaList>>, SourceLocation),
/// An @supports rule, with its conditional
Supports(SupportsCondition, SourceLocation),
/// A @viewport rule prelude.
Viewport,
/// A @keyframes rule, with its animation name and vendor prefix if exists.
Keyframes(KeyframesName, Option<VendorPrefix>, SourceLocation),
/// A @page rule prelude.
Page(SourceLocation),
/// A @document rule, with its conditional.
Document(DocumentCondition, SourceLocation),
}
#[cfg(feature = "gecko")]
fn register_namespace(ns: &Namespace) -> Result<i32, ()> {
use gecko_bindings::bindings;
let id = unsafe { bindings::Gecko_RegisterNamespace(ns.0.as_ptr()) };
if id == -1 {
Err(())
} else {
Ok(id)
}
}
#[cfg(feature = "servo")]
fn register_namespace(_: &Namespace) -> Result<(), ()> {
Ok(()) // servo doesn't use namespace ids
}
impl<'a> AtRuleParser for TopLevelRuleParser<'a> {
type Prelude = AtRulePrelude;
type AtRule = CssRule;
fn parse_prelude(
&mut self,
name: &str,
input: &mut Parser
) -> Result<AtRuleType<AtRulePrelude, CssRule>, ()> {
let location = get_location_with_offset(input.current_source_location(),
self.context.line_number_offset);
match_ignore_ascii_case! { name,
"import" => {
if self.state > State::Imports {
self.state = State::Invalid;
return Err(()) // "@import must be before any rule but @charset"
}
self.state = State::Imports;
let url_string = input.expect_url_or_string()?;
let specified_url = SpecifiedUrl::parse_from_string(url_string, &self.context)?;
let media = parse_media_query_list(&self.context, input);
let media = Arc::new(self.shared_lock.wrap(media));
let noop_loader = NoOpLoader;
let loader = if !specified_url.is_invalid() {
self.loader.expect("Expected a stylesheet loader for @import")
} else {
&noop_loader
};
let mut specified_url = Some(specified_url);
let arc = loader.request_stylesheet(media, &mut |media| {
ImportRule {
url: specified_url.take().unwrap(),
stylesheet: Arc::new(Stylesheet {
rules: CssRules::new(Vec::new(), self.shared_lock),
media: media,
shared_lock: self.shared_lock.clone(),
origin: self.context.stylesheet_origin,
url_data: self.context.url_data.clone(),
namespaces: RwLock::new(Namespaces::default()),
dirty_on_viewport_size_change: AtomicBool::new(false),
disabled: AtomicBool::new(false),
quirks_mode: self.context.quirks_mode,
}),
source_location: location,
}
}, &mut |import_rule| {
Arc::new(self.shared_lock.wrap(import_rule))
});
return Ok(AtRuleType::WithoutBlock(CssRule::Import(arc)))
},
"namespace" => {
if self.state > State::Namespaces {
self.state = State::Invalid;
return Err(()) // "@namespace must be before any rule but @charset and @import"
}
self.state = State::Namespaces;
let prefix_result = input.try(|input| input.expect_ident());
let url = Namespace::from(try!(input.expect_url_or_string()));
let id = register_namespace(&url)?;
let mut namespaces = self.namespaces.as_mut().unwrap();
let opt_prefix = if let Ok(prefix) = prefix_result {
let prefix = Prefix::from(prefix);
namespaces
.prefixes
.insert(prefix.clone(), (url.clone(), id));
Some(prefix)
} else {
namespaces.default = Some((url.clone(), id));
None
};
return Ok(AtRuleType::WithoutBlock(CssRule::Namespace(Arc::new(
self.shared_lock.wrap(NamespaceRule {
prefix: opt_prefix,
url: url,
source_location: location,
})
))))
},
// @charset is removed by rust-cssparser if its the first rule in the stylesheet
// anything left is invalid.
"charset" => return Err(()), // (insert appropriate error message)
_ => {}
}
// Don't allow starting with an invalid state
if self.state > State::Body {
self.state = State::Invalid;
return Err(());
}
self.state = State::Body;
// "Freeze" the namespace map (no more namespace rules can be parsed
// after this point), and stick it in the context.
if self.namespaces.is_some() {
let namespaces = &*self.namespaces.take().unwrap();
self.context.namespaces = Some(namespaces);
}
AtRuleParser::parse_prelude(&mut self.nested(), name, input)
}
#[inline]
fn parse_block(&mut self, prelude: AtRulePrelude, input: &mut Parser) -> Result<CssRule, ()> {
AtRuleParser::parse_block(&mut self.nested(), prelude, input)
}
}
impl<'a> QualifiedRuleParser for TopLevelRuleParser<'a> {
type Prelude = SelectorList<SelectorImpl>;
type QualifiedRule = CssRule;
#[inline]
fn parse_prelude(&mut self, input: &mut Parser) -> Result<SelectorList<SelectorImpl>, ()> {
self.state = State::Body;
// "Freeze" the namespace map (no more namespace rules can be parsed
// after this point), and stick it in the context.
if self.namespaces.is_some() {
let namespaces = &*self.namespaces.take().unwrap();
self.context.namespaces = Some(namespaces);
}
QualifiedRuleParser::parse_prelude(&mut self.nested(), input)
}
#[inline]
fn parse_block(
&mut self,
prelude: SelectorList<SelectorImpl>,
input: &mut Parser
) -> Result<CssRule, ()> {
QualifiedRuleParser::parse_block(&mut self.nested(), prelude, input)
}
}
#[derive(Clone)] // shallow, relatively cheap .clone
struct NestedRuleParser<'a, 'b: 'a> {
stylesheet_origin: Origin,
shared_lock: &'a SharedRwLock,
context: &'a ParserContext<'b>,
}
impl<'a, 'b> NestedRuleParser<'a, 'b> {
fn parse_nested_rules(
&mut self,
input: &mut Parser,
rule_type: CssRuleType
) -> Arc<Locked<CssRules>> {
let context = ParserContext::new_with_rule_type(self.context, Some(rule_type));
let nested_parser = NestedRuleParser {
stylesheet_origin: self.stylesheet_origin,
shared_lock: self.shared_lock,
context: &context,
};
let mut iter = RuleListParser::new_for_nested_rule(input, nested_parser);
let mut rules = Vec::new();
while let Some(result) = iter.next() {
match result {
Ok(rule) => rules.push(rule),
Err(range) => {
let pos = range.start;
let message = format!("Unsupported rule: '{}'", iter.input.slice(range));
log_css_error(iter.input, pos, &*message, self.context);
}
}
}
CssRules::new(rules, self.shared_lock)
}
}
#[cfg(feature = "servo")]
fn is_viewport_enabled() -> bool {
use servo_config::prefs::PREFS;
PREFS.get("layout.viewport.enabled").as_boolean().unwrap_or(false)
}
#[cfg(not(feature = "servo"))]
fn is_viewport_enabled() -> bool {
false // Gecko doesn't support @viewport.
}
impl<'a, 'b> AtRuleParser for NestedRuleParser<'a, 'b> {
type Prelude = AtRulePrelude;
type AtRule = CssRule;
fn parse_prelude(
&mut self,
name: &str,
input: &mut Parser
) -> Result<AtRuleType<AtRulePrelude, CssRule>, ()> {
let location =
get_location_with_offset(
input.current_source_location(),
self.context.line_number_offset
);
match_ignore_ascii_case! { name,
"media" => {
let media_queries = parse_media_query_list(self.context, input);
let arc = Arc::new(self.shared_lock.wrap(media_queries));
Ok(AtRuleType::WithBlock(AtRulePrelude::Media(arc, location)))
},
"supports" => {
let cond = SupportsCondition::parse(input)?;
Ok(AtRuleType::WithBlock(AtRulePrelude::Supports(cond, location)))
},
"font-face" => {
Ok(AtRuleType::WithBlock(AtRulePrelude::FontFace(location)))
},
"counter-style" => {
if !cfg!(feature = "gecko") {
// Support for this rule is not fully implemented in Servo yet.
return Err(())
}
let name = parse_counter_style_name(input)?;
// ASCII-case-insensitive matches for "decimal" are already
// lower-cased by `parse_counter_style_name`, so we can use ==
// here.
if name.0 == atom!("decimal") {
return Err(())
}
Ok(AtRuleType::WithBlock(AtRulePrelude::CounterStyle(name)))
},
"viewport" => {
if is_viewport_enabled() {
Ok(AtRuleType::WithBlock(AtRulePrelude::Viewport))
} else {
Err(())
}
},
"keyframes" | "-webkit-keyframes" | "-moz-keyframes" => {
let prefix = if starts_with_ignore_ascii_case(name, "-webkit-") {
Some(VendorPrefix::WebKit)
} else if starts_with_ignore_ascii_case(name, "-moz-") {
Some(VendorPrefix::Moz)
} else {
None
};
if cfg!(feature = "servo") &&
prefix.as_ref().map_or(false, |p| matches!(*p, VendorPrefix::Moz)) {
// Servo should not support @-moz-keyframes.
return Err(())
}
let name = KeyframesName::parse(self.context, input)?;
Ok(AtRuleType::WithBlock(AtRulePrelude::Keyframes(name, prefix, location)))
},
"page" => {
if cfg!(feature = "gecko") {
Ok(AtRuleType::WithBlock(AtRulePrelude::Page(location)))
} else {
Err(())
}
},
"-moz-document" => {
if cfg!(feature = "gecko") {
let cond = DocumentCondition::parse(self.context, input)?;
Ok(AtRuleType::WithBlock(AtRulePrelude::Document(cond, location)))
} else {
Err(())
}
},
_ => Err(())
}
}
fn parse_block(
&mut self,
prelude: AtRulePrelude,
input: &mut Parser
) -> Result<CssRule, ()> {
match prelude {
AtRulePrelude::FontFace(location) => {
let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::FontFace));
Ok(CssRule::FontFace(Arc::new(self.shared_lock.wrap(
parse_font_face_block(&context, input, location).into()))))
}
AtRulePrelude::CounterStyle(name) => {
let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::CounterStyle));
Ok(CssRule::CounterStyle(Arc::new(self.shared_lock.wrap(
parse_counter_style_body(name, &context, input)?.into()))))
}
AtRulePrelude::Media(media_queries, location) => {
Ok(CssRule::Media(Arc::new(self.shared_lock.wrap(MediaRule {
media_queries: media_queries,
rules: self.parse_nested_rules(input, CssRuleType::Media),
source_location: location,
}))))
}
AtRulePrelude::Supports(cond, location) => {
let enabled = cond.eval(self.context);
Ok(CssRule::Supports(Arc::new(self.shared_lock.wrap(SupportsRule {
condition: cond,
rules: self.parse_nested_rules(input, CssRuleType::Supports),
enabled: enabled,
source_location: location,
}))))
}
AtRulePrelude::Viewport => {
let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Viewport));
Ok(CssRule::Viewport(Arc::new(self.shared_lock.wrap(
try!(ViewportRule::parse(&context, input))))))
}
AtRulePrelude::Keyframes(name, prefix, location) => {
let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Keyframes));
Ok(CssRule::Keyframes(Arc::new(self.shared_lock.wrap(KeyframesRule {
name: name,
keyframes: parse_keyframe_list(&context, input, self.shared_lock),
vendor_prefix: prefix,
source_location: location,
}))))
}
AtRulePrelude::Page(location) => {
let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Page));
let declarations = parse_property_declaration_list(&context, input);
Ok(CssRule::Page(Arc::new(self.shared_lock.wrap(PageRule {
block: Arc::new(self.shared_lock.wrap(declarations)),
source_location: location,
}))))
}
AtRulePrelude::Document(cond, location) => {
if cfg!(feature = "gecko") {
Ok(CssRule::Document(Arc::new(self.shared_lock.wrap(DocumentRule {
condition: cond,
rules: self.parse_nested_rules(input, CssRuleType::Document),
source_location: location,
}))))
} else {
unreachable!()
}
}
}
}
}
impl<'a, 'b> QualifiedRuleParser for NestedRuleParser<'a, 'b> {
type Prelude = SelectorList<SelectorImpl>;
type QualifiedRule = CssRule;
fn parse_prelude(&mut self, input: &mut Parser) -> Result<SelectorList<SelectorImpl>, ()> {
let selector_parser = SelectorParser {
stylesheet_origin: self.stylesheet_origin,
namespaces: self.context.namespaces.unwrap(),
};
SelectorList::parse(&selector_parser, input)
}
fn parse_block(
&mut self,
prelude: SelectorList<SelectorImpl>,
input: &mut Parser
) -> Result<CssRule, ()> {
let location = get_location_with_offset(input.current_source_location(),
self.context.line_number_offset);
let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Style));
let declarations = parse_property_declaration_list(&context, input);
Ok(CssRule::Style(Arc::new(self.shared_lock.wrap(StyleRule {
selectors: prelude,
block: Arc::new(self.shared_lock.wrap(declarations)),
source_location: location,
}))))
}
}
/// Calculates the location of a rule's source given an offset.
fn get_location_with_offset(
location: SourceLocation,
offset: u64
) -> SourceLocation {
SourceLocation {
line: location.line + offset as usize - 1,
column: location.column,
}
}