1use super::feature::{Evaluator, QueryFeatureDescription};
9use super::feature::{FeatureFlags, KeywordDiscriminant};
10use crate::parser::{Parse, ParserContext};
11use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase};
12use crate::values::computed::{self, Ratio, ToComputedValue};
13use crate::values::specified::{Integer, Length, Number, Resolution};
14use crate::values::CSSFloat;
15use crate::{Atom, Zero};
16use cssparser::{Parser, Token};
17use selectors::kleene_value::KleeneValue;
18use std::cmp::Ordering;
19use std::fmt::{self, Write};
20use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
21
22#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
24pub enum FeatureType {
25 Media,
27 Container,
29}
30
31impl FeatureType {
32 fn features(&self) -> &'static [QueryFeatureDescription] {
33 #[cfg(feature = "gecko")]
34 use crate::gecko::media_features::MEDIA_FEATURES;
35 #[cfg(feature = "servo")]
36 use crate::servo::media_queries::MEDIA_FEATURES;
37
38 use crate::stylesheets::container_rule::CONTAINER_FEATURES;
39
40 match *self {
41 FeatureType::Media => &MEDIA_FEATURES,
42 FeatureType::Container => &CONTAINER_FEATURES,
43 }
44 }
45
46 fn find_feature(&self, name: &Atom) -> Option<(usize, &'static QueryFeatureDescription)> {
47 self.features()
48 .iter()
49 .enumerate()
50 .find(|(_, f)| f.name == *name)
51 }
52}
53
54#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
56enum LegacyRange {
57 Min,
59 Max,
61}
62
63#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
65enum Operator {
66 Equal,
68 GreaterThan,
70 GreaterThanEqual,
72 LessThan,
74 LessThanEqual,
76}
77
78impl ToCss for Operator {
79 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
80 where
81 W: fmt::Write,
82 {
83 dest.write_str(match *self {
84 Self::Equal => "=",
85 Self::LessThan => "<",
86 Self::LessThanEqual => "<=",
87 Self::GreaterThan => ">",
88 Self::GreaterThanEqual => ">=",
89 })
90 }
91}
92
93impl Operator {
94 fn is_compatible_with(self, right_op: Self) -> bool {
95 match self {
98 Self::Equal => false,
99 Self::GreaterThan | Self::GreaterThanEqual => {
100 matches!(right_op, Self::GreaterThan | Self::GreaterThanEqual)
101 },
102 Self::LessThan | Self::LessThanEqual => {
103 matches!(right_op, Self::LessThan | Self::LessThanEqual)
104 },
105 }
106 }
107
108 fn evaluate(&self, cmp: Ordering) -> bool {
109 match *self {
110 Self::Equal => cmp == Ordering::Equal,
111 Self::GreaterThan => cmp == Ordering::Greater,
112 Self::GreaterThanEqual => cmp == Ordering::Equal || cmp == Ordering::Greater,
113 Self::LessThan => cmp == Ordering::Less,
114 Self::LessThanEqual => cmp == Ordering::Equal || cmp == Ordering::Less,
115 }
116 }
117
118 fn parse<'i>(input: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
119 let location = input.current_source_location();
120 let operator = match *input.next()? {
121 Token::Delim('=') => return Ok(Operator::Equal),
122 Token::Delim('>') => Operator::GreaterThan,
123 Token::Delim('<') => Operator::LessThan,
124 ref t => return Err(location.new_unexpected_token_error(t.clone())),
125 };
126
127 let parsed_equal = input
136 .try_parse(|i| {
137 let t = i.next_including_whitespace().map_err(|_| ())?;
138 if !matches!(t, Token::Delim('=')) {
139 return Err(());
140 }
141 Ok(())
142 })
143 .is_ok();
144
145 if !parsed_equal {
146 return Ok(operator);
147 }
148
149 Ok(match operator {
150 Operator::GreaterThan => Operator::GreaterThanEqual,
151 Operator::LessThan => Operator::LessThanEqual,
152 _ => unreachable!(),
153 })
154 }
155}
156
157#[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)]
158enum QueryFeatureExpressionKind {
159 Empty,
161
162 Single(QueryExpressionValue),
164
165 LegacyRange(LegacyRange, QueryExpressionValue),
167
168 Range {
171 left: Option<(Operator, QueryExpressionValue)>,
172 right: Option<(Operator, QueryExpressionValue)>,
173 },
174}
175
176impl QueryFeatureExpressionKind {
177 fn evaluate<T>(
180 &self,
181 context_value: T,
182 mut compute: impl FnMut(&QueryExpressionValue) -> T,
183 ) -> bool
184 where
185 T: PartialOrd + Zero,
186 {
187 match *self {
188 Self::Empty => return !context_value.is_zero(),
189 Self::Single(ref value) => {
190 let value = compute(value);
191 let cmp = match context_value.partial_cmp(&value) {
192 Some(c) => c,
193 None => return false,
194 };
195 cmp == Ordering::Equal
196 },
197 Self::LegacyRange(ref range, ref value) => {
198 let value = compute(value);
199 let cmp = match context_value.partial_cmp(&value) {
200 Some(c) => c,
201 None => return false,
202 };
203 cmp == Ordering::Equal
204 || match range {
205 LegacyRange::Min => cmp == Ordering::Greater,
206 LegacyRange::Max => cmp == Ordering::Less,
207 }
208 },
209 Self::Range {
210 ref left,
211 ref right,
212 } => {
213 debug_assert!(left.is_some() || right.is_some());
214 if let Some((ref op, ref value)) = left {
215 let value = compute(value);
216 let cmp = match value.partial_cmp(&context_value) {
217 Some(c) => c,
218 None => return false,
219 };
220 if !op.evaluate(cmp) {
221 return false;
222 }
223 }
224 if let Some((ref op, ref value)) = right {
225 let value = compute(value);
226 let cmp = match context_value.partial_cmp(&value) {
227 Some(c) => c,
228 None => return false,
229 };
230 if !op.evaluate(cmp) {
231 return false;
232 }
233 }
234 true
235 },
236 }
237 }
238
239 fn non_ranged_value(&self) -> Option<&QueryExpressionValue> {
241 match *self {
242 Self::Empty => None,
243 Self::Single(ref v) => Some(v),
244 Self::LegacyRange(..) | Self::Range { .. } => {
245 debug_assert!(false, "Unexpected ranged value in non-ranged feature!");
246 None
247 },
248 }
249 }
250}
251
252#[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)]
255pub struct QueryFeatureExpression {
256 feature_type: FeatureType,
257 feature_index: usize,
258 kind: QueryFeatureExpressionKind,
259}
260
261impl ToCss for QueryFeatureExpression {
262 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
263 where
264 W: fmt::Write,
265 {
266 dest.write_char('(')?;
267
268 match self.kind {
269 QueryFeatureExpressionKind::Empty => self.write_name(dest)?,
270 QueryFeatureExpressionKind::Single(ref v)
271 | QueryFeatureExpressionKind::LegacyRange(_, ref v) => {
272 self.write_name(dest)?;
273 dest.write_str(": ")?;
274 v.to_css(dest, self)?;
275 },
276 QueryFeatureExpressionKind::Range {
277 ref left,
278 ref right,
279 } => {
280 if let Some((ref op, ref val)) = left {
281 val.to_css(dest, self)?;
282 dest.write_char(' ')?;
283 op.to_css(dest)?;
284 dest.write_char(' ')?;
285 }
286 self.write_name(dest)?;
287 if let Some((ref op, ref val)) = right {
288 dest.write_char(' ')?;
289 op.to_css(dest)?;
290 dest.write_char(' ')?;
291 val.to_css(dest, self)?;
292 }
293 },
294 }
295 dest.write_char(')')
296 }
297}
298
299fn consume_operation_or_colon<'i>(
300 input: &mut Parser<'i, '_>,
301) -> Result<Option<Operator>, ParseError<'i>> {
302 if input.try_parse(|input| input.expect_colon()).is_ok() {
303 return Ok(None);
304 }
305 Operator::parse(input).map(|op| Some(op))
306}
307
308#[allow(unused_variables)]
309fn disabled_by_pref(feature: &Atom, context: &ParserContext) -> bool {
310 #[cfg(feature = "gecko")]
311 {
312 if *feature == atom!("prefers-reduced-transparency") {
315 return !context.chrome_rules_enabled()
316 && !static_prefs::pref!("layout.css.prefers-reduced-transparency.enabled");
317 }
318
319 if *feature == atom!("inverted-colors") {
322 return !context.chrome_rules_enabled()
323 && !static_prefs::pref!("layout.css.inverted-colors.enabled");
324 }
325 }
326 false
327}
328
329impl QueryFeatureExpression {
330 fn new(
331 feature_type: FeatureType,
332 feature_index: usize,
333 kind: QueryFeatureExpressionKind,
334 ) -> Self {
335 debug_assert!(feature_index < feature_type.features().len());
336 Self {
337 feature_type,
338 feature_index,
339 kind,
340 }
341 }
342
343 fn write_name<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
344 where
345 W: fmt::Write,
346 {
347 let feature = self.feature();
348 if feature.flags.contains(FeatureFlags::WEBKIT_PREFIX) {
349 dest.write_str("-webkit-")?;
350 }
351
352 if let QueryFeatureExpressionKind::LegacyRange(range, _) = self.kind {
353 match range {
354 LegacyRange::Min => dest.write_str("min-")?,
355 LegacyRange::Max => dest.write_str("max-")?,
356 }
357 }
358
359 write!(dest, "{}", feature.name)?;
361
362 Ok(())
363 }
364
365 fn feature(&self) -> &'static QueryFeatureDescription {
366 &self.feature_type.features()[self.feature_index]
367 }
368
369 pub fn feature_flags(&self) -> FeatureFlags {
371 self.feature().flags
372 }
373
374 pub fn parse<'i, 't>(
380 context: &ParserContext,
381 input: &mut Parser<'i, 't>,
382 feature_type: FeatureType,
383 ) -> Result<Self, ParseError<'i>> {
384 input.expect_parenthesis_block()?;
385 input.parse_nested_block(|input| {
386 Self::parse_in_parenthesis_block(context, input, feature_type)
387 })
388 }
389
390 fn parse_feature_name<'i, 't>(
391 context: &ParserContext,
392 input: &mut Parser<'i, 't>,
393 feature_type: FeatureType,
394 ) -> Result<(usize, Option<LegacyRange>), ParseError<'i>> {
395 let mut flags = FeatureFlags::empty();
396 let location = input.current_source_location();
397 let ident = input.expect_ident()?;
398
399 if context.chrome_rules_enabled() {
400 flags.insert(FeatureFlags::CHROME_AND_UA_ONLY);
401 }
402
403 let mut feature_name = &**ident;
404 if starts_with_ignore_ascii_case(feature_name, "-webkit-") {
405 feature_name = &feature_name[8..];
406 flags.insert(FeatureFlags::WEBKIT_PREFIX);
407 }
408
409 let range = if starts_with_ignore_ascii_case(feature_name, "min-") {
410 feature_name = &feature_name[4..];
411 Some(LegacyRange::Min)
412 } else if starts_with_ignore_ascii_case(feature_name, "max-") {
413 feature_name = &feature_name[4..];
414 Some(LegacyRange::Max)
415 } else {
416 None
417 };
418
419 let atom = Atom::from(string_as_ascii_lowercase(feature_name));
420 let (feature_index, feature) = match feature_type.find_feature(&atom) {
421 Some((i, f)) => (i, f),
422 None => {
423 return Err(location.new_custom_error(
424 StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
425 ))
426 },
427 };
428
429 if disabled_by_pref(&feature.name, context)
430 || !flags.contains(feature.flags.parsing_requirements())
431 || (range.is_some() && !feature.allows_ranges())
432 {
433 return Err(location.new_custom_error(
434 StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
435 ));
436 }
437
438 Ok((feature_index, range))
439 }
440
441 fn parse_multi_range_syntax<'i, 't>(
446 context: &ParserContext,
447 input: &mut Parser<'i, 't>,
448 feature_type: FeatureType,
449 ) -> Result<Self, ParseError<'i>> {
450 let start = input.state();
451
452 let feature_index = loop {
456 if let Ok((index, range)) = Self::parse_feature_name(context, input, feature_type) {
458 if range.is_some() {
459 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
461 }
462 break index;
463 }
464 if input.is_exhausted() {
465 return Err(start
466 .source_location()
467 .new_custom_error(StyleParseErrorKind::UnspecifiedError));
468 }
469 };
470
471 input.reset(&start);
472
473 let feature = &feature_type.features()[feature_index];
474 let left_val = QueryExpressionValue::parse(feature, context, input)?;
475 let left_op = Operator::parse(input)?;
476
477 {
478 let (parsed_index, _) = Self::parse_feature_name(context, input, feature_type)?;
479 debug_assert_eq!(
480 parsed_index, feature_index,
481 "How did we find a different feature?"
482 );
483 }
484
485 let right_op = input.try_parse(Operator::parse).ok();
486 let right = match right_op {
487 Some(op) => {
488 if !left_op.is_compatible_with(op) {
489 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
490 }
491 Some((op, QueryExpressionValue::parse(feature, context, input)?))
492 },
493 None => None,
494 };
495 Ok(Self::new(
496 feature_type,
497 feature_index,
498 QueryFeatureExpressionKind::Range {
499 left: Some((left_op, left_val)),
500 right,
501 },
502 ))
503 }
504
505 pub fn parse_in_parenthesis_block<'i, 't>(
507 context: &ParserContext,
508 input: &mut Parser<'i, 't>,
509 feature_type: FeatureType,
510 ) -> Result<Self, ParseError<'i>> {
511 let (feature_index, range) =
512 match input.try_parse(|input| Self::parse_feature_name(context, input, feature_type)) {
513 Ok(v) => v,
514 Err(e) => {
515 if let Ok(expr) = Self::parse_multi_range_syntax(context, input, feature_type) {
516 return Ok(expr);
517 }
518 return Err(e);
519 },
520 };
521 let operator = input.try_parse(consume_operation_or_colon);
522 let operator = match operator {
523 Err(..) => {
524 if range.is_some() {
530 return Err(
531 input.new_custom_error(StyleParseErrorKind::RangedExpressionWithNoValue)
532 );
533 }
534
535 return Ok(Self::new(
536 feature_type,
537 feature_index,
538 QueryFeatureExpressionKind::Empty,
539 ));
540 },
541 Ok(operator) => operator,
542 };
543
544 let feature = &feature_type.features()[feature_index];
545
546 let value = QueryExpressionValue::parse(feature, context, input).map_err(|err| {
547 err.location
548 .new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue)
549 })?;
550
551 let kind = match range {
552 Some(range) => {
553 if operator.is_some() {
554 return Err(
555 input.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator)
556 );
557 }
558 QueryFeatureExpressionKind::LegacyRange(range, value)
559 },
560 None => match operator {
561 Some(operator) => {
562 if !feature.allows_ranges() {
563 return Err(input
564 .new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator));
565 }
566 QueryFeatureExpressionKind::Range {
567 left: None,
568 right: Some((operator, value)),
569 }
570 },
571 None => QueryFeatureExpressionKind::Single(value),
572 },
573 };
574
575 Ok(Self::new(feature_type, feature_index, kind))
576 }
577
578 pub fn matches(&self, context: &computed::Context) -> KleeneValue {
580 macro_rules! expect {
581 ($variant:ident, $v:expr) => {
582 match *$v {
583 QueryExpressionValue::$variant(ref v) => v,
584 _ => unreachable!("Unexpected QueryExpressionValue"),
585 }
586 };
587 }
588
589 KleeneValue::from(match self.feature().evaluator {
590 Evaluator::Length(eval) => {
591 let v = eval(context);
592 self.kind
593 .evaluate(v, |v| expect!(Length, v).to_computed_value(context))
594 },
595 Evaluator::OptionalLength(eval) => {
596 let v = match eval(context) {
597 Some(v) => v,
598 None => return KleeneValue::Unknown,
599 };
600 self.kind
601 .evaluate(v, |v| expect!(Length, v).to_computed_value(context))
602 },
603 Evaluator::Integer(eval) => {
604 let v = eval(context);
605 self.kind.evaluate(v, |v| *expect!(Integer, v))
606 },
607 Evaluator::Float(eval) => {
608 let v = eval(context);
609 self.kind.evaluate(v, |v| *expect!(Float, v))
610 },
611 Evaluator::NumberRatio(eval) => {
612 let ratio = eval(context);
613 self.kind
618 .evaluate(ratio, |v| expect!(NumberRatio, v).used_value())
619 },
620 Evaluator::OptionalNumberRatio(eval) => {
621 let ratio = match eval(context) {
622 Some(v) => v,
623 None => return KleeneValue::Unknown,
624 };
625 self.kind
627 .evaluate(ratio, |v| expect!(NumberRatio, v).used_value())
628 },
629 Evaluator::Resolution(eval) => {
630 let v = eval(context).dppx();
631 self.kind.evaluate(v, |v| {
632 expect!(Resolution, v).to_computed_value(context).dppx()
633 })
634 },
635 Evaluator::Enumerated { evaluator, .. } => {
636 let computed = self
637 .kind
638 .non_ranged_value()
639 .map(|v| *expect!(Enumerated, v));
640 return evaluator(context, computed);
641 },
642 Evaluator::BoolInteger(eval) => {
643 let computed = self
644 .kind
645 .non_ranged_value()
646 .map(|v| *expect!(BoolInteger, v));
647 let boolean = eval(context);
648 computed.map_or(boolean, |v| v == boolean)
649 },
650 })
651 }
652}
653
654#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
663pub enum QueryExpressionValue {
664 Length(Length),
666 Integer(i32),
668 Float(CSSFloat),
670 BoolInteger(bool),
672 NumberRatio(Ratio),
675 Resolution(Resolution),
677 Enumerated(KeywordDiscriminant),
680}
681
682impl QueryExpressionValue {
683 fn to_css<W>(&self, dest: &mut CssWriter<W>, for_expr: &QueryFeatureExpression) -> fmt::Result
684 where
685 W: fmt::Write,
686 {
687 match *self {
688 QueryExpressionValue::Length(ref l) => l.to_css(dest),
689 QueryExpressionValue::Integer(v) => v.to_css(dest),
690 QueryExpressionValue::Float(v) => v.to_css(dest),
691 QueryExpressionValue::BoolInteger(v) => dest.write_str(if v { "1" } else { "0" }),
692 QueryExpressionValue::NumberRatio(ratio) => ratio.to_css(dest),
693 QueryExpressionValue::Resolution(ref r) => r.to_css(dest),
694 QueryExpressionValue::Enumerated(value) => match for_expr.feature().evaluator {
695 Evaluator::Enumerated { serializer, .. } => dest.write_str(&*serializer(value)),
696 _ => unreachable!(),
697 },
698 }
699 }
700
701 fn parse<'i, 't>(
702 for_feature: &QueryFeatureDescription,
703 context: &ParserContext,
704 input: &mut Parser<'i, 't>,
705 ) -> Result<QueryExpressionValue, ParseError<'i>> {
706 Ok(match for_feature.evaluator {
707 Evaluator::OptionalLength(..) | Evaluator::Length(..) => {
708 let length = Length::parse(context, input)?;
709 QueryExpressionValue::Length(length)
710 },
711 Evaluator::Integer(..) => {
712 let integer = Integer::parse(context, input)?;
713 QueryExpressionValue::Integer(integer.value())
714 },
715 Evaluator::BoolInteger(..) => {
716 let integer = Integer::parse_non_negative(context, input)?;
717 let value = integer.value();
718 if value > 1 {
719 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
720 }
721 QueryExpressionValue::BoolInteger(value == 1)
722 },
723 Evaluator::Float(..) => {
724 let number = Number::parse(context, input)?;
725 QueryExpressionValue::Float(number.get())
726 },
727 Evaluator::OptionalNumberRatio(..) | Evaluator::NumberRatio(..) => {
728 use crate::values::specified::Ratio as SpecifiedRatio;
729 let ratio = SpecifiedRatio::parse(context, input)?;
730 QueryExpressionValue::NumberRatio(Ratio::new(ratio.0.get(), ratio.1.get()))
731 },
732 Evaluator::Resolution(..) => {
733 QueryExpressionValue::Resolution(Resolution::parse(context, input)?)
734 },
735 Evaluator::Enumerated { parser, .. } => {
736 QueryExpressionValue::Enumerated(parser(context, input)?)
737 },
738 })
739 }
740}