Bug 1366144: Consider a display: none or empty-content ::before and ::after as not matching directly. r?heycam draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Fri, 19 May 2017 16:29:20 +0200
changeset 581230 8126636e6bfba2bbf0f6e67f745166d3c2a69a5c
parent 581229 b1d964f4a8405e10eb99e9e050fed246706f7f82
child 629524 79b6d5425ccad721777fc7486b6890b92a751dcd
push id59813
push userbmo:emilio+bugs@crisal.io
push dateFri, 19 May 2017 14:50:08 +0000
reviewersheycam
bugs1366144, 1352565, 1364880
milestone55.0a1
Bug 1366144: Consider a display: none or empty-content ::before and ::after as not matching directly. r?heycam I believe this was the approach Bobby wanted to take in bug 1352565. Fine for me to do it this way, though I suspect I need to special-case Servo_ResolvePseudoStyle to account for this too. What we do right now is pretty broken anyway I believe, see bug 1364880. MozReview-Commit-ID: BYs57CCwDCr
layout/style/ServoStyleSet.cpp
servo/components/style/matching.rs
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -779,27 +779,29 @@ ServoStyleSet::ProbePseudoElementStyle(E
                              /* is_probe = */ true, mRawSet.get()).Consume();
   if (!computedValues) {
     return nullptr;
   }
 
   // For :before and :after pseudo-elements, having display: none or no
   // 'content' property is equivalent to not having the pseudo-element
   // at all.
+  //
+  // Servo handles this during the traversal, so we should never arrive here
+  // with display: none or no content.
+#ifdef DEBUG
   bool isBeforeOrAfter = aType == CSSPseudoElementType::before ||
                          aType == CSSPseudoElementType::after;
   if (isBeforeOrAfter) {
     const nsStyleDisplay* display = Servo_GetStyleDisplay(computedValues);
     const nsStyleContent* content = Servo_GetStyleContent(computedValues);
-    // XXXldb What is contentCount for |content: ""|?
-    if (display->mDisplay == StyleDisplay::None ||
-        content->ContentCount() == 0) {
-      return nullptr;
-    }
+    MOZ_ASSERT(display->mDisplay != StyleDisplay::None);
+    MOZ_ASSERT(content->ContentCount() > 0);
   }
+#endif
 
   nsIAtom* pseudoTag = nsCSSPseudoElements::GetPseudoAtom(aType);
   return GetContext(computedValues.forget(), aParentContext, pseudoTag, aType,
                     isBeforeOrAfter ? aOriginatingElement : nullptr);
 }
 
 already_AddRefed<nsStyleContext>
 ServoStyleSet::ProbePseudoElementStyle(Element* aOriginatingElement,
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -567,47 +567,47 @@ trait PrivateMatchMethods: TElement {
     }
 
     fn cascade_eager_pseudo(&self,
                             context: &mut StyleContext<Self>,
                             data: &mut ElementData,
                             pseudo: &PseudoElement) {
         debug_assert!(pseudo.is_eager());
         let (mut styles, restyle) = data.styles_and_restyle_mut();
-        let mut pseudo_style = styles.pseudos.get_mut(pseudo).unwrap();
-        let old_values = pseudo_style.values.take();
 
-        let new_values =
-            self.cascade_internal(context, &styles.primary, Some(pseudo_style));
+        let should_be_removed = {
+            let mut pseudo_style = styles.pseudos.get_mut(pseudo).unwrap();
+            let old_values = pseudo_style.values.take();
+
+            let new_values =
+                self.cascade_internal(context, &styles.primary, Some(pseudo_style));
 
-        if let Some(old) = old_values {
-            // ::before and ::after are element-backed in Gecko, so they do the
-            // damage calculation for themselves, when there's an actual pseudo.
-            //
-            // NOTE(emilio): An alternative to this check is to just remove the
-            // pseudo-style entry here if new_values is display: none or has
-            // an ineffective content property...
-            //
-            // I think it's nice to handle it here instead for symmetry with how
-            // we handle display: none elements, but the other approach may be
-            // ok too?
-            let is_unexisting_before_or_after =
+            if let Some(old) = old_values {
+                if cfg!(feature = "servo") || !pseudo.is_before_or_after() {
+                    self.accumulate_damage(&context.shared,
+                                           restyle.unwrap(),
+                                           &old,
+                                           &new_values,
+                                           Some(pseudo));
+                }
+            }
+
+            let should_be_removed =
                 pseudo.is_before_or_after() &&
-                self.existing_style_for_restyle_damage(&old, Some(pseudo)).is_none();
+                (new_values.get_box().clone_display() == display::T::none ||
+                 new_values.ineffective_content_property());
+
+            pseudo_style.values = Some(new_values);
 
-            if cfg!(feature = "servo") || is_unexisting_before_or_after {
-                self.accumulate_damage(&context.shared,
-                                       restyle.unwrap(),
-                                       &old,
-                                       &new_values,
-                                       Some(pseudo));
-            }
+            should_be_removed
+        };
+
+        if should_be_removed {
+            styles.pseudos.take(pseudo);
         }
-
-        pseudo_style.values = Some(new_values)
     }
 
 
     /// get_after_change_style removes the transition rules from the ComputedValues.
     /// If there is no transition rule in the ComputedValues, it returns None.
     #[cfg(feature = "gecko")]
     fn get_after_change_style(&self,
                               context: &mut StyleContext<Self>,
@@ -1411,28 +1411,16 @@ pub trait MatchMethods : TElement {
         //
         // This happens with display:none elements, and not-yet-existing
         // pseudo-elements.
         if new_style_is_display_none && old_style_is_display_none {
             // The style remains display:none. No need for damage.
             return RestyleDamage::empty()
         }
 
-        if pseudo.map_or(false, |p| p.is_before_or_after()) {
-            debug_assert!(old_style_is_display_none ||
-                          old_values.ineffective_content_property());
-            if new_style_is_display_none ||
-               new_values.ineffective_content_property() {
-                // The pseudo-element will remain undisplayed, so just avoid
-                // triggering any change.
-                return RestyleDamage::empty()
-            }
-            return RestyleDamage::reconstruct()
-        }
-
         // Something else. Be conservative for now.
         warn!("Reframing due to lack of old style source: {:?}, pseudo: {:?}",
                self, pseudo);
         RestyleDamage::reconstruct()
     }
 
     /// Cascade the eager pseudo-elements of this element.
     fn cascade_pseudos(&self,