style/stylesheets/
media_rule.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! An [`@media`][media] rule.
6//!
7//! [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media
8
9use crate::media_queries::MediaList;
10use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet};
11use crate::shared_lock::{DeepCloneWithLock, Locked};
12use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
13use crate::stylesheets::CssRules;
14use crate::values::{computed, DashedIdent};
15use crate::Atom;
16use cssparser::Parser;
17use cssparser::SourceLocation;
18#[cfg(feature = "gecko")]
19use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
20use selectors::kleene_value::KleeneValue;
21use servo_arc::Arc;
22use std::fmt::{self, Write};
23use style_traits::{CssStringWriter, CssWriter, ParseError, ToCss};
24
25/// An [`@media`][media] rule.
26///
27/// [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media
28#[derive(Debug, ToShmem)]
29pub struct MediaRule {
30    /// The list of media queries used by this media rule.
31    pub media_queries: Arc<Locked<MediaList>>,
32    /// The nested rules to this media rule.
33    pub rules: Arc<Locked<CssRules>>,
34    /// The source position where this media rule was found.
35    pub source_location: SourceLocation,
36}
37
38impl MediaRule {
39    /// Measure heap usage.
40    #[cfg(feature = "gecko")]
41    pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
42        // Measurement of other fields may be added later.
43        self.rules.unconditional_shallow_size_of(ops)
44            + self.rules.read_with(guard).size_of(guard, ops)
45    }
46}
47
48impl ToCssWithGuard for MediaRule {
49    // Serialization of MediaRule is not specced.
50    // https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSMediaRule
51    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
52        dest.write_str("@media ")?;
53        self.media_queries
54            .read_with(guard)
55            .to_css(&mut CssWriter::new(dest))?;
56        self.rules.read_with(guard).to_css_block(guard, dest)
57    }
58}
59
60impl DeepCloneWithLock for MediaRule {
61    fn deep_clone_with_lock(&self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard) -> Self {
62        let media_queries = self.media_queries.read_with(guard);
63        let rules = self.rules.read_with(guard);
64        MediaRule {
65            media_queries: Arc::new(lock.wrap(media_queries.clone())),
66            rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard))),
67            source_location: self.source_location.clone(),
68        }
69    }
70}
71
72/// The condition associated to a custom-media query.
73#[derive(Debug, ToShmem, Clone, MallocSizeOf)]
74pub enum CustomMediaCondition {
75    /// Unconditionally true.
76    True,
77    /// Unconditionally false.
78    False,
79    /// A MediaList.
80    MediaList(#[ignore_malloc_size_of = "Arc"] Arc<Locked<MediaList>>),
81}
82
83impl CustomMediaCondition {
84    /// Parses the possible keywords for this condition.
85    pub(crate) fn parse_keyword<'i>(input: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
86        Ok(try_match_ident_ignore_ascii_case! { input,
87            "true" => Self::True,
88            "false" => Self::False,
89        })
90    }
91}
92
93impl DeepCloneWithLock for CustomMediaCondition {
94    fn deep_clone_with_lock(&self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard) -> Self {
95        match self {
96            Self::True => Self::True,
97            Self::False => Self::False,
98            Self::MediaList(ref m) => {
99                Self::MediaList(Arc::new(lock.wrap(m.read_with(guard).clone())))
100            },
101        }
102    }
103}
104
105/// A `@custom-media` rule.
106/// https://drafts.csswg.org/mediaqueries-5/#custom-mq
107#[derive(Debug, ToShmem)]
108pub struct CustomMediaRule {
109    /// The name of the custom media rule.
110    pub name: DashedIdent,
111    /// The list of media conditions used by this media rule.
112    pub condition: CustomMediaCondition,
113    /// The source position where this media rule was found.
114    pub source_location: SourceLocation,
115}
116
117impl DeepCloneWithLock for CustomMediaRule {
118    fn deep_clone_with_lock(&self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard) -> Self {
119        Self {
120            name: self.name.clone(),
121            condition: self.condition.deep_clone_with_lock(lock, guard),
122            source_location: self.source_location.clone(),
123        }
124    }
125}
126
127impl ToCssWithGuard for CustomMediaRule {
128    // Serialization of MediaRule is not specced.
129    // https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSMediaRule
130    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
131        dest.write_str("@custom-media ")?;
132        self.name.to_css(&mut CssWriter::new(dest))?;
133        dest.write_char(' ')?;
134        match self.condition {
135            CustomMediaCondition::True => dest.write_str("true"),
136            CustomMediaCondition::False => dest.write_str("false"),
137            CustomMediaCondition::MediaList(ref m) => {
138                m.read_with(guard).to_css(&mut CssWriter::new(dest))
139            },
140        }
141    }
142}
143
144/// The currently effective @custom-media conditions.
145pub type CustomMediaMap = PrecomputedHashMap<Atom, CustomMediaCondition>;
146
147/// A struct that can evaluate custom media conditions.
148pub struct CustomMediaEvaluator<'a> {
149    map: Option<(&'a CustomMediaMap, &'a SharedRwLockReadGuard<'a>)>,
150    /// Set of media queries we're currently evaluating, needed for cycle detection.
151    currently_evaluating: PrecomputedHashSet<Atom>,
152}
153
154impl<'a> CustomMediaEvaluator<'a> {
155    /// Construct a new custom media evaluator with the given map and guard.
156    pub fn new(map: &'a CustomMediaMap, guard: &'a SharedRwLockReadGuard<'a>) -> Self {
157        Self {
158            map: Some((map, guard)),
159            currently_evaluating: Default::default(),
160        }
161    }
162
163    /// Returns an evaluator that can't evaluate custom queries.
164    pub fn none() -> Self {
165        Self {
166            map: None,
167            currently_evaluating: Default::default(),
168        }
169    }
170
171    /// Evaluates a custom media query.
172    pub fn matches(&mut self, ident: &DashedIdent, context: &computed::Context) -> KleeneValue {
173        let Some((map, guard)) = self.map else {
174            return KleeneValue::Unknown;
175        };
176        let Some(condition) = map.get(&ident.0) else {
177            return KleeneValue::Unknown;
178        };
179        let media = match condition {
180            CustomMediaCondition::True => return KleeneValue::True,
181            CustomMediaCondition::False => return KleeneValue::False,
182            CustomMediaCondition::MediaList(ref m) => m,
183        };
184        if !self.currently_evaluating.insert(ident.0.clone()) {
185            // Found a cycle while evaluating this rule.
186            return KleeneValue::False;
187        }
188        let result = media.read_with(guard).matches(context, self);
189        self.currently_evaluating.remove(&ident.0);
190        result.into()
191    }
192}