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}
682
683impl QueryExpressionValue {
684 fn to_css<W>(
685 &self,
686 dest: &mut CssWriter<W>,
687 for_expr: Option<&QueryFeatureExpression>,
688 ) -> fmt::Result
689 where
690 W: fmt::Write,
691 {
692 match *self {
693 QueryExpressionValue::Length(ref l) => l.to_css(dest),
694 QueryExpressionValue::Integer(v) => v.to_css(dest),
695 QueryExpressionValue::Float(v) => v.to_css(dest),
696 QueryExpressionValue::BoolInteger(v) => dest.write_str(if v { "1" } else { "0" }),
697 QueryExpressionValue::NumberRatio(ratio) => ratio.to_css(dest),
698 QueryExpressionValue::Resolution(ref r) => r.to_css(dest),
699 QueryExpressionValue::Keyword(k) => k.to_css(dest),
700 QueryExpressionValue::Percentage(v) => v.to_css(dest),
701 QueryExpressionValue::Angle(v) => v.to_css(dest),
702 QueryExpressionValue::Time(v) => v.to_css(dest),
703 QueryExpressionValue::Custom(ref v) => v.to_css(dest),
704 QueryExpressionValue::Enumerated(value) => match for_expr
705 .expect("caller should have passed for_expr")
706 .feature()
707 .evaluator
708 {
709 Evaluator::Enumerated { serializer, .. } => dest.write_str(&*serializer(value)),
710 _ => unreachable!(),
711 },
712 }
713 }
714
715 fn parse<'i, 't>(
716 for_feature: &QueryFeatureDescription,
717 context: &ParserContext,
718 input: &mut Parser<'i, 't>,
719 ) -> Result<QueryExpressionValue, ParseError<'i>> {
720 Ok(match for_feature.evaluator {
721 Evaluator::OptionalLength(..) | Evaluator::Length(..) => {
722 let length = Length::parse(context, input)?;
723 QueryExpressionValue::Length(length)
724 },
725 Evaluator::Integer(..) => {
726 let integer = Integer::parse(context, input)?;
727 QueryExpressionValue::Integer(integer.value())
728 },
729 Evaluator::BoolInteger(..) => {
730 let integer = Integer::parse_non_negative(context, input)?;
731 let value = integer.value();
732 if value > 1 {
733 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
734 }
735 QueryExpressionValue::BoolInteger(value == 1)
736 },
737 Evaluator::Float(..) => {
738 let number = Number::parse(context, input)?;
739 QueryExpressionValue::Float(number.get())
740 },
741 Evaluator::OptionalNumberRatio(..) | Evaluator::NumberRatio(..) => {
742 use crate::values::specified::Ratio as SpecifiedRatio;
743 let ratio = SpecifiedRatio::parse(context, input)?;
744 QueryExpressionValue::NumberRatio(Ratio::new(ratio.0.get(), ratio.1.get()))
745 },
746 Evaluator::Resolution(..) => {
747 QueryExpressionValue::Resolution(Resolution::parse(context, input)?)
748 },
749 Evaluator::Enumerated { parser, .. } => {
750 QueryExpressionValue::Enumerated(parser(context, input)?)
751 },
752 })
753 }
754
755 fn parse_for_style_range<'i, 't>(
761 context: &ParserContext,
762 input: &mut Parser<'i, 't>,
763 ) -> Result<Self, ParseError<'i>> {
764 if let Ok(number) = input.try_parse(|i| Number::parse(context, i)) {
765 return Ok(Self::Float(number.get()));
766 }
767 if let Ok(percent) = input.try_parse(|i| Percentage::parse(context, i)) {
768 return Ok(Self::Percentage(percent));
769 }
770 if let Ok(length) = input.try_parse(|i| Length::parse(context, i)) {
771 return Ok(Self::Length(length));
772 }
773 if let Ok(angle) = input.try_parse(|i| Angle::parse(context, i)) {
774 return Ok(Self::Angle(angle));
775 }
776 if let Ok(time) = input.try_parse(|i| Time::parse(context, i)) {
777 return Ok(Self::Time(time));
778 }
779 if let Ok(resolution) = input.try_parse(|i| Resolution::parse(context, i)) {
780 return Ok(Self::Resolution(resolution));
781 }
782 if let Ok(ident) = input.try_parse(|i| DashedIdent::parse(context, i)) {
783 return Ok(Self::Custom(ident));
784 }
785 if let Ok(keyword) = input.try_parse(|i| CSSWideKeyword::parse(i)) {
786 return Ok(Self::Keyword(keyword));
787 }
788 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
789 }
790}
791
792#[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)]
794pub enum QueryStyleRange {
795 #[allow(missing_docs)]
798 StyleRange2 {
799 value1: QueryExpressionValue,
800 op1: Operator,
801 value2: QueryExpressionValue,
802 },
803
804 #[allow(missing_docs)]
807 StyleRange3 {
808 value1: QueryExpressionValue,
809 op1: Operator,
810 value2: QueryExpressionValue,
811 op2: Operator,
812 value3: QueryExpressionValue,
813 },
814}
815
816impl ToCss for QueryStyleRange {
817 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
818 where
819 W: fmt::Write,
820 {
821 dest.write_char('(')?;
822 match self {
823 Self::StyleRange2 {
824 ref value1,
825 ref op1,
826 ref value2,
827 } => {
828 value1.to_css(dest, None)?;
829 dest.write_char(' ')?;
830 op1.to_css(dest)?;
831 dest.write_char(' ')?;
832 value2.to_css(dest, None)?;
833 },
834 Self::StyleRange3 {
835 ref value1,
836 ref op1,
837 ref value2,
838 ref op2,
839 ref value3,
840 } => {
841 value1.to_css(dest, None)?;
842 dest.write_char(' ')?;
843 op1.to_css(dest)?;
844 dest.write_char(' ')?;
845 value2.to_css(dest, None)?;
846 dest.write_char(' ')?;
847 op2.to_css(dest)?;
848 dest.write_char(' ')?;
849 value3.to_css(dest, None)?;
850 },
851 }
852 dest.write_char(')')
853 }
854}
855
856impl QueryStyleRange {
857 pub fn parse<'i, 't>(
865 context: &ParserContext,
866 input: &mut Parser<'i, 't>,
867 ) -> Result<Self, ParseError<'i>> {
868 let value1 = QueryExpressionValue::parse_for_style_range(context, input)?;
869 let op1 = Operator::parse(input)?;
870 let value2 = QueryExpressionValue::parse_for_style_range(context, input)?;
871
872 if let Ok(op2) = input.try_parse(|i| Operator::parse(i)) {
873 if op1.is_compatible_with(op2) {
874 let value3 = QueryExpressionValue::parse_for_style_range(context, input)?;
875 return Ok(Self::StyleRange3 {
876 value1,
877 op1,
878 value2,
879 op2,
880 value3,
881 });
882 }
883 }
884
885 Ok(Self::StyleRange2 {
886 value1,
887 op1,
888 value2,
889 })
890 }
891
892 pub fn evaluate(&self, context: &computed::Context) -> KleeneValue {
894 match self {
895 QueryStyleRange::StyleRange2 {
896 ref value1,
897 ref op1,
898 ref value2,
899 } => Self::compare_values(
900 Self::resolve_value(value1, context, &mut PrecomputedHashSet::default()).as_ref(),
901 Self::resolve_value(value2, context, &mut PrecomputedHashSet::default()).as_ref(),
902 )
903 .is_some_and(|c| op1.evaluate(c))
904 .into(),
905
906 QueryStyleRange::StyleRange3 {
907 ref value1,
908 ref op1,
909 ref value2,
910 ref op2,
911 ref value3,
912 } => {
913 let v1 = Self::resolve_value(value1, context, &mut PrecomputedHashSet::default());
914 let v2 = Self::resolve_value(value2, context, &mut PrecomputedHashSet::default());
915 Self::compare_values(v1.as_ref(), v2.as_ref())
916 .is_some_and(|c1| {
917 op1.evaluate(c1)
918 && Self::compare_values(
919 v2.as_ref(),
920 Self::resolve_value(
921 value3,
922 context,
923 &mut PrecomputedHashSet::default(),
924 )
925 .as_ref(),
926 )
927 .is_some_and(|c2| op2.evaluate(c2))
928 })
929 .into()
930 },
931 }
932 }
933
934 fn resolve_value(
936 value: &QueryExpressionValue,
937 context: &computed::Context,
938 visited_set: &mut PrecomputedHashSet<DashedIdent>,
939 ) -> Option<Component> {
940 match value {
941 QueryExpressionValue::Custom(ident) => {
942 let name = ident.undashed();
945 let stylist = context
946 .builder
947 .stylist
948 .expect("container queries should have a stylist around");
949 let registration = stylist.get_custom_property_registration(&name);
950 let current_value = context
951 .inherited_custom_properties()
952 .get(registration, &name)?;
953 match ¤t_value.v {
954 ValueInner::Component(component) => Some(component.clone()),
955 ValueInner::Universal(v) => {
956 if visited_set.insert(ident.clone()) {
960 Self::resolve_universal(&v.css, &v.url_data, context, visited_set)
961 } else {
962 None
963 }
964 },
965 ValueInner::List(_) => {
966 debug_assert!(false, "We don't parse list values in style queries");
967 None
968 },
969 }
970 },
971 QueryExpressionValue::Length(v) => {
972 Some(Component::Length(v.to_computed_value(context)))
973 },
974 QueryExpressionValue::Float(v) => Some(Component::Number(v.to_computed_value(context))),
975 QueryExpressionValue::Resolution(v) => {
976 Some(Component::Resolution(v.to_computed_value(context)))
977 },
978 QueryExpressionValue::Percentage(v) => {
979 Some(Component::Percentage(v.to_computed_value(context)))
980 },
981 QueryExpressionValue::Angle(v) => Some(Component::Angle(v.to_computed_value(context))),
982 QueryExpressionValue::Time(v) => Some(Component::Time(v.to_computed_value(context))),
983 QueryExpressionValue::Keyword(_) => None,
986 _ => {
987 debug_assert!(false, "unexpected value type in style range");
988 None
989 },
990 }
991 }
992
993 fn resolve_universal(
1000 css_text: &str,
1001 url_data: &UrlExtraData,
1002 context: &computed::Context,
1003 visited_set: &mut PrecomputedHashSet<DashedIdent>,
1004 ) -> Option<Component> {
1005 let parser_context = ParserContext::new(
1006 Origin::Author,
1007 url_data,
1008 Some(CssRuleType::Container),
1009 ParsingMode::DEFAULT,
1010 QuirksMode::NoQuirks,
1011 Default::default(),
1012 None,
1013 None,
1014 );
1015 let mut input = ParserInput::new(css_text);
1016 QueryExpressionValue::parse_for_style_range(&parser_context, &mut Parser::new(&mut input))
1017 .ok()
1018 .and_then(|parsed| Self::resolve_value(&parsed, context, visited_set))
1019 }
1020
1021 fn compare_values(value1: Option<&Component>, value2: Option<&Component>) -> Option<Ordering> {
1022 let value1 = value1?;
1023 let value2 = value2?;
1024 match (value1, value2) {
1025 (Component::Length(v1), Component::Length(v2)) => v1.partial_cmp(&v2),
1026 (Component::Number(v1), Component::Number(v2)) => v1.partial_cmp(&v2),
1027 (Component::Resolution(v1), Component::Resolution(v2)) => {
1028 v1.dppx().partial_cmp(&v2.dppx())
1029 },
1030 (Component::Percentage(v1), Component::Percentage(v2)) => v1.partial_cmp(&v2),
1031 (Component::Angle(v1), Component::Angle(v2)) => v1.partial_cmp(&v2),
1032 (Component::Time(v1), Component::Time(v2)) => v1.partial_cmp(&v2),
1033 (Component::Length(v1), Component::Number(v2)) => {
1034 if v2.is_zero() {
1035 v1.partial_cmp(&CSSPixelLength::zero())
1036 } else {
1037 None
1038 }
1039 },
1040 (Component::Number(v1), Component::Length(v2)) => {
1041 if v1.is_zero() {
1042 CSSPixelLength::zero().partial_cmp(&v2)
1043 } else {
1044 None
1045 }
1046 },
1047 _ => None,
1048 }
1049 }
1050}