1use super::feature::{Evaluator, QueryFeatureDescription};
9use super::feature::{FeatureFlags, KeywordDiscriminant};
10use crate::context::QuirksMode;
11use crate::custom_properties::{
12 self, ComputedSubstitutionFunctions, SubstitutionFunctionKind,
13 VariableValue as CustomVariableValue,
14};
15use crate::derives::*;
16use crate::dom::AttributeTracker;
17use crate::parser::{Parse, ParserContext};
18use crate::properties::CSSWideKeyword;
19use crate::properties_and_values::value::{ComputedValueComponent as Component, ValueInner};
20use crate::selector_map::PrecomputedHashSet;
21use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase};
22use crate::stylesheets::container_rule::AttrReferenceSet;
23use crate::stylesheets::{CssRuleType, Origin, UrlExtraData};
24use crate::values::computed::{self, CSSPixelLength, ToComputedValue};
25use crate::values::specified::{
26 Angle, Integer, Length, Number, Percentage, Ratio, Resolution, Time,
27};
28use crate::values::DashedIdent;
29use crate::{Atom, Zero};
30use cssparser::{Parser, Token};
31use selectors::kleene_value::KleeneValue;
32use std::cmp::Ordering;
33use std::fmt::{self, Write};
34use style_traits::{CssWriter, ParseError, ParsingMode, StyleParseErrorKind, ToCss};
35
36#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
38pub enum FeatureType {
39 Media,
41 Container,
43}
44
45impl FeatureType {
46 fn features(&self) -> &'static [QueryFeatureDescription] {
47 #[cfg(feature = "gecko")]
48 use crate::gecko::media_features::MEDIA_FEATURES;
49 #[cfg(feature = "servo")]
50 use crate::servo::media_features::MEDIA_FEATURES;
51
52 use crate::stylesheets::container_rule::CONTAINER_FEATURES;
53
54 match *self {
55 FeatureType::Media => &MEDIA_FEATURES,
56 FeatureType::Container => &CONTAINER_FEATURES,
57 }
58 }
59
60 fn find_feature(&self, name: &Atom) -> Option<(usize, &'static QueryFeatureDescription)> {
61 self.features()
62 .iter()
63 .enumerate()
64 .find(|(_, f)| f.name == *name)
65 }
66}
67
68#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
70enum LegacyRange {
71 Min,
73 Max,
75}
76
77#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
79pub enum Operator {
80 Equal,
82 GreaterThan,
84 GreaterThanEqual,
86 LessThan,
88 LessThanEqual,
90}
91
92impl ToCss for Operator {
93 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
94 where
95 W: fmt::Write,
96 {
97 dest.write_str(match *self {
98 Self::Equal => "=",
99 Self::LessThan => "<",
100 Self::LessThanEqual => "<=",
101 Self::GreaterThan => ">",
102 Self::GreaterThanEqual => ">=",
103 })
104 }
105}
106
107impl Operator {
108 fn is_compatible_with(self, right_op: Self) -> bool {
109 match self {
112 Self::Equal => false,
113 Self::GreaterThan | Self::GreaterThanEqual => {
114 matches!(right_op, Self::GreaterThan | Self::GreaterThanEqual)
115 },
116 Self::LessThan | Self::LessThanEqual => {
117 matches!(right_op, Self::LessThan | Self::LessThanEqual)
118 },
119 }
120 }
121
122 fn evaluate(&self, cmp: Ordering) -> bool {
123 match *self {
124 Self::Equal => cmp == Ordering::Equal,
125 Self::GreaterThan => cmp == Ordering::Greater,
126 Self::GreaterThanEqual => cmp == Ordering::Equal || cmp == Ordering::Greater,
127 Self::LessThan => cmp == Ordering::Less,
128 Self::LessThanEqual => cmp == Ordering::Equal || cmp == Ordering::Less,
129 }
130 }
131
132 fn parse(input: &mut Parser) -> Result<Self, ParseError> {
133 let operator = match *input.next()? {
134 Token::Delim('=') => return Ok(Operator::Equal),
135 Token::Delim('>') => Operator::GreaterThan,
136 Token::Delim('<') => Operator::LessThan,
137 _ => return Err(ParseError::unexpected_token()),
138 };
139
140 let parsed_equal = input
149 .try_parse(|i| {
150 let t = i.next_including_whitespace().map_err(|_| ())?;
151 if !matches!(t, Token::Delim('=')) {
152 return Err(());
153 }
154 Ok(())
155 })
156 .is_ok();
157
158 if !parsed_equal {
159 return Ok(operator);
160 }
161
162 Ok(match operator {
163 Operator::GreaterThan => Operator::GreaterThanEqual,
164 Operator::LessThan => Operator::LessThanEqual,
165 _ => unreachable!(),
166 })
167 }
168}
169
170#[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)]
171enum QueryFeatureExpressionKind {
172 Empty,
174
175 Single(QueryExpressionValue),
177
178 LegacyRange(LegacyRange, QueryExpressionValue),
180
181 Range {
184 left: Option<(Operator, QueryExpressionValue)>,
185 right: Option<(Operator, QueryExpressionValue)>,
186 },
187}
188
189impl QueryFeatureExpressionKind {
190 fn evaluate<T>(
193 &self,
194 context_value: T,
195 mut compute: impl FnMut(&QueryExpressionValue) -> T,
196 ) -> bool
197 where
198 T: PartialOrd + Zero,
199 {
200 match *self {
201 Self::Empty => !context_value.is_zero(),
202 Self::Single(ref value) => {
203 let value = compute(value);
204 let cmp = match context_value.partial_cmp(&value) {
205 Some(c) => c,
206 None => return false,
207 };
208 cmp == Ordering::Equal
209 },
210 Self::LegacyRange(ref range, ref value) => {
211 let value = compute(value);
212 let cmp = match context_value.partial_cmp(&value) {
213 Some(c) => c,
214 None => return false,
215 };
216 cmp == Ordering::Equal
217 || match range {
218 LegacyRange::Min => cmp == Ordering::Greater,
219 LegacyRange::Max => cmp == Ordering::Less,
220 }
221 },
222 Self::Range {
223 ref left,
224 ref right,
225 } => {
226 debug_assert!(left.is_some() || right.is_some());
227 if let Some((op, value)) = left {
228 let value = compute(value);
229 let cmp = match value.partial_cmp(&context_value) {
230 Some(c) => c,
231 None => return false,
232 };
233 if !op.evaluate(cmp) {
234 return false;
235 }
236 }
237 if let Some((op, value)) = right {
238 let value = compute(value);
239 let cmp = match context_value.partial_cmp(&value) {
240 Some(c) => c,
241 None => return false,
242 };
243 if !op.evaluate(cmp) {
244 return false;
245 }
246 }
247 true
248 },
249 }
250 }
251
252 fn non_ranged_value(&self) -> Option<&QueryExpressionValue> {
254 match *self {
255 Self::Empty => None,
256 Self::Single(ref v) => Some(v),
257 Self::LegacyRange(..) | Self::Range { .. } => {
258 debug_assert!(false, "Unexpected ranged value in non-ranged feature!");
259 None
260 },
261 }
262 }
263}
264
265#[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)]
268pub struct QueryFeatureExpression {
269 feature_type: FeatureType,
270 feature_index: usize,
271 kind: QueryFeatureExpressionKind,
272}
273
274impl ToCss for QueryFeatureExpression {
275 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
276 where
277 W: fmt::Write,
278 {
279 dest.write_char('(')?;
280
281 match self.kind {
282 QueryFeatureExpressionKind::Empty => self.write_name(dest)?,
283 QueryFeatureExpressionKind::Single(ref v)
284 | QueryFeatureExpressionKind::LegacyRange(_, ref v) => {
285 self.write_name(dest)?;
286 dest.write_str(": ")?;
287 v.to_css(dest, Some(self))?;
288 },
289 QueryFeatureExpressionKind::Range {
290 ref left,
291 ref right,
292 } => {
293 if let Some((op, val)) = left {
294 val.to_css(dest, Some(self))?;
295 dest.write_char(' ')?;
296 op.to_css(dest)?;
297 dest.write_char(' ')?;
298 }
299 self.write_name(dest)?;
300 if let Some((op, val)) = right {
301 dest.write_char(' ')?;
302 op.to_css(dest)?;
303 dest.write_char(' ')?;
304 val.to_css(dest, Some(self))?;
305 }
306 },
307 }
308 dest.write_char(')')
309 }
310}
311
312fn consume_operation_or_colon(input: &mut Parser) -> Result<Option<Operator>, ParseError> {
313 if input.try_parse(|input| input.expect_colon()).is_ok() {
314 return Ok(None);
315 }
316 Operator::parse(input).map(Some)
317}
318
319#[allow(unused_variables)]
320fn disabled_by_pref(feature: &Atom, context: &ParserContext) -> bool {
321 #[cfg(feature = "gecko")]
322 {
323 if *feature == atom!("prefers-reduced-transparency") {
326 return !context.chrome_rules_enabled()
327 && !crate::pref!("layout.css.prefers-reduced-transparency.enabled");
328 }
329
330 if *feature == atom!("inverted-colors") {
333 return !context.chrome_rules_enabled()
334 && !crate::pref!("layout.css.inverted-colors.enabled");
335 }
336 }
337 false
338}
339
340impl QueryFeatureExpression {
341 fn new(
342 feature_type: FeatureType,
343 feature_index: usize,
344 kind: QueryFeatureExpressionKind,
345 ) -> Self {
346 debug_assert!(feature_index < feature_type.features().len());
347 Self {
348 feature_type,
349 feature_index,
350 kind,
351 }
352 }
353
354 fn write_name<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
355 where
356 W: fmt::Write,
357 {
358 let feature = self.feature();
359 if feature.flags.contains(FeatureFlags::WEBKIT_PREFIX) {
360 dest.write_str("-webkit-")?;
361 }
362
363 if let QueryFeatureExpressionKind::LegacyRange(range, _) = self.kind {
364 match range {
365 LegacyRange::Min => dest.write_str("min-")?,
366 LegacyRange::Max => dest.write_str("max-")?,
367 }
368 }
369
370 write!(dest, "{}", feature.name)?;
372
373 Ok(())
374 }
375
376 fn feature(&self) -> &'static QueryFeatureDescription {
377 &self.feature_type.features()[self.feature_index]
378 }
379
380 pub fn feature_flags(&self) -> FeatureFlags {
382 self.feature().flags
383 }
384
385 fn parse_feature_name(
386 context: &ParserContext,
387 input: &mut Parser,
388 feature_type: FeatureType,
389 ) -> Result<(usize, Option<LegacyRange>), ParseError> {
390 let mut flags = FeatureFlags::empty();
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(ParseError::custom(
418 StyleParseErrorKind::MediaQueryExpectedFeatureName,
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(ParseError::custom(
428 StyleParseErrorKind::MediaQueryExpectedFeatureName,
429 ));
430 }
431
432 Ok((feature_index, range))
433 }
434
435 fn parse_multi_range_syntax(
440 context: &ParserContext,
441 input: &mut Parser,
442 feature_type: FeatureType,
443 ) -> Result<Self, ParseError> {
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(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
455 }
456 break index;
457 }
458 if input.is_exhausted() {
459 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
460 }
461 };
462
463 input.reset(&start);
464
465 let feature = &feature_type.features()[feature_index];
466 let left_val = QueryExpressionValue::parse(feature, context, input)?;
467 let left_op = Operator::parse(input)?;
468
469 {
470 let (parsed_index, _) = Self::parse_feature_name(context, input, feature_type)?;
471 debug_assert_eq!(
472 parsed_index, feature_index,
473 "How did we find a different feature?"
474 );
475 }
476
477 let right_op = input.try_parse(Operator::parse).ok();
478 let right = match right_op {
479 Some(op) => {
480 if !left_op.is_compatible_with(op) {
481 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
482 }
483 Some((op, QueryExpressionValue::parse(feature, context, input)?))
484 },
485 None => None,
486 };
487 Ok(Self::new(
488 feature_type,
489 feature_index,
490 QueryFeatureExpressionKind::Range {
491 left: Some((left_op, left_val)),
492 right,
493 },
494 ))
495 }
496
497 pub fn parse_in_parenthesis_block(
499 context: &ParserContext,
500 input: &mut Parser,
501 feature_type: FeatureType,
502 ) -> Result<Self, ParseError> {
503 let (feature_index, range) =
504 match input.try_parse(|input| Self::parse_feature_name(context, input, feature_type)) {
505 Ok(v) => v,
506 Err(e) => {
507 if let Ok(expr) = Self::parse_multi_range_syntax(context, input, feature_type) {
508 return Ok(expr);
509 }
510 return Err(e);
511 },
512 };
513 let operator = input.try_parse(consume_operation_or_colon);
514 let operator = match operator {
515 Err(..) => {
516 if range.is_some() {
522 return Err(ParseError::custom(
523 StyleParseErrorKind::RangedExpressionWithNoValue,
524 ));
525 }
526
527 return Ok(Self::new(
528 feature_type,
529 feature_index,
530 QueryFeatureExpressionKind::Empty,
531 ));
532 },
533 Ok(operator) => operator,
534 };
535
536 let feature = &feature_type.features()[feature_index];
537
538 let value = QueryExpressionValue::parse(feature, context, input)
539 .map_err(|_| ParseError::custom(StyleParseErrorKind::MediaQueryExpectedFeatureValue))?;
540
541 let kind = match range {
542 Some(range) => {
543 if operator.is_some() {
544 return Err(ParseError::custom(
545 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(ParseError::custom(
554 StyleParseErrorKind::MediaQueryUnexpectedOperator,
555 ));
556 }
557 QueryFeatureExpressionKind::Range {
558 left: None,
559 right: Some((operator, value)),
560 }
561 },
562 None => QueryFeatureExpressionKind::Single(value),
563 },
564 };
565
566 Ok(Self::new(feature_type, feature_index, kind))
567 }
568
569 pub fn matches(&self, context: &computed::Context) -> KleeneValue {
571 macro_rules! expect {
572 ($variant:ident, $v:expr) => {
573 match *$v {
574 QueryExpressionValue::$variant(ref v) => v,
575 _ => unreachable!("Unexpected QueryExpressionValue"),
576 }
577 };
578 }
579
580 KleeneValue::from(match self.feature().evaluator {
581 Evaluator::Length(eval) => {
582 let v = eval(context);
583 self.kind
584 .evaluate(v, |v| expect!(Length, v).to_computed_value(context))
585 },
586 Evaluator::OptionalLength(eval) => {
587 let v = match eval(context) {
588 Some(v) => v,
589 None => return KleeneValue::Unknown,
590 };
591 self.kind
592 .evaluate(v, |v| expect!(Length, v).to_computed_value(context))
593 },
594 Evaluator::Integer(eval) => {
595 let v = eval(context);
596 self.kind
597 .evaluate(v, |v| expect!(Integer, v).to_computed_value(context))
598 },
599 Evaluator::Float(eval) => {
600 let v = eval(context);
601 self.kind
602 .evaluate(v, |v| expect!(Float, v).to_computed_value(context))
603 },
604 Evaluator::NumberRatio(eval) => {
605 let ratio = eval(context);
606 self.kind.evaluate(ratio, |v| {
611 expect!(NumberRatio, v)
612 .to_computed_value(context)
613 .used_value()
614 })
615 },
616 Evaluator::OptionalNumberRatio(eval) => {
617 let ratio = match eval(context) {
618 Some(v) => v,
619 None => return KleeneValue::Unknown,
620 };
621 self.kind.evaluate(ratio, |v| {
623 expect!(NumberRatio, v)
624 .to_computed_value(context)
625 .used_value()
626 })
627 },
628 Evaluator::Resolution(eval) => {
629 let v = eval(context).dppx();
630 self.kind.evaluate(v, |v| {
631 expect!(Resolution, v).to_computed_value(context).dppx()
632 })
633 },
634 Evaluator::Enumerated { evaluator, .. } => {
635 let computed = self
636 .kind
637 .non_ranged_value()
638 .map(|v| *expect!(Enumerated, v));
639 return evaluator(context, computed);
640 },
641 Evaluator::BoolInteger(eval) => {
642 let computed = self
643 .kind
644 .non_ranged_value()
645 .map(|v| expect!(BoolInteger, v).to_computed_value(context));
646 let boolean = eval(context);
647 computed.map_or(boolean, |v| v == boolean as i32)
648 },
649 })
650 }
651}
652
653#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
662pub enum QueryExpressionValue {
663 Length(Length),
665 Integer(Integer),
667 Float(Number),
669 BoolInteger(Integer),
671 NumberRatio(Ratio),
674 Resolution(Resolution),
676 Enumerated(KeywordDiscriminant),
679 Keyword(CSSWideKeyword),
682 Percentage(Percentage),
684 Angle(Angle),
686 Time(Time),
688 Custom(DashedIdent),
690 Function(Box<CustomVariableValue>),
694}
695
696impl QueryExpressionValue {
697 fn to_css<W>(
698 &self,
699 dest: &mut CssWriter<W>,
700 for_expr: Option<&QueryFeatureExpression>,
701 ) -> fmt::Result
702 where
703 W: fmt::Write,
704 {
705 match *self {
706 QueryExpressionValue::Length(ref l) => l.to_css(dest),
707 QueryExpressionValue::Integer(ref v) => v.to_css(dest),
708 QueryExpressionValue::Float(ref v) => v.to_css(dest),
709 QueryExpressionValue::BoolInteger(ref v) => v.to_css(dest),
710 QueryExpressionValue::NumberRatio(ref ratio) => ratio.to_css(dest),
711 QueryExpressionValue::Resolution(ref r) => r.to_css(dest),
712 QueryExpressionValue::Keyword(k) => k.to_css(dest),
713 QueryExpressionValue::Percentage(ref v) => v.to_css(dest),
714 QueryExpressionValue::Angle(ref v) => v.to_css(dest),
715 QueryExpressionValue::Time(ref v) => v.to_css(dest),
716 QueryExpressionValue::Custom(ref v) => v.to_css(dest),
717 QueryExpressionValue::Function(ref f) => f.to_css(dest),
718 QueryExpressionValue::Enumerated(value) => match for_expr
719 .expect("caller should have passed for_expr")
720 .feature()
721 .evaluator
722 {
723 Evaluator::Enumerated { serializer, .. } => dest.write_str(&serializer(value)),
724 _ => unreachable!(),
725 },
726 }
727 }
728
729 fn parse(
730 for_feature: &QueryFeatureDescription,
731 context: &ParserContext,
732 input: &mut Parser,
733 ) -> Result<QueryExpressionValue, ParseError> {
734 Ok(match for_feature.evaluator {
735 Evaluator::OptionalLength(..) | Evaluator::Length(..) => {
736 let length = Length::parse(context, input)?;
737 QueryExpressionValue::Length(length)
738 },
739 Evaluator::Integer(..) => {
740 let integer = Integer::parse(context, input)?;
741 QueryExpressionValue::Integer(integer)
742 },
743 Evaluator::BoolInteger(..) => {
744 let integer = Integer::parse(context, input)?;
745 if matches!(integer.resolve(), Some(v) if v != 0 && v != 1) {
746 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
747 }
748 QueryExpressionValue::BoolInteger(integer)
749 },
750 Evaluator::Float(..) => {
751 let number = Number::parse(context, input)?;
752 QueryExpressionValue::Float(number)
753 },
754 Evaluator::OptionalNumberRatio(..) | Evaluator::NumberRatio(..) => {
755 use crate::values::specified::Ratio as SpecifiedRatio;
756 let ratio = SpecifiedRatio::parse(context, input)?;
757 QueryExpressionValue::NumberRatio(ratio)
758 },
759 Evaluator::Resolution(..) => {
760 QueryExpressionValue::Resolution(Resolution::parse(context, input)?)
761 },
762 Evaluator::Enumerated { parser, .. } => {
763 QueryExpressionValue::Enumerated(parser(context, input)?)
764 },
765 })
766 }
767
768 fn parse_for_style_range(
774 context: &ParserContext,
775 input: &mut Parser,
776 ) -> Result<Self, ParseError> {
777 if let Ok(number) = input.try_parse(|i| Number::parse(context, i)) {
778 return Ok(Self::Float(number));
779 }
780 if let Ok(percent) = input.try_parse(|i| Percentage::parse(context, i)) {
781 return Ok(Self::Percentage(percent));
782 }
783 if let Ok(length) = input.try_parse(|i| Length::parse(context, i)) {
784 return Ok(Self::Length(length));
785 }
786 if let Ok(angle) = input.try_parse(|i| Angle::parse(context, i)) {
787 return Ok(Self::Angle(angle));
788 }
789 if let Ok(time) = input.try_parse(|i| Time::parse(context, i)) {
790 return Ok(Self::Time(time));
791 }
792 if let Ok(resolution) = input.try_parse(|i| Resolution::parse(context, i)) {
793 return Ok(Self::Resolution(resolution));
794 }
795 if let Ok(ident) = input.try_parse(|i| DashedIdent::parse(context, i)) {
796 return Ok(Self::Custom(ident));
797 }
798 if let Ok(keyword) = input.try_parse(|i| CSSWideKeyword::parse(i)) {
799 return Ok(Self::Keyword(keyword));
800 }
801 input.skip_whitespace();
802 let start = input.position();
803 if let Ok(Token::Function(name)) = input.next() {
804 let parse_func = |input: &mut Parser| -> Result<CustomVariableValue, ParseError> {
807 input.parse_nested_block(|i| i.expect_no_error_token().map_err(Into::into))?;
808 CustomVariableValue::parse(
809 &mut Parser::new(input.slice_from(start)),
810 Some(&context.namespaces.prefixes),
811 context.url_data,
812 )
813 };
814
815 if SubstitutionFunctionKind::from_ident(name).is_ok() {
816 return Ok(Self::Function(Box::new(parse_func(input)?)));
817 }
818 }
819 Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
820 }
821
822 fn collect_attribute_references(&self, references: &mut AttrReferenceSet) {
823 if let Self::Function(f) = self {
824 f.collect_attribute_references(references)
825 }
826 }
827}
828
829#[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)]
831pub enum QueryStyleRange {
832 #[allow(missing_docs)]
835 StyleRange2 {
836 value1: QueryExpressionValue,
837 op1: Operator,
838 value2: QueryExpressionValue,
839 },
840
841 #[allow(missing_docs)]
844 StyleRange3 {
845 value1: QueryExpressionValue,
846 op1: Operator,
847 value2: QueryExpressionValue,
848 op2: Operator,
849 value3: QueryExpressionValue,
850 },
851}
852
853impl ToCss for QueryStyleRange {
854 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
855 where
856 W: fmt::Write,
857 {
858 match self {
859 Self::StyleRange2 {
860 value1,
861 op1,
862 value2,
863 } => {
864 value1.to_css(dest, None)?;
865 dest.write_char(' ')?;
866 op1.to_css(dest)?;
867 dest.write_char(' ')?;
868 value2.to_css(dest, None)
869 },
870 Self::StyleRange3 {
871 value1,
872 op1,
873 value2,
874 op2,
875 value3,
876 } => {
877 value1.to_css(dest, None)?;
878 dest.write_char(' ')?;
879 op1.to_css(dest)?;
880 dest.write_char(' ')?;
881 value2.to_css(dest, None)?;
882 dest.write_char(' ')?;
883 op2.to_css(dest)?;
884 dest.write_char(' ')?;
885 value3.to_css(dest, None)
886 },
887 }
888 }
889}
890
891impl QueryStyleRange {
892 pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
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 value1,
933 op1,
934 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 value1,
956 op1,
957 value2,
958 op2,
959 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 QueryExpressionValue::parse_for_style_range(&parser_context, &mut Parser::new(css_text))
1109 .ok()
1110 .and_then(|parsed| {
1111 Self::resolve_value(&parsed, context, attribute_tracker, visited_set)
1112 })
1113 }
1114
1115 fn compare_values(value1: Option<&Component>, value2: Option<&Component>) -> Option<Ordering> {
1116 let value1 = value1?;
1117 let value2 = value2?;
1118 match (value1, value2) {
1119 (Component::Length(v1), Component::Length(v2)) => v1.partial_cmp(v2),
1120 (Component::Number(v1), Component::Number(v2)) => v1.partial_cmp(v2),
1121 (Component::Resolution(v1), Component::Resolution(v2)) => {
1122 v1.dppx().partial_cmp(&v2.dppx())
1123 },
1124 (Component::Percentage(v1), Component::Percentage(v2)) => v1.partial_cmp(v2),
1125 (Component::Angle(v1), Component::Angle(v2)) => v1.partial_cmp(v2),
1126 (Component::Time(v1), Component::Time(v2)) => v1.partial_cmp(v2),
1127 (Component::Length(v1), Component::Number(v2)) => {
1128 if v2.is_zero() {
1129 v1.partial_cmp(&CSSPixelLength::zero())
1130 } else {
1131 None
1132 }
1133 },
1134 (Component::Number(v1), Component::Length(v2)) => {
1135 if v1.is_zero() {
1136 CSSPixelLength::zero().partial_cmp(v2)
1137 } else {
1138 None
1139 }
1140 },
1141 _ => None,
1142 }
1143 }
1144
1145 pub fn collect_attribute_references(&self, references: &mut AttrReferenceSet) {
1148 match self {
1149 QueryStyleRange::StyleRange2 {
1150 value1,
1151 op1: _,
1152 value2,
1153 } => {
1154 value1.collect_attribute_references(references);
1155 value2.collect_attribute_references(references);
1156 },
1157 QueryStyleRange::StyleRange3 {
1158 value1,
1159 op1: _,
1160 value2,
1161 op2: _,
1162 value3,
1163 } => {
1164 value1.collect_attribute_references(references);
1165 value2.collect_attribute_references(references);
1166 value3.collect_attribute_references(references);
1167 },
1168 }
1169 }
1170}