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
/* Implementation of the Intl object and its non-constructor properties. */
8
9
#include "builtin/intl/IntlObject.h"
10
11
#include "mozilla/Assertions.h"
12
#include "mozilla/Likely.h"
13
#include "mozilla/Range.h"
14
15
#include <algorithm>
16
#include <iterator>
17
18
#include "jsapi.h"
19
20
#include "builtin/Array.h"
21
#include "builtin/intl/Collator.h"
22
#include "builtin/intl/CommonFunctions.h"
23
#include "builtin/intl/DateTimeFormat.h"
24
#include "builtin/intl/LanguageTag.h"
25
#include "builtin/intl/NumberFormat.h"
26
#include "builtin/intl/PluralRules.h"
27
#include "builtin/intl/RelativeTimeFormat.h"
28
#include "builtin/intl/ScopedICUObject.h"
29
#include "builtin/intl/SharedIntlData.h"
30
#include "js/CharacterEncoding.h"
31
#include "js/Class.h"
32
#include "js/PropertySpec.h"
33
#include "js/Result.h"
34
#include "js/StableStringChars.h"
35
#include "unicode/ucal.h"
36
#include "unicode/udat.h"
37
#include "unicode/udatpg.h"
38
#include "unicode/uloc.h"
39
#include "unicode/utypes.h"
40
#include "vm/GlobalObject.h"
41
#include "vm/JSAtom.h"
42
#include "vm/JSContext.h"
43
#include "vm/JSObject.h"
44
#include "vm/StringType.h"
45
46
#include "vm/JSObject-inl.h"
47
#include "vm/NativeObject-inl.h"
48
49
using namespace js;
50
51
using mozilla::Range;
52
using mozilla::RangedPtr;
53
54
using JS::AutoStableStringChars;
55
56
using js::intl::CallICU;
57
using js::intl::DateTimeFormatOptions;
58
using js::intl::IcuLocale;
59
60
/******************** Intl ********************/
61
62
bool js::intl_GetCalendarInfo(JSContext* cx, unsigned argc, Value* vp) {
63
CallArgs args = CallArgsFromVp(argc, vp);
64
MOZ_ASSERT(args.length() == 1);
65
66
UniqueChars locale = intl::EncodeLocale(cx, args[0].toString());
67
if (!locale) {
68
return false;
69
}
70
71
UErrorCode status = U_ZERO_ERROR;
72
const UChar* uTimeZone = nullptr;
73
int32_t uTimeZoneLength = 0;
74
UCalendar* cal = ucal_open(uTimeZone, uTimeZoneLength, locale.get(),
75
UCAL_DEFAULT, &status);
76
if (U_FAILURE(status)) {
77
intl::ReportInternalError(cx);
78
return false;
79
}
80
ScopedICUObject<UCalendar, ucal_close> toClose(cal);
81
82
RootedObject info(cx, NewBuiltinClassInstance<PlainObject>(cx));
83
if (!info) {
84
return false;
85
}
86
87
RootedValue v(cx);
88
int32_t firstDayOfWeek = ucal_getAttribute(cal, UCAL_FIRST_DAY_OF_WEEK);
89
v.setInt32(firstDayOfWeek);
90
91
if (!DefineDataProperty(cx, info, cx->names().firstDayOfWeek, v)) {
92
return false;
93
}
94
95
int32_t minDays = ucal_getAttribute(cal, UCAL_MINIMAL_DAYS_IN_FIRST_WEEK);
96
v.setInt32(minDays);
97
if (!DefineDataProperty(cx, info, cx->names().minDays, v)) {
98
return false;
99
}
100
101
UCalendarWeekdayType prevDayType =
102
ucal_getDayOfWeekType(cal, UCAL_SATURDAY, &status);
103
if (U_FAILURE(status)) {
104
intl::ReportInternalError(cx);
105
return false;
106
}
107
108
RootedValue weekendStart(cx), weekendEnd(cx);
109
110
for (int i = UCAL_SUNDAY; i <= UCAL_SATURDAY; i++) {
111
UCalendarDaysOfWeek dayOfWeek = static_cast<UCalendarDaysOfWeek>(i);
112
UCalendarWeekdayType type = ucal_getDayOfWeekType(cal, dayOfWeek, &status);
113
if (U_FAILURE(status)) {
114
intl::ReportInternalError(cx);
115
return false;
116
}
117
118
if (prevDayType != type) {
119
switch (type) {
120
case UCAL_WEEKDAY:
121
// If the first Weekday after Weekend is Sunday (1),
122
// then the last Weekend day is Saturday (7).
123
// Otherwise we'll just take the previous days number.
124
weekendEnd.setInt32(i == 1 ? 7 : i - 1);
125
break;
126
case UCAL_WEEKEND:
127
weekendStart.setInt32(i);
128
break;
129
case UCAL_WEEKEND_ONSET:
130
case UCAL_WEEKEND_CEASE:
131
// At the time this code was added, ICU apparently never behaves this
132
// way, so just throw, so that users will report a bug and we can
133
// decide what to do.
134
intl::ReportInternalError(cx);
135
return false;
136
default:
137
break;
138
}
139
}
140
141
prevDayType = type;
142
}
143
144
MOZ_ASSERT(weekendStart.isInt32());
145
MOZ_ASSERT(weekendEnd.isInt32());
146
147
if (!DefineDataProperty(cx, info, cx->names().weekendStart, weekendStart)) {
148
return false;
149
}
150
151
if (!DefineDataProperty(cx, info, cx->names().weekendEnd, weekendEnd)) {
152
return false;
153
}
154
155
args.rval().setObject(*info);
156
return true;
157
}
158
159
static void ReportBadKey(JSContext* cx, HandleString key) {
160
if (UniqueChars chars = QuoteString(cx, key, '"')) {
161
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY,
162
chars.get());
163
}
164
}
165
166
template <typename ConstChar>
167
static bool MatchPart(RangedPtr<ConstChar> iter, const RangedPtr<ConstChar> end,
168
const char* part, size_t partlen) {
169
for (size_t i = 0; i < partlen; iter++, i++) {
170
if (iter == end || *iter != part[i]) {
171
return false;
172
}
173
}
174
175
return true;
176
}
177
178
template <typename ConstChar, size_t N>
179
inline bool MatchPart(RangedPtr<ConstChar>* iter,
180
const RangedPtr<ConstChar> end, const char (&part)[N]) {
181
if (!MatchPart(*iter, end, part, N - 1)) {
182
return false;
183
}
184
185
*iter += N - 1;
186
return true;
187
}
188
189
enum class DisplayNameStyle {
190
Narrow,
191
Short,
192
Long,
193
};
194
195
template <typename ConstChar>
196
static JSString* ComputeSingleDisplayName(JSContext* cx, UDateFormat* fmt,
197
UDateTimePatternGenerator* dtpg,
198
DisplayNameStyle style,
199
const Range<ConstChar>& pattern,
200
HandleString patternString) {
201
RangedPtr<ConstChar> iter = pattern.begin();
202
const RangedPtr<ConstChar> end = pattern.end();
203
204
auto MatchSlash = [cx, patternString, &iter, end]() {
205
if (MOZ_LIKELY(iter != end && *iter == '/')) {
206
iter++;
207
return true;
208
}
209
210
ReportBadKey(cx, patternString);
211
return false;
212
};
213
214
if (!MatchPart(&iter, end, "dates")) {
215
ReportBadKey(cx, patternString);
216
return nullptr;
217
}
218
219
if (!MatchSlash()) {
220
return nullptr;
221
}
222
223
if (MatchPart(&iter, end, "fields")) {
224
if (!MatchSlash()) {
225
return nullptr;
226
}
227
228
UDateTimePatternField fieldType;
229
230
if (MatchPart(&iter, end, "year")) {
231
fieldType = UDATPG_YEAR_FIELD;
232
} else if (MatchPart(&iter, end, "month")) {
233
fieldType = UDATPG_MONTH_FIELD;
234
} else if (MatchPart(&iter, end, "week")) {
235
fieldType = UDATPG_WEEK_OF_YEAR_FIELD;
236
} else if (MatchPart(&iter, end, "day")) {
237
fieldType = UDATPG_DAY_FIELD;
238
} else {
239
ReportBadKey(cx, patternString);
240
return nullptr;
241
}
242
243
// This part must be the final part with no trailing data.
244
if (iter != end) {
245
ReportBadKey(cx, patternString);
246
return nullptr;
247
}
248
249
int32_t resultSize;
250
const UChar* value = udatpg_getAppendItemName(dtpg, fieldType, &resultSize);
251
MOZ_ASSERT(resultSize >= 0);
252
253
return NewStringCopyN<CanGC>(cx, value, size_t(resultSize));
254
}
255
256
if (MatchPart(&iter, end, "gregorian")) {
257
if (!MatchSlash()) {
258
return nullptr;
259
}
260
261
UDateFormatSymbolType symbolType;
262
int32_t index;
263
264
if (MatchPart(&iter, end, "months")) {
265
if (!MatchSlash()) {
266
return nullptr;
267
}
268
269
switch (style) {
270
case DisplayNameStyle::Narrow:
271
symbolType = UDAT_STANDALONE_NARROW_MONTHS;
272
break;
273
274
case DisplayNameStyle::Short:
275
symbolType = UDAT_STANDALONE_SHORT_MONTHS;
276
break;
277
278
case DisplayNameStyle::Long:
279
symbolType = UDAT_STANDALONE_MONTHS;
280
break;
281
}
282
283
if (MatchPart(&iter, end, "january")) {
284
index = UCAL_JANUARY;
285
} else if (MatchPart(&iter, end, "february")) {
286
index = UCAL_FEBRUARY;
287
} else if (MatchPart(&iter, end, "march")) {
288
index = UCAL_MARCH;
289
} else if (MatchPart(&iter, end, "april")) {
290
index = UCAL_APRIL;
291
} else if (MatchPart(&iter, end, "may")) {
292
index = UCAL_MAY;
293
} else if (MatchPart(&iter, end, "june")) {
294
index = UCAL_JUNE;
295
} else if (MatchPart(&iter, end, "july")) {
296
index = UCAL_JULY;
297
} else if (MatchPart(&iter, end, "august")) {
298
index = UCAL_AUGUST;
299
} else if (MatchPart(&iter, end, "september")) {
300
index = UCAL_SEPTEMBER;
301
} else if (MatchPart(&iter, end, "october")) {
302
index = UCAL_OCTOBER;
303
} else if (MatchPart(&iter, end, "november")) {
304
index = UCAL_NOVEMBER;
305
} else if (MatchPart(&iter, end, "december")) {
306
index = UCAL_DECEMBER;
307
} else {
308
ReportBadKey(cx, patternString);
309
return nullptr;
310
}
311
} else if (MatchPart(&iter, end, "weekdays")) {
312
if (!MatchSlash()) {
313
return nullptr;
314
}
315
316
switch (style) {
317
case DisplayNameStyle::Narrow:
318
symbolType = UDAT_STANDALONE_NARROW_WEEKDAYS;
319
break;
320
321
case DisplayNameStyle::Short:
322
symbolType = UDAT_STANDALONE_SHORT_WEEKDAYS;
323
break;
324
325
case DisplayNameStyle::Long:
326
symbolType = UDAT_STANDALONE_WEEKDAYS;
327
break;
328
}
329
330
if (MatchPart(&iter, end, "monday")) {
331
index = UCAL_MONDAY;
332
} else if (MatchPart(&iter, end, "tuesday")) {
333
index = UCAL_TUESDAY;
334
} else if (MatchPart(&iter, end, "wednesday")) {
335
index = UCAL_WEDNESDAY;
336
} else if (MatchPart(&iter, end, "thursday")) {
337
index = UCAL_THURSDAY;
338
} else if (MatchPart(&iter, end, "friday")) {
339
index = UCAL_FRIDAY;
340
} else if (MatchPart(&iter, end, "saturday")) {
341
index = UCAL_SATURDAY;
342
} else if (MatchPart(&iter, end, "sunday")) {
343
index = UCAL_SUNDAY;
344
} else {
345
ReportBadKey(cx, patternString);
346
return nullptr;
347
}
348
} else if (MatchPart(&iter, end, "dayperiods")) {
349
if (!MatchSlash()) {
350
return nullptr;
351
}
352
353
symbolType = UDAT_AM_PMS;
354
355
if (MatchPart(&iter, end, "am")) {
356
index = UCAL_AM;
357
} else if (MatchPart(&iter, end, "pm")) {
358
index = UCAL_PM;
359
} else {
360
ReportBadKey(cx, patternString);
361
return nullptr;
362
}
363
} else {
364
ReportBadKey(cx, patternString);
365
return nullptr;
366
}
367
368
// This part must be the final part with no trailing data.
369
if (iter != end) {
370
ReportBadKey(cx, patternString);
371
return nullptr;
372
}
373
374
return CallICU(cx, [fmt, symbolType, index](UChar* chars, int32_t size,
375
UErrorCode* status) {
376
return udat_getSymbols(fmt, symbolType, index, chars, size, status);
377
});
378
}
379
380
ReportBadKey(cx, patternString);
381
return nullptr;
382
}
383
384
bool js::intl_ComputeDisplayNames(JSContext* cx, unsigned argc, Value* vp) {
385
CallArgs args = CallArgsFromVp(argc, vp);
386
MOZ_ASSERT(args.length() == 3);
387
388
// 1. Assert: locale is a string.
389
UniqueChars locale = intl::EncodeLocale(cx, args[0].toString());
390
if (!locale) {
391
return false;
392
}
393
394
// 2. Assert: style is a string.
395
DisplayNameStyle dnStyle;
396
{
397
JSLinearString* style = args[1].toString()->ensureLinear(cx);
398
if (!style) {
399
return false;
400
}
401
402
if (StringEqualsLiteral(style, "narrow")) {
403
dnStyle = DisplayNameStyle::Narrow;
404
} else if (StringEqualsLiteral(style, "short")) {
405
dnStyle = DisplayNameStyle::Short;
406
} else {
407
MOZ_ASSERT(StringEqualsLiteral(style, "long"));
408
dnStyle = DisplayNameStyle::Long;
409
}
410
}
411
412
// 3. Assert: keys is an Array.
413
RootedArrayObject keys(cx, &args[2].toObject().as<ArrayObject>());
414
if (!keys) {
415
return false;
416
}
417
418
// 4. Let result be ArrayCreate(0).
419
RootedArrayObject result(cx, NewDenseFullyAllocatedArray(cx, keys->length()));
420
if (!result) {
421
return false;
422
}
423
result->ensureDenseInitializedLength(cx, 0, keys->length());
424
425
UErrorCode status = U_ZERO_ERROR;
426
427
UDateFormat* fmt =
428
udat_open(UDAT_DEFAULT, UDAT_DEFAULT, IcuLocale(locale.get()), nullptr, 0,
429
nullptr, 0, &status);
430
if (U_FAILURE(status)) {
431
intl::ReportInternalError(cx);
432
return false;
433
}
434
ScopedICUObject<UDateFormat, udat_close> datToClose(fmt);
435
436
// UDateTimePatternGenerator will be needed for translations of date and
437
// time fields like "month", "week", "day" etc.
438
UDateTimePatternGenerator* dtpg =
439
udatpg_open(IcuLocale(locale.get()), &status);
440
if (U_FAILURE(status)) {
441
intl::ReportInternalError(cx);
442
return false;
443
}
444
ScopedICUObject<UDateTimePatternGenerator, udatpg_close> datPgToClose(dtpg);
445
446
// 5. For each element of keys,
447
RootedString keyValStr(cx);
448
RootedValue v(cx);
449
for (uint32_t i = 0; i < keys->length(); i++) {
450
if (!GetElement(cx, keys, keys, i, &v)) {
451
return false;
452
}
453
454
keyValStr = v.toString();
455
456
AutoStableStringChars stablePatternChars(cx);
457
if (!stablePatternChars.init(cx, keyValStr)) {
458
return false;
459
}
460
461
// 5.a. Perform an implementation dependent algorithm to map a key to a
462
// corresponding display name.
463
JSString* displayName =
464
stablePatternChars.isLatin1()
465
? ComputeSingleDisplayName(cx, fmt, dtpg, dnStyle,
466
stablePatternChars.latin1Range(),
467
keyValStr)
468
: ComputeSingleDisplayName(cx, fmt, dtpg, dnStyle,
469
stablePatternChars.twoByteRange(),
470
keyValStr);
471
if (!displayName) {
472
return false;
473
}
474
475
// 5.b. Append the result string to result.
476
result->setDenseElement(i, StringValue(displayName));
477
}
478
479
// 6. Return result.
480
args.rval().setObject(*result);
481
return true;
482
}
483
484
bool js::intl_GetLocaleInfo(JSContext* cx, unsigned argc, Value* vp) {
485
CallArgs args = CallArgsFromVp(argc, vp);
486
MOZ_ASSERT(args.length() == 1);
487
488
UniqueChars locale = intl::EncodeLocale(cx, args[0].toString());
489
if (!locale) {
490
return false;
491
}
492
493
RootedObject info(cx, NewBuiltinClassInstance<PlainObject>(cx));
494
if (!info) {
495
return false;
496
}
497
498
if (!DefineDataProperty(cx, info, cx->names().locale, args[0])) {
499
return false;
500
}
501
502
bool rtl = uloc_isRightToLeft(IcuLocale(locale.get()));
503
504
RootedValue dir(cx, StringValue(rtl ? cx->names().rtl : cx->names().ltr));
505
506
if (!DefineDataProperty(cx, info, cx->names().direction, dir)) {
507
return false;
508
}
509
510
args.rval().setObject(*info);
511
return true;
512
}
513
514
using SupportedLocaleKind = js::intl::SharedIntlData::SupportedLocaleKind;
515
516
// 9.2.2 BestAvailableLocale ( availableLocales, locale )
517
static JS::Result<JSString*> BestAvailableLocale(
518
JSContext* cx, SupportedLocaleKind kind, HandleLinearString locale,
519
HandleLinearString defaultLocale) {
520
// In the spec, [[availableLocales]] is formally a list of all available
521
// locales. But in our implementation, it's an *incomplete* list, not
522
// necessarily including the default locale (and all locales implied by it,
523
// e.g. "de" implied by "de-CH"), if that locale isn't in every
524
// [[availableLocales]] list (because that locale is supported through
525
// fallback, e.g. "de-CH" supported through "de").
526
//
527
// If we're considering the default locale, augment the spec loop with
528
// additional checks to also test whether the current prefix is a prefix of
529
// the default locale.
530
531
intl::SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref();
532
533
auto findLast = [](const auto* chars, size_t length) {
534
auto rbegin = std::make_reverse_iterator(chars + length);
535
auto rend = std::make_reverse_iterator(chars);
536
auto p = std::find(rbegin, rend, '-');
537
538
// |dist(chars, p.base())| is equal to |dist(p, rend)|, pick whichever you
539
// find easier to reason about when using reserve iterators.
540
ptrdiff_t r = std::distance(chars, p.base());
541
MOZ_ASSERT(r == std::distance(p, rend));
542
543
// But always subtract one to convert from the reverse iterator result to
544
// the correspoding forward iterator value, because reserve iterators point
545
// to one element past the forward iterator value.
546
return r - 1;
547
};
548
549
// Step 1.
550
RootedLinearString candidate(cx, locale);
551
552
// Step 2.
553
while (true) {
554
// Step 2.a.
555
bool supported = false;
556
if (!sharedIntlData.isSupportedLocale(cx, kind, candidate, &supported)) {
557
return cx->alreadyReportedError();
558
}
559
if (supported) {
560
return candidate.get();
561
}
562
563
if (defaultLocale && candidate->length() <= defaultLocale->length()) {
564
if (EqualStrings(candidate, defaultLocale)) {
565
return candidate.get();
566
}
567
568
if (candidate->length() < defaultLocale->length() &&
569
HasSubstringAt(defaultLocale, candidate, 0) &&
570
defaultLocale->latin1OrTwoByteChar(candidate->length()) == '-') {
571
return candidate.get();
572
}
573
}
574
575
// Step 2.b.
576
ptrdiff_t pos;
577
if (candidate->hasLatin1Chars()) {
578
JS::AutoCheckCannotGC nogc;
579
pos = findLast(candidate->latin1Chars(nogc), candidate->length());
580
} else {
581
JS::AutoCheckCannotGC nogc;
582
pos = findLast(candidate->twoByteChars(nogc), candidate->length());
583
}
584
585
if (pos < 0) {
586
return nullptr;
587
}
588
589
// Step 2.c.
590
size_t length = size_t(pos);
591
if (length >= 2 && candidate->latin1OrTwoByteChar(length - 2) == '-') {
592
length -= 2;
593
}
594
595
// Step 2.d.
596
candidate = NewDependentString(cx, candidate, 0, length);
597
if (!candidate) {
598
return cx->alreadyReportedError();
599
}
600
}
601
}
602
603
// 9.2.2 BestAvailableLocale ( availableLocales, locale )
604
//
605
// Carries an additional third argument in our implementation to provide the
606
// default locale. See the doc-comment in the header file.
607
bool js::intl_BestAvailableLocale(JSContext* cx, unsigned argc, Value* vp) {
608
CallArgs args = CallArgsFromVp(argc, vp);
609
MOZ_ASSERT(args.length() == 3);
610
611
SupportedLocaleKind kind;
612
{
613
JSLinearString* typeStr = args[0].toString()->ensureLinear(cx);
614
if (!typeStr) {
615
return false;
616
}
617
618
if (StringEqualsLiteral(typeStr, "Collator")) {
619
kind = SupportedLocaleKind::Collator;
620
} else if (StringEqualsLiteral(typeStr, "DateTimeFormat")) {
621
kind = SupportedLocaleKind::DateTimeFormat;
622
} else if (StringEqualsLiteral(typeStr, "ListFormat")) {
623
kind = SupportedLocaleKind::ListFormat;
624
} else if (StringEqualsLiteral(typeStr, "NumberFormat")) {
625
kind = SupportedLocaleKind::NumberFormat;
626
} else if (StringEqualsLiteral(typeStr, "PluralRules")) {
627
kind = SupportedLocaleKind::PluralRules;
628
} else {
629
MOZ_ASSERT(StringEqualsLiteral(typeStr, "RelativeTimeFormat"));
630
kind = SupportedLocaleKind::RelativeTimeFormat;
631
}
632
}
633
634
RootedLinearString locale(cx, args[1].toString()->ensureLinear(cx));
635
if (!locale) {
636
return false;
637
}
638
639
#ifdef DEBUG
640
{
641
intl::LanguageTag tag(cx);
642
bool ok;
643
JS_TRY_VAR_OR_RETURN_FALSE(
644
cx, ok, intl::LanguageTagParser::tryParse(cx, locale, tag));
645
MOZ_ASSERT(ok, "locale is a structurally valid language tag");
646
647
MOZ_ASSERT(!tag.unicodeExtension(),
648
"locale must contain no Unicode extensions");
649
650
if (!tag.canonicalize(
651
cx, intl::LanguageTag::UnicodeExtensionCanonicalForm::No)) {
652
return false;
653
}
654
655
JSString* tagStr = tag.toString(cx);
656
if (!tagStr) {
657
return false;
658
}
659
660
bool canonical;
661
if (!EqualStrings(cx, locale, tagStr, &canonical)) {
662
return false;
663
}
664
MOZ_ASSERT(canonical, "locale is a canonicalized language tag");
665
}
666
#endif
667
668
MOZ_ASSERT(args[2].isNull() || args[2].isString());
669
670
RootedLinearString defaultLocale(cx);
671
if (args[2].isString()) {
672
defaultLocale = args[2].toString()->ensureLinear(cx);
673
if (!defaultLocale) {
674
return false;
675
}
676
}
677
678
JSString* result;
679
JS_TRY_VAR_OR_RETURN_FALSE(
680
cx, result, BestAvailableLocale(cx, kind, locale, defaultLocale));
681
682
if (result) {
683
args.rval().setString(result);
684
} else {
685
args.rval().setUndefined();
686
}
687
return true;
688
}
689
690
bool js::intl_supportedLocaleOrFallback(JSContext* cx, unsigned argc,
691
Value* vp) {
692
CallArgs args = CallArgsFromVp(argc, vp);
693
MOZ_ASSERT(args.length() == 1);
694
695
RootedLinearString locale(cx, args[0].toString()->ensureLinear(cx));
696
if (!locale) {
697
return false;
698
}
699
700
intl::LanguageTag tag(cx);
701
bool ok;
702
JS_TRY_VAR_OR_RETURN_FALSE(
703
cx, ok, intl::LanguageTagParser::tryParse(cx, locale, tag));
704
705
RootedLinearString candidate(cx);
706
if (!ok) {
707
candidate = NewStringCopyZ<CanGC>(cx, intl::LastDitchLocale());
708
if (!candidate) {
709
return false;
710
}
711
} else {
712
if (!tag.canonicalize(
713
cx, intl::LanguageTag::UnicodeExtensionCanonicalForm::No)) {
714
return false;
715
}
716
717
// The default locale must be in [[AvailableLocales]], and that list must
718
// not contain any locales with Unicode extension sequences, so remove any
719
// present in the candidate.
720
tag.clearUnicodeExtension();
721
722
JSString* canonical = tag.toString(cx);
723
if (!canonical) {
724
return false;
725
}
726
727
candidate = canonical->ensureLinear(cx);
728
if (!candidate) {
729
return false;
730
}
731
732
for (const auto& mapping : js::intl::oldStyleLanguageTagMappings) {
733
const char* oldStyle = mapping.oldStyle;
734
const char* modernStyle = mapping.modernStyle;
735
736
if (StringEqualsAscii(candidate, oldStyle)) {
737
candidate = NewStringCopyZ<CanGC>(cx, modernStyle);
738
if (!candidate) {
739
return false;
740
}
741
break;
742
}
743
}
744
}
745
746
// 9.1 Internal slots of Service Constructors
747
//
748
// - [[AvailableLocales]] is a List [...]. The list must include the value
749
// returned by the DefaultLocale abstract operation (6.2.4), [...].
750
//
751
// That implies we must ignore any candidate which isn't supported by all Intl
752
// service constructors.
753
//
754
// Note: We don't test the supported locales of either Intl.ListFormat,
755
// Intl.PluralRules, Intl.RelativeTimeFormat, because ICU doesn't provide the
756
// necessary API to return actual set of supported locales for these
757
// constructors. Instead it returns the complete set of available locales for
758
// ULocale, which is a superset of the locales supported by Collator,
759
// NumberFormat, and DateTimeFormat.
760
bool isSupported = true;
761
for (auto kind :
762
{SupportedLocaleKind::Collator, SupportedLocaleKind::DateTimeFormat,
763
SupportedLocaleKind::NumberFormat}) {
764
JSString* supported;
765
JS_TRY_VAR_OR_RETURN_FALSE(
766
cx, supported, BestAvailableLocale(cx, kind, candidate, nullptr));
767
768
if (!supported) {
769
isSupported = false;
770
break;
771
}
772
}
773
774
if (!isSupported) {
775
candidate = NewStringCopyZ<CanGC>(cx, intl::LastDitchLocale());
776
if (!candidate) {
777
return false;
778
}
779
}
780
781
args.rval().setString(candidate);
782
return true;
783
}
784
785
const JSClass js::IntlClass = {js_Object_str,
786
JSCLASS_HAS_CACHED_PROTO(JSProto_Intl)};
787
788
static bool intl_toSource(JSContext* cx, unsigned argc, Value* vp) {
789
CallArgs args = CallArgsFromVp(argc, vp);
790
args.rval().setString(cx->names().Intl);
791
return true;
792
}
793
794
static const JSFunctionSpec intl_static_methods[] = {
795
JS_FN(js_toSource_str, intl_toSource, 0, 0),
796
JS_SELF_HOSTED_FN("getCanonicalLocales", "Intl_getCanonicalLocales", 1, 0),
797
JS_FS_END};
798
799
/**
800
* Initializes the Intl Object and its standard built-in properties.
801
* Spec: ECMAScript Internationalization API Specification, 8.0, 8.1
802
*/
803
JSObject* js::InitIntlClass(JSContext* cx, Handle<GlobalObject*> global) {
804
RootedObject proto(cx, GlobalObject::getOrCreateObjectPrototype(cx, global));
805
if (!proto) {
806
return nullptr;
807
}
808
809
// The |Intl| object is just a plain object with some "static" function
810
// properties and some constructor properties.
811
RootedObject intl(
812
cx, NewObjectWithGivenProto(cx, &IntlClass, proto, SingletonObject));
813
if (!intl) {
814
return nullptr;
815
}
816
817
// Add the static functions.
818
if (!JS_DefineFunctions(cx, intl, intl_static_methods)) {
819
return nullptr;
820
}
821
822
// Add the constructor properties.
823
RootedId ctorId(cx);
824
RootedValue ctorValue(cx);
825
for (const auto& protoKey :
826
{JSProto_Collator, JSProto_DateTimeFormat, JSProto_NumberFormat,
827
JSProto_PluralRules, JSProto_RelativeTimeFormat}) {
828
JSObject* ctor = GlobalObject::getOrCreateConstructor(cx, protoKey);
829
if (!ctor) {
830
return nullptr;
831
}
832
833
ctorId = NameToId(ClassName(protoKey, cx));
834
ctorValue.setObject(*ctor);
835
if (!DefineDataProperty(cx, intl, ctorId, ctorValue, 0)) {
836
return nullptr;
837
}
838
}
839
840
// The |Intl| object is fully set up now, so define the global property.
841
RootedValue intlValue(cx, ObjectValue(*intl));
842
if (!DefineDataProperty(cx, global, cx->names().Intl, intlValue,
843
JSPROP_RESOLVING)) {
844
return nullptr;
845
}
846
847
// Also cache |Intl| to implement spec language that conditions behavior
848
// based on values being equal to "the standard built-in |Intl| object".
849
// Use |setConstructor| to correspond with |JSProto_Intl|.
850
global->setConstructor(JSProto_Intl, ObjectValue(*intl));
851
852
return intl;
853
}