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 fn parse_feature_name<'i, 't>(
375 context: &ParserContext,
376 input: &mut Parser<'i, 't>,
377 feature_type: FeatureType,
378 ) -> Result<(usize, Option<LegacyRange>), ParseError<'i>> {
379 let mut flags = FeatureFlags::empty();
380 let location = input.current_source_location();
381 let ident = input.expect_ident()?;
382
383 if context.chrome_rules_enabled() {
384 flags.insert(FeatureFlags::CHROME_AND_UA_ONLY);
385 }
386
387 let mut feature_name = &**ident;
388 if starts_with_ignore_ascii_case(feature_name, "-webkit-") {
389 feature_name = &feature_name[8..];
390 flags.insert(FeatureFlags::WEBKIT_PREFIX);
391 }
392
393 let range = if starts_with_ignore_ascii_case(feature_name, "min-") {
394 feature_name = &feature_name[4..];
395 Some(LegacyRange::Min)
396 } else if starts_with_ignore_ascii_case(feature_name, "max-") {
397 feature_name = &feature_name[4..];
398 Some(LegacyRange::Max)
399 } else {
400 None
401 };
402
403 let atom = Atom::from(string_as_ascii_lowercase(feature_name));
404 let (feature_index, feature) = match feature_type.find_feature(&atom) {
405 Some((i, f)) => (i, f),
406 None => {
407 return Err(location.new_custom_error(
408 StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
409 ))
410 },
411 };
412
413 if disabled_by_pref(&feature.name, context)
414 || !flags.contains(feature.flags.parsing_requirements())
415 || (range.is_some() && !feature.allows_ranges())
416 {
417 return Err(location.new_custom_error(
418 StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
419 ));
420 }
421
422 Ok((feature_index, range))
423 }
424
425 fn parse_multi_range_syntax<'i, 't>(
430 context: &ParserContext,
431 input: &mut Parser<'i, 't>,
432 feature_type: FeatureType,
433 ) -> Result<Self, ParseError<'i>> {
434 let start = input.state();
435
436 let feature_index = loop {
440 if let Ok((index, range)) = Self::parse_feature_name(context, input, feature_type) {
442 if range.is_some() {
443 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
445 }
446 break index;
447 }
448 if input.is_exhausted() {
449 return Err(start
450 .source_location()
451 .new_custom_error(StyleParseErrorKind::UnspecifiedError));
452 }
453 };
454
455 input.reset(&start);
456
457 let feature = &feature_type.features()[feature_index];
458 let left_val = QueryExpressionValue::parse(feature, context, input)?;
459 let left_op = Operator::parse(input)?;
460
461 {
462 let (parsed_index, _) = Self::parse_feature_name(context, input, feature_type)?;
463 debug_assert_eq!(
464 parsed_index, feature_index,
465 "How did we find a different feature?"
466 );
467 }
468
469 let right_op = input.try_parse(Operator::parse).ok();
470 let right = match right_op {
471 Some(op) => {
472 if !left_op.is_compatible_with(op) {
473 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
474 }
475 Some((op, QueryExpressionValue::parse(feature, context, input)?))
476 },
477 None => None,
478 };
479 Ok(Self::new(
480 feature_type,
481 feature_index,
482 QueryFeatureExpressionKind::Range {
483 left: Some((left_op, left_val)),
484 right,
485 },
486 ))
487 }
488
489 pub fn parse_in_parenthesis_block<'i, 't>(
491 context: &ParserContext,
492 input: &mut Parser<'i, 't>,
493 feature_type: FeatureType,
494 ) -> Result<Self, ParseError<'i>> {
495 let (feature_index, range) =
496 match input.try_parse(|input| Self::parse_feature_name(context, input, feature_type)) {
497 Ok(v) => v,
498 Err(e) => {
499 if let Ok(expr) = Self::parse_multi_range_syntax(context, input, feature_type) {
500 return Ok(expr);
501 }
502 return Err(e);
503 },
504 };
505 let operator = input.try_parse(consume_operation_or_colon);
506 let operator = match operator {
507 Err(..) => {
508 if range.is_some() {
514 return Err(
515 input.new_custom_error(StyleParseErrorKind::RangedExpressionWithNoValue)
516 );
517 }
518
519 return Ok(Self::new(
520 feature_type,
521 feature_index,
522 QueryFeatureExpressionKind::Empty,
523 ));
524 },
525 Ok(operator) => operator,
526 };
527
528 let feature = &feature_type.features()[feature_index];
529
530 let value = QueryExpressionValue::parse(feature, context, input).map_err(|err| {
531 err.location
532 .new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue)
533 })?;
534
535 let kind = match range {
536 Some(range) => {
537 if operator.is_some() {
538 return Err(
539 input.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator)
540 );
541 }
542 QueryFeatureExpressionKind::LegacyRange(range, value)
543 },
544 None => match operator {
545 Some(operator) => {
546 if !feature.allows_ranges() {
547 return Err(input
548 .new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator));
549 }
550 QueryFeatureExpressionKind::Range {
551 left: None,
552 right: Some((operator, value)),
553 }
554 },
555 None => QueryFeatureExpressionKind::Single(value),
556 },
557 };
558
559 Ok(Self::new(feature_type, feature_index, kind))
560 }
561
562 pub fn matches(&self, context: &computed::Context) -> KleeneValue {
564 macro_rules! expect {
565 ($variant:ident, $v:expr) => {
566 match *$v {
567 QueryExpressionValue::$variant(ref v) => v,
568 _ => unreachable!("Unexpected QueryExpressionValue"),
569 }
570 };
571 }
572
573 KleeneValue::from(match self.feature().evaluator {
574 Evaluator::Length(eval) => {
575 let v = eval(context);
576 self.kind
577 .evaluate(v, |v| expect!(Length, v).to_computed_value(context))
578 },
579 Evaluator::OptionalLength(eval) => {
580 let v = match eval(context) {
581 Some(v) => v,
582 None => return KleeneValue::Unknown,
583 };
584 self.kind
585 .evaluate(v, |v| expect!(Length, v).to_computed_value(context))
586 },
587 Evaluator::Integer(eval) => {
588 let v = eval(context);
589 self.kind.evaluate(v, |v| *expect!(Integer, v))
590 },
591 Evaluator::Float(eval) => {
592 let v = eval(context);
593 self.kind.evaluate(v, |v| *expect!(Float, v))
594 },
595 Evaluator::NumberRatio(eval) => {
596 let ratio = eval(context);
597 self.kind
602 .evaluate(ratio, |v| expect!(NumberRatio, v).used_value())
603 },
604 Evaluator::OptionalNumberRatio(eval) => {
605 let ratio = match eval(context) {
606 Some(v) => v,
607 None => return KleeneValue::Unknown,
608 };
609 self.kind
611 .evaluate(ratio, |v| expect!(NumberRatio, v).used_value())
612 },
613 Evaluator::Resolution(eval) => {
614 let v = eval(context).dppx();
615 self.kind.evaluate(v, |v| {
616 expect!(Resolution, v).to_computed_value(context).dppx()
617 })
618 },
619 Evaluator::Enumerated { evaluator, .. } => {
620 let computed = self
621 .kind
622 .non_ranged_value()
623 .map(|v| *expect!(Enumerated, v));
624 return evaluator(context, computed);
625 },
626 Evaluator::BoolInteger(eval) => {
627 let computed = self
628 .kind
629 .non_ranged_value()
630 .map(|v| *expect!(BoolInteger, v));
631 let boolean = eval(context);
632 computed.map_or(boolean, |v| v == boolean)
633 },
634 })
635 }
636}
637
638#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
647pub enum QueryExpressionValue {
648 Length(Length),
650 Integer(i32),
652 Float(CSSFloat),
654 BoolInteger(bool),
656 NumberRatio(Ratio),
659 Resolution(Resolution),
661 Enumerated(KeywordDiscriminant),
664}
665
666impl QueryExpressionValue {
667 fn to_css<W>(&self, dest: &mut CssWriter<W>, for_expr: &QueryFeatureExpression) -> fmt::Result
668 where
669 W: fmt::Write,
670 {
671 match *self {
672 QueryExpressionValue::Length(ref l) => l.to_css(dest),
673 QueryExpressionValue::Integer(v) => v.to_css(dest),
674 QueryExpressionValue::Float(v) => v.to_css(dest),
675 QueryExpressionValue::BoolInteger(v) => dest.write_str(if v { "1" } else { "0" }),
676 QueryExpressionValue::NumberRatio(ratio) => ratio.to_css(dest),
677 QueryExpressionValue::Resolution(ref r) => r.to_css(dest),
678 QueryExpressionValue::Enumerated(value) => match for_expr.feature().evaluator {
679 Evaluator::Enumerated { serializer, .. } => dest.write_str(&*serializer(value)),
680 _ => unreachable!(),
681 },
682 }
683 }
684
685 fn parse<'i, 't>(
686 for_feature: &QueryFeatureDescription,
687 context: &ParserContext,
688 input: &mut Parser<'i, 't>,
689 ) -> Result<QueryExpressionValue, ParseError<'i>> {
690 Ok(match for_feature.evaluator {
691 Evaluator::OptionalLength(..) | Evaluator::Length(..) => {
692 let length = Length::parse(context, input)?;
693 QueryExpressionValue::Length(length)
694 },
695 Evaluator::Integer(..) => {
696 let integer = Integer::parse(context, input)?;
697 QueryExpressionValue::Integer(integer.value())
698 },
699 Evaluator::BoolInteger(..) => {
700 let integer = Integer::parse_non_negative(context, input)?;
701 let value = integer.value();
702 if value > 1 {
703 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
704 }
705 QueryExpressionValue::BoolInteger(value == 1)
706 },
707 Evaluator::Float(..) => {
708 let number = Number::parse(context, input)?;
709 QueryExpressionValue::Float(number.get())
710 },
711 Evaluator::OptionalNumberRatio(..) | Evaluator::NumberRatio(..) => {
712 use crate::values::specified::Ratio as SpecifiedRatio;
713 let ratio = SpecifiedRatio::parse(context, input)?;
714 QueryExpressionValue::NumberRatio(Ratio::new(ratio.0.get(), ratio.1.get()))
715 },
716 Evaluator::Resolution(..) => {
717 QueryExpressionValue::Resolution(Resolution::parse(context, input)?)
718 },
719 Evaluator::Enumerated { parser, .. } => {
720 QueryExpressionValue::Enumerated(parser(context, input)?)
721 },
722 })
723 }
724}