summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/intl/SharedIntlData.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/builtin/intl/SharedIntlData.cpp')
-rw-r--r--js/src/builtin/intl/SharedIntlData.cpp848
1 files changed, 848 insertions, 0 deletions
diff --git a/js/src/builtin/intl/SharedIntlData.cpp b/js/src/builtin/intl/SharedIntlData.cpp
new file mode 100644
index 0000000000..8b382f22fb
--- /dev/null
+++ b/js/src/builtin/intl/SharedIntlData.cpp
@@ -0,0 +1,848 @@
+/* -*- 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/. */
+
+/* Runtime-wide Intl data shared across compartments. */
+
+#include "builtin/intl/SharedIntlData.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/intl/Collator.h"
+#include "mozilla/intl/DateTimeFormat.h"
+#include "mozilla/intl/DateTimePatternGenerator.h"
+#include "mozilla/intl/Locale.h"
+#include "mozilla/intl/NumberFormat.h"
+#include "mozilla/intl/TimeZone.h"
+#include "mozilla/Span.h"
+#include "mozilla/TextUtils.h"
+
+#include <algorithm>
+#include <stdint.h>
+#include <string>
+#include <string.h>
+#include <string_view>
+#include <utility>
+
+#include "builtin/Array.h"
+#include "builtin/intl/CommonFunctions.h"
+#include "builtin/intl/TimeZoneDataGenerated.h"
+#include "js/Utility.h"
+#include "js/Vector.h"
+#include "vm/ArrayObject.h"
+#include "vm/JSAtomUtils.h" // Atomize
+#include "vm/JSContext.h"
+#include "vm/StringType.h"
+
+using js::HashNumber;
+
+template <typename Char>
+static constexpr Char ToUpperASCII(Char c) {
+ return mozilla::IsAsciiLowercaseAlpha(c) ? (c - 0x20) : c;
+}
+
+static_assert(ToUpperASCII('a') == 'A', "verifying 'a' uppercases correctly");
+static_assert(ToUpperASCII('m') == 'M', "verifying 'm' uppercases correctly");
+static_assert(ToUpperASCII('z') == 'Z', "verifying 'z' uppercases correctly");
+static_assert(ToUpperASCII(u'a') == u'A',
+ "verifying u'a' uppercases correctly");
+static_assert(ToUpperASCII(u'k') == u'K',
+ "verifying u'k' uppercases correctly");
+static_assert(ToUpperASCII(u'z') == u'Z',
+ "verifying u'z' uppercases correctly");
+
+template <typename Char>
+static HashNumber HashStringIgnoreCaseASCII(const Char* s, size_t length) {
+ uint32_t hash = 0;
+ for (size_t i = 0; i < length; i++) {
+ hash = mozilla::AddToHash(hash, ToUpperASCII(s[i]));
+ }
+ return hash;
+}
+
+js::intl::SharedIntlData::TimeZoneHasher::Lookup::Lookup(
+ JSLinearString* timeZone)
+ : js::intl::SharedIntlData::LinearStringLookup(timeZone) {
+ if (isLatin1) {
+ hash = HashStringIgnoreCaseASCII(latin1Chars, length);
+ } else {
+ hash = HashStringIgnoreCaseASCII(twoByteChars, length);
+ }
+}
+
+template <typename Char1, typename Char2>
+static bool EqualCharsIgnoreCaseASCII(const Char1* s1, const Char2* s2,
+ size_t len) {
+ for (const Char1* s1end = s1 + len; s1 < s1end; s1++, s2++) {
+ if (ToUpperASCII(*s1) != ToUpperASCII(*s2)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool js::intl::SharedIntlData::TimeZoneHasher::match(TimeZoneName key,
+ const Lookup& lookup) {
+ if (key->length() != lookup.length) {
+ return false;
+ }
+
+ // Compare time zone names ignoring ASCII case differences.
+ if (key->hasLatin1Chars()) {
+ const Latin1Char* keyChars = key->latin1Chars(lookup.nogc);
+ if (lookup.isLatin1) {
+ return EqualCharsIgnoreCaseASCII(keyChars, lookup.latin1Chars,
+ lookup.length);
+ }
+ return EqualCharsIgnoreCaseASCII(keyChars, lookup.twoByteChars,
+ lookup.length);
+ }
+
+ const char16_t* keyChars = key->twoByteChars(lookup.nogc);
+ if (lookup.isLatin1) {
+ return EqualCharsIgnoreCaseASCII(lookup.latin1Chars, keyChars,
+ lookup.length);
+ }
+ return EqualCharsIgnoreCaseASCII(keyChars, lookup.twoByteChars,
+ lookup.length);
+}
+
+static bool IsLegacyICUTimeZone(mozilla::Span<const char> timeZone) {
+ std::string_view timeZoneView(timeZone.data(), timeZone.size());
+ for (const auto& legacyTimeZone : js::timezone::legacyICUTimeZones) {
+ if (timeZoneView == legacyTimeZone) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool js::intl::SharedIntlData::ensureTimeZones(JSContext* cx) {
+ if (timeZoneDataInitialized) {
+ return true;
+ }
+
+ // If ensureTimeZones() was called previously, but didn't complete due to
+ // OOM, clear all sets/maps and start from scratch.
+ availableTimeZones.clearAndCompact();
+
+ auto timeZones = mozilla::intl::TimeZone::GetAvailableTimeZones();
+ if (timeZones.isErr()) {
+ ReportInternalError(cx, timeZones.unwrapErr());
+ return false;
+ }
+
+ Rooted<JSAtom*> timeZone(cx);
+ for (auto timeZoneName : timeZones.unwrap()) {
+ if (timeZoneName.isErr()) {
+ ReportInternalError(cx);
+ return false;
+ }
+ auto timeZoneSpan = timeZoneName.unwrap();
+
+ // Skip legacy ICU time zone names.
+ if (IsLegacyICUTimeZone(timeZoneSpan)) {
+ continue;
+ }
+
+ timeZone = Atomize(cx, timeZoneSpan.data(), timeZoneSpan.size());
+ if (!timeZone) {
+ return false;
+ }
+
+ TimeZoneHasher::Lookup lookup(timeZone);
+ TimeZoneSet::AddPtr p = availableTimeZones.lookupForAdd(lookup);
+
+ // ICU shouldn't report any duplicate time zone names, but if it does,
+ // just ignore the duplicate name.
+ if (!p && !availableTimeZones.add(p, timeZone)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ }
+
+ ianaZonesTreatedAsLinksByICU.clearAndCompact();
+
+ for (const char* rawTimeZone : timezone::ianaZonesTreatedAsLinksByICU) {
+ MOZ_ASSERT(rawTimeZone != nullptr);
+ timeZone = Atomize(cx, rawTimeZone, strlen(rawTimeZone));
+ if (!timeZone) {
+ return false;
+ }
+
+ TimeZoneHasher::Lookup lookup(timeZone);
+ TimeZoneSet::AddPtr p = ianaZonesTreatedAsLinksByICU.lookupForAdd(lookup);
+ MOZ_ASSERT(!p, "Duplicate entry in timezone::ianaZonesTreatedAsLinksByICU");
+
+ if (!ianaZonesTreatedAsLinksByICU.add(p, timeZone)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ }
+
+ ianaLinksCanonicalizedDifferentlyByICU.clearAndCompact();
+
+ Rooted<JSAtom*> linkName(cx);
+ Rooted<JSAtom*>& target = timeZone;
+ for (const auto& linkAndTarget :
+ timezone::ianaLinksCanonicalizedDifferentlyByICU) {
+ const char* rawLinkName = linkAndTarget.link;
+ const char* rawTarget = linkAndTarget.target;
+
+ MOZ_ASSERT(rawLinkName != nullptr);
+ linkName = Atomize(cx, rawLinkName, strlen(rawLinkName));
+ if (!linkName) {
+ return false;
+ }
+
+ MOZ_ASSERT(rawTarget != nullptr);
+ target = Atomize(cx, rawTarget, strlen(rawTarget));
+ if (!target) {
+ return false;
+ }
+
+ TimeZoneHasher::Lookup lookup(linkName);
+ TimeZoneMap::AddPtr p =
+ ianaLinksCanonicalizedDifferentlyByICU.lookupForAdd(lookup);
+ MOZ_ASSERT(
+ !p,
+ "Duplicate entry in timezone::ianaLinksCanonicalizedDifferentlyByICU");
+
+ if (!ianaLinksCanonicalizedDifferentlyByICU.add(p, linkName, target)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(!timeZoneDataInitialized,
+ "ensureTimeZones is neither reentrant nor thread-safe");
+ timeZoneDataInitialized = true;
+
+ return true;
+}
+
+bool js::intl::SharedIntlData::validateTimeZoneName(
+ JSContext* cx, HandleString timeZone, MutableHandle<JSAtom*> result) {
+ if (!ensureTimeZones(cx)) {
+ return false;
+ }
+
+ Rooted<JSLinearString*> timeZoneLinear(cx, timeZone->ensureLinear(cx));
+ if (!timeZoneLinear) {
+ return false;
+ }
+
+ TimeZoneHasher::Lookup lookup(timeZoneLinear);
+ if (TimeZoneSet::Ptr p = availableTimeZones.lookup(lookup)) {
+ result.set(*p);
+ }
+
+ return true;
+}
+
+bool js::intl::SharedIntlData::tryCanonicalizeTimeZoneConsistentWithIANA(
+ JSContext* cx, HandleString timeZone, MutableHandle<JSAtom*> result) {
+ if (!ensureTimeZones(cx)) {
+ return false;
+ }
+
+ Rooted<JSLinearString*> timeZoneLinear(cx, timeZone->ensureLinear(cx));
+ if (!timeZoneLinear) {
+ return false;
+ }
+
+ TimeZoneHasher::Lookup lookup(timeZoneLinear);
+ MOZ_ASSERT(availableTimeZones.has(lookup), "Invalid time zone name");
+
+ if (TimeZoneMap::Ptr p =
+ ianaLinksCanonicalizedDifferentlyByICU.lookup(lookup)) {
+ // The effectively supported time zones aren't known at compile time,
+ // when
+ // 1. SpiderMonkey was compiled with "--with-system-icu".
+ // 2. ICU's dynamic time zone data loading feature was used.
+ // (ICU supports loading time zone files at runtime through the
+ // ICU_TIMEZONE_FILES_DIR environment variable.)
+ // Ensure ICU supports the new target zone before applying the update.
+ TimeZoneName targetTimeZone = p->value();
+ TimeZoneHasher::Lookup targetLookup(targetTimeZone);
+ if (availableTimeZones.has(targetLookup)) {
+ result.set(targetTimeZone);
+ }
+ } else if (TimeZoneSet::Ptr p = ianaZonesTreatedAsLinksByICU.lookup(lookup)) {
+ result.set(*p);
+ }
+
+ return true;
+}
+
+JS::Result<js::intl::SharedIntlData::TimeZoneSet::Iterator>
+js::intl::SharedIntlData::availableTimeZonesIteration(JSContext* cx) {
+ if (!ensureTimeZones(cx)) {
+ return cx->alreadyReportedError();
+ }
+ return availableTimeZones.iter();
+}
+
+js::intl::SharedIntlData::LocaleHasher::Lookup::Lookup(JSLinearString* locale)
+ : js::intl::SharedIntlData::LinearStringLookup(locale) {
+ if (isLatin1) {
+ hash = mozilla::HashString(latin1Chars, length);
+ } else {
+ 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) {
+ if (key->length() != lookup.length) {
+ return false;
+ }
+
+ if (key->hasLatin1Chars()) {
+ const Latin1Char* keyChars = key->latin1Chars(lookup.nogc);
+ if (lookup.isLatin1) {
+ return EqualChars(keyChars, lookup.latin1Chars, lookup.length);
+ }
+ return EqualChars(keyChars, lookup.twoByteChars, lookup.length);
+ }
+
+ const char16_t* keyChars = key->twoByteChars(lookup.nogc);
+ if (lookup.isLatin1) {
+ return EqualChars(lookup.latin1Chars, keyChars, lookup.length);
+ }
+ return EqualChars(keyChars, lookup.twoByteChars, lookup.length);
+}
+
+template <class AvailableLocales>
+bool js::intl::SharedIntlData::getAvailableLocales(
+ JSContext* cx, LocaleSet& locales,
+ const AvailableLocales& availableLocales) {
+ 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);
+
+ for (const char* locale : availableLocales) {
+ size_t length = strlen(locale);
+
+ lang.clear();
+ if (!lang.append(locale, length)) {
+ return false;
+ }
+ MOZ_ASSERT(lang.length() == length);
+
+ std::replace(lang.begin(), lang.end(), '_', '-');
+
+ if (!addLocale(lang.begin(), length)) {
+ return false;
+ }
+
+ // From <https://tc39.es/ecma402/#sec-internal-slots>:
+ //
+ // For locales that include a script subtag in addition to language and
+ // region, the corresponding locale without a script subtag must also be
+ // supported; that is, if an implementation recognizes "zh-Hant-TW", it is
+ // also expected to recognize "zh-TW".
+
+ // 2 * Alpha language subtag
+ // + 1 separator
+ // + 4 * Alphanum script subtag
+ // + 1 separator
+ // + 2 * Alpha region subtag
+ using namespace mozilla::intl::LanguageTagLimits;
+ static constexpr size_t MinLanguageLength = 2;
+ static constexpr size_t MinLengthForScriptAndRegion =
+ MinLanguageLength + 1 + ScriptLength + 1 + AlphaRegionLength;
+
+ // Fast case: Skip locales without script subtags.
+ if (length < MinLengthForScriptAndRegion) {
+ continue;
+ }
+
+ // We don't need the full-fledged language tag parser when we just want to
+ // remove the script subtag.
+
+ // Find the separator between the language and script subtags.
+ const char* sep = std::char_traits<char>::find(lang.begin(), length, '-');
+ if (!sep) {
+ continue;
+ }
+
+ // Possible |script| subtag start position.
+ const char* script = sep + 1;
+
+ // Find the separator between the script and region subtags.
+ sep = std::char_traits<char>::find(script, lang.end() - script, '-');
+ if (!sep) {
+ continue;
+ }
+
+ // Continue with the next locale if we didn't find a script subtag.
+ size_t scriptLength = sep - script;
+ if (!mozilla::intl::IsStructurallyValidScriptTag<char>(
+ {script, scriptLength})) {
+ continue;
+ }
+
+ // Possible |region| subtag start position.
+ const char* region = sep + 1;
+
+ // Search if there's yet another subtag after the region subtag.
+ sep = std::char_traits<char>::find(region, lang.end() - region, '-');
+
+ // Continue with the next locale if we didn't find a region subtag.
+ size_t regionLength = (sep ? sep : lang.end()) - region;
+ if (!mozilla::intl::IsStructurallyValidRegionTag<char>(
+ {region, regionLength})) {
+ continue;
+ }
+
+ // We've found a script and a region subtag.
+
+ static constexpr size_t ScriptWithSeparatorLength = ScriptLength + 1;
+
+ // Remove the script subtag. Note: erase() needs non-const pointers, which
+ // means we can't directly pass |script|.
+ char* p = const_cast<char*>(script);
+ lang.erase(p, p + ScriptWithSeparatorLength);
+
+ MOZ_ASSERT(lang.length() == length - ScriptWithSeparatorLength);
+
+ // Add the locale with the script subtag removed.
+ if (!addLocale(lang.begin(), lang.length())) {
+ return false;
+ }
+ }
+
+ // Forcibly 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).
+ {
+ 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 <class AvailableLocales1, class AvailableLocales2>
+static bool IsSameAvailableLocales(const AvailableLocales1& availableLocales1,
+ const AvailableLocales2& availableLocales2) {
+ return std::equal(std::begin(availableLocales1), std::end(availableLocales1),
+ std::begin(availableLocales2), std::end(availableLocales2),
+ [](const char* a, const char* b) {
+ // Intentionally comparing pointer equivalence.
+ return a == b;
+ });
+}
+#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.
+ supportedLocales.clearAndCompact();
+ collatorSupportedLocales.clearAndCompact();
+
+ if (!getAvailableLocales(cx, supportedLocales,
+ mozilla::intl::Locale::GetAvailableLocales())) {
+ return false;
+ }
+ if (!getAvailableLocales(cx, collatorSupportedLocales,
+ mozilla::intl::Collator::GetAvailableLocales())) {
+ return false;
+ }
+
+ MOZ_ASSERT(IsSameAvailableLocales(
+ mozilla::intl::Locale::GetAvailableLocales(),
+ mozilla::intl::DateTimeFormat::GetAvailableLocales()));
+
+ MOZ_ASSERT(IsSameAvailableLocales(
+ mozilla::intl::Locale::GetAvailableLocales(),
+ mozilla::intl::NumberFormat::GetAvailableLocales()));
+
+ 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;
+ }
+
+ Rooted<JSLinearString*> 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::DisplayNames:
+ 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, SupportedLocaleKind kind) {
+ if (!ensureSupportedLocales(cx)) {
+ return nullptr;
+ }
+
+ LocaleSet* localeSet = nullptr;
+ switch (kind) {
+ case SupportedLocaleKind::Collator:
+ localeSet = &collatorSupportedLocales;
+ break;
+ case SupportedLocaleKind::DateTimeFormat:
+ case SupportedLocaleKind::DisplayNames:
+ case SupportedLocaleKind::ListFormat:
+ case SupportedLocaleKind::NumberFormat:
+ case SupportedLocaleKind::PluralRules:
+ case SupportedLocaleKind::RelativeTimeFormat:
+ case SupportedLocaleKind::Segmenter:
+ localeSet = &supportedLocales;
+ break;
+ default:
+ MOZ_CRASH("Invalid Intl constructor");
+ }
+
+ const uint32_t count = localeSet->count();
+ ArrayObject* result = NewDenseFullyAllocatedArray(cx, count);
+ if (!result) {
+ return nullptr;
+ }
+ result->setDenseInitializedLength(count);
+
+ uint32_t index = 0;
+ for (auto range = localeSet->iter(); !range.done(); range.next()) {
+ JSAtom* locale = range.get();
+ cx->markAtom(locale);
+
+ result->initDenseElement(index++, StringValue(locale));
+ }
+ MOZ_ASSERT(index == count);
+
+ return result;
+}
+
+#if DEBUG || MOZ_SYSTEM_ICU
+bool js::intl::SharedIntlData::ensureUpperCaseFirstLocales(JSContext* cx) {
+ if (upperCaseFirstInitialized) {
+ return true;
+ }
+
+ // If ensureUpperCaseFirstLocales() was called previously, but didn't
+ // complete due to OOM, clear all data and start from scratch.
+ upperCaseFirstLocales.clearAndCompact();
+
+ Rooted<JSAtom*> locale(cx);
+ for (const char* rawLocale : mozilla::intl::Collator::GetAvailableLocales()) {
+ auto collator = mozilla::intl::Collator::TryCreate(rawLocale);
+ if (collator.isErr()) {
+ ReportInternalError(cx, collator.unwrapErr());
+ return false;
+ }
+
+ auto caseFirst = collator.unwrap()->GetCaseFirst();
+ if (caseFirst.isErr()) {
+ ReportInternalError(cx, caseFirst.unwrapErr());
+ return false;
+ }
+
+ if (caseFirst.unwrap() != mozilla::intl::Collator::CaseFirst::Upper) {
+ continue;
+ }
+
+ locale = Atomize(cx, rawLocale, strlen(rawLocale));
+ if (!locale) {
+ return false;
+ }
+
+ LocaleHasher::Lookup lookup(locale);
+ LocaleSet::AddPtr p = upperCaseFirstLocales.lookupForAdd(lookup);
+
+ // ICU shouldn't report any duplicate locales, but if it does, just
+ // ignore the duplicated locale.
+ if (!p && !upperCaseFirstLocales.add(p, locale)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(
+ !upperCaseFirstInitialized,
+ "ensureUpperCaseFirstLocales is neither reentrant nor thread-safe");
+ upperCaseFirstInitialized = true;
+
+ return true;
+}
+#endif // DEBUG || MOZ_SYSTEM_ICU
+
+bool js::intl::SharedIntlData::isUpperCaseFirst(JSContext* cx,
+ HandleString locale,
+ bool* isUpperFirst) {
+#if DEBUG || MOZ_SYSTEM_ICU
+ if (!ensureUpperCaseFirstLocales(cx)) {
+ return false;
+ }
+#endif
+
+ Rooted<JSLinearString*> localeLinear(cx, 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(localeLinear, "da") ||
+ js::StringEqualsLiteral(localeLinear, "mt");
+#endif
+
+#if DEBUG || MOZ_SYSTEM_ICU
+ LocaleHasher::Lookup lookup(localeLinear);
+ *isUpperFirst = upperCaseFirstLocales.has(lookup);
+#else
+ *isUpperFirst = isDefaultUpperCaseFirstLocale;
+#endif
+
+#if !MOZ_SYSTEM_ICU
+ MOZ_ASSERT(*isUpperFirst == isDefaultUpperCaseFirstLocale,
+ "upper-case first locales don't match hard-coded list");
+#endif
+
+ return true;
+}
+
+#if DEBUG || MOZ_SYSTEM_ICU
+bool js::intl::SharedIntlData::ensureIgnorePunctuationLocales(JSContext* cx) {
+ if (ignorePunctuationInitialized) {
+ return true;
+ }
+
+ // If ensureIgnorePunctuationLocales() was called previously, but didn't
+ // complete due to OOM, clear all data and start from scratch.
+ ignorePunctuationLocales.clearAndCompact();
+
+ Rooted<JSAtom*> locale(cx);
+ for (const char* rawLocale : mozilla::intl::Collator::GetAvailableLocales()) {
+ auto collator = mozilla::intl::Collator::TryCreate(rawLocale);
+ if (collator.isErr()) {
+ ReportInternalError(cx, collator.unwrapErr());
+ return false;
+ }
+
+ auto ignorePunctuation = collator.unwrap()->GetIgnorePunctuation();
+ if (ignorePunctuation.isErr()) {
+ ReportInternalError(cx, ignorePunctuation.unwrapErr());
+ return false;
+ }
+
+ if (!ignorePunctuation.unwrap()) {
+ continue;
+ }
+
+ locale = Atomize(cx, rawLocale, strlen(rawLocale));
+ if (!locale) {
+ return false;
+ }
+
+ LocaleHasher::Lookup lookup(locale);
+ LocaleSet::AddPtr p = ignorePunctuationLocales.lookupForAdd(lookup);
+
+ // ICU shouldn't report any duplicate locales, but if it does, just
+ // ignore the duplicated locale.
+ if (!p && !ignorePunctuationLocales.add(p, locale)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(
+ !ignorePunctuationInitialized,
+ "ensureIgnorePunctuationLocales is neither reentrant nor thread-safe");
+ ignorePunctuationInitialized = true;
+
+ return true;
+}
+#endif // DEBUG || MOZ_SYSTEM_ICU
+
+bool js::intl::SharedIntlData::isIgnorePunctuation(JSContext* cx,
+ HandleString locale,
+ bool* ignorePunctuation) {
+#if DEBUG || MOZ_SYSTEM_ICU
+ if (!ensureIgnorePunctuationLocales(cx)) {
+ return false;
+ }
+#endif
+
+ Rooted<JSLinearString*> localeLinear(cx, 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(localeLinear, "th");
+#endif
+
+#if DEBUG || MOZ_SYSTEM_ICU
+ LocaleHasher::Lookup lookup(localeLinear);
+ *ignorePunctuation = ignorePunctuationLocales.has(lookup);
+#else
+ *ignorePunctuation = isDefaultIgnorePunctuationLocale;
+#endif
+
+#if !MOZ_SYSTEM_ICU
+ MOZ_ASSERT(*ignorePunctuation == isDefaultIgnorePunctuationLocale,
+ "ignore punctuation locales don't match hard-coded list");
+#endif
+
+ return true;
+}
+
+void js::intl::DateTimePatternGeneratorDeleter::operator()(
+ mozilla::intl::DateTimePatternGenerator* ptr) {
+ delete ptr;
+}
+
+static bool StringsAreEqual(const char* s1, const char* s2) {
+ return !strcmp(s1, s2);
+}
+
+mozilla::intl::DateTimePatternGenerator*
+js::intl::SharedIntlData::getDateTimePatternGenerator(JSContext* cx,
+ const char* locale) {
+ // Return the cached instance if the requested locale matches the locale
+ // of the cached generator.
+ if (dateTimePatternGeneratorLocale &&
+ StringsAreEqual(dateTimePatternGeneratorLocale.get(), locale)) {
+ return dateTimePatternGenerator.get();
+ }
+
+ auto result = mozilla::intl::DateTimePatternGenerator::TryCreate(locale);
+ if (result.isErr()) {
+ intl::ReportInternalError(cx, result.unwrapErr());
+ return nullptr;
+ }
+ // The UniquePtr needs to be recreated as it's using a different Deleter in
+ // order to be able to forward declare DateTimePatternGenerator in
+ // SharedIntlData.h.
+ UniqueDateTimePatternGenerator gen(result.unwrap().release());
+
+ JS::UniqueChars localeCopy = js::DuplicateString(cx, locale);
+ if (!localeCopy) {
+ return nullptr;
+ }
+
+ dateTimePatternGenerator = std::move(gen);
+ dateTimePatternGeneratorLocale = std::move(localeCopy);
+
+ return dateTimePatternGenerator.get();
+}
+
+void js::intl::SharedIntlData::destroyInstance() {
+ availableTimeZones.clearAndCompact();
+ ianaZonesTreatedAsLinksByICU.clearAndCompact();
+ ianaLinksCanonicalizedDifferentlyByICU.clearAndCompact();
+ supportedLocales.clearAndCompact();
+ collatorSupportedLocales.clearAndCompact();
+#if DEBUG || MOZ_SYSTEM_ICU
+ upperCaseFirstLocales.clearAndCompact();
+ ignorePunctuationLocales.clearAndCompact();
+#endif
+}
+
+void js::intl::SharedIntlData::trace(JSTracer* trc) {
+ // Atoms are always tenured.
+ if (!JS::RuntimeHeapIsMinorCollecting()) {
+ availableTimeZones.trace(trc);
+ ianaZonesTreatedAsLinksByICU.trace(trc);
+ ianaLinksCanonicalizedDifferentlyByICU.trace(trc);
+ supportedLocales.trace(trc);
+ collatorSupportedLocales.trace(trc);
+#if DEBUG || MOZ_SYSTEM_ICU
+ upperCaseFirstLocales.trace(trc);
+ ignorePunctuationLocales.trace(trc);
+#endif
+ }
+}
+
+size_t js::intl::SharedIntlData::sizeOfExcludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ return availableTimeZones.shallowSizeOfExcludingThis(mallocSizeOf) +
+ ianaZonesTreatedAsLinksByICU.shallowSizeOfExcludingThis(mallocSizeOf) +
+ ianaLinksCanonicalizedDifferentlyByICU.shallowSizeOfExcludingThis(
+ mallocSizeOf) +
+ supportedLocales.shallowSizeOfExcludingThis(mallocSizeOf) +
+ collatorSupportedLocales.shallowSizeOfExcludingThis(mallocSizeOf) +
+#if DEBUG || MOZ_SYSTEM_ICU
+ upperCaseFirstLocales.shallowSizeOfExcludingThis(mallocSizeOf) +
+ ignorePunctuationLocales.shallowSizeOfExcludingThis(mallocSizeOf) +
+#endif
+ mallocSizeOf(dateTimePatternGeneratorLocale.get());
+}