Source code
Revision control
Copy as Markdown
Other Tools
/* 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
//! Parsing of the stylesheet contents.
use crate::counter_style::{parse_counter_style_body, parse_counter_style_name_definition};
use crate::custom_properties::parse_name as parse_custom_property_name;
use crate::error_reporting::ContextualParseError;
use crate::font_face::parse_font_face_block;
use crate::media_queries::MediaList;
use crate::parser::{Parse, ParserContext};
use crate::properties::declaration_block::{
parse_property_declaration_list, DeclarationParserState, PropertyDeclarationBlock,
};
use crate::properties_and_values::rule::{parse_property_block, PropertyRuleName};
use crate::selector_parser::{SelectorImpl, SelectorParser};
use crate::shared_lock::{Locked, SharedRwLock};
use crate::str::starts_with_ignore_ascii_case;
use crate::stylesheets::container_rule::{ContainerCondition, ContainerRule};
use crate::stylesheets::document_rule::DocumentCondition;
use crate::stylesheets::font_feature_values_rule::parse_family_name_list;
use crate::stylesheets::import_rule::{ImportLayer, ImportRule, ImportSupportsCondition};
use crate::stylesheets::keyframes_rule::parse_keyframe_list;
use crate::stylesheets::layer_rule::{LayerBlockRule, LayerName, LayerStatementRule};
use crate::stylesheets::scope_rule::{ScopeBounds, ScopeRule};
use crate::stylesheets::supports_rule::SupportsCondition;
use crate::stylesheets::{
AllowImportRules, CorsMode, CssRule, CssRuleType, CssRuleTypes, CssRules, DocumentRule,
FontFeatureValuesRule, FontPaletteValuesRule, KeyframesRule, MarginRule, MarginRuleType,
MediaRule, NamespaceRule, PageRule, PageSelectors, RulesMutateError, StyleRule,
StylesheetLoader, SupportsRule, StartingStyleRule, NestedDeclarationsRule, PositionTryRule
};
use crate::values::computed::font::FamilyName;
use crate::values::{CssUrl, CustomIdent, DashedIdent, KeyframesName};
use crate::{Atom, Namespace, Prefix};
use cssparser::{
AtRuleParser, BasicParseError, BasicParseErrorKind, CowRcStr, DeclarationParser, Parser,
ParserState, QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser, SourceLocation,
SourcePosition,
};
use selectors::parser::{ParseRelative, SelectorList};
use servo_arc::Arc;
use style_traits::{ParseError, StyleParseErrorKind};
/// The information we need particularly to do CSSOM insertRule stuff.
pub struct InsertRuleContext<'a> {
/// The rule list we're about to insert into.
pub rule_list: &'a [CssRule],
/// The index we're about to get inserted at.
pub index: usize,
/// The containing rule types of our ancestors.
pub containing_rule_types: CssRuleTypes,
/// Rule type determining if and how we parse relative selector syntax.
pub parse_relative_rule_type: Option<CssRuleType>,
}
impl<'a> InsertRuleContext<'a> {
/// Returns the max rule state allowable for insertion at a given index in
/// the rule list.
pub fn max_rule_state_at_index(&self, index: usize) -> State {
let rule = match self.rule_list.get(index) {
Some(rule) => rule,
None => return State::Body,
};
match rule {
CssRule::Import(..) => State::Imports,
CssRule::Namespace(..) => State::Namespaces,
CssRule::LayerStatement(..) => {
// If there are @import / @namespace after this layer, then
// we're in the early-layers phase, otherwise we're in the body
// and everything is fair game.
let next_non_layer_statement_rule = self.rule_list[index + 1..]
.iter()
.find(|r| !matches!(*r, CssRule::LayerStatement(..)));
if let Some(non_layer) = next_non_layer_statement_rule {
if matches!(*non_layer, CssRule::Import(..) | CssRule::Namespace(..)) {
return State::EarlyLayers;
}
}
State::Body
},
_ => State::Body,
}
}
}
/// The parser for the top-level rules in a stylesheet.
pub struct TopLevelRuleParser<'a, 'i> {
/// A reference to the lock we need to use to create rules.
pub shared_lock: &'a SharedRwLock,
/// A reference to a stylesheet loader if applicable, for `@import` rules.
pub loader: Option<&'a dyn StylesheetLoader>,
/// The top-level parser context.
pub context: ParserContext<'a>,
/// The current state of the parser.
pub state: State,
/// Whether we have tried to parse was invalid due to being in the wrong
/// place (e.g. an @import rule was found while in the `Body` state). Reset
/// to `false` when `take_had_hierarchy_error` is called.
pub dom_error: Option<RulesMutateError>,
/// The info we need insert a rule in a list.
pub insert_rule_context: Option<InsertRuleContext<'a>>,
/// Whether @import rules will be allowed.
pub allow_import_rules: AllowImportRules,
/// Whether to keep declarations into first_declaration_block, rather than turning it into a
/// nested declarations rule.
pub wants_first_declaration_block: bool,
/// The first declaration block, only relevant when wants_first_declaration_block is true.
pub first_declaration_block: PropertyDeclarationBlock,
/// Parser state for declaration blocks in either nested rules or style rules.
pub declaration_parser_state: DeclarationParserState<'i>,
/// State we keep around only for error reporting purposes. Right now that contains just the
/// selectors stack for nesting, if any.
///
/// TODO(emilio): This isn't populated properly for `insertRule()` but...
pub error_reporting_state: Vec<SelectorList<SelectorImpl>>,
/// The rules we've parsed so far.
pub rules: Vec<CssRule>,
}
impl<'a, 'i> TopLevelRuleParser<'a, 'i> {
#[inline]
fn nested(&mut self) -> &mut NestedRuleParser<'a, 'i> {
// SAFETY: NestedRuleParser is just a repr(transparent) wrapper over TopLevelRuleParser
const_assert!(
std::mem::size_of::<TopLevelRuleParser<'static, 'static>>() ==
std::mem::size_of::<NestedRuleParser<'static, 'static>>()
);
const_assert!(
std::mem::align_of::<TopLevelRuleParser<'static, 'static>>() ==
std::mem::align_of::<NestedRuleParser<'static, 'static>>()
);
unsafe { &mut *(self as *mut _ as *mut NestedRuleParser<'a, 'i>) }
}
/// Returns the current state of the parser.
#[inline]
pub fn state(&self) -> State {
self.state
}
/// If we're in a nested state, this returns whether declarations can be parsed. See
/// RuleBodyItemParser::parse_declarations().
#[inline]
pub fn can_parse_declarations(&self) -> bool {
// We also have to check for page rules here because we currently don't
// have a bespoke parser for page rules, and parse them as though they
// are style rules.
self.in_style_or_page_rule()
}
#[inline]
fn in_style_rule(&self) -> bool {
self.context
.nesting_context
.rule_types
.contains(CssRuleType::Style)
}
#[inline]
fn in_page_rule(&self) -> bool {
self.context
.nesting_context
.rule_types
.contains(CssRuleType::Page)
}
#[inline]
fn in_style_or_page_rule(&self) -> bool {
let types = CssRuleTypes::from_bits(CssRuleType::Style.bit() | CssRuleType::Page.bit());
self.context.nesting_context.rule_types.intersects(types)
}
/// Checks whether we can parse a rule that would transition us to
/// `new_state`.
///
/// This is usually a simple branch, but we may need more bookkeeping if
/// doing `insertRule` from CSSOM.
fn check_state(&mut self, new_state: State) -> bool {
if self.state > new_state {
self.dom_error = Some(RulesMutateError::HierarchyRequest);
return false;
}
let ctx = match self.insert_rule_context {
Some(ref ctx) => ctx,
None => return true,
};
let max_rule_state = ctx.max_rule_state_at_index(ctx.index);
if new_state > max_rule_state {
self.dom_error = Some(RulesMutateError::HierarchyRequest);
return false;
}
// If there's anything that isn't a namespace rule (or import rule, but
// we checked that already at the beginning), reject with a
// StateError.
if new_state == State::Namespaces &&
ctx.rule_list[ctx.index..]
.iter()
.any(|r| !matches!(*r, CssRule::Namespace(..)))
{
self.dom_error = Some(RulesMutateError::InvalidState);
return false;
}
true
}
}
/// The current state of the parser.
#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
pub enum State {
/// We haven't started parsing rules.
Start = 1,
/// We're parsing early `@layer` statement rules.
EarlyLayers = 2,
/// We're parsing `@import` and early `@layer` statement rules.
Imports = 3,
/// We're parsing `@namespace` rules.
Namespaces = 4,
/// We're parsing the main body of the stylesheet.
Body = 5,
}
#[derive(Clone, Debug, MallocSizeOf, ToShmem)]
/// Vendor prefix.
pub enum VendorPrefix {
/// -moz prefix.
Moz,
/// -webkit prefix.
WebKit,
}
/// A rule prelude for at-rule with block.
pub enum AtRulePrelude {
/// A @font-face rule prelude.
FontFace,
/// A @font-feature-values rule prelude, with its FamilyName list.
FontFeatureValues(Vec<FamilyName>),
/// A @font-palette-values rule prelude, with its identifier.
FontPaletteValues(DashedIdent),
/// A @counter-style rule prelude, with its counter style name.
CounterStyle(CustomIdent),
/// A @media rule prelude, with its media queries.
Media(Arc<Locked<MediaList>>),
/// A @container rule prelude.
Container(Arc<ContainerCondition>),
/// An @supports rule, with its conditional
Supports(SupportsCondition),
/// A @keyframes rule, with its animation name and vendor prefix if exists.
Keyframes(KeyframesName, Option<VendorPrefix>),
/// A @page rule prelude, with its page name if it exists.
Page(PageSelectors),
/// A @property rule prelude.
Property(PropertyRuleName),
/// A @document rule, with its conditional.
Document(DocumentCondition),
/// A @import rule prelude.
Import(
CssUrl,
Arc<Locked<MediaList>>,
Option<ImportSupportsCondition>,
ImportLayer,
),
/// A @margin rule prelude.
Margin(MarginRuleType),
/// A @namespace rule prelude.
Namespace(Option<Prefix>, Namespace),
/// A @layer rule prelude.
Layer(Vec<LayerName>),
/// A @scope rule prelude.
Scope(ScopeBounds),
/// A @starting-style prelude.
StartingStyle,
/// A @position-try prelude for Anchor Positioning.
PositionTry(DashedIdent),
}
impl AtRulePrelude {
fn name(&self) -> &'static str {
match *self {
Self::FontFace => "font-face",
Self::FontFeatureValues(..) => "font-feature-values",
Self::FontPaletteValues(..) => "font-palette-values",
Self::CounterStyle(..) => "counter-style",
Self::Media(..) => "media",
Self::Container(..) => "container",
Self::Supports(..) => "supports",
Self::Keyframes(..) => "keyframes",
Self::Page(..) => "page",
Self::Property(..) => "property",
Self::Document(..) => "-moz-document",
Self::Import(..) => "import",
Self::Margin(..) => "margin",
Self::Namespace(..) => "namespace",
Self::Layer(..) => "layer",
Self::Scope(..) => "scope",
Self::StartingStyle => "starting-style",
Self::PositionTry(..) => "position-try",
}
}
}
impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a, 'i> {
type Prelude = AtRulePrelude;
type AtRule = SourcePosition;
type Error = StyleParseErrorKind<'i>;
fn parse_prelude<'t>(
&mut self,
name: CowRcStr<'i>,
input: &mut Parser<'i, 't>,
) -> Result<AtRulePrelude, ParseError<'i>> {
match_ignore_ascii_case! { &*name,
"import" => {
if !self.check_state(State::Imports) {
return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedImportRule))
}
if let AllowImportRules::No = self.allow_import_rules {
return Err(input.new_custom_error(StyleParseErrorKind::DisallowedImportRule))
}
// FIXME(emilio): We should always be able to have a loader
if self.loader.is_none() {
error!("Saw @import rule, but no way to trigger the load");
return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedImportRule))
}
let url_string = input.expect_url_or_string()?.as_ref().to_owned();
let url = CssUrl::parse_from_string(url_string, &self.context, CorsMode::None);
let (layer, supports) = ImportRule::parse_layer_and_supports(input, &mut self.context);
let media = MediaList::parse(&self.context, input);
let media = Arc::new(self.shared_lock.wrap(media));
return Ok(AtRulePrelude::Import(url, media, supports, layer));
},
"namespace" => {
if !self.check_state(State::Namespaces) {
return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedNamespaceRule))
}
let prefix = input.try_parse(|i| i.expect_ident_cloned())
.map(|s| Prefix::from(s.as_ref())).ok();
let maybe_namespace = match input.expect_url_or_string() {
Ok(url_or_string) => url_or_string,
Err(BasicParseError { kind: BasicParseErrorKind::UnexpectedToken(t), location }) => {
return Err(location.new_custom_error(StyleParseErrorKind::UnexpectedTokenWithinNamespace(t)))
}
Err(e) => return Err(e.into()),
};
let url = Namespace::from(maybe_namespace.as_ref());
return Ok(AtRulePrelude::Namespace(prefix, url));
},
// @charset is removed by rust-cssparser if it’s the first rule in the stylesheet
// anything left is invalid.
"charset" => {
self.dom_error = Some(RulesMutateError::HierarchyRequest);
return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedCharsetRule))
},
"layer" => {
let state_to_check = if self.state <= State::EarlyLayers {
// The real state depends on whether there's a block or not.
// We don't know that yet, but the parse_block check deals
// with that.
State::EarlyLayers
} else {
State::Body
};
if !self.check_state(state_to_check) {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
},
_ => {
// All other rules have blocks, so we do this check early in
// parse_block instead.
}
}
AtRuleParser::parse_prelude(self.nested(), name, input)
}
#[inline]
fn parse_block<'t>(
&mut self,
prelude: AtRulePrelude,
start: &ParserState,
input: &mut Parser<'i, 't>,
) -> Result<Self::AtRule, ParseError<'i>> {
if !self.check_state(State::Body) {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
AtRuleParser::parse_block(self.nested(), prelude, start, input)?;
self.state = State::Body;
Ok(start.position())
}
#[inline]
fn rule_without_block(
&mut self,
prelude: AtRulePrelude,
start: &ParserState,
) -> Result<Self::AtRule, ()> {
match prelude {
AtRulePrelude::Import(url, media, supports, layer) => {
let loader = self
.loader
.expect("Expected a stylesheet loader for @import");
let import_rule = loader.request_stylesheet(
url,
start.source_location(),
&self.context,
&self.shared_lock,
media,
supports,
layer,
);
self.state = State::Imports;
self.rules.push(CssRule::Import(import_rule))
},
AtRulePrelude::Namespace(prefix, url) => {
let namespaces = self.context.namespaces.to_mut();
let prefix = if let Some(prefix) = prefix {
namespaces.prefixes.insert(prefix.clone(), url.clone());
Some(prefix)
} else {
namespaces.default = Some(url.clone());
None
};
self.state = State::Namespaces;
self.rules.push(CssRule::Namespace(Arc::new(NamespaceRule {
prefix,
url,
source_location: start.source_location(),
})));
},
AtRulePrelude::Layer(..) => {
AtRuleParser::rule_without_block(self.nested(), prelude, start)?;
if self.state <= State::EarlyLayers {
self.state = State::EarlyLayers;
} else {
self.state = State::Body;
}
},
_ => AtRuleParser::rule_without_block(self.nested(), prelude, start)?,
};
Ok(start.position())
}
}
impl<'a, 'i> QualifiedRuleParser<'i> for TopLevelRuleParser<'a, 'i> {
type Prelude = SelectorList<SelectorImpl>;
type QualifiedRule = SourcePosition;
type Error = StyleParseErrorKind<'i>;
#[inline]
fn parse_prelude<'t>(
&mut self,
input: &mut Parser<'i, 't>,
) -> Result<Self::Prelude, ParseError<'i>> {
if !self.check_state(State::Body) {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
QualifiedRuleParser::parse_prelude(self.nested(), input)
}
#[inline]
fn parse_block<'t>(
&mut self,
prelude: Self::Prelude,
start: &ParserState,
input: &mut Parser<'i, 't>,
) -> Result<Self::QualifiedRule, ParseError<'i>> {
QualifiedRuleParser::parse_block(self.nested(), prelude, start, input)?;
self.state = State::Body;
Ok(start.position())
}
}
#[repr(transparent)]
#[derive(Deref, DerefMut)]
struct NestedRuleParser<'a, 'i>(TopLevelRuleParser<'a, 'i>);
struct NestedParseResult {
first_declaration_block: PropertyDeclarationBlock,
rules: Vec<CssRule>,
}
impl<'a, 'i> NestedRuleParser<'a, 'i> {
#[inline]
fn parse_relative(&self) -> ParseRelative {
self.context.nesting_context.parse_relative
}
// 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 at_rule_allowed(&self, prelude: &AtRulePrelude) -> bool {
match prelude {
AtRulePrelude::Media(..) |
AtRulePrelude::Supports(..) |
AtRulePrelude::Container(..) |
AtRulePrelude::Document(..) |
AtRulePrelude::Layer(..) |
AtRulePrelude::Scope(..) |
AtRulePrelude::StartingStyle => true,
AtRulePrelude::Namespace(..) |
AtRulePrelude::FontFace |
AtRulePrelude::FontFeatureValues(..) |
AtRulePrelude::FontPaletteValues(..) |
AtRulePrelude::CounterStyle(..) |
AtRulePrelude::Keyframes(..) |
AtRulePrelude::Page(..) |
AtRulePrelude::Property(..) |
AtRulePrelude::Import(..) |
AtRulePrelude::PositionTry(..) => !self.in_style_or_page_rule(),
AtRulePrelude::Margin(..) => self.in_page_rule(),
}
}
fn nest_for_rule<R>(&mut self, rule_type: CssRuleType, cb: impl FnOnce(&mut Self) -> R) -> R {
let old = self.context.nesting_context.save(rule_type);
let r = cb(self);
self.context.nesting_context.restore(old);
r
}
fn parse_nested_rules(
&mut self,
input: &mut Parser<'i, '_>,
rule_type: CssRuleType,
source_location: SourceLocation,
) -> Arc<Locked<CssRules>> {
let rules = self.parse_nested(input, rule_type, /* wants_first_declaration_block = */ false, source_location).rules;
CssRules::new(rules, &self.shared_lock)
}
fn parse_nested(
&mut self,
input: &mut Parser<'i, '_>,
rule_type: CssRuleType,
wants_first_declaration_block: bool,
source_location: SourceLocation,
) -> NestedParseResult {
debug_assert!(!self.wants_first_declaration_block, "Should've flushed previous declarations");
self.nest_for_rule(rule_type, |parser| {
parser.wants_first_declaration_block = wants_first_declaration_block;
let parse_declarations = parser.parse_declarations();
let mut rules = std::mem::take(&mut parser.rules);
let mut first_declaration_block = std::mem::take(&mut parser.first_declaration_block);
let mut iter = RuleBodyParser::new(input, parser);
while let Some(result) = iter.next() {
match result {
Ok(()) => {},
Err((error, slice)) => {
if parse_declarations {
let top = &mut **iter.parser;
top.declaration_parser_state
.did_error(&top.context, error, slice);
} else {
let location = error.location;
let error = ContextualParseError::InvalidRule(slice, error);
iter.parser.context.log_css_error(location, error);
}
},
}
}
parser.flush_declarations(source_location);
debug_assert!(
!parser.wants_first_declaration_block,
"Flushing declarations should take care of this."
);
debug_assert!(
!parser.declaration_parser_state.has_parsed_declarations(),
"Parsed but didn't consume declarations"
);
std::mem::swap(&mut parser.rules, &mut rules);
std::mem::swap(&mut parser.first_declaration_block, &mut first_declaration_block);
NestedParseResult {
first_declaration_block,
rules,
}
})
}
#[inline(never)]
fn handle_error_reporting_selectors_pre(
&mut self,
start: &ParserState,
selectors: &SelectorList<SelectorImpl>,
) {
use cssparser::ToCss;
debug_assert!(self.context.error_reporting_enabled());
self.error_reporting_state.push(selectors.clone());
'selector_loop: for selector in selectors.slice().iter() {
let mut current = selector.iter();
loop {
let mut found_host = false;
let mut found_non_host = false;
for component in &mut current {
if component.is_host() {
found_host = true;
} else {
found_non_host = true;
}
if found_host && found_non_host {
self.context.log_css_error(
start.source_location(),
ContextualParseError::NeverMatchingHostSelector(
selector.to_css_string(),
),
);
continue 'selector_loop;
}
}
if current.next_sequence().is_none() {
break;
}
}
}
}
fn handle_error_reporting_selectors_post(&mut self) {
self.error_reporting_state.pop();
}
#[inline]
fn flush_declarations(&mut self, source_location: SourceLocation) {
let parser = &mut **self;
let wants_first_declaration_block = parser.wants_first_declaration_block;
parser.wants_first_declaration_block = false;
parser.declaration_parser_state.report_errors_if_needed(&parser.context, &parser.error_reporting_state);
if !parser.declaration_parser_state.has_parsed_declarations() {
return;
}
let declarations = parser.declaration_parser_state.take_declarations();
if wants_first_declaration_block {
debug_assert!(parser.first_declaration_block.is_empty(), "How?");
parser.first_declaration_block = declarations;
} else {
let nested_rule = CssRule::NestedDeclarations(Arc::new(parser.shared_lock.wrap(NestedDeclarationsRule {
block: Arc::new(parser.shared_lock.wrap(declarations)),
source_location,
})));
parser.rules.push(nested_rule);
}
}
}
impl<'a, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'i> {
type Prelude = AtRulePrelude;
type AtRule = ();
type Error = StyleParseErrorKind<'i>;
fn parse_prelude<'t>(
&mut self,
name: CowRcStr<'i>,
input: &mut Parser<'i, 't>,
) -> Result<Self::Prelude, ParseError<'i>> {
Ok(match_ignore_ascii_case! { &*name,
"media" => {
let media_queries = MediaList::parse(&self.context, input);
let arc = Arc::new(self.shared_lock.wrap(media_queries));
AtRulePrelude::Media(arc)
},
"supports" => {
let cond = SupportsCondition::parse(input)?;
AtRulePrelude::Supports(cond)
},
"font-face" => {
AtRulePrelude::FontFace
},
"container" if cfg!(feature = "gecko") => {
let condition = Arc::new(ContainerCondition::parse(&self.context, input)?);
AtRulePrelude::Container(condition)
},
"layer" => {
let names = input.try_parse(|input| {
input.parse_comma_separated(|input| {
LayerName::parse(&self.context, input)
})
}).unwrap_or_default();
AtRulePrelude::Layer(names)
},
"font-feature-values" if cfg!(feature = "gecko") => {
let family_names = parse_family_name_list(&self.context, input)?;
AtRulePrelude::FontFeatureValues(family_names)
},
"font-palette-values" if static_prefs::pref!("layout.css.font-palette.enabled") => {
let name = DashedIdent::parse(&self.context, input)?;
AtRulePrelude::FontPaletteValues(name)
},
"counter-style" if cfg!(feature = "gecko") => {
let name = parse_counter_style_name_definition(input)?;
AtRulePrelude::CounterStyle(name)
},
"keyframes" | "-webkit-keyframes" | "-moz-keyframes" => {
let prefix = if starts_with_ignore_ascii_case(&*name, "-webkit-") {
Some(VendorPrefix::WebKit)
} else if starts_with_ignore_ascii_case(&*name, "-moz-") {
Some(VendorPrefix::Moz)
} else {
None
};
if cfg!(feature = "servo") &&
prefix.as_ref().map_or(false, |p| matches!(*p, VendorPrefix::Moz)) {
// Servo should not support @-moz-keyframes.
return Err(input.new_error(BasicParseErrorKind::AtRuleInvalid(name.clone())))
}
let name = KeyframesName::parse(&self.context, input)?;
AtRulePrelude::Keyframes(name, prefix)
},
"page" if cfg!(feature = "gecko") => {
AtRulePrelude::Page(
input.try_parse(|i| PageSelectors::parse(&self.context, i)).unwrap_or_default()
)
},
"property" if static_prefs::pref!("layout.css.properties-and-values.enabled") => {
let name = input.expect_ident_cloned()?;
let name = parse_custom_property_name(&name).map_err(|_| {
input.new_custom_error(StyleParseErrorKind::UnexpectedIdent(name.clone()))
})?;
AtRulePrelude::Property(PropertyRuleName(Atom::from(name)))
},
"-moz-document" if cfg!(feature = "gecko") => {
let cond = DocumentCondition::parse(&self.context, input)?;
AtRulePrelude::Document(cond)
},
"scope" if static_prefs::pref!("layout.css.at-scope.enabled") => {
let bounds = ScopeBounds::parse(&self.context, input, self.parse_relative())?;
AtRulePrelude::Scope(bounds)
},
"starting-style" if static_prefs::pref!("layout.css.starting-style-at-rules.enabled") => {
AtRulePrelude::StartingStyle
},
"position-try" if static_prefs::pref!("layout.css.anchor-positioning.enabled") => {
let name = DashedIdent::parse(&self.context, input)?;
AtRulePrelude::PositionTry(name)
},
_ => {
if static_prefs::pref!("layout.css.margin-rules.enabled") {
if let Some(margin_rule_type) = MarginRuleType::match_name(&name) {
return Ok(AtRulePrelude::Margin(margin_rule_type));
}
}
return Err(input.new_error(BasicParseErrorKind::AtRuleInvalid(name.clone())))
},
})
}
fn parse_block<'t>(
&mut self,
prelude: AtRulePrelude,
start: &ParserState,
input: &mut Parser<'i, 't>,
) -> Result<(), ParseError<'i>> {
if !self.at_rule_allowed(&prelude) {
self.dom_error = Some(RulesMutateError::HierarchyRequest);
return Err(input.new_error(BasicParseErrorKind::AtRuleInvalid(prelude.name().into())));
}
let source_location = start.source_location();
self.flush_declarations(source_location);
let rule = match prelude {
AtRulePrelude::FontFace => self.nest_for_rule(CssRuleType::FontFace, |p| {
CssRule::FontFace(Arc::new(p.shared_lock.wrap(
parse_font_face_block(&p.context, input, source_location).into(),
)))
}),
AtRulePrelude::FontFeatureValues(family_names) => {
self.nest_for_rule(CssRuleType::FontFeatureValues, |p| {
CssRule::FontFeatureValues(Arc::new(FontFeatureValuesRule::parse(
&p.context,
input,
family_names,
source_location,
)))
})
},
AtRulePrelude::FontPaletteValues(name) => {
self.nest_for_rule(CssRuleType::FontPaletteValues, |p| {
CssRule::FontPaletteValues(Arc::new(FontPaletteValuesRule::parse(
&p.context,
input,
name,
source_location,
)))
})
},
AtRulePrelude::CounterStyle(name) => {
let body = self.nest_for_rule(CssRuleType::CounterStyle, |p| {
parse_counter_style_body(name, &p.context, input, source_location)
})?;
CssRule::CounterStyle(Arc::new(self.shared_lock.wrap(body)))
},
AtRulePrelude::Media(media_queries) => {
CssRule::Media(Arc::new(MediaRule {
media_queries,
rules: self.parse_nested_rules(input, CssRuleType::Media, source_location),
source_location,
}))
},
AtRulePrelude::Supports(condition) => {
let enabled =
self.nest_for_rule(CssRuleType::Style, |p| condition.eval(&p.context));
CssRule::Supports(Arc::new(SupportsRule {
condition,
rules: self.parse_nested_rules(input, CssRuleType::Supports, source_location),
enabled,
source_location,
}))
},
AtRulePrelude::Keyframes(name, vendor_prefix) => {
self.nest_for_rule(CssRuleType::Keyframe, |p| {
let top = &mut **p;
CssRule::Keyframes(Arc::new(top.shared_lock.wrap(KeyframesRule {
name,
keyframes: parse_keyframe_list(&mut top.context, input, top.shared_lock),
vendor_prefix,
source_location,
})))
})
},
AtRulePrelude::Page(selectors) => {
let page_rule = if !static_prefs::pref!("layout.css.margin-rules.enabled") {
let declarations = self.nest_for_rule(CssRuleType::Page, |p| {
parse_property_declaration_list(&p.context, input, &[])
});
PageRule {
selectors,
rules: CssRules::new(vec![], self.shared_lock),
block: Arc::new(self.shared_lock.wrap(declarations)),
source_location,
}
} else {
let result = self.parse_nested(input, CssRuleType::Page, true, source_location);
PageRule {
selectors,
rules: CssRules::new(result.rules, self.shared_lock),
block: Arc::new(self.shared_lock.wrap(result.first_declaration_block)),
source_location,
}
};
CssRule::Page(Arc::new(self.shared_lock.wrap(page_rule)))
},
AtRulePrelude::Property(name) => self.nest_for_rule(CssRuleType::Property, |p| {
let rule_data =
parse_property_block(&p.context, input, name, source_location)?;
Ok::<CssRule, ParseError<'i>>(CssRule::Property(Arc::new(rule_data)))
})?,
AtRulePrelude::Document(condition) => {
if !cfg!(feature = "gecko") {
unreachable!()
}
CssRule::Document(Arc::new(DocumentRule {
condition,
rules: self.parse_nested_rules(input, CssRuleType::Document, source_location),
source_location,
}))
},
AtRulePrelude::Container(condition) => {
let source_location = start.source_location();
CssRule::Container(Arc::new(ContainerRule {
condition,
rules: self.parse_nested_rules(input, CssRuleType::Container, source_location),
source_location,
}))
},
AtRulePrelude::Layer(names) => {
let name = match names.len() {
0 | 1 => names.into_iter().next(),
_ => return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid)),
};
CssRule::LayerBlock(Arc::new(LayerBlockRule {
name,
rules: self.parse_nested_rules(input, CssRuleType::LayerBlock, source_location),
source_location,
}))
},
AtRulePrelude::Margin(rule_type) => {
let declarations = self.nest_for_rule(CssRuleType::Margin, |p| {
parse_property_declaration_list(&p.context, input, &[])
});
CssRule::Margin(Arc::new(MarginRule {
rule_type,
block: Arc::new(self.shared_lock.wrap(declarations)),
source_location,
}))
},
AtRulePrelude::Import(..) | AtRulePrelude::Namespace(..) => {
// These rules don't have blocks.
return Err(input.new_unexpected_token_error(cssparser::Token::CurlyBracketBlock));
},
AtRulePrelude::Scope(bounds) => {
CssRule::Scope(Arc::new(ScopeRule {
bounds,
rules: self.parse_nested_rules(input, CssRuleType::Scope, source_location),
source_location,
}))
},
AtRulePrelude::StartingStyle => {
CssRule::StartingStyle(Arc::new(StartingStyleRule {
rules: self.parse_nested_rules(input, CssRuleType::StartingStyle, source_location),
source_location,
}))
},
AtRulePrelude::PositionTry(name) => {
let declarations = self.nest_for_rule(CssRuleType::PositionTry, |p| {
parse_property_declaration_list(&p.context, input, &[])
});
CssRule::PositionTry(Arc::new(self.shared_lock.wrap(PositionTryRule {
name,
block: Arc::new(self.shared_lock.wrap(declarations)),
source_location,
})))
},
};
self.rules.push(rule);
Ok(())
}
#[inline]
fn rule_without_block(
&mut self,
prelude: AtRulePrelude,
start: &ParserState,
) -> Result<(), ()> {
if self.in_style_rule() {
return Err(());
}
let source_location = start.source_location();
let rule = match prelude {
AtRulePrelude::Layer(names) => {
if names.is_empty() {
return Err(());
}
CssRule::LayerStatement(Arc::new(LayerStatementRule {
names,
source_location,
}))
},
_ => return Err(()),
};
self.flush_declarations(source_location);
self.rules.push(rule);
Ok(())
}
}
impl<'a, 'i> QualifiedRuleParser<'i> for NestedRuleParser<'a, 'i> {
type Prelude = SelectorList<SelectorImpl>;
type QualifiedRule = ();
type Error = StyleParseErrorKind<'i>;
fn parse_prelude<'t>(
&mut self,
input: &mut Parser<'i, 't>,
) -> Result<Self::Prelude, ParseError<'i>> {
let selector_parser = SelectorParser {
stylesheet_origin: self.context.stylesheet_origin,
namespaces: &self.context.namespaces,
url_data: self.context.url_data,
for_supports_rule: false,
};
SelectorList::parse(&selector_parser, input, self.parse_relative())
}
fn parse_block<'t>(
&mut self,
selectors: Self::Prelude,
start: &ParserState,
input: &mut Parser<'i, 't>,
) -> Result<(), ParseError<'i>> {
let source_location = start.source_location();
let reporting_errors = self.context.error_reporting_enabled();
if reporting_errors {
self.handle_error_reporting_selectors_pre(start, &selectors);
}
self.flush_declarations(source_location);
let result = self.parse_nested(input, CssRuleType::Style, true, source_location);
if reporting_errors {
self.handle_error_reporting_selectors_post();
}
let block = Arc::new(self.shared_lock.wrap(result.first_declaration_block));
let top = &mut **self;
top.rules
.push(CssRule::Style(Arc::new(top.shared_lock.wrap(StyleRule {
selectors,
block,
rules: if result.rules.is_empty() {
None
} else {
Some(CssRules::new(result.rules, top.shared_lock))
},
source_location,
}))));
Ok(())
}
}
impl<'a, 'i> DeclarationParser<'i> for NestedRuleParser<'a, 'i> {
type Declaration = ();
type Error = StyleParseErrorKind<'i>;
fn parse_value<'t>(
&mut self,
name: CowRcStr<'i>,
input: &mut Parser<'i, 't>,
) -> Result<(), ParseError<'i>> {
let top = &mut **self;
top.declaration_parser_state
.parse_value(&top.context, name, input)
}
}
impl<'a, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>> for NestedRuleParser<'a, 'i> {
fn parse_qualified(&self) -> bool {
true
}
/// If nesting is disabled, we can't get there for a non-style-rule. If it's enabled, we parse
/// raw declarations there.
fn parse_declarations(&self) -> bool {
self.can_parse_declarations()
}
}