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::parser::ParserContext;
8 use crate::properties::{PropertyDeclaration, PropertyId, SourcePropertyDeclaration};
9 use crate::selector_parser::{SelectorImpl, SelectorParser};
10 use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked};
11 use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
12 use crate::str::CssStringWriter;
13 use crate::stylesheets::{CssRuleType, CssRules, Namespaces};
14 use cssparser::parse_important;
15 use cssparser::{Delimiter, Parser, SourceLocation, Token};
16 use cssparser::{ParseError as CssParseError, ParserInput};
17 #[cfg(feature = "gecko")]
18 use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
19 use selectors::parser::{Selector, SelectorParseErrorKind};
20 use servo_arc::Arc;
21 use std::ffi::{CStr, CString};
22 use std::fmt::{self, Write};
23 use std::str;
24 use style_traits::{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")]
size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize44 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 {
to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result52 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 {
deep_clone_with_lock( &self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard, params: &DeepCloneParams, ) -> Self60 fn deep_clone_with_lock(
61 &self,
62 lock: &SharedRwLock,
63 guard: &SharedRwLockReadGuard,
64 params: &DeepCloneParams,
65 ) -> Self {
66 let rules = self.rules.read_with(guard);
67 SupportsRule {
68 condition: self.condition.clone(),
69 rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard, params))),
70 enabled: self.enabled,
71 source_location: self.source_location.clone(),
72 }
73 }
74 }
75
76 /// An @supports condition
77 ///
78 /// <https://drafts.csswg.org/css-conditional-3/#at-supports>
79 #[derive(Clone, Debug, ToShmem)]
80 pub enum SupportsCondition {
81 /// `not (condition)`
82 Not(Box<SupportsCondition>),
83 /// `(condition)`
84 Parenthesized(Box<SupportsCondition>),
85 /// `(condition) and (condition) and (condition) ..`
86 And(Vec<SupportsCondition>),
87 /// `(condition) or (condition) or (condition) ..`
88 Or(Vec<SupportsCondition>),
89 /// `property-ident: value` (value can be any tokens)
90 Declaration(Declaration),
91 /// A `selector()` function.
92 Selector(RawSelector),
93 /// `-moz-bool-pref("pref-name")`
94 /// Since we need to pass it through FFI to get the pref value,
95 /// we store it as CString directly.
96 MozBoolPref(CString),
97 /// `(any tokens)` or `func(any tokens)`
98 FutureSyntax(String),
99 }
100
101 impl SupportsCondition {
102 /// Parse a condition
103 ///
104 /// <https://drafts.csswg.org/css-conditional/#supports_condition>
parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>>105 pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
106 if input.try_parse(|i| i.expect_ident_matching("not")).is_ok() {
107 let inner = SupportsCondition::parse_in_parens(input)?;
108 return Ok(SupportsCondition::Not(Box::new(inner)));
109 }
110
111 let in_parens = SupportsCondition::parse_in_parens(input)?;
112
113 let location = input.current_source_location();
114 let (keyword, wrapper) = match input.next() {
115 // End of input
116 Err(..) => return Ok(in_parens),
117 Ok(&Token::Ident(ref ident)) => {
118 match_ignore_ascii_case! { &ident,
119 "and" => ("and", SupportsCondition::And as fn(_) -> _),
120 "or" => ("or", SupportsCondition::Or as fn(_) -> _),
121 _ => return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())))
122 }
123 },
124 Ok(t) => return Err(location.new_unexpected_token_error(t.clone())),
125 };
126
127 let mut conditions = Vec::with_capacity(2);
128 conditions.push(in_parens);
129 loop {
130 conditions.push(SupportsCondition::parse_in_parens(input)?);
131 if input
132 .try_parse(|input| input.expect_ident_matching(keyword))
133 .is_err()
134 {
135 // Did not find the expected keyword.
136 // If we found some other token, it will be rejected by
137 // `Parser::parse_entirely` somewhere up the stack.
138 return Ok(wrapper(conditions));
139 }
140 }
141 }
142
143 /// Parses a functional supports condition.
parse_functional<'i, 't>( function: &str, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>144 fn parse_functional<'i, 't>(
145 function: &str,
146 input: &mut Parser<'i, 't>,
147 ) -> Result<Self, ParseError<'i>> {
148 match_ignore_ascii_case! { function,
149 // Although this is an internal syntax, it is not necessary
150 // to check parsing context as far as we accept any
151 // unexpected token as future syntax, and evaluate it to
152 // false when not in chrome / ua sheet.
153 // See https://drafts.csswg.org/css-conditional-3/#general_enclosed
154 "-moz-bool-pref" => {
155 let name = {
156 let name = input.expect_string()?;
157 CString::new(name.as_bytes())
158 }.map_err(|_| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))?;
159 Ok(SupportsCondition::MozBoolPref(name))
160 },
161 "selector" => {
162 let pos = input.position();
163 consume_any_value(input)?;
164 Ok(SupportsCondition::Selector(RawSelector(
165 input.slice_from(pos).to_owned()
166 )))
167 },
168 _ => {
169 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
170 },
171 }
172 }
173
174 /// <https://drafts.csswg.org/css-conditional-3/#supports_condition_in_parens>
parse_in_parens<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>>175 fn parse_in_parens<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
176 // Whitespace is normally taken care of in `Parser::next`,
177 // but we want to not include it in `pos` for the SupportsCondition::FutureSyntax cases.
178 while input.try_parse(Parser::expect_whitespace).is_ok() {}
179 let pos = input.position();
180 let location = input.current_source_location();
181 match *input.next()? {
182 Token::ParenthesisBlock => {
183 let nested = input
184 .try_parse(|input| input.parse_nested_block(parse_condition_or_declaration));
185 if nested.is_ok() {
186 return nested;
187 }
188 },
189 Token::Function(ref ident) => {
190 let ident = ident.clone();
191 let nested = input.try_parse(|input| {
192 input.parse_nested_block(|input| {
193 SupportsCondition::parse_functional(&ident, input)
194 })
195 });
196 if nested.is_ok() {
197 return nested;
198 }
199 },
200 ref t => return Err(location.new_unexpected_token_error(t.clone())),
201 }
202 input.parse_nested_block(consume_any_value)?;
203 Ok(SupportsCondition::FutureSyntax(
204 input.slice_from(pos).to_owned(),
205 ))
206 }
207
208 /// Evaluate a supports condition
eval(&self, cx: &ParserContext, namespaces: &Namespaces) -> bool209 pub fn eval(&self, cx: &ParserContext, namespaces: &Namespaces) -> bool {
210 match *self {
211 SupportsCondition::Not(ref cond) => !cond.eval(cx, namespaces),
212 SupportsCondition::Parenthesized(ref cond) => cond.eval(cx, namespaces),
213 SupportsCondition::And(ref vec) => vec.iter().all(|c| c.eval(cx, namespaces)),
214 SupportsCondition::Or(ref vec) => vec.iter().any(|c| c.eval(cx, namespaces)),
215 SupportsCondition::Declaration(ref decl) => decl.eval(cx),
216 SupportsCondition::MozBoolPref(ref name) => eval_moz_bool_pref(name, cx),
217 SupportsCondition::Selector(ref selector) => selector.eval(cx, namespaces),
218 SupportsCondition::FutureSyntax(_) => false,
219 }
220 }
221 }
222
223 #[cfg(feature = "gecko")]
eval_moz_bool_pref(name: &CStr, cx: &ParserContext) -> bool224 fn eval_moz_bool_pref(name: &CStr, cx: &ParserContext) -> bool {
225 use crate::gecko_bindings::bindings;
226 if !cx.in_ua_or_chrome_sheet() {
227 return false;
228 }
229 unsafe { bindings::Gecko_GetBoolPrefValue(name.as_ptr()) }
230 }
231
232 #[cfg(feature = "servo")]
eval_moz_bool_pref(_: &CStr, _: &ParserContext) -> bool233 fn eval_moz_bool_pref(_: &CStr, _: &ParserContext) -> bool {
234 false
235 }
236
237 /// supports_condition | declaration
238 /// <https://drafts.csswg.org/css-conditional/#dom-css-supports-conditiontext-conditiontext>
parse_condition_or_declaration<'i, 't>( input: &mut Parser<'i, 't>, ) -> Result<SupportsCondition, ParseError<'i>>239 pub fn parse_condition_or_declaration<'i, 't>(
240 input: &mut Parser<'i, 't>,
241 ) -> Result<SupportsCondition, ParseError<'i>> {
242 if let Ok(condition) = input.try_parse(SupportsCondition::parse) {
243 Ok(SupportsCondition::Parenthesized(Box::new(condition)))
244 } else {
245 Declaration::parse(input).map(SupportsCondition::Declaration)
246 }
247 }
248
249 impl ToCss for SupportsCondition {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,250 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
251 where
252 W: Write,
253 {
254 match *self {
255 SupportsCondition::Not(ref cond) => {
256 dest.write_str("not ")?;
257 cond.to_css(dest)
258 },
259 SupportsCondition::Parenthesized(ref cond) => {
260 dest.write_str("(")?;
261 cond.to_css(dest)?;
262 dest.write_str(")")
263 },
264 SupportsCondition::And(ref vec) => {
265 let mut first = true;
266 for cond in vec {
267 if !first {
268 dest.write_str(" and ")?;
269 }
270 first = false;
271 cond.to_css(dest)?;
272 }
273 Ok(())
274 },
275 SupportsCondition::Or(ref vec) => {
276 let mut first = true;
277 for cond in vec {
278 if !first {
279 dest.write_str(" or ")?;
280 }
281 first = false;
282 cond.to_css(dest)?;
283 }
284 Ok(())
285 },
286 SupportsCondition::Declaration(ref decl) => {
287 dest.write_str("(")?;
288 decl.to_css(dest)?;
289 dest.write_str(")")
290 },
291 SupportsCondition::Selector(ref selector) => {
292 dest.write_str("selector(")?;
293 selector.to_css(dest)?;
294 dest.write_str(")")
295 },
296 SupportsCondition::MozBoolPref(ref name) => {
297 dest.write_str("-moz-bool-pref(")?;
298 let name =
299 str::from_utf8(name.as_bytes()).expect("Should be parsed from valid UTF-8");
300 name.to_css(dest)?;
301 dest.write_str(")")
302 },
303 SupportsCondition::FutureSyntax(ref s) => dest.write_str(&s),
304 }
305 }
306 }
307
308 #[derive(Clone, Debug, ToShmem)]
309 /// A possibly-invalid CSS selector.
310 pub struct RawSelector(pub String);
311
312 impl ToCss for RawSelector {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,313 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
314 where
315 W: Write,
316 {
317 dest.write_str(&self.0)
318 }
319 }
320
321 impl RawSelector {
322 /// Tries to evaluate a `selector()` function.
eval(&self, context: &ParserContext, namespaces: &Namespaces) -> bool323 pub fn eval(&self, context: &ParserContext, namespaces: &Namespaces) -> bool {
324 #[cfg(feature = "gecko")]
325 {
326 if !static_prefs::pref!("layout.css.supports-selector.enabled") {
327 return false;
328 }
329 }
330
331 let mut input = ParserInput::new(&self.0);
332 let mut input = Parser::new(&mut input);
333 input
334 .parse_entirely(|input| -> Result<(), CssParseError<()>> {
335 let parser = SelectorParser {
336 namespaces,
337 stylesheet_origin: context.stylesheet_origin,
338 url_data: context.url_data,
339 };
340
341 #[allow(unused_variables)]
342 let selector = Selector::<SelectorImpl>::parse(&parser, input)
343 .map_err(|_| input.new_custom_error(()))?;
344
345 #[cfg(feature = "gecko")]
346 {
347 use crate::selector_parser::PseudoElement;
348 use selectors::parser::Component;
349
350 let has_any_unknown_webkit_pseudo = selector.has_pseudo_element() &&
351 selector.iter_raw_match_order().any(|component| {
352 matches!(
353 *component,
354 Component::PseudoElement(PseudoElement::UnknownWebkit(..))
355 )
356 });
357 if has_any_unknown_webkit_pseudo {
358 return Err(input.new_custom_error(()));
359 }
360 }
361
362 Ok(())
363 })
364 .is_ok()
365 }
366 }
367
368 #[derive(Clone, Debug, ToShmem)]
369 /// A possibly-invalid property declaration
370 pub struct Declaration(pub String);
371
372 impl ToCss for Declaration {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,373 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
374 where
375 W: Write,
376 {
377 dest.write_str(&self.0)
378 }
379 }
380
381 /// <https://drafts.csswg.org/css-syntax-3/#typedef-any-value>
consume_any_value<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(), ParseError<'i>>382 fn consume_any_value<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(), ParseError<'i>> {
383 input.expect_no_error_token().map_err(|err| err.into())
384 }
385
386 impl Declaration {
387 /// Parse a declaration
parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Declaration, ParseError<'i>>388 pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Declaration, ParseError<'i>> {
389 let pos = input.position();
390 input.expect_ident()?;
391 input.expect_colon()?;
392 consume_any_value(input)?;
393 Ok(Declaration(input.slice_from(pos).to_owned()))
394 }
395
396 /// Determine if a declaration parses
397 ///
398 /// <https://drafts.csswg.org/css-conditional-3/#support-definition>
eval(&self, context: &ParserContext) -> bool399 pub fn eval(&self, context: &ParserContext) -> bool {
400 debug_assert_eq!(context.rule_type(), CssRuleType::Style);
401
402 let mut input = ParserInput::new(&self.0);
403 let mut input = Parser::new(&mut input);
404 input
405 .parse_entirely(|input| -> Result<(), CssParseError<()>> {
406 let prop = input.expect_ident_cloned().unwrap();
407 input.expect_colon().unwrap();
408
409 let id =
410 PropertyId::parse(&prop, context).map_err(|_| input.new_custom_error(()))?;
411
412 let mut declarations = SourcePropertyDeclaration::new();
413 input.parse_until_before(Delimiter::Bang, |input| {
414 PropertyDeclaration::parse_into(&mut declarations, id, &context, input)
415 .map_err(|_| input.new_custom_error(()))
416 })?;
417 let _ = input.try_parse(parse_important);
418 Ok(())
419 })
420 .is_ok()
421 }
422 }
423