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