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.
--- 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