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