diff --git a/servo/components/style/data.rs b/servo/components/style/data.rs index 9eb831e07a11..46c1c6218259 100644 --- a/servo/components/style/data.rs +++ b/servo/components/style/data.rs @@ -80,7 +80,7 @@ pub struct EagerPseudoStyles(Option]>>); impl EagerPseudoStyles { /// Returns whether there are any pseudo styles. pub fn is_empty(&self) -> bool { - self.0.is_some() + self.0.is_none() } /// Returns a reference to the style for a given eager pseudo, if it exists. diff --git a/servo/components/style/matching.rs b/servo/components/style/matching.rs index 4bc5a1831670..49c521e87fd8 100644 --- a/servo/components/style/matching.rs +++ b/servo/components/style/matching.rs @@ -15,7 +15,7 @@ use cascade_info::CascadeInfo; use context::{SequentialTask, SharedStyleContext, StyleContext}; use data::{ComputedStyle, ElementData, ElementStyles, RestyleData}; use dom::{AnimationRules, SendElement, TElement, TNode}; -use properties::{CascadeFlags, ComputedValues, SHAREABLE, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP, cascade}; +use properties::{CascadeFlags, ComputedValues, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP, cascade}; use properties::longhands::display::computed_value as display; use restyle_hints::{RESTYLE_STYLE_ATTRIBUTE, RESTYLE_CSS_ANIMATIONS, RestyleHint}; use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode}; @@ -58,24 +58,6 @@ fn create_common_style_affecting_attributes_from_element(element: & flags } -/// The results returned from running selector matching on an element. -pub struct MatchResults { - /// A set of style relations (different hints about what rules matched or - /// could have matched). This is necessary if the style will be shared. - /// If None, the style will not be shared. - pub primary_relations: Option, - /// Whether the rule nodes changed during selector matching. - pub rule_nodes_changed: bool, -} - -impl MatchResults { - /// Returns true if the primary rule node is shareable with other nodes. - pub fn primary_is_shareable(&self) -> bool { - self.primary_relations.as_ref() - .map_or(false, relations_are_shareable) - } -} - /// Information regarding a style sharing candidate. /// /// Note that this information is stored in TLS and cleared after the traversal, @@ -430,15 +412,6 @@ pub enum StyleSharingResult { StyleWasShared(usize), } -/// Callers need to pass several boolean flags to cascade_primary_or_pseudo. -/// We encapsulate them in this struct to avoid mixing them up. -/// -/// FIXME(pcwalton): Unify with `CascadeFlags`, perhaps? -struct CascadeBooleans { - shareable: bool, - animate: bool, -} - trait PrivateMatchMethods: TElement { /// Returns the closest parent element that doesn't have a display: contents /// style (and thus generates a box). @@ -543,13 +516,9 @@ trait PrivateMatchMethods: TElement { fn cascade_internal(&self, context: &StyleContext, primary_style: &ComputedStyle, - pseudo_style: &Option<(&PseudoElement, &mut ComputedStyle)>, - booleans: &CascadeBooleans) + pseudo_style: &Option<(&PseudoElement, &mut ComputedStyle)>) -> Arc { let mut cascade_flags = CascadeFlags::empty(); - if booleans.shareable { - cascade_flags.insert(SHAREABLE) - } if self.skip_root_and_item_based_display_fixup() { cascade_flags.insert(SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP) } @@ -566,7 +535,7 @@ trait PrivateMatchMethods: TElement { data: &mut ElementData, pseudo: Option<&PseudoElement>, possibly_expired_animations: &mut Vec, - booleans: CascadeBooleans) { + animate: bool) { // Collect some values. let (mut styles, restyle) = data.styles_and_restyle_mut(); let mut primary_style = &mut styles.primary; @@ -577,10 +546,10 @@ trait PrivateMatchMethods: TElement { // Compute the new values. let mut new_values = self.cascade_internal(context, primary_style, - &pseudo_style, &booleans); + &pseudo_style); // Handle animations. - if booleans.animate { + if animate { self.process_animations(context, &mut old_values, &mut new_values, @@ -806,13 +775,71 @@ fn compute_rule_node(rule_tree: &RuleTree, impl PrivateMatchMethods for E {} +/// Controls whether the style sharing cache is used. +#[derive(Clone, Copy, PartialEq)] +pub enum StyleSharingBehavior { + /// Style sharing allowed. + Allow, + /// Style sharing disallowed. + Disallow, +} + /// The public API that elements expose for selector matching. pub trait MatchMethods : TElement { - /// Runs selector matching to (re)compute rule nodes for this element. - fn match_element(&self, + /// Performs selector matching and property cascading on an element and its eager pseudos. + fn match_and_cascade(&self, + context: &mut StyleContext, + data: &mut ElementData, + sharing: StyleSharingBehavior) + { + // Perform selector matching for the primary style. + let mut primary_relations = StyleRelations::empty(); + let _rule_node_changed = self.match_primary(context, data, &mut primary_relations); + + // Cascade properties and compute primary values. + let mut expired = vec![]; + self.cascade_primary(context, data, &mut expired); + + // Match and cascade eager pseudo-elements. + if !data.styles().is_display_none() { + let _pseudo_rule_nodes_changed = + self.match_pseudos(context, data); + self.cascade_pseudos(context, data, &mut expired); + } + + // If we have any pseudo elements, indicate so in the primary StyleRelations. + if !data.styles().pseudos.is_empty() { + primary_relations |= AFFECTED_BY_PSEUDO_ELEMENTS; + } + + // If the style is shareable, add it to the LRU cache. + if sharing == StyleSharingBehavior::Allow && relations_are_shareable(&primary_relations) { + context.thread_local + .style_sharing_candidate_cache + .insert_if_possible(self, + data.styles().primary.values(), + primary_relations); + } + } + + /// Performs the cascade, without matching. + fn cascade_primary_and_pseudos(&self, + context: &mut StyleContext, + mut data: &mut ElementData) + { + let mut possibly_expired_animations = vec![]; + self.cascade_primary(context, &mut data, &mut possibly_expired_animations); + self.cascade_pseudos(context, &mut data, &mut possibly_expired_animations); + } + + /// Runs selector matching to (re)compute the primary rule node for this element. + /// + /// Returns whether the primary rule node changed. + fn match_primary(&self, context: &mut StyleContext, - data: &mut ElementData) - -> MatchResults + data: &mut ElementData, + relations: &mut StyleRelations) + -> bool { let mut applicable_declarations = Vec::::with_capacity(16); @@ -821,74 +848,25 @@ pub trait MatchMethods : TElement { let style_attribute = self.style_attribute(); let animation_rules = self.get_animation_rules(None); let mut rule_nodes_changed = false; + let bloom = context.thread_local.bloom_filter.filter(); - // TODO(emilio): This is somewhat inefficient, because of a variety of - // reasons: - // - // * It doesn't coalesce flags. - // * It doesn't look at flags already sent in a task for the main - // thread to process. - // * It doesn't take advantage of us knowing that the traversal is - // sequential. - // - // I suspect (need to measure!) that we don't use to set flags on - // a lot of different elements, but we could end up posting the same - // flag over and over with this approach. - // - // If the number of elements is low, perhaps a small cache with the - // flags already sent would be appropriate. - // - // The sequential task business for this is kind of sad :(. - // - // Anyway, let's do the obvious thing for now. let tasks = &mut context.thread_local.tasks; let mut set_selector_flags = |element: &Self, flags: ElementSelectorFlags| { - // Apply the selector flags. - let self_flags = flags.for_self(); - if !self_flags.is_empty() { - if element == self { - unsafe { element.set_selector_flags(self_flags); } - } else { - if !element.has_selector_flags(self_flags) { - let task = - SequentialTask::set_selector_flags(element.clone(), - self_flags); - tasks.push(task); - } - } - } - let parent_flags = flags.for_parent(); - if !parent_flags.is_empty() { - if let Some(p) = element.parent_element() { - // Avoid the overhead of the SequentialTask if the flags are - // already set. - if !p.has_selector_flags(parent_flags) { - let task = SequentialTask::set_selector_flags(p, parent_flags); - tasks.push(task); - } - } - } + self.apply_selector_flags(tasks, element, flags); }; - // Borrow the stuff we need here so the borrow checker doesn't get mad - // at us later in the closure. - let guards = &context.shared.guards; - let rule_tree = &context.shared.stylist.rule_tree; - let bloom_filter = context.thread_local.bloom_filter.filter(); - // Compute the primary rule node. - let mut primary_relations = - stylist.push_applicable_declarations(self, - Some(bloom_filter), - style_attribute, - animation_rules, - None, - guards, - &mut applicable_declarations, - &mut set_selector_flags); + *relations = stylist.push_applicable_declarations(self, + Some(bloom), + style_attribute, + animation_rules, + None, + &context.shared.guards, + &mut applicable_declarations, + &mut set_selector_flags); let primary_rule_node = - compute_rule_node::(rule_tree, &mut applicable_declarations); + compute_rule_node::(&stylist.rule_tree, &mut applicable_declarations); if !data.has_styles() { data.set_styles(ElementStyles::new(ComputedStyle::new_partial(primary_rule_node))); rule_nodes_changed = true; @@ -897,6 +875,35 @@ pub trait MatchMethods : TElement { rule_nodes_changed = true; } + rule_nodes_changed + } + + /// Runs selector matching to (re)compute eager pseudo-element rule nodes for this + /// element. + /// + /// Returns whether any of the pseudo rule nodes changed (including, but not + /// limited to, cases where we match different pseudos altogether). + fn match_pseudos(&self, + context: &mut StyleContext, + data: &mut ElementData) + -> bool + { + let mut applicable_declarations = + Vec::::with_capacity(16); + let mut rule_nodes_changed = false; + + let tasks = &mut context.thread_local.tasks; + let mut set_selector_flags = |element: &Self, flags: ElementSelectorFlags| { + self.apply_selector_flags(tasks, element, flags); + }; + + // Borrow the stuff we need here so the borrow checker doesn't get mad + // at us later in the closure. + let stylist = &context.shared.stylist; + let guards = &context.shared.guards; + let rule_tree = &stylist.rule_tree; + let bloom_filter = context.thread_local.bloom_filter.filter(); + // Compute rule nodes for eagerly-cascaded pseudo-elements. let mut matches_different_pseudos = false; SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| { @@ -938,14 +945,59 @@ pub trait MatchMethods : TElement { } } - // If we have any pseudo elements, indicate so in the primary StyleRelations. - if data.styles().pseudos.is_empty() { - primary_relations |= AFFECTED_BY_PSEUDO_ELEMENTS; - } + rule_nodes_changed + } - MatchResults { - primary_relations: Some(primary_relations), - rule_nodes_changed: rule_nodes_changed, + /// Applies selector flags to an element, deferring mutations of the parent + /// until after the traversal. + /// + /// TODO(emilio): This is somewhat inefficient, because of a variety of + /// reasons: + /// + /// * It doesn't coalesce flags. + /// * It doesn't look at flags already sent in a task for the main + /// thread to process. + /// * It doesn't take advantage of us knowing that the traversal is + /// sequential. + /// + /// I suspect (need to measure!) that we don't use to set flags on + /// a lot of different elements, but we could end up posting the same + /// flag over and over with this approach. + /// + /// If the number of elements is low, perhaps a small cache with the + /// flags already sent would be appropriate. + /// + /// The sequential task business for this is kind of sad :(. + /// + /// Anyway, let's do the obvious thing for now. + fn apply_selector_flags(&self, + tasks: &mut Vec>, + element: &Self, + flags: ElementSelectorFlags) { + // Apply the selector flags. + let self_flags = flags.for_self(); + if !self_flags.is_empty() { + if element == self { + unsafe { element.set_selector_flags(self_flags); } + } else { + if !element.has_selector_flags(self_flags) { + let task = + SequentialTask::set_selector_flags(element.clone(), + self_flags); + tasks.push(task); + } + } + } + let parent_flags = flags.for_parent(); + if !parent_flags.is_empty() { + if let Some(p) = element.parent_element() { + // Avoid the overhead of the SequentialTask if the flags are + // already set. + if !p.has_selector_flags(parent_flags) { + let task = SequentialTask::set_selector_flags(p, parent_flags); + tasks.push(task); + } + } } } @@ -1163,51 +1215,33 @@ pub trait MatchMethods : TElement { } } - /// Run the CSS cascade and compute values for the element, potentially - /// starting any new transitions or animations. - fn cascade_element(&self, + /// Performs the cascade for the element's primary style. + fn cascade_primary(&self, context: &mut StyleContext, - mut data: &mut AtomicRefMut, - primary_is_shareable: bool) + mut data: &mut ElementData, + possibly_expired_animations: &mut Vec) { - let mut possibly_expired_animations = vec![]; + self.cascade_primary_or_pseudo(context, &mut data, None, + possibly_expired_animations, /* animate = */ true); + } - // Cascade the primary style. - self.cascade_primary_or_pseudo(context, data, None, - &mut possibly_expired_animations, - CascadeBooleans { - shareable: primary_is_shareable, - animate: true, - }); - - // Check whether the primary style is display:none. - let display_none = data.styles().primary.values().get_box().clone_display() == - display::T::none; - - // Cascade each pseudo-element. - // + /// Performs the cascade for the element's eager pseudos. + fn cascade_pseudos(&self, + context: &mut StyleContext, + mut data: &mut ElementData, + possibly_expired_animations: &mut Vec) + { // Note that we've already set up the map of matching pseudo-elements - // in match_element (and handled the damage implications of changing + // in match_pseudos (and handled the damage implications of changing // 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 us pass the mutable |data| to the 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. - if display_none { - data.styles_mut().pseudos.get_mut(&pseudo).unwrap().values = None; - continue; - } - // Only ::before and ::after are animatable. let animate = pseudo.is_before_or_after(); self.cascade_primary_or_pseudo(context, data, Some(&pseudo), - &mut possibly_expired_animations, - CascadeBooleans { - shareable: false, - animate: animate, - }); + possibly_expired_animations, animate); } } diff --git a/servo/components/style/properties/gecko.mako.rs b/servo/components/style/properties/gecko.mako.rs index 2d55fd3c4751..2d8424ff2e29 100644 --- a/servo/components/style/properties/gecko.mako.rs +++ b/servo/components/style/properties/gecko.mako.rs @@ -77,7 +77,6 @@ pub struct ComputedValues { % endfor custom_properties: Option>, - shareable: bool, pub writing_mode: WritingMode, pub root_font_size: Au, pub font_size_keyword: Option, @@ -87,7 +86,6 @@ impl ComputedValues { pub fn inherit_from(parent: &Self, default: &Self) -> Arc { Arc::new(ComputedValues { custom_properties: parent.custom_properties.clone(), - shareable: parent.shareable, writing_mode: parent.writing_mode, root_font_size: parent.root_font_size, font_size_keyword: parent.font_size_keyword, @@ -102,7 +100,6 @@ impl ComputedValues { } pub fn new(custom_properties: Option>, - shareable: bool, writing_mode: WritingMode, root_font_size: Au, font_size_keyword: Option, @@ -112,7 +109,6 @@ impl ComputedValues { ) -> Self { ComputedValues { custom_properties: custom_properties, - shareable: shareable, writing_mode: writing_mode, root_font_size: root_font_size, font_size_keyword: font_size_keyword, @@ -125,7 +121,6 @@ impl ComputedValues { pub fn default_values(pres_context: RawGeckoPresContextBorrowed) -> Arc { Arc::new(ComputedValues { custom_properties: None, - shareable: true, writing_mode: WritingMode::empty(), // FIXME(bz): This seems dubious root_font_size: longhands::font_size::get_initial_value(), // FIXME(bz): Also seems dubious? font_size_keyword: Some(Default::default()), diff --git a/servo/components/style/properties/properties.mako.rs b/servo/components/style/properties/properties.mako.rs index d00d4b381197..88121aa32f39 100644 --- a/servo/components/style/properties/properties.mako.rs +++ b/servo/components/style/properties/properties.mako.rs @@ -1542,7 +1542,6 @@ pub struct ComputedValues { ${style_struct.ident}: Arc, % endfor custom_properties: Option>, - shareable: bool, /// The writing mode of this computed values struct. pub writing_mode: WritingMode, /// The root element's computed font size. @@ -1555,7 +1554,6 @@ pub struct ComputedValues { impl ComputedValues { /// Construct a `ComputedValues` instance. pub fn new(custom_properties: Option>, - shareable: bool, writing_mode: WritingMode, root_font_size: Au, font_size_keyword: Option, @@ -1565,7 +1563,6 @@ impl ComputedValues { ) -> Self { ComputedValues { custom_properties: custom_properties, - shareable: shareable, writing_mode: writing_mode, root_font_size: root_font_size, font_size_keyword: font_size_keyword, @@ -1889,7 +1886,6 @@ mod lazy_static_module { }), % endfor custom_properties: None, - shareable: true, writing_mode: WritingMode::empty(), root_font_size: longhands::font_size::get_initial_value(), font_size_keyword: Some(Default::default()), @@ -1918,15 +1914,12 @@ static CASCADE_PROPERTY: [CascadePropertyFn; ${len(data.longhands)}] = [ bitflags! { /// A set of flags to tweak the behavior of the `cascade` function. pub flags CascadeFlags: u8 { - /// Whether the `ComputedValues` structure to be constructed should be - /// considered shareable. - const SHAREABLE = 0x01, /// Whether to inherit all styles from the parent. If this flag is not /// present, non-inherited styles are reset to their initial values. - const INHERIT_ALL = 0x02, + const INHERIT_ALL = 0x01, /// Whether to skip any root element and flex/grid item display style /// fixup. - const SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP = 0x04, + const SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP = 0x02, } } @@ -2033,7 +2026,6 @@ pub fn apply_declarations<'a, F, I>(device: &Device, let starting_style = if !flags.contains(INHERIT_ALL) { ComputedValues::new(custom_properties, - flags.contains(SHAREABLE), WritingMode::empty(), inherited_style.root_font_size, inherited_style.font_size_keyword, @@ -2047,7 +2039,6 @@ pub fn apply_declarations<'a, F, I>(device: &Device, ) } else { ComputedValues::new(custom_properties, - flags.contains(SHAREABLE), WritingMode::empty(), inherited_style.root_font_size, inherited_style.font_size_keyword, diff --git a/servo/components/style/traversal.rs b/servo/components/style/traversal.rs index 9712b3d84af7..f221a4c4780f 100644 --- a/servo/components/style/traversal.rs +++ b/servo/components/style/traversal.rs @@ -10,7 +10,7 @@ use atomic_refcell::{AtomicRefCell, AtomicRefMut}; use context::{SharedStyleContext, StyleContext, ThreadLocalStyleContext}; use data::{ElementData, ElementStyles, StoredRestyleHint}; use dom::{DirtyDescendants, NodeInfo, TElement, TNode}; -use matching::{MatchMethods, MatchResults}; +use matching::{MatchMethods, StyleSharingBehavior}; use restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_SELF}; use selector_parser::RestyleDamage; use servo_config::opts; @@ -395,9 +395,7 @@ fn resolve_style_internal(context: &mut StyleContext, } // Compute our style. - let match_results = element.match_element(context, &mut data); - element.cascade_element(context, &mut data, - match_results.primary_is_shareable()); + element.match_and_cascade(context, &mut data, StyleSharingBehavior::Disallow); // Conservatively mark us as having dirty descendants, since there might // be other unstyled siblings we miss when walking straight up the parent @@ -587,7 +585,7 @@ fn compute_style(_traversal: &D, } } - let match_results = match kind { + match kind { MatchAndCascade => { // Ensure the bloom filter is up to date. let dom_depth = @@ -602,42 +600,20 @@ fn compute_style(_traversal: &D, traversal_data.current_dom_depth = Some(dom_depth); context.thread_local.bloom_filter.assert_complete(element); - - - // Perform CSS selector matching. context.thread_local.statistics.elements_matched += 1; - element.match_element(context, &mut data) + + // Perform the matching and cascading. + element.match_and_cascade(context, &mut data, StyleSharingBehavior::Allow); } CascadeWithReplacements(hint) => { - let rule_nodes_changed = + let _rule_nodes_changed = element.cascade_with_replacements(hint, context, &mut data); - MatchResults { - primary_relations: None, - rule_nodes_changed: rule_nodes_changed, - } + element.cascade_primary_and_pseudos(context, &mut data); } CascadeOnly => { - MatchResults { - primary_relations: None, - rule_nodes_changed: false, - } + element.cascade_primary_and_pseudos(context, &mut data); } }; - - // Cascade properties and compute values. - let shareable = match_results.primary_is_shareable(); - unsafe { - element.cascade_element(context, &mut data, shareable); - } - - // If the style is shareable, add it to the LRU cache. - if shareable { - context.thread_local - .style_sharing_candidate_cache - .insert_if_possible(&element, - data.styles().primary.values(), - match_results.primary_relations.unwrap()); - } } fn preprocess_children(traversal: &D,