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