Bug 966166 - Part 2: Computation of counter style. r=dbaron
authorXidorn Quan <quanxunzhen@gmail.com>
Wed, 11 Jun 2014 21:11:00 -0400
changeset 188452 001216bdc605
parent 188451 d21febcb08a3
child 188453 fffcb4bbc8b1
push id26956
push userkwierso@gmail.com
push dateFri, 13 Jun 2014 00:23:52 +0000
treeherdermozilla-central@adcf3f05f813 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron
bugs966166
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 966166 - Part 2: Computation of counter style. r=dbaron
layout/base/nsPresContext.cpp
layout/base/nsPresContext.h
layout/build/nsLayoutStatics.cpp
layout/generic/nsBulletFrame.cpp
layout/style/CounterStyleManager.cpp
layout/style/CounterStyleManager.h
layout/style/moz.build
layout/style/nsStyleConsts.h
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -38,16 +38,17 @@
 #include "gfxPlatform.h"
 #include "nsCSSRules.h"
 #include "nsFontFaceLoader.h"
 #include "mozilla/EventListenerManager.h"
 #include "prenv.h"
 #include "nsObjectFrame.h"
 #include "nsTransitionManager.h"
 #include "nsAnimationManager.h"
+#include "CounterStyleManager.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/dom/Element.h"
 #include "nsIMessageManager.h"
 #include "mozilla/dom/MediaQueryList.h"
 #include "nsSMILAnimationController.h"
 #include "mozilla/css/ImageLoader.h"
 #include "mozilla/dom/PBrowserParent.h"
 #include "mozilla/dom/TabChild.h"
