From 5fb308d38c2cd843cd7d9e08f4204e090a1d91eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bargull?= Date: Fri, 18 Jun 2021 09:22:13 +0000 Subject: [PATCH] Bug 1715007 - Part 1: Add testing function to retrieve all available locales. r=tcampbell Add a testing function to retrieve all available locales in preparation to test the changes in part two. Differential Revision: https://phabricator.services.mozilla.com/D117016 --- js/src/builtin/TestingFunctions.cpp | 64 ++++++++++++++++++- js/src/builtin/intl/SharedIntlData.cpp | 45 +++++++++++++ js/src/builtin/intl/SharedIntlData.h | 7 ++ .../non262/Intl/available-locales-resolved.js | 41 ++++++++++++ .../Intl/available-locales-supported.js | 26 ++++++++ 5 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 js/src/tests/non262/Intl/available-locales-resolved.js create mode 100644 js/src/tests/non262/Intl/available-locales-supported.js diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index 544db33f5d55..219e9d631e67 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -40,6 +40,7 @@ #ifdef JS_HAS_INTL_API # include "builtin/intl/CommonFunctions.h" +# include "builtin/intl/SharedIntlData.h" #endif #include "builtin/ModuleObject.h" #include "builtin/Promise.h" @@ -7177,6 +7178,64 @@ static bool GetICUOptions(JSContext* cx, unsigned argc, Value* vp) { return true; } +static bool GetAvailableLocalesOf(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + RootedObject callee(cx, &args.callee()); + + if (!args.requireAtLeast(cx, "getAvailableLocalesOf", 1)) { + return false; + } + + HandleValue arg = args[0]; + if (!arg.isString()) { + ReportUsageErrorASCII(cx, callee, "First argument must be a string"); + return false; + } + + ArrayObject* result; +#ifdef JS_HAS_INTL_API + using SupportedLocaleKind = js::intl::SharedIntlData::SupportedLocaleKind; + + SupportedLocaleKind kind; + { + JSLinearString* typeStr = arg.toString()->ensureLinear(cx); + if (!typeStr) { + return false; + } + + if (StringEqualsLiteral(typeStr, "Collator")) { + kind = SupportedLocaleKind::Collator; + } else if (StringEqualsLiteral(typeStr, "DateTimeFormat")) { + kind = SupportedLocaleKind::DateTimeFormat; + } else if (StringEqualsLiteral(typeStr, "DisplayNames")) { + kind = SupportedLocaleKind::DisplayNames; + } else if (StringEqualsLiteral(typeStr, "ListFormat")) { + kind = SupportedLocaleKind::ListFormat; + } else if (StringEqualsLiteral(typeStr, "NumberFormat")) { + kind = SupportedLocaleKind::NumberFormat; + } else if (StringEqualsLiteral(typeStr, "PluralRules")) { + kind = SupportedLocaleKind::PluralRules; + } else if (StringEqualsLiteral(typeStr, "RelativeTimeFormat")) { + kind = SupportedLocaleKind::RelativeTimeFormat; + } else { + ReportUsageErrorASCII(cx, callee, "Unsupported Intl constructor name"); + return false; + } + } + + intl::SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref(); + result = sharedIntlData.availableLocalesOf(cx, kind); +#else + result = NewDenseEmptyArray(cx); +#endif + if (!result) { + return false; + } + + args.rval().setObject(*result); + return true; +} + static bool IsSmallFunction(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedObject callee(cx, &args.callee()); @@ -8221,7 +8280,6 @@ JS_FOR_WASM_FEATURES(WASM_FEATURE, WASM_FEATURE) "sequence of ECMAScript execution completes. This is used for testing\n" "WeakRefs.\n"), - JS_FN_HELP("numberToDouble", NumberToDouble, 1, 0, "numberToDouble(number)", " Return the input number as double-typed number."), @@ -8236,6 +8294,10 @@ JS_FN_HELP("getICUOptions", GetICUOptions, 0, 0, " timezone: the ICU default time zone, e.g. 'America/Los_Angeles'\n" " host-timezone: the host time zone, e.g. 'America/Los_Angeles'"), +JS_FN_HELP("getAvailableLocalesOf", GetAvailableLocalesOf, 0, 0, +"getAvailableLocalesOf(name)", +" Return an array of all available locales for the given Intl constuctor."), + JS_FN_HELP("isSmallFunction", IsSmallFunction, 1, 0, "isSmallFunction(fun)", " Returns true if a scripted function is small enough to be inlinable."), diff --git a/js/src/builtin/intl/SharedIntlData.cpp b/js/src/builtin/intl/SharedIntlData.cpp index 4b420699212c..77814eb8f1f8 100644 --- a/js/src/builtin/intl/SharedIntlData.cpp +++ b/js/src/builtin/intl/SharedIntlData.cpp @@ -17,6 +17,7 @@ #include #include +#include "builtin/Array.h" #include "builtin/intl/CommonFunctions.h" #include "builtin/intl/ScopedICUObject.h" #include "builtin/intl/TimeZoneDataGenerated.h" @@ -31,7 +32,9 @@ #include "unicode/uloc.h" #include "unicode/unum.h" #include "unicode/utypes.h" +#include "vm/ArrayObject.h" #include "vm/JSAtom.h" +#include "vm/JSContext.h" #include "vm/StringType.h" using js::HashNumber; @@ -484,6 +487,48 @@ bool js::intl::SharedIntlData::isSupportedLocale(JSContext* cx, 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: + 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) { diff --git a/js/src/builtin/intl/SharedIntlData.h b/js/src/builtin/intl/SharedIntlData.h index 9d611bca0e7a..91e8179d0c48 100644 --- a/js/src/builtin/intl/SharedIntlData.h +++ b/js/src/builtin/intl/SharedIntlData.h @@ -26,6 +26,8 @@ class DateTimePatternGenerator; namespace js { +class ArrayObject; + namespace intl { /** @@ -245,6 +247,11 @@ class SharedIntlData { JS::Handle locale, bool* supported); + /** + * Returns all available locales for |kind|. + */ + ArrayObject* availableLocalesOf(JSContext* cx, SupportedLocaleKind kind); + private: /** * The case first parameter (BCP47 key "kf") allows to switch the order of diff --git a/js/src/tests/non262/Intl/available-locales-resolved.js b/js/src/tests/non262/Intl/available-locales-resolved.js new file mode 100644 index 000000000000..76b144d2759e --- /dev/null +++ b/js/src/tests/non262/Intl/available-locales-resolved.js @@ -0,0 +1,41 @@ +// |reftest| skip-if(!this.hasOwnProperty('Intl')) + +if (typeof getAvailableLocalesOf === "undefined") { + var getAvailableLocalesOf = SpecialPowers.Cu.getJSTestingFunctions().getAvailableLocalesOf; +} + +function IsIntlService(c) { + return typeof c === "function" && + c.hasOwnProperty("prototype") && + c.prototype.hasOwnProperty("resolvedOptions"); +} + +const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsIntlService); + +// Test all Intl service constructors. +for (let intlConstructor of intlConstructors) { + // Retrieve all available locales of the given Intl service constructor. + let available = getAvailableLocalesOf(intlConstructor.name); + + // "best fit" matchers could potentially return a different locale, so we only + // test with "lookup" locale matchers. (NB: We don't yet support "best fit" + // matchers.) + let options = {localeMatcher: "lookup"}; + + if (intlConstructor === Intl.DisplayNames) { + // Intl.DisplayNames can't be constructed without the "type" option. + options.type = "language"; + } + + for (let locale of available) { + let obj = new intlConstructor(locale, options); + let resolved = obj.resolvedOptions(); + + // If |locale| is an available locale, the "lookup" locale matcher should + // pick exactly that locale. + assertEq(resolved.locale, locale); + } +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Intl/available-locales-supported.js b/js/src/tests/non262/Intl/available-locales-supported.js new file mode 100644 index 000000000000..d11b5d714f82 --- /dev/null +++ b/js/src/tests/non262/Intl/available-locales-supported.js @@ -0,0 +1,26 @@ +// |reftest| skip-if(!this.hasOwnProperty('Intl')) + +if (typeof getAvailableLocalesOf === "undefined") { + var getAvailableLocalesOf = SpecialPowers.Cu.getJSTestingFunctions().getAvailableLocalesOf; +} + +function IsIntlService(c) { + return typeof c === "function" && + c.hasOwnProperty("prototype") && + c.prototype.hasOwnProperty("resolvedOptions"); +} + +const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsIntlService); + +// Test all Intl service constructors. +for (let intlConstructor of intlConstructors) { + // Retrieve all available locales of the given Intl service constructor. + let available = getAvailableLocalesOf(intlConstructor.name); + + // All available locales must be reported as supported. + let supported = intlConstructor.supportedLocalesOf(available); + assertEqArray(supported, available); +} + +if (typeof reportCompare === "function") + reportCompare(true, true);