Bug 966168 - Implement symbols() CSS function. r=dbaron
authorXidorn Quan <quanxunzhen@gmail.com>
Thu, 25 Sep 2014 02:19:00 +0200
changeset 207597 1da6f91145034e5312c9a6d9603b4b4bef860e64
parent 207596 43a1f1f793022fefae4cfd107f66f731ec22fb08
child 207598 22b5ded1d700e862aab8c66013658575356bd1f0
push id49731
push usercbook@mozilla.com
push dateMon, 29 Sep 2014 07:18:32 +0000
treeherdermozilla-inbound@99b68a13246b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron
bugs966168
milestone35.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 966168 - Implement symbols() CSS function. r=dbaron
layout/base/nsCounterManager.cpp
layout/reftests/counter-style/reftest.list
layout/reftests/counter-style/symbols-function-invalid-ref.html
layout/reftests/counter-style/symbols-function-invalid.html
layout/reftests/counter-style/symbols-function-ref.html
layout/reftests/counter-style/symbols-function.html
layout/style/CounterStyleManager.cpp
layout/style/CounterStyleManager.h
layout/style/nsCSSKeywordList.h
layout/style/nsCSSParser.cpp
layout/style/nsCSSProps.cpp
layout/style/nsCSSProps.h
layout/style/nsCSSValue.cpp
layout/style/nsCSSValue.h
layout/style/nsComputedDOMStyle.cpp
layout/style/nsRuleNode.cpp
layout/style/test/property_database.js
layout/style/test/test_counter_descriptor_storage.html
--- a/layout/base/nsCounterManager.cpp
+++ b/layout/base/nsCounterManager.cpp
@@ -43,18 +43,27 @@ nsCounterUseNode::InitTextFrame(nsGenCon
   return false;
 }
 
 CounterStyle*
 nsCounterUseNode::GetCounterStyle()
 {
     if (!mCounterStyle) {
         const nsCSSValue& style = mCounterFunction->Item(mAllCounters ? 2 : 1);
-        mCounterStyle = mPresContext->CounterStyleManager()->
-            BuildCounterStyle(nsDependentString(style.GetStringBufferValue()));
+        CounterStyleManager* manager = mPresContext->CounterStyleManager();
+        if (style.GetUnit() == eCSSUnit_Ident) {
+            nsString ident;
+            style.GetStringValue(ident);
+            mCounterStyle = manager->BuildCounterStyle(ident);
+        } else if (style.GetUnit() == eCSSUnit_Symbols) {
+            mCounterStyle = manager->BuildCounterStyle(style.GetArrayValue());
+        } else {
+            NS_NOTREACHED("Unknown counter style");
+            mCounterStyle = CounterStyleManager::GetDecimalStyle();
+        }
     }
     return mCounterStyle;
 }
 
 // assign the correct |mValueAfter| value to a node that has been inserted
 // Should be called immediately after calling |Insert|.
 void nsCounterUseNode::Calc(nsCounterList *aList)
 {
--- a/layout/reftests/counter-style/reftest.list
+++ b/layout/reftests/counter-style/reftest.list
@@ -26,8 +26,10 @@ fails-if(B2G) == descriptor-suffix.html 
 == descriptor-pad-invalid.html      descriptor-pad-invalid-ref.html
 == descriptor-fallback.html         descriptor-fallback-ref.html
 == descriptor-symbols-invalid.html  descriptor-symbols-invalid-ref.html
 == name-case-sensitivity.html       name-case-sensitivity-ref.html
 == dependent-builtin.html           dependent-builtin-ref.html
 == redefine-builtin.html            redefine-builtin-ref.html
 == redefine-attr-mapping.html       redefine-attr-mapping-ref.html
 == disclosure-styles.html           disclosure-styles-ref.html
+== symbols-function.html            symbols-function-ref.html
+== symbols-function-invalid.html    symbols-function-invalid-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/counter-style/symbols-function-invalid-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<link rel="stylesheet" href="test-common.css">
+<style type="text/css">
+  .invalid {
+    list-style-type: lower-greek;
+  }
+</style>
+<ol start="-2" class="invalid">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/counter-style/symbols-function-invalid.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<link rel="stylesheet" href="test-common.css">
+<style type="text/css">
+  .invalid {
+    list-style-type: lower-greek;
+    list-style-type: symbols(a b c);
+    list-style-type: symbols(alphabetic a b c);
+    list-style-type: symbols(numeric 0 1 2);
+    list-style-type: symbols(additive 'a' 'b');
+    list-style-type: symbols(fixed);
+    list-style-type: symbols(alphabetic 'a');
+    list-style-type: symbols(numeric '0');
+  }
+</style>
+<ol start="-2" class="invalid">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/counter-style/symbols-function-ref.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<link rel="stylesheet" href="test-common.css">
+<style type="text/css">
+  @counter-style cyclic {
+    system: cyclic;
+    symbols: '*' '\2020' '\2021' '\A7';
+    suffix: ' ';
+  }
+  @counter-style numeric {
+    system: numeric;
+    symbols: '0' '1' '2';
+    suffix: ' ';
+  }
+  @counter-style alphabetic {
+    system: alphabetic;
+    symbols: '\26AA' '\26AB';
+    suffix: ' ';
+  }
+  @counter-style symbolic {
+    system: symbolic;
+    symbols: '*' '\2020' '\2021' '\A7';
+    suffix: ' ';
+  }
+  @counter-style fixed {
+    system: fixed;
+    symbols: '\25F0' '\25F1' '\25F2' '\25F3';
+    suffix: ' ';
+  }
+  @counter-style counter {
+    symbols: '*';
+  }
+  @counter-style counters {
+    system: numeric;
+    symbols: '0' '1';
+  }
+  .counter { counter-reset: a; }
+  .counter p { counter-increment: a 1; }
+  .counter p::after {
+    content: counter(a, counter);
+  }
+  .counter, .counters {
+    list-style-type: none;
+    counter-reset: a;
+  }
+  .counter li, .counters li {
+    counter-increment: a;
+    padding-right: .5em;
+  }
+  .counter li::after {
+    content: counter(a, counter);
+  }
+  .counters .counters li::after {
+    content: counters(a, '.', counters);
+  }
+</style>
+<ol start="-2" style="list-style-type: symbolic">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol start="-2" style="list-style-type: cyclic">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol start="-2" style="list-style-type: numeric">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol start="-2" style="list-style-type: alphabetic">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol start="-2" style="list-style-type: symbolic">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol start="-2" style="list-style-type: fixed">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol class="counter">
+  <li><li><li><li><li>
+</ol>
+<ol class="counters">
+  <li><ol class="counters"><li><li><li><li><li></ol></li>
+  <li><ol class="counters"><li><li><li><li><li></ol></li>
+</ol>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/counter-style/symbols-function.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<link rel="stylesheet" href="test-common.css">
+<style type="text/css">
+  .default {
+    list-style-type: symbols('*' '\2020' '\2021' '\A7');
+  }
+  .cyclic {
+    list-style-type: symbols(cyclic '*' '\2020' '\2021' '\A7');
+  }
+  .numeric {
+    list-style-type: symbols(numeric '0' '1' '2');
+  }
+  .alphabetic {
+    list-style-type: symbols(alphabetic '\26AA' '\26AB');
+  }
+  .symbolic {
+    list-style-type: symbols(symbolic '*' '\2020' '\2021' '\A7');
+  }
+  .fixed {
+    list-style-type: symbols(fixed '\25F0' '\25F1' '\25F2' '\25F3');
+  }
+  .counter, .counters {
+    list-style-type: none;
+    counter-reset: a;
+  }
+  .counter li, .counters li {
+    counter-increment: a;
+    padding-right: .5em;
+  }
+  .counter li::after {
+    content: counter(a, symbols('*'));
+  }
+  .counters .counters li::after {
+    content: counters(a, '.', symbols(numeric '0' '1'));
+  }
+</style>
+<ol start="-2" class="default">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol start="-2" class="cyclic">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol start="-2" class="numeric">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol start="-2" class="alphabetic">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol start="-2" class="symbolic">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol start="-2" class="fixed">
+  <li><li><li><li><li>
+  <li><li><li><li><li>
+</ol>
+<ol class="counter">
+  <li><li><li><li><li>
+</ol>
+<ol class="counters">
+  <li><ol class="counters"><li><li><li><li><li></ol></li>
+  <li><ol class="counters"><li><li><li><li><li></ol></li>
+</ol>
--- a/layout/style/CounterStyleManager.cpp
+++ b/layout/style/CounterStyleManager.cpp
@@ -537,16 +537,47 @@ EthiopicToText(CounterValue aOrdinal, ns
       if (groupIndexFromRight) {
         aResult.Append((char16_t) ETHIOPIC_TEN_THOUSAND);
       }
     }
   }
   return true;
 }
 
