Bug 1180118 - Part 3: Convert eRestyle_SomeDescendants into eRestyle_Self for elements that match selectors. r=bzbarsky
☠☠ backed out by 01576b408ea7 ☠ ☠
authorCameron McCormack <cam@mcc.id.au>
Tue, 04 Aug 2015 17:27:52 +1000
changeset 287737 4a7b7998035393642b06092c233100a4c3ebfbab
parent 287736 20984dfa43025254ccbebaf2f17fa1496660ef0b
child 287738 b99c358a6fea8ee0949f3d5f877e1d88d308aaf0
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbzbarsky
bugs1180118
milestone42.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1180118 - Part 3: Convert eRestyle_SomeDescendants into eRestyle_Self for elements that match selectors. r=bzbarsky
layout/base/RestyleManager.cpp
layout/base/RestyleManager.h
layout/base/RestyleTracker.h
layout/style/nsCSSRuleProcessor.cpp
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -36,16 +36,18 @@
 #include "nsIRootBox.h"
 #include "nsIDOMMutationEvent.h"
 #include "nsContentUtils.h"
 #include "nsIFrameInlines.h"
 #include "ActiveLayerTracker.h"
 #include "nsDisplayList.h"
 #include "RestyleTrackerInlines.h"
 #include "nsSMILAnimationController.h"
