summaryrefslogtreecommitdiffstats
path: root/servo/components/style/stylesheets/supports_rule.rs
diff options
context:
space:
mode:
Diffstat (limited to 'servo/components/style/stylesheets/supports_rule.rs')
-rw-r--r--servo/components/style/stylesheets/supports_rule.rs433
1 files changed, 433 insertions, 0 deletions
diff --git a/servo/components/style/stylesheets/supports_rule.rs b/servo/components/style/stylesheets/supports_rule.rs
new file mode 100644
index 0000000000..14b9afa18e
--- /dev/null
+++ b/servo/components/style/stylesheets/supports_rule.rs
@@ -0,0 +1,433 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! [@supports rules](https://drafts.csswg.org/css-conditional-3/#at-supports)
+
+use crate::font_face::{FontFaceSourceFormatKeyword, FontFaceSourceTechFlags};
+use crate::parser::ParserContext;
+use crate::properties::{PropertyDeclaration, PropertyId, SourcePropertyDeclaration};
+use crate::selector_parser::{SelectorImpl, SelectorParser};
+use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked};
+use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
+use crate::str::CssStringWriter;
+use crate::stylesheets::{CssRuleType, CssRules, Namespaces};
+use cssparser::parse_important;
+use cssparser::{Delimiter, Parser, SourceLocation, Token};
+use cssparser::{ParseError as CssParseError, ParserInput};
+#[cfg(feature = "gecko")]
+use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
+use selectors::parser::{Selector, SelectorParseErrorKind};
+use servo_arc::Arc;
+use std::ffi::{CStr, CString};
+use std::fmt::{self, Write};
+use std::str;
+use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
+
+/// An [`@supports`][supports] rule.
+///
+/// [supports]: https://drafts.csswg.org/css-conditional-3/#at-supports
+#[derive(Debug, ToShmem)]
+pub struct SupportsRule {
+ /// The parsed condition
+ pub condition: SupportsCondition,
+ /// Child rules
+ pub rules: Arc<Locked<CssRules>>,
+ /// The result of evaluating the condition
+ pub enabled: bool,
+ /// The line and column of the rule's source code.
+ pub source_location: SourceLocation,
+}
+
+impl SupportsRule {
+ /// Measure heap usage.
+ #[cfg(feature = "gecko")]
+ pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
+ // Measurement of other fields may be added later.
+ self.rules.unconditional_shallow_size_of(ops) +
+ self.rules.read_with(guard).size_of(guard, ops)
+ }
+}
+
+impl ToCssWithGuard for SupportsRule {
+ fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
+ dest.write_str("@supports ")?;
+ self.condition.to_css(&mut CssWriter::new(dest))?;
+ self.rules.read_with(guard).to_css_block(guard, dest)
+ }
+}
+
+impl DeepCloneWithLock for SupportsRule {
+ fn deep_clone_with_lock(
+ &self,
+ lock: &SharedRwLock,
+ guard: &SharedRwLockReadGuard,
+ params: &DeepCloneParams,
+ ) -> Self {
+ let rules = self.rules.read_with(guard);
+ SupportsRule {
+ condition: self.condition.clone(),
+ rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard, params))),
+ enabled: self.enabled,
+ source_location: self.source_location.clone(),
+ }
+ }
+}
+
+/// An @supports condition
+///
+/// <https://drafts.csswg.org/css-conditional-3/#at-supports>
+#[derive(Clone, Debug, ToShmem)]
+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),
+ /// A `selector()` function.
+ Selector(RawSelector),
+ /// `-moz-bool-pref("pref-name")`
+ /// Since we need to pass it through FFI to get the pref value,
+ /// we store it as CString directly.
+ MozBoolPref(CString),
+ /// `font-format(<font-format>)`
+ FontFormat(FontFaceSourceFormatKeyword),
+ /// `font-tech(<font-tech>)`
+ FontTech(FontFaceSourceTechFlags),
+ /// `(any tokens)` or `func(any tokens)`
+ FutureSyntax(String),
+}
+
+impl SupportsCondition {
+ /// Parse a condition
+ ///
+ /// <https://drafts.csswg.org/css-conditional/#supports_condition>
+ pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
+ if input.try_parse(|i| i.expect_ident_matching("not")).is_ok() {
+ let inner = SupportsCondition::parse_in_parens(input)?;
+ return Ok(SupportsCondition::Not(Box::new(inner)));
+ }
+
+ let in_parens = SupportsCondition::parse_in_parens(input)?;
+
+ let location = input.current_source_location();
+ let (keyword, wrapper) = match input.next() {
+ // End of input
+ Err(..) => return Ok(in_parens),
+ Ok(&Token::Ident(ref ident)) => {
+ match_ignore_ascii_case! { &ident,
+ "and" => ("and", SupportsCondition::And as fn(_) -> _),
+ "or" => ("or", SupportsCondition::Or as fn(_) -> _),
+ _ => return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())))
+ }
+ },
+ Ok(t) => return Err(location.new_unexpected_token_error(t.clone())),
+ };
+
+ let mut conditions = Vec::with_capacity(2);
+ conditions.push(in_parens);
+ loop {
+ conditions.push(SupportsCondition::parse_in_parens(input)?);
+ if input
+ .try_parse(|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));
+ }
+ }
+ }
+
+ /// Parses a functional supports condition.
+ fn parse_functional<'i, 't>(
+ function: &str,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<Self, ParseError<'i>> {
+ match_ignore_ascii_case! { function,
+ // Although this is an internal syntax, it is not necessary
+ // to check parsing context as far as we accept any
+ // unexpected token as future syntax, and evaluate it to
+ // false when not in chrome / ua sheet.
+ // See https://drafts.csswg.org/css-conditional-3/#general_enclosed
+ "-moz-bool-pref" => {
+ let name = {
+ let name = input.expect_string()?;
+ CString::new(name.as_bytes())
+ }.map_err(|_| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))?;
+ Ok(SupportsCondition::MozBoolPref(name))
+ },
+ "selector" => {
+ let pos = input.position();
+ consume_any_value(input)?;
+ Ok(SupportsCondition::Selector(RawSelector(
+ input.slice_from(pos).to_owned()
+ )))
+ },
+ "font-format" if static_prefs::pref!("layout.css.font-tech.enabled") => {
+ let kw = FontFaceSourceFormatKeyword::parse(input)?;
+ Ok(SupportsCondition::FontFormat(kw))
+ },
+ "font-tech" if static_prefs::pref!("layout.css.font-tech.enabled") => {
+ let flag = FontFaceSourceTechFlags::parse_one(input)?;
+ Ok(SupportsCondition::FontTech(flag))
+ },
+ _ => {
+ Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
+ },
+ }
+ }
+
+ /// <https://drafts.csswg.org/css-conditional-3/#supports_condition_in_parens>
+ fn parse_in_parens<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
+ // 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_parse(Parser::expect_whitespace).is_ok() {}
+ let pos = input.position();
+ let location = input.current_source_location();
+ match *input.next()? {
+ Token::ParenthesisBlock => {
+ let nested = input
+ .try_parse(|input| input.parse_nested_block(parse_condition_or_declaration));
+ if nested.is_ok() {
+ return nested;
+ }
+ },
+ Token::Function(ref ident) => {
+ let ident = ident.clone();
+ let nested = input.try_parse(|input| {
+ input.parse_nested_block(|input| {
+ SupportsCondition::parse_functional(&ident, input)
+ })
+ });
+ if nested.is_ok() {
+ return nested;
+ }
+ },
+ ref t => return Err(location.new_unexpected_token_error(t.clone())),
+ }
+ input.parse_nested_block(consume_any_value)?;
+ Ok(SupportsCondition::FutureSyntax(
+ input.slice_from(pos).to_owned(),
+ ))
+ }
+
+ /// Evaluate a supports condition
+ pub fn eval(&self, cx: &ParserContext, namespaces: &Namespaces) -> bool {
+ match *self {
+ SupportsCondition::Not(ref cond) => !cond.eval(cx, namespaces),
+ SupportsCondition::Parenthesized(ref cond) => cond.eval(cx, namespaces),
+ SupportsCondition::And(ref vec) => vec.iter().all(|c| c.eval(cx, namespaces)),
+ SupportsCondition::Or(ref vec) => vec.iter().any(|c| c.eval(cx, namespaces)),
+ SupportsCondition::Declaration(ref decl) => decl.eval(cx),
+ SupportsCondition::MozBoolPref(ref name) => eval_moz_bool_pref(name, cx),
+ SupportsCondition::Selector(ref selector) => selector.eval(cx, namespaces),
+ SupportsCondition::FontFormat(ref format) => eval_font_format(format),
+ SupportsCondition::FontTech(ref tech) => eval_font_tech(tech),
+ SupportsCondition::FutureSyntax(_) => false,
+ }
+ }
+}
+
+#[cfg(feature = "gecko")]
+fn eval_moz_bool_pref(name: &CStr, cx: &ParserContext) -> bool {
+ use crate::gecko_bindings::bindings;
+ if !cx.in_ua_or_chrome_sheet() {
+ return false;
+ }
+ unsafe { bindings::Gecko_GetBoolPrefValue(name.as_ptr()) }
+}
+
+fn eval_font_format(kw: &FontFaceSourceFormatKeyword) -> bool {
+ use crate::gecko_bindings::bindings;
+ unsafe { bindings::Gecko_IsFontFormatSupported(*kw) }
+}
+
+fn eval_font_tech(flag: &FontFaceSourceTechFlags) -> bool {
+ use crate::gecko_bindings::bindings;
+ unsafe { bindings::Gecko_IsFontTechSupported(*flag) }
+}
+
+#[cfg(feature = "servo")]
+fn eval_moz_bool_pref(_: &CStr, _: &ParserContext) -> bool {
+ false
+}
+
+/// supports_condition | declaration
+/// <https://drafts.csswg.org/css-conditional/#dom-css-supports-conditiontext-conditiontext>
+pub fn parse_condition_or_declaration<'i, 't>(
+ input: &mut Parser<'i, 't>,
+) -> Result<SupportsCondition, ParseError<'i>> {
+ if let Ok(condition) = input.try_parse(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 CssWriter<W>) -> fmt::Result
+ where
+ W: 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::Selector(ref selector) => {
+ dest.write_str("selector(")?;
+ selector.to_css(dest)?;
+ dest.write_str(")")
+ },
+ SupportsCondition::MozBoolPref(ref name) => {
+ dest.write_str("-moz-bool-pref(")?;
+ let name =
+ str::from_utf8(name.as_bytes()).expect("Should be parsed from valid UTF-8");
+ name.to_css(dest)?;
+ dest.write_str(")")
+ },
+ SupportsCondition::FontFormat(ref kw) => {
+ dest.write_str("font-format(")?;
+ kw.to_css(dest)?;
+ dest.write_str(")")
+ },
+ SupportsCondition::FontTech(ref flag) => {
+ dest.write_str("font-tech(")?;
+ flag.to_css(dest)?;
+ dest.write_str(")")
+ },
+ SupportsCondition::FutureSyntax(ref s) => dest.write_str(&s),
+ }
+ }
+}
+
+#[derive(Clone, Debug, ToShmem)]
+/// A possibly-invalid CSS selector.
+pub struct RawSelector(pub String);
+
+impl ToCss for RawSelector {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ dest.write_str(&self.0)
+ }
+}
+
+impl RawSelector {
+ /// Tries to evaluate a `selector()` function.
+ pub fn eval(&self, context: &ParserContext, namespaces: &Namespaces) -> bool {
+ let mut input = ParserInput::new(&self.0);
+ let mut input = Parser::new(&mut input);
+ input
+ .parse_entirely(|input| -> Result<(), CssParseError<()>> {
+ let parser = SelectorParser {
+ namespaces,
+ stylesheet_origin: context.stylesheet_origin,
+ url_data: context.url_data,
+ for_supports_rule: true,
+ };
+
+ Selector::<SelectorImpl>::parse(&parser, input)
+ .map_err(|_| input.new_custom_error(()))?;
+
+ Ok(())
+ })
+ .is_ok()
+ }
+}
+
+#[derive(Clone, Debug, ToShmem)]
+/// A possibly-invalid property declaration
+pub struct Declaration(pub String);
+
+impl ToCss for Declaration {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ dest.write_str(&self.0)
+ }
+}
+
+/// <https://drafts.csswg.org/css-syntax-3/#typedef-any-value>
+fn consume_any_value<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(), ParseError<'i>> {
+ input.expect_no_error_token().map_err(|err| err.into())
+}
+
+impl Declaration {
+ /// Parse a declaration
+ pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Declaration, ParseError<'i>> {
+ let pos = input.position();
+ input.expect_ident()?;
+ input.expect_colon()?;
+ consume_any_value(input)?;
+ Ok(Declaration(input.slice_from(pos).to_owned()))
+ }
+
+ /// Determine if a declaration parses
+ ///
+ /// <https://drafts.csswg.org/css-conditional-3/#support-definition>
+ pub fn eval(&self, context: &ParserContext) -> bool {
+ debug_assert_eq!(context.rule_type(), CssRuleType::Style);
+
+ let mut input = ParserInput::new(&self.0);
+ let mut input = Parser::new(&mut input);
+ input
+ .parse_entirely(|input| -> Result<(), CssParseError<()>> {
+ let prop = input.expect_ident_cloned().unwrap();
+ input.expect_colon().unwrap();
+
+ let id =
+ PropertyId::parse(&prop, context).map_err(|_| input.new_custom_error(()))?;
+
+ let mut declarations = SourcePropertyDeclaration::new();
+ input.parse_until_before(Delimiter::Bang, |input| {
+ PropertyDeclaration::parse_into(&mut declarations, id, &context, input)
+ .map_err(|_| input.new_custom_error(()))
+ })?;
+ let _ = input.try_parse(parse_important);
+ Ok(())
+ })
+ .is_ok()
+ }
+}