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