+static uint8_t
+GetDefaultSpeakAsForSystem(uint8_t aSystem)
+{
+  NS_ABORT_IF_FALSE(aSystem != NS_STYLE_COUNTER_SYSTEM_EXTENDS,
+                    "Extends system does not have static default speak-as");
+  switch (aSystem) {
+    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;
+  }
+}
+
+static bool
+SystemUsesNegativeSign(uint8_t aSystem)
+{
+  NS_ABORT_IF_FALSE(aSystem != NS_STYLE_COUNTER_SYSTEM_EXTENDS,
+                    "Cannot check this for extending style");
+  switch (aSystem) {
+    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;
+    default:
+      return false;
+  }
+}
+
 class BuiltinCounterStyle : public CounterStyle
 {
 public:
   friend class CounterStyleManager;
 
   // will be initialized by CounterStyleManager::InitializeBuiltinCounterStyles
   MOZ_CONSTEXPR BuiltinCounterStyle()
     : CounterStyle(NS_STYLE_LIST_STYLE_NONE)
@@ -571,20 +602,16 @@ public:
   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,
-                                 WritingMode aWritingMode,
-                                 nsSubstring& aResult,
-                                 bool& aIsRTL) MOZ_OVERRIDE;
   virtual bool GetInitialCounterText(CounterValue aOrdinal,
                                      WritingMode aWritingMode,
                                      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; }
@@ -841,25 +868,16 @@ BuiltinCounterStyle::UseNegativeSign()
     case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED:
     case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN:
       return false;
     default:
       return true;
   }
 }
 
-/* virtual */ void
-BuiltinCounterStyle::CallFallbackStyle(CounterValue aOrdinal,
-                                       WritingMode aWritingMode,
-                                       nsSubstring& aResult,
-                                       bool& aIsRTL)
-{
-  GetFallback()->GetCounterText(aOrdinal, aWritingMode, aResult, aIsRTL);
-}
-
 /* virtual */ bool
 BuiltinCounterStyle::GetInitialCounterText(CounterValue aOrdinal,
                                            WritingMode aWritingMode,
                                            nsSubstring& aResult,
                                            bool& aIsRTL)
 {
   aIsRTL = false;
   switch (mStyle) {
@@ -1350,27 +1368,20 @@ CustomCounterStyle::GetSpeakAs()
     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;
+  if (mSystem == NS_STYLE_COUNTER_SYSTEM_EXTENDS) {
+    return GetExtendsRoot()->UseNegativeSign();
   }
+  return SystemUsesNegativeSign(mSystem);
 }
 
 /* virtual */ void
 CustomCounterStyle::CallFallbackStyle(CounterValue aOrdinal,
                                       WritingMode aWritingMode,
                                       nsSubstring& aResult,
                                       bool& aIsRTL)
 {
@@ -1451,24 +1462,17 @@ CustomCounterStyle::GetSpeakAsAutoValue(
   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;
-  }
+  return GetDefaultSpeakAsForSystem(system);
 }
 
 // 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.)
@@ -1646,16 +1650,129 @@ CustomCounterStyle::GetExtendsRoot()
         // is shared by multiple counter styles.
         mExtendsRoot = custom->GetExtendsRoot();
       }
     }
   }
   return mExtendsRoot;
 }
 
