1use super::feature::{Evaluator, QueryFeatureDescription};
9use super::feature::{FeatureFlags, KeywordDiscriminant};
10use crate::context::QuirksMode;
11use crate::custom_properties::{
12 self, ComputedSubstitutionFunctions, VariableValue as CustomVariableValue,
13};
14use crate::derives::*;
15use crate::dom::AttributeTracker;
16use crate::parser::{Parse, ParserContext};
17use crate::properties::{self, CSSWideKeyword};
18use crate::properties_and_values::value::{ComputedValueComponent as Component, ValueInner};
19use crate::selector_map::PrecomputedHashSet;
20use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase};
21use crate::stylesheets::{CssRuleType, Origin, UrlExtraData};
22use crate::values::computed::{self, CSSPixelLength, Ratio, ToComputedValue};
23use crate::values::specified::{Angle, Integer, Length, Number, Percentage, Resolution, Time};
24use crate::values::{CSSFloat, DashedIdent};
25use crate::{Atom, Zero};
26use cssparser::{Parser, ParserInput, Token};
27use selectors::kleene_value::KleeneValue;
28use std::cmp::Ordering;
29use std::fmt::{self, Write};
30use style_traits::{CssWriter, ParseError, ParsingMode, StyleParseErrorKind, ToCss};
31
32#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
34pub enum FeatureType {
35 Media,
37 Container,
39}
40
41impl FeatureType {
42 fn features(&self) -> &'static [QueryFeatureDescription] {
43 #[cfg(feature = "gecko")]
44 use crate::gecko::media_features::MEDIA_FEATURES;
45 #[cfg(feature = "servo")]
46 use crate::servo::media_features::MEDIA_FEATURES;
47
48 use crate::stylesheets::container_rule::CONTAINER_FEATURES;
49
50 match *self {
51 FeatureType::Media => &MEDIA_FEATURES,
52 FeatureType::Container => &CONTAINER_FEATURES,
53 }
54 }
55
56 fn find_feature(&self, name: &Atom) -> Option<(usize, &'static QueryFeatureDescription)> {
57 self.features()
58 .iter()
59 .enumerate()
60 .find(|(_, f)| f.name == *name)
61 }
62}
63
64#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
66enum LegacyRange {
67 Min,
69 Max,
71}
72
73#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
75pub enum Operator {
76 Equal,
78 GreaterThan,
80 GreaterThanEqual,
82 LessThan,
84 LessThanEqual,
86}
87
88impl ToCss for Operator {
89 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
90 where
91 W: fmt::Write,
92 {
93 dest.write_str(match *self {
94 Self::Equal => "=",
95 Self::LessThan => "<",
96 Self::LessThanEqual => "<=",
97 Self::GreaterThan => ">",
98 Self::GreaterThanEqual => ">=",
99 })
100 }
101}
102
103impl Operator {
104 fn is_compatible_with(self, right_op: Self) -> bool {
105 match self {
108 Self::Equal => false,
109 Self::GreaterThan | Self::GreaterThanEqual => {
110 matches!(right_op, Self::GreaterThan | Self::GreaterThanEqual)
111 },
112 Self::LessThan | Self::LessThanEqual => {
113 matches!(right_op, Self::LessThan | Self::LessThanEqual)
114 },
115 }
116 }
117
118 fn evaluate(&self, cmp: Ordering) -> bool {
119 match *self {
120 Self::Equal => cmp == Ordering::Equal,
121 Self::GreaterThan => cmp == Ordering::Greater,
122 Self::GreaterThanEqual => cmp == Ordering::Equal || cmp == Ordering::Greater,
123 Self::LessThan => cmp == Ordering::Less,
124 Self::LessThanEqual => cmp == Ordering::Equal || cmp == Ordering::Less,
125 }
126 }
127
128 fn parse<'i>(input: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
129 let location = input.current_source_location();
130 let operator = match *input.next()? {
131 Token::Delim('=') => return Ok(Operator::Equal),
132 Token::Delim('>') => Operator::GreaterThan,
133 Token::Delim('<') => Operator::LessThan,
134 ref t => return Err(location.new_unexpected_token_error(t.clone())),
135 };
136
137 let parsed_equal = input
146 .try_parse(|i| {
147 let t = i.next_including_whitespace().map_err(|_| ())?;
148 if !matches!(t, Token::Delim('=')) {
149 return Err(());
150 }
151 Ok(())
152 })
153 .is_ok();
154
155 if !parsed_equal {
156 return Ok(operator);
157 }
158
159 Ok(match operator {
160 Operator::GreaterThan => Operator::GreaterThanEqual,
161 Operator::LessThan => Operator::LessThanEqual,
162 _ => unreachable!(),
163 })
164 }
165}
166
167#[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)]
168enum QueryFeatureExpressionKind {
169 Empty,
171
172 Single(QueryExpressionValue),
174
175 LegacyRange(LegacyRange, QueryExpressionValue),
177
178 Range {
181 left: Option<(Operator, QueryExpressionValue)>,
182 right: Option<(Operator, QueryExpressionValue)>,
183 },
184}
185
186impl QueryFeatureExpressionKind {
187 fn evaluate<T>(
190 &self,
191 context_value: T,
192 mut compute: impl FnMut(&QueryExpressionValue) -> T,
193 ) -> bool
194 where
195 T: PartialOrd + Zero,
196 {
197 match *self {
198 Self::Empty => return !context_value.is_zero(),
199 Self::Single(ref value) => {
200 let value = compute(value);
201 let cmp = match context_value.partial_cmp(&value) {
202 Some(c) => c,
203 None => return false,
204 };
205 cmp == Ordering::Equal
206 },
207 Self::LegacyRange(ref range, ref value) => {
208 let value = compute(value);
209 let cmp = match context_value.partial_cmp(&value) {
210 Some(c) => c,
211 None => return false,
212 };
213 cmp == Ordering::Equal
214 || match range {
215 LegacyRange::Min => cmp == Ordering::Greater,
216 LegacyRange::Max => cmp == Ordering::Less,
217 }
218 },
219 Self::Range {
220 ref left,
221 ref right,
222 } => {
223 debug_assert!(left.is_some() || right.is_some());
224 if let Some((ref op, ref value)) = left {
225 let value = compute(value);
226 let cmp = match value.partial_cmp(&context_value) {
227 Some(c) => c,
228 None => return false,
229 };
230 if !op.evaluate(cmp) {
231 return false;
232 }
233 }
234 if let Some((ref op, ref value)) = right {
235 let value = compute(value);
236 let cmp = match context_value.partial_cmp(&value) {
237 Some(c) => c,
238 None => return false,
239 };
240 if !op.evaluate(cmp) {
241 return false;
242 }
243 }
244 true
245 },
246 }
247 }
248
249 fn non_ranged_value(&self) -> Option<&QueryExpressionValue> {
251 match *self {
252 Self::Empty => None,
253 Self::Single(ref v) => Some(v),
254 Self::LegacyRange(..) | Self::Range { .. } => {
255 debug_assert!(false, "Unexpected ranged value in non-ranged feature!");
256 None
257 },
258 }
259 }
260}
261
262#[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)]
265pub struct QueryFeatureExpression {
266 feature_type: FeatureType,
267 feature_index: usize,
268 kind: QueryFeatureExpressionKind,
269}
270
271impl ToCss for QueryFeatureExpression {
272 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
273 where
274 W: fmt::Write,
275 {
276 dest.write_char('(')?;
277
278 match self.kind {
279 QueryFeatureExpressionKind::Empty => self.write_name(dest)?,
280 QueryFeatureExpressionKind::Single(ref v)
281 | QueryFeatureExpressionKind::LegacyRange(_, ref v) => {
282 self.write_name(dest)?;
283 dest.write_str(": ")?;
284 v.to_css(dest, Some(self))?;
285 },
286 QueryFeatureExpressionKind::Range {
287 ref left,
288 ref right,
289 } => {
290 if let Some((ref op, ref val)) = left {
291 val.to_css(dest, Some(self))?;
292 dest.write_char(' ')?;
293 op.to_css(dest)?;
294 dest.write_char(' ')?;
295 }
296 self.write_name(dest)?;
297 if let Some((ref op, ref val)) = right {
298 dest.write_char(' ')?;
299 op.to_css(dest)?;
300 dest.write_char(' ')?;
301 val.to_css(dest, Some(self))?;
302 }
303 },
304 }
305 dest.write_char(')')
306 }
307}
308
309fn consume_operation_or_colon<'i>(
310 input: &mut Parser<'i, '_>,
311) -> Result<Option<Operator>, ParseError<'i>> {
312 if input.try_parse(|input| input.expect_colon()).is_ok() {
313 return Ok(None);
314 }
315 Operator::parse(input).map(|op| Some(op))
316}
317
318#[allow(unused_variables)]
319fn disabled_by_pref(feature: &Atom, context: &ParserContext) -> bool {
320 #[cfg(feature = "gecko")]
321 {
322 if *feature == atom!("prefers-reduced-transparency") {
325 return !context.chrome_rules_enabled()
326 && !static_prefs::pref!("layout.css.prefers-reduced-transparency.enabled");
327 }
328
329 if *feature == atom!("inverted-colors") {
332 return !context.chrome_rules_enabled()
333 && !static_prefs::pref!("layout.css.inverted-colors.enabled");
334 }
335 }
336 false
337}
338
339impl QueryFeatureExpression {
340 fn new(
341 feature_type: FeatureType,
342 feature_index: usize,
343 kind: QueryFeatureExpressionKind,
344 ) -> Self {
345 debug_assert!(feature_index < feature_type.features().len());
346 Self {
347 feature_type,
348 feature_index,
349 kind,
350 }
351 }
352
353 fn write_name<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
354 where
355 W: fmt::Write,
356 {
357 let feature = self.feature();
358 if feature.flags.contains(FeatureFlags::WEBKIT_PREFIX) {
359 dest.write_str("-webkit-")?;
360 }
361
362 if let QueryFeatureExpressionKind::LegacyRange(range, _) = self.kind {
363 match range {
364 LegacyRange::Min => dest.write_str("min-")?,
365 LegacyRange::Max => dest.write_str("max-")?,
366 }
367 }
368
369 write!(dest, "{}", feature.name)?;
371
372 Ok(())
373 }
374
375 fn feature(&self) -> &'static QueryFeatureDescription {
376 &self.feature_type.features()[self.feature_index]
377 }
378
379 pub fn feature_flags(&self) -> FeatureFlags {
381 self.feature().flags
382 }
383
384 fn parse_feature_name<'i, 't>(
385 context: &ParserContext,
386 input: &mut Parser<'i, 't>,
387 feature_type: FeatureType,
388 ) -> Result<(usize, Option<LegacyRange>), ParseError<'i>> {
389 let mut flags = FeatureFlags::empty();
390 let location = input.current_source_location();
391 let ident = input.expect_ident()?;
392
393 if context.chrome_rules_enabled() {
394 flags.insert(FeatureFlags::CHROME_AND_UA_ONLY);
395 }
396
397 let mut feature_name = &**ident;
398 if starts_with_ignore_ascii_case(feature_name, "-webkit-") {
399 feature_name = &feature_name[8..];
400 flags.insert(FeatureFlags::WEBKIT_PREFIX);
401 }
402
403 let range = if starts_with_ignore_ascii_case(feature_name, "min-") {
404 feature_name = &feature_name[4..];
405 Some(LegacyRange::Min)
406 } else if starts_with_ignore_ascii_case(feature_name, "max-") {
407 feature_name = &feature_name[4..];
408 Some(LegacyRange::Max)
409 } else {
410 None
411 };
412
413 let atom = Atom::from(string_as_ascii_lowercase(feature_name));
414 let (feature_index, feature) = match feature_type.find_feature(&atom) {
415 Some((i, f)) => (i, f),
416 None => {
417 return Err(location.new_custom_error(
418 StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
419 ))
420 },
421 };
422
423 if disabled_by_pref(&feature.name, context)
424 || !flags.contains(feature.flags.parsing_requirements())
425 || (range.is_some() && !feature.allows_ranges())
426 {
427 return Err(location.new_custom_error(
428 StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
429 ));
430 }
431
432 Ok((feature_index, range))
433 }
434
435 fn parse_multi_range_syntax<'i, 't>(
440 context: &ParserContext,
441 input: &mut Parser<'i, 't>,
442 feature_type: FeatureType,
443 ) -> Result<Self, ParseError<'i>> {
444 let start = input.state();
445
446 let feature_index = loop {
450 if let Ok((index, range)) = Self::parse_feature_name(context, input, feature_type) {
452 if range.is_some() {
453 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
455 }
456 break index;
457 }
458 if input.is_exhausted() {
459 return Err(start
460 .source_location()
461 .new_custom_error(StyleParseErrorKind::UnspecifiedError));
462 }
463 };
464
465 input.reset(&start);
466
467 let feature = &feature_type.features()[feature_index];
468 let left_val = QueryExpressionValue::parse(feature, context, input)?;
469 let left_op = Operator::parse(input)?;
470
471 {
472 let (parsed_index, _) = Self::parse_feature_name(context, input, feature_type)?;
473 debug_assert_eq!(
474 parsed_index, feature_index,
475 "How did we find a different feature?"
476 );
477 }
478
479 let right_op = input.try_parse(Operator::parse).ok();
480 let right = match right_op {
481 Some(op) => {
482 if !left_op.is_compatible_with(op) {
483 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
484 }
485 Some((op, QueryExpressionValue::parse(feature, context, input)?))
486 },
487 None => None,
488 };
489 Ok(Self::new(
490 feature_type,
491 feature_index,
492 QueryFeatureExpressionKind::Range {
493 left: Some((left_op, left_val)),
494 right,
495 },
496 ))
497 }
498
499 pub fn parse_in_parenthesis_block<'i, 't>(
501 context: &ParserContext,
502 input: &mut Parser<'i, 't>,
503 feature_type: FeatureType,
504 ) -> Result<Self, ParseError<'i>> {
505 let (feature_index, range) =
506 match input.try_parse(|input| Self::parse_feature_name(context, input, feature_type)) {
507 Ok(v) => v,
508 Err(e) => {
509 if let Ok(expr) = Self::parse_multi_range_syntax(context, input, feature_type) {
510 return Ok(expr);
511 }
512 return Err(e);
513 },
514 };
515 let operator = input.try_parse(consume_operation_or_colon);
516 let operator = match operator {
517 Err(..) => {
518 if range.is_some() {
524 return Err(
525 input.new_custom_error(StyleParseErrorKind::RangedExpressionWithNoValue)
526 );
527 }
528
529 return Ok(Self::new(
530 feature_type,
531 feature_index,
532 QueryFeatureExpressionKind::Empty,
533 ));
534 },
535 Ok(operator) => operator,
536 };
537
538 let feature = &feature_type.features()[feature_index];
539
540 let value = QueryExpressionValue::parse(feature, context, input).map_err(|err| {
541 err.location
542 .new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue)
543 })?;
544
545 let kind = match range {
546 Some(range) => {
547 if operator.is_some() {
548 return Err(
549 input.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator)
550 );
551 }
552 QueryFeatureExpressionKind::LegacyRange(range, value)
553 },
554 None => match operator {
555 Some(operator) => {
556 if !feature.allows_ranges() {
557 return Err(input
558 .new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator));
559 }
560 QueryFeatureExpressionKind::Range {
561 left: None,
562 right: Some((operator, value)),
563 }
564 },
565 None => QueryFeatureExpressionKind::Single(value),
566 },
567 };
568
569 Ok(Self::new(feature_type, feature_index, kind))
570 }
571
572 pub fn matches(&self, context: &computed::Context) -> KleeneValue {
574 macro_rules! expect {
575 ($variant:ident, $v:expr) => {
576 match *$v {
577 QueryExpressionValue::$variant(ref v) => v,
578 _ => unreachable!("Unexpected QueryExpressionValue"),
579 }
580 };
581 }
582
583 KleeneValue::from(match self.feature().evaluator {
584 Evaluator::Length(eval) => {
585 let v = eval(context);
586 self.kind
587 .evaluate(v, |v| expect!(Length, v).to_computed_value(context))
588 },
589 Evaluator::OptionalLength(eval) => {
590 let v = match eval(context) {
591 Some(v) => v,
592 None => return KleeneValue::Unknown,
593 };
594 self.kind
595 .evaluate(v, |v| expect!(Length, v).to_computed_value(context))
596 },
597 Evaluator::Integer(eval) => {
598 let v = eval(context);
599 self.kind.evaluate(v, |v| *expect!(Integer, v))
600 },
601 Evaluator::Float(eval) => {
602 let v = eval(context);
603 self.kind.evaluate(v, |v| *expect!(Float, v))
604 },
605 Evaluator::NumberRatio(eval) => {
606 let ratio = eval(context);
607 self.kind
612 .evaluate(ratio, |v| expect!(NumberRatio, v).used_value())
613 },
614 Evaluator::OptionalNumberRatio(eval) => {
615 let ratio = match eval(context) {
616 Some(v) => v,
617 None => return KleeneValue::Unknown,
618 };
619 self.kind
621 .evaluate(ratio, |v| expect!(NumberRatio, v).used_value())
622 },
623 Evaluator::Resolution(eval) => {
624 let v = eval(context).dppx();
625 self.kind.evaluate(v, |v| {
626 expect!(Resolution, v).to_computed_value(context).dppx()
627 })
628 },
629 Evaluator::Enumerated { evaluator, .. } => {
630 let computed = self
631 .kind
632 .non_ranged_value()
633 .map(|v| *expect!(Enumerated, v));
634 return evaluator(context, computed);
635 },
636 Evaluator::BoolInteger(eval) => {
637 let computed = self
638 .kind
639 .non_ranged_value()
640 .map(|v| *expect!(BoolInteger, v));
641 let boolean = eval(context);
642 computed.map_or(boolean, |v| v == boolean)
643 },
644 })
645 }
646}
647
648#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
657pub enum QueryExpressionValue {
658 Length(Length),
660 Integer(i32),
662 Float(CSSFloat),
664 BoolInteger(bool),
666 NumberRatio(Ratio),
669 Resolution(Resolution),
671 Enumerated(KeywordDiscriminant),
674 Keyword(CSSWideKeyword),
677 Percentage(Percentage),
679 Angle(Angle),
681 Time(Time),
683 Custom(DashedIdent),
685 Function(Box<CustomVariableValue>),
689}
690
691impl QueryExpressionValue {
692 fn to_css<W>(
693 &self,
694 dest: &mut CssWriter<W>,
695 for_expr: Option<&QueryFeatureExpression>,
696 ) -> fmt::Result
697 where
698 W: fmt::Write,
699 {
700 match *self {
701 QueryExpressionValue::Length(ref l) => l.to_css(dest),
702 QueryExpressionValue::Integer(v) => v.to_css(dest),
703 QueryExpressionValue::Float(v) => v.to_css(dest),
704 QueryExpressionValue::BoolInteger(v) => dest.write_str(if v { "1" } else { "0" }),
705 QueryExpressionValue::NumberRatio(ratio) => ratio.to_css(dest),
706 QueryExpressionValue::Resolution(ref r) => r.to_css(dest),
707 QueryExpressionValue::Keyword(k) => k.to_css(dest),
708 QueryExpressionValue::Percentage(v) => v.to_css(dest),
709 QueryExpressionValue::Angle(v) => v.to_css(dest),
710 QueryExpressionValue::Time(v) => v.to_css(dest),
711 QueryExpressionValue::Custom(ref v) => v.to_css(dest),
712 QueryExpressionValue::Function(ref f) => f.to_css(dest),
713 QueryExpressionValue::Enumerated(value) => match for_expr
714 .expect("caller should have passed for_expr")
715 .feature()
716 .evaluator
717 {
718 Evaluator::Enumerated { serializer, .. } => dest.write_str(&*serializer(value)),
719 _ => unreachable!(),
720 },
721 }
722 }
723
724 fn parse<'i, 't>(
725 for_feature: &QueryFeatureDescription,
726 context: &ParserContext,
727 input: &mut Parser<'i, 't>,
728 ) -> Result<QueryExpressionValue, ParseError<'i>> {
729 Ok(match for_feature.evaluator {
730 Evaluator::OptionalLength(..) | Evaluator::Length(..) => {
731 let length = Length::parse(context, input)?;
732 QueryExpressionValue::Length(length)
733 },
734 Evaluator::Integer(..) => {
735 let integer = Integer::parse(context, input)?;
736 QueryExpressionValue::Integer(integer.value())
737 },
738 Evaluator::BoolInteger(..) => {
739 let integer = Integer::parse_non_negative(context, input)?;
740 let value = integer.value();
741 if value > 1 {
742 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
743 }
744 QueryExpressionValue::BoolInteger(value == 1)
745 },
746 Evaluator::Float(..) => {
747 let number = Number::parse(context, input)?;
748 QueryExpressionValue::Float(number.get())
749 },
750 Evaluator::OptionalNumberRatio(..) | Evaluator::NumberRatio(..) => {
751 use crate::values::specified::Ratio as SpecifiedRatio;
752 let ratio = SpecifiedRatio::parse(context, input)?;
753 QueryExpressionValue::NumberRatio(Ratio::new(ratio.0.get(), ratio.1.get()))
754 },
755 Evaluator::Resolution(..) => {
756 QueryExpressionValue::Resolution(Resolution::parse(context, input)?)
757 },
758 Evaluator::Enumerated { parser, .. } => {
759 QueryExpressionValue::Enumerated(parser(context, input)?)
760 },
761 })
762 }
763
764 fn parse_for_style_range<'i, 't>(
770 context: &ParserContext,
771 input: &mut Parser<'i, 't>,
772 ) -> Result<Self, ParseError<'i>> {
773 if let Ok(number) = input.try_parse(|i| Number::parse(context, i)) {
774 return Ok(Self::Float(number.get()));
775 }
776 if let Ok(percent) = input.try_parse(|i| Percentage::parse(context, i)) {
777 return Ok(Self::Percentage(percent));
778 }
779 if let Ok(length) = input.try_parse(|i| Length::parse(context, i)) {
780 return Ok(Self::Length(length));
781 }
782 if let Ok(angle) = input.try_parse(|i| Angle::parse(context, i)) {
783 return Ok(Self::Angle(angle));
784 }
785 if let Ok(time) = input.try_parse(|i| Time::parse(context, i)) {
786 return Ok(Self::Time(time));
787 }
788 if let Ok(resolution) = input.try_parse(|i| Resolution::parse(context, i)) {
789 return Ok(Self::Resolution(resolution));
790 }
791 if let Ok(ident) = input.try_parse(|i| DashedIdent::parse(context, i)) {
792 return Ok(Self::Custom(ident));
793 }
794 if let Ok(keyword) = input.try_parse(|i| CSSWideKeyword::parse(i)) {
795 return Ok(Self::Keyword(keyword));
796 }
797 input.skip_whitespace();
798 let start = input.position();
799 if let Ok(Token::Function(ref name)) = input.next() {
800 let parse_func =
803 |input: &mut Parser<'i, 't>| -> Result<CustomVariableValue, ParseError<'i>> {
804 input.parse_nested_block(|i| i.expect_no_error_token().map_err(Into::into))?;
805 let mut input = ParserInput::new(input.slice_from(start));
806 CustomVariableValue::parse(
807 &mut Parser::new(&mut input),
808 Some(&context.namespaces.prefixes),
809 context.url_data,
810 )
811 };
812
813 if properties::enabled_arbitrary_substitution_functions()
814 .iter()
815 .any(|n| n.eq_ignore_ascii_case(name))
816 {
817 return Ok(Self::Function(Box::new(parse_func(input)?)));
818 }
819 }
820 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
821 }
822}
823
824#[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)]
826pub enum QueryStyleRange {
827 #[allow(missing_docs)]
830 StyleRange2 {
831 value1: QueryExpressionValue,
832 op1: Operator,
833 value2: QueryExpressionValue,
834 },
835
836 #[allow(missing_docs)]
839 StyleRange3 {
840 value1: QueryExpressionValue,
841 op1: Operator,
842 value2: QueryExpressionValue,
843 op2: Operator,
844 value3: QueryExpressionValue,
845 },
846}
847
848impl ToCss for QueryStyleRange {
849 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
850 where
851 W: fmt::Write,
852 {
853 dest.write_char('(')?;
854 match self {
855 Self::StyleRange2 {
856 ref value1,
857 ref op1,
858 ref value2,
859 } => {
860 value1.to_css(dest, None)?;
861 dest.write_char(' ')?;
862 op1.to_css(dest)?;
863 dest.write_char(' ')?;
864 value2.to_css(dest, None)?;
865 },
866 Self::StyleRange3 {
867 ref value1,
868 ref op1,
869 ref value2,
870 ref op2,
871 ref value3,
872 } => {
873 value1.to_css(dest, None)?;
874 dest.write_char(' ')?;
875 op1.to_css(dest)?;
876 dest.write_char(' ')?;
877 value2.to_css(dest, None)?;
878 dest.write_char(' ')?;
879 op2.to_css(dest)?;
880 dest.write_char(' ')?;
881 value3.to_css(dest, None)?;
882 },
883 }
884 dest.write_char(')')
885 }
886}
887
888impl QueryStyleRange {
889 pub fn parse<'i, 't>(
897 context: &ParserContext,
898 input: &mut Parser<'i, 't>,
899 ) -> Result<Self, ParseError<'i>> {
900 let value1 = QueryExpressionValue::parse_for_style_range(context, input)?;
901 let op1 = Operator::parse(input)?;
902 let value2 = QueryExpressionValue::parse_for_style_range(context, input)?;
903
904 if let Ok(op2) = input.try_parse(|i| Operator::parse(i)) {
905 if op1.is_compatible_with(op2) {
906 let value3 = QueryExpressionValue::parse_for_style_range(context, input)?;
907 return Ok(Self::StyleRange3 {
908 value1,
909 op1,
910 value2,
911 op2,
912 value3,
913 });
914 }
915 }
916
917 Ok(Self::StyleRange2 {
918 value1,
919 op1,
920 value2,
921 })
922 }
923
924 pub fn evaluate(
926 &self,
927 context: &computed::Context,
928 attribute_tracker: &mut AttributeTracker,
929 ) -> KleeneValue {
930 match self {
931 QueryStyleRange::StyleRange2 {
932 ref value1,
933 ref op1,
934 ref value2,
935 } => Self::compare_values(
936 Self::resolve_value(
937 value1,
938 context,
939 attribute_tracker,
940 &mut PrecomputedHashSet::default(),
941 )
942 .as_ref(),
943 Self::resolve_value(
944 value2,
945 context,
946 attribute_tracker,
947 &mut PrecomputedHashSet::default(),
948 )
949 .as_ref(),
950 )
951 .is_some_and(|c| op1.evaluate(c))
952 .into(),
953
954 QueryStyleRange::StyleRange3 {
955 ref value1,
956 ref op1,
957 ref value2,
958 ref op2,
959 ref value3,
960 } => {
961 let v1 = Self::resolve_value(
962 value1,
963 context,
964 attribute_tracker,
965 &mut PrecomputedHashSet::default(),
966 );
967 let v2 = Self::resolve_value(
968 value2,
969 context,
970 attribute_tracker,
971 &mut PrecomputedHashSet::default(),
972 );
973 Self::compare_values(v1.as_ref(), v2.as_ref())
974 .is_some_and(|c1| {
975 op1.evaluate(c1)
976 && Self::compare_values(
977 v2.as_ref(),
978 Self::resolve_value(
979 value3,
980 context,
981 attribute_tracker,
982 &mut PrecomputedHashSet::default(),
983 )
984 .as_ref(),
985 )
986 .is_some_and(|c2| op2.evaluate(c2))
987 })
988 .into()
989 },
990 }
991 }
992
993 fn resolve_value(
995 value: &QueryExpressionValue,
996 context: &computed::Context,
997 attribute_tracker: &mut AttributeTracker,
998 visited_set: &mut PrecomputedHashSet<DashedIdent>,
999 ) -> Option<Component> {
1000 match value {
1001 QueryExpressionValue::Custom(ident) => {
1002 let name = ident.undashed();
1005 let stylist = context
1006 .builder
1007 .stylist
1008 .expect("container queries should have a stylist around");
1009 let registration = stylist.get_custom_property_registration(&name);
1010 let current_value = context
1011 .inherited_custom_properties()
1012 .get(registration, &name)?;
1013 match ¤t_value.v {
1014 ValueInner::Component(component) => Some(component.clone()),
1015 ValueInner::Universal(v) => {
1016 if visited_set.insert(ident.clone()) {
1020 Self::resolve_universal(
1021 &v.css,
1022 &v.url_data,
1023 context,
1024 attribute_tracker,
1025 visited_set,
1026 )
1027 } else {
1028 None
1029 }
1030 },
1031 ValueInner::List(_) => {
1032 debug_assert!(false, "We don't parse list values in style queries");
1033 None
1034 },
1035 }
1036 },
1037 QueryExpressionValue::Function(value) => {
1038 let sub_funcs = ComputedSubstitutionFunctions::new(
1039 Some(context.inherited_custom_properties().clone()),
1040 None,
1041 );
1042 let stylist = context
1043 .builder
1044 .stylist
1045 .expect("container queries should have a stylist around");
1046 let substituted = custom_properties::substitute(
1047 &value,
1048 &sub_funcs,
1049 stylist,
1050 context,
1051 attribute_tracker,
1052 )
1053 .ok()?;
1054 Self::resolve_universal(
1055 &substituted.css,
1056 &value.url_data,
1057 context,
1058 attribute_tracker,
1059 visited_set,
1060 )
1061 },
1062 QueryExpressionValue::Length(v) => {
1063 Some(Component::Length(v.to_computed_value(context)))
1064 },
1065 QueryExpressionValue::Float(v) => Some(Component::Number(v.to_computed_value(context))),
1066 QueryExpressionValue::Resolution(v) => {
1067 Some(Component::Resolution(v.to_computed_value(context)))
1068 },
1069 QueryExpressionValue::Percentage(v) => {
1070 Some(Component::Percentage(v.to_computed_value(context)))
1071 },
1072 QueryExpressionValue::Angle(v) => Some(Component::Angle(v.to_computed_value(context))),
1073 QueryExpressionValue::Time(v) => Some(Component::Time(v.to_computed_value(context))),
1074 QueryExpressionValue::Keyword(_) => None,
1077 _ => {
1078 debug_assert!(false, "unexpected value type in style range");
1079 None
1080 },
1081 }
1082 }
1083
1084 fn resolve_universal(
1091 css_text: &str,
1092 url_data: &UrlExtraData,
1093 context: &computed::Context,
1094 attribute_tracker: &mut AttributeTracker,
1095 visited_set: &mut PrecomputedHashSet<DashedIdent>,
1096 ) -> Option<Component> {
1097 let parser_context = ParserContext::new(
1098 Origin::Author,
1099 url_data,
1100 Some(CssRuleType::Container),
1101 ParsingMode::DEFAULT,
1102 QuirksMode::NoQuirks,
1103 Default::default(),
1104 None,
1105 None,
1106 Default::default(),
1107 );
1108 let mut input = ParserInput::new(css_text);
1109 QueryExpressionValue::parse_for_style_range(&parser_context, &mut Parser::new(&mut input))
1110 .ok()
1111 .and_then(|parsed| {
1112 Self::resolve_value(&parsed, context, attribute_tracker, visited_set)
1113 })
1114 }
1115
1116 fn compare_values(value1: Option<&Component>, value2: Option<&Component>) -> Option<Ordering> {
1117 let value1 = value1?;
1118 let value2 = value2?;
1119 match (value1, value2) {
1120 (Component::Length(v1), Component::Length(v2)) => v1.partial_cmp(&v2),
1121 (Component::Number(v1), Component::Number(v2)) => v1.partial_cmp(&v2),
1122 (Component::Resolution(v1), Component::Resolution(v2)) => {
1123 v1.dppx().partial_cmp(&v2.dppx())
1124 },
1125 (Component::Percentage(v1), Component::Percentage(v2)) => v1.partial_cmp(&v2),
1126 (Component::Angle(v1), Component::Angle(v2)) => v1.partial_cmp(&v2),
1127 (Component::Time(v1), Component::Time(v2)) => v1.partial_cmp(&v2),
1128 (Component::Length(v1), Component::Number(v2)) => {
1129 if v2.is_zero() {
1130 v1.partial_cmp(&CSSPixelLength::zero())
1131 } else {
1132 None
1133 }
1134 },
1135 (Component::Number(v1), Component::Length(v2)) => {
1136 if v1.is_zero() {
1137 CSSPixelLength::zero().partial_cmp(&v2)
1138 } else {
1139 None
1140 }
1141 },
1142 _ => None,
1143 }
1144 }
1145}