use crate::declaration::{parse_declaration, DeclarationBlock, DeclarationList}; use crate::error::{Error, ParserError, PrinterError}; use crate::media_query::*; use crate::printer::Printer; use crate::properties::custom::TokenList; use crate::rules::container::{ContainerCondition, ContainerName, ContainerRule}; use crate::rules::font_feature_values::FontFeatureValuesRule; use crate::rules::font_palette_values::FontPaletteValuesRule; use crate::rules::layer::{LayerBlockRule, LayerStatementRule}; use crate::rules::nesting::NestedDeclarationsRule; use crate::rules::property::PropertyRule; use crate::rules::scope::ScopeRule; use crate::rules::starting_style::StartingStyleRule; use crate::rules::view_transition::ViewTransitionRule; use crate::rules::viewport::ViewportRule; use crate::properties::font::FamilyName; use crate::rules::{ counter_style::CounterStyleRule, custom_media::CustomMediaRule, document::MozDocumentRule, font_face::{FontFaceDeclarationParser, FontFaceRule}, import::ImportRule, keyframes::{KeyframeListParser, KeyframesName, KeyframesRule}, layer::LayerName, media::MediaRule, namespace::NamespaceRule, nesting::NestingRule, page::{PageRule, PageSelector}, style::StyleRule, supports::{SupportsCondition, SupportsRule}, unknown::UnknownAtRule, CssRule, CssRuleList, Location, }; use crate::selector::{SelectorList, SelectorParser}; use crate::traits::{Parse, ParseWithOptions}; use crate::values::ident::{CustomIdent, DashedIdent}; use crate::values::string::CowArcStr; use crate::vendor_prefix::VendorPrefix; #[cfg(feature = "visitor")] use crate::visitor::{Visit, VisitTypes, Visitor}; use bitflags::bitflags; use cssparser::*; use parcel_selectors::parser::{NestingRequirement, ParseErrorRecovery}; use std::sync::{Arc, RwLock}; bitflags! { /// Parser feature flags to enable. #[derive(Clone, Debug, Default)] pub struct ParserFlags: u8 { /// Whether the enable the [CSS nesting](https://www.w3.org/TR/css-nesting-1/) draft syntax. const NESTING = 1 << 0; /// Whether to enable the [custom media](https://drafts.csswg.org/mediaqueries-5/#custom-mq) draft syntax. const CUSTOM_MEDIA = 1 << 1; /// Whether to enable the non-standard >>> and /deep/ selector combinators used by Vue and Angular. const DEEP_SELECTOR_COMBINATOR = 1 << 2; } } /// CSS parsing options. #[derive(Clone, Debug, Default)] pub struct ParserOptions<'o, 'i> { /// Filename to use in error messages. pub filename: String, /// Whether the enable [CSS modules](https://github.com/css-modules/css-modules). pub css_modules: Option>, /// The source index to assign to all parsed rules. Impacts the source map when /// the style sheet is serialized. pub source_index: u32, /// Whether to ignore invalid rules and declarations rather than erroring. pub error_recovery: bool, /// A list that will be appended to when a warning occurs. pub warnings: Option>>>>>, /// Feature flags to enable. pub flags: ParserFlags, } impl<'o, 'i> ParserOptions<'o, 'i> { #[inline] pub(crate) fn warn(&self, warning: ParseError<'i, ParserError<'i>>) { if let Some(warnings) = &self.warnings { if let Ok(mut warnings) = warnings.write() { warnings.push(Error::from(warning, self.filename.clone())); } } } } #[derive(Clone, Default)] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] pub struct DefaultAtRuleParser; impl<'i> crate::traits::AtRuleParser<'i> for DefaultAtRuleParser { type AtRule = DefaultAtRule; type Error = (); type Prelude = (); } #[derive(PartialEq, Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] pub struct DefaultAtRule; impl crate::traits::ToCss for DefaultAtRule { fn to_css(&self, _: &mut Printer) -> Result<(), PrinterError> { Err(PrinterError { kind: crate::error::PrinterErrorKind::FmtError, loc: None, }) } } #[cfg(feature = "into_owned")] impl<'any> static_self::IntoOwned<'any> for DefaultAtRule { type Owned = Self; fn into_owned(self) -> Self { self } } #[cfg(feature = "visitor")] #[cfg_attr(docsrs, doc(cfg(feature = "visitor")))] impl<'i, V: Visitor<'i, DefaultAtRule>> Visit<'i, DefaultAtRule, V> for DefaultAtRule { const CHILD_TYPES: VisitTypes = VisitTypes::empty(); fn visit_children(&mut self, _: &mut V) -> Result<(), V::Error> { Ok(()) } } #[derive(PartialEq, PartialOrd)] enum State { Start = 1, Layers = 2, Imports = 3, Namespaces = 4, Body = 5, } /// The parser for the top-level rules in a stylesheet. pub struct TopLevelRuleParser<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> { pub options: &'a ParserOptions<'o, 'i>, state: State, at_rule_parser: &'a mut T, rules: &'a mut CssRuleList<'i, T::AtRule>, } impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> TopLevelRuleParser<'a, 'o, 'i, T> { pub fn new( options: &'a ParserOptions<'o, 'i>, at_rule_parser: &'a mut T, rules: &'a mut CssRuleList<'i, T::AtRule>, ) -> Self { TopLevelRuleParser { options, state: State::Start, at_rule_parser, rules, } } pub fn nested<'x: 'b>(&'x mut self) -> NestedRuleParser<'x, 'o, 'i, T> { NestedRuleParser { options: &self.options, at_rule_parser: self.at_rule_parser, declarations: DeclarationList::new(), important_declarations: DeclarationList::new(), rules: &mut self.rules, is_in_style_rule: false, allow_declarations: false, } } } /// A rule prelude for at-rule with block. #[derive(Debug)] #[allow(dead_code)] pub enum AtRulePrelude<'i, T> { /// A @font-face rule prelude. FontFace, /// A @font-feature-values rule prelude, with its FamilyName list. FontFeatureValues(Vec>), /// A @font-palette-values rule prelude, with its name. FontPaletteValues(DashedIdent<'i>), /// A @counter-style rule prelude, with its counter style name. CounterStyle(CustomIdent<'i>), /// A @media rule prelude, with its media queries. Media(MediaList<'i>), /// A @custom-media rule prelude. CustomMedia(DashedIdent<'i>, MediaList<'i>), /// An @supports rule, with its conditional Supports(SupportsCondition<'i>), /// A @viewport rule prelude. Viewport(VendorPrefix), /// A @keyframes rule, with its animation name and vendor prefix if exists. Keyframes(KeyframesName<'i>, VendorPrefix), /// A @page rule prelude. Page(Vec>), /// A @-moz-document rule. MozDocument, /// A @import rule prelude. Import( CowRcStr<'i>, MediaList<'i>, Option>, Option>>, ), /// A @namespace rule prelude. Namespace(Option>, CowRcStr<'i>), /// A @charset rule prelude. Charset, /// A @nest prelude. Nest(SelectorList<'i>), /// An @layer prelude. Layer(Vec>), /// An @property prelude. Property(DashedIdent<'i>), /// A @container prelude. /// Spec: https://drafts.csswg.org/css-conditional-5/#container-rule /// @container [ ? ? ]! Container(Option>, Option>), /// A @starting-style prelude. StartingStyle, /// A @scope rule prelude. Scope(Option>, Option>), /// A @view-transition rule prelude. ViewTransition, /// An unknown prelude. Unknown(CowArcStr<'i>, TokenList<'i>), /// A custom prelude. Custom(T), } impl<'i, T> AtRulePrelude<'i, T> { // https://drafts.csswg.org/css-nesting/#conditionals // In addition to nested style rules, this specification allows nested group rules inside // of style rules: any at-rule whose body contains style rules can be nested inside of a // style rule as well. fn allowed_in_style_rule(&self) -> bool { match *self { Self::Media(..) | Self::Supports(..) | Self::Container(..) | Self::MozDocument | Self::Layer(..) | Self::StartingStyle | Self::Scope(..) | Self::Nest(..) | Self::Unknown(..) | Self::Custom(..) => true, Self::Namespace(..) | Self::FontFace | Self::FontFeatureValues(..) | Self::FontPaletteValues(..) | Self::CounterStyle(..) | Self::Keyframes(..) | Self::Page(..) | Self::Property(..) | Self::Import(..) | Self::CustomMedia(..) | Self::Viewport(..) | Self::Charset | Self::ViewTransition => false, } } } impl<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for TopLevelRuleParser<'a, 'o, 'i, T> { type Prelude = AtRulePrelude<'i, T::Prelude>; type AtRule = (); type Error = ParserError<'i>; fn parse_prelude<'t>( &mut self, name: CowRcStr<'i>, input: &mut Parser<'i, 't>, ) -> Result> { match_ignore_ascii_case! { &*name, "import" => { if self.state > State::Imports { return Err(input.new_custom_error(ParserError::UnexpectedImportRule)) } let url_string = input.expect_url_or_string()?.clone(); let layer = if input.try_parse(|input| input.expect_ident_matching("layer")).is_ok() { Some(None) } else if input.try_parse(|input| input.expect_function_matching("layer")).is_ok() { let name = input.parse_nested_block(LayerName::parse).map(|name| Some(name))?; Some(name) } else { None }; let supports = if input.try_parse(|input| input.expect_function_matching("supports")).is_ok() { Some(input.parse_nested_block(|input| { input.try_parse(SupportsCondition::parse).or_else(|_| SupportsCondition::parse_declaration(input)) })?) } else { None }; let media = MediaList::parse(input, &self.options)?; return Ok(AtRulePrelude::Import(url_string, media, supports, layer)); }, "namespace" => { if self.state > State::Namespaces { return Err(input.new_custom_error(ParserError::UnexpectedNamespaceRule)) } let prefix = input.try_parse(|input| input.expect_ident_cloned()).ok(); let namespace = input.expect_url_or_string()?; let prelude = AtRulePrelude::Namespace(prefix, namespace); return Ok(prelude); }, "charset" => { // @charset is removed by rust-cssparser if it’s the first rule in the stylesheet. // Anything left is technically invalid, however, users often concatenate CSS files // together, so we are more lenient and simply ignore @charset rules in the middle of a file. input.expect_string()?; return Ok(AtRulePrelude::Charset) }, "custom-media" if self.options.flags.contains(ParserFlags::CUSTOM_MEDIA) => { let name = DashedIdent::parse(input)?; let media = MediaList::parse(input, &self.options)?; return Ok(AtRulePrelude::CustomMedia(name, media)) }, _ => {} } AtRuleParser::parse_prelude(&mut self.nested(), name, input) } #[inline] fn parse_block<'t>( &mut self, prelude: Self::Prelude, start: &ParserState, input: &mut Parser<'i, 't>, ) -> Result> { self.state = State::Body; AtRuleParser::parse_block(&mut self.nested(), prelude, start, input) } #[inline] fn rule_without_block( &mut self, prelude: AtRulePrelude<'i, T::Prelude>, start: &ParserState, ) -> Result { let loc = start.source_location(); let loc = Location { source_index: self.options.source_index, line: loc.line, column: loc.column, }; match prelude { AtRulePrelude::Import(url, media, supports, layer) => { self.state = State::Imports; self.rules.0.push(CssRule::Import(ImportRule { url: url.into(), layer, supports, media, loc, })); Ok(()) } AtRulePrelude::Namespace(prefix, url) => { self.state = State::Namespaces; self.rules.0.push(CssRule::Namespace(NamespaceRule { prefix: prefix.map(|x| x.into()), url: url.into(), loc, })); Ok(()) } AtRulePrelude::CustomMedia(name, query) => { self.state = State::Body; self.rules.0.push(CssRule::CustomMedia(CustomMediaRule { name, query, loc })); Ok(()) } AtRulePrelude::Layer(_) => { // @layer statements are allowed before @import rules, but cannot be interleaved. if self.state <= State::Layers { self.state = State::Layers; } else { self.state = State::Body; } AtRuleParser::rule_without_block(&mut self.nested(), prelude, start) } AtRulePrelude::Charset => Ok(()), AtRulePrelude::Unknown(name, prelude) => { self.rules.0.push(CssRule::Unknown(UnknownAtRule { name, prelude, block: None, loc, })); Ok(()) } AtRulePrelude::Custom(_) => { self.state = State::Body; AtRuleParser::rule_without_block(&mut self.nested(), prelude, start) } _ => Err(()), } } } impl<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> QualifiedRuleParser<'i> for TopLevelRuleParser<'a, 'o, 'i, T> { type Prelude = SelectorList<'i>; type QualifiedRule = (); type Error = ParserError<'i>; #[inline] fn parse_prelude<'t>( &mut self, input: &mut Parser<'i, 't>, ) -> Result> { self.state = State::Body; QualifiedRuleParser::parse_prelude(&mut self.nested(), input) } #[inline] fn parse_block<'t>( &mut self, prelude: Self::Prelude, start: &ParserState, input: &mut Parser<'i, 't>, ) -> Result> { QualifiedRuleParser::parse_block(&mut self.nested(), prelude, start, input) } } pub struct NestedRuleParser<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> { pub options: &'a ParserOptions<'o, 'i>, pub at_rule_parser: &'a mut T, declarations: DeclarationList<'i>, important_declarations: DeclarationList<'i>, rules: &'a mut CssRuleList<'i, T::AtRule>, is_in_style_rule: bool, allow_declarations: bool, } impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> NestedRuleParser<'a, 'o, 'i, T> { pub fn parse_nested<'t>( &mut self, input: &mut Parser<'i, 't>, is_style_rule: bool, ) -> Result<(DeclarationBlock<'i>, CssRuleList<'i, T::AtRule>), ParseError<'i, ParserError<'i>>> { let mut rules = CssRuleList(vec![]); let mut nested_parser = NestedRuleParser { options: self.options, at_rule_parser: self.at_rule_parser, declarations: DeclarationList::new(), important_declarations: DeclarationList::new(), rules: &mut rules, is_in_style_rule: self.is_in_style_rule || is_style_rule, allow_declarations: self.allow_declarations || self.is_in_style_rule || is_style_rule, }; let parse_declarations = nested_parser.parse_declarations(); let mut errors = Vec::new(); let mut iter = RuleBodyParser::new(input, &mut nested_parser); while let Some(result) = iter.next() { match result { Ok(()) => {} Err((e, _)) => { if parse_declarations { iter.parser.declarations.clear(); iter.parser.important_declarations.clear(); errors.push(e); } else { if iter.parser.options.error_recovery { iter.parser.options.warn(e); continue; } return Err(e); } } } } if parse_declarations { if !errors.is_empty() { if self.options.error_recovery { for err in errors { self.options.warn(err); } } else { return Err(errors.remove(0)); } } } Ok(( DeclarationBlock { declarations: nested_parser.declarations, important_declarations: nested_parser.important_declarations, }, rules, )) } fn parse_style_block<'t>( &mut self, input: &mut Parser<'i, 't>, ) -> Result, ParseError<'i, ParserError<'i>>> { let loc = input.current_source_location(); let loc = Location { source_index: self.options.source_index, line: loc.line, column: loc.column, }; // Declarations can be immediately within @media and @supports blocks that are nested within a parent style rule. // These are wrapped in an (invisible) NestedDeclarationsRule. let (declarations, mut rules) = self.parse_nested(input, false)?; if declarations.len() > 0 { rules.0.insert( 0, CssRule::NestedDeclarations(NestedDeclarationsRule { declarations, loc }), ) } Ok(rules) } fn loc(&self, start: &ParserState) -> Location { let loc = start.source_location(); Location { source_index: self.options.source_index, line: loc.line, column: loc.column, } } } impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for NestedRuleParser<'a, 'o, 'i, T> { type Prelude = AtRulePrelude<'i, T::Prelude>; type AtRule = (); type Error = ParserError<'i>; fn parse_prelude<'t>( &mut self, name: CowRcStr<'i>, input: &mut Parser<'i, 't>, ) -> Result> { let result = match_ignore_ascii_case! { &*name, "media" => { let media = MediaList::parse(input, &self.options)?; AtRulePrelude::Media(media) }, "supports" => { let cond = SupportsCondition::parse(input, )?; AtRulePrelude::Supports(cond) }, "font-face" => { AtRulePrelude::FontFace }, // "font-feature-values" => { // if !cfg!(feature = "gecko") { // // Support for this rule is not fully implemented in Servo yet. // return Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone()))) // } // let family_names = parse_family_name_list(self.context, input)?; // Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::FontFeatureValues(family_names))) // }, "font-feature-values" => { let names = match Vec::::parse(input) { Ok(names) => names, Err(e) => return Err(e) }; AtRulePrelude::FontFeatureValues(names) }, "font-palette-values" => { let name = DashedIdent::parse(input)?; AtRulePrelude::FontPaletteValues(name) }, "counter-style" => { let name = CustomIdent::parse(input)?; AtRulePrelude::CounterStyle(name) }, "viewport" | "-ms-viewport" => { let prefix = if starts_with_ignore_ascii_case(&*name, "-ms") { VendorPrefix::Ms } else { VendorPrefix::None }; AtRulePrelude::Viewport(prefix) }, "keyframes" | "-webkit-keyframes" | "-moz-keyframes" | "-o-keyframes" | "-ms-keyframes" => { let prefix = if starts_with_ignore_ascii_case(&*name, "-webkit-") { VendorPrefix::WebKit } else if starts_with_ignore_ascii_case(&*name, "-moz-") { VendorPrefix::Moz } else if starts_with_ignore_ascii_case(&*name, "-o-") { VendorPrefix::O } else if starts_with_ignore_ascii_case(&*name, "-ms-") { VendorPrefix::Ms } else { VendorPrefix::None }; let name = input.try_parse(KeyframesName::parse)?; AtRulePrelude::Keyframes(name, prefix) }, "page" => { let selectors = input.try_parse(|input| input.parse_comma_separated(PageSelector::parse)).unwrap_or_default(); AtRulePrelude::Page(selectors) }, "-moz-document" => { // Firefox only supports the url-prefix() function with no arguments as a legacy CSS hack. // See https://css-tricks.com/snippets/css/css-hacks-targeting-firefox/ input.expect_function_matching("url-prefix")?; input.parse_nested_block(|input| { // Firefox also allows an empty string as an argument... // https://github.com/mozilla/gecko-dev/blob/0077f2248712a1b45bf02f0f866449f663538164/servo/components/style/stylesheets/document_rule.rs#L303 let _ = input.try_parse(|input| -> Result<(), ParseError<'i, Self::Error>> { let s = input.expect_string()?; if !s.is_empty() { return Err(input.new_custom_error(ParserError::InvalidValue)) } Ok(()) }); input.expect_exhausted()?; Ok(()) })?; AtRulePrelude::MozDocument }, "layer" => { let names = match Vec::::parse(input) { Ok(names) => names, Err(ParseError { kind: ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput), .. }) => Vec::new(), Err(e) => return Err(e) }; AtRulePrelude::Layer(names) }, "container" => { let name = input.try_parse(ContainerName::parse).ok(); match input.try_parse(|input| ContainerCondition::parse_with_options(input, &self.options)) { Ok(condition) => AtRulePrelude::Container(name, Some(condition)), Err(e) => { if name.is_some() && input.is_exhausted() { // name only, no condition - allowed by new syntax AtRulePrelude::Container(name, None) } else { // condition parsing failed (e.g., empty brackets or invalid tokens) return Err(e); } } } }, "starting-style" => { AtRulePrelude::StartingStyle }, "scope" => { let selector_parser = SelectorParser { is_nesting_allowed: true, options: &self.options, }; let scope_start = if input.try_parse(|input| input.expect_parenthesis_block()).is_ok() { Some(input.parse_nested_block(|input| { // https://drafts.csswg.org/css-cascade-6/#scoped-rules // TODO: disallow pseudo elements? SelectorList::parse_relative(&selector_parser, input, ParseErrorRecovery::IgnoreInvalidSelector, NestingRequirement::None) })?) } else { None }; let scope_end = if input.try_parse(|input| input.expect_ident_matching("to")).is_ok() { input.expect_parenthesis_block()?; Some(input.parse_nested_block(|input| { SelectorList::parse_relative(&selector_parser, input, ParseErrorRecovery::IgnoreInvalidSelector, NestingRequirement::None) })?) } else { None }; AtRulePrelude::Scope(scope_start, scope_end) }, "view-transition" => { AtRulePrelude::ViewTransition }, "nest" if self.is_in_style_rule => { self.options.warn(input.new_custom_error(ParserError::DeprecatedNestRule)); let selector_parser = SelectorParser { is_nesting_allowed: true, options: &self.options, }; let selectors = SelectorList::parse(&selector_parser, input, ParseErrorRecovery::DiscardList, NestingRequirement::Contained)?; AtRulePrelude::Nest(selectors) }, "value" if self.options.css_modules.is_some() => { return Err(input.new_custom_error(ParserError::DeprecatedCssModulesValueRule)); }, "property" => { let name = DashedIdent::parse(input)?; return Ok(AtRulePrelude::Property(name)) }, _ => parse_custom_at_rule_prelude(&name, input, self.options, self.at_rule_parser)? }; if self.is_in_style_rule && !result.allowed_in_style_rule() { return Err(input.new_error(BasicParseErrorKind::AtRuleInvalid(name.clone()))); } Ok(result) } #[inline] fn rule_without_block( &mut self, prelude: AtRulePrelude<'i, T::Prelude>, start: &ParserState, ) -> Result { let loc = self.loc(start); match prelude { AtRulePrelude::Layer(names) => { if self.is_in_style_rule || names.is_empty() { return Err(()); } self.rules.0.push(CssRule::LayerStatement(LayerStatementRule { names, loc })); Ok(()) } AtRulePrelude::Unknown(name, prelude) => { self.rules.0.push(CssRule::Unknown(UnknownAtRule { name, prelude, block: None, loc, })); Ok(()) } AtRulePrelude::Custom(prelude) => { self.rules.0.push(parse_custom_at_rule_without_block( prelude, start, self.options, self.at_rule_parser, self.is_in_style_rule, )?); Ok(()) } _ => Err(()), } } fn parse_block<'t>( &mut self, prelude: Self::Prelude, start: &ParserState, input: &mut Parser<'i, 't>, ) -> Result<(), ParseError<'i, Self::Error>> { let loc = self.loc(start); match prelude { AtRulePrelude::FontFace => { let mut decl_parser = FontFaceDeclarationParser; let mut parser = RuleBodyParser::new(input, &mut decl_parser); let mut properties = vec![]; while let Some(decl) = parser.next() { if let Ok(decl) = decl { properties.push(decl); } } self.rules.0.push(CssRule::FontFace(FontFaceRule { properties, loc })); Ok(()) } // AtRuleBlockPrelude::FontFeatureValues(family_names) => { // let context = ParserContext::new_with_rule_type( // self.context, // CssRuleType::FontFeatureValues, // self.namespaces, // ); // Ok(CssRule::FontFeatureValues(Arc::new(self.shared_lock.wrap( // FontFeatureValuesRule::parse( // &context, // input, // family_names, // start.source_location(), // ), // )))) // }, AtRulePrelude::FontPaletteValues(name) => { let rule = FontPaletteValuesRule::parse(name, input, loc)?; self.rules.0.push(CssRule::FontPaletteValues(rule)); Ok(()) } AtRulePrelude::CounterStyle(name) => { self.rules.0.push(CssRule::CounterStyle(CounterStyleRule { name, declarations: DeclarationBlock::parse(input, self.options)?, loc, })); Ok(()) } AtRulePrelude::Media(query) => { let rules = self.parse_style_block(input)?; self.rules.0.push(CssRule::Media(MediaRule { query, rules, loc })); Ok(()) } AtRulePrelude::Supports(condition) => { let rules = self.parse_style_block(input)?; self.rules.0.push(CssRule::Supports(SupportsRule { condition, rules, loc })); Ok(()) } AtRulePrelude::Container(name, condition) => { let rules = self.parse_style_block(input)?; self.rules.0.push(CssRule::Container(ContainerRule { name, condition, rules, loc, })); Ok(()) } AtRulePrelude::Scope(scope_start, scope_end) => { let rules = self.parse_style_block(input)?; self.rules.0.push(CssRule::Scope(ScopeRule { scope_start, scope_end, rules, loc, })); Ok(()) } AtRulePrelude::Viewport(vendor_prefix) => { self.rules.0.push(CssRule::Viewport(ViewportRule { vendor_prefix, // TODO: parse viewport descriptors rather than properties // https://drafts.csswg.org/css-device-adapt/#viewport-desc declarations: DeclarationBlock::parse(input, self.options)?, loc, })); Ok(()) } AtRulePrelude::Keyframes(name, vendor_prefix) => { let mut parser = KeyframeListParser; let iter = RuleBodyParser::new(input, &mut parser); self.rules.0.push(CssRule::Keyframes(KeyframesRule { name, keyframes: iter.filter_map(Result::ok).collect(), vendor_prefix, loc, })); Ok(()) } AtRulePrelude::Page(selectors) => { let rule = PageRule::parse(selectors, input, loc, self.options)?; self.rules.0.push(CssRule::Page(rule)); Ok(()) } AtRulePrelude::MozDocument => { let rules = self.parse_style_block(input)?; self.rules.0.push(CssRule::MozDocument(MozDocumentRule { rules, loc })); Ok(()) } AtRulePrelude::Layer(names) => { let name = if names.is_empty() { None } else if names.len() == 1 { names.into_iter().next() } else { return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid)); }; let rules = self.parse_style_block(input)?; self.rules.0.push(CssRule::LayerBlock(LayerBlockRule { name, rules, loc })); Ok(()) } AtRulePrelude::Property(name) => { self.rules.0.push(CssRule::Property(PropertyRule::parse(name, input, loc)?)); Ok(()) } AtRulePrelude::Import(..) | AtRulePrelude::Namespace(..) | AtRulePrelude::CustomMedia(..) | AtRulePrelude::Charset => { // These rules don't have blocks. Err(input.new_unexpected_token_error(Token::CurlyBracketBlock)) } AtRulePrelude::StartingStyle => { let rules = self.parse_style_block(input)?; self.rules.0.push(CssRule::StartingStyle(StartingStyleRule { rules, loc })); Ok(()) } AtRulePrelude::ViewTransition => { self .rules .0 .push(CssRule::ViewTransition(ViewTransitionRule::parse(input, loc)?)); Ok(()) } AtRulePrelude::Nest(selectors) => { let (declarations, rules) = self.parse_nested(input, true)?; self.rules.0.push(CssRule::Nesting(NestingRule { style: StyleRule { selectors, declarations, vendor_prefix: VendorPrefix::empty(), rules, loc, }, loc, })); Ok(()) } AtRulePrelude::FontFeatureValues(family_names) => { let rule = FontFeatureValuesRule::parse(family_names, input, loc, self.options)?; self.rules.0.push(CssRule::FontFeatureValues(rule)); Ok(()) } AtRulePrelude::Unknown(name, prelude) => { self.rules.0.push(CssRule::Unknown(UnknownAtRule { name, prelude, block: Some(TokenList::parse(input, &self.options, 0)?), loc, })); Ok(()) } AtRulePrelude::Custom(prelude) => { self.rules.0.push(parse_custom_at_rule_body( prelude, input, start, self.options, self.at_rule_parser, self.is_in_style_rule, )?); Ok(()) } } } } impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> QualifiedRuleParser<'i> for NestedRuleParser<'a, 'o, 'i, T> { type Prelude = SelectorList<'i>; type QualifiedRule = (); type Error = ParserError<'i>; fn parse_prelude<'t>( &mut self, input: &mut Parser<'i, 't>, ) -> Result> { let selector_parser = SelectorParser { is_nesting_allowed: true, options: &self.options, }; if self.is_in_style_rule { SelectorList::parse_relative( &selector_parser, input, ParseErrorRecovery::DiscardList, NestingRequirement::Implicit, ) } else { SelectorList::parse( &selector_parser, input, ParseErrorRecovery::DiscardList, NestingRequirement::None, ) } } fn parse_block<'t>( &mut self, selectors: Self::Prelude, start: &ParserState, input: &mut Parser<'i, 't>, ) -> Result<(), ParseError<'i, Self::Error>> { let loc = self.loc(start); let (declarations, rules) = self.parse_nested(input, true)?; self.rules.0.push(CssRule::Style(StyleRule { selectors, vendor_prefix: VendorPrefix::empty(), declarations, rules, loc, })); Ok(()) } } /// Parse a declaration within {} block: `color: blue` impl<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> cssparser::DeclarationParser<'i> for NestedRuleParser<'a, 'o, 'i, T> { type Declaration = (); type Error = ParserError<'i>; fn parse_value<'t>( &mut self, name: CowRcStr<'i>, input: &mut cssparser::Parser<'i, 't>, ) -> Result> { if self.rules.0.is_empty() { parse_declaration( name, input, &mut self.declarations, &mut self.important_declarations, &self.options, ) } else if let Some(CssRule::NestedDeclarations(last)) = self.rules.0.last_mut() { parse_declaration( name, input, &mut last.declarations.declarations, &mut last.declarations.important_declarations, &self.options, ) } else { let loc = self.loc(&input.state()); let mut nested = NestedDeclarationsRule { declarations: DeclarationBlock::new(), loc, }; parse_declaration( name, input, &mut nested.declarations.declarations, &mut nested.declarations.important_declarations, &self.options, )?; self.rules.0.push(CssRule::NestedDeclarations(nested)); Ok(()) } } } impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> RuleBodyItemParser<'i, (), ParserError<'i>> for NestedRuleParser<'a, 'o, 'i, T> { fn parse_qualified(&self) -> bool { true } fn parse_declarations(&self) -> bool { self.allow_declarations } } fn parse_custom_at_rule_prelude<'i, 't, T: crate::traits::AtRuleParser<'i>>( name: &CowRcStr<'i>, input: &mut Parser<'i, 't>, options: &ParserOptions<'_, 'i>, at_rule_parser: &mut T, ) -> Result, ParseError<'i, ParserError<'i>>> { match at_rule_parser.parse_prelude(name.clone(), input, options) { Ok(prelude) => return Ok(AtRulePrelude::Custom(prelude)), Err(ParseError { kind: ParseErrorKind::Basic(BasicParseErrorKind::AtRuleInvalid(..)), .. }) => {} Err(err) => { return Err(match &err.kind { ParseErrorKind::Basic(kind) => ParseError { kind: ParseErrorKind::Basic(kind.clone()), location: err.location, }, _ => input.new_custom_error(ParserError::AtRulePreludeInvalid), }) } } options.warn(input.new_error(BasicParseErrorKind::AtRuleInvalid(name.clone()))); input.skip_whitespace(); let tokens = TokenList::parse(input, &options, 0)?; Ok(AtRulePrelude::Unknown(name.into(), tokens)) } fn parse_custom_at_rule_body<'i, 't, T: crate::traits::AtRuleParser<'i>>( prelude: T::Prelude, input: &mut Parser<'i, 't>, start: &ParserState, options: &ParserOptions<'_, 'i>, at_rule_parser: &mut T, is_nested: bool, ) -> Result, ParseError<'i, ParserError<'i>>> { at_rule_parser .parse_block(prelude, start, input, options, is_nested) .map(|prelude| CssRule::Custom(prelude)) .map_err(|err| match &err.kind { ParseErrorKind::Basic(kind) => ParseError { kind: ParseErrorKind::Basic(kind.clone()), location: err.location, }, _ => input.new_error(BasicParseErrorKind::AtRuleBodyInvalid), }) } fn parse_custom_at_rule_without_block<'i, 't, T: crate::traits::AtRuleParser<'i>>( prelude: T::Prelude, start: &ParserState, options: &ParserOptions<'_, 'i>, at_rule_parser: &mut T, is_nested: bool, ) -> Result, ()> { at_rule_parser .rule_without_block(prelude, start, options, is_nested) .map(|prelude| CssRule::Custom(prelude)) } pub fn parse_rule_list<'a, 'o, 'i, 't, T: crate::traits::AtRuleParser<'i>>( input: &mut Parser<'i, 't>, options: &'a ParserOptions<'o, 'i>, at_rule_parser: &mut T, ) -> Result, ParseError<'i, ParserError<'i>>> { let mut parser = NestedRuleParser { options, at_rule_parser, declarations: DeclarationList::new(), important_declarations: DeclarationList::new(), rules: &mut CssRuleList(Vec::new()), is_in_style_rule: false, allow_declarations: false, }; let (_, rules) = parser.parse_nested(input, false)?; Ok(rules) } pub fn parse_style_block<'a, 'o, 'i, 't, T: crate::traits::AtRuleParser<'i>>( input: &mut Parser<'i, 't>, options: &'a ParserOptions<'o, 'i>, at_rule_parser: &mut T, is_nested: bool, ) -> Result, ParseError<'i, ParserError<'i>>> { let mut parser = NestedRuleParser { options, at_rule_parser, declarations: DeclarationList::new(), important_declarations: DeclarationList::new(), rules: &mut CssRuleList(Vec::new()), is_in_style_rule: is_nested, allow_declarations: true, }; parser.parse_style_block(input) } #[inline] pub fn starts_with_ignore_ascii_case(string: &str, prefix: &str) -> bool { string.len() >= prefix.len() && string.as_bytes()[0..prefix.len()].eq_ignore_ascii_case(prefix.as_bytes()) }