aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEmily McDonough <emcdonough@mozilla.com>2023-06-06 23:39:04 +0200
committerOriol Brufau <obrufau@igalia.com>2023-06-09 11:18:05 +0200
commit01c6eb3556e86402992e0512a714ac7094b2beb6 (patch)
tree923dfa504f06e7bff30a11bc0c2feeffacec11b6
parente4bb1df87826bc386ecf371cff68507933926739 (diff)
downloadservo-01c6eb3556e86402992e0512a714ac7094b2beb6.tar.gz
servo-01c6eb3556e86402992e0512a714ac7094b2beb6.zip
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
-rw-r--r--components/style/stylesheets/mod.rs2
-rw-r--r--components/style/stylesheets/page_rule.rs95
-rw-r--r--components/style/stylesheets/rule_parser.rs17
-rw-r--r--components/style/stylist.rs73
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<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(),
}
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<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(),
}))))
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<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(..) => {},
_ => {