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