gecko-dev/servo/components/style/stylesheets.rs
Simon Sapin ec40d2a379 servo: Merge #13134 - Add lots of Arc’s in style, and prepare for using DOMRefCell (from servo:archery); r=emilio
<!-- Please describe your changes on the following line: -->

`DOMRefCell` usage is not there year because of thread-safety questions, but I have this much already that I’d like to land before it bitrots.

r? @emilio

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: -->
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
- [ ] These changes fix #__ (github issue number if applicable).

<!-- Either: -->
- [ ] There are tests for these changes OR
- [x] These changes do not require new tests because refactor

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->

Source-Repo: https://github.com/servo/servo
Source-Revision: bbfe38e35f3f6a51009c0411647c89c8b5616357

--HG--
rename : servo/components/script/dom/bindings/cell.rs => servo/components/style/domrefcell.rs
2016-08-31 16:30:56 -05:00

569 lines
19 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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/. */
//! Style sheets and their CSS rules.
use cssparser::{AtRuleParser, Parser, QualifiedRuleParser, decode_stylesheet_bytes};
use cssparser::{AtRuleType, RuleListParser, Token};
use encoding::EncodingRef;
use error_reporting::ParseErrorReporter;
use font_face::{FontFaceRule, parse_font_face_block};
use keyframes::{Keyframe, parse_keyframe_list};
use media_queries::{Device, MediaQueryList, parse_media_query_list};
use parser::{ParserContext, ParserContextExtraData, log_css_error};
use properties::{PropertyDeclarationBlock, parse_property_declaration_list};
use selector_impl::TheSelectorImpl;
use selectors::parser::{Selector, parse_selector_list};
use smallvec::SmallVec;
use std::cell::Cell;
use std::iter::Iterator;
use std::slice;
use std::sync::Arc;
use string_cache::{Atom, Namespace};
use url::Url;
use viewport::ViewportRule;
/// Each style rule has an origin, which determines where it enters the cascade.
///
/// http://dev.w3.org/csswg/css-cascade/#cascading-origins
#[derive(Clone, PartialEq, Eq, Copy, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum Origin {
/// http://dev.w3.org/csswg/css-cascade/#cascade-origin-ua
UserAgent,
/// http://dev.w3.org/csswg/css-cascade/#cascade-origin-author
Author,
/// http://dev.w3.org/csswg/css-cascade/#cascade-origin-user
User,
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct Stylesheet {
/// List of rules in the order they were found (important for
/// cascading order)
pub rules: Vec<CSSRule>,
/// List of media associated with the Stylesheet, if any.
pub media: Option<MediaQueryList>,
pub origin: Origin,
pub dirty_on_viewport_size_change: bool,
}
/// This structure holds the user-agent and user stylesheets.
pub struct UserAgentStylesheets {
pub user_or_user_agent_stylesheets: Vec<Stylesheet>,
pub quirks_mode_stylesheet: Stylesheet,
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum CSSRule {
// No Charset here, CSSCharsetRule has been removed from CSSOM
// https://drafts.csswg.org/cssom/#changes-from-5-december-2013
Namespace(Arc<NamespaceRule>),
Style(Arc<StyleRule>),
Media(Arc<MediaRule>),
FontFace(Arc<FontFaceRule>),
Viewport(Arc<ViewportRule>),
Keyframes(Arc<KeyframesRule>),
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct NamespaceRule {
/// `None` for the default Namespace
pub prefix: Option<Atom>,
pub url: Namespace,
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct KeyframesRule {
pub name: Atom,
pub keyframes: Vec<Arc<Keyframe>>,
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct MediaRule {
pub media_queries: Arc<MediaQueryList>,
pub rules: Vec<CSSRule>,
}
impl MediaRule {
#[inline]
pub fn evaluate(&self, device: &Device) -> bool {
self.media_queries.evaluate(device)
}
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct StyleRule {
pub selectors: Vec<Selector<TheSelectorImpl>>,
pub declarations: Arc<PropertyDeclarationBlock>,
}
impl Stylesheet {
pub fn from_bytes_iter<I: Iterator<Item=Vec<u8>>>(
input: I, base_url: Url, protocol_encoding_label: Option<&str>,
environment_encoding: Option<EncodingRef>, origin: Origin,
error_reporter: Box<ParseErrorReporter + Send>,
extra_data: ParserContextExtraData) -> Stylesheet {
let mut bytes = vec![];
// TODO: incremental decoding and tokenization/parsing
for chunk in input {
bytes.extend_from_slice(&chunk)
}
Stylesheet::from_bytes(&bytes, base_url, protocol_encoding_label,
environment_encoding, origin, error_reporter,
extra_data)
}
pub fn from_bytes(bytes: &[u8],
base_url: Url,
protocol_encoding_label: Option<&str>,
environment_encoding: Option<EncodingRef>,
origin: Origin, error_reporter: Box<ParseErrorReporter + Send>,
extra_data: ParserContextExtraData)
-> Stylesheet {
// TODO: bytes.as_slice could be bytes.container_as_bytes()
let (string, _) = decode_stylesheet_bytes(
bytes, protocol_encoding_label, environment_encoding);
Stylesheet::from_str(&string, base_url, origin, error_reporter, extra_data)
}
pub fn from_str(css: &str, base_url: Url, origin: Origin,
error_reporter: Box<ParseErrorReporter + Send>,
extra_data: ParserContextExtraData) -> Stylesheet {
let rule_parser = TopLevelRuleParser {
context: ParserContext::new_with_extra_data(origin, &base_url, error_reporter.clone(),
extra_data),
state: Cell::new(State::Start),
};
let mut input = Parser::new(css);
input.look_for_viewport_percentages();
let mut rules = vec![];
{
let mut iter = RuleListParser::new_for_stylesheet(&mut input, rule_parser);
while let Some(result) = iter.next() {
match result {
Ok(rule) => {
if let CSSRule::Namespace(ref rule) = rule {
if let Some(ref prefix) = rule.prefix {
iter.parser.context.selector_context.namespace_prefixes.insert(
prefix.clone(), rule.url.clone());
} else {
iter.parser.context.selector_context.default_namespace =
Some(rule.url.clone());
}
}
rules.push(rule);
}
Err(range) => {
let pos = range.start;
let message = format!("Invalid rule: '{}'", iter.input.slice(range));
let context = ParserContext::new(origin, &base_url, error_reporter.clone());
log_css_error(iter.input, pos, &*message, &context);
}
}
}
}
Stylesheet {
origin: origin,
rules: rules,
media: None,
dirty_on_viewport_size_change:
input.seen_viewport_percentages(),
}
}
/// Set the MediaQueryList associated with the style-sheet.
pub fn set_media(&mut self, media: Option<MediaQueryList>) {
self.media = media;
}
/// Returns whether the style-sheet applies for the current device depending
/// on the associated MediaQueryList.
///
/// Always true if no associated MediaQueryList exists.
pub fn is_effective_for_device(&self, device: &Device) -> bool {
self.media.as_ref().map_or(true, |ref media| media.evaluate(device))
}
/// Return an iterator over all the rules within the style-sheet.
#[inline]
pub fn rules(&self) -> Rules {
Rules::new(self.rules.iter(), None)
}
/// Return an iterator over the effective rules within the style-sheet, as
/// according to the supplied `Device`.
///
/// If a condition does not hold, its associated conditional group rule and
/// nested rules will be skipped. Use `rules` if all rules need to be
/// examined.
#[inline]
pub fn effective_rules<'a>(&'a self, device: &'a Device) -> Rules<'a> {
Rules::new(self.rules.iter(), Some(device))
}
}
/// `CSSRule` iterator.
///
/// The iteration order is pre-order. Specifically, this implies that a
/// conditional group rule will come before its nested rules.
pub struct Rules<'a> {
// 2 because normal case is likely to be just one level of nesting (@media)
stack: SmallVec<[slice::Iter<'a, CSSRule>; 2]>,
device: Option<&'a Device>
}
impl<'a> Rules<'a> {
fn new(iter: slice::Iter<'a, CSSRule>, device: Option<&'a Device>) -> Rules<'a> {
let mut stack: SmallVec<[slice::Iter<'a, CSSRule>; 2]> = SmallVec::new();
stack.push(iter);
Rules { stack: stack, device: device }
}
}
impl<'a> Iterator for Rules<'a> {
type Item = &'a CSSRule;
fn next(&mut self) -> Option<&'a CSSRule> {
while !self.stack.is_empty() {
let top = self.stack.len() - 1;
while let Some(rule) = self.stack[top].next() {
// handle conditional group rules
if let &CSSRule::Media(ref rule) = rule {
if let Some(device) = self.device {
if rule.evaluate(device) {
self.stack.push(rule.rules.iter());
} else {
continue
}
} else {
self.stack.push(rule.rules.iter());
}
}
return Some(rule)
}
self.stack.pop();
}
None
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
// TODO: track total number of rules in style-sheet for upper bound?
(0, None)
}
}
pub mod rule_filter {
//! Specific `CSSRule` variant iterators.
use std::marker::PhantomData;
use super::super::font_face::FontFaceRule;
use super::super::viewport::ViewportRule;
use super::{CSSRule, KeyframesRule, MediaRule, StyleRule};
macro_rules! rule_filter {
($variant:ident -> $value:ty) => {
/// An iterator that only yields rules that are of the synonymous `CSSRule` variant.
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
pub struct $variant<'a, I> {
iter: I,
_lifetime: PhantomData<&'a ()>
}
impl<'a, I> $variant<'a, I>
where I: Iterator<Item=&'a CSSRule> {
#[inline]
pub fn new(iter: I) -> $variant<'a, I> {
$variant {
iter: iter,
_lifetime: PhantomData
}
}
}
impl<'a, I> Iterator for $variant<'a, I>
where I: Iterator<Item=&'a CSSRule> {
type Item = &'a $value;
fn next(&mut self) -> Option<&'a $value> {
while let Some(rule) = self.iter.next() {
match *rule {
CSSRule::$variant(ref value) => return Some(value),
_ => continue
}
}
None
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(0, self.iter.size_hint().1)
}
}
}
}
rule_filter!(Media -> MediaRule);
rule_filter!(Style -> StyleRule);
rule_filter!(FontFace -> FontFaceRule);
rule_filter!(Viewport -> ViewportRule);
rule_filter!(Keyframes -> KeyframesRule);
}
/// Extension methods for `CSSRule` iterators.
pub trait CSSRuleIteratorExt<'a>: Iterator<Item=&'a CSSRule> + Sized {
/// Yield only @font-face rules.
fn font_face(self) -> rule_filter::FontFace<'a, Self>;
/// Yield only @media rules.
fn media(self) -> rule_filter::Media<'a, Self>;
/// Yield only style rules.
fn style(self) -> rule_filter::Style<'a, Self>;
/// Yield only @viewport rules.
fn viewport(self) -> rule_filter::Viewport<'a, Self>;
/// Yield only @keyframes rules.
fn keyframes(self) -> rule_filter::Keyframes<'a, Self>;
}
impl<'a, I> CSSRuleIteratorExt<'a> for I where I: Iterator<Item=&'a CSSRule> {
#[inline]
fn font_face(self) -> rule_filter::FontFace<'a, I> {
rule_filter::FontFace::new(self)
}
#[inline]
fn media(self) -> rule_filter::Media<'a, I> {
rule_filter::Media::new(self)
}
#[inline]
fn style(self) -> rule_filter::Style<'a, I> {
rule_filter::Style::new(self)
}
#[inline]
fn viewport(self) -> rule_filter::Viewport<'a, I> {
rule_filter::Viewport::new(self)
}
#[inline]
fn keyframes(self) -> rule_filter::Keyframes<'a, I> {
rule_filter::Keyframes::new(self)
}
}
fn parse_nested_rules(context: &ParserContext, input: &mut Parser) -> Vec<CSSRule> {
let mut iter = RuleListParser::new_for_nested_rule(input,
NestedRuleParser { context: context });
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, &context);
}
}
}
rules
}
struct TopLevelRuleParser<'a> {
context: ParserContext<'a>,
state: Cell<State>,
}
#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
enum State {
Start = 1,
Imports = 2,
Namespaces = 3,
Body = 4,
}
enum AtRulePrelude {
/// A @font-face rule prelude.
FontFace,
/// A @media rule prelude, with its media queries.
Media(Arc<MediaQueryList>),
/// A @viewport rule prelude.
Viewport,
/// A @keyframes rule, with its animation name.
Keyframes(Atom),
}
impl<'a> AtRuleParser for TopLevelRuleParser<'a> {
type Prelude = AtRulePrelude;
type AtRule = CSSRule;
fn parse_prelude(&self, name: &str, input: &mut Parser)
-> Result<AtRuleType<AtRulePrelude, CSSRule>, ()> {
match_ignore_ascii_case! { name,
"import" => {
if self.state.get() <= State::Imports {
self.state.set(State::Imports);
// TODO: support @import
return Err(()) // "@import is not supported yet"
} else {
return Err(()) // "@import must be before any rule but @charset"
}
},
"namespace" => {
if self.state.get() <= State::Namespaces {
self.state.set(State::Namespaces);
let prefix = input.try(|input| input.expect_ident()).ok().map(|p| p.into());
let url = Namespace(Atom::from(try!(input.expect_url_or_string())));
return Ok(AtRuleType::WithoutBlock(CSSRule::Namespace(Arc::new(NamespaceRule {
prefix: prefix,
url: url,
}))))
} else {
return Err(()) // "@namespace must be before any rule but @charset and @import"
}
},
// @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)
_ => {}
}
self.state.set(State::Body);
AtRuleParser::parse_prelude(&NestedRuleParser { context: &self.context }, name, input)
}
#[inline]
fn parse_block(&self, prelude: AtRulePrelude, input: &mut Parser) -> Result<CSSRule, ()> {
AtRuleParser::parse_block(&NestedRuleParser { context: &self.context }, prelude, input)
}
}
impl<'a> QualifiedRuleParser for TopLevelRuleParser<'a> {
type Prelude = Vec<Selector<TheSelectorImpl>>;
type QualifiedRule = CSSRule;
#[inline]
fn parse_prelude(&self, input: &mut Parser) -> Result<Vec<Selector<TheSelectorImpl>>, ()> {
self.state.set(State::Body);
QualifiedRuleParser::parse_prelude(&NestedRuleParser { context: &self.context }, input)
}
#[inline]
fn parse_block(&self, prelude: Vec<Selector<TheSelectorImpl>>, input: &mut Parser) -> Result<CSSRule, ()> {
QualifiedRuleParser::parse_block(&NestedRuleParser { context: &self.context },
prelude, input)
}
}
struct NestedRuleParser<'a, 'b: 'a> {
context: &'a ParserContext<'b>,
}
impl<'a, 'b> AtRuleParser for NestedRuleParser<'a, 'b> {
type Prelude = AtRulePrelude;
type AtRule = CSSRule;
fn parse_prelude(&self, name: &str, input: &mut Parser)
-> Result<AtRuleType<AtRulePrelude, CSSRule>, ()> {
match_ignore_ascii_case! { name,
"media" => {
let media_queries = parse_media_query_list(input);
Ok(AtRuleType::WithBlock(AtRulePrelude::Media(Arc::new(media_queries))))
},
"font-face" => {
Ok(AtRuleType::WithBlock(AtRulePrelude::FontFace))
},
"viewport" => {
if ::util::prefs::PREFS.get("layout.viewport.enabled").as_boolean().unwrap_or(false) {
Ok(AtRuleType::WithBlock(AtRulePrelude::Viewport))
} else {
Err(())
}
},
"keyframes" => {
let name = match input.next() {
Ok(Token::Ident(ref value)) if value != "none" => Atom::from(&**value),
Ok(Token::QuotedString(value)) => Atom::from(&*value),
_ => return Err(())
};
Ok(AtRuleType::WithBlock(AtRulePrelude::Keyframes(Atom::from(name))))
},
_ => Err(())
}
}
fn parse_block(&self, prelude: AtRulePrelude, input: &mut Parser) -> Result<CSSRule, ()> {
match prelude {
AtRulePrelude::FontFace => {
Ok(CSSRule::FontFace(Arc::new(try!(parse_font_face_block(self.context, input)))))
}
AtRulePrelude::Media(media_queries) => {
Ok(CSSRule::Media(Arc::new(MediaRule {
media_queries: media_queries,
rules: parse_nested_rules(self.context, input),
})))
}
AtRulePrelude::Viewport => {
Ok(CSSRule::Viewport(Arc::new(try!(ViewportRule::parse(input, self.context)))))
}
AtRulePrelude::Keyframes(name) => {
Ok(CSSRule::Keyframes(Arc::new(KeyframesRule {
name: name,
keyframes: parse_keyframe_list(&self.context, input),
})))
}
}
}
}
impl<'a, 'b> QualifiedRuleParser for NestedRuleParser<'a, 'b> {
type Prelude = Vec<Selector<TheSelectorImpl>>;
type QualifiedRule = CSSRule;
fn parse_prelude(&self, input: &mut Parser) -> Result<Vec<Selector<TheSelectorImpl>>, ()> {
parse_selector_list(&self.context.selector_context, input)
}
fn parse_block(&self, prelude: Vec<Selector<TheSelectorImpl>>, input: &mut Parser) -> Result<CSSRule, ()> {
Ok(CSSRule::Style(Arc::new(StyleRule {
selectors: prelude,
declarations: Arc::new(parse_property_declaration_list(self.context, input))
})))
}
}