Bug 1258017 - Redesign and simplify rule tree GC. r=dbaron
authorBobby Holley <bobbyholley@gmail.com>
Thu, 24 Mar 2016 18:40:40 -0700
changeset 290727 dec6f360210220d77472538bb0863dfbdc37ef37
parent 290726 d478d82a9103926631bec33cf15fd315ee9429f0
child 290728 69b9c67c96ecdb08386b517b11f5b6895989b505
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron
bugs1258017
milestone48.0a1
Bug 1258017 - Redesign and simplify rule tree GC. r=dbaron The basic idea here is as follows: * Rule nodes are reference-counted, but releasing them adds them to a linked list rather than freeing them. This allows for the reuse that motivated the original GC scheme. * We get rid of the marking, and instead rely on the reference count. * Sweeping no longer requires a complicated traversal. We just pop items off the free list until it's empty. When a child is destroyed, its parent may go onto the free list. * We remove special handling for the root node, and use a regular reference-counted edge from the style set. * The free list automatically asserts that it's empty (meaning all nodes have been freed) in its destructor, which runs when the style set is destroyed. * We get rid of the list of style context roots on the style set. We still need a count though, because of the HasCachedStyleData check.
layout/base/nsPresShell.cpp
layout/style/StyleSetHandle.h
layout/style/StyleSetHandleInlines.h
layout/style/nsRuleNode.cpp
layout/style/nsRuleNode.h
layout/style/nsStyleContext.cpp
layout/style/nsStyleContext.h
layout/style/nsStyleSet.cpp
layout/style/nsStyleSet.h
layout/style/nsStyleStruct.h
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -863,19 +863,21 @@ PresShell::Init(nsIDocument* aDocument,
 
   // The document viewer owns both view manager and pres shell.
   mViewManager->SetPresShell(this);
 
   // Bind the context to the presentation shell.
   mPresContext = aPresContext;
   aPresContext->SetShell(this);
 
-  // Now we can initialize the style set.
-  aStyleSet->Init(aPresContext);
+  // Now we can initialize the style set. Make sure to set the member before
+  // calling Init, since various subroutines need to find the style set off
+  // the PresContext during initialization.
   mStyleSet = aStyleSet;
+  mStyleSet->Init(aPresContext);
 
   // Notify our prescontext that it now has a compatibility mode.  Note that
   // this MUST happen after we set up our style set but before we create any
   // frames.
   mPresContext->CompatibilityModeChanged();
 
   // Add the preference style sheet.
   UpdatePreferenceStyles();
--- a/layout/style/StyleSetHandle.h
+++ b/layout/style/StyleSetHandle.h
@@ -153,16 +153,19 @@ public:
     inline nsRestyleHint HasStateDependentStyle(dom::Element* aElement,
                                                 EventStates aStateMask);
     inline nsRestyleHint HasStateDependentStyle(
         dom::Element* aElement,
         mozilla::CSSPseudoElementType aPseudoType,
         dom::Element* aPseudoElement,
         EventStates aStateMask);
 
+    inline void RootStyleContextAdded();
+    inline void RootStyleContextRemoved();
+
   private:
     // Stores a pointer to an nsStyleSet or a ServoStyleSet.  The least
     // significant bit is 0 for the former, 1 for the latter.  This is
     // valid as the least significant bit will never be used for a pointer
     // value on platforms we care about.
     uintptr_t mValue;
   };
 
--- a/layout/style/StyleSetHandleInlines.h
+++ b/layout/style/StyleSetHandleInlines.h
@@ -229,13 +229,33 @@ StyleSetHandle::Ptr::HasStateDependentSt
                                             CSSPseudoElementType aPseudoType,
                                             dom::Element* aPseudoElement,
                                             EventStates aStateMask)
 {
   FORWARD(HasStateDependentStyle, (aElement, aPseudoType, aPseudoElement,
                                    aStateMask));
 }
 
