From 01c6eb3556e86402992e0512a714ac7094b2beb6 Mon Sep 17 00:00:00 2001 From: Emily McDonough Date: Tue, 6 Jun 2023 23:39:04 +0200 Subject: style: Implement basic @page-rule selector parsing This does not support any of the pseudo page types. Differential Revision: https://phabricator.services.mozilla.com/D131532 --- components/style/stylesheets/mod.rs | 2 +- components/style/stylesheets/page_rule.rs | 95 ++++++++++++++++++++++++++--- components/style/stylesheets/rule_parser.rs | 17 ++++-- components/style/stylist.rs | 73 ++++++++++++++++++++-- 4 files changed, 167 insertions(+), 20 deletions(-) diff --git a/components/style/stylesheets/mod.rs b/components/style/stylesheets/mod.rs index f6348c6190d..d48b7504797 100644 --- a/components/style/stylesheets/mod.rs +++ b/components/style/stylesheets/mod.rs @@ -56,7 +56,7 @@ pub use self::loader::StylesheetLoader; pub use self::media_rule::MediaRule; pub use self::namespace_rule::NamespaceRule; pub use self::origin::{Origin, OriginSet, OriginSetIterator, PerOrigin, PerOriginIter}; -pub use self::page_rule::PageRule; +pub use self::page_rule::{PageRule, PageSelector, PageSelectors}; pub use self::rule_list::{CssRules, CssRulesHelpers}; pub use self::rule_parser::{InsertRuleContext, State, TopLevelRuleParser}; pub use self::rules_iterator::{AllRules, EffectiveRules}; diff --git a/components/style/stylesheets/page_rule.rs b/components/style/stylesheets/page_rule.rs index 3edf562258f..7bf62f5c9e7 100644 --- a/components/style/stylesheets/page_rule.rs +++ b/components/style/stylesheets/page_rule.rs @@ -6,27 +6,100 @@ //! //! [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::str::CssStringWriter; -use cssparser::SourceLocation; +use crate::values::{AtomIdent, CustomIdent}; +use style_traits::{CssWriter, ParseError, ToCss}; +use cssparser::{ToCss as CssParserToCss, Parser, SourceLocation}; #[cfg(feature = "gecko")] use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; use servo_arc::Arc; use std::fmt::{self, Write}; +/// Type of a single [`@page`][page selector] +/// +/// We do not support pseudo selectors yet. +/// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors +#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem)] +pub struct PageSelector(pub AtomIdent); + +impl PageSelector { + /// Checks if the ident matches a page-name's ident. + /// + /// This does not currently take pseudo selectors into account. + #[inline] + pub fn ident_matches(&self, other: &CustomIdent) -> bool { + self.0.0 == other.0 + } +} + +impl Parse for PageSelector { + fn parse<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + let s = input.expect_ident()?; + Ok(PageSelector(AtomIdent::from(&**s))) + } +} + +impl ToCss for PageSelector { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write + { + (&self.0).to_css(dest) + } +} + +/// 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. /// -/// In this subset, [page selectors][page-selectors] are not implemented. -/// /// [page]: https://drafts.csswg.org/css2/page.html#page-box /// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors -#[derive(Debug, ToShmem)] +#[derive(Clone, Debug, ToShmem)] pub struct PageRule { + /// Selectors of the page-rule + pub selectors: PageSelectors, /// The declaration block this page rule contains. pub block: Arc>, /// The source position this rule was found at. @@ -38,7 +111,7 @@ impl PageRule { #[cfg(feature = "gecko")] pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize { // Measurement of other fields may be added later. - self.block.unconditional_shallow_size_of(ops) + self.block.read_with(guard).size_of(ops) + self.block.unconditional_shallow_size_of(ops) + self.block.read_with(guard).size_of(ops) + self.selectors.size_of(ops) } } @@ -46,13 +119,18 @@ 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 { - dest.write_str("@page { ")?; + dest.write_str("@page ")?; + if !self.selectors.is_empty() { + self.selectors.to_css(&mut CssWriter::new(dest))?; + dest.write_char(' ')?; + } + dest.write_str("{ ")?; let declaration_block = self.block.read_with(guard); declaration_block.to_css(dest)?; if !declaration_block.declarations().is_empty() { - dest.write_str(" ")?; + dest.write_char(' ')?; } - dest.write_str("}") + dest.write_char('}') } } @@ -64,6 +142,7 @@ impl DeepCloneWithLock for PageRule { _params: &DeepCloneParams, ) -> Self { PageRule { + selectors: self.selectors.clone(), block: Arc::new(lock.wrap(self.block.read_with(&guard).clone())), source_location: self.source_location.clone(), } diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs index 3c0d8a5c231..868137b52cd 100644 --- a/components/style/stylesheets/rule_parser.rs +++ b/components/style/stylesheets/rule_parser.rs @@ -23,8 +23,8 @@ use crate::stylesheets::stylesheet::Namespaces; use crate::stylesheets::supports_rule::SupportsCondition; use crate::stylesheets::{ viewport_rule, AllowImportRules, CorsMode, CssRule, CssRuleType, CssRules, DocumentRule, - FontFeatureValuesRule, KeyframesRule, MediaRule, NamespaceRule, PageRule, RulesMutateError, - ScrollTimelineRule, StyleRule, StylesheetLoader, SupportsRule, ViewportRule, + FontFeatureValuesRule, KeyframesRule, MediaRule, NamespaceRule, PageRule, PageSelectors, + RulesMutateError, ScrollTimelineRule, StyleRule, StylesheetLoader, SupportsRule, ViewportRule, }; use crate::values::computed::font::FamilyName; use crate::values::{CssUrl, CustomIdent, KeyframesName, TimelineName}; @@ -168,8 +168,8 @@ pub enum AtRulePrelude { Viewport, /// A @keyframes rule, with its animation name and vendor prefix if exists. Keyframes(KeyframesName, Option), - /// A @page rule prelude. - Page, + /// A @page rule prelude, with its page name if it exists. + Page(PageSelectors), /// A @document rule, with its conditional. Document(DocumentCondition), /// A @import rule prelude. @@ -469,7 +469,11 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { AtRulePrelude::Keyframes(name, prefix) }, "page" if cfg!(feature = "gecko") => { - AtRulePrelude::Page + AtRulePrelude::Page(if static_prefs::pref!("layout.css.named-pages.enabled") { + input.try_parse(|i| PageSelectors::parse(self.context, i)).unwrap_or_default() + } else { + PageSelectors::default() + }) }, "-moz-document" if cfg!(feature = "gecko") => { let cond = DocumentCondition::parse(self.context, input)?; @@ -583,7 +587,7 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { }, )))) }, - AtRulePrelude::Page => { + AtRulePrelude::Page(selectors) => { let context = ParserContext::new_with_rule_type( self.context, CssRuleType::Page, @@ -592,6 +596,7 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { let declarations = parse_property_declaration_list(&context, input, None); Ok(CssRule::Page(Arc::new(self.shared_lock.wrap(PageRule { + selectors, block: Arc::new(self.shared_lock.wrap(declarations)), source_location: start.source_location(), })))) diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 14f88c4fe22..dffc2e849ce 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -1612,6 +1612,52 @@ impl LayerOrderedMap { } } +/// Wrapper to allow better tracking of memory usage by page rule lists. +/// +/// This includes the layer ID for use with the named page table. +#[derive(Clone, Debug, MallocSizeOf)] +pub struct PageRuleData { + /// Layer ID for sorting page rules after matching. + pub layer: LayerId, + /// Page rule + #[ignore_malloc_size_of = "Arc, stylesheet measures as primary ref"] + pub rule: Arc>, +} + +/// Wrapper to allow better tracking of memory usage by page rule lists. +/// +/// This is meant to be used by the global page rule list which are already +/// sorted by layer ID, since all global page rules are less specific than all +/// named page rules that match a certain page. +#[derive(Clone, Debug, Deref, MallocSizeOf)] +pub struct PageRuleDataNoLayer( + #[ignore_malloc_size_of = "Arc, stylesheet measures as primary ref"] + pub Arc>, +); + +/// Stores page rules indexed by page names. +#[derive(Clone, Debug, Default, MallocSizeOf)] +pub struct PageRuleMap { + /// Global, unnamed page rules. + pub global: LayerOrderedVec, + /// Named page rules + pub named: PrecomputedHashMap>, +} + +impl PageRuleMap { + #[inline] + fn clear(&mut self) { + self.global.clear(); + self.named.clear(); + } +} + +impl MallocShallowSizeOf for PageRuleMap { + fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.global.size_of(ops) + self.named.shallow_size_of(ops) + } +} + /// This struct holds data which users of Stylist may want to extract /// from stylesheets which can be done at the same time as updating. #[derive(Clone, Debug, Default)] @@ -1631,7 +1677,7 @@ pub struct ExtraStyleData { /// A map of effective page rules. #[cfg(feature = "gecko")] - pub pages: LayerOrderedVec>>, + pub pages: PageRuleMap, /// A map of effective scroll-timeline rules. #[cfg(feature = "gecko")] @@ -1666,8 +1712,25 @@ impl ExtraStyleData { } /// Add the given @page rule. - fn add_page(&mut self, rule: &Arc>, layer: LayerId) { - self.pages.push(rule.clone(), layer); + fn add_page( + &mut self, + guard: &SharedRwLockReadGuard, + rule: &Arc>, + layer: LayerId, + ) -> Result<(), AllocErr> { + let page_rule = rule.read_with(guard); + if page_rule.selectors.0.is_empty() { + self.pages.global.push(PageRuleDataNoLayer(rule.clone()), layer); + } else { + // TODO: Handle pseudo-classes + self.pages.named.try_reserve(page_rule.selectors.0.len())?; + for name in page_rule.selectors.as_slice() { + let vec = self.pages.named.entry(name.0.0.clone()).or_default(); + vec.try_reserve(1)?; + vec.push(PageRuleData{layer, rule: rule.clone()}); + } + } + Ok(()) } /// Add the given @scroll-timeline rule. @@ -1685,7 +1748,7 @@ impl ExtraStyleData { self.font_faces.sort(layers); self.font_feature_values.sort(layers); self.counter_styles.sort(layers); - self.pages.sort(layers); + self.pages.global.sort(layers); self.scroll_timelines.sort(layers); } @@ -2518,7 +2581,7 @@ impl CascadeData { }, #[cfg(feature = "gecko")] CssRule::Page(ref rule) => { - self.extra_data.add_page(rule, current_layer_id); + self.extra_data.add_page(guard, rule, current_layer_id)?; }, CssRule::Viewport(..) => {}, _ => { -- cgit v1.2.3