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