Skip to content

Commit

Permalink
Auto merge of #16250 - bholley:pseudo_repr, r=emilio
Browse files Browse the repository at this point in the history
Don't use a HashMap for pseudo-element styles

Reviewed in https://bugzilla.mozilla.org/show_bug.cgi?id=1335708

@bors-servo try

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/16250)
<!-- Reviewable:end -->
  • Loading branch information
bors-servo committed Apr 3, 2017
2 parents 806584d + 3f0d022 commit eee25e2
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 139 deletions.
34 changes: 15 additions & 19 deletions components/script_layout_interface/wrapper_traits.rs
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -397,47 +397,43 @@ 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(
&context.guards,
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()
}
}
}
Expand Down
113 changes: 85 additions & 28 deletions components/style/data.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -73,50 +73,107 @@ 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 {
/// Trivially construct a new `ElementStyles`.
pub fn new(primary: ComputedStyle) -> Self {
ElementStyles {
primary: primary,
pseudos: PseudoStyles::empty(),
pseudos: EagerPseudoStyles(None),
cached_pseudos: PseudoElementCache::default(),
}
}

Expand Down
84 changes: 76 additions & 8 deletions components/style/gecko/selector_parser.rs
Expand Up @@ -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 {
Expand All @@ -55,6 +87,35 @@ impl PseudoElement {
self.1
}

/// Whether this pseudo-element is ::before or ::after.
#[inline]
pub fn is_before_or_after(&self) -> bool {
*self.as_atom() == atom!(":before") ||
*self.as_atom() == atom!(":after")
}

/// Whether this pseudo-element is eagerly-cascaded.
#[inline]
pub fn is_eager(&self) -> bool {
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.
#[inline]
pub fn is_lazy(&self) -> bool {
!self.is_eager() && !self.is_precomputed()
}

/// Whether this pseudo-element is precomputed.
#[inline]
pub fn is_precomputed(&self) -> bool {
self.is_anon_box()
}

/// Construct a pseudo-element from an `Atom`, receiving whether it is also
/// an anonymous box, and don't check it on release builds.
///
Expand Down Expand Up @@ -398,7 +459,8 @@ impl SelectorImpl {
///
/// We resolve the others lazily, see `Servo_ResolvePseudoStyle`.
pub fn pseudo_element_cascade_type(pseudo: &PseudoElement) -> PseudoElementCascadeType {
if Self::pseudo_is_before_or_after(pseudo) {
if pseudo.is_eager() {
debug_assert!(!pseudo.is_anon_box());
return PseudoElementCascadeType::Eager
}

Expand All @@ -409,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)
Expand All @@ -423,13 +498,6 @@ impl SelectorImpl {
include!("generated/gecko_pseudo_element_helper.rs")
}

#[inline]
/// Returns whether the given pseudo-element is `::before` or `::after`.
pub fn pseudo_is_before_or_after(pseudo: &PseudoElement) -> bool {
*pseudo.as_atom() == atom!(":before") ||
*pseudo.as_atom() == atom!(":after")
}

#[inline]
/// Returns the relevant state flag for a given non-tree-structural
/// pseudo-class.
Expand Down

0 comments on commit eee25e2

Please sign in to comment.