Issue #2259 - Performance improvements in Locale

- Move BestAvailableLocale function to C++
- Move default locale computation to C++
- Add available-locales set to SharedIntlData
- Remove separate sets for DateFormat and NumberFormat available locales.

Based-on: m-c 1373089
This commit is contained in:
Martok 2023-06-29 23:08:11 +02:00 committed by roytam1
commit 4ad4a82a10
28 changed files with 647 additions and 461 deletions

View file

@ -24,7 +24,6 @@
#include "jsobjinlines.h"
using namespace js;
using js::intl::GetAvailableLocales;
using js::intl::IcuLocale;
using js::intl::ReportInternalError;
using js::intl::SharedIntlData;
@ -185,19 +184,6 @@ js::CreateCollatorPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObjec
return proto;
}
bool
js::intl_Collator_availableLocales(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0);
RootedValue result(cx);
if (!GetAvailableLocales(cx, ucol_countAvailable, ucol_getAvailable, &result))
return false;
args.rval().set(result);
return true;
}
bool
js::intl_availableCollations(JSContext* cx, unsigned argc, Value* vp)
{

View file

@ -52,17 +52,6 @@ CreateCollatorPrototype(JSContext* cx, JS::Handle<JSObject*> Intl,
extern MOZ_MUST_USE bool
intl_Collator(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns an object indicating the supported locales for collation
* by having a true-valued property for each such locale with the
* canonicalized language tag as the property name. The object has no
* prototype.
*
* Usage: availableLocales = intl_Collator_availableLocales()
*/
extern MOZ_MUST_USE bool
intl_Collator_availableLocales(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns an array with the collation type identifiers per Unicode
* Technical Standard 35, Unicode Locale Data Markup Language, for the

View file

@ -30,7 +30,7 @@ function resolveCollatorInternals(lazyCollatorData)
var relevantExtensionKeys = Collator.relevantExtensionKeys;
// Step 17.
var r = ResolveLocale(callFunction(Collator.availableLocales, Collator),
var r = ResolveLocale("Collator",
lazyCollatorData.requestedLocales,
lazyCollatorData.opt,
relevantExtensionKeys,
@ -203,8 +203,7 @@ function Intl_Collator_supportedLocalesOf(locales /*, options*/) {
var options = arguments.length > 1 ? arguments[1] : undefined;
// Step 1.
var availableLocales = callFunction(collatorInternalProperties.availableLocales,
collatorInternalProperties);
var availableLocales = "Collator";
// Step 2.
var requestedLocales = CanonicalizeLocaleList(locales);
@ -222,17 +221,6 @@ function Intl_Collator_supportedLocalesOf(locales /*, options*/) {
var collatorInternalProperties = {
sortLocaleData: collatorSortLocaleData,
searchLocaleData: collatorSearchLocaleData,
_availableLocales: null,
availableLocales: function()
{
var locales = this._availableLocales;
if (locales)
return locales;
locales = intl_Collator_availableLocales();
addSpecialMissingLanguageTags(locales);
return (this._availableLocales = locales);
},
relevantExtensionKeys: ["co", "kn", "kf"]
};
@ -245,10 +233,8 @@ function collatorActualLocale(locale) {
// If |locale| is the default locale (e.g. da-DK), but only supported
// through a fallback (da), we need to get the actual locale before we
// can call intl_isUpperCaseFirst. Also see BestAvailableLocaleHelper.
var availableLocales = callFunction(collatorInternalProperties.availableLocales,
collatorInternalProperties);
return BestAvailableLocaleIgnoringDefault(availableLocales, locale);
// can call intl_isUpperCaseFirst. Also see intl_BestAvailableLocale.
return BestAvailableLocaleIgnoringDefault("Collator", locale);
}

View file

@ -83,33 +83,10 @@ js::intl::ReportInternalError(JSContext* cx)
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
}
bool
js::intl::GetAvailableLocales(JSContext* cx, CountAvailable countAvailable,
GetAvailable getAvailable, MutableHandleValue result)
{
RootedObject locales(cx, NewObjectWithGivenProto<PlainObject>(cx, nullptr));
if (!locales)
return false;
const js::intl::OldStyleLanguageTagMapping
js::intl::oldStyleLanguageTagMappings[] = {
{"pa-PK", "pa-Arab-PK"}, {"zh-CN", "zh-Hans-CN"},
{"zh-HK", "zh-Hant-HK"}, {"zh-SG", "zh-Hans-SG"},
{"zh-TW", "zh-Hant-TW"},
};
uint32_t count = countAvailable();
for (uint32_t i = 0; i < count; i++) {
const char* locale = getAvailable(i);
auto lang = DuplicateString(cx, locale);
if (!lang)
return false;
char* p;
while ((p = strchr(lang.get(), '_')))
*p = '-';
RootedAtom a(cx, Atomize(cx, lang.get(), strlen(lang.get())));
if (!a)
return false;
if (!DefineProperty(cx, locales, a->asPropertyName(), TrueHandleValue, nullptr, nullptr,
JSPROP_ENUMERATE))
{
return false;
}
}
result.setObject(*locales);
return true;
}

View file

@ -71,6 +71,32 @@ StringsAreEqual(JSAutoByteString& s1, const char* s2)
return !strcmp(s1.ptr(), s2);
}
/**
* The last-ditch locale is used if none of the available locales satisfies a
* request. "en-GB" is used based on the assumptions that English is the most
* common second language, that both en-GB and en-US are normally available in
* an implementation, and that en-GB is more representative of the English used
* in other locales.
*/
static inline const char* LastDitchLocale() { return "en-GB"; }
/**
* Certain old, commonly-used language tags that lack a script, are expected to
* nonetheless imply one. This object maps these old-style tags to modern
* equivalents.
*/
struct OldStyleLanguageTagMapping {
const char* const oldStyle;
const char* const modernStyle;
// Provide a constructor to catch missing initializers in the mappings array.
constexpr OldStyleLanguageTagMapping(const char* oldStyle,
const char* modernStyle)
: oldStyle(oldStyle), modernStyle(modernStyle) {}
};
extern const OldStyleLanguageTagMapping oldStyleLanguageTagMappings[5];
static inline const char*
IcuLocale(const char* locale)
{
@ -129,25 +155,6 @@ CallICU(JSContext* cx, const ICUStringFunction& strFn)
return NewStringCopyN<CanGC>(cx, chars.begin(), size_t(size));
}
// CountAvailable and GetAvailable describe the signatures used for ICU API
// to determine available locales for various functionality.
using CountAvailable = int32_t (*)();
using GetAvailable = const char* (*)(int32_t localeIndex);
/**
* Return an object whose own property names are the locales indicated as
* available by |countAvailable| that provides an overall count, and by
* |getAvailable| that when called passing a number less than that count,
* returns the corresponding locale as a borrowed string. For example:
*
* RootedValue v(cx);
* if (!GetAvailableLocales(cx, unum_countAvailable, unum_getAvailable, &v))
* return false;
*/
extern bool
GetAvailableLocales(JSContext* cx, CountAvailable countAvailable, GetAvailable getAvailable,
JS::MutableHandle<JS::Value> result);
} // namespace intl
} // namespace js

View file

@ -138,78 +138,12 @@ function IsASCIIAlphaString(s) {
return true;
}
// The last-ditch locale is used if none of the available locales satisfies a
// request. "en-GB" is used based on the assumptions that English is the most
// common second language, that both en-GB and en-US are normally available in
// an implementation, and that en-GB is more representative of the English used
// in other locales.
function lastDitchLocale() {
// Per bug 1177929, strings don't clone out of self-hosted code as atoms,
// breaking IonBuilder::constant. Put this in a function for now.
return "en-GB";
}
// Certain old, commonly-used language tags that lack a script, are expected to
// nonetheless imply one. This object maps these old-style tags to modern
// equivalents.
var oldStyleLanguageTagMappings = {
"pa-PK": "pa-Arab-PK",
"zh-CN": "zh-Hans-CN",
"zh-HK": "zh-Hant-HK",
"zh-SG": "zh-Hans-SG",
"zh-TW": "zh-Hant-TW",
};
var localeCandidateCache = {
runtimeDefaultLocale: undefined,
candidateDefaultLocale: undefined,
};
var localeCache = {
runtimeDefaultLocale: undefined,
defaultLocale: undefined,
};
/**
* Compute the candidate default locale: the locale *requested* to be used as
* the default locale. We'll use it if and only if ICU provides support (maybe
* fallback support, e.g. supporting "de-ZA" through "de" support implied by a
* "de-DE" locale).
*/
function DefaultLocaleIgnoringAvailableLocales() {
const runtimeDefaultLocale = RuntimeDefaultLocale();
if (runtimeDefaultLocale === localeCandidateCache.runtimeDefaultLocale)
return localeCandidateCache.candidateDefaultLocale;
// If we didn't get a cache hit, compute the candidate default locale and
// cache it. Fall back on the last-ditch locale when necessary.
var candidate = intl_TryValidateAndCanonicalizeLanguageTag(runtimeDefaultLocale);
if (candidate === null) {
candidate = lastDitchLocale();
} 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.
candidate = removeUnicodeExtensions(candidate);
if (hasOwn(candidate, oldStyleLanguageTagMappings))
candidate = oldStyleLanguageTagMappings[candidate];
}
// Cache the candidate locale until the runtime default locale changes.
localeCandidateCache.candidateDefaultLocale = candidate;
localeCandidateCache.runtimeDefaultLocale = runtimeDefaultLocale;
assertIsValidAndCanonicalLanguageTag(candidate, "the candidate");
return candidate;
}
/**
* Returns the BCP 47 language tag for the host environment's current locale.
*
@ -221,29 +155,13 @@ function DefaultLocale() {
return localeCache.defaultLocale;
// If we didn't have a cache hit, compute the candidate default locale.
// Then use it as the actual default locale if ICU supports that locale
// (perhaps via fallback). Otherwise use the last-ditch locale.
var candidate = DefaultLocaleIgnoringAvailableLocales();
var locale;
if (BestAvailableLocaleIgnoringDefault(callFunction(collatorInternalProperties.availableLocales,
collatorInternalProperties),
candidate) &&
BestAvailableLocaleIgnoringDefault(callFunction(numberFormatInternalProperties.availableLocales,
numberFormatInternalProperties),
candidate) &&
BestAvailableLocaleIgnoringDefault(callFunction(dateTimeFormatInternalProperties.availableLocales,
dateTimeFormatInternalProperties),
candidate))
{
locale = candidate;
} else {
locale = lastDitchLocale();
}
var locale = intl_supportedLocaleOrFallback(runtimeDefaultLocale);
assertIsValidAndCanonicalLanguageTag(locale, "the computed default locale");
assert(startOfUnicodeExtensions(locale) < 0,
"the computed default locale must not contain a Unicode extension sequence");
// Cache the computed locale until the runtime default locale changes.
localeCache.defaultLocale = locale;
localeCache.runtimeDefaultLocale = runtimeDefaultLocale;
@ -251,31 +169,6 @@ function DefaultLocale() {
}
/**
* Add old-style language tags without script code for locales that in current
* usage would include a script subtag. Also add an entry for the last-ditch
* locale, in case ICU doesn't directly support it (but does support it through
* fallback, e.g. supporting "en-GB" indirectly using "en" support).
*/
function addSpecialMissingLanguageTags(availableLocales) {
// 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.
var oldStyleLocales = std_Object_getOwnPropertyNames(oldStyleLanguageTagMappings);
for (var i = 0; i < oldStyleLocales.length; i++) {
var oldStyleLocale = oldStyleLocales[i];
if (availableLocales[oldStyleLanguageTagMappings[oldStyleLocale]])
availableLocales[oldStyleLocale] = true;
}
// Also forcibly provide the last-ditch locale.
var lastDitch = lastDitchLocale();
assert(lastDitch === "en-GB" && availableLocales["en"],
"shouldn't be a need to add every locale implied by the last-" +
"ditch locale, merely just the last-ditch locale");
availableLocales[lastDitch] = true;
}
/**
* Canonicalizes a locale list.
*
@ -336,49 +229,6 @@ function CanonicalizeLocaleList(locales) {
}
function BestAvailableLocaleHelper(availableLocales, locale, considerDefaultLocale) {
assertIsValidAndCanonicalLanguageTag(locale, "BestAvailableLocale locale");
assert(startOfUnicodeExtensions(locale) < 0, "locale must contain no Unicode extensions");
// 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.
var defaultLocale;
if (considerDefaultLocale)
defaultLocale = DefaultLocale();
var candidate = locale;
while (true) {
if (availableLocales[candidate])
return candidate;
if (considerDefaultLocale && candidate.length <= defaultLocale.length) {
if (candidate === defaultLocale)
return candidate;
if (callFunction(std_String_startsWith, defaultLocale, candidate + "-"))
return candidate;
}
var pos = callFunction(std_String_lastIndexOf, candidate, "-");
if (pos === -1)
return undefined;
if (pos >= 2 && candidate[pos - 2] === "-")
pos -= 2;
candidate = callFunction(String_substring, candidate, 0, pos);
}
}
/**
* Compares a BCP 47 language tag against the locales in availableLocales
* and returns the best available match. Uses the fallback
@ -388,16 +238,15 @@ function BestAvailableLocaleHelper(availableLocales, locale, considerDefaultLoca
* Spec: RFC 4647, section 3.4.
*/
function BestAvailableLocale(availableLocales, locale) {
return BestAvailableLocaleHelper(availableLocales, locale, true);
return intl_BestAvailableLocale(availableLocales, locale, DefaultLocale());
}
/**
* Identical to BestAvailableLocale, but does not consider the default locale
* during computation.
*/
function BestAvailableLocaleIgnoringDefault(availableLocales, locale) {
return BestAvailableLocaleHelper(availableLocales, locale, false);
return intl_BestAvailableLocale(availableLocales, locale, null);
}
/**

View file

@ -34,7 +34,6 @@ using JS::TimeClip;
using js::intl::CallICU;
using js::intl::DateTimeFormatOptions;
using js::intl::GetAvailableLocales;
using js::intl::IcuLocale;
using js::intl::INITIAL_CHAR_BUFFER_SIZE;
using js::intl::SharedIntlData;
@ -228,19 +227,6 @@ js::AddMozDateTimeFormatConstructor(JSContext* cx, JS::Handle<JSObject*> intl)
return mozDateTimeFormatProto != nullptr;
}
bool
js::intl_DateTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0);
RootedValue result(cx);
if (!GetAvailableLocales(cx, udat_countAvailable, udat_getAvailable, &result))
return false;
args.rval().set(result);
return true;
}
static bool
DefaultCalendar(JSContext* cx, const JSAutoByteString& locale, MutableHandleValue rval)
{

View file

@ -54,17 +54,6 @@ CreateDateTimeFormatPrototype(JSContext* cx, JS::Handle<JSObject*> Intl,
extern MOZ_MUST_USE bool
intl_DateTimeFormat(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns an object indicating the supported locales for date and time
* formatting by having a true-valued property for each such locale with the
* canonicalized language tag as the property name. The object has no
* prototype.
*
* Usage: availableLocales = intl_DateTimeFormat_availableLocales()
*/
extern MOZ_MUST_USE bool
intl_DateTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns an array with the calendar type identifiers per Unicode
* Technical Standard 35, Unicode Locale Data Markup Language, for the

View file

@ -62,7 +62,7 @@ function resolveDateTimeFormatInternals(lazyDateTimeFormatData) {
var localeData = DateTimeFormat.localeData;
// Step 11.
var r = ResolveLocale(callFunction(DateTimeFormat.availableLocales, DateTimeFormat),
var r = ResolveLocale("DateTimeFormat",
lazyDateTimeFormatData.requestedLocales,
lazyDateTimeFormatData.localeOpt,
DateTimeFormat.relevantExtensionKeys,
@ -852,8 +852,7 @@ function Intl_DateTimeFormat_supportedLocalesOf(locales /*, options*/) {
var options = arguments.length > 1 ? arguments[1] : undefined;
// Step 1.
var availableLocales = callFunction(dateTimeFormatInternalProperties.availableLocales,
dateTimeFormatInternalProperties);
var availableLocales = "DateTimeFormat";
// Step 2.
var requestedLocales = CanonicalizeLocaleList(locales);
@ -870,17 +869,6 @@ function Intl_DateTimeFormat_supportedLocalesOf(locales /*, options*/) {
*/
var dateTimeFormatInternalProperties = {
localeData: dateTimeFormatLocaleData,
_availableLocales: null,
availableLocales: function()
{
var locales = this._availableLocales;
if (locales)
return locales;
locales = intl_DateTimeFormat_availableLocales();
addSpecialMissingLanguageTags(locales);
return (this._availableLocales = locales);
},
relevantExtensionKeys: ["ca", "nu", "hc"]
};

View file

@ -13,6 +13,7 @@
#include "unicode/udatpg.h"
#include "unicode/udisplaycontext.h"
#include "unicode/uenum.h"
#include "unicode/uloc.h"
#include "unicode/unum.h"
#include "unicode/unumsys.h"
#include "unicode/upluralrules.h"

View file

@ -11,6 +11,9 @@
#include "mozilla/Likely.h"
#include "mozilla/Range.h"
#include <algorithm>
#include <iterator>
#include "jsapi.h"
#include "jscntxt.h"
#include "jsobj.h"
@ -18,12 +21,15 @@
#include "builtin/intl/Collator.h"
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/DateTimeFormat.h"
#include "builtin/intl/LanguageTag.h"
#include "builtin/intl/ICUHeader.h"
#include "builtin/intl/Locale.h"
#include "builtin/intl/NumberFormat.h"
#include "builtin/intl/PluralRules.h"
#include "builtin/intl/RelativeTimeFormat.h"
#include "builtin/intl/ScopedICUObject.h"
#include "builtin/intl/SharedIntlData.h"
#include "js/Result.h"
#include "vm/GlobalObject.h"
#include "jsobjinlines.h"
@ -32,7 +38,6 @@ using namespace js;
using js::intl::CallICU;
using js::intl::DateTimeFormatOptions;
using js::intl::GetAvailableLocales;
using js::intl::IcuLocale;
using js::intl::INITIAL_CHAR_BUFFER_SIZE;
using js::intl::StringsAreEqual;
@ -407,6 +412,275 @@ js::intl_ComputeDisplayNames(JSContext* cx, unsigned argc, Value* vp)
return true;
}
using SupportedLocaleKind = js::intl::SharedIntlData::SupportedLocaleKind;
// 9.2.2 BestAvailableLocale ( availableLocales, locale )
static JS::Result<JSString*>
BestAvailableLocale(JSContext* cx, SupportedLocaleKind kind, HandleLinearString locale,
HandleLinearString defaultLocale)
{
// In the spec, [[availableLocales]] is formally a list of all available
// locales. But in our implementation, it's an *incomplete* list, not
// necessarily including the default locale (and all locales implied by it,
// e.g. "de" implied by "de-CH"), if that locale isn't in every
// [[availableLocales]] list (because that locale is supported through
// fallback, e.g. "de-CH" supported through "de").
//
// If we're considering the default locale, augment the spec loop with
// additional checks to also test whether the current prefix is a prefix of
// the default locale.
intl::SharedIntlData& sharedIntlData = cx->sharedIntlData;
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 corresponding forward iterator value, because reserve iterators point
// to one element past the forward iterator value.
return r - 1;
};
// Step 1.
RootedLinearString candidate(cx, locale);
// Step 2.
while (true) {
// Step 2.a.
bool supported = false;
if (!sharedIntlData.isSupportedLocale(cx, kind, candidate, &supported)) {
return cx->alreadyReportedError();
}
if (supported) {
return candidate.get();
}
if (defaultLocale && candidate->length() <= defaultLocale->length()) {
if (EqualStrings(candidate, defaultLocale)) {
return candidate.get();
}
if (candidate->length() < defaultLocale->length() &&
HasSubstringAt(defaultLocale, candidate, 0) &&
defaultLocale->latin1OrTwoByteChar(candidate->length()) == '-') {
return candidate.get();
}
}
// Step 2.b.
ptrdiff_t pos;
if (candidate->hasLatin1Chars()) {
JS::AutoCheckCannotGC nogc;
pos = findLast(candidate->latin1Chars(nogc), candidate->length());
} else {
JS::AutoCheckCannotGC nogc;
pos = findLast(candidate->twoByteChars(nogc), candidate->length());
}
if (pos < 0) {
return nullptr;
}
// Step 2.c.
size_t length = size_t(pos);
if (length >= 2 && candidate->latin1OrTwoByteChar(length - 2) == '-') {
length -= 2;
}
// Step 2.d.
candidate = NewDependentString(cx, candidate, 0, length);
if (!candidate) {
return cx->alreadyReportedError();
}
}
}
// 9.2.2 BestAvailableLocale ( availableLocales, locale )
//
// Carries an additional third argument in our implementation to provide the
// default locale. See the doc-comment in the header file.
bool
js::intl_BestAvailableLocale(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 3);
SupportedLocaleKind kind;
{
JSLinearString* typeStr = args[0].toString()->ensureLinear(cx);
if (!typeStr) {
return false;
}
if (StringEqualsAscii(typeStr, "Collator")) {
kind = SupportedLocaleKind::Collator;
} else if (StringEqualsAscii(typeStr, "DateTimeFormat")) {
kind = SupportedLocaleKind::DateTimeFormat;
} else if (StringEqualsAscii(typeStr, "NumberFormat")) {
kind = SupportedLocaleKind::NumberFormat;
} else if (StringEqualsAscii(typeStr, "PluralRules")) {
kind = SupportedLocaleKind::PluralRules;
} else {
MOZ_ASSERT(StringEqualsAscii(typeStr, "RelativeTimeFormat"));
kind = SupportedLocaleKind::RelativeTimeFormat;
}
}
RootedLinearString locale(cx, args[1].toString()->ensureLinear(cx));
if (!locale) {
return false;
}
#ifdef DEBUG
{
intl::LanguageTag tag(cx);
bool ok;
JS_TRY_VAR_OR_RETURN_FALSE(cx, ok, intl::LanguageTagParser::tryParse(cx, locale, tag));
MOZ_ASSERT(ok, "locale is a structurally valid language tag");
auto isUnicodeExtension = [](const auto& extension) {
return extension[0] == 'u';
};
MOZ_ASSERT(std::none_of(tag.extensions().begin(), tag.extensions().end(), isUnicodeExtension),
"locale must contain no Unicode extensions");
if (!tag.canonicalize(cx, intl::LanguageTag::UnicodeExtensionCanonicalForm::No)) {
return false;
}
JSString* tagStr = tag.toString(cx);
if (!tagStr) {
return false;
}
bool canonical;
if (!EqualStrings(cx, locale, tagStr, &canonical)) {
return false;
}
MOZ_ASSERT(canonical, "locale is a canonicalized language tag");
}
#endif
MOZ_ASSERT(args[2].isNull() || args[2].isString());
RootedLinearString defaultLocale(cx);
if (args[2].isString()) {
defaultLocale = args[2].toString()->ensureLinear(cx);
if (!defaultLocale) {
return false;
}
}
JSString* result;
JS_TRY_VAR_OR_RETURN_FALSE(cx, result, BestAvailableLocale(cx, kind, locale, defaultLocale));
if (result) {
args.rval().setString(result);
} else {
args.rval().setUndefined();
}
return true;
}
bool
js::intl_supportedLocaleOrFallback(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
RootedLinearString locale(cx, args[0].toString()->ensureLinear(cx));
if (!locale) {
return false;
}
intl::LanguageTag tag(cx);
bool ok;
JS_TRY_VAR_OR_RETURN_FALSE(cx, ok, intl::LanguageTagParser::tryParse(cx, locale, tag));
RootedLinearString candidate(cx);
if (!ok) {
candidate = NewStringCopyZ<CanGC>(cx, intl::LastDitchLocale());
if (!candidate) {
return false;
}
} else {
if (!tag.canonicalize(cx, intl::LanguageTag::UnicodeExtensionCanonicalForm::No)) {
return false;
}
// The default locale must be in [[AvailableLocales]], and that list must
// not contain any locales with Unicode extension sequences, so remove any
// present in the candidate.
tag.clearUnicodeExtension();
JSString* canonical = tag.toString(cx);
if (!canonical) {
return false;
}
candidate = canonical->ensureLinear(cx);
if (!candidate) {
return false;
}
for (const auto& mapping : js::intl::oldStyleLanguageTagMappings) {
const char* oldStyle = mapping.oldStyle;
const char* modernStyle = mapping.modernStyle;
if (StringEqualsAscii(candidate, oldStyle)) {
candidate = NewStringCopyZ<CanGC>(cx, modernStyle);
if (!candidate) {
return false;
}
break;
}
}
}
// 9.1 Internal slots of Service Constructors
//
// - [[AvailableLocales]] is a List [...]. The list must include the value
// returned by the DefaultLocale abstract operation (6.2.4), [...].
//
// That implies we must ignore any candidate which isn't supported by all Intl
// service constructors.
//
// Note: We don't test the supported locales of either Intl.PluralRules or
// Intl.RelativeTimeFormat, because ICU doesn't provide the necessary API to
// return actual set of supported locales for these constructors. Instead it
// returns the complete set of available locales for ULocale, which is a
// superset of the locales supported by Collator, NumberFormat, and
// DateTimeFormat.
bool isSupported = true;
for (auto kind : {SupportedLocaleKind::Collator, SupportedLocaleKind::DateTimeFormat,
SupportedLocaleKind::NumberFormat}) {
JSString* supported;
JS_TRY_VAR_OR_RETURN_FALSE(cx, supported, BestAvailableLocale(cx, kind, candidate, nullptr));
if (!supported) {
isSupported = false;
break;
}
}
if (!isSupported) {
candidate = NewStringCopyZ<CanGC>(cx, intl::LastDitchLocale());
if (!candidate) {
return false;
}
}
args.rval().setString(candidate);
return true;
}
const Class js::IntlClass = {
js_Object_str,
JSCLASS_HAS_CACHED_PROTO(JSProto_Intl)

View file

@ -95,6 +95,35 @@ intl_GetCalendarInfo(JSContext* cx, unsigned argc, JS::Value* vp);
extern MOZ_MUST_USE bool
intl_ComputeDisplayNames(JSContext* cx, unsigned argc, JS::Value* vp);
/**
* Compares a BCP 47 language tag against the locales in availableLocales and
* returns the best available match -- or |undefined| 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 |defaultOrNull|; otherwise pass null.
*
* Spec: ECMAScript Internationalization API Specification, 9.2.2.
* Spec: RFC 4647, section 3.4.
*
* Usage: result = intl_BestAvailableLocale("Collator", locale, defaultOrNull)
*/
extern MOZ_MUST_USE bool
intl_BestAvailableLocale(JSContext* cx, unsigned argc, JS::Value* vp);
/**
* Returns the input locale in its canonicalized form if ICU supports that
* locale (perhaps via fallback, e.g. supporting "de-ZA" through "de" support
* implied by a "de-DE" locale). Otherwise uses the last-ditch locale.
*
* Usage: result = intl_supportedLocaleOrFallback(locale)
*/
extern MOZ_MUST_USE bool
intl_supportedLocaleOrFallback(JSContext* cx, unsigned argc, JS::Value* vp);
} // namespace js
#endif /* builtin_intl_IntlObject_h */

View file

@ -58,7 +58,7 @@ function Intl_getCalendarInfo(locales) {
// 5. Let r be ResolveLocale(%DateTimeFormat%.[[availableLocales]],
// requestedLocales, localeOpt,
// %DateTimeFormat%.[[relevantExtensionKeys]], localeData).
const r = ResolveLocale(callFunction(DateTimeFormat.availableLocales, DateTimeFormat),
const r = ResolveLocale("DateTimeFormat",
requestedLocales,
localeOpt,
DateTimeFormat.relevantExtensionKeys,
@ -129,11 +129,11 @@ function Intl_getDisplayNames(locales, options) {
// 7. Let r be ResolveLocale(%DateTimeFormat%.[[availableLocales]], requestedLocales, localeOpt,
// %DateTimeFormat%.[[relevantExtensionKeys]], localeData).
const r = ResolveLocale(callFunction(DateTimeFormat.availableLocales, DateTimeFormat),
requestedLocales,
localeOpt,
DateTimeFormat.relevantExtensionKeys,
localeData);
const r = ResolveLocale("DateTimeFormat",
requestedLocales,
localeOpt,
DateTimeFormat.relevantExtensionKeys,
localeData);
// 8. Let style be ? GetOption(options, "style", "string", « "long", "short", "narrow" », "long").
const style = GetOption(options, "style", "string", ["long", "short", "narrow"], "long");

View file

@ -199,6 +199,15 @@ bool LanguageTag::setUnicodeExtension(UniqueChars extension) {
return extensions_.append(std::move(extension));
}
void LanguageTag::clearUnicodeExtension() {
auto p = std::find_if(extensions().begin(), extensions().end(),
[](const auto& ext) { return ext[0] == 'u'; });
if (p != extensions().end()) {
size_t index = std::distance(extensions().begin(), p);
extensions_.erase(extensions_.begin() + index);
}
}
template <size_t InitialCapacity>
static bool SortAlphabetically(JSContext* cx,
Vector<UniqueChars, InitialCapacity>& subtags) {
@ -754,8 +763,13 @@ bool LanguageTag::canonicalizeTransformExtension(
return true;
}
bool LanguageTag::appendTo(JSContext* cx, StringBuffer& sb) const {
return LanguageTagToString(cx, *this, sb);
JSString* LanguageTag::toString(JSContext* cx) const {
StringBuffer sb(cx);
if (!LanguageTagToString(cx, *this, sb)) {
return nullptr;
}
return sb.finishString();
}
// Zero-terminated ICU Locale ID.

View file

@ -34,8 +34,6 @@ class JSString;
namespace js {
class StringBuffer;
namespace intl {
#ifdef DEBUG
@ -315,6 +313,11 @@ class MOZ_STACK_CLASS LanguageTag final {
*/
bool setUnicodeExtension(JS::UniqueChars extension);
/**
* Remove any Unicode extension subtag if present.
*/
void clearUnicodeExtension();
/**
* Set the private-use subtag. The input must be a valid, case-normalized
* private-use subtag or the empty string.
@ -374,10 +377,9 @@ class MOZ_STACK_CLASS LanguageTag final {
}
/**
* Append the string representation of this language tag to the given
* string buffer.
* Return the string representation of this language tag.
*/
bool appendTo(JSContext* cx, StringBuffer& sb) const;
JSString* toString(JSContext* cx) const;
/**
* Add likely-subtags to the language tag.

View file

@ -106,12 +106,7 @@ static LocaleObject* CreateLocaleObject(JSContext* cx, HandleObject prototype,
}
}
StringBuffer sb(cx);
if (!tag.appendTo(cx, sb)) {
return nullptr;
}
RootedString tagStr(cx, sb.finishString());
RootedString tagStr(cx, tag.toString(cx));
if (!tagStr) {
return nullptr;
}
@ -1320,12 +1315,7 @@ bool js::intl_ValidateAndCanonicalizeLanguageTag(JSContext* cx, unsigned argc,
return false;
}
StringBuffer sb(cx);
if (!tag.appendTo(cx, sb)) {
return false;
}
JSString* resultStr = sb.finishString();
JSString* resultStr = tag.toString(cx);
if (!resultStr) {
return false;
}
@ -1358,12 +1348,7 @@ bool js::intl_TryValidateAndCanonicalizeLanguageTag(JSContext* cx,
return false;
}
StringBuffer sb(cx);
if (!tag.appendTo(cx, sb)) {
return false;
}
JSString* resultStr = sb.finishString();
JSString* resultStr = tag.toString(cx);
if (!resultStr) {
return false;
}

View file

@ -36,7 +36,6 @@ using mozilla::IsNaN;
using mozilla::IsNegativeZero;
using js::intl::CallICU;
using js::intl::DateTimeFormatOptions;
using js::intl::GetAvailableLocales;
using js::intl::IcuLocale;
using js::intl::INITIAL_CHAR_BUFFER_SIZE;
using js::intl::StringsAreEqual;
@ -203,19 +202,6 @@ js::CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalO
return proto;
}
bool
js::intl_NumberFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0);
RootedValue result(cx);
if (!GetAvailableLocales(cx, unum_countAvailable, unum_getAvailable, &result))
return false;
args.rval().set(result);
return true;
}
bool
js::intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp)
{

View file

@ -50,17 +50,6 @@ CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObjec
extern MOZ_MUST_USE bool
intl_NumberFormat(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns an object indicating the supported locales for number formatting
* by having a true-valued property for each such locale with the
* canonicalized language tag as the property name. The object has no
* prototype.
*
* Usage: availableLocales = intl_NumberFormat_availableLocales()
*/
extern MOZ_MUST_USE bool
intl_NumberFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns the numbering system type identifier per Unicode
* Technical Standard 35, Unicode Locale Data Markup Language, for the

View file

@ -12,17 +12,6 @@
*/
var numberFormatInternalProperties = {
localeData: numberFormatLocaleData,
_availableLocales: null,
availableLocales: function()
{
var locales = this._availableLocales;
if (locales)
return locales;
locales = intl_NumberFormat_availableLocales();
addSpecialMissingLanguageTags(locales);
return (this._availableLocales = locales);
},
relevantExtensionKeys: ["nu"]
};
@ -43,7 +32,7 @@ function resolveNumberFormatInternals(lazyNumberFormatData) {
var localeData = NumberFormat.localeData;
// Step 8.
var r = ResolveLocale(callFunction(NumberFormat.availableLocales, NumberFormat),
var r = ResolveLocale("NumberFormat",
lazyNumberFormatData.requestedLocales,
lazyNumberFormatData.opt,
NumberFormat.relevantExtensionKeys,
@ -396,8 +385,7 @@ function Intl_NumberFormat_supportedLocalesOf(locales /*, options*/) {
var options = arguments.length > 1 ? arguments[1] : undefined;
// Step 1.
var availableLocales = callFunction(numberFormatInternalProperties.availableLocales,
numberFormatInternalProperties);
var availableLocales = "NumberFormat";
// Step 2.
var requestedLocales = CanonicalizeLocaleList(locales);

View file

@ -28,7 +28,6 @@ using namespace js;
using mozilla::AssertedCast;
using js::intl::CallICU;
using js::intl::GetAvailableLocales;
using js::intl::IcuLocale;
using js::intl::INITIAL_CHAR_BUFFER_SIZE;
using js::intl::StringsAreEqual;
@ -180,21 +179,6 @@ js::CreatePluralRulesPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalOb
return proto;
}
bool
js::intl_PluralRules_availableLocales(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0);
RootedValue result(cx);
// We're going to use ULocale availableLocales as per ICU recommendation:
// https://ssl.icu-project.org/trac/ticket/12756
if (!GetAvailableLocales(cx, uloc_countAvailable, uloc_getAvailable, &result))
return false;
args.rval().set(result);
return true;
}
/**
*
* This creates new UNumberFormat with calculated digit formatting

View file

@ -49,17 +49,6 @@ CreatePluralRulesPrototype(JSContext* cx, JS::Handle<JSObject*> Intl,
extern MOZ_MUST_USE bool
intl_PluralRules(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns an object indicating the supported locales for plural rules
* by having a true-valued property for each such locale with the
* canonicalized language tag as the property name. The object has no
* prototype.
*
* Usage: availableLocales = intl_PluralRules_availableLocales()
*/
extern MOZ_MUST_USE bool
intl_PluralRules_availableLocales(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns a plural rule for the number x according to the effective
* locale and the formatting options of the given PluralRules.

View file

@ -11,17 +11,6 @@
*/
var pluralRulesInternalProperties = {
localeData: pluralRulesLocaleData,
_availableLocales: null,
availableLocales: function()
{
var locales = this._availableLocales;
if (locales)
return locales;
locales = intl_PluralRules_availableLocales();
addSpecialMissingLanguageTags(locales);
return (this._availableLocales = locales);
},
relevantExtensionKeys: [],
};
@ -50,7 +39,7 @@ function resolvePluralRulesInternals(lazyPluralRulesData) {
var localeData = PluralRules.localeData;
// Step 11.
const r = ResolveLocale(callFunction(PluralRules.availableLocales, PluralRules),
const r = ResolveLocale("PluralRules",
lazyPluralRulesData.requestedLocales,
lazyPluralRulesData.opt,
PluralRules.relevantExtensionKeys,
@ -187,8 +176,8 @@ function Intl_PluralRules_supportedLocalesOf(locales /*, options*/) {
var options = arguments.length > 1 ? arguments[1] : undefined;
// Step 1.
var availableLocales = callFunction(pluralRulesInternalProperties.availableLocales,
pluralRulesInternalProperties);
var availableLocales = "PluralRules";
// Step 2.
let requestedLocales = CanonicalizeLocaleList(locales);

View file

@ -26,7 +26,6 @@ using mozilla::Range;
using mozilla::RangedPtr;
using js::intl::CallICU;
using js::intl::GetAvailableLocales;
using js::intl::IcuLocale;
using js::intl::INITIAL_CHAR_BUFFER_SIZE;
using js::intl::StringsAreEqual;
@ -164,22 +163,6 @@ js::CreateRelativeTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<G
return proto;
}
bool
js::intl_RelativeTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0);
RootedValue result(cx);
// We're going to use ULocale availableLocales as per ICU recommendation:
// https://ssl.icu-project.org/trac/ticket/12756
if (!GetAvailableLocales(cx, uloc_countAvailable, uloc_getAvailable, &result))
return false;
args.rval().set(result);
return true;
}
enum class RelativeTimeNumeric
{
/**

View file

@ -39,17 +39,6 @@ extern JSObject*
CreateRelativeTimeFormatPrototype(JSContext* cx, JS::Handle<JSObject*> Intl,
JS::Handle<GlobalObject*> global);
/**
* Returns an object indicating the supported locales for relative time format
* by having a true-valued property for each such locale with the
* canonicalized language tag as the property name. The object has no
* prototype.
*
* Usage: availableLocales = intl_RelativeTimeFormat_availableLocales()
*/
extern MOZ_MUST_USE bool
intl_RelativeTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns a relative time as a string formatted according to the effective
* locale and the formatting options of the given RelativeTimeFormat.

View file

@ -11,17 +11,6 @@
*/
var relativeTimeFormatInternalProperties = {
localeData: relativeTimeFormatLocaleData,
_availableLocales: null,
availableLocales: function() // eslint-disable-line object-shorthand
{
var locales = this._availableLocales;
if (locales)
return locales;
locales = intl_RelativeTimeFormat_availableLocales();
addSpecialMissingLanguageTags(locales);
return (this._availableLocales = locales);
},
relevantExtensionKeys: [],
};
@ -41,7 +30,7 @@ function resolveRelativeTimeFormatInternals(lazyRelativeTimeFormatData) {
var RelativeTimeFormat = relativeTimeFormatInternalProperties;
// Steps 7-8.
const r = ResolveLocale(callFunction(RelativeTimeFormat.availableLocales, RelativeTimeFormat),
const r = ResolveLocale("RelativeTimeFormat",
lazyRelativeTimeFormatData.requestedLocales,
lazyRelativeTimeFormatData.opt,
RelativeTimeFormat.relevantExtensionKeys,
@ -159,8 +148,8 @@ function Intl_RelativeTimeFormat_supportedLocalesOf(locales /*, options*/) {
var options = arguments.length > 1 ? arguments[1] : undefined;
// Step 1.
var availableLocales = callFunction(relativeTimeFormatInternalProperties.availableLocales,
relativeTimeFormatInternalProperties);
var availableLocales = "RelativeTimeFormat";
// Step 2.
let requestedLocales = CanonicalizeLocaleList(locales);

View file

@ -10,6 +10,7 @@
#include "mozilla/Assertions.h"
#include "mozilla/HashFunctions.h"
#include <algorithm>
#include <stdint.h>
#include "jsatom.h"
@ -21,6 +22,7 @@
#include "builtin/intl/ScopedICUObject.h"
#include "builtin/intl/TimeZoneDataGenerated.h"
#include "js/Utility.h"
#include "js/Vector.h"
using js::HashNumber;
using js::intl::StringsAreEqual;
@ -278,6 +280,13 @@ js::intl::SharedIntlData::LocaleHasher::Lookup::Lookup(JSLinearString* locale)
hash = mozilla::HashString(twoByteChars, length);
}
js::intl::SharedIntlData::LocaleHasher::Lookup::Lookup(const char* chars,
size_t length)
: js::intl::SharedIntlData::LinearStringLookup(chars, length)
{
hash = mozilla::HashString(latin1Chars, length);
}
bool
js::intl::SharedIntlData::LocaleHasher::match(Locale key, const Lookup& lookup)
{
@ -297,6 +306,173 @@ js::intl::SharedIntlData::LocaleHasher::match(Locale key, const Lookup& lookup)
return EqualChars(keyChars, lookup.twoByteChars, lookup.length);
}
bool
js::intl::SharedIntlData::getAvailableLocales(JSContext* cx, LocaleSet& locales,
CountAvailable countAvailable,
GetAvailable getAvailable)
{
auto addLocale = [cx, &locales](const char* locale, size_t length) {
JSAtom* atom = Atomize(cx, locale, length);
if (!atom)
return false;
LocaleHasher::Lookup lookup(atom);
LocaleSet::AddPtr p = locales.lookupForAdd(lookup);
// ICU shouldn't report any duplicate locales, but if it does, just
// ignore the duplicated locale.
if (!p && !locales.add(p, atom)) {
ReportOutOfMemory(cx);
return false;
}
return true;
};
js::Vector<char, 16> lang(cx);
int32_t count = countAvailable();
for (int32_t i = 0; i < count; i++) {
const char* locale = getAvailable(i);
size_t length = strlen(locale);
lang.clear();
if (!lang.append(locale, length))
return false;
std::replace(lang.begin(), lang.end(), '_', '-');
if (!addLocale(lang.begin(), length))
return false;
}
// Add old-style language tags without script code for locales that in current
// usage would include a script subtag. Also add an entry for the last-ditch
// locale, in case ICU doesn't directly support it (but does support it
// through fallback, e.g. supporting "en-GB" indirectly using "en" support).
// 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;
LocaleHasher::Lookup lookup(modernStyle, strlen(modernStyle));
if (locales.has(lookup)) {
if (!addLocale(oldStyle, strlen(oldStyle)))
return false;
}
}
// Also forcibly provide the last-ditch locale.
{
const char* lastDitch = intl::LastDitchLocale();
MOZ_ASSERT(strcmp(lastDitch, "en-GB") == 0);
#ifdef DEBUG
static constexpr char lastDitchParent[] = "en";
LocaleHasher::Lookup lookup(lastDitchParent, strlen(lastDitchParent));
MOZ_ASSERT(locales.has(lookup),
"shouldn't be a need to add every locale implied by the "
"last-ditch locale, merely just the last-ditch locale");
#endif
if (!addLocale(lastDitch, strlen(lastDitch)))
return false;
}
return true;
}
#ifdef DEBUG
template <typename CountAvailable, typename GetAvailable>
static bool
IsSameAvailableLocales(CountAvailable countAvailable1,
GetAvailable getAvailable1,
CountAvailable countAvailable2,
GetAvailable getAvailable2)
{
int32_t count = countAvailable1();
if (count != countAvailable2()) {
return false;
}
for (int32_t i = 0; i < count; i++) {
if (getAvailable1(i) != getAvailable2(i)) {
return false;
}
}
return true;
}
#endif
bool
js::intl::SharedIntlData::ensureSupportedLocales(JSContext* cx)
{
if (supportedLocalesInitialized)
return true;
// If ensureSupportedLocales() was called previously, but didn't complete due
// to OOM, clear all data and start from scratch.
if (supportedLocales.initialized())
supportedLocales.finish();
if (collatorSupportedLocales.initialized())
collatorSupportedLocales.finish();
if (!supportedLocales.init() ||
!collatorSupportedLocales.init()) {
ReportOutOfMemory(cx);
return false;
}
if (!getAvailableLocales(cx, supportedLocales, uloc_countAvailable, uloc_getAvailable))
return false;
if (!getAvailableLocales(cx, collatorSupportedLocales, ucol_countAvailable, ucol_getAvailable))
return false;
MOZ_ASSERT(IsSameAvailableLocales(uloc_countAvailable, uloc_getAvailable,
udat_countAvailable, udat_getAvailable));
MOZ_ASSERT(IsSameAvailableLocales(uloc_countAvailable, uloc_getAvailable,
unum_countAvailable, unum_getAvailable));
MOZ_ASSERT(!supportedLocalesInitialized, "ensureSupportedLocales is neither reentrant nor thread-safe");
supportedLocalesInitialized = true;
return true;
}
bool
js::intl::SharedIntlData::isSupportedLocale(JSContext* cx,
SupportedLocaleKind kind,
HandleString locale,
bool* supported)
{
if (!ensureSupportedLocales(cx))
return false;
RootedLinearString localeLinear(cx, locale->ensureLinear(cx));
if (!localeLinear)
return false;
LocaleHasher::Lookup lookup(localeLinear);
switch (kind) {
case SupportedLocaleKind::Collator:
*supported = collatorSupportedLocales.has(lookup);
return true;
case SupportedLocaleKind::DateTimeFormat:
case SupportedLocaleKind::NumberFormat:
case SupportedLocaleKind::PluralRules:
case SupportedLocaleKind::RelativeTimeFormat:
*supported = supportedLocales.has(lookup);
return true;
}
MOZ_CRASH("Invalid Intl constructor");
return true;
}
bool
js::intl::SharedIntlData::ensureUpperCaseFirstLocales(JSContext* cx)
{
@ -393,6 +569,8 @@ js::intl::SharedIntlData::destroyInstance()
availableTimeZones.finish();
ianaZonesTreatedAsLinksByICU.finish();
ianaLinksCanonicalizedDifferentlyByICU.finish();
supportedLocales.finish();
collatorSupportedLocales.finish();
upperCaseFirstLocales.finish();
}
@ -404,6 +582,8 @@ js::intl::SharedIntlData::trace(JSTracer* trc)
availableTimeZones.trace(trc);
ianaZonesTreatedAsLinksByICU.trace(trc);
ianaLinksCanonicalizedDifferentlyByICU.trace(trc);
supportedLocales.trace(trc);
collatorSupportedLocales.trace(trc);
upperCaseFirstLocales.trace(trc);
}
}
@ -414,5 +594,7 @@ js::intl::SharedIntlData::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf
return availableTimeZones.sizeOfExcludingThis(mallocSizeOf) +
ianaZonesTreatedAsLinksByICU.sizeOfExcludingThis(mallocSizeOf) +
ianaLinksCanonicalizedDifferentlyByICU.sizeOfExcludingThis(mallocSizeOf) +
supportedLocales.sizeOfExcludingThis(mallocSizeOf) +
collatorSupportedLocales.sizeOfExcludingThis(mallocSizeOf) +
upperCaseFirstLocales.sizeOfExcludingThis(mallocSizeOf);
}

View file

@ -49,6 +49,12 @@ class SharedIntlData
else
twoByteChars = string->twoByteChars(nogc);
}
LinearStringLookup(const char* chars, size_t length)
: isLatin1(true), length(length)
{
latin1Chars = reinterpret_cast<const JS::Latin1Char*>(chars);
}
};
private:
@ -161,6 +167,76 @@ class SharedIntlData
*/
bool tryCanonicalizeTimeZoneConsistentWithIANA(JSContext* cx, JS::HandleString timeZone,
JS::MutableHandleString result);
private:
using Locale = JSAtom*;
struct LocaleHasher
{
struct Lookup : LinearStringLookup
{
explicit Lookup(JSLinearString* locale);
Lookup(const char* chars, size_t length);
};
static js::HashNumber hash(const Lookup& lookup) { return lookup.hash; }
static bool match(Locale key, const Lookup& lookup);
};
using LocaleSet = GCHashSet<Locale, LocaleHasher, SystemAllocPolicy>;
// Set of supported locales for all Intl service constructors except Collator,
// which uses its own set.
//
// UDateFormat:
// udat_[count,get]Available() return the same results as their
// uloc_[count,get]Available() counterparts.
//
// UNumberFormatter:
// unum_[count,get]Available() return the same results as their
// uloc_[count,get]Available() counterparts.
//
// UPluralRules and URelativeDateTimeFormatter:
// We're going to use ULocale availableLocales as per ICU recommendation:
// https://unicode-org.atlassian.net/browse/ICU-12756
LocaleSet supportedLocales;
// ucol_[count,get]Available() return different results compared to
// uloc_[count,get]Available(), we can't use |supportedLocales| here.
LocaleSet collatorSupportedLocales;
bool supportedLocalesInitialized = false;
// CountAvailable and GetAvailable describe the signatures used for ICU API
// to determine available locales for various functionality.
using CountAvailable = int32_t (*)();
using GetAvailable = const char* (*)(int32_t localeIndex);
static bool getAvailableLocales(JSContext* cx, LocaleSet& locales,
CountAvailable countAvailable,
GetAvailable getAvailable);
/**
* Precomputes the available locales sets.
*/
bool ensureSupportedLocales(JSContext* cx);
public:
enum class SupportedLocaleKind {
Collator,
DateTimeFormat,
NumberFormat,
PluralRules,
RelativeTimeFormat
};
/**
* Sets |supported| to true if |locale| is supported by the requested Intl
* service constructor. Otherwise sets |supported| to false.
*/
MOZ_MUST_USE bool isSupportedLocale(JSContext* cx, SupportedLocaleKind kind,
JS::Handle<JSString*> locale,
bool* supported);
private:
/**
* The case first parameter (BCP47 key "kf") allows to switch the order of
@ -178,23 +254,6 @@ class SharedIntlData
* track locales which use upper-case first as their default setting.
*/
using Locale = JSAtom*;
struct LocaleHasher
{
struct Lookup : LinearStringLookup
{
explicit Lookup(JSLinearString* locale);
};
static js::HashNumber hash(const Lookup& lookup) { return lookup.hash; }
static bool match(Locale key, const Lookup& lookup);
};
using LocaleSet = js::GCHashSet<Locale,
LocaleHasher,
js::SystemAllocPolicy>;
LocaleSet upperCaseFirstLocales;
bool upperCaseFirstInitialized = false;

View file

@ -2462,12 +2462,12 @@ static const JSFunctionSpec intrinsic_functions[] = {
// See builtin/intl/*.h for descriptions of the intl_* functions.
JS_FN("intl_availableCalendars", intl_availableCalendars, 1,0),
JS_FN("intl_availableCollations", intl_availableCollations, 1,0),
JS_FN("intl_BestAvailableLocale", intl_BestAvailableLocale, 3, 0),
JS_FN("intl_supportedLocaleOrFallback", intl_supportedLocaleOrFallback, 1, 0),
JS_FN("intl_canonicalizeTimeZone", intl_canonicalizeTimeZone, 1,0),
JS_FN("intl_Collator", intl_Collator, 2,0),
JS_FN("intl_Collator_availableLocales", intl_Collator_availableLocales, 0,0),
JS_FN("intl_CompareStrings", intl_CompareStrings, 3,0),
JS_FN("intl_DateTimeFormat", intl_DateTimeFormat, 2,0),
JS_FN("intl_DateTimeFormat_availableLocales", intl_DateTimeFormat_availableLocales, 0,0),
JS_FN("intl_defaultCalendar", intl_defaultCalendar, 1,0),
JS_FN("intl_defaultTimeZone", intl_defaultTimeZone, 0,0),
JS_FN("intl_defaultTimeZoneOffset", intl_defaultTimeZoneOffset, 0,0),
@ -2478,18 +2478,15 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("intl_isUpperCaseFirst", intl_isUpperCaseFirst, 1,0),
JS_FN("intl_IsValidTimeZoneName", intl_IsValidTimeZoneName, 1,0),
JS_FN("intl_NumberFormat", intl_NumberFormat, 2,0),
JS_FN("intl_NumberFormat_availableLocales", intl_NumberFormat_availableLocales, 0,0),
JS_FN("intl_numberingSystem", intl_numberingSystem, 1,0),
JS_FN("intl_patternForSkeleton", intl_patternForSkeleton, 2,0),
JS_FN("intl_patternForStyle", intl_patternForStyle, 3,0),
JS_FN("intl_PluralRules_availableLocales", intl_PluralRules_availableLocales, 0,0),
JS_FN("intl_GetPluralCategories", intl_GetPluralCategories, 2, 0),
JS_FN("intl_SelectPluralRule", intl_SelectPluralRule, 2,0),
JS_FN("intl_toLocaleLowerCase", intl_toLocaleLowerCase, 2,0),
JS_FN("intl_toLocaleUpperCase", intl_toLocaleUpperCase, 2,0),
JS_FN("intl_ValidateAndCanonicalizeLanguageTag", intl_ValidateAndCanonicalizeLanguageTag, 2, 0),
JS_FN("intl_TryValidateAndCanonicalizeLanguageTag", intl_TryValidateAndCanonicalizeLanguageTag, 1, 0),
JS_FN("intl_RelativeTimeFormat_availableLocales", intl_RelativeTimeFormat_availableLocales, 0,0),
JS_FN("intl_FormatRelativeTime", intl_FormatRelativeTime, 3,0),
JS_INLINABLE_FN("IsCollator",