summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/intl/IntlObject.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /js/src/builtin/intl/IntlObject.cpp
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/builtin/intl/IntlObject.cpp')
-rw-r--r--js/src/builtin/intl/IntlObject.cpp846
1 files changed, 846 insertions, 0 deletions
diff --git a/js/src/builtin/intl/IntlObject.cpp b/js/src/builtin/intl/IntlObject.cpp
new file mode 100644
index 0000000000..35bb4035b3
--- /dev/null
+++ b/js/src/builtin/intl/IntlObject.cpp
@@ -0,0 +1,846 @@
+/* -*- 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/Likely.h"
+#include "mozilla/Range.h"
+
+#include <algorithm>
+#include <iterator>
+
+#include "jsapi.h"
+
+#include "builtin/Array.h"
+#include "builtin/intl/Collator.h"
+#include "builtin/intl/CommonFunctions.h"
+#include "builtin/intl/DateTimeFormat.h"
+#include "builtin/intl/LanguageTag.h"
+#include "builtin/intl/NumberFormat.h"
+#include "builtin/intl/PluralRules.h"
+#include "builtin/intl/RelativeTimeFormat.h"
+#include "builtin/intl/ScopedICUObject.h"
+#include "builtin/intl/SharedIntlData.h"
+#include "js/CharacterEncoding.h"
+#include "js/Class.h"
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/PropertySpec.h"
+#include "js/Result.h"
+#include "js/StableStringChars.h"
+#include "unicode/ucal.h"
+#include "unicode/udat.h"
+#include "unicode/udatpg.h"
+#include "unicode/uloc.h"
+#include "unicode/utypes.h"
+#include "vm/GlobalObject.h"
+#include "vm/JSAtom.h"
+#include "vm/JSContext.h"
+#include "vm/JSObject.h"
+#include "vm/PlainObject.h" // js::PlainObject
+#include "vm/StringType.h"
+
+#include "vm/JSObject-inl.h"
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+
+using mozilla::Range;
+using mozilla::RangedPtr;
+
+using JS::AutoStableStringChars;
+
+using js::intl::CallICU;
+using js::intl::DateTimeFormatOptions;
+using js::intl::IcuLocale;
+
+/******************** 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;
+ }
+
+ UErrorCode status = U_ZERO_ERROR;
+ const UChar* uTimeZone = nullptr;
+ int32_t uTimeZoneLength = 0;
+ UCalendar* cal = ucal_open(uTimeZone, uTimeZoneLength, locale.get(),
+ UCAL_DEFAULT, &status);
+ if (U_FAILURE(status)) {
+ intl::ReportInternalError(cx);
+ return false;
+ }
+ ScopedICUObject<UCalendar, ucal_close> toClose(cal);
+
+ RootedObject info(cx, NewBuiltinClassInstance<PlainObject>(cx));
+ if (!info) {
+ return false;
+ }
+
+ RootedValue v(cx);
+ int32_t firstDayOfWeek = ucal_getAttribute(cal, UCAL_FIRST_DAY_OF_WEEK);
+ v.setInt32(firstDayOfWeek);
+
+ if (!DefineDataProperty(cx, info, cx->names().firstDayOfWeek, v)) {
+ return false;
+ }
+
+ int32_t minDays = ucal_getAttribute(cal, UCAL_MINIMAL_DAYS_IN_FIRST_WEEK);
+ v.setInt32(minDays);
+ if (!DefineDataProperty(cx, info, cx->names().minDays, v)) {
+ return false;
+ }
+
+ UCalendarWeekdayType prevDayType =
+ ucal_getDayOfWeekType(cal, UCAL_SATURDAY, &status);
+ if (U_FAILURE(status)) {
+ intl::ReportInternalError(cx);
+ return false;
+ }
+
+ RootedValue weekendStart(cx), weekendEnd(cx);
+
+ for (int i = UCAL_SUNDAY; i <= UCAL_SATURDAY; i++) {
+ UCalendarDaysOfWeek dayOfWeek = static_cast<UCalendarDaysOfWeek>(i);
+ UCalendarWeekdayType type = ucal_getDayOfWeekType(cal, dayOfWeek, &status);
+ if (U_FAILURE(status)) {
+ intl::ReportInternalError(cx);
+ return false;
+ }
+
+ if (prevDayType != type) {
+ switch (type) {
+ case UCAL_WEEKDAY:
+ // If the first Weekday after Weekend is Sunday (1),
+ // then the last Weekend day is Saturday (7).
+ // Otherwise we'll just take the previous days number.
+ weekendEnd.setInt32(i == 1 ? 7 : i - 1);
+ break;
+ case UCAL_WEEKEND:
+ weekendStart.setInt32(i);
+ break;
+ case UCAL_WEEKEND_ONSET:
+ case UCAL_WEEKEND_CEASE:
+ // At the time this code was added, ICU apparently never behaves this
+ // way, so just throw, so that users will report a bug and we can
+ // decide what to do.
+ intl::ReportInternalError(cx);
+ return false;
+ default:
+ break;
+ }
+ }
+
+ prevDayType = type;
+ }
+
+ MOZ_ASSERT(weekendStart.isInt32());
+ MOZ_ASSERT(weekendEnd.isInt32());
+
+ if (!DefineDataProperty(cx, info, cx->names().weekendStart, weekendStart)) {
+ return false;
+ }
+
+ if (!DefineDataProperty(cx, info, cx->names().weekendEnd, weekendEnd)) {
+ return false;
+ }
+
+ args.rval().setObject(*info);
+ return true;
+}
+
+static void ReportBadKey(JSContext* cx, HandleString key) {
+ if (UniqueChars chars = QuoteString(cx, key, '"')) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY,
+ chars.get());
+ }
+}
+
+template <typename ConstChar>
+static bool MatchPart(RangedPtr<ConstChar> iter, const RangedPtr<ConstChar> end,
+ const char* part, size_t partlen) {
+ for (size_t i = 0; i < partlen; iter++, i++) {
+ if (iter == end || *iter != part[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+template <typename ConstChar, size_t N>
+inline bool MatchPart(RangedPtr<ConstChar>* iter,
+ const RangedPtr<ConstChar> end, const char (&part)[N]) {
+ if (!MatchPart(*iter, end, part, N - 1)) {
+ return false;
+ }
+
+ *iter += N - 1;
+ return true;
+}
+
+enum class DisplayNameStyle {
+ Narrow,
+ Short,
+ Long,
+};
+
+template <typename ConstChar>
+static JSString* ComputeSingleDisplayName(JSContext* cx, UDateFormat* fmt,
+ UDateTimePatternGenerator* dtpg,
+ DisplayNameStyle style,
+ const Range<ConstChar>& pattern,
+ HandleString patternString) {
+ RangedPtr<ConstChar> iter = pattern.begin();
+ const RangedPtr<ConstChar> end = pattern.end();
+
+ auto MatchSlash = [cx, patternString, &iter, end]() {
+ if (MOZ_LIKELY(iter != end && *iter == '/')) {
+ iter++;
+ return true;
+ }
+
+ ReportBadKey(cx, patternString);
+ return false;
+ };
+
+ if (!MatchPart(&iter, end, "dates")) {
+ ReportBadKey(cx, patternString);
+ return nullptr;
+ }
+
+ if (!MatchSlash()) {
+ return nullptr;
+ }
+
+ if (MatchPart(&iter, end, "fields")) {
+ if (!MatchSlash()) {
+ return nullptr;
+ }
+
+ UDateTimePatternField fieldType;
+
+ if (MatchPart(&iter, end, "year")) {
+ fieldType = UDATPG_YEAR_FIELD;
+ } else if (MatchPart(&iter, end, "month")) {
+ fieldType = UDATPG_MONTH_FIELD;
+ } else if (MatchPart(&iter, end, "week")) {
+ fieldType = UDATPG_WEEK_OF_YEAR_FIELD;
+ } else if (MatchPart(&iter, end, "day")) {
+ fieldType = UDATPG_DAY_FIELD;
+ } else {
+ ReportBadKey(cx, patternString);
+ return nullptr;
+ }
+
+ // This part must be the final part with no trailing data.
+ if (iter != end) {
+ ReportBadKey(cx, patternString);
+ return nullptr;
+ }
+
+ int32_t resultSize;
+ const UChar* value = udatpg_getAppendItemName(dtpg, fieldType, &resultSize);
+ MOZ_ASSERT(resultSize >= 0);
+
+ return NewStringCopyN<CanGC>(cx, value, size_t(resultSize));
+ }
+
+ if (MatchPart(&iter, end, "gregorian")) {
+ if (!MatchSlash()) {
+ return nullptr;
+ }
+
+ UDateFormatSymbolType symbolType;
+ int32_t index;
+
+ if (MatchPart(&iter, end, "months")) {
+ if (!MatchSlash()) {
+ return nullptr;
+ }
+
+ switch (style) {
+ case DisplayNameStyle::Narrow:
+ symbolType = UDAT_STANDALONE_NARROW_MONTHS;
+ break;
+
+ case DisplayNameStyle::Short:
+ symbolType = UDAT_STANDALONE_SHORT_MONTHS;
+ break;
+
+ case DisplayNameStyle::Long:
+ symbolType = UDAT_STANDALONE_MONTHS;
+ break;
+ }
+
+ if (MatchPart(&iter, end, "january")) {
+ index = UCAL_JANUARY;
+ } else if (MatchPart(&iter, end, "february")) {
+ index = UCAL_FEBRUARY;
+ } else if (MatchPart(&iter, end, "march")) {
+ index = UCAL_MARCH;
+ } else if (MatchPart(&iter, end, "april")) {
+ index = UCAL_APRIL;
+ } else if (MatchPart(&iter, end, "may")) {
+ index = UCAL_MAY;
+ } else if (MatchPart(&iter, end, "june")) {
+ index = UCAL_JUNE;
+ } else if (MatchPart(&iter, end, "july")) {
+ index = UCAL_JULY;
+ } else if (MatchPart(&iter, end, "august")) {
+ index = UCAL_AUGUST;
+ } else if (MatchPart(&iter, end, "september")) {
+ index = UCAL_SEPTEMBER;
+ } else if (MatchPart(&iter, end, "october")) {
+ index = UCAL_OCTOBER;
+ } else if (MatchPart(&iter, end, "november")) {
+ index = UCAL_NOVEMBER;
+ } else if (MatchPart(&iter, end, "december")) {
+ index = UCAL_DECEMBER;
+ } else {
+ ReportBadKey(cx, patternString);
+ return nullptr;
+ }
+ } else if (MatchPart(&iter, end, "weekdays")) {
+ if (!MatchSlash()) {
+ return nullptr;
+ }
+
+ switch (style) {
+ case DisplayNameStyle::Narrow:
+ symbolType = UDAT_STANDALONE_NARROW_WEEKDAYS;
+ break;
+
+ case DisplayNameStyle::Short:
+ symbolType = UDAT_STANDALONE_SHORT_WEEKDAYS;
+ break;
+
+ case DisplayNameStyle::Long:
+ symbolType = UDAT_STANDALONE_WEEKDAYS;
+ break;
+ }
+
+ if (MatchPart(&iter, end, "monday")) {
+ index = UCAL_MONDAY;
+ } else if (MatchPart(&iter, end, "tuesday")) {
+ index = UCAL_TUESDAY;
+ } else if (MatchPart(&iter, end, "wednesday")) {
+ index = UCAL_WEDNESDAY;
+ } else if (MatchPart(&iter, end, "thursday")) {
+ index = UCAL_THURSDAY;
+ } else if (MatchPart(&iter, end, "friday")) {
+ index = UCAL_FRIDAY;
+ } else if (MatchPart(&iter, end, "saturday")) {
+ index = UCAL_SATURDAY;
+ } else if (MatchPart(&iter, end, "sunday")) {
+ index = UCAL_SUNDAY;
+ } else {
+ ReportBadKey(cx, patternString);
+ return nullptr;
+ }
+ } else if (MatchPart(&iter, end, "dayperiods")) {
+ if (!MatchSlash()) {
+ return nullptr;
+ }
+
+ symbolType = UDAT_AM_PMS;
+
+ if (MatchPart(&iter, end, "am")) {
+ index = UCAL_AM;
+ } else if (MatchPart(&iter, end, "pm")) {
+ index = UCAL_PM;
+ } else {
+ ReportBadKey(cx, patternString);
+ return nullptr;
+ }
+ } else {
+ ReportBadKey(cx, patternString);
+ return nullptr;
+ }
+
+ // This part must be the final part with no trailing data.
+ if (iter != end) {
+ ReportBadKey(cx, patternString);
+ return nullptr;
+ }
+
+ return CallICU(cx, [fmt, symbolType, index](UChar* chars, int32_t size,
+ UErrorCode* status) {
+ return udat_getSymbols(fmt, symbolType, index, chars, size, status);
+ });
+ }
+
+ ReportBadKey(cx, patternString);
+ return nullptr;
+}
+
+bool js::intl_ComputeDisplayNames(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 3);
+
+ // 1. Assert: locale is a string.
+ UniqueChars locale = intl::EncodeLocale(cx, args[0].toString());
+ if (!locale) {
+ return false;
+ }
+
+ // 2. Assert: style is a string.
+ DisplayNameStyle dnStyle;
+ {
+ JSLinearString* style = args[1].toString()->ensureLinear(cx);
+ if (!style) {
+ return false;
+ }
+
+ if (StringEqualsLiteral(style, "narrow")) {
+ dnStyle = DisplayNameStyle::Narrow;
+ } else if (StringEqualsLiteral(style, "short")) {
+ dnStyle = DisplayNameStyle::Short;
+ } else {
+ MOZ_ASSERT(StringEqualsLiteral(style, "long"));
+ dnStyle = DisplayNameStyle::Long;
+ }
+ }
+
+ // 3. Assert: keys is an Array.
+ RootedArrayObject keys(cx, &args[2].toObject().as<ArrayObject>());
+ if (!keys) {
+ return false;
+ }
+
+ // 4. Let result be ArrayCreate(0).
+ RootedArrayObject result(cx, NewDenseFullyAllocatedArray(cx, keys->length()));
+ if (!result) {
+ return false;
+ }
+ result->ensureDenseInitializedLength(0, keys->length());
+
+ UErrorCode status = U_ZERO_ERROR;
+
+ UDateFormat* fmt =
+ udat_open(UDAT_DEFAULT, UDAT_DEFAULT, IcuLocale(locale.get()), nullptr, 0,
+ nullptr, 0, &status);
+ if (U_FAILURE(status)) {
+ intl::ReportInternalError(cx);
+ return false;
+ }
+ ScopedICUObject<UDateFormat, udat_close> datToClose(fmt);
+
+ // UDateTimePatternGenerator will be needed for translations of date and
+ // time fields like "month", "week", "day" etc.
+ UDateTimePatternGenerator* dtpg =
+ udatpg_open(IcuLocale(locale.get()), &status);
+ if (U_FAILURE(status)) {
+ intl::ReportInternalError(cx);
+ return false;
+ }
+ ScopedICUObject<UDateTimePatternGenerator, udatpg_close> datPgToClose(dtpg);
+
+ // 5. For each element of keys,
+ RootedString keyValStr(cx);
+ RootedValue v(cx);
+ for (uint32_t i = 0; i < keys->length(); i++) {
+ if (!GetElement(cx, keys, keys, i, &v)) {
+ return false;
+ }
+
+ keyValStr = v.toString();
+
+ AutoStableStringChars stablePatternChars(cx);
+ if (!stablePatternChars.init(cx, keyValStr)) {
+ return false;
+ }
+
+ // 5.a. Perform an implementation dependent algorithm to map a key to a
+ // corresponding display name.
+ JSString* displayName =
+ stablePatternChars.isLatin1()
+ ? ComputeSingleDisplayName(cx, fmt, dtpg, dnStyle,
+ stablePatternChars.latin1Range(),
+ keyValStr)
+ : ComputeSingleDisplayName(cx, fmt, dtpg, dnStyle,
+ stablePatternChars.twoByteRange(),
+ keyValStr);
+ if (!displayName) {
+ return false;
+ }
+
+ // 5.b. Append the result string to result.
+ result->setDenseElement(i, StringValue(displayName));
+ }
+
+ // 6. Return result.
+ args.rval().setObject(*result);
+ return true;
+}
+
+bool js::intl_GetLocaleInfo(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;
+ }
+
+ RootedObject info(cx, NewBuiltinClassInstance<PlainObject>(cx));
+ if (!info) {
+ return false;
+ }
+
+ if (!DefineDataProperty(cx, info, cx->names().locale, args[0])) {
+ return false;
+ }
+
+ bool rtl = uloc_isRightToLeft(IcuLocale(locale.get()));
+
+ RootedValue dir(cx, StringValue(rtl ? cx->names().rtl : cx->names().ltr));
+
+ if (!DefineDataProperty(cx, info, cx->names().direction, dir)) {
+ return false;
+ }
+
+ args.rval().setObject(*info);
+ return true;
+}
+
+using SupportedLocaleKind = js::intl::SharedIntlData::SupportedLocaleKind;
+
+// 9.2.2 BestAvailableLocale ( availableLocales, locale )
+static JS::Result<JSString*> BestAvailableLocale(
+ JSContext* cx, SupportedLocaleKind kind, HandleLinearString locale,
+ HandleLinearString 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.
+ RootedLinearString 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 && candidate->length() <= defaultLocale->length()) {
+ if (EqualStrings(candidate, defaultLocale)) {
+ return candidate.get();
+ }
+
+ if (candidate->length() < defaultLocale->length() &&
+ HasSubstringAt(defaultLocale, candidate, 0) &&
+ defaultLocale->latin1OrTwoByteChar(candidate->length()) == '-') {
+ 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;
+ }
+ }
+
+ RootedLinearString locale(cx, args[1].toString()->ensureLinear(cx));
+ if (!locale) {
+ return false;
+ }
+
+#ifdef DEBUG
+ {
+ intl::LanguageTag tag(cx);
+ bool ok;
+ JS_TRY_VAR_OR_RETURN_FALSE(
+ cx, ok, intl::LanguageTagParser::tryParse(cx, locale, tag));
+ MOZ_ASSERT(ok, "locale is a structurally valid language tag");
+
+ MOZ_ASSERT(!tag.unicodeExtension(),
+ "locale must contain no Unicode extensions");
+
+ if (!tag.canonicalize(cx)) {
+ return false;
+ }
+
+ JSString* tagStr = tag.toString(cx);
+ if (!tagStr) {
+ return false;
+ }
+
+ bool canonical;
+ if (!EqualStrings(cx, locale, tagStr, &canonical)) {
+ return false;
+ }
+ MOZ_ASSERT(canonical, "locale is a canonicalized language tag");
+ }
+#endif
+
+ MOZ_ASSERT(args[2].isNull() || args[2].isString());
+
+ RootedLinearString 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);
+
+ RootedLinearString locale(cx, args[0].toString()->ensureLinear(cx));
+ if (!locale) {
+ return false;
+ }
+
+ intl::LanguageTag tag(cx);
+ bool ok;
+ JS_TRY_VAR_OR_RETURN_FALSE(
+ cx, ok, intl::LanguageTagParser::tryParse(cx, locale, tag));
+
+ RootedLinearString candidate(cx);
+ if (!ok) {
+ candidate = NewStringCopyZ<CanGC>(cx, intl::LastDitchLocale());
+ if (!candidate) {
+ return false;
+ }
+ } else {
+ if (!tag.canonicalize(cx)) {
+ return false;
+ }
+
+ // 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();
+
+ JSString* canonical = tag.toString(cx);
+ if (!canonical) {
+ return false;
+ }
+
+ candidate = canonical->ensureLinear(cx);
+ if (!candidate) {
+ return false;
+ }
+
+ for (const auto& mapping : js::intl::oldStyleLanguageTagMappings) {
+ const char* oldStyle = mapping.oldStyle;
+ const char* modernStyle = mapping.modernStyle;
+
+ if (StringEqualsAscii(candidate, oldStyle)) {
+ candidate = NewStringCopyZ<CanGC>(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.
+ //
+ // Note: We don't test the supported locales of either Intl.ListFormat,
+ // Intl.PluralRules, Intl.RelativeTimeFormat, because ICU doesn't provide the
+ // necessary API to return actual set of supported locales for these
+ // constructors. Instead it returns the complete set of available locales for
+ // ULocale, which is a superset of the locales supported by Collator,
+ // NumberFormat, and DateTimeFormat.
+ bool isSupported = true;
+ for (auto kind :
+ {SupportedLocaleKind::Collator, SupportedLocaleKind::DateTimeFormat,
+ SupportedLocaleKind::NumberFormat}) {
+ JSString* supported;
+ JS_TRY_VAR_OR_RETURN_FALSE(
+ cx, supported, BestAvailableLocale(cx, kind, candidate, nullptr));
+
+ if (!supported) {
+ isSupported = false;
+ break;
+ }
+ }
+
+ if (!isSupported) {
+ candidate = NewStringCopyZ<CanGC>(cx, intl::LastDitchLocale());
+ if (!candidate) {
+ return false;
+ }
+ }
+
+ args.rval().setString(candidate);
+ 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_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) {
+ Handle<GlobalObject*> global = cx->global();
+ RootedObject proto(cx, GlobalObject::getOrCreateObjectPrototype(cx, global));
+ if (!proto) {
+ return nullptr;
+ }
+
+ // 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};