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/. */
7 /* Intl.DisplayNames implementation. */
9 #include "builtin/intl/DisplayNames.h"
11 #include "mozilla/Assertions.h"
12 #include "mozilla/intl/DisplayNames.h"
13 #include "mozilla/PodOperations.h"
14 #include "mozilla/Span.h"
21 #include "builtin/intl/CommonFunctions.h"
22 #include "builtin/intl/FormatBuffer.h"
23 #include "gc/AllocKind.h"
24 #include "gc/GCContext.h"
25 #include "js/CallArgs.h"
27 #include "js/experimental/Intl.h" // JS::AddMozDisplayNamesConstructor
28 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
29 #include "js/Printer.h"
30 #include "js/PropertyAndElement.h" // JS_DefineFunctions, JS_DefineProperties
31 #include "js/PropertyDescriptor.h"
32 #include "js/PropertySpec.h"
33 #include "js/RootingAPI.h"
34 #include "js/TypeDecls.h"
35 #include "js/Utility.h"
36 #include "vm/GlobalObject.h"
37 #include "vm/JSContext.h"
38 #include "vm/JSObject.h"
39 #include "vm/Runtime.h"
40 #include "vm/SelfHosting.h"
42 #include "vm/StringType.h"
44 #include "vm/JSObject-inl.h"
45 #include "vm/NativeObject-inl.h"
49 const JSClassOps
DisplayNamesObject::classOps_
= {nullptr, /* addProperty */
50 nullptr, /* delProperty */
51 nullptr, /* enumerate */
52 nullptr, /* newEnumerate */
53 nullptr, /* resolve */
54 nullptr, /* mayResolve */
55 DisplayNamesObject::finalize
};
57 const JSClass
DisplayNamesObject::class_
= {
59 JSCLASS_HAS_RESERVED_SLOTS(DisplayNamesObject::SLOT_COUNT
) |
60 JSCLASS_HAS_CACHED_PROTO(JSProto_DisplayNames
) |
61 JSCLASS_FOREGROUND_FINALIZE
,
62 &DisplayNamesObject::classOps_
, &DisplayNamesObject::classSpec_
};
64 const JSClass
& DisplayNamesObject::protoClass_
= PlainObject::class_
;
66 static bool displayNames_toSource(JSContext
* cx
, unsigned argc
, Value
* vp
) {
67 CallArgs args
= CallArgsFromVp(argc
, vp
);
68 args
.rval().setString(cx
->names().DisplayNames
);
72 static const JSFunctionSpec displayNames_static_methods
[] = {
73 JS_SELF_HOSTED_FN("supportedLocalesOf",
74 "Intl_DisplayNames_supportedLocalesOf", 1, 0),
77 static const JSFunctionSpec displayNames_methods
[] = {
78 JS_SELF_HOSTED_FN("of", "Intl_DisplayNames_of", 1, 0),
79 JS_SELF_HOSTED_FN("resolvedOptions", "Intl_DisplayNames_resolvedOptions", 0,
81 JS_FN("toSource", displayNames_toSource
, 0, 0), JS_FS_END
};
83 static const JSPropertySpec displayNames_properties
[] = {
84 JS_STRING_SYM_PS(toStringTag
, "Intl.DisplayNames", JSPROP_READONLY
),
87 static bool DisplayNames(JSContext
* cx
, unsigned argc
, Value
* vp
);
89 const ClassSpec
DisplayNamesObject::classSpec_
= {
90 GenericCreateConstructor
<DisplayNames
, 2, gc::AllocKind::FUNCTION
>,
91 GenericCreatePrototype
<DisplayNamesObject
>,
92 displayNames_static_methods
,
95 displayNames_properties
,
97 ClassSpec::DontDefineConstructor
};
99 enum class DisplayNamesOptions
{
102 // Calendar display names are no longer available with the current spec
103 // proposal text, but may be re-enabled in the future. For our internal use
104 // we still need to have them present, so use a feature guard for now.
109 * Initialize a new Intl.DisplayNames object using the named self-hosted
112 static bool InitializeDisplayNamesObject(JSContext
* cx
, HandleObject obj
,
113 Handle
<PropertyName
*> initializer
,
116 DisplayNamesOptions dnoptions
) {
117 FixedInvokeArgs
<4> args(cx
);
119 args
[0].setObject(*obj
);
120 args
[1].set(locales
);
121 args
[2].set(options
);
122 args
[3].setBoolean(dnoptions
== DisplayNamesOptions::EnableMozExtensions
);
124 RootedValue
ignored(cx
);
125 if (!CallSelfHostedFunction(cx
, initializer
, NullHandleValue
, args
,
130 MOZ_ASSERT(ignored
.isUndefined(),
131 "Unexpected return value from non-legacy Intl object initializer");
136 * Intl.DisplayNames ([ locales [ , options ]])
138 static bool DisplayNames(JSContext
* cx
, const CallArgs
& args
,
139 DisplayNamesOptions dnoptions
) {
141 if (!ThrowIfNotConstructing(cx
, args
, "Intl.DisplayNames")) {
145 // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
146 RootedObject
proto(cx
);
147 if (dnoptions
== DisplayNamesOptions::Standard
) {
148 if (!GetPrototypeFromBuiltinConstructor(cx
, args
, JSProto_DisplayNames
,
153 RootedObject
newTarget(cx
, &args
.newTarget().toObject());
154 if (!GetPrototypeFromConstructor(cx
, newTarget
, JSProto_Null
, &proto
)) {
159 Rooted
<DisplayNamesObject
*> displayNames(cx
);
160 displayNames
= NewObjectWithClassProto
<DisplayNamesObject
>(cx
, proto
);
165 HandleValue locales
= args
.get(0);
166 HandleValue options
= args
.get(1);
169 if (!InitializeDisplayNamesObject(cx
, displayNames
,
170 cx
->names().InitializeDisplayNames
, locales
,
171 options
, dnoptions
)) {
176 args
.rval().setObject(*displayNames
);
180 static bool DisplayNames(JSContext
* cx
, unsigned argc
, Value
* vp
) {
181 CallArgs args
= CallArgsFromVp(argc
, vp
);
182 return DisplayNames(cx
, args
, DisplayNamesOptions::Standard
);
185 static bool MozDisplayNames(JSContext
* cx
, unsigned argc
, Value
* vp
) {
186 CallArgs args
= CallArgsFromVp(argc
, vp
);
187 return DisplayNames(cx
, args
, DisplayNamesOptions::EnableMozExtensions
);
190 void js::DisplayNamesObject::finalize(JS::GCContext
* gcx
, JSObject
* obj
) {
191 MOZ_ASSERT(gcx
->onMainThread());
193 if (mozilla::intl::DisplayNames
* displayNames
=
194 obj
->as
<DisplayNamesObject
>().getDisplayNames()) {
195 intl::RemoveICUCellMemory(gcx
, obj
, DisplayNamesObject::EstimatedMemoryUse
);
200 bool JS::AddMozDisplayNamesConstructor(JSContext
* cx
, HandleObject intl
) {
201 RootedObject
ctor(cx
, GlobalObject::createConstructor(
202 cx
, MozDisplayNames
, cx
->names().DisplayNames
, 2));
208 cx
, GlobalObject::createBlankPrototype
<PlainObject
>(cx
, cx
->global()));
213 if (!LinkConstructorAndPrototype(cx
, ctor
, proto
)) {
217 if (!JS_DefineFunctions(cx
, ctor
, displayNames_static_methods
)) {
221 if (!JS_DefineFunctions(cx
, proto
, displayNames_methods
)) {
225 if (!JS_DefineProperties(cx
, proto
, displayNames_properties
)) {
229 RootedValue
ctorValue(cx
, ObjectValue(*ctor
));
230 return DefineDataProperty(cx
, intl
, cx
->names().DisplayNames
, ctorValue
, 0);
233 static mozilla::intl::DisplayNames
* NewDisplayNames(
234 JSContext
* cx
, const char* locale
,
235 mozilla::intl::DisplayNames::Options
& options
) {
236 auto result
= mozilla::intl::DisplayNames::TryCreate(locale
, options
);
237 if (result
.isErr()) {
238 intl::ReportInternalError(cx
, result
.unwrapErr());
241 return result
.unwrap().release();
244 static mozilla::intl::DisplayNames
* GetOrCreateDisplayNames(
245 JSContext
* cx
, Handle
<DisplayNamesObject
*> displayNames
, const char* locale
,
246 mozilla::intl::DisplayNames::Options
& options
) {
247 // Obtain a cached mozilla::intl::DisplayNames object.
248 mozilla::intl::DisplayNames
* dn
= displayNames
->getDisplayNames();
250 dn
= NewDisplayNames(cx
, locale
, options
);
254 displayNames
->setDisplayNames(dn
);
256 intl::AddICUCellMemory(displayNames
,
257 DisplayNamesObject::EstimatedMemoryUse
);
262 static void ReportInvalidOptionError(JSContext
* cx
, HandleString type
,
263 HandleString option
) {
264 if (UniqueChars optionStr
= QuoteString(cx
, option
, '"')) {
265 if (UniqueChars typeStr
= QuoteString(cx
, type
)) {
266 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
267 JSMSG_INVALID_OPTION_VALUE
, typeStr
.get(),
273 static void ReportInvalidOptionError(JSContext
* cx
, const char* type
,
274 HandleString option
) {
275 if (UniqueChars str
= QuoteString(cx
, option
, '"')) {
276 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
277 JSMSG_INVALID_OPTION_VALUE
, type
, str
.get());
281 static void ReportInvalidOptionError(JSContext
* cx
, const char* type
,
284 const char* str
= NumberToCString(&cbuf
, option
);
286 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
287 JSMSG_INVALID_DIGITS_VALUE
, str
);
291 * intl_ComputeDisplayName(displayNames, locale, calendar, style,
292 * languageDisplay, fallback, type, code)
294 bool js::intl_ComputeDisplayName(JSContext
* cx
, unsigned argc
, Value
* vp
) {
295 CallArgs args
= CallArgsFromVp(argc
, vp
);
296 MOZ_ASSERT(args
.length() == 8);
298 Rooted
<DisplayNamesObject
*> displayNames(
299 cx
, &args
[0].toObject().as
<DisplayNamesObject
>());
301 UniqueChars locale
= intl::EncodeLocale(cx
, args
[1].toString());
306 Rooted
<JSLinearString
*> calendar(cx
, args
[2].toString()->ensureLinear(cx
));
311 Rooted
<JSLinearString
*> code(cx
, args
[7].toString()->ensureLinear(cx
));
316 mozilla::intl::DisplayNames::Style style
;
318 JSLinearString
* styleStr
= args
[3].toString()->ensureLinear(cx
);
323 if (StringEqualsLiteral(styleStr
, "long")) {
324 style
= mozilla::intl::DisplayNames::Style::Long
;
325 } else if (StringEqualsLiteral(styleStr
, "short")) {
326 style
= mozilla::intl::DisplayNames::Style::Short
;
327 } else if (StringEqualsLiteral(styleStr
, "narrow")) {
328 style
= mozilla::intl::DisplayNames::Style::Narrow
;
330 MOZ_ASSERT(StringEqualsLiteral(styleStr
, "abbreviated"));
331 style
= mozilla::intl::DisplayNames::Style::Abbreviated
;
335 mozilla::intl::DisplayNames::LanguageDisplay languageDisplay
;
337 JSLinearString
* language
= args
[4].toString()->ensureLinear(cx
);
342 if (StringEqualsLiteral(language
, "dialect")) {
343 languageDisplay
= mozilla::intl::DisplayNames::LanguageDisplay::Dialect
;
345 MOZ_ASSERT(language
->empty() ||
346 StringEqualsLiteral(language
, "standard"));
347 languageDisplay
= mozilla::intl::DisplayNames::LanguageDisplay::Standard
;
351 mozilla::intl::DisplayNames::Fallback fallback
;
353 JSLinearString
* fallbackStr
= args
[5].toString()->ensureLinear(cx
);
358 if (StringEqualsLiteral(fallbackStr
, "none")) {
359 fallback
= mozilla::intl::DisplayNames::Fallback::None
;
361 MOZ_ASSERT(StringEqualsLiteral(fallbackStr
, "code"));
362 fallback
= mozilla::intl::DisplayNames::Fallback::Code
;
366 Rooted
<JSLinearString
*> type(cx
, args
[6].toString()->ensureLinear(cx
));
371 mozilla::intl::DisplayNames::Options options
{
376 // If a calendar exists, set it as an option.
377 JS::UniqueChars calendarChars
= nullptr;
378 if (!calendar
->empty()) {
379 calendarChars
= JS_EncodeStringToUTF8(cx
, calendar
);
380 if (!calendarChars
) {
385 mozilla::intl::DisplayNames
* dn
=
386 GetOrCreateDisplayNames(cx
, displayNames
, locale
.get(), options
);
391 // The "code" is usually a small ASCII string, so try to avoid an allocation
392 // by copying it to the stack. Unfortunately we can't pass a string span of
393 // the JSString directly to the unified DisplayNames API, as the
394 // intl::FormatBuffer will be written to. This writing can trigger a GC and
395 // invalidate the span, creating a nogc rooting hazard.
396 JS::UniqueChars utf8
= nullptr;
397 unsigned char ascii
[32];
398 mozilla::Span
<const char> codeSpan
= nullptr;
399 if (code
->length() < 32 && code
->hasLatin1Chars() && StringIsAscii(code
)) {
400 JS::AutoCheckCannotGC nogc
;
401 mozilla::PodCopy(ascii
, code
->latin1Chars(nogc
), code
->length());
403 mozilla::Span(reinterpret_cast<const char*>(ascii
), code
->length());
405 utf8
= JS_EncodeStringToUTF8(cx
, code
);
409 codeSpan
= mozilla::MakeStringSpan(utf8
.get());
412 intl::FormatBuffer
<char16_t
, intl::INITIAL_CHAR_BUFFER_SIZE
> buffer(cx
);
413 mozilla::Result
<mozilla::Ok
, mozilla::intl::DisplayNamesError
> result
=
416 if (StringEqualsLiteral(type
, "language")) {
417 result
= dn
->GetLanguage(buffer
, codeSpan
, fallback
);
418 } else if (StringEqualsLiteral(type
, "script")) {
419 result
= dn
->GetScript(buffer
, codeSpan
, fallback
);
420 } else if (StringEqualsLiteral(type
, "region")) {
421 result
= dn
->GetRegion(buffer
, codeSpan
, fallback
);
422 } else if (StringEqualsLiteral(type
, "currency")) {
423 result
= dn
->GetCurrency(buffer
, codeSpan
, fallback
);
424 } else if (StringEqualsLiteral(type
, "calendar")) {
425 result
= dn
->GetCalendar(buffer
, codeSpan
, fallback
);
426 } else if (StringEqualsLiteral(type
, "weekday")) {
427 double d
= LinearStringToNumber(code
);
428 if (!IsInteger(d
) || d
< 1 || d
> 7) {
429 ReportInvalidOptionError(cx
, "weekday", d
);
433 dn
->GetWeekday(buffer
, static_cast<mozilla::intl::Weekday
>(d
),
434 mozilla::MakeStringSpan(calendarChars
.get()), fallback
);
435 } else if (StringEqualsLiteral(type
, "month")) {
436 double d
= LinearStringToNumber(code
);
437 if (!IsInteger(d
) || d
< 1 || d
> 13) {
438 ReportInvalidOptionError(cx
, "month", d
);
443 dn
->GetMonth(buffer
, static_cast<mozilla::intl::Month
>(d
),
444 mozilla::MakeStringSpan(calendarChars
.get()), fallback
);
446 } else if (StringEqualsLiteral(type
, "quarter")) {
447 double d
= LinearStringToNumber(code
);
449 // Inlined implementation of `IsValidQuarterCode ( quarter )`.
450 if (!IsInteger(d
) || d
< 1 || d
> 4) {
451 ReportInvalidOptionError(cx
, "quarter", d
);
456 dn
->GetQuarter(buffer
, static_cast<mozilla::intl::Quarter
>(d
),
457 mozilla::MakeStringSpan(calendarChars
.get()), fallback
);
459 } else if (StringEqualsLiteral(type
, "dayPeriod")) {
460 mozilla::intl::DayPeriod dayPeriod
;
461 if (StringEqualsLiteral(code
, "am")) {
462 dayPeriod
= mozilla::intl::DayPeriod::AM
;
463 } else if (StringEqualsLiteral(code
, "pm")) {
464 dayPeriod
= mozilla::intl::DayPeriod::PM
;
466 ReportInvalidOptionError(cx
, "dayPeriod", code
);
469 result
= dn
->GetDayPeriod(buffer
, dayPeriod
,
470 mozilla::MakeStringSpan(calendarChars
.get()),
474 MOZ_ASSERT(StringEqualsLiteral(type
, "dateTimeField"));
475 mozilla::intl::DateTimeField field
;
476 if (StringEqualsLiteral(code
, "era")) {
477 field
= mozilla::intl::DateTimeField::Era
;
478 } else if (StringEqualsLiteral(code
, "year")) {
479 field
= mozilla::intl::DateTimeField::Year
;
480 } else if (StringEqualsLiteral(code
, "quarter")) {
481 field
= mozilla::intl::DateTimeField::Quarter
;
482 } else if (StringEqualsLiteral(code
, "month")) {
483 field
= mozilla::intl::DateTimeField::Month
;
484 } else if (StringEqualsLiteral(code
, "weekOfYear")) {
485 field
= mozilla::intl::DateTimeField::WeekOfYear
;
486 } else if (StringEqualsLiteral(code
, "weekday")) {
487 field
= mozilla::intl::DateTimeField::Weekday
;
488 } else if (StringEqualsLiteral(code
, "day")) {
489 field
= mozilla::intl::DateTimeField::Day
;
490 } else if (StringEqualsLiteral(code
, "dayPeriod")) {
491 field
= mozilla::intl::DateTimeField::DayPeriod
;
492 } else if (StringEqualsLiteral(code
, "hour")) {
493 field
= mozilla::intl::DateTimeField::Hour
;
494 } else if (StringEqualsLiteral(code
, "minute")) {
495 field
= mozilla::intl::DateTimeField::Minute
;
496 } else if (StringEqualsLiteral(code
, "second")) {
497 field
= mozilla::intl::DateTimeField::Second
;
498 } else if (StringEqualsLiteral(code
, "timeZoneName")) {
499 field
= mozilla::intl::DateTimeField::TimeZoneName
;
501 ReportInvalidOptionError(cx
, "dateTimeField", code
);
505 intl::SharedIntlData
& sharedIntlData
= cx
->runtime()->sharedIntlData
.ref();
506 mozilla::intl::DateTimePatternGenerator
* dtpgen
=
507 sharedIntlData
.getDateTimePatternGenerator(cx
, locale
.get());
512 result
= dn
->GetDateTimeField(buffer
, field
, *dtpgen
, fallback
);
515 if (result
.isErr()) {
516 switch (result
.unwrapErr()) {
517 case mozilla::intl::DisplayNamesError::InternalError
:
518 intl::ReportInternalError(cx
);
520 case mozilla::intl::DisplayNamesError::OutOfMemory
:
521 ReportOutOfMemory(cx
);
523 case mozilla::intl::DisplayNamesError::InvalidOption
:
524 ReportInvalidOptionError(cx
, type
, code
);
526 case mozilla::intl::DisplayNamesError::DuplicateVariantSubtag
:
527 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
528 JSMSG_DUPLICATE_VARIANT_SUBTAG
);
530 case mozilla::intl::DisplayNamesError::InvalidLanguageTag
:
531 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
532 JSMSG_INVALID_LANGUAGE_TAG
);
538 JSString
* str
= buffer
.toString(cx
);
544 args
.rval().setUndefined();
546 args
.rval().setString(str
);