Coverage Report

Created: 2018-09-25 14:53

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