/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //! Style resolution for a given element or pseudo-element. use applicable_declarations::ApplicableDeclarationList; use context::{CascadeInputs, ElementCascadeInputs, StyleContext}; use data::{ElementStyles, EagerPseudoStyles}; use dom::TElement; use log::LogLevel::Trace; use matching::{CascadeVisitedMode, MatchMethods}; use properties::{AnimationRules, CascadeFlags, ComputedValues}; use properties::{IS_LINK, IS_ROOT_ELEMENT, IS_VISITED_LINK}; use properties::{PROHIBIT_DISPLAY_CONTENTS, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP}; use properties::{VISITED_DEPENDENT_ONLY, cascade}; use properties::longhands::display::computed_value::T as display; use rule_tree::StrongRuleNode; use selector_parser::{PseudoElement, SelectorImpl}; use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode, VisitedHandlingMode}; use servo_arc::Arc; use stylist::RuleInclusion; /// Whether pseudo-elements should be resolved or not. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum PseudoElementResolution { /// Only resolve pseudo-styles if possibly applicable. IfApplicable, /// Force pseudo-element resolution. Force, } /// A struct that takes care of resolving the style of a given element. pub struct StyleResolverForElement<'a, 'ctx, 'le, E> where 'ctx: 'a, 'le: 'ctx, E: TElement + MatchMethods + 'le, { element: E, context: &'a mut StyleContext<'ctx, E>, rule_inclusion: RuleInclusion, pseudo_resolution: PseudoElementResolution, _marker: ::std::marker::PhantomData<&'le E>, } struct MatchingResults { rule_node: StrongRuleNode, relevant_link_found: bool, } /// A style returned from the resolver machinery. pub struct ResolvedStyle(pub Arc); /// The primary style of an element or an element-backed pseudo-element. pub struct PrimaryStyle { /// The style itself. pub style: ResolvedStyle, /// Whether the style was reused from another element via the rule node (see /// `StyleSharingCache::lookup_by_rules`). pub reused_via_rule_node: bool, } /// A set of style returned from the resolver machinery. pub struct ResolvedElementStyles { /// Primary style. pub primary: PrimaryStyle, /// Pseudo styles. pub pseudos: EagerPseudoStyles, } impl PrimaryStyle { /// Convenience accessor for the style. pub fn style(&self) -> &ComputedValues { &*self.style.0 } } impl From for ElementStyles { fn from(r: ResolvedElementStyles) -> ElementStyles { ElementStyles { primary: Some(r.primary.style.0), pseudos: r.pseudos, } } } 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| 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(|x| &**x), layout_parent_style.map(|s| &**s)) } fn eager_pseudo_is_definitely_not_generated( pseudo: &PseudoElement, style: &ComputedValues, ) -> bool { use properties::computed_value_flags::{INHERITS_CONTENT, INHERITS_DISPLAY}; if !pseudo.is_before_or_after() { return false; } if !style.flags.intersects(INHERITS_DISPLAY) && style.get_box().clone_display() == display::none { return true; } if !style.flags.intersects(INHERITS_CONTENT) && style.ineffective_content_property() { return true; } false } impl<'a, 'ctx, 'le, E> StyleResolverForElement<'a, 'ctx, 'le, E> where 'ctx: 'a, 'le: 'ctx, E: TElement + MatchMethods + 'le, { /// Trivially construct a new StyleResolverForElement. pub fn new( element: E, context: &'a mut StyleContext<'ctx, E>, rule_inclusion: RuleInclusion, pseudo_resolution: PseudoElementResolution, ) -> Self { Self { element, context, rule_inclusion, pseudo_resolution, _marker: ::std::marker::PhantomData, } } /// Resolve just the style of a given element. pub fn resolve_primary_style( &mut self, parent_style: Option<&ComputedValues>, layout_parent_style: Option<&ComputedValues>, ) -> PrimaryStyle { let primary_results = self.match_primary(VisitedHandlingMode::AllLinksUnvisited); let relevant_link_found = primary_results.relevant_link_found; let visited_rules = if relevant_link_found { let visited_matching_results = self.match_primary(VisitedHandlingMode::RelevantLinkVisited); Some(visited_matching_results.rule_node) } else { None }; self.cascade_primary_style( CascadeInputs { rules: Some(primary_results.rule_node), visited_rules, }, parent_style, layout_parent_style, ) } fn cascade_primary_style( &mut self, inputs: CascadeInputs, parent_style: Option<&ComputedValues>, layout_parent_style: Option<&ComputedValues>, ) -> PrimaryStyle { // Before doing the cascade, check the sharing cache and see if we can // reuse the style via rule node identity. let may_reuse = !self.element.is_native_anonymous() && parent_style.is_some() && inputs.rules.is_some(); if may_reuse { let cached = self.context.thread_local.sharing_cache.lookup_by_rules( self.context.shared, parent_style.unwrap(), inputs.rules.as_ref().unwrap(), inputs.visited_rules.as_ref(), self.element, ); if let Some(mut primary_style) = cached { self.context.thread_local.statistics.styles_reused += 1; primary_style.reused_via_rule_node |= true; return primary_style; } } // No style to reuse. Cascade the style, starting with visited style // if necessary. PrimaryStyle { style: self.cascade_style_and_visited( inputs, parent_style, layout_parent_style, /* pseudo = */ None, ), reused_via_rule_node: false, } } /// Resolve the style of a given element, and all its eager pseudo-elements. pub fn resolve_style( &mut self, parent_style: Option<&ComputedValues>, layout_parent_style: Option<&ComputedValues>, ) -> ResolvedElementStyles { let primary_style = self.resolve_primary_style(parent_style, layout_parent_style); let mut pseudo_styles = EagerPseudoStyles::default(); if self.element.implemented_pseudo_element().is_none() { let layout_parent_style_for_pseudo = if primary_style.style().is_display_contents() { layout_parent_style } else { Some(primary_style.style()) }; SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| { let pseudo_style = self.resolve_pseudo_style( &pseudo, &primary_style, layout_parent_style_for_pseudo ); if let Some(style) = pseudo_style { if !matches!(self.pseudo_resolution, PseudoElementResolution::Force) && eager_pseudo_is_definitely_not_generated(&pseudo, &style.0) { return; } pseudo_styles.set(&pseudo, style.0); } }) } ResolvedElementStyles { primary: primary_style, pseudos: pseudo_styles, } } /// Resolve an element's styles with the default inheritance parent/layout /// parents. pub fn resolve_style_with_default_parents(&mut self) -> ResolvedElementStyles { 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, ) -> ResolvedStyle { 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>, ) -> ResolvedStyle { let mut style_if_visited = None; if parent_style.map_or(false, |s| s.visited_style().is_some()) || inputs.visited_rules.is_some() { style_if_visited = Some(self.cascade_style( inputs.visited_rules.as_ref().or(inputs.rules.as_ref()), /* style_if_visited = */ None, parent_style, layout_parent_style, CascadeVisitedMode::Visited, pseudo, )); } ResolvedStyle( 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, ) -> ResolvedElementStyles { with_default_parent_styles(self.element, move |parent_style, layout_parent_style| { let primary_style = self.cascade_primary_style( inputs.primary, parent_style, layout_parent_style, ); let mut pseudo_styles = EagerPseudoStyles::default(); if let Some(mut pseudo_array) = inputs.pseudos.into_array() { let layout_parent_style_for_pseudo = if primary_style.style().is_display_contents() { layout_parent_style } else { Some(primary_style.style()) }; for (i, inputs) in pseudo_array.iter_mut().enumerate() { if let Some(inputs) = inputs.take() { let pseudo = PseudoElement::from_eager_index(i); let style = self.cascade_style_and_visited( inputs, Some(primary_style.style()), layout_parent_style_for_pseudo, Some(&pseudo), ); if !matches!(self.pseudo_resolution, PseudoElementResolution::Force) && eager_pseudo_is_definitely_not_generated(&pseudo, &style.0) { continue; } pseudo_styles.set(&pseudo, style.0); } } } ResolvedElementStyles { primary: primary_style, pseudos: pseudo_styles, } }) } fn resolve_pseudo_style( &mut self, pseudo: &PseudoElement, originating_element_style: &PrimaryStyle, layout_parent_style: Option<&ComputedValues>, ) -> Option { let rules = self.match_pseudo( originating_element_style.style(), pseudo, VisitedHandlingMode::AllLinksUnvisited ); let rules = match rules { Some(rules) => rules, None => return None, }; let mut visited_rules = None; if originating_element_style.style().visited_style().is_some() { visited_rules = self.match_pseudo( originating_element_style.style(), pseudo, VisitedHandlingMode::RelevantLinkVisited, ); } Some(self.cascade_style_and_visited( CascadeInputs { rules: Some(rules), visited_rules }, Some(originating_element_style.style()), layout_parent_style, Some(pseudo), )) } fn match_primary( &mut self, visited_handling: VisitedHandlingMode, ) -> MatchingResults { debug!("Match primary for {:?}, visited: {:?}", self.element, visited_handling); let mut applicable_declarations = ApplicableDeclarationList::new(); let map = &mut self.context.thread_local.selector_flags; let bloom_filter = self.context.thread_local.bloom_filter.filter(); let nth_index_cache = &mut self.context.thread_local.nth_index_cache; let mut matching_context = MatchingContext::new_for_visited( MatchingMode::Normal, Some(bloom_filter), Some(nth_index_cache), visited_handling, self.context.shared.quirks_mode(), ); let stylist = &self.context.shared.stylist; let implemented_pseudo = self.element.implemented_pseudo_element(); { let resolving_element = self.element; let mut set_selector_flags = |element: &E, flags: ElementSelectorFlags| { resolving_element.apply_selector_flags(map, element, flags); }; // Compute the primary rule node. stylist.push_applicable_declarations( &self.element, implemented_pseudo.as_ref(), self.element.style_attribute(), self.element.get_smil_override(), self.element.get_animation_rules(), self.rule_inclusion, &mut applicable_declarations, &mut matching_context, &mut set_selector_flags, ); } // FIXME(emilio): This is a hack for animations, and should go away. self.element.unset_dirty_style_attribute(); let relevant_link_found = matching_context.relevant_link_found; let rule_node = stylist.rule_tree().compute_rule_node( &mut applicable_declarations, &self.context.shared.guards ); if log_enabled!(Trace) { trace!("Matched rules for {:?}:", self.element); for rn in rule_node.self_and_ancestors() { let source = rn.style_source(); if source.is_some() { trace!(" > {:?}", source); } } } MatchingResults { rule_node, relevant_link_found } } fn match_pseudo( &mut self, originating_element_style: &ComputedValues, pseudo_element: &PseudoElement, visited_handling: VisitedHandlingMode, ) -> Option { debug!("Match pseudo {:?} for {:?}, visited: {:?}", self.element, pseudo_element, visited_handling); debug_assert!(pseudo_element.is_eager() || pseudo_element.is_lazy()); debug_assert!(self.element.implemented_pseudo_element().is_none(), "Element pseudos can't have any other pseudo."); let mut applicable_declarations = ApplicableDeclarationList::new(); let stylist = &self.context.shared.stylist; if !self.element.may_generate_pseudo(pseudo_element, originating_element_style) { return None; } let bloom_filter = self.context.thread_local.bloom_filter.filter(); let nth_index_cache = &mut self.context.thread_local.nth_index_cache; let mut matching_context = MatchingContext::new_for_visited( MatchingMode::ForStatelessPseudoElement, Some(bloom_filter), Some(nth_index_cache), visited_handling, self.context.shared.quirks_mode(), ); let map = &mut self.context.thread_local.selector_flags; let resolving_element = self.element; let mut set_selector_flags = |element: &E, flags: ElementSelectorFlags| { resolving_element.apply_selector_flags(map, element, flags); }; // NB: We handle animation rules for ::before and ::after when // traversing them. stylist.push_applicable_declarations( &self.element, Some(pseudo_element), None, None, AnimationRules(None, None), self.rule_inclusion, &mut applicable_declarations, &mut matching_context, &mut set_selector_flags ); if applicable_declarations.is_empty() { return None; } let rule_node = stylist.rule_tree().compute_rule_node( &mut applicable_declarations, &self.context.shared.guards ); Some(rule_node) } fn cascade_style( &mut self, rules: Option<&StrongRuleNode>, style_if_visited: Option>, mut parent_style: Option<&ComputedValues>, layout_parent_style: Option<&ComputedValues>, cascade_visited: CascadeVisitedMode, pseudo: Option<&PseudoElement>, ) -> Arc { debug_assert!( self.element.implemented_pseudo_element().is_none() || pseudo.is_none(), "Pseudo-elements can't have other pseudos!" ); debug_assert!(pseudo.map_or(true, |p| p.is_eager())); let mut cascade_flags = CascadeFlags::empty(); if self.element.skip_root_and_item_based_display_fixup() || pseudo.map_or(false, |p| p.skip_item_based_display_fixup()) { cascade_flags.insert(SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP); } if pseudo.is_none() && self.element.is_link() { cascade_flags.insert(IS_LINK); if self.element.is_visited_link() && self.context.shared.visited_styles_enabled { cascade_flags.insert(IS_VISITED_LINK); } } if cascade_visited.visited_dependent_only() { // If this element is a link, we want its visited style to inherit // from the regular style of its parent, because only the // visitedness of the relevant link should influence style. if pseudo.is_some() || !self.element.is_link() { parent_style = parent_style.map(|s| { s.visited_style().unwrap_or(s) }); } cascade_flags.insert(VISITED_DEPENDENT_ONLY); } if self.element.is_native_anonymous() || pseudo.is_some() { cascade_flags.insert(PROHIBIT_DISPLAY_CONTENTS); } else if self.element.is_root() { cascade_flags.insert(IS_ROOT_ELEMENT); } let implemented_pseudo = self.element.implemented_pseudo_element(); let pseudo = pseudo.or(implemented_pseudo.as_ref()); let mut conditions = Default::default(); let values = cascade( self.context.shared.stylist.device(), pseudo, rules.unwrap_or(self.context.shared.stylist.rule_tree().root()), &self.context.shared.guards, parent_style, parent_style, layout_parent_style, style_if_visited, &self.context.thread_local.font_metrics_provider, cascade_flags, self.context.shared.quirks_mode(), Some(&self.context.thread_local.rule_cache), &mut conditions, ); self.context .thread_local .rule_cache .insert_if_possible(&values, pseudo, &conditions); values } }