stylo: Get rules from Gecko XBL stylesheets in cascading. draft
authorTing-Yu Lin <tlin@mozilla.com>
Mon, 05 Jun 2017 14:17:18 +0800
changeset 590195 d00fb33cdef48b67c71aa368dbc04f366db9de8b
parent 590194 7a6372fc69fd3b748159bb75ad26affae4d55798
child 590196 51c345c3ced19b373f0abfe2a902760294b16f93
push id62633
push userbmo:tlin@mozilla.com
push dateWed, 07 Jun 2017 09:41:32 +0000
milestone55.0a1
stylo: Get rules from Gecko XBL stylesheets in cascading. MozReview-Commit-ID: EsmRpBSZC3K
servo/components/style/dom.rs
servo/components/style/gecko/wrapper.rs
servo/components/style/rule_tree/mod.rs
servo/components/style/stylist.rs
--- a/servo/components/style/dom.rs
+++ b/servo/components/style/dom.rs
@@ -16,16 +16,17 @@ use font_metrics::FontMetricsProvider;
 use properties::{ComputedValues, PropertyDeclarationBlock};
 #[cfg(feature = "gecko")] use properties::animated_properties::AnimationValue;
 #[cfg(feature = "gecko")] use properties::animated_properties::TransitionProperty;
 use rule_tree::CascadeLevel;
 use selector_parser::{ElementExt, PreExistingComputedValues, PseudoElement};
 use selectors::matching::{ElementSelectorFlags, VisitedHandlingMode};
 use shared_lock::Locked;
 use sink::Push;
+use smallvec::VecLike;
 use std::fmt;
 #[cfg(feature = "gecko")] use std::collections::HashMap;
 use std::fmt::Debug;
 use std::hash::Hash;
 use std::ops::Deref;
 use stylearc::Arc;
 use stylist::ApplicableDeclarationBlock;
 use thread_state;
@@ -556,16 +557,24 @@ pub trait TElement : Eq + PartialEq + De
         let data = match self.borrow_data() {
             Some(d) => d,
             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)
+                                             -> 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)
                                 -> HashMap<TransitionProperty, Arc<AnimationValue>>;
 
     /// Does a rough (and cheap) check for whether or not transitions might need to be updated that
     /// will quickly return false for the common case of no transitions specified or running. If
     /// this returns false, we definitely don't need to update transitions but if it returns true
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -18,16 +18,17 @@ use app_units::Au;
 use atomic_refcell::AtomicRefCell;
 use context::{QuirksMode, SharedStyleContext, UpdateAnimationsTasks};
 use data::ElementData;
 use dom::{self, DescendantsBit, LayoutIterator, NodeInfo, TElement, TNode, UnsafeNode};
 use dom::{OpaqueNode, PresentationalHintsSynthesizer};
 use element_state::ElementState;
 use error_reporting::RustLogReporter;
 use font_metrics::{FontMetrics, FontMetricsProvider, FontMetricsQueryResult};
+use gecko::data::PerDocumentStyleData;
 use gecko::global_style_data::GLOBAL_STYLE_DATA;
 use gecko::selector_parser::{SelectorImpl, NonTSPseudoClass, PseudoElement};
 use gecko::snapshot_helpers;
 use gecko_bindings::bindings;
 use gecko_bindings::bindings::{Gecko_DropStyleChildrenIterator, Gecko_MaybeCreateStyleChildrenIterator};
 use gecko_bindings::bindings::{Gecko_ElementState, Gecko_GetLastChild, Gecko_GetNextStyleChild};
 use gecko_bindings::bindings::{Gecko_IsRootElement, Gecko_MatchesElement, Gecko_Namespace};
 use gecko_bindings::bindings::{Gecko_SetNodeFlags, Gecko_UnsetNodeFlags};
@@ -44,40 +45,41 @@ use gecko_bindings::bindings::Gecko_GetS
 use gecko_bindings::bindings::Gecko_GetStyleContext;
 use gecko_bindings::bindings::Gecko_GetUnvisitedLinkAttrDeclarationBlock;
 use gecko_bindings::bindings::Gecko_GetVisitedLinkAttrDeclarationBlock;
 use gecko_bindings::bindings::Gecko_IsSignificantChild;
 use gecko_bindings::bindings::Gecko_MatchStringArgPseudo;
 use gecko_bindings::bindings::Gecko_UnsetDirtyStyleAttr;
 use gecko_bindings::bindings::Gecko_UpdateAnimations;
 use gecko_bindings::structs;
