diff options
-rw-r--r-- | components/script_layout_interface/wrapper_traits.rs | 34 | ||||
-rw-r--r-- | components/style/data.rs | 113 | ||||
-rw-r--r-- | components/style/gecko/selector_parser.rs | 51 | ||||
-rw-r--r-- | components/style/matching.rs | 40 | ||||
-rw-r--r-- | components/style/selector_parser.rs | 16 | ||||
-rw-r--r-- | components/style/servo/selector_parser.rs | 48 |
6 files changed, 206 insertions, 96 deletions
diff --git a/components/script_layout_interface/wrapper_traits.rs b/components/script_layout_interface/wrapper_traits.rs index 08de74f0e21..a1c59d83114 100644 --- a/components/script_layout_interface/wrapper_traits.rs +++ b/components/script_layout_interface/wrapper_traits.rs @@ -339,7 +339,7 @@ pub trait ThreadSafeLayoutElement: Clone + Copy + Sized + Debug + .unwrap() .borrow() .styles().pseudos - .contains_key(&PseudoElement::Before) { + .has(&PseudoElement::Before) { Some(self.with_pseudo(PseudoElementType::Before(None))) } else { None @@ -352,7 +352,7 @@ pub trait ThreadSafeLayoutElement: Clone + Copy + Sized + Debug + .unwrap() .borrow() .styles().pseudos - .contains_key(&PseudoElement::After) { + .has(&PseudoElement::After) { Some(self.with_pseudo(PseudoElementType::After(None))) } else { None @@ -397,31 +397,29 @@ pub trait ThreadSafeLayoutElement: Clone + Copy + Sized + Debug + // Precompute non-eagerly-cascaded pseudo-element styles if not // cached before. let style_pseudo = other.style_pseudo_element(); + let mut data = self.get_style_data().unwrap().borrow_mut(); match style_pseudo.cascade_type() { // Already computed during the cascade. - PseudoElementCascadeType::Eager => {}, + PseudoElementCascadeType::Eager => { + data.styles().pseudos.get(&style_pseudo) + .unwrap().values().clone() + }, PseudoElementCascadeType::Precomputed => { - if !self.get_style_data() - .unwrap() - .borrow() - .styles().pseudos.contains_key(&style_pseudo) { - let mut data = self.get_style_data().unwrap().borrow_mut(); + if !data.styles().cached_pseudos.contains_key(&style_pseudo) { let new_style = context.stylist.precomputed_values_for_pseudo( &context.guards, &style_pseudo, Some(data.styles().primary.values()), CascadeFlags::empty()); - data.styles_mut().pseudos + data.styles_mut().cached_pseudos .insert(style_pseudo.clone(), new_style); } + data.styles().cached_pseudos.get(&style_pseudo) + .unwrap().values().clone() } PseudoElementCascadeType::Lazy => { - if !self.get_style_data() - .unwrap() - .borrow() - .styles().pseudos.contains_key(&style_pseudo) { - let mut data = self.get_style_data().unwrap().borrow_mut(); + if !data.styles().cached_pseudos.contains_key(&style_pseudo) { let new_style = context.stylist .lazily_compute_pseudo_element_style( @@ -429,15 +427,13 @@ pub trait ThreadSafeLayoutElement: Clone + Copy + Sized + Debug + unsafe { &self.unsafe_get() }, &style_pseudo, data.styles().primary.values()); - data.styles_mut().pseudos + data.styles_mut().cached_pseudos .insert(style_pseudo.clone(), new_style.unwrap()); } + data.styles().cached_pseudos.get(&style_pseudo) + .unwrap().values().clone() } } - - self.get_style_data().unwrap().borrow() - .styles().pseudos.get(&style_pseudo) - .unwrap().values().clone() } } } diff --git a/components/style/data.rs b/components/style/data.rs index a5a1e1712cb..9eb831e07a1 100644 --- a/components/style/data.rs +++ b/components/style/data.rs @@ -11,11 +11,11 @@ use properties::ComputedValues; use properties::longhands::display::computed_value as display; use restyle_hints::{RESTYLE_CSS_ANIMATIONS, RESTYLE_DESCENDANTS, RESTYLE_LATER_SIBLINGS, RESTYLE_SELF, RestyleHint}; use rule_tree::StrongRuleNode; -use selector_parser::{PseudoElement, RestyleDamage, Snapshot}; -use std::collections::HashMap; +use selector_parser::{EAGER_PSEUDO_COUNT, PseudoElement, RestyleDamage, Snapshot}; +#[cfg(feature = "servo")] use std::collections::HashMap; use std::fmt; -use std::hash::BuildHasherDefault; -use std::ops::{Deref, DerefMut}; +#[cfg(feature = "servo")] use std::hash::BuildHasherDefault; +use std::ops::Deref; use std::sync::Arc; use stylist::Stylist; use thread_state; @@ -73,42 +73,98 @@ impl fmt::Debug for ComputedStyle { } } -type PseudoStylesInner = HashMap<PseudoElement, ComputedStyle, - BuildHasherDefault<::fnv::FnvHasher>>; - -/// A set of styles for a given element's pseudo-elements. -/// -/// This is a map from pseudo-element to `ComputedStyle`. -/// -/// TODO(emilio): This should probably be a small array by default instead of a -/// full-blown `HashMap`. +/// A list of styles for eagerly-cascaded pseudo-elements. Lazily-allocated. #[derive(Clone, Debug)] -pub struct PseudoStyles(PseudoStylesInner); +pub struct EagerPseudoStyles(Option<Box<[Option<ComputedStyle>]>>); -impl PseudoStyles { - /// Construct an empty set of `PseudoStyles`. - pub fn empty() -> Self { - PseudoStyles(HashMap::with_hasher(Default::default())) +impl EagerPseudoStyles { + /// Returns whether there are any pseudo styles. + pub fn is_empty(&self) -> bool { + self.0.is_some() } -} -impl Deref for PseudoStyles { - type Target = PseudoStylesInner; - fn deref(&self) -> &Self::Target { &self.0 } -} + /// Returns a reference to the style for a given eager pseudo, if it exists. + pub fn get(&self, pseudo: &PseudoElement) -> Option<&ComputedStyle> { + debug_assert!(pseudo.is_eager()); + self.0.as_ref().and_then(|p| p[pseudo.eager_index()].as_ref()) + } + + /// Returns a mutable reference to the style for a given eager pseudo, if it exists. + pub fn get_mut(&mut self, pseudo: &PseudoElement) -> Option<&mut ComputedStyle> { + debug_assert!(pseudo.is_eager()); + self.0.as_mut().and_then(|p| p[pseudo.eager_index()].as_mut()) + } -impl DerefMut for PseudoStyles { - fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } + /// Returns true if the EagerPseudoStyles has a ComputedStyle for |pseudo|. + pub fn has(&self, pseudo: &PseudoElement) -> bool { + self.get(pseudo).is_some() + } + + /// Inserts a pseudo-element. The pseudo-element must not already exist. + pub fn insert(&mut self, pseudo: &PseudoElement, style: ComputedStyle) { + debug_assert!(!self.has(pseudo)); + if self.0.is_none() { + self.0 = Some(vec![None; EAGER_PSEUDO_COUNT].into_boxed_slice()); + } + self.0.as_mut().unwrap()[pseudo.eager_index()] = Some(style); + } + + /// Removes a pseudo-element style if it exists, and returns it. + pub fn take(&mut self, pseudo: &PseudoElement) -> Option<ComputedStyle> { + let result = match self.0.as_mut() { + None => return None, + Some(arr) => arr[pseudo.eager_index()].take(), + }; + let empty = self.0.as_ref().unwrap().iter().all(|x| x.is_none()); + if empty { + self.0 = None; + } + result + } + + /// Returns a list of the pseudo-elements. + pub fn keys(&self) -> Vec<PseudoElement> { + let mut v = Vec::new(); + if let Some(ref arr) = self.0 { + for i in 0..EAGER_PSEUDO_COUNT { + if arr[i].is_some() { + v.push(PseudoElement::from_eager_index(i)); + } + } + } + v + } + + /// Sets the rule node for a given pseudo-element, which must already have an entry. + /// + /// Returns true if the rule node changed. + pub fn set_rules(&mut self, pseudo: &PseudoElement, rules: StrongRuleNode) -> bool { + debug_assert!(self.has(pseudo)); + let mut style = self.get_mut(pseudo).unwrap(); + let changed = style.rules != rules; + style.rules = rules; + changed + } } +/// A cache of precomputed and lazy pseudo-elements, used by servo. This isn't +/// a very efficient design, but is the result of servo having previously used +/// the eager pseudo map (when it was a map) for this cache. +#[cfg(feature = "servo")] +type PseudoElementCache = HashMap<PseudoElement, ComputedStyle, BuildHasherDefault<::fnv::FnvHasher>>; +#[cfg(feature = "gecko")] +type PseudoElementCache = (); + /// The styles associated with a node, including the styles for any /// pseudo-elements. #[derive(Clone, Debug)] pub struct ElementStyles { /// The element's style. pub primary: ComputedStyle, - /// The map of styles for the element's pseudos. - pub pseudos: PseudoStyles, + /// A list of the styles for the element's eagerly-cascaded pseudo-elements. + pub pseudos: EagerPseudoStyles, + /// NB: This is an empty field for gecko. + pub cached_pseudos: PseudoElementCache, } impl ElementStyles { @@ -116,7 +172,8 @@ impl ElementStyles { pub fn new(primary: ComputedStyle) -> Self { ElementStyles { primary: primary, - pseudos: PseudoStyles::empty(), + pseudos: EagerPseudoStyles(None), + cached_pseudos: PseudoElementCache::default(), } } diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs index e64adbd9b08..c6e4a524142 100644 --- a/components/style/gecko/selector_parser.rs +++ b/components/style/gecko/selector_parser.rs @@ -42,7 +42,39 @@ use string_cache::{Atom, Namespace, WeakAtom, WeakNamespace}; #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct PseudoElement(Atom, bool); +/// List of eager pseudos. Keep this in sync with the count below. +macro_rules! each_eager_pseudo { + ($macro_name:ident, $atom_macro:ident) => { + $macro_name!($atom_macro!(":after"), 0); + $macro_name!($atom_macro!(":before"), 1); + } +} + +/// The number of eager pseudo-elements (just ::before and ::after). +pub const EAGER_PSEUDO_COUNT: usize = 2; + + impl PseudoElement { + /// Gets the canonical index of this eagerly-cascaded pseudo-element. + #[inline] + pub fn eager_index(&self) -> usize { + macro_rules! case { + ($atom:expr, $idx:expr) => { if *self.as_atom() == $atom { return $idx; } } + } + each_eager_pseudo!(case, atom); + panic!("Not eager") + } + + /// Creates a pseudo-element from an eager index. + #[inline] + pub fn from_eager_index(i: usize) -> Self { + macro_rules! case { + ($atom:expr, $idx:expr) => { if i == $idx { return PseudoElement($atom, false); } } + } + each_eager_pseudo!(case, atom); + panic!("Not eager") + } + /// Get the pseudo-element as an atom. #[inline] pub fn as_atom(&self) -> &Atom { @@ -65,7 +97,11 @@ impl PseudoElement { /// Whether this pseudo-element is eagerly-cascaded. #[inline] pub fn is_eager(&self) -> bool { - self.is_before_or_after() + macro_rules! case { + ($atom:expr, $idx:expr) => { if *self.as_atom() == $atom { return true; } } + } + each_eager_pseudo!(case, atom); + return false; } /// Whether this pseudo-element is lazily-cascaded. @@ -435,6 +471,19 @@ impl SelectorImpl { PseudoElementCascadeType::Lazy } + /// A helper to traverse each eagerly cascaded pseudo-element, executing + /// `fun` on it. + #[inline] + pub fn each_eagerly_cascaded_pseudo_element<F>(mut fun: F) + where F: FnMut(PseudoElement), + { + macro_rules! case { + ($atom:expr, $idx:expr) => { fun(PseudoElement($atom, false)); } + } + each_eager_pseudo!(case, atom); + } + + #[inline] /// Executes a function for each pseudo-element. pub fn each_pseudo_element<F>(mut fun: F) diff --git a/components/style/matching.rs b/components/style/matching.rs index 0ab4c45e51e..29865b69e0a 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -25,7 +25,6 @@ use selectors::matching::{ElementSelectorFlags, StyleRelations}; use selectors::matching::AFFECTED_BY_PSEUDO_ELEMENTS; use servo_config::opts; use sink::ForgetfulSink; -use std::collections::hash_map::Entry; use std::sync::Arc; use stylist::ApplicableDeclarationBlock; @@ -902,7 +901,7 @@ pub trait MatchMethods : TElement { // Compute rule nodes for eagerly-cascaded pseudo-elements. let mut matches_different_pseudos = false; SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| { - let mut per_pseudo = &mut data.styles_mut().pseudos; + let mut pseudos = &mut data.styles_mut().pseudos; debug_assert!(applicable_declarations.is_empty()); let pseudo_animation_rules = if pseudo.is_before_or_after() { self.get_animation_rules(Some(&pseudo)) @@ -920,19 +919,13 @@ pub trait MatchMethods : TElement { if !applicable_declarations.is_empty() { let new_rules = compute_rule_node::<Self>(rule_tree, &mut applicable_declarations); - match per_pseudo.entry(pseudo) { - Entry::Occupied(mut e) => { - if e.get().rules != new_rules { - e.get_mut().rules = new_rules; - rule_nodes_changed = true; - } - }, - Entry::Vacant(e) => { - e.insert(ComputedStyle::new_partial(new_rules)); - matches_different_pseudos = true; - } + if pseudos.has(&pseudo) { + rule_nodes_changed = pseudos.set_rules(&pseudo, new_rules); + } else { + pseudos.insert(&pseudo, ComputedStyle::new_partial(new_rules)); + matches_different_pseudos = true; } - } else if per_pseudo.remove(&pseudo).is_some() { + } else if pseudos.take(&pseudo).is_some() { matches_different_pseudos = true; } }); @@ -947,7 +940,7 @@ pub trait MatchMethods : TElement { } // If we have any pseudo elements, indicate so in the primary StyleRelations. - if !data.styles().pseudos.is_empty() { + if data.styles().pseudos.is_empty() { primary_relations |= AFFECTED_BY_PSEUDO_ELEMENTS; } @@ -994,10 +987,10 @@ pub trait MatchMethods : TElement { animation_rule.as_ref(), primary_rules); - let iter = element_styles.pseudos.iter_mut().filter(|&(p, _)| p.is_before_or_after()); - for (pseudo, ref mut computed) in iter { - let animation_rule = self.get_animation_rule(Some(pseudo)); - let pseudo_rules = &mut computed.rules; + let pseudos = &mut element_styles.pseudos; + for pseudo in pseudos.keys().iter().filter(|p| p.is_before_or_after()) { + let animation_rule = self.get_animation_rule(Some(&pseudo)); + let pseudo_rules = &mut pseudos.get_mut(&pseudo).unwrap().rules; replace_rule_node(CascadeLevel::Animations, animation_rule.as_ref(), pseudo_rules); @@ -1196,11 +1189,10 @@ pub trait MatchMethods : TElement { // // Note that we've already set up the map of matching pseudo-elements // in match_element (and handled the damage implications of changing - // which pseudos match), so now we can just iterate the map. This does - // mean collecting the keys, so that the borrow checker will let us pass - // the mutable |data| to the inner cascade function. - let matched_pseudos: Vec<PseudoElement> = - data.styles().pseudos.keys().cloned().collect(); + // which pseudos match), so now we can just iterate what we have. This + // does mean collecting owned pseudos, so that the borrow checker will + // let us pass the mutable |data| to the inner cascade function. + let matched_pseudos = data.styles().pseudos.keys(); for pseudo in matched_pseudos { // If the new primary style is display:none, we don't need pseudo // styles, but we still need to clear any stale values. diff --git a/components/style/selector_parser.rs b/components/style/selector_parser.rs index 936cf121b3f..52f0a6b6d51 100644 --- a/components/style/selector_parser.rs +++ b/components/style/selector_parser.rs @@ -114,22 +114,6 @@ pub trait ElementExt: Element<Impl=SelectorImpl> + Debug { } impl SelectorImpl { - /// A helper to traverse each eagerly cascaded pseudo-element, executing - /// `fun` on it. - /// - /// TODO(emilio): We can optimize this for Gecko using the pseudo-element - /// macro, and we should consider doing that for Servo too. - #[inline] - pub fn each_eagerly_cascaded_pseudo_element<F>(mut fun: F) - where F: FnMut(PseudoElement), - { - Self::each_pseudo_element(|pseudo| { - if pseudo.is_eager() { - fun(pseudo) - } - }) - } - /// A helper to traverse each precomputed pseudo-element, executing `fun` on /// it. /// diff --git a/components/style/servo/selector_parser.rs b/components/style/servo/selector_parser.rs index a3d545c2a96..a2be7c46415 100644 --- a/components/style/servo/selector_parser.rs +++ b/components/style/servo/selector_parser.rs @@ -19,6 +19,7 @@ use selectors::parser::{AttrSelector, SelectorMethods}; use std::borrow::Cow; use std::fmt; use std::fmt::Debug; +use std::mem; /// A pseudo-element, both public and private. /// @@ -26,10 +27,13 @@ use std::fmt::Debug; #[derive(Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[allow(missing_docs)] +#[repr(usize)] pub enum PseudoElement { + // Eager pseudos. Keep these first so that eager_index() works. + After = 0, Before, - After, Selection, + // Non-eager pseudos. DetailsSummary, DetailsContent, ServoText, @@ -48,8 +52,8 @@ impl ToCss for PseudoElement { fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { use self::PseudoElement::*; dest.write_str(match *self { - Before => "::before", After => "::after", + Before => "::before", Selection => "::selection", DetailsSummary => "::-servo-details-summary", DetailsContent => "::-servo-details-content", @@ -67,15 +71,30 @@ impl ToCss for PseudoElement { } } +/// The number of eager pseudo-elements. Keep this in sync with cascade_type. +pub const EAGER_PSEUDO_COUNT: usize = 3; + impl PseudoElement { + /// Gets the canonical index of this eagerly-cascaded pseudo-element. + #[inline] + pub fn eager_index(&self) -> usize { + debug_assert!(self.is_eager()); + self.clone() as usize + } + + /// Creates a pseudo-element from an eager index. + #[inline] + pub fn from_eager_index(i: usize) -> Self { + assert!(i < EAGER_PSEUDO_COUNT); + let result: PseudoElement = unsafe { mem::transmute(i) }; + debug_assert!(result.is_eager()); + result + } + /// Whether the current pseudo element is :before or :after. #[inline] pub fn is_before_or_after(&self) -> bool { - match *self { - PseudoElement::Before | - PseudoElement::After => true, - _ => false, - } + matches!(*self, PseudoElement::After | PseudoElement::Before) } /// Whether this pseudo-element is eagerly-cascaded. @@ -99,11 +118,13 @@ impl PseudoElement { /// Returns which kind of cascade type has this pseudo. /// /// For more info on cascade types, see docs/components/style.md + /// + /// Note: Keep this in sync with EAGER_PSEUDO_COUNT. #[inline] pub fn cascade_type(&self) -> PseudoElementCascadeType { match *self { - PseudoElement::Before | PseudoElement::After | + PseudoElement::Before | PseudoElement::Selection => PseudoElementCascadeType::Eager, PseudoElement::DetailsSummary => PseudoElementCascadeType::Lazy, PseudoElement::DetailsContent | @@ -387,6 +408,17 @@ impl SelectorImpl { pseudo.cascade_type() } + /// A helper to traverse each eagerly cascaded pseudo-element, executing + /// `fun` on it. + #[inline] + pub fn each_eagerly_cascaded_pseudo_element<F>(mut fun: F) + where F: FnMut(PseudoElement), + { + for i in 0..EAGER_PSEUDO_COUNT { + fun(PseudoElement::from_eager_index(i)); + } + } + /// Executes `fun` for each pseudo-element. #[inline] pub fn each_pseudo_element<F>(mut fun: F) |