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 /* Intl.DisplayNames implementation. */
8
9 #include "builtin/intl/DisplayNames.h"
10
11 #include "mozilla/ArrayUtils.h"
12 #include "mozilla/Assertions.h"
13 #include "mozilla/Span.h"
14 #include "mozilla/TextUtils.h"
15
16 #include <algorithm>
17
18 #include "jsapi.h"
19 #include "jsfriendapi.h"
20 #include "jsnum.h"
21 #include "jspubtd.h"
22
23 #include "builtin/intl/CommonFunctions.h"
24 #include "builtin/intl/LanguageTag.h"
25 #include "builtin/intl/ScopedICUObject.h"
26 #include "builtin/intl/SharedIntlData.h"
27 #include "gc/AllocKind.h"
28 #include "gc/FreeOp.h"
29 #include "gc/Rooting.h"
30 #include "js/CallArgs.h"
31 #include "js/Class.h"
32 #include "js/GCVector.h"
33 #include "js/PropertyDescriptor.h"
34 #include "js/PropertySpec.h"
35 #include "js/Result.h"
36 #include "js/RootingAPI.h"
37 #include "js/TypeDecls.h"
38 #include "js/Utility.h"
39 #include "unicode/ucal.h"
40 #include "unicode/ucurr.h"
41 #include "unicode/udat.h"
42 #include "unicode/udatpg.h"
43 #include "unicode/udisplaycontext.h"
44 #include "unicode/uldnames.h"
45 #include "unicode/uloc.h"
46 #include "unicode/umachine.h"
47 #include "unicode/utypes.h"
48 #include "vm/GlobalObject.h"
49 #include "vm/JSAtom.h"
50 #include "vm/JSContext.h"
51 #include "vm/JSObject.h"
52 #include "vm/List.h"
53 #include "vm/Printer.h"
54 #include "vm/Runtime.h"
55 #include "vm/SelfHosting.h"
56 #include "vm/Stack.h"
57 #include "vm/StringType.h"
58
59 #include "vm/JSObject-inl.h"
60 #include "vm/List-inl.h"
61 #include "vm/NativeObject-inl.h"
62
63 using namespace js;
64
65 using js::intl::CallICU;
66 using js::intl::IcuLocale;
67
68 const JSClassOps DisplayNamesObject::classOps_ = {nullptr, /* addProperty */
69 nullptr, /* delProperty */
70 nullptr, /* enumerate */
71 nullptr, /* newEnumerate */
72 nullptr, /* resolve */
73 nullptr, /* mayResolve */
74 DisplayNamesObject::finalize};
75
76 const JSClass DisplayNamesObject::class_ = {
77 js_Object_str,
78 JSCLASS_HAS_RESERVED_SLOTS(DisplayNamesObject::SLOT_COUNT) |
79 JSCLASS_HAS_CACHED_PROTO(JSProto_DisplayNames) |
80 JSCLASS_FOREGROUND_FINALIZE,
81 &DisplayNamesObject::classOps_, &DisplayNamesObject::classSpec_};
82
83 const JSClass& DisplayNamesObject::protoClass_ = PlainObject::class_;
84
displayNames_toSource(JSContext * cx,unsigned argc,Value * vp)85 static bool displayNames_toSource(JSContext* cx, unsigned argc, Value* vp) {
86 CallArgs args = CallArgsFromVp(argc, vp);
87 args.rval().setString(cx->names().DisplayNames);
88 return true;
89 }
90
91 static const JSFunctionSpec displayNames_static_methods[] = {
92 JS_SELF_HOSTED_FN("supportedLocalesOf",
93 "Intl_DisplayNames_supportedLocalesOf", 1, 0),
94 JS_FS_END};
95
96 static const JSFunctionSpec displayNames_methods[] = {
97 JS_SELF_HOSTED_FN("of", "Intl_DisplayNames_of", 1, 0),
98 JS_SELF_HOSTED_FN("resolvedOptions", "Intl_DisplayNames_resolvedOptions", 0,
99 0),
100 JS_FN(js_toSource_str, displayNames_toSource, 0, 0), JS_FS_END};
101
102 static const JSPropertySpec displayNames_properties[] = {
103 JS_STRING_SYM_PS(toStringTag, "Intl.DisplayNames", JSPROP_READONLY),
104 JS_PS_END};
105
106 static bool DisplayNames(JSContext* cx, unsigned argc, Value* vp);
107
108 const ClassSpec DisplayNamesObject::classSpec_ = {
109 GenericCreateConstructor<DisplayNames, 0, gc::AllocKind::FUNCTION>,
110 GenericCreatePrototype<DisplayNamesObject>,
111 displayNames_static_methods,
112 nullptr,
113 displayNames_methods,
114 displayNames_properties,
115 nullptr,
116 ClassSpec::DontDefineConstructor};
117
118 enum class DisplayNamesOptions {
119 Standard,
120
121 // Calendar display names are no longer available with the current spec
122 // proposal text, but may be re-enabled in the future. For our internal use
123 // we still need to have them present, so use a feature guard for now.
124 EnableMozExtensions,
125 };
126
127 /**
128 * Initialize a new Intl.DisplayNames object using the named self-hosted
129 * function.
130 */
InitializeDisplayNamesObject(JSContext * cx,HandleObject obj,HandlePropertyName initializer,HandleValue locales,HandleValue options,DisplayNamesOptions dnoptions)131 static bool InitializeDisplayNamesObject(JSContext* cx, HandleObject obj,
132 HandlePropertyName initializer,
133 HandleValue locales,
134 HandleValue options,
135 DisplayNamesOptions dnoptions) {
136 FixedInvokeArgs<4> args(cx);
137
138 args[0].setObject(*obj);
139 args[1].set(locales);
140 args[2].set(options);
141 args[3].setBoolean(dnoptions == DisplayNamesOptions::EnableMozExtensions);
142
143 RootedValue ignored(cx);
144 if (!CallSelfHostedFunction(cx, initializer, NullHandleValue, args,
145 &ignored)) {
146 return false;
147 }
148
149 MOZ_ASSERT(ignored.isUndefined(),
150 "Unexpected return value from non-legacy Intl object initializer");
151 return true;
152 }
153
154 /**
155 * Intl.DisplayNames ([ locales [ , options ]])
156 */
DisplayNames(JSContext * cx,const CallArgs & args,DisplayNamesOptions dnoptions)157 static bool DisplayNames(JSContext* cx, const CallArgs& args,
158 DisplayNamesOptions dnoptions) {
159 // Step 1.
160 if (!ThrowIfNotConstructing(cx, args, "Intl.DisplayNames")) {
161 return false;
162 }
163
164 // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
165 RootedObject proto(cx);
166 if (dnoptions == DisplayNamesOptions::Standard) {
167 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_DisplayNames,
168 &proto)) {
169 return false;
170 }
171 } else {
172 RootedObject newTarget(cx, &args.newTarget().toObject());
173 if (!GetPrototypeFromConstructor(cx, newTarget, JSProto_Null, &proto)) {
174 return false;
175 }
176 }
177
178 Rooted<DisplayNamesObject*> displayNames(cx);
179 displayNames = NewObjectWithClassProto<DisplayNamesObject>(cx, proto);
180 if (!displayNames) {
181 return false;
182 }
183
184 HandleValue locales = args.get(0);
185 HandleValue options = args.get(1);
186
187 // Steps 3-26.
188 if (!InitializeDisplayNamesObject(cx, displayNames,
189 cx->names().InitializeDisplayNames, locales,
190 options, dnoptions)) {
191 return false;
192 }
193
194 // Step 27.
195 args.rval().setObject(*displayNames);
196 return true;
197 }
198
DisplayNames(JSContext * cx,unsigned argc,Value * vp)199 static bool DisplayNames(JSContext* cx, unsigned argc, Value* vp) {
200 CallArgs args = CallArgsFromVp(argc, vp);
201 return DisplayNames(cx, args, DisplayNamesOptions::Standard);
202 }
203
MozDisplayNames(JSContext * cx,unsigned argc,Value * vp)204 static bool MozDisplayNames(JSContext* cx, unsigned argc, Value* vp) {
205 CallArgs args = CallArgsFromVp(argc, vp);
206 return DisplayNames(cx, args, DisplayNamesOptions::EnableMozExtensions);
207 }
208
finalize(JSFreeOp * fop,JSObject * obj)209 void js::DisplayNamesObject::finalize(JSFreeOp* fop, JSObject* obj) {
210 MOZ_ASSERT(fop->onMainThread());
211
212 if (ULocaleDisplayNames* ldn =
213 obj->as<DisplayNamesObject>().getLocaleDisplayNames()) {
214 intl::RemoveICUCellMemory(fop, obj, DisplayNamesObject::EstimatedMemoryUse);
215
216 uldn_close(ldn);
217 }
218 }
219
AddDisplayNamesConstructor(JSContext * cx,HandleObject intl)220 bool js::AddDisplayNamesConstructor(JSContext* cx, HandleObject intl) {
221 JSObject* ctor =
222 GlobalObject::getOrCreateConstructor(cx, JSProto_DisplayNames);
223 if (!ctor) {
224 return false;
225 }
226
227 RootedValue ctorValue(cx, ObjectValue(*ctor));
228 return DefineDataProperty(cx, intl, cx->names().DisplayNames, ctorValue, 0);
229 }
230
AddMozDisplayNamesConstructor(JSContext * cx,HandleObject intl)231 bool js::AddMozDisplayNamesConstructor(JSContext* cx, HandleObject intl) {
232 RootedObject ctor(cx, GlobalObject::createConstructor(
233 cx, MozDisplayNames, cx->names().DisplayNames, 0));
234 if (!ctor) {
235 return false;
236 }
237
238 RootedObject proto(
239 cx, GlobalObject::createBlankPrototype<PlainObject>(cx, cx->global()));
240 if (!proto) {
241 return false;
242 }
243
244 if (!LinkConstructorAndPrototype(cx, ctor, proto)) {
245 return false;
246 }
247
248 if (!JS_DefineFunctions(cx, ctor, displayNames_static_methods)) {
249 return false;
250 }
251
252 if (!JS_DefineFunctions(cx, proto, displayNames_methods)) {
253 return false;
254 }
255
256 if (!JS_DefineProperties(cx, proto, displayNames_properties)) {
257 return false;
258 }
259
260 RootedValue ctorValue(cx, ObjectValue(*ctor));
261 return DefineDataProperty(cx, intl, cx->names().DisplayNames, ctorValue, 0);
262 }
263
264 enum class DisplayNamesStyle { Long, Short, Narrow };
265
NewULocaleDisplayNames(JSContext * cx,const char * locale,DisplayNamesStyle displayStyle)266 static ULocaleDisplayNames* NewULocaleDisplayNames(
267 JSContext* cx, const char* locale, DisplayNamesStyle displayStyle) {
268 UErrorCode status = U_ZERO_ERROR;
269
270 UDisplayContext contexts[] = {
271 // Use the standard names, not the dialect names.
272 // For example "English (GB)" instead of "British English".
273 UDISPCTX_STANDARD_NAMES,
274
275 // Assume the display names are used in a stand-alone context.
276 UDISPCTX_CAPITALIZATION_FOR_STANDALONE,
277
278 // Select either the long or short form. There's no separate narrow form
279 // available in ICU, therefore we equate "narrow"/"short" styles here.
280 displayStyle == DisplayNamesStyle::Long ? UDISPCTX_LENGTH_FULL
281 : UDISPCTX_LENGTH_SHORT,
282
283 // Don't apply substitutes, because we need to apply our own fallbacks.
284 UDISPCTX_NO_SUBSTITUTE,
285 };
286
287 ULocaleDisplayNames* ldn = uldn_openForContext(
288 IcuLocale(locale), contexts, mozilla::ArrayLength(contexts), &status);
289 if (U_FAILURE(status)) {
290 intl::ReportInternalError(cx);
291 return nullptr;
292 }
293 return ldn;
294 }
295
GetOrCreateLocaleDisplayNames(JSContext * cx,Handle<DisplayNamesObject * > displayNames,const char * locale,DisplayNamesStyle displayStyle)296 static ULocaleDisplayNames* GetOrCreateLocaleDisplayNames(
297 JSContext* cx, Handle<DisplayNamesObject*> displayNames, const char* locale,
298 DisplayNamesStyle displayStyle) {
299 // Obtain a cached ULocaleDisplayNames object.
300 ULocaleDisplayNames* ldn = displayNames->getLocaleDisplayNames();
301 if (!ldn) {
302 ldn = NewULocaleDisplayNames(cx, locale, displayStyle);
303 if (!ldn) {
304 return nullptr;
305 }
306 displayNames->setLocaleDisplayNames(ldn);
307
308 intl::AddICUCellMemory(displayNames,
309 DisplayNamesObject::EstimatedMemoryUse);
310 }
311 return ldn;
312 }
313
ReportInvalidOptionError(JSContext * cx,const char * type,HandleString option)314 static void ReportInvalidOptionError(JSContext* cx, const char* type,
315 HandleString option) {
316 if (UniqueChars str = QuoteString(cx, option, '"')) {
317 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
318 JSMSG_INVALID_OPTION_VALUE, type, str.get());
319 }
320 }
321
ReportInvalidOptionError(JSContext * cx,const char * type,double option)322 static void ReportInvalidOptionError(JSContext* cx, const char* type,
323 double option) {
324 ToCStringBuf cbuf;
325 if (const char* str = NumberToCString(cx, &cbuf, option)) {
326 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
327 JSMSG_INVALID_DIGITS_VALUE, str);
328 }
329 }
330
GetLanguageDisplayName(JSContext * cx,Handle<DisplayNamesObject * > displayNames,const char * locale,DisplayNamesStyle displayStyle,HandleLinearString languageStr)331 static JSString* GetLanguageDisplayName(
332 JSContext* cx, Handle<DisplayNamesObject*> displayNames, const char* locale,
333 DisplayNamesStyle displayStyle, HandleLinearString languageStr) {
334 bool ok;
335 intl::LanguageTag tag(cx);
336 JS_TRY_VAR_OR_RETURN_NULL(
337 cx, ok, intl::LanguageTagParser::tryParseBaseName(cx, languageStr, tag));
338 if (!ok) {
339 ReportInvalidOptionError(cx, "language", languageStr);
340 return nullptr;
341 }
342
343 // ICU always canonicalizes the input locale, but since we know that ICU's
344 // canonicalization is incomplete, we need to perform our own canonicalization
345 // to ensure consistent result.
346 // FIXME: spec issue - language tag canonicalisation not performed:
347 // https://github.com/tc39/proposal-intl-displaynames/issues/70
348 if (!tag.canonicalizeBaseName(cx)) {
349 return nullptr;
350 }
351
352 UniqueChars languageChars = tag.toStringZ(cx);
353 if (!languageChars) {
354 return nullptr;
355 }
356
357 ULocaleDisplayNames* ldn =
358 GetOrCreateLocaleDisplayNames(cx, displayNames, locale, displayStyle);
359 if (!ldn) {
360 return nullptr;
361 }
362
363 return CallICU(cx, [ldn, &languageChars](UChar* chars, uint32_t size,
364 UErrorCode* status) {
365 int32_t res =
366 uldn_localeDisplayName(ldn, languageChars.get(), chars, size, status);
367
368 // |uldn_localeDisplayName| reports U_ILLEGAL_ARGUMENT_ERROR when no
369 // display name was found.
370 if (*status == U_ILLEGAL_ARGUMENT_ERROR) {
371 *status = U_ZERO_ERROR;
372 res = 0;
373 }
374 return res;
375 });
376 }
377
GetScriptDisplayName(JSContext * cx,Handle<DisplayNamesObject * > displayNames,const char * locale,DisplayNamesStyle displayStyle,HandleLinearString scriptStr)378 static JSString* GetScriptDisplayName(JSContext* cx,
379 Handle<DisplayNamesObject*> displayNames,
380 const char* locale,
381 DisplayNamesStyle displayStyle,
382 HandleLinearString scriptStr) {
383 intl::ScriptSubtag script;
384 if (!intl::ParseStandaloneScriptTag(scriptStr, script)) {
385 ReportInvalidOptionError(cx, "script", scriptStr);
386 return nullptr;
387 }
388
389 intl::LanguageTag tag(cx);
390 tag.setLanguage("und");
391 tag.setScript(script);
392
393 // ICU always canonicalizes the input locale, but since we know that ICU's
394 // canonicalization is incomplete, we need to perform our own canonicalization
395 // to ensure consistent result.
396 // FIXME: spec issue - language tag canonicalisation not performed:
397 // https://github.com/tc39/proposal-intl-displaynames/issues/70
398 if (!tag.canonicalizeBaseName(cx)) {
399 return nullptr;
400 }
401 MOZ_ASSERT(tag.script().present());
402
403 // |uldn_scriptDisplayName| doesn't use the stand-alone form for script
404 // subtags, so we're using |uloc_getDisplayScript| instead. (This only applies
405 // to the long form.)
406 //
407 // ICU bug: https://unicode-org.atlassian.net/browse/ICU-9301
408 if (displayStyle == DisplayNamesStyle::Long) {
409 // |uloc_getDisplayScript| expects a full locale identifier as its input.
410 UniqueChars scriptChars = tag.toStringZ(cx);
411 if (!scriptChars) {
412 return nullptr;
413 }
414
415 return CallICU(cx, [locale, &scriptChars](UChar* chars, uint32_t size,
416 UErrorCode* status) {
417 int32_t res =
418 uloc_getDisplayScript(scriptChars.get(), locale, chars, size, status);
419
420 // |uloc_getDisplayScript| reports U_USING_DEFAULT_WARNING when no display
421 // name was found.
422 if (*status == U_USING_DEFAULT_WARNING) {
423 *status = U_ZERO_ERROR;
424 res = 0;
425 }
426 return res;
427 });
428 }
429
430 // Note: ICU requires the script subtag to be in canonical case.
431 const intl::ScriptSubtag& canonicalScript = tag.script();
432
433 char scriptChars[intl::LanguageTagLimits::ScriptLength + 1];
434 std::copy_n(canonicalScript.span().data(), canonicalScript.length(),
435 scriptChars);
436 scriptChars[canonicalScript.length()] = '\0';
437
438 ULocaleDisplayNames* ldn =
439 GetOrCreateLocaleDisplayNames(cx, displayNames, locale, displayStyle);
440 if (!ldn) {
441 return nullptr;
442 }
443
444 return CallICU(cx, [ldn, scriptChars](UChar* chars, uint32_t size,
445 UErrorCode* status) {
446 int32_t res = uldn_scriptDisplayName(ldn, scriptChars, chars, size, status);
447
448 // |uldn_scriptDisplayName| reports U_ILLEGAL_ARGUMENT_ERROR when no display
449 // name was found.
450 if (*status == U_ILLEGAL_ARGUMENT_ERROR) {
451 *status = U_ZERO_ERROR;
452 res = 0;
453 }
454 return res;
455 });
456 }
457
GetRegionDisplayName(JSContext * cx,Handle<DisplayNamesObject * > displayNames,const char * locale,DisplayNamesStyle displayStyle,HandleLinearString regionStr)458 static JSString* GetRegionDisplayName(JSContext* cx,
459 Handle<DisplayNamesObject*> displayNames,
460 const char* locale,
461 DisplayNamesStyle displayStyle,
462 HandleLinearString regionStr) {
463 intl::RegionSubtag region;
464 if (!intl::ParseStandaloneRegionTag(regionStr, region)) {
465 ReportInvalidOptionError(cx, "region", regionStr);
466 return nullptr;
467 }
468
469 intl::LanguageTag tag(cx);
470 tag.setLanguage("und");
471 tag.setRegion(region);
472
473 // ICU always canonicalizes the input locale, but since we know that ICU's
474 // canonicalization is incomplete, we need to perform our own canonicalization
475 // to ensure consistent result.
476 // FIXME: spec issue - language tag canonicalisation not performed:
477 // https://github.com/tc39/proposal-intl-displaynames/issues/70
478 if (!tag.canonicalizeBaseName(cx)) {
479 return nullptr;
480 }
481 MOZ_ASSERT(tag.region().present());
482
483 // Note: ICU requires the region subtag to be in canonical case.
484 const intl::RegionSubtag& canonicalRegion = tag.region();
485
486 char regionChars[intl::LanguageTagLimits::RegionLength + 1];
487 std::copy_n(canonicalRegion.span().data(), canonicalRegion.length(),
488 regionChars);
489 regionChars[canonicalRegion.length()] = '\0';
490
491 ULocaleDisplayNames* ldn =
492 GetOrCreateLocaleDisplayNames(cx, displayNames, locale, displayStyle);
493 if (!ldn) {
494 return nullptr;
495 }
496
497 return CallICU(cx, [ldn, regionChars](UChar* chars, uint32_t size,
498 UErrorCode* status) {
499 int32_t res = uldn_regionDisplayName(ldn, regionChars, chars, size, status);
500
501 // |uldn_regionDisplayName| reports U_ILLEGAL_ARGUMENT_ERROR when no display
502 // name was found.
503 if (*status == U_ILLEGAL_ARGUMENT_ERROR) {
504 *status = U_ZERO_ERROR;
505 res = 0;
506 }
507 return res;
508 });
509 }
510
GetCurrencyDisplayName(JSContext * cx,const char * locale,DisplayNamesStyle displayStyle,HandleLinearString currencyStr)511 static JSString* GetCurrencyDisplayName(JSContext* cx, const char* locale,
512 DisplayNamesStyle displayStyle,
513 HandleLinearString currencyStr) {
514 // Inlined implementation of `IsWellFormedCurrencyCode ( currency )`.
515 if (currencyStr->length() != 3) {
516 ReportInvalidOptionError(cx, "currency", currencyStr);
517 return nullptr;
518 }
519
520 char16_t currency[] = {currencyStr->latin1OrTwoByteChar(0),
521 currencyStr->latin1OrTwoByteChar(1),
522 currencyStr->latin1OrTwoByteChar(2), '\0'};
523
524 if (!mozilla::IsAsciiAlpha(currency[0]) ||
525 !mozilla::IsAsciiAlpha(currency[1]) ||
526 !mozilla::IsAsciiAlpha(currency[2])) {
527 ReportInvalidOptionError(cx, "currency", currencyStr);
528 return nullptr;
529 }
530
531 UCurrNameStyle currencyStyle;
532 switch (displayStyle) {
533 case DisplayNamesStyle::Long:
534 currencyStyle = UCURR_LONG_NAME;
535 break;
536 case DisplayNamesStyle::Short:
537 currencyStyle = UCURR_SYMBOL_NAME;
538 break;
539 case DisplayNamesStyle::Narrow:
540 currencyStyle = UCURR_NARROW_SYMBOL_NAME;
541 break;
542 }
543
544 int32_t length = 0;
545 UErrorCode status = U_ZERO_ERROR;
546 const char16_t* name =
547 ucurr_getName(currency, locale, currencyStyle, nullptr, &length, &status);
548 if (U_FAILURE(status)) {
549 intl::ReportInternalError(cx);
550 return nullptr;
551 }
552 MOZ_ASSERT(length >= 0);
553
554 if (status == U_USING_DEFAULT_WARNING) {
555 return cx->emptyString();
556 }
557
558 return NewStringCopyN<CanGC>(cx, name, size_t(length));
559 }
560
GetDateTimeDisplayNames(JSContext * cx,Handle<DisplayNamesObject * > displayNames,const char * locale,HandleLinearString calendar,UDateFormatSymbolType symbolType,mozilla::Span<const int32_t> indices)561 static ListObject* GetDateTimeDisplayNames(
562 JSContext* cx, Handle<DisplayNamesObject*> displayNames, const char* locale,
563 HandleLinearString calendar, UDateFormatSymbolType symbolType,
564 mozilla::Span<const int32_t> indices) {
565 if (auto* names = displayNames->getDateTimeNames()) {
566 return names;
567 }
568
569 intl::LanguageTag tag(cx);
570 if (!intl::LanguageTagParser::parse(cx, mozilla::MakeStringSpan(locale),
571 tag)) {
572 return nullptr;
573 }
574
575 JS::RootedVector<intl::UnicodeExtensionKeyword> keywords(cx);
576 if (!keywords.emplaceBack("ca", calendar)) {
577 return nullptr;
578 }
579
580 if (!intl::ApplyUnicodeExtensionToTag(cx, tag, keywords)) {
581 return nullptr;
582 }
583
584 UniqueChars localeWithCalendar = tag.toStringZ(cx);
585 if (!localeWithCalendar) {
586 return nullptr;
587 }
588
589 constexpr char16_t* timeZone = nullptr;
590 constexpr int32_t timeZoneLength = 0;
591
592 constexpr char16_t* pattern = nullptr;
593 constexpr int32_t patternLength = 0;
594
595 UErrorCode status = U_ZERO_ERROR;
596 UDateFormat* fmt =
597 udat_open(UDAT_DEFAULT, UDAT_DEFAULT, IcuLocale(localeWithCalendar.get()),
598 timeZone, timeZoneLength, pattern, patternLength, &status);
599 if (U_FAILURE(status)) {
600 intl::ReportInternalError(cx);
601 return nullptr;
602 }
603 ScopedICUObject<UDateFormat, udat_close> datToClose(fmt);
604
605 Rooted<ListObject*> names(cx, ListObject::create(cx));
606 if (!names) {
607 return nullptr;
608 }
609
610 RootedValue value(cx);
611 for (uint32_t i = 0; i < indices.size(); i++) {
612 uint32_t index = indices[i];
613 JSString* name =
614 CallICU(cx, [fmt, symbolType, index](UChar* chars, int32_t size,
615 UErrorCode* status) {
616 return udat_getSymbols(fmt, symbolType, index, chars, size, status);
617 });
618 if (!name) {
619 return nullptr;
620 }
621
622 value.setString(name);
623 if (!names->append(cx, value)) {
624 return nullptr;
625 }
626 }
627
628 displayNames->setDateTimeNames(names);
629 return names;
630 }
631
GetWeekdayDisplayName(JSContext * cx,Handle<DisplayNamesObject * > displayNames,const char * locale,HandleLinearString calendar,DisplayNamesStyle displayStyle,HandleLinearString code)632 static JSString* GetWeekdayDisplayName(JSContext* cx,
633 Handle<DisplayNamesObject*> displayNames,
634 const char* locale,
635 HandleLinearString calendar,
636 DisplayNamesStyle displayStyle,
637 HandleLinearString code) {
638 double weekday;
639 if (!StringToNumber(cx, code, &weekday)) {
640 return nullptr;
641 }
642
643 // Inlined implementation of `IsValidWeekdayCode ( weekday )`.
644 if (!IsInteger(weekday) || weekday < 1 || weekday > 7) {
645 ReportInvalidOptionError(cx, "weekday", weekday);
646 return nullptr;
647 }
648
649 UDateFormatSymbolType symbolType;
650 switch (displayStyle) {
651 case DisplayNamesStyle::Long:
652 symbolType = UDAT_STANDALONE_WEEKDAYS;
653 break;
654
655 case DisplayNamesStyle::Short:
656 // ICU "short" is CLDR "abbreviated"; "shorter" is CLDR "short" format.
657 symbolType = UDAT_STANDALONE_SHORTER_WEEKDAYS;
658 break;
659
660 case DisplayNamesStyle::Narrow:
661 symbolType = UDAT_STANDALONE_NARROW_WEEKDAYS;
662 break;
663 }
664
665 static constexpr int32_t indices[] = {
666 UCAL_MONDAY, UCAL_TUESDAY, UCAL_WEDNESDAY, UCAL_THURSDAY,
667 UCAL_FRIDAY, UCAL_SATURDAY, UCAL_SUNDAY};
668
669 ListObject* names =
670 GetDateTimeDisplayNames(cx, displayNames, locale, calendar, symbolType,
671 mozilla::MakeSpan(indices));
672 if (!names) {
673 return nullptr;
674 }
675 MOZ_ASSERT(names->length() == mozilla::ArrayLength(indices));
676
677 return names->get(int32_t(weekday) - 1).toString();
678 }
679
GetMonthDisplayName(JSContext * cx,Handle<DisplayNamesObject * > displayNames,const char * locale,HandleLinearString calendar,DisplayNamesStyle displayStyle,HandleLinearString code)680 static JSString* GetMonthDisplayName(JSContext* cx,
681 Handle<DisplayNamesObject*> displayNames,
682 const char* locale,
683 HandleLinearString calendar,
684 DisplayNamesStyle displayStyle,
685 HandleLinearString code) {
686 double month;
687 if (!StringToNumber(cx, code, &month)) {
688 return nullptr;
689 }
690
691 // Inlined implementation of `IsValidMonthCode ( month )`.
692 if (!IsInteger(month) || month < 1 || month > 13) {
693 ReportInvalidOptionError(cx, "month", month);
694 return nullptr;
695 }
696
697 UDateFormatSymbolType symbolType;
698 switch (displayStyle) {
699 case DisplayNamesStyle::Long:
700 symbolType = UDAT_STANDALONE_MONTHS;
701 break;
702
703 case DisplayNamesStyle::Short:
704 symbolType = UDAT_STANDALONE_SHORT_MONTHS;
705 break;
706
707 case DisplayNamesStyle::Narrow:
708 symbolType = UDAT_STANDALONE_NARROW_MONTHS;
709 break;
710 }
711
712 static constexpr int32_t indices[] = {
713 UCAL_JANUARY, UCAL_FEBRUARY, UCAL_MARCH, UCAL_APRIL,
714 UCAL_MAY, UCAL_JUNE, UCAL_JULY, UCAL_AUGUST,
715 UCAL_SEPTEMBER, UCAL_OCTOBER, UCAL_NOVEMBER, UCAL_DECEMBER,
716 UCAL_UNDECIMBER};
717
718 ListObject* names =
719 GetDateTimeDisplayNames(cx, displayNames, locale, calendar, symbolType,
720 mozilla::MakeSpan(indices));
721 if (!names) {
722 return nullptr;
723 }
724 MOZ_ASSERT(names->length() == mozilla::ArrayLength(indices));
725
726 return names->get(int32_t(month) - 1).toString();
727 }
728
GetQuarterDisplayName(JSContext * cx,Handle<DisplayNamesObject * > displayNames,const char * locale,HandleLinearString calendar,DisplayNamesStyle displayStyle,HandleLinearString code)729 static JSString* GetQuarterDisplayName(JSContext* cx,
730 Handle<DisplayNamesObject*> displayNames,
731 const char* locale,
732 HandleLinearString calendar,
733 DisplayNamesStyle displayStyle,
734 HandleLinearString code) {
735 double quarter;
736 if (!StringToNumber(cx, code, &quarter)) {
737 return nullptr;
738 }
739
740 // Inlined implementation of `IsValidQuarterCode ( quarter )`.
741 if (!IsInteger(quarter) || quarter < 1 || quarter > 4) {
742 ReportInvalidOptionError(cx, "quarter", quarter);
743 return nullptr;
744 }
745
746 UDateFormatSymbolType symbolType;
747 switch (displayStyle) {
748 case DisplayNamesStyle::Long:
749 symbolType = UDAT_STANDALONE_QUARTERS;
750 break;
751
752 case DisplayNamesStyle::Short:
753 case DisplayNamesStyle::Narrow:
754 // CLDR "narrow" style not supported in ICU.
755 symbolType = UDAT_STANDALONE_SHORT_QUARTERS;
756 break;
757 }
758
759 // ICU doesn't provide an enum for quarters.
760 static constexpr int32_t indices[] = {0, 1, 2, 3};
761
762 ListObject* names =
763 GetDateTimeDisplayNames(cx, displayNames, locale, calendar, symbolType,
764 mozilla::MakeSpan(indices));
765 if (!names) {
766 return nullptr;
767 }
768 MOZ_ASSERT(names->length() == mozilla::ArrayLength(indices));
769
770 return names->get(int32_t(quarter) - 1).toString();
771 }
772
GetDayPeriodDisplayName(JSContext * cx,Handle<DisplayNamesObject * > displayNames,const char * locale,HandleLinearString calendar,HandleLinearString dayPeriod)773 static JSString* GetDayPeriodDisplayName(
774 JSContext* cx, Handle<DisplayNamesObject*> displayNames, const char* locale,
775 HandleLinearString calendar, HandleLinearString dayPeriod) {
776 // Inlined implementation of `IsValidDayPeriodCode ( dayperiod )`.
777 uint32_t index;
778 if (StringEqualsLiteral(dayPeriod, "am")) {
779 index = 0;
780 } else if (StringEqualsLiteral(dayPeriod, "pm")) {
781 index = 1;
782 } else {
783 ReportInvalidOptionError(cx, "dayPeriod", dayPeriod);
784 return nullptr;
785 }
786
787 UDateFormatSymbolType symbolType = UDAT_AM_PMS;
788
789 static constexpr int32_t indices[] = {UCAL_AM, UCAL_PM};
790
791 ListObject* names =
792 GetDateTimeDisplayNames(cx, displayNames, locale, calendar, symbolType,
793 mozilla::MakeSpan(indices));
794 if (!names) {
795 return nullptr;
796 }
797 MOZ_ASSERT(names->length() == mozilla::ArrayLength(indices));
798
799 return names->get(index).toString();
800 }
801
GetDateTimeFieldDisplayName(JSContext * cx,const char * locale,DisplayNamesStyle displayStyle,HandleLinearString dateTimeField)802 static JSString* GetDateTimeFieldDisplayName(JSContext* cx, const char* locale,
803 DisplayNamesStyle displayStyle,
804 HandleLinearString dateTimeField) {
805 // Inlined implementation of `IsValidDateTimeFieldCode ( field )`.
806 UDateTimePatternField field;
807 if (StringEqualsLiteral(dateTimeField, "era")) {
808 field = UDATPG_ERA_FIELD;
809 } else if (StringEqualsLiteral(dateTimeField, "year")) {
810 field = UDATPG_YEAR_FIELD;
811 } else if (StringEqualsLiteral(dateTimeField, "quarter")) {
812 field = UDATPG_QUARTER_FIELD;
813 } else if (StringEqualsLiteral(dateTimeField, "month")) {
814 field = UDATPG_MONTH_FIELD;
815 } else if (StringEqualsLiteral(dateTimeField, "weekOfYear")) {
816 field = UDATPG_WEEK_OF_YEAR_FIELD;
817 } else if (StringEqualsLiteral(dateTimeField, "weekday")) {
818 field = UDATPG_WEEKDAY_FIELD;
819 } else if (StringEqualsLiteral(dateTimeField, "day")) {
820 field = UDATPG_DAY_FIELD;
821 } else if (StringEqualsLiteral(dateTimeField, "dayPeriod")) {
822 field = UDATPG_DAYPERIOD_FIELD;
823 } else if (StringEqualsLiteral(dateTimeField, "hour")) {
824 field = UDATPG_HOUR_FIELD;
825 } else if (StringEqualsLiteral(dateTimeField, "minute")) {
826 field = UDATPG_MINUTE_FIELD;
827 } else if (StringEqualsLiteral(dateTimeField, "second")) {
828 field = UDATPG_SECOND_FIELD;
829 } else if (StringEqualsLiteral(dateTimeField, "timeZoneName")) {
830 field = UDATPG_ZONE_FIELD;
831 } else {
832 ReportInvalidOptionError(cx, "dateTimeField", dateTimeField);
833 return nullptr;
834 }
835
836 UDateTimePGDisplayWidth width;
837 switch (displayStyle) {
838 case DisplayNamesStyle::Long:
839 width = UDATPG_WIDE;
840 break;
841 case DisplayNamesStyle::Short:
842 width = UDATPG_ABBREVIATED;
843 break;
844 case DisplayNamesStyle::Narrow:
845 width = UDATPG_NARROW;
846 break;
847 }
848
849 intl::SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref();
850 UDateTimePatternGenerator* dtpg =
851 sharedIntlData.getDateTimePatternGenerator(cx, locale);
852 if (!dtpg) {
853 return nullptr;
854 }
855
856 return intl::CallICU(cx, [dtpg, field, width](UChar* chars, uint32_t size,
857 UErrorCode* status) {
858 return udatpg_getFieldDisplayName(dtpg, field, width, chars, size, status);
859 });
860 }
861
862 /**
863 * intl_ComputeDisplayName(displayNames, locale, calendar, style, type, code)
864 */
intl_ComputeDisplayName(JSContext * cx,unsigned argc,Value * vp)865 bool js::intl_ComputeDisplayName(JSContext* cx, unsigned argc, Value* vp) {
866 CallArgs args = CallArgsFromVp(argc, vp);
867 MOZ_ASSERT(args.length() == 6);
868
869 Rooted<DisplayNamesObject*> displayNames(
870 cx, &args[0].toObject().as<DisplayNamesObject>());
871
872 UniqueChars locale = intl::EncodeLocale(cx, args[1].toString());
873 if (!locale) {
874 return false;
875 }
876
877 RootedLinearString calendar(cx, args[2].toString()->ensureLinear(cx));
878 if (!calendar) {
879 return false;
880 }
881
882 RootedLinearString code(cx, args[5].toString()->ensureLinear(cx));
883 if (!code) {
884 return false;
885 }
886
887 DisplayNamesStyle displayStyle;
888 {
889 JSLinearString* style = args[3].toString()->ensureLinear(cx);
890 if (!style) {
891 return false;
892 }
893
894 if (StringEqualsLiteral(style, "long")) {
895 displayStyle = DisplayNamesStyle::Long;
896 } else if (StringEqualsLiteral(style, "short")) {
897 displayStyle = DisplayNamesStyle::Short;
898 } else {
899 MOZ_ASSERT(StringEqualsLiteral(style, "narrow"));
900 displayStyle = DisplayNamesStyle::Narrow;
901 }
902 }
903
904 JSLinearString* type = args[4].toString()->ensureLinear(cx);
905 if (!type) {
906 return false;
907 }
908
909 JSString* result;
910 if (StringEqualsLiteral(type, "language")) {
911 result = GetLanguageDisplayName(cx, displayNames, locale.get(),
912 displayStyle, code);
913 } else if (StringEqualsLiteral(type, "script")) {
914 result = GetScriptDisplayName(cx, displayNames, locale.get(), displayStyle,
915 code);
916 } else if (StringEqualsLiteral(type, "region")) {
917 result = GetRegionDisplayName(cx, displayNames, locale.get(), displayStyle,
918 code);
919 } else if (StringEqualsLiteral(type, "currency")) {
920 result = GetCurrencyDisplayName(cx, locale.get(), displayStyle, code);
921 } else if (StringEqualsLiteral(type, "weekday")) {
922 result = GetWeekdayDisplayName(cx, displayNames, locale.get(), calendar,
923 displayStyle, code);
924 } else if (StringEqualsLiteral(type, "month")) {
925 result = GetMonthDisplayName(cx, displayNames, locale.get(), calendar,
926 displayStyle, code);
927 } else if (StringEqualsLiteral(type, "quarter")) {
928 result = GetQuarterDisplayName(cx, displayNames, locale.get(), calendar,
929 displayStyle, code);
930 } else if (StringEqualsLiteral(type, "dayPeriod")) {
931 result =
932 GetDayPeriodDisplayName(cx, displayNames, locale.get(), calendar, code);
933 } else {
934 MOZ_ASSERT(StringEqualsLiteral(type, "dateTimeField"));
935 result = GetDateTimeFieldDisplayName(cx, locale.get(), displayStyle, code);
936 }
937 if (!result) {
938 return false;
939 }
940
941 args.rval().setString(result);
942 return true;
943 }
944