diff --git a/components/script_layout_interface/wrapper_traits.rs b/components/script_layout_interface/wrapper_traits.rs index cac426156da..579ac767d13 100644 --- a/components/script_layout_interface/wrapper_traits.rs +++ b/components/script_layout_interface/wrapper_traits.rs @@ -426,6 +426,7 @@ pub trait ThreadSafeLayoutElement: Clone + Copy + Sized + Debug + &style_pseudo, RuleInclusion::All, data.styles.primary(), + /* is_probe = */ false, &ServoMetricsProvider) .unwrap() .clone() diff --git a/components/style/context.rs b/components/style/context.rs index 6a8f3535ce4..18822ca2930 100644 --- a/components/style/context.rs +++ b/components/style/context.rs @@ -184,7 +184,7 @@ impl Default for CascadeInputs { impl CascadeInputs { /// Construct inputs from previous cascade results, if any. - fn new_from_style(style: &Arc) -> Self { + pub fn new_from_style(style: &Arc) -> Self { CascadeInputs { rules: style.rules.clone(), visited_rules: style.get_visited_style().and_then(|v| v.rules.clone()), @@ -204,6 +204,11 @@ impl CascadeInputs { 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 { diff --git a/components/style/matching.rs b/components/style/matching.rs index 167b18e7969..49c329d325c 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -151,7 +151,10 @@ impl CascadeVisitedMode { fn rules<'a>(&self, inputs: &'a CascadeInputs) -> &'a StrongRuleNode { match *self { CascadeVisitedMode::Unvisited => inputs.rules(), - CascadeVisitedMode::Visited => inputs.visited_rules(), + CascadeVisitedMode::Visited => match inputs.get_visited_rules() { + Some(rules) => rules, + None => inputs.rules(), + } } } @@ -166,7 +169,7 @@ impl CascadeVisitedMode { /// 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. - fn values<'a>(&self, values: &'a Arc) -> &'a Arc { + pub fn values<'a>(&self, values: &'a Arc) -> &'a Arc { if *self == CascadeVisitedMode::Visited && values.get_visited_style().is_some() { return values.visited_style(); } @@ -288,10 +291,35 @@ trait PrivateMatchMethods: TElement { } } + /// 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, @@ -299,6 +327,7 @@ trait PrivateMatchMethods: TElement { 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(); @@ -318,23 +347,18 @@ trait PrivateMatchMethods: TElement { // Grab the inherited values. let parent_el; - let parent_data; + 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 => { - parent_el = self.inheritance_parent(); - 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() - }); - parent_style.map(|s| cascade_visited.values(s)) + 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()); @@ -391,11 +415,15 @@ trait PrivateMatchMethods: TElement { /// 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() { @@ -459,15 +487,19 @@ trait PrivateMatchMethods: TElement { 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); @@ -485,7 +517,10 @@ trait PrivateMatchMethods: TElement { // 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(primary_inputs) { + // + // 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 } @@ -494,6 +529,7 @@ trait PrivateMatchMethods: TElement { None, primary_inputs, None, + /* parent_info = */ None, cascade_visited) }; @@ -582,6 +618,7 @@ trait PrivateMatchMethods: TElement { data.styles.get_primary(), primary_inputs, Some(pseudo_inputs), + /* parent_info = */ None, cascade_visited) }; @@ -625,6 +662,7 @@ trait PrivateMatchMethods: TElement { Some(primary_style), CascadeTarget::Normal, CascadeVisitedMode::Unvisited, + /* parent_info = */ None, None)) } @@ -877,6 +915,23 @@ 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)] @@ -944,13 +999,21 @@ pub trait MatchMethods : TElement { 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. @@ -1017,10 +1080,13 @@ pub trait MatchMethods : TElement { // 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); @@ -1211,8 +1277,10 @@ pub trait MatchMethods : TElement { // Compute rule nodes for eagerly-cascaded pseudo-elements. let mut matches_different_pseudos = false; SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| { - // For pseudo-elements, we only try to match visited rules if there - // are also unvisited rules. (This matches Gecko's behavior.) + // 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 @@ -1587,6 +1655,7 @@ pub trait MatchMethods : TElement { Some(primary_style), CascadeTarget::Normal, CascadeVisitedMode::Unvisited, + /* parent_info = */ None, None) } diff --git a/components/style/stylist.rs b/components/style/stylist.rs index f398d0f16c9..da2bfd6a1e6 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -7,7 +7,7 @@ use {Atom, LocalName, Namespace}; use applicable_declarations::{ApplicableDeclarationBlock, ApplicableDeclarationList}; use bit_vec::BitVec; -use context::QuirksMode; +use context::{CascadeInputs, QuirksMode}; use dom::TElement; use element_state::ElementState; use error_reporting::create_error_reporter; @@ -16,6 +16,7 @@ use font_metrics::FontMetricsProvider; use gecko_bindings::structs::{nsIAtom, StyleRuleInclusion}; use invalidation::element::invalidation_map::InvalidationMap; use invalidation::media_queries::EffectiveMediaQueryResults; +use matching::CascadeVisitedMode; use media_queries::Device; use properties::{self, CascadeFlags, ComputedValues}; use properties::{AnimationRules, PropertyDeclarationBlock}; @@ -27,7 +28,7 @@ use selector_parser::{SelectorImpl, PseudoElement}; use selectors::attr::NamespaceConstraint; use selectors::bloom::BloomFilter; use selectors::matching::{ElementSelectorFlags, matches_selector, MatchingContext, MatchingMode}; -use selectors::matching::AFFECTED_BY_PRESENTATIONAL_HINTS; +use selectors::matching::{VisitedHandlingMode, AFFECTED_BY_PRESENTATIONAL_HINTS}; use selectors::parser::{AncestorHashes, Combinator, Component, Selector, SelectorAndHashes}; use selectors::parser::{SelectorIter, SelectorMethods}; use selectors::sink::Push; @@ -679,47 +680,90 @@ impl Stylist { element: &E, pseudo: &PseudoElement, rule_inclusion: RuleInclusion, - parent_style: &ComputedValues, + parent_style: &Arc, + is_probe: bool, font_metrics: &FontMetricsProvider) -> Option> where E: TElement, { - let rule_node = - self.lazy_pseudo_rules(guards, element, pseudo, rule_inclusion); - self.compute_pseudo_element_style_with_rulenode(rule_node.as_ref(), - guards, - parent_style, - font_metrics) + let cascade_inputs = + self.lazy_pseudo_rules(guards, element, pseudo, is_probe, rule_inclusion); + self.compute_pseudo_element_style_with_inputs(&cascade_inputs, + guards, + parent_style, + font_metrics) } - /// Computes a pseudo-element style lazily using the given rulenode. This - /// can be used for truly lazy pseudo-elements or to avoid redoing selector - /// matching for eager pseudo-elements when we need to recompute their style - /// with a new parent style. - pub fn compute_pseudo_element_style_with_rulenode(&self, - rule_node: Option<&StrongRuleNode>, - guards: &StylesheetGuards, - parent_style: &ComputedValues, - font_metrics: &FontMetricsProvider) - -> Option> + /// Computes a pseudo-element style lazily using the given CascadeInputs. + /// This can be used for truly lazy pseudo-elements or to avoid redoing + /// selector matching for eager pseudo-elements when we need to recompute + /// their style with a new parent style. + pub fn compute_pseudo_element_style_with_inputs(&self, + inputs: &CascadeInputs, + guards: &StylesheetGuards, + parent_style: &Arc, + font_metrics: &FontMetricsProvider) + -> Option> { - let rule_node = match rule_node { - Some(rule_node) => rule_node, - None => return None + // 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() { + 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() { + // 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() { + Some(rules) => rules, + None => inputs.rules() + }; + // 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 computed = + properties::cascade(&self.device, + rule_node, + guards, + Some(inherited_style), + Some(inherited_style), + None, + None, + &create_error_reporter(), + font_metrics, + CascadeFlags::empty(), + self.quirks_mode); + + Some(Arc::new(computed)) + } else { + None + }; + + // 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 root; + let rules = if let Some(rules) = inputs.get_rules() { + rules + } else { + root = self.rule_tree.root(); + &root }; // Read the comment on `precomputed_values_for_pseudo` to see why it's // difficult to assert that display: contents nodes never arrive here // (tl;dr: It doesn't apply for replaced elements and such, but the // computed value is still "contents"). - // Bug 1364242: We need to add visited support for lazy pseudos let computed = properties::cascade(&self.device, - rule_node, + rules, guards, Some(parent_style), Some(parent_style), - None, + visited_values, None, &create_error_reporter(), font_metrics, @@ -729,7 +773,7 @@ impl Stylist { Some(Arc::new(computed)) } - /// Computes the rule node for a lazily-cascaded pseudo-element. + /// Computes the cascade inputs for a lazily-cascaded pseudo-element. /// /// See the documentation on lazy pseudo-elements in /// docs/components/style.md @@ -737,14 +781,15 @@ impl Stylist { guards: &StylesheetGuards, element: &E, pseudo: &PseudoElement, + is_probe: bool, rule_inclusion: RuleInclusion) - -> Option + -> CascadeInputs where E: TElement { let pseudo = pseudo.canonical(); debug_assert!(pseudo.is_lazy()); if self.pseudos_map.get(&pseudo).is_none() { - return None + return CascadeInputs::default() } // Apply the selector flags. We should be in sequential mode @@ -777,7 +822,7 @@ impl Stylist { } }; - // Bug 1364242: We need to add visited support for lazy pseudos + let mut inputs = CascadeInputs::default(); let mut declarations = ApplicableDeclarationList::new(); let mut matching_context = MatchingContext::new(MatchingMode::ForStatelessPseudoElement, @@ -792,19 +837,52 @@ impl Stylist { &mut declarations, &mut matching_context, &mut set_selector_flags); - if declarations.is_empty() { - return None - } - let rule_node = - self.rule_tree.insert_ordered_rules_with_important( + if !declarations.is_empty() { + let rule_node = self.rule_tree.insert_ordered_rules_with_important( declarations.into_iter().map(|a| a.order_and_level()), guards); - if rule_node == self.rule_tree.root() { - None - } else { - Some(rule_node) + if rule_node != self.rule_tree.root() { + inputs.set_rules(VisitedHandlingMode::AllLinksUnvisited, + rule_node); + } + }; + + if is_probe && !inputs.has_rules() { + // When probing, don't compute visited styles if we have no + // unvisited styles. + return inputs; } + + if matching_context.relevant_link_found { + let mut declarations = ApplicableDeclarationList::new(); + let mut matching_context = + MatchingContext::new_for_visited(MatchingMode::ForStatelessPseudoElement, + None, + VisitedHandlingMode::RelevantLinkVisited, + self.quirks_mode); + self.push_applicable_declarations(element, + Some(&pseudo), + None, + None, + AnimationRules(None, None), + rule_inclusion, + &mut declarations, + &mut matching_context, + &mut set_selector_flags); + if !declarations.is_empty() { + let rule_node = + self.rule_tree.insert_ordered_rules_with_important( + 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 } /// Set a given device, which may change the styles that apply to the diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index 2295e01b6ce..faf69b35f8d 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -10,7 +10,7 @@ use selectors::Element; use std::env; use std::fmt::Write; use std::ptr; -use style::context::{QuirksMode, SharedStyleContext, StyleContext}; +use style::context::{CascadeInputs, QuirksMode, SharedStyleContext, StyleContext}; use style::context::ThreadLocalStyleContext; use style::data::{ElementData, ElementStyles, RestyleData}; use style::dom::{AnimationOnlyDirtyDescendants, DirtyDescendants}; @@ -1479,7 +1479,7 @@ pub extern "C" fn Servo_ResolvePseudoStyle(element: RawGeckoElementBorrowed, &pseudo, RuleInclusion::All, &data.styles, - ComputedValues::arc_from_borrowed(&inherited_style).map(|v| v.as_ref()), + ComputedValues::arc_from_borrowed(&inherited_style), &*doc_data, is_probe ); @@ -1533,7 +1533,7 @@ fn get_pseudo_style( pseudo: &PseudoElement, rule_inclusion: RuleInclusion, styles: &ElementStyles, - inherited_styles: Option<&ComputedValues>, + inherited_styles: Option<&Arc>, doc_data: &PerDocumentStyleDataImpl, is_probe: bool, ) -> Option> { @@ -1550,20 +1550,20 @@ fn get_pseudo_style( inherited_styles.unwrap_or(styles.primary()); let guards = StylesheetGuards::same(guard); let metrics = get_metrics_provider_for_product(); - let rule_node = match styles.pseudos.get(&pseudo) { - Some(styles) => styles.rules.as_ref(), - None => None, + let inputs = match styles.pseudos.get(&pseudo) { + Some(styles) => CascadeInputs::new_from_style(styles), + None => return None, }; doc_data.stylist - .compute_pseudo_element_style_with_rulenode( - rule_node, + .compute_pseudo_element_style_with_inputs( + &inputs, &guards, inherited_styles, &metrics) }, _ => { debug_assert!(inherited_styles.is_none() || - ptr::eq(inherited_styles.unwrap(), + ptr::eq(&**inherited_styles.unwrap(), &**styles.primary())); styles.pseudos.get(&pseudo).cloned() }, @@ -1572,7 +1572,7 @@ fn get_pseudo_style( PseudoElementCascadeType::Precomputed => unreachable!("No anonymous boxes"), PseudoElementCascadeType::Lazy => { debug_assert!(inherited_styles.is_none() || - ptr::eq(inherited_styles.unwrap(), + ptr::eq(&**inherited_styles.unwrap(), &**styles.primary())); let base = if pseudo.inherits_from_default_values() { doc_data.default_computed_values() @@ -1588,6 +1588,7 @@ fn get_pseudo_style( &pseudo, rule_inclusion, base, + is_probe, &metrics) }, };