Skip to main content

style/queries/
condition.rs

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//! A query condition:
6//!
7//! https://drafts.csswg.org/mediaqueries-4/#typedef-media-condition
8//! https://drafts.csswg.org/css-contain-3/#typedef-container-condition
9
10use super::{FeatureFlags, FeatureType, QueryFeatureExpression, QueryStyleRange};
11use crate::computed_value_flags::ComputedValueFlags;
12use crate::context::QuirksMode;
13use crate::custom_properties;
14use crate::derives::*;
15use crate::dom::AttributeTracker;
16use crate::properties::CSSWideKeyword;
17use crate::properties_and_values::rule::Descriptors as PropertyDescriptors;
18use crate::properties_and_values::value::{
19    AllowComputationallyDependent, ComputedValue as ComputedRegisteredValue,
20    SpecifiedValue as SpecifiedRegisteredValue,
21};
22use crate::stylesheets::{CssRuleType, CustomMediaEvaluator, Origin, UrlExtraData};
23use crate::stylist::Stylist;
24use crate::values::{computed, AtomString, DashedIdent};
25use crate::{error_reporting::ContextualParseError, parser::Parse, parser::ParserContext};
26use cssparser::{
27    match_ignore_ascii_case, parse_important, Parser, ParserInput, SourcePosition, Token,
28};
29use selectors::kleene_value::KleeneValue;
30use servo_arc::Arc;
31use std::fmt::{self, Write};
32use style_traits::{CssWriter, ParseError, ParsingMode, StyleParseErrorKind, ToCss};
33
34/// A binary `and` or `or` operator.
35#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
36#[allow(missing_docs)]
37pub enum Operator {
38    And,
39    Or,
40}
41
42/// Whether to allow an `or` condition or not during parsing.
43#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToCss)]
44enum AllowOr {
45    Yes,
46    No,
47}
48
49#[derive(Clone, Debug, PartialEq, ToShmem)]
50enum StyleFeatureValue {
51    Value(Option<Arc<custom_properties::SpecifiedValue>>),
52    Keyword(CSSWideKeyword),
53}
54
55/// Trait for query elements that parse a series of conditions separated by
56/// AND or OR operators, or prefixed with NOT.
57///
58/// This is used by both QueryCondition and StyleQuery as they support similar
59/// syntax for combining multiple conditions with a boolean operator.
60trait OperationParser: Sized {
61    /// https://drafts.csswg.org/mediaqueries-5/#typedef-media-condition or
62    /// https://drafts.csswg.org/mediaqueries-5/#typedef-media-condition-without-or
63    /// (depending on `allow_or`).
64    fn parse_internal<'i, 't>(
65        context: &ParserContext,
66        input: &mut Parser<'i, 't>,
67        feature_type: FeatureType,
68        allow_or: AllowOr,
69    ) -> Result<Self, ParseError<'i>> {
70        let location = input.current_source_location();
71
72        if input.try_parse(|i| i.expect_ident_matching("not")).is_ok() {
73            let inner_condition = Self::parse_in_parens(context, input, feature_type)?;
74            return Ok(Self::new_not(Box::new(inner_condition)));
75        }
76
77        let first_condition = Self::parse_in_parens(context, input, feature_type)?;
78        let operator = match input.try_parse(Operator::parse) {
79            Ok(op) => op,
80            Err(..) => return Ok(first_condition),
81        };
82
83        if allow_or == AllowOr::No && operator == Operator::Or {
84            return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
85        }
86
87        let mut conditions = vec![];
88        conditions.push(first_condition);
89        conditions.push(Self::parse_in_parens(context, input, feature_type)?);
90
91        let delim = match operator {
92            Operator::And => "and",
93            Operator::Or => "or",
94        };
95
96        loop {
97            if input.try_parse(|i| i.expect_ident_matching(delim)).is_err() {
98                return Ok(Self::new_operation(conditions.into_boxed_slice(), operator));
99            }
100
101            conditions.push(Self::parse_in_parens(context, input, feature_type)?);
102        }
103    }
104
105    // Parse a condition in parentheses, or `<general-enclosed>`.
106    fn parse_in_parens<'i, 't>(
107        context: &ParserContext,
108        input: &mut Parser<'i, 't>,
109        feature_type: FeatureType,
110    ) -> Result<Self, ParseError<'i>>;
111
112    // Helpers to create the appropriate enum variant of the implementing type:
113    // Create a Not result that encapsulates the `inner` condition.
114    fn new_not(inner: Box<Self>) -> Self;
115
116    // Create an Operation result with the given list of `conditions` using `operator`.
117    fn new_operation(conditions: Box<[Self]>, operator: Operator) -> Self;
118}
119
120/// https://drafts.csswg.org/css-conditional-5/#typedef-style-query
121#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
122pub enum StyleQuery {
123    /// A negation of a condition.
124    Not(Box<StyleQuery>),
125    /// A set of joint operations.
126    Operation(Box<[StyleQuery]>, Operator),
127    /// A condition wrapped in parenthesis.
128    InParens(Box<StyleQuery>),
129    /// A feature query (`--foo: bar` or just `--foo`).
130    Feature(StyleFeature),
131    /// An unknown "general-enclosed" term.
132    GeneralEnclosed(String),
133}
134
135impl ToCss for StyleQuery {
136    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
137    where
138        W: fmt::Write,
139    {
140        match *self {
141            StyleQuery::Not(ref c) => {
142                dest.write_str("not ")?;
143                c.maybe_parenthesized(dest)
144            },
145            StyleQuery::Operation(ref list, op) => {
146                let mut iter = list.iter();
147                let item = iter.next().unwrap();
148                item.maybe_parenthesized(dest)?;
149                for item in iter {
150                    dest.write_char(' ')?;
151                    op.to_css(dest)?;
152                    dest.write_char(' ')?;
153                    item.maybe_parenthesized(dest)?;
154                }
155                Ok(())
156            },
157            StyleQuery::InParens(ref c) => match &**c {
158                StyleQuery::Feature(_) | StyleQuery::InParens(_) => {
159                    dest.write_char('(')?;
160                    c.to_css(dest)?;
161                    dest.write_char(')')
162                },
163                _ => c.to_css(dest),
164            },
165            StyleQuery::Feature(ref f) => f.to_css(dest),
166            StyleQuery::GeneralEnclosed(ref s) => dest.write_str(&s),
167        }
168    }
169}
170
171impl StyleQuery {
172    // Helper for to_css when handling values within boolean operators:
173    // GeneralEnclosed includes its parens in the string, so we don't need to
174    // wrap the value with an additional set here.
175    fn maybe_parenthesized<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
176    where
177        W: fmt::Write,
178    {
179        if let StyleQuery::GeneralEnclosed(ref s) = self {
180            dest.write_str(&s)
181        } else {
182            dest.write_char('(')?;
183            self.to_css(dest)?;
184            dest.write_char(')')
185        }
186    }
187
188    fn parse<'i, 't>(
189        context: &ParserContext,
190        input: &mut Parser<'i, 't>,
191        feature_type: FeatureType,
192    ) -> Result<Self, ParseError<'i>> {
193        if !static_prefs::pref!("layout.css.style-queries.enabled")
194            || feature_type != FeatureType::Container
195        {
196            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
197        }
198
199        if let Ok(feature) = input.try_parse(|input| StyleFeature::parse(context, input)) {
200            return Ok(Self::Feature(feature));
201        }
202
203        let inner = Self::parse_internal(context, input, feature_type, AllowOr::Yes)?;
204        Ok(Self::InParens(Box::new(inner)))
205    }
206
207    fn parse_in_parenthesis_block<'i>(
208        context: &ParserContext,
209        input: &mut Parser<'i, '_>,
210    ) -> Result<Self, ParseError<'i>> {
211        // Base case. Make sure to preserve this error as it's more generally
212        // relevant.
213        let feature_error = match input.try_parse(|input| StyleFeature::parse(context, input)) {
214            Ok(feature) => return Ok(Self::Feature(feature)),
215            Err(e) => e,
216        };
217
218        if let Ok(inner) = Self::parse(context, input, FeatureType::Container) {
219            return Ok(inner);
220        }
221
222        Err(feature_error)
223    }
224
225    fn try_parse_block<'i, T, F>(
226        context: &ParserContext,
227        input: &mut Parser<'i, '_>,
228        start: SourcePosition,
229        parse: F,
230    ) -> Option<T>
231    where
232        F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i>>,
233    {
234        let nested = input.try_parse(|input| input.parse_nested_block(parse));
235        match nested {
236            Ok(nested) => Some(nested),
237            Err(e) => {
238                // We're about to swallow the error in a `<general-enclosed>`
239                // condition, so report it while we can.
240                let loc = e.location;
241                let error = ContextualParseError::InvalidMediaRule(input.slice_from(start), e);
242                context.log_css_error(loc, error);
243                None
244            },
245        }
246    }
247
248    fn matches(
249        &self,
250        ctx: &computed::Context,
251        attribute_tracker: &mut AttributeTracker,
252    ) -> KleeneValue {
253        ctx.builder
254            .add_flags(ComputedValueFlags::DEPENDS_ON_CONTAINER_STYLE_QUERY);
255        match *self {
256            StyleQuery::Feature(ref f) => f.matches(ctx, attribute_tracker),
257            StyleQuery::Not(ref c) => !c.matches(ctx, attribute_tracker),
258            StyleQuery::InParens(ref c) => c.matches(ctx, attribute_tracker),
259            StyleQuery::Operation(ref conditions, op) => {
260                debug_assert!(!conditions.is_empty(), "We never create an empty op");
261                match op {
262                    Operator::And => KleeneValue::any_false(conditions.iter(), |c| {
263                        c.matches(ctx, attribute_tracker)
264                    }),
265                    Operator::Or => {
266                        KleeneValue::any(conditions.iter(), |c| c.matches(ctx, attribute_tracker))
267                    },
268                }
269            },
270            StyleQuery::GeneralEnclosed(_) => KleeneValue::Unknown,
271        }
272    }
273}
274
275impl OperationParser for StyleQuery {
276    fn parse_in_parens<'i, 't>(
277        context: &ParserContext,
278        input: &mut Parser<'i, 't>,
279        feature_type: FeatureType,
280    ) -> Result<Self, ParseError<'i>> {
281        assert!(feature_type == FeatureType::Container);
282        input.skip_whitespace();
283        let start = input.position();
284        let start_location = input.current_source_location();
285        match *input.next()? {
286            Token::ParenthesisBlock => {
287                if let Some(nested) = Self::try_parse_block(context, input, start, |i| {
288                    Self::parse_in_parenthesis_block(context, i)
289                }) {
290                    return Ok(nested);
291                }
292                // Accept <ident>: <any-value> as a GeneralEnclosed (which evaluates
293                // to false, but does not invalidate the query as a whole).
294                input.parse_nested_block(|i| {
295                    i.expect_ident()?;
296                    i.expect_colon()?;
297                    consume_any_value(i)
298                })?;
299                Ok(Self::GeneralEnclosed(input.slice_from(start).to_owned()))
300            },
301            ref t => return Err(start_location.new_unexpected_token_error(t.clone())),
302        }
303    }
304
305    fn new_not(inner: Box<Self>) -> Self {
306        Self::Not(inner)
307    }
308
309    fn new_operation(conditions: Box<[Self]>, operator: Operator) -> Self {
310        Self::Operation(conditions, operator)
311    }
312}
313
314/// A style query feature:
315/// https://drafts.csswg.org/css-conditional-5/#typedef-style-feature
316#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem)]
317pub enum StyleFeature {
318    /// A property name and optional value to match.
319    Plain(StyleFeaturePlain),
320    /// A style query range expression.
321    Range(QueryStyleRange),
322}
323
324impl StyleFeature {
325    fn parse<'i, 't>(
326        context: &ParserContext,
327        input: &mut Parser<'i, 't>,
328    ) -> Result<Self, ParseError<'i>> {
329        if let Ok(range) = input.try_parse(|i| QueryStyleRange::parse(context, i)) {
330            return Ok(Self::Range(range));
331        }
332
333        Ok(Self::Plain(StyleFeaturePlain::parse(context, input)?))
334    }
335
336    fn matches(
337        &self,
338        ctx: &computed::Context,
339        attribute_tracker: &mut AttributeTracker,
340    ) -> KleeneValue {
341        match self {
342            Self::Plain(plain) => plain.matches(ctx, attribute_tracker),
343            Self::Range(range) => range.evaluate(ctx, attribute_tracker),
344        }
345    }
346}
347
348/// A style feature consisting of a custom property name and (optionally) value.
349#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
350pub struct StyleFeaturePlain {
351    name: custom_properties::Name,
352    #[ignore_malloc_size_of = "StyleFeatureValue has an Arc variant"]
353    value: StyleFeatureValue,
354}
355
356impl ToCss for StyleFeaturePlain {
357    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
358    where
359        W: fmt::Write,
360    {
361        dest.write_str("--")?;
362        crate::values::serialize_atom_identifier(&self.name, dest)?;
363        match self.value {
364            StyleFeatureValue::Keyword(k) => {
365                dest.write_str(": ")?;
366                k.to_css(dest)?;
367            },
368            StyleFeatureValue::Value(Some(ref v)) => {
369                dest.write_str(": ")?;
370                v.to_css(dest)?;
371            },
372            StyleFeatureValue::Value(None) => (),
373        }
374        Ok(())
375    }
376}
377
378impl StyleFeaturePlain {
379    fn parse<'i, 't>(
380        context: &ParserContext,
381        input: &mut Parser<'i, 't>,
382    ) -> Result<Self, ParseError<'i>> {
383        let ident = input.expect_ident()?;
384        // TODO(emilio): Maybe support non-custom properties?
385        let name = match custom_properties::parse_name(ident.as_ref()) {
386            Ok(name) => custom_properties::Name::from(name),
387            Err(()) => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
388        };
389        let value = if input.try_parse(|i| i.expect_colon()).is_ok() {
390            input.skip_whitespace();
391            if let Ok(keyword) = input.try_parse(|i| CSSWideKeyword::parse(i)) {
392                StyleFeatureValue::Keyword(keyword)
393            } else {
394                let value = custom_properties::SpecifiedValue::parse(
395                    input,
396                    Some(&context.namespaces.prefixes),
397                    &context.url_data,
398                )?;
399                // `!important` is allowed (but ignored) after the value.
400                let _ = input.try_parse(parse_important);
401                StyleFeatureValue::Value(Some(Arc::new(value)))
402            }
403        } else {
404            StyleFeatureValue::Value(None)
405        };
406        Ok(Self { name, value })
407    }
408
409    // Substitute custom-property references in `value`, then re-parse and compute it,
410    // and compare against `current_value`.
411    fn substitute_and_compare(
412        value: &Arc<custom_properties::SpecifiedValue>,
413        registration: &PropertyDescriptors,
414        stylist: &Stylist,
415        ctx: &computed::Context,
416        attribute_tracker: &mut AttributeTracker,
417        current_value: Option<&ComputedRegisteredValue>,
418    ) -> bool {
419        let substitution_functions = custom_properties::ComputedSubstitutionFunctions::new(
420            Some(ctx.inherited_custom_properties().clone()),
421            None,
422        );
423        let custom_properties::SubstitutionResult { css, attr_taint } =
424            match custom_properties::substitute(
425                &value,
426                &substitution_functions,
427                stylist,
428                ctx,
429                attribute_tracker,
430            ) {
431                Ok(sub) => sub,
432                Err(_) => return current_value.is_none(),
433            };
434        if registration.is_universal() {
435            return match current_value {
436                Some(v) => v.as_universal().is_some_and(|v| v.css == css),
437                None => css.is_empty(),
438            };
439        }
440        let mut input = cssparser::ParserInput::new(&css);
441        let mut parser = Parser::new(&mut input);
442        let computed = SpecifiedRegisteredValue::compute(
443            &mut parser,
444            registration,
445            None,
446            &value.url_data,
447            ctx,
448            AllowComputationallyDependent::Yes,
449            attr_taint,
450        )
451        .ok();
452        computed.as_ref() == current_value
453    }
454
455    fn matches(
456        &self,
457        ctx: &computed::Context,
458        attribute_tracker: &mut AttributeTracker,
459    ) -> KleeneValue {
460        // FIXME(emilio): Confirm this is the right style to query.
461        let stylist = ctx
462            .builder
463            .stylist
464            .expect("container queries should have a stylist around");
465        let registration = stylist.get_custom_property_registration(&self.name);
466        let current_value = ctx
467            .inherited_custom_properties()
468            .get(registration, &self.name);
469        KleeneValue::from(match self.value {
470            StyleFeatureValue::Value(Some(ref v)) => {
471                if ctx.container_info.is_none() {
472                    // If no container, custom props are guaranteed-unknown.
473                    false
474                } else if v.has_references() {
475                    // If there are --var() references in the query value,
476                    // try to substitute them before comparing to current.
477                    Self::substitute_and_compare(
478                        v,
479                        registration,
480                        stylist,
481                        ctx,
482                        attribute_tracker,
483                        current_value,
484                    )
485                } else {
486                    custom_properties::compute_variable_value(&v, registration, ctx).as_ref()
487                        == current_value
488                }
489            },
490            StyleFeatureValue::Value(None) => current_value.is_some(),
491            StyleFeatureValue::Keyword(kw) => {
492                match kw {
493                    CSSWideKeyword::Unset => current_value.is_none(),
494                    CSSWideKeyword::Initial => {
495                        if let Some(initial) = &registration.initial_value {
496                            let v = custom_properties::compute_variable_value(
497                                &initial,
498                                registration,
499                                ctx,
500                            );
501                            v.as_ref() == current_value
502                        } else {
503                            current_value.is_none()
504                        }
505                    },
506                    CSSWideKeyword::Inherit => {
507                        if let Some(inherited) = ctx
508                            .container_info
509                            .as_ref()
510                            .expect("queries should provide container info")
511                            .inherited_style()
512                        {
513                            inherited.custom_properties().get(registration, &self.name)
514                                == current_value
515                        } else {
516                            false
517                        }
518                    },
519                    // Cascade-dependent keywords, such as revert and revert-layer,
520                    // are invalid as values in a style feature, and cause the
521                    // container style query to be false.
522                    // https://drafts.csswg.org/css-conditional-5/#evaluate-a-style-range
523                    CSSWideKeyword::Revert
524                    | CSSWideKeyword::RevertLayer
525                    | CSSWideKeyword::RevertRule => false,
526                }
527            },
528        })
529    }
530}
531
532/// A boolean value for a pref query.
533#[derive(
534    Clone,
535    Debug,
536    MallocSizeOf,
537    PartialEq,
538    Eq,
539    Parse,
540    SpecifiedValueInfo,
541    ToComputedValue,
542    ToCss,
543    ToShmem,
544)]
545#[repr(u8)]
546#[allow(missing_docs)]
547pub enum BoolValue {
548    False,
549    True,
550}
551
552/// Simple values we support for -moz-pref(). We don't want to deal with calc() and other
553/// shenanigans for now.
554#[derive(
555    Clone,
556    Debug,
557    Eq,
558    MallocSizeOf,
559    Parse,
560    PartialEq,
561    SpecifiedValueInfo,
562    ToComputedValue,
563    ToCss,
564    ToShmem,
565)]
566#[repr(u8)]
567pub enum MozPrefFeatureValue<I> {
568    /// No pref value, implicitly bool, but also used to represent missing prefs.
569    #[css(skip)]
570    None,
571    /// A bool value.
572    Boolean(BoolValue),
573    /// An integer value, useful for int prefs.
574    Integer(I),
575    /// A string pref value.
576    String(crate::values::AtomString),
577}
578
579type SpecifiedMozPrefFeatureValue = MozPrefFeatureValue<crate::values::specified::Integer>;
580/// The computed -moz-pref() value.
581pub type ComputedMozPrefFeatureValue = MozPrefFeatureValue<crate::values::computed::Integer>;
582
583/// A custom -moz-pref(<name>, <value>) query feature.
584#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
585pub struct MozPrefFeature {
586    name: crate::values::AtomString,
587    value: SpecifiedMozPrefFeatureValue,
588}
589
590impl MozPrefFeature {
591    fn parse<'i, 't>(
592        context: &ParserContext,
593        input: &mut Parser<'i, 't>,
594        feature_type: FeatureType,
595    ) -> Result<Self, ParseError<'i>> {
596        use crate::parser::Parse;
597        if !context.chrome_rules_enabled() || feature_type != FeatureType::Media {
598            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
599        }
600        let name = AtomString::parse(context, input)?;
601        let value = if input.try_parse(|i| i.expect_comma()).is_ok() {
602            SpecifiedMozPrefFeatureValue::parse(context, input)?
603        } else {
604            SpecifiedMozPrefFeatureValue::None
605        };
606        Ok(Self { name, value })
607    }
608
609    #[cfg(feature = "gecko")]
610    fn matches(&self, ctx: &computed::Context) -> KleeneValue {
611        use crate::values::computed::ToComputedValue;
612        let value = self.value.to_computed_value(ctx);
613        KleeneValue::from(unsafe {
614            crate::gecko_bindings::bindings::Gecko_EvalMozPrefFeature(self.name.as_ptr(), &value)
615        })
616    }
617
618    #[cfg(feature = "servo")]
619    fn matches(&self, _: &computed::Context) -> KleeneValue {
620        KleeneValue::Unknown
621    }
622}
623
624impl ToCss for MozPrefFeature {
625    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
626    where
627        W: fmt::Write,
628    {
629        self.name.to_css(dest)?;
630        if !matches!(self.value, MozPrefFeatureValue::None) {
631            dest.write_str(", ")?;
632            self.value.to_css(dest)?;
633        }
634        Ok(())
635    }
636}
637
638/// Represents a condition.
639#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
640pub enum QueryCondition {
641    /// A simple feature expression, implicitly parenthesized.
642    Feature(QueryFeatureExpression),
643    /// A custom media query reference in a boolean context, implicitly parenthesized.
644    Custom(DashedIdent),
645    /// A negation of a condition.
646    Not(Box<QueryCondition>),
647    /// A set of joint operations.
648    Operation(Box<[QueryCondition]>, Operator),
649    /// A condition wrapped in parenthesis.
650    InParens(Box<QueryCondition>),
651    /// A <style> query.
652    Style(StyleQuery),
653    /// A -moz-pref() query.
654    MozPref(MozPrefFeature),
655    /// [ <function-token> <any-value>? ) ] | [ ( <any-value>? ) ]
656    GeneralEnclosed(String, UrlExtraData),
657}
658
659impl ToCss for QueryCondition {
660    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
661    where
662        W: fmt::Write,
663    {
664        match *self {
665            // NOTE(emilio): QueryFeatureExpression already includes the
666            // parenthesis.
667            QueryCondition::Feature(ref f) => f.to_css(dest),
668            QueryCondition::Custom(ref name) => {
669                dest.write_char('(')?;
670                name.to_css(dest)?;
671                dest.write_char(')')
672            },
673            QueryCondition::Not(ref c) => {
674                dest.write_str("not ")?;
675                c.to_css(dest)
676            },
677            QueryCondition::InParens(ref c) => {
678                dest.write_char('(')?;
679                c.to_css(dest)?;
680                dest.write_char(')')
681            },
682            QueryCondition::Style(ref c) => {
683                dest.write_str("style(")?;
684                c.to_css(dest)?;
685                dest.write_char(')')
686            },
687            QueryCondition::MozPref(ref c) => {
688                dest.write_str("-moz-pref(")?;
689                c.to_css(dest)?;
690                dest.write_char(')')
691            },
692            QueryCondition::Operation(ref list, op) => {
693                let mut iter = list.iter();
694                iter.next().unwrap().to_css(dest)?;
695                for item in iter {
696                    dest.write_char(' ')?;
697                    op.to_css(dest)?;
698                    dest.write_char(' ')?;
699                    item.to_css(dest)?;
700                }
701                Ok(())
702            },
703            QueryCondition::GeneralEnclosed(ref s, _) => dest.write_str(&s),
704        }
705    }
706}
707
708/// <https://drafts.csswg.org/css-syntax-3/#typedef-any-value>
709fn consume_any_value<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(), ParseError<'i>> {
710    input.expect_no_error_token().map_err(Into::into)
711}
712
713impl QueryCondition {
714    /// Parse a single condition.
715    pub fn parse<'i, 't>(
716        context: &ParserContext,
717        input: &mut Parser<'i, 't>,
718        feature_type: FeatureType,
719    ) -> Result<Self, ParseError<'i>> {
720        Self::parse_internal(context, input, feature_type, AllowOr::Yes)
721    }
722
723    fn visit<F>(&self, visitor: &mut F)
724    where
725        F: FnMut(&Self),
726    {
727        visitor(self);
728        match *self {
729            Self::Custom(..)
730            | Self::Feature(..)
731            | Self::GeneralEnclosed(..)
732            | Self::Style(..)
733            | Self::MozPref(..) => {},
734            Self::Not(ref cond) => cond.visit(visitor),
735            Self::Operation(ref conds, _op) => {
736                for cond in conds.iter() {
737                    cond.visit(visitor);
738                }
739            },
740            Self::InParens(ref cond) => cond.visit(visitor),
741        }
742    }
743
744    /// Returns the union of all flags in the expression. This is useful for
745    /// container queries.
746    pub fn cumulative_flags(&self) -> FeatureFlags {
747        let mut result = FeatureFlags::empty();
748        self.visit(&mut |condition| {
749            if let Self::Style(..) = condition {
750                result.insert(FeatureFlags::STYLE);
751            }
752            if let Self::Feature(ref f) = condition {
753                result.insert(f.feature_flags())
754            }
755        });
756        result
757    }
758
759    /// Parse a single condition, disallowing `or` expressions.
760    ///
761    /// To be used from the legacy query syntax.
762    pub fn parse_disallow_or<'i, 't>(
763        context: &ParserContext,
764        input: &mut Parser<'i, 't>,
765        feature_type: FeatureType,
766    ) -> Result<Self, ParseError<'i>> {
767        Self::parse_internal(context, input, feature_type, AllowOr::No)
768    }
769
770    fn parse_in_parenthesis_block<'i>(
771        context: &ParserContext,
772        input: &mut Parser<'i, '_>,
773        feature_type: FeatureType,
774    ) -> Result<Self, ParseError<'i>> {
775        // Base case. Make sure to preserve this error as it's more generally
776        // relevant.
777        let feature_error = match input.try_parse(|input| {
778            QueryFeatureExpression::parse_in_parenthesis_block(context, input, feature_type)
779        }) {
780            Ok(expr) => return Ok(Self::Feature(expr)),
781            Err(e) => e,
782        };
783        if static_prefs::pref!("layout.css.custom-media.enabled") {
784            if let Ok(custom) = input.try_parse(|input| DashedIdent::parse(context, input)) {
785                return Ok(Self::Custom(custom));
786            }
787        }
788        if let Ok(inner) = Self::parse(context, input, feature_type) {
789            return Ok(Self::InParens(Box::new(inner)));
790        }
791        Err(feature_error)
792    }
793
794    fn try_parse_block<'i, T, F>(
795        context: &ParserContext,
796        input: &mut Parser<'i, '_>,
797        start: SourcePosition,
798        parse: F,
799    ) -> Option<T>
800    where
801        F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i>>,
802    {
803        let nested = input.try_parse(|input| input.parse_nested_block(parse));
804        match nested {
805            Ok(nested) => Some(nested),
806            Err(e) => {
807                // We're about to swallow the error in a `<general-enclosed>`
808                // condition, so report it while we can.
809                let loc = e.location;
810                let error = ContextualParseError::InvalidMediaRule(input.slice_from(start), e);
811                context.log_css_error(loc, error);
812                None
813            },
814        }
815    }
816
817    /// Whether this condition matches the device and quirks mode.
818    /// https://drafts.csswg.org/mediaqueries/#evaluating
819    /// https://drafts.csswg.org/mediaqueries/#typedef-general-enclosed
820    /// Kleene 3-valued logic is adopted here due to the introduction of
821    /// <general-enclosed>.
822    pub fn matches(
823        &self,
824        context: &computed::Context,
825        custom: &mut CustomMediaEvaluator,
826        attribute_tracker: &mut AttributeTracker,
827    ) -> KleeneValue {
828        match *self {
829            Self::Custom(ref f) => custom.matches(f, context),
830            Self::Feature(ref f) => f.matches(context),
831            Self::GeneralEnclosed(ref str, ref url_data) => {
832                self.matches_general(&str, url_data, context, custom, attribute_tracker)
833            },
834            Self::InParens(ref c) => c.matches(context, custom, attribute_tracker),
835            Self::Not(ref c) => !c.matches(context, custom, attribute_tracker),
836            Self::Style(ref c) => c.matches(context, attribute_tracker),
837            Self::MozPref(ref c) => c.matches(context),
838            Self::Operation(ref conditions, op) => {
839                debug_assert!(!conditions.is_empty(), "We never create an empty op");
840                match op {
841                    Operator::And => KleeneValue::any_false(conditions.iter(), |c| {
842                        c.matches(context, custom, attribute_tracker)
843                    }),
844                    Operator::Or => KleeneValue::any(conditions.iter(), |c| {
845                        c.matches(context, custom, attribute_tracker)
846                    }),
847                }
848            },
849        }
850    }
851
852    /// For a condition that was parsed as GeneralEnclosed, try applying custom-property
853    /// substitution and re-parse the result.
854    fn matches_general(
855        &self,
856        css_text: &str,
857        url_data: &UrlExtraData,
858        context: &computed::Context,
859        custom: &mut CustomMediaEvaluator,
860        attribute_tracker: &mut AttributeTracker,
861    ) -> KleeneValue {
862        // This only applies (currently, at least) to container queries.
863        if !context.in_container_query {
864            return KleeneValue::Unknown;
865        }
866
867        let stylist = context
868            .builder
869            .stylist
870            .expect("container query should provide a Stylist");
871
872        // Parse the text as a custom-property value to identify references.
873        let mut input = ParserInput::new(css_text);
874        let value = match custom_properties::SpecifiedValue::parse(
875            &mut Parser::new(&mut input),
876            None, // TODO: what Namespaces should we pass here?
877            url_data,
878        ) {
879            Ok(val) => val,
880            Err(_) => return KleeneValue::Unknown,
881        };
882
883        // If no references, we're not going to end up with a new result, just bail out.
884        if !value.has_references() {
885            return KleeneValue::Unknown;
886        }
887
888        // Substitute var() functions if possible.
889        let substitution_functions = custom_properties::ComputedSubstitutionFunctions::new(
890            Some(context.inherited_custom_properties().clone()),
891            None,
892        );
893        let custom_properties::SubstitutionResult { css, attr_taint } =
894            match custom_properties::substitute(
895                &value,
896                &substitution_functions,
897                stylist,
898                context,
899                attribute_tracker,
900            ) {
901                Ok(sub) => sub,
902                Err(_) => return KleeneValue::Unknown,
903            };
904
905        // Re-parse the result as a query-condition, and evaluate it.
906        let parser_context = ParserContext::new(
907            Origin::Author,
908            url_data,
909            Some(CssRuleType::Container),
910            ParsingMode::DEFAULT,
911            QuirksMode::NoQuirks,
912            /* namespaces = */ Default::default(),
913            /* error_reporter = */ None,
914            /* use_counters = */ None,
915            attr_taint,
916        );
917        let mut input = ParserInput::new(&css);
918        let result = match Self::parse(
919            &parser_context,
920            &mut Parser::new(&mut input),
921            FeatureType::Container,
922        ) {
923            Ok(Self::GeneralEnclosed(..)) => {
924                // If the result is still GeneralEnclosed, the query is unknown.
925                KleeneValue::Unknown
926            },
927            Ok(query) => query.matches(context, custom, attribute_tracker),
928            Err(_) => KleeneValue::Unknown,
929        };
930
931        result
932    }
933}
934
935impl OperationParser for QueryCondition {
936    /// Parse a condition in parentheses, or `<general-enclosed>`.
937    ///
938    /// https://drafts.csswg.org/mediaqueries/#typedef-media-in-parens
939    fn parse_in_parens<'i, 't>(
940        context: &ParserContext,
941        input: &mut Parser<'i, 't>,
942        feature_type: FeatureType,
943    ) -> Result<Self, ParseError<'i>> {
944        input.skip_whitespace();
945        let start = input.position();
946        let start_location = input.current_source_location();
947        match *input.next()? {
948            Token::ParenthesisBlock => {
949                let nested = Self::try_parse_block(context, input, start, |input| {
950                    Self::parse_in_parenthesis_block(context, input, feature_type)
951                });
952                if let Some(nested) = nested {
953                    return Ok(nested);
954                }
955            },
956            Token::Function(ref name) => {
957                match_ignore_ascii_case! { name,
958                    "style" => {
959                        let query = Self::try_parse_block(context, input, start, |input| {
960                            StyleQuery::parse(context, input, feature_type)
961                        });
962                        if let Some(query) = query {
963                            return Ok(Self::Style(query));
964                        }
965                    },
966                    "-moz-pref" => {
967                        let feature = Self::try_parse_block(context, input, start, |input| {
968                            MozPrefFeature::parse(context, input, feature_type)
969                        });
970                        if let Some(feature) = feature {
971                            return Ok(Self::MozPref(feature));
972                        }
973                    },
974                    _ => {},
975                }
976            },
977            ref t => return Err(start_location.new_unexpected_token_error(t.clone())),
978        }
979        input.parse_nested_block(consume_any_value)?;
980        Ok(Self::GeneralEnclosed(
981            input.slice_from(start).to_owned(),
982            context.url_data.clone(),
983        ))
984    }
985
986    fn new_not(inner: Box<Self>) -> Self {
987        Self::Not(inner)
988    }
989
990    fn new_operation(conditions: Box<[Self]>, operator: Operator) -> Self {
991        Self::Operation(conditions, operator)
992    }
993}