Bug 1363699 part 1 - Make named CounterStyle objects not refcounted. r?heycam draft
authorXidorn Quan <me@upsuper.org>
Sat, 13 May 2017 21:42:23 +1000
changeset 577605 3fc65aeedfc5f40533e429e1bcec19ecb7e573a6
parent 577604 3b21430cadc5193ced8b4a4281dce0f170eb82ae
child 577606 779d2ca188eec848021a9c2fd28d85f42976e9ad
push id58733
push userxquan@mozilla.com
push dateMon, 15 May 2017 03:46:44 +0000
reviewersheycam
bugs1363699
milestone55.0a1
Bug 1363699 part 1 - Make named CounterStyle objects not refcounted. r?heycam This change does the following: * Introduce a new smart pointer called CounterStylePtr which either holds an AnonymousCounterStyle strongly, or a named CounterStyle managed by CounterStyleManager weakly, and use it to replace all RefPtr<CounterStyle> around the codebase. * Rename CounterStyleManager::mCacheTable to mStyles to reflect the fact that it is used to manage all styles, not just for caching. * Add a retired styles list which collect all named CounterStyle evicted from mStyles, and post a PostRefreshObserver to destroy objects in that list after next flush. * Remove helper functions for counter style in nsStyleList and expose mCounterStyle directly, to make code simpler with the new pointer. Reason for adding a new smart pointer type rather than making their AddRef/Release behave like BuiltinCounterStyle is that, it is possible that after a flush, some stale style structs may still be alive. They can contain pointer to destroyed CounterStyle objects. Although the actual content may never be accessed anymore, RefPtr may still access the object for refcounting during destruction. MozReview-Commit-ID: xxegwSDhNb
layout/base/nsCounterManager.h
layout/base/nsPresContext.cpp
layout/generic/ReflowInput.cpp
layout/generic/nsBlockFrame.cpp
layout/generic/nsBulletFrame.cpp
layout/style/CounterStyleManager.cpp
layout/style/CounterStyleManager.h
layout/style/ServoBindings.cpp
layout/style/nsComputedDOMStyle.cpp
layout/style/nsRuleNode.cpp
layout/style/nsStyleStruct.h
--- a/layout/base/nsCounterManager.h
+++ b/layout/base/nsCounterManager.h
@@ -77,29 +77,28 @@ struct nsCounterNode : public nsGenConNo
 
 struct nsCounterUseNode : public nsCounterNode {
     // The same structure passed through the style system:  an array
     // containing the values in the counter() or counters() in the order
     // given in the CSS spec.
     RefPtr<nsCSSValue::Array> mCounterFunction;
 
     nsPresContext* mPresContext;
-    RefPtr<mozilla::CounterStyle> mCounterStyle;
+    mozilla::CounterStylePtr mCounterStyle;
 
     // false for counter(), true for counters()
     bool mAllCounters;
 
     // args go directly to member variables here and of nsGenConNode
     nsCounterUseNode(nsPresContext* aPresContext,
                      nsCSSValue::Array* aCounterFunction,
                      uint32_t aContentIndex, bool aAllCounters)
         : nsCounterNode(aContentIndex, USE)
         , mCounterFunction(aCounterFunction)
         , mPresContext(aPresContext)
-        , mCounterStyle(nullptr)
         , mAllCounters(aAllCounters)
     {
         NS_ASSERTION(aContentIndex <= INT32_MAX, "out of range");
     }
 
     virtual bool InitTextFrame(nsGenConList* aList,
             nsIFrame* aPseudoFrame, nsIFrame* aTextFrame) override;
 
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -2277,16 +2277,39 @@ nsPresContext::UserFontSetUpdated(gfxUse
   // given the weight, width, and slant from the nsStyleFont). If it does,
   // mark that frame dirty and skip inspecting its descendants.
   nsIFrame* root = mShell->GetRootFrame();
   if (root) {
     nsFontFaceUtils::MarkDirtyForFontChange(root, aUpdatedFont);
   }
 }
 
+class CounterStyleCleaner : public nsAPostRefreshObserver
+{
+public:
+  CounterStyleCleaner(nsRefreshDriver* aRefreshDriver,
+                      CounterStyleManager* aCounterStyleManager)
+    : mRefreshDriver(aRefreshDriver)
+    , mCounterStyleManager(aCounterStyleManager)
+  {
+  }
+  virtual ~CounterStyleCleaner() {}
+
+  void DidRefresh() final
+  {
+    mRefreshDriver->RemovePostRefreshObserver(this);
+    mCounterStyleManager->CleanRetiredStyles();
+    delete this;
+  }
+
+private:
+  RefPtr<nsRefreshDriver> mRefreshDriver;
+  RefPtr<CounterStyleManager> mCounterStyleManager;
+};
+
 void
 nsPresContext::FlushCounterStyles()
 {
   if (!mShell) {
     return; // we've been torn down
   }
   if (mCounterStyleManager->IsInitial()) {
     // Still in its initial state, no need to clean.
@@ -2294,16 +2317,18 @@ nsPresContext::FlushCounterStyles()
   }
 
   if (mCounterStylesDirty) {
     bool changed = mCounterStyleManager->NotifyRuleChanged();
     if (changed) {
       PresShell()->NotifyCounterStylesAreDirty();
       PostRebuildAllStyleDataEvent(NS_STYLE_HINT_REFLOW,
                                    eRestyle_ForceDescendants);
+      RefreshDriver()->AddPostRefreshObserver(
+        new CounterStyleCleaner(RefreshDriver(), mCounterStyleManager));
     }
     mCounterStylesDirty = false;
   }
 }
 
 void
 nsPresContext::RebuildCounterStyles()
 {
--- a/layout/generic/ReflowInput.cpp
+++ b/layout/generic/ReflowInput.cpp
@@ -128,17 +128,17 @@ FontSizeInflationListMarginAdjustment(co
     const nsBlockFrame* blockFrame = static_cast<const nsBlockFrame*>(aFrame);
 
     // We only want to adjust the margins if we're dealing with an ordered
     // list.
     if (inflation > 1.0f &&
         blockFrame->HasBullet() &&
         inflation > 1.0f) {
 
-      auto listStyleType = aFrame->StyleList()->GetCounterStyle()->GetStyle();
+      auto listStyleType = aFrame->StyleList()->mCounterStyle->GetStyle();
       if (listStyleType != NS_STYLE_LIST_STYLE_NONE &&
           listStyleType != NS_STYLE_LIST_STYLE_DISC &&
           listStyleType != NS_STYLE_LIST_STYLE_CIRCLE &&
           listStyleType != NS_STYLE_LIST_STYLE_SQUARE &&
           listStyleType != NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED &&
           listStyleType != NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN) {
         // The HTML spec states that the default padding for ordered lists
         // begins at 40px, indicating that we have 40px of space to place a
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -7050,17 +7050,17 @@ nsBlockFrame::SetInitialChildList(ChildL
       }
       possibleListItem = parent;
     }
     if (mozilla::StyleDisplay::ListItem ==
           possibleListItem->StyleDisplay()->mDisplay &&
         !GetPrevInFlow()) {
       // Resolve style for the bullet frame
       const nsStyleList* styleList = StyleList();
-      CounterStyle* style = styleList->GetCounterStyle();
+      CounterStyle* style = styleList->mCounterStyle;
 
       CreateBulletFrameForListItem(
         style->IsBullet(),
         styleList->mListStylePosition == NS_STYLE_LIST_STYLE_POSITION_INSIDE);
     }
   } else {
     nsContainerFrame::SetInitialChildList(aListID, aChildList);
   }
@@ -7105,17 +7105,17 @@ nsBlockFrame::CreateBulletFrameForListIt
 
 bool
 nsBlockFrame::BulletIsEmpty() const
 {
   NS_ASSERTION(mContent->GetPrimaryFrame()->StyleDisplay()->mDisplay ==
                mozilla::StyleDisplay::ListItem && HasOutsideBullet(),
                "should only care when we have an outside bullet");
   const nsStyleList* list = StyleList();
-  return list->GetCounterStyle()->IsNone() &&
+  return list->mCounterStyle->IsNone() &&
          !list->GetListStyleImage();
 }
 
 void
 nsBlockFrame::GetSpokenBulletText(nsAString& aText) const
 {
   const nsStyleList* myList = StyleList();
   if (myList->GetListStyleImage()) {
--- a/layout/generic/nsBulletFrame.cpp
+++ b/layout/generic/nsBulletFrame.cpp
@@ -91,17 +91,17 @@ bool
 nsBulletFrame::IsEmpty()
 {
   return IsSelfEmpty();
 }
 
 bool
 nsBulletFrame::IsSelfEmpty()
 {
-  return StyleList()->GetCounterStyle()->IsNone();
+  return StyleList()->mCounterStyle->IsNone();
 }
 
 /* virtual */ void
 nsBulletFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
 {
   nsFrame::DidSetStyleContext(aOldStyleContext);
 
   imgRequestProxy *newRequest = StyleList()->GetListStyleImage();
@@ -152,21 +152,21 @@ nsBulletFrame::DidSetStyleContext(nsStyl
   // Update the list bullet accessible. If old style list isn't available then
   // no need to update the accessible tree because it's not created yet.
   if (aOldStyleContext) {
     nsAccessibilityService* accService = nsIPresShell::AccService();
     if (accService) {
       const nsStyleList* oldStyleList = aOldStyleContext->PeekStyleList();
       if (oldStyleList) {
         bool hadBullet = oldStyleList->GetListStyleImage() ||
-          !oldStyleList->GetCounterStyle()->IsNone();
+          !oldStyleList->mCounterStyle->IsNone();
 
         const nsStyleList* newStyleList = StyleList();
         bool hasBullet = newStyleList->GetListStyleImage() ||
-          !newStyleList->GetCounterStyle()->IsNone();
+          !newStyleList->mCounterStyle->IsNone();
 
         if (hadBullet != hasBullet) {
           accService->UpdateListBullet(PresContext()->GetPresShell(), mContent,
                                        hasBullet);
         }
       }
     }
   }
@@ -691,17 +691,17 @@ nsBulletFrame::BuildDisplayList(nsDispla
   aLists.Content()->AppendNewToTop(
     new (aBuilder) nsDisplayBullet(aBuilder, this));
 }
 
 Maybe<BulletRenderer>
 nsBulletFrame::CreateBulletRenderer(nsRenderingContext& aRenderingContext, nsPoint aPt)
 {
   const nsStyleList* myList = StyleList();
-  CounterStyle* listStyleType = myList->GetCounterStyle();
+  CounterStyle* listStyleType = myList->mCounterStyle;
   nsMargin padding = mPadding.GetPhysicalMargin(GetWritingMode());
 
   if (myList->GetListStyleImage() && mImageRequest) {
     uint32_t status;
     mImageRequest->GetImageStatus(&status);
     if (status & imgIRequest::STATUS_LOAD_COMPLETE &&
         !(status & imgIRequest::STATUS_ERROR)) {
       nsCOMPtr<imgIContainer> imageCon;
@@ -899,17 +899,17 @@ nsBulletFrame::SetListItemOrdinal(int32_
   *aChanged = oldOrdinal != mOrdinal;
 
   return nsCounterManager::IncrementCounter(mOrdinal, aIncrement);
 }
 
 void
 nsBulletFrame::GetListItemText(nsAString& aResult)
 {
-  CounterStyle* style = StyleList()->GetCounterStyle();
+  CounterStyle* style = StyleList()->mCounterStyle;
   NS_ASSERTION(style->GetStyle() != NS_STYLE_LIST_STYLE_NONE &&
                style->GetStyle() != NS_STYLE_LIST_STYLE_DISC &&
                style->GetStyle() != NS_STYLE_LIST_STYLE_CIRCLE &&
                style->GetStyle() != NS_STYLE_LIST_STYLE_SQUARE &&
                style->GetStyle() != NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED &&
                style->GetStyle() != NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN,
                "we should be using specialized code for these types");
 
@@ -986,17 +986,17 @@ nsBulletFrame::GetDesiredSize(nsPresCont
   // fully there, we'll end up with mIntrinsicSize not matching our size, but
   // won't trigger a reflow in OnStartContainer (because mIntrinsicSize will
   // match the image size).
   mIntrinsicSize.SizeTo(wm, 0, 0);
 
   nscoord bulletSize;
 
   nsAutoString text;
-  switch (myList->GetCounterStyle()->GetStyle()) {
+  switch (myList->mCounterStyle->GetStyle()) {
     case NS_STYLE_LIST_STYLE_NONE:
       finalSize.ISize(wm) = finalSize.BSize(wm) = 0;
       aMetrics.SetBlockStartAscent(0);
       break;
 
     case NS_STYLE_LIST_STYLE_DISC:
     case NS_STYLE_LIST_STYLE_CIRCLE:
     case NS_STYLE_LIST_STYLE_SQUARE: {
@@ -1108,17 +1108,17 @@ nsBulletFrame::GetPrefISize(nsRenderingC
 // Otherwise, we use the default implementation, same as nsFrame.
 static inline bool
 IsIgnoreable(const nsIFrame* aFrame, nscoord aISize)
 {
   if (aISize != nscoord(0)) {
     return false;
   }
   auto listStyle = aFrame->StyleList();
-  return listStyle->GetCounterStyle()->IsNone() &&
+  return listStyle->mCounterStyle->IsNone() &&
          !listStyle->GetListStyleImage();
 }
 
 /* virtual */ void
 nsBulletFrame::AddInlineMinISize(nsRenderingContext* aRenderingContext,
                                  nsIFrame::InlineMinISizeData* aData)
 {
   nscoord isize = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
@@ -1345,17 +1345,17 @@ nscoord
 nsBulletFrame::GetLogicalBaseline(WritingMode aWritingMode) const
 {
   nscoord ascent = 0, baselinePadding;
   if (GetStateBits() & BULLET_FRAME_IMAGE_LOADING) {
     ascent = BSize(aWritingMode);
   } else {
     RefPtr<nsFontMetrics> fm =
       nsLayoutUtils::GetFontMetricsForFrame(this, GetFontSizeInflation());
-    CounterStyle* listStyleType = StyleList()->GetCounterStyle();
+    CounterStyle* listStyleType = StyleList()->mCounterStyle;
     switch (listStyleType->GetStyle()) {
       case NS_STYLE_LIST_STYLE_NONE:
         break;
 
       case NS_STYLE_LIST_STYLE_DISC:
       case NS_STYLE_LIST_STYLE_CIRCLE:
       case NS_STYLE_LIST_STYLE_SQUARE:
         ascent = fm->MaxAscent();
@@ -1382,17 +1382,17 @@ nsBulletFrame::GetLogicalBaseline(Writin
   }
   return ascent +
     GetLogicalUsedMargin(aWritingMode).BStart(aWritingMode);
 }
 
 void
 nsBulletFrame::GetSpokenText(nsAString& aText)
 {
-  CounterStyle* style = StyleList()->GetCounterStyle();
+  CounterStyle* style = StyleList()->mCounterStyle;
   bool isBullet;
   style->GetSpokenCounterText(mOrdinal, GetWritingMode(), aText, isBullet);
   if (isBullet) {
     if (!style->IsNone()) {
       aText.Append(' ');
     }
   } else {
     nsAutoString prefix, suffix;
--- a/layout/style/CounterStyleManager.cpp
+++ b/layout/style/CounterStyleManager.cpp
@@ -606,20 +606,16 @@ public:
   virtual CounterStyle* GetFallback() override;
   virtual uint8_t GetSpeakAs() override;
   virtual bool UseNegativeSign() override;
 
   virtual bool GetInitialCounterText(CounterValue aOrdinal,
                                      WritingMode aWritingMode,
                                      nsSubstring& aResult,
                                      bool& aIsRTL) override;
-
-  // Builtin counter style does not need refcount at all
-  NS_IMETHOD_(MozExternalRefCountType) AddRef() override { return 2; }
-  NS_IMETHOD_(MozExternalRefCountType) Release() override { return 2; }
 };
 
 /* virtual */ void
 BuiltinCounterStyle::GetStyleName(nsSubstring& aResult)
 {
   MOZ_ASSERT(mStyle != NS_STYLE_LIST_STYLE_CUSTOM);
   const nsAFlatCString& str =
     nsCSSProps::ValueToKeyword(mStyle, nsCSSProps::kListStyleKTable);
@@ -957,57 +953,46 @@ BuiltinCounterStyle::GetInitialCounterTe
     default:
       NS_NOTREACHED("Unknown builtin counter style");
       return false;
   }
 }
 
 class DependentBuiltinCounterStyle final : public BuiltinCounterStyle
 {
-private:
-  ~DependentBuiltinCounterStyle() {}
 public:
   DependentBuiltinCounterStyle(int32_t aStyle, CounterStyleManager* aManager)
     : BuiltinCounterStyle(aStyle),
       mManager(aManager)
   {
     NS_ASSERTION(IsDependentStyle(), "Not a dependent builtin style");
     MOZ_ASSERT(!IsCustomStyle(), "Not a builtin style");
   }
 
   virtual CounterStyle* GetFallback() override;
 
-  // DependentBuiltinCounterStyle is managed in the same way as
-  // CustomCounterStyle.
-  NS_IMETHOD_(MozExternalRefCountType) AddRef() override;
-  NS_IMETHOD_(MozExternalRefCountType) Release() override;
-
   void* operator new(size_t sz, nsPresContext* aPresContext)
   {
     return aPresContext->PresShell()->AllocateByObjectID(
         eArenaObjectID_DependentBuiltinCounterStyle, sz);
   }
 
-private:
   void Destroy()
   {
     nsIPresShell* shell = mManager->PresContext()->PresShell();
     this->~DependentBuiltinCounterStyle();
     shell->FreeByObjectID(eArenaObjectID_DependentBuiltinCounterStyle, this);
   }
 
-  CounterStyleManager* mManager;
+private:
+  ~DependentBuiltinCounterStyle() {}
 
-  nsAutoRefCnt mRefCnt;
-  NS_DECL_OWNINGTHREAD
+  CounterStyleManager* mManager;
 };
 
-NS_IMPL_ADDREF(DependentBuiltinCounterStyle)
-NS_IMPL_RELEASE_WITH_DESTROY(DependentBuiltinCounterStyle, Destroy())
-
 /* virtual */ CounterStyle*
 DependentBuiltinCounterStyle::GetFallback()
 {
   switch (GetStyle()) {
     case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL:
     case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL:
     case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL:
     case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL:
@@ -1024,18 +1009,16 @@ DependentBuiltinCounterStyle::GetFallbac
     default:
       NS_NOTREACHED("Not a valid dependent builtin style");
       return BuiltinCounterStyle::GetFallback();
   }
 }
 
 class CustomCounterStyle final : public CounterStyle
 {
-private:
-  ~CustomCounterStyle() {}
 public:
   CustomCounterStyle(nsIAtom* aName,
                      CounterStyleManager* aManager,
                      nsCSSCounterStyleRule* aRule)
     : CounterStyle(NS_STYLE_LIST_STYLE_CUSTOM),
       mName(aName),
       mManager(aManager),
       mRule(aRule),
@@ -1090,36 +1073,32 @@ public:
                                      nsSubstring& aResult,
                                      bool& aIsRTL) override;
 
   bool IsExtendsSystem()
   {
     return mSystem == NS_STYLE_COUNTER_SYSTEM_EXTENDS;
   }
 
-  // CustomCounterStyle should be reference-counted because it may be
-  // dereferenced from the manager but still referenced by nodes and
-  // frames before the style change is propagated.
-  NS_IMETHOD_(MozExternalRefCountType) AddRef() override;
-  NS_IMETHOD_(MozExternalRefCountType) Release() override;
-
   void* operator new(size_t sz, nsPresContext* aPresContext)
   {
     return aPresContext->PresShell()->AllocateByObjectID(
         eArenaObjectID_CustomCounterStyle, sz);
   }
 
-private:
   void Destroy()
   {
     nsIPresShell* shell = mManager->PresContext()->PresShell();
     this->~CustomCounterStyle();
     shell->FreeByObjectID(eArenaObjectID_CustomCounterStyle, this);
   }
 
+private:
+  ~CustomCounterStyle() {}
+
   const nsTArray<nsString>& GetSymbols();
   const nsTArray<AdditiveSymbol>& GetAdditiveSymbols();
 
   // The speak-as values of counter styles may form a loop, and the
   // loops may have complex interaction with the loop formed by
   // extending. To solve this problem, the computation of speak-as is
   // divided into two phases:
   // 1. figure out the raw value, by ComputeRawSpeakAs, and
@@ -1185,24 +1164,18 @@ private:
   // That counter must not speak as another counter.
   CounterStyle* mSpeakAsCounter;
 
   CounterStyle* mExtends;
   // This field refers to the last counter in the extends chain. The
   // counter must be either a builtin style or a style whose system is
   // not 'extends'.
   CounterStyle* mExtendsRoot;
-
-  nsAutoRefCnt mRefCnt;
-  NS_DECL_OWNINGTHREAD
 };
 
-NS_IMPL_ADDREF(CustomCounterStyle)
-NS_IMPL_RELEASE_WITH_DESTROY(CustomCounterStyle, Destroy())
-
 void
 CustomCounterStyle::ResetCachedData()
 {
   mSymbols.Clear();
   mAdditiveSymbols.Clear();
   mFlags &= ~(FLAG_NEGATIVE_INITED |
               FLAG_PREFIX_INITED |
               FLAG_SUFFIX_INITED |
@@ -1982,53 +1955,65 @@ CounterStyle::CallFallbackStyle(CounterV
 }
 
 static BuiltinCounterStyle gBuiltinStyleTable[NS_STYLE_LIST_STYLE__MAX];
 
 CounterStyleManager::CounterStyleManager(nsPresContext* aPresContext)
   : mPresContext(aPresContext)
 {
   // Insert the static styles into cache table
-  mCacheTable.Put(nsGkAtoms::none, GetNoneStyle());
-  mCacheTable.Put(nsGkAtoms::decimal, GetDecimalStyle());
+  mStyles.Put(nsGkAtoms::none, GetNoneStyle());
+  mStyles.Put(nsGkAtoms::decimal, GetDecimalStyle());
 }
 
 CounterStyleManager::~CounterStyleManager()
 {
   MOZ_ASSERT(!mPresContext, "Disconnect should have been called");
 }
 
 /* static */ void
 CounterStyleManager::InitializeBuiltinCounterStyles()
 {
   for (uint32_t i = 0; i < NS_STYLE_LIST_STYLE__MAX; ++i) {
     gBuiltinStyleTable[i].mStyle = i;
   }
 }
 
 void
+CounterStyleManager::DestroyCounterStyle(CounterStyle* aCounterStyle)
+{
+  if (aCounterStyle->IsCustomStyle()) {
+    MOZ_ASSERT(!aCounterStyle->AsAnonymous(), "Anonymous counter styles "
+               "are not managed by CounterStyleManager");
+    static_cast<CustomCounterStyle*>(aCounterStyle)->Destroy();
+  } else if (aCounterStyle->IsDependentStyle()) {
+    static_cast<DependentBuiltinCounterStyle*>(aCounterStyle)->Destroy();
+  } else {
+    MOZ_ASSERT_UNREACHABLE("Builtin counter styles should not be destroyed");
+  }
+}
+
+void
 CounterStyleManager::Disconnect()
 {
-#ifdef DEBUG
-  for (auto iter = mCacheTable.Iter(); !iter.Done(); iter.Next()) {
-    CounterStyle* style = iter.UserData();
-    style->AddRef();
-    auto refcnt = style->Release();
-    NS_ASSERTION(!style->IsDependentStyle() || refcnt == 1,
-                 "Counter style is still referenced by other objects.");
+  CleanRetiredStyles();
+  for (auto iter = mStyles.Iter(); !iter.Done(); iter.Next()) {
+    CounterStyle* style = iter.Data();
+    if (style->IsDependentStyle()) {
+      DestroyCounterStyle(style);
+    }
   }
-#endif
-  mCacheTable.Clear();
+  mStyles.Clear();
   mPresContext = nullptr;
 }
 
 CounterStyle*
 CounterStyleManager::BuildCounterStyle(nsIAtom* aName)
 {
-  CounterStyle* data = mCacheTable.GetWeak(aName);
+  CounterStyle* data = mStyles.Get(aName);
   if (data) {
     return data;
   }
 
   // It is intentional that the predefined names are case-insensitive
   // but the user-defined names case-sensitive.
   // XXXheycam ServoStyleSets do not support custom counter styles yet.  Bug
   // 1328319.
@@ -2053,17 +2038,17 @@ CounterStyleManager::BuildCounterStyle(n
       } else {
         data = GetBuiltinStyle(type);
       }
     }
   }
   if (!data) {
     data = GetDecimalStyle();
   }
-  mCacheTable.Put(aName, data);
+  mStyles.Put(aName, data);
   return data;
 }
 
 /* static */ CounterStyle*
 CounterStyleManager::GetBuiltinStyle(int32_t aStyle)
 {
   MOZ_ASSERT(0 <= aStyle && aStyle < NS_STYLE_LIST_STYLE__MAX,
              "Require a valid builtin style constant");
@@ -2071,19 +2056,18 @@ CounterStyleManager::GetBuiltinStyle(int
              "Cannot get dependent builtin style");
   return &gBuiltinStyleTable[aStyle];
 }
 
 bool
 CounterStyleManager::NotifyRuleChanged()
 {
   bool changed = false;
-  nsTArray<RefPtr<CounterStyle>> kungFuDeathGrip;
-  for (auto iter = mCacheTable.Iter(); !iter.Done(); iter.Next()) {
-    RefPtr<CounterStyle>& style = iter.Data();
+  for (auto iter = mStyles.Iter(); !iter.Done(); iter.Next()) {
+    CounterStyle* style = iter.Data();
     bool toBeUpdated = false;
     bool toBeRemoved = false;
     // XXXheycam ServoStyleSets do not support custom counter styles yet.  Bug
     // 1328319.
     StyleSetHandle styleSet = mPresContext->StyleSet();
     // When this assertion is removed, please remove the hack to avoid it in
     // nsStyleList::nsStyleList.
     NS_ASSERTION(styleSet->IsGecko(),
@@ -2094,51 +2078,51 @@ CounterStyleManager::NotifyRuleChanged()
     if (!newRule) {
       if (style->IsCustomStyle()) {
         toBeRemoved = true;
       }
     } else {
       if (!style->IsCustomStyle()) {
         toBeRemoved = true;
       } else {
-        auto custom = static_cast<CustomCounterStyle*>(style.get());
+        auto custom = static_cast<CustomCounterStyle*>(style);
         if (custom->GetRule() != newRule) {
           toBeRemoved = true;
         } else if (custom->GetRuleGeneration() != newRule->GetGeneration()) {
           toBeUpdated = true;
           custom->ResetCachedData();
         }
       }
     }
     changed = changed || toBeUpdated || toBeRemoved;
     if (toBeRemoved) {
       if (style->IsDependentStyle()) {
-        if (style->IsCustomStyle()) {
-          // Since |style| is being removed from mCacheTable, it won't be
-          // visited by our post-removal iteration. So, we have to give it a
-          // manual ResetDependentData() call. (This only really matters if
-          // something else is holding a reference and keeping it alive.)
-          static_cast<CustomCounterStyle*>(style.get())->ResetDependentData();
-        }
-        // The object has to be held here so that it will not be released
-        // before all pointers that refer to it are reset. It will be released
-        // when kungFuDeathGrip goes out of scope at the end of this function.
-        kungFuDeathGrip.AppendElement(style);
+        // Add object to retired list so we can clean them up later.
+        mRetiredStyles.AppendElement(style);
       }
       iter.Remove();
     }
   }
 
   if (changed) {
-    for (auto iter = mCacheTable.Iter(); !iter.Done(); iter.Next()) {
-      CounterStyle* style = iter.UserData();
+    for (auto iter = mStyles.Iter(); !iter.Done(); iter.Next()) {
+      CounterStyle* style = iter.Data();
       if (style->IsCustomStyle()) {
         CustomCounterStyle* custom = static_cast<CustomCounterStyle*>(style);
         custom->ResetDependentData();
       }
       // There is no dependent data cached in DependentBuiltinCounterStyle
       // instances, so we don't need to reset their data.
     }
   }
   return changed;
 }
 
+void
+CounterStyleManager::CleanRetiredStyles()
+{
+  nsTArray<CounterStyle*> list(Move(mRetiredStyles));
+  for (CounterStyle* style : list) {
+    DestroyCounterStyle(style);
+  }
+}
+
 } // namespace mozilla
--- a/layout/style/CounterStyleManager.h
+++ b/layout/style/CounterStyleManager.h
@@ -2,17 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef mozilla_CounterStyleManager_h_
 #define mozilla_CounterStyleManager_h_
 
 #include "nsStringFwd.h"
-#include "nsRefPtrHashtable.h"
+#include "nsDataHashtable.h"
 #include "nsHashKeys.h"
 
 #include "nsStyleConsts.h"
 
 #include "mozilla/Attributes.h"
 
 #include "nsCSSValue.h"
 
@@ -91,18 +91,16 @@ public:
                                  bool& aIsRTL);
   virtual bool GetInitialCounterText(CounterValue aOrdinal,
                                      WritingMode aWritingMode,
                                      nsSubstring& aResult,
                                      bool& aIsRTL) = 0;
 
   virtual AnonymousCounterStyle* AsAnonymous() { return nullptr; }
 
-  NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
-
 protected:
   int32_t mStyle;
 };
 
 class AnonymousCounterStyle final : public CounterStyle
 {
 public:
   explicit AnonymousCounterStyle(const nsSubstring& aContent);
@@ -127,41 +125,140 @@ public:
                                      bool& aIsRTL) override;
 
   virtual AnonymousCounterStyle* AsAnonymous() override { return this; }
 
   bool IsSingleString() const { return mSingleString; }
   uint8_t GetSystem() const { return mSystem; }
   const nsTArray<nsString>& GetSymbols() const { return mSymbols; }
 
-  NS_INLINE_DECL_REFCOUNTING(AnonymousCounterStyle, override)
+  NS_INLINE_DECL_REFCOUNTING(AnonymousCounterStyle)
 
 private:
   ~AnonymousCounterStyle() {}
 
   bool mSingleString;
   uint8_t mSystem;
   nsTArray<nsString> mSymbols;
 };
 
+// A smart pointer to CounterStyle. It either owns a reference to an
+// anonymous counter style, or weakly refers to a named counter style
+// managed by counter style manager.
+class CounterStylePtr
+{
+public:
+  CounterStylePtr() : mRaw(0) {}
+  CounterStylePtr(const CounterStylePtr& aOther)
+    : mRaw(aOther.mRaw)
+  {
+    if (IsAnonymous()) {
+      AsAnonymous()->AddRef();
+    }
+  }
+  ~CounterStylePtr() { Reset(); }
+
+  CounterStylePtr& operator=(const CounterStylePtr& aOther)
+  {
+    if (this != &aOther) {
+      Reset();
+      new (this) CounterStylePtr(aOther);
+    }
+    return *this;
+  }
+  CounterStylePtr& operator=(decltype(nullptr))
+  {
+    Reset();
+    return *this;
+  }
+  CounterStylePtr& operator=(AnonymousCounterStyle* aCounterStyle)
+  {
+    Reset();
+    if (aCounterStyle) {
+      CounterStyle* raw = do_AddRef(aCounterStyle).take();
+      AssertPointerAligned(raw);
+      mRaw = reinterpret_cast<uintptr_t>(raw) | kAnonymousFlag;
+    }
+    return *this;
+  }
+  CounterStylePtr& operator=(CounterStyle* aCounterStyle)
+  {
+    Reset();
+    if (aCounterStyle) {
+      MOZ_ASSERT(!aCounterStyle->AsAnonymous());
+      AssertPointerAligned(aCounterStyle);
+      mRaw = reinterpret_cast<uintptr_t>(aCounterStyle);
+    }
+    return *this;
+  }
+
+  operator CounterStyle*() const & { return Get(); }
+  operator CounterStyle*() const && = delete;
+  CounterStyle* operator->() const { return Get(); }
+  explicit operator bool() const { return !!mRaw; }
+  bool operator!() const { return !mRaw; }
+  bool operator==(const CounterStylePtr& aOther) const
+    { return mRaw == aOther.mRaw; }
+  bool operator!=(const CounterStylePtr& aOther) const
+    { return mRaw != aOther.mRaw; }
+
+private:
+  CounterStyle* Get() const
+  {
+    return reinterpret_cast<CounterStyle*>(mRaw & ~kAnonymousFlag);
+  }
+  void AssertPointerAligned(CounterStyle* aPointer)
+  {
+    // This can be checked at compile time via
+    // > static_assert(alignof(CounterStyle) >= 2);
+    // but MSVC2015 doesn't support using alignof on an abstract class.
+    // Once we move to MSVC2017, we can replace this runtime check with
+    // the compile time check above.
+    MOZ_ASSERT(!(reinterpret_cast<uintptr_t>(aPointer) & kAnonymousFlag));
+  }
+
+  bool IsAnonymous() const { return !!(mRaw & kAnonymousFlag); }
+  AnonymousCounterStyle* AsAnonymous()
+  {
+    MOZ_ASSERT(IsAnonymous());
+    return static_cast<AnonymousCounterStyle*>(
+      reinterpret_cast<CounterStyle*>(mRaw & ~kAnonymousFlag));
+  }
+
+  void Reset()
+  {
+    if (IsAnonymous()) {
+      AsAnonymous()->Release();
+    }
+    mRaw = 0;
+  }
+
+  // mRaw contains the pointer, and its last bit is used for the flag.
+  // If the flag is set, this pointer owns an AnonymousCounterStyle,
+  // otherwise, it is a weak pointer referring a named counter style
+  // managed by CounterStyleManager.
+  static const uintptr_t kAnonymousFlag = 1;
+  uintptr_t mRaw;
+};
+
 class CounterStyleManager final
 {
 private:
   ~CounterStyleManager();
 public:
   explicit CounterStyleManager(nsPresContext* aPresContext);
 
   static void InitializeBuiltinCounterStyles();
 
   void Disconnect();
 
   bool IsInitial() const
   {
     // only 'none' and 'decimal'
-    return mCacheTable.Count() == 2;
+    return mStyles.Count() == 2;
   }
 
   CounterStyle* BuildCounterStyle(nsIAtom* aName);
 
   static CounterStyle* GetBuiltinStyle(int32_t aStyle);
   static CounterStyle* GetNoneStyle()
   {
     return GetBuiltinStyle(NS_STYLE_LIST_STYLE_NONE);
@@ -171,21 +268,28 @@ public:
     return GetBuiltinStyle(NS_STYLE_LIST_STYLE_DECIMAL);
   }
 
   // This method will scan all existing counter styles generated by this
   // manager, and remove or mark data dirty accordingly. It returns true
   // if any counter style is changed, false elsewise. This method should
   // be called when any counter style may be affected.
   bool NotifyRuleChanged();
+  // NotifyRuleChanged will evict no longer needed counter styles into
+  // mRetiredStyles, and this function destroys all objects listed there.
+  // It should be called only after no one may ever use those objects.
+  void CleanRetiredStyles();
 
   nsPresContext* PresContext() const { return mPresContext; }
 
   NS_INLINE_DECL_REFCOUNTING(CounterStyleManager)
 
 private:
+  void DestroyCounterStyle(CounterStyle* aCounterStyle);
+
   nsPresContext* mPresContext;
-  nsRefPtrHashtable<nsRefPtrHashKey<nsIAtom>, CounterStyle> mCacheTable;
+  nsDataHashtable<nsRefPtrHashKey<nsIAtom>, CounterStyle*> mStyles;
+  nsTArray<CounterStyle*> mRetiredStyles;
 };
 
 } // namespace mozilla
 
 #endif /* !defined(mozilla_CounterStyleManager_h_) */
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -1107,27 +1107,25 @@ Gecko_SetImageOrientationAsFromImage(nsS
 void
 Gecko_CopyImageOrientationFrom(nsStyleVisibility* aDst,
                                const nsStyleVisibility* aSrc)
 {
   aDst->mImageOrientation = aSrc->mImageOrientation;
 }
 
 void
-Gecko_SetListStyleType(nsStyleList* style_struct, uint32_t type)
+Gecko_SetListStyleType(nsStyleList* aList, uint32_t aType)
 {
-  // Builtin counter styles are static and use no-op refcounting, and thus are
-  // safe to use off-main-thread.
-  style_struct->SetCounterStyle(CounterStyleManager::GetBuiltinStyle(type));
+  aList->mCounterStyle = CounterStyleManager::GetBuiltinStyle(aType);
 }
 
 void
-Gecko_CopyListStyleTypeFrom(nsStyleList* dst, const nsStyleList* src)
+Gecko_CopyListStyleTypeFrom(nsStyleList* aDst, const nsStyleList* aSrc)
 {
-  dst->SetCounterStyle(src->GetCounterStyle());
+  aDst->mCounterStyle = aSrc->mCounterStyle;
 }
 
 already_AddRefed<css::URLValue>
 ServoBundledURI::IntoCssUrl()
 {
   if (!mURLString) {
     return nullptr;
   }
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -3709,23 +3709,23 @@ nsComputedDOMStyle::DoGetListStylePositi
                                    nsCSSProps::kListStylePositionKTable));
   return val.forget();
 }
 
 already_AddRefed<CSSValue>
 nsComputedDOMStyle::DoGetListStyleType()
 {
   RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
-  CounterStyle* style = StyleList()->GetCounterStyle();
+  CounterStyle* style = StyleList()->mCounterStyle;
   AnonymousCounterStyle* anonymous = style->AsAnonymous();
   nsAutoString tmp;
   if (!anonymous) {
     // want SetIdent
     nsString type;
-    StyleList()->GetListStyleType(type);
+    style->GetStyleName(type);
     nsStyleUtil::AppendEscapedCSSIdent(type, tmp);
   } else if (anonymous->IsSingleString()) {
     const nsTArray<nsString>& symbols = anonymous->GetSymbols();
     MOZ_ASSERT(symbols.Length() == 1);
     nsStyleUtil::AppendEscapedCSSString(symbols[0], tmp);
   } else {
     tmp.AppendLiteral("symbols(");
 
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -7949,34 +7949,38 @@ nsRuleNode::ComputeListData(void* aStart
     break;
   }
   default:
     MOZ_ASSERT(false, "unexpected value unit");
   }
 
   // list-style-type: string, none, inherit, initial
   const nsCSSValue* typeValue = aRuleData->ValueForListStyleType();
+  auto setListStyleType = [this, list](nsIAtom* type) {
+    list->mCounterStyle = mPresContext->
+      CounterStyleManager()->BuildCounterStyle(type);
+  };
   switch (typeValue->GetUnit()) {
     case eCSSUnit_Unset:
     case eCSSUnit_Inherit: {
       conditions.SetUncacheable();
-      list->SetCounterStyle(parentList->GetCounterStyle());
+      list->mCounterStyle = parentList->mCounterStyle;
       break;
     }
     case eCSSUnit_Initial:
-      list->SetListStyleType(nsGkAtoms::disc, mPresContext);
+      setListStyleType(nsGkAtoms::disc);
       break;
     case eCSSUnit_AtomIdent: {
-      list->SetListStyleType(typeValue->GetAtomValue(), mPresContext);
+      setListStyleType(typeValue->GetAtomValue());
       break;
     }
     case eCSSUnit_String: {
       nsString str;
       typeValue->GetStringValue(str);
-      list->SetCounterStyle(new AnonymousCounterStyle(str));
+      list->mCounterStyle = new AnonymousCounterStyle(str);
       break;
     }
     case eCSSUnit_Enumerated: {
       // For compatibility with html attribute map.
       // This branch should never be called for value from CSS.
       int32_t intValue = typeValue->GetIntValue();
       nsCOMPtr<nsIAtom> name;
       switch (intValue) {
@@ -7992,21 +7996,22 @@ nsRuleNode::ComputeListData(void* aStart
         case NS_STYLE_LIST_STYLE_UPPER_ALPHA:
           name = nsGkAtoms::upperAlpha;
           break;
         default:
           name = NS_Atomize(nsCSSProps::ValueToKeyword(
                   intValue, nsCSSProps::kListStyleKTable));
           break;
       }
-      list->SetListStyleType(name, mPresContext);
+      setListStyleType(name);
       break;
     }
     case eCSSUnit_Symbols:
-      list->SetCounterStyle(new AnonymousCounterStyle(typeValue->GetArrayValue()));
+      list->mCounterStyle =
+        new AnonymousCounterStyle(typeValue->GetArrayValue());
       break;
     case eCSSUnit_Null:
       break;
     default:
       NS_NOTREACHED("Unexpected value unit");
   }
 
   // list-style-image: url, none, inherit
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -1471,44 +1471,27 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
 
   imgRequestProxy* GetListStyleImage() const
   {
     return mListStyleImage ? mListStyleImage->get() : nullptr;
   }
 
   already_AddRefed<nsIURI> GetListStyleImageURI() const;
 
-  void GetListStyleType(nsSubstring& aType) const { mCounterStyle->GetStyleName(aType); }
-  mozilla::CounterStyle* GetCounterStyle() const
-  {
-    return mCounterStyle.get();
-  }
-  void SetCounterStyle(mozilla::CounterStyle* aStyle)
-  {
-    // NB: This function is called off-main-thread during parallel restyle, but
-    // only with builtin styles that use dummy refcounting.
-    MOZ_ASSERT(NS_IsMainThread() || !aStyle->IsDependentStyle());
-    mCounterStyle = aStyle;
-  }
-  void SetListStyleType(nsIAtom* aType, nsPresContext* aPresContext)
-  {
-    SetCounterStyle(aPresContext->CounterStyleManager()->BuildCounterStyle(aType));
-  }
-
   const nsStyleQuoteValues::QuotePairArray& GetQuotePairs() const;
 
   void SetQuotesInherit(const nsStyleList* aOther);
   void SetQuotesInitial();
   void SetQuotesNone();
   void SetQuotes(nsStyleQuoteValues::QuotePairArray&& aValues);
 
   uint8_t mListStylePosition;                  // [inherited]
   RefPtr<nsStyleImageRequest> mListStyleImage; // [inherited]
+  mozilla::CounterStylePtr mCounterStyle;      // [inherited]
 private:
-  RefPtr<mozilla::CounterStyle> mCounterStyle; // [inherited]
   RefPtr<nsStyleQuoteValues> mQuotes;   // [inherited]
   nsStyleList& operator=(const nsStyleList& aOther) = delete;
 public:
   nsRect        mImageRegion;           // [inherited] the rect to use within an image
 
 private:
   // nsStyleQuoteValues objects representing two common values, for sharing.
   static mozilla::StaticRefPtr<nsStyleQuoteValues> sInitialQuotes;