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