supports_rule.rs (15068B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 4 5 //! [@supports rules](https://drafts.csswg.org/css-conditional-3/#at-supports) 6 7 use crate::derives::*; 8 use crate::font_face::{FontFaceSourceFormatKeyword, FontFaceSourceTechFlags}; 9 use crate::parser::ParserContext; 10 use crate::properties::{PropertyDeclaration, PropertyId, SourcePropertyDeclaration}; 11 use crate::selector_parser::{SelectorImpl, SelectorParser}; 12 use crate::shared_lock::{DeepCloneWithLock, Locked}; 13 use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; 14 use crate::stylesheets::{CssRuleType, CssRules}; 15 use cssparser::parse_important; 16 use cssparser::{match_ignore_ascii_case, ParseError as CssParseError, ParserInput}; 17 use cssparser::{Delimiter, Parser, SourceLocation, Token}; 18 #[cfg(feature = "gecko")] 19 use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; 20 use selectors::parser::{Selector, SelectorParseErrorKind}; 21 use servo_arc::Arc; 22 use std::fmt::{self, Write}; 23 use std::str; 24 use style_traits::{CssStringWriter, CssWriter, ParseError, StyleParseErrorKind, ToCss}; 25 26 /// An [`@supports`][supports] rule. 27 /// 28 /// [supports]: https://drafts.csswg.org/css-conditional-3/#at-supports 29 #[derive(Debug, ToShmem)] 30 pub struct SupportsRule { 31 /// The parsed condition 32 pub condition: SupportsCondition, 33 /// Child rules 34 pub rules: Arc<Locked<CssRules>>, 35 /// The result of evaluating the condition 36 pub enabled: bool, 37 /// The line and column of the rule's source code. 38 pub source_location: SourceLocation, 39 } 40 41 impl SupportsRule { 42 /// Measure heap usage. 43 #[cfg(feature = "gecko")] 44 pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize { 45 // Measurement of other fields may be added later. 46 self.rules.unconditional_shallow_size_of(ops) 47 + self.rules.read_with(guard).size_of(guard, ops) 48 } 49 } 50 51 impl ToCssWithGuard for SupportsRule { 52 fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { 53 dest.write_str("@supports ")?; 54 self.condition.to_css(&mut CssWriter::new(dest))?; 55 self.rules.read_with(guard).to_css_block(guard, dest) 56 } 57 } 58 59 impl DeepCloneWithLock for SupportsRule { 60 fn deep_clone_with_lock(&self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard) -> Self { 61 let rules = self.rules.read_with(guard); 62 SupportsRule { 63 condition: self.condition.clone(), 64 rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard))), 65 enabled: self.enabled, 66 source_location: self.source_location.clone(), 67 } 68 } 69 } 70 71 /// An @supports condition 72 /// 73 /// <https://drafts.csswg.org/css-conditional-3/#at-supports> 74 #[derive(Clone, Debug, ToShmem)] 75 pub enum SupportsCondition { 76 /// `not (condition)` 77 Not(Box<SupportsCondition>), 78 /// `(condition)` 79 Parenthesized(Box<SupportsCondition>), 80 /// `(condition) and (condition) and (condition) ..` 81 And(Vec<SupportsCondition>), 82 /// `(condition) or (condition) or (condition) ..` 83 Or(Vec<SupportsCondition>), 84 /// `property-ident: value` (value can be any tokens) 85 Declaration(Declaration), 86 /// A `selector()` function. 87 Selector(RawSelector), 88 /// `font-format(<font-format>)` 89 FontFormat(FontFaceSourceFormatKeyword), 90 /// `font-tech(<font-tech>)` 91 FontTech(FontFaceSourceTechFlags), 92 /// `(any tokens)` or `func(any tokens)` 93 FutureSyntax(String), 94 } 95 96 impl SupportsCondition { 97 /// Parse a condition 98 /// 99 /// <https://drafts.csswg.org/css-conditional/#supports_condition> 100 pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> { 101 if input.try_parse(|i| i.expect_ident_matching("not")).is_ok() { 102 let inner = SupportsCondition::parse_in_parens(input)?; 103 return Ok(SupportsCondition::Not(Box::new(inner))); 104 } 105 106 let in_parens = SupportsCondition::parse_in_parens(input)?; 107 108 let location = input.current_source_location(); 109 let (keyword, wrapper) = match input.next() { 110 // End of input 111 Err(..) => return Ok(in_parens), 112 Ok(&Token::Ident(ref ident)) => { 113 match_ignore_ascii_case! { &ident, 114 "and" => ("and", SupportsCondition::And as fn(_) -> _), 115 "or" => ("or", SupportsCondition::Or as fn(_) -> _), 116 _ => return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone()))) 117 } 118 }, 119 Ok(t) => return Err(location.new_unexpected_token_error(t.clone())), 120 }; 121 122 let mut conditions = Vec::with_capacity(2); 123 conditions.push(in_parens); 124 loop { 125 conditions.push(SupportsCondition::parse_in_parens(input)?); 126 if input 127 .try_parse(|input| input.expect_ident_matching(keyword)) 128 .is_err() 129 { 130 // Did not find the expected keyword. 131 // If we found some other token, it will be rejected by 132 // `Parser::parse_entirely` somewhere up the stack. 133 return Ok(wrapper(conditions)); 134 } 135 } 136 } 137 138 /// Parses a functional supports condition. 139 fn parse_functional<'i, 't>( 140 function: &str, 141 input: &mut Parser<'i, 't>, 142 ) -> Result<Self, ParseError<'i>> { 143 match_ignore_ascii_case! { function, 144 "selector" => { 145 let pos = input.position(); 146 consume_any_value(input)?; 147 Ok(SupportsCondition::Selector(RawSelector( 148 input.slice_from(pos).to_owned() 149 ))) 150 }, 151 "font-format" if static_prefs::pref!("layout.css.font-tech.enabled") => { 152 let kw = FontFaceSourceFormatKeyword::parse(input)?; 153 Ok(SupportsCondition::FontFormat(kw)) 154 }, 155 "font-tech" if static_prefs::pref!("layout.css.font-tech.enabled") => { 156 let flag = FontFaceSourceTechFlags::parse_one(input)?; 157 Ok(SupportsCondition::FontTech(flag)) 158 }, 159 _ => { 160 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 161 }, 162 } 163 } 164 165 /// Parses an `@import` condition as per 166 /// https://drafts.csswg.org/css-cascade-5/#typedef-import-conditions 167 pub fn parse_for_import<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> { 168 input.expect_function_matching("supports")?; 169 input.parse_nested_block(parse_condition_or_declaration) 170 } 171 172 /// <https://drafts.csswg.org/css-conditional-3/#supports_condition_in_parens> 173 fn parse_in_parens<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> { 174 // Whitespace is normally taken care of in `Parser::next`, but we want to not include it in 175 // `pos` for the SupportsCondition::FutureSyntax cases. 176 input.skip_whitespace(); 177 let pos = input.position(); 178 let location = input.current_source_location(); 179 match *input.next()? { 180 Token::ParenthesisBlock => { 181 let nested = input 182 .try_parse(|input| input.parse_nested_block(parse_condition_or_declaration)); 183 if let Ok(nested) = nested { 184 return Ok(Self::Parenthesized(Box::new(nested))); 185 } 186 }, 187 Token::Function(ref ident) => { 188 let ident = ident.clone(); 189 let nested = input.try_parse(|input| { 190 input.parse_nested_block(|input| { 191 SupportsCondition::parse_functional(&ident, input) 192 }) 193 }); 194 if nested.is_ok() { 195 return nested; 196 } 197 }, 198 ref t => return Err(location.new_unexpected_token_error(t.clone())), 199 } 200 input.parse_nested_block(consume_any_value)?; 201 Ok(SupportsCondition::FutureSyntax( 202 input.slice_from(pos).to_owned(), 203 )) 204 } 205 206 /// Evaluate a supports condition 207 pub fn eval(&self, cx: &ParserContext) -> bool { 208 match *self { 209 SupportsCondition::Not(ref cond) => !cond.eval(cx), 210 SupportsCondition::Parenthesized(ref cond) => cond.eval(cx), 211 SupportsCondition::And(ref vec) => vec.iter().all(|c| c.eval(cx)), 212 SupportsCondition::Or(ref vec) => vec.iter().any(|c| c.eval(cx)), 213 SupportsCondition::Declaration(ref decl) => decl.eval(cx), 214 SupportsCondition::Selector(ref selector) => selector.eval(cx), 215 SupportsCondition::FontFormat(ref format) => eval_font_format(format), 216 SupportsCondition::FontTech(ref tech) => eval_font_tech(tech), 217 SupportsCondition::FutureSyntax(_) => false, 218 } 219 } 220 } 221 222 #[cfg(feature = "gecko")] 223 fn eval_font_format(kw: &FontFaceSourceFormatKeyword) -> bool { 224 use crate::gecko_bindings::bindings; 225 unsafe { bindings::Gecko_IsFontFormatSupported(*kw) } 226 } 227 228 #[cfg(feature = "gecko")] 229 fn eval_font_tech(flag: &FontFaceSourceTechFlags) -> bool { 230 use crate::gecko_bindings::bindings; 231 unsafe { bindings::Gecko_IsFontTechSupported(*flag) } 232 } 233 234 #[cfg(feature = "servo")] 235 fn eval_font_format(_: &FontFaceSourceFormatKeyword) -> bool { 236 false 237 } 238 239 #[cfg(feature = "servo")] 240 fn eval_font_tech(_: &FontFaceSourceTechFlags) -> bool { 241 false 242 } 243 244 /// supports_condition | declaration 245 /// <https://drafts.csswg.org/css-conditional/#dom-css-supports-conditiontext-conditiontext> 246 pub fn parse_condition_or_declaration<'i, 't>( 247 input: &mut Parser<'i, 't>, 248 ) -> Result<SupportsCondition, ParseError<'i>> { 249 if let Ok(condition) = input.try_parse(SupportsCondition::parse) { 250 Ok(condition) 251 } else { 252 Declaration::parse(input).map(SupportsCondition::Declaration) 253 } 254 } 255 256 impl ToCss for SupportsCondition { 257 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 258 where 259 W: Write, 260 { 261 match *self { 262 SupportsCondition::Not(ref cond) => { 263 dest.write_str("not ")?; 264 cond.to_css(dest) 265 }, 266 SupportsCondition::Parenthesized(ref cond) => { 267 dest.write_char('(')?; 268 cond.to_css(dest)?; 269 dest.write_char(')') 270 }, 271 SupportsCondition::And(ref vec) => { 272 let mut first = true; 273 for cond in vec { 274 if !first { 275 dest.write_str(" and ")?; 276 } 277 first = false; 278 cond.to_css(dest)?; 279 } 280 Ok(()) 281 }, 282 SupportsCondition::Or(ref vec) => { 283 let mut first = true; 284 for cond in vec { 285 if !first { 286 dest.write_str(" or ")?; 287 } 288 first = false; 289 cond.to_css(dest)?; 290 } 291 Ok(()) 292 }, 293 SupportsCondition::Declaration(ref decl) => decl.to_css(dest), 294 SupportsCondition::Selector(ref selector) => { 295 dest.write_str("selector(")?; 296 selector.to_css(dest)?; 297 dest.write_char(')') 298 }, 299 SupportsCondition::FontFormat(ref kw) => { 300 dest.write_str("font-format(")?; 301 kw.to_css(dest)?; 302 dest.write_char(')') 303 }, 304 SupportsCondition::FontTech(ref flag) => { 305 dest.write_str("font-tech(")?; 306 flag.to_css(dest)?; 307 dest.write_char(')') 308 }, 309 SupportsCondition::FutureSyntax(ref s) => dest.write_str(&s), 310 } 311 } 312 } 313 314 #[derive(Clone, Debug, ToShmem)] 315 /// A possibly-invalid CSS selector. 316 pub struct RawSelector(pub String); 317 318 impl ToCss for RawSelector { 319 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 320 where 321 W: Write, 322 { 323 dest.write_str(&self.0) 324 } 325 } 326 327 impl RawSelector { 328 /// Tries to evaluate a `selector()` function. 329 pub fn eval(&self, context: &ParserContext) -> bool { 330 let mut input = ParserInput::new(&self.0); 331 let mut input = Parser::new(&mut input); 332 input 333 .parse_entirely(|input| -> Result<(), CssParseError<()>> { 334 let parser = SelectorParser { 335 namespaces: &context.namespaces, 336 stylesheet_origin: context.stylesheet_origin, 337 url_data: context.url_data, 338 for_supports_rule: true, 339 }; 340 341 Selector::<SelectorImpl>::parse(&parser, input) 342 .map_err(|_| input.new_custom_error(()))?; 343 344 Ok(()) 345 }) 346 .is_ok() 347 } 348 } 349 350 #[derive(Clone, Debug, ToShmem)] 351 /// A possibly-invalid property declaration 352 pub struct Declaration(pub String); 353 354 impl ToCss for Declaration { 355 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 356 where 357 W: Write, 358 { 359 dest.write_str(&self.0) 360 } 361 } 362 363 /// <https://drafts.csswg.org/css-syntax-3/#typedef-any-value> 364 fn consume_any_value<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(), ParseError<'i>> { 365 input.expect_no_error_token().map_err(|err| err.into()) 366 } 367 368 impl Declaration { 369 /// Parse a declaration 370 pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Declaration, ParseError<'i>> { 371 let pos = input.position(); 372 input.expect_ident()?; 373 input.expect_colon()?; 374 consume_any_value(input)?; 375 Ok(Declaration(input.slice_from(pos).to_owned())) 376 } 377 378 /// Determine if a declaration parses 379 /// 380 /// <https://drafts.csswg.org/css-conditional-3/#support-definition> 381 pub fn eval(&self, context: &ParserContext) -> bool { 382 debug_assert!(context.rule_types().contains(CssRuleType::Style)); 383 384 let mut input = ParserInput::new(&self.0); 385 let mut input = Parser::new(&mut input); 386 input 387 .parse_entirely(|input| -> Result<(), CssParseError<()>> { 388 let prop = input.expect_ident_cloned().unwrap(); 389 input.expect_colon().unwrap(); 390 391 let id = 392 PropertyId::parse(&prop, context).map_err(|_| input.new_custom_error(()))?; 393 394 let mut declarations = SourcePropertyDeclaration::default(); 395 input.parse_until_before(Delimiter::Bang, |input| { 396 PropertyDeclaration::parse_into(&mut declarations, id, &context, input) 397 .map_err(|_| input.new_custom_error(())) 398 })?; 399 let _ = input.try_parse(parse_important); 400 Ok(()) 401 }) 402 .is_ok() 403 } 404 }