/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* Intl.DisplayNames implementation. */ #include "builtin/intl/DisplayNames.h" #include "mozilla/Assertions.h" #include "mozilla/intl/DisplayNames.h" #include "mozilla/Span.h" #include "jspubtd.h" #include "builtin/intl/CommonFunctions.h" #include "builtin/intl/FormatBuffer.h" #include "builtin/intl/LocaleNegotiation.h" #include "builtin/intl/ParameterNegotiation.h" #include "builtin/intl/UsingEnum.h" #include "builtin/Number.h" #include "gc/AllocKind.h" #include "gc/GCContext.h" #include "js/CallArgs.h" #include "js/Class.h" #include "js/experimental/Intl.h" // JS::AddMozDisplayNamesConstructor #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* #include "js/Printer.h" #include "js/PropertyAndElement.h" // JS_DefineFunctions, JS_DefineProperties #include "js/PropertyDescriptor.h" #include "js/PropertySpec.h" #include "js/RootingAPI.h" #include "js/TypeDecls.h" #include "js/Utility.h" #include "vm/GlobalObject.h" #include "vm/JSContext.h" #include "vm/JSObject.h" #include "vm/Runtime.h" #include "vm/SelfHosting.h" #include "vm/Stack.h" #include "vm/StringType.h" #include "vm/JSObject-inl.h" #include "vm/NativeObject-inl.h" using namespace js; using namespace js::intl; const JSClassOps DisplayNamesObject::classOps_ = { nullptr, /* addProperty */ nullptr, /* delProperty */ nullptr, /* enumerate */ nullptr, /* newEnumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ DisplayNamesObject::finalize, }; const JSClass DisplayNamesObject::class_ = { "Intl.DisplayNames", JSCLASS_HAS_RESERVED_SLOTS(DisplayNamesObject::SLOT_COUNT) | JSCLASS_HAS_CACHED_PROTO(JSProto_DisplayNames) | JSCLASS_BACKGROUND_FINALIZE, &DisplayNamesObject::classOps_, &DisplayNamesObject::classSpec_, }; const JSClass& DisplayNamesObject::protoClass_ = PlainObject::class_; static bool displayNames_supportedLocalesOf(JSContext* cx, unsigned argc, Value* vp); static bool displayNames_of(JSContext* cx, unsigned argc, Value* vp); static bool displayNames_resolvedOptions(JSContext* cx, unsigned argc, Value* vp); static bool displayNames_toSource(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setString(cx->names().DisplayNames); return true; } static const JSFunctionSpec displayNames_static_methods[] = { JS_FN("supportedLocalesOf", displayNames_supportedLocalesOf, 1, 0), JS_FS_END, }; static const JSFunctionSpec displayNames_methods[] = { JS_FN("of", displayNames_of, 1, 0), JS_FN("resolvedOptions", displayNames_resolvedOptions, 0, 0), JS_FN("toSource", displayNames_toSource, 0, 0), JS_FS_END, }; static const JSPropertySpec displayNames_properties[] = { JS_STRING_SYM_PS(toStringTag, "Intl.DisplayNames", JSPROP_READONLY), JS_PS_END, }; static bool DisplayNames(JSContext* cx, unsigned argc, Value* vp); const ClassSpec DisplayNamesObject::classSpec_ = { GenericCreateConstructor, GenericCreatePrototype, displayNames_static_methods, nullptr, displayNames_methods, displayNames_properties, nullptr, ClassSpec::DontDefineConstructor, }; static constexpr std::string_view StyleToString( DisplayNamesOptions::Style style) { #ifndef USING_ENUM using enum DisplayNamesOptions::Style; #else USING_ENUM(DisplayNamesOptions::Style, Long, Short, Narrow, Abbreviated); #endif switch (style) { case Long: return "long"; case Short: return "short"; case Narrow: return "narrow"; case Abbreviated: return "abbreviated"; } MOZ_CRASH("invalid display names style"); } static constexpr std::string_view TypeToString(DisplayNamesOptions::Type type) { #ifndef USING_ENUM using enum DisplayNamesOptions::Type; #else USING_ENUM(DisplayNamesOptions::Type, Language, Region, Script, Currency, Calendar, DateTimeField, Weekday, Month, Quarter, DayPeriod); #endif switch (type) { case Language: return "language"; case Region: return "region"; case Script: return "script"; case Currency: return "currency"; case Calendar: return "calendar"; case DateTimeField: return "dateTimeField"; case Weekday: return "weekday"; case Month: return "month"; case Quarter: return "quarter"; case DayPeriod: return "dayPeriod"; } MOZ_CRASH("invalid display names type"); } static constexpr std::string_view FallbackToString( DisplayNamesOptions::Fallback fallback) { #ifndef USING_ENUM using enum DisplayNamesOptions::Fallback; #else USING_ENUM(DisplayNamesOptions::Fallback, Code, None); #endif switch (fallback) { case Code: return "code"; case None: return "none"; } MOZ_CRASH("invalid display names fallback"); } static constexpr std::string_view LanguageDisplayToString( DisplayNamesOptions::LanguageDisplay languageDisplay) { #ifndef USING_ENUM using enum DisplayNamesOptions::LanguageDisplay; #else USING_ENUM(DisplayNamesOptions::LanguageDisplay, Dialect, Standard); #endif switch (languageDisplay) { case Dialect: return "dialect"; case Standard: return "standard"; } MOZ_CRASH("invalid display names language display"); } enum class DisplayNamesKind { Standard, // Calendar display names are no longer available with the current spec // proposal text, but may be re-enabled in the future. For our internal use // we still need to have them present, so use a feature guard for now. EnableMozExtensions, }; /** * Intl.DisplayNames ( locales, options ) */ static bool DisplayNames(JSContext* cx, const CallArgs& args, DisplayNamesKind kind) { // Step 1. if (!ThrowIfNotConstructing(cx, args, "Intl.DisplayNames")) { return false; } // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor). Rooted proto(cx); if (kind == DisplayNamesKind::Standard) { if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_DisplayNames, &proto)) { return false; } } else { Rooted newTarget(cx, &args.newTarget().toObject()); if (!GetPrototypeFromConstructor(cx, newTarget, JSProto_Null, &proto)) { return false; } } Rooted displayNames(cx); displayNames = NewObjectWithClassProto(cx, proto); if (!displayNames) { return false; } // Step 3. (Inlined ResolveOptions) // ResolveOptions, step 1. Rooted requestedLocales(cx, cx); if (!CanonicalizeLocaleList(cx, args.get(0), &requestedLocales)) { return false; } Rooted requestedLocalesArray( cx, LocalesListToArray(cx, requestedLocales)); if (!requestedLocalesArray) { return false; } displayNames->setRequestedLocales(requestedLocalesArray); auto dnOptions = cx->make_unique(); if (!dnOptions) { return false; } dnOptions->mozExtensions = kind == DisplayNamesKind::EnableMozExtensions; // ResolveOptions, steps 2-3. Rooted options( cx, RequireObjectArg(cx, "options", "Intl.DisplayNames", args.get(1))); if (!options) { return false; } // ResolveOptions, step 4. LocaleMatcher matcher; if (!GetLocaleMatcherOption(cx, options, &matcher)) { return false; } // ResolveOptions, step 5. // // This implementation only supports the "lookup" locale matcher, therefore // the "localeMatcher" option doesn't need to be stored. // ResolveOptions, step 6. if (kind == DisplayNamesKind::EnableMozExtensions) { Rooted calendar(cx); if (!GetUnicodeExtensionOption(cx, options, UnicodeExtensionKey::Calendar, &calendar)) { return false; } if (calendar) { displayNames->setCalendar(calendar); } } // ResolveOptions, step 7. (Not applicable) // ResolveOptions, step 8. (Performed in ResolveLocale) // ResolveOptions, step 9. (Return) // Step 4. (Not applicable when ResolveOptions is inlined.) // Step 5. (Performed in ResolveRelativeTimeFormat) // Steps 6-7. if (kind == DisplayNamesKind::Standard) { static constexpr auto styles = MapOptions( DisplayNamesOptions::Style::Long, DisplayNamesOptions::Style::Short, DisplayNamesOptions::Style::Narrow); if (!GetStringOption(cx, options, cx->names().style, styles, DisplayNamesOptions::Style::Long, &dnOptions->style)) { return false; } } else { static constexpr auto styles = MapOptions( DisplayNamesOptions::Style::Long, DisplayNamesOptions::Style::Short, DisplayNamesOptions::Style::Narrow, DisplayNamesOptions::Style::Abbreviated); if (!GetStringOption(cx, options, cx->names().style, styles, DisplayNamesOptions::Style::Long, &dnOptions->style)) { return false; } } // Step 8. mozilla::Maybe type{}; if (kind == DisplayNamesKind::EnableMozExtensions) { static constexpr auto types = MapOptions( DisplayNamesOptions::Type::Language, DisplayNamesOptions::Type::Region, DisplayNamesOptions::Type::Script, DisplayNamesOptions::Type::Currency, DisplayNamesOptions::Type::Calendar, DisplayNamesOptions::Type::DateTimeField, DisplayNamesOptions::Type::Weekday, DisplayNamesOptions::Type::Month, DisplayNamesOptions::Type::Quarter, DisplayNamesOptions::Type::DayPeriod); if (!GetStringOption(cx, options, cx->names().type, types, &type)) { return false; } } else { static constexpr auto types = MapOptions( DisplayNamesOptions::Type::Language, DisplayNamesOptions::Type::Region, DisplayNamesOptions::Type::Script, DisplayNamesOptions::Type::Currency, DisplayNamesOptions::Type::Calendar, DisplayNamesOptions::Type::DateTimeField); if (!GetStringOption(cx, options, cx->names().type, types, &type)) { return false; } } // Step 9. if (type.isNothing()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_UNDEFINED_TYPE); return false; } // Step 10 dnOptions->type = *type; // Steps 11-12. static constexpr auto fallbacks = MapOptions( DisplayNamesOptions::Fallback::Code, DisplayNamesOptions::Fallback::None); if (!GetStringOption(cx, options, cx->names().fallback, fallbacks, DisplayNamesOptions::Fallback::Code, &dnOptions->fallback)) { return false; } // Steps 13-14. (Performed in ResolveDisplayNames) // Steps 17 and 20.a. static constexpr auto languageDisplays = MapOptions( DisplayNamesOptions::LanguageDisplay::Dialect, DisplayNamesOptions::LanguageDisplay::Standard); if (!GetStringOption(cx, options, cx->names().languageDisplay, languageDisplays, DisplayNamesOptions::LanguageDisplay::Dialect, &dnOptions->languageDisplay)) { return false; } // Assign the options to |displayNames|. displayNames->setOptions(dnOptions.release()); AddCellMemory(displayNames, sizeof(DisplayNamesOptions), MemoryUse::IntlOptions); // Steps 15-16, 18-19, 20.b-c, and 21-23. (Not applicable) // Step 24. args.rval().setObject(*displayNames); return true; } static bool DisplayNames(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return DisplayNames(cx, args, DisplayNamesKind::Standard); } static bool MozDisplayNames(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return DisplayNames(cx, args, DisplayNamesKind::EnableMozExtensions); } void js::intl::DisplayNamesObject::finalize(JS::GCContext* gcx, JSObject* obj) { auto* dn = &obj->as(); if (auto* options = dn->getOptions()) { gcx->delete_(obj, options, MemoryUse::IntlOptions); } if (auto* displayNames = dn->getDisplayNames()) { RemoveICUCellMemory(gcx, obj, DisplayNamesObject::EstimatedMemoryUse); delete displayNames; } } bool JS::AddMozDisplayNamesConstructor(JSContext* cx, Handle intl) { Rooted ctor( cx, GlobalObject::createConstructor(cx, MozDisplayNames, cx->names().DisplayNames, 2)); if (!ctor) { return false; } Rooted proto( cx, GlobalObject::createBlankPrototype(cx, cx->global())); if (!proto) { return false; } if (!LinkConstructorAndPrototype(cx, ctor, proto)) { return false; } if (!JS_DefineFunctions(cx, ctor, displayNames_static_methods)) { return false; } if (!JS_DefineFunctions(cx, proto, displayNames_methods)) { return false; } if (!JS_DefineProperties(cx, proto, displayNames_properties)) { return false; } Rooted ctorValue(cx, ObjectValue(*ctor)); return DefineDataProperty(cx, intl, cx->names().DisplayNames, ctorValue, 0); } /** * Resolve the actual locale to finish initialization of the DisplayNames. */ static bool ResolveLocale(JSContext* cx, Handle displayNames) { // Return if the locale was already resolved. if (displayNames->isLocaleResolved()) { return true; } bool mozExtensions = displayNames->getOptions()->mozExtensions; Rooted requestedLocales( cx, &displayNames->getRequestedLocales()->as()); // %Intl.DisplayNames%.[[RelevantExtensionKeys]] is « ». mozilla::EnumSet relevantExtensionKeys{}; // MozDisplayNames supports the "ca" Unicode extension. if (mozExtensions) { relevantExtensionKeys += UnicodeExtensionKey::Calendar; } // Initialize locale options from constructor arguments. Rooted localeOptions(cx); if (mozExtensions) { if (auto* ca = displayNames->getCalendar()) { localeOptions.setUnicodeExtension(UnicodeExtensionKey::Calendar, ca); } } // Use the default locale data. auto localeData = LocaleData::Default; // Resolve the actual locale. Rooted resolved(cx); if (!ResolveLocale(cx, AvailableLocaleKind::DisplayNames, requestedLocales, localeOptions, relevantExtensionKeys, localeData, &resolved)) { return false; } // Finish initialization by setting the actual locale and calendar. auto* locale = resolved.toLocale(cx); if (!locale) { return false; } displayNames->setLocale(locale); if (mozExtensions) { auto ca = resolved.extension(UnicodeExtensionKey::Calendar); MOZ_ASSERT(ca, "resolved calendar is non-null"); displayNames->setCalendar(ca); } MOZ_ASSERT(displayNames->isLocaleResolved(), "locale successfully resolved"); return true; } static auto ToDisplayNamesStyle(DisplayNamesOptions::Style style) { #ifndef USING_ENUM using enum mozilla::intl::DisplayNames::Style; #else USING_ENUM(mozilla::intl::DisplayNames::Style, Long, Short, Narrow, Abbreviated); #endif switch (style) { case DisplayNamesOptions::Style::Long: return Long; case DisplayNamesOptions::Style::Short: return Short; case DisplayNamesOptions::Style::Narrow: return Narrow; case DisplayNamesOptions::Style::Abbreviated: return Abbreviated; } MOZ_CRASH("invalid display names style"); } static auto ToDisplayNamesLanguageDisplay( DisplayNamesOptions::LanguageDisplay languageDisplay) { #ifndef USING_ENUM using enum mozilla::intl::DisplayNames::LanguageDisplay; #else USING_ENUM(mozilla::intl::DisplayNames::LanguageDisplay, Dialect, Standard); #endif switch (languageDisplay) { case DisplayNamesOptions::LanguageDisplay::Dialect: return Dialect; case DisplayNamesOptions::LanguageDisplay::Standard: return Standard; } MOZ_CRASH("invalid display names language display"); } static auto ToDisplayNamesFallback(DisplayNamesOptions::Fallback fallback) { #ifndef USING_ENUM using enum mozilla::intl::DisplayNames::Fallback; #else USING_ENUM(mozilla::intl::DisplayNames::Fallback, Code, None); #endif switch (fallback) { case DisplayNamesOptions::Fallback::Code: return Code; case DisplayNamesOptions::Fallback::None: return None; } MOZ_CRASH("invalid display names fallback"); } static mozilla::intl::DisplayNames* NewDisplayNames( JSContext* cx, Handle displayNames) { if (!ResolveLocale(cx, displayNames)) { return nullptr; } auto dnOptions = *displayNames->getOptions(); auto locale = EncodeLocale(cx, displayNames->getLocale()); if (!locale) { return nullptr; } mozilla::intl::DisplayNames::Options options = { .style = ToDisplayNamesStyle(dnOptions.style), .languageDisplay = ToDisplayNamesLanguageDisplay(dnOptions.languageDisplay), }; auto result = mozilla::intl::DisplayNames::TryCreate(locale.get(), options); if (result.isErr()) { ReportInternalError(cx, result.unwrapErr()); return nullptr; } return result.unwrap().release(); } static mozilla::intl::DisplayNames* GetOrCreateDisplayNames( JSContext* cx, Handle displayNames) { // Obtain a cached mozilla::intl::DisplayNames object. if (auto* dn = displayNames->getDisplayNames()) { return dn; } auto* dn = NewDisplayNames(cx, displayNames); if (!dn) { return nullptr; } displayNames->setDisplayNames(dn); AddICUCellMemory(displayNames, DisplayNamesObject::EstimatedMemoryUse); return dn; } // The "code" is usually a small ASCII string, so try to avoid an allocation // by copying it to the stack. Unfortunately we can't pass a string span of // the JSString directly to the unified DisplayNames API, as the // intl::FormatBuffer will be written to. This writing can trigger a GC and // invalidate the span, creating a nogc rooting hazard. class StringUtf8Chars final { size_t length_ = 0; char inlineChars_[32] = {}; UniqueChars allocChars_ = nullptr; public: bool init(JSContext* cx, Handle string) { length_ = string->length(); if (length_ < 32 && StringIsAscii(string)) { CopyChars(reinterpret_cast(inlineChars_), *string); } else { allocChars_ = JS_EncodeStringToUTF8(cx, string); if (!allocChars_) { return false; } } return true; } const char* chars() const { return allocChars_ ? allocChars_.get() : inlineChars_; } size_t length() const { return length_; } operator mozilla::Span() const { return {chars(), length()}; } }; static void ReportInvalidOptionError(JSContext* cx, DisplayNamesOptions::Type type, Handle option) { auto sv = TypeToString(type); MOZ_ASSERT(sv.data()[sv.length()] == '\0', "string must be zero-terminated"); if (auto str = QuoteString(cx, option, '"')) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_OPTION_VALUE, sv.data(), str.get()); } } static void ReportInvalidOptionError(JSContext* cx, DisplayNamesOptions::Type type, double option) { ToCStringBuf cbuf; const char* str = NumberToCString(&cbuf, option); MOZ_ASSERT(str); JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_DIGITS_VALUE, str); } /** * Return the display name for the requested code or undefined if no applicable * display name was found. */ static bool ComputeDisplayName(JSContext* cx, Handle displayNames, Handle code, MutableHandle rvalue) { auto* dn = GetOrCreateDisplayNames(cx, displayNames); if (!dn) { return false; } auto dnOptions = *displayNames->getOptions(); auto type = dnOptions.type; auto fallback = ToDisplayNamesFallback(dnOptions.fallback); FormatBuffer buffer(cx); mozilla::Result result = mozilla::Ok{}; switch (type) { case DisplayNamesOptions::Type::Language: { StringUtf8Chars codeChars{}; if (!codeChars.init(cx, code)) { return false; } result = dn->GetLanguage(buffer, codeChars, fallback); break; } case DisplayNamesOptions::Type::Region: { StringUtf8Chars codeChars{}; if (!codeChars.init(cx, code)) { return false; } result = dn->GetRegion(buffer, codeChars, fallback); break; } case DisplayNamesOptions::Type::Script: { StringUtf8Chars codeChars{}; if (!codeChars.init(cx, code)) { return false; } result = dn->GetScript(buffer, codeChars, fallback); break; } case DisplayNamesOptions::Type::Currency: { StringUtf8Chars codeChars{}; if (!codeChars.init(cx, code)) { return false; } result = dn->GetCurrency(buffer, codeChars, fallback); break; } case DisplayNamesOptions::Type::Calendar: { StringUtf8Chars codeChars{}; if (!codeChars.init(cx, code)) { return false; } result = dn->GetCalendar(buffer, codeChars, fallback); break; } case DisplayNamesOptions::Type::DateTimeField: { mozilla::intl::DateTimeField field; if (StringEqualsLiteral(code, "era")) { field = mozilla::intl::DateTimeField::Era; } else if (StringEqualsLiteral(code, "year")) { field = mozilla::intl::DateTimeField::Year; } else if (StringEqualsLiteral(code, "quarter")) { field = mozilla::intl::DateTimeField::Quarter; } else if (StringEqualsLiteral(code, "month")) { field = mozilla::intl::DateTimeField::Month; } else if (StringEqualsLiteral(code, "weekOfYear")) { field = mozilla::intl::DateTimeField::WeekOfYear; } else if (StringEqualsLiteral(code, "weekday")) { field = mozilla::intl::DateTimeField::Weekday; } else if (StringEqualsLiteral(code, "day")) { field = mozilla::intl::DateTimeField::Day; } else if (StringEqualsLiteral(code, "dayPeriod")) { field = mozilla::intl::DateTimeField::DayPeriod; } else if (StringEqualsLiteral(code, "hour")) { field = mozilla::intl::DateTimeField::Hour; } else if (StringEqualsLiteral(code, "minute")) { field = mozilla::intl::DateTimeField::Minute; } else if (StringEqualsLiteral(code, "second")) { field = mozilla::intl::DateTimeField::Second; } else if (StringEqualsLiteral(code, "timeZoneName")) { field = mozilla::intl::DateTimeField::TimeZoneName; } else { ReportInvalidOptionError(cx, type, code); return false; } auto locale = EncodeLocale(cx, displayNames->getLocale()); if (!locale) { return false; } auto& sharedIntlData = cx->runtime()->sharedIntlData.ref(); auto* dtpgen = sharedIntlData.getDateTimePatternGenerator(cx, locale.get()); if (!dtpgen) { return false; } result = dn->GetDateTimeField(buffer, field, *dtpgen, fallback); break; } case DisplayNamesOptions::Type::Weekday: { double d = LinearStringToNumber(code); if (!IsInteger(d) || d < 1 || d > 7) { ReportInvalidOptionError(cx, type, d); return false; } auto calendarChars = EncodeAscii(cx, displayNames->getCalendar()); if (!calendarChars) { return false; } result = dn->GetWeekday(buffer, static_cast(d), mozilla::MakeStringSpan(calendarChars.get()), fallback); break; } case DisplayNamesOptions::Type::Month: { double d = LinearStringToNumber(code); if (!IsInteger(d) || d < 1 || d > 13) { ReportInvalidOptionError(cx, type, d); return false; } auto calendarChars = EncodeAscii(cx, displayNames->getCalendar()); if (!calendarChars) { return false; } result = dn->GetMonth(buffer, static_cast(d), mozilla::MakeStringSpan(calendarChars.get()), fallback); break; } case DisplayNamesOptions::Type::Quarter: { double d = LinearStringToNumber(code); // Inlined implementation of `IsValidQuarterCode ( quarter )`. if (!IsInteger(d) || d < 1 || d > 4) { ReportInvalidOptionError(cx, type, d); return false; } auto calendarChars = EncodeAscii(cx, displayNames->getCalendar()); if (!calendarChars) { return false; } result = dn->GetQuarter(buffer, static_cast(d), mozilla::MakeStringSpan(calendarChars.get()), fallback); break; } case DisplayNamesOptions::Type::DayPeriod: { mozilla::intl::DayPeriod dayPeriod; if (StringEqualsLiteral(code, "am")) { dayPeriod = mozilla::intl::DayPeriod::AM; } else if (StringEqualsLiteral(code, "pm")) { dayPeriod = mozilla::intl::DayPeriod::PM; } else { ReportInvalidOptionError(cx, type, code); return false; } auto calendarChars = EncodeAscii(cx, displayNames->getCalendar()); if (!calendarChars) { return false; } result = dn->GetDayPeriod(buffer, dayPeriod, mozilla::MakeStringSpan(calendarChars.get()), fallback); break; } } if (result.isErr()) { switch (result.unwrapErr()) { case mozilla::intl::DisplayNamesError::InternalError: ReportInternalError(cx); break; case mozilla::intl::DisplayNamesError::OutOfMemory: ReportOutOfMemory(cx); break; case mozilla::intl::DisplayNamesError::InvalidOption: ReportInvalidOptionError(cx, type, code); break; case mozilla::intl::DisplayNamesError::DuplicateVariantSubtag: JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DUPLICATE_VARIANT_SUBTAG); break; case mozilla::intl::DisplayNamesError::InvalidLanguageTag: JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_LANGUAGE_TAG); break; } return false; } auto* str = buffer.toString(cx); if (!str) { return false; } if (str->empty()) { rvalue.setUndefined(); } else { rvalue.setString(str); } return true; } static bool IsDisplayNames(Handle v) { return v.isObject() && v.toObject().is(); } /** * Intl.DisplayNames.prototype.of ( code ) */ static bool displayNames_of(JSContext* cx, const CallArgs& args) { Rooted displayNames( cx, &args.thisv().toObject().as()); // Step 3. auto* str = JS::ToString(cx, args.get(0)); if (!str) { return false; } Rooted code(cx, str->ensureLinear(cx)); if (!code) { return false; } // Steps 4-8. return ComputeDisplayName(cx, displayNames, code, args.rval()); } /** * Intl.DisplayNames.prototype.of ( code ) */ static bool displayNames_of(JSContext* cx, unsigned argc, Value* vp) { // Steps 1-2. CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } /** * Intl.DisplayNames.prototype.resolvedOptions ( ) */ static bool displayNames_resolvedOptions(JSContext* cx, const CallArgs& args) { Rooted displayNames( cx, &args.thisv().toObject().as()); if (!ResolveLocale(cx, displayNames)) { return false; } auto dnOptions = *displayNames->getOptions(); // Step 3. Rooted options(cx, cx); // Step 4. if (!options.emplaceBack(NameToId(cx->names().locale), StringValue(displayNames->getLocale()))) { return false; } auto* style = NewStringCopy(cx, StyleToString(dnOptions.style)); if (!style) { return false; } if (!options.emplaceBack(NameToId(cx->names().style), StringValue(style))) { return false; } auto* type = NewStringCopy(cx, TypeToString(dnOptions.type)); if (!type) { return false; } if (!options.emplaceBack(NameToId(cx->names().type), StringValue(type))) { return false; } auto* fallback = NewStringCopy(cx, FallbackToString(dnOptions.fallback)); if (!fallback) { return false; } if (!options.emplaceBack(NameToId(cx->names().fallback), StringValue(fallback))) { return false; } // languageDisplay is only present for language display names. if (dnOptions.type == DisplayNamesOptions::Type::Language) { auto* languageDisplay = NewStringCopy( cx, LanguageDisplayToString(dnOptions.languageDisplay)); if (!languageDisplay) { return false; } if (!options.emplaceBack(NameToId(cx->names().languageDisplay), StringValue(languageDisplay))) { return false; } } if (dnOptions.mozExtensions) { if (!options.emplaceBack(NameToId(cx->names().calendar), StringValue(displayNames->getCalendar()))) { return false; } } // Step 5. auto* result = NewPlainObjectWithUniqueNames(cx, options); if (!result) { return false; } args.rval().setObject(*result); return true; } /** * Intl.DisplayNames.prototype.resolvedOptions ( ) */ static bool displayNames_resolvedOptions(JSContext* cx, unsigned argc, Value* vp) { // Steps 1-2. CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod( cx, args); } /** * Intl.DisplayNames.supportedLocalesOf ( locales [ , options ] ) */ static bool displayNames_supportedLocalesOf(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // Steps 1-3. auto* array = SupportedLocalesOf(cx, AvailableLocaleKind::DisplayNames, args.get(0), args.get(1)); if (!array) { return false; } args.rval().setObject(*array); return true; }