/* -*- 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/. */ /* Implementation of the Intl object and its non-constructor properties. */ #include "builtin/intl/IntlObject.h" #include "mozilla/Assertions.h" #include "mozilla/intl/Calendar.h" #include "mozilla/intl/Collator.h" #include "mozilla/intl/Currency.h" #include "mozilla/intl/Locale.h" #include "mozilla/intl/MeasureUnitGenerated.h" #include "mozilla/intl/TimeZone.h" #include #include #include #include #include #include "builtin/Array.h" #include "builtin/intl/CommonFunctions.h" #include "builtin/intl/FormatBuffer.h" #include "builtin/intl/NumberingSystemsGenerated.h" #include "builtin/intl/SharedIntlData.h" #include "builtin/intl/StringAsciiChars.h" #include "ds/Sort.h" #include "js/Class.h" #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* #include "js/GCAPI.h" #include "js/GCVector.h" #include "js/PropertySpec.h" #include "js/Result.h" #include "js/StableStringChars.h" #include "vm/GlobalObject.h" #include "vm/JSAtom.h" #include "vm/JSContext.h" #include "vm/PlainObject.h" // js::PlainObject #include "vm/StringType.h" #include "vm/WellKnownAtom.h" // js_*_str #include "vm/JSObject-inl.h" #include "vm/NativeObject-inl.h" using namespace js; /******************** Intl ********************/ bool js::intl_GetCalendarInfo(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 1); UniqueChars locale = intl::EncodeLocale(cx, args[0].toString()); if (!locale) { return false; } auto result = mozilla::intl::Calendar::TryCreate(locale.get()); if (result.isErr()) { intl::ReportInternalError(cx, result.unwrapErr()); return false; } auto calendar = result.unwrap(); RootedObject info(cx, NewPlainObject(cx)); if (!info) { return false; } RootedValue v(cx); v.setInt32(static_cast(calendar->GetFirstDayOfWeek())); if (!DefineDataProperty(cx, info, cx->names().firstDayOfWeek, v)) { return false; } v.setInt32(calendar->GetMinimalDaysInFirstWeek()); if (!DefineDataProperty(cx, info, cx->names().minDays, v)) { return false; } Rooted weekendArray(cx, NewDenseEmptyArray(cx)); if (!weekendArray) { return false; } auto weekend = calendar->GetWeekend(); if (weekend.isErr()) { intl::ReportInternalError(cx, weekend.unwrapErr()); return false; } for (auto day : weekend.unwrap()) { if (!NewbornArrayPush(cx, weekendArray, Int32Value(static_cast(day)))) { return false; } } v.setObject(*weekendArray); if (!DefineDataProperty(cx, info, cx->names().weekend, v)) { return false; } args.rval().setObject(*info); return true; } static void ReportBadKey(JSContext* cx, JSString* key) { if (UniqueChars chars = QuoteString(cx, key, '"')) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, chars.get()); } } static bool SameOrParentLocale(JSLinearString* locale, JSLinearString* otherLocale) { // Return true if |locale| is the same locale as |otherLocale|. if (locale->length() == otherLocale->length()) { return EqualStrings(locale, otherLocale); } // Also return true if |locale| is the parent locale of |otherLocale|. if (locale->length() < otherLocale->length()) { return HasSubstringAt(otherLocale, locale, 0) && otherLocale->latin1OrTwoByteChar(locale->length()) == '-'; } return false; } using SupportedLocaleKind = js::intl::SharedIntlData::SupportedLocaleKind; // 9.2.2 BestAvailableLocale ( availableLocales, locale ) static JS::Result BestAvailableLocale( JSContext* cx, SupportedLocaleKind kind, Handle locale, Handle defaultLocale) { // In the spec, [[availableLocales]] is formally a list of all available // locales. But in our implementation, it's an *incomplete* list, not // necessarily including the default locale (and all locales implied by it, // e.g. "de" implied by "de-CH"), if that locale isn't in every // [[availableLocales]] list (because that locale is supported through // fallback, e.g. "de-CH" supported through "de"). // // If we're considering the default locale, augment the spec loop with // additional checks to also test whether the current prefix is a prefix of // the default locale. intl::SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref(); auto findLast = [](const auto* chars, size_t length) { auto rbegin = std::make_reverse_iterator(chars + length); auto rend = std::make_reverse_iterator(chars); auto p = std::find(rbegin, rend, '-'); // |dist(chars, p.base())| is equal to |dist(p, rend)|, pick whichever you // find easier to reason about when using reserve iterators. ptrdiff_t r = std::distance(chars, p.base()); MOZ_ASSERT(r == std::distance(p, rend)); // But always subtract one to convert from the reverse iterator result to // the correspoding forward iterator value, because reserve iterators point // to one element past the forward iterator value. return r - 1; }; // Step 1. Rooted candidate(cx, locale); // Step 2. while (true) { // Step 2.a. bool supported = false; if (!sharedIntlData.isSupportedLocale(cx, kind, candidate, &supported)) { return cx->alreadyReportedError(); } if (supported) { return candidate.get(); } if (defaultLocale && SameOrParentLocale(candidate, defaultLocale)) { return candidate.get(); } // Step 2.b. ptrdiff_t pos; if (candidate->hasLatin1Chars()) { JS::AutoCheckCannotGC nogc; pos = findLast(candidate->latin1Chars(nogc), candidate->length()); } else { JS::AutoCheckCannotGC nogc; pos = findLast(candidate->twoByteChars(nogc), candidate->length()); } if (pos < 0) { return nullptr; } // Step 2.c. size_t length = size_t(pos); if (length >= 2 && candidate->latin1OrTwoByteChar(length - 2) == '-') { length -= 2; } // Step 2.d. candidate = NewDependentString(cx, candidate, 0, length); if (!candidate) { return cx->alreadyReportedError(); } } } // 9.2.2 BestAvailableLocale ( availableLocales, locale ) // // Carries an additional third argument in our implementation to provide the // default locale. See the doc-comment in the header file. bool js::intl_BestAvailableLocale(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 3); SupportedLocaleKind kind; { JSLinearString* typeStr = args[0].toString()->ensureLinear(cx); if (!typeStr) { return false; } if (StringEqualsLiteral(typeStr, "Collator")) { kind = SupportedLocaleKind::Collator; } else if (StringEqualsLiteral(typeStr, "DateTimeFormat")) { kind = SupportedLocaleKind::DateTimeFormat; } else if (StringEqualsLiteral(typeStr, "DisplayNames")) { kind = SupportedLocaleKind::DisplayNames; } else if (StringEqualsLiteral(typeStr, "ListFormat")) { kind = SupportedLocaleKind::ListFormat; } else if (StringEqualsLiteral(typeStr, "NumberFormat")) { kind = SupportedLocaleKind::NumberFormat; } else if (StringEqualsLiteral(typeStr, "PluralRules")) { kind = SupportedLocaleKind::PluralRules; } else { MOZ_ASSERT(StringEqualsLiteral(typeStr, "RelativeTimeFormat")); kind = SupportedLocaleKind::RelativeTimeFormat; } } Rooted locale(cx, args[1].toString()->ensureLinear(cx)); if (!locale) { return false; } #ifdef DEBUG { MOZ_ASSERT(StringIsAscii(locale), "language tags are ASCII-only"); // |locale| is a structurally valid language tag. mozilla::intl::Locale tag; using ParserError = mozilla::intl::LocaleParser::ParserError; mozilla::Result parse_result = Ok(); { intl::StringAsciiChars chars(locale); if (!chars.init(cx)) { return false; } parse_result = mozilla::intl::LocaleParser::TryParse(chars, tag); } if (parse_result.isErr()) { MOZ_ASSERT(parse_result.unwrapErr() == ParserError::OutOfMemory, "locale is a structurally valid language tag"); intl::ReportInternalError(cx); return false; } MOZ_ASSERT(!tag.GetUnicodeExtension(), "locale must contain no Unicode extensions"); if (auto result = tag.Canonicalize(); result.isErr()) { MOZ_ASSERT( result.unwrapErr() != mozilla::intl::Locale::CanonicalizationError::DuplicateVariant); intl::ReportInternalError(cx); return false; } intl::FormatBuffer buffer(cx); if (auto result = tag.ToString(buffer); result.isErr()) { intl::ReportInternalError(cx, result.unwrapErr()); return false; } JSLinearString* tagStr = buffer.toString(cx); if (!tagStr) { return false; } MOZ_ASSERT(EqualStrings(locale, tagStr), "locale is a canonicalized language tag"); } #endif MOZ_ASSERT(args[2].isNull() || args[2].isString()); Rooted defaultLocale(cx); if (args[2].isString()) { defaultLocale = args[2].toString()->ensureLinear(cx); if (!defaultLocale) { return false; } } JSString* result; JS_TRY_VAR_OR_RETURN_FALSE( cx, result, BestAvailableLocale(cx, kind, locale, defaultLocale)); if (result) { args.rval().setString(result); } else { args.rval().setUndefined(); } return true; } bool js::intl_supportedLocaleOrFallback(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 1); Rooted locale(cx, args[0].toString()->ensureLinear(cx)); if (!locale) { return false; } mozilla::intl::Locale tag; bool canParseLocale = false; if (StringIsAscii(locale)) { intl::StringAsciiChars chars(locale); if (!chars.init(cx)) { return false; } // Tell the analysis the |tag.canonicalize()| method can't GC. JS::AutoSuppressGCAnalysis nogc; canParseLocale = mozilla::intl::LocaleParser::TryParse(chars, tag).isOk() && tag.Canonicalize().isOk(); } Rooted candidate(cx); if (!canParseLocale) { candidate = NewStringCopyZ(cx, intl::LastDitchLocale()); if (!candidate) { return false; } } else { // The default locale must be in [[AvailableLocales]], and that list must // not contain any locales with Unicode extension sequences, so remove any // present in the candidate. tag.ClearUnicodeExtension(); intl::FormatBuffer buffer(cx); if (auto result = tag.ToString(buffer); result.isErr()) { intl::ReportInternalError(cx, result.unwrapErr()); return false; } candidate = buffer.toAsciiString(cx); if (!candidate) { return false; } // Certain old-style language tags lack a script code, but in current // usage they *would* include a script code. Map these over to modern // forms. for (const auto& mapping : js::intl::oldStyleLanguageTagMappings) { const char* oldStyle = mapping.oldStyle; const char* modernStyle = mapping.modernStyle; if (StringEqualsAscii(candidate, oldStyle)) { candidate = NewStringCopyZ(cx, modernStyle); if (!candidate) { return false; } break; } } } // 9.1 Internal slots of Service Constructors // // - [[AvailableLocales]] is a List [...]. The list must include the value // returned by the DefaultLocale abstract operation (6.2.4), [...]. // // That implies we must ignore any candidate which isn't supported by all // Intl service constructors. Rooted supportedCollator(cx); JS_TRY_VAR_OR_RETURN_FALSE( cx, supportedCollator, BestAvailableLocale(cx, SupportedLocaleKind::Collator, candidate, nullptr)); Rooted supportedDateTimeFormat(cx); JS_TRY_VAR_OR_RETURN_FALSE( cx, supportedDateTimeFormat, BestAvailableLocale(cx, SupportedLocaleKind::DateTimeFormat, candidate, nullptr)); #ifdef DEBUG // Note: We don't test the supported locales of the remaining Intl service // constructors, because the set of supported locales is exactly equal to // the set of supported locales of Intl.DateTimeFormat. for (auto kind : {SupportedLocaleKind::DisplayNames, SupportedLocaleKind::ListFormat, SupportedLocaleKind::NumberFormat, SupportedLocaleKind::PluralRules, SupportedLocaleKind::RelativeTimeFormat}) { JSLinearString* supported; JS_TRY_VAR_OR_RETURN_FALSE( cx, supported, BestAvailableLocale(cx, kind, candidate, nullptr)); MOZ_ASSERT(!!supported == !!supportedDateTimeFormat); MOZ_ASSERT_IF(supported, EqualStrings(supported, supportedDateTimeFormat)); } #endif // Accept the candidate locale if it is supported by all Intl service // constructors. if (supportedCollator && supportedDateTimeFormat) { // Use the actually supported locale instead of the candidate locale. For // example when the candidate locale "en-US-posix" is supported through // "en-US", use "en-US" as the default locale. // // Also prefer the supported locale with more subtags. For example when // requesting "de-CH" and Intl.DateTimeFormat supports "de-CH", but // Intl.Collator only "de", still return "de-CH" as the result. if (SameOrParentLocale(supportedCollator, supportedDateTimeFormat)) { candidate = supportedDateTimeFormat; } else { candidate = supportedCollator; } } else { candidate = NewStringCopyZ(cx, intl::LastDitchLocale()); if (!candidate) { return false; } } args.rval().setString(candidate); return true; } using StringList = GCVector; /** * Create a sorted array from a list of strings. */ static ArrayObject* CreateArrayFromList(JSContext* cx, MutableHandle list) { // Reserve scratch space for MergeSort(). size_t initialLength = list.length(); if (!list.growBy(initialLength)) { return nullptr; } // Sort all strings in alphabetical order. MOZ_ALWAYS_TRUE( MergeSort(list.begin(), initialLength, list.begin() + initialLength, [](const auto* a, const auto* b, bool* lessOrEqual) { *lessOrEqual = CompareStrings(a, b) <= 0; return true; })); // Ensure we don't add duplicate entries to the array. auto* end = std::unique( list.begin(), list.begin() + initialLength, [](const auto* a, const auto* b) { return EqualStrings(a, b); }); // std::unique leaves the elements after |end| with an unspecified value, so // remove them first. And also delete the elements in the scratch space. list.shrinkBy(std::distance(end, list.end())); // And finally copy the strings into the result array. auto* array = NewDenseFullyAllocatedArray(cx, list.length()); if (!array) { return nullptr; } array->setDenseInitializedLength(list.length()); for (size_t i = 0; i < list.length(); ++i) { array->initDenseElement(i, StringValue(list[i])); } return array; } /** * Create an array from a sorted list of strings. */ template static ArrayObject* CreateArrayFromSortedList( JSContext* cx, const std::array& list) { // Ensure the list is sorted and doesn't contain duplicates. #ifdef DEBUG // See bug 1583449 for why the lambda can't be in the MOZ_ASSERT. auto isLargerThanOrEqual = [](const auto& a, const auto& b) { return std::strcmp(a, b) >= 0; }; #endif MOZ_ASSERT(std::adjacent_find(std::begin(list), std::end(list), isLargerThanOrEqual) == std::end(list)); size_t length = std::size(list); Rooted array(cx, NewDenseFullyAllocatedArray(cx, length)); if (!array) { return nullptr; } array->ensureDenseInitializedLength(0, length); for (size_t i = 0; i < length; ++i) { auto* str = NewStringCopyZ(cx, list[i]); if (!str) { return nullptr; } array->initDenseElement(i, StringValue(str)); } return array; } /** * Create an array from an intl::Enumeration. */ template static bool EnumerationIntoList(JSContext* cx, Enumeration values, MutableHandle list) { for (auto value : values) { if (value.isErr()) { intl::ReportInternalError(cx); return false; } auto span = value.unwrap(); // Skip over known, unsupported values. std::string_view sv(span.data(), span.size()); if (std::any_of(std::begin(unsupported), std::end(unsupported), [sv](const auto& e) { return sv == e; })) { continue; } auto* string = NewStringCopy(cx, span); if (!string) { return false; } if (!list.append(string)) { return false; } } return true; } /** * Returns the list of calendar types which mustn't be returned by * |Intl.supportedValuesOf()|. */ static constexpr auto UnsupportedCalendars() { // No calendar values are currently unsupported. return std::array{}; } // Defined outside of the function to workaround bugs in GCC<9. // Also see . static constexpr auto UnsupportedCalendarsArray = UnsupportedCalendars(); /** * AvailableCalendars ( ) */ static ArrayObject* AvailableCalendars(JSContext* cx) { Rooted list(cx, StringList(cx)); { // Hazard analysis complains that the mozilla::Result destructor calls a // GC function, which is unsound when returning an unrooted value. Work // around this issue by restricting the lifetime of |keywords| to a // separate block. auto keywords = mozilla::intl::Calendar::GetBcp47KeywordValuesForLocale(""); if (keywords.isErr()) { intl::ReportInternalError(cx, keywords.unwrapErr()); return nullptr; } static constexpr auto& unsupported = UnsupportedCalendarsArray; if (!EnumerationIntoList(cx, keywords.unwrap(), &list)) { return nullptr; } } return CreateArrayFromList(cx, &list); } /** * Returns the list of collation types which mustn't be returned by * |Intl.supportedValuesOf()|. */ static constexpr auto UnsupportedCollations() { return std::array{ "search", "standard", }; } // Defined outside of the function to workaround bugs in GCC<9. // Also see . static constexpr auto UnsupportedCollationsArray = UnsupportedCollations(); /** * AvailableCollations ( ) */ static ArrayObject* AvailableCollations(JSContext* cx) { Rooted list(cx, StringList(cx)); { // Hazard analysis complains that the mozilla::Result destructor calls a // GC function, which is unsound when returning an unrooted value. Work // around this issue by restricting the lifetime of |keywords| to a // separate block. auto keywords = mozilla::intl::Collator::GetBcp47KeywordValues(); if (keywords.isErr()) { intl::ReportInternalError(cx, keywords.unwrapErr()); return nullptr; } static constexpr auto& unsupported = UnsupportedCollationsArray; if (!EnumerationIntoList(cx, keywords.unwrap(), &list)) { return nullptr; } } return CreateArrayFromList(cx, &list); } /** * Returns a list of known, unsupported currencies which are returned by * |Currency::GetISOCurrencies()|. */ static constexpr auto UnsupportedCurrencies() { // "MVP" is also marked with "questionable, remove?" in ucurr.cpp, but only // this single currency code isn't supported by |Intl.DisplayNames| and // therefore must be excluded by |Intl.supportedValuesOf|. return std::array{ "LSM", // https://unicode-org.atlassian.net/browse/ICU-21687 }; } /** * Return a list of known, missing currencies which aren't returned by * |Currency::GetISOCurrencies()|. */ static constexpr auto MissingCurrencies() { return std::array{ "SLE", // https://unicode-org.atlassian.net/browse/ICU-21989 "VED", // https://unicode-org.atlassian.net/browse/ICU-21989 }; } // Defined outside of the function to workaround bugs in GCC<9. // Also see . static constexpr auto UnsupportedCurrenciesArray = UnsupportedCurrencies(); static constexpr auto MissingCurrenciesArray = MissingCurrencies(); /** * AvailableCurrencies ( ) */ static ArrayObject* AvailableCurrencies(JSContext* cx) { Rooted list(cx, StringList(cx)); { // Hazard analysis complains that the mozilla::Result destructor calls a // GC function, which is unsound when returning an unrooted value. Work // around this issue by restricting the lifetime of |currencies| to a // separate block. auto currencies = mozilla::intl::Currency::GetISOCurrencies(); if (currencies.isErr()) { intl::ReportInternalError(cx, currencies.unwrapErr()); return nullptr; } static constexpr auto& unsupported = UnsupportedCurrenciesArray; if (!EnumerationIntoList(cx, currencies.unwrap(), &list)) { return nullptr; } } // Add known missing values. for (const char* value : MissingCurrenciesArray) { auto* string = NewStringCopyZ(cx, value); if (!string) { return nullptr; } if (!list.append(string)) { return nullptr; } } return CreateArrayFromList(cx, &list); } /** * AvailableNumberingSystems ( ) */ static ArrayObject* AvailableNumberingSystems(JSContext* cx) { static constexpr std::array numberingSystems = { NUMBERING_SYSTEMS_WITH_SIMPLE_DIGIT_MAPPINGS}; return CreateArrayFromSortedList(cx, numberingSystems); } /** * AvailableTimeZones ( ) */ static ArrayObject* AvailableTimeZones(JSContext* cx) { // Unsorted list of canonical time zone names, possibly containing // duplicates. Rooted timeZones(cx, StringList(cx)); intl::SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref(); auto iterResult = sharedIntlData.availableTimeZonesIteration(cx); if (iterResult.isErr()) { return nullptr; } auto iter = iterResult.unwrap(); Rooted validatedTimeZone(cx); Rooted ianaTimeZone(cx); for (; !iter.done(); iter.next()) { validatedTimeZone = iter.get(); // Canonicalize the time zone before adding it to the result array. // Some time zone names are canonicalized differently by ICU -- handle // those first. ianaTimeZone.set(nullptr); if (!sharedIntlData.tryCanonicalizeTimeZoneConsistentWithIANA( cx, validatedTimeZone, &ianaTimeZone)) { return nullptr; } JSLinearString* timeZone; if (ianaTimeZone) { cx->markAtom(ianaTimeZone); timeZone = ianaTimeZone; } else { // Call into ICU to canonicalize the time zone. JS::AutoStableStringChars stableChars(cx); if (!stableChars.initTwoByte(cx, validatedTimeZone)) { return nullptr; } intl::FormatBuffer canonicalTimeZone(cx); auto result = mozilla::intl::TimeZone::GetCanonicalTimeZoneID( stableChars.twoByteRange(), canonicalTimeZone); if (result.isErr()) { intl::ReportInternalError(cx, result.unwrapErr()); return nullptr; } timeZone = canonicalTimeZone.toString(cx); if (!timeZone) { return nullptr; } // Canonicalize both to "UTC" per CanonicalizeTimeZoneName(). if (StringEqualsLiteral(timeZone, "Etc/UTC") || StringEqualsLiteral(timeZone, "Etc/GMT")) { timeZone = cx->names().UTC; } } if (!timeZones.append(timeZone)) { return nullptr; } } return CreateArrayFromList(cx, &timeZones); } template constexpr auto MeasurementUnitNames( const mozilla::intl::SimpleMeasureUnit (&units)[N]) { std::array array = {}; for (size_t i = 0; i < N; ++i) { array[i] = units[i].name; } return array; } /** * AvailableUnits ( ) */ static ArrayObject* AvailableUnits(JSContext* cx) { static constexpr auto simpleMeasureUnitNames = MeasurementUnitNames(mozilla::intl::simpleMeasureUnits); return CreateArrayFromSortedList(cx, simpleMeasureUnitNames); } bool js::intl_SupportedValuesOf(JSContext* cx, unsigned argc, JS::Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 1); MOZ_ASSERT(args[0].isString()); JSLinearString* key = args[0].toString()->ensureLinear(cx); if (!key) { return false; } ArrayObject* list; if (StringEqualsLiteral(key, "calendar")) { list = AvailableCalendars(cx); } else if (StringEqualsLiteral(key, "collation")) { list = AvailableCollations(cx); } else if (StringEqualsLiteral(key, "currency")) { list = AvailableCurrencies(cx); } else if (StringEqualsLiteral(key, "numberingSystem")) { list = AvailableNumberingSystems(cx); } else if (StringEqualsLiteral(key, "timeZone")) { list = AvailableTimeZones(cx); } else if (StringEqualsLiteral(key, "unit")) { list = AvailableUnits(cx); } else { ReportBadKey(cx, key); return false; } if (!list) { return false; } args.rval().setObject(*list); return true; } static bool intl_toSource(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setString(cx->names().Intl); return true; } static const JSFunctionSpec intl_static_methods[] = { JS_FN(js_toSource_str, intl_toSource, 0, 0), JS_SELF_HOSTED_FN("getCanonicalLocales", "Intl_getCanonicalLocales", 1, 0), JS_SELF_HOSTED_FN("supportedValuesOf", "Intl_supportedValuesOf", 1, 0), JS_FS_END}; static const JSPropertySpec intl_static_properties[] = { JS_STRING_SYM_PS(toStringTag, "Intl", JSPROP_READONLY), JS_PS_END}; static JSObject* CreateIntlObject(JSContext* cx, JSProtoKey key) { RootedObject proto(cx, &cx->global()->getObjectPrototype()); // The |Intl| object is just a plain object with some "static" function // properties and some constructor properties. return NewTenuredObjectWithGivenProto(cx, &IntlClass, proto); } /** * Initializes the Intl Object and its standard built-in properties. * Spec: ECMAScript Internationalization API Specification, 8.0, 8.1 */ static bool IntlClassFinish(JSContext* cx, HandleObject intl, HandleObject proto) { // Add the constructor properties. RootedId ctorId(cx); RootedValue ctorValue(cx); for (const auto& protoKey : {JSProto_Collator, JSProto_DateTimeFormat, JSProto_DisplayNames, JSProto_ListFormat, JSProto_Locale, JSProto_NumberFormat, JSProto_PluralRules, JSProto_RelativeTimeFormat}) { JSObject* ctor = GlobalObject::getOrCreateConstructor(cx, protoKey); if (!ctor) { return false; } ctorId = NameToId(ClassName(protoKey, cx)); ctorValue.setObject(*ctor); if (!DefineDataProperty(cx, intl, ctorId, ctorValue, 0)) { return false; } } return true; } static const ClassSpec IntlClassSpec = { CreateIntlObject, nullptr, intl_static_methods, intl_static_properties, nullptr, nullptr, IntlClassFinish}; const JSClass js::IntlClass = {"Intl", JSCLASS_HAS_CACHED_PROTO(JSProto_Intl), JS_NULL_CLASS_OPS, &IntlClassSpec};