tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Mjs/src/builtin/String.cpp | 2+-
Mjs/src/builtin/TestingFunctions.cpp | 22+++++++++++-----------
Mjs/src/builtin/intl/Collator.cpp | 37++++---------------------------------
Mjs/src/builtin/intl/Collator.js | 20++++++++++++++++++++
Mjs/src/builtin/intl/CommonFunctions.h | 5+++++
Mjs/src/builtin/intl/CommonFunctions.js | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mjs/src/builtin/intl/DateTimeFormat.cpp | 25++-----------------------
Mjs/src/builtin/intl/DateTimeFormat.js | 20++++++++++++++++++++
Mjs/src/builtin/intl/DisplayNames.cpp | 25++-----------------------
Mjs/src/builtin/intl/DisplayNames.js | 18++++++++++++++++++
Mjs/src/builtin/intl/DurationFormat.cpp | 23++---------------------
Mjs/src/builtin/intl/DurationFormat.js | 18++++++++++++++++++
Mjs/src/builtin/intl/GlobalIntlData.cpp | 2+-
Mjs/src/builtin/intl/IntlObject.cpp | 297++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mjs/src/builtin/intl/IntlObject.h | 11+++++++++++
Mjs/src/builtin/intl/ListFormat.cpp | 25++-----------------------
Mjs/src/builtin/intl/ListFormat.js | 18++++++++++++++++++
Mjs/src/builtin/intl/Locale.cpp | 1-
Djs/src/builtin/intl/LocaleNegotiation.cpp | 492-------------------------------------------------------------------------------
Djs/src/builtin/intl/LocaleNegotiation.h | 70----------------------------------------------------------------------
Mjs/src/builtin/intl/NumberFormat.cpp | 25++-----------------------
Mjs/src/builtin/intl/NumberFormat.js | 22++++++++++++++++++++++
Mjs/src/builtin/intl/PluralRules.cpp | 25++-----------------------
Mjs/src/builtin/intl/PluralRules.js | 22++++++++++++++++++++++
Mjs/src/builtin/intl/RelativeTimeFormat.cpp | 25++-----------------------
Mjs/src/builtin/intl/RelativeTimeFormat.js | 20++++++++++++++++++++
Mjs/src/builtin/intl/Segmenter.cpp | 25++-----------------------
Mjs/src/builtin/intl/Segmenter.js | 20++++++++++++++++++++
Mjs/src/builtin/intl/SharedIntlData.cpp | 126+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mjs/src/builtin/intl/SharedIntlData.h | 52++++++++++++++++++++++++++--------------------------
Djs/src/builtin/intl/moz.build | 36------------------------------------
Mjs/src/moz.build | 21++++++++++++++++++++-
Mjs/src/vm/CommonPropertyNames.h | 1-
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") \