From 7a0006647716138c2ffa11b192d657df957129b5 Mon Sep 17 00:00:00 2001 From: Igor Gutorov Date: Sat, 3 Feb 2018 18:36:12 +0200 Subject: [PATCH] style: Move content property out of mako. --- components/layout/construct.rs | 15 +- components/layout/fragment.rs | 8 +- components/layout/generated_content.rs | 8 +- components/layout/text.rs | 2 +- components/layout/wrapper.rs | 10 +- components/style/properties/gecko.mako.rs | 56 ++--- .../properties/longhand/counters.mako.rs | 235 +----------------- components/style/values/computed/counters.rs | 191 ++++++++++++++ components/style/values/computed/mod.rs | 2 +- components/style/values/specified/counters.rs | 54 ++++ components/style/values/specified/mod.rs | 2 +- 11 files changed, 302 insertions(+), 281 deletions(-) diff --git a/components/layout/construct.rs b/components/layout/construct.rs index bbbd9287e130..3849e43ad51e 100644 --- a/components/layout/construct.rs +++ b/components/layout/construct.rs @@ -37,14 +37,12 @@ use script_layout_interface::{LayoutElementType, LayoutNodeType, is_image_data}; use script_layout_interface::wrapper_traits::{PseudoElementType, ThreadSafeLayoutElement, ThreadSafeLayoutNode}; use servo_config::opts; use servo_url::ServoUrl; -use std::borrow::ToOwned; use std::collections::LinkedList; use std::marker::PhantomData; use std::mem; use std::sync::Arc; use std::sync::atomic::Ordering; use style::computed_values::caption_side::T as CaptionSide; -use style::computed_values::content::ContentItem; use style::computed_values::display::T as Display; use style::computed_values::empty_cells::T as EmptyCells; use style::computed_values::float::T as Float; @@ -58,6 +56,7 @@ use style::properties::longhands::list_style_image; use style::selector_parser::{PseudoElement, RestyleDamage}; use style::servo::restyle_damage::ServoRestyleDamage; use style::values::Either; +use style::values::computed::counters::ContentItem; use table::TableFlow; use table_caption::TableCaptionFlow; use table_cell::TableCellFlow; @@ -598,7 +597,7 @@ impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode> // Add whitespace results. They will be stripped out later on when // between block elements, and retained when between inline elements. let fragment_info = SpecificFragmentInfo::UnscannedText( - Box::new(UnscannedTextFragmentInfo::new(" ".to_owned(), None)) + Box::new(UnscannedTextFragmentInfo::new(Box::::from(" "), None)) ); let fragment = Fragment::from_opaque_node_and_style(whitespace_node, whitespace_pseudo, @@ -754,7 +753,7 @@ impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode> ContentItem::String(string) => { let info = Box::new(UnscannedTextFragmentInfo::new(string, None)); SpecificFragmentInfo::UnscannedText(info) - } + }, content_item => { let content_item = Box::new(GeneratedContentInfo::ContentItem(content_item)); SpecificFragmentInfo::GeneratedContent(content_item) @@ -873,7 +872,7 @@ impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode> whitespace_damage)) => { // Instantiate the whitespace fragment. let fragment_info = SpecificFragmentInfo::UnscannedText( - Box::new(UnscannedTextFragmentInfo::new(" ".to_owned(), None)) + Box::new(UnscannedTextFragmentInfo::new(Box::::from(" "), None)) ); let fragment = Fragment::from_opaque_node_and_style(whitespace_node, @@ -895,7 +894,7 @@ impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode> if is_empty && node_style.has_padding_or_border() { // An empty inline box needs at least one fragment to draw its background and borders. let info = SpecificFragmentInfo::UnscannedText( - Box::new(UnscannedTextFragmentInfo::new(String::new(), None)) + Box::new(UnscannedTextFragmentInfo::new(Box::::from(""), None)) ); let fragment = Fragment::from_opaque_node_and_style(node.opaque(), node.get_pseudo_element_type(), @@ -1296,7 +1295,7 @@ impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode> unscanned_marker_fragments.push_back(Fragment::new( node, SpecificFragmentInfo::UnscannedText( - Box::new(UnscannedTextFragmentInfo::new(text, None)) + Box::new(UnscannedTextFragmentInfo::new(Box::::from(text), None)) ), self.layout_context)); let marker_fragments = @@ -1899,7 +1898,7 @@ where E: TElement, { let info = SpecificFragmentInfo::UnscannedText( - Box::new(UnscannedTextFragmentInfo::new(String::from(text), None)) + Box::new(UnscannedTextFragmentInfo::new(Box::::from(text), None)) ); let text_style = context.stylist.style_for_anonymous::( &context.guards, diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index 4de15fdbb32e..91aee5ba83a7 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -44,7 +44,6 @@ use style::computed_values::border_collapse::T as BorderCollapse; use style::computed_values::box_sizing::T as BoxSizing; use style::computed_values::clear::T as Clear; use style::computed_values::color::T as Color; -use style::computed_values::content::ContentItem; use style::computed_values::display::T as Display; use style::computed_values::mix_blend_mode::T as MixBlendMode; use style::computed_values::overflow_wrap::T as OverflowWrap; @@ -61,6 +60,7 @@ use style::servo::restyle_damage::ServoRestyleDamage; use style::str::char_is_whitespace; use style::values::{self, Either, Auto}; use style::values::computed::{Length, LengthOrPercentage, LengthOrPercentageOrAuto}; +use style::values::computed::counters::ContentItem; use style::values::generics::box_::VerticalAlign; use style::values::generics::transform; use text; @@ -570,9 +570,9 @@ pub struct UnscannedTextFragmentInfo { impl UnscannedTextFragmentInfo { /// Creates a new instance of `UnscannedTextFragmentInfo` from the given text. #[inline] - pub fn new(text: String, selection: Option>) -> UnscannedTextFragmentInfo { + pub fn new(text: Box, selection: Option>) -> UnscannedTextFragmentInfo { UnscannedTextFragmentInfo { - text: text.into_boxed_str(), + text: text, selection: selection, } } @@ -760,7 +760,7 @@ impl Fragment { let mut ellipsis_fragment = self.transform( self.border_box.size, SpecificFragmentInfo::UnscannedText( - Box::new(UnscannedTextFragmentInfo::new(text_overflow_string, None)) + Box::new(UnscannedTextFragmentInfo::new(text_overflow_string.into_boxed_str(), None)) ) ); unscanned_ellipsis_fragments.push_back(ellipsis_fragment); diff --git a/components/layout/generated_content.rs b/components/layout/generated_content.rs index 0251c528e6c3..bef641ed782a 100644 --- a/components/layout/generated_content.rs +++ b/components/layout/generated_content.rs @@ -15,12 +15,12 @@ use gfx::display_list::OpaqueNode; use script_layout_interface::wrapper_traits::PseudoElementType; use smallvec::SmallVec; use std::collections::{HashMap, LinkedList}; -use style::computed_values::content::ContentItem; use style::computed_values::display::T as Display; use style::computed_values::list_style_type::T as ListStyleType; use style::properties::ComputedValues; use style::selector_parser::RestyleDamage; use style::servo::restyle_damage::ServoRestyleDamage; +use style::values::computed::counters::ContentItem; use text::TextRunScanner; use traversal::InorderFlowTraversal; @@ -189,7 +189,7 @@ impl<'a, 'b> ResolveGeneratedContentFragmentMutator<'a, 'b> { let temporary_counter = Counter::new(); let counter = self.traversal .counters - .get(&*counter_name) + .get(&**counter_name) .unwrap_or(&temporary_counter); new_info = counter.render(self.traversal.layout_context, fragment.node, @@ -204,7 +204,7 @@ impl<'a, 'b> ResolveGeneratedContentFragmentMutator<'a, 'b> { let temporary_counter = Counter::new(); let counter = self.traversal .counters - .get(&*counter_name) + .get(&**counter_name) .unwrap_or(&temporary_counter); new_info = counter.render(self.traversal.layout_context, fragment.node, @@ -437,7 +437,7 @@ fn render_text(layout_context: &LayoutContext, -> Option { let mut fragments = LinkedList::new(); let info = SpecificFragmentInfo::UnscannedText( - Box::new(UnscannedTextFragmentInfo::new(string, None)) + Box::new(UnscannedTextFragmentInfo::new(string.into_boxed_str(), None)) ); fragments.push_back(Fragment::from_opaque_node_and_style(node, pseudo, diff --git a/components/layout/text.rs b/components/layout/text.rs index 535c255bc76c..6541b27f3ac2 100644 --- a/components/layout/text.rs +++ b/components/layout/text.rs @@ -532,7 +532,7 @@ fn split_first_fragment_at_newline_if_necessary(fragments: &mut LinkedList ThreadSafeLayoutNodeHelpers for T { let style = self.as_element().unwrap().resolved_style(); return match style.as_ref().get_counters().content { - content::T::Items(ref value) if !value.is_empty() => { - TextContent::GeneratedContent((*value).clone()) + Content::Items(ref value) if !value.is_empty() => { + TextContent::GeneratedContent((*value).to_vec()) } _ => TextContent::GeneratedContent(vec![]), }; } - return TextContent::Text(self.node_text_content()); + TextContent::Text(self.node_text_content().into_boxed_str()) } fn restyle_damage(self) -> RestyleDamage { @@ -156,7 +156,7 @@ impl ThreadSafeLayoutNodeHelpers for T { } pub enum TextContent { - Text(String), + Text(Box), GeneratedContent(Vec), } diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index 5b68b1eae1bf..176578c5e6e4 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -5459,8 +5459,7 @@ clip-path } pub fn set_content(&mut self, v: longhands::content::computed_value::T, device: &Device) { - use properties::longhands::content::computed_value::T; - use properties::longhands::content::computed_value::ContentItem; + use values::computed::counters::{Content, ContentItem}; use values::generics::CounterStyleOrNone; use gecko_bindings::structs::nsStyleContentData; use gecko_bindings::structs::nsStyleContentType; @@ -5494,8 +5493,8 @@ clip-path } match v { - T::None | - T::Normal => { + Content::None | + Content::Normal => { // Ensure destructors run, otherwise we could leak. if !self.gecko.mContents.is_empty() { unsafe { @@ -5503,14 +5502,14 @@ clip-path } } }, - T::MozAltContent => { + Content::MozAltContent => { unsafe { Gecko_ClearAndResizeStyleContents(&mut self.gecko, 1); *self.gecko.mContents[0].mContent.mString.as_mut() = ptr::null_mut(); } self.gecko.mContents[0].mType = eStyleContentType_AltContent; }, - T::Items(items) => { + Content::Items(items) => { unsafe { Gecko_ClearAndResizeStyleContents(&mut self.gecko, items.len() as u32); @@ -5522,8 +5521,8 @@ clip-path unsafe { *self.gecko.mContents[i].mContent.mString.as_mut() = ptr::null_mut(); } - match item { - ContentItem::String(value) => { + match *item { + ContentItem::String(ref value) => { self.gecko.mContents[i].mType = eStyleContentType_String; unsafe { // NB: we share allocators, so doing this is fine. @@ -5531,17 +5530,18 @@ clip-path as_utf16_and_forget(&value); } } - ContentItem::Attr(attr) => { + ContentItem::Attr(ref attr) => { self.gecko.mContents[i].mType = eStyleContentType_Attr; - let s = if let Some((_, ns)) = attr.namespace { - format!("{}|{}", ns, attr.attribute) - } else { - attr.attribute.into() - }; unsafe { // NB: we share allocators, so doing this is fine. - *self.gecko.mContents[i].mContent.mString.as_mut() = - as_utf16_and_forget(&s); + *self.gecko.mContents[i].mContent.mString.as_mut() = match attr.namespace { + Some((_, ns)) => { + as_utf16_and_forget(&format!("{}|{}", ns, attr.attribute)) + }, + None => { + as_utf16_and_forget(&attr.attribute) + } + }; } } ContentItem::OpenQuote @@ -5552,13 +5552,13 @@ clip-path => self.gecko.mContents[i].mType = eStyleContentType_NoOpenQuote, ContentItem::NoCloseQuote => self.gecko.mContents[i].mType = eStyleContentType_NoCloseQuote, - ContentItem::Counter(name, style) => { + ContentItem::Counter(ref name, ref style) => { set_counter_function(&mut self.gecko.mContents[i], - eStyleContentType_Counter, &name, "", style, device); + eStyleContentType_Counter, &name, "", style.clone(), device); } - ContentItem::Counters(name, sep, style) => { + ContentItem::Counters(ref name, ref sep, ref style) => { set_counter_function(&mut self.gecko.mContents[i], - eStyleContentType_Counters, &name, &sep, style, device); + eStyleContentType_Counters, &name, &sep, style.clone(), device); } ContentItem::Url(ref url) => { unsafe { @@ -5586,22 +5586,22 @@ clip-path pub fn clone_content(&self) -> longhands::content::computed_value::T { use gecko::conversions::string_from_chars_pointer; use gecko_bindings::structs::nsStyleContentType::*; - use properties::longhands::content::computed_value::{T, ContentItem}; + use values::computed::counters::{Content, ContentItem}; use values::Either; use values::generics::CounterStyleOrNone; use values::specified::url::SpecifiedUrl; use values::specified::Attr; if self.gecko.mContents.is_empty() { - return T::Normal; + return Content::Normal; } if self.gecko.mContents.len() == 1 && self.gecko.mContents[0].mType == eStyleContentType_AltContent { - return T::MozAltContent; + return Content::MozAltContent; } - T::Items( + Content::Items( self.gecko.mContents.iter().map(|gecko_content| { match gecko_content.mType { eStyleContentType_OpenQuote => ContentItem::OpenQuote, @@ -5611,7 +5611,7 @@ clip-path eStyleContentType_String => { let gecko_chars = unsafe { gecko_content.mContent.mString.as_ref() }; let string = unsafe { string_from_chars_pointer(*gecko_chars) }; - ContentItem::String(string) + ContentItem::String(string.into_boxed_str()) }, eStyleContentType_Attr => { let gecko_chars = unsafe { gecko_content.mContent.mString.as_ref() }; @@ -5641,10 +5641,10 @@ clip-path unreachable!("counter function shouldn't have single string type"), }; if gecko_content.mType == eStyleContentType_Counter { - ContentItem::Counter(ident, style) + ContentItem::Counter(ident.into_boxed_str(), style) } else { let separator = gecko_function.mSeparator.to_string(); - ContentItem::Counters(ident, separator, style) + ContentItem::Counters(ident.into_boxed_str(), separator.into_boxed_str(), style) } }, eStyleContentType_Image => { @@ -5659,7 +5659,7 @@ clip-path }, _ => panic!("Found unexpected value in style struct for content property"), } - }).collect() + }).collect::>().into_boxed_slice() ) } diff --git a/components/style/properties/longhand/counters.mako.rs b/components/style/properties/longhand/counters.mako.rs index 79585b6734a2..06c1e5b76d2f 100644 --- a/components/style/properties/longhand/counters.mako.rs +++ b/components/style/properties/longhand/counters.mako.rs @@ -6,235 +6,12 @@ <% data.new_style_struct("Counters", inherited=False, gecko_name="Content") %> -<%helpers:longhand name="content" boxed="True" animation_value_type="discrete" - spec="https://drafts.csswg.org/css-content/#propdef-content"> - #[cfg(feature = "gecko")] - use values::generics::CounterStyleOrNone; - #[cfg(feature = "gecko")] - use values::specified::url::SpecifiedUrl; - #[cfg(feature = "gecko")] - use values::specified::Attr; - - #[cfg(feature = "servo")] - use super::list_style_type; - - pub use self::computed_value::T as SpecifiedValue; - pub use self::computed_value::ContentItem; - - pub mod computed_value { - use cssparser; - use std::fmt::{self, Write}; - use style_traits::{CssWriter, ToCss}; - #[cfg(feature = "gecko")] - use values::specified::url::SpecifiedUrl; - - #[cfg(feature = "servo")] - type CounterStyleType = super::super::list_style_type::computed_value::T; - #[cfg(feature = "gecko")] - type CounterStyleType = ::values::generics::CounterStyleOrNone; - - #[cfg(feature = "gecko")] - use values::specified::Attr; - - #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue)] - pub enum ContentItem { - /// Literal string content. - String(String), - /// `counter(name, style)`. - Counter(String, CounterStyleType), - /// `counters(name, separator, style)`. - Counters(String, String, CounterStyleType), - /// `open-quote`. - OpenQuote, - /// `close-quote`. - CloseQuote, - /// `no-open-quote`. - NoOpenQuote, - /// `no-close-quote`. - NoCloseQuote, - - % if product == "gecko": - /// `attr([namespace? `|`]? ident)` - Attr(Attr), - /// `url(url)` - Url(SpecifiedUrl), - % endif - } - - impl ToCss for ContentItem { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: Write, - { - match *self { - ContentItem::String(ref s) => s.to_css(dest), - ContentItem::Counter(ref s, ref counter_style) => { - dest.write_str("counter(")?; - cssparser::serialize_identifier(&**s, dest)?; - dest.write_str(", ")?; - counter_style.to_css(dest)?; - dest.write_str(")") - } - ContentItem::Counters(ref s, ref separator, ref counter_style) => { - dest.write_str("counters(")?; - cssparser::serialize_identifier(&**s, dest)?; - dest.write_str(", ")?; - separator.to_css(dest)?; - dest.write_str(", ")?; - counter_style.to_css(dest)?; - dest.write_str(")") - } - ContentItem::OpenQuote => dest.write_str("open-quote"), - ContentItem::CloseQuote => dest.write_str("close-quote"), - ContentItem::NoOpenQuote => dest.write_str("no-open-quote"), - ContentItem::NoCloseQuote => dest.write_str("no-close-quote"), - - % if product == "gecko": - ContentItem::Attr(ref attr) => { - attr.to_css(dest) - } - ContentItem::Url(ref url) => url.to_css(dest), - % endif - } - } - } - - #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue)] - pub enum T { - Normal, - None, - #[cfg(feature = "gecko")] - MozAltContent, - Items(Vec), - } - - impl ToCss for T { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: Write, - { - match *self { - T::Normal => dest.write_str("normal"), - T::None => dest.write_str("none"), - % if product == "gecko": - T::MozAltContent => dest.write_str("-moz-alt-content"), - % endif - T::Items(ref content) => { - let mut iter = content.iter(); - iter.next().unwrap().to_css(dest)?; - for c in iter { - dest.write_str(" ")?; - c.to_css(dest)?; - } - Ok(()) - } - } - } - } - } - #[inline] - pub fn get_initial_value() -> computed_value::T { - computed_value::T::Normal - } - - #[cfg(feature = "servo")] - fn parse_counter_style(context: &ParserContext, input: &mut Parser) -> list_style_type::computed_value::T { - input.try(|input| { - input.expect_comma()?; - list_style_type::parse(context, input) - }).unwrap_or(list_style_type::computed_value::T::Decimal) - } - - #[cfg(feature = "gecko")] - fn parse_counter_style(context: &ParserContext, input: &mut Parser) -> CounterStyleOrNone { - input.try(|input| { - input.expect_comma()?; - CounterStyleOrNone::parse(context, input) - }).unwrap_or(CounterStyleOrNone::decimal()) - } - - // normal | none | [ | | open-quote | close-quote | no-open-quote | - // no-close-quote ]+ - // TODO: , attr() - pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) - -> Result> { - if input.try(|input| input.expect_ident_matching("normal")).is_ok() { - return Ok(SpecifiedValue::Normal) - } - if input.try(|input| input.expect_ident_matching("none")).is_ok() { - return Ok(SpecifiedValue::None) - } - % if product == "gecko": - if input.try(|input| input.expect_ident_matching("-moz-alt-content")).is_ok() { - return Ok(SpecifiedValue::MozAltContent) - } - % endif - - let mut content = vec![]; - loop { - % if product == "gecko": - if let Ok(mut url) = input.try(|i| SpecifiedUrl::parse(context, i)) { - url.build_image_value(); - content.push(ContentItem::Url(url)); - continue; - } - % endif - // FIXME: remove clone() when lifetimes are non-lexical - match input.next().map(|t| t.clone()) { - Ok(Token::QuotedString(ref value)) => { - content.push(ContentItem::String(value.as_ref().to_owned())) - } - Ok(Token::Function(ref name)) => { - let result = match_ignore_ascii_case! { &name, - "counter" => Some(input.parse_nested_block(|input| { - let name = input.expect_ident()?.as_ref().to_owned(); - let style = parse_counter_style(context, input); - Ok(ContentItem::Counter(name, style)) - })), - "counters" => Some(input.parse_nested_block(|input| { - let name = input.expect_ident()?.as_ref().to_owned(); - input.expect_comma()?; - let separator = input.expect_string()?.as_ref().to_owned(); - let style = parse_counter_style(context, input); - Ok(ContentItem::Counters(name, separator, style)) - })), - % if product == "gecko": - "attr" => Some(input.parse_nested_block(|input| { - Ok(ContentItem::Attr(Attr::parse_function(context, input)?)) - })), - % endif - _ => None - }; - match result { - Some(result) => content.push(result?), - None => return Err(input.new_custom_error( - StyleParseErrorKind::UnexpectedFunction(name.clone()) - )) - } - } - Ok(Token::Ident(ref ident)) => { - let valid = match_ignore_ascii_case! { &ident, - "open-quote" => { content.push(ContentItem::OpenQuote); true }, - "close-quote" => { content.push(ContentItem::CloseQuote); true }, - "no-open-quote" => { content.push(ContentItem::NoOpenQuote); true }, - "no-close-quote" => { content.push(ContentItem::NoCloseQuote); true }, - - _ => false, - }; - if !valid { - return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone()))) - } - } - Err(_) => break, - Ok(t) => return Err(input.new_unexpected_token_error(t)) - } - } - if content.is_empty() { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - Ok(SpecifiedValue::Items(content)) - } - +${helpers.predefined_type("content", + "Content", + "computed::Content::normal()", + initial_specified_value="specified::Content::normal()", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-content/#propdef-content")} ${helpers.predefined_type( "counter-increment", diff --git a/components/style/values/computed/counters.rs b/components/style/values/computed/counters.rs index 415c76d2a68a..71fecc394b04 100644 --- a/components/style/values/computed/counters.rs +++ b/components/style/values/computed/counters.rs @@ -4,11 +4,202 @@ //! Computed values for counter properties +#[cfg(feature = "servo")] +use computed_values::list_style_type::T as ListStyleType; +use cssparser::{self, Parser, Token}; +use parser::{Parse, ParserContext}; +use selectors::parser::SelectorParseErrorKind; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; +#[cfg(feature = "gecko")] +use values::generics::CounterStyleOrNone; use values::generics::counters::CounterIncrement as GenericCounterIncrement; use values::generics::counters::CounterReset as GenericCounterReset; +#[cfg(feature = "gecko")] +use values::specified::Attr; +#[cfg(feature = "gecko")] +use values::specified::url::SpecifiedUrl; +pub use values::specified::{Content, ContentItem}; /// A computed value for the `counter-increment` property. pub type CounterIncrement = GenericCounterIncrement; /// A computed value for the `counter-increment` property. pub type CounterReset = GenericCounterReset; + +impl Content { + /// Set `content` property to `normal`. + #[inline] + pub fn normal() -> Self { + Content::Normal + } + + #[cfg(feature = "servo")] + fn parse_counter_style( + input: &mut Parser + ) -> ListStyleType { + input.try(|input| { + input.expect_comma()?; + ListStyleType::parse(input) + }).unwrap_or(ListStyleType::Decimal) + } + + #[cfg(feature = "gecko")] + fn parse_counter_style( + context: &ParserContext, + input: &mut Parser + ) -> CounterStyleOrNone { + input.try(|input| { + input.expect_comma()?; + CounterStyleOrNone::parse(context, input) + }).unwrap_or(CounterStyleOrNone::decimal()) + } +} + +impl Parse for Content { + // normal | none | [ | | open-quote | close-quote | no-open-quote | + // no-close-quote ]+ + // TODO: , attr() + fn parse<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't> + ) -> Result> { + if input.try(|input| input.expect_ident_matching("normal")).is_ok() { + return Ok(Content::Normal); + } + if input.try(|input| input.expect_ident_matching("none")).is_ok() { + return Ok(Content::None); + } + #[cfg(feature = "gecko")] { + if input.try(|input| input.expect_ident_matching("-moz-alt-content")).is_ok() { + return Ok(Content::MozAltContent); + } + } + + let mut content = vec![]; + loop { + #[cfg(feature = "gecko")] { + if let Ok(mut url) = input.try(|i| SpecifiedUrl::parse(_context, i)) { + url.build_image_value(); + content.push(ContentItem::Url(url)); + continue; + } + } + // FIXME: remove clone() when lifetimes are non-lexical + match input.next().map(|t| t.clone()) { + Ok(Token::QuotedString(ref value)) => { + content.push(ContentItem::String(value.as_ref().to_owned().into_boxed_str())); + } + Ok(Token::Function(ref name)) => { + let result = match_ignore_ascii_case! { &name, + "counter" => Some(input.parse_nested_block(|input| { + let name = input.expect_ident()?.as_ref().to_owned().into_boxed_str(); + #[cfg(feature = "servo")] + let style = Content::parse_counter_style(input); + #[cfg(feature = "gecko")] + let style = Content::parse_counter_style(_context, input); + Ok(ContentItem::Counter(name, style)) + })), + "counters" => Some(input.parse_nested_block(|input| { + let name = input.expect_ident()?.as_ref().to_owned().into_boxed_str(); + input.expect_comma()?; + let separator = input.expect_string()?.as_ref().to_owned().into_boxed_str(); + #[cfg(feature = "servo")] + let style = Content::parse_counter_style(input); + #[cfg(feature = "gecko")] + let style = Content::parse_counter_style(_context, input); + Ok(ContentItem::Counters(name, separator, style)) + })), + #[cfg(feature = "gecko")] + "attr" => Some(input.parse_nested_block(|input| { + Ok(ContentItem::Attr(Attr::parse_function(_context, input)?)) + })), + _ => None + }; + match result { + Some(result) => content.push(result?), + None => return Err(input.new_custom_error( + StyleParseErrorKind::UnexpectedFunction(name.clone()) + )) + } + } + Ok(Token::Ident(ref ident)) => { + content.push( + match_ignore_ascii_case! { &ident, + "open-quote" => ContentItem::OpenQuote, + "close-quote" => ContentItem::CloseQuote, + "no-open-quote" => ContentItem::NoOpenQuote, + "no-close-quote" => ContentItem::NoCloseQuote, + _ => return Err(input.new_custom_error( + SelectorParseErrorKind::UnexpectedIdent(ident.clone()))) + } + ); + } + Err(_) => break, + Ok(t) => return Err(input.new_unexpected_token_error(t)) + } + } + if content.is_empty() { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + Ok(Content::Items(content.into_boxed_slice())) + } +} + +impl ToCss for Content { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where W: Write, + { + match *self { + Content::Normal => dest.write_str("normal"), + Content::None => dest.write_str("none"), + #[cfg(feature = "gecko")] + Content::MozAltContent => dest.write_str("-moz-alt-content"), + Content::Items(ref content) => { + let mut iter = content.iter(); + iter.next().unwrap().to_css(dest)?; + for c in iter { + dest.write_str(" ")?; + c.to_css(dest)?; + } + Ok(()) + } + } + } +} + +impl ToCss for ContentItem { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where W: Write, + { + match *self { + ContentItem::String(ref s) => s.to_css(dest), + ContentItem::Counter(ref s, ref counter_style) => { + dest.write_str("counter(")?; + cssparser::serialize_identifier(&**s, dest)?; + dest.write_str(", ")?; + counter_style.to_css(dest)?; + dest.write_str(")") + } + ContentItem::Counters(ref s, ref separator, ref counter_style) => { + dest.write_str("counters(")?; + cssparser::serialize_identifier(&**s, dest)?; + dest.write_str(", ")?; + separator.to_css(dest)?; + dest.write_str(", ")?; + counter_style.to_css(dest)?; + dest.write_str(")") + } + ContentItem::OpenQuote => dest.write_str("open-quote"), + ContentItem::CloseQuote => dest.write_str("close-quote"), + ContentItem::NoOpenQuote => dest.write_str("no-open-quote"), + ContentItem::NoCloseQuote => dest.write_str("no-close-quote"), + #[cfg(feature = "gecko")] + ContentItem::Attr(ref attr) => { + attr.to_css(dest) + } + #[cfg(feature = "gecko")] + ContentItem::Url(ref url) => url.to_css(dest), + } + } +} diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index e883cc79a89d..0194c0b5b3aa 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -46,7 +46,7 @@ pub use self::font::{MozScriptLevel, MozScriptMinSize, MozScriptSizeMultiplier, pub use self::box_::{AnimationIterationCount, AnimationName, Display, OverscrollBehavior, Contain}; pub use self::box_::{OverflowClipBox, ScrollSnapType, TouchAction, VerticalAlign, WillChange}; pub use self::color::{Color, ColorPropertyValue, RGBAColor}; -pub use self::counters::{CounterIncrement, CounterReset}; +pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset}; pub use self::effects::{BoxShadow, Filter, SimpleShadow}; pub use self::flex::FlexBasis; pub use self::image::{Gradient, GradientItem, Image, ImageLayer, LineDirection, MozImageRect}; diff --git a/components/style/values/specified/counters.rs b/components/style/values/specified/counters.rs index ec8915eeaa49..e3ba041ee57c 100644 --- a/components/style/values/specified/counters.rs +++ b/components/style/values/specified/counters.rs @@ -4,13 +4,21 @@ //! Specified types for counter properties. +#[cfg(feature = "servo")] +use computed_values::list_style_type::T as ListStyleType; use cssparser::{Token, Parser}; use parser::{Parse, ParserContext}; use style_traits::{ParseError, StyleParseErrorKind}; use values::CustomIdent; +#[cfg(feature = "gecko")] +use values::generics::CounterStyleOrNone; use values::generics::counters::CounterIncrement as GenericCounterIncrement; use values::generics::counters::CounterReset as GenericCounterReset; +#[cfg(feature = "gecko")] +use values::specified::Attr; use values::specified::Integer; +#[cfg(feature = "gecko")] +use values::specified::url::SpecifiedUrl; /// A specified value for the `counter-increment` property. pub type CounterIncrement = GenericCounterIncrement; @@ -65,3 +73,49 @@ fn parse_counters<'i, 't>( Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } } + +#[cfg(feature = "servo")] +type CounterStyleType = ListStyleType; +#[cfg(feature = "gecko")] +type CounterStyleType = CounterStyleOrNone; + +/// The specified value for the `content` property. +/// +/// https://drafts.csswg.org/css-content/#propdef-content +#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue)] +pub enum Content { + /// `normal` reserved keyword. + Normal, + /// `none` reserved keyword. + None, + /// `-moz-alt-content`. + #[cfg(feature = "gecko")] + MozAltContent, + /// Content items. + Items(Box<[ContentItem]>), +} + +/// Items for the `content` property. +#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue)] +pub enum ContentItem { + /// Literal string content. + String(Box), + /// `counter(name, style)`. + Counter(Box, CounterStyleType), + /// `counters(name, separator, style)`. + Counters(Box, Box, CounterStyleType), + /// `open-quote`. + OpenQuote, + /// `close-quote`. + CloseQuote, + /// `no-open-quote`. + NoOpenQuote, + /// `no-close-quote`. + NoCloseQuote, + /// `attr([namespace? `|`]? ident)` + #[cfg(feature = "gecko")] + Attr(Attr), + /// `url(url)` + #[cfg(feature = "gecko")] + Url(SpecifiedUrl), +} diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index 16ac786a5316..4bca52bd94b9 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -40,7 +40,7 @@ pub use self::font::{MozScriptLevel, MozScriptMinSize, MozScriptSizeMultiplier, pub use self::box_::{AnimationIterationCount, AnimationName, Display, OverscrollBehavior, Contain}; pub use self::box_::{OverflowClipBox, ScrollSnapType, TouchAction, VerticalAlign, WillChange}; pub use self::color::{Color, ColorPropertyValue, RGBAColor}; -pub use self::counters::{CounterIncrement, CounterReset}; +pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset}; pub use self::effects::{BoxShadow, Filter, SimpleShadow}; pub use self::flex::FlexBasis; #[cfg(feature = "gecko")]