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