diff --git a/components/style/context.rs b/components/style/context.rs index 5300b04b498..f36df67a55c 100644 --- a/components/style/context.rs +++ b/components/style/context.rs @@ -7,7 +7,6 @@ #[cfg(feature = "servo")] use animation::Animation; use animation::PropertyAnimation; use app_units::Au; -use arrayvec::ArrayVec; use bloom::StyleBloom; use cache::LRUCache; use data::{EagerPseudoStyles, ElementData}; @@ -19,8 +18,8 @@ use font_metrics::FontMetricsProvider; #[cfg(feature = "servo")] use parking_lot::RwLock; use properties::ComputedValues; use rule_tree::StrongRuleNode; -use selector_parser::{EAGER_PSEUDO_COUNT, PseudoElement, SnapshotMap}; -use selectors::matching::{ElementSelectorFlags, VisitedHandlingMode}; +use selector_parser::{EAGER_PSEUDO_COUNT, SnapshotMap}; +use selectors::matching::ElementSelectorFlags; use shared_lock::StylesheetGuards; use sharing::{ValidationData, StyleSharingCandidateCache}; use std::fmt; @@ -162,36 +161,17 @@ impl<'a> SharedStyleContext<'a> { /// within the `CurrentElementInfo`. At the end of the cascade, they are folded /// down into the main `ComputedValues` to reduce memory usage per element while /// still remaining accessible. -#[derive(Clone)] +#[derive(Clone, Default)] pub struct CascadeInputs { /// The rule node representing the ordered list of rules matched for this /// node. - rules: Option, + pub rules: Option, /// The rule node representing the ordered list of rules matched for this /// node if visited, only computed if there's a relevant link for this /// element. A element's "relevant link" is the element being matched if it /// is a link or the nearest ancestor link. - visited_rules: Option, - - /// The element's computed values if visited, only computed if there's a - /// relevant link for this element. A element's "relevant link" is the - /// element being matched if it is a link or the nearest ancestor link. - /// - /// We also store a reference to this inside the regular ComputedValues to - /// avoid refactoring all APIs to become aware of multiple ComputedValues - /// objects. - visited_values: Option>, -} - -impl Default for CascadeInputs { - fn default() -> Self { - CascadeInputs { - rules: None, - visited_rules: None, - visited_values: None, - } - } + pub visited_rules: Option, } impl CascadeInputs { @@ -200,125 +180,8 @@ impl CascadeInputs { CascadeInputs { rules: style.rules.clone(), visited_rules: style.get_visited_style().and_then(|v| v.rules.clone()), - // Values will be re-cascaded if necessary, so this can be None. - visited_values: None, } } - - /// Whether there are any rules. Rules will be present after unvisited - /// matching or pulled from a previous cascade if no matching is expected. - pub fn has_rules(&self) -> bool { - self.rules.is_some() - } - - /// Gets a mutable reference to the rule node, if any. - pub fn get_rules_mut(&mut self) -> Option<&mut StrongRuleNode> { - self.rules.as_mut() - } - - /// Gets a reference to the rule node, if any. - pub fn get_rules(&self) -> Option<&StrongRuleNode> { - self.rules.as_ref() - } - - /// Gets a reference to the rule node. Panic if the element does not have - /// rule node. - pub fn rules(&self) -> &StrongRuleNode { - self.rules.as_ref().unwrap() - } - - /// Sets the rule node depending on visited mode. - /// Returns whether the rules changed. - pub fn set_rules(&mut self, - visited_handling: VisitedHandlingMode, - rules: StrongRuleNode) - -> bool { - match visited_handling { - VisitedHandlingMode::AllLinksVisitedAndUnvisited => { - unreachable!("We should never try to selector match with \ - AllLinksVisitedAndUnvisited"); - }, - VisitedHandlingMode::AllLinksUnvisited => self.set_unvisited_rules(rules), - VisitedHandlingMode::RelevantLinkVisited => self.set_visited_rules(rules), - } - } - - /// Sets the unvisited rule node, and returns whether it changed. - fn set_unvisited_rules(&mut self, rules: StrongRuleNode) -> bool { - if let Some(ref old_rules) = self.rules { - if *old_rules == rules { - return false - } - } - self.rules = Some(rules); - true - } - - /// Whether there are any visited rules. Visited rules will be present - /// after visited matching or pulled from a previous cascade (assuming there - /// was a relevant link at the time) if no matching is expected. - pub fn has_visited_rules(&self) -> bool { - self.visited_rules.is_some() - } - - /// Gets a reference to the visited rule node, if any. - pub fn get_visited_rules(&self) -> Option<&StrongRuleNode> { - self.visited_rules.as_ref() - } - - /// Gets a mutable reference to the visited rule node, if any. - pub fn get_visited_rules_mut(&mut self) -> Option<&mut StrongRuleNode> { - self.visited_rules.as_mut() - } - - /// Gets a reference to the visited rule node. Panic if the element does not - /// have visited rule node. - pub fn visited_rules(&self) -> &StrongRuleNode { - self.visited_rules.as_ref().unwrap() - } - - /// Sets the visited rule node, and returns whether it changed. - fn set_visited_rules(&mut self, rules: StrongRuleNode) -> bool { - if let Some(ref old_rules) = self.visited_rules { - if *old_rules == rules { - return false - } - } - self.visited_rules = Some(rules); - true - } - - /// Takes the visited rule node. - pub fn take_visited_rules(&mut self) -> Option { - self.visited_rules.take() - } - - /// Whether there are any visited values. - pub fn has_visited_values(&self) -> bool { - self.visited_values.is_some() - } - - /// Gets a reference to the visited computed values. Panic if the element - /// does not have visited computed values. - pub fn visited_values(&self) -> &Arc { - self.visited_values.as_ref().unwrap() - } - - /// Sets the visited computed values. - pub fn set_visited_values(&mut self, values: Arc) { - self.visited_values = Some(values); - } - - /// Take the visited computed values. - pub fn take_visited_values(&mut self) -> Option> { - self.visited_values.take() - } - - /// Clone the visited computed values Arc. Used to store a reference to the - /// visited values inside the regular values. - pub fn clone_visited_values(&self) -> Option> { - self.visited_values.clone() - } } // We manually implement Debug for CascadeInputs so that we can avoid the @@ -363,160 +226,9 @@ impl EagerPseudoCascadeInputs { })) } - /// Returns whether there are any pseudo inputs. - pub fn is_empty(&self) -> bool { - self.0.is_none() - } - - /// Returns a reference to the inputs for a given eager pseudo, if they exist. - pub fn get(&self, pseudo: &PseudoElement) -> Option<&CascadeInputs> { - debug_assert!(pseudo.is_eager()); - self.0.as_ref().and_then(|p| p[pseudo.eager_index()].as_ref()) - } - - /// Returns a mutable reference to the inputs for a given eager pseudo, if they exist. - pub fn get_mut(&mut self, pseudo: &PseudoElement) -> Option<&mut CascadeInputs> { - debug_assert!(pseudo.is_eager()); - self.0.as_mut().and_then(|p| p[pseudo.eager_index()].as_mut()) - } - - /// Returns true if the EagerPseudoCascadeInputs has a inputs 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, inputs: CascadeInputs) { - debug_assert!(!self.has(pseudo)); - if self.0.is_none() { - self.0 = Some(Default::default()); - } - self.0.as_mut().unwrap()[pseudo.eager_index()] = Some(inputs); - } - - /// Removes a pseudo-element inputs if they exist, and returns it. - pub fn take(&mut self, pseudo: &PseudoElement) -> Option { - 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) -> ArrayVec<[PseudoElement; EAGER_PSEUDO_COUNT]> { - let mut v = ArrayVec::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 - } - - /// Adds the unvisited rule node for a given pseudo-element, which may or - /// may not exist. - /// - /// Returns true if the pseudo-element is new. - fn add_unvisited_rules(&mut self, - pseudo: &PseudoElement, - rules: StrongRuleNode) - -> bool { - if let Some(mut inputs) = self.get_mut(pseudo) { - inputs.set_unvisited_rules(rules); - return false - } - let mut inputs = CascadeInputs::default(); - inputs.set_unvisited_rules(rules); - self.insert(pseudo, inputs); - true - } - - /// Remove the unvisited rule node for a given pseudo-element, which may or - /// may not exist. Since removing the rule node implies we don't need any - /// other data for the pseudo, take the entire pseudo if found. - /// - /// Returns true if the pseudo-element was removed. - fn remove_unvisited_rules(&mut self, pseudo: &PseudoElement) -> bool { - self.take(pseudo).is_some() - } - - /// Adds the visited rule node for a given pseudo-element. It is assumed to - /// already exist because unvisited inputs should have been added first. - /// - /// Returns true if the pseudo-element is new. (Always false, but returns a - /// bool for parity with `add_unvisited_rules`.) - fn add_visited_rules(&mut self, - pseudo: &PseudoElement, - rules: StrongRuleNode) - -> bool { - debug_assert!(self.has(pseudo)); - let mut inputs = self.get_mut(pseudo).unwrap(); - inputs.set_visited_rules(rules); - false - } - - /// Remove the visited rule node for a given pseudo-element, which may or - /// may not exist. - /// - /// Returns true if the psuedo-element was removed. (Always false, but - /// returns a bool for parity with `remove_unvisited_rules`.) - fn remove_visited_rules(&mut self, pseudo: &PseudoElement) -> bool { - if let Some(mut inputs) = self.get_mut(pseudo) { - inputs.take_visited_rules(); - } - false - } - - /// Adds a rule node for a given pseudo-element, which may or may not exist. - /// The type of rule node depends on the visited mode. - /// - /// Returns true if the pseudo-element is new. - pub fn add_rules(&mut self, - pseudo: &PseudoElement, - visited_handling: VisitedHandlingMode, - rules: StrongRuleNode) - -> bool { - match visited_handling { - VisitedHandlingMode::AllLinksVisitedAndUnvisited => { - unreachable!("We should never try to selector match with \ - AllLinksVisitedAndUnvisited"); - }, - VisitedHandlingMode::AllLinksUnvisited => { - self.add_unvisited_rules(&pseudo, rules) - }, - VisitedHandlingMode::RelevantLinkVisited => { - self.add_visited_rules(&pseudo, rules) - }, - } - } - - /// Removes a rule node for a given pseudo-element, which may or may not - /// exist. The type of rule node depends on the visited mode. - /// - /// Returns true if the psuedo-element was removed. - pub fn remove_rules(&mut self, - pseudo: &PseudoElement, - visited_handling: VisitedHandlingMode) - -> bool { - match visited_handling { - VisitedHandlingMode::AllLinksVisitedAndUnvisited => { - unreachable!("We should never try to selector match with \ - AllLinksVisitedAndUnvisited"); - }, - VisitedHandlingMode::AllLinksUnvisited => { - self.remove_unvisited_rules(&pseudo) - }, - VisitedHandlingMode::RelevantLinkVisited => { - self.remove_visited_rules(&pseudo) - }, - } + /// Returns the list of rules, if they exist. + pub fn into_array(self) -> Option<[Option; EAGER_PSEUDO_COUNT]> { + self.0 } } @@ -530,56 +242,20 @@ impl EagerPseudoCascadeInputs { #[derive(Clone, Debug)] pub struct ElementCascadeInputs { /// The element's cascade inputs. - pub primary: Option, + pub primary: CascadeInputs, /// A list of the inputs for the element's eagerly-cascaded pseudo-elements. pub pseudos: EagerPseudoCascadeInputs, } -impl Default for ElementCascadeInputs { - /// Construct an empty `ElementCascadeInputs`. - fn default() -> Self { - ElementCascadeInputs { - primary: None, - pseudos: EagerPseudoCascadeInputs(None), - } - } -} - impl ElementCascadeInputs { /// Construct inputs from previous cascade results, if any. pub fn new_from_element_data(data: &ElementData) -> Self { - if !data.has_styles() { - return ElementCascadeInputs::default() - } + debug_assert!(data.has_styles()); ElementCascadeInputs { - primary: Some(CascadeInputs::new_from_style(data.styles.primary())), + primary: CascadeInputs::new_from_style(data.styles.primary()), pseudos: EagerPseudoCascadeInputs::new_from_style(&data.styles.pseudos), } } - - /// Returns whether we have primary inputs. - pub fn has_primary(&self) -> bool { - self.primary.is_some() - } - - /// Gets the primary inputs. Panic if unavailable. - pub fn primary(&self) -> &CascadeInputs { - self.primary.as_ref().unwrap() - } - - /// Gets the mutable primary inputs. Panic if unavailable. - pub fn primary_mut(&mut self) -> &mut CascadeInputs { - self.primary.as_mut().unwrap() - } - - /// Ensure primary inputs exist and create them if they do not. - /// Returns a mutable reference to the primary inputs. - pub fn ensure_primary(&mut self) -> &mut CascadeInputs { - if self.primary.is_none() { - self.primary = Some(CascadeInputs::default()); - } - self.primary.as_mut().unwrap() - } } /// Information about the current element being processed. We group this @@ -598,11 +274,6 @@ pub struct CurrentElementInfo { /// A Vec of possibly expired animations. Used only by Servo. #[allow(dead_code)] pub possibly_expired_animations: Vec, - /// Temporary storage for various intermediate inputs that are eventually - /// used by by the cascade. At the end of the cascade, they are folded down - /// into the main `ComputedValues` to reduce memory usage per element while - /// still remaining accessible. - pub cascade_inputs: ElementCascadeInputs, } /// Statistics gathered during the traversal. We gather statistics on each @@ -906,7 +577,6 @@ impl ThreadLocalStyleContext { is_initial_style: !data.has_styles(), validation_data: ValidationData::default(), possibly_expired_animations: Vec::new(), - cascade_inputs: ElementCascadeInputs::default(), }); } @@ -951,24 +621,6 @@ pub struct StyleContext<'a, E: TElement + 'a> { pub thread_local: &'a mut ThreadLocalStyleContext, } -impl<'a, E: TElement + 'a> StyleContext<'a, E> { - /// Returns a reference to the cascade inputs. Panics if there is no - /// `CurrentElementInfo`. - pub fn cascade_inputs(&self) -> &ElementCascadeInputs { - &self.thread_local.current_element_info - .as_ref().unwrap() - .cascade_inputs - } - - /// Returns a mutable reference to the cascade inputs. Panics if there is - /// no `CurrentElementInfo`. - pub fn cascade_inputs_mut(&mut self) -> &mut ElementCascadeInputs { - &mut self.thread_local.current_element_info - .as_mut().unwrap() - .cascade_inputs - } -} - /// Why we're doing reflow. #[derive(PartialEq, Copy, Clone, Debug)] pub enum ReflowGoal { diff --git a/components/style/matching.rs b/components/style/matching.rs index 0cd55918457..6b20226a620 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -7,46 +7,18 @@ #![allow(unsafe_code)] #![deny(missing_docs)] -use applicable_declarations::ApplicableDeclarationList; -use cascade_info::CascadeInfo; -use context::{CascadeInputs, SelectorFlagsMap, SharedStyleContext, StyleContext}; +use context::{ElementCascadeInputs, SelectorFlagsMap, SharedStyleContext, StyleContext}; use data::{ElementData, ElementStyles, RestyleData}; -use dom::{TElement, TNode}; -use font_metrics::FontMetricsProvider; +use dom::TElement; use invalidation::element::restyle_hints::{RESTYLE_CSS_ANIMATIONS, RESTYLE_CSS_TRANSITIONS}; use invalidation::element::restyle_hints::{RESTYLE_SMIL, RESTYLE_STYLE_ATTRIBUTE}; use invalidation::element::restyle_hints::RestyleHint; -use log::LogLevel::Trace; -use properties::{AnimationRules, CascadeFlags, ComputedValues}; -use properties::{IS_ROOT_ELEMENT, PROHIBIT_DISPLAY_CONTENTS, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP}; -use properties::{VISITED_DEPENDENT_ONLY, cascade}; +use properties::ComputedValues; use properties::longhands::display::computed_value as display; use rule_tree::{CascadeLevel, StrongRuleNode}; -use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl}; -use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode, StyleRelations}; -use selectors::matching::VisitedHandlingMode; -use sharing::StyleSharingBehavior; +use selector_parser::{PseudoElement, RestyleDamage}; +use selectors::matching::ElementSelectorFlags; use stylearc::Arc; -use stylist::RuleInclusion; - -/// Whether we are cascading for an eager pseudo-element or something else. -/// -/// Controls where we inherit styles from, and whether display:contents is -/// prohibited. -#[derive(PartialEq, Copy, Clone, Debug)] -enum CascadeTarget { - /// Inherit from the parent element, as normal CSS dictates, _or_ from the - /// closest non-Native Anonymous element in case this is Native Anonymous - /// Content. display:contents is allowed. - Normal, - /// Inherit from the primary style, this is used while computing eager - /// pseudos, like ::before and ::after when we're traversing the parent. - /// Also prohibits display:contents from having an effect. - /// - /// TODO(emilio) display:contents really should apply to ::before/::after. - /// https://github.com/w3c/csswg-drafts/issues/1345 - EagerPseudo, -} /// Represents the result of comparing an element's old and new style. pub struct StyleDifference { @@ -78,11 +50,12 @@ pub enum StyleChange { /// Whether or not newly computed values for an element need to be cascade /// to children. +#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)] pub enum ChildCascadeRequirement { /// Old and new computed values were the same, or we otherwise know that /// we won't bother recomputing style for children, so we can skip cascading /// the new values into child elements. - CanSkipCascade, + CanSkipCascade = 0, /// Old and new computed values were different, so we must cascade the /// new values to children. /// @@ -91,11 +64,11 @@ pub enum ChildCascadeRequirement { /// property values. When we do that, we can treat `MustCascadeChildren` as /// "must cascade unless we know that changes to these properties can be /// ignored". - MustCascadeChildren, + MustCascadeChildren = 1, /// The same as `MustCascadeChildren`, but for the entire subtree. This is /// used to handle root font-size updates needing to recascade the whole /// document. - MustCascadeDescendants, + MustCascadeDescendants = 2, } bitflags! { @@ -133,129 +106,7 @@ pub enum CascadeVisitedMode { Visited, } -/// Various helper methods to ease navigating the style storage locations -/// depending on the current cascade mode. impl CascadeVisitedMode { - /// Returns whether there is a rule node based on the cascade mode. - /// Rules will be present after matching or pulled from a previous cascade - /// if no matching is expected. For visited, this means rules exist only - /// if a revelant link existed when matching was last done. - fn has_rules(&self, inputs: &CascadeInputs) -> bool { - match *self { - CascadeVisitedMode::Unvisited => inputs.has_rules(), - CascadeVisitedMode::Visited => inputs.has_visited_rules(), - } - } - - /// Returns the rule node based on the cascade mode. - fn rules<'a>(&self, inputs: &'a CascadeInputs) -> &'a StrongRuleNode { - match *self { - CascadeVisitedMode::Unvisited => inputs.rules(), - CascadeVisitedMode::Visited => match inputs.get_visited_rules() { - Some(rules) => rules, - None => inputs.rules(), - } - } - } - - /// Returns a mutable rules node based on the cascade mode, if any. - fn get_rules_mut<'a>(&self, inputs: &'a mut CascadeInputs) -> Option<&'a mut StrongRuleNode> { - match *self { - CascadeVisitedMode::Unvisited => inputs.get_rules_mut(), - CascadeVisitedMode::Visited => inputs.get_visited_rules_mut(), - } - } - - /// Returns the computed values based on the cascade mode. In visited mode, - /// visited values are only returned if they already exist. If they don't, - /// we fallback to the regular, unvisited styles. - pub fn values<'a>(&self, values: &'a Arc) -> &'a Arc { - if *self == CascadeVisitedMode::Visited && values.get_visited_style().is_some() { - return values.visited_style(); - } - - values - } - - /// Set the primary computed values based on the cascade mode. - fn set_primary_values(&self, - styles: &mut ElementStyles, - inputs: &mut CascadeInputs, - values: Arc) { - // Unvisited values are stored in permanent storage on `ElementData`. - // Visited values are stored temporarily in `CascadeInputs` and then - // folded into the unvisited values when they cascade. - match *self { - CascadeVisitedMode::Unvisited => styles.primary = Some(values), - CascadeVisitedMode::Visited => inputs.set_visited_values(values), - } - } - - /// Set the primary computed values based on the cascade mode. - fn set_pseudo_values(&self, - styles: &mut ElementStyles, - inputs: &mut CascadeInputs, - pseudo: &PseudoElement, - values: Arc) { - // Unvisited values are stored in permanent storage on `ElementData`. - // Visited values are stored temporarily in `CascadeInputs` and then - // folded into the unvisited values when they cascade. - match *self { - CascadeVisitedMode::Unvisited => styles.pseudos.set(pseudo, values), - CascadeVisitedMode::Visited => inputs.set_visited_values(values), - } - } - - /// Take the primary computed values based on the cascade mode. - fn take_primary_values(&self, - styles: &mut ElementStyles, - inputs: &mut CascadeInputs) - -> Option> { - // Unvisited values are stored in permanent storage on `ElementData`. - // Visited values are stored temporarily in `CascadeInputs` and then - // folded into the unvisited values when they cascade. - match *self { - CascadeVisitedMode::Unvisited => styles.primary.take(), - CascadeVisitedMode::Visited => inputs.take_visited_values(), - } - } - - /// Take the pseudo computed values based on the cascade mode. - fn take_pseudo_values(&self, - styles: &mut ElementStyles, - inputs: &mut CascadeInputs, - pseudo: &PseudoElement) - -> Option> { - // Unvisited values are stored in permanent storage on `ElementData`. - // Visited values are stored temporarily in `CascadeInputs` and then - // folded into the unvisited values when they cascade. - match *self { - CascadeVisitedMode::Unvisited => styles.pseudos.take(pseudo), - CascadeVisitedMode::Visited => inputs.take_visited_values(), - } - } - - /// Returns whether there might be visited values that should be inserted - /// within the regular computed values based on the cascade mode. - pub fn visited_values_for_insertion(&self) -> bool { - *self == CascadeVisitedMode::Unvisited - } - - /// Returns whether animations should be processed based on the cascade - /// mode. At the moment, it appears we don't need to support animating - /// visited styles. - fn should_process_animations(&self) -> bool { - *self == CascadeVisitedMode::Unvisited - } - - /// Returns whether we should accumulate restyle damage based on the cascade - /// mode. At the moment, it appears we don't need to do so for visited - /// styles. TODO: Verify this is correct as part of - /// https://bugzilla.mozilla.org/show_bug.cgi?id=1364484. - fn should_accumulate_damage(&self) -> bool { - *self == CascadeVisitedMode::Unvisited - } - /// Returns whether the cascade should filter to only visited dependent /// properties based on the cascade mode. pub fn visited_dependent_only(&self) -> bool { @@ -264,419 +115,39 @@ impl CascadeVisitedMode { } trait PrivateMatchMethods: TElement { - /// Returns the closest parent element that doesn't have a display: contents - /// style (and thus generates a box). - /// - /// This is needed to correctly handle blockification of flex and grid - /// items. - /// - /// Returns itself if the element has no parent. In practice this doesn't - /// happen because the root element is blockified per spec, but it could - /// happen if we decide to not blockify for roots of disconnected subtrees, - /// which is a kind of dubious beahavior. - fn layout_parent(&self) -> Self { - let mut current = self.clone(); - loop { - current = match current.traversal_parent() { - Some(el) => el, - None => return current, - }; - - let is_display_contents = - current.borrow_data().unwrap().styles.primary().is_display_contents(); - - if !is_display_contents { - return current; - } - } - } - - /// Get the ComputedValues (if any) for our inheritance parent. - fn get_inherited_style_and_parent(&self) -> ParentElementAndStyle { - let parent_el = self.inheritance_parent(); - let parent_data = parent_el.as_ref().and_then(|e| e.borrow_data()); - let parent_style = parent_data.as_ref().map(|d| { - // Sometimes Gecko eagerly styles things without processing - // pending restyles first. In general we'd like to avoid this, - // but there can be good reasons (for example, needing to - // construct a frame for some small piece of newly-added - // content in order to do something specific with that frame, - // but not wanting to flush all of layout). - debug_assert!(cfg!(feature = "gecko") || - parent_el.unwrap().has_current_styles(d)); - d.styles.primary() - }); - - ParentElementAndStyle { - element: parent_el, - style: parent_style.cloned(), - } - } - - /// A common path for the cascade used by both primary elements and eager - /// pseudo-elements after collecting the appropriate rules to use. - /// - /// `primary_style` is expected to be Some for eager pseudo-elements. - /// - /// `parent_info` is our style parent and its primary style, if - /// it's already been computed. - fn cascade_with_rules(&self, - shared_context: &SharedStyleContext, - font_metrics_provider: &FontMetricsProvider, - rule_node: &StrongRuleNode, - primary_style: Option<&Arc>, - cascade_target: CascadeTarget, - cascade_visited: CascadeVisitedMode, - parent_info: Option<&ParentElementAndStyle>, - visited_values_to_insert: Option>) - -> Arc { - let mut cascade_info = CascadeInfo::new(); - let mut cascade_flags = CascadeFlags::empty(); - if self.skip_root_and_item_based_display_fixup() { - cascade_flags.insert(SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP) - } - if cascade_visited.visited_dependent_only() { - cascade_flags.insert(VISITED_DEPENDENT_ONLY); - } - if self.is_native_anonymous() || cascade_target == CascadeTarget::EagerPseudo { - cascade_flags.insert(PROHIBIT_DISPLAY_CONTENTS); - } else if self.is_root() { - cascade_flags.insert(IS_ROOT_ELEMENT); - } - - // Grab the inherited values. - let parent_el; - let element_and_style; // So parent_el and style_to_inherit_from are known live. - let style_to_inherit_from = match cascade_target { - CascadeTarget::Normal => { - let info = match parent_info { - Some(element_and_style) => element_and_style, - None => { - element_and_style = self.get_inherited_style_and_parent(); - &element_and_style - } - }; - parent_el = info.element; - info.style.as_ref().map(|s| cascade_visited.values(s)) - } - CascadeTarget::EagerPseudo => { - parent_el = Some(self.clone()); - Some(cascade_visited.values(primary_style.unwrap())) - } - }; - - let mut layout_parent_el = parent_el.clone(); - let layout_parent_data; - let mut layout_parent_style = style_to_inherit_from; - if style_to_inherit_from.map_or(false, |s| s.is_display_contents()) { - layout_parent_el = Some(layout_parent_el.unwrap().layout_parent()); - layout_parent_data = layout_parent_el.as_ref().unwrap().borrow_data().unwrap(); - layout_parent_style = Some(cascade_visited.values(layout_parent_data.styles.primary())); - } - - let style_to_inherit_from = style_to_inherit_from.map(|x| &**x); - let layout_parent_style = layout_parent_style.map(|x| &**x); - - // Propagate the "can be fragmented" bit. It would be nice to - // encapsulate this better. - // - // Note that this is technically not needed for pseudos since we already - // do that when we resolve the non-pseudo style, but it doesn't hurt - // anyway. - // - // TODO(emilio): This is servo-only, move somewhere else? - if let Some(ref p) = layout_parent_style { - let can_be_fragmented = - p.is_multicol() || - layout_parent_el.as_ref().unwrap().as_node().can_be_fragmented(); - unsafe { self.as_node().set_can_be_fragmented(can_be_fragmented); } - } - - // Invoke the cascade algorithm. - let values = - Arc::new(cascade(shared_context.stylist.device(), - rule_node, - &shared_context.guards, - style_to_inherit_from, - layout_parent_style, - visited_values_to_insert, - Some(&mut cascade_info), - font_metrics_provider, - cascade_flags, - shared_context.quirks_mode)); - - cascade_info.finish(&self.as_node()); - values - } - - /// A common path for the cascade used by both primary elements and eager - /// pseudo-elements. - /// - /// `primary_style` is expected to be Some for eager pseudo-elements. - /// - /// `parent_info` is our style parent and its primary style, if - /// it's already been computed. - fn cascade_internal(&self, - context: &StyleContext, - primary_style: Option<&Arc>, - primary_inputs: &CascadeInputs, - eager_pseudo_inputs: Option<&CascadeInputs>, - parent_info: Option<&ParentElementAndStyle>, - cascade_visited: CascadeVisitedMode) - -> Arc { - if let Some(pseudo) = self.implemented_pseudo_element() { - debug_assert!(eager_pseudo_inputs.is_none()); - - // This is an element-backed pseudo, just grab the styles from the - // parent if it's eager, and recascade otherwise. - // - // We also recascade if the eager pseudo-style has any animation - // rules, because we don't cascade those during the eager traversal. - // - // We could make that a bit better if the complexity cost is not too - // big, but given further restyles are posted directly to - // pseudo-elements, it doesn't seem worth the effort at a glance. - // - // For the same reason as described in match_primary, if we are - // computing default styles, we aren't guaranteed the parent - // will have eagerly computed our styles, so we just handled it - // below like a lazy pseudo. - let only_default_rules = context.shared.traversal_flags.for_default_styles(); - if pseudo.is_eager() && !only_default_rules { - debug_assert!(pseudo.is_before_or_after()); - let parent = self.parent_element().unwrap(); - if !parent.may_have_animations() || - self.get_animation_rules().is_empty() { - let parent_data = parent.borrow_data().unwrap(); - let pseudo_style = - parent_data.styles.pseudos.get(&pseudo).unwrap(); - let values = cascade_visited.values(pseudo_style); - return values.clone() - } - } - } - - // Find possible visited computed styles to insert within the regular - // computed values we are about to create. - let visited_values_to_insert = if cascade_visited.visited_values_for_insertion() { - match eager_pseudo_inputs { - Some(ref s) => s.clone_visited_values(), - None => primary_inputs.clone_visited_values(), - } - } else { - None - }; - - // Grab the rule node. - let inputs = eager_pseudo_inputs.unwrap_or(primary_inputs); - // We'd really like to take the rules here to avoid refcount traffic, - // but animation's usage of `apply_declarations` make this tricky. - // See bug 1375525. - let rule_node = cascade_visited.rules(inputs); - let cascade_target = if eager_pseudo_inputs.is_some() { - CascadeTarget::EagerPseudo - } else { - CascadeTarget::Normal - }; - - self.cascade_with_rules(context.shared, - &context.thread_local.font_metrics_provider, - rule_node, - primary_style, - cascade_target, - cascade_visited, - parent_info, - visited_values_to_insert) - } - - /// Computes values and damage for the primary style of an element, setting - /// them on the ElementData. - /// - /// `parent_info` is our style parent and its primary style. - fn cascade_primary(&self, - context: &mut StyleContext, - data: &mut ElementData, - important_rules_changed: bool, - parent_info: &ParentElementAndStyle, - cascade_visited: CascadeVisitedMode) - -> ChildCascadeRequirement { - debug!("Cascade primary for {:?}, visited: {:?}", self, cascade_visited); - - let mut old_values = cascade_visited.take_primary_values( - &mut data.styles, - context.cascade_inputs_mut().primary_mut() - ); - - let mut new_values = { - let primary_inputs = context.cascade_inputs().primary(); - - // If there was no relevant link at the time of matching, we won't - // have any visited rules, so there may not be anything do for the - // visited case. This early return is especially important for the - // `cascade_primary_and_pseudos` path since we rely on the state of - // some previous matching run. - // - // Note that we cannot take this early return if our parent has - // visited style, because then we too have visited style. - if !cascade_visited.has_rules(primary_inputs) && !parent_info.has_visited_style() { - return ChildCascadeRequirement::CanSkipCascade - } - - // Compute the new values. - self.cascade_internal(context, - None, - primary_inputs, - None, - /* parent_info = */ None, - cascade_visited) - }; - - // NB: Animations for pseudo-elements in Gecko are handled while - // traversing the pseudo-elements themselves. - if !context.shared.traversal_flags.for_animation_only() && - cascade_visited.should_process_animations() { - self.process_animations(context, - &mut old_values, - &mut new_values, - important_rules_changed); - } - - let mut child_cascade_requirement = - ChildCascadeRequirement::CanSkipCascade; - if cascade_visited.should_accumulate_damage() { - child_cascade_requirement = - self.accumulate_damage(&context.shared, - &mut data.restyle, - old_values.as_ref().map(|v| v.as_ref()), - &new_values, - None); - - // Handle root font-size changes. - // - // TODO(emilio): This should arguably be outside of the path for - // getComputedStyle/getDefaultComputedStyle, but it's unclear how to - // do it without duplicating a bunch of code. - if self.is_root() && !self.is_native_anonymous() && - !context.shared.traversal_flags.for_default_styles() { - let device = context.shared.stylist.device(); - let new_font_size = new_values.get_font().clone_font_size(); - - // If the root font-size changed since last time, and something - // in the document did use rem units, ensure we recascade the - // entire tree. - if old_values.map_or(true, |v| v.get_font().clone_font_size() != new_font_size) { - // FIXME(emilio): This can fire when called from a document - // from the bfcache (bug 1376897). - debug_assert!(self.owner_doc_matches_for_testing(device)); - device.set_root_font_size(new_font_size); - if device.used_root_font_size() { - child_cascade_requirement = ChildCascadeRequirement::MustCascadeDescendants; - } - } - } - } - - // If there were visited values to insert, ensure they do in fact exist - // inside the new values. - debug_assert!(!cascade_visited.visited_values_for_insertion() || - context.cascade_inputs().primary().has_visited_values() == - new_values.has_visited_style()); - - // Set the new computed values. - let primary_inputs = context.cascade_inputs_mut().primary_mut(); - cascade_visited.set_primary_values(&mut data.styles, - primary_inputs, - new_values); - - // Return whether the damage indicates we must cascade new inherited - // values into children. - child_cascade_requirement - } - - /// Computes values and damage for the eager pseudo-element styles of an - /// element, setting them on the ElementData. - fn cascade_eager_pseudo(&self, - context: &mut StyleContext, - data: &mut ElementData, - pseudo: &PseudoElement, - cascade_visited: CascadeVisitedMode) { - debug_assert!(pseudo.is_eager()); - - let old_values = cascade_visited.take_pseudo_values( - &mut data.styles, - context.cascade_inputs_mut().pseudos.get_mut(pseudo).unwrap(), - pseudo - ); - - let new_values = { - let pseudo_inputs = context.cascade_inputs().pseudos - .get(pseudo).unwrap(); - - // If there was no relevant link at the time of matching, we won't - // have any visited rules, so there may not be anything do for the - // visited case. This early return is especially important for the - // `cascade_primary_and_pseudos` path since we rely on the state of - // some previous matching run. - if !cascade_visited.has_rules(pseudo_inputs) { - return - } - - // Primary inputs should already have rules populated since it's - // always processed before eager pseudos. - let primary_inputs = context.cascade_inputs().primary(); - debug_assert!(cascade_visited.has_rules(primary_inputs)); - - self.cascade_internal(context, - data.styles.get_primary(), - primary_inputs, - Some(pseudo_inputs), - /* parent_info = */ None, - cascade_visited) - }; - - if cascade_visited.should_accumulate_damage() { - self.accumulate_damage(&context.shared, - &mut data.restyle, - old_values.as_ref().map(|v| v.as_ref()), - &new_values, - Some(pseudo)); - } - - let pseudo_inputs = context.cascade_inputs_mut().pseudos - .get_mut(pseudo).unwrap(); - cascade_visited.set_pseudo_values(&mut data.styles, - pseudo_inputs, - pseudo, - new_values); - } - - /// get_after_change_style removes the transition rules from the ComputedValues. /// If there is no transition rule in the ComputedValues, it returns None. #[cfg(feature = "gecko")] - fn get_after_change_style(&self, - context: &mut StyleContext, - primary_style: &Arc) - -> Option> { + fn get_after_change_style( + &self, + context: &mut StyleContext, + primary_style: &Arc + ) -> Option> { + use context::CascadeInputs; + use style_resolver::StyleResolverForElement; + use stylist::RuleInclusion; + let rule_node = primary_style.rules(); let without_transition_rules = context.shared.stylist.rule_tree().remove_transition_rule_if_applicable(rule_node); if without_transition_rules == *rule_node { - // We don't have transition rule in this case, so return None to let the caller - // use the original ComputedValues. + // We don't have transition rule in this case, so return None to let + // the caller use the original ComputedValues. return None; } - // This currently passes through visited styles, if they exist. - // When fixing bug 868975, compute after change for visited styles as - // well, along with updating the rest of the animation processing. - Some(self.cascade_with_rules(context.shared, - &context.thread_local.font_metrics_provider, - &without_transition_rules, - Some(primary_style), - CascadeTarget::Normal, - CascadeVisitedMode::Unvisited, - /* parent_info = */ None, - primary_style.get_visited_style().cloned())) + // FIXME(bug 868975): We probably need to transition visited style as + // well. + let inputs = + CascadeInputs { + rules: Some(without_transition_rules), + visited_rules: primary_style.get_visited_style().and_then(|s| s.rules.clone()), + }; + + let style = + StyleResolverForElement::new(*self, context, RuleInclusion::All) + .cascade_style_and_visited_with_default_parents(inputs); + + Some(style) } #[cfg(feature = "gecko")] @@ -788,6 +259,7 @@ trait PrivateMatchMethods: TElement { new_values: &mut Arc, _important_rules_changed: bool) { use animation; + use dom::TNode; let possibly_expired_animations = &mut context.thread_local.current_element_info.as_mut().unwrap() @@ -819,6 +291,7 @@ trait PrivateMatchMethods: TElement { } } + /// Computes and applies non-redundant damage. #[cfg(feature = "gecko")] fn accumulate_damage_for(&self, @@ -829,7 +302,6 @@ trait PrivateMatchMethods: TElement { pseudo: Option<&PseudoElement>) -> ChildCascadeRequirement { use properties::computed_value_flags::*; - // Don't accumulate damage if we're in a restyle for reconstruction. if shared_context.traversal_flags.for_reconstruct() { return ChildCascadeRequirement::MustCascadeChildren; @@ -889,8 +361,9 @@ trait PrivateMatchMethods: TElement { context: &SharedStyleContext, style: &mut Arc, possibly_expired_animations: &mut Vec<::animation::PropertyAnimation>, - font_metrics: &FontMetricsProvider) { + font_metrics: &::font_metrics::FontMetricsProvider) { use animation::{self, Animation}; + use dom::TNode; // Finish any expired transitions. let this_opaque = self.as_node().opaque(); @@ -931,457 +404,167 @@ trait PrivateMatchMethods: TElement { impl PrivateMatchMethods for E {} -/// A struct that holds an element we inherit from and its ComputedValues. -#[derive(Debug)] -struct ParentElementAndStyle { - /// Our style parent element. - element: Option, - /// Element's primary ComputedValues. Not a borrow because we can't prove - /// that the thing hanging off element won't change while we're passing this - /// struct around. - style: Option>, -} - -impl ParentElementAndStyle { - fn has_visited_style(&self) -> bool { - self.style.as_ref().map_or(false, |v| { v.get_visited_style().is_some() }) - } -} - -/// Collects the outputs of the primary matching process, including the rule -/// node and other associated data. -#[derive(Debug)] -pub struct MatchingResults { - /// Whether the rules changed. - rules_changed: bool, - /// Whether there are any changes of important rules overriding animations. - important_rules_overriding_animation_changed: bool, - /// Records certains relations between elements noticed during matching (and - /// also extended after matching). - relations: StyleRelations, - /// Whether we encountered a "relevant link" while matching _any_ selector - /// for this element. (This differs from `RelevantLinkStatus` which tracks - /// the status for the _current_ selector only.) - relevant_link_found: bool, -} - -impl MatchingResults { - /// Create `MatchingResults` with only the basic required outputs. - fn new(rules_changed: bool, important_rules: bool) -> Self { - Self { - rules_changed: rules_changed, - important_rules_overriding_animation_changed: important_rules, - relations: StyleRelations::default(), - relevant_link_found: false, - } - } - - /// Create `MatchingResults` from the output fields of `MatchingContext`. - fn new_from_context(rules_changed: bool, - important_rules: bool, - context: MatchingContext) - -> Self { - Self { - rules_changed: rules_changed, - important_rules_overriding_animation_changed: important_rules, - relations: context.relations, - relevant_link_found: context.relevant_link_found, - } - } -} - /// The public API that elements expose for selector matching. pub trait MatchMethods : TElement { - /// 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 - ) -> ChildCascadeRequirement { - debug!("Match and cascade for {:?}", self); - - // Perform selector matching for the primary style. - let primary_results = - self.match_primary(context, data, VisitedHandlingMode::AllLinksUnvisited); - let important_rules_changed = - primary_results.important_rules_overriding_animation_changed; - - // If there's a relevant link involved, match and cascade primary styles - // as if the link is visited as well. This is done before the regular - // cascade because the visited ComputedValues are placed within the - // regular ComputedValues, which is immutable after the cascade. - let relevant_link_found = primary_results.relevant_link_found; - if relevant_link_found { - self.match_primary(context, data, VisitedHandlingMode::RelevantLinkVisited); - } - - // Even if there is no relevant link, we need to cascade visited styles - // if our parent has visited styles. - let parent_and_styles = self.get_inherited_style_and_parent(); - if relevant_link_found || parent_and_styles.has_visited_style() { - self.cascade_primary( - context, - data, - important_rules_changed, - &parent_and_styles, - CascadeVisitedMode::Visited - ); - } - - // Cascade properties and compute primary values. - let child_cascade_requirement = - self.cascade_primary( - context, - data, - important_rules_changed, - &parent_and_styles, - CascadeVisitedMode::Unvisited - ); - - // Match and cascade eager pseudo-elements. - if !data.styles.is_display_none() { - self.match_pseudos(context, data, VisitedHandlingMode::AllLinksUnvisited); - - // If there's a relevant link involved, match and cascade eager - // pseudo-element styles as if the link is visited as well. - // This runs after matching for regular styles because matching adds - // each pseudo as needed to the PseudoMap, and this runs before - // cascade for regular styles because the visited ComputedValues - // are placed within the regular ComputedValues, which is immutable - // after the cascade. - if relevant_link_found { - self.match_pseudos(context, data, VisitedHandlingMode::RelevantLinkVisited); - self.cascade_pseudos(context, data, CascadeVisitedMode::Visited); - } - - self.cascade_pseudos(context, data, CascadeVisitedMode::Unvisited); - } - - // If the style is shareable, add it to the LRU cache. - if sharing == StyleSharingBehavior::Allow { - // If we previously tried to match this element against the cache, - // the revalidation match results will already be cached. Otherwise - // we'll have None, and compute them later on-demand. - // - // If we do have the results, grab them here to satisfy the borrow - // checker. - let validation_data = - context.thread_local - .current_element_info - .as_mut().unwrap() - .validation_data - .take(); - - let dom_depth = context.thread_local.bloom_filter.matching_depth(); - context.thread_local - .style_sharing_candidate_cache - .insert_if_possible(self, - data.styles.primary(), - primary_results.relations, - validation_data, - dom_depth); - } - - child_cascade_requirement - } - - /// Performs the cascade, without matching. - fn cascade_primary_and_pseudos(&self, - context: &mut StyleContext, - mut data: &mut ElementData, - important_rules_changed: bool) - -> ChildCascadeRequirement - { - // If there's a relevant link involved, cascade styles as if the link is - // visited as well. This is done before the regular cascade because the - // visited ComputedValues are placed within the regular ComputedValues, - // which is immutable after the cascade. If there aren't any visited - // rules, these calls will return without cascading. - let parent_and_styles = self.get_inherited_style_and_parent(); - self.cascade_primary(context, &mut data, important_rules_changed, - &parent_and_styles, - CascadeVisitedMode::Visited); - let child_cascade_requirement = - self.cascade_primary(context, &mut data, important_rules_changed, - &parent_and_styles, - CascadeVisitedMode::Unvisited); - self.cascade_pseudos(context, &mut data, CascadeVisitedMode::Visited); - self.cascade_pseudos(context, &mut data, CascadeVisitedMode::Unvisited); - child_cascade_requirement - } - - /// Runs selector matching to (re)compute the primary rule node for this - /// element. + /// Returns the closest parent element that doesn't have a display: contents + /// style (and thus generates a box). /// - /// Returns `MatchingResults` with the new rules and other associated data - /// from the matching process. - fn match_primary( + /// This is needed to correctly handle blockification of flex and grid + /// items. + /// + /// Returns itself if the element has no parent. In practice this doesn't + /// happen because the root element is blockified per spec, but it could + /// happen if we decide to not blockify for roots of disconnected subtrees, + /// which is a kind of dubious beahavior. + fn layout_parent(&self) -> Self { + let mut current = self.clone(); + loop { + current = match current.traversal_parent() { + Some(el) => el, + None => return current, + }; + + let is_display_contents = + current.borrow_data().unwrap().styles.primary().is_display_contents(); + + if !is_display_contents { + return current; + } + } + } + + /// Updates the styles with the new ones, diffs them, and stores the restyle + /// damage. + fn finish_restyle( &self, context: &mut StyleContext, - data: &mut ElementData, - visited_handling: VisitedHandlingMode - ) -> MatchingResults { - debug!("Match primary for {:?}, visited: {:?}", self, visited_handling); + mut data: &mut ElementData, + mut new_styles: ElementStyles, + important_rules_changed: bool, + ) -> ChildCascadeRequirement { + use dom::TNode; + use std::cmp; + use std::mem; - let mut primary_inputs = context.thread_local.current_element_info - .as_mut().unwrap() - .cascade_inputs.ensure_primary(); + debug_assert!(new_styles.primary.is_some(), "How did that happen?"); - let only_default_rules = context.shared.traversal_flags.for_default_styles(); - let implemented_pseudo = self.implemented_pseudo_element(); - if let Some(ref pseudo) = implemented_pseudo { - // We don't expect to match against a non-canonical pseudo-element. - debug_assert_eq!(*pseudo, pseudo.canonical()); - if pseudo.is_eager() && !only_default_rules { - // If it's an eager element-backed pseudo, we can generally just - // grab the matched rules from the parent, and then update - // animations. - // - // However, if we're computing default styles, then we might - // have traversed to this pseudo-implementing element without - // any pseudo styles stored on the parent. For example, if - // document-level style sheets cause the element to exist, due - // to ::before rules, then those rules won't be found when - // computing default styles on the parent, so we won't have - // bothered to store pseudo styles there. In this case, we just - // treat it like a lazily computed pseudo. - let parent = self.parent_element().unwrap(); - let parent_data = parent.borrow_data().unwrap(); - let pseudo_style = - parent_data.styles.pseudos.get(&pseudo).unwrap(); - let mut rules = pseudo_style.rules().clone(); - if parent.may_have_animations() { - let animation_rules = self.get_animation_rules(); + if !context.shared.traversal_flags.for_animation_only() { + self.process_animations( + context, + &mut data.styles.primary, + &mut new_styles.primary.as_mut().unwrap(), + important_rules_changed, + ); + } - // Handle animations here. - if let Some(animation_rule) = animation_rules.0 { - let animation_rule_node = - context.shared.stylist.rule_tree() - .update_rule_at_level(CascadeLevel::Animations, - Some(&animation_rule), - &mut rules, - &context.shared.guards); - if let Some(node) = animation_rule_node { - rules = node; - } - } + // First of all, update the styles. + let old_styles = mem::replace(&mut data.styles, new_styles); - if let Some(animation_rule) = animation_rules.1 { - let animation_rule_node = - context.shared.stylist.rule_tree() - .update_rule_at_level(CascadeLevel::Transitions, - Some(&animation_rule), - &mut rules, - &context.shared.guards); - if let Some(node) = animation_rule_node { - rules = node; - } - } - } - let important_rules_changed = - self.has_animations() && - data.has_styles() && - data.important_rules_are_different(&rules, - &context.shared.guards); + // Propagate the "can be fragmented" bit. It would be nice to + // encapsulate this better. + // + // Note that this is technically not needed for pseudos since we already + // do that when we resolve the non-pseudo style, but it doesn't hurt + // anyway. + if cfg!(feature = "servo") { + let layout_parent = + self.inheritance_parent().map(|e| e.layout_parent()); + let layout_parent_data = + layout_parent.as_ref().and_then(|e| e.borrow_data()); + let layout_parent_style = + layout_parent_data.as_ref().map(|d| d.styles.primary()); - let rules_changed = primary_inputs.set_rules(visited_handling, rules); - - return MatchingResults::new(rules_changed, important_rules_changed) + if let Some(ref p) = layout_parent_style { + let can_be_fragmented = + p.is_multicol() || + layout_parent.as_ref().unwrap().as_node().can_be_fragmented(); + unsafe { self.as_node().set_can_be_fragmented(can_be_fragmented); } } } - let mut applicable_declarations = ApplicableDeclarationList::new(); - - let stylist = &context.shared.stylist; - let style_attribute = self.style_attribute(); - - let map = &mut context.thread_local.selector_flags; - let mut set_selector_flags = |element: &Self, flags: ElementSelectorFlags| { - self.apply_selector_flags(map, element, flags); - }; - - let rule_inclusion = if only_default_rules { - RuleInclusion::DefaultOnly - } else { - RuleInclusion::All - }; - - let bloom_filter = context.thread_local.bloom_filter.filter(); - let mut matching_context = - MatchingContext::new_for_visited(MatchingMode::Normal, - Some(bloom_filter), - visited_handling, - context.shared.quirks_mode); - - { - let smil_override = self.get_smil_override(); - let animation_rules = self.get_animation_rules(); - - // Compute the primary rule node. - stylist.push_applicable_declarations(self, - implemented_pseudo.as_ref(), - style_attribute, - smil_override, - animation_rules, - rule_inclusion, - &mut applicable_declarations, - &mut matching_context, - &mut set_selector_flags); + // Don't accumulate damage if we're in a restyle for reconstruction. + if context.shared.traversal_flags.for_reconstruct() { + return ChildCascadeRequirement::MustCascadeChildren; } - self.unset_dirty_style_attribute(); - let primary_rule_node = stylist.rule_tree().compute_rule_node( - &mut applicable_declarations, - &context.shared.guards + let new_primary_style = data.styles.primary.as_ref().unwrap(); + + let mut cascade_requirement = ChildCascadeRequirement::CanSkipCascade; + if self.is_root() && !self.is_native_anonymous() { + let device = context.shared.stylist.device(); + let new_font_size = new_primary_style.get_font().clone_font_size(); + + if old_styles.primary.as_ref().map_or(true, |s| s.get_font().clone_font_size() != new_font_size) { + debug_assert!(self.owner_doc_matches_for_testing(device)); + device.set_root_font_size(new_font_size); + // If the root font-size changed since last time, and something + // in the document did use rem units, ensure we recascade the + // entire tree. + if device.used_root_font_size() { + cascade_requirement = ChildCascadeRequirement::MustCascadeDescendants; + } + } + } + + // Also, don't do anything if there was no style. + let old_primary_style = match old_styles.primary { + Some(s) => s, + None => return ChildCascadeRequirement::MustCascadeChildren, + }; + + cascade_requirement = cmp::max( + cascade_requirement, + self.accumulate_damage_for( + context.shared, + &mut data.restyle, + &old_primary_style, + new_primary_style, + None, + ) ); - if log_enabled!(Trace) { - trace!("Matched rules:"); - for rn in primary_rule_node.self_and_ancestors() { - let source = rn.style_source(); - if source.is_some() { - trace!(" > {:?}", source); + if data.styles.pseudos.is_empty() && old_styles.pseudos.is_empty() { + return cascade_requirement; + } + + // If it matched a different number of pseudos, reconstruct. + if data.styles.pseudos.is_empty() != old_styles.pseudos.is_empty() { + data.restyle.damage |= RestyleDamage::reconstruct(); + return cascade_requirement; + } + + let pseudo_styles = + old_styles.pseudos.as_array().unwrap().iter().zip( + data.styles.pseudos.as_array().unwrap().iter()); + + for (i, (old, new)) in pseudo_styles.enumerate() { + match (old, new) { + (&Some(ref old), &Some(ref new)) => { + self.accumulate_damage_for( + context.shared, + &mut data.restyle, + old, + new, + Some(&PseudoElement::from_eager_index(i)), + ); + } + (&None, &None) => {}, + _ => { + data.restyle.damage |= RestyleDamage::reconstruct(); + return cascade_requirement; } } } - let important_rules_changed = - self.has_animations() && - data.has_styles() && - data.important_rules_are_different( - &primary_rule_node, - &context.shared.guards - ); - - let rules_changed = primary_inputs.set_rules(visited_handling, primary_rule_node); - - MatchingResults::new_from_context(rules_changed, - important_rules_changed, - matching_context) + cascade_requirement } - /// Runs selector matching to (re)compute eager pseudo-element rule nodes - /// for this element. - fn match_pseudos(&self, - context: &mut StyleContext, - data: &mut ElementData, - visited_handling: VisitedHandlingMode) - { - debug!("Match pseudos for {:?}, visited: {:?}", self, visited_handling); - - if self.implemented_pseudo_element().is_some() { - // Element pseudos can't have any other pseudo. - return; - } - - let mut applicable_declarations = ApplicableDeclarationList::new(); - - // 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_inclusion = if context.shared.traversal_flags.for_default_styles() { - RuleInclusion::DefaultOnly - } else { - RuleInclusion::All - }; - - // Compute rule nodes for eagerly-cascaded pseudo-elements. - let mut matches_different_pseudos = false; - SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| { - // For eager pseudo-elements, we only try to match visited rules if - // there are also unvisited rules. (This matches Gecko's behavior - // for probing pseudo-elements, and for eager pseudo-elements Gecko - // does not try to resolve style if the probe says there isn't any.) - if visited_handling == VisitedHandlingMode::RelevantLinkVisited && - !context.cascade_inputs().pseudos.has(&pseudo) { - return - } - - if self.may_generate_pseudo(&pseudo, data.styles.primary()) { - let bloom_filter = context.thread_local.bloom_filter.filter(); - - let mut matching_context = - MatchingContext::new_for_visited(MatchingMode::ForStatelessPseudoElement, - Some(bloom_filter), - visited_handling, - context.shared.quirks_mode); - - let map = &mut context.thread_local.selector_flags; - let mut set_selector_flags = |element: &Self, flags: ElementSelectorFlags| { - self.apply_selector_flags(map, element, flags); - }; - - debug_assert!(applicable_declarations.is_empty()); - // NB: We handle animation rules for ::before and ::after when - // traversing them. - stylist.push_applicable_declarations(self, - Some(&pseudo), - None, - None, - AnimationRules(None, None), - rule_inclusion, - &mut applicable_declarations, - &mut matching_context, - &mut set_selector_flags); - } - - let pseudos = &mut context.thread_local.current_element_info - .as_mut().unwrap() - .cascade_inputs.pseudos; - if !applicable_declarations.is_empty() { - let rules = stylist.rule_tree().compute_rule_node( - &mut applicable_declarations, - &guards - ); - matches_different_pseudos |= !data.styles.pseudos.has(&pseudo); - pseudos.add_rules( - &pseudo, - visited_handling, - rules - ); - } else { - matches_different_pseudos |= data.styles.pseudos.has(&pseudo); - pseudos.remove_rules( - &pseudo, - visited_handling - ); - data.styles.pseudos.take(&pseudo); - } - }); - - if matches_different_pseudos && data.restyle.is_restyle() { - // Any changes to the matched pseudo-elements trigger - // reconstruction. - data.restyle.damage |= RestyleDamage::reconstruct(); - } - } /// 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. + /// TODO(emilio): This is somewhat inefficient, because it doesn't take + /// advantage of us knowing that the traversal is sequential. fn apply_selector_flags(&self, map: &mut SelectorFlagsMap, element: &Self, @@ -1458,13 +641,22 @@ pub trait MatchMethods : TElement { &self, replacements: RestyleHint, context: &mut StyleContext, + cascade_inputs: &mut ElementCascadeInputs, ) -> bool { let mut result = false; - result |= self.replace_rules_internal(replacements, context, - CascadeVisitedMode::Unvisited); + result |= self.replace_rules_internal( + replacements, + context, + CascadeVisitedMode::Unvisited, + cascade_inputs, + ); if !context.shared.traversal_flags.for_animation_only() { - result |= self.replace_rules_internal(replacements, context, - CascadeVisitedMode::Visited); + result |= self.replace_rules_internal( + replacements, + context, + CascadeVisitedMode::Visited, + cascade_inputs + ); } result } @@ -1477,7 +669,8 @@ pub trait MatchMethods : TElement { &self, replacements: RestyleHint, context: &mut StyleContext, - cascade_visited: CascadeVisitedMode + cascade_visited: CascadeVisitedMode, + cascade_inputs: &mut ElementCascadeInputs, ) -> bool { use properties::PropertyDeclarationBlock; use shared_lock::Locked; @@ -1488,8 +681,13 @@ pub trait MatchMethods : TElement { let stylist = &context.shared.stylist; let guards = &context.shared.guards; - let mut primary_inputs = context.cascade_inputs_mut().primary_mut(); - let primary_rules = match cascade_visited.get_rules_mut(primary_inputs) { + let primary_rules = + match cascade_visited { + CascadeVisitedMode::Unvisited => cascade_inputs.primary.rules.as_mut(), + CascadeVisitedMode::Visited => cascade_inputs.primary.visited_rules.as_mut(), + }; + + let primary_rules = match primary_rules { Some(r) => r, None => return false, }; @@ -1518,6 +716,7 @@ pub trait MatchMethods : TElement { result |= replace_rule_node(CascadeLevel::StyleAttributeImportant, style_attribute, primary_rules); + // FIXME(emilio): Still a hack! self.unset_dirty_style_attribute(); } return result; @@ -1564,12 +763,12 @@ pub trait MatchMethods : TElement { /// Given the old and new style of this element, and whether it's a /// pseudo-element, compute the restyle damage used to determine which /// kind of layout or painting operations we'll need. - fn compute_style_difference(&self, - old_values: &ComputedValues, - new_values: &Arc, - pseudo: Option<&PseudoElement>) - -> StyleDifference - { + fn compute_style_difference( + &self, + old_values: &ComputedValues, + new_values: &Arc, + pseudo: Option<&PseudoElement> + ) -> StyleDifference { debug_assert!(pseudo.map_or(true, |p| p.is_eager())); if let Some(source) = self.existing_style_for_restyle_damage(old_values, pseudo) { return RestyleDamage::compute_style_difference(source, new_values) @@ -1642,55 +841,36 @@ pub trait MatchMethods : TElement { StyleDifference::new(damage, StyleChange::Changed) } - /// Performs the cascade for the element's eager pseudos. - fn cascade_pseudos(&self, - context: &mut StyleContext, - mut data: &mut ElementData, - cascade_visited: CascadeVisitedMode) - { - debug!("Cascade pseudos for {:?}, visited: {:?}", self, - cascade_visited); - // Note that we've already set up the map of matching pseudo-elements - // 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 cascade function. - let matched_pseudos = context.cascade_inputs().pseudos.keys(); - for pseudo in matched_pseudos { - debug!("Cascade pseudo for {:?} {:?}", self, pseudo); - self.cascade_eager_pseudo(context, data, &pseudo, cascade_visited); - } - } - /// Returns computed values without animation and transition rules. - fn get_base_style(&self, - shared_context: &SharedStyleContext, - font_metrics_provider: &FontMetricsProvider, - primary_style: &Arc, - pseudo_style: Option<&Arc>) - -> Arc { - let relevant_style = pseudo_style.unwrap_or(primary_style); - let rule_node = relevant_style.rules(); + fn get_base_style( + &self, + context: &mut StyleContext, + style: &Arc, + ) -> Arc { + use style_resolver::StyleResolverForElement; + + let rule_node = style.rules.as_ref().unwrap(); let without_animation_rules = - shared_context.stylist.rule_tree().remove_animation_rules(rule_node); + context.shared.stylist.rule_tree().remove_animation_rules(rule_node); if without_animation_rules == *rule_node { - // Note that unwrapping here is fine, because the style is - // only incomplete during the styling process. - return relevant_style.clone(); + // Note that unwrapping here is fine, because the style is only + // incomplete during the styling process. + return style.clone(); } // This currently ignores visited styles, which seems acceptable, // as existing browsers don't appear to animate visited styles. - self.cascade_with_rules(shared_context, - font_metrics_provider, - &without_animation_rules, - Some(primary_style), - CascadeTarget::Normal, - CascadeVisitedMode::Unvisited, - /* parent_info = */ None, - None) - } + let inputs = + CascadeInputs { + rules: Some(without_animation_rules), + visited_rules: None, + }; + let style = + StyleResolverForElement::new(*self, context, RuleInclusion::All) + .cascade_style_and_visited_with_default_parents(inputs); + style + } } impl MatchMethods for E {} diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index de94c5a0fca..58f0b3bcc68 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -2576,11 +2576,11 @@ pub fn cascade(device: &Device, flags: CascadeFlags, quirks_mode: QuirksMode) -> ComputedValues { - debug_assert_eq!(parent_style.is_some(), layout_parent_style.is_some()); + debug_assert!(layout_parent_style.is_none() || parent_style.is_some()); let (inherited_style, layout_parent_style) = match parent_style { Some(parent_style) => { (parent_style, - layout_parent_style.unwrap()) + layout_parent_style.unwrap_or(parent_style)) }, None => { (device.default_computed_values(), diff --git a/components/style/sharing/mod.rs b/components/style/sharing/mod.rs index 39ddd2faf89..c7c2c946cc8 100644 --- a/components/style/sharing/mod.rs +++ b/components/style/sharing/mod.rs @@ -75,7 +75,7 @@ use dom::{TElement, SendElement}; use matching::{ChildCascadeRequirement, MatchMethods}; use properties::ComputedValues; use selector_parser::RestyleDamage; -use selectors::matching::{ElementSelectorFlags, VisitedHandlingMode, StyleRelations}; +use selectors::matching::{ElementSelectorFlags, VisitedHandlingMode}; use smallvec::SmallVec; use std::mem; use std::ops::Deref; @@ -494,11 +494,8 @@ impl StyleSharingCandidateCache { pub fn insert_if_possible(&mut self, element: &E, style: &ComputedValues, - relations: StyleRelations, - mut validation_data: ValidationData, + validation_data: ValidationData, dom_depth: usize) { - use selectors::matching::AFFECTED_BY_PRESENTATIONAL_HINTS; - let parent = match element.traversal_parent() { Some(element) => element, None => { @@ -525,13 +522,6 @@ impl StyleSharingCandidateCache { return; } - // Take advantage of the information we've learned during - // selector-matching. - if !relations.intersects(AFFECTED_BY_PRESENTATIONAL_HINTS) { - debug_assert!(validation_data.pres_hints.as_ref().map_or(true, |v| v.is_empty())); - validation_data.pres_hints = Some(SmallVec::new()); - } - debug!("Inserting into cache: {:?} with parent {:?}", element, parent); if self.dom_depth != dom_depth { diff --git a/components/style/style_resolver.rs b/components/style/style_resolver.rs index e926ba69f95..1dc38baa975 100644 --- a/components/style/style_resolver.rs +++ b/components/style/style_resolver.rs @@ -6,7 +6,7 @@ use applicable_declarations::ApplicableDeclarationList; use cascade_info::CascadeInfo; -use context::StyleContext; +use context::{CascadeInputs, ElementCascadeInputs, StyleContext}; use data::{ElementStyles, EagerPseudoStyles}; use dom::TElement; use log::LogLevel::Trace; @@ -42,11 +42,37 @@ struct MatchingResults { pub struct PrimaryStyle { /// The style per se. pub style: Arc, +} - /// Whether a relevant link was found while computing this style. - /// - /// FIXME(emilio): Slightly out of place? - pub relevant_link_found: bool, +fn with_default_parent_styles(element: E, f: F) -> R +where + E: TElement, + F: FnOnce(Option<&ComputedValues>, Option<&ComputedValues>) -> R, +{ + let parent_el = element.inheritance_parent(); + let parent_data = parent_el.as_ref().and_then(|e| e.borrow_data()); + let parent_style = parent_data.as_ref().map(|d| { + // Sometimes Gecko eagerly styles things without processing + // pending restyles first. In general we'd like to avoid this, + // but there can be good reasons (for example, needing to + // construct a frame for some small piece of newly-added + // content in order to do something specific with that frame, + // but not wanting to flush all of layout). + debug_assert!(cfg!(feature = "gecko") || + parent_el.unwrap().has_current_styles(d)); + d.styles.primary() + }); + + let mut layout_parent_el = parent_el.clone(); + let layout_parent_data; + let mut layout_parent_style = parent_style; + if parent_style.map_or(false, |s| s.is_display_contents()) { + layout_parent_el = Some(layout_parent_el.unwrap().layout_parent()); + layout_parent_data = layout_parent_el.as_ref().unwrap().borrow_data().unwrap(); + layout_parent_style = Some(layout_parent_data.styles.primary()); + } + + f(parent_style.map(|s| &**s), layout_parent_style.map(|s| &**s)) } impl<'a, 'ctx, 'le, E> StyleResolverForElement<'a, 'ctx, 'le, E> @@ -95,7 +121,7 @@ where if should_compute_visited_style { visited_style = Some(self.cascade_style( - visited_rules.as_ref().unwrap_or(&primary_results.rule_node), + visited_rules.as_ref(), /* style_if_visited = */ None, parent_style, layout_parent_style, @@ -105,7 +131,7 @@ where } let style = self.cascade_style( - &primary_results.rule_node, + Some(&primary_results.rule_node), visited_style, parent_style, layout_parent_style, @@ -113,7 +139,7 @@ where /* pseudo = */ None, ); - PrimaryStyle { style, relevant_link_found, } + PrimaryStyle { style, } } @@ -137,7 +163,7 @@ where } } - { + if self.element.implemented_pseudo_element().is_none() { let layout_parent_style_for_pseudo = if primary_style.style.is_display_contents() { layout_parent_style @@ -163,6 +189,115 @@ where } } + /// Resolve an element's styles with the default inheritance parent/layout + /// parents. + pub fn resolve_style_with_default_parents(&mut self) -> ElementStyles { + with_default_parent_styles(self.element, |parent_style, layout_parent_style| { + self.resolve_style(parent_style, layout_parent_style) + }) + } + + /// Cascade a set of rules, using the default parent for inheritance. + pub fn cascade_style_and_visited_with_default_parents( + &mut self, + inputs: CascadeInputs, + ) -> Arc { + with_default_parent_styles(self.element, |parent_style, layout_parent_style| { + self.cascade_style_and_visited( + inputs, + parent_style, + layout_parent_style, + /* pseudo = */ None + ) + }) + } + + fn cascade_style_and_visited( + &mut self, + inputs: CascadeInputs, + parent_style: Option<&ComputedValues>, + layout_parent_style: Option<&ComputedValues>, + pseudo: Option<&PseudoElement>, + ) -> Arc { + let mut style_if_visited = None; + if parent_style.map_or(false, |s| s.get_visited_style().is_some()) || + inputs.visited_rules.is_some() { + style_if_visited = Some(self.cascade_style( + inputs.visited_rules.as_ref(), + /* style_if_visited = */ None, + parent_style, + layout_parent_style, + CascadeVisitedMode::Visited, + pseudo, + )); + } + self.cascade_style( + inputs.rules.as_ref(), + style_if_visited, + parent_style, + layout_parent_style, + CascadeVisitedMode::Unvisited, + pseudo, + ) + } + + /// Cascade the element and pseudo-element styles with the default parents. + pub fn cascade_styles_with_default_parents( + &mut self, + inputs: ElementCascadeInputs, + ) -> ElementStyles { + use properties::longhands::display::computed_value::T as display; + with_default_parent_styles(self.element, move |parent_style, layout_parent_style| { + let primary_style = PrimaryStyle { + style: self.cascade_style_and_visited( + inputs.primary, + parent_style, + layout_parent_style, + /* pseudo = */ None, + ), + }; + + let mut pseudo_styles = EagerPseudoStyles::default(); + let pseudo_array = inputs.pseudos.into_array(); + if pseudo_array.is_none() || + primary_style.style.get_box().clone_display() == display::none { + return ElementStyles { + primary: Some(primary_style.style), + pseudos: pseudo_styles, + } + } + + { + let layout_parent_style_for_pseudo = + if primary_style.style.is_display_contents() { + layout_parent_style + } else { + Some(&*primary_style.style) + }; + + for (i, mut inputs) in pseudo_array.unwrap().iter_mut().enumerate() { + if let Some(inputs) = inputs.take() { + let pseudo = PseudoElement::from_eager_index(i); + pseudo_styles.set( + &pseudo, + self.cascade_style_and_visited( + inputs, + Some(&*primary_style.style), + layout_parent_style_for_pseudo, + Some(&pseudo), + ) + ) + } + } + } + + ElementStyles { + primary: Some(primary_style.style), + pseudos: pseudo_styles, + } + }) + } + fn resolve_pseudo_style( &mut self, pseudo: &PseudoElement, @@ -179,33 +314,23 @@ where None => return None, }; - let mut visited_style = None; - if originating_element_style.relevant_link_found { - let visited_rules = self.match_pseudo( + let mut visited_rules = None; + if originating_element_style.style.get_visited_style().is_some() { + visited_rules = self.match_pseudo( &originating_element_style.style, pseudo, VisitedHandlingMode::RelevantLinkVisited, ); - - if let Some(ref rules) = visited_rules { - visited_style = Some(self.cascade_style( - rules, - /* style_if_visited = */ None, - Some(&originating_element_style.style), - layout_parent_style, - CascadeVisitedMode::Visited, - Some(pseudo), - )); - } } - Some(self.cascade_style( - &rules, - visited_style, + Some(self.cascade_style_and_visited( + CascadeInputs { + rules: Some(rules), + visited_rules + }, Some(&originating_element_style.style), layout_parent_style, - CascadeVisitedMode::Unvisited, - Some(pseudo) + Some(pseudo), )) } @@ -335,9 +460,9 @@ where fn cascade_style( &mut self, - rules: &StrongRuleNode, + rules: Option<&StrongRuleNode>, style_if_visited: Option>, - parent_style: Option<&ComputedValues>, + mut parent_style: Option<&ComputedValues>, layout_parent_style: Option<&ComputedValues>, cascade_visited: CascadeVisitedMode, pseudo: Option<&PseudoElement>, @@ -349,6 +474,9 @@ where cascade_flags.insert(SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP); } if cascade_visited.visited_dependent_only() { + parent_style = parent_style.map(|s| { + s.get_visited_style().map(|s| &**s).unwrap_or(s) + }); cascade_flags.insert(VISITED_DEPENDENT_ONLY); } if self.element.is_native_anonymous() || pseudo.is_some() { @@ -360,13 +488,12 @@ where let values = Arc::new(cascade( self.context.shared.stylist.device(), - rules, + rules.unwrap_or(self.context.shared.stylist.rule_tree().root()), &self.context.shared.guards, parent_style, layout_parent_style, style_if_visited, Some(&mut cascade_info), - &*self.context.shared.error_reporter, &self.context.thread_local.font_metrics_provider, cascade_flags, self.context.shared.quirks_mode diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 0a7f6c105f8..dadcec02a1e 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -15,7 +15,6 @@ use font_metrics::FontMetricsProvider; use gecko_bindings::structs::{nsIAtom, StyleRuleInclusion}; use invalidation::element::invalidation_map::InvalidationMap; use invalidation::media_queries::{EffectiveMediaQueryResults, ToMediaListKey}; -use matching::CascadeVisitedMode; use media_queries::Device; use properties::{self, CascadeFlags, ComputedValues}; use properties::{AnimationRules, PropertyDeclarationBlock}; @@ -719,24 +718,30 @@ impl Stylist { { // We may have only visited rules in cases when we are actually // resolving, not probing, pseudo-element style. - if !inputs.has_rules() && !inputs.has_visited_rules() { + if inputs.rules.is_none() && inputs.visited_rules.is_none() { return None } // We need to compute visited values if we have visited rules or if our // parent has visited values. - let visited_values = if inputs.has_visited_rules() || parent_style.get_visited_style().is_some() { + let visited_values = if inputs.visited_rules.is_some() || parent_style.get_visited_style().is_some() { // Slightly annoying: we know that inputs has either rules or // visited rules, but we can't do inputs.rules() up front because // maybe it just has visited rules, so can't unwrap_or. - let rule_node = match inputs.get_visited_rules() { + let rule_node = match inputs.visited_rules.as_ref() { Some(rules) => rules, - None => inputs.rules() + None => inputs.rules.as_ref().unwrap(), }; // We want to use the visited bits (if any) from our parent style as // our parent. - let mode = CascadeVisitedMode::Visited; - let inherited_style = mode.values(parent_style); + let inherited_style = + parent_style.get_visited_style().unwrap_or(&*parent_style); + + // FIXME(emilio): The lack of layout_parent_style here could be + // worrying, but we're probably dropping the display fixup for + // pseudos other than before and after, so it's probably ok. + // + // (Though the flags don't indicate so!) let computed = properties::cascade(&self.device, rule_node, @@ -756,7 +761,7 @@ impl Stylist { // We may not have non-visited rules, if we only had visited ones. In // that case we want to use the root rulenode for our non-visited rules. - let rules = inputs.get_rules().unwrap_or(self.rule_tree.root()); + let rules = inputs.rules.as_ref().unwrap_or(self.rule_tree.root()); // Read the comment on `precomputed_values_for_pseudo` to see why it's // difficult to assert that display: contents nodes never arrive here @@ -849,10 +854,10 @@ impl Stylist { let rule_node = self.rule_tree.compute_rule_node(&mut declarations, guards); debug_assert!(rule_node != *self.rule_tree.root()); - inputs.set_rules(VisitedHandlingMode::AllLinksUnvisited, rule_node); + inputs.rules = Some(rule_node); } - if is_probe && !inputs.has_rules() { + if is_probe && inputs.rules.is_none() { // When probing, don't compute visited styles if we have no // unvisited styles. return inputs; @@ -880,8 +885,7 @@ impl Stylist { declarations.into_iter().map(|a| a.order_and_level()), guards); if rule_node != *self.rule_tree.root() { - inputs.set_rules(VisitedHandlingMode::RelevantLinkVisited, - rule_node); + inputs.visited_rules = Some(rule_node); } } } diff --git a/components/style/traversal.rs b/components/style/traversal.rs index d7b0835a991..59a568e31a7 100644 --- a/components/style/traversal.rs +++ b/components/style/traversal.rs @@ -10,8 +10,9 @@ use data::{ElementData, ElementStyles}; use dom::{NodeInfo, OpaqueNode, TElement, TNode}; use invalidation::element::restyle_hints::{RECASCADE_SELF, RECASCADE_DESCENDANTS, RestyleHint}; use matching::{ChildCascadeRequirement, MatchMethods}; -use sharing::{StyleSharingBehavior, StyleSharingTarget}; +use sharing::StyleSharingTarget; use smallvec::SmallVec; +use style_resolver::StyleResolverForElement; use stylist::RuleInclusion; /// A per-traversal-level chunk of data. This is sent down by the traversal, and @@ -765,7 +766,8 @@ where data.restyle.set_restyled(); } - match kind { + let mut important_rules_changed = false; + let new_styles = match kind { MatchAndCascade => { debug_assert!(!context.shared.traversal_flags.for_animation_only(), "MatchAndCascade shouldn't be processed during \ @@ -790,35 +792,62 @@ where context.thread_local.statistics.elements_matched += 1; + important_rules_changed = true; + // Perform the matching and cascading. - element.match_and_cascade( - context, - data, - StyleSharingBehavior::Allow - ) + let new_styles = + StyleResolverForElement::new(element, context, RuleInclusion::All) + .resolve_style_with_default_parents(); + + // If we previously tried to match this element against the cache, + // the revalidation match results will already be cached. Otherwise + // we'll have None, and compute them later on-demand. + // + // If we do have the results, grab them here to satisfy the borrow + // checker. + let validation_data = + context.thread_local + .current_element_info + .as_mut().unwrap() + .validation_data + .take(); + + let dom_depth = context.thread_local.bloom_filter.matching_depth(); + context.thread_local + .style_sharing_candidate_cache + .insert_if_possible( + &element, + new_styles.primary(), + validation_data, + dom_depth + ); + + new_styles } CascadeWithReplacements(flags) => { // Skipping full matching, load cascade inputs from previous values. - *context.cascade_inputs_mut() = + let mut cascade_inputs = ElementCascadeInputs::new_from_element_data(data); - let important_rules_changed = element.replace_rules(flags, context); - element.cascade_primary_and_pseudos( - context, - data, - important_rules_changed - ) + important_rules_changed = + element.replace_rules(flags, context, &mut cascade_inputs); + StyleResolverForElement::new(element, context, RuleInclusion::All) + .cascade_styles_with_default_parents(cascade_inputs) } CascadeOnly => { // Skipping full matching, load cascade inputs from previous values. - *context.cascade_inputs_mut() = + let cascade_inputs = ElementCascadeInputs::new_from_element_data(data); - element.cascade_primary_and_pseudos( - context, - data, - /* important_rules_changed = */ false - ) + StyleResolverForElement::new(element, context, RuleInclusion::All) + .cascade_styles_with_default_parents(cascade_inputs) } - } + }; + + element.finish_restyle( + context, + data, + new_styles, + important_rules_changed + ) } fn preprocess_children( diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index 3e049e1da3d..cb848fecc65 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -664,32 +664,35 @@ pub extern "C" fn Servo_StyleSet_GetBaseComputedValuesForElement(raw_data: RawSe let doc_data = PerDocumentStyleData::from_ffi(raw_data).borrow(); let global_style_data = &*GLOBAL_STYLE_DATA; let guard = global_style_data.shared_lock.read(); - let shared_context = create_shared_context(&global_style_data, - &guard, - &doc_data, - TraversalFlags::empty(), - unsafe { &*snapshots }); + let element = GeckoElement(element); let element_data = element.borrow_data().unwrap(); let styles = &element_data.styles; - let pseudo = PseudoElement::from_pseudo_type(pseudo_type); - let pseudos = &styles.pseudos; - let pseudo_style = match pseudo { - Some(ref p) => { - let style = pseudos.get(p); - debug_assert!(style.is_some()); - style - } - None => None, + if let Some(pseudo) = PseudoElement::from_pseudo_type(pseudo_type) { + // This style already doesn't have animations. + return styles + .pseudos + .get(&pseudo) + .expect("GetBaseComputedValuesForElement for an unexisting pseudo?") + .clone().into_strong(); + } + + let shared = create_shared_context(&global_style_data, + &guard, + &doc_data, + TraversalFlags::empty(), + unsafe { &*snapshots }); + let mut tlc = ThreadLocalStyleContext::new(&shared); + let mut context = StyleContext { + shared: &shared, + thread_local: &mut tlc, }; - let provider = get_metrics_provider_for_product(); - element.get_base_style(&shared_context, - &provider, - styles.primary(), - pseudo_style) - .into_strong() + element.get_base_style( + &mut context, + styles.primary(), + ).into_strong() } #[no_mangle]