1use crate::parser::{Parse, ParserContext};
10use crate::properties::PropertyDeclarationBlock;
11use crate::shared_lock::{
12 DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard,
13};
14use crate::stylesheets::{style_or_page_rule_to_css, CssRules};
15use crate::values::{AtomIdent, CustomIdent};
16use cssparser::{Parser, SourceLocation, Token};
17#[cfg(feature = "gecko")]
18use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
19use servo_arc::Arc;
20use smallvec::SmallVec;
21use std::fmt::{self, Write};
22use style_traits::{CssStringWriter, CssWriter, ParseError, ToCss};
23
24macro_rules! page_pseudo_classes {
25 ($($(#[$($meta:tt)+])* $id:ident => $val:literal,)+) => {
26 #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
30 #[repr(u8)]
31 pub enum PagePseudoClass {
32 $($(#[$($meta)+])* $id,)+
33 }
34 impl PagePseudoClass {
35 fn parse<'i, 't>(
36 input: &mut Parser<'i, 't>,
37 ) -> Result<Self, ParseError<'i>> {
38 let loc = input.current_source_location();
39 let colon = input.next_including_whitespace()?;
40 if *colon != Token::Colon {
41 return Err(loc.new_unexpected_token_error(colon.clone()));
42 }
43
44 let ident = input.next_including_whitespace()?;
45 if let Token::Ident(s) = ident {
46 return match_ignore_ascii_case! { &**s,
47 $($val => Ok(PagePseudoClass::$id),)+
48 _ => Err(loc.new_unexpected_token_error(Token::Ident(s.clone()))),
49 };
50 }
51 Err(loc.new_unexpected_token_error(ident.clone()))
52 }
53 #[inline]
54 fn to_str(&self) -> &'static str {
55 match *self {
56 $(PagePseudoClass::$id => concat!(':', $val),)+
57 }
58 }
59 }
60 }
61}
62
63page_pseudo_classes! {
64 First => "first",
68 Blank => "blank",
72 Left => "left",
76 Right => "right",
80}
81
82bitflags! {
83 #[derive(Clone, Copy)]
88 #[repr(C)]
89 pub struct PagePseudoClassFlags : u8 {
90 const NONE = 0;
92 const FIRST = 1 << 0;
94 const BLANK = 1 << 1;
96 const LEFT = 1 << 2;
98 const RIGHT = 1 << 3;
100 }
101}
102
103impl PagePseudoClassFlags {
104 #[inline]
106 pub fn new(other: &PagePseudoClass) -> Self {
107 match *other {
108 PagePseudoClass::First => PagePseudoClassFlags::FIRST,
109 PagePseudoClass::Blank => PagePseudoClassFlags::BLANK,
110 PagePseudoClass::Left => PagePseudoClassFlags::LEFT,
111 PagePseudoClass::Right => PagePseudoClassFlags::RIGHT,
112 }
113 }
114 #[inline]
116 pub fn contains_class(self, other: &PagePseudoClass) -> bool {
117 self.intersects(PagePseudoClassFlags::new(other))
118 }
119}
120
121type PagePseudoClasses = SmallVec<[PagePseudoClass; 4]>;
122
123#[derive(Clone, Debug, MallocSizeOf, ToShmem)]
127pub struct PageSelector {
128 pub name: AtomIdent,
132 pub pseudos: PagePseudoClasses,
136}
137
138#[inline]
149fn selector_specificity(g: usize, h: usize, f: bool) -> u32 {
150 let h = h.min(0xFFFF) as u32;
151 let g = (g.min(0x7FFF) as u32) << 16;
152 let f = if f { 0x80000000 } else { 0 };
153 h + g + f
154}
155
156impl PageSelector {
157 #[inline]
161 pub fn ident_matches(&self, other: &CustomIdent) -> bool {
162 self.name.0 == other.0
163 }
164
165 #[inline]
168 pub fn matches(&self, name: &CustomIdent, flags: PagePseudoClassFlags) -> bool {
169 self.ident_matches(name) && self.flags_match(flags)
170 }
171
172 pub fn flags_match(&self, flags: PagePseudoClassFlags) -> bool {
181 self.pseudos.iter().all(|pc| flags.contains_class(pc))
182 }
183
184 pub fn match_specificity(&self, flags: PagePseudoClassFlags) -> Option<u32> {
195 let mut g: usize = 0;
196 let mut h: usize = 0;
197 for pc in self.pseudos.iter() {
198 if !flags.contains_class(pc) {
199 return None;
200 }
201 match pc {
202 PagePseudoClass::First | PagePseudoClass::Blank => g += 1,
203 PagePseudoClass::Left | PagePseudoClass::Right => h += 1,
204 }
205 }
206 Some(selector_specificity(g, h, !self.name.0.is_empty()))
207 }
208}
209
210impl ToCss for PageSelector {
211 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
212 where
213 W: Write,
214 {
215 self.name.to_css(dest)?;
216 for pc in self.pseudos.iter() {
217 dest.write_str(pc.to_str())?;
218 }
219 Ok(())
220 }
221}
222
223fn parse_page_name<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AtomIdent, ParseError<'i>> {
224 let s = input.expect_ident()?;
225 Ok(AtomIdent::from(&**s))
226}
227
228impl Parse for PageSelector {
229 fn parse<'i, 't>(
230 _context: &ParserContext,
231 input: &mut Parser<'i, 't>,
232 ) -> Result<Self, ParseError<'i>> {
233 let name = input.try_parse(parse_page_name);
234 let mut pseudos = PagePseudoClasses::default();
235 while let Ok(pc) = input.try_parse(PagePseudoClass::parse) {
236 pseudos.push(pc);
237 }
238 let name = match name {
240 Ok(name) => name,
241 Err(..) if !pseudos.is_empty() => AtomIdent::new(atom!("")),
242 Err(err) => return Err(err),
243 };
244 Ok(PageSelector { name, pseudos })
245 }
246}
247
248#[derive(Clone, Debug, Default, MallocSizeOf, ToCss, ToShmem)]
252#[css(comma)]
253pub struct PageSelectors(#[css(iterable)] pub Box<[PageSelector]>);
254
255impl PageSelectors {
256 #[inline]
258 pub fn new(s: Vec<PageSelector>) -> Self {
259 PageSelectors(s.into())
260 }
261 #[inline]
263 pub fn is_empty(&self) -> bool {
264 self.as_slice().is_empty()
265 }
266 #[inline]
268 pub fn as_slice(&self) -> &[PageSelector] {
269 &*self.0
270 }
271}
272
273impl Parse for PageSelectors {
274 fn parse<'i, 't>(
275 context: &ParserContext,
276 input: &mut Parser<'i, 't>,
277 ) -> Result<Self, ParseError<'i>> {
278 Ok(PageSelectors::new(input.parse_comma_separated(|i| {
279 PageSelector::parse(context, i)
280 })?))
281 }
282}
283
284#[derive(Clone, Debug, ToShmem)]
292pub struct PageRule {
293 pub selectors: PageSelectors,
295 pub rules: Arc<Locked<CssRules>>,
297 pub block: Arc<Locked<PropertyDeclarationBlock>>,
299 pub source_location: SourceLocation,
301}
302
303impl PageRule {
304 #[cfg(feature = "gecko")]
306 pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
307 self.rules.unconditional_shallow_size_of(ops)
309 + self.rules.read_with(guard).size_of(guard, ops)
310 + self.block.unconditional_shallow_size_of(ops)
311 + self.block.read_with(guard).size_of(ops)
312 + self.selectors.size_of(ops)
313 }
314 pub fn match_specificity(&self, flags: PagePseudoClassFlags) -> Option<u32> {
324 if self.selectors.is_empty() {
325 return Some(selector_specificity(0, 0, false));
328 }
329 let mut specificity = None;
330 for s in self.selectors.0.iter().map(|s| s.match_specificity(flags)) {
331 specificity = s.max(specificity);
332 }
333 specificity
334 }
335}
336
337impl ToCssWithGuard for PageRule {
338 fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
340 dest.write_str("@page ")?;
342 if !self.selectors.is_empty() {
343 self.selectors.to_css(&mut CssWriter::new(dest))?;
344 dest.write_char(' ')?;
345 }
346 style_or_page_rule_to_css(Some(&self.rules), &self.block, guard, dest)
347 }
348}
349
350impl DeepCloneWithLock for PageRule {
351 fn deep_clone_with_lock(&self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard) -> Self {
352 let rules = self.rules.read_with(&guard);
353 PageRule {
354 selectors: self.selectors.clone(),
355 block: Arc::new(lock.wrap(self.block.read_with(&guard).clone())),
356 rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard))),
357 source_location: self.source_location.clone(),
358 }
359 }
360}