Selector mathing: Move the pseudo-elements buckets inside Stylist.

Refactor to have only one Stylist per document.
This commit is contained in:
Simon Sapin 2014-01-08 14:54:16 +00:00
parent 1d6f94fd96
commit a16087da5f
4 changed files with 112 additions and 112 deletions

View file

@ -22,7 +22,7 @@ use style::{Before, After};
pub trait MatchMethods { pub trait MatchMethods {
fn match_node(&self, stylist: &Stylist); fn match_node(&self, stylist: &Stylist);
fn match_subtree(&self, stylist: ~[RWArc<Stylist>]); fn match_subtree(&self, stylist: RWArc<Stylist>);
fn cascade_before_node(&self, parent: Option<LayoutNode>); fn cascade_before_node(&self, parent: Option<LayoutNode>);
fn cascade_node(&self, parent: Option<LayoutNode>); fn cascade_node(&self, parent: Option<LayoutNode>);
@ -32,26 +32,26 @@ pub trait MatchMethods {
impl<'self> MatchMethods for LayoutNode<'self> { impl<'self> MatchMethods for LayoutNode<'self> {
fn match_node(&self, stylist: &Stylist) { fn match_node(&self, stylist: &Stylist) {
let applicable_declarations = do self.with_element |element| { let style_attribute = do self.with_element |element| {
let style_attribute = match *element.style_attribute() { match *element.style_attribute() {
None => None, None => None,
Some(ref style_attribute) => Some(style_attribute) Some(ref style_attribute) => Some(style_attribute)
}; }
stylist.get_applicable_declarations(self, style_attribute)
}; };
match *self.mutate_layout_data().ptr { match *self.mutate_layout_data().ptr {
Some(ref mut layout_data) => { Some(ref mut layout_data) => {
match stylist.get_pseudo_element() { layout_data.applicable_declarations = stylist.get_applicable_declarations(
Some(Before) => layout_data.before_applicable_declarations = applicable_declarations, self, style_attribute, None);
Some(After) => layout_data.after_applicable_declarations = applicable_declarations, layout_data.before_applicable_declarations = stylist.get_applicable_declarations(
None => layout_data.applicable_declarations = applicable_declarations, self, None, Some(Before));
} layout_data.after_applicable_declarations = stylist.get_applicable_declarations(
self, None, Some(After));
} }
None => fail!("no layout data") None => fail!("no layout data")
} }
} }
fn match_subtree(&self, stylists: ~[RWArc<Stylist>]) { fn match_subtree(&self, stylist: RWArc<Stylist>) {
let num_tasks = rt::default_sched_threads() * 2; let num_tasks = rt::default_sched_threads() * 2;
let mut node_count = 0; let mut node_count = 0;
let mut nodes_per_task = vec::from_elem(num_tasks, ~[]); let mut nodes_per_task = vec::from_elem(num_tasks, ~[]);
@ -70,8 +70,8 @@ impl<'self> MatchMethods for LayoutNode<'self> {
for nodes in nodes_per_task.move_iter() { for nodes in nodes_per_task.move_iter() {
if nodes.len() > 0 { if nodes.len() > 0 {
let chan = chan.clone(); let chan = chan.clone();
let stylists = stylists.clone(); let stylist = stylist.clone();
// FIXME(pcwalton): This transmute is to work around the fact that we have no // FIXME(pcwalton): This transmute is to work around the fact that we have no
// mechanism for safe fork/join parallelism. If we had such a thing, then we could // mechanism for safe fork/join parallelism. If we had such a thing, then we could
// close over the lifetime-bounded `LayoutNode`. But we can't, so we force it with // close over the lifetime-bounded `LayoutNode`. But we can't, so we force it with
@ -80,19 +80,16 @@ impl<'self> MatchMethods for LayoutNode<'self> {
cast::transmute(nodes) cast::transmute(nodes)
}; };
do task::spawn_with((evil, stylists)) |(evil, stylists)| { do task::spawn_with((evil, stylist)) |(evil, stylist)| {
let nodes: ~[LayoutNode] = unsafe { let nodes: ~[LayoutNode] = unsafe {
cast::transmute(evil) cast::transmute(evil)
}; };
let nodes = Cell::new(nodes); let nodes = Cell::new(nodes);
for stylist in stylists.iter() { do stylist.read |stylist| {
do stylist.read |stylist| { let nodes = nodes.take();
nodes.with_ref(|nodes|{ for node in nodes.iter() {
for node in nodes.iter() { node.match_node(stylist);
node.match_node(stylist);
}
});
} }
} }
chan.send(()); chan.send(());

View file

@ -3,17 +3,16 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use style::{Stylesheet, Stylist, UserAgentOrigin, with_errors_silenced}; use style::{Stylesheet, Stylist, UserAgentOrigin, with_errors_silenced};
use style::PseudoElement;
use extra::url; use extra::url;
pub fn new_stylist(pseudo_element: Option<PseudoElement>) -> Stylist { pub fn new_stylist() -> Stylist {
let mut stylist = Stylist::new(pseudo_element); let mut stylist = Stylist::new();
let ua_stylesheet = with_errors_silenced(|| Stylesheet::from_bytes( let ua_stylesheet = with_errors_silenced(|| Stylesheet::from_bytes(
include_bin!("user-agent.css"), include_bin!("user-agent.css"),
url::from_str("chrome:///user-agent.css").unwrap(), url::from_str("chrome:///user-agent.css").unwrap(),
None, None,
None)); None));
stylist.add_stylesheet(&ua_stylesheet, UserAgentOrigin); stylist.add_stylesheet(ua_stylesheet, UserAgentOrigin);
stylist stylist
} }

View file

@ -51,7 +51,6 @@ use std::comm::Port;
use std::task; use std::task;
use std::util; use std::util;
use style::{AuthorOrigin, Stylesheet, Stylist}; use style::{AuthorOrigin, Stylesheet, Stylist};
use style::{Before, After};
/// Information needed by the layout task. /// Information needed by the layout task.
struct LayoutTask { struct LayoutTask {
@ -82,7 +81,7 @@ struct LayoutTask {
/// A cached display list. /// A cached display list.
display_list: Option<Arc<DisplayList<OpaqueNode>>>, display_list: Option<Arc<DisplayList<OpaqueNode>>>,
stylists: ~[RWArc<Stylist>], stylist: RWArc<Stylist>,
/// The channel on which messages can be sent to the profiler. /// The channel on which messages can be sent to the profiler.
profiler_chan: ProfilerChan, profiler_chan: ProfilerChan,
@ -237,14 +236,6 @@ impl LayoutTask {
profiler_chan: ProfilerChan) profiler_chan: ProfilerChan)
-> LayoutTask { -> LayoutTask {
let mut stylists = ~[];
// We implemented parsing/selector-matching only for Before and After.
// FirstLine and FirstLetter have to be added later.
let stylist_owners = ~[Some(Before), Some(After), None];
for pseudo_element in stylist_owners.iter() {
stylists.push(RWArc::new(new_stylist(*pseudo_element)));
}
LayoutTask { LayoutTask {
id: id, id: id,
port: port, port: port,
@ -257,7 +248,7 @@ impl LayoutTask {
display_list: None, display_list: None,
stylists: stylists, stylist: RWArc::new(new_stylist()),
profiler_chan: profiler_chan, profiler_chan: profiler_chan,
opts: opts.clone() opts: opts.clone()
} }
@ -356,12 +347,8 @@ impl LayoutTask {
fn handle_add_stylesheet(&mut self, sheet: Stylesheet) { fn handle_add_stylesheet(&mut self, sheet: Stylesheet) {
let sheet = Cell::new(sheet); let sheet = Cell::new(sheet);
for stylist in self.stylists.iter() { do self.stylist.write |stylist| {
do stylist.write |stylist| { stylist.add_stylesheet(sheet.take(), AuthorOrigin);
sheet.with_ref(|sheet|{
stylist.add_stylesheet(sheet, AuthorOrigin);
});
}
} }
} }
@ -458,7 +445,7 @@ impl LayoutTask {
ReflowDocumentDamage => {} ReflowDocumentDamage => {}
_ => { _ => {
do profile(time::LayoutSelectorMatchCategory, self.profiler_chan.clone()) { do profile(time::LayoutSelectorMatchCategory, self.profiler_chan.clone()) {
node.match_subtree(self.stylists.clone()); node.match_subtree(self.stylist.clone());
node.cascade_subtree(None); node.cascade_subtree(None);
} }
} }

View file

@ -70,7 +70,6 @@ impl SelectorMap {
N:TNode<E>>( N:TNode<E>>(
&self, &self,
node: &N, node: &N,
pseudo_element: Option<PseudoElement>,
matching_rules_list: &mut ~[Rule]) { matching_rules_list: &mut ~[Rule]) {
// At the end, we're going to sort the rules that we added, so remember where we began. // At the end, we're going to sort the rules that we added, so remember where we began.
let init_len = matching_rules_list.len(); let init_len = matching_rules_list.len();
@ -78,7 +77,6 @@ impl SelectorMap {
match element.get_attr(None, "id") { match element.get_attr(None, "id") {
Some(id) => { Some(id) => {
SelectorMap::get_matching_rules_from_hash(node, SelectorMap::get_matching_rules_from_hash(node,
pseudo_element,
&self.id_hash, &self.id_hash,
id, id,
matching_rules_list) matching_rules_list)
@ -90,7 +88,6 @@ impl SelectorMap {
Some(ref class_attr) => { Some(ref class_attr) => {
for class in class_attr.split_iter(SELECTOR_WHITESPACE) { for class in class_attr.split_iter(SELECTOR_WHITESPACE) {
SelectorMap::get_matching_rules_from_hash(node, SelectorMap::get_matching_rules_from_hash(node,
pseudo_element,
&self.class_hash, &self.class_hash,
class, class,
matching_rules_list) matching_rules_list)
@ -102,12 +99,10 @@ impl SelectorMap {
// HTML elements in HTML documents must be matched case-insensitively. // HTML elements in HTML documents must be matched case-insensitively.
// TODO(pradeep): Case-sensitivity depends on the document type. // TODO(pradeep): Case-sensitivity depends on the document type.
SelectorMap::get_matching_rules_from_hash(node, SelectorMap::get_matching_rules_from_hash(node,
pseudo_element,
&self.element_hash, &self.element_hash,
element.get_local_name().to_ascii_lower(), element.get_local_name().to_ascii_lower(),
matching_rules_list); matching_rules_list);
SelectorMap::get_matching_rules(node, SelectorMap::get_matching_rules(node,
pseudo_element,
self.universal_rules, self.universal_rules,
matching_rules_list); matching_rules_list);
}); });
@ -119,13 +114,12 @@ impl SelectorMap {
fn get_matching_rules_from_hash<E:TElement, fn get_matching_rules_from_hash<E:TElement,
N:TNode<E>>( N:TNode<E>>(
node: &N, node: &N,
pseudo_element: Option<PseudoElement>,
hash: &HashMap<~str,~[Rule]>, hash: &HashMap<~str,~[Rule]>,
key: &str, key: &str,
matching_rules: &mut ~[Rule]) { matching_rules: &mut ~[Rule]) {
match hash.find(&key.to_str()) { match hash.find(&key.to_str()) {
Some(rules) => { Some(rules) => {
SelectorMap::get_matching_rules(node, pseudo_element, *rules, matching_rules) SelectorMap::get_matching_rules(node, *rules, matching_rules)
} }
None => {} None => {}
} }
@ -135,11 +129,10 @@ impl SelectorMap {
fn get_matching_rules<E:TElement, fn get_matching_rules<E:TElement,
N:TNode<E>>( N:TNode<E>>(
node: &N, node: &N,
pseudo_element: Option<PseudoElement>,
rules: &[Rule], rules: &[Rule],
matching_rules: &mut ~[Rule]) { matching_rules: &mut ~[Rule]) {
for rule in rules.iter() { for rule in rules.iter() {
if matches_selector(rule.selector.get(), node, pseudo_element) { if matches_compound_selector(rule.selector.get(), node) {
// TODO(pradeep): Is the cloning inefficient? // TODO(pradeep): Is the cloning inefficient?
matching_rules.push(rule.clone()); matching_rules.push(rule.clone());
} }
@ -198,7 +191,7 @@ impl SelectorMap {
/// Retrieve the first ID name in Rule, or None otherwise. /// Retrieve the first ID name in Rule, or None otherwise.
fn get_id_name(rule: &Rule) -> Option<~str> { fn get_id_name(rule: &Rule) -> Option<~str> {
let simple_selector_sequence = &rule.selector.get().compound_selectors.simple_selectors; let simple_selector_sequence = &rule.selector.get().simple_selectors;
for ss in simple_selector_sequence.iter() { for ss in simple_selector_sequence.iter() {
match *ss { match *ss {
// TODO(pradeep): Implement case-sensitivity based on the document type and quirks // TODO(pradeep): Implement case-sensitivity based on the document type and quirks
@ -212,7 +205,7 @@ impl SelectorMap {
/// Retrieve the FIRST class name in Rule, or None otherwise. /// Retrieve the FIRST class name in Rule, or None otherwise.
fn get_class_name(rule: &Rule) -> Option<~str> { fn get_class_name(rule: &Rule) -> Option<~str> {
let simple_selector_sequence = &rule.selector.get().compound_selectors.simple_selectors; let simple_selector_sequence = &rule.selector.get().simple_selectors;
for ss in simple_selector_sequence.iter() { for ss in simple_selector_sequence.iter() {
match *ss { match *ss {
// TODO(pradeep): Implement case-sensitivity based on the document type and quirks // TODO(pradeep): Implement case-sensitivity based on the document type and quirks
@ -226,7 +219,7 @@ impl SelectorMap {
/// Retrieve the name if it is a type selector, or None otherwise. /// Retrieve the name if it is a type selector, or None otherwise.
fn get_element_name(rule: &Rule) -> Option<~str> { fn get_element_name(rule: &Rule) -> Option<~str> {
let simple_selector_sequence = &rule.selector.get().compound_selectors.simple_selectors; let simple_selector_sequence = &rule.selector.get().simple_selectors;
for ss in simple_selector_sequence.iter() { for ss in simple_selector_sequence.iter() {
match *ss { match *ss {
// HTML elements in HTML documents must be matched case-insensitively // HTML elements in HTML documents must be matched case-insensitively
@ -240,51 +233,62 @@ impl SelectorMap {
} }
pub struct Stylist { pub struct Stylist {
priv ua_rule_map: PerOriginSelectorMap, priv element_map: PerPseudoElementSelectorMap,
priv author_rule_map: PerOriginSelectorMap, priv before_map: PerPseudoElementSelectorMap,
priv user_rule_map: PerOriginSelectorMap, priv after_map: PerPseudoElementSelectorMap,
priv stylesheet_index: uint, priv stylesheet_index: uint,
priv pseudo_element: Option<PseudoElement>,
} }
impl Stylist { impl Stylist {
#[inline] #[inline]
pub fn new(pseudo_element: Option<PseudoElement>) -> Stylist { pub fn new() -> Stylist {
Stylist { Stylist {
ua_rule_map: PerOriginSelectorMap::new(), element_map: PerPseudoElementSelectorMap::new(),
author_rule_map: PerOriginSelectorMap::new(), before_map: PerPseudoElementSelectorMap::new(),
user_rule_map: PerOriginSelectorMap::new(), after_map: PerPseudoElementSelectorMap::new(),
stylesheet_index: 0u, stylesheet_index: 0u,
pseudo_element: pseudo_element,
} }
} }
pub fn add_stylesheet(&mut self, stylesheet: &Stylesheet, origin: StylesheetOrigin) { pub fn add_stylesheet(&mut self, stylesheet: Stylesheet, origin: StylesheetOrigin) {
let rule_map = match origin { let (mut element_map, mut before_map, mut after_map) = match origin {
UserAgentOrigin => &mut self.ua_rule_map, UserAgentOrigin => (
AuthorOrigin => &mut self.author_rule_map, &mut self.element_map.user_agent,
UserOrigin => &mut self.user_rule_map, &mut self.before_map.user_agent,
&mut self.after_map.user_agent,
),
AuthorOrigin => (
&mut self.element_map.author,
&mut self.before_map.author,
&mut self.after_map.author,
),
UserOrigin => (
&mut self.element_map.user,
&mut self.before_map.user,
&mut self.after_map.user,
),
}; };
let mut added_normal_declarations = false;
let mut added_important_declarations = false;
let mut style_rule_index = 0u; let mut style_rule_index = 0u;
// Take apart the StyleRule into individual Rules and insert // Take apart the StyleRule into individual Rules and insert
// them into the SelectorMap of that priority. // them into the SelectorMap of that priority.
macro_rules! append( macro_rules! append(
($priority: ident, $flag: ident) => { ($priority: ident) => {
if style_rule.declarations.$priority.get().len() > 0 { if style_rule.declarations.$priority.get().len() > 0 {
$flag = true;
for selector in style_rule.selectors.iter() { for selector in style_rule.selectors.iter() {
// TODO: avoid copying? let map = match selector.pseudo_element {
if selector.pseudo_element == self.pseudo_element { None => &mut element_map,
rule_map.$priority.insert(Rule { Some(Before) => &mut before_map,
selector: Arc::new(selector.clone()), Some(After) => &mut after_map,
declarations: style_rule.declarations.$priority.clone(), };
index: style_rule_index, map.$priority.insert(Rule {
stylesheet_index: self.stylesheet_index, // TODO: avoid copying?
}); selector: Arc::new(selector.compound_selectors.clone()),
} specificity: selector.specificity,
declarations: style_rule.declarations.$priority.clone(),
index: style_rule_index,
stylesheet_index: self.stylesheet_index,
});
} }
} }
}; };
@ -292,8 +296,8 @@ impl Stylist {
let device = &Device { media_type: Screen }; // TODO, use Print when printing let device = &Device { media_type: Screen }; // TODO, use Print when printing
do iter_style_rules(stylesheet.rules.as_slice(), device) |style_rule| { do iter_style_rules(stylesheet.rules.as_slice(), device) |style_rule| {
append!(normal, added_normal_declarations); append!(normal);
append!(important, added_important_declarations); append!(important);
style_rule_index += 1u; style_rule_index += 1u;
} }
self.stylesheet_index += 1; self.stylesheet_index += 1;
@ -305,20 +309,26 @@ impl Stylist {
N:TNode<E>>( N:TNode<E>>(
&self, &self,
element: &N, element: &N,
style_attribute: Option<&PropertyDeclarationBlock>) style_attribute: Option<&PropertyDeclarationBlock>,
pseudo_element: Option<PseudoElement>)
-> ~[Arc<~[PropertyDeclaration]>] { -> ~[Arc<~[PropertyDeclaration]>] {
assert!(element.is_element()); assert!(element.is_element());
assert!(style_attribute.is_none() || self.pseudo_element.is_none(), assert!(style_attribute.is_none() || pseudo_element.is_none(),
"Style attributes do not apply to pseudo-elements"); "Style attributes do not apply to pseudo-elements");
let map = match pseudo_element {
None => &self.element_map,
Some(Before) => &self.before_map,
Some(After) => &self.after_map,
};
// In cascading order: // In cascading order:
let rule_map_list = [ let rule_map_list = [
&self.ua_rule_map.normal, &map.user_agent.normal,
&self.user_rule_map.normal, &map.user.normal,
&self.author_rule_map.normal, &map.author.normal,
&self.author_rule_map.important, &map.author.important,
&self.user_rule_map.important, &map.user.important,
&self.ua_rule_map.important &map.user_agent.important
]; ];
// We keep track of the indices of each of the rule maps in the list we're building so that // We keep track of the indices of each of the rule maps in the list we're building so that
@ -330,7 +340,7 @@ impl Stylist {
for (i, rule_map) in rule_map_list.iter().enumerate() { for (i, rule_map) in rule_map_list.iter().enumerate() {
rule_map_indices[i] = matching_rules_list.len(); rule_map_indices[i] = matching_rules_list.len();
rule_map.get_all_matching_rules(element, self.pseudo_element, &mut matching_rules_list); rule_map.get_all_matching_rules(element, &mut matching_rules_list);
} }
let count = matching_rules_list.len(); let count = matching_rules_list.len();
@ -373,10 +383,6 @@ impl Stylist {
applicable_declarations applicable_declarations
} }
pub fn get_pseudo_element(&self) -> Option<PseudoElement> {
self.pseudo_element
}
} }
struct PerOriginSelectorMap { struct PerOriginSelectorMap {
@ -394,40 +400,47 @@ impl PerOriginSelectorMap {
} }
} }
struct PerPseudoElementSelectorMap {
user_agent: PerOriginSelectorMap,
author: PerOriginSelectorMap,
user: PerOriginSelectorMap,
}
impl PerPseudoElementSelectorMap {
#[inline]
fn new() -> PerPseudoElementSelectorMap {
PerPseudoElementSelectorMap {
user_agent: PerOriginSelectorMap::new(),
author: PerOriginSelectorMap::new(),
user: PerOriginSelectorMap::new(),
}
}
}
#[deriving(Clone)] #[deriving(Clone)]
struct Rule { struct Rule {
// This is an Arc because Rule will essentially be cloned for every node // This is an Arc because Rule will essentially be cloned for every node
// that it matches. Selector contains an owned vector (through // that it matches. Selector contains an owned vector (through
// CompoundSelector) and we want to avoid the allocation. // CompoundSelector) and we want to avoid the allocation.
selector: Arc<Selector>, selector: Arc<CompoundSelector>,
declarations: Arc<~[PropertyDeclaration]>, declarations: Arc<~[PropertyDeclaration]>,
// Index of the parent StyleRule in the parent Stylesheet (useful for // Index of the parent StyleRule in the parent Stylesheet (useful for
// breaking ties while cascading). // breaking ties while cascading).
index: uint, index: uint,
// Index of the parent stylesheet among all the stylesheets // Index of the parent stylesheet among all the stylesheets
stylesheet_index: uint, stylesheet_index: uint,
specificity: u32,
} }
impl Ord for Rule { impl Ord for Rule {
#[inline] #[inline]
fn lt(&self, other: &Rule) -> bool { fn lt(&self, other: &Rule) -> bool {
let this_rank = (self.selector.get().specificity, self.stylesheet_index, self.index); let this_rank = (self.specificity, self.stylesheet_index, self.index);
let other_rank = (other.selector.get().specificity, other.stylesheet_index, other.index); let other_rank = (other.specificity, other.stylesheet_index, other.index);
this_rank < other_rank this_rank < other_rank
} }
} }
#[inline]
fn matches_selector<E:TElement,
N:TNode<E>>(
selector: &Selector,
element: &N,
pseudo_element: Option<PseudoElement>)
-> bool {
selector.pseudo_element == pseudo_element &&
matches_compound_selector::<E,N>(&selector.compound_selectors, element)
}
fn matches_compound_selector<E:TElement,N:TNode<E>>(selector: &CompoundSelector, element: &N) fn matches_compound_selector<E:TElement,N:TNode<E>>(selector: &CompoundSelector, element: &N)
-> bool { -> bool {
if !do selector.simple_selectors.iter().all |simple_selector| { if !do selector.simple_selectors.iter().all |simple_selector| {
@ -712,6 +725,9 @@ fn match_attribute<E:TElement,
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use extra::arc::Arc;
use super::{Rule, SelectorMap};
/// Helper method to get some Rules from selector strings. /// Helper method to get some Rules from selector strings.
/// Each sublist of the result contains the Rules for one StyleRule. /// Each sublist of the result contains the Rules for one StyleRule.
fn get_mock_rules(css_selectors: &[&str]) -> ~[~[Rule]] { fn get_mock_rules(css_selectors: &[&str]) -> ~[~[Rule]] {
@ -724,7 +740,8 @@ mod tests {
parse_selector_list(tokenize(*selectors).map(|(c, _)| c).to_owned_vec(), &namespaces) parse_selector_list(tokenize(*selectors).map(|(c, _)| c).to_owned_vec(), &namespaces)
.unwrap().move_iter().map(|s| { .unwrap().move_iter().map(|s| {
Rule { Rule {
selector: Arc::new(s), specificity: s.specificity,
selector: Arc::new(s.compound_selectors),
declarations: Arc::new(~[]), declarations: Arc::new(~[]),
index: i, index: i,
stylesheet_index: 0u, stylesheet_index: 0u,