mirror of
https://github.com/servo/servo.git
synced 2025-08-04 05:00:08 +01:00
This enum type used to contain the result of parsing one CSS source declaration (`name: value;`) and expanding shorthands. Enum types are as big as the biggest of their variant (plus discriminant), which was quite big because some shorthands expand to many longhand properties. This type was returned through many functions and methods, wrapped and rewrapped in `Result` with different error types. This presumably caused significant `memmove` traffic. Instead, we now allocate an `ArrayVec` on the stack and pass `&mut` references to it for various functions to push into it. This type is also very big, but we never move it. We still use an intermediate data structure because we sometimes decide after shorthand expansion that a declaration is invalid after all and that we’re gonna drop it. Only later do we push to a `PropertyDeclarationBlock`, with an entire `ArrayVec` or nothing. In future work we can try to avoid a large stack-allocated array, and instead writing directly to the heap allocation of the `Vec` inside `PropertyDeclarationBlock`. However this is tricky: we need to preserve this "all or nothing" aspect of parsing one source declaration, and at the same time we want to make it as little error-prone as possible for the various call sites. `PropertyDeclarationBlock` curently does property deduplication incrementally: as each `PropertyDeclaration` is pushed, we check if an existing declaration of the same property exists and if so overwrite it. To get rid of the stack allocated array we’d need to somehow deduplicate separately after pushing multiple `PropertyDeclaration`.
221 lines
7.9 KiB
Rust
221 lines
7.9 KiB
Rust
/* 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/. */
|
|
|
|
//! [@supports rules](https://drafts.csswg.org/css-conditional-3/#at-supports)
|
|
|
|
use cssparser::{parse_important, Parser, Token};
|
|
use parser::ParserContext;
|
|
use properties::{PropertyId, PropertyDeclaration, SourcePropertyDeclaration};
|
|
use std::fmt;
|
|
use style_traits::ToCss;
|
|
use stylesheets::CssRuleType;
|
|
|
|
#[derive(Debug)]
|
|
/// An @supports condition
|
|
///
|
|
/// https://drafts.csswg.org/css-conditional-3/#at-supports
|
|
pub enum SupportsCondition {
|
|
/// `not (condition)`
|
|
Not(Box<SupportsCondition>),
|
|
/// `(condition)`
|
|
Parenthesized(Box<SupportsCondition>),
|
|
/// `(condition) and (condition) and (condition) ..`
|
|
And(Vec<SupportsCondition>),
|
|
/// `(condition) or (condition) or (condition) ..`
|
|
Or(Vec<SupportsCondition>),
|
|
/// `property-ident: value` (value can be any tokens)
|
|
Declaration(Declaration),
|
|
/// `(any tokens)` or `func(any tokens)`
|
|
FutureSyntax(String),
|
|
}
|
|
|
|
impl SupportsCondition {
|
|
/// Parse a condition
|
|
///
|
|
/// https://drafts.csswg.org/css-conditional/#supports_condition
|
|
pub fn parse(input: &mut Parser) -> Result<SupportsCondition, ()> {
|
|
if let Ok(_) = input.try(|i| i.expect_ident_matching("not")) {
|
|
let inner = SupportsCondition::parse_in_parens(input)?;
|
|
return Ok(SupportsCondition::Not(Box::new(inner)));
|
|
}
|
|
|
|
let in_parens = SupportsCondition::parse_in_parens(input)?;
|
|
|
|
let (keyword, wrapper) = match input.next() {
|
|
Err(()) => {
|
|
// End of input
|
|
return Ok(in_parens)
|
|
}
|
|
Ok(Token::Ident(ident)) => {
|
|
match_ignore_ascii_case! { &ident,
|
|
"and" => ("and", SupportsCondition::And as fn(_) -> _),
|
|
"or" => ("or", SupportsCondition::Or as fn(_) -> _),
|
|
_ => return Err(())
|
|
}
|
|
}
|
|
_ => return Err(())
|
|
};
|
|
|
|
let mut conditions = Vec::with_capacity(2);
|
|
conditions.push(in_parens);
|
|
loop {
|
|
conditions.push(SupportsCondition::parse_in_parens(input)?);
|
|
if input.try(|input| input.expect_ident_matching(keyword)).is_err() {
|
|
// Did not find the expected keyword.
|
|
// If we found some other token,
|
|
// it will be rejected by `Parser::parse_entirely` somewhere up the stack.
|
|
return Ok(wrapper(conditions))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// https://drafts.csswg.org/css-conditional-3/#supports_condition_in_parens
|
|
fn parse_in_parens(input: &mut Parser) -> Result<SupportsCondition, ()> {
|
|
// Whitespace is normally taken care of in `Parser::next`,
|
|
// but we want to not include it in `pos` for the SupportsCondition::FutureSyntax cases.
|
|
while input.try(Parser::expect_whitespace).is_ok() {}
|
|
let pos = input.position();
|
|
match input.next()? {
|
|
Token::ParenthesisBlock => {
|
|
input.parse_nested_block(|input| {
|
|
// `input.try()` not needed here since the alternative uses `consume_all()`.
|
|
parse_condition_or_declaration(input).or_else(|()| {
|
|
consume_all(input);
|
|
Ok(SupportsCondition::FutureSyntax(input.slice_from(pos).to_owned()))
|
|
})
|
|
})
|
|
}
|
|
Token::Function(_) => {
|
|
input.parse_nested_block(|i| Ok(consume_all(i))).unwrap();
|
|
Ok(SupportsCondition::FutureSyntax(input.slice_from(pos).to_owned()))
|
|
}
|
|
_ => Err(())
|
|
}
|
|
}
|
|
|
|
/// Evaluate a supports condition
|
|
pub fn eval(&self, cx: &ParserContext) -> bool {
|
|
match *self {
|
|
SupportsCondition::Not(ref cond) => !cond.eval(cx),
|
|
SupportsCondition::Parenthesized(ref cond) => cond.eval(cx),
|
|
SupportsCondition::And(ref vec) => vec.iter().all(|c| c.eval(cx)),
|
|
SupportsCondition::Or(ref vec) => vec.iter().any(|c| c.eval(cx)),
|
|
SupportsCondition::Declaration(ref decl) => decl.eval(cx),
|
|
SupportsCondition::FutureSyntax(_) => false
|
|
}
|
|
}
|
|
}
|
|
|
|
/// supports_condition | declaration
|
|
/// https://drafts.csswg.org/css-conditional/#dom-css-supports-conditiontext-conditiontext
|
|
pub fn parse_condition_or_declaration(input: &mut Parser) -> Result<SupportsCondition, ()> {
|
|
if let Ok(condition) = input.try(SupportsCondition::parse) {
|
|
Ok(SupportsCondition::Parenthesized(Box::new(condition)))
|
|
} else {
|
|
Declaration::parse(input).map(SupportsCondition::Declaration)
|
|
}
|
|
}
|
|
|
|
impl ToCss for SupportsCondition {
|
|
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
|
|
where W: fmt::Write {
|
|
match *self {
|
|
SupportsCondition::Not(ref cond) => {
|
|
dest.write_str("not ")?;
|
|
cond.to_css(dest)
|
|
}
|
|
SupportsCondition::Parenthesized(ref cond) => {
|
|
dest.write_str("(")?;
|
|
cond.to_css(dest)?;
|
|
dest.write_str(")")
|
|
}
|
|
SupportsCondition::And(ref vec) => {
|
|
let mut first = true;
|
|
for cond in vec {
|
|
if !first {
|
|
dest.write_str(" and ")?;
|
|
}
|
|
first = false;
|
|
cond.to_css(dest)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
SupportsCondition::Or(ref vec) => {
|
|
let mut first = true;
|
|
for cond in vec {
|
|
if !first {
|
|
dest.write_str(" or ")?;
|
|
}
|
|
first = false;
|
|
cond.to_css(dest)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
SupportsCondition::Declaration(ref decl) => {
|
|
dest.write_str("(")?;
|
|
decl.to_css(dest)?;
|
|
dest.write_str(")")
|
|
}
|
|
SupportsCondition::FutureSyntax(ref s) => dest.write_str(&s),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
/// A possibly-invalid property declaration
|
|
pub struct Declaration {
|
|
/// The property name
|
|
pub prop: String,
|
|
/// The property value
|
|
pub val: String,
|
|
}
|
|
|
|
impl ToCss for Declaration {
|
|
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
|
|
where W: fmt::Write {
|
|
dest.write_str(&self.prop)?;
|
|
dest.write_str(":")?;
|
|
// no space, the `val` already contains any possible spaces
|
|
dest.write_str(&self.val)
|
|
}
|
|
}
|
|
|
|
/// Slurps up input till exhausted, return string from source position
|
|
fn parse_anything(input: &mut Parser) -> String {
|
|
let pos = input.position();
|
|
consume_all(input);
|
|
input.slice_from(pos).to_owned()
|
|
}
|
|
|
|
/// consume input till done
|
|
fn consume_all(input: &mut Parser) {
|
|
while let Ok(_) = input.next() {}
|
|
}
|
|
|
|
impl Declaration {
|
|
/// Parse a declaration
|
|
pub fn parse(input: &mut Parser) -> Result<Declaration, ()> {
|
|
let prop = input.expect_ident()?.into_owned();
|
|
input.expect_colon()?;
|
|
let val = parse_anything(input);
|
|
Ok(Declaration { prop: prop, val: val })
|
|
}
|
|
|
|
/// Determine if a declaration parses
|
|
///
|
|
/// https://drafts.csswg.org/css-conditional-3/#support-definition
|
|
pub fn eval(&self, cx: &ParserContext) -> bool {
|
|
let id = if let Ok(id) = PropertyId::parse((&*self.prop).into()) {
|
|
id
|
|
} else {
|
|
return false
|
|
};
|
|
let mut input = Parser::new(&self.val);
|
|
let context = ParserContext::new_with_rule_type(cx, Some(CssRuleType::Style));
|
|
let mut declarations = SourcePropertyDeclaration::new();
|
|
let res = PropertyDeclaration::parse_into(&mut declarations, id, &context, &mut input);
|
|
let _ = input.try(parse_important);
|
|
res.is_ok() && input.is_exhausted()
|
|
}
|
|
}
|