Bug 1349651 - Implement has_author_rules for Servo RuleNode. r=bholley draft
authorMatt Brubeck <mbrubeck@mozilla.com>
Mon, 08 May 2017 11:18:44 -0700
changeset 574514 f578337b6a0df7bd1fbe0ec2722e3bd311df6dfb
parent 574513 939f8ce062723b73e8d01212e30845c9a56f42f2
child 574515 3ffd9bcb8e9cde10c362d0d40e3fea8d103ab0bb
push id57739
push userbmo:mbrubeck@mozilla.com
push dateTue, 09 May 2017 00:00:11 +0000
reviewersbholley
bugs1349651
milestone55.0a1
Bug 1349651 - Implement has_author_rules for Servo RuleNode. r=bholley MozReview-Commit-ID: 4SInOGSPjVX
layout/base/nsPresContext.cpp
layout/style/ServoBindingList.h
servo/components/style/rule_tree/mod.rs
servo/ports/geckolib/glue.rs
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -2229,16 +2229,17 @@ nsPresContext::HasAuthorSpecifiedRules(c
 
     nsIAtom *pseudoTag = aFrame->StyleContext()->GetPseudo();
     RefPtr<RawServoRuleNode> ruleNode;
     ruleNode = mShell->StyleSet()->AsServo()->ResolveRuleNode(elem, pseudoTag);
     if (!ruleNode) {
       return false;
     }
     return Servo_HasAuthorSpecifiedRules(ruleNode,
+                                         elem,
                                          ruleTypeMask,
                                          UseDocumentColors());
   }
 }
 
 gfxUserFontSet*
 nsPresContext::GetUserFontSet(bool aFlushUserFontSet)
 {
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -385,17 +385,18 @@ SERVO_BINDING_FUNC(Servo_ResolveStyle, S
                    bool allow_stale)
 SERVO_BINDING_FUNC(Servo_ResolvePseudoStyle, ServoComputedValuesStrong,
                    RawGeckoElementBorrowed element, nsIAtom* pseudo_tag,
                    bool is_probe, RawServoStyleSetBorrowed set)
 SERVO_BINDING_FUNC(Servo_ResolveRuleNode, RawServoRuleNodeStrong,
                    RawGeckoElementBorrowed element, nsIAtom* pseudo_tag,
                    RawServoStyleSetBorrowed set)
 SERVO_BINDING_FUNC(Servo_HasAuthorSpecifiedRules, bool,
-                   RawServoRuleNodeBorrowed rules,
+                   RawServoRuleNodeBorrowed rule_node,
+                   RawGeckoElementBorrowed element,
                    uint32_t rule_type_mask,
                    bool author_colors_allowed)
 
 // Resolves style for an element or pseudo-element without processing pending
 // restyles first. The Element and its ancestors may be unstyled, have pending
 // restyles, or be in a display:none subtree. Styles are cached when possible,
 // though caching is not possible within display:none subtrees, and the styles
 // may be invalidated by already-scheduled restyles.
--- a/servo/components/style/rule_tree/mod.rs
+++ b/servo/components/style/rule_tree/mod.rs
@@ -787,21 +787,190 @@ impl StrongRuleNode {
 
     unsafe fn maybe_gc(&self) {
         debug_assert!(self.get().is_root(), "Can't call GC on a non-root node!");
         if self.get().free_count.load(Ordering::Relaxed) > RULE_TREE_GC_INTERVAL {
             self.gc();
         }
     }
 
-    /// TODO: docs
-    pub fn has_author_specified_rules(&self,
-                                      _rule_type_mask: u32,
-                                      _author_colors_allowed: bool) -> bool {
-        true // TODO
+    /// Implementation of `nsRuleNode::HasAuthorSpecifiedRules` for Servo rule nodes.
+    ///
+    /// Returns true if any properties specified by `rule_type_mask` was set by an author rule.
+    #[cfg(feature = "gecko")]
+    pub fn has_author_specified_rules<E>(&self,
+                                         mut element: E,
+                                         guards: &StylesheetGuards,
+                                         rule_type_mask: u32,
+                                         _author_colors_allowed: bool)
+        -> bool
+        where E: ::dom::TElement
+    {
+        use properties::{CSSWideKeyword, LonghandId, LonghandIdSet, PropertyDeclarationId};
+        use std::borrow::Cow;
+        use gecko_bindings::structs::{NS_AUTHOR_SPECIFIED_BACKGROUND, NS_AUTHOR_SPECIFIED_BORDER};
+        use gecko_bindings::structs::{NS_AUTHOR_SPECIFIED_PADDING, NS_AUTHOR_SPECIFIED_TEXT_SHADOW};
+
+        // Reset properties:
+        const BACKGROUND_PROPS: &'static [LonghandId] = &[
+            LonghandId::BackgroundColor,
+            LonghandId::BackgroundImage,
+        ];
+
+        const BORDER_PROPS: &'static [LonghandId] = &[
+            LonghandId::BorderTopColor,
+            LonghandId::BorderTopStyle,
+            LonghandId::BorderTopWidth,
+            LonghandId::BorderRightColor,
+            LonghandId::BorderRightStyle,
+            LonghandId::BorderRightWidth,
+            LonghandId::BorderBottomColor,
+            LonghandId::BorderBottomStyle,
+            LonghandId::BorderBottomWidth,
+            LonghandId::BorderLeftColor,
+            LonghandId::BorderLeftStyle,
+            LonghandId::BorderLeftWidth,
+            LonghandId::BorderTopLeftRadius,
+            LonghandId::BorderTopRightRadius,
+            LonghandId::BorderBottomRightRadius,
+            LonghandId::BorderBottomLeftRadius,
+        ];
+
+        const PADDING_PROPS: &'static [LonghandId] = &[
+            LonghandId::PaddingTop,
+            LonghandId::PaddingRight,
+            LonghandId::PaddingBottom,
+            LonghandId::PaddingLeft,
+        ];
+
+        // Inherited properties:
+        const TEXT_SHADOW_PROPS: &'static [LonghandId] = &[
+            LonghandId::TextShadow,
+        ];
+
+        fn inherited(id: LonghandId) -> bool {
+            id == LonghandId::TextShadow
+        }
+
+        // Set of properties that we are currently interested in.
+        let mut properties = LonghandIdSet::new();
+
+        if rule_type_mask & NS_AUTHOR_SPECIFIED_BACKGROUND != 0 {
+            for id in BACKGROUND_PROPS {
+                properties.insert(*id);
+            }
+        }
+        if rule_type_mask & NS_AUTHOR_SPECIFIED_BORDER != 0 {
+            for id in BORDER_PROPS {
+                properties.insert(*id);
+            }
+        }
+        if rule_type_mask & NS_AUTHOR_SPECIFIED_PADDING != 0 {
+            for id in PADDING_PROPS {
+                properties.insert(*id);
+            }
+        }
+        if rule_type_mask & NS_AUTHOR_SPECIFIED_TEXT_SHADOW != 0 {
+            for id in TEXT_SHADOW_PROPS {
+                properties.insert(*id);
+            }
+        }
+
+        let mut element_rule_node = Cow::Borrowed(self);
+
+        loop {
+            // We need to be careful not to count styles covered up by user-important or
+            // UA-important declarations.  But we do want to catch explicit inherit styling in
+            // those and check our parent element to see whether we have user styling for
+            // those properties.  Note that we don't care here about inheritance due to lack of
+            // a specified value, since all the properties we care about are reset properties.
+            //
+            // FIXME: The above comment is copied from Gecko, but the last sentence is no longer
+            // correct since 'text-shadow' support was added.  This is a bug in Gecko, replicated
+            // in Stylo for now: https://bugzilla.mozilla.org/show_bug.cgi?id=1363088
+
+            let mut inherited_properties = LonghandIdSet::new();
+            let mut have_explicit_ua_inherit = false;
+
+            for node in element_rule_node.self_and_ancestors() {
+                let declarations = match node.style_source() {
+                    Some(source) => source.read(node.cascade_level().guard(guards)).declarations(),
+                    None => continue
+                };
+
+                // Iterate over declarations of the longhands we care about.
+                let node_importance = node.importance();
+                let longhands = declarations.iter().rev()
+                    .filter_map(|&(ref declaration, importance)| {
+                        if importance != node_importance { return None }
+                        match declaration.id() {
+                            PropertyDeclarationId::Longhand(id) => {
+                                Some((id, declaration))
+                            }
+                            _ => None
+                        }
+                    });
+
+                match node.cascade_level() {
+                    // Non-author rules:
+                    CascadeLevel::UANormal |
+                    CascadeLevel::UAImportant |
+                    CascadeLevel::UserNormal |
+                    CascadeLevel::UserImportant => {
+                        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 inherited from an
+                                // author rule from an ancestor element's rule nodes.
+                                if declaration.get_css_wide_keyword() == Some(CSSWideKeyword::Inherit) ||
+                                    (declaration.get_css_wide_keyword() == Some(CSSWideKeyword::Unset) &&
+                                     inherited(id))
+                                {
+                                    have_explicit_ua_inherit = true;
+                                    inherited_properties.insert(id);
+                                }
+                            }
+                        }
+                    }
+                    // Author rules:
+                    CascadeLevel::PresHints |
+                    CascadeLevel::AuthorNormal |
+                    CascadeLevel::StyleAttributeNormal |
+                    CascadeLevel::SMILOverride |
+                    CascadeLevel::Animations |
+                    CascadeLevel::AuthorImportant |
+                    CascadeLevel::StyleAttributeImportant |
+                    CascadeLevel::Transitions => {
+                        for (id, _) in longhands {
+                            if properties.contains(id) {
+                                return true
+                            }
+                        }
+                    }
+                }
+            }
+
+            if !have_explicit_ua_inherit { break }
+
+            // Continue to the parent element and search for the inherited properties.
+            element = match element.parent_element() {
+                Some(parent) => parent,
+                None => break
+            };
+            let parent_data = element.mutate_data().unwrap();
+            let parent_rule_node = parent_data.styles().primary.rules.clone();
+            element_rule_node = Cow::Owned(parent_rule_node);
+
+            properties = inherited_properties;
+        }
+
+        false
     }
 
     /// Returns true if there is either animation or transition level rule.
     pub fn has_animation_or_transition_rules(&self) -> bool {
         self.self_and_ancestors()
             .take_while(|node| node.cascade_level() >= CascadeLevel::SMILOverride)
             .any(|node| node.cascade_level().is_animation())
     }
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -996,22 +996,29 @@ pub extern "C" fn Servo_ResolvePseudoSty
         Some(values) => values.into_strong(),
         None if !is_probe => data.styles().primary.values().clone().into_strong(),
         None => Strong::null(),
     }
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_HasAuthorSpecifiedRules(rule_node: RawServoRuleNodeBorrowed,
+                                                element: RawGeckoElementBorrowed,
                                                 rule_type_mask: u32,
                                                 author_colors_allowed: bool)
     -> bool
 {
-    StrongRuleNode::from_ffi(&rule_node)
-        .has_author_specified_rules(rule_type_mask, author_colors_allowed)
+    let element = GeckoElement(element);
+    let guard = (*GLOBAL_STYLE_DATA).shared_lock.read();
+    let guards = StylesheetGuards::same(&guard);
+
+    StrongRuleNode::from_ffi(&rule_node).has_author_specified_rules(element,
+                                                                    &guards,
+                                                                    rule_type_mask,
+                                                                    author_colors_allowed)
 }
 
 fn get_pseudo_rule_node(guard: &SharedRwLockReadGuard,
                         element: GeckoElement,
                         pseudo_tag: *mut nsIAtom,
                         styles: &ElementStyles,
                         doc_data: &PerDocumentStyleData)
                         -> Option<StrongRuleNode>