@@ -959,19 +960,22 @@ nsPresContext::Init(nsDeviceContext* aDe
   mCurAppUnitsPerDevPixel = AppUnitsPerDevPixel();
 
   mEventManager = new mozilla::EventStateManager();
 
   mTransitionManager = new nsTransitionManager(this);
 
   mAnimationManager = new nsAnimationManager(this);
 
-  // FIXME: Why is mozilla:: needed?
+  // Since there are methods in nsPresContext have the same name as the
+  // classes, it is necessary to prefix them with the namespace here.
   mRestyleManager = new mozilla::RestyleManager(this);
 
+  mCounterStyleManager = new mozilla::CounterStyleManager(this);
+
   if (mDocument->GetDisplayDocument()) {
     NS_ASSERTION(mDocument->GetDisplayDocument()->GetShell() &&
                  mDocument->GetDisplayDocument()->GetShell()->GetPresContext(),
                  "Why are we being initialized?");
     mRefreshDriver = mDocument->GetDisplayDocument()->GetShell()->
       GetPresContext()->RefreshDriver();
   } else {
     nsIDocument* parent = mDocument->GetParentDocument();
@@ -1137,16 +1141,20 @@ nsPresContext::SetShell(nsIPresShell* aS
     if (mAnimationManager) {
       mAnimationManager->Disconnect();
       mAnimationManager = nullptr;
     }
     if (mRestyleManager) {
       mRestyleManager->Disconnect();
       mRestyleManager = nullptr;
     }
+    if (mCounterStyleManager) {
+      mCounterStyleManager->Disconnect();
+      mCounterStyleManager = nullptr;
+    }
 
     if (IsRoot()) {
       // Have to cancel our plugin geometry timer, because the
       // callback for that depends on a non-null presshell.
       static_cast<nsRootPresContext*>(this)->CancelApplyPluginGeometryTimer();
     }
   }
 }
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -64,16 +64,17 @@ class nsTransitionManager;
 class nsAnimationManager;
 class nsRefreshDriver;
 class nsIWidget;
 class nsDeviceContext;
 
 namespace mozilla {
 class EventStateManager;
 class RestyleManager;
+class CounterStyleManager;
 namespace dom {
 class MediaQueryList;
 }
 namespace layers {
 class ContainerLayer;
 }
 }
 
@@ -237,16 +238,20 @@ public:
     { return PresShell()->FrameConstructor(); }
 
   nsTransitionManager* TransitionManager() { return mTransitionManager; }
   nsAnimationManager* AnimationManager() { return mAnimationManager; }
 
   nsRefreshDriver* RefreshDriver() { return mRefreshDriver; }
 
   mozilla::RestyleManager* RestyleManager() { return mRestyleManager; }
+
+  mozilla::CounterStyleManager* CounterStyleManager() {
+    return mCounterStyleManager;
+  }
 #endif
 
   /**
    * Rebuilds all style data by throwing out the old rule tree and
    * building a new one, and additionally applying aExtraHint (which
    * must not contain nsChangeHint_ReconstructFrame) to the root frame.
    * Also rebuild the user font set.
    */
@@ -1177,16 +1182,17 @@ protected:
                                             // Cannot reintroduce cycles
                                             // since there is no dependency
                                             // from gfx back to layout.
   nsRefPtr<mozilla::EventStateManager> mEventManager;
   nsRefPtr<nsRefreshDriver> mRefreshDriver;
   nsRefPtr<nsTransitionManager> mTransitionManager;
   nsRefPtr<nsAnimationManager> mAnimationManager;
   nsRefPtr<mozilla::RestyleManager> mRestyleManager;
+  nsRefPtr<mozilla::CounterStyleManager> mCounterStyleManager;
   nsIAtom*              mMedium;        // initialized by subclass ctors;
                                         // weak pointer to static atom
   nsCOMPtr<nsIAtom> mMediaEmulated;
 
   nsILinkHandler*       mLinkHandler;   // [WEAK]
 
   // Formerly mLangGroup; moving from charset-oriented langGroup to
   // maintaining actual language settings everywhere (see bug 524107).
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -58,16 +58,17 @@
 #include "nsSVGUtils.h"
 #include "nsMathMLAtoms.h"
 #include "nsMathMLOperators.h"
 #include "Navigator.h"
 #include "DOMStorageObserver.h"
 #include "CacheObserver.h"
 #include "DisplayItemClip.h"
 #include "ActiveLayerTracker.h"
+#include "CounterStyleManager.h"
 
 #include "AudioChannelService.h"
 #include "mozilla/dom/DataStoreService.h"
 
 #ifdef MOZ_XUL
 #include "nsXULPopupManager.h"
 #include "nsXULContentUtils.h"
 #include "nsXULPrototypeCache.h"
@@ -288,16 +289,18 @@ nsLayoutStatics::Initialize()
   HTMLVideoElement::Init();
 
 #ifdef MOZ_XUL
   nsMenuBarListener::InitializeStatics();
 #endif
 
   CacheObserver::Init();
 
+  CounterStyleManager::InitializeBuiltinCounterStyles();
+
   return NS_OK;
 }
 
 void
 nsLayoutStatics::Shutdown()
 {
   // Don't need to shutdown nsWindowMemoryReporter, that will be done by the
   // memory reporter manager.
--- a/layout/generic/nsBulletFrame.cpp
+++ b/layout/generic/nsBulletFrame.cpp
@@ -794,17 +794,17 @@ enum CJKIdeographicLang {
 struct CJKIdeographicData {
   const char16_t *negative;
   char16_t digit[10];
   char16_t unit[3];
   char16_t unit10K[2];
   uint8_t lang;
   bool informal;
 };
-static const char16_t gJapaneseNegative[] = {
+extern const char16_t gJapaneseNegative[] = {
   0x30de, 0x30a4, 0x30ca, 0x30b9, 0x0000
 };
 static const CJKIdeographicData gDataJapaneseInformal = {
   gJapaneseNegative,          // negative
   {                           // digit
     0x3007, 0x4e00, 0x4e8c, 0x4e09, 0x56db,
     0x4e94, 0x516d, 0x4e03, 0x516b, 0x4e5d
   },
@@ -819,17 +819,17 @@ static const CJKIdeographicData gDataJap
     0x96f6, 0x58f1, 0x5f10, 0x53c2, 0x56db,
     0x4f0d, 0x516d, 0x4e03, 0x516b, 0x4e5d
   },
   { 0x62fe, 0x767e, 0x9621 }, // unit
   { 0x842c, 0x5104 },         // unit10K
   JAPANESE,                   // lang
   false                       // informal
 };
-static const char16_t gKoreanNegative[] = {
+extern const char16_t gKoreanNegative[] = {
   0xb9c8, 0xc774, 0xb108, 0xc2a4, 0x0020, 0x0000
 };
 static const CJKIdeographicData gDataKoreanHangulFormal = {
   gKoreanNegative,            // negative
   {                           // digit
     0xc601, 0xc77c, 0xc774, 0xc0bc, 0xc0ac,
     0xc624, 0xc721, 0xce60, 0xd314, 0xad6c
   },
@@ -855,17 +855,17 @@ static const CJKIdeographicData gDataKor
     0x96f6, 0x58f9, 0x8cb3, 0x53c3, 0x56db,
     0x4e94, 0x516d, 0x4e03, 0x516b, 0x4e5d
   },
   { 0x62fe, 0x767e, 0x4edf }, // unit
   { 0x842c, 0x5104 },         // unit10K
   KOREAN,                     // lang
   false                       // informal
 };
-static const char16_t gSimpChineseNegative[] = {
+extern const char16_t gSimpChineseNegative[] = {
   0x8d1f, 0x0000
 };
 static const CJKIdeographicData gDataSimpChineseInformal = {
   gSimpChineseNegative,       // negative
   {                           // digit
     0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db,
     0x4e94, 0x516d, 0x4e03, 0x516b, 0x4e5d
   },
@@ -880,17 +880,17 @@ static const CJKIdeographicData gDataSim
     0x96f6, 0x58f9, 0x8d30, 0x53c1, 0x8086,
     0x4f0d, 0x9646, 0x67d2, 0x634c, 0x7396
   },
   { 0x62fe, 0x4f70, 0x4edf }, // unit
   { 0x4e07, 0x4ebf },         // unit10K
   CHINESE,                    // lang
   false                       // informal
 };
-static const char16_t gTradChineseNegative[] = {
+extern const char16_t gTradChineseNegative[] = {
   0x8ca0, 0x0000
 };
 static const CJKIdeographicData gDataTradChineseInformal = {
   gTradChineseNegative,       // negative
   {                           // digit
     0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db,
     0x4e94, 0x516d, 0x4e03, 0x516b, 0x4e5d
   },
new file mode 100644
--- /dev/null
+++ b/layout/style/CounterStyleManager.cpp
@@ -0,0 +1,1632 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "CounterStyleManager.h"
+
+#include "mozilla/Types.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/ArrayUtils.h"
+#include "nsString.h"
+#include "nsStyleSet.h"
+#include "nsCSSRules.h"
+#include "nsTArray.h"
+#include "nsBulletFrame.h"
+#include "nsTHashtable.h"
+#include "nsUnicodeProperties.h"
+
+// XXX The following global constants are defined in nsBulletFrame.cpp
+// They are temporarily referenced here. All counter text generating code should
+// be moved here in some later patches.
+extern const char16_t gJapaneseNegative[];
+extern const char16_t gKoreanNegative[];
+extern const char16_t gSimpChineseNegative[];
+extern const char16_t gTradChineseNegative[];
+
+namespace mozilla {
+
+struct AdditiveSymbol
+{
+  CounterValue weight;
+  nsString symbol;
+};
+
+struct NegativeType
+{
+  nsString before, after;
+};
+
+struct PadType
+{
+  int32_t width;
+  nsString symbol;
+};
+
+// This limitation will be applied to some systems, and pad descriptor.
+// Any initial representation generated by symbolic or additive which is
+// longer than this limitation will be dropped. If any pad is longer
+// than this, the whole counter text will be dropped as well.
+// The spec requires user agents to support at least 60 Unicode code-
+// points for counter text. However, this constant only limits the
+// length in 16-bit units. So it has to be at least 120, since code-
+// points outside the BMP will need 2 16-bit units.
+#define LENGTH_LIMIT 150
+
+static bool
+GetCyclicCounterText(CounterValue aOrdinal,
+                     nsSubstring& aResult,
+                     const nsTArray<nsString>& aSymbols)
+{
+  NS_ABORT_IF_FALSE(aSymbols.Length() >= 1,
+                    "No symbol available for cyclic counter.");
+  auto n = aSymbols.Length();
+  CounterValue index = (aOrdinal - 1) % n;
+  aResult = aSymbols[index >= 0 ? index : index + n];
+  return true;
+}
+
+static bool
+GetFixedCounterText(CounterValue aOrdinal,
+                    nsSubstring& aResult,
+                    CounterValue aStart,
+                    const nsTArray<nsString>& aSymbols)
+{
+  CounterValue index = aOrdinal - aStart;
+  if (index >= 0 && index < CounterValue(aSymbols.Length())) {
+    aResult = aSymbols[index];
+    return true;
+  } else {
+    return false;
+  }
+}
+
+static bool
+GetSymbolicCounterText(CounterValue aOrdinal,
+                       nsSubstring& aResult,
+                       const nsTArray<nsString>& aSymbols)
+{
+  NS_ABORT_IF_FALSE(aSymbols.Length() >= 1,
+                    "No symbol available for symbolic counter.");
+  NS_ABORT_IF_FALSE(aOrdinal >= 0, "Invalid ordinal.");
+  if (aOrdinal == 0) {
+    return false;
+  }
+
+  aResult.Truncate();
+  auto n = aSymbols.Length();
+  const nsString& symbol = aSymbols[(aOrdinal - 1) % n];
+  size_t len = (aOrdinal + n - 1) / n;
+  auto symbolLength = symbol.Length();
+  if (symbolLength > 0) {
+    if (len > LENGTH_LIMIT || symbolLength > LENGTH_LIMIT ||
+        len * symbolLength > LENGTH_LIMIT) {
+      return false;
+    }
+    for (size_t i = 0; i < len; ++i) {
+      aResult.Append(symbol);
+    }
+  }
+  return true;
+}
+
+static bool
+GetAlphabeticCounterText(CounterValue aOrdinal,
+                         nsSubstring& aResult,
+                         const nsTArray<nsString>& aSymbols)
+{
+  NS_ABORT_IF_FALSE(aSymbols.Length() >= 2,
+                    "Too few symbols for alphabetic counter.");
+  NS_ABORT_IF_FALSE(aOrdinal >= 0, "Invalid ordinal.");
+  if (aOrdinal == 0) {
+    return false;
+  }
+
+  auto n = aSymbols.Length();
+  // The precise length of this array should be
+  // ceil(log((double) aOrdinal / n * (n - 1) + 1) / log(n)).
+  // The max length is slightly smaller than which defined below.
+  nsAutoTArray<int32_t, std::numeric_limits<CounterValue>::digits> indexes;
+  while (aOrdinal > 0) {
+    --aOrdinal;
+    indexes.AppendElement(aOrdinal % n);
+    aOrdinal /= n;
+  }
+
+  aResult.Truncate();
+  for (auto i = indexes.Length(); i > 0; --i) {
+    aResult.Append(aSymbols[indexes[i - 1]]);
+  }
+  return true;
+}
+
+static bool
+GetNumericCounterText(CounterValue aOrdinal,
+                      nsSubstring& aResult,
+                      const nsTArray<nsString>& aSymbols)
+{
+  NS_ABORT_IF_FALSE(aSymbols.Length() >= 2,
+                    "Too few symbols for numeric counter.");
+  NS_ABORT_IF_FALSE(aOrdinal >= 0, "Invalid ordinal.");
+
+  if (aOrdinal == 0) {
+    aResult = aSymbols[0];
+    return true;
+  }
+
+  auto n = aSymbols.Length();
+  nsAutoTArray<int32_t, std::numeric_limits<CounterValue>::digits> indexes;
+  while (aOrdinal > 0) {
+    indexes.AppendElement(aOrdinal % n);
+    aOrdinal /= n;
+  }
+
+  aResult.Truncate();
+  for (auto i = indexes.Length(); i > 0; --i) {
+    aResult.Append(aSymbols[indexes[i - 1]]);
+  }
+  return true;
+}
+
+static bool
+GetAdditiveCounterText(CounterValue aOrdinal,
+                       nsSubstring& aResult,
+                       const nsTArray<AdditiveSymbol>& aSymbols)
+{
+  NS_ABORT_IF_FALSE(aOrdinal >= 0, "Invalid ordinal.");
+
+  if (aOrdinal == 0) {
+    const AdditiveSymbol& last = aSymbols.LastElement();
+    if (last.weight == 0) {
+      aResult = last.symbol;
+      return true;
+    }
+    return false;
+  }
+
+  aResult.Truncate();
+  size_t length = 0;
+  for (size_t i = 0, iEnd = aSymbols.Length(); i < iEnd; ++i) {
+    const AdditiveSymbol& symbol = aSymbols[i];
+    if (symbol.weight == 0) {
+      break;
+    }
+    CounterValue times = aOrdinal / symbol.weight;
+    if (times > 0) {
+      auto symbolLength = symbol.symbol.Length();
+      if (symbolLength > 0) {
+        length += times * symbolLength;
+        if (times > LENGTH_LIMIT ||
+            symbolLength > LENGTH_LIMIT ||
+            length > LENGTH_LIMIT) {
+          return false;
+        }
+        for (CounterValue j = 0; j < times; ++j) {
+          aResult.Append(symbol.symbol);
+        }
+      }
+      aOrdinal -= times * symbol.weight;
+    }
+  }
+  return aOrdinal == 0;
+}
+
+class BuiltinCounterStyle : public CounterStyle
+{
+public:
+  friend class CounterStyleManager;
+
+  // will be initialized by CounterStyleManager::InitializeBuiltinCounterStyles
+  MOZ_CONSTEXPR BuiltinCounterStyle()
+    : CounterStyle(NS_STYLE_LIST_STYLE_NONE)
+  {
+  }
+
+protected:
+  MOZ_CONSTEXPR BuiltinCounterStyle(int32_t aStyle)
+    : CounterStyle(aStyle)
+  {
+  }
+
+public:
+  virtual void GetPrefix(nsSubstring& aResult) MOZ_OVERRIDE;
+  virtual void GetSuffix(nsSubstring& aResult) MOZ_OVERRIDE;
+  virtual void GetSpokenCounterText(CounterValue aOrdinal,
+                                    nsSubstring& aResult,
+                                    bool& aIsBullet) MOZ_OVERRIDE;
+  virtual bool IsBullet() MOZ_OVERRIDE;
+
+  virtual void GetNegative(NegativeType& aResult) MOZ_OVERRIDE;
+  virtual bool IsOrdinalInRange(CounterValue aOrdinal) MOZ_OVERRIDE;
+  virtual bool IsOrdinalInAutoRange(CounterValue aOrdinal) MOZ_OVERRIDE;
+  virtual void GetPad(PadType& aResult) MOZ_OVERRIDE;
+  virtual CounterStyle* GetFallback() MOZ_OVERRIDE;
+  virtual uint8_t GetSpeakAs() MOZ_OVERRIDE;
+  virtual bool UseNegativeSign() MOZ_OVERRIDE;
+
+  virtual void CallFallbackStyle(CounterValue aOrdinal,
+                                 nsSubstring& aResult,
+                                 bool& aIsRTL) MOZ_OVERRIDE;
+  virtual bool GetInitialCounterText(CounterValue aOrdinal,
+                                     nsSubstring& aResult,
+                                     bool& aIsRTL) MOZ_OVERRIDE;
+
+  // Builtin counter style does not need refcount at all
+  NS_IMETHOD_(MozExternalRefCountType) AddRef() MOZ_OVERRIDE { return 2; }
+  NS_IMETHOD_(MozExternalRefCountType) Release() MOZ_OVERRIDE { return 2; }
+};
+
+/* virtual */ void
+BuiltinCounterStyle::GetPrefix(nsSubstring& aResult)
+{
+  aResult.Truncate();
+}
+
+/* virtual */ void
+BuiltinCounterStyle::GetSuffix(nsSubstring& aResult)
+{
+  // XXX nsBulletFrame::GetListItemSuffix accepts only nsString&, so we reassign
+  // the result here. This could be avoided when the whole code is moved here.
+  nsAutoString result;
+  nsBulletFrame::GetListItemSuffix(mStyle, result);
+  aResult = result;
+}
+
+// XXX stolen from nsBlockFrame.cpp
+static const char16_t kDiscCharacter = 0x2022;
+static const char16_t kCircleCharacter = 0x25e6;
+static const char16_t kSquareCharacter = 0x25aa;
+
+/* virtual */ void
+BuiltinCounterStyle::GetSpokenCounterText(CounterValue aOrdinal,
+                                          nsSubstring& aResult,
+                                          bool& aIsBullet)
+{
+  switch (mStyle) {
+    case NS_STYLE_LIST_STYLE_NONE:
+      aResult.Truncate();
+      aIsBullet = true;
+      break;
+    case NS_STYLE_LIST_STYLE_DISC:
+      aResult.Assign(kDiscCharacter);
+      aIsBullet = true;
+      break;
+    case NS_STYLE_LIST_STYLE_CIRCLE:
+      aResult.Assign(kCircleCharacter);
+      aIsBullet = true;
+      break;
+    case NS_STYLE_LIST_STYLE_SQUARE:
+      aResult.Assign(kSquareCharacter);
+      aIsBullet = true;
+      break;
+    default:
+      CounterStyle::GetSpokenCounterText(aOrdinal, aResult, aIsBullet);
+      aIsBullet = false;
+      break;
+  }
+}
+
+/* virtual */ bool
+BuiltinCounterStyle::IsBullet()
+{
+  switch (mStyle) {
+    case NS_STYLE_LIST_STYLE_DISC:
+    case NS_STYLE_LIST_STYLE_CIRCLE:
+    case NS_STYLE_LIST_STYLE_SQUARE:
+      return true;
+    default:
+      return false;
+  }
+}
+
+/* virtual */ void
+BuiltinCounterStyle::GetNegative(NegativeType& aResult)
+{
+  switch (mStyle) {
+    case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL:
+    case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL:
+    case NS_STYLE_LIST_STYLE_MOZ_JAPANESE_FORMAL:
+    case NS_STYLE_LIST_STYLE_MOZ_JAPANESE_INFORMAL:
+      aResult.before = gJapaneseNegative;
+      break;
+
+    case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL:
+    case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL:
+    case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL:
+      aResult.before = gKoreanNegative;
+      break;
+
+    case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL:
+    case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL:
+    case NS_STYLE_LIST_STYLE_MOZ_SIMP_CHINESE_FORMAL:
+    case NS_STYLE_LIST_STYLE_MOZ_SIMP_CHINESE_INFORMAL:
+      aResult.before = gSimpChineseNegative;
+      break;
+
+    case NS_STYLE_LIST_STYLE_CJK_IDEOGRAPHIC:
+    case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL:
+    case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL:
+    case NS_STYLE_LIST_STYLE_MOZ_TRAD_CHINESE_FORMAL:
+    case NS_STYLE_LIST_STYLE_MOZ_TRAD_CHINESE_INFORMAL:
+      aResult.before = gTradChineseNegative;
+      break;
+
+    default:
+      aResult.before.AssignLiteral(MOZ_UTF16("-"));
+  }
+  aResult.after.Truncate();
+}
+
+/* virtual */ bool
+BuiltinCounterStyle::IsOrdinalInRange(CounterValue aOrdinal)
+{
+  switch (mStyle) {
+    default:
+    // cyclic
+    case NS_STYLE_LIST_STYLE_NONE:
+    case NS_STYLE_LIST_STYLE_DISC:
+    case NS_STYLE_LIST_STYLE_CIRCLE:
+    case NS_STYLE_LIST_STYLE_SQUARE:
+    // use DecimalToText
+    case NS_STYLE_LIST_STYLE_DECIMAL:
+    case NS_STYLE_LIST_STYLE_DECIMAL_LEADING_ZERO:
+    // use CJKIdeographicToText
+    case NS_STYLE_LIST_STYLE_CJK_IDEOGRAPHIC:
+    case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL:
+    case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL:
+    case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL:
+    case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL:
+    case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL:
+    case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL:
+    case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL:
+    case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL:
+    case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL:
+    case NS_STYLE_LIST_STYLE_MOZ_JAPANESE_FORMAL:
+    case NS_STYLE_LIST_STYLE_MOZ_JAPANESE_INFORMAL:
+    case NS_STYLE_LIST_STYLE_MOZ_TRAD_CHINESE_FORMAL:
+    case NS_STYLE_LIST_STYLE_MOZ_TRAD_CHINESE_INFORMAL:
+    case NS_STYLE_LIST_STYLE_MOZ_SIMP_CHINESE_FORMAL:
+    case NS_STYLE_LIST_STYLE_MOZ_SIMP_CHINESE_INFORMAL:
+    // use OtherDecimalToText
+    case NS_STYLE_LIST_STYLE_MOZ_ARABIC_INDIC:
+    case NS_STYLE_LIST_STYLE_MOZ_PERSIAN:
+    case NS_STYLE_LIST_STYLE_MOZ_URDU:
+    case NS_STYLE_LIST_STYLE_MOZ_DEVANAGARI:
+    case NS_STYLE_LIST_STYLE_MOZ_GURMUKHI:
+    case NS_STYLE_LIST_STYLE_MOZ_GUJARATI:
+    case NS_STYLE_LIST_STYLE_MOZ_ORIYA:
+    case NS_STYLE_LIST_STYLE_MOZ_KANNADA:
+    case NS_STYLE_LIST_STYLE_MOZ_MALAYALAM:
+    case NS_STYLE_LIST_STYLE_MOZ_THAI:
+    case NS_STYLE_LIST_STYLE_MOZ_LAO:
+    case NS_STYLE_LIST_STYLE_MOZ_MYANMAR:
+    case NS_STYLE_LIST_STYLE_MOZ_KHMER:
+    case NS_STYLE_LIST_STYLE_MOZ_BENGALI:
+    case NS_STYLE_LIST_STYLE_MOZ_TELUGU:
+      return true;
+
+    // use CharListDecimalToText
+    case NS_STYLE_LIST_STYLE_CJK_DECIMAL:
+      return aOrdinal >= 0;
+
+    // use RomanToText
+    case NS_STYLE_LIST_STYLE_LOWER_ROMAN:
+    case NS_STYLE_LIST_STYLE_UPPER_ROMAN:
+      return aOrdinal >= 1 && aOrdinal <= 3999;
+
+    // use CharListToText
+    case NS_STYLE_LIST_STYLE_LOWER_ALPHA:
+    case NS_STYLE_LIST_STYLE_UPPER_ALPHA:
+    case NS_STYLE_LIST_STYLE_KATAKANA:
+    case NS_STYLE_LIST_STYLE_HIRAGANA:
+    case NS_STYLE_LIST_STYLE_KATAKANA_IROHA:
+    case NS_STYLE_LIST_STYLE_HIRAGANA_IROHA:
+    case NS_STYLE_LIST_STYLE_LOWER_GREEK:
+    case NS_STYLE_LIST_STYLE_MOZ_CJK_HEAVENLY_STEM:
+    case NS_STYLE_LIST_STYLE_MOZ_CJK_EARTHLY_BRANCH:
+    case NS_STYLE_LIST_STYLE_MOZ_HANGUL:
+    case NS_STYLE_LIST_STYLE_MOZ_HANGUL_CONSONANT:
+    case NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_HALEHAME:
+    case NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_HALEHAME_AM:
+    case NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_HALEHAME_TI_ER:
+    case NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_HALEHAME_TI_ET:
+    // use EthiopicToText
+    case NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_NUMERIC:
+      return aOrdinal >= 1;
+
+    // use HebrewToText
+    case NS_STYLE_LIST_STYLE_HEBREW:
+      return aOrdinal >= 1 && aOrdinal <= 999999;
+
+    // use ArmenianToText
+    case NS_STYLE_LIST_STYLE_ARMENIAN:
+    // use TamilToText
+    case NS_STYLE_LIST_STYLE_MOZ_TAMIL:
+      return aOrdinal >= 1 && aOrdinal <= 9999;
+
+    // use GeorgianToText
+    case NS_STYLE_LIST_STYLE_GEORGIAN:
+      return aOrdinal >= 1 && aOrdinal <= 19999;
+  }
+}
+
+/* virtual */ bool
+BuiltinCounterStyle::IsOrdinalInAutoRange(CounterValue aOrdinal)
+{
+  switch (mStyle) {
+    // cyclic:
+    case NS_STYLE_LIST_STYLE_NONE:
+    case NS_STYLE_LIST_STYLE_DISC:
+    case NS_STYLE_LIST_STYLE_CIRCLE:
+    case NS_STYLE_LIST_STYLE_SQUARE:
+    // numeric:
+    case NS_STYLE_LIST_STYLE_DECIMAL:
+    case NS_STYLE_LIST_STYLE_DECIMAL_LEADING_ZERO:
+    case NS_STYLE_LIST_STYLE_CJK_DECIMAL:
+    case NS_STYLE_LIST_STYLE_MOZ_ARABIC_INDIC:
+    case NS_STYLE_LIST_STYLE_MOZ_PERSIAN:
+    case NS_STYLE_LIST_STYLE_MOZ_URDU:
+    case NS_STYLE_LIST_STYLE_MOZ_DEVANAGARI:
+    case NS_STYLE_LIST_STYLE_MOZ_GURMUKHI:
+    case NS_STYLE_LIST_STYLE_MOZ_GUJARATI:
+    case NS_STYLE_LIST_STYLE_MOZ_ORIYA:
+    case NS_STYLE_LIST_STYLE_MOZ_KANNADA:
+    case NS_STYLE_LIST_STYLE_MOZ_MALAYALAM:
+    case NS_STYLE_LIST_STYLE_MOZ_THAI:
+    case NS_STYLE_LIST_STYLE_MOZ_LAO:
+    case NS_STYLE_LIST_STYLE_MOZ_MYANMAR:
+    case NS_STYLE_LIST_STYLE_MOZ_KHMER:
+    case NS_STYLE_LIST_STYLE_MOZ_BENGALI:
+    case NS_STYLE_LIST_STYLE_MOZ_TELUGU:
+    // fixed: no predefined counter style
+      return true;
+
+    // alphabetic:
+    case NS_STYLE_LIST_STYLE_LOWER_ALPHA:
+    case NS_STYLE_LIST_STYLE_UPPER_ALPHA:
+    case NS_STYLE_LIST_STYLE_LOWER_GREEK:
+    case NS_STYLE_LIST_STYLE_KATAKANA:
+    case NS_STYLE_LIST_STYLE_HIRAGANA:
+    case NS_STYLE_LIST_STYLE_KATAKANA_IROHA:
+    case NS_STYLE_LIST_STYLE_HIRAGANA_IROHA:
+    case NS_STYLE_LIST_STYLE_MOZ_CJK_HEAVENLY_STEM:
+    case NS_STYLE_LIST_STYLE_MOZ_CJK_EARTHLY_BRANCH:
+    case NS_STYLE_LIST_STYLE_MOZ_HANGUL:
+    case NS_STYLE_LIST_STYLE_MOZ_HANGUL_CONSONANT:
+    case NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_HALEHAME:
+    case NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_HALEHAME_AM:
+    case NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_HALEHAME_TI_ER:
+    case NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_HALEHAME_TI_ET:
+    // symbolic: no predefined counter style
+      return aOrdinal >= 1;
+
+    // additive:
+    case NS_STYLE_LIST_STYLE_LOWER_ROMAN:
+    case NS_STYLE_LIST_STYLE_UPPER_ROMAN:
+    case NS_STYLE_LIST_STYLE_ARMENIAN:
+    case NS_STYLE_LIST_STYLE_GEORGIAN:
+    case NS_STYLE_LIST_STYLE_HEBREW:
+      return aOrdinal >= 0;
+
+    // complex predefined:
+    case NS_STYLE_LIST_STYLE_CJK_IDEOGRAPHIC:
+    case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL:
+    case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL:
+    case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL:
+    case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL:
+    case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL:
+    case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL:
+    case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL:
+    case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL:
+    case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL:
+    case NS_STYLE_LIST_STYLE_MOZ_JAPANESE_FORMAL:
+    case NS_STYLE_LIST_STYLE_MOZ_JAPANESE_INFORMAL:
+    case NS_STYLE_LIST_STYLE_MOZ_TRAD_CHINESE_FORMAL:
+    case NS_STYLE_LIST_STYLE_MOZ_TRAD_CHINESE_INFORMAL:
+    case NS_STYLE_LIST_STYLE_MOZ_SIMP_CHINESE_FORMAL:
+    case NS_STYLE_LIST_STYLE_MOZ_SIMP_CHINESE_INFORMAL:
+    case NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_NUMERIC:
+    case NS_STYLE_LIST_STYLE_MOZ_TAMIL:
+      return IsOrdinalInRange(aOrdinal);
+
+    default:
+      NS_NOTREACHED("Unknown counter style");
+      return false;
+  }
+}
+
+/* virtual */ void
+BuiltinCounterStyle::GetPad(PadType& aResult)
+{
+  switch (mStyle) {
+    case NS_STYLE_LIST_STYLE_DECIMAL_LEADING_ZERO:
+      aResult.width = 2;
+      aResult.symbol.AssignLiteral(MOZ_UTF16("0"));
+      break;
+    default:
+      aResult.width = 0;
+      aResult.symbol.Truncate();
+      break;
+  }
+}
+
+/* virtual */ CounterStyle*
+BuiltinCounterStyle::GetFallback()
+{
+  // Fallback of dependent builtin counter styles are handled in class
+  // DependentBuiltinCounterStyle.
+  return CounterStyleManager::GetDecimalStyle();
+}
+
+/* virtual */ uint8_t
+BuiltinCounterStyle::GetSpeakAs()
+{
+  switch (mStyle) {
+    case NS_STYLE_LIST_STYLE_NONE:
+    case NS_STYLE_LIST_STYLE_DISC:
+    case NS_STYLE_LIST_STYLE_CIRCLE:
+    case NS_STYLE_LIST_STYLE_SQUARE:
+      return NS_STYLE_COUNTER_SPEAKAS_BULLETS;
+    case NS_STYLE_LIST_STYLE_LOWER_ALPHA:
+    case NS_STYLE_LIST_STYLE_UPPER_ALPHA:
+    case NS_STYLE_LIST_STYLE_KATAKANA:
+    case NS_STYLE_LIST_STYLE_HIRAGANA:
+    case NS_STYLE_LIST_STYLE_KATAKANA_IROHA:
+    case NS_STYLE_LIST_STYLE_HIRAGANA_IROHA:
+    case NS_STYLE_LIST_STYLE_LOWER_GREEK:
+      return NS_STYLE_COUNTER_SPEAKAS_SPELL_OUT;
+    default:
+      return NS_STYLE_COUNTER_SPEAKAS_NUMBERS;
+  }
+}
+
+/* virtual */ bool
+BuiltinCounterStyle::UseNegativeSign()
+{
+  switch (mStyle) {
+    case NS_STYLE_LIST_STYLE_NONE:
+    case NS_STYLE_LIST_STYLE_DISC:
+    case NS_STYLE_LIST_STYLE_CIRCLE:
+    case NS_STYLE_LIST_STYLE_SQUARE:
+      return false;
+    default:
+      return true;
+  }
+}
+
+/* virtual */ void
+BuiltinCounterStyle::CallFallbackStyle(CounterValue aOrdinal,
+                                       nsSubstring& aResult,
+                                       bool& aIsRTL)
+{
+  GetFallback()->GetCounterText(aOrdinal, aResult, aIsRTL);
+}
+
+/* virtual */ bool
+BuiltinCounterStyle::GetInitialCounterText(CounterValue aOrdinal,
+                                           nsSubstring& aResult,
+                                           bool& aIsRTL)
+{
+  // Since nsBulletFrame::AppendCounterText use nsString& as parameter, we
+  // have to reassign it.
+  nsAutoString result;
+  nsBulletFrame::AppendCounterText(mStyle, aOrdinal, result, aIsRTL);
+  aResult = result;
+  // Out-of-range ordinals should have been filtered out before invoking this
+  // function.  Since all builtin styles are defined in a continuous range,
+  // no extendable fallback should have happend inside the function above,
+  // consequently this method will always succeed.
+  return true;
+}
+
+class DependentBuiltinCounterStyle MOZ_FINAL : public BuiltinCounterStyle
+{
+public:
+  DependentBuiltinCounterStyle(int32_t aStyle, CounterStyleManager* aManager)
+    : BuiltinCounterStyle(aStyle), 
+      mManager(aManager)
+  {
+    NS_ASSERTION(IsDependentStyle(), "Not a dependent builtin style");
+    NS_ABORT_IF_FALSE(!IsCustomStyle(), "Not a builtin style");
+  }
+
+  virtual CounterStyle* GetFallback() MOZ_OVERRIDE;
+
+  // DependentBuiltinCounterStyle is managed in the same way as
+  // CustomCounterStyle.
+  NS_INLINE_DECL_REFCOUNTING(DependentBuiltinCounterStyle)
+
+private:
+  CounterStyleManager* mManager;
+};
+
+/* 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:
+    case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL:
+    case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL:
+    case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL:
+    case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL:
+    case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL:
+      // These styles all have a larger range than cjk-decimal, so the
+      // only case fallback is accessed is that they are extended.
+      // Since extending styles will cache the data themselves, we need
+      // not cache it here.
+      return mManager->BuildCounterStyle(NS_LITERAL_STRING("cjk-decimal"));
+    default:
+      NS_NOTREACHED("Not a valid dependent builtin style");
+      return BuiltinCounterStyle::GetFallback();
+  }
+}
+
+class CustomCounterStyle MOZ_FINAL : public CounterStyle
+{
+public:
+  CustomCounterStyle(CounterStyleManager* aManager,
+                     nsCSSCounterStyleRule* aRule)
+    : CounterStyle(NS_STYLE_LIST_STYLE_CUSTOM),
+      mManager(aManager),
+      mRule(aRule),
+      mRuleGeneration(aRule->GetGeneration()),
+      mSystem(aRule->GetSystem()),
+      mFlags(0),
+      mFallback(nullptr),
+      mSpeakAsCounter(nullptr),
+      mExtends(nullptr),
+      mExtendsRoot(nullptr)
+  {
+  }
+
+  // This method will clear all cached data in the style and update the
+  // generation number of the rule. It should be called when the rule of
+  // this style is changed.
+  void ResetCachedData();
+
+  // This method will reset all cached data which may depend on other
+  // counter style. It will reset all pointers to other counter styles.
+  // For counter style extends other, in addition, all fields will be
+  // reset to uninitialized state. This method should be called when any
+  // other counter style is added, removed, or changed.
+  void ResetDependentData();
+
+  nsCSSCounterStyleRule* GetRule() const { return mRule; }
+  uint32_t GetRuleGeneration() const { return mRuleGeneration; }
+
+  virtual void GetPrefix(nsSubstring& aResult) MOZ_OVERRIDE;
+  virtual void GetSuffix(nsSubstring& aResult) MOZ_OVERRIDE;
+  virtual void GetSpokenCounterText(CounterValue aOrdinal,
+                                    nsSubstring& aResult,
+                                    bool& aIsBullet) MOZ_OVERRIDE;
+  virtual bool IsBullet() MOZ_OVERRIDE;
+
+  virtual void GetNegative(NegativeType& aResult) MOZ_OVERRIDE;
+  virtual bool IsOrdinalInRange(CounterValue aOrdinal) MOZ_OVERRIDE;
+  virtual bool IsOrdinalInAutoRange(CounterValue aOrdinal) MOZ_OVERRIDE;
+  virtual void GetPad(PadType& aResult) MOZ_OVERRIDE;
+  virtual CounterStyle* GetFallback() MOZ_OVERRIDE;
+  virtual uint8_t GetSpeakAs() MOZ_OVERRIDE;
+  virtual bool UseNegativeSign() MOZ_OVERRIDE;
+
+  virtual void CallFallbackStyle(CounterValue aOrdinal,
+                                 nsSubstring& aResult,
+                                 bool& aIsRTL) MOZ_OVERRIDE;
+  virtual bool GetInitialCounterText(CounterValue aOrdinal,
+                                     nsSubstring& aResult,
+                                     bool& aIsRTL) MOZ_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_INLINE_DECL_REFCOUNTING(CustomCounterStyle)
+
+private:
+  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
+  // 2. eliminate loop, by ComputeSpeakAs.
+  // See comments before the definitions of these methods for details.
+  uint8_t GetSpeakAsAutoValue();
+  void ComputeRawSpeakAs(uint8_t& aSpeakAs,
+                         CounterStyle*& aSpeakAsCounter);
+  CounterStyle* ComputeSpeakAs();
+
+  CounterStyle* ComputeExtends();
+  CounterStyle* GetExtends();
+  CounterStyle* GetExtendsRoot();
+
+  // CounterStyleManager should always overlive any CounterStyle as it
+  // is owned by nsPresContext, and will be released after all nodes and
+  // frames are released.
+  CounterStyleManager* mManager;
+
+  nsRefPtr<nsCSSCounterStyleRule> mRule;
+  uint32_t mRuleGeneration;
+
+  uint8_t mSystem;
+  uint8_t mSpeakAs;
+
+  enum {
+    // loop detection
+    FLAG_EXTENDS_VISITED = 1 << 0,
+    FLAG_EXTENDS_LOOP    = 1 << 1,
+    FLAG_SPEAKAS_VISITED  = 1 << 2,
+    FLAG_SPEAKAS_LOOP     = 1 << 3,
+    // field status
+    FLAG_NEGATIVE_INITED  = 1 << 4,
+    FLAG_PREFIX_INITED    = 1 << 5,
+    FLAG_SUFFIX_INITED    = 1 << 6,
+    FLAG_PAD_INITED       = 1 << 7,
+    FLAG_SPEAKAS_INITED   = 1 << 8,
+  };
+  uint16_t mFlags;
+
+  // Fields below will be initialized when necessary.
+  nsTArray<nsString> mSymbols;
+  nsTArray<AdditiveSymbol> mAdditiveSymbols;
+  NegativeType mNegative;
+  nsString mPrefix, mSuffix;
+  PadType mPad;
+
+  // CounterStyleManager will guarantee that none of the pointers below
+  // refers to a freed CounterStyle. There are two possible cases where
+  // the manager will release its reference to a CounterStyle: 1. the
+  // manager itself is released, 2. a rule is invalidated. In the first
+  // case, all counter style are removed from the manager, and should
+  // also have been dereferenced from other objects. All styles will be
+  // released all together. In the second case, CounterStyleManager::
+  // NotifyRuleChanged will guarantee that all pointers will be reset
+  // before any CounterStyle is released.
+
+  CounterStyle* mFallback;
+  // This field refers to the last counter in a speak-as chain.
+  // 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;
+};
+
+void
+CustomCounterStyle::ResetCachedData()
+{
+  mSymbols.Clear();
+  mAdditiveSymbols.Clear();
+  mFlags &= ~(FLAG_NEGATIVE_INITED |
+              FLAG_PREFIX_INITED |
+              FLAG_SUFFIX_INITED |
+              FLAG_PAD_INITED |
+              FLAG_SPEAKAS_INITED);
+  mFallback = nullptr;
+  mSpeakAsCounter = nullptr;
+  mExtends = nullptr;
+  mExtendsRoot = nullptr;
+  mRuleGeneration = mRule->GetGeneration();
+}
+
+void
+CustomCounterStyle::ResetDependentData()
+{
+  mFlags &= ~FLAG_SPEAKAS_INITED;
+  mSpeakAsCounter = nullptr;
+  mFallback = nullptr;
+  mExtends = nullptr;
+  mExtendsRoot = nullptr;
+  if (IsExtendsSystem()) {
+    mFlags &= ~(FLAG_NEGATIVE_INITED |
+                FLAG_PREFIX_INITED |
+                FLAG_SUFFIX_INITED |
+                FLAG_PAD_INITED);
+  }
+}
+
+/* virtual */ void
+CustomCounterStyle::GetPrefix(nsSubstring& aResult)
+{
+  if (!(mFlags & FLAG_PREFIX_INITED)) {
+    mFlags |= FLAG_PREFIX_INITED;
+
+    const nsCSSValue& value = mRule->GetDesc(eCSSCounterDesc_Prefix);
+    if (value.UnitHasStringValue()) {
+      value.GetStringValue(mPrefix);
+    } else if (IsExtendsSystem()) {
+      GetExtends()->GetPrefix(mPrefix);
+    } else {
+      mPrefix.Truncate();
+    }
+  }
+  aResult = mPrefix;
+}
+
+/* virtual */ void
+CustomCounterStyle::GetSuffix(nsSubstring& aResult)
+{
+  if (!(mFlags & FLAG_SUFFIX_INITED)) {
+    mFlags |= FLAG_SUFFIX_INITED;
+
+    const nsCSSValue& value = mRule->GetDesc(eCSSCounterDesc_Suffix);
+    if (value.UnitHasStringValue()) {
+      value.GetStringValue(mSuffix);
+    } else if (IsExtendsSystem()) {
+      GetExtends()->GetSuffix(mSuffix);
+    } else {
+      mSuffix.AssignLiteral(MOZ_UTF16(". "));
+    }
+  }
+  aResult = mSuffix;
+}
+
+/* virtual */ void
+CustomCounterStyle::GetSpokenCounterText(CounterValue aOrdinal,
+                                         nsSubstring& aResult,
+                                         bool& aIsBullet)
+{
+  if (GetSpeakAs() != NS_STYLE_COUNTER_SPEAKAS_OTHER) {
+    CounterStyle::GetSpokenCounterText(aOrdinal, aResult, aIsBullet);
+  } else {
+    NS_ABORT_IF_FALSE(mSpeakAsCounter,
+                      "mSpeakAsCounter should have been initialized.");
+    mSpeakAsCounter->GetSpokenCounterText(aOrdinal, aResult, aIsBullet);
+  }
+}
+
+/* virtual */ bool
+CustomCounterStyle::IsBullet()
+{
+  switch (mSystem) {
+    case NS_STYLE_COUNTER_SYSTEM_CYCLIC:
+      // Only use ::-moz-list-bullet for cyclic system
+      return true;
+    case NS_STYLE_COUNTER_SYSTEM_EXTENDS:
+      return GetExtendsRoot()->IsBullet();
+    default:
+      return false;
+  }
+}
+
+/* virtual */ void
+CustomCounterStyle::GetNegative(NegativeType& aResult)
+{
+  if (!(mFlags & FLAG_NEGATIVE_INITED)) {
+    mFlags |= FLAG_NEGATIVE_INITED;
+    const nsCSSValue& value = mRule->GetDesc(eCSSCounterDesc_Negative);
+    switch (value.GetUnit()) {
+      case eCSSUnit_Ident:
+      case eCSSUnit_String:
+        value.GetStringValue(mNegative.before);
+        mNegative.after.Truncate();
+        break;
+      case eCSSUnit_Pair: {
+        const nsCSSValuePair& pair = value.GetPairValue();
+        pair.mXValue.GetStringValue(mNegative.before);
+        pair.mYValue.GetStringValue(mNegative.after);
+        break;
+      }
+      default: {
+        if (IsExtendsSystem()) {
+          GetExtends()->GetNegative(mNegative);
+        } else {
+          mNegative.before.AssignLiteral(MOZ_UTF16("-"));
+          mNegative.after.Truncate();
+        }
+      }
+    }
+  }
+  aResult = mNegative;
+}
+
+static inline bool
+IsRangeValueInfinite(const nsCSSValue& aValue)
+{
+  return aValue.GetUnit() == eCSSUnit_Enumerated &&
+         aValue.GetIntValue() == NS_STYLE_COUNTER_RANGE_INFINITE;
+}
+
+/* virtual */ bool
+CustomCounterStyle::IsOrdinalInRange(CounterValue aOrdinal)
+{
+  const nsCSSValue& value = mRule->GetDesc(eCSSCounterDesc_Range);
+  if (value.GetUnit() == eCSSUnit_PairList) {
+    for (const nsCSSValuePairList* item = value.GetPairListValue();
+         item != nullptr; item = item->mNext) {
+      const nsCSSValue& lowerBound = item->mXValue;
+      const nsCSSValue& upperBound = item->mYValue;
+      if ((IsRangeValueInfinite(lowerBound) ||
+           aOrdinal >= lowerBound.GetIntValue()) &&
+          (IsRangeValueInfinite(upperBound) ||
+           aOrdinal <= upperBound.GetIntValue())) {
+        return true;
+      }
+    }
+    return false;
+  } else if (IsExtendsSystem() && value.GetUnit() == eCSSUnit_None) {
+    // Only use the range of extended style when 'range' is not specified.
+    return GetExtends()->IsOrdinalInRange(aOrdinal);
+  }
+  return IsOrdinalInAutoRange(aOrdinal);
+}
+
+/* virtual */ bool
+CustomCounterStyle::IsOrdinalInAutoRange(CounterValue aOrdinal)
+{
+  switch (mSystem) {
+    case NS_STYLE_COUNTER_SYSTEM_CYCLIC:
+    case NS_STYLE_COUNTER_SYSTEM_NUMERIC:
+    case NS_STYLE_COUNTER_SYSTEM_FIXED:
+      return true;
+    case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC:
+    case NS_STYLE_COUNTER_SYSTEM_SYMBOLIC:
+      return aOrdinal >= 1;
+    case NS_STYLE_COUNTER_SYSTEM_ADDITIVE:
+      return aOrdinal >= 0;
+    case NS_STYLE_COUNTER_SYSTEM_EXTENDS:
+      return GetExtendsRoot()->IsOrdinalInAutoRange(aOrdinal);
+    default:
+      NS_NOTREACHED("Invalid system for computing auto value.");
+      return false;
+  }
+}
+
+/* virtual */ void
+CustomCounterStyle::GetPad(PadType& aResult)
+{
+  if (!(mFlags & FLAG_PAD_INITED)) {
+    mFlags |= FLAG_PAD_INITED;
+    const nsCSSValue& value = mRule->GetDesc(eCSSCounterDesc_Pad);
+    if (value.GetUnit() == eCSSUnit_Pair) {
+      const nsCSSValuePair& pair = value.GetPairValue();
+      mPad.width = pair.mXValue.GetIntValue();
+      pair.mYValue.GetStringValue(mPad.symbol);
+    } else if (IsExtendsSystem()) {
+      GetExtends()->GetPad(mPad);
+    } else {
+      mPad.width = 0;
+      mPad.symbol.Truncate();
+    }
+  }
+  aResult = mPad;
+}
+
+/* virtual */ CounterStyle*
+CustomCounterStyle::GetFallback()
+{
+  if (!mFallback) {
+    const nsCSSValue& value = mRule->GetDesc(eCSSCounterDesc_Fallback);
+    if (value.UnitHasStringValue()) {
+      mFallback = mManager->BuildCounterStyle(
+          nsDependentString(value.GetStringBufferValue()));
+    } else if (IsExtendsSystem()) {
+      mFallback = GetExtends()->GetFallback();
+    } else {
+      mFallback = CounterStyleManager::GetDecimalStyle();
+    }
+  }
+  return mFallback;
+}
+
+/* virtual */ uint8_t
+CustomCounterStyle::GetSpeakAs()
+{
+  if (!(mFlags & FLAG_SPEAKAS_INITED)) {
+    ComputeSpeakAs();
+  }
+  return mSpeakAs;
+}
+
+/* virtual */ bool
+CustomCounterStyle::UseNegativeSign()
+{
+  switch (mSystem) {
+    case NS_STYLE_COUNTER_SYSTEM_SYMBOLIC:
+    case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC:
+    case NS_STYLE_COUNTER_SYSTEM_NUMERIC:
+    case NS_STYLE_COUNTER_SYSTEM_ADDITIVE:
+      return true;
+    case NS_STYLE_COUNTER_SYSTEM_EXTENDS:
+      return GetExtendsRoot()->UseNegativeSign();
+    default:
+      return false;
+  }
+}
+
+/* virtual */ void
+CustomCounterStyle::CallFallbackStyle(CounterValue aOrdinal,
+                                      nsSubstring& aResult,
+                                      bool& aIsRTL)
+{
+  CounterStyle* fallback = GetFallback();
+  // If it recursively falls back to this counter style again,
+  // it will then fallback to decimal to break the loop.
+  mFallback = CounterStyleManager::GetDecimalStyle();
+  fallback->GetCounterText(aOrdinal, aResult, aIsRTL);
+  mFallback = fallback;
+}
+
+/* virtual */ bool
+CustomCounterStyle::GetInitialCounterText(CounterValue aOrdinal,
+                                          nsSubstring& aResult,
+                                          bool& aIsRTL)
+{
+  switch (mSystem) {
+    case NS_STYLE_COUNTER_SYSTEM_CYCLIC:
+      return GetCyclicCounterText(aOrdinal, aResult, GetSymbols());
+    case NS_STYLE_COUNTER_SYSTEM_FIXED: {
+      int32_t start = mRule->GetSystemArgument().GetIntValue();
+      return GetFixedCounterText(aOrdinal, aResult, start, GetSymbols());
+    }
+    case NS_STYLE_COUNTER_SYSTEM_SYMBOLIC:
+      return GetSymbolicCounterText(aOrdinal, aResult, GetSymbols());
+    case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC:
+      return GetAlphabeticCounterText(aOrdinal, aResult, GetSymbols());
+    case NS_STYLE_COUNTER_SYSTEM_NUMERIC:
+      return GetNumericCounterText(aOrdinal, aResult, GetSymbols());
+    case NS_STYLE_COUNTER_SYSTEM_ADDITIVE:
+      return GetAdditiveCounterText(aOrdinal, aResult, GetAdditiveSymbols());
+    case NS_STYLE_COUNTER_SYSTEM_EXTENDS:
+      return GetExtendsRoot()->
+        GetInitialCounterText(aOrdinal, aResult, aIsRTL);
+    default:
+      NS_NOTREACHED("Invalid system.");
+      return false;
+  }
+}
+
+const nsTArray<nsString>&
+CustomCounterStyle::GetSymbols()
+{
+  if (mSymbols.IsEmpty()) {
+    const nsCSSValue& values = mRule->GetDesc(eCSSCounterDesc_Symbols);
+    for (const nsCSSValueList* item = values.GetListValue();
+         item; item = item->mNext) {
+      nsString* symbol = mSymbols.AppendElement();
+      item->mValue.GetStringValue(*symbol);
+    }
+    mSymbols.Compact();
+  }
+  return mSymbols;
+}
+
+const nsTArray<AdditiveSymbol>&
+CustomCounterStyle::GetAdditiveSymbols()
+{
+  if (mAdditiveSymbols.IsEmpty()) {
+    const nsCSSValue& values = mRule->GetDesc(eCSSCounterDesc_AdditiveSymbols);
+    for (const nsCSSValuePairList* item = values.GetPairListValue();
+         item; item = item->mNext) {
+      AdditiveSymbol* symbol = mAdditiveSymbols.AppendElement();
+      symbol->weight = item->mXValue.GetIntValue();
+      item->mYValue.GetStringValue(symbol->symbol);
+    }
+    mAdditiveSymbols.Compact();
+  }
+  return mAdditiveSymbols;
+}
+
+// This method is used to provide the computed value for 'auto'.
+uint8_t
+CustomCounterStyle::GetSpeakAsAutoValue()
+{
+  uint8_t system = mSystem;
+  if (IsExtendsSystem()) {
+    CounterStyle* root = GetExtendsRoot();
+    if (!root->IsCustomStyle()) {
+      // It is safe to call GetSpeakAs on non-custom style.
+      return root->GetSpeakAs();
+    }
+    system = static_cast<CustomCounterStyle*>(root)->mSystem;
+  }
+  switch (system) {
+    case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC:
+      return NS_STYLE_COUNTER_SPEAKAS_SPELL_OUT;
+    case NS_STYLE_COUNTER_SYSTEM_CYCLIC:
+      return NS_STYLE_COUNTER_SPEAKAS_BULLETS;
+    default:
+      return NS_STYLE_COUNTER_SPEAKAS_NUMBERS;
+  }
+}
+
+// This method corresponds to the first stage of computation of the
+// value of speak-as. It will extract the value from the rule and
+// possibly recursively call itself on the extended style to figure
+// out the raw value. To keep things clear, this method is designed to
+// have no side effects (but functions it calls may still affect other
+// fields in the style.)
+void
+CustomCounterStyle::ComputeRawSpeakAs(uint8_t& aSpeakAs,
+                                    CounterStyle*& aSpeakAsCounter)
+{
+  NS_ASSERTION(!(mFlags & FLAG_SPEAKAS_INITED),
+               "ComputeRawSpeakAs is called with speak-as inited.");
+
+  const nsCSSValue& value = mRule->GetDesc(eCSSCounterDesc_SpeakAs);
+  switch (value.GetUnit()) {
+    case eCSSUnit_Auto:
+      aSpeakAs = GetSpeakAsAutoValue();
+      break;
+    case eCSSUnit_Enumerated:
+      aSpeakAs = value.GetIntValue();
+      break;
+    case eCSSUnit_Ident:
+      aSpeakAs = NS_STYLE_COUNTER_SPEAKAS_OTHER;
+      aSpeakAsCounter = mManager->BuildCounterStyle(
+          nsDependentString(value.GetStringBufferValue()));
+      break;
+    case eCSSUnit_Null: {
+      if (!IsExtendsSystem()) {
+        aSpeakAs = GetSpeakAsAutoValue();
+      } else {
+        CounterStyle* extended = GetExtends();
+        if (!extended->IsCustomStyle()) {
+          // It is safe to call GetSpeakAs on non-custom style.
+          aSpeakAs = extended->GetSpeakAs();
+        } else {
+          CustomCounterStyle* custom =
+            static_cast<CustomCounterStyle*>(extended);
+          if (!(custom->mFlags & FLAG_SPEAKAS_INITED)) {
+            custom->ComputeRawSpeakAs(aSpeakAs, aSpeakAsCounter);
+          } else {
+            aSpeakAs = custom->mSpeakAs;
+            aSpeakAsCounter = custom->mSpeakAsCounter;
+          }
+        }
+      }
+      break;
+    }
+    default:
+      NS_NOTREACHED("Invalid speak-as value");
+  }
+}
+
+// This method corresponds to the second stage of getting speak-as
+// related values. It will recursively figure out the final value of
+// mSpeakAs and mSpeakAsCounter. This method returns nullptr if the
+// caller is in a loop, and the root counter style in the chain
+// otherwise. It use the same loop detection algorithm as
+// CustomCounterStyle::ComputeExtends, see comments before that
+// method for more details.
+CounterStyle*
+CustomCounterStyle::ComputeSpeakAs()
+{
+  if (mFlags & FLAG_SPEAKAS_INITED) {
+    if (mSpeakAs == NS_STYLE_COUNTER_SPEAKAS_OTHER) {
+      return mSpeakAsCounter;
+    }
+    return this;
+  }
+
+  if (mFlags & FLAG_SPEAKAS_VISITED) {
+    // loop detected
+    mFlags |= FLAG_SPEAKAS_LOOP;
+    return nullptr;
+  }
+
+  CounterStyle* speakAsCounter;
+  ComputeRawSpeakAs(mSpeakAs, speakAsCounter);
+
+  bool inLoop = false;
+  if (mSpeakAs != NS_STYLE_COUNTER_SPEAKAS_OTHER) {
+    mSpeakAsCounter = nullptr;
+  } else if (!speakAsCounter->IsCustomStyle()) {
+    mSpeakAsCounter = speakAsCounter;
+  } else {
+    mFlags |= FLAG_SPEAKAS_VISITED;
+    CounterStyle* target =
+      static_cast<CustomCounterStyle*>(speakAsCounter)->ComputeSpeakAs();
+    mFlags &= ~FLAG_SPEAKAS_VISITED;
+
+    if (target) {
+      NS_ASSERTION(!(mFlags & FLAG_SPEAKAS_LOOP),
+                   "Invalid state for speak-as loop detecting");
+      mSpeakAsCounter = target;
+    } else {
+      mSpeakAs = GetSpeakAsAutoValue();
+      mSpeakAsCounter = nullptr;
+      if (mFlags & FLAG_SPEAKAS_LOOP) {
+        mFlags &= ~FLAG_SPEAKAS_LOOP;
+      } else {
+        inLoop = true;
+      }
+    }
+  }
+
+  mFlags |= FLAG_SPEAKAS_INITED;
+  if (inLoop) {
+    return nullptr;
+  }
+  return mSpeakAsCounter ? mSpeakAsCounter : this;
+}
+
+// This method will recursively figure out mExtends in the whole chain.
+// It will return nullptr if the caller is in a loop, and return this
+// otherwise. To detect the loop, this method marks the style VISITED
+// before the recursive call. When a VISITED style is reached again, the
+// loop is detected, and flag LOOP will be marked on the first style in
+// loop. mExtends of all counter styles in loop will be set to decimal
+// according to the spec.
+CounterStyle*
+CustomCounterStyle::ComputeExtends()
+{
+  if (!IsExtendsSystem() || mExtends) {
+    return this;
+  }
+  if (mFlags & FLAG_EXTENDS_VISITED) {
+    // loop detected
+    mFlags |= FLAG_EXTENDS_LOOP;
+    return nullptr;
+  }
+
+  const nsCSSValue& value = mRule->GetSystemArgument();
+  CounterStyle* nextCounter = mManager->BuildCounterStyle(
+      nsDependentString(value.GetStringBufferValue()));
+  CounterStyle* target = nextCounter;
+  if (nextCounter->IsCustomStyle()) {
+    mFlags |= FLAG_EXTENDS_VISITED;
+    target = static_cast<CustomCounterStyle*>(nextCounter)->ComputeExtends();
+    mFlags &= ~FLAG_EXTENDS_VISITED;
+  }
+
+  if (target) {
+    NS_ASSERTION(!(mFlags & FLAG_EXTENDS_LOOP),
+                 "Invalid state for extends loop detecting");
+    mExtends = nextCounter;
+    return this;
+  } else {
+    mExtends = CounterStyleManager::GetDecimalStyle();
+    if (mFlags & FLAG_EXTENDS_LOOP) {
+      mFlags &= ~FLAG_EXTENDS_LOOP;
+      return this;
+    } else {
+      return nullptr;
+    }
+  }
+}
+
+CounterStyle*
+CustomCounterStyle::GetExtends()
+{
+  if (!mExtends) {
+    // Any extends loop will be eliminated in the method below.
+    ComputeExtends();
+  }
+  return mExtends;
+}
+
+CounterStyle*
+CustomCounterStyle::GetExtendsRoot()
+{
+  if (!mExtendsRoot) {
+    CounterStyle* extended = GetExtends();
+    mExtendsRoot = extended;
+    if (extended->IsCustomStyle()) {
+      CustomCounterStyle* custom = static_cast<CustomCounterStyle*>(extended);
+      if (custom->IsExtendsSystem()) {
+        // This will make mExtendsRoot in the whole extends chain be
+        // set recursively, which could save work when part of a chain
+        // is shared by multiple counter styles.
+        mExtendsRoot = custom->GetExtendsRoot();
+      }
+    }
+  }
+  return mExtendsRoot;
+}
+
+bool
+CounterStyle::IsDependentStyle() const
+{
+  switch (mStyle) {
+    // CustomCounterStyle
+    case NS_STYLE_LIST_STYLE_CUSTOM:
+    // DependentBuiltinCounterStyle
+    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:
+    case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL:
+    case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL:
+    case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL:
+    case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL:
+    case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL:
+      return true;
+
+    // BuiltinCounterStyle
+    default:
+      return false;
+  }
+}
+
+static int32_t
+CountGraphemeClusters(const nsSubstring& aText)
+{
+  using mozilla::unicode::ClusterIterator;
+  ClusterIterator iter(aText.Data(), aText.Length());
+  int32_t result = 0;
+  while (!iter.AtEnd()) {
+    ++result;
+    iter.Next();
+  }
+  return result;
+}
+
+void
+CounterStyle::GetCounterText(CounterValue aOrdinal,
+                             nsSubstring& aResult,
+                             bool& aIsRTL)
+{
+  bool success = IsOrdinalInRange(aOrdinal);
+  aIsRTL = false;
+
+  if (success) {
+    // generate initial representation
+    bool useNegativeSign = UseNegativeSign();
+    nsAutoString initialText;
+    CounterValue ordinal;
+    if (!useNegativeSign) {
+      ordinal = aOrdinal;
+    } else {
+      CheckedInt<CounterValue> absolute(Abs(aOrdinal));
+      ordinal = absolute.isValid() ?
+        absolute.value() : std::numeric_limits<CounterValue>::max();
+    }
+    success = GetInitialCounterText(ordinal, initialText, aIsRTL);
+
+    // add pad & negative, build the final result
+    if (success) {
+      PadType pad;
+      GetPad(pad);
+      // We have to calculate the difference here since suffix part of negative
+      // sign may be appended to initialText later.
+      int32_t diff = pad.width - CountGraphemeClusters(initialText);
+      aResult.Truncate();
+      if (useNegativeSign && aOrdinal < 0) {
+        NegativeType negative;
+        GetNegative(negative);
+        aResult.Append(negative.before);
+        // There is nothing between the suffix part of negative and initial
+        // representation, so we append it directly here.
+        initialText.Append(negative.after);
+      }
+      if (diff > 0) {
+        auto length = pad.symbol.Length();
+        if (diff > LENGTH_LIMIT || length > LENGTH_LIMIT ||
+            diff * length > LENGTH_LIMIT) {
+          success = false;
+        } else if (length > 0) {
+          for (int32_t i = 0; i < diff; ++i) {
+            aResult.Append(pad.symbol);
+          }
+        }
+      }
+      if (success) {
+        aResult.Append(initialText);
+      }
+    }
+  }
+
+  if (!success) {
+    CallFallbackStyle(aOrdinal, aResult, aIsRTL);
+  }
+}
+
+/* virtual */ void
+CounterStyle::GetSpokenCounterText(CounterValue aOrdinal,
+                                   nsSubstring& aResult,
+                                   bool& aIsBullet)
+{
+  bool isRTL; // we don't care about direction for spoken text
+  aIsBullet = false;
+  switch (GetSpeakAs()) {
+    case NS_STYLE_COUNTER_SPEAKAS_BULLETS:
+      aResult.Assign(kDiscCharacter);
+      aIsBullet = true;
+      break;
+    case NS_STYLE_COUNTER_SPEAKAS_NUMBERS:
+      // use decimal to generate numeric text
+      // XXX change to DecimalToText after moving it here
+      CounterStyleManager::GetDecimalStyle()->
+        GetCounterText(aOrdinal, aResult, isRTL);
+      break;
+    case NS_STYLE_COUNTER_SPEAKAS_SPELL_OUT:
+      // we currently do not actually support 'spell-out',
+      // so 'words' is used instead.
+    case NS_STYLE_COUNTER_SPEAKAS_WORDS:
+      GetCounterText(aOrdinal, aResult, isRTL);
+      break;
+    case NS_STYLE_COUNTER_SPEAKAS_OTHER:
+      // This should be processed by CustomCounterStyle
+      NS_NOTREACHED("Invalid speak-as value");
+      break;
+    default:
+      NS_NOTREACHED("Unknown speak-as value");
+      break;
+  }
+}
+
+static BuiltinCounterStyle gBuiltinStyleTable[NS_STYLE_LIST_STYLE__MAX];
+
+CounterStyleManager::CounterStyleManager(nsPresContext* aPresContext)
+  : mPresContext(aPresContext)
+{
+  // Insert the static styles into cache table
+  mCacheTable.Put(NS_LITERAL_STRING("none"), GetNoneStyle());
+  mCacheTable.Put(NS_LITERAL_STRING("decimal"), GetDecimalStyle());
+}
+
+CounterStyleManager::~CounterStyleManager()
+{
+  NS_ABORT_IF_FALSE(!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;
+  }
+}
+
+#ifdef DEBUG
+static PLDHashOperator
+CheckRefCount(const nsSubstring& aKey,
+              CounterStyle* aStyle,
+              void* aArg)
+{
+  aStyle->AddRef();
+  auto refcnt = aStyle->Release();
+  NS_ASSERTION(!aStyle->IsDependentStyle() || refcnt == 1,
+               "Counter style is still referenced by other objects.");
+  return PL_DHASH_NEXT;
+}
+#endif
+
+void
+CounterStyleManager::Disconnect()
+{
+#ifdef DEBUG
+  mCacheTable.EnumerateRead(CheckRefCount, nullptr);
+#endif
+  mCacheTable.Clear();
+  mPresContext = nullptr;
+}
+
+CounterStyle*
+CounterStyleManager::BuildCounterStyle(const nsSubstring& aName)
+{
+  CounterStyle* data = mCacheTable.GetWeak(aName);
+  if (data) {
+    return data;
+  }
+
+  // It is intentional that the predefined names are case-insensitive
+  // but the user-defined names case-sensitive.
+  nsCSSCounterStyleRule* rule =
+    mPresContext->StyleSet()->CounterStyleRuleForName(mPresContext, aName);
+  if (rule) {
+    data = new CustomCounterStyle(this, rule);
+  } else {
+    int32_t type;
+    nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(aName);
+    if (nsCSSProps::FindKeyword(keyword, nsCSSProps::kListStyleKTable, type)) {
+      if (gBuiltinStyleTable[type].IsDependentStyle()) {
+        data = new DependentBuiltinCounterStyle(type, this);
+      } else {
+        data = GetBuiltinStyle(type);
+      }
+    }
+  }
+  if (!data) {
+    data = GetDecimalStyle();
+  }
+  mCacheTable.Put(aName, data);
+  return data;
+}
+
+/* static */ CounterStyle*
+CounterStyleManager::GetBuiltinStyle(int32_t aStyle)
+{
+  NS_ABORT_IF_FALSE(0 <= aStyle && aStyle < NS_STYLE_LIST_STYLE__MAX,
+                    "Require a valid builtin style constant");
+  NS_ABORT_IF_FALSE(!gBuiltinStyleTable[aStyle].IsDependentStyle(),
+                    "Cannot get dependent builtin style");
+  return &gBuiltinStyleTable[aStyle];
+}
+
+struct InvalidateOldStyleData
+{
+  explicit InvalidateOldStyleData(nsPresContext* aPresContext)
+    : mPresContext(aPresContext),
+      mChanged(false)
+  {
+  }
+
+  nsPresContext* mPresContext;
+  nsTArray<nsRefPtr<CounterStyle>> mToBeRemoved;
+  bool mChanged;
+};
+
+static PLDHashOperator
+InvalidateOldStyle(const nsSubstring& aKey,
+                   nsRefPtr<CounterStyle>& aStyle,
+                   void* aArg)
+{
+  InvalidateOldStyleData* data = static_cast<InvalidateOldStyleData*>(aArg);
+  bool toBeUpdated = false;
+  bool toBeRemoved = false;
+  nsCSSCounterStyleRule* newRule = data->mPresContext->
+    StyleSet()->CounterStyleRuleForName(data->mPresContext, aKey);
+  if (!newRule) {
+    if (aStyle->IsCustomStyle()) {
+      toBeRemoved = true;
+    }
+  } else {
+    if (!aStyle->IsCustomStyle()) {
+      toBeRemoved = true;
+    } else {
+      CustomCounterStyle* style =
+        static_cast<CustomCounterStyle*>(aStyle.get());
+      if (style->GetRule() != newRule) {
+        toBeRemoved = true;
+      } else if (style->GetRuleGeneration() != newRule->GetGeneration()) {
+        toBeUpdated = true;
+        style->ResetCachedData();
+      }
+    }
+  }
+  data->mChanged = data->mChanged || toBeUpdated || toBeRemoved;
+  if (toBeRemoved) {
+    if (aStyle->IsDependentStyle()) {
+      // 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 the MarkAndCleanData goes out of scope at the end
+      // of NotifyRuleChanged().
+      data->mToBeRemoved.AppendElement(aStyle);
+    }
+    return PL_DHASH_REMOVE;
+  }
+  return PL_DHASH_NEXT;
+}
+
+static PLDHashOperator
+InvalidateDependentData(const nsSubstring& aKey,
+                        CounterStyle* aStyle,
+                        void* aArg)
+{
+  if (aStyle->IsCustomStyle()) {
+    CustomCounterStyle* custom = static_cast<CustomCounterStyle*>(aStyle);
+    custom->ResetDependentData();
+  }
+  // There is no dependent data cached in DependentBuiltinCounterStyle
+  // instances, so we don't need to reset their data.
+  return PL_DHASH_NEXT;
+}
+
+bool
+CounterStyleManager::NotifyRuleChanged()
+{
+  InvalidateOldStyleData data(mPresContext);
+  mCacheTable.Enumerate(InvalidateOldStyle, &data);
+  if (data.mChanged) {
+    mCacheTable.EnumerateRead(InvalidateDependentData, nullptr);
+  }
+  return data.mChanged;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/layout/style/CounterStyleManager.h
@@ -0,0 +1,139 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsHashKeys.h"
+
+#include "nsStyleConsts.h"
+
+#include "mozilla/NullPtr.h"
+#include "mozilla/Attributes.h"
+
+class nsPresContext;
+class nsCSSCounterStyleRule;
+
+namespace mozilla {
+
+typedef int32_t CounterValue;
+
+class CounterStyleManager;
+
+struct NegativeType;
+struct PadType;
+
+class CounterStyle
+{
+protected:
+  explicit MOZ_CONSTEXPR CounterStyle(int32_t aStyle)
+    : mStyle(aStyle)
+  {
+  }
+
+private:
+  CounterStyle(const CounterStyle& aOther) MOZ_DELETE;
+  void operator=(const CounterStyle& other) MOZ_DELETE;
+
+public:
+  int32_t GetStyle() const { return mStyle; }
+  bool IsNone() const { return mStyle == NS_STYLE_LIST_STYLE_NONE; }
+  bool IsCustomStyle() const { return mStyle == NS_STYLE_LIST_STYLE_CUSTOM; }
+  // A style is dependent if it depends on the counter style manager.
+  // Custom styles are certainly dependent. In addition, some builtin
+  // styles are dependent for fallback.
+  bool IsDependentStyle() const;
+
+  virtual void GetPrefix(nsSubstring& aResult) = 0;
+  virtual void GetSuffix(nsSubstring& aResult) = 0;
+  void GetCounterText(CounterValue aOrdinal,
+                      nsSubstring& aResult,
+                      bool& aIsRTL);
+  virtual void GetSpokenCounterText(CounterValue aOrdinal,
+                                    nsSubstring& aResult,
+                                    bool& aIsBullet);
+
+  // XXX This method could be removed once ::-moz-list-bullet and
+  //     ::-moz-list-number are completely merged into ::marker.
+  virtual bool IsBullet() = 0;
+
+  virtual void GetNegative(NegativeType& aResult) = 0;
+  /**
+   * This method returns whether an ordinal is in the range of this
+   * counter style. Note that, it is possible that an ordinal in range
+   * is rejected by the generating algorithm.
+   */
+  virtual bool IsOrdinalInRange(CounterValue aOrdinal) = 0;
+  /**
+   * This method returns whether an ordinal is in the default range of
+   * this counter style. This is the effective range when no 'range'
+   * descriptor is specified.
+   */
+  virtual bool IsOrdinalInAutoRange(CounterValue aOrdinal) = 0;
+  virtual void GetPad(PadType& aResult) = 0;
+  virtual CounterStyle* GetFallback() = 0;
+  virtual uint8_t GetSpeakAs() = 0;
+  virtual bool UseNegativeSign() = 0;
+
+  virtual void CallFallbackStyle(CounterValue aOrdinal,
+                                 nsSubstring& aResult,
+                                 bool& aIsRTL) = 0;
+  virtual bool GetInitialCounterText(CounterValue aOrdinal,
+                                     nsSubstring& aResult,
+                                     bool& aIsRTL) = 0;
+
+  NS_IMETHOD_(MozExternalRefCountType) AddRef() = 0;
+  NS_IMETHOD_(MozExternalRefCountType) Release() = 0;
+
+protected:
+  int32_t mStyle;
+};
+
+class CounterStyleManager MOZ_FINAL
+{
+public:
+  CounterStyleManager(nsPresContext* aPresContext);
+  ~CounterStyleManager();
+
+  static void InitializeBuiltinCounterStyles();
+
+  void Disconnect();
+
+  bool IsInitial() const
+  {
+    // only 'none' and 'decimal'
+    return mCacheTable.Count() == 2;
+  }
+
+  CounterStyle* BuildCounterStyle(const nsSubstring& aName);
+
+  static CounterStyle* GetBuiltinStyle(int32_t aStyle);
+  static CounterStyle* GetNoneStyle()
+  {
+    return GetBuiltinStyle(NS_STYLE_LIST_STYLE_NONE);
+  }
+  static CounterStyle* GetDecimalStyle()
+  {
+    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();
+
+  NS_INLINE_DECL_REFCOUNTING(CounterStyleManager)
+
+private:
+  nsPresContext* mPresContext;
+  nsRefPtrHashtable<nsStringHashKey, CounterStyle> mCacheTable;
+};
+
+} // namespace mozilla
+
+#endif /* !defined(mozilla_CounterStyleManager_h_) */
--- a/layout/style/moz.build
+++ b/layout/style/moz.build
@@ -79,16 +79,17 @@ EXPORTS.mozilla.css += [
     'Loader.h',
     'NameSpaceRule.h',
     'Rule.h',
     'StyleRule.h',
 ]
 
 UNIFIED_SOURCES += [
     'AnimationCommon.cpp',
+    'CounterStyleManager.cpp',
     'CSS.cpp',
     'CSSRuleList.cpp',
     'CSSVariableDeclarations.cpp',
     'CSSVariableResolver.cpp',
     'CSSVariableValues.cpp',
     'Declaration.cpp',
     'ErrorReporter.cpp',
     'ImageLoader.cpp',
--- a/layout/style/nsStyleConsts.h
+++ b/layout/style/nsStyleConsts.h
@@ -629,16 +629,17 @@ static inline mozilla::css::Side operato
 #define NS_STYLE_OVERFLOW_SCROLLBARS_HORIZONTAL 5
 #define NS_STYLE_OVERFLOW_SCROLLBARS_VERTICAL   6
 
 // See nsStyleDisplay.mOverflowClipBox
 #define NS_STYLE_OVERFLOW_CLIP_BOX_PADDING_BOX  0
 #define NS_STYLE_OVERFLOW_CLIP_BOX_CONTENT_BOX  1
 
 // See nsStyleList
+#define NS_STYLE_LIST_STYLE_CUSTOM                -1 // for @counter-style
 #define NS_STYLE_LIST_STYLE_NONE                  0
 #define NS_STYLE_LIST_STYLE_DISC                  1
 #define NS_STYLE_LIST_STYLE_CIRCLE                2
 #define NS_STYLE_LIST_STYLE_SQUARE                3
 #define NS_STYLE_LIST_STYLE_DECIMAL               4
 #define NS_STYLE_LIST_STYLE_DECIMAL_LEADING_ZERO  5
 #define NS_STYLE_LIST_STYLE_LOWER_ROMAN           6
 #define NS_STYLE_LIST_STYLE_UPPER_ROMAN           7
@@ -691,16 +692,17 @@ static inline mozilla::css::Side operato
 #define NS_STYLE_LIST_STYLE_MOZ_KHMER                 147
 #define NS_STYLE_LIST_STYLE_MOZ_HANGUL                148
 #define NS_STYLE_LIST_STYLE_MOZ_HANGUL_CONSONANT      149
 #define NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_HALEHAME     150
 #define NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_NUMERIC      151
 #define NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_HALEHAME_AM  152
 #define NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_HALEHAME_TI_ER  153
 #define NS_STYLE_LIST_STYLE_MOZ_ETHIOPIC_HALEHAME_TI_ET  154
+#define NS_STYLE_LIST_STYLE__MAX                         155
 
 // See nsStyleList
 #define NS_STYLE_LIST_STYLE_POSITION_INSIDE     0
 #define NS_STYLE_LIST_STYLE_POSITION_OUTSIDE    1
 
 // See nsStyleMargin
 #define NS_STYLE_MARGIN_SIZE_AUTO               0
 
@@ -1067,16 +1069,17 @@ static inline mozilla::css::Side operato
 #define NS_STYLE_COUNTER_SYSTEM_EXTENDS     6
 
 #define NS_STYLE_COUNTER_RANGE_INFINITE     0
 
 #define NS_STYLE_COUNTER_SPEAKAS_BULLETS    0
 #define NS_STYLE_COUNTER_SPEAKAS_NUMBERS    1
 #define NS_STYLE_COUNTER_SPEAKAS_WORDS      2
 #define NS_STYLE_COUNTER_SPEAKAS_SPELL_OUT  3
+#define NS_STYLE_COUNTER_SPEAKAS_OTHER      255 // refer to another style
 
 /*****************************************************************************
  * Constants for media features.                                             *
  *****************************************************************************/
 
 // orientation
 #define NS_STYLE_ORIENTATION_PORTRAIT           0
 #define NS_STYLE_ORIENTATION_LANDSCAPE          1