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