tor-browser

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

commit 4a17a424d0a010673a97d3f74cb209d43ec1c390
parent be0b07601c8c685e12fa8841f41b4d19f399085c
Author: André Bargull <andre.bargull@gmail.com>
Date:   Tue, 16 Dec 2025 18:23:27 +0000

Bug 2005531 - Part 3: Add separate LocaleNegotiation files. r=spidermonkey-reviewers,dminor

Add "LocaleNegotiation.{h,cpp}" for locale negotation [1] related operations. Later
patches will add more functions to this file.

[1] https://tc39.es/ecma402/#locale-and-parameter-negotiation

Differential Revision: https://phabricator.services.mozilla.com/D276021

Diffstat:
Mjs/src/builtin/intl/GlobalIntlData.cpp | 2+-
Mjs/src/builtin/intl/IntlObject.cpp | 277++-----------------------------------------------------------------------------
Mjs/src/builtin/intl/IntlObject.h | 11-----------
Ajs/src/builtin/intl/LocaleNegotiation.cpp | 304+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ajs/src/builtin/intl/LocaleNegotiation.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
Mjs/src/moz.build | 1+
6 files changed, 360 insertions(+), 282 deletions(-)

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/IntlObject.h" +#include "builtin/intl/LocaleNegotiation.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,7 +12,6 @@ #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" @@ -24,18 +23,15 @@ #include "builtin/Array.h" #include "builtin/intl/CommonFunctions.h" -#include "builtin/intl/FormatBuffer.h" +#include "builtin/intl/LocaleNegotiation.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" @@ -116,103 +112,6 @@ 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 AvailableLocaleKind = js::intl::AvailableLocaleKind; - -// 9.2.2 BestAvailableLocale ( availableLocales, locale ) -static JS::Result<JSLinearString*> BestAvailableLocale( - JSContext* cx, AvailableLocaleKind 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.isAvailableLocale(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 @@ -221,6 +120,8 @@ 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; { JSLinearString* typeStr = args[0].toString()->ensureLinear(cx); @@ -255,54 +156,6 @@ 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); @@ -313,10 +166,10 @@ bool js::intl_BestAvailableLocale(JSContext* cx, unsigned argc, Value* vp) { } } - JSString* result; - JS_TRY_VAR_OR_RETURN_FALSE( - cx, result, BestAvailableLocale(cx, kind, locale, defaultLocale)); - + Rooted<JSLinearString*> result(cx); + if (!intl::BestAvailableLocale(cx, kind, locale, defaultLocale, &result)) { + return false; + } if (result) { args.rval().setString(result); } else { @@ -325,122 +178,6 @@ 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, 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()); -} - using StringList = GCVector<JSLinearString*>; /** diff --git a/js/src/builtin/intl/IntlObject.h b/js/src/builtin/intl/IntlObject.h @@ -67,17 +67,6 @@ 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/LocaleNegotiation.cpp b/js/src/builtin/intl/LocaleNegotiation.cpp @@ -0,0 +1,304 @@ +/* -*- 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 "builtin/intl/FormatBuffer.h" +#include "builtin/intl/SharedIntlData.h" +#include "builtin/intl/StringAsciiChars.h" +#include "js/Result.h" +#include "vm/JSContext.h" +#include "vm/StringType.h" + +using namespace js; +using namespace js::intl; + +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 ) +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; + }; + + // 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) { +#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 + + 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; +} + +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 @@ -0,0 +1,47 @@ +/* -*- 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::intl { + +enum class AvailableLocaleKind; + +/** + * 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 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/moz.build b/js/src/moz.build @@ -489,6 +489,7 @@ if CONFIG["JS_HAS_INTL_API"]: "builtin/intl/LanguageTag.cpp", "builtin/intl/ListFormat.cpp", "builtin/intl/Locale.cpp", + "builtin/intl/LocaleNegotiation.cpp", "builtin/intl/NumberFormat.cpp", "builtin/intl/PluralRules.cpp", "builtin/intl/RelativeTimeFormat.cpp",