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