Bug 1331047: Implement the new traversal semantics for stylo. r?bholley,hiro draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Sat, 22 Apr 2017 23:12:01 +0200
changeset 568644 b50eeee21129d1d7dee0e984ea5793c8b9ccbb57
parent 568643 708d1afaa6553f5672137a3c720bf5a6dc46a5f2
child 568645 54ed97f40063b9c35f26da984cbb02d4e602ba9f
push id55935
push userbmo:emilio+bugs@crisal.io
push dateWed, 26 Apr 2017 11:57:38 +0000
reviewersbholley, hiro
bugs1331047
milestone55.0a1
Bug 1331047: Implement the new traversal semantics for stylo. r?bholley,hiro MozReview-Commit-ID: 4BXx9JpGZKX
layout/base/ServoRestyleManager.cpp
layout/style/ServoBindings.cpp
layout/style/ServoBindings.h
layout/style/ServoStyleSet.cpp
servo/components/script/layout_wrapper.rs
servo/components/style/context.rs
servo/components/style/data.rs
servo/components/style/dom.rs
servo/components/style/gecko/selector_parser.rs
servo/components/style/gecko/wrapper.rs
servo/components/style/matching.rs
servo/components/style/restyle_hints.rs
servo/components/style/stylist.rs
servo/components/style/traversal.rs
servo/ports/geckolib/glue.rs
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -208,19 +208,27 @@ ServoRestyleManager::ProcessPostTraversa
   // elements, though that is buggy right now even in non-stylo mode, see
   // bug 1251799.
   const bool recreateContext = oldStyleContext &&
     oldStyleContext->StyleSource().AsServoComputedValues() != computedValues;
 
   RefPtr<nsStyleContext> newContext = nullptr;
   if (recreateContext) {
     MOZ_ASSERT(styleFrame || displayContentsNode);
+
+    auto pseudo = aElement->GetPseudoElementType();
+    nsIAtom* pseudoTag = pseudo == CSSPseudoElementType::NotPseudo
+      ? nullptr : nsCSSPseudoElements::GetPseudoAtom(pseudo);
+
     newContext =
-      aStyleSet->GetContext(computedValues.forget(), aParentContext, nullptr,
-                            CSSPseudoElementType::NotPseudo, aElement);
+      aStyleSet->GetContext(computedValues.forget(),
+                            aParentContext,
+                            pseudoTag,
+                            pseudo,
+                            aElement);
 
     newContext->EnsureSameStructsCached(oldStyleContext);
 
     // XXX This could not always work as expected: there are kinds of content
     // with the first split and the last sharing style, but others not. We
     // should handle those properly.
     // XXXbz I think the UpdateStyleOfOwnedAnonBoxes call below handles _that_
     // right, but not other cases where we happen to have different styles on
@@ -233,57 +241,16 @@ ServoRestyleManager::ProcessPostTraversa
     if (MOZ_UNLIKELY(displayContentsNode)) {
       MOZ_ASSERT(!styleFrame);
       displayContentsNode->mStyle = newContext;
     }
 
     if (styleFrame) {
       styleFrame->UpdateStyleOfOwnedAnonBoxes(*aStyleSet, aChangeList, changeHint);
     }
-
-    // Update pseudo-elements state if appropriate.
-    const static CSSPseudoElementType pseudosToRestyle[] = {
-      CSSPseudoElementType::before,
-      CSSPseudoElementType::after,
-    };
-
-    for (CSSPseudoElementType pseudoType : pseudosToRestyle) {
-      nsIAtom* pseudoTag = nsCSSPseudoElements::GetPseudoAtom(pseudoType);
-
-      if (nsIFrame* pseudoFrame = FrameForPseudoElement(aElement, pseudoTag)) {
-        // TODO: we could maybe make this more performant via calling into
-        // Servo just once to know which pseudo-elements we've got to restyle?
-        RefPtr<nsStyleContext> pseudoContext =
-          aStyleSet->ProbePseudoElementStyle(aElement, pseudoType, newContext);
-        MOZ_ASSERT(pseudoContext, "should have taken the ReconstructFrame path above");
-        pseudoFrame->SetStyleContext(pseudoContext);
-
-        if (pseudoFrame->GetStateBits() & NS_FRAME_OWNS_ANON_BOXES) {
-          // XXX It really would be good to pass the actual changehint for our
-          // ::before/::after here, but we never computed it!
-          pseudoFrame->UpdateStyleOfOwnedAnonBoxes(*aStyleSet, aChangeList,
-                                                   nsChangeHint_Hints_NotHandledForDescendants);
-        }
-
-        // We only care restyling text nodes, since other type of nodes
-        // (images), are still not supported. If that eventually changes, we
-        // may have to write more code here... Or not, I don't think too
-        // many inherited properties can affect those other frames.
-        StyleChildrenIterator it(pseudoFrame->GetContent());
-        for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
-          if (n->IsNodeOfType(nsINode::eTEXT)) {
-            RefPtr<nsStyleContext> childContext =
-              aStyleSet->ResolveStyleForText(n, pseudoContext);
-            MOZ_ASSERT(n->GetPrimaryFrame(),
-                       "How? This node is created at FC time!");
-            n->GetPrimaryFrame()->SetStyleContext(childContext);
-          }
-        }
-      }
-    }
   }
 
   bool descendantsNeedFrames = aElement->HasFlag(NODE_DESCENDANTS_NEED_FRAMES);
   bool traverseElementChildren =
     aElement->HasDirtyDescendantsForServo() || descendantsNeedFrames;
   bool traverseTextChildren = recreateContext || descendantsNeedFrames;
   if (traverseElementChildren || traverseTextChildren) {
     nsStyleContext* upToDateContext =
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -282,37 +282,45 @@ Gecko_SetOwnerDocumentNeedsStyleFlush(Ra
 
   if (nsIPresShell* shell = aElement->OwnerDoc()->GetShell()) {
     shell->SetNeedStyleFlush();
     shell->ObserveStyleFlushes();
   }
 }
 
 nsStyleContext*
-Gecko_GetStyleContext(RawGeckoNodeBorrowed aNode, nsIAtom* aPseudoTagOrNull)
+Gecko_GetStyleContext(RawGeckoElementBorrowed aElement,
+                      nsIAtom* aPseudoTagOrNull)
 {
-  MOZ_ASSERT(aNode->IsContent());
   nsIFrame* relevantFrame =
-    ServoRestyleManager::FrameForPseudoElement(aNode->AsContent(),
-                                               aPseudoTagOrNull);
+    ServoRestyleManager::FrameForPseudoElement(aElement, aPseudoTagOrNull);
   if (relevantFrame) {
     return relevantFrame->StyleContext();
   }
 
   if (aPseudoTagOrNull) {
     return nullptr;
   }
 
   // FIXME(emilio): Is there a shorter path?
   nsCSSFrameConstructor* fc =
-    aNode->OwnerDoc()->GetShell()->GetPresContext()->FrameConstructor();
+    aElement->OwnerDoc()->GetShell()->GetPresContext()->FrameConstructor();
 
   // NB: This is only called for CalcStyleDifference, and we handle correctly
   // the display: none case since Servo still has the older style.
-  return fc->GetDisplayContentsStyleFor(aNode->AsContent());
+  return fc->GetDisplayContentsStyleFor(aElement);
+}
+
+nsIAtom*
+Gecko_GetImplementedPseudo(RawGeckoElementBorrowed aElement)
+{
+  CSSPseudoElementType pseudo = aElement->GetPseudoElementType();
+  if (pseudo == CSSPseudoElementType::NotPseudo)
+    return nullptr;
+  return nsCSSPseudoElements::GetPseudoAtom(pseudo);
 }
 
 nsChangeHint
 Gecko_CalcStyleDifference(nsStyleContext* aOldStyleContext,
                           ServoComputedValuesBorrowed aComputedValues)
 {
   MOZ_ASSERT(aOldStyleContext);
   MOZ_ASSERT(aComputedValues);
@@ -407,79 +415,90 @@ Gecko_GetExtraContentStyleDeclarations(R
   const HTMLTableCellElement* cell = static_cast<const HTMLTableCellElement*>(aElement);
   if (nsMappedAttributes* attrs = cell->GetMappedAttributesInheritedFromTable()) {
     const RefPtr<RawServoDeclarationBlock>& servo = attrs->GetServoStyle();
     return reinterpret_cast<const RawServoDeclarationBlockStrong*>(&servo);
   }
   return nullptr;
 }
 
+static nsIAtom*
+PseudoTagAndCorrectElementFor(const Element*& aElementOrPseudo) {
+  if (aElementOrPseudo->IsGeneratedContentContainerForBefore()) {
+    aElementOrPseudo = aElementOrPseudo->GetParent()->AsElement();
+    return nsCSSPseudoElements::before;
+  }
+
+  if (aElementOrPseudo->IsGeneratedContentContainerForAfter()) {
+    aElementOrPseudo = aElementOrPseudo->GetParent()->AsElement();
+    return nsCSSPseudoElements::after;
+  }
+
+  return nullptr;
+}
+
 bool
 Gecko_GetAnimationRule(RawGeckoElementBorrowed aElement,
-                       nsIAtom* aPseudoTag,
                        EffectCompositor::CascadeLevel aCascadeLevel,
                        RawServoAnimationValueMapBorrowed aAnimationValues)
 {
-  MOZ_ASSERT(aElement, "Invalid GeckoElement");
-  MOZ_ASSERT(!aPseudoTag ||
-             aPseudoTag == nsCSSPseudoElements::before ||
-             aPseudoTag == nsCSSPseudoElements::after);
+  MOZ_ASSERT(aElement);
 
   nsIDocument* doc = aElement->GetComposedDoc();
   if (!doc || !doc->GetShell()) {
     return false;
   }
   nsPresContext* presContext = doc->GetShell()->GetPresContext();
   if (!presContext || !presContext->IsDynamic()) {
     // For print or print preview, ignore animations.
     return false;
   }
 
+  nsIAtom* pseudoTag = PseudoTagAndCorrectElementFor(aElement);
+
   CSSPseudoElementType pseudoType =
     nsCSSPseudoElements::GetPseudoType(
-      aPseudoTag,
+      pseudoTag,
       nsCSSProps::EnabledState::eIgnoreEnabledState);
 
   return presContext->EffectCompositor()
-    ->GetServoAnimationRule(aElement, pseudoType,
+    ->GetServoAnimationRule(aElement,
+                            pseudoType,
                             aCascadeLevel,
                             aAnimationValues);
 }
 
 bool
 Gecko_StyleAnimationsEquals(RawGeckoStyleAnimationListBorrowed aA,
                             RawGeckoStyleAnimationListBorrowed aB)
 {
   return *aA == *aB;
 }
 
 void
 Gecko_UpdateAnimations(RawGeckoElementBorrowed aElement,
-                       nsIAtom* aPseudoTagOrNull,
                        ServoComputedValuesBorrowedOrNull aOldComputedValues,
                        ServoComputedValuesBorrowedOrNull aComputedValues,
                        ServoComputedValuesBorrowedOrNull aParentComputedValues,
                        UpdateAnimationsTasks aTasks)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aElement);
-  MOZ_ASSERT(!aPseudoTagOrNull ||
-             aPseudoTagOrNull == nsCSSPseudoElements::before ||
-             aPseudoTagOrNull == nsCSSPseudoElements::after);
 
   nsPresContext* presContext = nsContentUtils::GetContextForContent(aElement);
   if (!presContext) {
     return;
   }
 
+  nsIAtom* pseudoTag = PseudoTagAndCorrectElementFor(aElement);
   if (presContext->IsDynamic() && aElement->IsInComposedDoc()) {
     const ServoComputedValuesWithParent servoValues =
       { aComputedValues, aParentComputedValues };
     CSSPseudoElementType pseudoType =
-      nsCSSPseudoElements::GetPseudoType(aPseudoTagOrNull,
+      nsCSSPseudoElements::GetPseudoType(pseudoTag,
                                          CSSEnabledState::eForAllContent);
 
     if (aTasks & UpdateAnimationsTasks::CSSAnimations) {
       presContext->AnimationManager()->
         UpdateAnimations(const_cast<dom::Element*>(aElement), pseudoType,
                          servoValues);
     }
 
@@ -504,99 +523,89 @@ Gecko_UpdateAnimations(RawGeckoElementBo
     if (aTasks & UpdateAnimationsTasks::EffectProperties) {
       presContext->EffectCompositor()->UpdateEffectProperties(
         servoValues, const_cast<dom::Element*>(aElement), pseudoType);
     }
   }
 }
 
 bool
-Gecko_ElementHasAnimations(RawGeckoElementBorrowed aElement,
-                           nsIAtom* aPseudoTagOrNull)
+Gecko_ElementHasAnimations(RawGeckoElementBorrowed aElement)
 {
-  MOZ_ASSERT(!aPseudoTagOrNull ||
-             aPseudoTagOrNull == nsCSSPseudoElements::before ||
-             aPseudoTagOrNull == nsCSSPseudoElements::after);
-
+  nsIAtom* pseudoTag = PseudoTagAndCorrectElementFor(aElement);
   CSSPseudoElementType pseudoType =
-    nsCSSPseudoElements::GetPseudoType(aPseudoTagOrNull,
+    nsCSSPseudoElements::GetPseudoType(pseudoTag,
                                        CSSEnabledState::eForAllContent);
 
   return !!EffectSet::GetEffectSet(aElement, pseudoType);
 }
 
 bool
-Gecko_ElementHasCSSAnimations(RawGeckoElementBorrowed aElement,
-                              nsIAtom* aPseudoTagOrNull)
+Gecko_ElementHasCSSAnimations(RawGeckoElementBorrowed aElement)
 {
+  nsIAtom* pseudoTag = PseudoTagAndCorrectElementFor(aElement);
   nsAnimationManager::CSSAnimationCollection* collection =
     nsAnimationManager::CSSAnimationCollection
-                      ::GetAnimationCollection(aElement, aPseudoTagOrNull);
+                      ::GetAnimationCollection(aElement, pseudoTag);
 
   return collection && !collection->mAnimations.IsEmpty();
 }
 
 bool
-Gecko_ElementHasCSSTransitions(RawGeckoElementBorrowed aElement,
-                               nsIAtom* aPseudoTagOrNull)
+Gecko_ElementHasCSSTransitions(RawGeckoElementBorrowed aElement)
 {
+  nsIAtom* pseudoTag = PseudoTagAndCorrectElementFor(aElement);
   nsTransitionManager::CSSTransitionCollection* collection =
     nsTransitionManager::CSSTransitionCollection
-                       ::GetAnimationCollection(aElement, aPseudoTagOrNull);
+                       ::GetAnimationCollection(aElement, pseudoTag);
 
   return collection && !collection->mAnimations.IsEmpty();
 }
 
 size_t
-Gecko_ElementTransitions_Length(RawGeckoElementBorrowed aElement,
-                                nsIAtom* aPseudoTagOrNull)
+Gecko_ElementTransitions_Length(RawGeckoElementBorrowed aElement)
 {
+  nsIAtom* pseudoTag = PseudoTagAndCorrectElementFor(aElement);
   nsTransitionManager::CSSTransitionCollection* collection =
     nsTransitionManager::CSSTransitionCollection
-                       ::GetAnimationCollection(aElement, aPseudoTagOrNull);
+                       ::GetAnimationCollection(aElement, pseudoTag);
 
   return collection ? collection->mAnimations.Length() : 0;
 }
 
 static CSSTransition*
-GetCurrentTransitionAt(RawGeckoElementBorrowed aElement,
-                       nsIAtom* aPseudoTagOrNull,
-                       size_t aIndex)
+GetCurrentTransitionAt(RawGeckoElementBorrowed aElement, size_t aIndex)
 {
+  nsIAtom* pseudoTag = PseudoTagAndCorrectElementFor(aElement);
   nsTransitionManager::CSSTransitionCollection* collection =
     nsTransitionManager::CSSTransitionCollection
-                       ::GetAnimationCollection(aElement, aPseudoTagOrNull);
+                       ::GetAnimationCollection(aElement, pseudoTag);
   if (!collection) {
     return nullptr;
   }
   nsTArray<RefPtr<CSSTransition>>& transitions = collection->mAnimations;
   return aIndex < transitions.Length()
          ? transitions[aIndex].get()
          : nullptr;
 }
 
 nsCSSPropertyID
 Gecko_ElementTransitions_PropertyAt(RawGeckoElementBorrowed aElement,
-                                    nsIAtom* aPseudoTagOrNull,
                                     size_t aIndex)
 {
-  CSSTransition* transition = GetCurrentTransitionAt(aElement,
-                                                     aPseudoTagOrNull,
-                                                     aIndex);
+  CSSTransition* transition = GetCurrentTransitionAt(aElement, aIndex);
   return transition ? transition->TransitionProperty()
                     : nsCSSPropertyID::eCSSProperty_UNKNOWN;
 }
 
 RawServoAnimationValueBorrowedOrNull
 Gecko_ElementTransitions_EndValueAt(RawGeckoElementBorrowed aElement,
-                                    nsIAtom* aPseudoTagOrNull,
                                     size_t aIndex)
 {
   CSSTransition* transition = GetCurrentTransitionAt(aElement,
-                                                     aPseudoTagOrNull,
                                                      aIndex);
   return transition ? transition->ToValue().mServo.get() : nullptr;
 }
 
 double
 Gecko_GetProgressFromComputedTiming(RawGeckoComputedTimingBorrowed aComputedTiming)
 {
   return aComputedTiming->mProgress.Value();
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -185,43 +185,35 @@ RawServoDeclarationBlockStrongBorrowedOr
 Gecko_GetStyleAttrDeclarationBlock(RawGeckoElementBorrowed element);
 RawServoDeclarationBlockStrongBorrowedOrNull
 Gecko_GetHTMLPresentationAttrDeclarationBlock(RawGeckoElementBorrowed element);
 RawServoDeclarationBlockStrongBorrowedOrNull
 Gecko_GetExtraContentStyleDeclarations(RawGeckoElementBorrowed element);
 
 // Animations
 bool
-Gecko_GetAnimationRule(RawGeckoElementBorrowed aElement,
-                       nsIAtom* aPseudoTag,
+Gecko_GetAnimationRule(RawGeckoElementBorrowed aElementOrPseudo,
                        mozilla::EffectCompositor::CascadeLevel aCascadeLevel,
                        RawServoAnimationValueMapBorrowed aAnimationValues);
 bool Gecko_StyleAnimationsEquals(RawGeckoStyleAnimationListBorrowed,
                                  RawGeckoStyleAnimationListBorrowed);
-void Gecko_UpdateAnimations(RawGeckoElementBorrowed aElement,
-                            nsIAtom* aPseudoTagOrNull,
+void Gecko_UpdateAnimations(RawGeckoElementBorrowed aElementOrPseudo,
                             ServoComputedValuesBorrowedOrNull aOldComputedValues,
                             ServoComputedValuesBorrowedOrNull aComputedValues,
                             ServoComputedValuesBorrowedOrNull aParentComputedValues,
                             mozilla::UpdateAnimationsTasks aTasks);
-bool Gecko_ElementHasAnimations(RawGeckoElementBorrowed aElement,
-                                nsIAtom* aPseudoTagOrNull);
-bool Gecko_ElementHasCSSAnimations(RawGeckoElementBorrowed aElement,
-                                   nsIAtom* aPseudoTagOrNull);
-bool Gecko_ElementHasCSSTransitions(RawGeckoElementBorrowed aElement,
-                                    nsIAtom* aPseudoTagOrNull);
-size_t Gecko_ElementTransitions_Length(RawGeckoElementBorrowed aElement,
-                                       nsIAtom* aPseudoTagOrNull);
+bool Gecko_ElementHasAnimations(RawGeckoElementBorrowed aElementOrPseudo);
+bool Gecko_ElementHasCSSAnimations(RawGeckoElementBorrowed aElementOrPseudo);
+bool Gecko_ElementHasCSSTransitions(RawGeckoElementBorrowed aElementOrPseudo);
+size_t Gecko_ElementTransitions_Length(RawGeckoElementBorrowed aElementOrPseudo);
 nsCSSPropertyID Gecko_ElementTransitions_PropertyAt(
-  RawGeckoElementBorrowed aElement,
-  nsIAtom* aPseudoTagOrNull,
+  RawGeckoElementBorrowed aElementOrPseudo,
   size_t aIndex);
 RawServoAnimationValueBorrowedOrNull Gecko_ElementTransitions_EndValueAt(
-  RawGeckoElementBorrowed aElement,
-  nsIAtom* aPseudoTagOrNull,
+  RawGeckoElementBorrowed aElementOrPseudo,
   size_t aIndex);
 double Gecko_GetProgressFromComputedTiming(RawGeckoComputedTimingBorrowed aComputedTiming);
 double Gecko_GetPositionInSegment(
   RawGeckoAnimationPropertySegmentBorrowed aSegment,
   double aProgress,
   mozilla::ComputedTimingFunction::BeforeFlag aBeforeFlag);
 // Get servo's AnimationValue for |aProperty| from the cached base style
 // |aBaseStyles|.
@@ -306,18 +298,19 @@ void Gecko_SetContentDataArray(nsStyleCo
 uint32_t Gecko_GetNodeFlags(RawGeckoNodeBorrowed node);
 void Gecko_SetNodeFlags(RawGeckoNodeBorrowed node, uint32_t flags);
 void Gecko_UnsetNodeFlags(RawGeckoNodeBorrowed node, uint32_t flags);
 void Gecko_SetOwnerDocumentNeedsStyleFlush(RawGeckoElementBorrowed element);
 
 // Incremental restyle.
 // Also, we might want a ComputedValues to ComputedValues API for animations?
 // Not if we do them in Gecko...
-nsStyleContext* Gecko_GetStyleContext(RawGeckoNodeBorrowed node,
+nsStyleContext* Gecko_GetStyleContext(RawGeckoElementBorrowed element,
                                       nsIAtom* aPseudoTagOrNull);
+nsIAtom* Gecko_GetImplementedPseudo(RawGeckoElementBorrowed element);
 nsChangeHint Gecko_CalcStyleDifference(nsStyleContext* oldstyle,
                                        ServoComputedValuesBorrowed newstyle);
 nsChangeHint Gecko_HintsHandledForDescendants(nsChangeHint aHint);
 
 // Element snapshot.
 ServoElementSnapshotOwned Gecko_CreateElementSnapshot(RawGeckoElementBorrowed element);
 void Gecko_DropElementSnapshot(ServoElementSnapshotOwned snapshot);
 
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -436,17 +436,16 @@ ServoStyleSet::ResolveTransientStyle(Ele
                     aPseudoType, nullptr);
 }
 
 already_AddRefed<ServoComputedValues>
 ServoStyleSet::ResolveTransientServoStyle(Element* aElement,
                                           nsIAtom* aPseudoTag)
 {
   PreTraverseSync();
-
   return ResolveStyleLazily(aElement, aPseudoTag);
 }
 
 // aFlags is an nsStyleSet flags bitfield
 already_AddRefed<nsStyleContext>
 ServoStyleSet::ResolveInheritingAnonymousBoxStyle(nsIAtom* aPseudoTag,
                                                   nsStyleContext* aParentContext,
                                                   uint32_t aFlags)
--- a/servo/components/script/layout_wrapper.rs
+++ b/servo/components/script/layout_wrapper.rs
@@ -456,26 +456,26 @@ impl<'le> TElement for ServoLayoutElemen
     unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags) {
         self.element.insert_selector_flags(flags);
     }
 
     fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool {
         self.element.has_selector_flags(flags)
     }
 
-    fn has_animations(&self, _pseudo: Option<&PseudoElement>) -> bool {
-        panic!("this should be only called on gecko");
+    fn has_animations(&self) -> bool {
+        unreachable!("this should be only called on gecko");
     }
 
-    fn has_css_animations(&self, _pseudo: Option<&PseudoElement>) -> bool {
-        panic!("this should be only called on gecko");
+    fn has_css_animations(&self) -> bool {
+        unreachable!("this should be only called on gecko");
     }
 
-    fn has_css_transitions(&self, _pseudo: Option<&PseudoElement>) -> bool {
-        panic!("this should be only called on gecko");
+    fn has_css_transitions(&self) -> bool {
+        unreachable!("this should be only called on gecko");
     }
 }
 
 impl<'le> PartialEq for ServoLayoutElement<'le> {
     fn eq(&self, other: &Self) -> bool {
         self.as_node() == other.as_node()
     }
 }
--- a/servo/components/style/context.rs
+++ b/servo/components/style/context.rs
@@ -15,17 +15,16 @@ use dom::{OpaqueNode, TNode, TElement, S
 use error_reporting::ParseErrorReporter;
 use euclid::Size2D;
 use fnv::FnvHashMap;
 use font_metrics::FontMetricsProvider;
 #[cfg(feature = "gecko")] use gecko_bindings::structs;
 use matching::StyleSharingCandidateCache;
 use parking_lot::RwLock;
 #[cfg(feature = "gecko")] use properties::ComputedValues;
-#[cfg(feature = "gecko")] use selector_parser::PseudoElement;
 use selectors::matching::ElementSelectorFlags;
 #[cfg(feature = "servo")] use servo_config::opts;
 use shared_lock::StylesheetGuards;
 use std::collections::HashMap;
 #[cfg(not(feature = "servo"))] use std::env;
 use std::fmt;
 use std::ops::Add;
 use std::sync::{Arc, Mutex};
@@ -265,17 +264,18 @@ impl TraversalStatistics {
     /// from lots of tiny traversals.
     pub fn is_large_traversal(&self) -> bool {
         self.elements_traversed >= 50
     }
 }
 
 #[cfg(feature = "gecko")]
 bitflags! {
-    /// Represents which tasks are performed in a SequentialTask of UpdateAnimations.
+    /// Represents which tasks are performed in a SequentialTask of
+    /// UpdateAnimations.
     pub flags UpdateAnimationsTasks: u8 {
         /// Update CSS Animations.
         const CSS_ANIMATIONS = structs::UpdateAnimationsTasks_CSSAnimations,
         /// Update CSS Transitions.
         const CSS_TRANSITIONS = structs::UpdateAnimationsTasks_CSSTransitions,
         /// Update effect properties.
         const EFFECT_PROPERTIES = structs::UpdateAnimationsTasks_EffectProperties,
         /// Update animation cacade results for animations running on the compositor.
@@ -291,54 +291,52 @@ pub enum SequentialTask<E: TElement> {
     /// Entry to avoid an unused type parameter error on servo.
     Unused(SendElement<E>),
 
     /// Performs one of a number of possible tasks related to updating animations based on the
     /// |tasks| field. These include updating CSS animations/transitions that changed as part
     /// of the non-animation style traversal, and updating the computed effect properties.
     #[cfg(feature = "gecko")]
     UpdateAnimations {
-        /// The target element.
+        /// The target element or pseudo-element.
         el: SendElement<E>,
-        /// The target pseudo element.
-        pseudo: Option<PseudoElement>,
         /// The before-change style for transitions. We use before-change style as the initial
         /// value of its Keyframe. Required if |tasks| includes CSSTransitions.
         before_change_style: Option<Arc<ComputedValues>>,
         /// The tasks which are performed in this SequentialTask.
         tasks: UpdateAnimationsTasks
     },
 }
 
 impl<E: TElement> SequentialTask<E> {
     /// Executes this task.
     pub fn execute(self) {
         use self::SequentialTask::*;
         debug_assert!(thread_state::get() == thread_state::LAYOUT);
         match self {
             Unused(_) => unreachable!(),
             #[cfg(feature = "gecko")]
-            UpdateAnimations { el, pseudo, before_change_style, tasks } => {
-                unsafe { el.update_animations(pseudo.as_ref(), before_change_style, tasks) };
+            UpdateAnimations { el, before_change_style, tasks } => {
+                unsafe { el.update_animations(before_change_style, tasks) };
             }
         }
     }
 
     /// Creates a task to update various animation-related state on
     /// a given (pseudo-)element.
     #[cfg(feature = "gecko")]
     pub fn update_animations(el: E,
-                             pseudo: Option<PseudoElement>,
                              before_change_style: Option<Arc<ComputedValues>>,
                              tasks: UpdateAnimationsTasks) -> Self {
         use self::SequentialTask::*;
-        UpdateAnimations { el: unsafe { SendElement::new(el) },
-                           pseudo: pseudo,
-                           before_change_style: before_change_style,
-                           tasks: tasks }
+        UpdateAnimations {
+            el: unsafe { SendElement::new(el) },
+            before_change_style: before_change_style,
+            tasks: tasks,
+        }
     }
 }
 
 /// Map from Elements to ElementSelectorFlags. Used to defer applying selector
 /// flags until after the traversal.
 pub struct SelectorFlagsMap<E: TElement> {
     /// The hashmap storing the flags to apply.
     map: FnvHashMap<SendElement<E>, ElementSelectorFlags>,
--- a/servo/components/style/data.rs
+++ b/servo/components/style/data.rs
@@ -527,16 +527,31 @@ impl ElementData {
 
     /// Sets the computed element styles.
     pub fn set_styles(&mut self, styles: ElementStyles) {
         debug_assert!(self.get_restyle().map_or(true, |r| r.snapshot.is_none()),
                       "Traversal should have expanded snapshots");
         self.styles = Some(styles);
     }
 
+    /// Set the computed element rules, and returns whether the rules changed.
+    pub fn set_rules(&mut self, rules: StrongRuleNode) -> bool {
+        if !self.has_styles() {
+            self.set_styles(ElementStyles::new(ComputedStyle::new_partial(rules)));
+            return true;
+        }
+
+        if self.styles().primary.rules == rules {
+            return false;
+        }
+
+        self.styles_mut().primary.rules = rules;
+        true
+    }
+
     /// Returns true if the Element has a RestyleData.
     pub fn has_restyle(&self) -> bool {
         self.restyle.is_some()
     }
 
     /// Drops any RestyleData.
     pub fn clear_restyle(&mut self) {
         self.restyle = None;
--- a/servo/components/style/dom.rs
+++ b/servo/components/style/dom.rs
@@ -269,21 +269,30 @@ pub unsafe fn raw_note_descendants<E, B>
 /// A trait used to synthesize presentational hints for HTML element attributes.
 pub trait PresentationalHintsSynthetizer {
     /// Generate the proper applicable declarations due to presentational hints,
     /// and insert them into `hints`.
     fn synthesize_presentational_hints_for_legacy_attributes<V>(&self, hints: &mut V)
         where V: Push<ApplicableDeclarationBlock>;
 }
 
-/// The animation rules. The first one is for Animation cascade level, and the second one is for
+/// The animation rules.
+///
+/// The first one is for Animation cascade level, and the second one is for
 /// Transition cascade level.
 pub struct AnimationRules(pub Option<Arc<Locked<PropertyDeclarationBlock>>>,
                           pub Option<Arc<Locked<PropertyDeclarationBlock>>>);
 
+impl AnimationRules {
+    /// Returns whether these animation rules represents an actual rule or not.
+    pub fn is_empty(&self) -> bool {
+        self.0.is_none() && self.1.is_none()
+    }
+}
+
 /// The element trait, the main abstraction the style crate acts over.
 pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone +
                      ElementExt + PresentationalHintsSynthetizer {
     /// The concrete node type.
     type ConcreteNode: TNode<ConcreteElement = Self>;
 
     /// Type of the font metrics provider
     ///
@@ -315,36 +324,35 @@ pub trait TElement : Eq + PartialEq + De
             self.parent_element()
         }
     }
 
     /// Get this element's style attribute.
     fn style_attribute(&self) -> Option<&Arc<Locked<PropertyDeclarationBlock>>>;
 
     /// Get this element's animation rules.
-    fn get_animation_rules(&self, _pseudo: Option<&PseudoElement>) -> AnimationRules {
+    fn get_animation_rules(&self) -> AnimationRules {
         AnimationRules(None, None)
     }
 
     /// Get this element's animation rule by the cascade level.
     fn get_animation_rule_by_cascade(&self,
-                                     _pseudo: Option<&PseudoElement>,
                                      _cascade_level: CascadeLevel)
                                      -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
         None
     }
 
     /// Get this element's animation rule.
-    fn get_animation_rule(&self, _pseudo: Option<&PseudoElement>)
+    fn get_animation_rule(&self)
                           -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
         None
     }
 
     /// Get this element's transition rule.
-    fn get_transition_rule(&self, _pseudo: Option<&PseudoElement>)
+    fn get_transition_rule(&self)
                            -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
         None
     }
 
     /// Get this element's state, for non-tree-structural pseudos.
     fn get_state(&self) -> ElementState;
 
     /// Whether this element has an attribute with a given namespace.
@@ -418,16 +426,28 @@ pub trait TElement : Eq + PartialEq + De
     /// Only safe to call with exclusive access to the element.
     unsafe fn unset_animation_only_dirty_descendants(&self) {
     }
 
     /// Returns true if this element is native anonymous (only Gecko has native
     /// anonymous content).
     fn is_native_anonymous(&self) -> bool { false }
 
+    /// Returns the pseudo-element implemented by this element, if any.
+    ///
+    /// Gecko traverses pseudo-elements during the style traversal, and we need
+    /// to know this so we can properly grab the pseudo-element style from the
+    /// parent element.
+    ///
+    /// Note that we still need to compute the pseudo-elements before-hand,
+    /// given otherwise we don't know if we need to create an element or not.
+    ///
+    /// Servo doesn't have to deal with this.
+    fn implemented_pseudo_element(&self) -> Option<PseudoElement> { None }
+
     /// Atomically stores the number of children of this node that we will
     /// need to process during bottom-up traversal.
     fn store_children_to_process(&self, n: isize);
 
     /// Atomically notes that a child has been processed during bottom-up
     /// traversal. Returns the number of children left to process.
     fn did_process_child(&self) -> isize;
 
@@ -459,76 +479,75 @@ pub trait TElement : Eq + PartialEq + De
     /// set to run after the threads join.
     unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags);
 
     /// Returns true if the element has all the specified selector flags.
     fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool;
 
     /// Creates a task to update various animation state on a given (pseudo-)element.
     #[cfg(feature = "gecko")]
-    fn update_animations(&self, _pseudo: Option<&PseudoElement>,
+    fn update_animations(&self,
                          before_change_style: Option<Arc<ComputedValues>>,
                          tasks: UpdateAnimationsTasks);
 
     /// Returns true if the element has relevant animations. Relevant
     /// animations are those animations that are affecting the element's style
     /// or are scheduled to do so in the future.
-    fn has_animations(&self, _pseudo: Option<&PseudoElement>) -> bool;
+    fn has_animations(&self) -> bool;
 
     /// Returns true if the element has a CSS animation.
-    fn has_css_animations(&self, _pseudo: Option<&PseudoElement>) -> bool;
+    fn has_css_animations(&self) -> bool;
 
     /// Returns true if the element has a CSS transition (including running transitions and
     /// completed transitions).
-    fn has_css_transitions(&self, _pseudo: Option<&PseudoElement>) -> bool;
+    fn has_css_transitions(&self) -> bool;
 
     /// Returns true if the element has animation restyle hints.
     fn has_animation_restyle_hints(&self) -> bool {
         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 the current existing CSS transitions, by |property, end value| pairs in a HashMap.
     #[cfg(feature = "gecko")]
-    fn get_css_transitions_info(&self,
-                                pseudo: Option<&PseudoElement>)
+    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
     /// we can perform the more thoroughgoing check, needs_transitions_update, to further
     /// reduce the possibility of false positives.
     #[cfg(feature = "gecko")]
     fn might_need_transitions_update(&self,
-                                     old_values: &Option<&Arc<ComputedValues>>,
-                                     new_values: &Arc<ComputedValues>,
-                                     pseudo: Option<&PseudoElement>) -> bool;
+                                     old_values: Option<&ComputedValues>,
+                                     new_values: &ComputedValues)
+                                     -> bool;
 
     /// Returns true if one of the transitions needs to be updated on this element. We check all
     /// the transition properties to make sure that updating transitions is necessary.
     /// This method should only be called if might_needs_transitions_update returns true when
     /// passed the same parameters.
     #[cfg(feature = "gecko")]
     fn needs_transitions_update(&self,
-                                before_change_style: &Arc<ComputedValues>,
-                                after_change_style: &Arc<ComputedValues>,
-                                pseudo: Option<&PseudoElement>) -> bool;
+                                before_change_style: &ComputedValues,
+                                after_change_style: &ComputedValues)
+                                -> bool;
 
     /// Returns true if we need to update transitions for the specified property on this element.
     #[cfg(feature = "gecko")]
     fn needs_transitions_update_per_property(&self,
                                              property: TransitionProperty,
                                              combined_duration: f32,
-                                             before_change_style: &Arc<ComputedValues>,
-                                             after_change_style: &Arc<ComputedValues>,
+                                             before_change_style: &ComputedValues,
+                                             after_change_style: &ComputedValues,
                                              existing_transitions: &HashMap<TransitionProperty,
                                                                             Arc<AnimationValue>>)
                                              -> bool;
 }
 
 /// Trait abstracting over different kinds of dirty-descendants bits.
 pub trait DescendantsBit<E: TElement> {
     /// Returns true if the Element has the bit.
--- a/servo/components/style/gecko/selector_parser.rs
+++ b/servo/components/style/gecko/selector_parser.rs
@@ -49,16 +49,36 @@ macro_rules! each_eager_pseudo {
     }
 }
 
 /// The number of eager pseudo-elements (just ::before and ::after).
 pub const EAGER_PSEUDO_COUNT: usize = 2;
 
 
 impl PseudoElement {
+    /// Returns the kind of cascade type that a given pseudo is going to use.
+    ///
+    /// In Gecko we only compute ::before and ::after eagerly. We save the rules
+    /// for anonymous boxes separately, so we resolve them as precomputed
+    /// pseudos.
+    ///
+    /// We resolve the others lazily, see `Servo_ResolvePseudoStyle`.
+    pub fn cascade_type(&self) -> PseudoElementCascadeType {
+        if self.is_eager() {
+            debug_assert!(!self.is_anon_box());
+            return PseudoElementCascadeType::Eager
+        }
+
+        if self.is_anon_box() {
+            return PseudoElementCascadeType::Precomputed
+        }
+
+        PseudoElementCascadeType::Lazy
+    }
+
     /// Gets the canonical index of this eagerly-cascaded pseudo-element.
     #[inline]
     pub fn eager_index(&self) -> usize {
         macro_rules! case {
             ($atom:expr, $idx:expr) => { if *self.as_atom() == $atom { return $idx; } }
         }
         each_eager_pseudo!(case, atom);
         panic!("Not eager")
@@ -432,34 +452,19 @@ impl<'a> ::selectors::Parser for Selecto
 
     fn namespace_for_prefix(&self, prefix: &Atom) -> Option<Namespace> {
         self.namespaces.prefixes.get(prefix).cloned()
     }
 }
 
 impl SelectorImpl {
     #[inline]
-    /// Returns the kind of cascade type that a given pseudo is going to use.
-    ///
-    /// In Gecko we only compute ::before and ::after eagerly. We save the rules
-    /// for anonymous boxes separately, so we resolve them as precomputed
-    /// pseudos.
-    ///
-    /// We resolve the others lazily, see `Servo_ResolvePseudoStyle`.
+    /// Legacy alias for PseudoElement::cascade_type.
     pub fn pseudo_element_cascade_type(pseudo: &PseudoElement) -> PseudoElementCascadeType {
-        if pseudo.is_eager() {
-            debug_assert!(!pseudo.is_anon_box());
-            return PseudoElementCascadeType::Eager
-        }
-
-        if pseudo.is_anon_box() {
-            return PseudoElementCascadeType::Precomputed
-        }
-
-        PseudoElementCascadeType::Lazy
+        pseudo.cascade_type()
     }
 
     /// A helper to traverse each eagerly cascaded pseudo-element, executing
     /// `fun` on it.
     #[inline]
     pub fn each_eagerly_cascaded_pseudo_element<F>(mut fun: F)
         where F: FnMut(PseudoElement),
     {
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -415,22 +415,25 @@ fn selector_flags_to_node_flags(flags: E
     if flags.contains(HAS_EMPTY_SELECTOR) {
         gecko_flags |= NODE_HAS_EMPTY_SELECTOR as u32;
     }
 
     gecko_flags
 }
 
 fn get_animation_rule(element: &GeckoElement,
-                      pseudo: Option<&PseudoElement>,
                       cascade_level: CascadeLevel)
                       -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
-    let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo);
+    // FIXME(emilio): This is quite dumb, why an RwLock, it's local to this
+    // function?
+    //
+    // Also, we should try to reuse the PDB, to avoid creating extra rule nodes.
     let animation_values = Arc::new(RwLock::new(AnimationValueMap::new()));
-    if unsafe { Gecko_GetAnimationRule(element.0, atom_ptr, cascade_level,
+    if unsafe { Gecko_GetAnimationRule(element.0,
+                                       cascade_level,
                                        HasArcFFI::arc_as_borrowed(&animation_values)) } {
         let shared_lock = &GLOBAL_STYLE_DATA.shared_lock;
         Some(Arc::new(shared_lock.wrap(
             PropertyDeclarationBlock::from_animation_value_map(&animation_values.read()))))
     } else {
         None
     }
 }
@@ -520,40 +523,38 @@ impl<'le> TElement for GeckoElement<'le>
         unsafe { GeckoNode(&*(self.0 as *const _ as *const RawGeckoNode)) }
     }
 
     fn style_attribute(&self) -> Option<&Arc<Locked<PropertyDeclarationBlock>>> {
         let declarations = unsafe { Gecko_GetStyleAttrDeclarationBlock(self.0) };
         declarations.map(|s| s.as_arc_opt()).unwrap_or(None)
     }
 
-    fn get_animation_rules(&self, pseudo: Option<&PseudoElement>) -> AnimationRules {
-        AnimationRules(self.get_animation_rule(pseudo),
-                       self.get_transition_rule(pseudo))
+    fn get_animation_rules(&self) -> AnimationRules {
+        AnimationRules(self.get_animation_rule(),
+                       self.get_transition_rule())
     }
 
-    fn get_animation_rule_by_cascade(&self,
-                                     pseudo: Option<&PseudoElement>,
-                                     cascade_level: ServoCascadeLevel)
+    fn get_animation_rule_by_cascade(&self, cascade_level: ServoCascadeLevel)
                                      -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
         match cascade_level {
-            ServoCascadeLevel::Animations => self.get_animation_rule(pseudo),
-            ServoCascadeLevel::Transitions => self.get_transition_rule(pseudo),
+            ServoCascadeLevel::Animations => self.get_animation_rule(),
+            ServoCascadeLevel::Transitions => self.get_transition_rule(),
             _ => panic!("Unsupported cascade level for getting the animation rule")
         }
     }
 
-    fn get_animation_rule(&self, pseudo: Option<&PseudoElement>)
+    fn get_animation_rule(&self)
                           -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
-        get_animation_rule(self, pseudo, CascadeLevel::Animations)
+        get_animation_rule(self, CascadeLevel::Animations)
     }
 
-    fn get_transition_rule(&self, pseudo: Option<&PseudoElement>)
+    fn get_transition_rule(&self)
                            -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
-        get_animation_rule(self, pseudo, CascadeLevel::Transitions)
+        get_animation_rule(self, CascadeLevel::Transitions)
     }
 
     fn get_state(&self) -> ElementState {
         unsafe {
             ElementState::from_bits_truncate(Gecko_ElementState(self.0))
         }
     }
 
@@ -578,17 +579,17 @@ impl<'le> TElement for GeckoElement<'le>
     }
 
     fn existing_style_for_restyle_damage<'a>(&'a self,
                                              _existing_values: &'a Arc<ComputedValues>,
                                              pseudo: Option<&PseudoElement>)
                                              -> Option<&'a nsStyleContext> {
         let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo);
         unsafe {
-            let context_ptr = Gecko_GetStyleContext(self.as_node().0, atom_ptr);
+            let context_ptr = Gecko_GetStyleContext(self.0, atom_ptr);
             context_ptr.as_ref()
         }
     }
 
     fn has_dirty_descendants(&self) -> bool {
         self.flags() & (NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO as u32) != 0
     }
 
@@ -630,16 +631,32 @@ impl<'le> TElement for GeckoElement<'le>
     unsafe fn unset_animation_only_dirty_descendants(&self) {
         self.unset_flags(NODE_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO as u32)
     }
 
     fn is_native_anonymous(&self) -> bool {
         self.flags() & (NODE_IS_NATIVE_ANONYMOUS as u32) != 0
     }
 
+    fn implemented_pseudo_element(&self) -> Option<PseudoElement> {
+        if !self.is_native_anonymous() {
+            return None;
+        }
+
+        let maybe_atom =
+            unsafe { bindings::Gecko_GetImplementedPseudo(self.0) };
+
+        if maybe_atom.is_null() {
+            return None;
+        }
+
+        let atom = Atom::from(maybe_atom);
+        Some(PseudoElement::from_atom_unchecked(atom, /* anon_box = */ false))
+    }
+
     fn store_children_to_process(&self, _: isize) {
         // This is only used for bottom-up traversal, and is thus a no-op for Gecko.
     }
 
     fn did_process_child(&self) -> isize {
         panic!("Atomic child count not implemented in Gecko");
     }
 
@@ -662,136 +679,125 @@ impl<'le> TElement for GeckoElement<'le>
     }
 
     fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool {
         let node_flags = selector_flags_to_node_flags(flags);
         (self.flags() & node_flags) == node_flags
     }
 
     fn update_animations(&self,
-                         pseudo: Option<&PseudoElement>,
                          before_change_style: Option<Arc<ComputedValues>>,
                          tasks: UpdateAnimationsTasks) {
-        // We have to update animations even if the element has no computed style
-        // since it means the element is in a display:none subtree, we should destroy
-        // all CSS animations in display:none subtree.
+        // We have to update animations even if the element has no computed
+        // style since it means the element is in a display:none subtree, we
+        // should destroy all CSS animations in display:none subtree.
         let computed_data = self.borrow_data();
         let computed_values =
-            computed_data.as_ref().map(|d|
-                pseudo.map_or_else(|| d.styles().primary.values(),
-                                   |p| d.styles().pseudos.get(p).unwrap().values())
-            );
-        let computed_values_opt = computed_values.map(|v|
-            *HasArcFFI::arc_as_borrowed(v)
-        );
-
-        let parent_element = if pseudo.is_none() {
-            self.parent_element()
-        } else {
-            Some(*self)
-        };
-        let parent_data = parent_element.as_ref().and_then(|e| e.borrow_data());
-        let parent_values = parent_data.as_ref().map(|d| d.styles().primary.values());
-        let parent_values_opt = parent_values.map(|v|
-            *HasArcFFI::arc_as_borrowed(v)
-        );
-
-        let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo);
-        let before_change_values = before_change_style.as_ref().map(|v| *HasArcFFI::arc_as_borrowed(v));
+            computed_data.as_ref().map(|d| d.styles().primary.values());
+        let computed_values_opt =
+            computed_values.map(|v| *HasArcFFI::arc_as_borrowed(v));
+        let parent_element = self.parent_element();
+        let parent_data =
+            parent_element.as_ref().and_then(|e| e.borrow_data());
+        let parent_values =
+            parent_data.as_ref().map(|d| d.styles().primary.values());
+        let parent_values_opt =
+            parent_values.map(|v| *HasArcFFI::arc_as_borrowed(v));
+        let before_change_values =
+            before_change_style.as_ref().map(|v| *HasArcFFI::arc_as_borrowed(v));
         unsafe {
-            Gecko_UpdateAnimations(self.0, atom_ptr,
+            Gecko_UpdateAnimations(self.0,
                                    before_change_values,
                                    computed_values_opt,
                                    parent_values_opt,
                                    tasks.bits());
         }
     }
 
-    fn has_animations(&self, pseudo: Option<&PseudoElement>) -> bool {
-        let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo);
-        unsafe { Gecko_ElementHasAnimations(self.0, atom_ptr) }
+    fn has_animations(&self) -> bool {
+        unsafe { Gecko_ElementHasAnimations(self.0) }
     }
 
-    fn has_css_animations(&self, pseudo: Option<&PseudoElement>) -> bool {
-        let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo);
-        unsafe { Gecko_ElementHasCSSAnimations(self.0, atom_ptr) }
+    fn has_css_animations(&self) -> bool {
+        unsafe { Gecko_ElementHasCSSAnimations(self.0) }
     }
 
-    fn has_css_transitions(&self, pseudo: Option<&PseudoElement>) -> bool {
-        let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo);
-        unsafe { Gecko_ElementHasCSSTransitions(self.0, atom_ptr) }
+    fn has_css_transitions(&self) -> bool {
+        unsafe { Gecko_ElementHasCSSTransitions(self.0) }
     }
 
-    fn get_css_transitions_info(&self,
-                                pseudo: Option<&PseudoElement>)
+    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 atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo);
-        let collection_length = unsafe { Gecko_ElementTransitions_Length(self.0, atom_ptr) };
+        let collection_length =
+            unsafe { Gecko_ElementTransitions_Length(self.0) };
         let mut map = HashMap::with_capacity(collection_length);
         for i in 0..collection_length {
             let (property, raw_end_value) = unsafe {
-                (Gecko_ElementTransitions_PropertyAt(self.0, atom_ptr, i as usize).into(),
-                 Gecko_ElementTransitions_EndValueAt(self.0, atom_ptr, i as usize))
+                (Gecko_ElementTransitions_PropertyAt(self.0, i as usize).into(),
+                 Gecko_ElementTransitions_EndValueAt(self.0, i as usize))
             };
             let end_value = AnimationValue::arc_from_borrowed(&raw_end_value);
             debug_assert!(end_value.is_some());
             map.insert(property, end_value.unwrap().clone());
         }
         map
     }
 
     fn might_need_transitions_update(&self,
-                                     old_values: &Option<&Arc<ComputedValues>>,
-                                     new_values: &Arc<ComputedValues>,
-                                     pseudo: Option<&PseudoElement>) -> bool {
+                                     old_values: Option<&ComputedValues>,
+                                     new_values: &ComputedValues) -> bool {
         use properties::longhands::display::computed_value as display;
 
-        if old_values.is_none() {
-            return false;
-        }
+        let old_values = match old_values {
+            Some(v) => v,
+            None => return false,
+        };
 
-        let ref new_box_style = new_values.get_box();
-        let transition_not_running = !self.has_css_transitions(pseudo) &&
+        let new_box_style = new_values.get_box();
+        let transition_not_running = !self.has_css_transitions() &&
                                      new_box_style.transition_property_count() == 1 &&
                                      new_box_style.transition_combined_duration_at(0) <= 0.0f32;
         let new_display_style = new_box_style.clone_display();
-        let old_display_style = old_values.map(|ref old| old.get_box().clone_display()).unwrap();
+        let old_display_style = old_values.get_box().clone_display();
 
         new_box_style.transition_property_count() > 0 &&
         !transition_not_running &&
         (new_display_style != display::T::none &&
          old_display_style != display::T::none)
     }
 
-    // Detect if there are any changes that require us to update transitions. This is used as a
-    // more thoroughgoing check than the, cheaper might_need_transitions_update check.
+    // Detect if there are any changes that require us to update transitions.
+    // This is used as a more thoroughgoing check than the, cheaper
+    // might_need_transitions_update check.
+    //
     // The following logic shadows the logic used on the Gecko side
-    // (nsTransitionManager::DoUpdateTransitions) where we actually perform the update.
+    // (nsTransitionManager::DoUpdateTransitions) where we actually perform the
+    // update.
+    //
     // https://drafts.csswg.org/css-transitions/#starting
     fn needs_transitions_update(&self,
-                                before_change_style: &Arc<ComputedValues>,
-                                after_change_style: &Arc<ComputedValues>,
-                                pseudo: Option<&PseudoElement>) -> bool {
+                                before_change_style: &ComputedValues,
+                                after_change_style: &ComputedValues)
+                                -> bool {
         use gecko_bindings::structs::nsCSSPropertyID;
         use properties::{PropertyId, animated_properties};
         use std::collections::HashSet;
 
-        debug_assert!(self.might_need_transitions_update(&Some(before_change_style),
-                                                         after_change_style,
-                                                         pseudo),
+        debug_assert!(self.might_need_transitions_update(Some(before_change_style),
+                                                         after_change_style),
                       "We should only call needs_transitions_update if \
                        might_need_transitions_update returns true");
 
-        let ref after_change_box_style = after_change_style.get_box();
+        let after_change_box_style = after_change_style.get_box();
         let transitions_count = after_change_box_style.transition_property_count();
-        let existing_transitions = self.get_css_transitions_info(pseudo);
+        let existing_transitions = self.get_css_transitions_info();
         let mut transitions_to_keep = if !existing_transitions.is_empty() &&
                                          (after_change_box_style.transition_nscsspropertyid_at(0) !=
                                               nsCSSPropertyID::eCSSPropertyExtra_all_properties) {
             Some(HashSet::<TransitionProperty>::with_capacity(transitions_count))
         } else {
             None
         };
 
@@ -852,18 +858,18 @@ impl<'le> TElement for GeckoElement<'le>
         transitions_to_keep.map_or(false, |set| {
             existing_transitions.keys().any(|property| !set.contains(property))
         })
     }
 
     fn needs_transitions_update_per_property(&self,
                                              property: TransitionProperty,
                                              combined_duration: f32,
-                                             before_change_style: &Arc<ComputedValues>,
-                                             after_change_style: &Arc<ComputedValues>,
+                                             before_change_style: &ComputedValues,
+                                             after_change_style: &ComputedValues,
                                              existing_transitions: &HashMap<TransitionProperty,
                                                                             Arc<AnimationValue>>)
                                              -> bool {
         use properties::animated_properties::AnimatedProperty;
 
         // We don't allow transitions on properties that are not interpolable.
         if property.is_discrete() {
             return false;
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -24,16 +24,25 @@ use rule_tree::{CascadeLevel, RuleTree, 
 use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl};
 use selectors::bloom::BloomFilter;
 use selectors::matching::{ElementSelectorFlags, StyleRelations};
 use selectors::matching::AFFECTED_BY_PSEUDO_ELEMENTS;
 use sink::ForgetfulSink;
 use std::sync::Arc;
 use stylist::ApplicableDeclarationBlock;
 
+/// The way a style should be inherited.
+enum InheritMode {
+    /// Inherit from the parent element, as normal CSS dictates.
+    FromParentElement,
+    /// Inherit from the primary style, this is used while computing eager
+    /// pseudos, like ::before and ::after when we're traversing the parent.
+    FromPrimaryStyle,
+}
+
 /// Determines the amount of relations where we're going to share style.
 #[inline]
 fn relations_are_shareable(relations: &StyleRelations) -> bool {
     use selectors::matching::*;
     !relations.intersects(AFFECTED_BY_ID_SELECTOR |
                           AFFECTED_BY_PSEUDO_ELEMENTS |
                           AFFECTED_BY_STYLE_ATTRIBUTE |
                           AFFECTED_BY_PRESENTATIONAL_HINTS)
@@ -72,16 +81,18 @@ pub struct StyleSharingCandidateCache<E:
     cache: LRUCache<StyleSharingCandidate<E>>,
 }
 
 /// A cache miss result.
 #[derive(Clone, Debug)]
 pub enum CacheMiss {
     /// The parents don't match.
     Parent,
+    /// One element was NAC, while the other wasn't.
+    NativeAnonymousContent,
     /// The local name of the element and the candidate don't match.
     LocalName,
     /// The namespace of the element and the candidate don't match.
     Namespace,
     /// One of the element or the candidate was a link, but the other one
     /// wasn't.
     Link,
     /// The element and the candidate match different kind of rules. This can
@@ -131,16 +142,22 @@ fn element_matches_candidate<E: TElement
     // for parent computed style. The latter check allows us to share style
     // between cousins if the parents shared style.
     let parent = element.parent_element();
     let candidate_parent = candidate_element.parent_element();
     if parent != candidate_parent && !same_computed_values(parent, candidate_parent) {
         miss!(Parent)
     }
 
+    if element.is_native_anonymous() {
+        debug_assert!(!candidate_element.is_native_anonymous(),
+                      "Why inserting NAC into the cache?");
+        miss!(NativeAnonymousContent)
+    }
+
     if *element.get_local_name() != *candidate_element.get_local_name() {
         miss!(LocalName)
     }
 
     if *element.get_namespace() != *candidate_element.get_namespace() {
         miss!(Namespace)
     }
 
@@ -292,31 +309,35 @@ impl<E: TElement> StyleSharingCandidateC
         let parent = match element.parent_element() {
             Some(element) => element,
             None => {
                 debug!("Failing to insert to the cache: no parent element");
                 return;
             }
         };
 
+        if element.is_native_anonymous() {
+            debug!("Failing to insert into the cache: NAC");
+            return;
+        }
+
         // These are things we don't check in the candidate match because they
         // are either uncommon or expensive.
         if !relations_are_shareable(&relations) {
             debug!("Failing to insert to the cache: {:?}", relations);
             return;
         }
 
         // Make sure we noted any presentational hints in the StyleRelations.
         if cfg!(debug_assertions) {
             let mut hints = ForgetfulSink::new();
             element.synthesize_presentational_hints_for_legacy_attributes(&mut hints);
             debug_assert!(hints.is_empty(), "Style relations should not be shareable!");
         }
 
-
         let box_style = style.get_box();
         if box_style.specifies_transitions() {
             debug!("Failing to insert to the cache: transitions");
             return;
         }
 
         if box_style.specifies_animations() {
             debug!("Failing to insert to the cache: animations");
@@ -381,45 +402,48 @@ trait PrivateMatchMethods: TElement {
         }
     }
 
     fn cascade_with_rules(&self,
                           shared_context: &SharedStyleContext,
                           font_metrics_provider: &FontMetricsProvider,
                           rule_node: &StrongRuleNode,
                           primary_style: &ComputedStyle,
-                          is_pseudo: bool)
+                          inherit_mode: InheritMode)
                           -> Arc<ComputedValues> {
         let mut cascade_info = CascadeInfo::new();
         let mut cascade_flags = CascadeFlags::empty();
         if self.skip_root_and_item_based_display_fixup() {
             cascade_flags.insert(SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP)
         }
 
         // Grab the inherited values.
         let parent_el;
         let parent_data;
-        let style_to_inherit_from = if !is_pseudo {
-            parent_el = self.parent_element();
-            parent_data = parent_el.as_ref().and_then(|e| e.borrow_data());
-            let parent_values = parent_data.as_ref().map(|d| {
-                // Sometimes Gecko eagerly styles things without processing
-                // pending restyles first. In general we'd like to avoid this,
-                // but there can be good reasons (for example, needing to
-                // construct a frame for some small piece of newly-added
-                // content in order to do something specific with that frame,
-                // but not wanting to flush all of layout).
-                debug_assert!(cfg!(feature = "gecko") || d.has_current_styles());
-                d.styles().primary.values()
-            });
+        let style_to_inherit_from = match inherit_mode {
+            InheritMode::FromParentElement => {
+                parent_el = self.parent_element();
+                parent_data = parent_el.as_ref().and_then(|e| e.borrow_data());
+                let parent_values = parent_data.as_ref().map(|d| {
+                    // Sometimes Gecko eagerly styles things without processing
+                    // pending restyles first. In general we'd like to avoid this,
+                    // but there can be good reasons (for example, needing to
+                    // construct a frame for some small piece of newly-added
+                    // content in order to do something specific with that frame,
+                    // but not wanting to flush all of layout).
+                    debug_assert!(cfg!(feature = "gecko") || d.has_current_styles());
+                    d.styles().primary.values()
+                });
 
-            parent_values
-        } else {
-            parent_el = Some(self.clone());
-            Some(primary_style.values())
+                parent_values
+            }
+            InheritMode::FromPrimaryStyle => {
+                parent_el = Some(self.clone());
+                Some(primary_style.values())
+            }
         };
 
         let mut layout_parent_el = parent_el.clone();
         let layout_parent_data;
         let mut layout_parent_style = style_to_inherit_from;
         if style_to_inherit_from.map_or(false, |s| s.is_display_contents()) {
             layout_parent_el = Some(layout_parent_el.unwrap().layout_parent());
             layout_parent_data = layout_parent_el.as_ref().unwrap().borrow_data().unwrap();
@@ -427,25 +451,26 @@ trait PrivateMatchMethods: TElement {
         }
 
         let style_to_inherit_from = style_to_inherit_from.map(|x| &**x);
         let layout_parent_style = layout_parent_style.map(|x| &**x);
 
         // Propagate the "can be fragmented" bit. It would be nice to
         // encapsulate this better.
         //
-        // Note that this is not needed for pseudos since we already do that
-        // when we resolve the non-pseudo style.
-        if !is_pseudo {
-            if let Some(ref p) = layout_parent_style {
-                let can_be_fragmented =
-                    p.is_multicol() ||
-                    layout_parent_el.as_ref().unwrap().as_node().can_be_fragmented();
-                unsafe { self.as_node().set_can_be_fragmented(can_be_fragmented); }
-            }
+        // Note that this is technically not needed for pseudos since we already
+        // do that when we resolve the non-pseudo style, but it doesn't hurt
+        // anyway.
+        //
+        // TODO(emilio): This is servo-only, move somewhere else?
+        if let Some(ref p) = layout_parent_style {
+            let can_be_fragmented =
+                p.is_multicol() ||
+                layout_parent_el.as_ref().unwrap().as_node().can_be_fragmented();
+            unsafe { self.as_node().set_can_be_fragmented(can_be_fragmented); }
         }
 
         // Invoke the cascade algorithm.
         let values =
             Arc::new(cascade(&shared_context.stylist.device,
                              rule_node,
                              &shared_context.guards,
                              style_to_inherit_from,
@@ -457,35 +482,41 @@ trait PrivateMatchMethods: TElement {
 
         cascade_info.finish(&self.as_node());
         values
     }
 
     fn cascade_internal(&self,
                         context: &StyleContext<Self>,
                         primary_style: &ComputedStyle,
-                        pseudo_style: Option<&ComputedStyle>)
+                        eager_pseudo_style: Option<&ComputedStyle>)
                         -> Arc<ComputedValues> {
         // Grab the rule node.
-        let rule_node = &pseudo_style.unwrap_or(primary_style).rules;
+        let rule_node = &eager_pseudo_style.unwrap_or(primary_style).rules;
+        let inherit_mode = if eager_pseudo_style.is_some() {
+            InheritMode::FromPrimaryStyle
+        } else {
+            InheritMode::FromParentElement
+        };
 
         self.cascade_with_rules(context.shared,
                                 &context.thread_local.font_metrics_provider,
                                 rule_node,
                                 primary_style,
-                                pseudo_style.is_some())
+                                inherit_mode)
     }
 
     /// Computes values and damage for the primary or pseudo style of an element,
     /// setting them on the ElementData.
     fn cascade_primary_or_pseudo(&self,
                                  context: &mut StyleContext<Self>,
                                  data: &mut ElementData,
-                                 pseudo: Option<&PseudoElement>,
-                                 animate: bool) {
+                                 pseudo: Option<&PseudoElement>) {
+        debug_assert!(pseudo.is_none() || self.implemented_pseudo_element().is_none(),
+                      "Pseudo-element-implementing elements can't have pseudos!");
         // Collect some values.
         let (mut styles, restyle) = data.styles_and_restyle_mut();
         let mut primary_style = &mut styles.primary;
         let pseudos = &mut styles.pseudos;
         let mut pseudo_style = match pseudo {
             Some(p) => {
                 let style = pseudos.get_mut(p);
                 debug_assert!(style.is_some());
@@ -495,81 +526,117 @@ trait PrivateMatchMethods: TElement {
         };
 
         let mut old_values = match pseudo_style {
             Some(ref mut s) => s.values.take(),
             None => primary_style.values.take(),
         };
 
         // Compute the new values.
-        let mut new_values = self.cascade_internal(context,
-                                                   primary_style,
-                                                   pseudo_style.as_ref().map(|s| &**s));
+        let mut new_values = match self.implemented_pseudo_element() {
+            Some(ref pseudo) => {
+                // This is an element-backed pseudo, just grab the styles from
+                // the parent if it's eager, and recascade otherwise.
+                //
+                // We also recascade if the eager pseudo-style has any animation
+                // rules, because we don't cascade those during the eager
+                // traversal. We could make that a bit better if the complexity
+                // cost is not too big, but given further restyles are posted
+                // directly to pseudo-elements, it doesn't seem worth the effort
+                // at a glance.
+                if pseudo.is_eager() &&
+                   self.get_animation_rules().is_empty() {
+                    let parent = self.parent_element().unwrap();
 
-        // Handle animations.
-        if animate && !context.shared.traversal_flags.for_animation_only() {
+                    let parent_data = parent.borrow_data().unwrap();
+                    let pseudo_style =
+                        parent_data.styles().pseudos.get(pseudo).unwrap();
+                    pseudo_style.values().clone()
+                } else {
+                    self.cascade_internal(context,
+                                          primary_style,
+                                          None)
+                }
+            }
+            None => {
+                // Else it's an eager pseudo or a normal element, do the cascade
+                // work.
+                self.cascade_internal(context,
+                                      primary_style,
+                                      pseudo_style.as_ref().map(|s| &**s))
+            }
+        };
+
+        // NB: Animations for pseudo-elements in Gecko are handled while
+        // traversing the pseudo-elements themselves.
+        if pseudo.is_none() &&
+           !context.shared.traversal_flags.for_animation_only() {
             self.process_animations(context,
                                     &mut old_values,
                                     &mut new_values,
-                                    primary_style,
-                                    pseudo,
-                                    pseudo_style.as_ref().map(|s| &**s));
+                                    primary_style);
         }
 
         // Accumulate restyle damage.
         if let Some(old) = old_values {
-            self.accumulate_damage(&context.shared,
-                                   restyle.unwrap(),
-                                   &old,
-                                   &new_values,
-                                   pseudo);
+            // ::before and ::after are element-backed in Gecko, so they do
+            // the damage calculation for themselves.
+            //
+            // FIXME(emilio): We have more element-backed stuff, and this is
+            // redundant for them right now.
+            if cfg!(feature = "servo") ||
+               pseudo.map_or(true, |p| !p.is_before_or_after()) {
+                self.accumulate_damage(&context.shared,
+                                       restyle.unwrap(),
+                                       &old,
+                                       &new_values,
+                                       pseudo);
+            }
         }
 
         // Set the new computed values.
         let mut relevant_style = pseudo_style.unwrap_or(primary_style);
         relevant_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>,
-                              primary_style: &ComputedStyle,
-                              pseudo_style: Option<&ComputedStyle>)
+                              primary_style: &ComputedStyle)
                               -> Option<Arc<ComputedValues>> {
-        let relevant_style = pseudo_style.unwrap_or(primary_style);
-        let rule_node = &relevant_style.rules;
+        let rule_node = &primary_style.rules;
         let without_transition_rules =
             context.shared.stylist.rule_tree.remove_transition_rule_if_applicable(rule_node);
         if without_transition_rules == *rule_node {
             // We don't have transition rule in this case, so return None to let the caller
             // use the original ComputedValues.
             return None;
         }
 
         Some(self.cascade_with_rules(context.shared,
                                      &context.thread_local.font_metrics_provider,
                                      &without_transition_rules,
                                      primary_style,
-                                     pseudo_style.is_some()))
+                                     InheritMode::FromParentElement))
     }
 
     #[cfg(feature = "gecko")]
     fn needs_animations_update(&self,
-                               old_values: &Option<Arc<ComputedValues>>,
-                               new_values: &Arc<ComputedValues>,
-                               pseudo: Option<&PseudoElement>) -> bool {
-        let ref new_box_style = new_values.get_box();
+                               old_values: Option<&Arc<ComputedValues>>,
+                               new_values: &ComputedValues)
+                               -> bool {
+        let new_box_style = new_values.get_box();
         let has_new_animation_style = new_box_style.animation_name_count() >= 1 &&
                                       new_box_style.animation_name_at(0).0.len() != 0;
-        let has_animations = self.has_css_animations(pseudo);
+        let has_animations = self.has_css_animations();
 
-        old_values.as_ref().map_or(has_new_animation_style, |ref old| {
-            let ref old_box_style = old.get_box();
+        old_values.map_or(has_new_animation_style, |old| {
+            let old_box_style = old.get_box();
             let old_display_style = old_box_style.clone_display();
             let new_display_style = new_box_style.clone_display();
             // FIXME: Bug 1344581: We still need to compare keyframe rules.
             !old_box_style.animations_equals(&new_box_style) ||
              (old_display_style == display::T::none &&
               new_display_style != display::T::none &&
               has_new_animation_style) ||
              (old_display_style != display::T::none &&
@@ -578,50 +645,44 @@ trait PrivateMatchMethods: TElement {
         })
     }
 
     #[cfg(feature = "gecko")]
     fn process_animations(&self,
                           context: &mut StyleContext<Self>,
                           old_values: &mut Option<Arc<ComputedValues>>,
                           new_values: &mut Arc<ComputedValues>,
-                          primary_style: &ComputedStyle,
-                          pseudo: Option<&PseudoElement>,
-                          pseudo_style: Option<&ComputedStyle>) {
+                          primary_style: &ComputedStyle) {
         use context::{CSS_ANIMATIONS, CSS_TRANSITIONS, EFFECT_PROPERTIES};
         use context::UpdateAnimationsTasks;
 
-        debug_assert_eq!(pseudo.is_some(), pseudo_style.is_some());
-
         let mut tasks = UpdateAnimationsTasks::empty();
-        if self.needs_animations_update(old_values, new_values, pseudo) {
+        if self.needs_animations_update(old_values.as_ref(), new_values) {
             tasks.insert(CSS_ANIMATIONS);
         }
 
-        let before_change_style = if self.might_need_transitions_update(&old_values.as_ref(),
-                                                                        new_values,
-                                                                        pseudo) {
-            let after_change_style = if self.has_css_transitions(pseudo) {
-                self.get_after_change_style(context, primary_style, pseudo_style)
+        let before_change_style = if self.might_need_transitions_update(old_values.as_ref().map(|s| &**s),
+                                                                        new_values) {
+            let after_change_style = if self.has_css_transitions() {
+                self.get_after_change_style(context, primary_style)
             } else {
                 None
             };
 
-            // In order to avoid creating a SequentialTask for transitions which may not be updated,
-            // we check it per property to make sure Gecko side will really update transition.
+            // In order to avoid creating a SequentialTask for transitions which
+            // may not be updated, we check it per property to make sure Gecko
+            // side will really update transition.
             let needs_transitions_update = {
-                // We borrow new_values here, so need to add a scope to make sure we release it
-                // before assigning a new value to it.
-                let after_change_style_ref = match after_change_style {
-                    Some(ref value) => value,
-                    None => &new_values
-                };
+                // We borrow new_values here, so need to add a scope to make
+                // sure we release it before assigning a new value to it.
+                let after_change_style_ref =
+                    after_change_style.as_ref().unwrap_or(&new_values);
+
                 self.needs_transitions_update(old_values.as_ref().unwrap(),
-                                              after_change_style_ref,
-                                              pseudo)
+                                              after_change_style_ref)
             };
 
             if needs_transitions_update {
                 if let Some(values_without_transitions) = after_change_style {
                     *new_values = values_without_transitions;
                 }
                 tasks.insert(CSS_TRANSITIONS);
 
@@ -629,39 +690,34 @@ trait PrivateMatchMethods: TElement {
                 old_values.clone()
             } else {
                 None
             }
         } else {
             None
         };
 
-        if self.has_animations(pseudo) {
+        if self.has_animations() {
             tasks.insert(EFFECT_PROPERTIES);
         }
 
         if !tasks.is_empty() {
             let task = ::context::SequentialTask::update_animations(*self,
-                                                                    pseudo.cloned(),
                                                                     before_change_style,
                                                                     tasks);
             context.thread_local.tasks.push(task);
         }
     }
 
     #[cfg(feature = "servo")]
     fn process_animations(&self,
                           context: &mut StyleContext<Self>,
                           old_values: &mut Option<Arc<ComputedValues>>,
                           new_values: &mut Arc<ComputedValues>,
-                          _primary_style: &ComputedStyle,
-                          pseudo: Option<&PseudoElement>,
-                          pseudo_style: Option<&ComputedStyle>) {
-        debug_assert_eq!(pseudo.is_some(), pseudo_style.is_some());
-
+                          _primary_style: &ComputedStyle) {
         let possibly_expired_animations =
             &mut context.thread_local.current_element_info.as_mut().unwrap()
                         .possibly_expired_animations;
         let shared_context = context.shared;
         if let Some(ref mut old) = *old_values {
             self.update_animations_for_cascade(shared_context, old,
                                                possibly_expired_animations,
                                                &context.thread_local.font_metrics_provider);
@@ -684,16 +740,20 @@ trait PrivateMatchMethods: TElement {
                 &**values,
                 new_values,
                 &shared_context.timer,
                 &possibly_expired_animations);
         }
     }
 
     /// Computes and applies non-redundant damage.
+    ///
+    /// FIXME(emilio): Damage for non-::before and non-::after element-backed
+    /// pseudo-elements should be refactored to go on themselves (right now they
+    /// do, but we apply this twice).
     #[cfg(feature = "gecko")]
     fn accumulate_damage(&self,
                          shared_context: &SharedStyleContext,
                          restyle: &mut RestyleData,
                          old_values: &Arc<ComputedValues>,
                          new_values: &Arc<ComputedValues>,
                          pseudo: Option<&PseudoElement>) {
         // Don't accumulate damage if we're in a restyle for reconstruction.
@@ -710,17 +770,19 @@ trait PrivateMatchMethods: TElement {
 
         // Add restyle damage, but only the bits that aren't redundant with respect
         // to damage applied on our ancestors.
         //
         // See https://bugzilla.mozilla.org/show_bug.cgi?id=1301258#c12
         // for followup work to make the optimization here more optimal by considering
         // each bit individually.
         if !restyle.damage.contains(RestyleDamage::reconstruct()) {
-            let new_damage = self.compute_restyle_damage(&old_values, &new_values, pseudo);
+            let new_damage = self.compute_restyle_damage(&old_values,
+                                                         &new_values,
+                                                         pseudo);
             if !restyle.damage_handled.contains(new_damage) {
                 restyle.damage |= new_damage;
             }
         }
     }
 
     /// Computes and applies restyle damage unless we've already maxed it out.
     #[cfg(feature = "servo")]
@@ -807,17 +869,21 @@ pub enum StyleSharingBehavior {
     /// Style sharing allowed.
     Allow,
     /// Style sharing disallowed.
     Disallow,
 }
 
 /// The public API that elements expose for selector matching.
 pub trait MatchMethods : TElement {
-    /// Performs selector matching and property cascading on an element and its eager pseudos.
+    /// Performs selector matching and property cascading on an element and its
+    /// eager pseudos.
+    ///
+    /// Returns whether the traversal should stop immediately, because a
+    /// previous ::before or ::after pseudo-element has stopped matching.
     fn match_and_cascade(&self,
                          context: &mut StyleContext<Self>,
                          data: &mut ElementData,
                          sharing: StyleSharingBehavior)
     {
         // Perform selector matching for the primary style.
         let mut primary_relations = StyleRelations::empty();
         let _rule_node_changed = self.match_primary(context, data, &mut primary_relations);
@@ -872,63 +938,101 @@ pub trait MatchMethods : TElement {
     ///
     /// Returns whether the primary rule node changed.
     fn match_primary(&self,
                      context: &mut StyleContext<Self>,
                      data: &mut ElementData,
                      relations: &mut StyleRelations)
                      -> bool
     {
+        let implemented_pseudo = self.implemented_pseudo_element();
+        if let Some(ref pseudo) = implemented_pseudo {
+            if pseudo.is_eager() {
+                // If it's an eager element-backed pseudo, just grab the matched
+                // rules from the parent, and update animations.
+                let parent = self.parent_element().unwrap();
+                let parent_data = parent.borrow_data().unwrap();
+                let pseudo_style =
+                    parent_data.styles().pseudos.get(&pseudo).unwrap();
+                let mut rules = pseudo_style.rules.clone();
+                let animation_rules = self.get_animation_rules();
+
+                // Handle animations here.
+                if let Some(animation_rule) = animation_rules.0 {
+                    let animation_rule_node =
+                        context.shared.stylist.rule_tree
+                            .update_rule_at_level(CascadeLevel::Animations,
+                                                  Some(&animation_rule),
+                                                  &mut rules,
+                                                  &context.shared.guards);
+                    if let Some(node) = animation_rule_node {
+                        rules = node;
+                    }
+                }
+
+                if let Some(animation_rule) = animation_rules.1 {
+                    let animation_rule_node =
+                        context.shared.stylist.rule_tree
+                            .update_rule_at_level(CascadeLevel::Transitions,
+                                                  Some(&animation_rule),
+                                                  &mut rules,
+                                                  &context.shared.guards);
+                    if let Some(node) = animation_rule_node {
+                        rules = node;
+                    }
+                }
+
+                return data.set_rules(rules);
+            }
+        }
+
         let mut applicable_declarations =
             Vec::<ApplicableDeclarationBlock>::with_capacity(16);
 
         let stylist = &context.shared.stylist;
         let style_attribute = self.style_attribute();
-        let animation_rules = self.get_animation_rules(None);
-        let mut rule_nodes_changed = false;
+        let animation_rules = self.get_animation_rules();
         let bloom = context.thread_local.bloom_filter.filter();
 
         let map = &mut context.thread_local.selector_flags;
         let mut set_selector_flags = |element: &Self, flags: ElementSelectorFlags| {
             self.apply_selector_flags(map, element, flags);
         };
 
         // Compute the primary rule node.
         *relations = stylist.push_applicable_declarations(self,
                                                           Some(bloom),
                                                           style_attribute,
                                                           animation_rules,
-                                                          None,
+                                                          implemented_pseudo.as_ref(),
                                                           &context.shared.guards,
                                                           &mut applicable_declarations,
                                                           &mut set_selector_flags);
 
         let primary_rule_node =
             compute_rule_node::<Self>(&stylist.rule_tree, &mut applicable_declarations);
-        if !data.has_styles() {
-            data.set_styles(ElementStyles::new(ComputedStyle::new_partial(primary_rule_node)));
-            rule_nodes_changed = true;
-        } else if data.styles().primary.rules != primary_rule_node {
-            data.styles_mut().primary.rules = primary_rule_node;
-            rule_nodes_changed = true;
-        }
 
-        rule_nodes_changed
+        return data.set_rules(primary_rule_node);
     }
 
     /// Runs selector matching to (re)compute eager pseudo-element rule nodes for this
     /// element.
     ///
     /// Returns whether any of the pseudo rule nodes changed (including, but not
     /// limited to, cases where we match different pseudos altogether).
     fn match_pseudos(&self,
                      context: &mut StyleContext<Self>,
                      data: &mut ElementData)
                      -> bool
     {
+        if self.implemented_pseudo_element().is_some() {
+            // Element pseudos can't have any other pseudo.
+            return false;
+        }
+
         let mut applicable_declarations =
             Vec::<ApplicableDeclarationBlock>::with_capacity(16);
         let mut rule_nodes_changed = false;
 
         let map = &mut context.thread_local.selector_flags;
         let mut set_selector_flags = |element: &Self, flags: ElementSelectorFlags| {
             self.apply_selector_flags(map, element, flags);
         };
@@ -940,24 +1044,22 @@ pub trait MatchMethods : TElement {
         let rule_tree = &stylist.rule_tree;
         let bloom_filter = context.thread_local.bloom_filter.filter();
 
         // Compute rule nodes for eagerly-cascaded pseudo-elements.
         let mut matches_different_pseudos = false;
         SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
             let mut pseudos = &mut data.styles_mut().pseudos;
             debug_assert!(applicable_declarations.is_empty());
-            let pseudo_animation_rules = if pseudo.is_before_or_after() {
-                self.get_animation_rules(Some(&pseudo))
-            } else {
-                AnimationRules(None, None)
-            };
+            // NB: We handle animation rules for ::before and ::after when
+            // traversing them.
             stylist.push_applicable_declarations(self,
                                                  Some(bloom_filter),
-                                                 None, pseudo_animation_rules,
+                                                 None,
+                                                 AnimationRules(None, None),
                                                  Some(&pseudo),
                                                  &guards,
                                                  &mut applicable_declarations,
                                                  &mut set_selector_flags);
 
             if !applicable_declarations.is_empty() {
                 let new_rules =
                     compute_rule_node::<Self>(rule_tree, &mut applicable_declarations);
@@ -1069,61 +1171,46 @@ pub trait MatchMethods : TElement {
             };
 
             // RESTYLE_CSS_ANIMATIONS or RESTYLE_CSS_TRANSITIONS is processed prior to other
             // restyle hints in the name of animation-only traversal. Rest of restyle hints
             // will be processed in a subsequent normal traversal.
             if hint.intersects(RestyleHint::for_animations()) {
                 debug_assert!(context.shared.traversal_flags.for_animation_only());
 
-                use data::EagerPseudoStyles;
                 let mut replace_rule_node_for_animation = |level: CascadeLevel,
-                                                           primary_rules: &mut StrongRuleNode,
-                                                           pseudos: &mut EagerPseudoStyles| {
-                    let animation_rule = self.get_animation_rule_by_cascade(None, level);
+                                                           primary_rules: &mut StrongRuleNode| {
+                    let animation_rule = self.get_animation_rule_by_cascade(level);
                     replace_rule_node(level,
                                       animation_rule.as_ref(),
                                       primary_rules);
-
-                    for pseudo in pseudos.keys().iter().filter(|p| p.is_before_or_after()) {
-                        let animation_rule = self.get_animation_rule_by_cascade(Some(&pseudo), level);
-                        let pseudo_rules = &mut pseudos.get_mut(&pseudo).unwrap().rules;
-                        replace_rule_node(level,
-                                          animation_rule.as_ref(),
-                                          pseudo_rules);
-                    }
                 };
 
                 // Apply Transition rules and Animation rules if the corresponding restyle hint
                 // is contained.
-                let pseudos = &mut element_styles.pseudos;
                 if hint.contains(RESTYLE_CSS_TRANSITIONS) {
                     replace_rule_node_for_animation(CascadeLevel::Transitions,
-                                                    primary_rules,
-                                                    pseudos);
+                                                    primary_rules);
                 }
 
                 if hint.contains(RESTYLE_CSS_ANIMATIONS) {
                     replace_rule_node_for_animation(CascadeLevel::Animations,
-                                                    primary_rules,
-                                                    pseudos);
+                                                    primary_rules);
                 }
             } else if hint.contains(RESTYLE_STYLE_ATTRIBUTE) {
                 let style_attribute = self.style_attribute();
                 replace_rule_node(CascadeLevel::StyleAttributeNormal,
                                   style_attribute,
                                   primary_rules);
                 replace_rule_node(CascadeLevel::StyleAttributeImportant,
                                   style_attribute,
                                   primary_rules);
-                // The per-pseudo rule nodes never change in this path.
             }
         }
 
-        // The per-pseudo rule nodes never change in this path.
         rule_node_changed
     }
 
     /// Attempts to share a style with another node. This method is unsafe
     /// because it depends on the `style_sharing_candidate_cache` having only
     /// live nodes in it, and we have no way to guarantee that at the type
     /// system level yet.
     unsafe fn share_style_if_possible(&self,
@@ -1135,16 +1222,21 @@ pub trait MatchMethods : TElement {
             return StyleSharingResult::CannotShare
         }
 
         if self.parent_element().is_none() {
             debug!("{:?} Cannot share style: element has style attribute", self);
             return StyleSharingResult::CannotShare
         }
 
+        if self.is_native_anonymous() {
+            debug!("{:?} Cannot share style: NAC", self);
+            return StyleSharingResult::CannotShare;
+        }
+
         if self.style_attribute().is_some() {
             debug!("{:?} Cannot share style: element has style attribute", self);
             return StyleSharingResult::CannotShare
         }
 
         if self.has_attr(&ns!(), &local_name!("id")) {
             debug!("{:?} Cannot share style: element has id", self);
             return StyleSharingResult::CannotShare
@@ -1294,34 +1386,32 @@ pub trait MatchMethods : TElement {
         }
     }
 
     /// Performs the cascade for the element's primary style.
     fn cascade_primary(&self,
                        context: &mut StyleContext<Self>,
                        mut data: &mut ElementData)
     {
-        self.cascade_primary_or_pseudo(context, &mut data, None, /* animate = */ true);
+        self.cascade_primary_or_pseudo(context, &mut data, None);
     }
 
     /// Performs the cascade for the element's eager pseudos.
     fn cascade_pseudos(&self,
                        context: &mut StyleContext<Self>,
                        mut data: &mut ElementData)
     {
         // Note that we've already set up the map of matching pseudo-elements
         // in match_pseudos (and handled the damage implications of changing
         // which pseudos match), so now we can just iterate what we have. This
         // does mean collecting owned pseudos, so that the borrow checker will
         // let us pass the mutable |data| to the cascade function.
         let matched_pseudos = data.styles().pseudos.keys();
         for pseudo in matched_pseudos {
-            // Only ::before and ::after are animatable.
-            let animate = pseudo.is_before_or_after();
-            self.cascade_primary_or_pseudo(context, data, Some(&pseudo), animate);
+            self.cascade_primary_or_pseudo(context, data, Some(&pseudo));
         }
     }
 
     /// Returns computed values without animation and transition rules.
     fn get_base_style(&self,
                       shared_context: &SharedStyleContext,
                       font_metrics_provider: &FontMetricsProvider,
                       primary_style: &ComputedStyle,
@@ -1336,14 +1426,14 @@ pub trait MatchMethods : TElement {
             // only incomplete during the styling process.
             return relevant_style.values.as_ref().unwrap().clone();
         }
 
         self.cascade_with_rules(shared_context,
                                 font_metrics_provider,
                                 &without_animation_rules,
                                 primary_style,
-                                pseudo_style.is_some())
+                                InheritMode::FromParentElement)
     }
 
 }
 
 impl<E: TElement> MatchMethods for E {}
--- a/servo/components/style/restyle_hints.rs
+++ b/servo/components/style/restyle_hints.rs
@@ -12,17 +12,17 @@ use element_state::*;
 #[cfg(feature = "gecko")]
 use gecko_bindings::structs::nsRestyleHint;
 #[cfg(feature = "servo")]
 use heapsize::HeapSizeOf;
 use selector_parser::{AttrValue, NonTSPseudoClass, Snapshot, SelectorImpl};
 use selectors::{Element, MatchAttr};
 use selectors::matching::{ElementSelectorFlags, StyleRelations};
 use selectors::matching::matches_selector;
-use selectors::parser::{AttrSelector, Combinator, ComplexSelector, Component};
+use selectors::parser::{AttrSelector, Combinator, Component, Selector};
 use selectors::parser::{SelectorInner, SelectorIter, SelectorMethods};
 use selectors::visitor::SelectorVisitor;
 use std::clone::Clone;
 
 bitflags! {
     /// When the ElementState of an element (like IN_HOVER_STATE) changes,
     /// certain pseudo-classes (like :hover) may require us to restyle that
     /// element, its siblings, and/or its descendants. Similarly, when various
@@ -109,18 +109,17 @@ impl RestyleHint {
     pub fn for_animations() -> Self {
         RESTYLE_CSS_ANIMATIONS | RESTYLE_CSS_TRANSITIONS
     }
 }
 
 #[cfg(feature = "gecko")]
 impl From<nsRestyleHint> for RestyleHint {
     fn from(raw: nsRestyleHint) -> Self {
-        use std::mem;
-        let raw_bits: u32 = unsafe { mem::transmute(raw) };
+        let raw_bits: u32 = raw.0;
         // FIXME(bholley): Finish aligning the binary representations here and
         // then .expect() the result of the checked version.
         if Self::from_bits(raw_bits).is_none() {
             warn!("stylo: dropping unsupported restyle hint bits");
         }
 
         let mut bits = Self::from_bits_truncate(raw_bits);
 
@@ -561,32 +560,44 @@ impl DependencySet {
         } else {
             self.state_deps.push(dep)
         }
     }
 
     /// Adds a selector to this `DependencySet`, and returns whether it may need
     /// cache revalidation, that is, whether two siblings of the same "shape"
     /// may have different style due to this selector.
-    pub fn note_selector(&mut self,
-                         base: &ComplexSelector<SelectorImpl>)
-                         -> bool
-    {
-        let mut next = Some(base.clone());
+    pub fn note_selector(&mut self, selector: &Selector<SelectorImpl>) -> bool {
+        let mut is_pseudo_element = selector.pseudo_element.is_some();
+
+        let mut next = Some(selector.inner.complex.clone());
         let mut combinator = None;
         let mut needs_revalidation = false;
 
         while let Some(current) = next.take() {
             // Set up our visitor.
             let mut visitor = SensitivitiesVisitor {
                 sensitivities: Sensitivities::new(),
                 hint: combinator_to_restyle_hint(combinator),
                 needs_revalidation: false,
             };
 
+            if is_pseudo_element {
+                // TODO(emilio): use more fancy restyle hints to avoid restyling
+                // the whole subtree when pseudos change.
+                //
+                // We currently need is_pseudo_element to handle eager pseudos
+                // (so the style the parent stores doesn't become stale), and
+                // restyle_descendants to handle all of them (::before and
+                // ::after, because we find them in the subtree, and other lazy
+                // pseudos for the same reason).
+                visitor.hint |= RESTYLE_SELF | RESTYLE_DESCENDANTS;
+                is_pseudo_element = false;
+            }
+
             {
                 // Visit all the simple selectors.
                 let mut iter = current.iter();
                 let mut index = 0usize;
                 for ss in &mut iter {
                     ss.visit(&mut visitor);
                     index += 1;
                 }
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -328,17 +328,17 @@ impl Stylist {
                                              locked.clone(),
                                              self.rules_source_order,
                                              selector.specificity));
                     }
                     self.rules_source_order += 1;
 
                     for selector in &style_rule.selectors.0 {
                         let needs_cache_revalidation =
-                            self.dependencies.note_selector(&selector.inner.complex);
+                            self.dependencies.note_selector(selector);
                         if needs_cache_revalidation {
                             self.selectors_for_cache_revalidation.push(selector.clone());
                         }
                     }
                 }
                 CssRule::Import(ref import) => {
                     let import = import.read_with(guard);
                     self.add_stylesheet(&import.stylesheet, guard, extra_data)
@@ -639,17 +639,19 @@ impl Stylist {
                                         -> StyleRelations
         where E: TElement +
                  fmt::Debug +
                  PresentationalHintsSynthetizer,
               V: Push<ApplicableDeclarationBlock> + VecLike<ApplicableDeclarationBlock>,
               F: FnMut(&E, ElementSelectorFlags),
     {
         debug_assert!(!self.is_device_dirty);
-        debug_assert!(style_attribute.is_none() || pseudo_element.is_none(),
+        // Have you heard about ::-moz-color-swatch? Now you have.
+        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.as_ref().map_or(true, |p| !p.is_precomputed()));
 
         let map = match pseudo_element {
             Some(ref pseudo) => self.pseudos_map.get(pseudo).unwrap(),
             None => &self.element_map,
         };
 
--- a/servo/components/style/traversal.rs
+++ b/servo/components/style/traversal.rs
@@ -264,24 +264,30 @@ pub trait DomTraversal<E: TElement> : Sy
             return true;
         }
 
         match node.as_element() {
             None => Self::text_node_needs_traversal(node),
             Some(el) => {
                 // If the element is native-anonymous and an ancestor frame will
                 // be reconstructed, the child and all its descendants will be
-                // destroyed. In that case, we don't need to traverse the subtree.
+                // destroyed. In that case, we wouldn't need to traverse the
+                // subtree...
+                //
+                // Except if there could be transitions of pseudo-elements, in which
+                // case we still need to process them, unfortunately.
                 if el.is_native_anonymous() {
-                    if let Some(parent) = el.parent_element() {
-                        let parent_data = parent.borrow_data().unwrap();
-                        if let Some(r) = parent_data.get_restyle() {
-                            if (r.damage | r.damage_handled()).contains(RestyleDamage::reconstruct()) {
-                                debug!("Element {:?} is in doomed NAC subtree - culling traversal", el);
-                                return false;
+                    if el.implemented_pseudo_element().map_or(true, |p| !p.is_before_or_after()) {
+                        if let Some(parent) = el.parent_element() {
+                            let parent_data = parent.borrow_data().unwrap();
+                            if let Some(r) = parent_data.get_restyle() {
+                                if (r.damage | r.damage_handled()).contains(RestyleDamage::reconstruct()) {
+                                    debug!("Element {:?} is in doomed NAC subtree - culling traversal", el);
+                                    return false;
+                                }
                             }
                         }
                     }
                 }
 
                 // In case of animation-only traversal we need to traverse
                 // the element if the element has animation only dirty
                 // descendants bit, animation-only restyle hint or recascade.
@@ -391,24 +397,28 @@ pub trait DomTraversal<E: TElement> : Sy
         // an Element is changed.
         if cfg!(feature = "gecko") && thread_local.is_initial_style() &&
            parent_data.styles().primary.values().has_moz_binding() {
             if log.allow() { debug!("Parent {:?} has XBL binding, deferring traversal", parent); }
             return false;
         }
 
         return true;
-
     }
 
     /// Helper for the traversal implementations to select the children that
     /// should be enqueued for processing.
     fn traverse_children<F>(&self, thread_local: &mut Self::ThreadLocalContext, parent: E, mut f: F)
         where F: FnMut(&mut Self::ThreadLocalContext, E::ConcreteNode)
     {
+        // We bailed out early, so nothing else to do in this subtree.
+        if thread_local.borrow_mut().current_element_info.is_none() {
+            return;
+        }
+
         // Check if we're allowed to traverse past this element.
         let should_traverse =
             self.should_traverse_children(thread_local.borrow_mut(), parent,
                                           &parent.borrow_data().unwrap(), MayLog);
         thread_local.borrow_mut().end_element(parent);
         if !should_traverse {
             return;
         }
@@ -484,17 +494,19 @@ fn resolve_style_internal<E, F>(context:
             context.thread_local.bloom_filter.rebuild(element);
         } else {
             context.thread_local.bloom_filter.push(parent.unwrap());
             context.thread_local.bloom_filter.assert_complete(element);
         }
 
         // Compute our style.
         context.thread_local.begin_element(element, &data);
-        element.match_and_cascade(context, &mut data, StyleSharingBehavior::Disallow);
+        element.match_and_cascade(context,
+                                  &mut data,
+                                  StyleSharingBehavior::Disallow);
         context.thread_local.end_element(element);
 
         // Conservatively mark us as having dirty descendants, since there might
         // be other unstyled siblings we miss when walking straight up the parent
         // chain.
         unsafe { element.note_descendants::<DirtyDescendants>() };
     }
 
@@ -556,16 +568,42 @@ pub fn resolve_style<E, F, G, H>(context
 pub fn recalc_style_at<E, D>(traversal: &D,
                              traversal_data: &PerLevelTraversalData,
                              context: &mut StyleContext<E>,
                              element: E,
                              mut data: &mut AtomicRefMut<ElementData>)
     where E: TElement,
           D: DomTraversal<E>
 {
+    if let Some(pseudo) = element.implemented_pseudo_element() {
+        // We have this special-case for the case a parent is reframed and
+        // we're a ::before or ::after pseudo.
+        //
+        // We need to conservatively continue the traversal to style the
+        // pseudo-element in order to properly process potentially-new
+        // transitions that we won't see otherwise.
+        //
+        // But it may be that we no longer match, so detect that case and
+        // act appropriately here, by effectively doing no work.
+        //
+        // This means that we effectively do nothing in this case, but
+        // that's fine, because we're going away anyway.
+        //
+        // Note that this is inherent to the traversal, not to the one-off
+        // restyles for getComputedStyle, so we should be good there.
+        if pseudo.is_before_or_after() {
+            let parent = element.parent_element().unwrap();
+            let parent_data = parent.borrow_data().unwrap();
+            if parent_data.styles().pseudos.get(&pseudo).is_none() {
+                debug_assert!(data.restyle().damage_handled.contains(RestyleDamage::reconstruct()));
+                return;
+            }
+        }
+    }
+
     context.thread_local.begin_element(element, &data);
     context.thread_local.statistics.elements_traversed += 1;
     debug_assert!(data.get_restyle().map_or(true, |r| {
         r.snapshot.is_none() && !r.has_sibling_invalidations()
     }), "Should've computed the final hint and handled later_siblings already");
 
     let compute_self = !data.has_current_styles();
     let mut inherited_style_changed = false;
@@ -599,19 +637,23 @@ pub fn recalc_style_at<E, D>(traversal: 
                           "animation restyle hint should be handled during \
                            animation-only restyles");
             r.recascade = false;
             r.hint.propagate(&context.shared.traversal_flags)
         },
     };
     debug_assert!(data.has_current_styles() ||
                   context.shared.traversal_flags.for_animation_only(),
-                  "Should have computed style or haven't yet valid computed style in case of animation-only restyle");
-    trace!("propagated_hint={:?}, inherited_style_changed={:?}",
-           propagated_hint, inherited_style_changed);
+                  "Should have computed style or haven't yet valid computed \
+                   style in case of animation-only restyle");
+    trace!("propagated_hint={:?}, inherited_style_changed={:?}, \
+            is_display_none={:?}, implementing_pseudo={:?}",
+           propagated_hint, inherited_style_changed,
+           data.styles().is_display_none(),
+           element.implemented_pseudo_element());
 
     let has_dirty_descendants_for_this_restyle =
         if context.shared.traversal_flags.for_animation_only() {
             element.has_animation_only_dirty_descendants()
         } else {
             element.has_dirty_descendants()
         };
 
@@ -656,17 +698,18 @@ pub fn recalc_style_at<E, D>(traversal: 
     // check display:none on the parent when inserting new children (which
     // can be moderately expensive). Instead, DOM implementations can
     // unconditionally set the dirty descendants bit on any styled parent,
     // and let the traversal sort it out.
     //
     // The second case is when we are in a restyle for reconstruction,
     // where we won't need to perform a post-traversal to pick up any
     // change hints.
-    if data.styles().is_display_none() || context.shared.traversal_flags.for_reconstruct() {
+    if data.styles().is_display_none() ||
+       context.shared.traversal_flags.for_reconstruct() {
         unsafe { element.unset_dirty_descendants(); }
     }
 }
 
 fn compute_style<E, D>(_traversal: &D,
                        traversal_data: &PerLevelTraversalData,
                        context: &mut StyleContext<E>,
                        element: E,
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -18,17 +18,17 @@ use style::context::{ThreadLocalStyleCon
 use style::data::{ElementData, ElementStyles, RestyleData};
 use style::dom::{AnimationOnlyDirtyDescendants, DirtyDescendants};
 use style::dom::{ShowSubtreeData, TElement, TNode};
 use style::error_reporting::RustLogReporter;
 use style::font_metrics::get_metrics_provider_for_product;
 use style::gecko::data::{PerDocumentStyleData, PerDocumentStyleDataImpl};
 use style::gecko::global_style_data::{GLOBAL_STYLE_DATA, GlobalStyleData};
 use style::gecko::restyle_damage::GeckoRestyleDamage;
-use style::gecko::selector_parser::{SelectorImpl, PseudoElement};
+use style::gecko::selector_parser::PseudoElement;
 use style::gecko::traversal::RecalcStyleOnly;
 use style::gecko::wrapper::GeckoElement;
 use style::gecko_bindings::bindings;
 use style::gecko_bindings::bindings::{RawGeckoKeyframeListBorrowed, RawGeckoKeyframeListBorrowedMut};
 use style::gecko_bindings::bindings::{RawServoDeclarationBlockBorrowed, RawServoDeclarationBlockStrong};
 use style::gecko_bindings::bindings::{RawServoMediaList, RawServoMediaListBorrowed, RawServoMediaListStrong};
 use style::gecko_bindings::bindings::{RawServoMediaRule, RawServoMediaRuleBorrowed};
 use style::gecko_bindings::bindings::{RawServoNamespaceRule, RawServoNamespaceRuleBorrowed};
@@ -161,17 +161,18 @@ fn create_shared_context<'a>(global_styl
         local_context_creation_data: Mutex::new(local_context_data),
         timer: Timer::new(),
         // FIXME Find the real QuirksMode information for this document
         quirks_mode: QuirksMode::NoQuirks,
         traversal_flags: traversal_flags,
     }
 }
 
-fn traverse_subtree(element: GeckoElement, raw_data: RawServoStyleSetBorrowed,
+fn traverse_subtree(element: GeckoElement,
+                    raw_data: RawServoStyleSetBorrowed,
                     traversal_flags: TraversalFlags) {
     // When new content is inserted in a display:none subtree, we will call into
     // servo to try to style it. Detect that here and bail out.
     if let Some(parent) = element.parent_element() {
         if parent.borrow_data().map_or(true, |d| d.styles().is_display_none()) {
             debug!("{:?} has unstyled parent {:?} - ignoring call to traverse_subtree", element, parent);
             return;
         }
@@ -930,22 +931,25 @@ pub extern "C" fn Servo_ResolvePseudoSty
     let guard = global_style_data.shared_lock.read();
     match get_pseudo_style(&guard, element, pseudo_tag, data.styles(), doc_data) {
         Some(values) => values.into_strong(),
         None if !is_probe => data.styles().primary.values().clone().into_strong(),
         None => Strong::null(),
     }
 }
 
-fn get_pseudo_style(guard: &SharedRwLockReadGuard, element: GeckoElement, pseudo_tag: *mut nsIAtom,
-                    styles: &ElementStyles, doc_data: &PerDocumentStyleData)
+fn get_pseudo_style(guard: &SharedRwLockReadGuard,
+                    element: GeckoElement,
+                    pseudo_tag: *mut nsIAtom,
+                    styles: &ElementStyles,
+                    doc_data: &PerDocumentStyleData)
                     -> Option<Arc<ComputedValues>>
 {
     let pseudo = PseudoElement::from_atom_unchecked(Atom::from(pseudo_tag), false);
-    match SelectorImpl::pseudo_element_cascade_type(&pseudo) {
+    match pseudo.cascade_type() {
         PseudoElementCascadeType::Eager => styles.pseudos.get(&pseudo).map(|s| s.values().clone()),
         PseudoElementCascadeType::Precomputed => unreachable!("No anonymous boxes"),
         PseudoElementCascadeType::Lazy => {
             let d = doc_data.borrow_mut();
             let base = styles.primary.values();
             let guards = StylesheetGuards::same(guard);
             let metrics = get_metrics_provider_for_product();
             d.stylist.lazily_compute_pseudo_element_style(&guards,