+AnonymousCounterStyle::AnonymousCounterStyle(const nsCSSValue::Array* aParams)
+  : CounterStyle(NS_STYLE_LIST_STYLE_CUSTOM)
+{
+  mSystem = aParams->Item(0).GetIntValue();
+  for (const nsCSSValueList* item = aParams->Item(1).GetListValue();
+       item; item = item->mNext) {
+    item->mValue.GetStringValue(*mSymbols.AppendElement());
+  }
+  mSymbols.Compact();
+}
+
+/* virtual */ void
+AnonymousCounterStyle::GetPrefix(nsAString& aResult)
+{
+  aResult.Truncate();
+}
+
+/* virtual */ void
+AnonymousCounterStyle::GetSuffix(nsAString& aResult)
+{
+  aResult = ' ';
+}
+
+/* virtual */ bool
+AnonymousCounterStyle::IsBullet()
+{
+  switch (mSystem) {
+    case NS_STYLE_COUNTER_SYSTEM_CYCLIC:
+      // Only use ::-moz-list-bullet for cyclic system
+      return true;
+    default:
+      return false;
+  }
+}
+
+/* virtual */ void
+AnonymousCounterStyle::GetNegative(NegativeType& aResult)
+{
+  aResult.before.AssignLiteral(MOZ_UTF16("-"));
+  aResult.after.Truncate();
+}
+
+/* virtual */ bool
+AnonymousCounterStyle::IsOrdinalInRange(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;
+    default:
+      NS_NOTREACHED("Invalid system.");
+      return false;
+  }
+}
+
+/* virtual */ bool
+AnonymousCounterStyle::IsOrdinalInAutoRange(CounterValue aOrdinal)
+{
+  return AnonymousCounterStyle::IsOrdinalInRange(aOrdinal);
+}
+
+/* virtual */ void
+AnonymousCounterStyle::GetPad(PadType& aResult)
+{
+  aResult.width = 0;
+  aResult.symbol.Truncate();
+}
+
+/* virtual */ CounterStyle*
+AnonymousCounterStyle::GetFallback()
+{
+  return CounterStyleManager::GetDecimalStyle();
+}
+
+/* virtual */ uint8_t
+AnonymousCounterStyle::GetSpeakAs()
+{
+  return GetDefaultSpeakAsForSystem(mSystem);
+}
+
+/* virtual */ bool
+AnonymousCounterStyle::UseNegativeSign()
+{
+  return SystemUsesNegativeSign(mSystem);
+}
+
+/* virtual */ bool
+AnonymousCounterStyle::GetInitialCounterText(CounterValue aOrdinal,
+                                             WritingMode aWritingMode,
+                                             nsAString& aResult,
+                                             bool& aIsRTL)
+{
+  switch (mSystem) {
+    case NS_STYLE_COUNTER_SYSTEM_CYCLIC:
+      return GetCyclicCounterText(aOrdinal, aResult, mSymbols);
+    case NS_STYLE_COUNTER_SYSTEM_FIXED:
+      return GetFixedCounterText(aOrdinal, aResult, 1, mSymbols);
+    case NS_STYLE_COUNTER_SYSTEM_SYMBOLIC:
+      return GetSymbolicCounterText(aOrdinal, aResult, mSymbols);
+    case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC:
+      return GetAlphabeticCounterText(aOrdinal, aResult, mSymbols);
+    case NS_STYLE_COUNTER_SYSTEM_NUMERIC:
+      return GetNumericCounterText(aOrdinal, aResult, mSymbols);
+    default:
+      NS_NOTREACHED("Invalid system.");
+      return false;
+  }
+}
+
 bool
 CounterStyle::IsDependentStyle() const
 {
   switch (mStyle) {
     // CustomCounterStyle
     case NS_STYLE_LIST_STYLE_CUSTOM:
     // DependentBuiltinCounterStyle
     case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL:
@@ -1777,16 +1894,25 @@ CounterStyle::GetSpokenCounterText(Count
       NS_NOTREACHED("Invalid speak-as value");
       break;
     default:
       NS_NOTREACHED("Unknown speak-as value");
       break;
   }
 }
 
+/* virtual */ void
+CounterStyle::CallFallbackStyle(CounterValue aOrdinal,
+                                WritingMode aWritingMode,
+                                nsAString& aResult,
+                                bool& aIsRTL)
+{
+  GetFallback()->GetCounterText(aOrdinal, aWritingMode, aResult, aIsRTL);
+}
+
 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());
@@ -1856,16 +1982,22 @@ CounterStyleManager::BuildCounterStyle(c
   }
   if (!data) {
     data = GetDecimalStyle();
   }
   mCacheTable.Put(aName, data);
   return data;
 }
 