-use gecko_bindings::structs::{RawGeckoElement, RawGeckoNode};
+use gecko_bindings::structs::{RawGeckoElement, RawGeckoNode, RawGeckoXBLBinding};
 use gecko_bindings::structs::{nsIAtom, nsIContent, nsINode_BooleanFlag, nsStyleContext};
 use gecko_bindings::structs::ELEMENT_HANDLED_SNAPSHOT;
 use gecko_bindings::structs::ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO;
 use gecko_bindings::structs::ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO;
 use gecko_bindings::structs::ELEMENT_HAS_SNAPSHOT;
 use gecko_bindings::structs::EffectCompositor_CascadeLevel as CascadeLevel;
 use gecko_bindings::structs::NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE;
 use gecko_bindings::structs::NODE_IS_NATIVE_ANONYMOUS;
-use gecko_bindings::sugar::ownership::HasArcFFI;
+use gecko_bindings::sugar::ownership::{HasArcFFI, HasSimpleFFI};
 use logical_geometry::WritingMode;
 use media_queries::Device;
 use properties::{ComputedValues, parse_style_attribute};
 use properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock};
 use properties::animated_properties::{AnimationValue, AnimationValueMap, TransitionProperty};
 use properties::style_structs::Font;
 use rule_tree::CascadeLevel as ServoCascadeLevel;
 use selector_parser::ElementExt;
 use selectors::Element;
 use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator, CaseSensitivity, NamespaceConstraint};
 use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
 use selectors::matching::{RelevantLinkStatus, VisitedHandlingMode};
 use shared_lock::Locked;
 use sink::Push;
+use smallvec::VecLike;
 use std::cell::RefCell;
 use std::collections::HashMap;
 use std::fmt;
 use std::hash::{Hash, Hasher};
 use std::mem;
 use std::ops::DerefMut;
 use std::ptr;
 use string_cache::{Atom, Namespace, WeakAtom, WeakNamespace};
@@ -196,16 +198,22 @@ impl<'ln> GeckoNode<'ln> {
 
         if parent_el.map_or(false, |el| el.has_shadow_root()) {
             return false;
         }
 
         true
     }
 
+    /// This logic is duplicated in Gecko's nsIContent::IsRootOfNativeAnonymousSubtree.
+    fn is_root_of_native_anonymous_subtree(&self) -> bool {
+        use gecko_bindings::structs::NODE_IS_NATIVE_ANONYMOUS_ROOT;
+        return self.flags() & (NODE_IS_NATIVE_ANONYMOUS_ROOT as u32) != 0
+    }
+
     fn contains_non_whitespace_content(&self) -> bool {
         unsafe { Gecko_IsSignificantChild(self.0, true, false) }
     }
 }
 
 impl<'ln> NodeInfo for GeckoNode<'ln> {
     #[inline]
     fn is_element(&self) -> bool {
@@ -335,16 +343,47 @@ impl<'a> Iterator for GeckoChildrenItera
             },
             GeckoChildrenIterator::GeckoIterator(ref mut it) => unsafe {
                 Gecko_GetNextStyleChild(it).map(GeckoNode)
             }
         }
     }
 }
 
