1use crate::parser::{Parse, ParserContext};
10use crate::properties::PropertyDeclarationBlock;
11use crate::shared_lock::{
12 DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard,
13};
14use crate::str::CssStringWriter;
15use crate::stylesheets::{CssRules, style_or_page_rule_to_css};
16use crate::values::{AtomIdent, CustomIdent};
17use cssparser::{Parser, SourceLocation, Token};
18#[cfg(feature = "gecko")]
19use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
20use servo_arc::Arc;
21use smallvec::SmallVec;
22use std::fmt::{self, Write};
23use style_traits::{CssWriter, ParseError, ToCss};
24
25macro_rules! page_pseudo_classes {
26 ($($(#[$($meta:tt)+])* $id:ident => $val:literal,)+) => {
27 #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
31 #[repr(u8)]
32 pub enum PagePseudoClass {
33 $($(#[$($meta)+])* $id,)+
34 }
35 impl PagePseudoClass {
36 fn parse<'i, 't>(
37 input: &mut Parser<'i, 't>,
38 ) -> Result<Self, ParseError<'i>> {
39 let loc = input.current_source_location();
40 let colon = input.next_including_whitespace()?;
41 if *colon != Token::Colon {
42 return Err(loc.new_unexpected_token_error(colon.clone()));
43 }
44
45 let ident = input.next_including_whitespace()?;
46 if let Token::Ident(s) = ident {
47 return match_ignore_ascii_case! { &**s,
48 $($val => Ok(PagePseudoClass::$id),)+
49 _ => Err(loc.new_unexpected_token_error(Token::Ident(s.clone()))),
50 };
51 }
52 Err(loc.new_unexpected_token_error(ident.clone()))
53 }
54 #[inline]
55 fn to_str(&self) -> &'static str {
56 match *self {
57 $(PagePseudoClass::$id => concat!(':', $val),)+
58 }
59 }
60 }
61 }
62}
63
64page_pseudo_classes! {
65 First => "first",
69 Blank => "blank",
73 Left => "left",
77 Right => "right",
81}
82
83bitflags! {
84 #[derive(Clone, Copy)]
89 #[repr(C)]
90 pub struct PagePseudoClassFlags : u8 {
91 const NONE = 0;
93 const FIRST = 1 << 0;
95 const BLANK = 1 << 1;
97 const LEFT = 1 << 2;
99 const RIGHT = 1 << 3;
101 }
102}
103
104impl PagePseudoClassFlags {
105 #[inline]
107 pub fn new(other: &PagePseudoClass) -> Self {
108 match *other {
109 PagePseudoClass::First => PagePseudoClassFlags::FIRST,
110 PagePseudoClass::Blank => PagePseudoClassFlags::BLANK,
111 PagePseudoClass::Left => PagePseudoClassFlags::LEFT,
112 PagePseudoClass::Right => PagePseudoClassFlags::RIGHT,
113 }
114 }
115 #[inline]
117 pub fn contains_class(self, other: &PagePseudoClass) -> bool {
118 self.intersects(PagePseudoClassFlags::new(other))
119 }
120}
121
122type PagePseudoClasses = SmallVec<[PagePseudoClass; 4]>;
123
124#[derive(Clone, Debug, MallocSizeOf, ToShmem)]
128pub struct PageSelector {
129 pub name: AtomIdent,
133 pub pseudos: PagePseudoClasses,
137}
138
139#[inline]
150fn selector_specificity(g: usize, h: usize, f: bool) -> u32 {
151 let h = h.min(0xFFFF) as u32;
152 let g = (g.min(0x7FFF) as u32) << 16;
153 let f = if f {
154 0x80000000
155 } else {
156 0
157 };
158 h + g + f
159}
160
161impl PageSelector {
162 #[inline]
166 pub fn ident_matches(&self, other: &CustomIdent) -> bool {
167 self.name.0 == other.0
168 }
169
170 #[inline]
173 pub fn matches(&self, name: &CustomIdent, flags: PagePseudoClassFlags) -> bool {
174 self.ident_matches(name) && self.flags_match(flags)
175 }
176
177 pub fn flags_match(&self, flags: PagePseudoClassFlags) -> bool {
186 self.pseudos.iter().all(|pc| flags.contains_class(pc))
187 }
188
189 pub fn match_specificity(&self, flags: PagePseudoClassFlags) -> Option<u32> {
200 let mut g: usize = 0;
201 let mut h: usize = 0;
202 for pc in self.pseudos.iter() {
203 if !flags.contains_class(pc) {
204 return None;
205 }
206 match pc {
207 PagePseudoClass::First | PagePseudoClass::Blank => g += 1,
208 PagePseudoClass::Left | PagePseudoClass::Right => h += 1,
209 }
210 }
211 Some(selector_specificity(g, h, !self.name.0.is_empty()))
212 }
213}
214
215impl ToCss for PageSelector {
216 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
217 where
218 W: Write,
219 {
220 self.name.to_css(dest)?;
221 for pc in self.pseudos.iter() {
222 dest.write_str(pc.to_str())?;
223 }
224 Ok(())
225 }
226}
227
228fn parse_page_name<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AtomIdent, ParseError<'i>> {
229 let s = input.expect_ident()?;
230 Ok(AtomIdent::from(&**s))
231}
232
233impl Parse for PageSelector {
234 fn parse<'i, 't>(
235 _context: &ParserContext,
236 input: &mut Parser<'i, 't>,
237 ) -> Result<Self, ParseError<'i>> {
238 let name = input.try_parse(parse_page_name);
239 let mut pseudos = PagePseudoClasses::default();
240 while let Ok(pc) = input.try_parse(PagePseudoClass::parse) {
241 pseudos.push(pc);
242 }
243 let name = match name {
245 Ok(name) => name,
246 Err(..) if !pseudos.is_empty() => AtomIdent::new(atom!("")),
247 Err(err) => return Err(err),
248 };
249 Ok(PageSelector { name, pseudos })
250 }
251}
252
253#[derive(Clone, Debug, Default, MallocSizeOf, ToCss, ToShmem)]
257#[css(comma)]
258pub struct PageSelectors(#[css(iterable)] pub Box<[PageSelector]>);
259
260impl PageSelectors {
261 #[inline]
263 pub fn new(s: Vec<PageSelector>) -> Self {
264 PageSelectors(s.into())
265 }
266 #[inline]
268 pub fn is_empty(&self) -> bool {
269 self.as_slice().is_empty()
270 }
271 #[inline]
273 pub fn as_slice(&self) -> &[PageSelector] {
274 &*self.0
275 }
276}
277
278impl Parse for PageSelectors {
279 fn parse<'i, 't>(
280 context: &ParserContext,
281 input: &mut Parser<'i, 't>,
282 ) -> Result<Self, ParseError<'i>> {
283 Ok(PageSelectors::new(input.parse_comma_separated(|i| {
284 PageSelector::parse(context, i)
285 })?))
286 }
287}
288
289#[derive(Clone, Debug, ToShmem)]
297pub struct PageRule {
298 pub selectors: PageSelectors,
300 pub rules: Arc<Locked<CssRules>>,
302 pub block: Arc<Locked<PropertyDeclarationBlock>>,
304 pub source_location: SourceLocation,
306}
307
308impl PageRule {
309 #[cfg(feature = "gecko")]
311 pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
312 self.rules.unconditional_shallow_size_of(ops) +
314 self.rules.read_with(guard).size_of(guard, ops) +
315 self.block.unconditional_shallow_size_of(ops) +
316 self.block.read_with(guard).size_of(ops) +
317 self.selectors.size_of(ops)
318 }
319 pub fn match_specificity(&self, flags: PagePseudoClassFlags) -> Option<u32> {
329 if self.selectors.is_empty() {
330 return Some(selector_specificity(0, 0, false));
333 }
334 let mut specificity = None;
335 for s in self.selectors.0.iter().map(|s| s.match_specificity(flags)) {
336 specificity = s.max(specificity);
337 }
338 specificity
339 }
340}
341
342impl ToCssWithGuard for PageRule {
343 fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
345 dest.write_str("@page ")?;
347 if !self.selectors.is_empty() {
348 self.selectors.to_css(&mut CssWriter::new(dest))?;
349 dest.write_char(' ')?;
350 }
351 style_or_page_rule_to_css(Some(&self.rules), &self.block, guard, dest)
352 }
353}
354
355impl DeepCloneWithLock for PageRule {
356 fn deep_clone_with_lock(
357 &self,
358 lock: &SharedRwLock,
359 guard: &SharedRwLockReadGuard,
360 ) -> Self {
361 let rules = self.rules.read_with(&guard);
362 PageRule {
363 selectors: self.selectors.clone(),
364 block: Arc::new(lock.wrap(self.block.read_with(&guard).clone())),
365 rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard))),
366 source_location: self.source_location.clone(),
367 }
368 }
369}