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