style: Introduce StyleResolverForElement. draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Sun, 09 Jul 2017 21:19:04 +0200
changeset 606874 2c453d62c33b8c41b0ac117f7801d3cb44f1240a
parent 606838 cd8e3f97bbde3df4248cbb566007f30dba7bb567
child 606875 7c1e3a43c05be81a599f7732210f5094c43862d3
push id67817
push userbmo:emilio+bugs@crisal.io
push dateTue, 11 Jul 2017 14:32:38 +0000
milestone56.0a1
style: Introduce StyleResolverForElement. This still doesn't make use of it so far, but I prefer introducing it atomically, then introduce its usage. MozReview-Commit-ID: 9dRUsl3srHp
servo/components/style/lib.rs
servo/components/style/style_resolver.rs
--- a/servo/components/style/lib.rs
+++ b/servo/components/style/lib.rs
@@ -118,16 +118,17 @@ pub mod media_queries;
 pub mod parallel;
 pub mod parser;
 pub mod rule_tree;
 pub mod scoped_tls;
 pub mod selector_map;
 pub mod selector_parser;
 pub mod shared_lock;
 pub mod sharing;
+pub mod style_resolver;
 pub mod stylist;
 #[cfg(feature = "servo")] #[allow(unsafe_code)] pub mod servo;
 pub mod sequential;
 pub mod str;
 pub mod style_adjuster;
 pub mod stylesheet_set;
 pub mod stylesheets;
 pub mod thread_state;
new file mode 100644
--- /dev/null
+++ b/servo/components/style/style_resolver.rs
@@ -0,0 +1,370 @@
+/* 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 cascade_info::CascadeInfo;
+use context::StyleContext;
+use data::{ElementStyles, EagerPseudoStyles};
+use dom::TElement;
+use log::LogLevel::Trace;
+use matching::{CascadeVisitedMode, MatchMethods};
+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 rule_tree::StrongRuleNode;
+use selector_parser::{PseudoElement, SelectorImpl};
+use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode, VisitedHandlingMode};
+use stylearc::Arc;
+use stylist::RuleInclusion;
+
+/// A struct that takes care of resolving the style of a given element.
+pub struct StyleResolverForElement<'a, 'b, E>
+where
+    'b: 'a,
+    E: TElement + MatchMethods + 'static,
+{
+    element: E,
+    context: &'a mut StyleContext<'b, E>,
+    rule_inclusion: RuleInclusion,
+}
+
+struct MatchingResults {
+    rule_node: StrongRuleNode,
+    relevant_link_found: bool,
+}
+
+/// The primary style of an element or an element-backed pseudo-element.
+pub struct PrimaryStyle {
+    /// The style per se.
+    pub style: Arc<ComputedValues>,
+
+    /// Whether a relevant link was found while computing this style.
+    ///
+    /// FIXME(emilio): Slightly out of place?
+    pub relevant_link_found: bool,
+}
+
+impl<'a, 'b, E> StyleResolverForElement<'a, 'b, E>
+where
+    'b: 'a,
+    E: TElement + MatchMethods + 'static,
+{
+    /// Trivially construct a new StyleResolverForElement.
+    pub fn new(
+        element: E,
+        context: &'a mut StyleContext<'b, E>,
+        rule_inclusion: RuleInclusion,
+    ) -> Self {
+        Self { element, context, rule_inclusion, }
+    }
+
+    /// 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
+        };
+
+        let mut visited_style = None;
+        let should_compute_visited_style =
+            relevant_link_found ||
+            parent_style.and_then(|s| s.get_visited_style()).is_some();
+
+        if should_compute_visited_style {
+            visited_style = Some(self.cascade_style(
+                visited_rules.as_ref().unwrap_or(&primary_results.rule_node),
+                /* style_if_visited = */ None,
+                parent_style,
+                layout_parent_style,
+                CascadeVisitedMode::Visited,
+                /* pseudo = */ None,
+            ));
+        }
+
+        let style = self.cascade_style(
+            &primary_results.rule_node,
+            visited_style,
+            parent_style,
+            layout_parent_style,
+            CascadeVisitedMode::Unvisited,
+            /* pseudo = */ None,
+        );
+
+        PrimaryStyle { style, relevant_link_found, }
+    }
+
+
+    /// 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>,
+    ) -> ElementStyles {
+        use properties::longhands::display::computed_value::T as display;
+
+        let primary_style =
+            self.resolve_primary_style(parent_style, layout_parent_style);
+
+        let mut pseudo_styles = EagerPseudoStyles::default();
+        if primary_style.style.get_box().clone_display() == display::none {
+            return ElementStyles {
+                // FIXME(emilio): Remove the Option<>.
+                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)
+                };
+            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 {
+                    pseudo_styles.set(&pseudo, style);
+                }
+            })
+        }
+
+        ElementStyles {
+            // FIXME(emilio): Remove the Option<>.
+            primary: Some(primary_style.style),
+            pseudos: pseudo_styles,
+        }
+    }
+
+    fn resolve_pseudo_style(
+        &mut self,
+        pseudo: &PseudoElement,
+        originating_element_style: &PrimaryStyle,
+        layout_parent_style: Option<&ComputedValues>,
+    ) -> Option<Arc<ComputedValues>> {
+        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_style = None;
+        if originating_element_style.relevant_link_found {
+            let 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(&originating_element_style.style),
+            layout_parent_style,
+            CascadeVisitedMode::Unvisited,
+            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 mut matching_context =
+            MatchingContext::new_for_visited(
+                MatchingMode::Normal,
+                Some(bloom_filter),
+                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 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<StrongRuleNode> {
+        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 mut matching_context =
+            MatchingContext::new_for_visited(
+                MatchingMode::ForStatelessPseudoElement,
+                Some(bloom_filter),
+                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: &StrongRuleNode,
+        style_if_visited: Option<Arc<ComputedValues>>,
+        parent_style: Option<&ComputedValues>,
+        layout_parent_style: Option<&ComputedValues>,
+        cascade_visited: CascadeVisitedMode,
+        pseudo: Option<&PseudoElement>,
+    ) -> Arc<ComputedValues> {
+        let mut cascade_info = CascadeInfo::new();
+        let mut cascade_flags = CascadeFlags::empty();
+
+        if self.element.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.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 values =
+            Arc::new(cascade(
+                self.context.shared.stylist.device(),
+                rules,
+                &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
+            ));
+
+        cascade_info.finish(&self.element.as_node());
+        values
+    }
+}