media_rule.rs (7137B)
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 9 use crate::derives::*; 10 use crate::media_queries::MediaList; 11 use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet}; 12 use crate::shared_lock::{DeepCloneWithLock, Locked}; 13 use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; 14 use crate::stylesheets::CssRules; 15 use crate::values::{computed, DashedIdent}; 16 use crate::Atom; 17 use cssparser::Parser; 18 use cssparser::SourceLocation; 19 #[cfg(feature = "gecko")] 20 use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; 21 use selectors::kleene_value::KleeneValue; 22 use servo_arc::Arc; 23 use std::fmt::{self, Write}; 24 use 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)] 30 pub 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 39 impl 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 49 impl 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 61 impl 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)] 75 pub 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 84 impl 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 94 impl 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)] 109 pub 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 118 impl 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 128 impl 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. 146 pub type CustomMediaMap = PrecomputedHashMap<Atom, CustomMediaCondition>; 147 148 /// A struct that can evaluate custom media conditions. 149 pub 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 155 impl<'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 }