stylo: Fix pseudo element matching for XBL stylesheets (Bug 1371577) draft
authorTing-Yu Lin <tlin@mozilla.com>
Mon, 12 Jun 2017 14:30:37 +0800
changeset 593902 7b9986d43ec2e4b8d75d2be440749c823abc8b31
parent 593717 b266a8d8fd595b84a7d6218d7b8c6b7af0b5027c
child 593903 37c2dda89f4fa595e363978eda2bc9bddbab1978
push id63859
push userbmo:tlin@mozilla.com
push dateWed, 14 Jun 2017 08:50:15 +0000
bugs1371577
milestone56.0a1
stylo: Fix pseudo element matching for XBL stylesheets (Bug 1371577) MozReview-Commit-ID: GzWbztqW0V1
servo/components/style/dom.rs
servo/components/style/gecko/wrapper.rs
servo/components/style/stylist.rs
--- a/servo/components/style/dom.rs
+++ b/servo/components/style/dom.rs
@@ -568,17 +568,18 @@ pub trait TElement : Eq + PartialEq + De
             None => return false,
         };
         return data.get_restyle()
                    .map_or(false, |r| r.hint.has_animation_hint());
     }
 
     /// Gets declarations from XBL bindings from the element. Only gecko element could have this.
     fn get_declarations_from_xbl_bindings<V>(&self,
-                                             _: &mut V)
+                                             _pseudo_element: Option<&PseudoElement>,
+                                             _applicable_declarations: &mut V)
                                              -> bool
         where V: Push<ApplicableDeclarationBlock> + VecLike<ApplicableDeclarationBlock> {
         false
     }
 
     /// Gets the current existing CSS transitions, by |property, end value| pairs in a HashMap.
     #[cfg(feature = "gecko")]
     fn get_css_transitions_info(&self)
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -377,27 +377,29 @@ impl<'lb> GeckoXBLBinding<'lb> {
 
     fn inherits_style(&self) -> bool {
         unsafe { bindings::Gecko_XBLBinding_InheritsStyle(self.0) }
     }
 
     // Implements Gecko's nsXBLBinding::WalkRules().
     fn get_declarations_for<E, V>(&self,
                                   element: &E,
+                                  pseudo_element: Option<&PseudoElement>,
                                   applicable_declarations: &mut V)
         where E: TElement,
               V: Push<ApplicableDeclarationBlock> + VecLike<ApplicableDeclarationBlock> {
         if let Some(base_binding) = self.base_binding() {
-            base_binding.get_declarations_for(element, applicable_declarations);
+            base_binding.get_declarations_for(element, pseudo_element, applicable_declarations);
         }
 
         let raw_data = unsafe { bindings::Gecko_XBLBinding_GetRawServoStyleSet(self.0) };
         if let Some(raw_data) = raw_data {
             let data = PerDocumentStyleData::from_ffi(&*raw_data).borrow();
             data.stylist.push_applicable_declarations_as_xbl_only_stylist(element,
+                                                                          pseudo_element,
                                                                           applicable_declarations);
         }
     }
 }
 
 /// A simple wrapper over a non-null Gecko `Element` pointer.
 #[derive(Clone, Copy)]
 pub struct GeckoElement<'le>(pub &'le RawGeckoElement);
