From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- servo/components/style/stylesheets/page_rule.rs | 366 ++++++++++++++++++++++++ 1 file changed, 366 insertions(+) create mode 100644 servo/components/style/stylesheets/page_rule.rs (limited to 'servo/components/style/stylesheets/page_rule.rs') diff --git a/servo/components/style/stylesheets/page_rule.rs b/servo/components/style/stylesheets/page_rule.rs new file mode 100644 index 0000000000..a1618309a3 --- /dev/null +++ b/servo/components/style/stylesheets/page_rule.rs @@ -0,0 +1,366 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! A [`@page`][page] rule. +//! +//! [page]: https://drafts.csswg.org/css2/page.html#page-box + +use crate::parser::{Parse, ParserContext}; +use crate::properties::PropertyDeclarationBlock; +use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked}; +use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; +use crate::stylesheets::CssRules; +use crate::str::CssStringWriter; +use crate::values::{AtomIdent, CustomIdent}; +use cssparser::{Parser, SourceLocation, Token}; +#[cfg(feature = "gecko")] +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; +use servo_arc::Arc; +use smallvec::SmallVec; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ParseError, ToCss}; + +macro_rules! page_pseudo_classes { + ($($(#[$($meta:tt)+])* $id:ident => $val:literal,)+) => { + /// [`@page`][page] rule pseudo-classes. + /// + /// https://drafts.csswg.org/css-page-3/#page-selectors + #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] + #[repr(u8)] + pub enum PagePseudoClass { + $($(#[$($meta)+])* $id,)+ + } + impl PagePseudoClass { + fn parse<'i, 't>( + input: &mut Parser<'i, 't>, + ) -> Result> { + let loc = input.current_source_location(); + let colon = input.next_including_whitespace()?; + if *colon != Token::Colon { + return Err(loc.new_unexpected_token_error(colon.clone())); + } + + let ident = input.next_including_whitespace()?; + if let Token::Ident(s) = ident { + return match_ignore_ascii_case! { &**s, + $($val => Ok(PagePseudoClass::$id),)+ + _ => Err(loc.new_unexpected_token_error(Token::Ident(s.clone()))), + }; + } + Err(loc.new_unexpected_token_error(ident.clone())) + } + #[inline] + fn to_str(&self) -> &'static str { + match *self { + $(PagePseudoClass::$id => concat!(':', $val),)+ + } + } + } + } +} + +page_pseudo_classes! { + /// [`:first`][first] pseudo-class + /// + /// [first] https://drafts.csswg.org/css-page-3/#first-pseudo + First => "first", + /// [`:blank`][blank] pseudo-class + /// + /// [blank] https://drafts.csswg.org/css-page-3/#blank-pseudo + Blank => "blank", + /// [`:left`][left] pseudo-class + /// + /// [left]: https://drafts.csswg.org/css-page-3/#spread-pseudos + Left => "left", + /// [`:right`][right] pseudo-class + /// + /// [right]: https://drafts.csswg.org/css-page-3/#spread-pseudos + Right => "right", +} + +bitflags! { + /// Bit-flags for pseudo-class. This should only be used for querying if a + /// page-rule applies. + /// + /// https://drafts.csswg.org/css-page-3/#page-selectors + #[derive(Clone, Copy)] + #[repr(C)] + pub struct PagePseudoClassFlags : u8 { + /// No pseudo-classes + const NONE = 0; + /// Flag for PagePseudoClass::First + const FIRST = 1 << 0; + /// Flag for PagePseudoClass::Blank + const BLANK = 1 << 1; + /// Flag for PagePseudoClass::Left + const LEFT = 1 << 2; + /// Flag for PagePseudoClass::Right + const RIGHT = 1 << 3; + } +} + +impl PagePseudoClassFlags { + /// Creates a pseudo-class flags object with a single pseudo-class. + #[inline] + pub fn new(other: &PagePseudoClass) -> Self { + match *other { + PagePseudoClass::First => PagePseudoClassFlags::FIRST, + PagePseudoClass::Blank => PagePseudoClassFlags::BLANK, + PagePseudoClass::Left => PagePseudoClassFlags::LEFT, + PagePseudoClass::Right => PagePseudoClassFlags::RIGHT, + } + } + /// Checks if the given pseudo class applies to this set of flags. + #[inline] + pub fn contains_class(self, other: &PagePseudoClass) -> bool { + self.intersects(PagePseudoClassFlags::new(other)) + } +} + +type PagePseudoClasses = SmallVec<[PagePseudoClass; 4]>; + +/// Type of a single [`@page`][page selector] +/// +/// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors +#[derive(Clone, Debug, MallocSizeOf, ToShmem)] +pub struct PageSelector { + /// Page name + /// + /// https://drafts.csswg.org/css-page-3/#page-type-selector + pub name: AtomIdent, + /// Pseudo-classes for [`@page`][page-selectors] + /// + /// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors + pub pseudos: PagePseudoClasses, +} + +impl PageSelector { + /// Checks if the ident matches a page-name's ident. + /// + /// This does not take pseudo selectors into account. + #[inline] + pub fn ident_matches(&self, other: &CustomIdent) -> bool { + self.name.0 == other.0 + } + + /// Checks that this selector matches the ident and all pseudo classes are + /// present in the provided flags. + #[inline] + pub fn matches(&self, name: &CustomIdent, flags: PagePseudoClassFlags) -> bool { + self.ident_matches(name) && self.flags_match(flags) + } + + /// Checks that all pseudo classes in this selector are present in the + /// provided flags. + /// + /// Equivalent to, but may be more efficient than: + /// + /// ``` + /// match_specificity(flags).is_some() + /// ``` + pub fn flags_match(&self, flags: PagePseudoClassFlags) -> bool { + self.pseudos.iter().all(|pc| flags.contains_class(pc)) + } + + /// Implements specificity calculation for a page selector given a set of + /// page pseudo-classes to match with. + /// If this selector includes any pseudo-classes that are not in the flags, + /// then this will return None. + /// + /// To fit the specificity calculation into a 32-bit value, this limits the + /// maximum count of :first and :blank to 32767, and the maximum count of + /// :left and :right to 65535. + /// + /// https://drafts.csswg.org/css-page-3/#cascading-and-page-context + pub fn match_specificity(&self, flags: PagePseudoClassFlags) -> Option { + let mut g: usize = 0; + let mut h: usize = 0; + for pc in self.pseudos.iter() { + if !flags.contains_class(pc) { + return None; + } + match pc { + PagePseudoClass::First | PagePseudoClass::Blank => g += 1, + PagePseudoClass::Left | PagePseudoClass::Right => h += 1, + } + } + let h = h.min(0xFFFF) as u32; + let g = (g.min(0x7FFF) as u32) << 16; + let f = if self.name.0.is_empty() { + 0 + } else { + 0x80000000 + }; + Some(h + g + f) + } +} + +impl ToCss for PageSelector { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + self.name.to_css(dest)?; + for pc in self.pseudos.iter() { + dest.write_str(pc.to_str())?; + } + Ok(()) + } +} + +fn parse_page_name<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { + let s = input.expect_ident()?; + Ok(AtomIdent::from(&**s)) +} + +impl Parse for PageSelector { + fn parse<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + let name = input + .try_parse(parse_page_name) + .unwrap_or(AtomIdent(atom!(""))); + let mut pseudos = PagePseudoClasses::default(); + while let Ok(pc) = input.try_parse(PagePseudoClass::parse) { + pseudos.push(pc); + } + Ok(PageSelector { name, pseudos }) + } +} + +/// A list of [`@page`][page selectors] +/// +/// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors +#[derive(Clone, Debug, Default, MallocSizeOf, ToCss, ToShmem)] +#[css(comma)] +pub struct PageSelectors(#[css(iterable)] pub Box<[PageSelector]>); + +impl PageSelectors { + /// Creates a new PageSelectors from a Vec, as from parse_comma_separated + #[inline] + pub fn new(s: Vec) -> Self { + PageSelectors(s.into()) + } + /// Returns true iff there are any page selectors + #[inline] + pub fn is_empty(&self) -> bool { + self.as_slice().is_empty() + } + /// Get the underlying PageSelector data as a slice + #[inline] + pub fn as_slice(&self) -> &[PageSelector] { + &*self.0 + } +} + +impl Parse for PageSelectors { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + Ok(PageSelectors::new(input.parse_comma_separated(|i| { + PageSelector::parse(context, i) + })?)) + } +} + +/// A [`@page`][page] rule. +/// +/// This implements only a limited subset of the CSS +/// 2.2 syntax. +/// +/// [page]: https://drafts.csswg.org/css2/page.html#page-box +/// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors +#[derive(Clone, Debug, ToShmem)] +pub struct PageRule { + /// Selectors of the page-rule + pub selectors: PageSelectors, + /// Nested rules. + pub rules: Arc>, + /// The declaration block this page rule contains. + pub block: Arc>, + /// The source position this rule was found at. + pub source_location: SourceLocation, +} + +impl PageRule { + /// Measure heap usage. + #[cfg(feature = "gecko")] + pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize { + // Measurement of other fields may be added later. + self.rules.unconditional_shallow_size_of(ops) + + self.rules.read_with(guard).size_of(guard, ops) + + self.block.unconditional_shallow_size_of(ops) + + self.block.read_with(guard).size_of(ops) + + self.selectors.size_of(ops) + } + /// Computes the specificity of this page rule when matched with flags. + /// + /// Computing this value has linear-complexity with the size of the + /// selectors, so the caller should usually call this once and cache the + /// result. + /// + /// Returns None if the flags do not match this page rule. + /// + /// The return type is ordered by page-rule specificity. + pub fn match_specificity(&self, flags: PagePseudoClassFlags) -> Option { + let mut specificity = None; + for s in self.selectors.0.iter().map(|s| s.match_specificity(flags)) { + specificity = s.max(specificity); + } + specificity + } +} + +impl ToCssWithGuard for PageRule { + /// Serialization of PageRule is not specced, adapted from steps for + /// StyleRule. + fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { + // https://drafts.csswg.org/cssom/#serialize-a-css-rule + dest.write_str("@page ")?; + if !self.selectors.is_empty() { + self.selectors.to_css(&mut CssWriter::new(dest))?; + dest.write_char(' ')?; + } + dest.write_char('{')?; + + // TODO: share more/most of this with style rules + // https://bugzilla.mozilla.org/1867164 + let declaration_block = self.block.read_with(guard); + let has_declarations = !declaration_block.declarations().is_empty(); + + let rules = self.rules.read_with(guard); + if !rules.is_empty() { + if has_declarations { + dest.write_str("\n ")?; + declaration_block.to_css(dest)?; + } + return rules.to_css_block_without_opening(guard, dest); + } + + if has_declarations { + dest.write_char(' ')?; + declaration_block.to_css(dest)?; + } + dest.write_str(" }") + } +} + +impl DeepCloneWithLock for PageRule { + fn deep_clone_with_lock( + &self, + lock: &SharedRwLock, + guard: &SharedRwLockReadGuard, + params: &DeepCloneParams, + ) -> Self { + let rules = self.rules.read_with(&guard); + PageRule { + selectors: self.selectors.clone(), + block: Arc::new(lock.wrap(self.block.read_with(&guard).clone())), + rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard, params))), + source_location: self.source_location.clone(), + } + } +} -- cgit v1.2.3