1use super::{FeatureFlags, FeatureType, QueryFeatureExpression};
11use crate::custom_properties;
12use crate::stylesheets::CustomMediaEvaluator;
13use crate::values::{computed, AtomString, DashedIdent};
14use crate::{error_reporting::ContextualParseError, parser::ParserContext};
15use cssparser::{Parser, SourcePosition, Token};
16use selectors::kleene_value::KleeneValue;
17use servo_arc::Arc;
18use std::fmt::{self, Write};
19use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
20
21#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
23#[allow(missing_docs)]
24pub enum Operator {
25 And,
26 Or,
27}
28
29#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToCss)]
31enum AllowOr {
32 Yes,
33 No,
34}
35
36#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
39pub struct StyleFeature {
40 name: custom_properties::Name,
41 #[ignore_malloc_size_of = "Arc"]
43 value: Option<Arc<custom_properties::SpecifiedValue>>,
44}
45
46impl ToCss for StyleFeature {
47 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
48 where
49 W: fmt::Write,
50 {
51 dest.write_str("--")?;
52 crate::values::serialize_atom_identifier(&self.name, dest)?;
53 if let Some(ref v) = self.value {
54 dest.write_str(": ")?;
55 v.to_css(dest)?;
56 }
57 Ok(())
58 }
59}
60
61impl StyleFeature {
62 fn parse<'i, 't>(
63 context: &ParserContext,
64 input: &mut Parser<'i, 't>,
65 feature_type: FeatureType,
66 ) -> Result<Self, ParseError<'i>> {
67 if !static_prefs::pref!("layout.css.style-queries.enabled")
68 || feature_type != FeatureType::Container
69 {
70 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
71 }
72 let ident = input.expect_ident()?;
74 let name = match custom_properties::parse_name(ident.as_ref()) {
76 Ok(name) => custom_properties::Name::from(name),
77 Err(()) => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
78 };
79 let value = if input.try_parse(|i| i.expect_colon()).is_ok() {
80 input.skip_whitespace();
81 Some(Arc::new(custom_properties::SpecifiedValue::parse(
82 input,
83 &context.url_data,
84 )?))
85 } else {
86 None
87 };
88 Ok(Self { name, value })
89 }
90
91 fn matches(&self, ctx: &computed::Context) -> KleeneValue {
92 let registration = ctx
94 .builder
95 .stylist
96 .expect("container queries should have a stylist around")
97 .get_custom_property_registration(&self.name);
98 let current_value = ctx
99 .inherited_custom_properties()
100 .get(registration, &self.name);
101 KleeneValue::from(match self.value {
102 Some(ref v) => current_value.is_some_and(|cur| {
103 custom_properties::compute_variable_value(v, registration, ctx)
104 .is_some_and(|v| v == *cur)
105 }),
106 None => current_value.is_some(),
107 })
108 }
109}
110
111#[derive(
113 Clone,
114 Debug,
115 MallocSizeOf,
116 PartialEq,
117 Eq,
118 Parse,
119 SpecifiedValueInfo,
120 ToComputedValue,
121 ToCss,
122 ToShmem,
123)]
124#[repr(u8)]
125#[allow(missing_docs)]
126pub enum BoolValue {
127 False,
128 True,
129}
130
131#[derive(
134 Clone,
135 Debug,
136 Eq,
137 MallocSizeOf,
138 Parse,
139 PartialEq,
140 SpecifiedValueInfo,
141 ToComputedValue,
142 ToCss,
143 ToShmem,
144)]
145#[repr(u8)]
146pub enum MozPrefFeatureValue<I> {
147 #[css(skip)]
149 None,
150 Boolean(BoolValue),
152 Integer(I),
154 String(crate::values::AtomString),
156}
157
158type SpecifiedMozPrefFeatureValue = MozPrefFeatureValue<crate::values::specified::Integer>;
159pub type ComputedMozPrefFeatureValue = MozPrefFeatureValue<crate::values::computed::Integer>;
161
162#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
164pub struct MozPrefFeature {
165 name: crate::values::AtomString,
166 value: SpecifiedMozPrefFeatureValue,
167}
168
169impl MozPrefFeature {
170 fn parse<'i, 't>(
171 context: &ParserContext,
172 input: &mut Parser<'i, 't>,
173 feature_type: FeatureType,
174 ) -> Result<Self, ParseError<'i>> {
175 use crate::parser::Parse;
176 if !context.chrome_rules_enabled() || feature_type != FeatureType::Media {
177 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
178 }
179 let name = AtomString::parse(context, input)?;
180 let value = if input.try_parse(|i| i.expect_comma()).is_ok() {
181 SpecifiedMozPrefFeatureValue::parse(context, input)?
182 } else {
183 SpecifiedMozPrefFeatureValue::None
184 };
185 Ok(Self { name, value })
186 }
187
188 #[cfg(feature = "gecko")]
189 fn matches(&self, ctx: &computed::Context) -> KleeneValue {
190 use crate::values::computed::ToComputedValue;
191 let value = self.value.to_computed_value(ctx);
192 KleeneValue::from(unsafe {
193 crate::gecko_bindings::bindings::Gecko_EvalMozPrefFeature(self.name.as_ptr(), &value)
194 })
195 }
196
197 #[cfg(feature = "servo")]
198 fn matches(&self, _: &computed::Context) -> KleeneValue {
199 KleeneValue::Unknown
200 }
201}
202
203impl ToCss for MozPrefFeature {
204 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
205 where
206 W: fmt::Write,
207 {
208 self.name.to_css(dest)?;
209 if !matches!(self.value, MozPrefFeatureValue::None) {
210 dest.write_str(", ")?;
211 self.value.to_css(dest)?;
212 }
213 Ok(())
214 }
215}
216
217#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
219pub enum QueryCondition {
220 Feature(QueryFeatureExpression),
222 Custom(DashedIdent),
224 Not(Box<QueryCondition>),
226 Operation(Box<[QueryCondition]>, Operator),
228 InParens(Box<QueryCondition>),
230 Style(StyleFeature),
232 MozPref(MozPrefFeature),
234 GeneralEnclosed(String),
236}
237
238impl ToCss for QueryCondition {
239 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
240 where
241 W: fmt::Write,
242 {
243 match *self {
244 QueryCondition::Feature(ref f) => f.to_css(dest),
247 QueryCondition::Custom(ref name) => {
248 dest.write_char('(')?;
249 name.to_css(dest)?;
250 dest.write_char(')')
251 },
252 QueryCondition::Not(ref c) => {
253 dest.write_str("not ")?;
254 c.to_css(dest)
255 },
256 QueryCondition::InParens(ref c) => {
257 dest.write_char('(')?;
258 c.to_css(dest)?;
259 dest.write_char(')')
260 },
261 QueryCondition::Style(ref c) => {
262 dest.write_str("style(")?;
263 c.to_css(dest)?;
264 dest.write_char(')')
265 },
266 QueryCondition::MozPref(ref c) => {
267 dest.write_str("-moz-pref(")?;
268 c.to_css(dest)?;
269 dest.write_char(')')
270 },
271 QueryCondition::Operation(ref list, op) => {
272 let mut iter = list.iter();
273 iter.next().unwrap().to_css(dest)?;
274 for item in iter {
275 dest.write_char(' ')?;
276 op.to_css(dest)?;
277 dest.write_char(' ')?;
278 item.to_css(dest)?;
279 }
280 Ok(())
281 },
282 QueryCondition::GeneralEnclosed(ref s) => dest.write_str(&s),
283 }
284 }
285}
286
287fn consume_any_value<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(), ParseError<'i>> {
289 input.expect_no_error_token().map_err(Into::into)
290}
291
292impl QueryCondition {
293 pub fn parse<'i, 't>(
295 context: &ParserContext,
296 input: &mut Parser<'i, 't>,
297 feature_type: FeatureType,
298 ) -> Result<Self, ParseError<'i>> {
299 Self::parse_internal(context, input, feature_type, AllowOr::Yes)
300 }
301
302 fn visit<F>(&self, visitor: &mut F)
303 where
304 F: FnMut(&Self),
305 {
306 visitor(self);
307 match *self {
308 Self::Custom(..)
309 | Self::Feature(..)
310 | Self::GeneralEnclosed(..)
311 | Self::Style(..)
312 | Self::MozPref(..) => {},
313 Self::Not(ref cond) => cond.visit(visitor),
314 Self::Operation(ref conds, _op) => {
315 for cond in conds.iter() {
316 cond.visit(visitor);
317 }
318 },
319 Self::InParens(ref cond) => cond.visit(visitor),
320 }
321 }
322
323 pub fn cumulative_flags(&self) -> FeatureFlags {
326 let mut result = FeatureFlags::empty();
327 self.visit(&mut |condition| {
328 if let Self::Style(..) = condition {
329 result.insert(FeatureFlags::STYLE);
330 }
331 if let Self::Feature(ref f) = condition {
332 result.insert(f.feature_flags())
333 }
334 });
335 result
336 }
337
338 pub fn parse_disallow_or<'i, 't>(
342 context: &ParserContext,
343 input: &mut Parser<'i, 't>,
344 feature_type: FeatureType,
345 ) -> Result<Self, ParseError<'i>> {
346 Self::parse_internal(context, input, feature_type, AllowOr::No)
347 }
348
349 fn parse_internal<'i, 't>(
353 context: &ParserContext,
354 input: &mut Parser<'i, 't>,
355 feature_type: FeatureType,
356 allow_or: AllowOr,
357 ) -> Result<Self, ParseError<'i>> {
358 let location = input.current_source_location();
359 if input.try_parse(|i| i.expect_ident_matching("not")).is_ok() {
360 let inner_condition = Self::parse_in_parens(context, input, feature_type)?;
361 return Ok(QueryCondition::Not(Box::new(inner_condition)));
362 }
363
364 let first_condition = Self::parse_in_parens(context, input, feature_type)?;
365 let operator = match input.try_parse(Operator::parse) {
366 Ok(op) => op,
367 Err(..) => return Ok(first_condition),
368 };
369
370 if allow_or == AllowOr::No && operator == Operator::Or {
371 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
372 }
373
374 let mut conditions = vec![];
375 conditions.push(first_condition);
376 conditions.push(Self::parse_in_parens(context, input, feature_type)?);
377
378 let delim = match operator {
379 Operator::And => "and",
380 Operator::Or => "or",
381 };
382
383 loop {
384 if input.try_parse(|i| i.expect_ident_matching(delim)).is_err() {
385 return Ok(QueryCondition::Operation(
386 conditions.into_boxed_slice(),
387 operator,
388 ));
389 }
390
391 conditions.push(Self::parse_in_parens(context, input, feature_type)?);
392 }
393 }
394
395 fn parse_in_parenthesis_block<'i>(
396 context: &ParserContext,
397 input: &mut Parser<'i, '_>,
398 feature_type: FeatureType,
399 ) -> Result<Self, ParseError<'i>> {
400 let feature_error = match input.try_parse(|input| {
403 QueryFeatureExpression::parse_in_parenthesis_block(context, input, feature_type)
404 }) {
405 Ok(expr) => return Ok(Self::Feature(expr)),
406 Err(e) => e,
407 };
408 if let Ok(inner) = Self::parse(context, input, feature_type) {
409 return Ok(Self::InParens(Box::new(inner)));
410 }
411 Err(feature_error)
412 }
413
414 fn try_parse_block<'i, T, F>(
415 context: &ParserContext,
416 input: &mut Parser<'i, '_>,
417 start: SourcePosition,
418 parse: F,
419 ) -> Option<T>
420 where
421 F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i>>,
422 {
423 let nested = input.try_parse(|input| input.parse_nested_block(parse));
424 match nested {
425 Ok(nested) => Some(nested),
426 Err(e) => {
427 let loc = e.location;
430 let error = ContextualParseError::InvalidMediaRule(input.slice_from(start), e);
431 context.log_css_error(loc, error);
432 None
433 },
434 }
435 }
436
437 pub fn parse_in_parens<'i, 't>(
441 context: &ParserContext,
442 input: &mut Parser<'i, 't>,
443 feature_type: FeatureType,
444 ) -> Result<Self, ParseError<'i>> {
445 input.skip_whitespace();
446 let start = input.position();
447 let start_location = input.current_source_location();
448 match *input.next()? {
449 Token::ParenthesisBlock => {
450 let nested = Self::try_parse_block(context, input, start, |input| {
451 Self::parse_in_parenthesis_block(context, input, feature_type)
452 });
453 if let Some(nested) = nested {
454 return Ok(nested);
455 }
456 },
457 Token::Function(ref name) => {
458 match_ignore_ascii_case! { name,
459 "style" => {
460 let feature = Self::try_parse_block(context, input, start, |input| {
461 StyleFeature::parse(context, input, feature_type)
462 });
463 if let Some(feature) = feature {
464 return Ok(Self::Style(feature));
465 }
466 },
467 "-moz-pref" => {
468 let feature = Self::try_parse_block(context, input, start, |input| {
469 MozPrefFeature::parse(context, input, feature_type)
470 });
471 if let Some(feature) = feature {
472 return Ok(Self::MozPref(feature));
473 }
474 },
475 _ => {},
476 }
477 },
478 ref t => return Err(start_location.new_unexpected_token_error(t.clone())),
479 }
480 input.parse_nested_block(consume_any_value)?;
481 Ok(Self::GeneralEnclosed(input.slice_from(start).to_owned()))
482 }
483
484 pub fn matches(
490 &self,
491 context: &computed::Context,
492 custom: &mut CustomMediaEvaluator,
493 ) -> KleeneValue {
494 match *self {
495 QueryCondition::Custom(ref f) => custom.matches(f, context),
496 QueryCondition::Feature(ref f) => f.matches(context),
497 QueryCondition::GeneralEnclosed(_) => KleeneValue::Unknown,
498 QueryCondition::InParens(ref c) => c.matches(context, custom),
499 QueryCondition::Not(ref c) => !c.matches(context, custom),
500 QueryCondition::Style(ref c) => c.matches(context),
501 QueryCondition::MozPref(ref c) => c.matches(context),
502 QueryCondition::Operation(ref conditions, op) => {
503 debug_assert!(!conditions.is_empty(), "We never create an empty op");
504 match op {
505 Operator::And => {
506 KleeneValue::any_false(conditions.iter(), |c| c.matches(context, custom))
507 },
508 Operator::Or => {
509 KleeneValue::any(conditions.iter(), |c| c.matches(context, custom))
510 },
511 }
512 },
513 }
514 }
515}