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
This commit is contained in:
Emily McDonough 2023-06-06 23:39:04 +02:00 committed by Oriol Brufau
parent e4bb1df878
commit 01c6eb3556
4 changed files with 167 additions and 20 deletions

View file

@ -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};

View file

@ -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<Self, ParseError<'i>> {
let s = input.expect_ident()?;
Ok(PageSelector(AtomIdent::from(&**s)))
}
}
impl ToCss for PageSelector {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> 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<PageSelector>) -> 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<Self, ParseError<'i>> {
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<Locked<PropertyDeclarationBlock>>,
/// 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(),
}

View file

@ -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<VendorPrefix>),
/// 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(),
}))))

View file

@ -1612,6 +1612,52 @@ impl<T: 'static> LayerOrderedMap<T> {
}
}
/// 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<Locked<PageRule>>,
}
/// 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<Locked<PageRule>>,
);
/// Stores page rules indexed by page names.
#[derive(Clone, Debug, Default, MallocSizeOf)]
pub struct PageRuleMap {
/// Global, unnamed page rules.
pub global: LayerOrderedVec<PageRuleDataNoLayer>,
/// Named page rules
pub named: PrecomputedHashMap<Atom, SmallVec<[PageRuleData; 1]>>,
}
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<Arc<Locked<PageRule>>>,
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<Locked<PageRule>>, layer: LayerId) {
self.pages.push(rule.clone(), layer);
fn add_page(
&mut self,
guard: &SharedRwLockReadGuard,
rule: &Arc<Locked<PageRule>>,
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(..) => {},
_ => {