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