1use crate::font_face::{FontFaceSourceFormatKeyword, FontFaceSourceTechFlags};
8use crate::parser::ParserContext;
9use crate::properties::{PropertyDeclaration, PropertyId, SourcePropertyDeclaration};
10use crate::selector_parser::{SelectorImpl, SelectorParser};
11use crate::shared_lock::{DeepCloneWithLock, Locked};
12use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
13use crate::stylesheets::{CssRuleType, CssRules};
14use cssparser::parse_important;
15use cssparser::{Delimiter, Parser, SourceLocation, Token};
16use cssparser::{ParseError as CssParseError, ParserInput};
17#[cfg(feature = "gecko")]
18use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
19use selectors::parser::{Selector, SelectorParseErrorKind};
20use servo_arc::Arc;
21use std::fmt::{self, Write};
22use std::str;
23use style_traits::{CssStringWriter, CssWriter, ParseError, StyleParseErrorKind, ToCss};
24
25#[derive(Debug, ToShmem)]
29pub struct SupportsRule {
30 pub condition: SupportsCondition,
32 pub rules: Arc<Locked<CssRules>>,
34 pub enabled: bool,
36 pub source_location: SourceLocation,
38}
39
40impl SupportsRule {
41 #[cfg(feature = "gecko")]
43 pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
44 self.rules.unconditional_shallow_size_of(ops)
46 + self.rules.read_with(guard).size_of(guard, ops)
47 }
48}
49
50impl ToCssWithGuard for SupportsRule {
51 fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
52 dest.write_str("@supports ")?;
53 self.condition.to_css(&mut CssWriter::new(dest))?;
54 self.rules.read_with(guard).to_css_block(guard, dest)
55 }
56}
57
58impl DeepCloneWithLock for SupportsRule {
59 fn deep_clone_with_lock(&self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard) -> Self {
60 let rules = self.rules.read_with(guard);
61 SupportsRule {
62 condition: self.condition.clone(),
63 rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard))),
64 enabled: self.enabled,
65 source_location: self.source_location.clone(),
66 }
67 }
68}
69
70#[derive(Clone, Debug, ToShmem)]
74pub enum SupportsCondition {
75 Not(Box<SupportsCondition>),
77 Parenthesized(Box<SupportsCondition>),
79 And(Vec<SupportsCondition>),
81 Or(Vec<SupportsCondition>),
83 Declaration(Declaration),
85 Selector(RawSelector),
87 FontFormat(FontFaceSourceFormatKeyword),
89 FontTech(FontFaceSourceTechFlags),
91 FutureSyntax(String),
93}
94
95impl SupportsCondition {
96 pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
100 if input.try_parse(|i| i.expect_ident_matching("not")).is_ok() {
101 let inner = SupportsCondition::parse_in_parens(input)?;
102 return Ok(SupportsCondition::Not(Box::new(inner)));
103 }
104
105 let in_parens = SupportsCondition::parse_in_parens(input)?;
106
107 let location = input.current_source_location();
108 let (keyword, wrapper) = match input.next() {
109 Err(..) => return Ok(in_parens),
111 Ok(&Token::Ident(ref ident)) => {
112 match_ignore_ascii_case! { &ident,
113 "and" => ("and", SupportsCondition::And as fn(_) -> _),
114 "or" => ("or", SupportsCondition::Or as fn(_) -> _),
115 _ => return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())))
116 }
117 },
118 Ok(t) => return Err(location.new_unexpected_token_error(t.clone())),
119 };
120
121 let mut conditions = Vec::with_capacity(2);
122 conditions.push(in_parens);
123 loop {
124 conditions.push(SupportsCondition::parse_in_parens(input)?);
125 if input
126 .try_parse(|input| input.expect_ident_matching(keyword))
127 .is_err()
128 {
129 return Ok(wrapper(conditions));
133 }
134 }
135 }
136
137 fn parse_functional<'i, 't>(
139 function: &str,
140 input: &mut Parser<'i, 't>,
141 ) -> Result<Self, ParseError<'i>> {
142 match_ignore_ascii_case! { function,
143 "selector" => {
144 let pos = input.position();
145 consume_any_value(input)?;
146 Ok(SupportsCondition::Selector(RawSelector(
147 input.slice_from(pos).to_owned()
148 )))
149 },
150 "font-format" if static_prefs::pref!("layout.css.font-tech.enabled") => {
151 let kw = FontFaceSourceFormatKeyword::parse(input)?;
152 Ok(SupportsCondition::FontFormat(kw))
153 },
154 "font-tech" if static_prefs::pref!("layout.css.font-tech.enabled") => {
155 let flag = FontFaceSourceTechFlags::parse_one(input)?;
156 Ok(SupportsCondition::FontTech(flag))
157 },
158 _ => {
159 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
160 },
161 }
162 }
163
164 pub fn parse_for_import<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
167 input.expect_function_matching("supports")?;
168 input.parse_nested_block(parse_condition_or_declaration)
169 }
170
171 fn parse_in_parens<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
173 input.skip_whitespace();
176 let pos = input.position();
177 let location = input.current_source_location();
178 match *input.next()? {
179 Token::ParenthesisBlock => {
180 let nested = input
181 .try_parse(|input| input.parse_nested_block(parse_condition_or_declaration));
182 if let Ok(nested) = nested {
183 return Ok(Self::Parenthesized(Box::new(nested)));
184 }
185 },
186 Token::Function(ref ident) => {
187 let ident = ident.clone();
188 let nested = input.try_parse(|input| {
189 input.parse_nested_block(|input| {
190 SupportsCondition::parse_functional(&ident, input)
191 })
192 });
193 if nested.is_ok() {
194 return nested;
195 }
196 },
197 ref t => return Err(location.new_unexpected_token_error(t.clone())),
198 }
199 input.parse_nested_block(consume_any_value)?;
200 Ok(SupportsCondition::FutureSyntax(
201 input.slice_from(pos).to_owned(),
202 ))
203 }
204
205 pub fn eval(&self, cx: &ParserContext) -> bool {
207 match *self {
208 SupportsCondition::Not(ref cond) => !cond.eval(cx),
209 SupportsCondition::Parenthesized(ref cond) => cond.eval(cx),
210 SupportsCondition::And(ref vec) => vec.iter().all(|c| c.eval(cx)),
211 SupportsCondition::Or(ref vec) => vec.iter().any(|c| c.eval(cx)),
212 SupportsCondition::Declaration(ref decl) => decl.eval(cx),
213 SupportsCondition::Selector(ref selector) => selector.eval(cx),
214 SupportsCondition::FontFormat(ref format) => eval_font_format(format),
215 SupportsCondition::FontTech(ref tech) => eval_font_tech(tech),
216 SupportsCondition::FutureSyntax(_) => false,
217 }
218 }
219}
220
221#[cfg(feature = "gecko")]
222fn eval_font_format(kw: &FontFaceSourceFormatKeyword) -> bool {
223 use crate::gecko_bindings::bindings;
224 unsafe { bindings::Gecko_IsFontFormatSupported(*kw) }
225}
226
227#[cfg(feature = "gecko")]
228fn eval_font_tech(flag: &FontFaceSourceTechFlags) -> bool {
229 use crate::gecko_bindings::bindings;
230 unsafe { bindings::Gecko_IsFontTechSupported(*flag) }
231}
232
233#[cfg(feature = "servo")]
234fn eval_font_format(_: &FontFaceSourceFormatKeyword) -> bool {
235 false
236}
237
238#[cfg(feature = "servo")]
239fn eval_font_tech(_: &FontFaceSourceTechFlags) -> bool {
240 false
241}
242
243pub fn parse_condition_or_declaration<'i, 't>(
246 input: &mut Parser<'i, 't>,
247) -> Result<SupportsCondition, ParseError<'i>> {
248 if let Ok(condition) = input.try_parse(SupportsCondition::parse) {
249 Ok(condition)
250 } else {
251 Declaration::parse(input).map(SupportsCondition::Declaration)
252 }
253}
254
255impl ToCss for SupportsCondition {
256 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
257 where
258 W: Write,
259 {
260 match *self {
261 SupportsCondition::Not(ref cond) => {
262 dest.write_str("not ")?;
263 cond.to_css(dest)
264 },
265 SupportsCondition::Parenthesized(ref cond) => {
266 dest.write_char('(')?;
267 cond.to_css(dest)?;
268 dest.write_char(')')
269 },
270 SupportsCondition::And(ref vec) => {
271 let mut first = true;
272 for cond in vec {
273 if !first {
274 dest.write_str(" and ")?;
275 }
276 first = false;
277 cond.to_css(dest)?;
278 }
279 Ok(())
280 },
281 SupportsCondition::Or(ref vec) => {
282 let mut first = true;
283 for cond in vec {
284 if !first {
285 dest.write_str(" or ")?;
286 }
287 first = false;
288 cond.to_css(dest)?;
289 }
290 Ok(())
291 },
292 SupportsCondition::Declaration(ref decl) => decl.to_css(dest),
293 SupportsCondition::Selector(ref selector) => {
294 dest.write_str("selector(")?;
295 selector.to_css(dest)?;
296 dest.write_char(')')
297 },
298 SupportsCondition::FontFormat(ref kw) => {
299 dest.write_str("font-format(")?;
300 kw.to_css(dest)?;
301 dest.write_char(')')
302 },
303 SupportsCondition::FontTech(ref flag) => {
304 dest.write_str("font-tech(")?;
305 flag.to_css(dest)?;
306 dest.write_char(')')
307 },
308 SupportsCondition::FutureSyntax(ref s) => dest.write_str(&s),
309 }
310 }
311}
312
313#[derive(Clone, Debug, ToShmem)]
314pub struct RawSelector(pub String);
316
317impl ToCss for RawSelector {
318 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
319 where
320 W: Write,
321 {
322 dest.write_str(&self.0)
323 }
324}
325
326impl RawSelector {
327 pub fn eval(&self, context: &ParserContext) -> bool {
329 let mut input = ParserInput::new(&self.0);
330 let mut input = Parser::new(&mut input);
331 input
332 .parse_entirely(|input| -> Result<(), CssParseError<()>> {
333 let parser = SelectorParser {
334 namespaces: &context.namespaces,
335 stylesheet_origin: context.stylesheet_origin,
336 url_data: context.url_data,
337 for_supports_rule: true,
338 };
339
340 Selector::<SelectorImpl>::parse(&parser, input)
341 .map_err(|_| input.new_custom_error(()))?;
342
343 Ok(())
344 })
345 .is_ok()
346 }
347}
348
349#[derive(Clone, Debug, ToShmem)]
350pub struct Declaration(pub String);
352
353impl ToCss for Declaration {
354 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
355 where
356 W: Write,
357 {
358 dest.write_str(&self.0)
359 }
360}
361
362fn consume_any_value<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(), ParseError<'i>> {
364 input.expect_no_error_token().map_err(|err| err.into())
365}
366
367impl Declaration {
368 pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Declaration, ParseError<'i>> {
370 let pos = input.position();
371 input.expect_ident()?;
372 input.expect_colon()?;
373 consume_any_value(input)?;
374 Ok(Declaration(input.slice_from(pos).to_owned()))
375 }
376
377 pub fn eval(&self, context: &ParserContext) -> bool {
381 debug_assert!(context.rule_types().contains(CssRuleType::Style));
382
383 let mut input = ParserInput::new(&self.0);
384 let mut input = Parser::new(&mut input);
385 input
386 .parse_entirely(|input| -> Result<(), CssParseError<()>> {
387 let prop = input.expect_ident_cloned().unwrap();
388 input.expect_colon().unwrap();
389
390 let id =
391 PropertyId::parse(&prop, context).map_err(|_| input.new_custom_error(()))?;
392
393 let mut declarations = SourcePropertyDeclaration::default();
394 input.parse_until_before(Delimiter::Bang, |input| {
395 PropertyDeclaration::parse_into(&mut declarations, id, &context, input)
396 .map_err(|_| input.new_custom_error(()))
397 })?;
398 let _ = input.try_parse(parse_important);
399 Ok(())
400 })
401 .is_ok()
402 }
403}