counters.rs (10689B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 4 5 //! Specified types for counter properties. 6 7 #[cfg(feature = "servo")] 8 use crate::computed_values::list_style_type::T as ListStyleType; 9 #[cfg(feature = "gecko")] 10 use crate::counter_style::CounterStyle; 11 use crate::parser::{Parse, ParserContext}; 12 use crate::values::generics::counters as generics; 13 use crate::values::generics::counters::CounterPair; 14 use crate::values::specified::image::Image; 15 use crate::values::specified::Attr; 16 use crate::values::specified::Integer; 17 use crate::values::CustomIdent; 18 use cssparser::{match_ignore_ascii_case, Parser, Token}; 19 use selectors::parser::SelectorParseErrorKind; 20 use style_traits::{ParseError, StyleParseErrorKind}; 21 22 #[derive(PartialEq)] 23 enum CounterType { 24 Increment, 25 Set, 26 Reset, 27 } 28 29 impl CounterType { 30 fn default_value(&self) -> i32 { 31 match *self { 32 Self::Increment => 1, 33 Self::Reset | Self::Set => 0, 34 } 35 } 36 } 37 38 /// A specified value for the `counter-increment` property. 39 pub type CounterIncrement = generics::GenericCounterIncrement<Integer>; 40 41 impl Parse for CounterIncrement { 42 fn parse<'i, 't>( 43 context: &ParserContext, 44 input: &mut Parser<'i, 't>, 45 ) -> Result<Self, ParseError<'i>> { 46 Ok(Self::new(parse_counters( 47 context, 48 input, 49 CounterType::Increment, 50 )?)) 51 } 52 } 53 54 /// A specified value for the `counter-set` property. 55 pub type CounterSet = generics::GenericCounterSet<Integer>; 56 57 impl Parse for CounterSet { 58 fn parse<'i, 't>( 59 context: &ParserContext, 60 input: &mut Parser<'i, 't>, 61 ) -> Result<Self, ParseError<'i>> { 62 Ok(Self::new(parse_counters(context, input, CounterType::Set)?)) 63 } 64 } 65 66 /// A specified value for the `counter-reset` property. 67 pub type CounterReset = generics::GenericCounterReset<Integer>; 68 69 impl Parse for CounterReset { 70 fn parse<'i, 't>( 71 context: &ParserContext, 72 input: &mut Parser<'i, 't>, 73 ) -> Result<Self, ParseError<'i>> { 74 Ok(Self::new(parse_counters( 75 context, 76 input, 77 CounterType::Reset, 78 )?)) 79 } 80 } 81 82 fn parse_counters<'i, 't>( 83 context: &ParserContext, 84 input: &mut Parser<'i, 't>, 85 counter_type: CounterType, 86 ) -> Result<Vec<CounterPair<Integer>>, ParseError<'i>> { 87 if input 88 .try_parse(|input| input.expect_ident_matching("none")) 89 .is_ok() 90 { 91 return Ok(vec![]); 92 } 93 94 let mut counters = Vec::new(); 95 loop { 96 let location = input.current_source_location(); 97 let (name, is_reversed) = match input.next() { 98 Ok(&Token::Ident(ref ident)) => { 99 (CustomIdent::from_ident(location, ident, &["none"])?, false) 100 }, 101 Ok(&Token::Function(ref name)) 102 if counter_type == CounterType::Reset && name.eq_ignore_ascii_case("reversed") => 103 { 104 input 105 .parse_nested_block(|input| Ok((CustomIdent::parse(input, &["none"])?, true)))? 106 }, 107 Ok(t) => { 108 let t = t.clone(); 109 return Err(location.new_unexpected_token_error(t)); 110 }, 111 Err(_) => break, 112 }; 113 114 let value = match input.try_parse(|input| Integer::parse(context, input)) { 115 Ok(start) => { 116 if start.value() == i32::min_value() { 117 // The spec says that values must be clamped to the valid range, 118 // and we reserve i32::min_value() as an internal magic value. 119 // https://drafts.csswg.org/css-lists/#auto-numbering 120 Integer::new(i32::min_value() + 1) 121 } else { 122 start 123 } 124 }, 125 _ => Integer::new(if is_reversed { 126 i32::min_value() 127 } else { 128 counter_type.default_value() 129 }), 130 }; 131 counters.push(CounterPair { 132 name, 133 value, 134 is_reversed, 135 }); 136 } 137 138 if !counters.is_empty() { 139 Ok(counters) 140 } else { 141 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) 142 } 143 } 144 145 /// The specified value for the `content` property. 146 pub type Content = generics::GenericContent<Image>; 147 148 /// The specified value for a content item in the `content` property. 149 pub type ContentItem = generics::GenericContentItem<Image>; 150 151 impl Content { 152 #[cfg(feature = "servo")] 153 fn parse_counter_style(_: &ParserContext, input: &mut Parser) -> ListStyleType { 154 input 155 .try_parse(|input| { 156 input.expect_comma()?; 157 ListStyleType::parse(input) 158 }) 159 .unwrap_or(ListStyleType::Decimal) 160 } 161 162 #[cfg(feature = "gecko")] 163 fn parse_counter_style(context: &ParserContext, input: &mut Parser) -> CounterStyle { 164 use crate::counter_style::CounterStyleParsingFlags; 165 input 166 .try_parse(|input| { 167 input.expect_comma()?; 168 CounterStyle::parse(context, input, CounterStyleParsingFlags::empty()) 169 }) 170 .unwrap_or_else(|_| CounterStyle::decimal()) 171 } 172 } 173 174 impl Parse for Content { 175 // normal | none | [ <string> | <counter> | open-quote | close-quote | no-open-quote | 176 // no-close-quote ]+ 177 // TODO: <uri>, attr(<identifier>) 178 #[cfg_attr(feature = "servo", allow(unused_mut))] 179 fn parse<'i, 't>( 180 context: &ParserContext, 181 input: &mut Parser<'i, 't>, 182 ) -> Result<Self, ParseError<'i>> { 183 if input 184 .try_parse(|input| input.expect_ident_matching("normal")) 185 .is_ok() 186 { 187 return Ok(generics::Content::Normal); 188 } 189 if input 190 .try_parse(|input| input.expect_ident_matching("none")) 191 .is_ok() 192 { 193 return Ok(generics::Content::None); 194 } 195 196 let mut items = thin_vec::ThinVec::new(); 197 let mut alt_start = None; 198 loop { 199 if alt_start.is_none() { 200 if let Ok(image) = input.try_parse(|i| Image::parse_forbid_none(context, i)) { 201 items.push(generics::ContentItem::Image(image)); 202 continue; 203 } 204 } 205 let Ok(t) = input.next() else { break }; 206 match *t { 207 Token::QuotedString(ref value) => { 208 items.push(generics::ContentItem::String( 209 value.as_ref().to_owned().into(), 210 )); 211 }, 212 Token::Function(ref name) => { 213 // FIXME(emilio): counter() / counters() should be valid per spec past 214 // the alt marker, but it's likely non-trivial to support and other 215 // browsers don't support it either, so restricting it for now. 216 let result = match_ignore_ascii_case! { &name, 217 "counter" if alt_start.is_none() => input.parse_nested_block(|input| { 218 let name = CustomIdent::parse(input, &[])?; 219 let style = Content::parse_counter_style(context, input); 220 Ok(generics::ContentItem::Counter(name, style)) 221 }), 222 "counters" if alt_start.is_none() => input.parse_nested_block(|input| { 223 let name = CustomIdent::parse(input, &[])?; 224 input.expect_comma()?; 225 let separator = input.expect_string()?.as_ref().to_owned().into(); 226 let style = Content::parse_counter_style(context, input); 227 Ok(generics::ContentItem::Counters(name, separator, style)) 228 }), 229 "attr" if !static_prefs::pref!("layout.css.attr.enabled") => input.parse_nested_block(|input| { 230 Ok(generics::ContentItem::Attr(Attr::parse_function(context, input)?)) 231 }), 232 _ => { 233 use style_traits::StyleParseErrorKind; 234 let name = name.clone(); 235 return Err(input.new_custom_error( 236 StyleParseErrorKind::UnexpectedFunction(name), 237 )) 238 } 239 }?; 240 items.push(result); 241 }, 242 Token::Ident(ref ident) if alt_start.is_none() => { 243 items.push(match_ignore_ascii_case! { &ident, 244 "open-quote" => generics::ContentItem::OpenQuote, 245 "close-quote" => generics::ContentItem::CloseQuote, 246 "no-open-quote" => generics::ContentItem::NoOpenQuote, 247 "no-close-quote" => generics::ContentItem::NoCloseQuote, 248 #[cfg(feature = "gecko")] 249 "-moz-alt-content" if context.in_ua_sheet() => { 250 generics::ContentItem::MozAltContent 251 }, 252 #[cfg(feature = "gecko")] 253 "-moz-label-content" if context.chrome_rules_enabled() => { 254 generics::ContentItem::MozLabelContent 255 }, 256 _ =>{ 257 let ident = ident.clone(); 258 return Err(input.new_custom_error( 259 SelectorParseErrorKind::UnexpectedIdent(ident) 260 )); 261 } 262 }); 263 }, 264 Token::Delim('/') 265 if alt_start.is_none() 266 && !items.is_empty() 267 && static_prefs::pref!("layout.css.content.alt-text.enabled") => 268 { 269 alt_start = Some(items.len()); 270 }, 271 ref t => { 272 let t = t.clone(); 273 return Err(input.new_unexpected_token_error(t)); 274 }, 275 } 276 } 277 if items.is_empty() { 278 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); 279 } 280 let alt_start = alt_start.unwrap_or(items.len()); 281 Ok(generics::Content::Items(generics::GenericContentItems { 282 items, 283 alt_start, 284 })) 285 } 286 }