+void
+StyleSetHandle::Ptr::RootStyleContextAdded()
+{
+  if (IsGecko()) {
+    AsGecko()->RootStyleContextAdded();
+  } else {
+    // Not needed.
+  }
+}
+
+void
+StyleSetHandle::Ptr::RootStyleContextRemoved()
+{
+  if (IsGecko()) {
+    RootStyleContextAdded();
+  } else {
+    // Not needed.
+  }
+}
+
 } // namespace mozilla
 
 #undef FORWARD
 
 #endif // mozilla_StyleSetHandleInlines_h
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -1444,72 +1444,31 @@ nsRuleNode::operator new(size_t sz, nsPr
 {
   // Check the recycle list first.
   return aPresContext->PresShell()->AllocateByObjectID(eArenaObjectID_nsRuleNode, sz);
 }
 
 // Overridden to prevent the global delete from being called, since the memory
 // came out of an nsIArena instead of the global delete operator's heap.
 void
-nsRuleNode::DestroyInternal(nsRuleNode ***aDestroyQueueTail)
-{
-  nsRuleNode *destroyQueue, **destroyQueueTail;
-  if (aDestroyQueueTail) {
-    destroyQueueTail = *aDestroyQueueTail;
-  } else {
-    destroyQueue = nullptr;
-    destroyQueueTail = &destroyQueue;
-  }
-
-  if (ChildrenAreHashed()) {
-    PLDHashTable *children = ChildrenHash();
-    for (auto iter = children->Iter(); !iter.Done(); iter.Next()) {
-      auto entry = static_cast<ChildrenHashEntry*>(iter.Get());
-      *destroyQueueTail = entry->mRuleNode;
-      destroyQueueTail = &entry->mRuleNode->mNextSibling;
-    }
-    *destroyQueueTail = nullptr; // ensure null-termination
-    delete children;
-  } else if (HaveChildren()) {
-    *destroyQueueTail = ChildrenList();
-    do {
-      destroyQueueTail = &(*destroyQueueTail)->mNextSibling;
-    } while (*destroyQueueTail);
-  }
-  mChildren.asVoid = nullptr;
-
-  if (aDestroyQueueTail) {
-    // Our caller destroys the queue.
-    *aDestroyQueueTail = destroyQueueTail;
-  } else {
-    // We have to do destroy the queue.  When we destroy each node, it
-    // will add its children to the queue.
-    while (destroyQueue) {
-      nsRuleNode *cur = destroyQueue;
-      destroyQueue = destroyQueue->mNextSibling;
-      if (!destroyQueue) {
-        NS_ASSERTION(destroyQueueTail == &cur->mNextSibling, "mangled list");
-        destroyQueueTail = &destroyQueue;
-      }
-      cur->DestroyInternal(&destroyQueueTail);
-    }
-  }
-
+nsRuleNode::Destroy()
+{
   // Destroy ourselves.
   this->~nsRuleNode();
 
   // Don't let the memory be freed, since it will be recycled
   // instead. Don't call the global operator delete.
   mPresContext->PresShell()->FreeByObjectID(eArenaObjectID_nsRuleNode, this);
 }
 
-nsRuleNode* nsRuleNode::CreateRootNode(nsPresContext* aPresContext)
-{
-  return new (aPresContext)
-    nsRuleNode(aPresContext, nullptr, nullptr, SheetType::Unknown, false);
+already_AddRefed<nsRuleNode>
+nsRuleNode::CreateRootNode(nsPresContext* aPresContext)
+{
+  return do_AddRef(new (aPresContext)
+    nsRuleNode(aPresContext, nullptr, nullptr, SheetType::Unknown, false));
 }
 
 nsRuleNode::nsRuleNode(nsPresContext* aContext, nsRuleNode* aParent,
                        nsIStyleRule* aRule, SheetType aLevel,
                        bool aIsImportant)
   : mPresContext(aContext),
     mParent(aParent),
     mRule(aRule),
@@ -1524,37 +1483,36 @@ nsRuleNode::nsRuleNode(nsPresContext* aC
   MOZ_ASSERT(IsRoot() == !aRule,
              "non-root rule nodes must have a rule");
 
   mChildren.asVoid = nullptr;
   MOZ_COUNT_CTOR(nsRuleNode);
 
   NS_ASSERTION(IsRoot() || GetLevel() == aLevel, "not enough bits");
   NS_ASSERTION(IsRoot() || IsImportantRule() == aIsImportant, "yikes");
-  /* If IsRoot(), then aContext->StyleSet() is typically null at this
-     point.  In any case, we don't want to treat the root rulenode as
-     unused.  */
-  if (!IsRoot()) {
-    mParent->AddRef();
-    MOZ_ASSERT(aContext->StyleSet()->IsGecko(),
-               "ServoStyleSets should not have rule nodes");
-    aContext->StyleSet()->AsGecko()->RuleNodeUnused();
-  }
+  MOZ_ASSERT(aContext->StyleSet()->IsGecko(),
+             "ServoStyleSets should not have rule nodes");
+  aContext->StyleSet()->AsGecko()->RuleNodeUnused(this, /* aMayGC = */ false);
 
   // nsStyleSet::GetContext depends on there being only one animation
   // rule.
   MOZ_ASSERT(IsRoot() || GetLevel() != SheetType::Animation ||
              mParent->IsRoot() ||
              mParent->GetLevel() != SheetType::Animation,
              "must be only one rule at animation level");
 }
 
 nsRuleNode::~nsRuleNode()
 {
+  MOZ_ASSERT(!HaveChildren());
   MOZ_COUNT_DTOR(nsRuleNode);
+  if (mParent) {
+    mParent->RemoveChild(this);
+  }
+
   if (mStyleData.mResetData || mStyleData.mInheritedData)
     mStyleData.Destroy(mDependentBits, mPresContext);
 }
 
 nsRuleNode*
 nsRuleNode::Transition(nsIStyleRule* aRule, SheetType aLevel,
                        bool aIsImportantRule)
 {
@@ -1654,16 +1612,44 @@ nsRuleNode::ConvertChildrenToHash(int32_
     auto entry =
       static_cast<ChildrenHashEntry*>(hash->Add(&key));
     NS_ASSERTION(!entry->mRuleNode, "duplicate entries in list");
     entry->mRuleNode = curr;
   }
   SetChildrenHash(hash);
 }
 
+void
+nsRuleNode::RemoveChild(nsRuleNode* aNode)
+{
+  MOZ_ASSERT(HaveChildren());
+  if (ChildrenAreHashed()) {
+    PLDHashTable* children = ChildrenHash();
+    Key key = aNode->GetKey();
+    MOZ_ASSERT(children->Search(&key));
+    children->Remove(&key);
+    if (children->EntryCount() == 0) {
+      delete children;
+      mChildren.asVoid = nullptr;
+    }
+  } else {
+    // This linear traversal is unfortunate, but we do the same thing when
+    // adding nodes. The traversal is bounded by kMaxChildrenInList.
+    nsRuleNode** curr = &mChildren.asList;
+    while (*curr != aNode) {
+      curr = &((*curr)->mNextSibling);
+      MOZ_ASSERT(*curr);
+    }
+    *curr = (*curr)->mNextSibling;
+
+    // If there was one element in the list, this sets mChildren.asList
+    // to 0, and HaveChildren() will return false.
+  }
+}
+
 inline void
 nsRuleNode::PropagateNoneBit(uint32_t aBit, nsRuleNode* aHighestNode)
 {
   nsRuleNode* curr = this;
   for (;;) {
     NS_ASSERTION(!(curr->mNoneBits & aBit), "propagating too far");
     curr->mNoneBits |= aBit;
     if (curr == aHighestNode)
@@ -10086,126 +10072,16 @@ nsRuleNode::GetStyleData(nsStyleStructID
 
   // Nothing is cached.  We'll have to delve further and examine our rules.
   data = WalkRuleTree(aSID, aContext);
 
   MOZ_ASSERT(data, "should have aborted on out-of-memory");
   return data;
 }
 
-void
-nsRuleNode::Mark()
-{
-  for (nsRuleNode *node = this;
-       node && !(node->mDependentBits & NS_RULE_NODE_GC_MARK);
-       node = node->mParent)
-    node->mDependentBits |= NS_RULE_NODE_GC_MARK;
-}
-
-bool
-nsRuleNode::DestroyIfNotMarked()
-{
-  // If we're not marked, then we have to delete ourself.
-  // However, we never allow the root node to GC itself, because nsStyleSet
-  // wants to hold onto the root node and not worry about re-creating a
-  // rule walker if the root node is deleted.
-  MOZ_ASSERT(mPresContext->StyleSet()->IsGecko(),
-             "ServoStyleSets should not have rule nodes");
-  if (!(mDependentBits & NS_RULE_NODE_GC_MARK) &&
-      // Skip this only if we're the *current* root and not an old one.
-      !(IsRoot() && mPresContext->StyleSet()->AsGecko()->GetRuleTree() == this)) {
-    Destroy();
-    return true;
-  }
-
-  // Clear our mark, for the next time around.
-  mDependentBits &= ~NS_RULE_NODE_GC_MARK;
-  return false;
-}
-
-void
-nsRuleNode::SweepChildren(nsTArray<nsRuleNode*>& aSweepQueue)
-{
-  NS_ASSERTION(!(mDependentBits & NS_RULE_NODE_GC_MARK),
-               "missing DestroyIfNotMarked() call");
-  NS_ASSERTION(HaveChildren(),
-               "why call SweepChildren with no children?");
-  uint32_t childrenDestroyed = 0;
-  nsRuleNode* survivorsWithChildren = nullptr;
-  if (ChildrenAreHashed()) {
-    PLDHashTable* children = ChildrenHash();
-    uint32_t oldChildCount = children->EntryCount();
-    for (auto iter = children->Iter(); !iter.Done(); iter.Next()) {
-      auto entry = static_cast<ChildrenHashEntry*>(iter.Get());
-      nsRuleNode* node = entry->mRuleNode;
-      if (node->DestroyIfNotMarked()) {
-        iter.Remove();
-      } else if (node->HaveChildren()) {
-        // When children are hashed mNextSibling is not normally used but we
-        // use it here to build a list of children that needs to be swept.
-        nsRuleNode** headQ = &survivorsWithChildren;
-        node->mNextSibling = *headQ;
-        *headQ = node;
-      }
-    }
-    childrenDestroyed = oldChildCount - children->EntryCount();
-    if (childrenDestroyed == oldChildCount) {
-      delete children;
-      mChildren.asVoid = nullptr;
-    }
-  } else {
-    for (nsRuleNode** children = ChildrenListPtr(); *children; ) {
-      nsRuleNode* next = (*children)->mNextSibling;
-      if ((*children)->DestroyIfNotMarked()) {
-        // This rule node was destroyed, unlink it from the list by
-        // making *children point to the next entry.
-        *children = next;
-        ++childrenDestroyed;
-      } else {
-        children = &(*children)->mNextSibling;
-      }
-    }
-    survivorsWithChildren = ChildrenList();
-  }
-  if (survivorsWithChildren) {
-    aSweepQueue.AppendElement(survivorsWithChildren);
-  }
-  NS_ASSERTION(childrenDestroyed <= mRefCnt, "wrong ref count");
-  mRefCnt -= childrenDestroyed;
-  NS_POSTCONDITION(IsRoot() || mRefCnt > 0,
-                   "We didn't get swept, so we'd better have style contexts "
-                   "pointing to us or to one of our descendants, which means "
-                   "we'd better have a nonzero mRefCnt here!");
-}
-
-bool
-nsRuleNode::Sweep()
-{
-  NS_ASSERTION(IsRoot(), "must start sweeping at a root");
-  NS_ASSERTION(!mNextSibling, "root must not have mNextSibling");
-
-  if (DestroyIfNotMarked()) {
-    return true;
-  }
-
-  AutoTArray<nsRuleNode*, 70> sweepQueue;
-  sweepQueue.AppendElement(this);
-  while (!sweepQueue.IsEmpty()) {
-    nsTArray<nsRuleNode*>::index_type last = sweepQueue.Length() - 1;
-    nsRuleNode* ruleNode = sweepQueue[last];
-    sweepQueue.RemoveElementAt(last);
-    for (; ruleNode; ruleNode = ruleNode->mNextSibling) {
-      if (ruleNode->HaveChildren()) {
-        ruleNode->SweepChildren(sweepQueue);
-      }
-    }
-  }
-  return false;
-}
-
 /* static */ bool
 nsRuleNode::HasAuthorSpecifiedRules(nsStyleContext* aStyleContext,
                                     uint32_t ruleTypeMask,
                                     bool aAuthorColorsAllowed)
 {
   uint32_t inheritBits = 0;
   if (ruleTypeMask & NS_AUTHOR_SPECIFIED_BACKGROUND)
     inheritBits |= NS_STYLE_INHERIT_BIT(Background);
--- a/layout/style/nsRuleNode.h
+++ b/layout/style/nsRuleNode.h
@@ -7,16 +7,17 @@
  * a node in the lexicographic tree of rules that match an element,
  * responsible for converting the rules' information into computed style
  */
 
 #ifndef nsRuleNode_h___
 #define nsRuleNode_h___
 
 #include "mozilla/ArenaObjectID.h"
+#include "mozilla/LinkedList.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/RangedArray.h"
 #include "mozilla/RuleNodeCacheConditions.h"
 #include "mozilla/SheetType.h"
 #include "nsPresContext.h"
 #include "nsStyleStruct.h"
 
 class nsCSSPropertySet;
@@ -341,21 +342,21 @@ struct nsCachedStyleData
   ~nsCachedStyleData() {}
 };
 
 /**
  * nsRuleNode is a node in a lexicographic tree (the "rule tree")
  * indexed by style rules (implementations of nsIStyleRule).
  *
  * The rule tree is owned by the nsStyleSet and is destroyed when the
- * presentation of the document goes away.  It is garbage-collected
- * (using mark-and-sweep garbage collection) during the lifetime of the
- * document (when dynamic changes cause the destruction of enough style
- * contexts).  Rule nodes are marked if they are pointed to by a style
- * context or one of their descendants is.
+ * presentation of the document goes away. Its entries are reference-
+ * counted, with strong references held by child nodes, style structs
+ * and (for the root), the style set. Rule nodes are not immediately
+ * destroyed when their reference-count drops to zero, but are instead
+ * destroyed during a GC sweep.
  *
  * An nsStyleContext, which represents the computed style data for an
  * element, points to an nsRuleNode.  The path from the root of the rule
  * tree to the nsStyleContext's mRuleNode gives the list of the rules
  * matched, from least important in the cascading order to most
  * important in the cascading order.
  *
  * The reason for using a lexicographic tree is that it allows for
@@ -384,17 +385,20 @@ struct nsCachedStyleData
  * represented by an nsRuleNode are also immutable.
  */
 
 enum nsFontSizeType {
   eFontSize_HTML = 1,
   eFontSize_CSS = 2
 };
 
-class nsRuleNode {
+// Note: This LinkedListElement is used for storing unused nodes in the
+// linked list on nsStyleSet. We use mNextSibling for the singly-linked
+// sibling list.
+class nsRuleNode : public mozilla::LinkedListElement<nsRuleNode> {
 public:
   enum RuleDetail {
     eRuleNone, // No props have been specified at all.
     eRulePartialReset, // At least one prop with a non-"inherit" value
                        // has been specified.  No props have been
                        // specified with an "inherit" value.  At least
                        // one prop remains unspecified.
     eRulePartialMixed, // At least one prop with a non-"inherit" value
@@ -410,21 +414,21 @@ public:
                     // a non-"inherit" value.
     eRuleFullInherited  // All props have been specified with "inherit"
                         // values.
   };
 
 private:
   nsPresContext* const mPresContext; // Our pres context.
 
-  nsRuleNode* const mParent; // A pointer to the parent node in the tree.
-                             // This enables us to walk backwards from the
-                             // most specific rule matched to the least
-                             // specific rule (which is the optimal order to
-                             // use for lookups of style properties.
+  const RefPtr<nsRuleNode> mParent; // A pointer to the parent node in the tree.
+                                    // This enables us to walk backwards from the
+                                    // most specific rule matched to the least
+                                    // specific rule (which is the optimal order to
+                                    // use for lookups of style properties.
 
   const nsCOMPtr<nsIStyleRule> mRule; // A pointer to our specific rule.
 
   nsRuleNode* mNextSibling; // This value should be used only by the
                             // parent, since the parent may store
                             // children in a hash, which means this
                             // pointer is not meaningful.  Order of
                             // siblings is also not meaningful.
@@ -452,19 +456,16 @@ private:
   };
 
   static PLDHashNumber
   ChildrenHashHashKey(const void *aKey);
 
   static bool
   ChildrenHashMatchEntry(const PLDHashEntryHdr *aHdr, const void *aKey);
 
-  void SweepChildren(nsTArray<nsRuleNode*>& aSweepQueue);
-  bool DestroyIfNotMarked();
-
   static const PLDHashTableOps ChildrenHashOps;
 
   Key GetKey() const {
     return Key(mRule, GetLevel(), IsImportantRule());
   }
 
   // The children of this node are stored in either a hashtable or list
   // that maps from rules to our nsRuleNode children.  When matching
@@ -510,16 +511,18 @@ private:
   }
   void SetChildrenHash(PLDHashTable *aHashtable) {
     NS_ASSERTION(!(intptr_t(aHashtable) & kTypeMask),
                  "pointer not 2-byte aligned");
     mChildren.asHash = (PLDHashTable*)(intptr_t(aHashtable) | kHashType);
   }
   void ConvertChildrenToHash(int32_t aNumKids);
 
+  void RemoveChild(nsRuleNode* aNode);
+
   nsCachedStyleData mStyleData;   // Any data we cached on the rule node.
 
   uint32_t mDependentBits; // Used to cache the fact that we can look up
                            // cached data under a parent rule.
 
   uint32_t mNoneBits; // Used to cache the fact that the branch to this
                       // node specifies no non-inherited data for a
                       // given struct type.  (This usually implies that
@@ -533,41 +536,35 @@ private:
                       // out of the rule tree early and just inherit
                       // from the parent style context.  The presence of
                       // this bit means we should just get inherited
                       // data from the parent style context, and it is
                       // never used for reset structs since their
                       // Compute*Data functions don't initialize from
                       // inherited data.
 
-  // Reference count.  This just counts the style contexts that reference this
-  // rulenode.  And children the rulenode has had.  When this goes to 0 or
-  // stops being 0, we notify the style set.
-  // Note, in particular, that when a child is removed mRefCnt is NOT
-  // decremented.  This is on purpose; the notifications to the style set are
-  // only used to determine when it's worth running GC on the ruletree, and
-  // this setup makes it so we only count unused ruletree leaves for purposes
-  // of deciding when to GC.  We could more accurately count unused rulenodes
-  // by releasing/addrefing our parent when our refcount transitions to or from
-  // 0, but it doesn't seem worth it to do that.
+  // Reference count. Style contexts hold strong references to their rule node,
+  // and rule nodes hold strong references to their parent.
+  //
+  // When the refcount drops to zero, we don't necessarily free the node.
+  // Instead, we notify the style set, which performs periodic sweeps.
   uint32_t mRefCnt;
 
 public:
   // Infallible overloaded new operator that allocates from a presShell arena.
   void* operator new(size_t sz, nsPresContext* aContext) CPP_THROW_NEW;
-  void Destroy() { DestroyInternal(nullptr); }
+  void Destroy();
 
   // Implemented in nsStyleSet.h, since it needs to know about nsStyleSet.
   inline void AddRef();
 
   // Implemented in nsStyleSet.h, since it needs to know about nsStyleSet.
   inline void Release();
 
 protected:
-  void DestroyInternal(nsRuleNode ***aDestroyQueueTail);
   void PropagateDependentBit(nsStyleStructID aSID, nsRuleNode* aHighestNode,
                              void* aStruct);
   void PropagateNoneBit(uint32_t aBit, nsRuleNode* aHighestNode);
   static void PropagateGrandancestorBit(nsStyleContext* aContext,
                                         nsStyleContext* aContextInheritedFrom);
 
   const void* SetDefaultOnRoot(const nsStyleStructID aSID,
                                nsStyleContext* aContext);
@@ -804,17 +801,17 @@ protected:
 
 private:
   nsRuleNode(nsPresContext* aPresContext, nsRuleNode* aParent,
              nsIStyleRule* aRule, mozilla::SheetType aLevel, bool aIsImportant);
   ~nsRuleNode();
 
 public:
   // This is infallible; it will never return nullptr.
-  static nsRuleNode* CreateRootNode(nsPresContext* aPresContext);
+  static already_AddRefed<nsRuleNode> CreateRootNode(nsPresContext* aPresContext);
 
   static void EnsureBlockDisplay(uint8_t& display,
                                  bool aConvertListItem = false);
   static void EnsureInlineDisplay(uint8_t& display);
 
   // Transition never returns null; on out of memory it'll just return |this|.
   nsRuleNode* Transition(nsIStyleRule* aRule, mozilla::SheetType aLevel,
                          bool aIsImportantRule);
@@ -955,27 +952,16 @@ public:
     return data;                                                              \
   }
 
   #include "nsStyleStructList.h"
 
   #undef STYLE_STRUCT_RESET
   #undef STYLE_STRUCT_INHERITED
 
-  /*
-   * Garbage collection.  Mark walks up the tree, marking any unmarked
-   * ancestors until it reaches a marked one.  Sweep recursively sweeps
-   * the children, destroys any that are unmarked, and clears marks,
-   * returning true if the node on which it was called was destroyed.
-   * If children are hashed, the mNextSibling field on the children is
-   * temporarily used internally by Sweep.
-   */
-  void Mark();
-  bool Sweep();
-
   static bool
     HasAuthorSpecifiedRules(nsStyleContext* aStyleContext,
                             uint32_t ruleTypeMask,
                             bool aAuthorColorsAllowed);
 
   /**
    * Fill in to aPropertiesOverridden all of the properties in aProperties
    * that, for this rule node, have a declaration that is higher than the
--- a/layout/style/nsStyleContext.cpp
+++ b/layout/style/nsStyleContext.cpp
@@ -110,49 +110,38 @@ nsStyleContext::nsStyleContext(nsStyleCo
 #ifdef DEBUG
     nsRuleNode *r1 = mParent->RuleNode(), *r2 = aRuleNode;
     while (r1->GetParent())
       r1 = r1->GetParent();
     while (r2->GetParent())
       r2 = r2->GetParent();
     NS_ASSERTION(r1 == r2, "must be in the same rule tree as parent");
 #endif
+  } else {
+    mRuleNode->PresContext()->PresShell()->StyleSet()->RootStyleContextAdded();
   }
 
-  mRuleNode->AddRef();
   mRuleNode->SetUsedDirectly(); // before ApplyStyleFixups()!
-
-  if (!mParent) {
-    // Add as a root before ApplyStyleFixups, since ApplyStyleFixups
-    // can trigger rule tree GC.
-    nsStyleSet* styleSet =
-      mRuleNode->PresContext()->PresShell()->StyleSet()->GetAsGecko();
-    if (styleSet) {
-      styleSet->AddStyleContextRoot(this);
-    }
-  }
-
   ApplyStyleFixups(aSkipParentDisplayBasedStyleFixup);
 
   #define eStyleStruct_LastItem (nsStyleStructID_Length - 1)
   NS_ASSERTION(NS_STYLE_INHERIT_MASK & NS_STYLE_INHERIT_BIT(LastItem),
                "NS_STYLE_INHERIT_MASK must be bigger, and other bits shifted");
   #undef eStyleStruct_LastItem
 }
 
 nsStyleContext::~nsStyleContext()
 {
   NS_ASSERTION((nullptr == mChild) && (nullptr == mEmptyChild), "destructing context with children");
 
   nsPresContext *presContext = mRuleNode->PresContext();
-  nsStyleSet* styleSet = presContext->PresShell()->StyleSet()->GetAsGecko();
-
-  NS_ASSERTION(!styleSet ||
-               styleSet->GetRuleTree() == mRuleNode->RuleTree() ||
-               styleSet->IsInRuleTreeReconstruct(),
+  StyleSetHandle styleSet = presContext->PresShell()->StyleSet();
+  NS_ASSERTION(!styleSet->IsGecko() ||
+               styleSet->AsGecko()->GetRuleTree() == mRuleNode->RuleTree() ||
+               styleSet->AsGecko()->IsInRuleTreeReconstruct(),
                "destroying style context from old rule tree too late");
 
 #ifdef DEBUG
   if (sExpensiveStyleStructAssertionsEnabled) {
     // Assert that the style structs we are about to destroy are not referenced
     // anywhere else in the style context tree.  These checks are expensive,
     // which is why they are not enabled by default.
     nsStyleContext* root = this;
@@ -163,24 +152,20 @@ nsStyleContext::~nsStyleContext()
                                         std::numeric_limits<int32_t>::max());
   } else {
     // In DEBUG builds when the pref is not enabled, we perform a more limited
     // check just of the children of this style context.
     AssertStructsNotUsedElsewhere(this, 2);
   }
 #endif
 
-  mRuleNode->Release();
-
-  if (styleSet) {
-    styleSet->NotifyStyleContextDestroyed(this);
-  }
-
   if (mParent) {
     mParent->RemoveChild(this);
+  } else {
+    styleSet->RootStyleContextRemoved();
   }
 
   // Free up our data structs.
   mCachedInheritedData.DestroyStructs(mBits, presContext);
   if (mCachedResetData) {
     mCachedResetData->Destroy(mBits, presContext);
   }
 
@@ -1122,40 +1107,16 @@ nsStyleContext::CalcStyleDifference(nsSt
     if (change) {
       NS_UpdateHint(hint, nsChangeHint_RepaintFrame);
     }
   }
 
   return NS_SubtractHint(hint, nsChangeHint_NeutralChange);
 }
 
-void
-nsStyleContext::Mark()
-{
-  // Mark our rule node.
-  mRuleNode->Mark();
-
-  // Mark our children (i.e., tell them to mark their rule nodes, etc.).
-  if (mChild) {
-    nsStyleContext* child = mChild;
-    do {
-      child->Mark();
-      child = child->mNextSibling;
-    } while (mChild != child);
-  }
-  
-  if (mEmptyChild) {
-    nsStyleContext* child = mEmptyChild;
-    do {
-      child->Mark();
-      child = child->mNextSibling;
-    } while (mEmptyChild != child);
-  }
-}
-
 #ifdef DEBUG
 void nsStyleContext::List(FILE* out, int32_t aIndent, bool aListDescendants)
 {
   nsAutoCString str;
   // Indent
   int32_t ix;
   for (ix = aIndent; --ix >= 0; ) {
     str.AppendLiteral("  ");
--- a/layout/style/nsStyleContext.h
+++ b/layout/style/nsStyleContext.h
@@ -32,18 +32,16 @@ enum class CSSPseudoElementType : uint8_
  * GetUniqueStyleData).  When style data change,
  * nsFrameManager::ReResolveStyleContext creates a new style context.
  *
  * Style contexts are reference counted.  References are generally held
  * by:
  *  1. the |nsIFrame|s that are using the style context and
  *  2. any *child* style contexts (this might be the reverse of
  *     expectation, but it makes sense in this case)
- * Style contexts participate in the mark phase of rule node garbage
- * collection.
  */
 
 class nsStyleContext final
 {
 public:
   /**
    * Create a new style context.
    * @param aParent  The parent of a style context is used for CSS
@@ -274,22 +272,16 @@ public:
   bool HasCachedDependentStyleData(nsStyleStructID aSID) {
     return mBits & nsCachedStyleData::GetBitForSID(aSID);
   }
 
   nsRuleNode* RuleNode() { return mRuleNode; }
   void AddStyleBit(const uint64_t& aBit) { mBits |= aBit; }
 
   /*
-   * Mark this style context's rule node (and its ancestors) to prevent
-   * it from being garbage collected.
-   */
-  void Mark();
-
-  /*
    * Get the style data for a style struct.  This is the most important
    * member function of nsStyleContext.  It fills in a const pointer
    * to a style data struct that is appropriate for the style context's
    * frame.  This struct may be shared with other contexts (either in
    * the rule tree or the style context tree), so it should not be
    * modified.
    *
    * This function will NOT return null (even when out of memory) when
@@ -585,17 +577,17 @@ private:
   nsCOMPtr<nsIAtom> mPseudoTag;
 
   // The rule node is the node in the lexicographic tree of rule nodes
   // (the "rule tree") that indicates which style rules are used to
   // compute the style data, and in what cascading order.  The least
   // specific rule matched is the one whose rule node is a child of the
   // root of the rule tree, and the most specific rule matched is the
   // |mRule| member of |mRuleNode|.
-  nsRuleNode* const       mRuleNode;
+  const RefPtr<nsRuleNode> mRuleNode;
 
   // mCachedInheritedData and mCachedResetData point to both structs that
   // are owned by this style context and structs that are owned by one of
   // this style context's ancestors (which are indirectly owned since this
   // style context owns a reference to its parent).  If the bit in |mBits|
   // is set for a struct, that means that the pointer for that struct is
   // owned by an ancestor or by mRuleNode rather than by this style context.
   // Since style contexts typically have some inherited data but only sometimes
--- a/layout/style/nsStyleSet.cpp
+++ b/layout/style/nsStyleSet.cpp
@@ -182,21 +182,26 @@ nsStyleSet::IsCSSSheetType(SheetType aSh
   }
   return false;
 }
 
 nsStyleSet::nsStyleSet()
   : mRuleTree(nullptr),
     mBatching(0),
     mInShutdown(false),
+    mInGC(false),
     mAuthorStyleDisabled(false),
     mInReconstruct(false),
     mInitFontFeatureValuesLookup(true),
     mNeedsRestyleAfterEnsureUniqueInner(false),
     mDirty(0),
+    mRootStyleContextCount(0),
+#ifdef DEBUG
+    mOldRootNode(nullptr),
+#endif
     mUnusedRuleNodeCount(0)
 {
 }
 
 nsStyleSet::~nsStyleSet()
 {
   for (SheetType type : gCSSSheetTypes) {
     for (CSSStyleSheet* sheet : mSheets[type]) {
@@ -241,19 +246,16 @@ nsStyleSet::SizeOfIncludingThis(MallocSi
     n += mSheets[type].ShallowSizeOfExcludingThis(aMallocSizeOf);
   }
 
   for (uint32_t i = 0; i < mScopedDocSheetRuleProcessors.Length(); i++) {
     n += mScopedDocSheetRuleProcessors[i]->SizeOfIncludingThis(aMallocSizeOf);
   }
   n += mScopedDocSheetRuleProcessors.ShallowSizeOfExcludingThis(aMallocSizeOf);
 
-  n += mRoots.ShallowSizeOfExcludingThis(aMallocSizeOf);
-  n += mOldRuleTrees.ShallowSizeOfExcludingThis(aMallocSizeOf);
-
   return n;
 }
 
 void
 nsStyleSet::Init(nsPresContext *aPresContext)
 {
   mFirstLineRule = new nsEmptyStyleRule;
   mFirstLetterRule = new nsEmptyStyleRule;
@@ -272,61 +274,40 @@ nsStyleSet::Init(nsPresContext *aPresCon
   GatherRuleProcessors(SheetType::Transition);
 }
 
 nsresult
 nsStyleSet::BeginReconstruct()
 {
   NS_ASSERTION(!mInReconstruct, "Unmatched begin/end?");
   NS_ASSERTION(mRuleTree, "Reconstructing before first construction?");
+  mInReconstruct = true;
 
   // Clear any ArenaRefPtr-managed style contexts, as we don't want them
   // held on to after the rule tree has been reconstructed.
   PresContext()->PresShell()->ClearArenaRefPtrs(eArenaObjectID_nsStyleContext);
 
-  // Create a new rule tree root
-  nsRuleNode* newTree = nsRuleNode::CreateRootNode(mRuleTree->PresContext());
+#ifdef DEBUG
+  MOZ_ASSERT(!mOldRootNode);
+  mOldRootNode = mRuleTree;
+#endif
 
-  // Save the old rule tree so we can destroy it later
-  if (!mOldRuleTrees.AppendElement(mRuleTree)) {
-    newTree->Destroy();
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  // We need to keep mRoots so that the rule tree GC will only free the
-  // rule trees that really aren't referenced anymore (which should be
-  // all of them, if there are no bugs in reresolution code).
-
-  mInReconstruct = true;
-  mRuleTree = newTree;
+  // Create a new rule tree root, dropping the reference to our old rule tree.
+  // After reconstruction, we will re-enable GC, and allow everything to be
+  // collected.
+  mRuleTree = nsRuleNode::CreateRootNode(mRuleTree->PresContext());
 
   return NS_OK;
 }
 
 void
 nsStyleSet::EndReconstruct()
 {
   NS_ASSERTION(mInReconstruct, "Unmatched begin/end?");
   mInReconstruct = false;
-#ifdef DEBUG
-  for (int32_t i = mRoots.Length() - 1; i >= 0; --i) {
-    // Since nsStyleContext's mParent and mRuleNode are immutable, and
-    // style contexts own their parents, and nsStyleContext asserts in
-    // its constructor that the style context and its parent are in the
-    // same rule tree, we don't need to check any of the children of
-    // mRoots; we only need to check the rule nodes of mRoots
-    // themselves.
-
-    NS_ASSERTION(mRoots[i]->RuleNode()->RuleTree() == mRuleTree,
-                 "style context has old rule node");
-  }
-#endif
-  // This *should* destroy the only element of mOldRuleTrees, but in
-  // case of some bugs (which would trigger the above assertions), it
-  // won't.
   GCRuleTrees();
 }
 
 typedef nsDataHashtable<nsPtrHashKey<nsINode>, uint32_t> ScopeDepthCache;
 
 // Returns the depth of a style scope element, with 1 being the depth of
 // a style scope element that has no ancestor style scope elements.  The
 // depth does not count intervening non-scope elements.
@@ -2192,97 +2173,49 @@ nsStyleSet::AppendPageRules(nsTArray<nsC
   }
   return true;
 }
 
 void
 nsStyleSet::BeginShutdown()
 {
   mInShutdown = 1;
-  mRoots.Clear(); // no longer valid, since we won't keep it up to date
 }
 
 void
 nsStyleSet::Shutdown()
 {
-  mRuleTree->Destroy();
   mRuleTree = nullptr;
-
-  // We can have old rule trees either because:
-  //   (1) we failed the assertions in EndReconstruct, or
-  //   (2) we're shutting down within a reconstruct (see bug 462392)
-  for (uint32_t i = mOldRuleTrees.Length(); i > 0; ) {
-    --i;
-    mOldRuleTrees[i]->Destroy();
-  }
-  mOldRuleTrees.Clear();
+  GCRuleTrees();
+  MOZ_ASSERT(mUnusedRuleNodeList.isEmpty());
+  MOZ_ASSERT(mUnusedRuleNodeCount == 0);
 }
 
-static const uint32_t kGCInterval = 300;
-
-// Notification that a style context with a null parent has been created.
-void
-nsStyleSet::AddStyleContextRoot(nsStyleContext* aStyleContext)
-{
-  // aStyleContext has not been fully initialized, but its parent and
-  // rule node are correct.
-  MOZ_ASSERT(!aStyleContext->GetParent());
-  mRoots.AppendElement(aStyleContext);
-}
-
-void
-nsStyleSet::NotifyStyleContextDestroyed(nsStyleContext* aStyleContext)
-{
-  if (mInShutdown)
-    return;
-
-  // Remove style contexts from mRoots even if mOldRuleTree is non-null.  This
-  // could be a style context from the new ruletree!
-  if (!aStyleContext->GetParent()) {
-    mRoots.RemoveElement(aStyleContext);
-  }
-
-  if (mInReconstruct)
-    return;
-
-  if (mUnusedRuleNodeCount >= kGCInterval) {
-    GCRuleTrees();
-  }
-}
 
 void
 nsStyleSet::GCRuleTrees()
 {
-  mUnusedRuleNodeCount = 0;
+  MOZ_ASSERT(!mInReconstruct);
+  MOZ_ASSERT(!mInGC);
+  mInGC = true;
 
-  // Mark the style context tree by marking all style contexts which
-  // have no parent, which will mark all descendants.  This will reach
-  // style contexts in the undisplayed map and "additional style
-  // contexts" since they are descendants of the roots.
-  for (int32_t i = mRoots.Length() - 1; i >= 0; --i) {
-    mRoots[i]->Mark();
+  while (!mUnusedRuleNodeList.isEmpty()) {
+    nsRuleNode* node = mUnusedRuleNodeList.popFirst();
+#ifdef DEBUG
+    if (node == mOldRootNode) {
+      // Flag that we've GCed the old root, if any.
+      mOldRootNode = nullptr;
+    }
+#endif
+    node->Destroy();
   }
 
-  // Sweep the rule tree.
-#ifdef DEBUG
-  bool deleted =
-#endif
-    mRuleTree->Sweep();
-  NS_ASSERTION(!deleted, "Root node must not be gc'd");
-
-  // Sweep the old rule trees.
-  for (uint32_t i = mOldRuleTrees.Length(); i > 0; ) {
-    --i;
-    if (mOldRuleTrees[i]->Sweep()) {
-      // It was deleted, as it should be.
-      mOldRuleTrees.RemoveElementAt(i);
-    } else {
-      NS_NOTREACHED("old rule tree still referenced");
-    }
-  }
+  MOZ_ASSERT(!mOldRootNode, "Should have GCed old root node");
+  mUnusedRuleNodeCount = 0;
+  mInGC = false;
 }
 
 already_AddRefed<nsStyleContext>
 nsStyleSet::ReparentStyleContext(nsStyleContext* aStyleContext,
                                  nsStyleContext* aNewParentContext,
                                  Element* aElement)
 {
   MOZ_ASSERT(aStyleContext, "aStyleContext must not be null");
--- a/layout/style/nsStyleSet.h
+++ b/layout/style/nsStyleSet.h
@@ -10,16 +10,17 @@
  */
 
 #ifndef nsStyleSet_h_
 #define nsStyleSet_h_
 
 #include "mozilla/Attributes.h"
 #include "mozilla/CSSStyleSheet.h"
 #include "mozilla/EnumeratedArray.h"
+#include "mozilla/LinkedList.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/SheetType.h"
 
 #include "nsIStyleRuleProcessor.h"
 #include "nsBindingManager.h"
 #include "nsRuleNode.h"
 #include "nsTArray.h"
 #include "nsCOMArray.h"
@@ -256,24 +257,16 @@ class nsStyleSet final
 
   // Begin ignoring style context destruction, to avoid lots of unnecessary
   // work on document teardown.
   void BeginShutdown();
 
   // Free all of the data associated with this style set.
   void Shutdown();
 
-  // Notification that a style context with a null parent has been created.
-  // The argument is a style context that has not been fully initialized,
-  // but its parent and rule node are correct.
-  void AddStyleContextRoot(nsStyleContext* aStyleContext);
-
-  // Notification that a style context is being destroyed.
-  void NotifyStyleContextDestroyed(nsStyleContext* aStyleContext);
-
   // Get a new style context that lives in a different parent
   // The new context will be the same as the old if the new parent is the
   // same as the old parent.
   // aElement should be non-null if this is a style context for an
   // element or pseudo-element; in the latter case it should be the
   // real element the pseudo-element is for.
   already_AddRefed<nsStyleContext>
   ReparentStyleContext(nsStyleContext* aStyleContext,
@@ -358,35 +351,50 @@ class nsStyleSet final
   nsresult BeginReconstruct();
   // Note: EndReconstruct should not be called if BeginReconstruct fails
   void EndReconstruct();
 
   bool IsInRuleTreeReconstruct() const {
     return mInReconstruct;
   }
 
+  void RootStyleContextAdded() {
+    ++mRootStyleContextCount;
+  }
+  void RootStyleContextRemoved() {
+    MOZ_ASSERT(mRootStyleContextCount > 0);
+    --mRootStyleContextCount;
+  }
+
   // Return whether the rule tree has cached data such that we need to
   // do dynamic change handling for changes that change the results of
   // media queries or require rebuilding all style data.
   // We don't care whether we have cached rule processors or whether
   // they have cached rule cascades; getting the rule cascades again in
   // order to do rule matching will get the correct rule cascade.
   bool HasCachedStyleData() const {
-    return (mRuleTree && mRuleTree->TreeHasCachedData()) || !mRoots.IsEmpty();
+    return (mRuleTree && mRuleTree->TreeHasCachedData()) || mRootStyleContextCount > 0;
   }
 
   // Notify the style set that a rulenode is no longer in use, or was
   // just created and is not in use yet.
-  void RuleNodeUnused() {
+  static const uint32_t kGCInterval = 300;
+  void RuleNodeUnused(nsRuleNode* aNode, bool aMayGC) {
     ++mUnusedRuleNodeCount;
+    mUnusedRuleNodeList.insertBack(aNode);
+    if (aMayGC && mUnusedRuleNodeCount >= kGCInterval && !mInGC && !mInReconstruct) {
+      GCRuleTrees();
+    }
   }
 
   // Notify the style set that a rulenode that wasn't in use now is
-  void RuleNodeInUse() {
+  void RuleNodeInUse(nsRuleNode* aNode) {
+    MOZ_ASSERT(mUnusedRuleNodeCount > 0);
     --mUnusedRuleNodeCount;
+    aNode->removeFrom(mUnusedRuleNodeList);
   }
 
   // Returns true if a restyle of the document is needed due to cloning
   // sheet inners.
   bool EnsureUniqueInnerOnCSSSheets();
 
   // Called by CSSStyleSheet::EnsureUniqueInner to let us know it cloned
   // its inner.
@@ -406,17 +414,21 @@ class nsStyleSet final
   // CSSStyleSheets.  See gCSSSheetTypes in nsStyleSet.cpp for the list
   // of CSS sheet types.
   static bool IsCSSSheetType(mozilla::SheetType aSheetType);
 
 private:
   nsStyleSet(const nsStyleSet& aCopy) = delete;
   nsStyleSet& operator=(const nsStyleSet& aCopy) = delete;
 
-  // Run mark-and-sweep GC on mRuleTree and mOldRuleTrees, based on mRoots.
+  // Free all the rules with reference-count zero. This continues iterating
+  // over the free list until it is empty, which allows immediate collection
+  // of nodes whose reference-count drops to zero during the destruction of
+  // a child node. This allows the collection of entire trees at once, since
+  // children hold their parents alive.
   void GCRuleTrees();
 
   nsresult DirtyRuleProcessors(mozilla::SheetType aType);
 
   // Update the rule processor list after a change to the style sheet list.
   nsresult GatherRuleProcessors(mozilla::SheetType aType);
 
   void AddImportantRules(nsRuleNode* aCurrLevelNode,
@@ -493,68 +505,78 @@ private:
   mozilla::EnumeratedArray<mozilla::SheetType, mozilla::SheetType::Count,
                            nsCOMPtr<nsIStyleRuleProcessor>> mRuleProcessors;
 
   // Rule processors for HTML5 scoped style sheets, one per scope.
   nsTArray<nsCOMPtr<nsIStyleRuleProcessor> > mScopedDocSheetRuleProcessors;
 
   RefPtr<nsBindingManager> mBindingManager;
 
-  nsRuleNode* mRuleTree; // This is the root of our rule tree.  It is a
-                         // lexicographic tree of matched rules that style
-                         // contexts use to look up properties.
+  RefPtr<nsRuleNode> mRuleTree; // This is the root of our rule tree.  It is a
+                                // lexicographic tree of matched rules that style
+                                // contexts use to look up properties.
 
   uint16_t mBatching;
 
   unsigned mInShutdown : 1;
+  unsigned mInGC : 1;
   unsigned mAuthorStyleDisabled: 1;
   unsigned mInReconstruct : 1;
   unsigned mInitFontFeatureValuesLookup : 1;
   unsigned mNeedsRestyleAfterEnsureUniqueInner : 1;
   unsigned mDirty : int(mozilla::SheetType::Count);  // one bit per sheet type
 
-  uint32_t mUnusedRuleNodeCount; // used to batch rule node GC
-  nsTArray<nsStyleContext*> mRoots; // style contexts with no parent
+  uint32_t mRootStyleContextCount;
+
+#ifdef DEBUG
+  // In debug builds, we stash a weak pointer here to the old root during
+  // reconstruction. During GC, we check for this pointer, and null it out
+  // when we encounter it. This allows us to assert that the old root (and
+  // thus all of its subtree) was GCed after reconstruction, which implies
+  // that there are no style contexts holding on to old rule nodes.
+  nsRuleNode* mOldRootNode;
+#endif
+
+  // Track our rule nodes with zero refcount. When this hits a threshold, we
+  // sweep and free. Keeping unused rule nodes around for a bit allows us to
+  // reuse them in many cases.
+  mozilla::LinkedList<nsRuleNode> mUnusedRuleNodeList;
+  uint32_t mUnusedRuleNodeCount;
 
   // Empty style rules to force things that restrict which properties
   // apply into different branches of the rule tree.
   RefPtr<nsEmptyStyleRule> mFirstLineRule, mFirstLetterRule, mPlaceholderRule;
 
   // Style rule which sets all properties to their initial values for
   // determining when context-sensitive values are in use.
   RefPtr<nsInitialStyleRule> mInitialStyleRule;
 
   // Style rule that sets the internal -x-text-zoom property on
   // <svg:text> elements to disable the effect of text zooming.
   RefPtr<nsDisableTextZoomStyleRule> mDisableTextZoomStyleRule;
 
-  // Old rule trees, which should only be non-empty between
-  // BeginReconstruct and EndReconstruct, but in case of bugs that cause
-  // style contexts to exist too long, may last longer.
-  nsTArray<nsRuleNode*> mOldRuleTrees;
-
   // whether font feature values lookup object needs initialization
   RefPtr<gfxFontFeatureValueSet> mFontFeatureValuesLookup;
 };
 
 #ifdef MOZILLA_INTERNAL_API
 inline
 void nsRuleNode::AddRef()
 {
-  if (mRefCnt++ == 0 && !IsRoot()) {
+  if (mRefCnt++ == 0) {
     MOZ_ASSERT(mPresContext->StyleSet()->IsGecko(),
                "ServoStyleSets should not have rule nodes");
-    mPresContext->StyleSet()->AsGecko()->RuleNodeInUse();
+    mPresContext->StyleSet()->AsGecko()->RuleNodeInUse(this);
   }
 }
 
 inline
 void nsRuleNode::Release()
 {
-  if (--mRefCnt == 0 && !IsRoot()) {
+  if (--mRefCnt == 0) {
     MOZ_ASSERT(mPresContext->StyleSet()->IsGecko(),
                "ServoStyleSets should not have rule nodes");
-    mPresContext->StyleSet()->AsGecko()->RuleNodeUnused();
+    mPresContext->StyleSet()->AsGecko()->RuleNodeUnused(this, /* aMayGC = */ true);
   }
 }
 #endif
 
 #endif
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -79,17 +79,17 @@ struct nsStyleVisibility;
 #define NS_STYLE_INELIGIBLE_FOR_SHARING    0x200000000
 // See nsStyleContext::HasChildThatUsesResetStyle
 #define NS_STYLE_HAS_CHILD_THAT_USES_RESET_STYLE 0x400000000
 // See nsStyleContext::GetPseudoEnum
 #define NS_STYLE_CONTEXT_TYPE_SHIFT        35
 
 // Additional bits for nsRuleNode's mDependentBits:
 #define NS_RULE_NODE_IS_ANIMATION_RULE      0x01000000
-#define NS_RULE_NODE_GC_MARK                0x02000000
+// Free bit here!
 #define NS_RULE_NODE_USED_DIRECTLY          0x04000000
 #define NS_RULE_NODE_IS_IMPORTANT           0x08000000
 #define NS_RULE_NODE_LEVEL_MASK             0xf0000000
 #define NS_RULE_NODE_LEVEL_SHIFT            28
 
 // Additional bits for nsRuleNode's mNoneBits:
 #define NS_RULE_NODE_HAS_ANIMATION_DATA     0x80000000