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