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