style: Don't let @namespace rules that aren't going to be inserted affect the namespace map.

Bug: 1464865
Reviewed-by: xidorn
MozReview-Commit-ID: 9bjlEBExqsr
This commit is contained in:
Emilio Cobos Álvarez 2018-05-28 23:56:20 +02:00
parent e77a28d543
commit 142c9eca4b
No known key found for this signature in database
GPG key ID: 056B727BB9C1027C
4 changed files with 94 additions and 75 deletions

View file

@ -46,7 +46,7 @@ pub use self::media_rule::MediaRule;
pub use self::namespace_rule::NamespaceRule; pub use self::namespace_rule::NamespaceRule;
pub use self::origin::{Origin, OriginSet, OriginSetIterator, PerOrigin, PerOriginIter}; pub use self::origin::{Origin, OriginSet, OriginSetIterator, PerOrigin, PerOriginIter};
pub use self::page_rule::PageRule; pub use self::page_rule::PageRule;
pub use self::rule_parser::{State, TopLevelRuleParser}; pub use self::rule_parser::{State, TopLevelRuleParser, InsertRuleContext};
pub use self::rule_list::{CssRules, CssRulesHelpers}; pub use self::rule_list::{CssRules, CssRulesHelpers};
pub use self::rules_iterator::{AllRules, EffectiveRules}; pub use self::rules_iterator::{AllRules, EffectiveRules};
pub use self::rules_iterator::{NestedRuleIterationCondition, RulesIterator}; pub use self::rules_iterator::{NestedRuleIterationCondition, RulesIterator};
@ -178,12 +178,6 @@ pub enum CssRuleType {
Viewport = 15, Viewport = 15,
} }
#[allow(missing_docs)]
pub enum SingleRuleParseError {
Syntax,
Hierarchy,
}
#[allow(missing_docs)] #[allow(missing_docs)]
pub enum RulesMutateError { pub enum RulesMutateError {
Syntax, Syntax,
@ -192,15 +186,6 @@ pub enum RulesMutateError {
InvalidState, InvalidState,
} }
impl From<SingleRuleParseError> for RulesMutateError {
fn from(other: SingleRuleParseError) -> Self {
match other {
SingleRuleParseError::Syntax => RulesMutateError::Syntax,
SingleRuleParseError::Hierarchy => RulesMutateError::HierarchyRequest,
}
}
}
impl CssRule { impl CssRule {
/// Returns the CSSOM rule type of this rule. /// Returns the CSSOM rule type of this rule.
pub fn rule_type(&self) -> CssRuleType { pub fn rule_type(&self) -> CssRuleType {
@ -236,11 +221,12 @@ impl CssRule {
/// Input state is None for a nested rule /// Input state is None for a nested rule
pub fn parse( pub fn parse(
css: &str, css: &str,
insert_rule_context: InsertRuleContext,
parent_stylesheet_contents: &StylesheetContents, parent_stylesheet_contents: &StylesheetContents,
shared_lock: &SharedRwLock, shared_lock: &SharedRwLock,
state: Option<State>, state: State,
loader: Option<&StylesheetLoader>, loader: Option<&StylesheetLoader>,
) -> Result<(Self, State), SingleRuleParseError> { ) -> Result<Self, RulesMutateError> {
let url_data = parent_stylesheet_contents.url_data.read(); let url_data = parent_stylesheet_contents.url_data.read();
let error_reporter = NullReporter; let error_reporter = NullReporter;
let context = ParserContext::new( let context = ParserContext::new(
@ -257,28 +243,23 @@ impl CssRule {
let mut guard = parent_stylesheet_contents.namespaces.write(); let mut guard = parent_stylesheet_contents.namespaces.write();
// nested rules are in the body state // nested rules are in the body state
let state = state.unwrap_or(State::Body);
let mut rule_parser = TopLevelRuleParser { let mut rule_parser = TopLevelRuleParser {
stylesheet_origin: parent_stylesheet_contents.origin, stylesheet_origin: parent_stylesheet_contents.origin,
context: context, context,
error_context: ParserErrorContext { error_context: ParserErrorContext {
error_reporter: &error_reporter, error_reporter: &error_reporter,
}, },
shared_lock: &shared_lock, shared_lock: &shared_lock,
loader: loader, loader,
state: state, state,
had_hierarchy_error: false, dom_error: None,
namespaces: &mut *guard, namespaces: &mut *guard,
insert_rule_context: Some(insert_rule_context),
}; };
parse_one_rule(&mut input, &mut rule_parser) parse_one_rule(&mut input, &mut rule_parser)
.map(|result| (result, rule_parser.state))
.map_err(|_| { .map_err(|_| {
if rule_parser.take_had_hierarchy_error() { rule_parser.dom_error.unwrap_or(RulesMutateError::Syntax)
SingleRuleParseError::Hierarchy
} else {
SingleRuleParseError::Syntax
}
}) })
} }
} }

View file

@ -13,7 +13,7 @@ use std::fmt::{self, Write};
use str::CssStringWriter; use str::CssStringWriter;
use stylesheets::{CssRule, RulesMutateError}; use stylesheets::{CssRule, RulesMutateError};
use stylesheets::loader::StylesheetLoader; use stylesheets::loader::StylesheetLoader;
use stylesheets::rule_parser::State; use stylesheets::rule_parser::{InsertRuleContext, State};
use stylesheets::stylesheet::StylesheetContents; use stylesheets::stylesheet::StylesheetContents;
/// A list of CSS rules. /// A list of CSS rules.
@ -141,7 +141,7 @@ impl CssRulesHelpers for RawOffsetArc<Locked<CssRules>> {
nested: bool, nested: bool,
loader: Option<&StylesheetLoader>, loader: Option<&StylesheetLoader>,
) -> Result<CssRule, RulesMutateError> { ) -> Result<CssRule, RulesMutateError> {
let state = { let new_rule = {
let read_guard = lock.read(); let read_guard = lock.read();
let rules = self.read_with(&read_guard); let rules = self.read_with(&read_guard);
@ -151,39 +151,33 @@ impl CssRulesHelpers for RawOffsetArc<Locked<CssRules>> {
} }
// Computes the parser state at the given index // Computes the parser state at the given index
if nested { let state = if nested {
None State::Body
} else if index == 0 { } else if index == 0 {
Some(State::Start) State::Start
} else { } else {
rules.0.get(index - 1).map(CssRule::rule_state) rules.0.get(index - 1).map(CssRule::rule_state).unwrap_or(State::Body)
} };
};
// Step 3, 4 let insert_rule_context = InsertRuleContext {
// XXXManishearth should we also store the namespace map? rule_list: &rules.0,
let (new_rule, new_state) = index,
CssRule::parse(&rule, parent_stylesheet_contents, lock, state, loader)?; };
// Steps 3, 4, 5, 6
CssRule::parse(
&rule,
insert_rule_context,
parent_stylesheet_contents,
lock,
state,
loader,
)?
};
{ {
let mut write_guard = lock.write(); let mut write_guard = lock.write();
let rules = self.write_with(&mut write_guard); let rules = self.write_with(&mut write_guard);
// Step 5
// Computes the maximum allowed parser state at a given index.
let rev_state = rules.0.get(index).map_or(State::Body, CssRule::rule_state);
if new_state > rev_state {
// We inserted a rule too early, e.g. inserting
// a regular style rule before @namespace rules
return Err(RulesMutateError::HierarchyRequest);
}
// Step 6
if let CssRule::Namespace(..) = new_rule {
if !rules.only_ns_or_import() {
return Err(RulesMutateError::InvalidState);
}
}
rules.0.insert(index, new_rule.clone()); rules.0.insert(index, new_rule.clone());
} }

View file

@ -19,7 +19,7 @@ use servo_arc::Arc;
use shared_lock::{Locked, SharedRwLock}; use shared_lock::{Locked, SharedRwLock};
use str::starts_with_ignore_ascii_case; use str::starts_with_ignore_ascii_case;
use style_traits::{ParseError, StyleParseErrorKind}; use style_traits::{ParseError, StyleParseErrorKind};
use stylesheets::{CssRule, CssRuleType, CssRules, Origin, StylesheetLoader}; use stylesheets::{CssRule, CssRuleType, CssRules, Origin, RulesMutateError, StylesheetLoader};
use stylesheets::{DocumentRule, FontFeatureValuesRule, KeyframesRule, MediaRule}; use stylesheets::{DocumentRule, FontFeatureValuesRule, KeyframesRule, MediaRule};
use stylesheets::{NamespaceRule, PageRule, StyleRule, SupportsRule, ViewportRule}; use stylesheets::{NamespaceRule, PageRule, StyleRule, SupportsRule, ViewportRule};
use stylesheets::document_rule::DocumentCondition; use stylesheets::document_rule::DocumentCondition;
@ -31,6 +31,14 @@ use stylesheets::viewport_rule;
use values::{CssUrl, CustomIdent, KeyframesName}; use values::{CssUrl, CustomIdent, KeyframesName};
use values::computed::font::FamilyName; use values::computed::font::FamilyName;
/// 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 parser for the top-level rules in a stylesheet. /// The parser for the top-level rules in a stylesheet.
pub struct TopLevelRuleParser<'a, R: 'a> { pub struct TopLevelRuleParser<'a, R: 'a> {
/// The origin of the stylesheet we're parsing. /// The origin of the stylesheet we're parsing.
@ -51,11 +59,13 @@ pub struct TopLevelRuleParser<'a, R: 'a> {
/// Whether we have tried to parse was invalid due to being in the wrong /// 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 /// place (e.g. an @import rule was found while in the `Body` state). Reset
/// to `false` when `take_had_hierarchy_error` is called. /// to `false` when `take_had_hierarchy_error` is called.
pub had_hierarchy_error: bool, pub dom_error: Option<RulesMutateError>,
/// The namespace map we use for parsing. Needs to start as `Some()`, and /// The namespace map we use for parsing. Needs to start as `Some()`, and
/// will be taken out after parsing namespace rules, and that reference will /// will be taken out after parsing namespace rules, and that reference will
/// be moved to `ParserContext`. /// be moved to `ParserContext`.
pub namespaces: &'a mut Namespaces, pub namespaces: &'a mut Namespaces,
/// The info we need insert a rule in a list.
pub insert_rule_context: Option<InsertRuleContext<'a>>,
} }
impl<'b, R> TopLevelRuleParser<'b, R> { impl<'b, R> TopLevelRuleParser<'b, R> {
@ -74,14 +84,43 @@ impl<'b, R> TopLevelRuleParser<'b, R> {
self.state self.state
} }
/// Returns whether we previously tried to parse a rule that was invalid /// Checks whether we can parse a rule that would transition us to
/// due to being in the wrong place (e.g. an @import rule was found after /// `new_state`.
/// a regular style rule). The state of this flag is reset when this ///
/// function is called. /// This is usually a simple branch, but we may need more bookkeeping if
pub fn take_had_hierarchy_error(&mut self) -> bool { /// doing `insertRule` from CSSOM.
let had_hierarchy_error = self.had_hierarchy_error; fn check_state(&mut self, new_state: State) -> bool {
self.had_hierarchy_error = false; if self.state > new_state {
had_hierarchy_error self.dom_error = Some(RulesMutateError::HierarchyRequest);
return false;
}
let ctx = match self.insert_rule_context {
Some(ref ctx) => ctx,
None => return true,
};
let next_rule_state = match ctx.rule_list.get(ctx.index) {
None => return true,
Some(rule) => rule.rule_state(),
};
if new_state > next_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
} }
} }
@ -151,9 +190,7 @@ impl<'a, 'i, R: ParseErrorReporter> AtRuleParser<'i> for TopLevelRuleParser<'a,
let location = input.current_source_location(); let location = input.current_source_location();
match_ignore_ascii_case! { &*name, match_ignore_ascii_case! { &*name,
"import" => { "import" => {
if self.state > State::Imports { if !self.check_state(State::Imports) {
// "@import must be before any rule but @charset"
self.had_hierarchy_error = true;
return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedImportRule)) return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedImportRule))
} }
@ -168,9 +205,7 @@ impl<'a, 'i, R: ParseErrorReporter> AtRuleParser<'i> for TopLevelRuleParser<'a,
return Ok(AtRuleType::WithoutBlock(prelude)); return Ok(AtRuleType::WithoutBlock(prelude));
}, },
"namespace" => { "namespace" => {
if self.state > State::Namespaces { if !self.check_state(State::Namespaces) {
// "@namespace must be before any rule but @charset and @import"
self.had_hierarchy_error = true;
return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedNamespaceRule)) return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedNamespaceRule))
} }
@ -190,12 +225,16 @@ impl<'a, 'i, R: ParseErrorReporter> AtRuleParser<'i> for TopLevelRuleParser<'a,
// @charset is removed by rust-cssparser if its the first rule in the stylesheet // @charset is removed by rust-cssparser if its the first rule in the stylesheet
// anything left is invalid. // anything left is invalid.
"charset" => { "charset" => {
self.had_hierarchy_error = true; self.dom_error = Some(RulesMutateError::HierarchyRequest);
return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedCharsetRule)) return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedCharsetRule))
} }
_ => {} _ => {}
} }
if !self.check_state(State::Body) {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
AtRuleParser::parse_prelude(&mut self.nested(), name, input) AtRuleParser::parse_prelude(&mut self.nested(), name, input)
} }
@ -264,6 +303,10 @@ impl<'a, 'i, R: ParseErrorReporter> QualifiedRuleParser<'i> for TopLevelRulePars
&mut self, &mut self,
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
) -> Result<QualifiedRuleParserPrelude, ParseError<'i>> { ) -> Result<QualifiedRuleParserPrelude, ParseError<'i>> {
if !self.check_state(State::Body) {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
QualifiedRuleParser::parse_prelude(&mut self.nested(), input) QualifiedRuleParser::parse_prelude(&mut self.nested(), input)
} }

View file

@ -368,7 +368,8 @@ impl Stylesheet {
context, context,
error_context, error_context,
state: State::Start, state: State::Start,
had_hierarchy_error: false, dom_error: None,
insert_rule_context: None,
namespaces, namespaces,
}; };