summaryrefslogtreecommitdiffstats
path: root/servo/components/style/style_resolver.rs
diff options
context:
space:
mode:
Diffstat (limited to 'servo/components/style/style_resolver.rs')
-rw-r--r--servo/components/style/style_resolver.rs589
1 files changed, 589 insertions, 0 deletions
diff --git a/servo/components/style/style_resolver.rs b/servo/components/style/style_resolver.rs
new file mode 100644
index 0000000000..a082b2cbff
--- /dev/null
+++ b/servo/components/style/style_resolver.rs
@@ -0,0 +1,589 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! Style resolution for a given element or pseudo-element.
+
+use crate::applicable_declarations::ApplicableDeclarationList;
+use crate::computed_value_flags::ComputedValueFlags;
+use crate::context::{CascadeInputs, ElementCascadeInputs, StyleContext};
+use crate::data::{EagerPseudoStyles, ElementStyles};
+use crate::dom::TElement;
+use crate::matching::MatchMethods;
+use crate::properties::longhands::display::computed_value::T as Display;
+use crate::properties::ComputedValues;
+use crate::rule_tree::StrongRuleNode;
+use crate::selector_parser::{PseudoElement, SelectorImpl};
+use crate::stylist::RuleInclusion;
+use log::Level::Trace;
+use selectors::matching::{MatchingContext, NeedsSelectorFlags};
+use selectors::matching::{MatchingMode, VisitedHandlingMode};
+use servo_arc::Arc;
+
+/// 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,
+ flags: ComputedValueFlags,
+}
+
+/// A style returned from the resolver machinery.
+pub struct ResolvedStyle(pub Arc<ComputedValues>);
+
+/// 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 ResolvedElementStyles {
+ /// Convenience accessor for the primary style.
+ pub fn primary_style(&self) -> &Arc<ComputedValues> {
+ &self.primary.style.0
+ }
+
+ /// Convenience mutable accessor for the style.
+ pub fn primary_style_mut(&mut self) -> &mut Arc<ComputedValues> {
+ &mut self.primary.style.0
+ }
+}
+
+impl PrimaryStyle {
+ /// Convenience accessor for the style.
+ pub fn style(&self) -> &ComputedValues {
+ &*self.style.0
+ }
+}
+
+impl From<ResolvedElementStyles> for ElementStyles {
+ fn from(r: ResolvedElementStyles) -> ElementStyles {
+ ElementStyles {
+ primary: Some(r.primary.style.0),
+ pseudos: r.pseudos,
+ }
+ }
+}
+
+fn with_default_parent_styles<E, F, R>(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 layout_parent_style_for_pseudo<'a>(
+ primary_style: &'a PrimaryStyle,
+ layout_parent_style: Option<&'a ComputedValues>,
+) -> Option<&'a ComputedValues> {
+ if primary_style.style().is_display_contents() {
+ layout_parent_style
+ } else {
+ Some(primary_style.style())
+ }
+}
+
+fn eager_pseudo_is_definitely_not_generated(
+ pseudo: &PseudoElement,
+ style: &ComputedValues,
+) -> bool {
+ if !pseudo.is_before_or_after() {
+ return false;
+ }
+
+ if !style
+ .flags
+ .intersects(ComputedValueFlags::DISPLAY_DEPENDS_ON_INHERITED_STYLE) &&
+ style.get_box().clone_display() == Display::None
+ {
+ return true;
+ }
+
+ if !style
+ .flags
+ .intersects(ComputedValueFlags::CONTENT_DEPENDS_ON_INHERITED_STYLE) &&
+ 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 inside_link = parent_style.map_or(false, |s| s.visited_style().is_some());
+
+ let visited_rules = if self.context.shared.visited_styles_enabled &&
+ (inside_link || self.element.is_link())
+ {
+ 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,
+ flags: primary_results.flags,
+ },
+ 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.matches_user_and_content_rules() &&
+ 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.is_pseudo_element() {
+ let layout_parent_style_for_pseudo =
+ layout_parent_style_for_pseudo(&primary_style, layout_parent_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,
+ )
+ })
+ }
+
+ /// Cascade a set of rules for pseudo element, using the default parent for inheritance.
+ pub fn cascade_style_and_visited_for_pseudo_with_default_parents(
+ &mut self,
+ inputs: CascadeInputs,
+ pseudo: &PseudoElement,
+ primary_style: &PrimaryStyle,
+ ) -> ResolvedStyle {
+ with_default_parent_styles(self.element, |_, layout_parent_style| {
+ let layout_parent_style_for_pseudo =
+ layout_parent_style_for_pseudo(primary_style, layout_parent_style);
+
+ self.cascade_style_and_visited(
+ inputs,
+ Some(primary_style.style()),
+ layout_parent_style_for_pseudo,
+ Some(pseudo),
+ )
+ })
+ }
+
+ fn cascade_style_and_visited(
+ &mut self,
+ inputs: CascadeInputs,
+ parent_style: Option<&ComputedValues>,
+ layout_parent_style: Option<&ComputedValues>,
+ pseudo: Option<&PseudoElement>,
+ ) -> ResolvedStyle {
+ debug_assert!(pseudo.map_or(true, |p| p.is_eager()));
+
+ let implemented_pseudo = self.element.implemented_pseudo_element();
+ let pseudo = pseudo.or(implemented_pseudo.as_ref());
+
+ let mut conditions = Default::default();
+ let values = self.context.shared.stylist.cascade_style_and_visited(
+ Some(self.element),
+ pseudo,
+ inputs,
+ &self.context.shared.guards,
+ pseudo.and(parent_style),
+ parent_style,
+ parent_style,
+ layout_parent_style,
+ Some(&self.context.thread_local.rule_cache),
+ &mut conditions,
+ );
+
+ self.context.thread_local.rule_cache.insert_if_possible(
+ &self.context.shared.guards,
+ &values,
+ pseudo,
+ &conditions,
+ );
+
+ ResolvedStyle(values)
+ }
+
+ /// 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<ResolvedStyle> {
+ let MatchingResults {
+ rule_node,
+ mut flags,
+ } = self.match_pseudo(
+ &originating_element_style.style.0,
+ pseudo,
+ VisitedHandlingMode::AllLinksUnvisited,
+ )?;
+
+ let mut visited_rules = None;
+ if originating_element_style.style().visited_style().is_some() {
+ visited_rules = self
+ .match_pseudo(
+ &originating_element_style.style.0,
+ pseudo,
+ VisitedHandlingMode::RelevantLinkVisited,
+ )
+ .map(|results| {
+ flags |= results.flags;
+ results.rule_node
+ });
+ }
+
+ Some(self.cascade_style_and_visited(
+ CascadeInputs {
+ rules: Some(rule_node),
+ visited_rules,
+ flags,
+ },
+ 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 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),
+ nth_index_cache,
+ visited_handling,
+ self.context.shared.quirks_mode(),
+ NeedsSelectorFlags::Yes,
+ );
+
+ let stylist = &self.context.shared.stylist;
+ let implemented_pseudo = self.element.implemented_pseudo_element();
+ // Compute the primary rule node.
+ stylist.push_applicable_declarations(
+ self.element,
+ implemented_pseudo.as_ref(),
+ self.element.style_attribute(),
+ self.element.smil_override(),
+ self.element.animation_declarations(self.context.shared),
+ self.rule_inclusion,
+ &mut applicable_declarations,
+ &mut matching_context,
+ );
+
+ // FIXME(emilio): This is a hack for animations, and should go away.
+ self.element.unset_dirty_style_attribute();
+
+ 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);
+ }
+ }
+ }
+
+ if matching_context.considered_relative_selector {
+ // This is a bit awkward - ideally, the flag is set directly where `considered_relative_selector`
+ // is; however, in that context, the implementation detail of `extra_data` is not visible, so
+ // it's done here. A trait for manipulating the flags is an option, but not worth it for a single flag.
+ matching_context
+ .extra_data
+ .cascade_input_flags
+ .insert(ComputedValueFlags::CONSIDERED_RELATIVE_SELECTOR);
+ }
+
+ MatchingResults {
+ rule_node,
+ flags: matching_context.extra_data.cascade_input_flags,
+ }
+ }
+
+ fn match_pseudo(
+ &mut self,
+ originating_element_style: &ComputedValues,
+ pseudo_element: &PseudoElement,
+ visited_handling: VisitedHandlingMode,
+ ) -> Option<MatchingResults> {
+ debug!(
+ "Match pseudo {:?} for {:?}, visited: {:?}",
+ self.element, pseudo_element, visited_handling
+ );
+ debug_assert!(pseudo_element.is_eager());
+ debug_assert!(
+ !self.element.is_pseudo_element(),
+ "Element pseudos can't have any other eager 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::<'_, E::Impl>::new_for_visited(
+ MatchingMode::ForStatelessPseudoElement,
+ Some(bloom_filter),
+ nth_index_cache,
+ visited_handling,
+ self.context.shared.quirks_mode(),
+ NeedsSelectorFlags::Yes,
+ );
+ matching_context.extra_data.originating_element_style = Some(originating_element_style);
+
+ // NB: We handle animation rules for ::before and ::after when
+ // traversing them.
+ stylist.push_applicable_declarations(
+ self.element,
+ Some(pseudo_element),
+ None,
+ None,
+ /* animation_declarations = */ Default::default(),
+ self.rule_inclusion,
+ &mut applicable_declarations,
+ &mut matching_context,
+ );
+
+ if applicable_declarations.is_empty() {
+ return None;
+ }
+
+ let rule_node = stylist
+ .rule_tree()
+ .compute_rule_node(&mut applicable_declarations, &self.context.shared.guards);
+
+ Some(MatchingResults {
+ rule_node,
+ flags: matching_context.extra_data.cascade_input_flags,
+ })
+ }
+}