Source code

Revision control

Other Tools

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
/* Runtime-wide Intl data shared across compartments. */
8
9
#include "builtin/intl/SharedIntlData.h"
10
11
#include "mozilla/Assertions.h"
12
#include "mozilla/HashFunctions.h"
13
#include "mozilla/TextUtils.h"
14
15
#include <algorithm>
16
#include <stdint.h>
17
#include <utility>
18
19
#include "builtin/intl/CommonFunctions.h"
20
#include "builtin/intl/ScopedICUObject.h"
21
#include "builtin/intl/TimeZoneDataGenerated.h"
22
#include "builtin/String.h"
23
#include "js/Utility.h"
24
#include "js/Vector.h"
25
#include "unicode/ucal.h"
26
#include "unicode/ucol.h"
27
#include "unicode/udat.h"
28
#include "unicode/udatpg.h"
29
#include "unicode/uenum.h"
30
#include "unicode/uloc.h"
31
#include "unicode/unum.h"
32
#include "unicode/utypes.h"
33
#include "vm/JSAtom.h"
34
#include "vm/StringType.h"
35
36
using js::HashNumber;
37
using js::intl::StringsAreEqual;
38
39
template <typename Char>
40
static constexpr Char ToUpperASCII(Char c) {
41
return mozilla::IsAsciiLowercaseAlpha(c) ? (c - 0x20) : c;
42
}
43
44
static_assert(ToUpperASCII('a') == 'A', "verifying 'a' uppercases correctly");
45
static_assert(ToUpperASCII('m') == 'M', "verifying 'm' uppercases correctly");
46
static_assert(ToUpperASCII('z') == 'Z', "verifying 'z' uppercases correctly");
47
static_assert(ToUpperASCII(u'a') == u'A',
48
"verifying u'a' uppercases correctly");
49
static_assert(ToUpperASCII(u'k') == u'K',
50
"verifying u'k' uppercases correctly");
51
static_assert(ToUpperASCII(u'z') == u'Z',
52
"verifying u'z' uppercases correctly");
53
54
template <typename Char>
55
static HashNumber HashStringIgnoreCaseASCII(const Char* s, size_t length) {
56
uint32_t hash = 0;
57
for (size_t i = 0; i < length; i++) {
58
hash = mozilla::AddToHash(hash, ToUpperASCII(s[i]));
59
}
60
return hash;
61
}
62
63
js::intl::SharedIntlData::TimeZoneHasher::Lookup::Lookup(
64
JSLinearString* timeZone)
65
: js::intl::SharedIntlData::LinearStringLookup(timeZone) {
66
if (isLatin1) {
67
hash = HashStringIgnoreCaseASCII(latin1Chars, length);
68
} else {
69
hash = HashStringIgnoreCaseASCII(twoByteChars, length);
70
}
71
}
72
73
template <typename Char1, typename Char2>
74
static bool EqualCharsIgnoreCaseASCII(const Char1* s1, const Char2* s2,
75
size_t len) {
76
for (const Char1* s1end = s1 + len; s1 < s1end; s1++, s2++) {
77
if (ToUpperASCII(*s1) != ToUpperASCII(*s2)) {
78
return false;
79
}
80
}
81
return true;
82
}
83
84
bool js::intl::SharedIntlData::TimeZoneHasher::match(TimeZoneName key,
85
const Lookup& lookup) {
86
if (key->length() != lookup.length) {
87
return false;
88
}
89
90
// Compare time zone names ignoring ASCII case differences.
91
if (key->hasLatin1Chars()) {
92
const Latin1Char* keyChars = key->latin1Chars(lookup.nogc);
93
if (lookup.isLatin1) {
94
return EqualCharsIgnoreCaseASCII(keyChars, lookup.latin1Chars,
95
lookup.length);
96
}
97
return EqualCharsIgnoreCaseASCII(keyChars, lookup.twoByteChars,
98
lookup.length);
99
}
100
101
const char16_t* keyChars = key->twoByteChars(lookup.nogc);
102
if (lookup.isLatin1) {
103
return EqualCharsIgnoreCaseASCII(lookup.latin1Chars, keyChars,
104
lookup.length);
105
}
106
return EqualCharsIgnoreCaseASCII(keyChars, lookup.twoByteChars,
107
lookup.length);
108
}
109
110
static bool IsLegacyICUTimeZone(const char* timeZone) {
111
for (const auto& legacyTimeZone : js::timezone::legacyICUTimeZones) {
112
if (StringsAreEqual(timeZone, legacyTimeZone)) {
113
return true;
114
}
115
}
116
return false;
117
}
118
119
bool js::intl::SharedIntlData::ensureTimeZones(JSContext* cx) {
120
if (timeZoneDataInitialized) {
121
return true;
122
}
123
124
// If ensureTimeZones() was called previously, but didn't complete due to
125
// OOM, clear all sets/maps and start from scratch.
126
availableTimeZones.clearAndCompact();
127
128
UErrorCode status = U_ZERO_ERROR;
129
UEnumeration* values = ucal_openTimeZones(&status);
130
if (U_FAILURE(status)) {
131
ReportInternalError(cx);
132
return false;
133
}
134
ScopedICUObject<UEnumeration, uenum_close> toClose(values);
135
136
RootedAtom timeZone(cx);
137
while (true) {
138
int32_t size;
139
const char* rawTimeZone = uenum_next(values, &size, &status);
140
if (U_FAILURE(status)) {
141
ReportInternalError(cx);
142
return false;
143
}
144
145
if (rawTimeZone == nullptr) {
146
break;
147
}
148
149
// Skip legacy ICU time zone names.
150
if (IsLegacyICUTimeZone(rawTimeZone)) {
151
continue;
152
}
153
154
MOZ_ASSERT(size >= 0);
155
timeZone = Atomize(cx, rawTimeZone, size_t(size));
156
if (!timeZone) {
157
return false;
158
}
159
160
TimeZoneHasher::Lookup lookup(timeZone);
161
TimeZoneSet::AddPtr p = availableTimeZones.lookupForAdd(lookup);
162
163
// ICU shouldn't report any duplicate time zone names, but if it does,
164
// just ignore the duplicate name.
165
if (!p && !availableTimeZones.add(p, timeZone)) {
166
ReportOutOfMemory(cx);
167
return false;
168
}
169
}
170
171
ianaZonesTreatedAsLinksByICU.clearAndCompact();
172
173
for (const char* rawTimeZone : timezone::ianaZonesTreatedAsLinksByICU) {
174
MOZ_ASSERT(rawTimeZone != nullptr);
175
timeZone = Atomize(cx, rawTimeZone, strlen(rawTimeZone));
176
if (!timeZone) {
177
return false;
178
}
179
180
TimeZoneHasher::Lookup lookup(timeZone);
181
TimeZoneSet::AddPtr p = ianaZonesTreatedAsLinksByICU.lookupForAdd(lookup);
182
MOZ_ASSERT(!p, "Duplicate entry in timezone::ianaZonesTreatedAsLinksByICU");
183
184
if (!ianaZonesTreatedAsLinksByICU.add(p, timeZone)) {
185
ReportOutOfMemory(cx);
186
return false;
187
}
188
}
189
190
ianaLinksCanonicalizedDifferentlyByICU.clearAndCompact();
191
192
RootedAtom linkName(cx);
193
RootedAtom& target = timeZone;
194
for (const auto& linkAndTarget :
195
timezone::ianaLinksCanonicalizedDifferentlyByICU) {
196
const char* rawLinkName = linkAndTarget.link;
197
const char* rawTarget = linkAndTarget.target;
198
199
MOZ_ASSERT(rawLinkName != nullptr);
200
linkName = Atomize(cx, rawLinkName, strlen(rawLinkName));
201
if (!linkName) {
202
return false;
203
}
204
205
MOZ_ASSERT(rawTarget != nullptr);
206
target = Atomize(cx, rawTarget, strlen(rawTarget));
207
if (!target) {
208
return false;
209
}
210
211
TimeZoneHasher::Lookup lookup(linkName);
212
TimeZoneMap::AddPtr p =
213
ianaLinksCanonicalizedDifferentlyByICU.lookupForAdd(lookup);
214
MOZ_ASSERT(
215
!p,
216
"Duplicate entry in timezone::ianaLinksCanonicalizedDifferentlyByICU");
217
218
if (!ianaLinksCanonicalizedDifferentlyByICU.add(p, linkName, target)) {
219
ReportOutOfMemory(cx);
220
return false;
221
}
222
}
223
224
MOZ_ASSERT(!timeZoneDataInitialized,
225
"ensureTimeZones is neither reentrant nor thread-safe");
226
timeZoneDataInitialized = true;
227
228
return true;
229
}
230
231
bool js::intl::SharedIntlData::validateTimeZoneName(JSContext* cx,
232
HandleString timeZone,
233
MutableHandleAtom result) {
234
if (!ensureTimeZones(cx)) {
235
return false;
236
}
237
238
RootedLinearString timeZoneLinear(cx, timeZone->ensureLinear(cx));
239
if (!timeZoneLinear) {
240
return false;
241
}
242
243
TimeZoneHasher::Lookup lookup(timeZoneLinear);
244
if (TimeZoneSet::Ptr p = availableTimeZones.lookup(lookup)) {
245
result.set(*p);
246
}
247
248
return true;
249
}
250
251
bool js::intl::SharedIntlData::tryCanonicalizeTimeZoneConsistentWithIANA(
252
JSContext* cx, HandleString timeZone, MutableHandleAtom result) {
253
if (!ensureTimeZones(cx)) {
254
return false;
255
}
256
257
RootedLinearString timeZoneLinear(cx, timeZone->ensureLinear(cx));
258
if (!timeZoneLinear) {
259
return false;
260
}
261
262
TimeZoneHasher::Lookup lookup(timeZoneLinear);
263
MOZ_ASSERT(availableTimeZones.has(lookup), "Invalid time zone name");
264
265
if (TimeZoneMap::Ptr p =
266
ianaLinksCanonicalizedDifferentlyByICU.lookup(lookup)) {
267
// The effectively supported time zones aren't known at compile time,
268
// when
269
// 1. SpiderMonkey was compiled with "--with-system-icu".
270
// 2. ICU's dynamic time zone data loading feature was used.
271
// (ICU supports loading time zone files at runtime through the
272
// ICU_TIMEZONE_FILES_DIR environment variable.)
273
// Ensure ICU supports the new target zone before applying the update.
274
TimeZoneName targetTimeZone = p->value();
275
TimeZoneHasher::Lookup targetLookup(targetTimeZone);
276
if (availableTimeZones.has(targetLookup)) {
277
result.set(targetTimeZone);
278
}
279
} else if (TimeZoneSet::Ptr p = ianaZonesTreatedAsLinksByICU.lookup(lookup)) {
280
result.set(*p);
281
}
282
283
return true;
284
}
285
286
js::intl::SharedIntlData::LocaleHasher::Lookup::Lookup(JSLinearString* locale)
287
: js::intl::SharedIntlData::LinearStringLookup(locale) {
288
if (isLatin1) {
289
hash = mozilla::HashString(latin1Chars, length);
290
} else {
291
hash = mozilla::HashString(twoByteChars, length);
292
}
293
}
294
295
js::intl::SharedIntlData::LocaleHasher::Lookup::Lookup(const char* chars,
296
size_t length)
297
: js::intl::SharedIntlData::LinearStringLookup(chars, length) {
298
hash = mozilla::HashString(latin1Chars, length);
299
}
300
301
bool js::intl::SharedIntlData::LocaleHasher::match(Locale key,
302
const Lookup& lookup) {
303
if (key->length() != lookup.length) {
304
return false;
305
}
306
307
if (key->hasLatin1Chars()) {
308
const Latin1Char* keyChars = key->latin1Chars(lookup.nogc);
309
if (lookup.isLatin1) {
310
return EqualChars(keyChars, lookup.latin1Chars, lookup.length);
311
}
312
return EqualChars(keyChars, lookup.twoByteChars, lookup.length);
313
}
314
315
const char16_t* keyChars = key->twoByteChars(lookup.nogc);
316
if (lookup.isLatin1) {
317
return EqualChars(lookup.latin1Chars, keyChars, lookup.length);
318
}
319
return EqualChars(keyChars, lookup.twoByteChars, lookup.length);
320
}
321
322
bool js::intl::SharedIntlData::getAvailableLocales(
323
JSContext* cx, LocaleSet& locales, CountAvailable countAvailable,
324
GetAvailable getAvailable) {
325
auto addLocale = [cx, &locales](const char* locale, size_t length) {
326
JSAtom* atom = Atomize(cx, locale, length);
327
if (!atom) {
328
return false;
329
}
330
331
LocaleHasher::Lookup lookup(atom);
332
LocaleSet::AddPtr p = locales.lookupForAdd(lookup);
333
334
// ICU shouldn't report any duplicate locales, but if it does, just
335
// ignore the duplicated locale.
336
if (!p && !locales.add(p, atom)) {
337
ReportOutOfMemory(cx);
338
return false;
339
}
340
341
return true;
342
};
343
344
js::Vector<char, 16> lang(cx);
345
346
int32_t count = countAvailable();
347
for (int32_t i = 0; i < count; i++) {
348
const char* locale = getAvailable(i);
349
size_t length = strlen(locale);
350
351
lang.clear();
352
if (!lang.append(locale, length)) {
353
return false;
354
}
355
356
std::replace(lang.begin(), lang.end(), '_', '-');
357
358
if (!addLocale(lang.begin(), length)) {
359
return false;
360
}
361
}
362
363
// Add old-style language tags without script code for locales that in current
364
// usage would include a script subtag. Also add an entry for the last-ditch
365
// locale, in case ICU doesn't directly support it (but does support it
366
// through fallback, e.g. supporting "en-GB" indirectly using "en" support).
367
368
// Certain old-style language tags lack a script code, but in current usage
369
// they *would* include a script code. Map these over to modern forms.
370
for (const auto& mapping : js::intl::oldStyleLanguageTagMappings) {
371
const char* oldStyle = mapping.oldStyle;
372
const char* modernStyle = mapping.modernStyle;
373
374
LocaleHasher::Lookup lookup(modernStyle, strlen(modernStyle));
375
if (locales.has(lookup)) {
376
if (!addLocale(oldStyle, strlen(oldStyle))) {
377
return false;
378
}
379
}
380
}
381
382
// Also forcibly provide the last-ditch locale.
383
{
384
const char* lastDitch = intl::LastDitchLocale();
385
MOZ_ASSERT(strcmp(lastDitch, "en-GB") == 0);
386
387
#ifdef DEBUG
388
static constexpr char lastDitchParent[] = "en";
389
390
LocaleHasher::Lookup lookup(lastDitchParent, strlen(lastDitchParent));
391
MOZ_ASSERT(locales.has(lookup),
392
"shouldn't be a need to add every locale implied by the "
393
"last-ditch locale, merely just the last-ditch locale");
394
#endif
395
396
if (!addLocale(lastDitch, strlen(lastDitch))) {
397
return false;
398
}
399
}
400
401
return true;
402
}
403
404
#ifdef DEBUG
405
template <typename CountAvailable, typename GetAvailable>
406
static bool IsSameAvailableLocales(CountAvailable countAvailable1,
407
GetAvailable getAvailable1,
408
CountAvailable countAvailable2,
409
GetAvailable getAvailable2) {
410
int32_t count = countAvailable1();
411
if (count != countAvailable2()) {
412
return false;
413
}
414
for (int32_t i = 0; i < count; i++) {
415
if (getAvailable1(i) != getAvailable2(i)) {
416
return false;
417
}
418
}
419
return true;
420
}
421
#endif
422
423
bool js::intl::SharedIntlData::ensureSupportedLocales(JSContext* cx) {
424
if (supportedLocalesInitialized) {
425
return true;
426
}
427
428
// If ensureSupportedLocales() was called previously, but didn't complete due
429
// to OOM, clear all data and start from scratch.
430
supportedLocales.clearAndCompact();
431
collatorSupportedLocales.clearAndCompact();
432
433
if (!getAvailableLocales(cx, supportedLocales, uloc_countAvailable,
434
uloc_getAvailable)) {
435
return false;
436
}
437
if (!getAvailableLocales(cx, collatorSupportedLocales, ucol_countAvailable,
438
ucol_getAvailable)) {
439
return false;
440
}
441
442
MOZ_ASSERT(IsSameAvailableLocales(uloc_countAvailable, uloc_getAvailable,
443
udat_countAvailable, udat_getAvailable));
444
445
MOZ_ASSERT(IsSameAvailableLocales(uloc_countAvailable, uloc_getAvailable,
446
unum_countAvailable, unum_getAvailable));
447
448
MOZ_ASSERT(!supportedLocalesInitialized,
449
"ensureSupportedLocales is neither reentrant nor thread-safe");
450
supportedLocalesInitialized = true;
451
452
return true;
453
}
454
455
bool js::intl::SharedIntlData::isSupportedLocale(JSContext* cx,
456
SupportedLocaleKind kind,
457
HandleString locale,
458
bool* supported) {
459
if (!ensureSupportedLocales(cx)) {
460
return false;
461
}
462
463
RootedLinearString localeLinear(cx, locale->ensureLinear(cx));
464
if (!localeLinear) {
465
return false;
466
}
467
468
LocaleHasher::Lookup lookup(localeLinear);
469
470
switch (kind) {
471
case SupportedLocaleKind::Collator:
472
*supported = collatorSupportedLocales.has(lookup);
473
return true;
474
case SupportedLocaleKind::DateTimeFormat:
475
case SupportedLocaleKind::ListFormat:
476
case SupportedLocaleKind::NumberFormat:
477
case SupportedLocaleKind::PluralRules:
478
case SupportedLocaleKind::RelativeTimeFormat:
479
*supported = supportedLocales.has(lookup);
480
return true;
481
}
482
MOZ_CRASH("Invalid Intl constructor");
483
}
484
485
#if DEBUG || MOZ_SYSTEM_ICU
486
bool js::intl::SharedIntlData::ensureUpperCaseFirstLocales(JSContext* cx) {
487
if (upperCaseFirstInitialized) {
488
return true;
489
}
490
491
// If ensureUpperCaseFirstLocales() was called previously, but didn't
492
// complete due to OOM, clear all data and start from scratch.
493
upperCaseFirstLocales.clearAndCompact();
494
495
UErrorCode status = U_ZERO_ERROR;
496
UEnumeration* available = ucol_openAvailableLocales(&status);
497
if (U_FAILURE(status)) {
498
ReportInternalError(cx);
499
return false;
500
}
501
ScopedICUObject<UEnumeration, uenum_close> toClose(available);
502
503
RootedAtom locale(cx);
504
while (true) {
505
int32_t size;
506
const char* rawLocale = uenum_next(available, &size, &status);
507
if (U_FAILURE(status)) {
508
ReportInternalError(cx);
509
return false;
510
}
511
512
if (rawLocale == nullptr) {
513
break;
514
}
515
516
UCollator* collator = ucol_open(rawLocale, &status);
517
if (U_FAILURE(status)) {
518
ReportInternalError(cx);
519
return false;
520
}
521
ScopedICUObject<UCollator, ucol_close> toCloseCollator(collator);
522
523
UColAttributeValue caseFirst =
524
ucol_getAttribute(collator, UCOL_CASE_FIRST, &status);
525
if (U_FAILURE(status)) {
526
ReportInternalError(cx);
527
return false;
528
}
529
530
if (caseFirst != UCOL_UPPER_FIRST) {
531
continue;
532
}
533
534
MOZ_ASSERT(size >= 0);
535
locale = Atomize(cx, rawLocale, size_t(size));
536
if (!locale) {
537
return false;
538
}
539
540
LocaleHasher::Lookup lookup(locale);
541
LocaleSet::AddPtr p = upperCaseFirstLocales.lookupForAdd(lookup);
542
543
// ICU shouldn't report any duplicate locales, but if it does, just
544
// ignore the duplicated locale.
545
if (!p && !upperCaseFirstLocales.add(p, locale)) {
546
ReportOutOfMemory(cx);
547
return false;
548
}
549
}
550
551
MOZ_ASSERT(
552
!upperCaseFirstInitialized,
553
"ensureUpperCaseFirstLocales is neither reentrant nor thread-safe");
554
upperCaseFirstInitialized = true;
555
556
return true;
557
}
558
#endif // DEBUG || MOZ_SYSTEM_ICU
559
560
bool js::intl::SharedIntlData::isUpperCaseFirst(JSContext* cx,
561
HandleString locale,
562
bool* isUpperFirst) {
563
#if DEBUG || MOZ_SYSTEM_ICU
564
if (!ensureUpperCaseFirstLocales(cx)) {
565
return false;
566
}
567
#endif
568
569
RootedLinearString localeLinear(cx, locale->ensureLinear(cx));
570
if (!localeLinear) {
571
return false;
572
}
573
574
#if !MOZ_SYSTEM_ICU
575
// "da" (Danish) and "mt" (Maltese) are the only two supported locales using
576
// upper-case first. CLDR also lists "cu" (Church Slavic) as an upper-case
577
// first locale, but since it's not supported in ICU, we don't care about it
578
// here.
579
bool isDefaultUpperCaseFirstLocale =
580
js::StringEqualsLiteral(localeLinear, "da") ||
581
js::StringEqualsLiteral(localeLinear, "mt");
582
#endif
583
584
#if DEBUG || MOZ_SYSTEM_ICU
585
LocaleHasher::Lookup lookup(localeLinear);
586
*isUpperFirst = upperCaseFirstLocales.has(lookup);
587
#else
588
*isUpperFirst = isDefaultUpperCaseFirstLocale;
589
#endif
590
591
#if !MOZ_SYSTEM_ICU
592
MOZ_ASSERT(*isUpperFirst == isDefaultUpperCaseFirstLocale,
593
"upper-case first locales don't match hard-coded list");
594
#endif
595
596
return true;
597
}
598
599
void js::intl::DateTimePatternGeneratorDeleter::operator()(
600
UDateTimePatternGenerator* ptr) {
601
udatpg_close(ptr);
602
}
603
604
UDateTimePatternGenerator*
605
js::intl::SharedIntlData::getDateTimePatternGenerator(JSContext* cx,
606
const char* locale) {
607
// Return the cached instance if the requested locale matches the locale
608
// of the cached generator.
609
if (dateTimePatternGeneratorLocale &&
610
StringsAreEqual(dateTimePatternGeneratorLocale.get(), locale)) {
611
return dateTimePatternGenerator.get();
612
}
613
614
UErrorCode status = U_ZERO_ERROR;
615
UniqueUDateTimePatternGenerator gen(udatpg_open(IcuLocale(locale), &status));
616
if (U_FAILURE(status)) {
617
intl::ReportInternalError(cx);
618
return nullptr;
619
}
620
621
JS::UniqueChars localeCopy = js::DuplicateString(cx, locale);
622
if (!localeCopy) {
623
return nullptr;
624
}
625
626
dateTimePatternGenerator = std::move(gen);
627
dateTimePatternGeneratorLocale = std::move(localeCopy);
628
629
return dateTimePatternGenerator.get();
630
}
631
632
void js::intl::SharedIntlData::destroyInstance() {
633
availableTimeZones.clearAndCompact();
634
ianaZonesTreatedAsLinksByICU.clearAndCompact();
635
ianaLinksCanonicalizedDifferentlyByICU.clearAndCompact();
636
supportedLocales.clearAndCompact();
637
collatorSupportedLocales.clearAndCompact();
638
#if DEBUG || MOZ_SYSTEM_ICU
639
upperCaseFirstLocales.clearAndCompact();
640
#endif
641
}
642
643
void js::intl::SharedIntlData::trace(JSTracer* trc) {
644
// Atoms are always tenured.
645
if (!JS::RuntimeHeapIsMinorCollecting()) {
646
availableTimeZones.trace(trc);
647
ianaZonesTreatedAsLinksByICU.trace(trc);
648
ianaLinksCanonicalizedDifferentlyByICU.trace(trc);
649
supportedLocales.trace(trc);
650
collatorSupportedLocales.trace(trc);
651
#if DEBUG || MOZ_SYSTEM_ICU
652
upperCaseFirstLocales.trace(trc);
653
#endif
654
}
655
}
656
657
size_t js::intl::SharedIntlData::sizeOfExcludingThis(
658
mozilla::MallocSizeOf mallocSizeOf) const {
659
return availableTimeZones.shallowSizeOfExcludingThis(mallocSizeOf) +
660
ianaZonesTreatedAsLinksByICU.shallowSizeOfExcludingThis(mallocSizeOf) +
661
ianaLinksCanonicalizedDifferentlyByICU.shallowSizeOfExcludingThis(
662
mallocSizeOf) +
663
supportedLocales.shallowSizeOfExcludingThis(mallocSizeOf) +
664
collatorSupportedLocales.shallowSizeOfExcludingThis(mallocSizeOf) +
665
#if DEBUG || MOZ_SYSTEM_ICU
666
upperCaseFirstLocales.shallowSizeOfExcludingThis(mallocSizeOf) +
667
#endif
668
mallocSizeOf(dateTimePatternGeneratorLocale.get());
669
}