mirror of
https://github.com/servo/servo.git
synced 2025-08-05 05:30:08 +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::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};
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
}))))
|
||||
|
|
|
@ -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(..) => {},
|
||||
_ => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue