diff --git a/components/script/dom/cssrule.rs b/components/script/dom/cssrule.rs index 1a24d5839bf..6bfe057d6d9 100644 --- a/components/script/dom/cssrule.rs +++ b/components/script/dom/cssrule.rs @@ -78,6 +78,7 @@ impl CSSRule { StyleCssRule::Import(s) => Root::upcast(CSSImportRule::new(window, parent_stylesheet, s)), StyleCssRule::Style(s) => Root::upcast(CSSStyleRule::new(window, parent_stylesheet, s)), StyleCssRule::FontFace(s) => Root::upcast(CSSFontFaceRule::new(window, parent_stylesheet, s)), + StyleCssRule::CounterStyle(_) => unimplemented!(), StyleCssRule::Keyframes(s) => Root::upcast(CSSKeyframesRule::new(window, parent_stylesheet, s)), StyleCssRule::Media(s) => Root::upcast(CSSMediaRule::new(window, parent_stylesheet, s)), StyleCssRule::Namespace(s) => Root::upcast(CSSNamespaceRule::new(window, parent_stylesheet, s)), diff --git a/components/style/counter_style.rs b/components/style/counter_style.rs new file mode 100644 index 00000000000..a78a64f31ec --- /dev/null +++ b/components/style/counter_style.rs @@ -0,0 +1,150 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! The [`@counter-style`][counter-style] at-rule. +//! +//! [counter-style]: https://drafts.csswg.org/css-counter-styles/ + +use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser}; +#[cfg(feature = "gecko")] use gecko::rules::CounterStyleDescriptors; +#[cfg(feature = "gecko")] use gecko_bindings::structs::nsCSSCounterDesc; +use parser::{ParserContext, log_css_error, Parse}; +use shared_lock::{SharedRwLockReadGuard, ToCssWithGuard}; +use std::fmt; +use style_traits::ToCss; +use values::CustomIdent; + +/// Parse the body (inside `{}`) of an @counter-style rule +pub fn parse_counter_style_body(name: CustomIdent, context: &ParserContext, input: &mut Parser) + -> Result { + let mut rule = CounterStyleRule::initial(name); + { + let parser = CounterStyleRuleParser { + context: context, + rule: &mut rule, + }; + let mut iter = DeclarationListParser::new(input, parser); + while let Some(declaration) = iter.next() { + if let Err(range) = declaration { + let pos = range.start; + let message = format!("Unsupported @counter-style descriptor declaration: '{}'", + iter.input.slice(range)); + log_css_error(iter.input, pos, &*message, context); + } + } + } + Ok(rule) +} + +struct CounterStyleRuleParser<'a, 'b: 'a> { + context: &'a ParserContext<'b>, + rule: &'a mut CounterStyleRule, +} + +/// Default methods reject all at rules. +impl<'a, 'b> AtRuleParser for CounterStyleRuleParser<'a, 'b> { + type Prelude = (); + type AtRule = (); +} + + +macro_rules! counter_style_descriptors { + ( + $( #[$doc: meta] $name: tt $ident: ident / $gecko_ident: ident: $ty: ty = $initial: expr, )+ + ) => { + /// An @counter-style rule + #[derive(Debug)] + pub struct CounterStyleRule { + name: CustomIdent, + $( + #[$doc] + $ident: $ty, + )+ + } + + impl CounterStyleRule { + fn initial(name: CustomIdent) -> Self { + CounterStyleRule { + name: name, + $( + $ident: $initial, + )+ + } + } + + /// Convert to Gecko types + #[cfg(feature = "gecko")] + pub fn set_descriptors(&self, descriptors: &mut CounterStyleDescriptors) { + $( + descriptors[nsCSSCounterDesc::$gecko_ident as usize].set_from(&self.$ident) + )* + } + } + + impl<'a, 'b> DeclarationParser for CounterStyleRuleParser<'a, 'b> { + type Declaration = (); + + fn parse_value(&mut self, name: &str, input: &mut Parser) -> Result<(), ()> { + match_ignore_ascii_case! { name, + $( + $name => { + // DeclarationParser also calls parse_entirely + // so we’d normally not need to, + // but in this case we do because we set the value as a side effect + // rather than returning it. + let value = input.parse_entirely(|i| Parse::parse(self.context, i))?; + self.rule.$ident = value + } + )* + _ => return Err(()) + } + Ok(()) + } + } + + impl ToCssWithGuard for CounterStyleRule { + fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result + where W: fmt::Write { + dest.write_str("@counter-style ")?; + self.name.to_css(dest)?; + dest.write_str(" {\n")?; + $( + dest.write_str(concat!(" ", $name, ": "))?; + ToCss::to_css(&self.$ident, dest)?; + dest.write_str(";\n")?; + )+ + dest.write_str("}") + } + } + } +} + +counter_style_descriptors! { + /// The algorithm for constructing a string representation of a counter value + "system" system / eCSSCounterDesc_System: System = System::Symbolic, +} + +/// Value of the 'system' descriptor +#[derive(Debug)] +pub enum System { + /// Cycles through provided symbols, doubling, tripling, etc. + Symbolic, +} + +impl Parse for System { + fn parse(_context: &ParserContext, input: &mut Parser) -> Result { + match_ignore_ascii_case! { &input.expect_ident()?, + "symbolic" => Ok(System::Symbolic), + _ => Err(()) + } + } +} + +impl ToCss for System { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self { + System::Symbolic => dest.write_str("symbolic") + } + } +} diff --git a/components/style/gecko/rules.rs b/components/style/gecko/rules.rs index 548266c4c40..0dced7dea56 100644 --- a/components/style/gecko/rules.rs +++ b/components/style/gecko/rules.rs @@ -6,10 +6,11 @@ use computed_values::{font_style, font_weight, font_stretch}; use computed_values::font_family::FamilyName; +use counter_style::System; use cssparser::UnicodeRange; use font_face::{FontFaceRuleData, Source}; use gecko_bindings::bindings; -use gecko_bindings::structs::{self, nsCSSFontFaceRule, nsCSSValue}; +use gecko_bindings::structs::{self, nsCSSFontFaceRule, nsCSSValue, nsCSSCounterDesc}; use gecko_bindings::sugar::ns_css_value::ToNsCssValue; use gecko_bindings::sugar::refptr::{RefPtr, UniqueRefPtr}; use shared_lock::{ToCssWithGuard, SharedRwLockReadGuard}; @@ -133,3 +134,15 @@ impl ToCssWithGuard for FontFaceRule { write!(dest, "{}", css_text) } } + +/// The type of nsCSSCounterStyleRule::mValues +pub type CounterStyleDescriptors = [nsCSSValue; nsCSSCounterDesc::eCSSCounterDesc_COUNT as usize]; + +impl ToNsCssValue for System { + fn convert(&self, v: &mut nsCSSValue) { + match *self { + System::Symbolic => v.set_enum(structs::NS_STYLE_COUNTER_SYSTEM_SYMBOLIC as i32), + } + } +} + diff --git a/components/style/lib.rs b/components/style/lib.rs index 74f5cc5d4b6..7479279418d 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -89,6 +89,7 @@ pub mod bloom; pub mod cache; pub mod cascade_info; pub mod context; +pub mod counter_style; pub mod custom_properties; pub mod data; pub mod dom; diff --git a/components/style/stylesheets.rs b/components/style/stylesheets.rs index 90f551cfdf8..7429370a458 100644 --- a/components/style/stylesheets.rs +++ b/components/style/stylesheets.rs @@ -7,6 +7,7 @@ #![deny(missing_docs)] use {Atom, Prefix, Namespace}; +use counter_style::{CounterStyleRule, parse_counter_style_body}; use cssparser::{AtRuleParser, Parser, QualifiedRuleParser}; use cssparser::{AtRuleType, RuleListParser, Token, parse_one_rule}; use cssparser::ToCss as ParserToCss; @@ -290,6 +291,7 @@ pub enum CssRule { Style(Arc>), Media(Arc>), FontFace(Arc>), + CounterStyle(Arc>), Viewport(Arc>), Keyframes(Arc>), Supports(Arc>), @@ -332,15 +334,16 @@ impl CssRule { #[allow(missing_docs)] pub fn rule_type(&self) -> CssRuleType { match *self { - CssRule::Style(_) => CssRuleType::Style, - CssRule::Import(_) => CssRuleType::Import, - CssRule::Media(_) => CssRuleType::Media, - CssRule::FontFace(_) => CssRuleType::FontFace, + CssRule::Style(_) => CssRuleType::Style, + CssRule::Import(_) => CssRuleType::Import, + CssRule::Media(_) => CssRuleType::Media, + CssRule::FontFace(_) => CssRuleType::FontFace, + CssRule::CounterStyle(_) => CssRuleType::CounterStyle, CssRule::Keyframes(_) => CssRuleType::Keyframes, CssRule::Namespace(_) => CssRuleType::Namespace, - CssRule::Viewport(_) => CssRuleType::Viewport, - CssRule::Supports(_) => CssRuleType::Supports, - CssRule::Page(_) => CssRuleType::Page, + CssRule::Viewport(_) => CssRuleType::Viewport, + CssRule::Supports(_) => CssRuleType::Supports, + CssRule::Page(_) => CssRuleType::Page, } } @@ -373,6 +376,7 @@ impl CssRule { CssRule::Namespace(_) | CssRule::Style(_) | CssRule::FontFace(_) | + CssRule::CounterStyle(_) | CssRule::Viewport(_) | CssRule::Keyframes(_) | CssRule::Page(_) => { @@ -446,6 +450,7 @@ impl ToCssWithGuard for CssRule { CssRule::Import(ref lock) => lock.read_with(guard).to_css(guard, dest), CssRule::Style(ref lock) => lock.read_with(guard).to_css(guard, dest), CssRule::FontFace(ref lock) => lock.read_with(guard).to_css(guard, dest), + CssRule::CounterStyle(ref lock) => lock.read_with(guard).to_css(guard, dest), CssRule::Viewport(ref lock) => lock.read_with(guard).to_css(guard, dest), CssRule::Keyframes(ref lock) => lock.read_with(guard).to_css(guard, dest), CssRule::Media(ref lock) => lock.read_with(guard).to_css(guard, dest), @@ -835,6 +840,7 @@ rule_filter! { effective_style_rules(Style => StyleRule), effective_media_rules(Media => MediaRule), effective_font_face_rules(FontFace => FontFaceRule), + effective_counter_style_rules(CounterStyle => CounterStyleRule), effective_viewport_rules(Viewport => ViewportRule), effective_keyframes_rules(Keyframes => KeyframesRule), effective_supports_rules(Supports => SupportsRule), @@ -916,6 +922,8 @@ pub enum VendorPrefix { enum AtRulePrelude { /// A @font-face rule prelude. FontFace, + /// A @counter-style rule prelude, with its counter style name. + CounterStyle(CustomIdent), /// A @media rule prelude, with its media queries. Media(Arc>), /// An @supports rule, with its conditional @@ -1103,6 +1111,14 @@ impl<'a, 'b> AtRuleParser for NestedRuleParser<'a, 'b> { "font-face" => { Ok(AtRuleType::WithBlock(AtRulePrelude::FontFace)) }, + "counter-style" => { + if !cfg!(feature = "gecko") { + // Support for this rule is not fully implemented in Servo yet. + return Err(()) + } + let name = CustomIdent::from_ident(input.expect_ident()?, &["decimal", "none"])?; + Ok(AtRuleType::WithBlock(AtRulePrelude::CounterStyle(name))) + }, "viewport" => { if is_viewport_enabled() { Ok(AtRuleType::WithBlock(AtRulePrelude::Viewport)) @@ -1149,6 +1165,11 @@ impl<'a, 'b> AtRuleParser for NestedRuleParser<'a, 'b> { Ok(CssRule::FontFace(Arc::new(self.shared_lock.wrap( parse_font_face_block(&context, input).into())))) } + AtRulePrelude::CounterStyle(name) => { + let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::CounterStyle)); + Ok(CssRule::CounterStyle(Arc::new(self.shared_lock.wrap( + parse_counter_style_body(name, &context, input)?)))) + } AtRulePrelude::Media(media_queries) => { Ok(CssRule::Media(Arc::new(self.shared_lock.wrap(MediaRule { media_queries: media_queries,