+/// A Simple wrapper over a non-null Gecko `nsXBLBinding` pointer.
+pub struct GeckoXBLBinding<'lb>(pub &'lb RawGeckoXBLBinding);
+
+impl<'lb> GeckoXBLBinding<'lb> {
+    fn base_binding(&self) -> Option<GeckoXBLBinding> {
+        unsafe { self.0.mNextBinding.mRawPtr.as_ref().map(GeckoXBLBinding) }
+    }
+
+    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,
+                                  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);
+        }
+
+        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,
+                                                                          applicable_declarations);
+        }
+    }
+}
+
 /// A simple wrapper over a non-null Gecko `Element` pointer.
 #[derive(Clone, Copy)]
 pub struct GeckoElement<'le>(pub &'le RawGeckoElement);
 
 impl<'le> fmt::Debug for GeckoElement<'le> {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         try!(write!(f, "<{}", self.get_local_name()));
         if let Some(id) = self.get_id() {
@@ -388,16 +427,24 @@ impl<'le> GeckoElement<'le> {
     }
 
     /// Returns a reference to the DOM slots for this Element, if they exist.
     fn get_dom_slots(&self) -> Option<&structs::FragmentOrElement_nsDOMSlots> {
         let slots = self.as_node().0.mSlots as *const structs::FragmentOrElement_nsDOMSlots;
         unsafe { slots.as_ref() }
     }
 
+    fn get_xbl_binding(&self) -> Option<GeckoXBLBinding> {
+        unsafe { bindings::Gecko_GetXBLBinding(self.0).map(GeckoXBLBinding) }
+    }
+
+    fn get_xbl_binding_parent(&self) -> Option<Self> {
+        unsafe { bindings::Gecko_GetBindingParent(self.0).map(GeckoElement) }
+    }
+
     /// Clear the element data for a given element.
     pub fn clear_data(&self) {
         let ptr = self.0.mServoData.get();
         unsafe {
             self.unset_flags(ELEMENT_HAS_SNAPSHOT as u32 |
                              ELEMENT_HANDLED_SNAPSHOT as u32);
         }
         if !ptr.is_null() {
@@ -851,16 +898,53 @@ impl<'le> TElement for GeckoElement<'le>
     fn has_css_animations(&self) -> bool {
         self.may_have_animations() && unsafe { Gecko_ElementHasCSSAnimations(self.0) }
     }
 
     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,
+                                             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);
+
+                // 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;
+                    }
+                }
+            }
+
+            if element.as_node().is_root_of_native_anonymous_subtree() {
+                // Deliberately cut off style inheritance here.
+                break;
+            }
+
+            current = element.get_xbl_binding_parent();
+        }
+
+        // If current has something, this means we cut off inheritance at some point in the
+        // loop.
+        current.is_some()
+    }
+
     fn get_css_transitions_info(&self)
                                 -> HashMap<TransitionProperty, Arc<AnimationValue>> {
         use gecko_bindings::bindings::Gecko_ElementTransitions_EndValueAt;
         use gecko_bindings::bindings::Gecko_ElementTransitions_Length;
         use gecko_bindings::bindings::Gecko_ElementTransitions_PropertyAt;
 
         let collection_length =
             unsafe { Gecko_ElementTransitions_Length(self.0) };
--- a/servo/components/style/rule_tree/mod.rs
+++ b/servo/components/style/rule_tree/mod.rs
@@ -401,16 +401,18 @@ const RULE_TREE_GC_INTERVAL: usize = 300
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum CascadeLevel {
     /// Normal User-Agent rules.
     UANormal = 0,
     /// Presentational hints.
     PresHints,
     /// User normal rules.
     UserNormal,
+    /// XBL <stylesheet> rules.
+    XBL,
     /// Author normal rules.
     AuthorNormal,
     /// Style attribute normal rules.
     StyleAttributeNormal,
     /// SVG SMIL animations.
     SMILOverride,
     /// CSS animations and script-generated animations.
     Animations,
@@ -1046,17 +1048,18 @@ impl StrongRuleNode {
                         }
                     });
 
                 match node.cascade_level() {
                     // Non-author rules:
                     CascadeLevel::UANormal |
                     CascadeLevel::UAImportant |
                     CascadeLevel::UserNormal |
-                    CascadeLevel::UserImportant => {
+                    CascadeLevel::UserImportant |
+                    CascadeLevel::XBL => {
                         for (id, declaration) in longhands {
                             if properties.contains(id) {
                                 // This property was set by a non-author rule.
                                 // Stop looking for it in this element's rule
                                 // nodes.
                                 properties.remove(id);
 
                                 // However, if it is inherited, then it might be
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -912,16 +912,36 @@ 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 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,
+                                                                  applicable_declarations: &mut V)
+        where E: TElement,
+              V: Push<ApplicableDeclarationBlock> + VecLike<ApplicableDeclarationBlock>,
+    {
+        let mut matching_context =
+            MatchingContext::new(MatchingMode::Normal, None);
+        let mut dummy_flag_setter = |_: &E, _: ElementSelectorFlags| {};
+
+        self.element_map.author.get_all_matching_rules(element,
+                                                       element,
+                                                       applicable_declarations,
+                                                       &mut matching_context,
+                                                       &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.
     pub fn push_applicable_declarations<E, V, F>(
                                         &self,
@@ -1014,25 +1034,36 @@ impl Stylist {
                                             context,
                                             flags_setter,
                                             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);
+        debug!("XBL: {:?}", context.relations);
+
         if rule_hash_target.matches_user_and_author_rules() && !only_default_rules {
-            // Step 3b: Author normal rules.
-            map.author.get_all_matching_rules(element,
-                                              &rule_hash_target,
-                                              applicable_declarations,
-                                              context,
-                                              flags_setter,
-                                              CascadeLevel::AuthorNormal);
-            debug!("author normal: {:?}", context.relations);
+            // 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,
+                                                  &rule_hash_target,
+                                                  applicable_declarations,
+                                                  context,
+                                                  flags_setter,
+                                                  CascadeLevel::AuthorNormal);
+                debug!("author normal: {:?}", context.relations);
+            } else {
+                debug!("Skipping author normal rules due to cut off inheritance");
+            }
 
             // Step 4: Normal style attributes.
             if let Some(sa) = style_attribute {
                 Push::push(
                     applicable_declarations,
                     ApplicableDeclarationBlock::from_declarations(sa.clone(),
                                                                   CascadeLevel::StyleAttributeNormal));
             }