Bumping manifests a=b2g-bump
[gecko.git] / layout / style / CounterStyleManager.cpp
blob6fa3f3164b6fb7716055e0d19d26cc1caff903eb
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/. */
7 #include "CounterStyleManager.h"
9 #include "mozilla/Types.h"
10 #include "mozilla/CheckedInt.h"
11 #include "mozilla/MathAlgorithms.h"
12 #include "mozilla/ArrayUtils.h"
13 #include "prprf.h"
14 #include "nsString.h"
15 #include "nsStyleSet.h"
16 #include "nsCSSRules.h"
17 #include "nsTArray.h"
18 #include "nsTHashtable.h"
19 #include "nsUnicodeProperties.h"
20 #include "WritingModes.h"
22 namespace mozilla {
24 struct AdditiveSymbol
26 CounterValue weight;
27 nsString symbol;
30 struct NegativeType
32 nsString before, after;
35 struct PadType
37 int32_t width;
38 nsString symbol;
41 // This limitation will be applied to some systems, and pad descriptor.
42 // Any initial representation generated by symbolic or additive which is
43 // longer than this limitation will be dropped. If any pad is longer
44 // than this, the whole counter text will be dropped as well.
45 // The spec requires user agents to support at least 60 Unicode code-
46 // points for counter text. However, this constant only limits the
47 // length in 16-bit units. So it has to be at least 120, since code-
48 // points outside the BMP will need 2 16-bit units.
49 #define LENGTH_LIMIT 150
51 static bool
52 GetCyclicCounterText(CounterValue aOrdinal,
53 nsSubstring& aResult,
54 const nsTArray<nsString>& aSymbols)
56 NS_ABORT_IF_FALSE(aSymbols.Length() >= 1,
57 "No symbol available for cyclic counter.");
58 auto n = aSymbols.Length();
59 CounterValue index = (aOrdinal - 1) % n;
60 aResult = aSymbols[index >= 0 ? index : index + n];
61 return true;
64 static bool
65 GetFixedCounterText(CounterValue aOrdinal,
66 nsSubstring& aResult,
67 CounterValue aStart,
68 const nsTArray<nsString>& aSymbols)
70 CounterValue index = aOrdinal - aStart;
71 if (index >= 0 && index < CounterValue(aSymbols.Length())) {
72 aResult = aSymbols[index];
73 return true;
74 } else {
75 return false;
79 static bool
80 GetSymbolicCounterText(CounterValue aOrdinal,
81 nsSubstring& aResult,
82 const nsTArray<nsString>& aSymbols)
84 NS_ABORT_IF_FALSE(aSymbols.Length() >= 1,
85 "No symbol available for symbolic counter.");
86 NS_ABORT_IF_FALSE(aOrdinal >= 0, "Invalid ordinal.");
87 if (aOrdinal == 0) {
88 return false;
91 aResult.Truncate();
92 auto n = aSymbols.Length();
93 const nsString& symbol = aSymbols[(aOrdinal - 1) % n];
94 size_t len = (aOrdinal + n - 1) / n;
95 auto symbolLength = symbol.Length();
96 if (symbolLength > 0) {
97 if (len > LENGTH_LIMIT || symbolLength > LENGTH_LIMIT ||
98 len * symbolLength > LENGTH_LIMIT) {
99 return false;
101 for (size_t i = 0; i < len; ++i) {
102 aResult.Append(symbol);
105 return true;
108 static bool
109 GetAlphabeticCounterText(CounterValue aOrdinal,
110 nsSubstring& aResult,
111 const nsTArray<nsString>& aSymbols)
113 NS_ABORT_IF_FALSE(aSymbols.Length() >= 2,
114 "Too few symbols for alphabetic counter.");
115 NS_ABORT_IF_FALSE(aOrdinal >= 0, "Invalid ordinal.");
116 if (aOrdinal == 0) {
117 return false;
120 auto n = aSymbols.Length();
121 // The precise length of this array should be
122 // ceil(log((double) aOrdinal / n * (n - 1) + 1) / log(n)).
123 // The max length is slightly smaller than which defined below.
124 nsAutoTArray<int32_t, std::numeric_limits<CounterValue>::digits> indexes;
125 while (aOrdinal > 0) {
126 --aOrdinal;
127 indexes.AppendElement(aOrdinal % n);
128 aOrdinal /= n;
131 aResult.Truncate();
132 for (auto i = indexes.Length(); i > 0; --i) {
133 aResult.Append(aSymbols[indexes[i - 1]]);
135 return true;
138 static bool
139 GetNumericCounterText(CounterValue aOrdinal,
140 nsSubstring& aResult,
141 const nsTArray<nsString>& aSymbols)
143 NS_ABORT_IF_FALSE(aSymbols.Length() >= 2,
144 "Too few symbols for numeric counter.");
145 NS_ABORT_IF_FALSE(aOrdinal >= 0, "Invalid ordinal.");
147 if (aOrdinal == 0) {
148 aResult = aSymbols[0];
149 return true;
152 auto n = aSymbols.Length();
153 nsAutoTArray<int32_t, std::numeric_limits<CounterValue>::digits> indexes;
154 while (aOrdinal > 0) {
155 indexes.AppendElement(aOrdinal % n);
156 aOrdinal /= n;
159 aResult.Truncate();
160 for (auto i = indexes.Length(); i > 0; --i) {
161 aResult.Append(aSymbols[indexes[i - 1]]);
163 return true;
166 static bool
167 GetAdditiveCounterText(CounterValue aOrdinal,
168 nsSubstring& aResult,
169 const nsTArray<AdditiveSymbol>& aSymbols)
171 NS_ABORT_IF_FALSE(aOrdinal >= 0, "Invalid ordinal.");
173 if (aOrdinal == 0) {
174 const AdditiveSymbol& last = aSymbols.LastElement();
175 if (last.weight == 0) {
176 aResult = last.symbol;
177 return true;
179 return false;
182 aResult.Truncate();
183 size_t length = 0;
184 for (size_t i = 0, iEnd = aSymbols.Length(); i < iEnd; ++i) {
185 const AdditiveSymbol& symbol = aSymbols[i];
186 if (symbol.weight == 0) {
187 break;
189 CounterValue times = aOrdinal / symbol.weight;
190 if (times > 0) {
191 auto symbolLength = symbol.symbol.Length();
192 if (symbolLength > 0) {
193 length += times * symbolLength;
194 if (times > LENGTH_LIMIT ||
195 symbolLength > LENGTH_LIMIT ||
196 length > LENGTH_LIMIT) {
197 return false;
199 for (CounterValue j = 0; j < times; ++j) {
200 aResult.Append(symbol.symbol);
203 aOrdinal -= times * symbol.weight;
206 return aOrdinal == 0;
209 static bool
210 DecimalToText(CounterValue aOrdinal, nsSubstring& aResult)
212 // 3 for additional digit, negative sign, and null
213 char cbuf[std::numeric_limits<CounterValue>::digits10 + 3];
214 PR_snprintf(cbuf, sizeof(cbuf), "%ld", aOrdinal);
215 aResult.AssignASCII(cbuf);
216 return true;
219 // We know cjk-ideographic need 31 characters to display 99,999,999,999,999,999
220 // georgian needs 6 at most
221 // armenian needs 12 at most
222 // hebrew may need more...
224 #define NUM_BUF_SIZE 34
226 enum CJKIdeographicLang {
227 CHINESE, KOREAN, JAPANESE
229 struct CJKIdeographicData {
230 char16_t digit[10];
231 char16_t unit[3];
232 char16_t unit10K[2];
233 uint8_t lang;
234 bool informal;
236 static const CJKIdeographicData gDataJapaneseInformal = {
237 { // digit
238 0x3007, 0x4e00, 0x4e8c, 0x4e09, 0x56db,
239 0x4e94, 0x516d, 0x4e03, 0x516b, 0x4e5d
241 { 0x5341, 0x767e, 0x5343 }, // unit
242 { 0x4e07, 0x5104 }, // unit10K
243 JAPANESE, // lang
244 true // informal
246 static const CJKIdeographicData gDataJapaneseFormal = {
247 { // digit
248 0x96f6, 0x58f1, 0x5f10, 0x53c2, 0x56db,
249 0x4f0d, 0x516d, 0x4e03, 0x516b, 0x4e5d
251 { 0x62fe, 0x767e, 0x9621 }, // unit
252 { 0x842c, 0x5104 }, // unit10K
253 JAPANESE, // lang
254 false // informal
256 static const CJKIdeographicData gDataKoreanHangulFormal = {
257 { // digit
258 0xc601, 0xc77c, 0xc774, 0xc0bc, 0xc0ac,
259 0xc624, 0xc721, 0xce60, 0xd314, 0xad6c
261 { 0xc2ed, 0xbc31, 0xcc9c }, // unit
262 { 0xb9cc, 0xc5b5 }, // unit10K
263 KOREAN, // lang
264 false // informal
266 static const CJKIdeographicData gDataKoreanHanjaInformal = {
267 { // digit
268 0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db,
269 0x4e94, 0x516d, 0x4e03, 0x516b, 0x4e5d
271 { 0x5341, 0x767e, 0x5343 }, // unit
272 { 0x842c, 0x5104 }, // unit10K
273 KOREAN, // lang
274 true // informal
276 static const CJKIdeographicData gDataKoreanHanjaFormal = {
277 { // digit
278 0x96f6, 0x58f9, 0x8cb3, 0x53c3, 0x56db,
279 0x4e94, 0x516d, 0x4e03, 0x516b, 0x4e5d
281 { 0x62fe, 0x767e, 0x4edf }, // unit
282 { 0x842c, 0x5104 }, // unit10K
283 KOREAN, // lang
284 false // informal
286 static const CJKIdeographicData gDataSimpChineseInformal = {
287 { // digit
288 0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db,
289 0x4e94, 0x516d, 0x4e03, 0x516b, 0x4e5d
291 { 0x5341, 0x767e, 0x5343 }, // unit
292 { 0x4e07, 0x4ebf }, // unit10K
293 CHINESE, // lang
294 true // informal
296 static const CJKIdeographicData gDataSimpChineseFormal = {
297 { // digit
298 0x96f6, 0x58f9, 0x8d30, 0x53c1, 0x8086,
299 0x4f0d, 0x9646, 0x67d2, 0x634c, 0x7396
301 { 0x62fe, 0x4f70, 0x4edf }, // unit
302 { 0x4e07, 0x4ebf }, // unit10K
303 CHINESE, // lang
304 false // informal
306 static const CJKIdeographicData gDataTradChineseInformal = {
307 { // digit
308 0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db,
309 0x4e94, 0x516d, 0x4e03, 0x516b, 0x4e5d
311 { 0x5341, 0x767e, 0x5343 }, // unit
312 { 0x842c, 0x5104 }, // unit10K
313 CHINESE, // lang
314 true // informal
316 static const CJKIdeographicData gDataTradChineseFormal = {
317 { // digit
318 0x96f6, 0x58f9, 0x8cb3, 0x53c3, 0x8086,
319 0x4f0d, 0x9678, 0x67d2, 0x634c, 0x7396
321 { 0x62fe, 0x4f70, 0x4edf }, // unit
322 { 0x842c, 0x5104 }, // unit10K
323 CHINESE, // lang
324 false // informal
327 static bool
328 CJKIdeographicToText(CounterValue aOrdinal, nsSubstring& aResult,
329 const CJKIdeographicData& data)
331 NS_ASSERTION(aOrdinal >= 0, "Only accept non-negative ordinal");
332 char16_t buf[NUM_BUF_SIZE];
333 int32_t idx = NUM_BUF_SIZE;
334 int32_t pos = 0;
335 bool needZero = (aOrdinal == 0);
336 int32_t unitidx = 0, unit10Kidx = 0;
337 do {
338 unitidx = pos % 4;
339 if (unitidx == 0) {
340 unit10Kidx = pos / 4;
342 int32_t cur = aOrdinal % 10;
343 if (cur == 0) {
344 if (needZero) {
345 needZero = false;
346 buf[--idx] = data.digit[0];
348 } else {
349 if (data.lang == CHINESE) {
350 needZero = true;
352 if (unit10Kidx != 0) {
353 if (data.lang == KOREAN && idx != NUM_BUF_SIZE) {
354 buf[--idx] = ' ';
356 buf[--idx] = data.unit10K[unit10Kidx - 1];
358 if (unitidx != 0) {
359 buf[--idx] = data.unit[unitidx - 1];
361 if (cur != 1) {
362 buf[--idx] = data.digit[cur];
363 } else {
364 bool needOne = true;
365 if (data.informal) {
366 switch (data.lang) {
367 case CHINESE:
368 if (unitidx == 1 &&
369 (aOrdinal == 1 || (pos > 4 && aOrdinal % 1000 == 1))) {
370 needOne = false;
372 break;
373 case JAPANESE:
374 if (unitidx > 0 &&
375 (unitidx != 3 || (pos == 3 && aOrdinal == 1))) {
376 needOne = false;
378 break;
379 case KOREAN:
380 if (unitidx > 0 || (pos == 4 && (aOrdinal % 1000) == 1)) {
381 needOne = false;
383 break;
386 if (needOne) {
387 buf[--idx] = data.digit[1];
390 unit10Kidx = 0;
392 aOrdinal /= 10;
393 pos++;
394 } while (aOrdinal > 0);
395 aResult.Assign(buf + idx, NUM_BUF_SIZE - idx);
396 return true;
399 #define HEBREW_GERESH 0x05F3
400 static const char16_t gHebrewDigit[22] =
402 // 1 2 3 4 5 6 7 8 9
403 0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, 0x05D8,
404 // 10 20 30 40 50 60 70 80 90
405 0x05D9, 0x05DB, 0x05DC, 0x05DE, 0x05E0, 0x05E1, 0x05E2, 0x05E4, 0x05E6,
406 // 100 200 300 400
407 0x05E7, 0x05E8, 0x05E9, 0x05EA
410 static bool
411 HebrewToText(CounterValue aOrdinal, nsSubstring& aResult)
413 if (aOrdinal < 1 || aOrdinal > 999999) {
414 return false;
417 bool outputSep = false;
418 nsAutoString allText, thousandsGroup;
419 do {
420 thousandsGroup.Truncate();
421 int32_t n3 = aOrdinal % 1000;
422 // Process digit for 100 - 900
423 for(int32_t n1 = 400; n1 > 0; )
425 if( n3 >= n1)
427 n3 -= n1;
428 thousandsGroup.Append(gHebrewDigit[(n1/100)-1+18]);
429 } else {
430 n1 -= 100;
431 } // if
432 } // for
434 // Process digit for 10 - 90
435 int32_t n2;
436 if( n3 >= 10 )
438 // Special process for 15 and 16
439 if(( 15 == n3 ) || (16 == n3)) {
440 // Special rule for religious reason...
441 // 15 is represented by 9 and 6, not 10 and 5
442 // 16 is represented by 9 and 7, not 10 and 6
443 n2 = 9;
444 thousandsGroup.Append(gHebrewDigit[ n2 - 1]);
445 } else {
446 n2 = n3 - (n3 % 10);
447 thousandsGroup.Append(gHebrewDigit[(n2/10)-1+9]);
448 } // if
449 n3 -= n2;
450 } // if
452 // Process digit for 1 - 9
453 if ( n3 > 0)
454 thousandsGroup.Append(gHebrewDigit[n3-1]);
455 if (outputSep)
456 thousandsGroup.Append((char16_t)HEBREW_GERESH);
457 if (allText.IsEmpty())
458 allText = thousandsGroup;
459 else
460 allText = thousandsGroup + allText;
461 aOrdinal /= 1000;
462 outputSep = true;
463 } while (aOrdinal >= 1);
465 aResult = allText;
466 return true;
469 // Convert ordinal to Ethiopic numeric representation.
470 // The detail is available at http://www.ethiopic.org/Numerals/
471 // The algorithm used here is based on the pseudo-code put up there by
472 // Daniel Yacob <yacob@geez.org>.
473 // Another reference is Unicode 3.0 standard section 11.1.
474 #define ETHIOPIC_ONE 0x1369
475 #define ETHIOPIC_TEN 0x1372
476 #define ETHIOPIC_HUNDRED 0x137B
477 #define ETHIOPIC_TEN_THOUSAND 0x137C
479 static bool
480 EthiopicToText(CounterValue aOrdinal, nsSubstring& aResult)
482 if (aOrdinal < 1) {
483 return false;
486 nsAutoString asciiNumberString; // decimal string representation of ordinal
487 DecimalToText(aOrdinal, asciiNumberString);
488 uint8_t asciiStringLength = asciiNumberString.Length();
490 // If number length is odd, add a leading "0"
491 // the leading "0" preconditions the string to always have the
492 // leading tens place populated, this avoids a check within the loop.
493 // If we didn't add the leading "0", decrement asciiStringLength so
494 // it will be equivalent to a zero-based index in both cases.
495 if (asciiStringLength & 1) {
496 asciiNumberString.Insert(NS_LITERAL_STRING("0"), 0);
497 } else {
498 asciiStringLength--;
501 aResult.Truncate();
502 // Iterate from the highest digits to lowest
503 // indexFromLeft indexes digits (0 = most significant)
504 // groupIndexFromRight indexes pairs of digits (0 = least significant)
505 for (uint8_t indexFromLeft = 0, groupIndexFromRight = asciiStringLength >> 1;
506 indexFromLeft <= asciiStringLength;
507 indexFromLeft += 2, groupIndexFromRight--) {
508 uint8_t tensValue = asciiNumberString.CharAt(indexFromLeft) & 0x0F;
509 uint8_t unitsValue = asciiNumberString.CharAt(indexFromLeft + 1) & 0x0F;
510 uint8_t groupValue = tensValue * 10 + unitsValue;
512 bool oddGroup = (groupIndexFromRight & 1);
514 // we want to clear ETHIOPIC_ONE when it is superfluous
515 if (aOrdinal > 1 &&
516 groupValue == 1 && // one without a leading ten
517 (oddGroup || indexFromLeft == 0)) { // preceding (100) or leading the sequence
518 unitsValue = 0;
521 // put it all together...
522 if (tensValue) {
523 // map onto Ethiopic "tens":
524 aResult.Append((char16_t) (tensValue + ETHIOPIC_TEN - 1));
526 if (unitsValue) {
527 //map onto Ethiopic "units":
528 aResult.Append((char16_t) (unitsValue + ETHIOPIC_ONE - 1));
530 // Add a separator for all even groups except the last,
531 // and for odd groups with non-zero value.
532 if (oddGroup) {
533 if (groupValue) {
534 aResult.Append((char16_t) ETHIOPIC_HUNDRED);
536 } else {
537 if (groupIndexFromRight) {
538 aResult.Append((char16_t) ETHIOPIC_TEN_THOUSAND);
542 return true;
545 class BuiltinCounterStyle : public CounterStyle
547 public:
548 friend class CounterStyleManager;
550 // will be initialized by CounterStyleManager::InitializeBuiltinCounterStyles
551 MOZ_CONSTEXPR BuiltinCounterStyle()
552 : CounterStyle(NS_STYLE_LIST_STYLE_NONE)
556 protected:
557 MOZ_CONSTEXPR explicit BuiltinCounterStyle(int32_t aStyle)
558 : CounterStyle(aStyle)
562 public:
563 virtual void GetPrefix(nsSubstring& aResult) MOZ_OVERRIDE;
564 virtual void GetSuffix(nsSubstring& aResult) MOZ_OVERRIDE;
565 virtual void GetSpokenCounterText(CounterValue aOrdinal,
566 WritingMode aWritingMode,
567 nsSubstring& aResult,
568 bool& aIsBullet) MOZ_OVERRIDE;
569 virtual bool IsBullet() MOZ_OVERRIDE;
571 virtual void GetNegative(NegativeType& aResult) MOZ_OVERRIDE;
572 virtual bool IsOrdinalInRange(CounterValue aOrdinal) MOZ_OVERRIDE;
573 virtual bool IsOrdinalInAutoRange(CounterValue aOrdinal) MOZ_OVERRIDE;
574 virtual void GetPad(PadType& aResult) MOZ_OVERRIDE;
575 virtual CounterStyle* GetFallback() MOZ_OVERRIDE;
576 virtual uint8_t GetSpeakAs() MOZ_OVERRIDE;
577 virtual bool UseNegativeSign() MOZ_OVERRIDE;
579 virtual void CallFallbackStyle(CounterValue aOrdinal,
580 WritingMode aWritingMode,
581 nsSubstring& aResult,
582 bool& aIsRTL) MOZ_OVERRIDE;
583 virtual bool GetInitialCounterText(CounterValue aOrdinal,
584 WritingMode aWritingMode,
585 nsSubstring& aResult,
586 bool& aIsRTL) MOZ_OVERRIDE;
588 // Builtin counter style does not need refcount at all
589 NS_IMETHOD_(MozExternalRefCountType) AddRef() MOZ_OVERRIDE { return 2; }
590 NS_IMETHOD_(MozExternalRefCountType) Release() MOZ_OVERRIDE { return 2; }
593 /* virtual */ void
594 BuiltinCounterStyle::GetPrefix(nsSubstring& aResult)
596 aResult.Truncate();
599 /* virtual */ void
600 BuiltinCounterStyle::GetSuffix(nsSubstring& aResult)
602 switch (mStyle) {
603 case NS_STYLE_LIST_STYLE_NONE:
604 aResult.Truncate();
605 break;
607 case NS_STYLE_LIST_STYLE_DISC:
608 case NS_STYLE_LIST_STYLE_CIRCLE:
609 case NS_STYLE_LIST_STYLE_SQUARE:
610 case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED:
611 case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN:
612 aResult = ' ';
613 break;
615 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL:
616 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL:
617 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL:
618 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL:
619 case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL:
620 case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL:
621 aResult = 0x3001;
622 break;
624 case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL:
625 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL:
626 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL:
627 aResult.AssignLiteral(MOZ_UTF16(", "));
628 break;
630 default:
631 aResult.AssignLiteral(MOZ_UTF16(". "));
632 break;
636 static const char16_t kDiscCharacter = 0x2022;
637 static const char16_t kCircleCharacter = 0x25e6;
638 static const char16_t kSquareCharacter = 0x25fe;
639 static const char16_t kRightPointingCharacter = 0x25b8;
640 static const char16_t kLeftPointingCharacter = 0x25c2;
641 static const char16_t kDownPointingCharacter = 0x25be;
643 /* virtual */ void
644 BuiltinCounterStyle::GetSpokenCounterText(CounterValue aOrdinal,
645 WritingMode aWritingMode,
646 nsSubstring& aResult,
647 bool& aIsBullet)
649 switch (mStyle) {
650 case NS_STYLE_LIST_STYLE_NONE:
651 case NS_STYLE_LIST_STYLE_DISC:
652 case NS_STYLE_LIST_STYLE_CIRCLE:
653 case NS_STYLE_LIST_STYLE_SQUARE:
654 case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED:
655 case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN: {
656 // Same as the initial representation
657 bool isRTL;
658 GetInitialCounterText(aOrdinal, aWritingMode, aResult, isRTL);
659 aIsBullet = true;
660 break;
662 default:
663 CounterStyle::GetSpokenCounterText(
664 aOrdinal, aWritingMode, aResult, aIsBullet);
665 break;
669 /* virtual */ bool
670 BuiltinCounterStyle::IsBullet()
672 switch (mStyle) {
673 case NS_STYLE_LIST_STYLE_DISC:
674 case NS_STYLE_LIST_STYLE_CIRCLE:
675 case NS_STYLE_LIST_STYLE_SQUARE:
676 case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED:
677 case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN:
678 return true;
679 default:
680 return false;
684 static const char16_t gJapaneseNegative[] = {
685 0x30de, 0x30a4, 0x30ca, 0x30b9, 0x0000
687 static const char16_t gKoreanNegative[] = {
688 0xb9c8, 0xc774, 0xb108, 0xc2a4, 0x0020, 0x0000
690 static const char16_t gSimpChineseNegative[] = {
691 0x8d1f, 0x0000
693 static const char16_t gTradChineseNegative[] = {
694 0x8ca0, 0x0000
697 /* virtual */ void
698 BuiltinCounterStyle::GetNegative(NegativeType& aResult)
700 switch (mStyle) {
701 case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL:
702 case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL:
703 aResult.before = gJapaneseNegative;
704 break;
706 case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL:
707 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL:
708 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL:
709 aResult.before = gKoreanNegative;
710 break;
712 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL:
713 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL:
714 aResult.before = gSimpChineseNegative;
715 break;
717 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL:
718 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL:
719 aResult.before = gTradChineseNegative;
720 break;
722 default:
723 aResult.before.AssignLiteral(MOZ_UTF16("-"));
725 aResult.after.Truncate();
728 /* virtual */ bool
729 BuiltinCounterStyle::IsOrdinalInRange(CounterValue aOrdinal)
731 switch (mStyle) {
732 default:
733 // cyclic
734 case NS_STYLE_LIST_STYLE_NONE:
735 case NS_STYLE_LIST_STYLE_DISC:
736 case NS_STYLE_LIST_STYLE_CIRCLE:
737 case NS_STYLE_LIST_STYLE_SQUARE:
738 case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED:
739 case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN:
740 // use DecimalToText
741 case NS_STYLE_LIST_STYLE_DECIMAL:
742 // use CJKIdeographicToText
743 case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL:
744 case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL:
745 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL:
746 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL:
747 case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL:
748 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL:
749 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL:
750 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL:
751 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL:
752 return true;
754 // use EthiopicToText
755 case NS_STYLE_LIST_STYLE_ETHIOPIC_NUMERIC:
756 return aOrdinal >= 1;
758 // use HebrewToText
759 case NS_STYLE_LIST_STYLE_HEBREW:
760 return aOrdinal >= 1 && aOrdinal <= 999999;
764 /* virtual */ bool
765 BuiltinCounterStyle::IsOrdinalInAutoRange(CounterValue aOrdinal)
767 switch (mStyle) {
768 // cyclic:
769 case NS_STYLE_LIST_STYLE_NONE:
770 case NS_STYLE_LIST_STYLE_DISC:
771 case NS_STYLE_LIST_STYLE_CIRCLE:
772 case NS_STYLE_LIST_STYLE_SQUARE:
773 case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED:
774 case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN:
775 // numeric:
776 case NS_STYLE_LIST_STYLE_DECIMAL:
777 return true;
779 // additive:
780 case NS_STYLE_LIST_STYLE_HEBREW:
781 return aOrdinal >= 0;
783 // complex predefined:
784 case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL:
785 case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL:
786 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL:
787 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL:
788 case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL:
789 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL:
790 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL:
791 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL:
792 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL:
793 case NS_STYLE_LIST_STYLE_ETHIOPIC_NUMERIC:
794 return IsOrdinalInRange(aOrdinal);
796 default:
797 NS_NOTREACHED("Unknown counter style");
798 return false;
802 /* virtual */ void
803 BuiltinCounterStyle::GetPad(PadType& aResult)
805 aResult.width = 0;
806 aResult.symbol.Truncate();
809 /* virtual */ CounterStyle*
810 BuiltinCounterStyle::GetFallback()
812 // Fallback of dependent builtin counter styles are handled in class
813 // DependentBuiltinCounterStyle.
814 return CounterStyleManager::GetDecimalStyle();
817 /* virtual */ uint8_t
818 BuiltinCounterStyle::GetSpeakAs()
820 switch (mStyle) {
821 case NS_STYLE_LIST_STYLE_NONE:
822 case NS_STYLE_LIST_STYLE_DISC:
823 case NS_STYLE_LIST_STYLE_CIRCLE:
824 case NS_STYLE_LIST_STYLE_SQUARE:
825 case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED:
826 case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN:
827 return NS_STYLE_COUNTER_SPEAKAS_BULLETS;
828 default:
829 return NS_STYLE_COUNTER_SPEAKAS_NUMBERS;
833 /* virtual */ bool
834 BuiltinCounterStyle::UseNegativeSign()
836 switch (mStyle) {
837 case NS_STYLE_LIST_STYLE_NONE:
838 case NS_STYLE_LIST_STYLE_DISC:
839 case NS_STYLE_LIST_STYLE_CIRCLE:
840 case NS_STYLE_LIST_STYLE_SQUARE:
841 case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED:
842 case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN:
843 return false;
844 default:
845 return true;
849 /* virtual */ void
850 BuiltinCounterStyle::CallFallbackStyle(CounterValue aOrdinal,
851 WritingMode aWritingMode,
852 nsSubstring& aResult,
853 bool& aIsRTL)
855 GetFallback()->GetCounterText(aOrdinal, aWritingMode, aResult, aIsRTL);
858 /* virtual */ bool
859 BuiltinCounterStyle::GetInitialCounterText(CounterValue aOrdinal,
860 WritingMode aWritingMode,
861 nsSubstring& aResult,
862 bool& aIsRTL)
864 aIsRTL = false;
865 switch (mStyle) {
866 // used by counters & extends counter-style code only
867 // XXX We really need to do this the same way we do list bullets.
868 case NS_STYLE_LIST_STYLE_NONE:
869 aResult.Truncate();
870 return true;
871 case NS_STYLE_LIST_STYLE_DISC:
872 aResult.Assign(kDiscCharacter);
873 return true;
874 case NS_STYLE_LIST_STYLE_CIRCLE:
875 aResult.Assign(kCircleCharacter);
876 return true;
877 case NS_STYLE_LIST_STYLE_SQUARE:
878 aResult.Assign(kSquareCharacter);
879 return true;
880 case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED:
881 if (aWritingMode.IsVertical()) {
882 aResult.Assign(kDownPointingCharacter);
883 } else if (aWritingMode.IsBidiLTR()) {
884 aResult.Assign(kRightPointingCharacter);
885 } else {
886 aResult.Assign(kLeftPointingCharacter);
888 return true;
889 case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN:
890 if (!aWritingMode.IsVertical()) {
891 aResult.Assign(kDownPointingCharacter);
892 } else if (aWritingMode.IsVerticalLR()) {
893 aResult.Assign(kRightPointingCharacter);
894 } else {
895 aResult.Assign(kLeftPointingCharacter);
897 return true;
899 case NS_STYLE_LIST_STYLE_DECIMAL:
900 return DecimalToText(aOrdinal, aResult);
902 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL:
903 return CJKIdeographicToText(aOrdinal, aResult, gDataTradChineseInformal);
904 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL:
905 return CJKIdeographicToText(aOrdinal, aResult, gDataTradChineseFormal);
906 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL:
907 return CJKIdeographicToText(aOrdinal, aResult, gDataSimpChineseInformal);
908 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL:
909 return CJKIdeographicToText(aOrdinal, aResult, gDataSimpChineseFormal);
910 case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL:
911 return CJKIdeographicToText(aOrdinal, aResult, gDataJapaneseInformal);
912 case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL:
913 return CJKIdeographicToText(aOrdinal, aResult, gDataJapaneseFormal);
914 case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL:
915 return CJKIdeographicToText(aOrdinal, aResult, gDataKoreanHangulFormal);
916 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL:
917 return CJKIdeographicToText(aOrdinal, aResult, gDataKoreanHanjaInformal);
918 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL:
919 return CJKIdeographicToText(aOrdinal, aResult, gDataKoreanHanjaFormal);
921 case NS_STYLE_LIST_STYLE_HEBREW:
922 aIsRTL = true;
923 return HebrewToText(aOrdinal, aResult);
925 case NS_STYLE_LIST_STYLE_ETHIOPIC_NUMERIC:
926 return EthiopicToText(aOrdinal, aResult);
928 default:
929 NS_NOTREACHED("Unknown builtin counter style");
930 return false;
934 class DependentBuiltinCounterStyle MOZ_FINAL : public BuiltinCounterStyle
936 private:
937 ~DependentBuiltinCounterStyle() {}
938 public:
939 DependentBuiltinCounterStyle(int32_t aStyle, CounterStyleManager* aManager)
940 : BuiltinCounterStyle(aStyle),
941 mManager(aManager)
943 NS_ASSERTION(IsDependentStyle(), "Not a dependent builtin style");
944 NS_ABORT_IF_FALSE(!IsCustomStyle(), "Not a builtin style");
947 virtual CounterStyle* GetFallback() MOZ_OVERRIDE;
949 // DependentBuiltinCounterStyle is managed in the same way as
950 // CustomCounterStyle.
951 NS_IMETHOD_(MozExternalRefCountType) AddRef() MOZ_OVERRIDE;
952 NS_IMETHOD_(MozExternalRefCountType) Release() MOZ_OVERRIDE;
954 void* operator new(size_t sz, nsPresContext* aPresContext) CPP_THROW_NEW
956 return aPresContext->PresShell()->AllocateByObjectID(
957 nsPresArena::DependentBuiltinCounterStyle_id, sz);
960 private:
961 void Destroy()
963 nsIPresShell* shell = mManager->PresContext()->PresShell();
964 this->~DependentBuiltinCounterStyle();
965 shell->FreeByObjectID(nsPresArena::DependentBuiltinCounterStyle_id, this);
968 CounterStyleManager* mManager;
970 nsAutoRefCnt mRefCnt;
971 NS_DECL_OWNINGTHREAD
974 NS_IMPL_ADDREF(DependentBuiltinCounterStyle)
975 NS_IMPL_RELEASE_WITH_DESTROY(DependentBuiltinCounterStyle, Destroy())
977 /* virtual */ CounterStyle*
978 DependentBuiltinCounterStyle::GetFallback()
980 switch (GetStyle()) {
981 case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL:
982 case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL:
983 case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL:
984 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL:
985 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL:
986 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL:
987 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL:
988 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL:
989 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL:
990 // These styles all have a larger range than cjk-decimal, so the
991 // only case fallback is accessed is that they are extended.
992 // Since extending styles will cache the data themselves, we need
993 // not cache it here.
994 return mManager->BuildCounterStyle(NS_LITERAL_STRING("cjk-decimal"));
995 default:
996 NS_NOTREACHED("Not a valid dependent builtin style");
997 return BuiltinCounterStyle::GetFallback();
1001 class CustomCounterStyle MOZ_FINAL : public CounterStyle
1003 private:
1004 ~CustomCounterStyle() {}
1005 public:
1006 CustomCounterStyle(CounterStyleManager* aManager,
1007 nsCSSCounterStyleRule* aRule)
1008 : CounterStyle(NS_STYLE_LIST_STYLE_CUSTOM),
1009 mManager(aManager),
1010 mRule(aRule),
1011 mRuleGeneration(aRule->GetGeneration()),
1012 mSystem(aRule->GetSystem()),
1013 mFlags(0),
1014 mFallback(nullptr),
1015 mSpeakAsCounter(nullptr),
1016 mExtends(nullptr),
1017 mExtendsRoot(nullptr)
1021 // This method will clear all cached data in the style and update the
1022 // generation number of the rule. It should be called when the rule of
1023 // this style is changed.
1024 void ResetCachedData();
1026 // This method will reset all cached data which may depend on other
1027 // counter style. It will reset all pointers to other counter styles.
1028 // For counter style extends other, in addition, all fields will be
1029 // reset to uninitialized state. This method should be called when any
1030 // other counter style is added, removed, or changed.
1031 void ResetDependentData();
1033 nsCSSCounterStyleRule* GetRule() const { return mRule; }
1034 uint32_t GetRuleGeneration() const { return mRuleGeneration; }
1036 virtual void GetPrefix(nsSubstring& aResult) MOZ_OVERRIDE;
1037 virtual void GetSuffix(nsSubstring& aResult) MOZ_OVERRIDE;
1038 virtual void GetSpokenCounterText(CounterValue aOrdinal,
1039 WritingMode aWritingMode,
1040 nsSubstring& aResult,
1041 bool& aIsBullet) MOZ_OVERRIDE;
1042 virtual bool IsBullet() MOZ_OVERRIDE;
1044 virtual void GetNegative(NegativeType& aResult) MOZ_OVERRIDE;
1045 virtual bool IsOrdinalInRange(CounterValue aOrdinal) MOZ_OVERRIDE;
1046 virtual bool IsOrdinalInAutoRange(CounterValue aOrdinal) MOZ_OVERRIDE;
1047 virtual void GetPad(PadType& aResult) MOZ_OVERRIDE;
1048 virtual CounterStyle* GetFallback() MOZ_OVERRIDE;
1049 virtual uint8_t GetSpeakAs() MOZ_OVERRIDE;
1050 virtual bool UseNegativeSign() MOZ_OVERRIDE;
1052 virtual void CallFallbackStyle(CounterValue aOrdinal,
1053 WritingMode aWritingMode,
1054 nsSubstring& aResult,
1055 bool& aIsRTL) MOZ_OVERRIDE;
1056 virtual bool GetInitialCounterText(CounterValue aOrdinal,
1057 WritingMode aWritingMode,
1058 nsSubstring& aResult,
1059 bool& aIsRTL) MOZ_OVERRIDE;
1061 bool IsExtendsSystem()
1063 return mSystem == NS_STYLE_COUNTER_SYSTEM_EXTENDS;
1066 // CustomCounterStyle should be reference-counted because it may be
1067 // dereferenced from the manager but still referenced by nodes and
1068 // frames before the style change is propagated.
1069 NS_IMETHOD_(MozExternalRefCountType) AddRef() MOZ_OVERRIDE;
1070 NS_IMETHOD_(MozExternalRefCountType) Release() MOZ_OVERRIDE;
1072 void* operator new(size_t sz, nsPresContext* aPresContext) CPP_THROW_NEW
1074 return aPresContext->PresShell()->AllocateByObjectID(
1075 nsPresArena::CustomCounterStyle_id, sz);
1078 private:
1079 void Destroy()
1081 nsIPresShell* shell = mManager->PresContext()->PresShell();
1082 this->~CustomCounterStyle();
1083 shell->FreeByObjectID(nsPresArena::CustomCounterStyle_id, this);
1086 const nsTArray<nsString>& GetSymbols();
1087 const nsTArray<AdditiveSymbol>& GetAdditiveSymbols();
1089 // The speak-as values of counter styles may form a loop, and the
1090 // loops may have complex interaction with the loop formed by
1091 // extending. To solve this problem, the computation of speak-as is
1092 // divided into two phases:
1093 // 1. figure out the raw value, by ComputeRawSpeakAs, and
1094 // 2. eliminate loop, by ComputeSpeakAs.
1095 // See comments before the definitions of these methods for details.
1096 uint8_t GetSpeakAsAutoValue();
1097 void ComputeRawSpeakAs(uint8_t& aSpeakAs,
1098 CounterStyle*& aSpeakAsCounter);
1099 CounterStyle* ComputeSpeakAs();
1101 CounterStyle* ComputeExtends();
1102 CounterStyle* GetExtends();
1103 CounterStyle* GetExtendsRoot();
1105 // CounterStyleManager should always overlive any CounterStyle as it
1106 // is owned by nsPresContext, and will be released after all nodes and
1107 // frames are released.
1108 CounterStyleManager* mManager;
1110 nsRefPtr<nsCSSCounterStyleRule> mRule;
1111 uint32_t mRuleGeneration;
1113 uint8_t mSystem;
1114 uint8_t mSpeakAs;
1116 enum {
1117 // loop detection
1118 FLAG_EXTENDS_VISITED = 1 << 0,
1119 FLAG_EXTENDS_LOOP = 1 << 1,
1120 FLAG_SPEAKAS_VISITED = 1 << 2,
1121 FLAG_SPEAKAS_LOOP = 1 << 3,
1122 // field status
1123 FLAG_NEGATIVE_INITED = 1 << 4,
1124 FLAG_PREFIX_INITED = 1 << 5,
1125 FLAG_SUFFIX_INITED = 1 << 6,
1126 FLAG_PAD_INITED = 1 << 7,
1127 FLAG_SPEAKAS_INITED = 1 << 8,
1129 uint16_t mFlags;
1131 // Fields below will be initialized when necessary.
1132 nsTArray<nsString> mSymbols;
1133 nsTArray<AdditiveSymbol> mAdditiveSymbols;
1134 NegativeType mNegative;
1135 nsString mPrefix, mSuffix;
1136 PadType mPad;
1138 // CounterStyleManager will guarantee that none of the pointers below
1139 // refers to a freed CounterStyle. There are two possible cases where
1140 // the manager will release its reference to a CounterStyle: 1. the
1141 // manager itself is released, 2. a rule is invalidated. In the first
1142 // case, all counter style are removed from the manager, and should
1143 // also have been dereferenced from other objects. All styles will be
1144 // released all together. In the second case, CounterStyleManager::
1145 // NotifyRuleChanged will guarantee that all pointers will be reset
1146 // before any CounterStyle is released.
1148 CounterStyle* mFallback;
1149 // This field refers to the last counter in a speak-as chain.
1150 // That counter must not speak as another counter.
1151 CounterStyle* mSpeakAsCounter;
1153 CounterStyle* mExtends;
1154 // This field refers to the last counter in the extends chain. The
1155 // counter must be either a builtin style or a style whose system is
1156 // not 'extends'.
1157 CounterStyle* mExtendsRoot;
1159 nsAutoRefCnt mRefCnt;
1160 NS_DECL_OWNINGTHREAD
1163 NS_IMPL_ADDREF(CustomCounterStyle)
1164 NS_IMPL_RELEASE_WITH_DESTROY(CustomCounterStyle, Destroy())
1166 void
1167 CustomCounterStyle::ResetCachedData()
1169 mSymbols.Clear();
1170 mAdditiveSymbols.Clear();
1171 mFlags &= ~(FLAG_NEGATIVE_INITED |
1172 FLAG_PREFIX_INITED |
1173 FLAG_SUFFIX_INITED |
1174 FLAG_PAD_INITED |
1175 FLAG_SPEAKAS_INITED);
1176 mFallback = nullptr;
1177 mSpeakAsCounter = nullptr;
1178 mExtends = nullptr;
1179 mExtendsRoot = nullptr;
1180 mRuleGeneration = mRule->GetGeneration();
1183 void
1184 CustomCounterStyle::ResetDependentData()
1186 mFlags &= ~FLAG_SPEAKAS_INITED;
1187 mSpeakAsCounter = nullptr;
1188 mFallback = nullptr;
1189 mExtends = nullptr;
1190 mExtendsRoot = nullptr;
1191 if (IsExtendsSystem()) {
1192 mFlags &= ~(FLAG_NEGATIVE_INITED |
1193 FLAG_PREFIX_INITED |
1194 FLAG_SUFFIX_INITED |
1195 FLAG_PAD_INITED);
1199 /* virtual */ void
1200 CustomCounterStyle::GetPrefix(nsSubstring& aResult)
1202 if (!(mFlags & FLAG_PREFIX_INITED)) {
1203 mFlags |= FLAG_PREFIX_INITED;
1205 const nsCSSValue& value = mRule->GetDesc(eCSSCounterDesc_Prefix);
1206 if (value.UnitHasStringValue()) {
1207 value.GetStringValue(mPrefix);
1208 } else if (IsExtendsSystem()) {
1209 GetExtends()->GetPrefix(mPrefix);
1210 } else {
1211 mPrefix.Truncate();
1214 aResult = mPrefix;
1217 /* virtual */ void
1218 CustomCounterStyle::GetSuffix(nsSubstring& aResult)
1220 if (!(mFlags & FLAG_SUFFIX_INITED)) {
1221 mFlags |= FLAG_SUFFIX_INITED;
1223 const nsCSSValue& value = mRule->GetDesc(eCSSCounterDesc_Suffix);
1224 if (value.UnitHasStringValue()) {
1225 value.GetStringValue(mSuffix);
1226 } else if (IsExtendsSystem()) {
1227 GetExtends()->GetSuffix(mSuffix);
1228 } else {
1229 mSuffix.AssignLiteral(MOZ_UTF16(". "));
1232 aResult = mSuffix;
1235 /* virtual */ void
1236 CustomCounterStyle::GetSpokenCounterText(CounterValue aOrdinal,
1237 WritingMode aWritingMode,
1238 nsSubstring& aResult,
1239 bool& aIsBullet)
1241 if (GetSpeakAs() != NS_STYLE_COUNTER_SPEAKAS_OTHER) {
1242 CounterStyle::GetSpokenCounterText(
1243 aOrdinal, aWritingMode, aResult, aIsBullet);
1244 } else {
1245 NS_ABORT_IF_FALSE(mSpeakAsCounter,
1246 "mSpeakAsCounter should have been initialized.");
1247 mSpeakAsCounter->GetSpokenCounterText(
1248 aOrdinal, aWritingMode, aResult, aIsBullet);
1252 /* virtual */ bool
1253 CustomCounterStyle::IsBullet()
1255 switch (mSystem) {
1256 case NS_STYLE_COUNTER_SYSTEM_CYCLIC:
1257 // Only use ::-moz-list-bullet for cyclic system
1258 return true;
1259 case NS_STYLE_COUNTER_SYSTEM_EXTENDS:
1260 return GetExtendsRoot()->IsBullet();
1261 default:
1262 return false;
1266 /* virtual */ void
1267 CustomCounterStyle::GetNegative(NegativeType& aResult)
1269 if (!(mFlags & FLAG_NEGATIVE_INITED)) {
1270 mFlags |= FLAG_NEGATIVE_INITED;
1271 const nsCSSValue& value = mRule->GetDesc(eCSSCounterDesc_Negative);
1272 switch (value.GetUnit()) {
1273 case eCSSUnit_Ident:
1274 case eCSSUnit_String:
1275 value.GetStringValue(mNegative.before);
1276 mNegative.after.Truncate();
1277 break;
1278 case eCSSUnit_Pair: {
1279 const nsCSSValuePair& pair = value.GetPairValue();
1280 pair.mXValue.GetStringValue(mNegative.before);
1281 pair.mYValue.GetStringValue(mNegative.after);
1282 break;
1284 default: {
1285 if (IsExtendsSystem()) {
1286 GetExtends()->GetNegative(mNegative);
1287 } else {
1288 mNegative.before.AssignLiteral(MOZ_UTF16("-"));
1289 mNegative.after.Truncate();
1294 aResult = mNegative;
1297 static inline bool
1298 IsRangeValueInfinite(const nsCSSValue& aValue)
1300 return aValue.GetUnit() == eCSSUnit_Enumerated &&
1301 aValue.GetIntValue() == NS_STYLE_COUNTER_RANGE_INFINITE;
1304 /* virtual */ bool
1305 CustomCounterStyle::IsOrdinalInRange(CounterValue aOrdinal)
1307 const nsCSSValue& value = mRule->GetDesc(eCSSCounterDesc_Range);
1308 if (value.GetUnit() == eCSSUnit_PairList) {
1309 for (const nsCSSValuePairList* item = value.GetPairListValue();
1310 item != nullptr; item = item->mNext) {
1311 const nsCSSValue& lowerBound = item->mXValue;
1312 const nsCSSValue& upperBound = item->mYValue;
1313 if ((IsRangeValueInfinite(lowerBound) ||
1314 aOrdinal >= lowerBound.GetIntValue()) &&
1315 (IsRangeValueInfinite(upperBound) ||
1316 aOrdinal <= upperBound.GetIntValue())) {
1317 return true;
1320 return false;
1321 } else if (IsExtendsSystem() && value.GetUnit() == eCSSUnit_None) {
1322 // Only use the range of extended style when 'range' is not specified.
1323 return GetExtends()->IsOrdinalInRange(aOrdinal);
1325 return IsOrdinalInAutoRange(aOrdinal);
1328 /* virtual */ bool
1329 CustomCounterStyle::IsOrdinalInAutoRange(CounterValue aOrdinal)
1331 switch (mSystem) {
1332 case NS_STYLE_COUNTER_SYSTEM_CYCLIC:
1333 case NS_STYLE_COUNTER_SYSTEM_NUMERIC:
1334 case NS_STYLE_COUNTER_SYSTEM_FIXED:
1335 return true;
1336 case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC:
1337 case NS_STYLE_COUNTER_SYSTEM_SYMBOLIC:
1338 return aOrdinal >= 1;
1339 case NS_STYLE_COUNTER_SYSTEM_ADDITIVE:
1340 return aOrdinal >= 0;
1341 case NS_STYLE_COUNTER_SYSTEM_EXTENDS:
1342 return GetExtendsRoot()->IsOrdinalInAutoRange(aOrdinal);
1343 default:
1344 NS_NOTREACHED("Invalid system for computing auto value.");
1345 return false;
1349 /* virtual */ void
1350 CustomCounterStyle::GetPad(PadType& aResult)
1352 if (!(mFlags & FLAG_PAD_INITED)) {
1353 mFlags |= FLAG_PAD_INITED;
1354 const nsCSSValue& value = mRule->GetDesc(eCSSCounterDesc_Pad);
1355 if (value.GetUnit() == eCSSUnit_Pair) {
1356 const nsCSSValuePair& pair = value.GetPairValue();
1357 mPad.width = pair.mXValue.GetIntValue();
1358 pair.mYValue.GetStringValue(mPad.symbol);
1359 } else if (IsExtendsSystem()) {
1360 GetExtends()->GetPad(mPad);
1361 } else {
1362 mPad.width = 0;
1363 mPad.symbol.Truncate();
1366 aResult = mPad;
1369 /* virtual */ CounterStyle*
1370 CustomCounterStyle::GetFallback()
1372 if (!mFallback) {
1373 const nsCSSValue& value = mRule->GetDesc(eCSSCounterDesc_Fallback);
1374 if (value.UnitHasStringValue()) {
1375 mFallback = mManager->BuildCounterStyle(
1376 nsDependentString(value.GetStringBufferValue()));
1377 } else if (IsExtendsSystem()) {
1378 mFallback = GetExtends()->GetFallback();
1379 } else {
1380 mFallback = CounterStyleManager::GetDecimalStyle();
1383 return mFallback;
1386 /* virtual */ uint8_t
1387 CustomCounterStyle::GetSpeakAs()
1389 if (!(mFlags & FLAG_SPEAKAS_INITED)) {
1390 ComputeSpeakAs();
1392 return mSpeakAs;
1395 /* virtual */ bool
1396 CustomCounterStyle::UseNegativeSign()
1398 switch (mSystem) {
1399 case NS_STYLE_COUNTER_SYSTEM_SYMBOLIC:
1400 case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC:
1401 case NS_STYLE_COUNTER_SYSTEM_NUMERIC:
1402 case NS_STYLE_COUNTER_SYSTEM_ADDITIVE:
1403 return true;
1404 case NS_STYLE_COUNTER_SYSTEM_EXTENDS:
1405 return GetExtendsRoot()->UseNegativeSign();
1406 default:
1407 return false;
1411 /* virtual */ void
1412 CustomCounterStyle::CallFallbackStyle(CounterValue aOrdinal,
1413 WritingMode aWritingMode,
1414 nsSubstring& aResult,
1415 bool& aIsRTL)
1417 CounterStyle* fallback = GetFallback();
1418 // If it recursively falls back to this counter style again,
1419 // it will then fallback to decimal to break the loop.
1420 mFallback = CounterStyleManager::GetDecimalStyle();
1421 fallback->GetCounterText(aOrdinal, aWritingMode, aResult, aIsRTL);
1422 mFallback = fallback;
1425 /* virtual */ bool
1426 CustomCounterStyle::GetInitialCounterText(CounterValue aOrdinal,
1427 WritingMode aWritingMode,
1428 nsSubstring& aResult,
1429 bool& aIsRTL)
1431 switch (mSystem) {
1432 case NS_STYLE_COUNTER_SYSTEM_CYCLIC:
1433 return GetCyclicCounterText(aOrdinal, aResult, GetSymbols());
1434 case NS_STYLE_COUNTER_SYSTEM_FIXED: {
1435 int32_t start = mRule->GetSystemArgument().GetIntValue();
1436 return GetFixedCounterText(aOrdinal, aResult, start, GetSymbols());
1438 case NS_STYLE_COUNTER_SYSTEM_SYMBOLIC:
1439 return GetSymbolicCounterText(aOrdinal, aResult, GetSymbols());
1440 case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC:
1441 return GetAlphabeticCounterText(aOrdinal, aResult, GetSymbols());
1442 case NS_STYLE_COUNTER_SYSTEM_NUMERIC:
1443 return GetNumericCounterText(aOrdinal, aResult, GetSymbols());
1444 case NS_STYLE_COUNTER_SYSTEM_ADDITIVE:
1445 return GetAdditiveCounterText(aOrdinal, aResult, GetAdditiveSymbols());
1446 case NS_STYLE_COUNTER_SYSTEM_EXTENDS:
1447 return GetExtendsRoot()->
1448 GetInitialCounterText(aOrdinal, aWritingMode, aResult, aIsRTL);
1449 default:
1450 NS_NOTREACHED("Invalid system.");
1451 return false;
1455 const nsTArray<nsString>&
1456 CustomCounterStyle::GetSymbols()
1458 if (mSymbols.IsEmpty()) {
1459 const nsCSSValue& values = mRule->GetDesc(eCSSCounterDesc_Symbols);
1460 for (const nsCSSValueList* item = values.GetListValue();
1461 item; item = item->mNext) {
1462 nsString* symbol = mSymbols.AppendElement();
1463 item->mValue.GetStringValue(*symbol);
1465 mSymbols.Compact();
1467 return mSymbols;
1470 const nsTArray<AdditiveSymbol>&
1471 CustomCounterStyle::GetAdditiveSymbols()
1473 if (mAdditiveSymbols.IsEmpty()) {
1474 const nsCSSValue& values = mRule->GetDesc(eCSSCounterDesc_AdditiveSymbols);
1475 for (const nsCSSValuePairList* item = values.GetPairListValue();
1476 item; item = item->mNext) {
1477 AdditiveSymbol* symbol = mAdditiveSymbols.AppendElement();
1478 symbol->weight = item->mXValue.GetIntValue();
1479 item->mYValue.GetStringValue(symbol->symbol);
1481 mAdditiveSymbols.Compact();
1483 return mAdditiveSymbols;
1486 // This method is used to provide the computed value for 'auto'.
1487 uint8_t
1488 CustomCounterStyle::GetSpeakAsAutoValue()
1490 uint8_t system = mSystem;
1491 if (IsExtendsSystem()) {
1492 CounterStyle* root = GetExtendsRoot();
1493 if (!root->IsCustomStyle()) {
1494 // It is safe to call GetSpeakAs on non-custom style.
1495 return root->GetSpeakAs();
1497 system = static_cast<CustomCounterStyle*>(root)->mSystem;
1499 switch (system) {
1500 case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC:
1501 return NS_STYLE_COUNTER_SPEAKAS_SPELL_OUT;
1502 case NS_STYLE_COUNTER_SYSTEM_CYCLIC:
1503 return NS_STYLE_COUNTER_SPEAKAS_BULLETS;
1504 default:
1505 return NS_STYLE_COUNTER_SPEAKAS_NUMBERS;
1509 // This method corresponds to the first stage of computation of the
1510 // value of speak-as. It will extract the value from the rule and
1511 // possibly recursively call itself on the extended style to figure
1512 // out the raw value. To keep things clear, this method is designed to
1513 // have no side effects (but functions it calls may still affect other
1514 // fields in the style.)
1515 void
1516 CustomCounterStyle::ComputeRawSpeakAs(uint8_t& aSpeakAs,
1517 CounterStyle*& aSpeakAsCounter)
1519 NS_ASSERTION(!(mFlags & FLAG_SPEAKAS_INITED),
1520 "ComputeRawSpeakAs is called with speak-as inited.");
1522 const nsCSSValue& value = mRule->GetDesc(eCSSCounterDesc_SpeakAs);
1523 switch (value.GetUnit()) {
1524 case eCSSUnit_Auto:
1525 aSpeakAs = GetSpeakAsAutoValue();
1526 break;
1527 case eCSSUnit_Enumerated:
1528 aSpeakAs = value.GetIntValue();
1529 break;
1530 case eCSSUnit_Ident:
1531 aSpeakAs = NS_STYLE_COUNTER_SPEAKAS_OTHER;
1532 aSpeakAsCounter = mManager->BuildCounterStyle(
1533 nsDependentString(value.GetStringBufferValue()));
1534 break;
1535 case eCSSUnit_Null: {
1536 if (!IsExtendsSystem()) {
1537 aSpeakAs = GetSpeakAsAutoValue();
1538 } else {
1539 CounterStyle* extended = GetExtends();
1540 if (!extended->IsCustomStyle()) {
1541 // It is safe to call GetSpeakAs on non-custom style.
1542 aSpeakAs = extended->GetSpeakAs();
1543 } else {
1544 CustomCounterStyle* custom =
1545 static_cast<CustomCounterStyle*>(extended);
1546 if (!(custom->mFlags & FLAG_SPEAKAS_INITED)) {
1547 custom->ComputeRawSpeakAs(aSpeakAs, aSpeakAsCounter);
1548 } else {
1549 aSpeakAs = custom->mSpeakAs;
1550 aSpeakAsCounter = custom->mSpeakAsCounter;
1554 break;
1556 default:
1557 NS_NOTREACHED("Invalid speak-as value");
1561 // This method corresponds to the second stage of getting speak-as
1562 // related values. It will recursively figure out the final value of
1563 // mSpeakAs and mSpeakAsCounter. This method returns nullptr if the
1564 // caller is in a loop, and the root counter style in the chain
1565 // otherwise. It use the same loop detection algorithm as
1566 // CustomCounterStyle::ComputeExtends, see comments before that
1567 // method for more details.
1568 CounterStyle*
1569 CustomCounterStyle::ComputeSpeakAs()
1571 if (mFlags & FLAG_SPEAKAS_INITED) {
1572 if (mSpeakAs == NS_STYLE_COUNTER_SPEAKAS_OTHER) {
1573 return mSpeakAsCounter;
1575 return this;
1578 if (mFlags & FLAG_SPEAKAS_VISITED) {
1579 // loop detected
1580 mFlags |= FLAG_SPEAKAS_LOOP;
1581 return nullptr;
1584 CounterStyle* speakAsCounter;
1585 ComputeRawSpeakAs(mSpeakAs, speakAsCounter);
1587 bool inLoop = false;
1588 if (mSpeakAs != NS_STYLE_COUNTER_SPEAKAS_OTHER) {
1589 mSpeakAsCounter = nullptr;
1590 } else if (!speakAsCounter->IsCustomStyle()) {
1591 mSpeakAsCounter = speakAsCounter;
1592 } else {
1593 mFlags |= FLAG_SPEAKAS_VISITED;
1594 CounterStyle* target =
1595 static_cast<CustomCounterStyle*>(speakAsCounter)->ComputeSpeakAs();
1596 mFlags &= ~FLAG_SPEAKAS_VISITED;
1598 if (target) {
1599 NS_ASSERTION(!(mFlags & FLAG_SPEAKAS_LOOP),
1600 "Invalid state for speak-as loop detecting");
1601 mSpeakAsCounter = target;
1602 } else {
1603 mSpeakAs = GetSpeakAsAutoValue();
1604 mSpeakAsCounter = nullptr;
1605 if (mFlags & FLAG_SPEAKAS_LOOP) {
1606 mFlags &= ~FLAG_SPEAKAS_LOOP;
1607 } else {
1608 inLoop = true;
1613 mFlags |= FLAG_SPEAKAS_INITED;
1614 if (inLoop) {
1615 return nullptr;
1617 return mSpeakAsCounter ? mSpeakAsCounter : this;
1620 // This method will recursively figure out mExtends in the whole chain.
1621 // It will return nullptr if the caller is in a loop, and return this
1622 // otherwise. To detect the loop, this method marks the style VISITED
1623 // before the recursive call. When a VISITED style is reached again, the
1624 // loop is detected, and flag LOOP will be marked on the first style in
1625 // loop. mExtends of all counter styles in loop will be set to decimal
1626 // according to the spec.
1627 CounterStyle*
1628 CustomCounterStyle::ComputeExtends()
1630 if (!IsExtendsSystem() || mExtends) {
1631 return this;
1633 if (mFlags & FLAG_EXTENDS_VISITED) {
1634 // loop detected
1635 mFlags |= FLAG_EXTENDS_LOOP;
1636 return nullptr;
1639 const nsCSSValue& value = mRule->GetSystemArgument();
1640 CounterStyle* nextCounter = mManager->BuildCounterStyle(
1641 nsDependentString(value.GetStringBufferValue()));
1642 CounterStyle* target = nextCounter;
1643 if (nextCounter->IsCustomStyle()) {
1644 mFlags |= FLAG_EXTENDS_VISITED;
1645 target = static_cast<CustomCounterStyle*>(nextCounter)->ComputeExtends();
1646 mFlags &= ~FLAG_EXTENDS_VISITED;
1649 if (target) {
1650 NS_ASSERTION(!(mFlags & FLAG_EXTENDS_LOOP),
1651 "Invalid state for extends loop detecting");
1652 mExtends = nextCounter;
1653 return this;
1654 } else {
1655 mExtends = CounterStyleManager::GetDecimalStyle();
1656 if (mFlags & FLAG_EXTENDS_LOOP) {
1657 mFlags &= ~FLAG_EXTENDS_LOOP;
1658 return this;
1659 } else {
1660 return nullptr;
1665 CounterStyle*
1666 CustomCounterStyle::GetExtends()
1668 if (!mExtends) {
1669 // Any extends loop will be eliminated in the method below.
1670 ComputeExtends();
1672 return mExtends;
1675 CounterStyle*
1676 CustomCounterStyle::GetExtendsRoot()
1678 if (!mExtendsRoot) {
1679 CounterStyle* extended = GetExtends();
1680 mExtendsRoot = extended;
1681 if (extended->IsCustomStyle()) {
1682 CustomCounterStyle* custom = static_cast<CustomCounterStyle*>(extended);
1683 if (custom->IsExtendsSystem()) {
1684 // This will make mExtendsRoot in the whole extends chain be
1685 // set recursively, which could save work when part of a chain
1686 // is shared by multiple counter styles.
1687 mExtendsRoot = custom->GetExtendsRoot();
1691 return mExtendsRoot;
1694 bool
1695 CounterStyle::IsDependentStyle() const
1697 switch (mStyle) {
1698 // CustomCounterStyle
1699 case NS_STYLE_LIST_STYLE_CUSTOM:
1700 // DependentBuiltinCounterStyle
1701 case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL:
1702 case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL:
1703 case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL:
1704 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL:
1705 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL:
1706 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL:
1707 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL:
1708 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL:
1709 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL:
1710 return true;
1712 // BuiltinCounterStyle
1713 default:
1714 return false;
1718 static int32_t
1719 CountGraphemeClusters(const nsSubstring& aText)
1721 using mozilla::unicode::ClusterIterator;
1722 ClusterIterator iter(aText.Data(), aText.Length());
1723 int32_t result = 0;
1724 while (!iter.AtEnd()) {
1725 ++result;
1726 iter.Next();
1728 return result;
1731 void
1732 CounterStyle::GetCounterText(CounterValue aOrdinal,
1733 WritingMode aWritingMode,
1734 nsSubstring& aResult,
1735 bool& aIsRTL)
1737 bool success = IsOrdinalInRange(aOrdinal);
1738 aIsRTL = false;
1740 if (success) {
1741 // generate initial representation
1742 bool useNegativeSign = UseNegativeSign();
1743 nsAutoString initialText;
1744 CounterValue ordinal;
1745 if (!useNegativeSign) {
1746 ordinal = aOrdinal;
1747 } else {
1748 CheckedInt<CounterValue> absolute(Abs(aOrdinal));
1749 ordinal = absolute.isValid() ?
1750 absolute.value() : std::numeric_limits<CounterValue>::max();
1752 success = GetInitialCounterText(
1753 ordinal, aWritingMode, initialText, aIsRTL);
1755 // add pad & negative, build the final result
1756 if (success) {
1757 PadType pad;
1758 GetPad(pad);
1759 // We have to calculate the difference here since suffix part of negative
1760 // sign may be appended to initialText later.
1761 int32_t diff = pad.width - CountGraphemeClusters(initialText);
1762 aResult.Truncate();
1763 if (useNegativeSign && aOrdinal < 0) {
1764 NegativeType negative;
1765 GetNegative(negative);
1766 aResult.Append(negative.before);
1767 // There is nothing between the suffix part of negative and initial
1768 // representation, so we append it directly here.
1769 initialText.Append(negative.after);
1771 if (diff > 0) {
1772 auto length = pad.symbol.Length();
1773 if (diff > LENGTH_LIMIT || length > LENGTH_LIMIT ||
1774 diff * length > LENGTH_LIMIT) {
1775 success = false;
1776 } else if (length > 0) {
1777 for (int32_t i = 0; i < diff; ++i) {
1778 aResult.Append(pad.symbol);
1782 if (success) {
1783 aResult.Append(initialText);
1788 if (!success) {
1789 CallFallbackStyle(aOrdinal, aWritingMode, aResult, aIsRTL);
1793 /* virtual */ void
1794 CounterStyle::GetSpokenCounterText(CounterValue aOrdinal,
1795 WritingMode aWritingMode,
1796 nsSubstring& aResult,
1797 bool& aIsBullet)
1799 bool isRTL; // we don't care about direction for spoken text
1800 aIsBullet = false;
1801 switch (GetSpeakAs()) {
1802 case NS_STYLE_COUNTER_SPEAKAS_BULLETS:
1803 aResult.Assign(kDiscCharacter);
1804 aIsBullet = true;
1805 break;
1806 case NS_STYLE_COUNTER_SPEAKAS_NUMBERS:
1807 DecimalToText(aOrdinal, aResult);
1808 break;
1809 case NS_STYLE_COUNTER_SPEAKAS_SPELL_OUT:
1810 // we currently do not actually support 'spell-out',
1811 // so 'words' is used instead.
1812 case NS_STYLE_COUNTER_SPEAKAS_WORDS:
1813 GetCounterText(aOrdinal, WritingMode(), aResult, isRTL);
1814 break;
1815 case NS_STYLE_COUNTER_SPEAKAS_OTHER:
1816 // This should be processed by CustomCounterStyle
1817 NS_NOTREACHED("Invalid speak-as value");
1818 break;
1819 default:
1820 NS_NOTREACHED("Unknown speak-as value");
1821 break;
1825 static BuiltinCounterStyle gBuiltinStyleTable[NS_STYLE_LIST_STYLE__MAX];
1827 CounterStyleManager::CounterStyleManager(nsPresContext* aPresContext)
1828 : mPresContext(aPresContext)
1830 // Insert the static styles into cache table
1831 mCacheTable.Put(NS_LITERAL_STRING("none"), GetNoneStyle());
1832 mCacheTable.Put(NS_LITERAL_STRING("decimal"), GetDecimalStyle());
1835 CounterStyleManager::~CounterStyleManager()
1837 NS_ABORT_IF_FALSE(!mPresContext, "Disconnect should have been called");
1840 /* static */ void
1841 CounterStyleManager::InitializeBuiltinCounterStyles()
1843 for (uint32_t i = 0; i < NS_STYLE_LIST_STYLE__MAX; ++i) {
1844 gBuiltinStyleTable[i].mStyle = i;
1848 #ifdef DEBUG
1849 static PLDHashOperator
1850 CheckRefCount(const nsSubstring& aKey,
1851 CounterStyle* aStyle,
1852 void* aArg)
1854 aStyle->AddRef();
1855 auto refcnt = aStyle->Release();
1856 NS_ASSERTION(!aStyle->IsDependentStyle() || refcnt == 1,
1857 "Counter style is still referenced by other objects.");
1858 return PL_DHASH_NEXT;
1860 #endif
1862 void
1863 CounterStyleManager::Disconnect()
1865 #ifdef DEBUG
1866 mCacheTable.EnumerateRead(CheckRefCount, nullptr);
1867 #endif
1868 mCacheTable.Clear();
1869 mPresContext = nullptr;
1872 CounterStyle*
1873 CounterStyleManager::BuildCounterStyle(const nsSubstring& aName)
1875 CounterStyle* data = mCacheTable.GetWeak(aName);
1876 if (data) {
1877 return data;
1880 // It is intentional that the predefined names are case-insensitive
1881 // but the user-defined names case-sensitive.
1882 nsCSSCounterStyleRule* rule =
1883 mPresContext->StyleSet()->CounterStyleRuleForName(mPresContext, aName);
1884 if (rule) {
1885 data = new (mPresContext) CustomCounterStyle(this, rule);
1886 } else {
1887 int32_t type;
1888 nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(aName);
1889 if (nsCSSProps::FindKeyword(keyword, nsCSSProps::kListStyleKTable, type)) {
1890 if (gBuiltinStyleTable[type].IsDependentStyle()) {
1891 data = new (mPresContext) DependentBuiltinCounterStyle(type, this);
1892 } else {
1893 data = GetBuiltinStyle(type);
1897 if (!data) {
1898 data = GetDecimalStyle();
1900 mCacheTable.Put(aName, data);
1901 return data;
1904 /* static */ CounterStyle*
1905 CounterStyleManager::GetBuiltinStyle(int32_t aStyle)
1907 NS_ABORT_IF_FALSE(0 <= aStyle && aStyle < NS_STYLE_LIST_STYLE__MAX,
1908 "Require a valid builtin style constant");
1909 NS_ABORT_IF_FALSE(!gBuiltinStyleTable[aStyle].IsDependentStyle(),
1910 "Cannot get dependent builtin style");
1911 return &gBuiltinStyleTable[aStyle];
1914 struct InvalidateOldStyleData
1916 explicit InvalidateOldStyleData(nsPresContext* aPresContext)
1917 : mPresContext(aPresContext),
1918 mChanged(false)
1922 nsPresContext* mPresContext;
1923 nsTArray<nsRefPtr<CounterStyle>> mToBeRemoved;
1924 bool mChanged;
1927 static PLDHashOperator
1928 InvalidateOldStyle(const nsSubstring& aKey,
1929 nsRefPtr<CounterStyle>& aStyle,
1930 void* aArg)
1932 InvalidateOldStyleData* data = static_cast<InvalidateOldStyleData*>(aArg);
1933 bool toBeUpdated = false;
1934 bool toBeRemoved = false;
1935 nsCSSCounterStyleRule* newRule = data->mPresContext->
1936 StyleSet()->CounterStyleRuleForName(data->mPresContext, aKey);
1937 if (!newRule) {
1938 if (aStyle->IsCustomStyle()) {
1939 toBeRemoved = true;
1941 } else {
1942 if (!aStyle->IsCustomStyle()) {
1943 toBeRemoved = true;
1944 } else {
1945 CustomCounterStyle* style =
1946 static_cast<CustomCounterStyle*>(aStyle.get());
1947 if (style->GetRule() != newRule) {
1948 toBeRemoved = true;
1949 } else if (style->GetRuleGeneration() != newRule->GetGeneration()) {
1950 toBeUpdated = true;
1951 style->ResetCachedData();
1955 data->mChanged = data->mChanged || toBeUpdated || toBeRemoved;
1956 if (toBeRemoved) {
1957 if (aStyle->IsDependentStyle()) {
1958 if (aStyle->IsCustomStyle()) {
1959 // Since |aStyle| is being removed from mCacheTable, it won't be visited
1960 // by our post-removal InvalidateDependentData() traversal. So, we have
1961 // to give it a manual ResetDependentData() call. (This only really
1962 // matters if something else is holding a reference & keeping it alive.)
1963 static_cast<CustomCounterStyle*>(aStyle.get())->ResetDependentData();
1965 // The object has to be held here so that it will not be released
1966 // before all pointers that refer to it are reset. It will be
1967 // released when the MarkAndCleanData goes out of scope at the end
1968 // of NotifyRuleChanged().
1969 data->mToBeRemoved.AppendElement(aStyle);
1971 return PL_DHASH_REMOVE;
1973 return PL_DHASH_NEXT;
1976 static PLDHashOperator
1977 InvalidateDependentData(const nsSubstring& aKey,
1978 CounterStyle* aStyle,
1979 void* aArg)
1981 if (aStyle->IsCustomStyle()) {
1982 CustomCounterStyle* custom = static_cast<CustomCounterStyle*>(aStyle);
1983 custom->ResetDependentData();
1985 // There is no dependent data cached in DependentBuiltinCounterStyle
1986 // instances, so we don't need to reset their data.
1987 return PL_DHASH_NEXT;
1990 bool
1991 CounterStyleManager::NotifyRuleChanged()
1993 InvalidateOldStyleData data(mPresContext);
1994 mCacheTable.Enumerate(InvalidateOldStyle, &data);
1995 if (data.mChanged) {
1996 mCacheTable.EnumerateRead(InvalidateDependentData, nullptr);
1998 return data.mChanged;
2001 } // namespace mozilla