Bug 1885489 - Part 5: Add SnapshotIterator::readInt32(). r=iain
[gecko.git] / js / src / builtin / intl / DisplayNames.cpp
blobd375be58f3d381e3f0f13efc89872a57499fa8dc
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"
16 #include <algorithm>
18 #include "jsnum.h"
19 #include "jspubtd.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"
26 #include "js/Class.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"
41 #include "vm/Stack.h"
42 #include "vm/StringType.h"
44 #include "vm/JSObject-inl.h"
45 #include "vm/NativeObject-inl.h"
47 using namespace js;
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_ = {
58 "Intl.DisplayNames",
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);
69 return true;
72 static const JSFunctionSpec displayNames_static_methods[] = {
73 JS_SELF_HOSTED_FN("supportedLocalesOf",
74 "Intl_DisplayNames_supportedLocalesOf", 1, 0),
75 JS_FS_END};
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,
80 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),
85 JS_PS_END};
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,
93 nullptr,
94 displayNames_methods,
95 displayNames_properties,
96 nullptr,
97 ClassSpec::DontDefineConstructor};
99 enum class DisplayNamesOptions {
100 Standard,
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.
105 EnableMozExtensions,
109 * Initialize a new Intl.DisplayNames object using the named self-hosted
110 * function.
112 static bool InitializeDisplayNamesObject(JSContext* cx, HandleObject obj,
113 Handle<PropertyName*> initializer,
114 HandleValue locales,
115 HandleValue options,
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,
126 &ignored)) {
127 return false;
130 MOZ_ASSERT(ignored.isUndefined(),
131 "Unexpected return value from non-legacy Intl object initializer");
132 return true;
136 * Intl.DisplayNames ([ locales [ , options ]])
138 static bool DisplayNames(JSContext* cx, const CallArgs& args,
139 DisplayNamesOptions dnoptions) {
140 // Step 1.
141 if (!ThrowIfNotConstructing(cx, args, "Intl.DisplayNames")) {
142 return false;
145 // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
146 RootedObject proto(cx);
147 if (dnoptions == DisplayNamesOptions::Standard) {
148 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_DisplayNames,
149 &proto)) {
150 return false;
152 } else {
153 RootedObject newTarget(cx, &args.newTarget().toObject());
154 if (!GetPrototypeFromConstructor(cx, newTarget, JSProto_Null, &proto)) {
155 return false;
159 Rooted<DisplayNamesObject*> displayNames(cx);
160 displayNames = NewObjectWithClassProto<DisplayNamesObject>(cx, proto);
161 if (!displayNames) {
162 return false;
165 HandleValue locales = args.get(0);
166 HandleValue options = args.get(1);
168 // Steps 3-26.
169 if (!InitializeDisplayNamesObject(cx, displayNames,
170 cx->names().InitializeDisplayNames, locales,
171 options, dnoptions)) {
172 return false;
175 // Step 27.
176 args.rval().setObject(*displayNames);
177 return true;
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);
196 delete displayNames;
200 bool JS::AddMozDisplayNamesConstructor(JSContext* cx, HandleObject intl) {
201 RootedObject ctor(cx, GlobalObject::createConstructor(
202 cx, MozDisplayNames, cx->names().DisplayNames, 2));
203 if (!ctor) {
204 return false;
207 RootedObject proto(
208 cx, GlobalObject::createBlankPrototype<PlainObject>(cx, cx->global()));
209 if (!proto) {
210 return false;
213 if (!LinkConstructorAndPrototype(cx, ctor, proto)) {
214 return false;
217 if (!JS_DefineFunctions(cx, ctor, displayNames_static_methods)) {
218 return false;
221 if (!JS_DefineFunctions(cx, proto, displayNames_methods)) {
222 return false;
225 if (!JS_DefineProperties(cx, proto, displayNames_properties)) {
226 return false;
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());
239 return nullptr;
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();
249 if (!dn) {
250 dn = NewDisplayNames(cx, locale, options);
251 if (!dn) {
252 return nullptr;
254 displayNames->setDisplayNames(dn);
256 intl::AddICUCellMemory(displayNames,
257 DisplayNamesObject::EstimatedMemoryUse);
259 return dn;
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(),
268 optionStr.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,
282 double option) {
283 ToCStringBuf cbuf;
284 const char* str = NumberToCString(&cbuf, option);
285 MOZ_ASSERT(str);
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());
302 if (!locale) {
303 return false;
306 Rooted<JSLinearString*> calendar(cx, args[2].toString()->ensureLinear(cx));
307 if (!calendar) {
308 return false;
311 Rooted<JSLinearString*> code(cx, args[7].toString()->ensureLinear(cx));
312 if (!code) {
313 return false;
316 mozilla::intl::DisplayNames::Style style;
318 JSLinearString* styleStr = args[3].toString()->ensureLinear(cx);
319 if (!styleStr) {
320 return false;
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;
329 } else {
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);
338 if (!language) {
339 return false;
342 if (StringEqualsLiteral(language, "dialect")) {
343 languageDisplay = mozilla::intl::DisplayNames::LanguageDisplay::Dialect;
344 } else {
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);
354 if (!fallbackStr) {
355 return false;
358 if (StringEqualsLiteral(fallbackStr, "none")) {
359 fallback = mozilla::intl::DisplayNames::Fallback::None;
360 } else {
361 MOZ_ASSERT(StringEqualsLiteral(fallbackStr, "code"));
362 fallback = mozilla::intl::DisplayNames::Fallback::Code;
366 Rooted<JSLinearString*> type(cx, args[6].toString()->ensureLinear(cx));
367 if (!type) {
368 return false;
371 mozilla::intl::DisplayNames::Options options{
372 style,
373 languageDisplay,
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) {
381 return false;
385 mozilla::intl::DisplayNames* dn =
386 GetOrCreateDisplayNames(cx, displayNames, locale.get(), options);
387 if (!dn) {
388 return false;
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());
402 codeSpan =
403 mozilla::Span(reinterpret_cast<const char*>(ascii), code->length());
404 } else {
405 utf8 = JS_EncodeStringToUTF8(cx, code);
406 if (!utf8) {
407 return false;
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 =
414 mozilla::Ok{};
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);
430 return false;
432 result =
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);
439 return false;
442 result =
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);
452 return false;
455 result =
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;
465 } else {
466 ReportInvalidOptionError(cx, "dayPeriod", code);
467 return false;
469 result = dn->GetDayPeriod(buffer, dayPeriod,
470 mozilla::MakeStringSpan(calendarChars.get()),
471 fallback);
473 } else {
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;
500 } else {
501 ReportInvalidOptionError(cx, "dateTimeField", code);
502 return false;
505 intl::SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref();
506 mozilla::intl::DateTimePatternGenerator* dtpgen =
507 sharedIntlData.getDateTimePatternGenerator(cx, locale.get());
508 if (!dtpgen) {
509 return false;
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);
519 break;
520 case mozilla::intl::DisplayNamesError::OutOfMemory:
521 ReportOutOfMemory(cx);
522 break;
523 case mozilla::intl::DisplayNamesError::InvalidOption:
524 ReportInvalidOptionError(cx, type, code);
525 break;
526 case mozilla::intl::DisplayNamesError::DuplicateVariantSubtag:
527 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
528 JSMSG_DUPLICATE_VARIANT_SUBTAG);
529 break;
530 case mozilla::intl::DisplayNamesError::InvalidLanguageTag:
531 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
532 JSMSG_INVALID_LANGUAGE_TAG);
533 break;
535 return false;
538 JSString* str = buffer.toString(cx);
539 if (!str) {
540 return false;
543 if (str->empty()) {
544 args.rval().setUndefined();
545 } else {
546 args.rval().setString(str);
549 return true;