+#include "nsCSSRuleProcessor.h"
+#include "ChildIterator.h"
 
 #ifdef ACCESSIBILITY
 #include "nsAccessibilityService.h"
 #endif
 
 namespace mozilla {
 
 using namespace layers;
@@ -1006,16 +1008,19 @@ RestyleManager::RestyleElement(Element* 
     nsStyleContext *oldContext = aPrimaryFrame->StyleContext();
     if (!oldContext->GetParent()) { // check that we're the root element
       nsRefPtr<nsStyleContext> newContext = mPresContext->StyleSet()->
         ResolveStyleFor(aElement, nullptr /* == oldContext->GetParent() */);
       if (oldContext->StyleFont()->mFont.size !=
           newContext->StyleFont()->mFont.size) {
         // The basis for 'rem' units has changed.
         mRebuildAllRestyleHint |= aRestyleHint;
+        if (aRestyleHint & eRestyle_SomeDescendants) {
+          mRebuildAllRestyleHint |= eRestyle_Subtree;
+        }
         NS_UpdateHint(mRebuildAllExtraHint, aMinHint);
         StartRebuildAllStyleData(aRestyleTracker);
         return;
       }
     }
   }
 
   if (aMinHint & nsChangeHint_ReconstructFrame) {
@@ -1553,16 +1558,19 @@ RestyleManager::RestyleForRemove(Element
 
 void
 RestyleManager::RebuildAllStyleData(nsChangeHint aExtraHint,
                                     nsRestyleHint aRestyleHint)
 {
   NS_ASSERTION(!(aExtraHint & nsChangeHint_ReconstructFrame),
                "Should not reconstruct the root of the frame tree.  "
                "Use ReconstructDocElementHierarchy instead.");
+  MOZ_ASSERT(!(aRestyleHint & ~(eRestyle_Subtree | eRestyle_ForceDescendants)),
+             "the only bits allowed in aRestyleHint are eRestyle_Subtree and "
+             "eRestyle_ForceDescendants");
 
   NS_UpdateHint(mRebuildAllExtraHint, aExtraHint);
   mRebuildAllRestyleHint |= aRestyleHint;
 
   // Processing the style changes could cause a flush that propagates to
   // the parent frame and thus destroys the pres shell, so we must hold
   // a reference.
   nsCOMPtr<nsIPresShell> presShell = mPresContext->GetPresShell();
@@ -1870,16 +1878,19 @@ RestyleManager::PostRestyleEventInternal
 
 void
 RestyleManager::PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
                                              nsRestyleHint aRestyleHint)
 {
   NS_ASSERTION(!(aExtraHint & nsChangeHint_ReconstructFrame),
                "Should not reconstruct the root of the frame tree.  "
                "Use ReconstructDocElementHierarchy instead.");
+  MOZ_ASSERT(!(aRestyleHint & eRestyle_SomeDescendants),
+             "PostRebuildAllStyleDataEvent does not handle "
+             "eRestyle_SomeDescendants");
 
   mDoRebuildAllStyleData = true;
   NS_UpdateHint(mRebuildAllExtraHint, aExtraHint);
   mRebuildAllRestyleHint |= aRestyleHint;
 
   // Get a restyle event posted if necessary
   PostRestyleEventInternal(false);
 }
@@ -2467,16 +2478,18 @@ RestyleManager::ReparentStyleContext(nsI
   return NS_OK;
 }
 
 ElementRestyler::ElementRestyler(nsPresContext* aPresContext,
                                  nsIFrame* aFrame,
                                  nsStyleChangeList* aChangeList,
                                  nsChangeHint aHintsHandledByAncestors,
                                  RestyleTracker& aRestyleTracker,
+                                 nsTArray<nsCSSSelector*>&
+                                   aSelectorsForDescendants,
                                  TreeMatchContext& aTreeMatchContext,
                                  nsTArray<nsIContent*>&
                                    aVisibleKidsOfHiddenElement,
                                  nsTArray<ContextToClear>& aContextsToClear,
                                  nsTArray<nsRefPtr<nsStyleContext>>&
                                    aSwappedStructOwners)
   : mPresContext(aPresContext)
   , mFrame(aFrame)
@@ -2485,16 +2498,17 @@ ElementRestyler::ElementRestyler(nsPresC
     // comment above assertion at start of ElementRestyler::Restyle.)
   , mContent(mFrame->GetContent() ? mFrame->GetContent() : mParentContent)
   , mChangeList(aChangeList)
   , mHintsHandled(NS_SubtractHint(aHintsHandledByAncestors,
                   NS_HintsNotHandledForDescendantsIn(aHintsHandledByAncestors)))
   , mParentFrameHintsNotHandledForDescendants(nsChangeHint(0))
   , mHintsNotHandledForDescendants(nsChangeHint(0))
   , mRestyleTracker(aRestyleTracker)
+  , mSelectorsForDescendants(aSelectorsForDescendants)
   , mTreeMatchContext(aTreeMatchContext)
   , mResolvedChild(nullptr)
   , mContextsToClear(aContextsToClear)
   , mSwappedStructOwners(aSwappedStructOwners)
 #ifdef ACCESSIBILITY
   , mDesiredA11yNotifications(eSendAllNotifications)
   , mKidsDesiredA11yNotifications(mDesiredA11yNotifications)
   , mOurA11yNotification(eDontNotify)
@@ -2517,16 +2531,17 @@ ElementRestyler::ElementRestyler(const E
   , mContent(mFrame->GetContent() ? mFrame->GetContent() : mParentContent)
   , mChangeList(aParentRestyler.mChangeList)
   , mHintsHandled(NS_SubtractHint(aParentRestyler.mHintsHandled,
                   NS_HintsNotHandledForDescendantsIn(aParentRestyler.mHintsHandled)))
   , mParentFrameHintsNotHandledForDescendants(
       aParentRestyler.mHintsNotHandledForDescendants)
   , mHintsNotHandledForDescendants(nsChangeHint(0))
   , mRestyleTracker(aParentRestyler.mRestyleTracker)
+  , mSelectorsForDescendants(aParentRestyler.mSelectorsForDescendants)
   , mTreeMatchContext(aParentRestyler.mTreeMatchContext)
   , mResolvedChild(nullptr)
   , mContextsToClear(aParentRestyler.mContextsToClear)
   , mSwappedStructOwners(aParentRestyler.mSwappedStructOwners)
 #ifdef ACCESSIBILITY
   , mDesiredA11yNotifications(aParentRestyler.mKidsDesiredA11yNotifications)
   , mKidsDesiredA11yNotifications(mDesiredA11yNotifications)
   , mOurA11yNotification(eDontNotify)
@@ -2563,16 +2578,17 @@ ElementRestyler::ElementRestyler(ParentC
   , mChangeList(aParentRestyler.mChangeList)
   , mHintsHandled(NS_SubtractHint(aParentRestyler.mHintsHandled,
                   NS_HintsNotHandledForDescendantsIn(aParentRestyler.mHintsHandled)))
   , mParentFrameHintsNotHandledForDescendants(
       // assume the worst
       nsChangeHint_Hints_NotHandledForDescendants)
   , mHintsNotHandledForDescendants(nsChangeHint(0))
   , mRestyleTracker(aParentRestyler.mRestyleTracker)
+  , mSelectorsForDescendants(aParentRestyler.mSelectorsForDescendants)
   , mTreeMatchContext(aParentRestyler.mTreeMatchContext)
   , mResolvedChild(nullptr)
   , mContextsToClear(aParentRestyler.mContextsToClear)
   , mSwappedStructOwners(aParentRestyler.mSwappedStructOwners)
 #ifdef ACCESSIBILITY
   , mDesiredA11yNotifications(aParentRestyler.mDesiredA11yNotifications)
   , mKidsDesiredA11yNotifications(mDesiredA11yNotifications)
   , mOurA11yNotification(eDontNotify)
@@ -2584,32 +2600,34 @@ ElementRestyler::ElementRestyler(ParentC
 {
 }
 
 ElementRestyler::ElementRestyler(nsPresContext* aPresContext,
                                  nsIContent* aContent,
                                  nsStyleChangeList* aChangeList,
                                  nsChangeHint aHintsHandledByAncestors,
                                  RestyleTracker& aRestyleTracker,
+                                 nsTArray<nsCSSSelector*>& aSelectorsForDescendants,
                                  TreeMatchContext& aTreeMatchContext,
                                  nsTArray<nsIContent*>&
                                    aVisibleKidsOfHiddenElement,
                                  nsTArray<ContextToClear>& aContextsToClear,
                                  nsTArray<nsRefPtr<nsStyleContext>>&
                                    aSwappedStructOwners)
   : mPresContext(aPresContext)
   , mFrame(nullptr)
   , mParentContent(nullptr)
   , mContent(aContent)
   , mChangeList(aChangeList)
   , mHintsHandled(NS_SubtractHint(aHintsHandledByAncestors,
                   NS_HintsNotHandledForDescendantsIn(aHintsHandledByAncestors)))
   , mParentFrameHintsNotHandledForDescendants(nsChangeHint(0))
   , mHintsNotHandledForDescendants(nsChangeHint(0))
   , mRestyleTracker(aRestyleTracker)
+  , mSelectorsForDescendants(aSelectorsForDescendants)
   , mTreeMatchContext(aTreeMatchContext)
   , mResolvedChild(nullptr)
   , mContextsToClear(aContextsToClear)
   , mSwappedStructOwners(aSwappedStructOwners)
 #ifdef ACCESSIBILITY
   , mDesiredA11yNotifications(eSendAllNotifications)
   , mKidsDesiredA11yNotifications(mDesiredA11yNotifications)
   , mOurA11yNotification(eDontNotify)
@@ -2699,16 +2717,88 @@ ElementRestyler::CaptureChange(nsStyleCo
     }
   }
   NS_UpdateHint(mHintsNotHandledForDescendants,
                 NS_HintsNotHandledForDescendantsIn(ourChange));
   LOG_RESTYLE("mHintsNotHandledForDescendants = %s",
               RestyleManager::ChangeHintToString(mHintsNotHandledForDescendants).get());
 }
 
+class MOZ_STACK_CLASS AutoSelectorArrayTruncater final
+{
+public:
+  AutoSelectorArrayTruncater(nsTArray<nsCSSSelector*>& aSelectorsForDescendants)
+    : mSelectorsForDescendants(aSelectorsForDescendants)
+    , mOriginalLength(aSelectorsForDescendants.Length())
+  {
+  }
+
+  ~AutoSelectorArrayTruncater()
+  {
+    mSelectorsForDescendants.TruncateLength(mOriginalLength);
+  }
+
+private:
+  nsTArray<nsCSSSelector*>& mSelectorsForDescendants;
+  size_t mOriginalLength;
+};
+
+/**
+ * Called when we are stopping a restyle with eRestyle_SomeDescendants, to
+ * search for descendants that match any of the selectors in
+ * mSelectorsForDescendants.  If the element does match one of the selectors,
+ * we cause it to be restyled with eRestyle_Self.
+ *
+ * We traverse down the flattened tree unless we find an element that
+ * (a) already has a pending restyle, or (b) does not have a pending restyle
+ * but does match one of the selectors in mSelectorsForDescendants.  For (a),
+ * we add the current mSelectorsForDescendants into the existing restyle data,
+ * and for (b) we add a new pending restyle with that array.  So in both
+ * cases, when we come to restyling this element back up in
+ * ProcessPendingRestyles, we will again find the eRestyle_SomeDescendants
+ * hint and its selectors array.
+ *
+ * This ensures that we don't visit descendant elements and check them
+ * against mSelectorsForDescendants more than once.
+ */
+void
+ElementRestyler::AddPendingRestylesForDescendantsMatchingSelectors(Element* aElement)
+{
+  if (mRestyleTracker.HasRestyleData(aElement)) {
+    nsRestyleHint rshint = eRestyle_SomeDescendants;
+    if (SelectorMatchesForRestyle(aElement)) {
+      rshint |= eRestyle_Self;
+    }
+    // XXX Traversing up the tree in AddPendingRestyle can be wasteful,
+    // as we can do this multiple times for descendants of the element
+    // we stopped restyling at, up in Restyle().  We should track the
+    // current restyle root as we traverse down here to avoid that.
+    RestyleHintData data;
+    data.mSelectorsForDescendants = mSelectorsForDescendants;
+    mRestyleTracker.AddPendingRestyle(aElement, rshint, nsChangeHint(0), &data);
+    return;
+  }
+
+  if (SelectorMatchesForRestyle(aElement)) {
+    RestyleHintData data;
+    data.mSelectorsForDescendants = mSelectorsForDescendants;
+    mRestyleTracker.AddPendingRestyle(aElement,
+                                      eRestyle_Self | eRestyle_SomeDescendants,
+                                      nsChangeHint(0), &data);
+    return;
+  }
+
+  FlattenedChildIterator it(aElement);
+  for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
+    if (n->IsElement()) {
+      AddPendingRestylesForDescendantsMatchingSelectors(n->AsElement());
+    }
+  }
+}
+
 /**
  * Recompute style for mFrame (which should not have a prev continuation
  * with the same style), all of its next continuations with the same
  * style, and all ib-split siblings of the same type (either block or
  * inline, skipping the intermediates of the other type) and accumulate
  * changes into mChangeList given that mHintsHandled is already accumulated
  * for an ancestor.
  * mParentContent is the content node used to resolve the parent style
@@ -2735,16 +2825,18 @@ ElementRestyler::Restyle(nsRestyleHint a
                "should not be trying to restyle this frame separately");
 
   MOZ_ASSERT(!(aRestyleHint & eRestyle_LaterSiblings),
              "eRestyle_LaterSiblings must not be part of aRestyleHint");
 
   AutoDisplayContentsAncestorPusher adcp(mTreeMatchContext, mFrame->PresContext(),
       mFrame->GetContent() ? mFrame->GetContent()->GetParent() : nullptr);
 
+  AutoSelectorArrayTruncater asat(mSelectorsForDescendants);
+
   // List of descendant elements of mContent we know we will eventually need to
   // restyle.  Before we return from this function, we call
   // RestyleTracker::AddRestyleRootsIfAwaitingRestyle to ensure they get
   // restyled in RestyleTracker::DoProcessRestyles.
   nsTArray<nsRefPtr<Element>> descendants;
 
   nsRestyleHint hintToRestore = nsRestyleHint(0);
   RestyleHintData hintDataToRestore;
@@ -2764,28 +2856,31 @@ ElementRestyler::Restyle(nsRestyleHint a
       // for the inner frame.
       mContent->GetPrimaryFrame() == mFrame) {
     mContent->OwnerDoc()->FlushPendingLinkUpdates();
     nsAutoPtr<RestyleTracker::RestyleData> restyleData;
     if (mRestyleTracker.GetRestyleData(mContent->AsElement(), restyleData)) {
       if (NS_UpdateHint(mHintsHandled, restyleData->mChangeHint)) {
         mChangeList->AppendChange(mFrame, mContent, restyleData->mChangeHint);
       }
+      mSelectorsForDescendants.AppendElements(
+          restyleData->mRestyleHintData.mSelectorsForDescendants);
       hintToRestore = restyleData->mRestyleHint;
       hintDataToRestore = Move(restyleData->mRestyleHintData);
       aRestyleHint = nsRestyleHint(aRestyleHint | restyleData->mRestyleHint);
       descendants.SwapElements(restyleData->mDescendants);
     }
   }
 
   // If we are restyling this frame with eRestyle_Self or weaker hints,
   // we restyle children with nsRestyleHint(0).  But we pass the
   // eRestyle_ForceDescendants flag down too.
   nsRestyleHint childRestyleHint =
-    nsRestyleHint(aRestyleHint & (eRestyle_Subtree |
+    nsRestyleHint(aRestyleHint & (eRestyle_SomeDescendants |
+                                  eRestyle_Subtree |
                                   eRestyle_ForceDescendants));
 
   nsRefPtr<nsStyleContext> oldContext = mFrame->StyleContext();
 
   // TEMPORARY (until bug 918064):  Call RestyleSelf for each
   // continuation or block-in-inline sibling.
 
   // We must make a single decision on how to process this frame and
@@ -2878,16 +2973,28 @@ ElementRestyler::Restyle(nsRestyleHint a
     // Send the accessibility notifications that RestyleChildren otherwise
     // would have sent.
     if (!(mHintsHandled & nsChangeHint_ReconstructFrame)) {
       InitializeAccessibilityNotifications(mFrame->StyleContext());
       SendAccessibilityNotifications();
     }
 
     mRestyleTracker.AddRestyleRootsIfAwaitingRestyle(descendants);
+
+    if (mContent->IsElement()) {
+      if ((aRestyleHint & eRestyle_SomeDescendants) &&
+          !mSelectorsForDescendants.IsEmpty()) {
+        FlattenedChildIterator it(mContent->AsElement());
+        for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
+          if (n->IsElement()) {
+            AddPendingRestylesForDescendantsMatchingSelectors(n->AsElement());
+          }
+        }
+      }
+    }
     return;
   }
 
   if (!swappedStructs) {
     // If we swapped any structs from the old context, then we need to keep
     // it alive until after the RestyleChildren call so that we can fix up
     // its descendants' cached structs.
     oldContext = nullptr;
@@ -3079,16 +3186,40 @@ ElementRestyler::ComputeRestyleResultFro
     LOG_RESTYLE_CONTINUE("NS_STYLE_IN_DISPLAY_NONE_SUBTREE differs between old"
                          " and new style contexts");
     return eRestyleResult_Continue;
   }
 
   return eRestyleResult_Stop;
 }
 
+bool
+ElementRestyler::SelectorMatchesForRestyle(Element* aElement)
+{
+  if (!aElement) {
+    return false;
+  }
+  for (nsCSSSelector* selector : mSelectorsForDescendants) {
+    if (nsCSSRuleProcessor::RestrictedSelectorMatches(aElement, selector,
+                                                      mTreeMatchContext)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool
+ElementRestyler::MustRestyleSelf(nsRestyleHint aRestyleHint,
+                                 Element* aElement)
+{
+  return (aRestyleHint & (eRestyle_Self | eRestyle_Subtree)) ||
+         ((aRestyleHint & eRestyle_SomeDescendants) &&
+          SelectorMatchesForRestyle(aElement));
+}
+
 ElementRestyler::RestyleResult
 ElementRestyler::RestyleSelf(nsIFrame* aSelf,
                              nsRestyleHint aRestyleHint,
                              uint32_t* aSwappedStructs)
 {
   MOZ_ASSERT(!(aRestyleHint & eRestyle_LaterSiblings),
              "eRestyle_LaterSiblings must not be part of aRestyleHint");
 
@@ -3102,17 +3233,17 @@ ElementRestyler::RestyleSelf(nsIFrame* a
 
   LOG_RESTYLE("RestyleSelf %s, aRestyleHint = %s",
               FrameTagToString(aSelf).get(),
               RestyleManager::RestyleHintToString(aRestyleHint).get());
   LOG_RESTYLE_INDENT();
 
   RestyleResult result;
 
-  if (aRestyleHint) {
+  if (aRestyleHint & ~eRestyle_SomeDescendants) {
     result = eRestyleResult_Continue;
   } else {
     result = ComputeRestyleResultFromFrame(aSelf);
   }
 
   nsChangeHint assumeDifferenceHint = NS_STYLE_HINT_NONE;
   nsRefPtr<nsStyleContext> oldContext = aSelf->StyleContext();
   nsStyleSet* styleSet = mPresContext->StyleSet();
@@ -3187,99 +3318,101 @@ ElementRestyler::RestyleSelf(nsIFrame* a
     LOG_RESTYLE("using previous continuation's context");
     newContext = prevContinuationContext;
   }
   else if (pseudoTag == nsCSSAnonBoxes::mozNonElement) {
     NS_ASSERTION(aSelf->GetContent(),
                  "non pseudo-element frame without content node");
     newContext = styleSet->ResolveStyleForNonElement(parentContext);
   }
-  else if (!(aRestyleHint & (eRestyle_Self | eRestyle_Subtree))) {
-    Element* element = ElementForStyleContext(mParentContent, aSelf, pseudoType);
-    if (!(aRestyleHint & ~(eRestyle_Force | eRestyle_ForceDescendants)) &&
-        !styleSet->IsInRuleTreeReconstruct()) {
-      LOG_RESTYLE("reparenting style context");
-      newContext =
-        styleSet->ReparentStyleContext(oldContext, parentContext, element);
-    } else {
-      // Use ResolveStyleWithReplacement either for actual replacements
-      // or, with no replacements, as a substitute for
-      // ReparentStyleContext that rebuilds the path in the rule tree
-      // rather than reusing the rule node, as we need to do during a
-      // rule tree reconstruct.
-      Element* pseudoElement = PseudoElementForStyleContext(aSelf, pseudoType);
-      MOZ_ASSERT(!element || element != pseudoElement,
-                 "pseudo-element for selector matching should be "
-                 "the anonymous content node that we create, "
-                 "not the real element");
-      LOG_RESTYLE("resolving style with replacement");
-      newContext =
-        styleSet->ResolveStyleWithReplacement(element, pseudoElement,
-                                              parentContext, oldContext,
-                                              aRestyleHint);
-    }
-  } else if (pseudoType == nsCSSPseudoElements::ePseudo_AnonBox) {
-    newContext = styleSet->ResolveAnonymousBoxStyle(pseudoTag,
-                                                    parentContext);
-  }
   else {
     Element* element = ElementForStyleContext(mParentContent, aSelf, pseudoType);
-    if (pseudoTag) {
-      if (pseudoTag == nsCSSPseudoElements::before ||
-          pseudoTag == nsCSSPseudoElements::after) {
-        // XXX what other pseudos do we need to treat like this?
-        newContext = styleSet->ProbePseudoElementStyle(element,
-                                                       pseudoType,
-                                                       parentContext,
-                                                       mTreeMatchContext);
-        if (!newContext) {
-          // This pseudo should no longer exist; gotta reframe
-          NS_UpdateHint(mHintsHandled, nsChangeHint_ReconstructFrame);
-          mChangeList->AppendChange(aSelf, element,
-                                    nsChangeHint_ReconstructFrame);
-          // We're reframing anyway; just keep the same context
-          newContext = oldContext;
-#ifdef DEBUG
-          // oldContext's parent might have had its style structs swapped out
-          // with parentContext, so to avoid any assertions that might
-          // otherwise trigger in oldContext's parent's destructor, we set a
-          // flag on oldContext to skip it and its descendants in
-          // nsStyleContext::AssertStructsNotUsedElsewhere.
-          if (oldContext->GetParent() != parentContext) {
-            oldContext->AddStyleBit(NS_STYLE_IS_GOING_AWAY);
-          }
-#endif
-        }
+    if (!MustRestyleSelf(aRestyleHint, element)) {
+      if (!(aRestyleHint & ~(eRestyle_Force | eRestyle_ForceDescendants)) &&
+          !styleSet->IsInRuleTreeReconstruct()) {
+        LOG_RESTYLE("reparenting style context");
+        newContext =
+          styleSet->ReparentStyleContext(oldContext, parentContext, element);
       } else {
-        // Don't expect XUL tree stuff here, since it needs a comparator and
-        // all.
-        NS_ASSERTION(pseudoType <
-                       nsCSSPseudoElements::ePseudo_PseudoElementCount,
-                     "Unexpected pseudo type");
-        Element* pseudoElement =
-          PseudoElementForStyleContext(aSelf, pseudoType);
-        MOZ_ASSERT(element != pseudoElement,
+        // Use ResolveStyleWithReplacement either for actual replacements
+        // or, with no replacements, as a substitute for
+        // ReparentStyleContext that rebuilds the path in the rule tree
+        // rather than reusing the rule node, as we need to do during a
+        // rule tree reconstruct.
+        Element* pseudoElement = PseudoElementForStyleContext(aSelf, pseudoType);
+        MOZ_ASSERT(!element || element != pseudoElement,
                    "pseudo-element for selector matching should be "
                    "the anonymous content node that we create, "
                    "not the real element");
-        newContext = styleSet->ResolvePseudoElementStyle(element,
-                                                         pseudoType,
-                                                         parentContext,
-                                                         pseudoElement);
+        LOG_RESTYLE("resolving style with replacement");
+        nsRestyleHint rshint = aRestyleHint & ~eRestyle_SomeDescendants;
+        newContext =
+          styleSet->ResolveStyleWithReplacement(element, pseudoElement,
+                                                parentContext, oldContext,
+                                                rshint);
       }
+    } else if (pseudoType == nsCSSPseudoElements::ePseudo_AnonBox) {
+      newContext = styleSet->ResolveAnonymousBoxStyle(pseudoTag,
+                                                      parentContext);
     }
     else {
-      NS_ASSERTION(aSelf->GetContent(),
-                   "non pseudo-element frame without content node");
-      // Skip parent display based style fixup for anonymous subtrees:
-      TreeMatchContext::AutoParentDisplayBasedStyleFixupSkipper
-        parentDisplayBasedFixupSkipper(mTreeMatchContext,
-                               element->IsRootOfNativeAnonymousSubtree());
-      newContext = styleSet->ResolveStyleFor(element, parentContext,
-                                             mTreeMatchContext);
+      if (pseudoTag) {
+        if (pseudoTag == nsCSSPseudoElements::before ||
+            pseudoTag == nsCSSPseudoElements::after) {
+          // XXX what other pseudos do we need to treat like this?
+          newContext = styleSet->ProbePseudoElementStyle(element,
+                                                         pseudoType,
+                                                         parentContext,
+                                                         mTreeMatchContext);
+          if (!newContext) {
+            // This pseudo should no longer exist; gotta reframe
+            NS_UpdateHint(mHintsHandled, nsChangeHint_ReconstructFrame);
+            mChangeList->AppendChange(aSelf, element,
+                                      nsChangeHint_ReconstructFrame);
+            // We're reframing anyway; just keep the same context
+            newContext = oldContext;
+#ifdef DEBUG
+            // oldContext's parent might have had its style structs swapped out
+            // with parentContext, so to avoid any assertions that might
+            // otherwise trigger in oldContext's parent's destructor, we set a
+            // flag on oldContext to skip it and its descendants in
+            // nsStyleContext::AssertStructsNotUsedElsewhere.
+            if (oldContext->GetParent() != parentContext) {
+              oldContext->AddStyleBit(NS_STYLE_IS_GOING_AWAY);
+            }
+#endif
+          }
+        } else {
+          // Don't expect XUL tree stuff here, since it needs a comparator and
+          // all.
+          NS_ASSERTION(pseudoType <
+                         nsCSSPseudoElements::ePseudo_PseudoElementCount,
+                       "Unexpected pseudo type");
+          Element* pseudoElement =
+            PseudoElementForStyleContext(aSelf, pseudoType);
+          MOZ_ASSERT(element != pseudoElement,
+                     "pseudo-element for selector matching should be "
+                     "the anonymous content node that we create, "
+                     "not the real element");
+          newContext = styleSet->ResolvePseudoElementStyle(element,
+                                                           pseudoType,
+                                                           parentContext,
+                                                           pseudoElement);
+        }
+      }
+      else {
+        NS_ASSERTION(aSelf->GetContent(),
+                     "non pseudo-element frame without content node");
+        // Skip parent display based style fixup for anonymous subtrees:
+        TreeMatchContext::AutoParentDisplayBasedStyleFixupSkipper
+          parentDisplayBasedFixupSkipper(mTreeMatchContext,
+                                 element->IsRootOfNativeAnonymousSubtree());
+        newContext = styleSet->ResolveStyleFor(element, parentContext,
+                                               mTreeMatchContext);
+      }
     }
   }
 
   MOZ_ASSERT(newContext);
 
   if (!parentContext) {
     if (oldContext->RuleNode() == newContext->RuleNode() &&
         oldContext->IsLinkContext() == newContext->IsLinkContext() &&
@@ -3459,19 +3592,19 @@ ElementRestyler::RestyleSelf(nsIFrame* a
     LOG_RESTYLE_INDENT();
     nsRefPtr<nsStyleContext> newExtraContext;
     nsIAtom* const extraPseudoTag = oldExtraContext->GetPseudo();
     const nsCSSPseudoElements::Type extraPseudoType =
       oldExtraContext->GetPseudoType();
     NS_ASSERTION(extraPseudoTag &&
                  extraPseudoTag != nsCSSAnonBoxes::mozNonElement,
                  "extra style context is not pseudo element");
-    if (!(aRestyleHint & (eRestyle_Self | eRestyle_Subtree))) {
-      Element* element = extraPseudoType != nsCSSPseudoElements::ePseudo_AnonBox
-                           ? mContent->AsElement() : nullptr;
+    Element* element = extraPseudoType != nsCSSPseudoElements::ePseudo_AnonBox
+                         ? mContent->AsElement() : nullptr;
+    if (!MustRestyleSelf(aRestyleHint, element)) {
       if (styleSet->IsInRuleTreeReconstruct()) {
         // Use ResolveStyleWithReplacement as a substitute for
         // ReparentStyleContext that rebuilds the path in the rule tree
         // rather than reusing the rule node, as we need to do during a
         // rule tree reconstruct.
         Element* pseudoElement =
           PseudoElementForStyleContext(aSelf, extraPseudoType);
         MOZ_ASSERT(!element || element != pseudoElement,
@@ -3688,30 +3821,34 @@ ElementRestyler::ComputeStyleChangeFor(n
   FramePropertyTable* propTable = presContext->PropertyTable();
 
   TreeMatchContext treeMatchContext(true,
                                     nsRuleWalker::eRelevantLinkUnvisited,
                                     presContext->Document());
   Element* parent =
     content ? content->GetParentElementCrossingShadowRoot() : nullptr;
   treeMatchContext.InitAncestors(parent);
+  nsTArray<nsCSSSelector*> selectorsForDescendants;
+  selectorsForDescendants.AppendElements(
+      aRestyleHintData.mSelectorsForDescendants);
   nsTArray<nsIContent*> visibleKidsOfHiddenElement;
   for (nsIFrame* ibSibling = aFrame; ibSibling;
        ibSibling = GetNextBlockInInlineSibling(propTable, ibSibling)) {
     // Outer loop over ib-split siblings
     for (nsIFrame* cont = ibSibling; cont; cont = cont->GetNextContinuation()) {
       if (GetPrevContinuationWithSameStyle(cont)) {
         // We already handled this element when dealing with its earlier
         // continuation.
         continue;
       }
 
       // Inner loop over next-in-flows of the current frame
       ElementRestyler restyler(presContext, cont, aChangeList,
                                aMinChange, aRestyleTracker,
+                               selectorsForDescendants,
                                treeMatchContext,
                                visibleKidsOfHiddenElement,
                                aContextsToClear, aSwappedStructOwners);
 
       restyler.Restyle(aRestyleHint);
 
       if (restyler.HintsHandledForFrame() & nsChangeHint_ReconstructFrame) {
         // If it's going to cause a framechange, then don't bother
@@ -3800,31 +3937,35 @@ ElementRestyler::RestyleUndisplayedNodes
     Element* element = undisplayed->mContent->AsElement();
     if (mRestyleTracker.GetRestyleData(element,
                                        undisplayedRestyleData)) {
       thisChildHint =
         nsRestyleHint(thisChildHint | undisplayedRestyleData->mRestyleHint);
     }
     nsRefPtr<nsStyleContext> undisplayedContext;
     nsStyleSet* styleSet = mPresContext->StyleSet();
-    if (thisChildHint & (eRestyle_Self | eRestyle_Subtree)) {
+    if (MustRestyleSelf(thisChildHint, element)) {
       undisplayedContext =
         styleSet->ResolveStyleFor(element, aParentContext, mTreeMatchContext);
     } else if (thisChildHint ||
                styleSet->IsInRuleTreeReconstruct()) {
+      // XXX Should the above condition ignore eRestyle_Force(Descendants)
+      // like the corresponding check in RestyleSelf?
+
       // Use ResolveStyleWithReplacement either for actual
       // replacements, or as a substitute for ReparentStyleContext
       // that rebuilds the path in the rule tree rather than reusing
       // the rule node, as we need to do during a rule tree
       // reconstruct.
+      nsRestyleHint rshint = thisChildHint & ~eRestyle_SomeDescendants;
       undisplayedContext =
         styleSet->ResolveStyleWithReplacement(element, nullptr,
                                               aParentContext,
                                               undisplayed->mStyle,
-                                              thisChildHint);
+                                              rshint);
     } else {
       undisplayedContext =
         styleSet->ReparentStyleContext(undisplayed->mStyle,
                                        aParentContext,
                                        element);
     }
     const nsStyleDisplay* display = undisplayedContext->StyleDisplay();
     if (display->mDisplay != aDisplay) {
@@ -4136,26 +4277,28 @@ RestyleManager::ComputeAndProcessStyleCh
                     "display:none descendant?");
   TreeMatchContext treeMatchContext(true,
                                     nsRuleWalker::eRelevantLinkUnvisited,
                                     frame->PresContext()->Document());
   nsIContent* parent = aElement->GetParent();
   Element* parentElement =
     parent && parent->IsElement() ? parent->AsElement() : nullptr;
   treeMatchContext.InitAncestors(parentElement);
+
+  nsTArray<nsCSSSelector*> selectorsForDescendants;
   nsTArray<nsIContent*> visibleKidsOfHiddenElement;
   nsTArray<ElementRestyler::ContextToClear> contextsToClear;
 
   // swappedStructOwners needs to be kept alive until after
   // ProcessRestyledFrames and ClearCachedInheritedStyleDataOnDescendants
   // calls; see comment in ElementRestyler::Restyle.
   nsTArray<nsRefPtr<nsStyleContext>> swappedStructOwners;
   nsStyleChangeList changeList;
   ElementRestyler r(frame->PresContext(), aElement, &changeList, aMinChange,
-                    aRestyleTracker, treeMatchContext,
+                    aRestyleTracker, selectorsForDescendants, treeMatchContext,
                     visibleKidsOfHiddenElement, contextsToClear,
                     swappedStructOwners);
   r.RestyleChildrenOfDisplayContentsElement(frame, aNewContext, aMinChange,
                                             aRestyleTracker,
                                             aRestyleHint, aRestyleHintData);
   ProcessRestyledFrames(changeList);
   ClearCachedInheritedStyleDataOnDescendants(contextsToClear);
 }
--- a/layout/base/RestyleManager.h
+++ b/layout/base/RestyleManager.h
@@ -514,16 +514,17 @@ public:
   };
 
   // Construct for the root of the subtree that we're restyling.
   ElementRestyler(nsPresContext* aPresContext,
                   nsIFrame* aFrame,
                   nsStyleChangeList* aChangeList,
                   nsChangeHint aHintsHandledByAncestors,
                   RestyleTracker& aRestyleTracker,
+                  nsTArray<nsCSSSelector*>& aSelectorsForDescendants,
                   TreeMatchContext& aTreeMatchContext,
                   nsTArray<nsIContent*>& aVisibleKidsOfHiddenElement,
                   nsTArray<ContextToClear>& aContextsToClear,
                   nsTArray<nsRefPtr<nsStyleContext>>& aSwappedStructOwners);
 
   // Construct for an element whose parent is being restyled.
   enum ConstructorFlags {
     FOR_OUT_OF_FLOW_CHILD = 1<<0
@@ -544,16 +545,17 @@ public:
                   nsIFrame* aFrame);
 
   // For restyling undisplayed content only (mFrame==null).
   ElementRestyler(nsPresContext* aPresContext,
                   nsIContent* aContent,
                   nsStyleChangeList* aChangeList,
                   nsChangeHint aHintsHandledByAncestors,
                   RestyleTracker& aRestyleTracker,
+                  nsTArray<nsCSSSelector*>& aSelectorsForDescendants,
                   TreeMatchContext& aTreeMatchContext,
                   nsTArray<nsIContent*>& aVisibleKidsOfHiddenElement,
                   nsTArray<ContextToClear>& aContextsToClear,
                   nsTArray<nsRefPtr<nsStyleContext>>& aSwappedStructOwners);
 
   /**
    * Restyle our frame's element and its subtree.
    *
@@ -634,16 +636,31 @@ private:
   /**
    * Restyle the children of this frame (and, in turn, their children).
    *
    * Second half of Restyle().
    */
   void RestyleChildren(nsRestyleHint aChildRestyleHint);
 
   /**
+   * Returns true iff a selector in mSelectorsForDescendants matches aElement.
+   * This is called when processing a eRestyle_SomeDescendants restyle hint.
+   */
+  bool SelectorMatchesForRestyle(Element* aElement);
+
+  /**
+   * Returns true iff aRestyleHint indicates that we should be restyling.
+   * Specifically, this will return true when eRestyle_Self or
+   * eRestyle_Subtree is present, or if eRestyle_SomeDescendants is
+   * present and the specified element matches one of the selectors in
+   * mSelectorsForDescendants.
+   */
+  bool MustRestyleSelf(nsRestyleHint aRestyleHint, Element* aElement);
+
+  /**
    * Helpers for Restyle().
    */
   void AddLayerChangesForAnimation();
 
   /**
    * Helpers for RestyleSelf().
    */
   void CaptureChange(nsStyleContext* aOldContext,
@@ -693,16 +710,18 @@ private:
   };
 
   enum A11yNotificationType {
     eDontNotify,
     eNotifyShown,
     eNotifyHidden
   };
 
+  void AddPendingRestylesForDescendantsMatchingSelectors(Element* aElement);
+
 #ifdef RESTYLE_LOGGING
   int32_t& LoggingDepth() { return mLoggingDepth; }
 #endif
 
 #ifdef DEBUG
   static nsCString RestyleResultToString(RestyleResult aRestyleResult);
 #endif
 
@@ -720,16 +739,17 @@ private:
   // the end of Restyle it is those handled for this frame as well).  We
   // need to generate a new change list entry for the frame when its
   // style comparision returns a hint other than one of these hints.
   nsChangeHint mHintsHandled;
   // See nsStyleContext::CalcStyleDifference
   nsChangeHint mParentFrameHintsNotHandledForDescendants;
   nsChangeHint mHintsNotHandledForDescendants;
   RestyleTracker& mRestyleTracker;
+  nsTArray<nsCSSSelector*>& mSelectorsForDescendants;
   TreeMatchContext& mTreeMatchContext;
   nsIFrame* mResolvedChild; // child that provides our parent style context
   // Array of style context subtrees in which we need to clear out cached
   // structs at the end of the restyle (after change hints have been
   // processed).
   nsTArray<ContextToClear>& mContextsToClear;
   // Style contexts that had old structs swapped into it and which should
   // stay alive until the end of the restyle.  (See comment in
--- a/layout/base/RestyleTracker.h
+++ b/layout/base/RestyleTracker.h
@@ -315,16 +315,24 @@ public:
    * eRestyle_LaterSiblings hint in it.
    *
    * The return value indicates whether any restyle data was found for
    * the element.  aData is set to nullptr iff false is returned.
    */
   bool GetRestyleData(Element* aElement, nsAutoPtr<RestyleData>& aData);
 
   /**
+   * Returns whether there is a RestyleData entry in mPendingRestyles
+   * for the given element.
+   */
+  bool HasRestyleData(Element* aElement) {
+    return mPendingRestyles.Contains(aElement);
+  }
+
+  /**
    * For each element in aElements, appends it to mRestyleRoots if it
    * has its restyle bit set.  This is used to ensure we restyle elements
    * that we did not add as restyle roots initially (due to there being
    * an ancestor with the restyle root bit set), but which we might
    * not have got around to restyling due to the restyle process
    * terminating early with eRestyleResult_Stop (see ElementRestyler::Restyle).
    *
    * This function must be called with elements in order such that
--- a/layout/style/nsCSSRuleProcessor.cpp
+++ b/layout/style/nsCSSRuleProcessor.cpp
@@ -2288,25 +2288,52 @@ static bool SelectorMatches(Element* aEl
       result = result || dependence;
     }
   }
   return result;
 }
 
 #undef STATE_CHECK
 
+#ifdef DEBUG
+static bool
+HasPseudoClassSelectorArgsWithCombinators(nsCSSSelector* aSelector)
+{
+  for (nsPseudoClassList* p = aSelector->mPseudoClassList; p; p = p->mNext) {
+    if (nsCSSPseudoClasses::HasSelectorListArg(p->mType)) {
+      for (nsCSSSelectorList* l = p->u.mSelectors; l; l = l->mNext) {
+        if (l->mSelectors->mNext) {
+          return true;
+        }
+      }
+    }
+  }
+  for (nsCSSSelector* n = aSelector->mNegations; n; n = n->mNegations) {
+    if (n->mNext) {
+      return true;
+    }
+  }
+  return false;
+}
+#endif
+
 /* static */ bool
 nsCSSRuleProcessor::RestrictedSelectorMatches(
     Element* aElement,
     nsCSSSelector* aSelector,
     TreeMatchContext& aTreeMatchContext)
 {
   MOZ_ASSERT(aSelector->IsRestrictedSelector(),
              "aSelector must not have a pseudo-element");
 
+  NS_WARN_IF_FALSE(!HasPseudoClassSelectorArgsWithCombinators(aSelector),
+                   "processing eRestyle_SomeDescendants can be slow if "
+                   "pseudo-classes with selector arguments can now have "
+                   "combinators in them");
+
   // We match aSelector as if :visited and :link both match visited and
   // unvisited links.
 
   NodeMatchContext nodeContext(EventStates(),
                                nsCSSRuleProcessor::IsLink(aElement));
   if (nodeContext.mIsRelevantLink) {
     aTreeMatchContext.SetHaveRelevantLink();
   }