selector_parser.rs (10064B)
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 //! The pseudo-classes and pseudo-elements supported by the style system. 6 7 #![deny(missing_docs)] 8 9 use crate::derives::*; 10 use crate::stylesheets::{Namespaces, Origin, UrlExtraData}; 11 use crate::values::serialize_atom_identifier; 12 use crate::Atom; 13 use cssparser::{match_ignore_ascii_case, Parser as CssParser, ParserInput}; 14 use dom::ElementState; 15 use selectors::parser::{ParseRelative, SelectorList}; 16 use std::fmt::{self, Debug, Write}; 17 use style_traits::{CssWriter, ParseError, ToCss}; 18 19 /// A convenient alias for the type that represents an attribute value used for 20 /// selector parser implementation. 21 pub type AttrValue = <SelectorImpl as ::selectors::SelectorImpl>::AttrValue; 22 23 #[cfg(feature = "servo")] 24 pub use crate::servo::selector_parser::*; 25 26 #[cfg(feature = "gecko")] 27 pub use crate::gecko::selector_parser::*; 28 29 #[cfg(feature = "servo")] 30 pub use crate::servo::selector_parser::ServoElementSnapshot as Snapshot; 31 32 #[cfg(feature = "gecko")] 33 pub use crate::gecko::snapshot::GeckoElementSnapshot as Snapshot; 34 35 #[cfg(feature = "servo")] 36 pub use crate::servo::restyle_damage::ServoRestyleDamage as RestyleDamage; 37 38 #[cfg(feature = "gecko")] 39 pub use crate::gecko::restyle_damage::GeckoRestyleDamage as RestyleDamage; 40 41 /// Servo's selector parser. 42 #[cfg_attr(feature = "servo", derive(MallocSizeOf))] 43 pub struct SelectorParser<'a> { 44 /// The origin of the stylesheet we're parsing. 45 pub stylesheet_origin: Origin, 46 /// The namespace set of the stylesheet. 47 pub namespaces: &'a Namespaces, 48 /// The extra URL data of the stylesheet, which is used to look up 49 /// whether we are parsing a chrome:// URL style sheet. 50 pub url_data: &'a UrlExtraData, 51 /// Whether we're parsing selectors for `@supports` 52 pub for_supports_rule: bool, 53 } 54 55 impl<'a> SelectorParser<'a> { 56 /// Parse a selector list with an author origin and without taking into 57 /// account namespaces. 58 /// 59 /// This is used for some DOM APIs like `querySelector`. 60 pub fn parse_author_origin_no_namespace<'i>( 61 input: &'i str, 62 url_data: &UrlExtraData, 63 ) -> Result<SelectorList<SelectorImpl>, ParseError<'i>> { 64 let namespaces = Namespaces::default(); 65 let parser = SelectorParser { 66 stylesheet_origin: Origin::Author, 67 namespaces: &namespaces, 68 url_data, 69 for_supports_rule: false, 70 }; 71 let mut input = ParserInput::new(input); 72 SelectorList::parse(&parser, &mut CssParser::new(&mut input), ParseRelative::No) 73 } 74 75 /// Whether we're parsing selectors in a user-agent stylesheet. 76 pub fn in_user_agent_stylesheet(&self) -> bool { 77 matches!(self.stylesheet_origin, Origin::UserAgent) 78 } 79 80 /// Whether we're parsing selectors in a stylesheet that has chrome 81 /// privilege. 82 pub fn chrome_rules_enabled(&self) -> bool { 83 self.url_data.chrome_rules_enabled() || self.stylesheet_origin == Origin::User 84 } 85 } 86 87 /// This enumeration determines how a pseudo-element cascades. 88 #[derive(Clone, Debug, Eq, PartialEq)] 89 pub enum PseudoElementCascadeType { 90 /// Eagerly cascaded pseudo-elements are "normal" pseudo-elements (i.e. 91 /// `::before` and `::after`). They inherit styles normally as another 92 /// selector would do, and they're computed as part of the cascade. 93 /// 94 /// These kind of pseudo-elements require more up-front computation and 95 /// storage and thus should used for public pseudo-elements that can be used 96 /// on many element types (such as `::before` and `::after`). 97 Eager, 98 /// Lazy pseudo-elements are affected by selector matching, but they're only 99 /// computed when needed, and not before. They're useful for general 100 /// pseudo-elements that are not very common or that do not apply to many 101 /// elements. For instance in Servo this is used for `::backdrop` and 102 /// `::marker`. 103 Lazy, 104 /// Precomputed pseudo-elements skip the cascade process entirely, mostly as 105 /// an optimisation since they are private pseudo-elements (like 106 /// `::-servo-details-content`). 107 /// 108 /// This pseudo-elements are resolved on the fly using *only* global rules 109 /// (rules of the form `*|*`), and applying them to the parent style so are 110 /// mainly useful for user-agent stylesheets. 111 Precomputed, 112 } 113 114 /// A per-pseudo map, from a given pseudo to a `T`. 115 #[derive(Clone, MallocSizeOf)] 116 pub struct PerPseudoElementMap<T> { 117 sparse: [i8; PSEUDO_COUNT], 118 dense: Vec<T>, 119 } 120 121 impl<T> Default for PerPseudoElementMap<T> { 122 fn default() -> Self { 123 Self { 124 dense: Vec::new(), 125 sparse: [const { -1 }; PSEUDO_COUNT], 126 } 127 } 128 } 129 130 impl<T> Debug for PerPseudoElementMap<T> 131 where 132 T: Debug, 133 { 134 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 135 f.write_char('[')?; 136 for idx in 0..=PSEUDO_COUNT { 137 if idx > 0 { 138 f.write_str(", ")?; 139 } 140 debug_assert!(self.sparse.get(idx).is_some()); 141 let i = self.sparse[idx]; 142 if i < 0 { 143 None::<T>.fmt(f)?; 144 } else { 145 Some(&self.dense[i as usize]).fmt(f)?; 146 } 147 } 148 f.write_char(']') 149 } 150 } 151 152 impl<T> PerPseudoElementMap<T> { 153 /// Get an entry in the map. 154 pub fn get(&self, pseudo: &PseudoElement) -> Option<&T> { 155 let idx = pseudo.index(); 156 debug_assert!(self.sparse.get(idx).is_some()); 157 let i = self.sparse[idx]; 158 if i < 0 { 159 None 160 } else { 161 Some(&self.dense[i as usize]) 162 } 163 } 164 165 /// Clear this enumerated array. 166 pub fn clear(&mut self) { 167 self.dense.clear(); 168 self.sparse.fill(-1); 169 } 170 171 /// Set an entry value. 172 /// 173 /// Returns an error if the element is not a simple pseudo. 174 pub fn set(&mut self, pseudo: &PseudoElement, value: T) { 175 let idx = pseudo.index(); 176 let i = self.sparse[idx]; 177 if i < 0 { 178 let i = self.dense.len() as i8; 179 self.dense.push(value); 180 self.sparse[idx] = i 181 } else { 182 self.dense[i as usize] = value 183 } 184 } 185 186 /// Get an entry for `pseudo`, or create it with calling `f`. 187 pub fn get_or_insert_with<F>(&mut self, pseudo: &PseudoElement, f: F) -> &mut T 188 where 189 F: FnOnce() -> T, 190 { 191 let idx = pseudo.index(); 192 let mut i = self.sparse[idx]; 193 if i < 0 { 194 i = self.dense.len() as i8; 195 debug_assert!(self.dense.len() < PSEUDO_COUNT); 196 self.dense.push(f()); 197 self.sparse[idx] = i; 198 } 199 debug_assert!(self.dense.get(i as usize).is_some()); 200 &mut self.dense[i as usize] 201 } 202 203 /// Get an iterator for the entries. 204 pub fn iter(&self) -> impl Iterator<Item = &T> { 205 self.dense.iter() 206 } 207 208 /// Get a mutable iterator for the entries. 209 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> { 210 self.dense.iter_mut() 211 } 212 } 213 214 /// Values for the :dir() pseudo class 215 /// 216 /// "ltr" and "rtl" values are normalized to lowercase. 217 #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] 218 pub struct Direction(pub Atom); 219 220 /// Horizontal values for the :dir() pseudo class 221 #[derive(Clone, Debug, Eq, PartialEq)] 222 pub enum HorizontalDirection { 223 /// :dir(ltr) 224 Ltr, 225 /// :dir(rtl) 226 Rtl, 227 } 228 229 impl Direction { 230 /// Parse a direction value. 231 pub fn parse<'i, 't>(parser: &mut CssParser<'i, 't>) -> Result<Self, ParseError<'i>> { 232 let ident = parser.expect_ident()?; 233 Ok(Direction(match_ignore_ascii_case! { &ident, 234 "rtl" => atom!("rtl"), 235 "ltr" => atom!("ltr"), 236 _ => Atom::from(ident.as_ref()), 237 })) 238 } 239 240 /// Convert this Direction into a HorizontalDirection, if applicable 241 pub fn as_horizontal_direction(&self) -> Option<HorizontalDirection> { 242 if self.0 == atom!("ltr") { 243 Some(HorizontalDirection::Ltr) 244 } else if self.0 == atom!("rtl") { 245 Some(HorizontalDirection::Rtl) 246 } else { 247 None 248 } 249 } 250 251 /// Gets the element state relevant to this :dir() selector. 252 pub fn element_state(&self) -> ElementState { 253 match self.as_horizontal_direction() { 254 Some(HorizontalDirection::Ltr) => ElementState::LTR, 255 Some(HorizontalDirection::Rtl) => ElementState::RTL, 256 None => ElementState::empty(), 257 } 258 } 259 } 260 261 impl ToCss for Direction { 262 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 263 where 264 W: Write, 265 { 266 serialize_atom_identifier(&self.0, dest) 267 } 268 } 269 270 #[cfg(test)] 271 mod tests { 272 use super::*; 273 274 #[test] 275 fn can_build_and_set_arbitrary_index() { 276 let mut map = <PerPseudoElementMap<i32>>::default(); 277 assert_eq!(map.get(&PseudoElement::After), None); 278 map.set(&PseudoElement::After, 3); 279 assert_eq!(map.get(&PseudoElement::After), Some(3).as_ref()); 280 281 assert_eq!(map.get(&PseudoElement::RubyText), None); 282 map.set(&PseudoElement::RubyText, 8); 283 assert_eq!(map.get(&PseudoElement::RubyText), Some(8).as_ref()); 284 285 assert_eq!( 286 map.get_or_insert_with(&PseudoElement::RubyText, || { 10 }), 287 &8 288 ); 289 map.set(&PseudoElement::RubyText, 9); 290 assert_eq!(map.get(&PseudoElement::RubyText), Some(9).as_ref()); 291 292 assert_eq!( 293 map.get_or_insert_with(&PseudoElement::FirstLine, || { 10 }), 294 &10 295 ); 296 assert_eq!(map.get(&PseudoElement::FirstLine), Some(10).as_ref()); 297 } 298 299 #[test] 300 fn can_iter() { 301 let mut map = <PerPseudoElementMap<i32>>::default(); 302 map.set(&PseudoElement::After, 3); 303 map.set(&PseudoElement::RubyText, 8); 304 assert_eq!(map.iter().cloned().collect::<Vec<_>>(), vec![3, 8]); 305 } 306 }