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