Bug 1325878: Rewrite insert_rule to avoid lock re-entrancy problems. r=xidorn

In particular, you can insertRule("@import url(\"already-loaded.css\");") and we
would try to clone the stylesheet, which without this change would borrow the
lock for reading.

MozReview-Commit-ID: 4jdNL5rngh7
Signed-off-by: Emilio Cobos Álvarez <emilio@crisal.io>
This commit is contained in:
Emilio Cobos Álvarez 2017-04-11 13:36:20 +08:00
parent 27ca9e0a46
commit 482740bb11
No known key found for this signature in database
GPG key ID: 056B727BB9C1027C
2 changed files with 96 additions and 57 deletions

View file

@ -135,53 +135,6 @@ impl CssRules {
})
}
/// https://drafts.csswg.org/cssom/#insert-a-css-rule
pub fn insert_rule(&mut self,
rule: &str,
parent_stylesheet: &Stylesheet,
index: usize,
nested: bool,
loader: Option<&StylesheetLoader>)
-> Result<CssRule, RulesMutateError> {
// Step 1, 2
if index > self.0.len() {
return Err(RulesMutateError::IndexSize);
}
// Computes the parser state at the given index
let state = if nested {
None
} else if index == 0 {
Some(State::Start)
} else {
self.0.get(index - 1).map(CssRule::rule_state)
};
// Step 3, 4
// XXXManishearth should we also store the namespace map?
let (new_rule, new_state) =
try!(CssRule::parse(&rule, parent_stylesheet, state, loader));
// Step 5
// Computes the maximum allowed parser state at a given index.
let rev_state = self.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 !self.only_ns_or_import() {
return Err(RulesMutateError::InvalidState);
}
}
self.0.insert(index, new_rule.clone());
Ok(new_rule)
}
/// https://drafts.csswg.org/cssom/#remove-a-css-rule
pub fn remove_rule(&mut self, index: usize) -> Result<(), RulesMutateError> {
// Step 1, 2
@ -207,6 +160,86 @@ impl CssRules {
}
}
/// A trait to implement helpers for `Arc<Locked<CssRules>>`.
pub trait CssRulesHelpers {
/// https://drafts.csswg.org/cssom/#insert-a-css-rule
///
/// Written in this funky way because parsing an @import rule may cause us
/// to clone a stylesheet from the same document due to caching in the CSS
/// loader.
///
/// TODO(emilio): We could also pass the write guard down into the loader
/// instead, but that seems overkill.
fn insert_rule(&self,
lock: &SharedRwLock,
rule: &str,
parent_stylesheet: &Stylesheet,
index: usize,
nested: bool,
loader: Option<&StylesheetLoader>)
-> Result<CssRule, RulesMutateError>;
}
impl CssRulesHelpers for Arc<Locked<CssRules>> {
fn insert_rule(&self,
lock: &SharedRwLock,
rule: &str,
parent_stylesheet: &Stylesheet,
index: usize,
nested: bool,
loader: Option<&StylesheetLoader>)
-> Result<CssRule, RulesMutateError> {
let state = {
let read_guard = lock.read();
let rules = self.read_with(&read_guard);
// Step 1, 2
if index > rules.0.len() {
return Err(RulesMutateError::IndexSize);
}
// Computes the parser state at the given index
if nested {
None
} else if index == 0 {
Some(State::Start)
} else {
rules.0.get(index - 1).map(CssRule::rule_state)
}
};
// Step 3, 4
// XXXManishearth should we also store the namespace map?
let (new_rule, new_state) =
try!(CssRule::parse(&rule, parent_stylesheet, state, loader));
{
let mut write_guard = lock.write();
let mut 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());
}
Ok(new_rule)
}
}
/// The structure servo uses to represent a stylesheet.
#[derive(Debug)]
pub struct Stylesheet {