commit 64712f35a3cfd3dc6cfa118d8c660fc15910670c
parent fe96a03c97e8fab2d79580c6151eb72656af9a2c
Author: Cristina Horotan <chorotan@mozilla.com>
Date: Fri, 12 Dec 2025 21:17:34 +0200
Revert "Bug 2005531 - Part 8: Remove no longer used self-hosting code. r=spidermonkey-reviewers,dminor" for causing SM failures
This reverts commit 3ba5316e145a67fd39429c13e6bd53053a599755.
Revert "Bug 2005531 - Part 7: Move supportedLocalesOf implementations to C++. r=spidermonkey-reviewers,dminor"
This reverts commit 5dd3814f154dba9b8e1ceade2e7da31af3d383cd.
Revert "Bug 2005531 - Part 6: Add SupportedLocalesOf C++ implementation. r=spidermonkey-reviewers,dminor"
This reverts commit e356b422d4814182e7cdda7aeff5096531885db3.
Revert "Bug 2005531 - Part 5: Move CanonicalizeLocaleList declaration to LocaleNegotiation. r=spidermonkey-reviewers,dminor"
This reverts commit d9e12a778949416e27f2cbf29ad19f1b421e9201.
Revert "Bug 2005531 - Part 4: Add separate LocaleNegotiation files. r=spidermonkey-reviewers,dminor"
This reverts commit 065c332bc2a7ff545544eec9d4eeabe3690c2791.
Revert "Bug 2005531 - Part 3: Rename SupportedLocaleKind to AvailableLocaleKind. r=spidermonkey-reviewers,dminor"
This reverts commit fb163b93205386cc920b2f77761dcdea6c4c94b7.
Revert "Bug 2005531 - Part 2: Move ensureLinear into caller. r=spidermonkey-reviewers,dminor"
This reverts commit b4f3df492a462e8f1a796080aa611232aa52b13d.
Revert "Bug 2005531 - Part 1: Add separate moz.build for js/src/builtin/intl. r=spidermonkey-reviewers,dminor"
This reverts commit ff6a6d24022bb152a7e59b6802cd45cbb3255029.
Diffstat:
33 files changed, 701 insertions(+), 926 deletions(-)
diff --git a/js/src/builtin/String.cpp b/js/src/builtin/String.cpp
@@ -34,7 +34,7 @@
# include "builtin/intl/CommonFunctions.h"
# include "builtin/intl/FormatBuffer.h"
# include "builtin/intl/GlobalIntlData.h"
-# include "builtin/intl/LocaleNegotiation.h"
+# include "builtin/intl/Locale.h"
#endif
#include "builtin/RegExp.h"
#include "gc/GC.h"
diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp
@@ -9795,9 +9795,9 @@ static bool GetAvailableLocalesOf(JSContext* cx, unsigned argc, Value* vp) {
ArrayObject* result;
#ifdef JS_HAS_INTL_API
- using AvailableLocaleKind = js::intl::AvailableLocaleKind;
+ using SupportedLocaleKind = js::intl::SharedIntlData::SupportedLocaleKind;
- AvailableLocaleKind kind;
+ SupportedLocaleKind kind;
{
JSLinearString* typeStr = arg.toString()->ensureLinear(cx);
if (!typeStr) {
@@ -9805,23 +9805,23 @@ static bool GetAvailableLocalesOf(JSContext* cx, unsigned argc, Value* vp) {
}
if (StringEqualsLiteral(typeStr, "Collator")) {
- kind = AvailableLocaleKind::Collator;
+ kind = SupportedLocaleKind::Collator;
} else if (StringEqualsLiteral(typeStr, "DateTimeFormat")) {
- kind = AvailableLocaleKind::DateTimeFormat;
+ kind = SupportedLocaleKind::DateTimeFormat;
} else if (StringEqualsLiteral(typeStr, "DisplayNames")) {
- kind = AvailableLocaleKind::DisplayNames;
+ kind = SupportedLocaleKind::DisplayNames;
} else if (StringEqualsLiteral(typeStr, "DurationFormat")) {
- kind = AvailableLocaleKind::DurationFormat;
+ kind = SupportedLocaleKind::DurationFormat;
} else if (StringEqualsLiteral(typeStr, "ListFormat")) {
- kind = AvailableLocaleKind::ListFormat;
+ kind = SupportedLocaleKind::ListFormat;
} else if (StringEqualsLiteral(typeStr, "NumberFormat")) {
- kind = AvailableLocaleKind::NumberFormat;
+ kind = SupportedLocaleKind::NumberFormat;
} else if (StringEqualsLiteral(typeStr, "PluralRules")) {
- kind = AvailableLocaleKind::PluralRules;
+ kind = SupportedLocaleKind::PluralRules;
} else if (StringEqualsLiteral(typeStr, "RelativeTimeFormat")) {
- kind = AvailableLocaleKind::RelativeTimeFormat;
+ kind = SupportedLocaleKind::RelativeTimeFormat;
} else if (StringEqualsLiteral(typeStr, "Segmenter")) {
- kind = AvailableLocaleKind::Segmenter;
+ kind = SupportedLocaleKind::Segmenter;
} else {
ReportUsageErrorASCII(cx, callee, "Unsupported Intl constructor name");
return false;
diff --git a/js/src/builtin/intl/Collator.cpp b/js/src/builtin/intl/Collator.cpp
@@ -17,7 +17,6 @@
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/FormatBuffer.h"
#include "builtin/intl/LanguageTag.h"
-#include "builtin/intl/LocaleNegotiation.h"
#include "builtin/intl/SharedIntlData.h"
#include "gc/GCContext.h"
#include "js/PropertySpec.h"
@@ -33,7 +32,6 @@
#include "vm/JSObject-inl.h"
using namespace js;
-using namespace js::intl;
using JS::AutoStableStringChars;
@@ -64,9 +62,6 @@ const JSClass CollatorObject::class_ = {
const JSClass& CollatorObject::protoClass_ = PlainObject::class_;
-static bool collator_supportedLocalesOf(JSContext* cx, unsigned argc,
- Value* vp);
-
static bool collator_toSource(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setString(cx->names().Collator);
@@ -74,7 +69,8 @@ static bool collator_toSource(JSContext* cx, unsigned argc, Value* vp) {
}
static const JSFunctionSpec collator_static_methods[] = {
- JS_FN("supportedLocalesOf", collator_supportedLocalesOf, 1, 0),
+ JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_Collator_supportedLocalesOf",
+ 1, 0),
JS_FS_END,
};
@@ -475,11 +471,7 @@ bool js::intl_isUpperCaseFirst(JSContext* cx, unsigned argc, Value* vp) {
SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref();
- Rooted<JSLinearString*> locale(cx, args[0].toString()->ensureLinear(cx));
- if (!locale) {
- return false;
- }
-
+ RootedString locale(cx, args[0].toString());
bool isUpperFirst;
if (!sharedIntlData.isUpperCaseFirst(cx, locale, &isUpperFirst)) {
return false;
@@ -496,11 +488,7 @@ bool js::intl_isIgnorePunctuation(JSContext* cx, unsigned argc, Value* vp) {
SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref();
- Rooted<JSLinearString*> locale(cx, args[0].toString()->ensureLinear(cx));
- if (!locale) {
- return false;
- }
-
+ RootedString locale(cx, args[0].toString());
bool isIgnorePunctuation;
if (!sharedIntlData.isIgnorePunctuation(cx, locale, &isIgnorePunctuation)) {
return false;
@@ -509,20 +497,3 @@ bool js::intl_isIgnorePunctuation(JSContext* cx, unsigned argc, Value* vp) {
args.rval().setBoolean(isIgnorePunctuation);
return true;
}
-
-/**
- * Intl.Collator.supportedLocalesOf ( locales [ , options ] )
- */
-static bool collator_supportedLocalesOf(JSContext* cx, unsigned argc,
- Value* vp) {
- CallArgs args = CallArgsFromVp(argc, vp);
-
- // Steps 1-3.
- auto* array = SupportedLocalesOf(cx, AvailableLocaleKind::Collator,
- args.get(0), args.get(1));
- if (!array) {
- return false;
- }
- args.rval().setObject(*array);
- return true;
-}
diff --git a/js/src/builtin/intl/Collator.js b/js/src/builtin/intl/Collator.js
@@ -247,6 +247,26 @@ function InitializeCollator(collator, locales, options) {
}
/**
+ * Returns the subset of the given locale list for which this locale list has a
+ * matching (possibly fallback) locale. Locales appear in the same order in the
+ * returned list as in the input list.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 10.2.2.
+ */
+function Intl_Collator_supportedLocalesOf(locales /*, options*/) {
+ var options = ArgumentsLength() > 1 ? GetArgument(1) : undefined;
+
+ // Step 1.
+ var availableLocales = "Collator";
+
+ // Step 2.
+ var requestedLocales = CanonicalizeLocaleList(locales);
+
+ // Step 3.
+ return SupportedLocales(availableLocales, requestedLocales, options);
+}
+
+/**
* Collator internal properties.
*
* Spec: ECMAScript Internationalization API Specification, 9.1 and 10.2.3.
diff --git a/js/src/builtin/intl/CommonFunctions.h b/js/src/builtin/intl/CommonFunctions.h
@@ -96,6 +96,11 @@ extern const OldStyleLanguageTagMapping oldStyleLanguageTagMappings[5];
extern JS::UniqueChars EncodeLocale(JSContext* cx, JSString* locale);
+using LocalesList = JS::StackGCVector<JSLinearString*>;
+
+bool CanonicalizeLocaleList(JSContext* cx, JS::Handle<JS::Value> locales,
+ JS::MutableHandle<LocalesList> result);
+
// The inline capacity we use for a Vector<char16_t>. Use this to ensure that
// our uses of ICU string functions, below and elsewhere, will try to fill the
// buffer's entire inline capacity before growing it and heap-allocating.
diff --git a/js/src/builtin/intl/CommonFunctions.js b/js/src/builtin/intl/CommonFunctions.js
@@ -564,6 +564,82 @@ function addUnicodeExtension(locale, extension) {
}
/**
+ * Returns the subset of requestedLocales for which availableLocales has a
+ * matching (possibly fallback) locale. Locales appear in the same order in the
+ * returned list as in the input list.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 9.2.7.
+ */
+function LookupSupportedLocales(availableLocales, requestedLocales) {
+ // Step 1.
+ var subset = [];
+
+ // Step 2.
+ for (var i = 0; i < requestedLocales.length; i++) {
+ var locale = requestedLocales[i];
+
+ // Step 2.a.
+ var noExtensionsLocale = removeUnicodeExtensions(locale);
+
+ // Step 2.b.
+ var availableLocale = BestAvailableLocale(
+ availableLocales,
+ noExtensionsLocale
+ );
+
+ // Step 2.c.
+ if (availableLocale !== undefined) {
+ DefineDataProperty(subset, subset.length, locale);
+ }
+ }
+
+ // Step 3.
+ return subset;
+}
+
+/**
+ * Returns the subset of requestedLocales for which availableLocales has a
+ * matching (possibly fallback) locale. Locales appear in the same order in the
+ * returned list as in the input list.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 9.2.8.
+ */
+function BestFitSupportedLocales(availableLocales, requestedLocales) {
+ // don't have anything better
+ return LookupSupportedLocales(availableLocales, requestedLocales);
+}
+
+/**
+ * Returns the subset of requestedLocales for which availableLocales has a
+ * matching (possibly fallback) locale. Locales appear in the same order in the
+ * returned list as in the input list.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 9.2.9.
+ */
+function SupportedLocales(availableLocales, requestedLocales, options) {
+ // Step 1.
+ var matcher;
+ if (options !== undefined) {
+ // Step 1.a.
+ options = ToObject(options);
+
+ // Step 1.b
+ matcher = options.localeMatcher;
+ if (matcher !== undefined) {
+ matcher = ToString(matcher);
+ if (matcher !== "lookup" && matcher !== "best fit") {
+ ThrowRangeError(JSMSG_INVALID_LOCALE_MATCHER, matcher);
+ }
+ }
+ }
+
+ // Steps 2-5.
+ return matcher === undefined || matcher === "best fit"
+ ? BestFitSupportedLocales(availableLocales, requestedLocales)
+ : LookupSupportedLocales(availableLocales, requestedLocales);
+}
+
+/**
* Extracts a property value from the provided options object, converts it to
* the required type, checks whether it is one of a list of allowed values,
* and fills in a fallback value if necessary.
diff --git a/js/src/builtin/intl/DateTimeFormat.cpp b/js/src/builtin/intl/DateTimeFormat.cpp
@@ -23,7 +23,6 @@
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/FormatBuffer.h"
#include "builtin/intl/LanguageTag.h"
-#include "builtin/intl/LocaleNegotiation.h"
#include "builtin/intl/SharedIntlData.h"
#include "builtin/temporal/Calendar.h"
#include "builtin/temporal/Instant.h"
@@ -56,7 +55,6 @@
#include "vm/NativeObject-inl.h"
using namespace js;
-using namespace js::intl;
using namespace js::temporal;
using JS::AutoStableStringChars;
@@ -93,9 +91,6 @@ const JSClass DateTimeFormatObject::class_ = {
const JSClass& DateTimeFormatObject::protoClass_ = PlainObject::class_;
-static bool dateTimeFormat_supportedLocalesOf(JSContext* cx, unsigned argc,
- Value* vp);
-
static bool dateTimeFormat_toSource(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setString(cx->names().DateTimeFormat);
@@ -103,7 +98,8 @@ static bool dateTimeFormat_toSource(JSContext* cx, unsigned argc, Value* vp) {
}
static const JSFunctionSpec dateTimeFormat_static_methods[] = {
- JS_FN("supportedLocalesOf", dateTimeFormat_supportedLocalesOf, 1, 0),
+ JS_SELF_HOSTED_FN("supportedLocalesOf",
+ "Intl_DateTimeFormat_supportedLocalesOf", 1, 0),
JS_FS_END,
};
@@ -2622,23 +2618,6 @@ bool js::intl_FormatDateTimeRange(JSContext* cx, unsigned argc, Value* vp) {
: FormatDateTimeRange(cx, df, dif, x, y, args.rval());
}
-/**
- * Intl.DateTimeFormat.supportedLocalesOf ( locales [ , options ] )
- */
-static bool dateTimeFormat_supportedLocalesOf(JSContext* cx, unsigned argc,
- Value* vp) {
- CallArgs args = CallArgsFromVp(argc, vp);
-
- // Steps 1-3.
- auto* array = SupportedLocalesOf(cx, AvailableLocaleKind::DateTimeFormat,
- args.get(0), args.get(1));
- if (!array) {
- return false;
- }
- args.rval().setObject(*array);
- return true;
-}
-
bool js::intl::TemporalObjectToLocaleString(
JSContext* cx, const CallArgs& args, DateTimeFormatKind formatKind,
Handle<Value> toLocaleStringTimeZone) {
diff --git a/js/src/builtin/intl/DateTimeFormat.js b/js/src/builtin/intl/DateTimeFormat.js
@@ -615,6 +615,26 @@ function InitializeDateTimeFormat(
/* eslint-enable complexity */
/**
+ * Returns the subset of the given locale list for which this locale list has a
+ * matching (possibly fallback) locale. Locales appear in the same order in the
+ * returned list as in the input list.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 12.3.2.
+ */
+function Intl_DateTimeFormat_supportedLocalesOf(locales /*, options*/) {
+ var options = ArgumentsLength() > 1 ? GetArgument(1) : undefined;
+
+ // Step 1.
+ var availableLocales = "DateTimeFormat";
+
+ // Step 2.
+ var requestedLocales = CanonicalizeLocaleList(locales);
+
+ // Step 3.
+ return SupportedLocales(availableLocales, requestedLocales, options);
+}
+
+/**
* DateTimeFormat internal properties.
*
* Spec: ECMAScript Internationalization API Specification, 9.1 and 12.3.3.
diff --git a/js/src/builtin/intl/DisplayNames.cpp b/js/src/builtin/intl/DisplayNames.cpp
@@ -18,7 +18,6 @@
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/FormatBuffer.h"
-#include "builtin/intl/LocaleNegotiation.h"
#include "gc/AllocKind.h"
#include "gc/GCContext.h"
#include "js/CallArgs.h"
@@ -44,7 +43,6 @@
#include "vm/NativeObject-inl.h"
using namespace js;
-using namespace js::intl;
const JSClassOps DisplayNamesObject::classOps_ = {
nullptr, /* addProperty */
@@ -67,9 +65,6 @@ const JSClass DisplayNamesObject::class_ = {
const JSClass& DisplayNamesObject::protoClass_ = PlainObject::class_;
-static bool displayNames_supportedLocalesOf(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);
@@ -77,7 +72,8 @@ static bool displayNames_toSource(JSContext* cx, unsigned argc, Value* vp) {
}
static const JSFunctionSpec displayNames_static_methods[] = {
- JS_FN("supportedLocalesOf", displayNames_supportedLocalesOf, 1, 0),
+ JS_SELF_HOSTED_FN("supportedLocalesOf",
+ "Intl_DisplayNames_supportedLocalesOf", 1, 0),
JS_FS_END,
};
@@ -559,20 +555,3 @@ bool js::intl_ComputeDisplayName(JSContext* cx, unsigned argc, Value* vp) {
return true;
}
-
-/**
- * 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;
-}
diff --git a/js/src/builtin/intl/DisplayNames.js b/js/src/builtin/intl/DisplayNames.js
@@ -307,6 +307,24 @@ function InitializeDisplayNames(displayNames, locales, options, mozExtensions) {
}
/**
+ * Returns the subset of the given locale list for which this locale list has a
+ * matching (possibly fallback) locale. Locales appear in the same order in the
+ * returned list as in the input list.
+ */
+function Intl_DisplayNames_supportedLocalesOf(locales /*, options*/) {
+ var options = ArgumentsLength() > 1 ? GetArgument(1) : undefined;
+
+ // Step 1.
+ var availableLocales = "DisplayNames";
+
+ // Step 2.
+ var requestedLocales = CanonicalizeLocaleList(locales);
+
+ // Step 3.
+ return SupportedLocales(availableLocales, requestedLocales, options);
+}
+
+/**
* Returns the resolved options for a DisplayNames object.
*/
function Intl_DisplayNames_of(code) {
diff --git a/js/src/builtin/intl/DurationFormat.cpp b/js/src/builtin/intl/DurationFormat.cpp
@@ -24,7 +24,6 @@
#include "builtin/intl/FormatBuffer.h"
#include "builtin/intl/LanguageTag.h"
#include "builtin/intl/ListFormat.h"
-#include "builtin/intl/LocaleNegotiation.h"
#include "builtin/intl/NumberFormat.h"
#include "builtin/temporal/Duration.h"
#include "gc/AllocKind.h"
@@ -69,8 +68,6 @@ const JSClass& DurationFormatObject::protoClass_ = PlainObject::class_;
static bool durationFormat_format(JSContext* cx, unsigned argc, Value* vp);
static bool durationFormat_formatToParts(JSContext* cx, unsigned argc,
Value* vp);
-static bool durationFormat_supportedLocalesOf(JSContext* cx, unsigned argc,
- Value* vp);
static bool durationFormat_toSource(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
@@ -79,7 +76,8 @@ static bool durationFormat_toSource(JSContext* cx, unsigned argc, Value* vp) {
}
static const JSFunctionSpec durationFormat_static_methods[] = {
- JS_FN("supportedLocalesOf", durationFormat_supportedLocalesOf, 1, 0),
+ JS_SELF_HOSTED_FN("supportedLocalesOf",
+ "Intl_DurationFormat_supportedLocalesOf", 1, 0),
JS_FS_END,
};
@@ -1802,23 +1800,6 @@ static bool durationFormat_formatToParts(JSContext* cx, unsigned argc,
cx, args);
}
-/**
- * Intl.DurationFormat.supportedLocalesOf ( locales [ , options ] )
- */
-static bool durationFormat_supportedLocalesOf(JSContext* cx, unsigned argc,
- Value* vp) {
- CallArgs args = CallArgsFromVp(argc, vp);
-
- // Steps 1-3.
- auto* array = SupportedLocalesOf(cx, AvailableLocaleKind::DurationFormat,
- args.get(0), args.get(1));
- if (!array) {
- return false;
- }
- args.rval().setObject(*array);
- return true;
-}
-
bool js::TemporalDurationToLocaleString(JSContext* cx,
const JS::CallArgs& args) {
MOZ_ASSERT(args.thisv().isObject());
diff --git a/js/src/builtin/intl/DurationFormat.js b/js/src/builtin/intl/DurationFormat.js
@@ -520,6 +520,24 @@ function GetDurationUnitOptions(
}
/**
+ * Returns the subset of the given locale list for which this locale list has a
+ * matching (possibly fallback) locale. Locales appear in the same order in the
+ * returned list as in the input list.
+ */
+function Intl_DurationFormat_supportedLocalesOf(locales /*, options*/) {
+ var options = ArgumentsLength() > 1 ? GetArgument(1) : undefined;
+
+ // Step 1.
+ var availableLocales = "DurationFormat";
+
+ // Step 2.
+ var requestedLocales = CanonicalizeLocaleList(locales);
+
+ // Step 3.
+ return SupportedLocales(availableLocales, requestedLocales, options);
+}
+
+/**
* Returns the resolved options for a DurationFormat object.
*/
function Intl_DurationFormat_resolvedOptions() {
diff --git a/js/src/builtin/intl/GlobalIntlData.cpp b/js/src/builtin/intl/GlobalIntlData.cpp
@@ -13,7 +13,7 @@
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/DateTimeFormat.h"
#include "builtin/intl/FormatBuffer.h"
-#include "builtin/intl/LocaleNegotiation.h"
+#include "builtin/intl/IntlObject.h"
#include "builtin/intl/NumberFormat.h"
#include "builtin/temporal/TimeZone.h"
#include "gc/Tracer.h"
diff --git a/js/src/builtin/intl/IntlObject.cpp b/js/src/builtin/intl/IntlObject.cpp
@@ -12,6 +12,7 @@
#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"
@@ -23,15 +24,18 @@
#include "builtin/Array.h"
#include "builtin/intl/CommonFunctions.h"
-#include "builtin/intl/LocaleNegotiation.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/JSAtomUtils.h" // ClassName
#include "vm/JSContext.h"
@@ -112,6 +116,103 @@ static void ReportBadKey(JSContext* cx, JSString* key) {
}
}
+static bool SameOrParentLocale(const JSLinearString* locale,
+ const 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<JSLinearString*> BestAvailableLocale(
+ JSContext* cx, SupportedLocaleKind kind, Handle<JSLinearString*> locale,
+ Handle<JSLinearString*> 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<JSLinearString*> 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
@@ -120,9 +221,7 @@ bool js::intl_BestAvailableLocale(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 3);
- using AvailableLocaleKind = js::intl::AvailableLocaleKind;
-
- AvailableLocaleKind kind;
+ SupportedLocaleKind kind;
{
JSLinearString* typeStr = args[0].toString()->ensureLinear(cx);
if (!typeStr) {
@@ -130,24 +229,24 @@ bool js::intl_BestAvailableLocale(JSContext* cx, unsigned argc, Value* vp) {
}
if (StringEqualsLiteral(typeStr, "Collator")) {
- kind = AvailableLocaleKind::Collator;
+ kind = SupportedLocaleKind::Collator;
} else if (StringEqualsLiteral(typeStr, "DateTimeFormat")) {
- kind = AvailableLocaleKind::DateTimeFormat;
+ kind = SupportedLocaleKind::DateTimeFormat;
} else if (StringEqualsLiteral(typeStr, "DisplayNames")) {
- kind = AvailableLocaleKind::DisplayNames;
+ kind = SupportedLocaleKind::DisplayNames;
} else if (StringEqualsLiteral(typeStr, "DurationFormat")) {
- kind = AvailableLocaleKind::DurationFormat;
+ kind = SupportedLocaleKind::DurationFormat;
} else if (StringEqualsLiteral(typeStr, "ListFormat")) {
- kind = AvailableLocaleKind::ListFormat;
+ kind = SupportedLocaleKind::ListFormat;
} else if (StringEqualsLiteral(typeStr, "NumberFormat")) {
- kind = AvailableLocaleKind::NumberFormat;
+ kind = SupportedLocaleKind::NumberFormat;
} else if (StringEqualsLiteral(typeStr, "PluralRules")) {
- kind = AvailableLocaleKind::PluralRules;
+ kind = SupportedLocaleKind::PluralRules;
} else if (StringEqualsLiteral(typeStr, "RelativeTimeFormat")) {
- kind = AvailableLocaleKind::RelativeTimeFormat;
+ kind = SupportedLocaleKind::RelativeTimeFormat;
} else {
MOZ_ASSERT(StringEqualsLiteral(typeStr, "Segmenter"));
- kind = AvailableLocaleKind::Segmenter;
+ kind = SupportedLocaleKind::Segmenter;
}
}
@@ -156,6 +255,54 @@ bool js::intl_BestAvailableLocale(JSContext* cx, unsigned argc, Value* vp) {
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<mozilla::Ok, ParserError> 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<char, intl::INITIAL_CHAR_BUFFER_SIZE> buffer(cx);
+ if (auto result = tag.ToString(buffer); result.isErr()) {
+ intl::ReportInternalError(cx, result.unwrapErr());
+ return false;
+ }
+
+ MOZ_ASSERT(StringEqualsAscii(locale, buffer.data(), buffer.length()),
+ "locale is a canonicalized language tag");
+ }
+#endif
+
MOZ_ASSERT(args[2].isNull() || args[2].isString());
Rooted<JSLinearString*> defaultLocale(cx);
@@ -166,10 +313,10 @@ bool js::intl_BestAvailableLocale(JSContext* cx, unsigned argc, Value* vp) {
}
}
- Rooted<JSLinearString*> result(cx);
- if (!intl::BestAvailableLocale(cx, kind, locale, defaultLocale, &result)) {
- return false;
- }
+ JSString* result;
+ JS_TRY_VAR_OR_RETURN_FALSE(
+ cx, result, BestAvailableLocale(cx, kind, locale, defaultLocale));
+
if (result) {
args.rval().setString(result);
} else {
@@ -178,6 +325,122 @@ bool js::intl_BestAvailableLocale(JSContext* cx, unsigned argc, Value* vp) {
return true;
}
+JSLinearString* js::intl::ComputeDefaultLocale(JSContext* cx) {
+ const char* locale = cx->realm()->getLocale();
+ if (!locale) {
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+
+ auto span = mozilla::MakeStringSpan(locale);
+
+ mozilla::intl::Locale tag;
+ bool canParseLocale =
+ mozilla::intl::LocaleParser::TryParse(span, tag).isOk() &&
+ tag.Canonicalize().isOk();
+
+ Rooted<JSLinearString*> candidate(cx);
+ if (!canParseLocale) {
+ candidate = NewStringCopyZ<CanGC>(cx, intl::LastDitchLocale());
+ if (!candidate) {
+ return nullptr;
+ }
+ } 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<char, intl::INITIAL_CHAR_BUFFER_SIZE> buffer(cx);
+ if (auto result = tag.ToString(buffer); result.isErr()) {
+ intl::ReportInternalError(cx, result.unwrapErr());
+ return nullptr;
+ }
+
+ candidate = buffer.toAsciiString(cx);
+ if (!candidate) {
+ return nullptr;
+ }
+
+ // 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<CanGC>(cx, modernStyle);
+ if (!candidate) {
+ return nullptr;
+ }
+ 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<JSLinearString*> supportedCollator(cx);
+ JS_TRY_VAR_OR_RETURN_NULL(
+ cx, supportedCollator,
+ BestAvailableLocale(cx, SupportedLocaleKind::Collator, candidate,
+ nullptr));
+
+ Rooted<JSLinearString*> supportedDateTimeFormat(cx);
+ JS_TRY_VAR_OR_RETURN_NULL(
+ 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::DurationFormat,
+ SupportedLocaleKind::ListFormat,
+ SupportedLocaleKind::NumberFormat,
+ SupportedLocaleKind::PluralRules,
+ SupportedLocaleKind::RelativeTimeFormat,
+ SupportedLocaleKind::Segmenter,
+ }) {
+ JSLinearString* supported;
+ JS_TRY_VAR_OR_RETURN_NULL(
+ 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)) {
+ return supportedDateTimeFormat;
+ }
+ return supportedCollator;
+ }
+
+ // Return the last ditch locale if the candidate locale isn't supported.
+ return NewStringCopyZ<CanGC>(cx, intl::LastDitchLocale());
+}
+
using StringList = GCVector<JSLinearString*>;
/**
diff --git a/js/src/builtin/intl/IntlObject.h b/js/src/builtin/intl/IntlObject.h
@@ -67,6 +67,17 @@ extern const JSClass IntlClass;
*/
[[nodiscard]] extern bool intl_SupportedValuesOf(JSContext* cx, unsigned argc,
JS::Value* vp);
+
+namespace intl {
+
+/**
+ * Return the supported locale for the default locale if ICU supports that
+ * default locale (perhaps via fallback, e.g. supporting "de-CH" through "de"
+ * support implied by a "de-DE" locale). Otherwise uses the last-ditch locale.
+ */
+JSLinearString* ComputeDefaultLocale(JSContext* cx);
+
+} // namespace intl
} // namespace js
#endif /* builtin_intl_IntlObject_h */
diff --git a/js/src/builtin/intl/ListFormat.cpp b/js/src/builtin/intl/ListFormat.cpp
@@ -14,7 +14,6 @@
#include "builtin/Array.h"
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/FormatBuffer.h"
-#include "builtin/intl/LocaleNegotiation.h"
#include "gc/GCContext.h"
#include "js/Utility.h"
#include "js/Vector.h"
@@ -27,7 +26,6 @@
#include "vm/ObjectOperations-inl.h"
using namespace js;
-using namespace js::intl;
const JSClassOps ListFormatObject::classOps_ = {
nullptr, // addProperty
@@ -52,9 +50,6 @@ const JSClass ListFormatObject::class_ = {
const JSClass& ListFormatObject::protoClass_ = PlainObject::class_;
-static bool listFormat_supportedLocalesOf(JSContext* cx, unsigned argc,
- Value* vp);
-
static bool listFormat_toSource(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setString(cx->names().ListFormat);
@@ -62,7 +57,8 @@ static bool listFormat_toSource(JSContext* cx, unsigned argc, Value* vp) {
}
static const JSFunctionSpec listFormat_static_methods[] = {
- JS_FN("supportedLocalesOf", listFormat_supportedLocalesOf, 1, 0),
+ JS_SELF_HOSTED_FN("supportedLocalesOf",
+ "Intl_ListFormat_supportedLocalesOf", 1, 0),
JS_FS_END,
};
@@ -381,20 +377,3 @@ bool js::intl_FormatList(JSContext* cx, unsigned argc, Value* vp) {
}
return FormatList(cx, lf, list, args.rval());
}
-
-/**
- * Intl.ListFormat.supportedLocalesOf ( locales [ , options ] )
- */
-static bool listFormat_supportedLocalesOf(JSContext* cx, unsigned argc,
- Value* vp) {
- CallArgs args = CallArgsFromVp(argc, vp);
-
- // Steps 1-3.
- auto* array = SupportedLocalesOf(cx, AvailableLocaleKind::ListFormat,
- args.get(0), args.get(1));
- if (!array) {
- return false;
- }
- args.rval().setObject(*array);
- return true;
-}
diff --git a/js/src/builtin/intl/ListFormat.js b/js/src/builtin/intl/ListFormat.js
@@ -176,6 +176,24 @@ function InitializeListFormat(listFormat, locales, options) {
}
/**
+ * Returns the subset of the given locale list for which this locale list has a
+ * matching (possibly fallback) locale. Locales appear in the same order in the
+ * returned list as in the input list.
+ */
+function Intl_ListFormat_supportedLocalesOf(locales /*, options*/) {
+ var options = ArgumentsLength() > 1 ? GetArgument(1) : undefined;
+
+ // Step 1.
+ var availableLocales = "ListFormat";
+
+ // Step 2.
+ var requestedLocales = CanonicalizeLocaleList(locales);
+
+ // Step 3.
+ return SupportedLocales(availableLocales, requestedLocales, options);
+}
+
+/**
* StringListFromIterable ( iterable )
*/
function StringListFromIterable(iterable, methodName) {
diff --git a/js/src/builtin/intl/Locale.cpp b/js/src/builtin/intl/Locale.cpp
@@ -25,7 +25,6 @@
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/FormatBuffer.h"
#include "builtin/intl/LanguageTag.h"
-#include "builtin/intl/LocaleNegotiation.h"
#include "builtin/intl/StringAsciiChars.h"
#include "builtin/String.h"
#include "js/Conversions.h"
diff --git a/js/src/builtin/intl/LocaleNegotiation.cpp b/js/src/builtin/intl/LocaleNegotiation.cpp
@@ -1,492 +0,0 @@
-/* -*- 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/. */
-
-#include "builtin/intl/LocaleNegotiation.h"
-
-#include "mozilla/Assertions.h"
-#include "mozilla/intl/Locale.h"
-
-#include <algorithm>
-#include <iterator>
-#include <stddef.h>
-
-#include "builtin/Array.h"
-#include "builtin/intl/CommonFunctions.h"
-#include "builtin/intl/FormatBuffer.h"
-#include "builtin/intl/SharedIntlData.h"
-#include "builtin/intl/StringAsciiChars.h"
-#include "js/Conversions.h"
-#include "js/Result.h"
-#include "vm/ArrayObject.h"
-#include "vm/GlobalObject.h"
-#include "vm/JSContext.h"
-#include "vm/Realm.h"
-#include "vm/StringType.h"
-
-#include "vm/NativeObject-inl.h"
-#include "vm/ObjectOperations-inl.h"
-
-using namespace js;
-using namespace js::intl;
-
-static bool AssertCanonicalLocaleWithoutUnicodeExtension(
- JSContext* cx, Handle<JSLinearString*> locale) {
-#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<mozilla::Ok, ParserError> 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<char, intl::INITIAL_CHAR_BUFFER_SIZE> buffer(cx);
- if (auto result = tag.ToString(buffer); result.isErr()) {
- intl::ReportInternalError(cx, result.unwrapErr());
- return false;
- }
-
- MOZ_ASSERT(StringEqualsAscii(locale, buffer.data(), buffer.length()),
- "locale is a canonicalized language tag");
-#endif
- return true;
-}
-
-static bool SameOrParentLocale(const JSLinearString* locale,
- const 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;
-}
-
-/**
- * 9.2.2 BestAvailableLocale ( availableLocales, locale )
- *
- * Compares a BCP 47 language tag against the locales in availableLocales and
- * returns the best available match. Uses the fallback mechanism of RFC 4647,
- * section 3.4.
- *
- * Spec: ECMAScript Internationalization API Specification, 9.2.2.
- * Spec: RFC 4647, section 3.4.
- */
-static JS::Result<JSLinearString*> BestAvailableLocale(
- JSContext* cx, AvailableLocaleKind availableLocales,
- Handle<JSLinearString*> locale, Handle<JSLinearString*> 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;
- };
-
- if (!AssertCanonicalLocaleWithoutUnicodeExtension(cx, locale)) {
- return cx->alreadyReportedError();
- }
-
- // Step 1.
- Rooted<JSLinearString*> candidate(cx, locale);
-
- // Step 2.
- while (true) {
- // Step 2.a.
- bool supported = false;
- if (!sharedIntlData.isAvailableLocale(cx, availableLocales, 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,
- AvailableLocaleKind availableLocales,
- Handle<JSLinearString*> locale,
- Handle<JSLinearString*> defaultLocale,
- MutableHandle<JSLinearString*> result) {
- JSLinearString* res;
- JS_TRY_VAR_OR_RETURN_FALSE(
- cx, res,
- BestAvailableLocale(cx, availableLocales, locale, defaultLocale));
- if (res) {
- result.set(res);
- } else {
- result.set(nullptr);
- }
- return true;
-}
-
-template <typename CharT>
-static size_t BaseNameLength(mozilla::Range<const CharT> locale) {
- // Search for the start of the first singleton subtag.
- for (size_t i = 0; i < locale.length(); i++) {
- if (locale[i] == '-') {
- MOZ_RELEASE_ASSERT(i + 2 < locale.length(), "invalid locale");
- if (locale[i + 2] == '-') {
- return i;
- }
- }
- }
- return locale.length();
-}
-
-static size_t BaseNameLength(JSLinearString* locale) {
- JS::AutoCheckCannotGC nogc;
- if (locale->hasLatin1Chars()) {
- return BaseNameLength(locale->latin1Range(nogc));
- }
- return BaseNameLength(locale->twoByteRange(nogc));
-}
-
-/**
- * Returns the subset of requestedLocales for which availableLocales has a
- * matching (possibly fallback) locale. Locales appear in the same order in the
- * returned list as in the input list.
- *
- * Spec: ECMAScript Internationalization API Specification, 9.2.7.
- * Spec: ECMAScript Internationalization API Specification, 9.2.8.
- */
-static bool LookupSupportedLocales(
- JSContext* cx, AvailableLocaleKind availableLocales,
- Handle<LocalesList> requestedLocales,
- MutableHandle<LocalesList> supportedLocales) {
- // Step 1.
- MOZ_ASSERT(supportedLocales.empty());
-
- Rooted<JSLinearString*> defaultLocale(
- cx, cx->global()->globalIntlData().defaultLocale(cx));
- if (!defaultLocale) {
- return false;
- }
-
- // Step 2.
- Rooted<JSLinearString*> noExtensionsLocale(cx);
- Rooted<JSLinearString*> availableLocale(cx);
- for (size_t i = 0; i < requestedLocales.length(); i++) {
- auto locale = requestedLocales[i];
-
- // Step 2.a.
- //
- // Use the base name to ignore any extension sequences.
- noExtensionsLocale =
- NewDependentString(cx, locale, 0, BaseNameLength(locale));
- if (!noExtensionsLocale) {
- return false;
- }
-
- // Step 2.b.
- JSLinearString* availableLocale;
- JS_TRY_VAR_OR_RETURN_FALSE(
- cx, availableLocale,
- BestAvailableLocale(cx, availableLocales, noExtensionsLocale,
- defaultLocale));
-
- // Step 2.c.
- if (availableLocale) {
- if (!supportedLocales.append(locale)) {
- return false;
- }
- }
- }
-
- // Step 3.
- return true;
-}
-
-/**
- * Returns the subset of requestedLocales for which availableLocales has a
- * matching (possibly fallback) locale. Locales appear in the same order in the
- * returned list as in the input list.
- *
- * Spec: ECMAScript Internationalization API Specification, 9.2.9.
- */
-static bool SupportedLocales(JSContext* cx,
- AvailableLocaleKind availableLocales,
- Handle<LocalesList> requestedLocales,
- Handle<Value> options,
- MutableHandle<LocalesList> supportedLocales) {
- // Step 1.
- if (!options.isUndefined()) {
- // Step 1.a.
- Rooted<JSObject*> obj(cx, ToObject(cx, options));
- if (!obj) {
- return false;
- }
-
- // Step 1.b.
- Rooted<Value> localeMatcher(cx);
- if (!GetProperty(cx, obj, obj, cx->names().localeMatcher, &localeMatcher)) {
- return false;
- }
-
- if (!localeMatcher.isUndefined()) {
- JSString* str = ToString(cx, localeMatcher);
- if (!str) {
- return false;
- }
-
- JSLinearString* linear = str->ensureLinear(cx);
- if (!linear) {
- return false;
- }
-
- if (!StringEqualsLiteral(linear, "lookup") &&
- !StringEqualsLiteral(linear, "best fit")) {
- if (auto chars = QuoteString(cx, linear)) {
- JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
- JSMSG_INVALID_LOCALE_MATCHER, chars.get());
- }
- return false;
- }
- }
- }
-
- // Steps 2-5.
- //
- // We don't yet support anything better than the lookup matcher.
- return LookupSupportedLocales(cx, availableLocales, requestedLocales,
- supportedLocales);
-}
-
-static ArrayObject* LocalesListToArray(JSContext* cx,
- Handle<LocalesList> locales) {
- auto* array = NewDenseFullyAllocatedArray(cx, locales.length());
- if (!array) {
- return nullptr;
- }
- array->setDenseInitializedLength(locales.length());
-
- for (size_t i = 0; i < locales.length(); i++) {
- array->initDenseElement(i, StringValue(locales[i]));
- }
- return array;
-}
-
-ArrayObject* js::intl::SupportedLocalesOf(JSContext* cx,
- AvailableLocaleKind availableLocales,
- Handle<Value> locales,
- Handle<Value> options) {
- Rooted<LocalesList> requestedLocales(cx, cx);
- if (!CanonicalizeLocaleList(cx, locales, &requestedLocales)) {
- return nullptr;
- }
-
- Rooted<LocalesList> supportedLocales(cx, cx);
- if (!SupportedLocales(cx, availableLocales, requestedLocales, options,
- &supportedLocales)) {
- return nullptr;
- }
-
- return LocalesListToArray(cx, supportedLocales);
-}
-
-JSLinearString* js::intl::ComputeDefaultLocale(JSContext* cx) {
- const char* locale = cx->realm()->getLocale();
- if (!locale) {
- ReportOutOfMemory(cx);
- return nullptr;
- }
-
- auto span = mozilla::MakeStringSpan(locale);
-
- mozilla::intl::Locale tag;
- bool canParseLocale =
- mozilla::intl::LocaleParser::TryParse(span, tag).isOk() &&
- tag.Canonicalize().isOk();
-
- Rooted<JSLinearString*> candidate(cx);
- if (!canParseLocale) {
- candidate = NewStringCopyZ<CanGC>(cx, intl::LastDitchLocale());
- if (!candidate) {
- return nullptr;
- }
- } 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<char, intl::INITIAL_CHAR_BUFFER_SIZE> buffer(cx);
- if (auto result = tag.ToString(buffer); result.isErr()) {
- intl::ReportInternalError(cx, result.unwrapErr());
- return nullptr;
- }
-
- candidate = buffer.toAsciiString(cx);
- if (!candidate) {
- return nullptr;
- }
-
- // 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<CanGC>(cx, modernStyle);
- if (!candidate) {
- return nullptr;
- }
- 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<JSLinearString*> supportedCollator(cx);
- JS_TRY_VAR_OR_RETURN_NULL(
- cx, supportedCollator,
- BestAvailableLocale(cx, AvailableLocaleKind::Collator, candidate,
- nullptr));
-
- Rooted<JSLinearString*> supportedDateTimeFormat(cx);
- JS_TRY_VAR_OR_RETURN_NULL(
- cx, supportedDateTimeFormat,
- BestAvailableLocale(cx, AvailableLocaleKind::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 : {
- AvailableLocaleKind::DisplayNames,
- AvailableLocaleKind::DurationFormat,
- AvailableLocaleKind::ListFormat,
- AvailableLocaleKind::NumberFormat,
- AvailableLocaleKind::PluralRules,
- AvailableLocaleKind::RelativeTimeFormat,
- AvailableLocaleKind::Segmenter,
- }) {
- JSLinearString* supported;
- JS_TRY_VAR_OR_RETURN_NULL(
- 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)) {
- return supportedDateTimeFormat;
- }
- return supportedCollator;
- }
-
- // Return the last ditch locale if the candidate locale isn't supported.
- return NewStringCopyZ<CanGC>(cx, intl::LastDitchLocale());
-}
diff --git a/js/src/builtin/intl/LocaleNegotiation.h b/js/src/builtin/intl/LocaleNegotiation.h
@@ -1,70 +0,0 @@
-/* -*- 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/. */
-
-#ifndef builtin_intl_LocaleNegotiation_h
-#define builtin_intl_LocaleNegotiation_h
-
-#include "js/RootingAPI.h"
-#include "js/TypeDecls.h"
-
-class JSLinearString;
-
-namespace js {
-class ArrayObject;
-}
-
-namespace js::intl {
-
-enum class AvailableLocaleKind;
-
-using LocalesList = JS::StackGCVector<JSLinearString*>;
-
-/**
- * Canonicalizes a locale list.
- *
- * Spec: ECMAScript Internationalization API Specification, 9.2.1.
- */
-bool CanonicalizeLocaleList(JSContext* cx, JS::Handle<JS::Value> locales,
- JS::MutableHandle<LocalesList> result);
-
-/**
- * Compares a BCP 47 language tag against the locales in availableLocales and
- * returns the best available match -- or |nullptr| if no match was found.
- * Uses the fallback mechanism of RFC 4647, section 3.4.
- *
- * The set of available locales consulted doesn't necessarily include the
- * default locale or any generalized forms of it (e.g. "de" is a more-general
- * form of "de-CH"). If you want to be sure to consider the default local and
- * its generalized forms (you usually will), pass the default locale as the
- * value of |defaultLocale|; otherwise pass |nullptr|.
- *
- * Spec: ECMAScript Internationalization API Specification, 9.2.2.
- * Spec: RFC 4647, section 3.4.
- */
-bool BestAvailableLocale(JSContext* cx, AvailableLocaleKind availableLocales,
- JS::Handle<JSLinearString*> locale,
- JS::Handle<JSLinearString*> defaultLocale,
- JS::MutableHandle<JSLinearString*> result);
-
-/**
- * Return the supported locales in |locales| which are supported according to
- * |availableLocales|.
- */
-ArrayObject* SupportedLocalesOf(JSContext* cx,
- AvailableLocaleKind availableLocales,
- JS::Handle<JS::Value> locales,
- JS::Handle<JS::Value> options);
-
-/**
- * Return the supported locale for the default locale if ICU supports that
- * default locale (perhaps via fallback, e.g. supporting "de-CH" through "de"
- * support implied by a "de-DE" locale). Otherwise uses the last-ditch locale.
- */
-JSLinearString* ComputeDefaultLocale(JSContext* cx);
-
-} // namespace js::intl
-
-#endif /* builtin_intl_LocaleNegotiation_h */
diff --git a/js/src/builtin/intl/NumberFormat.cpp b/js/src/builtin/intl/NumberFormat.cpp
@@ -32,7 +32,6 @@
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/FormatBuffer.h"
#include "builtin/intl/LanguageTag.h"
-#include "builtin/intl/LocaleNegotiation.h"
#include "builtin/intl/RelativeTimeFormat.h"
#include "gc/GCContext.h"
#include "js/CharacterEncoding.h"
@@ -51,7 +50,6 @@
#include "vm/NativeObject-inl.h"
using namespace js;
-using namespace js::intl;
using mozilla::AssertedCast;
@@ -81,9 +79,6 @@ const JSClass NumberFormatObject::class_ = {
const JSClass& NumberFormatObject::protoClass_ = PlainObject::class_;
-static bool numberFormat_supportedLocalesOf(JSContext* cx, unsigned argc,
- Value* vp);
-
static bool numberFormat_toSource(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setString(cx->names().NumberFormat);
@@ -91,7 +86,8 @@ static bool numberFormat_toSource(JSContext* cx, unsigned argc, Value* vp) {
}
static const JSFunctionSpec numberFormat_static_methods[] = {
- JS_FN("supportedLocalesOf", numberFormat_supportedLocalesOf, 1, 0),
+ JS_SELF_HOSTED_FN("supportedLocalesOf",
+ "Intl_NumberFormat_supportedLocalesOf", 1, 0),
JS_FS_END,
};
@@ -1445,20 +1441,3 @@ ArrayObject* js::intl::FormatNumberToParts(
return FormattedNumberToParts(cx, str, parts, DisplayNumberPartSource::No,
DisplayLiteralUnit::Yes, unit);
}
-
-/**
- * Intl.NumberFormat.supportedLocalesOf ( locales [ , options ] )
- */
-static bool numberFormat_supportedLocalesOf(JSContext* cx, unsigned argc,
- Value* vp) {
- CallArgs args = CallArgsFromVp(argc, vp);
-
- // Steps 1-3.
- auto* array = SupportedLocalesOf(cx, AvailableLocaleKind::NumberFormat,
- args.get(0), args.get(1));
- if (!array) {
- return false;
- }
- args.rval().setObject(*array);
- return true;
-}
diff --git a/js/src/builtin/intl/NumberFormat.js b/js/src/builtin/intl/NumberFormat.js
@@ -924,6 +924,28 @@ function CurrencyDigits(currency) {
return 2;
}
+/**
+ * 15.2.2 Intl.NumberFormat.supportedLocalesOf ( locales [ , options ] )
+ *
+ * Returns the subset of the given locale list for which this locale list has a
+ * matching (possibly fallback) locale. Locales appear in the same order in the
+ * returned list as in the input list.
+ *
+ * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6
+ */
+function Intl_NumberFormat_supportedLocalesOf(locales /*, options*/) {
+ var options = ArgumentsLength() > 1 ? GetArgument(1) : undefined;
+
+ // Step 1.
+ var availableLocales = "NumberFormat";
+
+ // Step 2.
+ var requestedLocales = CanonicalizeLocaleList(locales);
+
+ // Step 3.
+ return SupportedLocales(availableLocales, requestedLocales, options);
+}
+
function getNumberingSystems(locale) {
// ICU doesn't have an API to determine the set of numbering systems
// supported for a locale; it generally pretends that any numbering system
diff --git a/js/src/builtin/intl/PluralRules.cpp b/js/src/builtin/intl/PluralRules.cpp
@@ -14,7 +14,6 @@
#include "builtin/Array.h"
#include "builtin/intl/CommonFunctions.h"
-#include "builtin/intl/LocaleNegotiation.h"
#include "gc/GCContext.h"
#include "js/PropertySpec.h"
#include "vm/GlobalObject.h"
@@ -26,7 +25,6 @@
#include "vm/NativeObject-inl.h"
using namespace js;
-using namespace js::intl;
using mozilla::AssertedCast;
@@ -54,9 +52,6 @@ const JSClass PluralRulesObject::class_ = {
const JSClass& PluralRulesObject::protoClass_ = PlainObject::class_;
-static bool pluralRules_supportedLocalesOf(JSContext* cx, unsigned argc,
- Value* vp);
-
static bool pluralRules_toSource(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setString(cx->names().PluralRules);
@@ -64,7 +59,8 @@ static bool pluralRules_toSource(JSContext* cx, unsigned argc, Value* vp) {
}
static const JSFunctionSpec pluralRules_static_methods[] = {
- JS_FN("supportedLocalesOf", pluralRules_supportedLocalesOf, 1, 0),
+ JS_SELF_HOSTED_FN("supportedLocalesOf",
+ "Intl_PluralRules_supportedLocalesOf", 1, 0),
JS_FS_END,
};
@@ -522,20 +518,3 @@ bool js::intl_GetPluralCategories(JSContext* cx, unsigned argc, Value* vp) {
args.rval().setObject(*res);
return true;
}
-
-/**
- * Intl.PluralRules.supportedLocalesOf ( locales [ , options ] )
- */
-static bool pluralRules_supportedLocalesOf(JSContext* cx, unsigned argc,
- Value* vp) {
- CallArgs args = CallArgsFromVp(argc, vp);
-
- // Steps 1-3.
- auto* array = SupportedLocalesOf(cx, AvailableLocaleKind::PluralRules,
- args.get(0), args.get(1));
- if (!array) {
- return false;
- }
- args.rval().setObject(*array);
- return true;
-}
diff --git a/js/src/builtin/intl/PluralRules.js b/js/src/builtin/intl/PluralRules.js
@@ -230,6 +230,28 @@ function InitializePluralRules(pluralRules, locales, options) {
}
/**
+ * 16.2.2 Intl.PluralRules.supportedLocalesOf ( locales [ , options ] )
+ *
+ * Returns the subset of the given locale list for which this locale list has a
+ * matching (possibly fallback) locale. Locales appear in the same order in the
+ * returned list as in the input list.
+ *
+ * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6
+ */
+function Intl_PluralRules_supportedLocalesOf(locales /*, options*/) {
+ var options = ArgumentsLength() > 1 ? GetArgument(1) : undefined;
+
+ // Step 1.
+ var availableLocales = "PluralRules";
+
+ // Step 2.
+ var requestedLocales = CanonicalizeLocaleList(locales);
+
+ // Step 3.
+ return SupportedLocales(availableLocales, requestedLocales, options);
+}
+
+/**
* 16.3.3 Intl.PluralRules.prototype.select ( value )
*
* Returns a String value representing the plural category matching
diff --git a/js/src/builtin/intl/RelativeTimeFormat.cpp b/js/src/builtin/intl/RelativeTimeFormat.cpp
@@ -14,7 +14,6 @@
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/FormatBuffer.h"
#include "builtin/intl/LanguageTag.h"
-#include "builtin/intl/LocaleNegotiation.h"
#include "gc/GCContext.h"
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/Printer.h"
@@ -27,7 +26,6 @@
#include "vm/NativeObject-inl.h"
using namespace js;
-using namespace js::intl;
/**************** RelativeTimeFormat *****************/
@@ -55,9 +53,6 @@ const JSClass RelativeTimeFormatObject::class_ = {
const JSClass& RelativeTimeFormatObject::protoClass_ = PlainObject::class_;
-static bool relativeTimeFormat_supportedLocalesOf(JSContext* cx, unsigned argc,
- Value* vp);
-
static bool relativeTimeFormat_toSource(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
@@ -66,7 +61,8 @@ static bool relativeTimeFormat_toSource(JSContext* cx, unsigned argc,
}
static const JSFunctionSpec relativeTimeFormat_static_methods[] = {
- JS_FN("supportedLocalesOf", relativeTimeFormat_supportedLocalesOf, 1, 0),
+ JS_SELF_HOSTED_FN("supportedLocalesOf",
+ "Intl_RelativeTimeFormat_supportedLocalesOf", 1, 0),
JS_FS_END,
};
@@ -378,20 +374,3 @@ bool js::intl_FormatRelativeTime(JSContext* cx, unsigned argc, Value* vp) {
args.rval().setString(str);
return true;
}
-
-/**
- * Intl.RelativeTimeFormat.supportedLocalesOf ( locales [ , options ] )
- */
-static bool relativeTimeFormat_supportedLocalesOf(JSContext* cx, unsigned argc,
- Value* vp) {
- CallArgs args = CallArgsFromVp(argc, vp);
-
- // Steps 1-3.
- auto* array = SupportedLocalesOf(cx, AvailableLocaleKind::RelativeTimeFormat,
- args.get(0), args.get(1));
- if (!array) {
- return false;
- }
- args.rval().setObject(*array);
- return true;
-}
diff --git a/js/src/builtin/intl/RelativeTimeFormat.js b/js/src/builtin/intl/RelativeTimeFormat.js
@@ -198,6 +198,26 @@ function InitializeRelativeTimeFormat(relativeTimeFormat, locales, options) {
}
/**
+ * Returns the subset of the given locale list for which this locale list has a
+ * matching (possibly fallback) locale. Locales appear in the same order in the
+ * returned list as in the input list.
+ *
+ * Spec: ECMAScript 402 API, RelativeTimeFormat, 1.3.2.
+ */
+function Intl_RelativeTimeFormat_supportedLocalesOf(locales /*, options*/) {
+ var options = ArgumentsLength() > 1 ? GetArgument(1) : undefined;
+
+ // Step 1.
+ var availableLocales = "RelativeTimeFormat";
+
+ // Step 2.
+ var requestedLocales = CanonicalizeLocaleList(locales);
+
+ // Step 3.
+ return SupportedLocales(availableLocales, requestedLocales, options);
+}
+
+/**
* Returns a String value representing the written form of a relative date
* formatted according to the effective locale and the formatting options
* of this RelativeTimeFormat object.
diff --git a/js/src/builtin/intl/Segmenter.cpp b/js/src/builtin/intl/Segmenter.cpp
@@ -16,7 +16,6 @@
#include "builtin/Array.h"
#include "builtin/intl/CommonFunctions.h"
-#include "builtin/intl/LocaleNegotiation.h"
#include "builtin/intl/StringAsciiChars.h"
#include "gc/AllocKind.h"
#include "gc/GCContext.h"
@@ -42,7 +41,6 @@
#include "vm/NativeObject-inl.h"
using namespace js;
-using namespace js::intl;
const JSClassOps SegmenterObject::classOps_ = {
nullptr, // addProperty
@@ -68,9 +66,6 @@ const JSClass SegmenterObject::class_ = {
const JSClass& SegmenterObject::protoClass_ = PlainObject::class_;
-static bool segmenter_supportedLocalesOf(JSContext* cx, unsigned argc,
- Value* vp);
-
static bool segmenter_toSource(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setString(cx->names().Segmenter);
@@ -78,7 +73,8 @@ static bool segmenter_toSource(JSContext* cx, unsigned argc, Value* vp) {
}
static const JSFunctionSpec segmenter_static_methods[] = {
- JS_FN("supportedLocalesOf", segmenter_supportedLocalesOf, 1, 0),
+ JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_Segmenter_supportedLocalesOf",
+ 1, 0),
JS_FS_END,
};
@@ -1027,20 +1023,3 @@ bool js::intl_FindNextSegmentBoundaries(JSContext* cx, unsigned argc,
args.rval().setObject(*result);
return true;
}
-
-/**
- * Intl.Segmenter.supportedLocalesOf ( locales [ , options ] )
- */
-static bool segmenter_supportedLocalesOf(JSContext* cx, unsigned argc,
- Value* vp) {
- CallArgs args = CallArgsFromVp(argc, vp);
-
- // Steps 1-3.
- auto* array = SupportedLocalesOf(cx, AvailableLocaleKind::Segmenter,
- args.get(0), args.get(1));
- if (!array) {
- return false;
- }
- args.rval().setObject(*array);
- return true;
-}
diff --git a/js/src/builtin/intl/Segmenter.js b/js/src/builtin/intl/Segmenter.js
@@ -159,6 +159,26 @@ function InitializeSegmenter(segmenter, locales, options) {
}
/**
+ * Intl.Segmenter.supportedLocalesOf ( locales [, options ])
+ *
+ * Returns the subset of the given locale list for which this locale list has a
+ * matching (possibly fallback) locale. Locales appear in the same order in the
+ * returned list as in the input list.
+ */
+function Intl_Segmenter_supportedLocalesOf(locales /*, options*/) {
+ var options = ArgumentsLength() > 1 ? GetArgument(1) : undefined;
+
+ // Step 1.
+ var availableLocales = "Segmenter";
+
+ // Step 2.
+ var requestedLocales = CanonicalizeLocaleList(locales);
+
+ // Step 3.
+ return SupportedLocales(availableLocales, requestedLocales, options);
+}
+
+/**
* Intl.Segmenter.prototype.segment ( string )
*
* Create a new Segments object.
diff --git a/js/src/builtin/intl/SharedIntlData.cpp b/js/src/builtin/intl/SharedIntlData.cpp
@@ -561,21 +561,21 @@ static bool IsSameAvailableLocales(const AvailableLocales1& availableLocales1,
}
#endif
-bool js::intl::SharedIntlData::ensureAvailableLocales(JSContext* cx) {
- if (availableLocalesInitialized) {
+bool js::intl::SharedIntlData::ensureSupportedLocales(JSContext* cx) {
+ if (supportedLocalesInitialized) {
return true;
}
- // If ensureAvailableLocales() was called previously, but didn't complete due
+ // If ensureSupportedLocales() was called previously, but didn't complete due
// to OOM, clear all data and start from scratch.
- availableLocales.clearAndCompact();
- collatorAvailableLocales.clearAndCompact();
+ supportedLocales.clearAndCompact();
+ collatorSupportedLocales.clearAndCompact();
- if (!getAvailableLocales(cx, availableLocales,
+ if (!getAvailableLocales(cx, supportedLocales,
mozilla::intl::Locale::GetAvailableLocales())) {
return false;
}
- if (!getAvailableLocales(cx, collatorAvailableLocales,
+ if (!getAvailableLocales(cx, collatorSupportedLocales,
mozilla::intl::Collator::GetAvailableLocales())) {
return false;
}
@@ -588,61 +588,66 @@ bool js::intl::SharedIntlData::ensureAvailableLocales(JSContext* cx) {
mozilla::intl::Locale::GetAvailableLocales(),
mozilla::intl::NumberFormat::GetAvailableLocales()));
- MOZ_ASSERT(!availableLocalesInitialized,
- "ensureAvailableLocales is neither reentrant nor thread-safe");
- availableLocalesInitialized = true;
+ MOZ_ASSERT(!supportedLocalesInitialized,
+ "ensureSupportedLocales is neither reentrant nor thread-safe");
+ supportedLocalesInitialized = true;
return true;
}
-bool js::intl::SharedIntlData::isAvailableLocale(JSContext* cx,
- AvailableLocaleKind kind,
- Handle<JSLinearString*> locale,
- bool* available) {
- if (!ensureAvailableLocales(cx)) {
+bool js::intl::SharedIntlData::isSupportedLocale(JSContext* cx,
+ SupportedLocaleKind kind,
+ HandleString locale,
+ bool* supported) {
+ if (!ensureSupportedLocales(cx)) {
return false;
}
- LocaleHasher::Lookup lookup(locale);
+ JSLinearString* localeLinear = locale->ensureLinear(cx);
+ if (!localeLinear) {
+ return false;
+ }
+
+ LocaleHasher::Lookup lookup(localeLinear);
switch (kind) {
- case AvailableLocaleKind::Collator:
- *available = collatorAvailableLocales.has(lookup);
+ case SupportedLocaleKind::Collator:
+ *supported = collatorSupportedLocales.has(lookup);
return true;
- case AvailableLocaleKind::DateTimeFormat:
- case AvailableLocaleKind::DisplayNames:
- case AvailableLocaleKind::DurationFormat:
- case AvailableLocaleKind::ListFormat:
- case AvailableLocaleKind::NumberFormat:
- case AvailableLocaleKind::PluralRules:
- case AvailableLocaleKind::RelativeTimeFormat:
- case AvailableLocaleKind::Segmenter:
- *available = availableLocales.has(lookup);
+ case SupportedLocaleKind::DateTimeFormat:
+ case SupportedLocaleKind::DisplayNames:
+ case SupportedLocaleKind::DurationFormat:
+ case SupportedLocaleKind::ListFormat:
+ case SupportedLocaleKind::NumberFormat:
+ case SupportedLocaleKind::PluralRules:
+ case SupportedLocaleKind::RelativeTimeFormat:
+ case SupportedLocaleKind::Segmenter:
+ *supported = supportedLocales.has(lookup);
return true;
}
MOZ_CRASH("Invalid Intl constructor");
}
js::ArrayObject* js::intl::SharedIntlData::availableLocalesOf(
- JSContext* cx, AvailableLocaleKind kind) {
- if (!ensureAvailableLocales(cx)) {
+ JSContext* cx, SupportedLocaleKind kind) {
+ if (!ensureSupportedLocales(cx)) {
return nullptr;
}
LocaleSet* localeSet = nullptr;
switch (kind) {
- case AvailableLocaleKind::Collator:
- localeSet = &collatorAvailableLocales;
+ case SupportedLocaleKind::Collator:
+ localeSet = &collatorSupportedLocales;
break;
- case AvailableLocaleKind::DateTimeFormat:
- case AvailableLocaleKind::DisplayNames:
- case AvailableLocaleKind::DurationFormat:
- case AvailableLocaleKind::ListFormat:
- case AvailableLocaleKind::NumberFormat:
- case AvailableLocaleKind::PluralRules:
- case AvailableLocaleKind::RelativeTimeFormat:
- case AvailableLocaleKind::Segmenter:
- localeSet = &availableLocales;
+ case SupportedLocaleKind::DateTimeFormat:
+ case SupportedLocaleKind::DisplayNames:
+ case SupportedLocaleKind::DurationFormat:
+ case SupportedLocaleKind::ListFormat:
+ case SupportedLocaleKind::NumberFormat:
+ case SupportedLocaleKind::PluralRules:
+ case SupportedLocaleKind::RelativeTimeFormat:
+ case SupportedLocaleKind::Segmenter:
+ localeSet = &supportedLocales;
break;
default:
MOZ_CRASH("Invalid Intl constructor");
@@ -720,7 +725,7 @@ bool js::intl::SharedIntlData::ensureUpperCaseFirstLocales(JSContext* cx) {
#endif // DEBUG || MOZ_SYSTEM_ICU
bool js::intl::SharedIntlData::isUpperCaseFirst(JSContext* cx,
- Handle<JSLinearString*> locale,
+ HandleString locale,
bool* isUpperFirst) {
#if DEBUG || MOZ_SYSTEM_ICU
if (!ensureUpperCaseFirstLocales(cx)) {
@@ -728,17 +733,23 @@ bool js::intl::SharedIntlData::isUpperCaseFirst(JSContext* cx,
}
#endif
+ JSLinearString* localeLinear = locale->ensureLinear(cx);
+ if (!localeLinear) {
+ return false;
+ }
+
#if !MOZ_SYSTEM_ICU
// "da" (Danish) and "mt" (Maltese) are the only two supported locales using
// upper-case first. CLDR also lists "cu" (Church Slavic) as an upper-case
// first locale, but since it's not supported in ICU, we don't care about it
// here.
- bool isDefaultUpperCaseFirstLocale = js::StringEqualsLiteral(locale, "da") ||
- js::StringEqualsLiteral(locale, "mt");
+ bool isDefaultUpperCaseFirstLocale =
+ js::StringEqualsLiteral(localeLinear, "da") ||
+ js::StringEqualsLiteral(localeLinear, "mt");
#endif
#if DEBUG || MOZ_SYSTEM_ICU
- LocaleHasher::Lookup lookup(locale);
+ LocaleHasher::Lookup lookup(localeLinear);
*isUpperFirst = upperCaseFirstLocales.has(lookup);
#else
*isUpperFirst = isDefaultUpperCaseFirstLocale;
@@ -804,22 +815,29 @@ bool js::intl::SharedIntlData::ensureIgnorePunctuationLocales(JSContext* cx) {
}
#endif // DEBUG || MOZ_SYSTEM_ICU
-bool js::intl::SharedIntlData::isIgnorePunctuation(
- JSContext* cx, Handle<JSLinearString*> locale, bool* ignorePunctuation) {
+bool js::intl::SharedIntlData::isIgnorePunctuation(JSContext* cx,
+ HandleString locale,
+ bool* ignorePunctuation) {
#if DEBUG || MOZ_SYSTEM_ICU
if (!ensureIgnorePunctuationLocales(cx)) {
return false;
}
#endif
+ JSLinearString* localeLinear = locale->ensureLinear(cx);
+ if (!localeLinear) {
+ return false;
+ }
+
#if !MOZ_SYSTEM_ICU
// "th" (Thai) is the only supported locale which ignores punctuation by
// default.
- bool isDefaultIgnorePunctuationLocale = js::StringEqualsLiteral(locale, "th");
+ bool isDefaultIgnorePunctuationLocale =
+ js::StringEqualsLiteral(localeLinear, "th");
#endif
#if DEBUG || MOZ_SYSTEM_ICU
- LocaleHasher::Lookup lookup(locale);
+ LocaleHasher::Lookup lookup(localeLinear);
*ignorePunctuation = ignorePunctuationLocales.has(lookup);
#else
*ignorePunctuation = isDefaultIgnorePunctuationLocale;
@@ -877,8 +895,8 @@ void js::intl::SharedIntlData::destroyInstance() {
availableTimeZones.clearAndCompact();
ianaZonesTreatedAsLinksByICU.clearAndCompact();
ianaLinksCanonicalizedDifferentlyByICU.clearAndCompact();
- availableLocales.clearAndCompact();
- collatorAvailableLocales.clearAndCompact();
+ supportedLocales.clearAndCompact();
+ collatorSupportedLocales.clearAndCompact();
#if DEBUG || MOZ_SYSTEM_ICU
upperCaseFirstLocales.clearAndCompact();
ignorePunctuationLocales.clearAndCompact();
@@ -891,8 +909,8 @@ void js::intl::SharedIntlData::trace(JSTracer* trc) {
availableTimeZones.trace(trc);
ianaZonesTreatedAsLinksByICU.trace(trc);
ianaLinksCanonicalizedDifferentlyByICU.trace(trc);
- availableLocales.trace(trc);
- collatorAvailableLocales.trace(trc);
+ supportedLocales.trace(trc);
+ collatorSupportedLocales.trace(trc);
#if DEBUG || MOZ_SYSTEM_ICU
upperCaseFirstLocales.trace(trc);
ignorePunctuationLocales.trace(trc);
@@ -906,8 +924,8 @@ size_t js::intl::SharedIntlData::sizeOfExcludingThis(
ianaZonesTreatedAsLinksByICU.shallowSizeOfExcludingThis(mallocSizeOf) +
ianaLinksCanonicalizedDifferentlyByICU.shallowSizeOfExcludingThis(
mallocSizeOf) +
- availableLocales.shallowSizeOfExcludingThis(mallocSizeOf) +
- collatorAvailableLocales.shallowSizeOfExcludingThis(mallocSizeOf) +
+ supportedLocales.shallowSizeOfExcludingThis(mallocSizeOf) +
+ collatorSupportedLocales.shallowSizeOfExcludingThis(mallocSizeOf) +
#if DEBUG || MOZ_SYSTEM_ICU
upperCaseFirstLocales.shallowSizeOfExcludingThis(mallocSizeOf) +
ignorePunctuationLocales.shallowSizeOfExcludingThis(mallocSizeOf) +
diff --git a/js/src/builtin/intl/SharedIntlData.h b/js/src/builtin/intl/SharedIntlData.h
@@ -31,18 +31,6 @@ class ArrayObject;
namespace intl {
-enum class AvailableLocaleKind {
- Collator,
- DateTimeFormat,
- DisplayNames,
- DurationFormat,
- ListFormat,
- NumberFormat,
- PluralRules,
- RelativeTimeFormat,
- Segmenter,
-};
-
/**
* This deleter class exists so that mozilla::intl::DateTimePatternGenerator
* can be a forward declaration, but still be used inside of a UniquePtr.
@@ -271,7 +259,7 @@ class SharedIntlData {
using LocaleSet = GCHashSet<Locale, LocaleHasher, SystemAllocPolicy>;
- // Set of available locales for all Intl service constructors except Collator,
+ // Set of supported locales for all Intl service constructors except Collator,
// which uses its own set.
//
// UDateFormat:
@@ -285,13 +273,13 @@ class SharedIntlData {
// UListFormatter, UPluralRules, and URelativeDateTimeFormatter:
// We're going to use ULocale availableLocales as per ICU recommendation:
// https://unicode-org.atlassian.net/browse/ICU-12756
- LocaleSet availableLocales;
+ LocaleSet supportedLocales;
// ucol_[count,get]Available() return different results compared to
- // uloc_[count,get]Available(), we can't use |availableLocales| here.
- LocaleSet collatorAvailableLocales;
+ // uloc_[count,get]Available(), we can't use |supportedLocales| here.
+ LocaleSet collatorSupportedLocales;
- bool availableLocalesInitialized = false;
+ bool supportedLocalesInitialized = false;
// CountAvailable and GetAvailable describe the signatures used for ICU API
// to determine available locales for various functionality.
@@ -305,21 +293,33 @@ class SharedIntlData {
/**
* Precomputes the available locales sets.
*/
- bool ensureAvailableLocales(JSContext* cx);
+ bool ensureSupportedLocales(JSContext* cx);
public:
+ enum class SupportedLocaleKind {
+ Collator,
+ DateTimeFormat,
+ DisplayNames,
+ DurationFormat,
+ ListFormat,
+ NumberFormat,
+ PluralRules,
+ RelativeTimeFormat,
+ Segmenter,
+ };
+
/**
- * Sets |available| to true if |locale| is supported by the requested Intl
- * service constructor. Otherwise sets |available| to false.
+ * Sets |supported| to true if |locale| is supported by the requested Intl
+ * service constructor. Otherwise sets |supported| to false.
*/
- [[nodiscard]] bool isAvailableLocale(JSContext* cx, AvailableLocaleKind kind,
- JS::Handle<JSLinearString*> locale,
- bool* available);
+ [[nodiscard]] bool isSupportedLocale(JSContext* cx, SupportedLocaleKind kind,
+ JS::Handle<JSString*> locale,
+ bool* supported);
/**
* Returns all available locales for |kind|.
*/
- ArrayObject* availableLocalesOf(JSContext* cx, AvailableLocaleKind kind);
+ ArrayObject* availableLocalesOf(JSContext* cx, SupportedLocaleKind kind);
private:
/**
@@ -361,7 +361,7 @@ class SharedIntlData {
* Sets |isUpperFirst| to true if |locale| sorts upper-case characters
* before lower-case characters.
*/
- bool isUpperCaseFirst(JSContext* cx, JS::Handle<JSLinearString*> locale,
+ bool isUpperCaseFirst(JSContext* cx, JS::Handle<JSString*> locale,
bool* isUpperFirst);
private:
@@ -380,7 +380,7 @@ class SharedIntlData {
/**
* Sets |ignorePunctuation| to true if |locale| ignores punctuation.
*/
- bool isIgnorePunctuation(JSContext* cx, JS::Handle<JSLinearString*> locale,
+ bool isIgnorePunctuation(JSContext* cx, JS::Handle<JSString*> locale,
bool* ignorePunctuation);
private:
diff --git a/js/src/builtin/intl/moz.build b/js/src/builtin/intl/moz.build
@@ -1,36 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# 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/.
-
-FINAL_LIBRARY = "js"
-
-# Includes should be relative to parent path
-LOCAL_INCLUDES += ["!../..", "../.."]
-
-include("../../js-config.mozbuild")
-include("../../js-cxxflags.mozbuild")
-
-LOCAL_INCLUDES += [
- "/intl/icu_capi/bindings/cpp",
-]
-
-UNIFIED_SOURCES += [
- "Collator.cpp",
- "CommonFunctions.cpp",
- "DateTimeFormat.cpp",
- "DisplayNames.cpp",
- "DurationFormat.cpp",
- "GlobalIntlData.cpp",
- "IntlObject.cpp",
- "LanguageTag.cpp",
- "ListFormat.cpp",
- "Locale.cpp",
- "LocaleNegotiation.cpp",
- "NumberFormat.cpp",
- "PluralRules.cpp",
- "RelativeTimeFormat.cpp",
- "Segmenter.cpp",
- "SharedIntlData.cpp",
-]
diff --git a/js/src/moz.build b/js/src/moz.build
@@ -477,6 +477,25 @@ if CONFIG["ENABLE_EXPLICIT_RESOURCE_MANAGEMENT"]:
"vm/DisposableRecord.cpp",
]
+if CONFIG["JS_HAS_INTL_API"]:
+ UNIFIED_SOURCES += [
+ "builtin/intl/Collator.cpp",
+ "builtin/intl/CommonFunctions.cpp",
+ "builtin/intl/DateTimeFormat.cpp",
+ "builtin/intl/DisplayNames.cpp",
+ "builtin/intl/DurationFormat.cpp",
+ "builtin/intl/GlobalIntlData.cpp",
+ "builtin/intl/IntlObject.cpp",
+ "builtin/intl/LanguageTag.cpp",
+ "builtin/intl/ListFormat.cpp",
+ "builtin/intl/Locale.cpp",
+ "builtin/intl/NumberFormat.cpp",
+ "builtin/intl/PluralRules.cpp",
+ "builtin/intl/RelativeTimeFormat.cpp",
+ "builtin/intl/Segmenter.cpp",
+ "builtin/intl/SharedIntlData.cpp",
+ ]
+
if CONFIG["MOZ_INSTRUMENTS"]:
OS_LIBS += ["-framework CoreFoundation"]
SOURCES += [
@@ -548,7 +567,7 @@ DIRS += [
]
if CONFIG["JS_HAS_INTL_API"]:
- DIRS += ["builtin/intl", "builtin/temporal"]
+ DIRS += ["builtin/temporal"]
# Bug 1739321 - clang 13+ only emits debuginfo for classes that are actually
# constructed, and we cheat in a number of cases. The flag exists at least
diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h
@@ -303,7 +303,6 @@
MACRO_(literal, "literal") \
MACRO_(loc, "loc") \
MACRO_(locale, "locale") \
- MACRO_(localeMatcher, "localeMatcher") \
MACRO_(many, "many") \
MACRO_(MapConstructorInit, "MapConstructorInit") \
MACRO_(MapIteratorNext, "MapIteratorNext") \