+CounterStyle*
+CounterStyleManager::BuildCounterStyle(const nsCSSValue::Array* aParams)
+{
+  return new AnonymousCounterStyle(aParams);
+}
+
 /* 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];
--- a/layout/style/CounterStyleManager.h
+++ b/layout/style/CounterStyleManager.h
@@ -10,26 +10,29 @@
 #include "nsRefPtrHashtable.h"
 #include "nsHashKeys.h"
 
 #include "nsStyleConsts.h"
 
 #include "mozilla/NullPtr.h"
 #include "mozilla/Attributes.h"
 
+#include "nsCSSValue.h"
+
 class nsPresContext;
 class nsCSSCounterStyleRule;
 
 namespace mozilla {
 
 class WritingMode;
 
 typedef int32_t CounterValue;
 
 class CounterStyleManager;
+class AnonymousCounterStyle;
 
 struct NegativeType;
 struct PadType;
 
 class CounterStyle
 {
 protected:
   explicit MOZ_CONSTEXPR CounterStyle(int32_t aStyle)
@@ -81,29 +84,67 @@ public:
   virtual void GetPad(PadType& aResult) = 0;
   virtual CounterStyle* GetFallback() = 0;
   virtual uint8_t GetSpeakAs() = 0;
   virtual bool UseNegativeSign() = 0;
 
   virtual void CallFallbackStyle(CounterValue aOrdinal,
                                  WritingMode aWritingMode,
                                  nsSubstring& aResult,
-                                 bool& aIsRTL) = 0;
+                                 bool& aIsRTL);
   virtual bool GetInitialCounterText(CounterValue aOrdinal,
                                      WritingMode aWritingMode,
                                      nsSubstring& aResult,
                                      bool& aIsRTL) = 0;
 
+  virtual AnonymousCounterStyle* AsAnonymous() { return nullptr; }
+
   NS_IMETHOD_(MozExternalRefCountType) AddRef() = 0;
   NS_IMETHOD_(MozExternalRefCountType) Release() = 0;
 
 protected:
   int32_t mStyle;
 };
 
+class AnonymousCounterStyle MOZ_FINAL : public CounterStyle
+{
+public:
+  AnonymousCounterStyle(const nsCSSValue::Array* aValue);
+
+  virtual void GetPrefix(nsAString& aResult) MOZ_OVERRIDE;
+  virtual void GetSuffix(nsAString& aResult) 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 bool GetInitialCounterText(CounterValue aOrdinal,
+                                     WritingMode aWritingMode,
+                                     nsSubstring& aResult,
+                                     bool& aIsRTL) MOZ_OVERRIDE;
+
+  virtual AnonymousCounterStyle* AsAnonymous() MOZ_OVERRIDE { return this; }
+
+  uint8_t GetSystem() const { return mSystem; }
+  const nsTArray<nsString>& GetSymbols() const { return mSymbols; }
+
+  NS_INLINE_DECL_REFCOUNTING(AnonymousCounterStyle)
+
+private:
+  ~AnonymousCounterStyle() {}
+
+  uint8_t mSystem;
+  nsTArray<nsString> mSymbols;
+};
+
 class CounterStyleManager MOZ_FINAL
 {
 private:
   ~CounterStyleManager();
 public:
   explicit CounterStyleManager(nsPresContext* aPresContext);
 
   static void InitializeBuiltinCounterStyles();
@@ -112,16 +153,17 @@ public:
 
   bool IsInitial() const
   {
     // only 'none' and 'decimal'
     return mCacheTable.Count() == 2;
   }
 
   CounterStyle* BuildCounterStyle(const nsSubstring& aName);
+  CounterStyle* BuildCounterStyle(const nsCSSValue::Array* aParams);
 
   static CounterStyle* GetBuiltinStyle(int32_t aStyle);
   static CounterStyle* GetNoneStyle()
   {
     return GetBuiltinStyle(NS_STYLE_LIST_STYLE_NONE);
   }
   static CounterStyle* GetDecimalStyle()
   {
--- a/layout/style/nsCSSKeywordList.h
+++ b/layout/style/nsCSSKeywordList.h
@@ -718,12 +718,13 @@ CSS_KEY(nonzero, nonzero)
 CSS_KEY(optimizelegibility, optimizelegibility)
 CSS_KEY(optimizequality, optimizequality)
 CSS_KEY(optimizespeed, optimizespeed)
 CSS_KEY(reset-size, reset_size)
 //CSS_KEY(square, square)
 //CSS_KEY(start, start)
 CSS_KEY(srgb, srgb)
 CSS_KEY(symbolic, symbolic)
+CSS_KEY(symbols, symbols)
 CSS_KEY(text-after-edge, text_after_edge)
 CSS_KEY(text-before-edge, text_before_edge)
 CSS_KEY(use-script, use_script)
 CSS_KEY(-moz-crisp-edges, _moz_crisp_edges)
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -508,16 +508,17 @@ protected:
   bool ParseSupportsConditionTerms(bool& aConditionMet);
   enum SupportsConditionTermOperator { eAnd, eOr };
   bool ParseSupportsConditionTermsAfterOperator(
                                        bool& aConditionMet,
                                        SupportsConditionTermOperator aOperator);
 
   bool ParseCounterStyleRule(RuleAppendFunc aAppendFunc, void* aProcessData);
   bool ParseCounterStyleName(nsAString& aName, bool aForDefinition);
+  bool ParseCounterStyleNameValue(nsCSSValue& aValue);
   bool ParseCounterDescriptor(nsCSSCounterStyleRule *aRule);
   bool ParseCounterDescriptorValue(nsCSSCounterDesc aDescID,
                                    nsCSSValue& aValue);
   bool ParseCounterRange(nsCSSValuePair& aPair);
 
   /**
    * Parses the current input stream for a CSS token stream value and resolves
    * any variable references using the variables in aVariables.
@@ -931,16 +932,17 @@ protected:
   // also appear in an existing nsCSSProps::KTableValue,
   // to avoid duplicating them.
   bool ParseCustomIdent(nsCSSValue& aValue,
                         const nsAutoString& aIdentValue,
                         const nsCSSKeyword aExcludedKeywords[] = nullptr,
                         const nsCSSProps::KTableValue aPropertyKTable[] = nullptr);
   bool ParseCounter(nsCSSValue& aValue);
   bool ParseAttr(nsCSSValue& aValue);
+  bool ParseSymbols(nsCSSValue& aValue);
   bool SetValueToURL(nsCSSValue& aValue, const nsString& aURL);
   bool TranslateDimension(nsCSSValue& aValue, int32_t aVariantMask,
                             float aNumber, const nsString& aUnit);
   bool ParseImageOrientation(nsCSSValue& aAngle);
   bool ParseImageRect(nsCSSValue& aImage);
   bool ParseElement(nsCSSValue& aValue);
   bool ParseColorStop(nsCSSValueGradient* aGradient);
   bool ParseLinearGradient(nsCSSValue& aValue, bool aIsRepeating,
@@ -4354,16 +4356,27 @@ CSSParserImpl::ParseCounterStyleName(nsA
   aName = mToken.mIdent;
   if (nsCSSProps::IsPredefinedCounterStyle(aName)) {
     ToLowerCase(aName);
   }
   return true;
 }
 
 bool
+CSSParserImpl::ParseCounterStyleNameValue(nsCSSValue& aValue)
+{
+  nsString name;
+  if (ParseCounterStyleName(name, false)) {
+    aValue.SetStringValue(name, eCSSUnit_Ident);
+    return true;
+  }
+  return false;
+}
+
+bool
 CSSParserImpl::ParseCounterDescriptor(nsCSSCounterStyleRule* aRule)
 {
   if (eCSSToken_Ident != mToken.mType) {
     REPORT_UNEXPECTED_TOKEN(PECounterDescExpected);
     return false;
   }
 
   nsString descName = mToken.mIdent;
@@ -4414,22 +4427,22 @@ CSSParserImpl::ParseCounterDescriptorVal
           nsCSSValue start;
           if (!ParseVariant(start, VARIANT_INTEGER, nullptr)) {
             start.SetIntValue(1, eCSSUnit_Integer);
           }
           aValue.SetPairValue(system, start);
           return true;
         }
         case NS_STYLE_COUNTER_SYSTEM_EXTENDS: {
-          nsString name;
-          if (!ParseCounterStyleName(name, false)) {
+          nsCSSValue name;
+          if (!ParseCounterStyleNameValue(name)) {
             REPORT_UNEXPECTED_TOKEN(PECounterExtendsNotIdent);
             return false;
           }
-          aValue.SetPairValue(system, nsCSSValue(name, eCSSUnit_Ident));
+          aValue.SetPairValue(system, name);
           return true;
         }
         default:
           aValue = system;
           return true;
       }
     }
 
@@ -4477,24 +4490,18 @@ CSSParserImpl::ParseCounterDescriptorVal
       if (!ParseVariant(symbol, VARIANT_COUNTER_SYMBOL, nullptr) ||
           (!hasWidth && !ParseNonNegativeInteger(width))) {
         return false;
       }
       aValue.SetPairValue(width, symbol);
       return true;
     }
 
-    case eCSSCounterDesc_Fallback: {
-      nsString name;
-      if (!ParseCounterStyleName(name, false)) {
-        return false;
-      }
-      aValue.SetStringValue(name, eCSSUnit_Ident);
-      return true;
-    }
+    case eCSSCounterDesc_Fallback:
+      return ParseCounterStyleNameValue(aValue);
 
     case eCSSCounterDesc_Symbols: {
       nsCSSValueList* item = nullptr;
       for (;;) {
         nsCSSValue value;
         if (!ParseVariant(value, VARIANT_COUNTER_SYMBOL, nullptr)) {
           return !!item;
         }
@@ -4534,35 +4541,29 @@ CSSParserImpl::ParseCounterDescriptorVal
         item->mYValue = symbol;
         if (!ExpectSymbol(',', true)) {
           return true;
         }
       }
       // should always return in the loop
     }
 
-    case eCSSCounterDesc_SpeakAs: {
+    case eCSSCounterDesc_SpeakAs:
       if (ParseVariant(aValue, VARIANT_AUTO | VARIANT_KEYWORD,
                       nsCSSProps::kCounterSpeakAsKTable)) {
         if (aValue.GetUnit() == eCSSUnit_Enumerated &&
             aValue.GetIntValue() == NS_STYLE_COUNTER_SPEAKAS_SPELL_OUT) {
           // Currently spell-out is not supported, so it is explicitly
           // rejected here rather than parsed as a custom identifier.
           // See bug 1024178.
           return false;
         }
         return true;
       }
-      nsString name;
-      if (ParseCounterStyleName(name, false)) {
-        aValue.SetStringValue(name, eCSSUnit_Ident);
-        return true;
-      }
-      return false;
-    }
+      return ParseCounterStyleNameValue(aValue);
 
     default:
       NS_NOTREACHED("unknown descriptor");
       return false;
   }
 }
 
 bool
@@ -7201,25 +7202,25 @@ CSSParserImpl::ParseCounter(nsCSSValue& 
       if (eCSSToken_String != mToken.mType) {
         UngetToken();
         break;
       }
       val->Item(1).SetStringValue(mToken.mIdent, eCSSUnit_String);
     }
 
     // get optional type
-    nsString type = NS_LITERAL_STRING("decimal");
+    int32_t typeItem = eCSSUnit_Counters == unit ? 2 : 1;
+    nsCSSValue& type = val->Item(typeItem);
     if (ExpectSymbol(',', true)) {
-      if (!ParseCounterStyleName(type, false)) {
-        break;
-      }
-    }
-
-    int32_t typeItem = eCSSUnit_Counters == unit ? 2 : 1;
-    val->Item(typeItem).SetStringValue(type, eCSSUnit_Ident);
+      if (!ParseCounterStyleNameValue(type) && !ParseSymbols(type)) {
+        break;
+      }
+    } else {
+      type.SetStringValue(NS_LITERAL_STRING("decimal"), eCSSUnit_Ident);
+    }
 
     if (!ExpectSymbol(')', true)) {
       break;
     }
 
     aValue.SetArrayValue(val, unit);
     return true;
   }
@@ -7290,16 +7291,64 @@ CSSParserImpl::ParseAttr(nsCSSValue& aVa
   if (!ExpectSymbol(')', true)) {
     return false;
   }
   aValue.SetStringValue(attr, eCSSUnit_Attr);
   return true;
 }
 
 bool
+CSSParserImpl::ParseSymbols(nsCSSValue& aValue)
+{
+  if (!GetToken(true)) {
+    return false;
+  }
+  if (mToken.mType != eCSSToken_Function &&
+      !mToken.mIdent.LowerCaseEqualsLiteral("symbols")) {
+    UngetToken();
+    return false;
+  }
+
+  nsRefPtr<nsCSSValue::Array> params = nsCSSValue::Array::Create(2);
+  nsCSSValue& type = params->Item(0);
+  nsCSSValue& symbols = params->Item(1);
+
+  if (!ParseEnum(type, nsCSSProps::kCounterSymbolsSystemKTable)) {
+    type.SetIntValue(NS_STYLE_COUNTER_SYSTEM_SYMBOLIC, eCSSUnit_Enumerated);
+  }
+
+  bool first = true;
+  nsCSSValueList* item = symbols.SetListValue();
+  for (;;) {
+    // FIXME Should also include VARIANT_IMAGE. See bug 1071436.
+    if (!ParseVariant(item->mValue, VARIANT_STRING, nullptr)) {
+      break;
+    }
+    if (ExpectSymbol(')', true)) {
+      if (first) {
+        switch (type.GetIntValue()) {
+          case NS_STYLE_COUNTER_SYSTEM_NUMERIC:
+          case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC:
+            // require at least two symbols
+            return false;
+        }
+      }
+      aValue.SetArrayValue(params, eCSSUnit_Symbols);
+      return true;
+    }
+    item->mNext = new nsCSSValueList;
+    item = item->mNext;
+    first = false;
+  }
+
+  SkipUntil(')');
+  return false;
+}
+
+bool
 CSSParserImpl::SetValueToURL(nsCSSValue& aValue, const nsString& aURL)
 {
   if (!mSheetPrincipal) {
     NS_NOTREACHED("Codepaths that expect to parse URLs MUST pass in an "
                   "origin principal");
     return false;
   }
 
@@ -12864,21 +12913,20 @@ CSSParserImpl::ParseListStyle()
 
 bool
 CSSParserImpl::ParseListStyleType(nsCSSValue& aValue)
 {
   if (ParseVariant(aValue, VARIANT_INHERIT, nullptr)) {
     return true;
   }
 
-  nsString name;
-  if (ParseCounterStyleName(name, false)) {
-    aValue.SetStringValue(name, eCSSUnit_Ident);
-    return true;
-  }
+  if (ParseCounterStyleNameValue(aValue) || ParseSymbols(aValue)) {
+    return true;
+  }
+
   return false;
 }
 
 bool
 CSSParserImpl::ParseMargin()
 {
   static const nsCSSProperty kMarginSideIDs[] = {
     eCSSProperty_margin_top,
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -1977,16 +1977,25 @@ const KTableValue nsCSSProps::kCounterSy
   eCSSKeyword_alphabetic, NS_STYLE_COUNTER_SYSTEM_ALPHABETIC,
   eCSSKeyword_symbolic, NS_STYLE_COUNTER_SYSTEM_SYMBOLIC,
   eCSSKeyword_additive, NS_STYLE_COUNTER_SYSTEM_ADDITIVE,
   eCSSKeyword_fixed, NS_STYLE_COUNTER_SYSTEM_FIXED,
   eCSSKeyword_extends, NS_STYLE_COUNTER_SYSTEM_EXTENDS,
   eCSSKeyword_UNKNOWN, -1
 };
 
+const KTableValue nsCSSProps::kCounterSymbolsSystemKTable[] = {
+  eCSSKeyword_cyclic, NS_STYLE_COUNTER_SYSTEM_CYCLIC,
+  eCSSKeyword_numeric, NS_STYLE_COUNTER_SYSTEM_NUMERIC,
+  eCSSKeyword_alphabetic, NS_STYLE_COUNTER_SYSTEM_ALPHABETIC,
+  eCSSKeyword_symbolic, NS_STYLE_COUNTER_SYSTEM_SYMBOLIC,
+  eCSSKeyword_fixed, NS_STYLE_COUNTER_SYSTEM_FIXED,
+  eCSSKeyword_UNKNOWN, -1
+};
+
 const KTableValue nsCSSProps::kCounterRangeKTable[] = {
   eCSSKeyword_infinite, NS_STYLE_COUNTER_RANGE_INFINITE,
   eCSSKeyword_UNKNOWN, -1
 };
 
 const KTableValue nsCSSProps::kCounterSpeakAsKTable[] = {
   eCSSKeyword_bullets, NS_STYLE_COUNTER_SPEAKAS_BULLETS,
   eCSSKeyword_numbers, NS_STYLE_COUNTER_SPEAKAS_NUMBERS,
--- a/layout/style/nsCSSProps.h
+++ b/layout/style/nsCSSProps.h
@@ -652,16 +652,17 @@ public:
   static const KTableValue kWidthKTable[]; // also min-width, max-width
   static const KTableValue kWindowDraggingKTable[];
   static const KTableValue kWindowShadowKTable[];
   static const KTableValue kWordBreakKTable[];
   static const KTableValue kWordWrapKTable[];
   static const KTableValue kWritingModeKTable[];
   static const KTableValue kHyphensKTable[];
   static const KTableValue kCounterSystemKTable[];
+  static const KTableValue kCounterSymbolsSystemKTable[];
   static const KTableValue kCounterRangeKTable[];
   static const KTableValue kCounterSpeakAsKTable[];
 };
 
 inline nsCSSProps::EnabledState operator|(nsCSSProps::EnabledState a,
                                           nsCSSProps::EnabledState b)
 {
   return nsCSSProps::EnabledState(int(a) | int(b));
--- a/layout/style/nsCSSValue.cpp
+++ b/layout/style/nsCSSValue.cpp
@@ -848,31 +848,33 @@ nsCSSValue::AppendToString(nsCSSProperty
     nsAutoString  buffer;
     GetStringValue(buffer);
     if (unit == eCSSUnit_String) {
       nsStyleUtil::AppendEscapedCSSString(buffer, aResult);
     } else {
       nsStyleUtil::AppendEscapedCSSIdent(buffer, aResult);
     }
   }
-  else if (eCSSUnit_Array <= unit && unit <= eCSSUnit_Steps) {
+  else if (eCSSUnit_Array <= unit && unit <= eCSSUnit_Symbols) {
     switch (unit) {
       case eCSSUnit_Counter:  aResult.AppendLiteral("counter(");  break;
       case eCSSUnit_Counters: aResult.AppendLiteral("counters("); break;
       case eCSSUnit_Cubic_Bezier: aResult.AppendLiteral("cubic-bezier("); break;
       case eCSSUnit_Steps: aResult.AppendLiteral("steps("); break;
+      case eCSSUnit_Symbols: aResult.AppendLiteral("symbols("); break;
       default: break;
     }
 
     nsCSSValue::Array *array = GetArrayValue();
     bool mark = false;
     for (size_t i = 0, i_end = array->Count(); i < i_end; ++i) {
       if (mark && array->Item(i).GetUnit() != eCSSUnit_Null) {
-        if (unit == eCSSUnit_Array &&
-            eCSSProperty_transition_timing_function != aProperty)
+        if ((unit == eCSSUnit_Array &&
+             eCSSProperty_transition_timing_function != aProperty) ||
+            unit == eCSSUnit_Symbols)
           aResult.Append(' ');
         else
           aResult.AppendLiteral(", ");
       }
       if (unit == eCSSUnit_Steps && i == 1) {
         NS_ABORT_IF_FALSE(array->Item(i).GetUnit() == eCSSUnit_Enumerated &&
                           (array->Item(i).GetIntValue() ==
                             NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_START ||
@@ -882,16 +884,27 @@ nsCSSValue::AppendToString(nsCSSProperty
         if (array->Item(i).GetIntValue() ==
               NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_START) {
           aResult.AppendLiteral("start");
         } else {
           aResult.AppendLiteral("end");
         }
         continue;
       }
+      if (unit == eCSSUnit_Symbols && i == 0) {
+        NS_ABORT_IF_FALSE(array->Item(i).GetUnit() == eCSSUnit_Enumerated,
+                          "unexpected value");
+        int32_t system = array->Item(i).GetIntValue();
+        if (system != NS_STYLE_COUNTER_SYSTEM_SYMBOLIC) {
+          AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(
+                  system, nsCSSProps::kCounterSystemKTable), aResult);
+          mark = true;
+        }
+        continue;
+      }
       nsCSSProperty prop =
         ((eCSSUnit_Counter <= unit && unit <= eCSSUnit_Counters) &&
          i == array->Count() - 1)
         ? eCSSProperty_list_style_type : aProperty;
       if (array->Item(i).GetUnit() != eCSSUnit_Null) {
         array->Item(i).AppendToString(prop, aResult, aSerialization);
         mark = true;
       }
@@ -1400,16 +1413,17 @@ nsCSSValue::AppendToString(nsCSSProperty
     case eCSSUnit_Ident:        break;
     case eCSSUnit_URL:          break;
     case eCSSUnit_Image:        break;
     case eCSSUnit_Element:      break;
     case eCSSUnit_Array:        break;
     case eCSSUnit_Attr:
     case eCSSUnit_Cubic_Bezier:
     case eCSSUnit_Steps:
+    case eCSSUnit_Symbols:
     case eCSSUnit_Counter:
     case eCSSUnit_Counters:     aResult.Append(char16_t(')'));    break;
     case eCSSUnit_Local_Font:   break;
     case eCSSUnit_Font_Format:  break;
     case eCSSUnit_Function:     break;
     case eCSSUnit_Calc:         break;
     case eCSSUnit_Calc_Plus:    break;
     case eCSSUnit_Calc_Minus:   break;
@@ -1506,16 +1520,17 @@ nsCSSValue::SizeOfExcludingThis(mozilla:
       break;
 
     // Array
     case eCSSUnit_Array:
     case eCSSUnit_Counter:
     case eCSSUnit_Counters:
     case eCSSUnit_Cubic_Bezier:
     case eCSSUnit_Steps:
+    case eCSSUnit_Symbols:
     case eCSSUnit_Function:
     case eCSSUnit_Calc:
     case eCSSUnit_Calc_Plus:
     case eCSSUnit_Calc_Minus:
     case eCSSUnit_Calc_Times_L:
     case eCSSUnit_Calc_Times_R:
     case eCSSUnit_Calc_Divided:
       break;
--- a/layout/style/nsCSSValue.h
+++ b/layout/style/nsCSSValue.h
@@ -260,17 +260,18 @@ enum nsCSSUnit {
   eCSSUnit_Font_Format  = 16,     // (char16_t*) a font format name
   eCSSUnit_Element      = 17,     // (char16_t*) an element id
 
   eCSSUnit_Array        = 20,     // (nsCSSValue::Array*) a list of values
   eCSSUnit_Counter      = 21,     // (nsCSSValue::Array*) a counter(string,[string]) value
   eCSSUnit_Counters     = 22,     // (nsCSSValue::Array*) a counters(string,string[,string]) value
   eCSSUnit_Cubic_Bezier = 23,     // (nsCSSValue::Array*) a list of float values
   eCSSUnit_Steps        = 24,     // (nsCSSValue::Array*) a list of (integer, enumerated)
-  eCSSUnit_Function     = 25,     // (nsCSSValue::Array*) a function with
+  eCSSUnit_Symbols      = 25,     // (nsCSSValue::Array*) a symbols(enumerated, symbols) value
+  eCSSUnit_Function     = 26,     // (nsCSSValue::Array*) a function with
                                   //  parameters.  First elem of array is name,
                                   //  an nsCSSKeyword as eCSSUnit_Enumerated,
                                   //  the rest of the values are arguments.
 
   // The top level of a calc() expression is eCSSUnit_Calc.  All
   // remaining eCSSUnit_Calc_* units only occur inside these toplevel
   // calc values.
 
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -1059,20 +1059,21 @@ nsComputedDOMStyle::DoGetContent()
             typeItem = 2;
             str.AppendLiteral(", ");
             nsStyleUtil::AppendEscapedCSSString(
               nsDependentString(a->Item(1).GetStringBufferValue()), str);
           }
           NS_ABORT_IF_FALSE(eCSSUnit_None != a->Item(typeItem).GetUnit(),
                             "'none' should be handled as identifier value");
           nsString type;
-          a->Item(typeItem).GetStringValue(type);
+          a->Item(typeItem).AppendToString(eCSSProperty_list_style_type,
+                                           type, nsCSSValue::eNormalized);
           if (!type.LowerCaseEqualsLiteral("decimal")) {
             str.AppendLiteral(", ");
-            nsStyleUtil::AppendEscapedCSSIdent(type, str);
+            str.Append(type);
           }
 
           str.Append(char16_t(')'));
           val->SetString(str, nsIDOMCSSPrimitiveValue::CSS_COUNTER);
         }
         break;
       case eStyleContentType_OpenQuote:
         val->SetIdent(eCSSKeyword_open_quote);
@@ -2972,22 +2973,52 @@ nsComputedDOMStyle::DoGetListStylePositi
                                    nsCSSProps::kListStylePositionKTable));
   return val;
 }
 
 CSSValue*
 nsComputedDOMStyle::DoGetListStyleType()
 {
   nsROCSSPrimitiveValue *val = new nsROCSSPrimitiveValue;
-  // want SetIdent
-  nsString type;
-  StyleList()->GetListStyleType(type);
-  nsString value;
-  nsStyleUtil::AppendEscapedCSSIdent(type, value);
-  val->SetString(value);
+  CounterStyle* style = StyleList()->GetCounterStyle();
+  AnonymousCounterStyle* anonymous = style->AsAnonymous();
+  if (!anonymous) {
+    // want SetIdent
+    nsString type;
+    StyleList()->GetListStyleType(type);
+    nsString value;
+    nsStyleUtil::AppendEscapedCSSIdent(type, value);
+    val->SetString(value);
+  } else {
+    nsAutoString tmp;
+    tmp.AppendLiteral("symbols(");
+
+    uint8_t system = anonymous->GetSystem();
+    NS_ASSERTION(system == NS_STYLE_COUNTER_SYSTEM_CYCLIC ||
+                 system == NS_STYLE_COUNTER_SYSTEM_NUMERIC ||
+                 system == NS_STYLE_COUNTER_SYSTEM_ALPHABETIC ||
+                 system == NS_STYLE_COUNTER_SYSTEM_SYMBOLIC ||
+                 system == NS_STYLE_COUNTER_SYSTEM_FIXED,
+                 "Invalid system for anonymous counter style.");
+    if (system != NS_STYLE_COUNTER_SYSTEM_SYMBOLIC) {
+      AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(
+              system, nsCSSProps::kCounterSystemKTable), tmp);
+      tmp.Append(' ');
+    }
+
+    const nsTArray<nsString>& symbols = anonymous->GetSymbols();
+    NS_ASSERTION(symbols.Length() > 0,
+                 "No symbols in the anonymous counter style");
+    for (size_t i = 0, iend = symbols.Length(); i < iend; i++) {
+      nsStyleUtil::AppendEscapedCSSString(symbols[i], tmp);
+      tmp.Append(' ');
+    }
+    tmp.Replace(tmp.Length() - 1, 1, char16_t(')'));
+    val->SetString(tmp);
+  }
   return val;
 }
 
 CSSValue*
 nsComputedDOMStyle::DoGetImageRegion()
 {
   nsROCSSPrimitiveValue *val = new nsROCSSPrimitiveValue;
 
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -7141,16 +7141,21 @@ nsRuleNode::ComputeListData(void* aStart
         default:
           CopyASCIItoUTF16(nsCSSProps::ValueToKeyword(
                   intValue, nsCSSProps::kListStyleKTable), name);
           break;
       }
       list->SetListStyleType(name, mPresContext);
       break;
     }
+    case eCSSUnit_Symbols:
+      list->SetListStyleType(NS_LITERAL_STRING(""),
+                             mPresContext->CounterStyleManager()->
+                               BuildCounterStyle(typeValue->GetArrayValue()));
+      break;
     case eCSSUnit_Null:
       break;
     default:
       NS_NOTREACHED("Unexpected value unit");
   }
 
   // list-style-image: url, none, inherit
   const nsCSSValue* imageValue = aRuleData->ValueForListStyleImage();
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -2349,17 +2349,17 @@ var gCSSProperties = {
     quirks_values: { "000000": "#000000", "96ed2a": "#96ed2a", "fff": "#ffffff", "ffffff": "#ffffff", },
   },
   "content": {
     domProp: "content",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     /* XXX needs to be on pseudo-elements */
     initial_values: [ "normal", "none" ],
-    other_values: [ '""', "''", '"hello"', "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)", "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==')", 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")', 'counter(foo)', 'counter(bar, upper-roman)', 'counters(foo, ".")', "counters(bar, '-', lower-greek)", "'-' counter(foo) '.'", "attr(title)", "open-quote", "close-quote", "no-open-quote", "no-close-quote", "close-quote attr(title) counters(foo, '.', upper-alpha)", "counter(foo, none)", "counters(bar, '.', none)", "attr(\\32)", "attr(\\2)", "attr(-\\2)", "attr(-\\32)", "counter(\\2)", "counters(\\32, '.')", "counter(-\\32, upper-roman)", "counters(-\\2, '-', lower-greek)", "counter(\\()", "counters(a\\+b, '.')", "counter(\\}, upper-alpha)", "-moz-alt-content" ],
+    other_values: [ '""', "''", '"hello"', "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)", "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==')", 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")', 'counter(foo)', 'counter(bar, upper-roman)', 'counters(foo, ".")', "counters(bar, '-', lower-greek)", "'-' counter(foo) '.'", "attr(title)", "open-quote", "close-quote", "no-open-quote", "no-close-quote", "close-quote attr(title) counters(foo, '.', upper-alpha)", "counter(foo, none)", "counters(bar, '.', none)", "attr(\\32)", "attr(\\2)", "attr(-\\2)", "attr(-\\32)", "counter(\\2)", "counters(\\32, '.')", "counter(-\\32, upper-roman)", "counters(-\\2, '-', lower-greek)", "counter(\\()", "counters(a\\+b, '.')", "counter(\\}, upper-alpha)", "-moz-alt-content", "counter(foo, symbols('*'))", "counter(foo, symbols(numeric '0' '1'))", "counters(foo, '.', symbols('*'))", "counters(foo, '.', symbols(numeric '0' '1'))" ],
     invalid_values: [ 'counters(foo)', 'counter(foo, ".")', 'attr("title")', "attr('title')", "attr(2)", "attr(-2)", "counter(2)", "counters(-2, '.')", "-moz-alt-content 'foo'", "'foo' -moz-alt-content" ]
   },
   "counter-increment": {
     domProp: "counterIncrement",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "none" ],
     other_values: [ "foo 1", "bar", "foo 3 bar baz 2", "\\32  1", "-\\32  1", "-c 1", "\\32 1", "-\\32 1", "\\2  1", "-\\2  1", "-c 1", "\\2 1", "-\\2 1", "-\\7f \\9e 1" ],
@@ -2711,16 +2711,22 @@ var gCSSProperties = {
   },
   "list-style": {
     domProp: "listStyle",
     inherited: true,
     type: CSS_TYPE_TRUE_SHORTHAND,
     subproperties: [ "list-style-type", "list-style-position", "list-style-image" ],
     initial_values: [ "outside", "disc", "disc outside", "outside disc", "disc none", "none disc", "none disc outside", "none outside disc", "disc none outside", "disc outside none", "outside none disc", "outside disc none" ],
     other_values: [ "inside none", "none inside", "none none inside", "square", "none", "none none", "outside none none", "none outside none", "none none outside", "none outside", "outside none", "outside outside", "outside inside", "\\32 style", "\\32 style inside",
+      "symbols(\"*\" \"\\2020\" \"\\2021\" \"\\A7\")",
+      "symbols(cyclic \"*\" \"\\2020\" \"\\2021\" \"\\A7\")",
+      "inside symbols(\"*\" \"\\2020\" \"\\2021\" \"\\A7\")",
+      "symbols(\"*\" \"\\2020\" \"\\2021\" \"\\A7\") outside",
+      "none symbols(\"*\" \"\\2020\" \"\\2021\" \"\\A7\")",
+      "inside none symbols(\"*\" \"\\2020\" \"\\2021\" \"\\A7\")",
       'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")',
       'none url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")',
       'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==") none',
       'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==") outside',
       'outside url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")',
       'outside none url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")',
       'outside url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==") none',
       'none url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==") outside',
@@ -2781,17 +2787,19 @@ var gCSSProperties = {
       "-moz-devanagari", "-moz-gurmukhi", "-moz-gujarati",
       "-moz-oriya", "-moz-kannada", "-moz-malayalam", "-moz-bengali",
       "-moz-tamil", "-moz-telugu", "-moz-thai", "-moz-lao",
       "-moz-myanmar", "-moz-khmer",
       "-moz-hangul", "-moz-hangul-consonant",
       "-moz-ethiopic-halehame", "-moz-ethiopic-numeric",
       "-moz-ethiopic-halehame-am",
       "-moz-ethiopic-halehame-ti-er", "-moz-ethiopic-halehame-ti-et",
-      "other-style", "inside", "outside", "\\32 style"
+      "other-style", "inside", "outside", "\\32 style",
+      "symbols(\"*\" \"\\2020\" \"\\2021\" \"\\A7\")",
+      "symbols(cyclic '*' '\\2020' '\\2021' '\\A7')"
     ],
     invalid_values: []
   },
   "margin": {
     domProp: "margin",
     inherited: false,
     type: CSS_TYPE_TRUE_SHORTHAND,
     subproperties: [ "margin-top", "margin-right", "margin-bottom", "margin-left" ],
--- a/layout/style/test/test_counter_descriptor_storage.html
+++ b/layout/style/test/test_counter_descriptor_storage.html
@@ -162,17 +162,17 @@ function test_system_dep_desc() {
       base: "",
       base_tests: {
         system: "extends decimal",
         symbols: "",
         additiveSymbols: ""
       },
       tests: {
         system: [
-          [null, "extends", "fixed", "cyclic"],
+          [null, "extends", "fixed", "cyclic", "extends symbols('*')"],
           ["extends cjk-decimal", "ExTends cjk-decimal", "extends CJK-decimal"],
         ],
         symbols: [
           [null, "x", "x y"],
         ],
         additiveSymbols: [
           [null, "0 x", "1 y, 0 x"],
         ]
@@ -230,23 +230,23 @@ function test_system_indep_desc() {
       [null, "1 -1", "1 -1, 0 100", "-1 1, 100 0"],
     ],
     pad: [
       ["0 \"\"", "\"\" 0"],
       ["1 a", "a 1", "1    a", "\\61  1"],
       [null, "0", "\"\"", "0 0", "a a", "0 a a"],
     ],
     fallback: [
-      [null, "", "-", "0", "a b"],
+      [null, "", "-", "0", "a b", "symbols('*')"],
       ["a"],
       ["A"],
       ["decimal", "Decimal"],
     ],
     speakAs: [
-      [null, "", "-", "0", "a b"],
+      [null, "", "-", "0", "a b", "symbols('*')"],
       ["auto", "AuTo"],
       ["bullets", "BULLETs"],
       ["numbers", "NumBers"],
       ["words", "WordS"],
       // Currently spell-out is not supported, so it should be treated
       // as an invalid value.
       [null, "spell-out", "Spell-Out"],
       ["a"],