tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

CounterStyleManager.cpp (62003B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "CounterStyleManager.h"
      8 
      9 #include <type_traits>
     10 
     11 #include "mozilla/ArenaObjectID.h"
     12 #include "mozilla/ArrayUtils.h"
     13 #include "mozilla/CheckedInt.h"
     14 #include "mozilla/MathAlgorithms.h"
     15 #include "mozilla/PresShell.h"
     16 #include "mozilla/ServoBindings.h"
     17 #include "mozilla/ServoStyleSet.h"
     18 #include "mozilla/WritingModes.h"
     19 #include "nsPresContext.h"
     20 #include "nsPresContextInlines.h"
     21 #include "nsString.h"
     22 #include "nsTArray.h"
     23 #include "nsTHashtable.h"
     24 #include "nsUnicodeProperties.h"
     25 
     26 namespace mozilla {
     27 
     28 using AdditiveSymbol = StyleAdditiveSymbol;
     29 
     30 struct NegativeType {
     31  nsString before, after;
     32 };
     33 
     34 struct PadType {
     35  int32_t width;
     36  nsString symbol;
     37 };
     38 
     39 // This limitation will be applied to some systems, and pad descriptor.
     40 // Any initial representation generated by symbolic or additive which is
     41 // longer than this limitation will be dropped. If any pad is longer
     42 // than this, the whole counter text will be dropped as well.
     43 // The spec requires user agents to support at least 60 Unicode code-
     44 // points for counter text. However, this constant only limits the
     45 // length in 16-bit units. So it has to be at least 120, since code-
     46 // points outside the BMP will need 2 16-bit units.
     47 #define LENGTH_LIMIT 150
     48 
     49 static void SymbolToString(const StyleSymbol& aSymbol, nsAString& aResult) {
     50  if (aSymbol.IsIdent()) {
     51    return aSymbol.AsIdent().AsAtom()->ToString(aResult);
     52  }
     53  MOZ_ASSERT(aSymbol.IsString());
     54  return CopyUTF8toUTF16(aSymbol.AsString().AsString(), aResult);
     55 }
     56 
     57 static size_t SymbolLength(const StyleSymbol& aSymbol) {
     58  if (aSymbol.IsIdent()) {
     59    return aSymbol.AsIdent().AsAtom()->GetLength();
     60  }
     61  MOZ_ASSERT(aSymbol.IsString());
     62  return aSymbol.AsString().AsString().Length();
     63 }
     64 
     65 static bool GetCyclicCounterText(CounterValue aOrdinal, nsAString& aResult,
     66                                 Span<const StyleSymbol> aSymbols) {
     67  MOZ_ASSERT(aSymbols.Length() >= 1, "No symbol available for cyclic counter.");
     68  auto n = CounterValue(aSymbols.Length());
     69  CounterValue index = (aOrdinal - 1) % n;
     70  SymbolToString(aSymbols[index >= 0 ? index : index + n], aResult);
     71  return true;
     72 }
     73 
     74 static bool GetFixedCounterText(CounterValue aOrdinal, nsAString& aResult,
     75                                CounterValue aStart,
     76                                Span<const StyleSymbol> aSymbols) {
     77  CounterValue index = aOrdinal - aStart;
     78  if (index >= 0 && index < CounterValue(aSymbols.Length())) {
     79    SymbolToString(aSymbols[index], aResult);
     80    return true;
     81  }
     82  return false;
     83 }
     84 
     85 static bool GetSymbolicCounterText(CounterValue aOrdinal, nsAString& aResult,
     86                                   Span<const StyleSymbol> aSymbols) {
     87  MOZ_ASSERT(aSymbols.Length() >= 1,
     88             "No symbol available for symbolic counter.");
     89  MOZ_ASSERT(aOrdinal >= 0, "Invalid ordinal.");
     90  if (aOrdinal == 0) {
     91    return false;
     92  }
     93 
     94  aResult.Truncate();
     95  auto n = aSymbols.Length();
     96  const StyleSymbol& symbol = aSymbols[(aOrdinal - 1) % n];
     97  size_t len = (aOrdinal + n - 1) / n;
     98  auto symbolLength = SymbolLength(symbol);
     99  if (symbolLength > 0) {
    100    if (len > LENGTH_LIMIT || symbolLength > LENGTH_LIMIT ||
    101        len * symbolLength > LENGTH_LIMIT) {
    102      return false;
    103    }
    104    nsAutoString str;
    105    SymbolToString(symbol, str);
    106    for (size_t i = 0; i < len; ++i) {
    107      aResult.Append(str);
    108    }
    109  }
    110  return true;
    111 }
    112 
    113 static bool GetAlphabeticCounterText(CounterValue aOrdinal, nsAString& aResult,
    114                                     Span<const StyleSymbol> aSymbols) {
    115  MOZ_ASSERT(aSymbols.Length() >= 2, "Too few symbols for alphabetic counter.");
    116  MOZ_ASSERT(aOrdinal >= 0, "Invalid ordinal.");
    117  if (aOrdinal == 0) {
    118    return false;
    119  }
    120 
    121  auto n = aSymbols.Length();
    122  // The precise length of this array should be
    123  // ceil(log((double) aOrdinal / n * (n - 1) + 1) / log(n)).
    124  // The max length is slightly smaller than which defined below.
    125  AutoTArray<int32_t, std::numeric_limits<CounterValue>::digits> indexes;
    126  while (aOrdinal > 0) {
    127    --aOrdinal;
    128    indexes.AppendElement(aOrdinal % n);
    129    aOrdinal /= n;
    130  }
    131 
    132  aResult.Truncate();
    133  for (auto i = indexes.Length(); i > 0; --i) {
    134    const auto& symbol = aSymbols[indexes[i - 1]];
    135    if (symbol.IsIdent()) {
    136      aResult.Append(nsDependentAtomString(symbol.AsIdent().AsAtom()));
    137    } else {
    138      AppendUTF8toUTF16(symbol.AsString().AsString(), aResult);
    139    }
    140  }
    141  return true;
    142 }
    143 
    144 static bool GetNumericCounterText(CounterValue aOrdinal, nsAString& aResult,
    145                                  Span<const StyleSymbol> aSymbols) {
    146  MOZ_ASSERT(aSymbols.Length() >= 2, "Too few symbols for numeric counter.");
    147  MOZ_ASSERT(aOrdinal >= 0, "Invalid ordinal.");
    148 
    149  if (aOrdinal == 0) {
    150    SymbolToString(aSymbols[0], aResult);
    151    return true;
    152  }
    153 
    154  auto n = aSymbols.Length();
    155  AutoTArray<int32_t, std::numeric_limits<CounterValue>::digits> indexes;
    156  while (aOrdinal > 0) {
    157    indexes.AppendElement(aOrdinal % n);
    158    aOrdinal /= n;
    159  }
    160 
    161  aResult.Truncate();
    162  for (auto i = indexes.Length(); i > 0; --i) {
    163    const auto& symbol = aSymbols[indexes[i - 1]];
    164    if (symbol.IsIdent()) {
    165      aResult.Append(nsDependentAtomString(symbol.AsIdent().AsAtom()));
    166    } else {
    167      AppendUTF8toUTF16(symbol.AsString().AsString(), aResult);
    168    }
    169  }
    170  return true;
    171 }
    172 
    173 static bool GetAdditiveCounterText(CounterValue aOrdinal, nsAString& aResult,
    174                                   Span<const AdditiveSymbol> aSymbols) {
    175  MOZ_ASSERT(aOrdinal >= 0, "Invalid ordinal.");
    176 
    177  if (aOrdinal == 0) {
    178    const AdditiveSymbol& last = aSymbols[aSymbols.Length() - 1];
    179    if (last.weight == 0) {
    180      aResult = last.symbol;
    181      return true;
    182    }
    183    return false;
    184  }
    185 
    186  aResult.Truncate();
    187  size_t length = 0;
    188  for (size_t i = 0, iEnd = aSymbols.Length(); i < iEnd; ++i) {
    189    const AdditiveSymbol& symbol = aSymbols[i];
    190    if (symbol.weight == 0) {
    191      break;
    192    }
    193    CounterValue times = aOrdinal / symbol.weight;
    194    if (times > 0) {
    195      auto symbolLength = symbol.symbol.Length();
    196      if (symbolLength > 0) {
    197        length += times * symbolLength;
    198        if (times > LENGTH_LIMIT || symbolLength > LENGTH_LIMIT ||
    199            length > LENGTH_LIMIT) {
    200          return false;
    201        }
    202        for (CounterValue j = 0; j < times; ++j) {
    203          aResult.Append(symbol.symbol);
    204        }
    205      }
    206      aOrdinal -= times * symbol.weight;
    207    }
    208  }
    209  return aOrdinal == 0;
    210 }
    211 
    212 static bool DecimalToText(CounterValue aOrdinal, nsAString& aResult) {
    213  aResult.AppendInt(aOrdinal);
    214  return true;
    215 }
    216 
    217 // We know cjk-ideographic need 31 characters to display 99,999,999,999,999,999
    218 // georgian needs 6 at most
    219 // armenian needs 12 at most
    220 // hebrew may need more...
    221 
    222 #define NUM_BUF_SIZE 34
    223 
    224 enum CJKIdeographicLang { CHINESE, KOREAN, JAPANESE };
    225 struct CJKIdeographicData {
    226  char16_t digit[10];
    227  char16_t unit[3];
    228  char16_t unit10K[2];
    229  uint8_t lang;
    230  bool informal;
    231 };
    232 static const CJKIdeographicData gDataJapaneseInformal = {
    233    {0x3007, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
    234     0x4e5d},                  // digit
    235    {0x5341, 0x767e, 0x5343},  // unit
    236    {0x4e07, 0x5104},          // unit10K
    237    JAPANESE,                  // lang
    238    true                       // informal
    239 };
    240 static const CJKIdeographicData gDataJapaneseFormal = {
    241    {0x96f6, 0x58f1, 0x5f10, 0x53c2, 0x56db, 0x4f0d, 0x516d, 0x4e03, 0x516b,
    242     0x4e5d},                  // digit
    243    {0x62fe, 0x767e, 0x9621},  // unit
    244    {0x842c, 0x5104},          // unit10K
    245    JAPANESE,                  // lang
    246    false                      // informal
    247 };
    248 static const CJKIdeographicData gDataKoreanHangulFormal = {
    249    {0xc601, 0xc77c, 0xc774, 0xc0bc, 0xc0ac, 0xc624, 0xc721, 0xce60, 0xd314,
    250     0xad6c},                  // digit
    251    {0xc2ed, 0xbc31, 0xcc9c},  // unit
    252    {0xb9cc, 0xc5b5},          // unit10K
    253    KOREAN,                    // lang
    254    false                      // informal
    255 };
    256 static const CJKIdeographicData gDataKoreanHanjaInformal = {
    257    {0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
    258     0x4e5d},                  // digit
    259    {0x5341, 0x767e, 0x5343},  // unit
    260    {0x842c, 0x5104},          // unit10K
    261    KOREAN,                    // lang
    262    true                       // informal
    263 };
    264 static const CJKIdeographicData gDataKoreanHanjaFormal = {
    265    {0x96f6, 0x58f9, 0x8cb3, 0x53c3, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
    266     0x4e5d},                  // digit
    267    {0x62fe, 0x767e, 0x4edf},  // unit
    268    {0x842c, 0x5104},          // unit10K
    269    KOREAN,                    // lang
    270    false                      // informal
    271 };
    272 static const CJKIdeographicData gDataSimpChineseInformal = {
    273    {0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
    274     0x4e5d},                  // digit
    275    {0x5341, 0x767e, 0x5343},  // unit
    276    {0x4e07, 0x4ebf},          // unit10K
    277    CHINESE,                   // lang
    278    true                       // informal
    279 };
    280 static const CJKIdeographicData gDataSimpChineseFormal = {
    281    {0x96f6, 0x58f9, 0x8d30, 0x53c1, 0x8086, 0x4f0d, 0x9646, 0x67d2, 0x634c,
    282     0x7396},                  // digit
    283    {0x62fe, 0x4f70, 0x4edf},  // unit
    284    {0x4e07, 0x4ebf},          // unit10K
    285    CHINESE,                   // lang
    286    false                      // informal
    287 };
    288 static const CJKIdeographicData gDataTradChineseInformal = {
    289    {0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
    290     0x4e5d},                  // digit
    291    {0x5341, 0x767e, 0x5343},  // unit
    292    {0x842c, 0x5104},          // unit10K
    293    CHINESE,                   // lang
    294    true                       // informal
    295 };
    296 static const CJKIdeographicData gDataTradChineseFormal = {
    297    {0x96f6, 0x58f9, 0x8cb3, 0x53c3, 0x8086, 0x4f0d, 0x9678, 0x67d2, 0x634c,
    298     0x7396},                  // digit
    299    {0x62fe, 0x4f70, 0x4edf},  // unit
    300    {0x842c, 0x5104},          // unit10K
    301    CHINESE,                   // lang
    302    false                      // informal
    303 };
    304 
    305 static bool CJKIdeographicToText(CounterValue aOrdinal, nsAString& aResult,
    306                                 const CJKIdeographicData& data) {
    307  NS_ASSERTION(aOrdinal >= 0, "Only accept non-negative ordinal");
    308  char16_t buf[NUM_BUF_SIZE];
    309  int32_t idx = NUM_BUF_SIZE;
    310  int32_t pos = 0;
    311  bool needZero = (aOrdinal == 0);
    312  int32_t unitidx = 0, unit10Kidx = 0;
    313  do {
    314    unitidx = pos % 4;
    315    if (unitidx == 0) {
    316      unit10Kidx = pos / 4;
    317    }
    318    auto cur = static_cast<std::make_unsigned_t<CounterValue>>(aOrdinal) % 10;
    319    if (cur == 0) {
    320      if (needZero) {
    321        needZero = false;
    322        buf[--idx] = data.digit[0];
    323      }
    324    } else {
    325      if (data.lang == CHINESE) {
    326        needZero = true;
    327      }
    328      if (unit10Kidx != 0) {
    329        if (data.lang == KOREAN && idx != NUM_BUF_SIZE) {
    330          buf[--idx] = ' ';
    331        }
    332        buf[--idx] = data.unit10K[unit10Kidx - 1];
    333      }
    334      if (unitidx != 0) {
    335        buf[--idx] = data.unit[unitidx - 1];
    336      }
    337      if (cur != 1) {
    338        buf[--idx] = data.digit[cur];
    339      } else {
    340        bool needOne = true;
    341        if (data.informal) {
    342          switch (data.lang) {
    343            case CHINESE:
    344              if (unitidx == 1 &&
    345                  (aOrdinal == 1 || (pos > 4 && aOrdinal % 1000 == 1))) {
    346                needOne = false;
    347              }
    348              break;
    349            case JAPANESE:
    350              if (unitidx > 0 &&
    351                  (unitidx != 3 || (pos == 3 && aOrdinal == 1))) {
    352                needOne = false;
    353              }
    354              break;
    355            case KOREAN:
    356              if (unitidx > 0 || (pos == 4 && (aOrdinal % 1000) == 1)) {
    357                needOne = false;
    358              }
    359              break;
    360          }
    361        }
    362        if (needOne) {
    363          buf[--idx] = data.digit[1];
    364        }
    365      }
    366      unit10Kidx = 0;
    367    }
    368    aOrdinal /= 10;
    369    pos++;
    370  } while (aOrdinal > 0);
    371  aResult.Assign(buf + idx, NUM_BUF_SIZE - idx);
    372  return true;
    373 }
    374 
    375 #define HEBREW_GERESH 0x05F3
    376 static const char16_t gHebrewDigit[22] = {
    377    //   1       2       3       4       5       6       7       8       9
    378    0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, 0x05D8,
    379    //  10      20      30      40      50      60      70      80      90
    380    0x05D9, 0x05DB, 0x05DC, 0x05DE, 0x05E0, 0x05E1, 0x05E2, 0x05E4, 0x05E6,
    381    // 100     200     300     400
    382    0x05E7, 0x05E8, 0x05E9, 0x05EA};
    383 
    384 static bool HebrewToText(CounterValue aOrdinal, nsAString& aResult) {
    385  if (aOrdinal < 1 || aOrdinal > 999999) {
    386    return false;
    387  }
    388 
    389  bool outputSep = false;
    390  nsAutoString allText, thousandsGroup;
    391  do {
    392    thousandsGroup.Truncate();
    393    int32_t n3 = aOrdinal % 1000;
    394    // Process digit for 100 - 900
    395    for (int32_t n1 = 400; n1 > 0;) {
    396      if (n3 >= n1) {
    397        n3 -= n1;
    398        thousandsGroup.Append(gHebrewDigit[(n1 / 100) - 1 + 18]);
    399      } else {
    400        n1 -= 100;
    401      }  // if
    402    }  // for
    403 
    404    // Process digit for 10 - 90
    405    int32_t n2;
    406    if (n3 >= 10) {
    407      // Special process for 15 and 16
    408      if ((15 == n3) || (16 == n3)) {
    409        // Special rule for religious reason...
    410        // 15 is represented by 9 and 6, not 10 and 5
    411        // 16 is represented by 9 and 7, not 10 and 6
    412        n2 = 9;
    413        thousandsGroup.Append(gHebrewDigit[n2 - 1]);
    414      } else {
    415        n2 = n3 - (n3 % 10);
    416        thousandsGroup.Append(gHebrewDigit[(n2 / 10) - 1 + 9]);
    417      }  // if
    418      n3 -= n2;
    419    }  // if
    420 
    421    // Process digit for 1 - 9
    422    if (n3 > 0) {
    423      thousandsGroup.Append(gHebrewDigit[n3 - 1]);
    424    }
    425    if (outputSep) {
    426      thousandsGroup.Append((char16_t)HEBREW_GERESH);
    427    }
    428    if (allText.IsEmpty()) {
    429      allText = thousandsGroup;
    430    } else {
    431      allText = thousandsGroup + allText;
    432    }
    433    aOrdinal /= 1000;
    434    outputSep = true;
    435  } while (aOrdinal >= 1);
    436 
    437  aResult = allText;
    438  return true;
    439 }
    440 
    441 // Convert ordinal to Ethiopic numeric representation.
    442 // The detail is available at http://www.ethiopic.org/Numerals/
    443 // The algorithm used here is based on the pseudo-code put up there by
    444 // Daniel Yacob <yacob@geez.org>.
    445 // Another reference is Unicode 3.0 standard section 11.1.
    446 #define ETHIOPIC_ONE 0x1369
    447 #define ETHIOPIC_TEN 0x1372
    448 #define ETHIOPIC_HUNDRED 0x137B
    449 #define ETHIOPIC_TEN_THOUSAND 0x137C
    450 
    451 static bool EthiopicToText(CounterValue aOrdinal, nsAString& aResult) {
    452  if (aOrdinal < 1) {
    453    return false;
    454  }
    455 
    456  nsAutoString asciiNumberString;  // decimal string representation of ordinal
    457  DecimalToText(aOrdinal, asciiNumberString);
    458  uint8_t asciiStringLength = asciiNumberString.Length();
    459 
    460  // If number length is odd, add a leading "0"
    461  // the leading "0" preconditions the string to always have the
    462  // leading tens place populated, this avoids a check within the loop.
    463  // If we didn't add the leading "0", decrement asciiStringLength so
    464  // it will be equivalent to a zero-based index in both cases.
    465  if (asciiStringLength & 1) {
    466    asciiNumberString.InsertLiteral(u"0", 0);
    467  } else {
    468    asciiStringLength--;
    469  }
    470 
    471  aResult.Truncate();
    472  // Iterate from the highest digits to lowest
    473  // indexFromLeft       indexes digits (0 = most significant)
    474  // groupIndexFromRight indexes pairs of digits (0 = least significant)
    475  for (uint8_t indexFromLeft = 0, groupIndexFromRight = asciiStringLength >> 1;
    476       indexFromLeft <= asciiStringLength;
    477       indexFromLeft += 2, groupIndexFromRight--) {
    478    uint8_t tensValue = asciiNumberString.CharAt(indexFromLeft) & 0x0F;
    479    uint8_t unitsValue = asciiNumberString.CharAt(indexFromLeft + 1) & 0x0F;
    480    uint8_t groupValue = tensValue * 10 + unitsValue;
    481 
    482    bool oddGroup = (groupIndexFromRight & 1);
    483 
    484    // we want to clear ETHIOPIC_ONE when it is superfluous
    485    if (aOrdinal > 1 && groupValue == 1 &&  // one without a leading ten
    486        (oddGroup ||
    487         indexFromLeft == 0)) {  // preceding (100) or leading the sequence
    488      unitsValue = 0;
    489    }
    490 
    491    // put it all together...
    492    if (tensValue) {
    493      // map onto Ethiopic "tens":
    494      aResult.Append((char16_t)(tensValue + ETHIOPIC_TEN - 1));
    495    }
    496    if (unitsValue) {
    497      // map onto Ethiopic "units":
    498      aResult.Append((char16_t)(unitsValue + ETHIOPIC_ONE - 1));
    499    }
    500    // Add a separator for all even groups except the last,
    501    // and for odd groups with non-zero value.
    502    if (oddGroup) {
    503      if (groupValue) {
    504        aResult.Append((char16_t)ETHIOPIC_HUNDRED);
    505      }
    506    } else {
    507      if (groupIndexFromRight) {
    508        aResult.Append((char16_t)ETHIOPIC_TEN_THOUSAND);
    509      }
    510    }
    511  }
    512  return true;
    513 }
    514 
    515 static SpeakAs GetDefaultSpeakAsForSystem(StyleCounterSystem aSystem) {
    516  MOZ_ASSERT(aSystem != StyleCounterSystem::Extends,
    517             "Extends system does not have static default speak-as");
    518  switch (aSystem) {
    519    case StyleCounterSystem::Alphabetic:
    520      return SpeakAs::Spellout;
    521    case StyleCounterSystem::Cyclic:
    522      return SpeakAs::Bullets;
    523    default:
    524      return SpeakAs::Numbers;
    525  }
    526 }
    527 
    528 static bool SystemUsesNegativeSign(StyleCounterSystem aSystem) {
    529  MOZ_ASSERT(aSystem != StyleCounterSystem::Extends,
    530             "Cannot check this for extending style");
    531  switch (aSystem) {
    532    case StyleCounterSystem::Symbolic:
    533    case StyleCounterSystem::Alphabetic:
    534    case StyleCounterSystem::Numeric:
    535    case StyleCounterSystem::Additive:
    536      return true;
    537    default:
    538      return false;
    539  }
    540 }
    541 
    542 class BuiltinCounterStyle : public CounterStyle {
    543 public:
    544  constexpr BuiltinCounterStyle(ListStyle aStyle, nsStaticAtom* aName)
    545      : CounterStyle(aStyle), mName(aName) {}
    546 
    547  nsStaticAtom* GetStyleName() const { return mName; }
    548 
    549  void GetPrefix(nsAString& aResult) override;
    550  void GetSuffix(nsAString& aResult) override;
    551  void GetSpokenCounterText(CounterValue aOrdinal, WritingMode aWritingMode,
    552                            nsAString& aResult, bool& aIsBullet) override;
    553  bool IsBullet() override;
    554 
    555  void GetNegative(NegativeType& aResult) override;
    556  bool IsOrdinalInRange(CounterValue aOrdinal) override;
    557  bool IsOrdinalInAutoRange(CounterValue aOrdinal) override;
    558  void GetPad(PadType& aResult) override;
    559  CounterStyle* GetFallback() override;
    560  SpeakAs GetSpeakAs() override;
    561  bool UseNegativeSign() override;
    562 
    563  bool GetInitialCounterText(CounterValue aOrdinal, WritingMode aWritingMode,
    564                             nsAString& aResult, bool& aIsRTL) override;
    565 
    566 protected:
    567  constexpr BuiltinCounterStyle(const BuiltinCounterStyle& aOther)
    568      : CounterStyle(aOther.mStyle), mName(aOther.mName) {}
    569 
    570 private:
    571  nsStaticAtom* mName;
    572 };
    573 
    574 /* virtual */
    575 void BuiltinCounterStyle::GetPrefix(nsAString& aResult) { aResult.Truncate(); }
    576 
    577 /* virtual */
    578 void BuiltinCounterStyle::GetSuffix(nsAString& aResult) {
    579  switch (mStyle) {
    580    case ListStyle::None:
    581      aResult.Truncate();
    582      break;
    583 
    584    case ListStyle::Disc:
    585    case ListStyle::Circle:
    586    case ListStyle::Square:
    587    case ListStyle::DisclosureClosed:
    588    case ListStyle::DisclosureOpen:
    589    case ListStyle::EthiopicNumeric:
    590      aResult = ' ';
    591      break;
    592 
    593    case ListStyle::TradChineseInformal:
    594    case ListStyle::TradChineseFormal:
    595    case ListStyle::SimpChineseInformal:
    596    case ListStyle::SimpChineseFormal:
    597    case ListStyle::JapaneseInformal:
    598    case ListStyle::JapaneseFormal:
    599      aResult = 0x3001;
    600      break;
    601 
    602    case ListStyle::KoreanHangulFormal:
    603    case ListStyle::KoreanHanjaInformal:
    604    case ListStyle::KoreanHanjaFormal:
    605      aResult.AssignLiteral(u", ");
    606      break;
    607 
    608    default:
    609      aResult.AssignLiteral(u". ");
    610      break;
    611  }
    612 }
    613 
    614 static const char16_t kDiscCharacter = 0x2022;
    615 static const char16_t kCircleCharacter = 0x25e6;
    616 static const char16_t kSquareCharacter = 0x25aa;
    617 static const char16_t kRightPointingCharacter = 0x25b8;
    618 static const char16_t kLeftPointingCharacter = 0x25c2;
    619 static const char16_t kDownPointingCharacter = 0x25be;
    620 static const char16_t kUpPointingCharacter = 0x25b4;
    621 
    622 /* virtual */
    623 void BuiltinCounterStyle::GetSpokenCounterText(CounterValue aOrdinal,
    624                                               WritingMode aWritingMode,
    625                                               nsAString& aResult,
    626                                               bool& aIsBullet) {
    627  switch (mStyle) {
    628    case ListStyle::None:
    629    case ListStyle::Disc:
    630    case ListStyle::Circle:
    631    case ListStyle::Square:
    632    case ListStyle::DisclosureClosed:
    633    case ListStyle::DisclosureOpen: {
    634      // Same as the initial representation
    635      bool isRTL;
    636      GetInitialCounterText(aOrdinal, aWritingMode, aResult, isRTL);
    637      aIsBullet = true;
    638      break;
    639    }
    640    default:
    641      CounterStyle::GetSpokenCounterText(aOrdinal, aWritingMode, aResult,
    642                                         aIsBullet);
    643      break;
    644  }
    645 }
    646 
    647 /* virtual */
    648 bool BuiltinCounterStyle::IsBullet() {
    649  switch (mStyle) {
    650    case ListStyle::Disc:
    651    case ListStyle::Circle:
    652    case ListStyle::Square:
    653    case ListStyle::DisclosureClosed:
    654    case ListStyle::DisclosureOpen:
    655      return true;
    656    default:
    657      return false;
    658  }
    659 }
    660 
    661 static const char16_t gJapaneseNegative[] = {0x30de, 0x30a4, 0x30ca, 0x30b9,
    662                                             0x0000};
    663 static const char16_t gKoreanNegative[] = {0xb9c8, 0xc774, 0xb108,
    664                                           0xc2a4, 0x0020, 0x0000};
    665 static const char16_t gSimpChineseNegative[] = {0x8d1f, 0x0000};
    666 static const char16_t gTradChineseNegative[] = {0x8ca0, 0x0000};
    667 
    668 /* virtual */
    669 void BuiltinCounterStyle::GetNegative(NegativeType& aResult) {
    670  switch (mStyle) {
    671    case ListStyle::JapaneseFormal:
    672    case ListStyle::JapaneseInformal:
    673      aResult.before = gJapaneseNegative;
    674      break;
    675 
    676    case ListStyle::KoreanHangulFormal:
    677    case ListStyle::KoreanHanjaInformal:
    678    case ListStyle::KoreanHanjaFormal:
    679      aResult.before = gKoreanNegative;
    680      break;
    681 
    682    case ListStyle::SimpChineseFormal:
    683    case ListStyle::SimpChineseInformal:
    684      aResult.before = gSimpChineseNegative;
    685      break;
    686 
    687    case ListStyle::TradChineseFormal:
    688    case ListStyle::TradChineseInformal:
    689      aResult.before = gTradChineseNegative;
    690      break;
    691 
    692    default:
    693      aResult.before.AssignLiteral(u"-");
    694  }
    695  aResult.after.Truncate();
    696 }
    697 
    698 /* virtual */
    699 bool BuiltinCounterStyle::IsOrdinalInRange(CounterValue aOrdinal) {
    700  switch (mStyle) {
    701    default:
    702    // cyclic
    703    case ListStyle::None:
    704    case ListStyle::Disc:
    705    case ListStyle::Circle:
    706    case ListStyle::Square:
    707    case ListStyle::DisclosureClosed:
    708    case ListStyle::DisclosureOpen:
    709    // use DecimalToText
    710    case ListStyle::Decimal:
    711    // use CJKIdeographicToText
    712    case ListStyle::JapaneseFormal:
    713    case ListStyle::JapaneseInformal:
    714    case ListStyle::KoreanHanjaFormal:
    715    case ListStyle::KoreanHanjaInformal:
    716    case ListStyle::KoreanHangulFormal:
    717    case ListStyle::TradChineseFormal:
    718    case ListStyle::TradChineseInformal:
    719    case ListStyle::SimpChineseFormal:
    720    case ListStyle::SimpChineseInformal:
    721      return true;
    722 
    723    // use EthiopicToText
    724    case ListStyle::EthiopicNumeric:
    725      return aOrdinal >= 1;
    726 
    727    // use HebrewToText
    728    case ListStyle::Hebrew:
    729      return aOrdinal >= 1 && aOrdinal <= 999999;
    730  }
    731 }
    732 
    733 /* virtual */
    734 bool BuiltinCounterStyle::IsOrdinalInAutoRange(CounterValue aOrdinal) {
    735  switch (mStyle) {
    736    // cyclic:
    737    case ListStyle::None:
    738    case ListStyle::Disc:
    739    case ListStyle::Circle:
    740    case ListStyle::Square:
    741    case ListStyle::DisclosureClosed:
    742    case ListStyle::DisclosureOpen:
    743    // numeric:
    744    case ListStyle::Decimal:
    745      return true;
    746 
    747    // additive:
    748    case ListStyle::Hebrew:
    749      return aOrdinal >= 0;
    750 
    751    // complex predefined:
    752    case ListStyle::JapaneseFormal:
    753    case ListStyle::JapaneseInformal:
    754    case ListStyle::KoreanHanjaFormal:
    755    case ListStyle::KoreanHanjaInformal:
    756    case ListStyle::KoreanHangulFormal:
    757    case ListStyle::TradChineseFormal:
    758    case ListStyle::TradChineseInformal:
    759    case ListStyle::SimpChineseFormal:
    760    case ListStyle::SimpChineseInformal:
    761    case ListStyle::EthiopicNumeric:
    762      return IsOrdinalInRange(aOrdinal);
    763 
    764    default:
    765      MOZ_ASSERT_UNREACHABLE("Unknown counter style");
    766      return false;
    767  }
    768 }
    769 
    770 /* virtual */
    771 void BuiltinCounterStyle::GetPad(PadType& aResult) {
    772  aResult.width = 0;
    773  aResult.symbol.Truncate();
    774 }
    775 
    776 /* virtual */
    777 CounterStyle* BuiltinCounterStyle::GetFallback() {
    778  // Fallback of dependent builtin counter styles are handled in class
    779  // DependentBuiltinCounterStyle.
    780  return CounterStyleManager::GetDecimalStyle();
    781 }
    782 
    783 /* virtual */
    784 SpeakAs BuiltinCounterStyle::GetSpeakAs() {
    785  switch (mStyle) {
    786    case ListStyle::None:
    787    case ListStyle::Disc:
    788    case ListStyle::Circle:
    789    case ListStyle::Square:
    790    case ListStyle::DisclosureClosed:
    791    case ListStyle::DisclosureOpen:
    792      return SpeakAs::Bullets;
    793    default:
    794      return SpeakAs::Numbers;
    795  }
    796 }
    797 
    798 /* virtual */
    799 bool BuiltinCounterStyle::UseNegativeSign() {
    800  switch (mStyle) {
    801    case ListStyle::None:
    802    case ListStyle::Disc:
    803    case ListStyle::Circle:
    804    case ListStyle::Square:
    805    case ListStyle::DisclosureClosed:
    806    case ListStyle::DisclosureOpen:
    807      return false;
    808    default:
    809      return true;
    810  }
    811 }
    812 
    813 /* virtual */
    814 bool BuiltinCounterStyle::GetInitialCounterText(CounterValue aOrdinal,
    815                                                WritingMode aWritingMode,
    816                                                nsAString& aResult,
    817                                                bool& aIsRTL) {
    818  aIsRTL = false;
    819  switch (mStyle) {
    820    // used by counters & extends counter-style code only
    821    // XXX We really need to do this the same way we do list bullets.
    822    case ListStyle::None:
    823      aResult.Truncate();
    824      return true;
    825    case ListStyle::Disc:
    826      aResult.Assign(kDiscCharacter);
    827      return true;
    828    case ListStyle::Circle:
    829      aResult.Assign(kCircleCharacter);
    830      return true;
    831    case ListStyle::Square:
    832      aResult.Assign(kSquareCharacter);
    833      return true;
    834    case ListStyle::DisclosureClosed:
    835      if (aWritingMode.IsVertical()) {
    836        if (aWritingMode.IsBidiLTR()) {
    837          aResult.Assign(kDownPointingCharacter);
    838        } else {
    839          aResult.Assign(kUpPointingCharacter);
    840        }
    841      } else if (aWritingMode.IsBidiLTR()) {
    842        aResult.Assign(kRightPointingCharacter);
    843      } else {
    844        aResult.Assign(kLeftPointingCharacter);
    845      }
    846      return true;
    847    case ListStyle::DisclosureOpen:
    848      if (!aWritingMode.IsVertical()) {
    849        aResult.Assign(kDownPointingCharacter);
    850      } else if (aWritingMode.IsVerticalLR()) {
    851        aResult.Assign(kRightPointingCharacter);
    852      } else {
    853        aResult.Assign(kLeftPointingCharacter);
    854      }
    855      return true;
    856 
    857    case ListStyle::Decimal:
    858      return DecimalToText(aOrdinal, aResult);
    859 
    860    case ListStyle::TradChineseInformal:
    861      return CJKIdeographicToText(aOrdinal, aResult, gDataTradChineseInformal);
    862    case ListStyle::TradChineseFormal:
    863      return CJKIdeographicToText(aOrdinal, aResult, gDataTradChineseFormal);
    864    case ListStyle::SimpChineseInformal:
    865      return CJKIdeographicToText(aOrdinal, aResult, gDataSimpChineseInformal);
    866    case ListStyle::SimpChineseFormal:
    867      return CJKIdeographicToText(aOrdinal, aResult, gDataSimpChineseFormal);
    868    case ListStyle::JapaneseInformal:
    869      return CJKIdeographicToText(aOrdinal, aResult, gDataJapaneseInformal);
    870    case ListStyle::JapaneseFormal:
    871      return CJKIdeographicToText(aOrdinal, aResult, gDataJapaneseFormal);
    872    case ListStyle::KoreanHangulFormal:
    873      return CJKIdeographicToText(aOrdinal, aResult, gDataKoreanHangulFormal);
    874    case ListStyle::KoreanHanjaInformal:
    875      return CJKIdeographicToText(aOrdinal, aResult, gDataKoreanHanjaInformal);
    876    case ListStyle::KoreanHanjaFormal:
    877      return CJKIdeographicToText(aOrdinal, aResult, gDataKoreanHanjaFormal);
    878 
    879    case ListStyle::Hebrew:
    880      aIsRTL = true;
    881      return HebrewToText(aOrdinal, aResult);
    882 
    883    case ListStyle::EthiopicNumeric:
    884      return EthiopicToText(aOrdinal, aResult);
    885 
    886    default:
    887      MOZ_ASSERT_UNREACHABLE("Unknown builtin counter style");
    888      return false;
    889  }
    890 }
    891 
    892 static constexpr BuiltinCounterStyle gBuiltinStyleTable[] = {
    893 #define BUILTIN_COUNTER_STYLE(value_, atom_) \
    894  {ListStyle::value_, nsGkAtoms::atom_},
    895 #include "BuiltinCounterStyleList.h"
    896 #undef BUILTIN_COUNTER_STYLE
    897 };
    898 
    899 #define BUILTIN_COUNTER_STYLE(value_, atom_)                                   \
    900  static_assert(                                                               \
    901      gBuiltinStyleTable[static_cast<size_t>(ListStyle::value_)].GetStyle() == \
    902          ListStyle::value_,                                                   \
    903      "Builtin counter style " #atom_ " has unmatched index and value.");
    904 #include "BuiltinCounterStyleList.h"
    905 #undef BUILTIN_COUNTER_STYLE
    906 
    907 class DependentBuiltinCounterStyle final : public BuiltinCounterStyle {
    908 public:
    909  DependentBuiltinCounterStyle(ListStyle aStyle, CounterStyleManager* aManager)
    910      : BuiltinCounterStyle(gBuiltinStyleTable[static_cast<size_t>(aStyle)]),
    911        mManager(aManager) {
    912    NS_ASSERTION(IsDependentStyle(), "Not a dependent builtin style");
    913    MOZ_ASSERT(!IsCustomStyle(), "Not a builtin style");
    914  }
    915 
    916  virtual CounterStyle* GetFallback() override;
    917 
    918  void* operator new(size_t sz, nsPresContext* aPresContext) {
    919    return aPresContext->PresShell()->AllocateByObjectID(
    920        eArenaObjectID_DependentBuiltinCounterStyle, sz);
    921  }
    922 
    923  void Destroy() {
    924    PresShell* presShell = mManager->PresContext()->PresShell();
    925    this->~DependentBuiltinCounterStyle();
    926    presShell->FreeByObjectID(eArenaObjectID_DependentBuiltinCounterStyle,
    927                              this);
    928  }
    929 
    930 private:
    931  ~DependentBuiltinCounterStyle() = default;
    932 
    933  CounterStyleManager* mManager;
    934 };
    935 
    936 /* virtual */
    937 CounterStyle* DependentBuiltinCounterStyle::GetFallback() {
    938  switch (GetStyle()) {
    939    case ListStyle::JapaneseInformal:
    940    case ListStyle::JapaneseFormal:
    941    case ListStyle::KoreanHangulFormal:
    942    case ListStyle::KoreanHanjaInformal:
    943    case ListStyle::KoreanHanjaFormal:
    944    case ListStyle::SimpChineseInformal:
    945    case ListStyle::SimpChineseFormal:
    946    case ListStyle::TradChineseInformal:
    947    case ListStyle::TradChineseFormal:
    948      // These styles all have a larger range than cjk-decimal, so the
    949      // only case fallback is accessed is that they are extended.
    950      // Since extending styles will cache the data themselves, we need
    951      // not cache it here.
    952      return mManager->ResolveCounterStyle(nsGkAtoms::cjk_decimal);
    953    default:
    954      MOZ_ASSERT_UNREACHABLE("Not a valid dependent builtin style");
    955      return BuiltinCounterStyle::GetFallback();
    956  }
    957 }
    958 
    959 class CustomCounterStyle final : public CounterStyle {
    960 public:
    961  CustomCounterStyle(CounterStyleManager* aManager,
    962                     const StyleLockedCounterStyleRule* aRule)
    963      : CounterStyle(ListStyle::Custom),
    964        mManager(aManager),
    965        mRule(aRule),
    966        mRuleGeneration(Servo_CounterStyleRule_GetGeneration(aRule)),
    967        mSystem(Servo_CounterStyleRule_GetSystem(aRule)),
    968        mFlags(0),
    969        mFallback(nullptr),
    970        mSpeakAsCounter(nullptr),
    971        mExtends(nullptr),
    972        mExtendsRoot(nullptr) {}
    973 
    974  // This method will clear all cached data in the style and update the
    975  // generation number of the rule. It should be called when the rule of
    976  // this style is changed.
    977  void ResetCachedData();
    978 
    979  // This method will reset all cached data which may depend on other
    980  // counter style. It will reset all pointers to other counter styles.
    981  // For counter style extends other, in addition, all fields will be
    982  // reset to uninitialized state. This method should be called when any
    983  // other counter style is added, removed, or changed.
    984  void ResetDependentData();
    985 
    986  const StyleLockedCounterStyleRule* GetRule() const { return mRule; }
    987  uint32_t GetRuleGeneration() const { return mRuleGeneration; }
    988 
    989  void GetPrefix(nsAString& aResult) override;
    990  void GetSuffix(nsAString& aResult) override;
    991  void GetSpokenCounterText(CounterValue aOrdinal, WritingMode aWritingMode,
    992                            nsAString& aResult, bool& aIsBullet) override;
    993  bool IsBullet() override;
    994 
    995  void GetNegative(NegativeType& aResult) override;
    996  bool IsOrdinalInRange(CounterValue aOrdinal) override;
    997  bool IsOrdinalInAutoRange(CounterValue aOrdinal) override;
    998  void GetPad(PadType& aResult) override;
    999  CounterStyle* GetFallback() override;
   1000  SpeakAs GetSpeakAs() override;
   1001  bool UseNegativeSign() override;
   1002 
   1003  void CallFallbackStyle(CounterValue aOrdinal, WritingMode aWritingMode,
   1004                         nsAString& aResult, bool& aIsRTL) override;
   1005  bool GetInitialCounterText(CounterValue aOrdinal, WritingMode aWritingMode,
   1006                             nsAString& aResult, bool& aIsRTL) override;
   1007 
   1008  bool IsExtendsSystem() { return mSystem == StyleCounterSystem::Extends; }
   1009 
   1010  void* operator new(size_t sz, nsPresContext* aPresContext) {
   1011    return aPresContext->PresShell()->AllocateByObjectID(
   1012        eArenaObjectID_CustomCounterStyle, sz);
   1013  }
   1014 
   1015  void Destroy() {
   1016    PresShell* presShell = mManager->PresContext()->PresShell();
   1017    this->~CustomCounterStyle();
   1018    presShell->FreeByObjectID(eArenaObjectID_CustomCounterStyle, this);
   1019  }
   1020 
   1021 private:
   1022  ~CustomCounterStyle() = default;
   1023 
   1024  Span<const StyleSymbol> GetSymbols();
   1025  Span<const AdditiveSymbol> GetAdditiveSymbols();
   1026 
   1027  // The speak-as values of counter styles may form a loop, and the
   1028  // loops may have complex interaction with the loop formed by
   1029  // extending. To solve this problem, the computation of speak-as is
   1030  // divided into two phases:
   1031  // 1. figure out the raw value, by ComputeRawSpeakAs, and
   1032  // 2. eliminate loop, by ComputeSpeakAs.
   1033  // See comments before the definitions of these methods for details.
   1034  SpeakAs GetSpeakAsAutoValue();
   1035  void ComputeRawSpeakAs(SpeakAs& aSpeakAs, CounterStyle*& aSpeakAsCounter);
   1036  CounterStyle* ComputeSpeakAs();
   1037 
   1038  CounterStyle* ComputeExtends();
   1039  CounterStyle* GetExtends();
   1040  CounterStyle* GetExtendsRoot();
   1041 
   1042  // CounterStyleManager should always overlive any CounterStyle as it
   1043  // is owned by nsPresContext, and will be released after all nodes and
   1044  // frames are released.
   1045  CounterStyleManager* mManager;
   1046 
   1047  RefPtr<const StyleLockedCounterStyleRule> mRule;
   1048  uint32_t mRuleGeneration;
   1049 
   1050  StyleCounterSystem mSystem;
   1051  // GetSpeakAs will ensure that private member mSpeakAs is initialized before
   1052  // used
   1053  MOZ_INIT_OUTSIDE_CTOR SpeakAs mSpeakAs;
   1054 
   1055  enum {
   1056    // loop detection
   1057    FLAG_EXTENDS_VISITED = 1 << 0,
   1058    FLAG_EXTENDS_LOOP = 1 << 1,
   1059    FLAG_SPEAKAS_VISITED = 1 << 2,
   1060    FLAG_SPEAKAS_LOOP = 1 << 3,
   1061    // field status
   1062    FLAG_NEGATIVE_INITED = 1 << 4,
   1063    FLAG_PREFIX_INITED = 1 << 5,
   1064    FLAG_SUFFIX_INITED = 1 << 6,
   1065    FLAG_PAD_INITED = 1 << 7,
   1066    FLAG_SPEAKAS_INITED = 1 << 8,
   1067  };
   1068  uint16_t mFlags;
   1069 
   1070  // Fields below will be initialized when necessary.
   1071  StyleOwnedSlice<AdditiveSymbol> mAdditiveSymbols;
   1072  NegativeType mNegative;
   1073  nsString mPrefix, mSuffix;
   1074  PadType mPad;
   1075 
   1076  // CounterStyleManager will guarantee that none of the pointers below
   1077  // refers to a freed CounterStyle. There are two possible cases where
   1078  // the manager will release its reference to a CounterStyle: 1. the
   1079  // manager itself is released, 2. a rule is invalidated. In the first
   1080  // case, all counter style are removed from the manager, and should
   1081  // also have been dereferenced from other objects. All styles will be
   1082  // released all together. In the second case, CounterStyleManager::
   1083  // NotifyRuleChanged will guarantee that all pointers will be reset
   1084  // before any CounterStyle is released.
   1085 
   1086  CounterStyle* mFallback;
   1087  // This field refers to the last counter in a speak-as chain.
   1088  // That counter must not speak as another counter.
   1089  CounterStyle* mSpeakAsCounter;
   1090 
   1091  CounterStyle* mExtends;
   1092  // This field refers to the last counter in the extends chain. The
   1093  // counter must be either a builtin style or a style whose system is
   1094  // not 'extends'.
   1095  CounterStyle* mExtendsRoot;
   1096 };
   1097 
   1098 void CustomCounterStyle::ResetCachedData() {
   1099  mAdditiveSymbols.Clear();
   1100  mFlags &= ~(FLAG_NEGATIVE_INITED | FLAG_PREFIX_INITED | FLAG_SUFFIX_INITED |
   1101              FLAG_PAD_INITED | FLAG_SPEAKAS_INITED);
   1102  mFallback = nullptr;
   1103  mSpeakAsCounter = nullptr;
   1104  mExtends = nullptr;
   1105  mExtendsRoot = nullptr;
   1106  mRuleGeneration = Servo_CounterStyleRule_GetGeneration(mRule);
   1107 }
   1108 
   1109 void CustomCounterStyle::ResetDependentData() {
   1110  mFlags &= ~FLAG_SPEAKAS_INITED;
   1111  mSpeakAsCounter = nullptr;
   1112  mFallback = nullptr;
   1113  mExtends = nullptr;
   1114  mExtendsRoot = nullptr;
   1115  if (IsExtendsSystem()) {
   1116    mFlags &= ~(FLAG_NEGATIVE_INITED | FLAG_PREFIX_INITED | FLAG_SUFFIX_INITED |
   1117                FLAG_PAD_INITED);
   1118  }
   1119 }
   1120 
   1121 /* virtual */
   1122 void CustomCounterStyle::GetPrefix(nsAString& aResult) {
   1123  if (!(mFlags & FLAG_PREFIX_INITED)) {
   1124    mFlags |= FLAG_PREFIX_INITED;
   1125 
   1126    if (!Servo_CounterStyleRule_GetPrefix(mRule, &mPrefix)) {
   1127      if (IsExtendsSystem()) {
   1128        GetExtends()->GetPrefix(mPrefix);
   1129      } else {
   1130        mPrefix.Truncate();
   1131      }
   1132    }
   1133  }
   1134  aResult = mPrefix;
   1135 }
   1136 
   1137 /* virtual */
   1138 void CustomCounterStyle::GetSuffix(nsAString& aResult) {
   1139  if (!(mFlags & FLAG_SUFFIX_INITED)) {
   1140    mFlags |= FLAG_SUFFIX_INITED;
   1141 
   1142    if (!Servo_CounterStyleRule_GetSuffix(mRule, &mSuffix)) {
   1143      if (IsExtendsSystem()) {
   1144        GetExtends()->GetSuffix(mSuffix);
   1145      } else {
   1146        mSuffix.AssignLiteral(u". ");
   1147      }
   1148    }
   1149  }
   1150  aResult = mSuffix;
   1151 }
   1152 
   1153 /* virtual */
   1154 void CustomCounterStyle::GetSpokenCounterText(CounterValue aOrdinal,
   1155                                              WritingMode aWritingMode,
   1156                                              nsAString& aResult,
   1157                                              bool& aIsBullet) {
   1158  if (GetSpeakAs() != SpeakAs::Other) {
   1159    CounterStyle::GetSpokenCounterText(aOrdinal, aWritingMode, aResult,
   1160                                       aIsBullet);
   1161  } else {
   1162    MOZ_ASSERT(mSpeakAsCounter,
   1163               "mSpeakAsCounter should have been initialized.");
   1164    mSpeakAsCounter->GetSpokenCounterText(aOrdinal, aWritingMode, aResult,
   1165                                          aIsBullet);
   1166  }
   1167 }
   1168 
   1169 /* virtual */
   1170 bool CustomCounterStyle::IsBullet() {
   1171  switch (mSystem) {
   1172    case StyleCounterSystem::Cyclic:
   1173      // Only use ::-moz-list-bullet for cyclic system
   1174      return true;
   1175    case StyleCounterSystem::Extends:
   1176      return GetExtendsRoot()->IsBullet();
   1177    default:
   1178      return false;
   1179  }
   1180 }
   1181 
   1182 /* virtual */
   1183 void CustomCounterStyle::GetNegative(NegativeType& aResult) {
   1184  if (!(mFlags & FLAG_NEGATIVE_INITED)) {
   1185    mFlags |= FLAG_NEGATIVE_INITED;
   1186    if (!Servo_CounterStyleRule_GetNegative(mRule, &mNegative.before,
   1187                                            &mNegative.after)) {
   1188      if (IsExtendsSystem()) {
   1189        GetExtends()->GetNegative(mNegative);
   1190      } else {
   1191        mNegative.before.AssignLiteral(u"-");
   1192        mNegative.after.Truncate();
   1193      }
   1194    }
   1195  }
   1196  aResult = mNegative;
   1197 }
   1198 
   1199 /* virtual */
   1200 bool CustomCounterStyle::IsOrdinalInRange(CounterValue aOrdinal) {
   1201  auto inRange = Servo_CounterStyleRule_IsInRange(mRule, aOrdinal);
   1202  switch (inRange) {
   1203    case StyleIsOrdinalInRange::InRange:
   1204      return true;
   1205    case StyleIsOrdinalInRange::NotInRange:
   1206      return false;
   1207    case StyleIsOrdinalInRange::NoOrdinalSpecified:
   1208      if (IsExtendsSystem()) {
   1209        return GetExtends()->IsOrdinalInRange(aOrdinal);
   1210      }
   1211      break;
   1212    case StyleIsOrdinalInRange::Auto:
   1213      break;
   1214    default:
   1215      MOZ_ASSERT_UNREACHABLE("Unkown result from IsInRange?");
   1216  }
   1217  return IsOrdinalInAutoRange(aOrdinal);
   1218 }
   1219 
   1220 /* virtual */
   1221 bool CustomCounterStyle::IsOrdinalInAutoRange(CounterValue aOrdinal) {
   1222  switch (mSystem) {
   1223    case StyleCounterSystem::Cyclic:
   1224    case StyleCounterSystem::Numeric:
   1225    case StyleCounterSystem::Fixed:
   1226      return true;
   1227    case StyleCounterSystem::Alphabetic:
   1228    case StyleCounterSystem::Symbolic:
   1229      return aOrdinal >= 1;
   1230    case StyleCounterSystem::Additive:
   1231      return aOrdinal >= 0;
   1232    case StyleCounterSystem::Extends:
   1233      return GetExtendsRoot()->IsOrdinalInAutoRange(aOrdinal);
   1234    default:
   1235      MOZ_ASSERT_UNREACHABLE("Invalid system for computing auto value.");
   1236      return false;
   1237  }
   1238 }
   1239 
   1240 /* virtual */
   1241 void CustomCounterStyle::GetPad(PadType& aResult) {
   1242  if (!(mFlags & FLAG_PAD_INITED)) {
   1243    mFlags |= FLAG_PAD_INITED;
   1244    if (!Servo_CounterStyleRule_GetPad(mRule, &mPad.width, &mPad.symbol)) {
   1245      if (IsExtendsSystem()) {
   1246        GetExtends()->GetPad(mPad);
   1247      } else {
   1248        mPad.width = 0;
   1249        mPad.symbol.Truncate();
   1250      }
   1251    }
   1252  }
   1253  aResult = mPad;
   1254 }
   1255 
   1256 /* virtual */
   1257 CounterStyle* CustomCounterStyle::GetFallback() {
   1258  if (!mFallback) {
   1259    mFallback = CounterStyleManager::GetDecimalStyle();
   1260    if (nsAtom* fallback = Servo_CounterStyleRule_GetFallback(mRule)) {
   1261      mFallback = mManager->ResolveCounterStyle(fallback);
   1262    } else if (IsExtendsSystem()) {
   1263      mFallback = GetExtends()->GetFallback();
   1264    }
   1265  }
   1266  return mFallback;
   1267 }
   1268 
   1269 /* virtual */
   1270 SpeakAs CustomCounterStyle::GetSpeakAs() {
   1271  if (!(mFlags & FLAG_SPEAKAS_INITED)) {
   1272    ComputeSpeakAs();
   1273  }
   1274  return mSpeakAs;
   1275 }
   1276 
   1277 /* virtual */
   1278 bool CustomCounterStyle::UseNegativeSign() {
   1279  if (mSystem == StyleCounterSystem::Extends) {
   1280    return GetExtendsRoot()->UseNegativeSign();
   1281  }
   1282  return SystemUsesNegativeSign(mSystem);
   1283 }
   1284 
   1285 /* virtual */
   1286 void CustomCounterStyle::CallFallbackStyle(CounterValue aOrdinal,
   1287                                           WritingMode aWritingMode,
   1288                                           nsAString& aResult, bool& aIsRTL) {
   1289  CounterStyle* fallback = GetFallback();
   1290  // If it recursively falls back to this counter style again,
   1291  // it will then fallback to decimal to break the loop.
   1292  mFallback = CounterStyleManager::GetDecimalStyle();
   1293  fallback->GetCounterText(aOrdinal, aWritingMode, aResult, aIsRTL);
   1294  mFallback = fallback;
   1295 }
   1296 
   1297 /* virtual */
   1298 bool CustomCounterStyle::GetInitialCounterText(CounterValue aOrdinal,
   1299                                               WritingMode aWritingMode,
   1300                                               nsAString& aResult,
   1301                                               bool& aIsRTL) {
   1302  switch (mSystem) {
   1303    case StyleCounterSystem::Cyclic:
   1304      return GetCyclicCounterText(aOrdinal, aResult, GetSymbols());
   1305    case StyleCounterSystem::Fixed: {
   1306      int32_t start = Servo_CounterStyleRule_GetFixedFirstValue(mRule);
   1307      return GetFixedCounterText(aOrdinal, aResult, start, GetSymbols());
   1308    }
   1309    case StyleCounterSystem::Symbolic:
   1310      return GetSymbolicCounterText(aOrdinal, aResult, GetSymbols());
   1311    case StyleCounterSystem::Alphabetic:
   1312      return GetAlphabeticCounterText(aOrdinal, aResult, GetSymbols());
   1313    case StyleCounterSystem::Numeric:
   1314      return GetNumericCounterText(aOrdinal, aResult, GetSymbols());
   1315    case StyleCounterSystem::Additive:
   1316      return GetAdditiveCounterText(aOrdinal, aResult, GetAdditiveSymbols());
   1317    case StyleCounterSystem::Extends:
   1318      return GetExtendsRoot()->GetInitialCounterText(aOrdinal, aWritingMode,
   1319                                                     aResult, aIsRTL);
   1320    default:
   1321      MOZ_ASSERT_UNREACHABLE("Invalid system.");
   1322      return false;
   1323  }
   1324 }
   1325 
   1326 Span<const StyleSymbol> CustomCounterStyle::GetSymbols() {
   1327  size_t count = 0;
   1328  const StyleSymbol* ptr = Servo_CounterStyleRule_GetSymbols(mRule, &count);
   1329  return Span(ptr, count);
   1330 }
   1331 
   1332 Span<const AdditiveSymbol> CustomCounterStyle::GetAdditiveSymbols() {
   1333  if (mAdditiveSymbols.IsEmpty()) {
   1334    Servo_CounterStyleRule_GetAdditiveSymbols(mRule, &mAdditiveSymbols);
   1335  }
   1336  return mAdditiveSymbols.AsSpan();
   1337 }
   1338 
   1339 // This method is used to provide the computed value for 'auto'.
   1340 SpeakAs CustomCounterStyle::GetSpeakAsAutoValue() {
   1341  auto system = mSystem;
   1342  if (IsExtendsSystem()) {
   1343    CounterStyle* root = GetExtendsRoot();
   1344    if (!root->IsCustomStyle()) {
   1345      // It is safe to call GetSpeakAs on non-custom style.
   1346      return root->GetSpeakAs();
   1347    }
   1348    system = static_cast<CustomCounterStyle*>(root)->mSystem;
   1349  }
   1350  return GetDefaultSpeakAsForSystem(system);
   1351 }
   1352 
   1353 // This method corresponds to the first stage of computation of the
   1354 // value of speak-as. It will extract the value from the rule and
   1355 // possibly recursively call itself on the extended style to figure
   1356 // out the raw value. To keep things clear, this method is designed to
   1357 // have no side effects (but functions it calls may still affect other
   1358 // fields in the style.)
   1359 void CustomCounterStyle::ComputeRawSpeakAs(SpeakAs& aSpeakAs,
   1360                                           CounterStyle*& aSpeakAsCounter) {
   1361  NS_ASSERTION(!(mFlags & FLAG_SPEAKAS_INITED),
   1362               "ComputeRawSpeakAs is called with speak-as inited.");
   1363 
   1364  auto speakAs = StyleCounterSpeakAs::None();
   1365  Servo_CounterStyleRule_GetSpeakAs(mRule, &speakAs);
   1366  switch (speakAs.tag) {
   1367    case StyleCounterSpeakAs::Tag::Auto:
   1368      aSpeakAs = GetSpeakAsAutoValue();
   1369      break;
   1370    case StyleCounterSpeakAs::Tag::Bullets:
   1371      aSpeakAs = SpeakAs::Bullets;
   1372      break;
   1373    case StyleCounterSpeakAs::Tag::Numbers:
   1374      aSpeakAs = SpeakAs::Numbers;
   1375      break;
   1376    case StyleCounterSpeakAs::Tag::Words:
   1377      aSpeakAs = SpeakAs::Words;
   1378      break;
   1379    case StyleCounterSpeakAs::Tag::Ident:
   1380      aSpeakAs = SpeakAs::Other;
   1381      aSpeakAsCounter = mManager->ResolveCounterStyle(speakAs.AsIdent());
   1382      break;
   1383    case StyleCounterSpeakAs::Tag::None: {
   1384      if (!IsExtendsSystem()) {
   1385        aSpeakAs = GetSpeakAsAutoValue();
   1386      } else {
   1387        CounterStyle* extended = GetExtends();
   1388        if (!extended->IsCustomStyle()) {
   1389          // It is safe to call GetSpeakAs on non-custom style.
   1390          aSpeakAs = extended->GetSpeakAs();
   1391        } else {
   1392          CustomCounterStyle* custom =
   1393              static_cast<CustomCounterStyle*>(extended);
   1394          if (!(custom->mFlags & FLAG_SPEAKAS_INITED)) {
   1395            custom->ComputeRawSpeakAs(aSpeakAs, aSpeakAsCounter);
   1396          } else {
   1397            aSpeakAs = custom->mSpeakAs;
   1398            aSpeakAsCounter = custom->mSpeakAsCounter;
   1399          }
   1400        }
   1401      }
   1402      break;
   1403    }
   1404    default:
   1405      MOZ_ASSERT_UNREACHABLE("Invalid speak-as value");
   1406  }
   1407 }
   1408 
   1409 // This method corresponds to the second stage of getting speak-as
   1410 // related values. It will recursively figure out the final value of
   1411 // mSpeakAs and mSpeakAsCounter. This method returns nullptr if the
   1412 // caller is in a loop, and the root counter style in the chain
   1413 // otherwise. It use the same loop detection algorithm as
   1414 // CustomCounterStyle::ComputeExtends, see comments before that
   1415 // method for more details.
   1416 CounterStyle* CustomCounterStyle::ComputeSpeakAs() {
   1417  if (mFlags & FLAG_SPEAKAS_INITED) {
   1418    if (mSpeakAs == SpeakAs::Other) {
   1419      return mSpeakAsCounter;
   1420    }
   1421    return this;
   1422  }
   1423 
   1424  if (mFlags & FLAG_SPEAKAS_VISITED) {
   1425    // loop detected
   1426    mFlags |= FLAG_SPEAKAS_LOOP;
   1427    return nullptr;
   1428  }
   1429 
   1430  CounterStyle* speakAsCounter;
   1431  ComputeRawSpeakAs(mSpeakAs, speakAsCounter);
   1432 
   1433  bool inLoop = false;
   1434  if (mSpeakAs != SpeakAs::Other) {
   1435    mSpeakAsCounter = nullptr;
   1436  } else if (!speakAsCounter->IsCustomStyle()) {
   1437    mSpeakAsCounter = speakAsCounter;
   1438  } else {
   1439    mFlags |= FLAG_SPEAKAS_VISITED;
   1440    CounterStyle* target =
   1441        static_cast<CustomCounterStyle*>(speakAsCounter)->ComputeSpeakAs();
   1442    mFlags &= ~FLAG_SPEAKAS_VISITED;
   1443 
   1444    if (target) {
   1445      NS_ASSERTION(!(mFlags & FLAG_SPEAKAS_LOOP),
   1446                   "Invalid state for speak-as loop detecting");
   1447      mSpeakAsCounter = target;
   1448    } else {
   1449      mSpeakAs = GetSpeakAsAutoValue();
   1450      mSpeakAsCounter = nullptr;
   1451      if (mFlags & FLAG_SPEAKAS_LOOP) {
   1452        mFlags &= ~FLAG_SPEAKAS_LOOP;
   1453      } else {
   1454        inLoop = true;
   1455      }
   1456    }
   1457  }
   1458 
   1459  mFlags |= FLAG_SPEAKAS_INITED;
   1460  if (inLoop) {
   1461    return nullptr;
   1462  }
   1463  return mSpeakAsCounter ? mSpeakAsCounter : this;
   1464 }
   1465 
   1466 // This method will recursively figure out mExtends in the whole chain.
   1467 // It will return nullptr if the caller is in a loop, and return this
   1468 // otherwise. To detect the loop, this method marks the style VISITED
   1469 // before the recursive call. When a VISITED style is reached again, the
   1470 // loop is detected, and flag LOOP will be marked on the first style in
   1471 // loop. mExtends of all counter styles in loop will be set to decimal
   1472 // according to the spec.
   1473 CounterStyle* CustomCounterStyle::ComputeExtends() {
   1474  if (!IsExtendsSystem() || mExtends) {
   1475    return this;
   1476  }
   1477  if (mFlags & FLAG_EXTENDS_VISITED) {
   1478    // loop detected
   1479    mFlags |= FLAG_EXTENDS_LOOP;
   1480    return nullptr;
   1481  }
   1482 
   1483  nsAtom* extended = Servo_CounterStyleRule_GetExtended(mRule);
   1484  CounterStyle* nextCounter = mManager->ResolveCounterStyle(extended);
   1485  CounterStyle* target = nextCounter;
   1486  if (nextCounter->IsCustomStyle()) {
   1487    mFlags |= FLAG_EXTENDS_VISITED;
   1488    target = static_cast<CustomCounterStyle*>(nextCounter)->ComputeExtends();
   1489    mFlags &= ~FLAG_EXTENDS_VISITED;
   1490  }
   1491 
   1492  if (target) {
   1493    NS_ASSERTION(!(mFlags & FLAG_EXTENDS_LOOP),
   1494                 "Invalid state for extends loop detecting");
   1495    mExtends = nextCounter;
   1496    return this;
   1497  } else {
   1498    mExtends = CounterStyleManager::GetDecimalStyle();
   1499    if (mFlags & FLAG_EXTENDS_LOOP) {
   1500      mFlags &= ~FLAG_EXTENDS_LOOP;
   1501      return this;
   1502    } else {
   1503      return nullptr;
   1504    }
   1505  }
   1506 }
   1507 
   1508 CounterStyle* CustomCounterStyle::GetExtends() {
   1509  if (!mExtends) {
   1510    // Any extends loop will be eliminated in the method below.
   1511    ComputeExtends();
   1512  }
   1513  return mExtends;
   1514 }
   1515 
   1516 CounterStyle* CustomCounterStyle::GetExtendsRoot() {
   1517  if (!mExtendsRoot) {
   1518    CounterStyle* extended = GetExtends();
   1519    mExtendsRoot = extended;
   1520    if (extended->IsCustomStyle()) {
   1521      CustomCounterStyle* custom = static_cast<CustomCounterStyle*>(extended);
   1522      if (custom->IsExtendsSystem()) {
   1523        // This will make mExtendsRoot in the whole extends chain be
   1524        // set recursively, which could save work when part of a chain
   1525        // is shared by multiple counter styles.
   1526        mExtendsRoot = custom->GetExtendsRoot();
   1527      }
   1528    }
   1529  }
   1530  return mExtendsRoot;
   1531 }
   1532 
   1533 AnonymousCounterStyle::AnonymousCounterStyle(StyleSymbolsType aType,
   1534                                             Span<const StyleSymbol> aSymbols)
   1535    : CounterStyle(ListStyle::Custom),
   1536      mSymbolsType(aType),
   1537      mSymbols(aSymbols) {}
   1538 
   1539 /* virtual */
   1540 void AnonymousCounterStyle::GetPrefix(nsAString& aResult) {
   1541  aResult.Truncate();
   1542 }
   1543 
   1544 /* virtual */
   1545 void AnonymousCounterStyle::GetSuffix(nsAString& aResult) { aResult = ' '; }
   1546 
   1547 /* virtual */
   1548 bool AnonymousCounterStyle::IsBullet() {
   1549  // Only use ::-moz-list-bullet for cyclic system
   1550  return mSymbolsType == StyleSymbolsType::Cyclic;
   1551 }
   1552 
   1553 /* virtual */
   1554 void AnonymousCounterStyle::GetNegative(NegativeType& aResult) {
   1555  aResult.before.AssignLiteral(u"-");
   1556  aResult.after.Truncate();
   1557 }
   1558 
   1559 /* virtual */
   1560 bool AnonymousCounterStyle::IsOrdinalInRange(CounterValue aOrdinal) {
   1561  switch (mSymbolsType) {
   1562    case StyleSymbolsType::Cyclic:
   1563    case StyleSymbolsType::Numeric:
   1564    case StyleSymbolsType::Fixed:
   1565      return true;
   1566    case StyleSymbolsType::Alphabetic:
   1567    case StyleSymbolsType::Symbolic:
   1568      return aOrdinal >= 1;
   1569    default:
   1570      MOZ_ASSERT_UNREACHABLE("Invalid system.");
   1571      return false;
   1572  }
   1573 }
   1574 
   1575 /* virtual */
   1576 bool AnonymousCounterStyle::IsOrdinalInAutoRange(CounterValue aOrdinal) {
   1577  return AnonymousCounterStyle::IsOrdinalInRange(aOrdinal);
   1578 }
   1579 
   1580 /* virtual */
   1581 void AnonymousCounterStyle::GetPad(PadType& aResult) {
   1582  aResult.width = 0;
   1583  aResult.symbol.Truncate();
   1584 }
   1585 
   1586 /* virtual */
   1587 CounterStyle* AnonymousCounterStyle::GetFallback() {
   1588  return CounterStyleManager::GetDecimalStyle();
   1589 }
   1590 
   1591 StyleCounterSystem AnonymousCounterStyle::GetSystem() const {
   1592  switch (mSymbolsType) {
   1593    case StyleSymbolsType::Cyclic:
   1594      return StyleCounterSystem::Cyclic;
   1595    case StyleSymbolsType::Numeric:
   1596      return StyleCounterSystem::Numeric;
   1597    case StyleSymbolsType::Fixed:
   1598      return StyleCounterSystem::Fixed;
   1599    case StyleSymbolsType::Alphabetic:
   1600      return StyleCounterSystem::Alphabetic;
   1601    case StyleSymbolsType::Symbolic:
   1602      return StyleCounterSystem::Symbolic;
   1603  }
   1604  MOZ_ASSERT_UNREACHABLE("Unknown symbols() type");
   1605  return StyleCounterSystem::Cyclic;
   1606 }
   1607 
   1608 /* virtual */
   1609 SpeakAs AnonymousCounterStyle::GetSpeakAs() {
   1610  return GetDefaultSpeakAsForSystem(GetSystem());
   1611 }
   1612 
   1613 /* virtual */
   1614 bool AnonymousCounterStyle::UseNegativeSign() {
   1615  return SystemUsesNegativeSign(GetSystem());
   1616 }
   1617 
   1618 /* virtual */
   1619 bool AnonymousCounterStyle::GetInitialCounterText(CounterValue aOrdinal,
   1620                                                  WritingMode aWritingMode,
   1621                                                  nsAString& aResult,
   1622                                                  bool& aIsRTL) {
   1623  switch (mSymbolsType) {
   1624    case StyleSymbolsType::Cyclic:
   1625      return GetCyclicCounterText(aOrdinal, aResult, mSymbols);
   1626    case StyleSymbolsType::Numeric:
   1627      return GetNumericCounterText(aOrdinal, aResult, mSymbols);
   1628    case StyleSymbolsType::Fixed:
   1629      return GetFixedCounterText(aOrdinal, aResult, 1, mSymbols);
   1630    case StyleSymbolsType::Alphabetic:
   1631      return GetAlphabeticCounterText(aOrdinal, aResult, mSymbols);
   1632    case StyleSymbolsType::Symbolic:
   1633      return GetSymbolicCounterText(aOrdinal, aResult, mSymbols);
   1634  }
   1635  MOZ_ASSERT_UNREACHABLE("Invalid system.");
   1636  return false;
   1637 }
   1638 
   1639 bool CounterStyle::IsDependentStyle() const {
   1640  switch (mStyle) {
   1641    // CustomCounterStyle
   1642    case ListStyle::Custom:
   1643    // DependentBuiltinCounterStyle
   1644    case ListStyle::JapaneseInformal:
   1645    case ListStyle::JapaneseFormal:
   1646    case ListStyle::KoreanHangulFormal:
   1647    case ListStyle::KoreanHanjaInformal:
   1648    case ListStyle::KoreanHanjaFormal:
   1649    case ListStyle::SimpChineseInformal:
   1650    case ListStyle::SimpChineseFormal:
   1651    case ListStyle::TradChineseInformal:
   1652    case ListStyle::TradChineseFormal:
   1653      return true;
   1654 
   1655    // BuiltinCounterStyle
   1656    default:
   1657      return false;
   1658  }
   1659 }
   1660 
   1661 void CounterStyle::GetCounterText(CounterValue aOrdinal,
   1662                                  WritingMode aWritingMode, nsAString& aResult,
   1663                                  bool& aIsRTL) {
   1664  bool success = IsOrdinalInRange(aOrdinal);
   1665  aIsRTL = false;
   1666 
   1667  if (success) {
   1668    // generate initial representation
   1669    bool useNegativeSign = UseNegativeSign();
   1670    nsAutoString initialText;
   1671    CounterValue ordinal;
   1672    if (!useNegativeSign) {
   1673      ordinal = aOrdinal;
   1674    } else {
   1675      CheckedInt<CounterValue> absolute(Abs(aOrdinal));
   1676      ordinal = absolute.isValid() ? absolute.value()
   1677                                   : std::numeric_limits<CounterValue>::max();
   1678    }
   1679    success = GetInitialCounterText(ordinal, aWritingMode, initialText, aIsRTL);
   1680 
   1681    // add pad & negative, build the final result
   1682    if (success) {
   1683      aResult.Truncate();
   1684      if (useNegativeSign && aOrdinal < 0) {
   1685        NegativeType negative;
   1686        GetNegative(negative);
   1687        aResult.Append(negative.before);
   1688        // There is nothing between the suffix part of negative and initial
   1689        // representation, so we append it directly here.
   1690        initialText.Append(negative.after);
   1691      }
   1692      PadType pad;
   1693      GetPad(pad);
   1694      int32_t diff =
   1695          pad.width -
   1696          narrow_cast<int32_t>(unicode::CountGraphemeClusters(initialText) +
   1697                               unicode::CountGraphemeClusters(aResult));
   1698      if (diff > 0) {
   1699        auto length = pad.symbol.Length();
   1700        if (diff > LENGTH_LIMIT || length > LENGTH_LIMIT ||
   1701            diff * length > LENGTH_LIMIT) {
   1702          success = false;
   1703        } else if (length > 0) {
   1704          for (int32_t i = 0; i < diff; ++i) {
   1705            aResult.Append(pad.symbol);
   1706          }
   1707        }
   1708      }
   1709      if (success) {
   1710        aResult.Append(initialText);
   1711      }
   1712    }
   1713  }
   1714 
   1715  if (!success) {
   1716    CallFallbackStyle(aOrdinal, aWritingMode, aResult, aIsRTL);
   1717  }
   1718 }
   1719 
   1720 /* virtual */
   1721 void CounterStyle::GetSpokenCounterText(CounterValue aOrdinal,
   1722                                        WritingMode aWritingMode,
   1723                                        nsAString& aResult, bool& aIsBullet) {
   1724  bool isRTL;  // we don't care about direction for spoken text
   1725  aIsBullet = false;
   1726  switch (GetSpeakAs()) {
   1727    case SpeakAs::Bullets:
   1728      aResult.Assign(kDiscCharacter);
   1729      aIsBullet = true;
   1730      break;
   1731    case SpeakAs::Numbers:
   1732      DecimalToText(aOrdinal, aResult);
   1733      break;
   1734    case SpeakAs::Spellout:
   1735      // we currently do not actually support 'spell-out',
   1736      // so 'words' is used instead.
   1737    case SpeakAs::Words:
   1738      GetCounterText(aOrdinal, WritingMode(), aResult, isRTL);
   1739      break;
   1740    case SpeakAs::Other:
   1741      // This should be processed by CustomCounterStyle
   1742      MOZ_ASSERT_UNREACHABLE("Invalid speak-as value");
   1743      break;
   1744    default:
   1745      MOZ_ASSERT_UNREACHABLE("Unknown speak-as value");
   1746      break;
   1747  }
   1748 }
   1749 
   1750 /* virtual */
   1751 void CounterStyle::CallFallbackStyle(CounterValue aOrdinal,
   1752                                     WritingMode aWritingMode,
   1753                                     nsAString& aResult, bool& aIsRTL) {
   1754  GetFallback()->GetCounterText(aOrdinal, aWritingMode, aResult, aIsRTL);
   1755 }
   1756 
   1757 CounterStyleManager::CounterStyleManager(nsPresContext* aPresContext)
   1758    : mPresContext(aPresContext) {
   1759  // Insert the static styles into cache table
   1760  mStyles.InsertOrUpdate(nsGkAtoms::none, GetNoneStyle());
   1761  mStyles.InsertOrUpdate(nsGkAtoms::decimal, GetDecimalStyle());
   1762  mStyles.InsertOrUpdate(nsGkAtoms::disc, GetDiscStyle());
   1763 }
   1764 
   1765 CounterStyleManager::~CounterStyleManager() {
   1766  MOZ_ASSERT(!mPresContext, "Disconnect should have been called");
   1767 }
   1768 
   1769 void CounterStyleManager::DestroyCounterStyle(CounterStyle* aCounterStyle) {
   1770  if (aCounterStyle->IsCustomStyle()) {
   1771    MOZ_ASSERT(!aCounterStyle->AsAnonymous(),
   1772               "Anonymous counter styles "
   1773               "are not managed by CounterStyleManager");
   1774    static_cast<CustomCounterStyle*>(aCounterStyle)->Destroy();
   1775  } else if (aCounterStyle->IsDependentStyle()) {
   1776    static_cast<DependentBuiltinCounterStyle*>(aCounterStyle)->Destroy();
   1777  } else {
   1778    MOZ_ASSERT_UNREACHABLE("Builtin counter styles should not be destroyed");
   1779  }
   1780 }
   1781 
   1782 void CounterStyleManager::Disconnect() {
   1783  CleanRetiredStyles();
   1784  for (CounterStyle* style : mStyles.Values()) {
   1785    if (style->IsDependentStyle()) {
   1786      DestroyCounterStyle(style);
   1787    }
   1788  }
   1789  mStyles.Clear();
   1790  mPresContext = nullptr;
   1791 }
   1792 
   1793 CounterStyle* CounterStyleManager::ResolveCounterStyle(nsAtom* aName) {
   1794  MOZ_ASSERT(NS_IsMainThread());
   1795  CounterStyle* data = GetCounterStyle(aName);
   1796  if (data) {
   1797    return data;
   1798  }
   1799 
   1800  // Names are compared case-sensitively here. Predefined names should
   1801  // have been lowercased by the parser.
   1802  ServoStyleSet* styleSet = mPresContext->StyleSet();
   1803  auto* rule = styleSet->CounterStyleRuleForName(aName);
   1804  if (rule) {
   1805    MOZ_ASSERT(Servo_CounterStyleRule_GetName(rule) == aName);
   1806    data = new (mPresContext) CustomCounterStyle(this, rule);
   1807  } else {
   1808    for (const BuiltinCounterStyle& item : gBuiltinStyleTable) {
   1809      if (item.GetStyleName() == aName) {
   1810        const auto style = item.GetStyle();
   1811        data = item.IsDependentStyle()
   1812                   ? new (mPresContext)
   1813                         DependentBuiltinCounterStyle(style, this)
   1814                   : GetBuiltinStyle(style);
   1815        break;
   1816      }
   1817    }
   1818  }
   1819  if (!data) {
   1820    data = GetDecimalStyle();
   1821  }
   1822  mStyles.InsertOrUpdate(aName, data);
   1823  return data;
   1824 }
   1825 
   1826 /* static */
   1827 CounterStyle* CounterStyleManager::GetBuiltinStyle(ListStyle aStyle) {
   1828  MOZ_ASSERT(size_t(aStyle) < std::size(gBuiltinStyleTable),
   1829             "Require a valid builtin style constant");
   1830  MOZ_ASSERT(!gBuiltinStyleTable[size_t(aStyle)].IsDependentStyle(),
   1831             "Cannot get dependent builtin style");
   1832  // No method of BuiltinCounterStyle mutates the struct itself, so it
   1833  // should be fine to cast const away.
   1834  return const_cast<BuiltinCounterStyle*>(&gBuiltinStyleTable[size_t(aStyle)]);
   1835 }
   1836 
   1837 bool CounterStyleManager::NotifyRuleChanged() {
   1838  bool changed = false;
   1839  for (auto iter = mStyles.Iter(); !iter.Done(); iter.Next()) {
   1840    CounterStyle* style = iter.Data();
   1841    bool toBeUpdated = false;
   1842    bool toBeRemoved = false;
   1843    ServoStyleSet* styleSet = mPresContext->StyleSet();
   1844    auto* newRule = styleSet->CounterStyleRuleForName(iter.Key());
   1845    if (!newRule) {
   1846      if (style->IsCustomStyle()) {
   1847        toBeRemoved = true;
   1848      }
   1849    } else {
   1850      if (!style->IsCustomStyle()) {
   1851        toBeRemoved = true;
   1852      } else {
   1853        auto custom = static_cast<CustomCounterStyle*>(style);
   1854        if (custom->GetRule() != newRule) {
   1855          toBeRemoved = true;
   1856        } else {
   1857          auto generation = Servo_CounterStyleRule_GetGeneration(newRule);
   1858          if (custom->GetRuleGeneration() != generation) {
   1859            toBeUpdated = true;
   1860            custom->ResetCachedData();
   1861          }
   1862        }
   1863      }
   1864    }
   1865    changed = changed || toBeUpdated || toBeRemoved;
   1866    if (toBeRemoved) {
   1867      if (style->IsDependentStyle()) {
   1868        // Add object to retired list so we can clean them up later.
   1869        mRetiredStyles.AppendElement(style);
   1870      }
   1871      iter.Remove();
   1872    }
   1873  }
   1874 
   1875  if (changed) {
   1876    for (CounterStyle* style : mStyles.Values()) {
   1877      if (style->IsCustomStyle()) {
   1878        CustomCounterStyle* custom = static_cast<CustomCounterStyle*>(style);
   1879        custom->ResetDependentData();
   1880      }
   1881      // There is no dependent data cached in DependentBuiltinCounterStyle
   1882      // instances, so we don't need to reset their data.
   1883    }
   1884  }
   1885  return changed;
   1886 }
   1887 
   1888 void CounterStyleManager::CleanRetiredStyles() {
   1889  nsTArray<CounterStyle*> list(std::move(mRetiredStyles));
   1890  for (CounterStyle* style : list) {
   1891    DestroyCounterStyle(style);
   1892  }
   1893 }
   1894 
   1895 }  // namespace mozilla