@@ -937,26 +939,29 @@ impl<'le> TElement for GeckoElement<'le>
 
     fn has_css_transitions(&self) -> bool {
         self.may_have_animations() && unsafe { Gecko_ElementHasCSSTransitions(self.0) }
     }
 
     // Implements Gecko's nsBindingManager::WalkRules(). Returns whether to cut off the
     // inheritance.
     fn get_declarations_from_xbl_bindings<V>(&self,
+                                             pseudo_element: Option<&PseudoElement>,
                                              applicable_declarations: &mut V)
                                              -> bool
         where V: Push<ApplicableDeclarationBlock> + VecLike<ApplicableDeclarationBlock> {
         // Walk the binding scope chain, starting with the binding attached to our content, up
         // till we run out of scopes or we get cut off.
         let mut current = Some(*self);
 
         while let Some(element) = current {
             if let Some(binding) = element.get_xbl_binding() {
-                binding.get_declarations_for(self, applicable_declarations);
+                binding.get_declarations_for(self,
+                                             pseudo_element,
+                                             applicable_declarations);
 
                 // If we're not looking at our original element, allow the binding to cut off
                 // style inheritance.
                 if element != *self {
                     if !binding.inherits_style() {
                         // Go no further; we're not inheriting style from anything above here.
                         break;
                     }
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -928,35 +928,77 @@ impl Stylist {
         // during multiple layout passes, but this is totally bogus, in the
         // sense that it's updated asynchronously.
         //
         // This should probably be an argument to `update`, and use the quirks
         // mode info in the `SharedLayoutContext`.
         self.quirks_mode = quirks_mode;
     }
 
+    /// Returns the correspond PerPseudoElementSelectorMap given PseudoElement.
+    fn get_element_map(&self,
+                   pseudo_element: Option<&PseudoElement>) -> &PerPseudoElementSelectorMap
+    {
+        debug_assert!(pseudo_element.is_none() ||
+                      self.pseudos_map.get(pseudo_element.unwrap()).is_some());
+        match pseudo_element {
+            Some(pseudo) => self.pseudos_map.get(pseudo).unwrap(),
+            None => &self.element_map,
+        }
+    }
+
+    /// Returns the rule hash target given an element.
+    fn rule_hash_target<E>(&self,
+                           element: &E) -> E
+        where E: TElement {
+        let is_implemented_pseudo =
+            element.implemented_pseudo_element().is_some();
+
+        // NB: This causes use to rule has pseudo selectors based on the
+        // properties of the originating element (which is fine, given the
+        // find_first_from_right usage).
+        let rule_hash_target = if is_implemented_pseudo {
+            element.closest_non_native_anonymous_ancestor().unwrap()
+        } else {
+            *element
+        };
+
+        rule_hash_target
+    }
+
     /// Returns the applicable CSS declarations for the given element by
     /// treating us as an XBL stylesheet-only stylist.
     pub fn push_applicable_declarations_as_xbl_only_stylist<E, V>(&self,
                                                                   element: &E,
+                                                                  pseudo_element: Option<&PseudoElement>,
                                                                   applicable_declarations: &mut V)
         where E: TElement,
               V: Push<ApplicableDeclarationBlock> + VecLike<ApplicableDeclarationBlock>,
     {
         let mut matching_context =
             MatchingContext::new(MatchingMode::Normal, None, self.quirks_mode);
         let mut dummy_flag_setter = |_: &E, _: ElementSelectorFlags| {};
 
-        self.element_map.author.get_all_matching_rules(element,
-                                                       element,
-                                                       applicable_declarations,
-                                                       &mut matching_context,
-                                                       self.quirks_mode,
-                                                       &mut dummy_flag_setter,
-                                                       CascadeLevel::XBL);
+        // Bail out if we don't have the selector map of the pseudo element.
+        if pseudo_element.is_some() && self.pseudos_map.get(pseudo_element.unwrap()).is_none() {
+            return;
+        }
+
+        let map = self.get_element_map(pseudo_element);
+        let rule_hash_target = self.rule_hash_target(element);
+
+        // nsXBLPrototypeResources::ComputeServoStyleSet() added XBL stylesheets under author
+        // (doc) level.
+        map.author.get_all_matching_rules(element,
+                                          &rule_hash_target,
+                                          applicable_declarations,
+                                          &mut matching_context,
+                                          self.quirks_mode,
+                                          &mut dummy_flag_setter,
+                                          CascadeLevel::XBL);
     }
 
     /// Returns the applicable CSS declarations for the given element.
     ///
     /// This corresponds to `ElementRuleCollector` in WebKit.
     ///
     /// The `StyleRelations` recorded in `MatchingContext` indicate hints about
     /// which kind of rules have matched.
@@ -978,32 +1020,18 @@ impl Stylist {
         debug_assert!(!self.is_device_dirty);
         // Gecko definitely has pseudo-elements with style attributes, like
         // ::-moz-color-swatch.
         debug_assert!(cfg!(feature = "gecko") ||
                       style_attribute.is_none() || pseudo_element.is_none(),
                       "Style attributes do not apply to pseudo-elements");
         debug_assert!(pseudo_element.map_or(true, |p| !p.is_precomputed()));
 
-        let map = match pseudo_element {
-            Some(pseudo) => self.pseudos_map.get(pseudo).unwrap(),
-            None => &self.element_map,
-        };
-
-        let is_implemented_pseudo =
-            element.implemented_pseudo_element().is_some();
-
-        // NB: This causes use to rule has pseudo selectors based on the
-        // properties of the originating element (which is fine, given the
-        // find_first_from_right usage).
-        let rule_hash_target = if is_implemented_pseudo {
-            element.closest_non_native_anonymous_ancestor().unwrap()
-        } else {
-            *element
-        };
+        let map = self.get_element_map(pseudo_element);
+        let rule_hash_target = self.rule_hash_target(element);
 
         debug!("Determining if style is shareable: pseudo: {}",
                pseudo_element.is_some());
 
         let only_default_rules = rule_inclusion == RuleInclusion::DefaultOnly;
 
         // Step 1: Normal user-agent rules.
         map.user_agent.get_all_matching_rules(element,
@@ -1055,17 +1083,18 @@ impl Stylist {
                                             CascadeLevel::UserNormal);
             debug!("user normal: {:?}", context.relations);
         } else {
             debug!("skipping user rules");
         }
 
         // Step 3b: XBL rules.
         let cut_off_inheritance =
-            rule_hash_target.get_declarations_from_xbl_bindings(applicable_declarations);
+            rule_hash_target.get_declarations_from_xbl_bindings(pseudo_element,
+                                                                applicable_declarations);
         debug!("XBL: {:?}", context.relations);
 
         if rule_hash_target.matches_user_and_author_rules() && !only_default_rules {
             // Gecko skips author normal rules if cutting off inheritance.
             // See nsStyleSet::FileRules().
             if !cut_off_inheritance {
                 // Step 3c: Author normal rules.
                 map.author.get_all_matching_rules(element,