mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +01:00
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:
parent
e4bb1df878
commit
01c6eb3556
4 changed files with 167 additions and 20 deletions
|
@ -56,7 +56,7 @@ pub use self::loader::StylesheetLoader;
|
||||||
pub use self::media_rule::MediaRule;
|
pub use self::media_rule::MediaRule;
|
||||||
pub use self::namespace_rule::NamespaceRule;
|
pub use self::namespace_rule::NamespaceRule;
|
||||||
pub use self::origin::{Origin, OriginSet, OriginSetIterator, PerOrigin, PerOriginIter};
|
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_list::{CssRules, CssRulesHelpers};
|
||||||
pub use self::rule_parser::{InsertRuleContext, State, TopLevelRuleParser};
|
pub use self::rule_parser::{InsertRuleContext, State, TopLevelRuleParser};
|
||||||
pub use self::rules_iterator::{AllRules, EffectiveRules};
|
pub use self::rules_iterator::{AllRules, EffectiveRules};
|
||||||
|
|
|
@ -6,27 +6,100 @@
|
||||||
//!
|
//!
|
||||||
//! [page]: https://drafts.csswg.org/css2/page.html#page-box
|
//! [page]: https://drafts.csswg.org/css2/page.html#page-box
|
||||||
|
|
||||||
|
use crate::parser::{Parse, ParserContext};
|
||||||
use crate::properties::PropertyDeclarationBlock;
|
use crate::properties::PropertyDeclarationBlock;
|
||||||
use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked};
|
use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked};
|
||||||
use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
|
use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
|
||||||
use crate::str::CssStringWriter;
|
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")]
|
#[cfg(feature = "gecko")]
|
||||||
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
|
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
|
||||||
use servo_arc::Arc;
|
use servo_arc::Arc;
|
||||||
use std::fmt::{self, Write};
|
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.
|
/// A [`@page`][page] rule.
|
||||||
///
|
///
|
||||||
/// This implements only a limited subset of the CSS
|
/// This implements only a limited subset of the CSS
|
||||||
/// 2.2 syntax.
|
/// 2.2 syntax.
|
||||||
///
|
///
|
||||||
/// In this subset, [page selectors][page-selectors] are not implemented.
|
|
||||||
///
|
|
||||||
/// [page]: https://drafts.csswg.org/css2/page.html#page-box
|
/// [page]: https://drafts.csswg.org/css2/page.html#page-box
|
||||||
/// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors
|
/// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors
|
||||||
#[derive(Debug, ToShmem)]
|
#[derive(Clone, Debug, ToShmem)]
|
||||||
pub struct PageRule {
|
pub struct PageRule {
|
||||||
|
/// Selectors of the page-rule
|
||||||
|
pub selectors: PageSelectors,
|
||||||
/// The declaration block this page rule contains.
|
/// The declaration block this page rule contains.
|
||||||
pub block: Arc<Locked<PropertyDeclarationBlock>>,
|
pub block: Arc<Locked<PropertyDeclarationBlock>>,
|
||||||
/// The source position this rule was found at.
|
/// The source position this rule was found at.
|
||||||
|
@ -38,7 +111,7 @@ impl PageRule {
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
|
pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
|
||||||
// Measurement of other fields may be added later.
|
// 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
|
/// Serialization of PageRule is not specced, adapted from steps for
|
||||||
/// StyleRule.
|
/// StyleRule.
|
||||||
fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
|
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);
|
let declaration_block = self.block.read_with(guard);
|
||||||
declaration_block.to_css(dest)?;
|
declaration_block.to_css(dest)?;
|
||||||
if !declaration_block.declarations().is_empty() {
|
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,
|
_params: &DeepCloneParams,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
PageRule {
|
PageRule {
|
||||||
|
selectors: self.selectors.clone(),
|
||||||
block: Arc::new(lock.wrap(self.block.read_with(&guard).clone())),
|
block: Arc::new(lock.wrap(self.block.read_with(&guard).clone())),
|
||||||
source_location: self.source_location.clone(),
|
source_location: self.source_location.clone(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,8 +23,8 @@ use crate::stylesheets::stylesheet::Namespaces;
|
||||||
use crate::stylesheets::supports_rule::SupportsCondition;
|
use crate::stylesheets::supports_rule::SupportsCondition;
|
||||||
use crate::stylesheets::{
|
use crate::stylesheets::{
|
||||||
viewport_rule, AllowImportRules, CorsMode, CssRule, CssRuleType, CssRules, DocumentRule,
|
viewport_rule, AllowImportRules, CorsMode, CssRule, CssRuleType, CssRules, DocumentRule,
|
||||||
FontFeatureValuesRule, KeyframesRule, MediaRule, NamespaceRule, PageRule, RulesMutateError,
|
FontFeatureValuesRule, KeyframesRule, MediaRule, NamespaceRule, PageRule, PageSelectors,
|
||||||
ScrollTimelineRule, StyleRule, StylesheetLoader, SupportsRule, ViewportRule,
|
RulesMutateError, ScrollTimelineRule, StyleRule, StylesheetLoader, SupportsRule, ViewportRule,
|
||||||
};
|
};
|
||||||
use crate::values::computed::font::FamilyName;
|
use crate::values::computed::font::FamilyName;
|
||||||
use crate::values::{CssUrl, CustomIdent, KeyframesName, TimelineName};
|
use crate::values::{CssUrl, CustomIdent, KeyframesName, TimelineName};
|
||||||
|
@ -168,8 +168,8 @@ pub enum AtRulePrelude {
|
||||||
Viewport,
|
Viewport,
|
||||||
/// A @keyframes rule, with its animation name and vendor prefix if exists.
|
/// A @keyframes rule, with its animation name and vendor prefix if exists.
|
||||||
Keyframes(KeyframesName, Option<VendorPrefix>),
|
Keyframes(KeyframesName, Option<VendorPrefix>),
|
||||||
/// A @page rule prelude.
|
/// A @page rule prelude, with its page name if it exists.
|
||||||
Page,
|
Page(PageSelectors),
|
||||||
/// A @document rule, with its conditional.
|
/// A @document rule, with its conditional.
|
||||||
Document(DocumentCondition),
|
Document(DocumentCondition),
|
||||||
/// A @import rule prelude.
|
/// A @import rule prelude.
|
||||||
|
@ -469,7 +469,11 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
|
||||||
AtRulePrelude::Keyframes(name, prefix)
|
AtRulePrelude::Keyframes(name, prefix)
|
||||||
},
|
},
|
||||||
"page" if cfg!(feature = "gecko") => {
|
"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") => {
|
"-moz-document" if cfg!(feature = "gecko") => {
|
||||||
let cond = DocumentCondition::parse(self.context, input)?;
|
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(
|
let context = ParserContext::new_with_rule_type(
|
||||||
self.context,
|
self.context,
|
||||||
CssRuleType::Page,
|
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);
|
let declarations = parse_property_declaration_list(&context, input, None);
|
||||||
Ok(CssRule::Page(Arc::new(self.shared_lock.wrap(PageRule {
|
Ok(CssRule::Page(Arc::new(self.shared_lock.wrap(PageRule {
|
||||||
|
selectors,
|
||||||
block: Arc::new(self.shared_lock.wrap(declarations)),
|
block: Arc::new(self.shared_lock.wrap(declarations)),
|
||||||
source_location: start.source_location(),
|
source_location: start.source_location(),
|
||||||
}))))
|
}))))
|
||||||
|
|
|
@ -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
|
/// This struct holds data which users of Stylist may want to extract
|
||||||
/// from stylesheets which can be done at the same time as updating.
|
/// from stylesheets which can be done at the same time as updating.
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
|
@ -1631,7 +1677,7 @@ pub struct ExtraStyleData {
|
||||||
|
|
||||||
/// A map of effective page rules.
|
/// A map of effective page rules.
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
pub pages: LayerOrderedVec<Arc<Locked<PageRule>>>,
|
pub pages: PageRuleMap,
|
||||||
|
|
||||||
/// A map of effective scroll-timeline rules.
|
/// A map of effective scroll-timeline rules.
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
|
@ -1666,8 +1712,25 @@ impl ExtraStyleData {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add the given @page rule.
|
/// Add the given @page rule.
|
||||||
fn add_page(&mut self, rule: &Arc<Locked<PageRule>>, layer: LayerId) {
|
fn add_page(
|
||||||
self.pages.push(rule.clone(), layer);
|
&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.
|
/// Add the given @scroll-timeline rule.
|
||||||
|
@ -1685,7 +1748,7 @@ impl ExtraStyleData {
|
||||||
self.font_faces.sort(layers);
|
self.font_faces.sort(layers);
|
||||||
self.font_feature_values.sort(layers);
|
self.font_feature_values.sort(layers);
|
||||||
self.counter_styles.sort(layers);
|
self.counter_styles.sort(layers);
|
||||||
self.pages.sort(layers);
|
self.pages.global.sort(layers);
|
||||||
self.scroll_timelines.sort(layers);
|
self.scroll_timelines.sort(layers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2518,7 +2581,7 @@ impl CascadeData {
|
||||||
},
|
},
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
CssRule::Page(ref rule) => {
|
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(..) => {},
|
CssRule::Viewport(..) => {},
|
||||||
_ => {
|
_ => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue