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