diff --git a/js/src/builtin/intl/Collator.cpp b/js/src/builtin/intl/Collator.cpp index ad77736471..446ffe7369 100644 --- a/js/src/builtin/intl/Collator.cpp +++ b/js/src/builtin/intl/Collator.cpp @@ -233,7 +233,6 @@ js::intl_availableCollations(JSContext* cx, unsigned argc, Value* vp) if (!DefineElement(cx, collations, index++, NullHandleValue)) return false; - RootedString jscollation(cx); RootedValue element(cx); for (uint32_t i = 0; i < count; i++) { @@ -251,8 +250,7 @@ js::intl_availableCollations(JSContext* cx, unsigned argc, Value* vp) continue; // ICU returns old-style keyword values; map them to BCP 47 equivalents. - jscollation = JS_NewStringCopyZ(cx, uloc_toUnicodeLocaleType("co", collation)); - + JSString* jscollation = JS_NewStringCopyZ(cx, uloc_toUnicodeLocaleType("co", collation)); if (!jscollation) return false; element = StringValue(jscollation); diff --git a/js/src/builtin/intl/Collator.js b/js/src/builtin/intl/Collator.js index d9ef15a931..87aa6efbae 100644 --- a/js/src/builtin/intl/Collator.js +++ b/js/src/builtin/intl/Collator.js @@ -82,15 +82,11 @@ function resolveCollatorInternals(lazyCollatorData) // Steps 21-22. var s = lazyCollatorData.rawSensitivity; if (s === undefined) { - if (collatorIsSorting) { - // Step 21.a. - s = "variant"; - } else { - // Step 21.b. - var dataLocale = r.dataLocale; - var dataLocaleData = localeData(dataLocale); - s = dataLocaleData.sensitivity; - } + // In theory the default sensitivity for the "search" collator is + // locale dependent; in reality the CLDR/ICU default strength is + // always tertiary. Therefore use "variant" as the default value for + // both collation modes. + s = "variant"; } internalProps.sensitivity = s; @@ -263,49 +259,96 @@ var collatorInternalProperties = { /** - * Returns the default caseFirst values for the given locale and usage. The - * first element in the returned array denotes the default value per ES2017 - * Intl, 9.1 Internal slots of Service Constructors. + * Returns the actual locale used when a collator for |locale| is constructed. */ -function collatorCaseFirst(locale, usage) { +function collatorActualLocale(locale) { assert(typeof locale === "string", "locale should be string"); - assert(usage === "sort" || usage === "search", "invalid usage option"); - if (usage === "sort") { - // 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); - var actualLocale = BestAvailableLocaleIgnoringDefault(availableLocales, 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); +} - if (intl_isUpperCaseFirst(actualLocale)) - return ["upper", "false", "lower"]; - } + +/** + * Returns the default caseFirst values for the given locale. The first + * element in the returned array denotes the default value per ES2017 Intl, + * 9.1 Internal slots of Service Constructors. + */ +function collatorSortCaseFirst(locale) { + var actualLocale = collatorActualLocale(locale); + if (intl_isUpperCaseFirst(actualLocale)) + return ["upper", "false", "lower"]; // Default caseFirst values for all other languages. return ["false", "lower", "upper"]; } -function collatorSortLocaleData(locale) { +/** + * Returns the default caseFirst value for the given locale. + */ +function collatorSortCaseFirstDefault(locale) { + var actualLocale = collatorActualLocale(locale); + if (intl_isUpperCaseFirst(actualLocale)) + return "upper"; + + // Default caseFirst value for all other languages. + return "false"; +} + +function collatorSortLocaleData() { + /* eslint-disable object-shorthand */ return { - co: intl_availableCollations(locale), - kn: ["false", "true"], - kf: collatorCaseFirst(locale, "sort"), + co: intl_availableCollations, + kn: function() { + return ["false", "true"]; + }, + kf: collatorSortCaseFirst, + default: { + co: function() { + // The first element of the collations array must be |null| + // per ES2017 Intl, 10.2.3 Internal Slots. + return null; + }, + kn: function() { + return "false"; + }, + kf: collatorSortCaseFirstDefault, + } }; + /* eslint-enable object-shorthand */ } -function collatorSearchLocaleData(locale) { +function collatorSearchLocaleData() { + /* eslint-disable object-shorthand */ return { - co: [null], - kn: ["false", "true"], - kf: collatorCaseFirst(locale, "search"), - // In theory the default sensitivity is locale dependent; - // in reality the CLDR/ICU default strength is always tertiary. - sensitivity: "variant" + co: function() { + return [null]; + }, + kn: function() { + return ["false", "true"]; + }, + kf: function() { + return ["false", "lower", "upper"]; + }, + default: { + co: function() { + return null; + }, + kn: function() { + return "false"; + }, + kf: function() { + return "false"; + }, + } }; + /* eslint-enable object-shorthand */ } @@ -354,6 +397,7 @@ function Intl_Collator_compare_get() { // Step 2. return internals.boundCompare; } +_SetCanonicalName(Intl_Collator_compare_get, "get compare"); /** diff --git a/js/src/builtin/intl/CommonFunctions.cpp b/js/src/builtin/intl/CommonFunctions.cpp index f83ba0b423..c904d94a49 100644 --- a/js/src/builtin/intl/CommonFunctions.cpp +++ b/js/src/builtin/intl/CommonFunctions.cpp @@ -92,7 +92,6 @@ js::intl::GetAvailableLocales(JSContext* cx, CountAvailable countAvailable, return false; uint32_t count = countAvailable(); - RootedValue t(cx, BooleanValue(true)); for (uint32_t i = 0; i < count; i++) { const char* locale = getAvailable(i); auto lang = DuplicateString(cx, locale); @@ -104,7 +103,7 @@ js::intl::GetAvailableLocales(JSContext* cx, CountAvailable countAvailable, RootedAtom a(cx, Atomize(cx, lang.get(), strlen(lang.get()))); if (!a) return false; - if (!DefineProperty(cx, locales, a->asPropertyName(), t, nullptr, nullptr, + if (!DefineProperty(cx, locales, a->asPropertyName(), TrueHandleValue, nullptr, nullptr, JSPROP_ENUMERATE)) { return false; diff --git a/js/src/builtin/intl/CommonFunctions.js b/js/src/builtin/intl/CommonFunctions.js index 59fbeaea3b..cf5a615721 100644 --- a/js/src/builtin/intl/CommonFunctions.js +++ b/js/src/builtin/intl/CommonFunctions.js @@ -767,8 +767,6 @@ function BestAvailableLocaleIgnoringDefault(availableLocales, locale) { return BestAvailableLocaleHelper(availableLocales, locale, false); } -var noRelevantExtensionKeys = []; - /** * Compares a BCP 47 language priority list against the set of locales in * availableLocales and determines the best available language to meet the @@ -925,21 +923,17 @@ function ResolveLocale(availableLocales, requestedLocales, options, relevantExte // Step 8. var supportedExtension = "-u"; + // In this implementation, localeData is a function, not an object. + var localeDataProvider = localeData(); + // Steps 9-12. - var i = 0; - var len = relevantExtensionKeys.length; - while (i < len) { + for (var i = 0; i < relevantExtensionKeys.length; i++) { // Steps 12.a-c. var key = relevantExtensionKeys[i]; - // In this implementation, localeData is a function, not an object. - var foundLocaleData = localeData(foundLocale); - var keyLocaleData = foundLocaleData[key]; - - // Locale data provides default value. - // Step 12.d. - var value = keyLocaleData[0]; - assert(typeof value === "string" || value === null, "unexpected locale data value"); + // Steps 12.b-d (The locale data is only computed when needed). + var keyLocaleData = undefined; + var value = undefined; // Locale tag may override. @@ -958,6 +952,9 @@ function ResolveLocale(availableLocales, requestedLocales, options, relevantExte // Step 12.f.ii. if (requestedValue !== undefined) { + // Steps 12.b-c. + keyLocaleData = callFunction(localeDataProvider[key], null, foundLocale); + // Step 12.f.ii.1. if (requestedValue !== "") { // Step 12.f.ii.1.a. @@ -983,18 +980,29 @@ function ResolveLocale(availableLocales, requestedLocales, options, relevantExte var optionsValue = options[key]; // Step 12.g, 12.gg.ii. - if (optionsValue !== undefined && - optionsValue !== value && - callFunction(ArrayIndexOf, keyLocaleData, optionsValue) !== -1) - { - value = optionsValue; - supportedExtensionAddition = ""; + if (optionsValue !== undefined && optionsValue !== value) { + // Steps 12.b-c. + if (keyLocaleData === undefined) + keyLocaleData = callFunction(localeDataProvider[key], null, foundLocale); + + if (callFunction(ArrayIndexOf, keyLocaleData, optionsValue) !== -1) { + value = optionsValue; + supportedExtensionAddition = ""; + } + } + + // Locale data provides default value. + if (value === undefined) { + // Steps 12.b-d. + value = keyLocaleData === undefined + ? callFunction(localeDataProvider.default[key], null, foundLocale) + : keyLocaleData[0]; } // Steps 12.h-j. + assert(typeof value === "string" || value === null, "unexpected locale data value"); result[key] = value; supportedExtension += supportedExtensionAddition; - i++; } // Step 13. diff --git a/js/src/builtin/intl/DateTimeFormat.cpp b/js/src/builtin/intl/DateTimeFormat.cpp index fed655736a..449456d30f 100644 --- a/js/src/builtin/intl/DateTimeFormat.cpp +++ b/js/src/builtin/intl/DateTimeFormat.cpp @@ -241,6 +241,30 @@ js::intl_DateTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp return true; } +static bool +DefaultCalendar(JSContext* cx, const JSAutoByteString& locale, MutableHandleValue rval) +{ + UErrorCode status = U_ZERO_ERROR; + UCalendar* cal = ucal_open(nullptr, 0, locale.ptr(), UCAL_DEFAULT, &status); + + // This correctly handles nullptr |cal| when opening failed. + ScopedICUObject closeCalendar(cal); + + const char* calendar = ucal_getType(cal, &status); + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + + // ICU returns old-style keyword values; map them to BCP 47 equivalents + JSString* str = JS_NewStringCopyZ(cx, uloc_toUnicodeLocaleType("ca", calendar)); + if (!str) + return false; + + rval.setString(str); + return true; +} + struct CalendarAlias { const char* const calendar; @@ -269,31 +293,15 @@ js::intl_availableCalendars(JSContext* cx, unsigned argc, Value* vp) uint32_t index = 0; // We need the default calendar for the locale as the first result. - UErrorCode status = U_ZERO_ERROR; - RootedString jscalendar(cx); - { - UCalendar* cal = ucal_open(nullptr, 0, locale.ptr(), UCAL_DEFAULT, &status); + RootedValue element(cx); + if (!DefaultCalendar(cx, locale, &element)) + return false; - // This correctly handles nullptr |cal| when opening failed. - ScopedICUObject closeCalendar(cal); - - const char* calendar = ucal_getType(cal, &status); - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); - return false; - } - - // ICU returns old-style keyword values; map them to BCP 47 equivalents - jscalendar = JS_NewStringCopyZ(cx, uloc_toUnicodeLocaleType("ca", calendar)); - if (!jscalendar) - return false; - } - - RootedValue element(cx, StringValue(jscalendar)); if (!DefineElement(cx, calendars, index++, element)) return false; // Now get the calendars that "would make a difference", i.e., not the default. + UErrorCode status = U_ZERO_ERROR; UEnumeration* values = ucal_getKeywordValuesForLocale("ca", locale.ptr(), false, &status); if (U_FAILURE(status)) { intl::ReportInternalError(cx); @@ -317,7 +325,7 @@ js::intl_availableCalendars(JSContext* cx, unsigned argc, Value* vp) // ICU returns old-style keyword values; map them to BCP 47 equivalents calendar = uloc_toUnicodeLocaleType("ca", calendar); - jscalendar = JS_NewStringCopyZ(cx, calendar); + JSString* jscalendar = JS_NewStringCopyZ(cx, calendar); if (!jscalendar) return false; element = StringValue(jscalendar); @@ -327,7 +335,7 @@ js::intl_availableCalendars(JSContext* cx, unsigned argc, Value* vp) // ICU doesn't return calendar aliases, append them here. for (const auto& calendarAlias : calendarAliases) { if (StringsAreEqual(calendar, calendarAlias.calendar)) { - jscalendar = JS_NewStringCopyZ(cx, calendarAlias.alias); + JSString* jscalendar = JS_NewStringCopyZ(cx, calendarAlias.alias); if (!jscalendar) return false; element = StringValue(jscalendar); @@ -341,6 +349,20 @@ js::intl_availableCalendars(JSContext* cx, unsigned argc, Value* vp) return true; } +bool +js::intl_defaultCalendar(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + MOZ_ASSERT(args[0].isString()); + + JSAutoByteString locale(cx, args[0].toString()); + if (!locale) + return false; + + return DefaultCalendar(cx, locale, args.rval()); +} + bool js::intl_IsValidTimeZoneName(JSContext* cx, unsigned argc, Value* vp) { diff --git a/js/src/builtin/intl/DateTimeFormat.h b/js/src/builtin/intl/DateTimeFormat.h index b84cdd446c..8173bb6662 100644 --- a/js/src/builtin/intl/DateTimeFormat.h +++ b/js/src/builtin/intl/DateTimeFormat.h @@ -76,6 +76,16 @@ intl_DateTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp); extern MOZ_MUST_USE bool intl_availableCalendars(JSContext* cx, unsigned argc, Value* vp); +/** + * Returns the calendar type identifier per Unicode Technical Standard 35, + * Unicode Locale Data Markup Language, for the default calendar for the given + * locale. + * + * Usage: calendar = intl_defaultCalendar(locale) + */ +extern MOZ_MUST_USE bool +intl_defaultCalendar(JSContext* cx, unsigned argc, Value* vp); + /** * 6.4.1 IsValidTimeZoneName ( timeZone ) * diff --git a/js/src/builtin/intl/DateTimeFormat.js b/js/src/builtin/intl/DateTimeFormat.js index 443fcdb1fd..5ac11d37fb 100644 --- a/js/src/builtin/intl/DateTimeFormat.js +++ b/js/src/builtin/intl/DateTimeFormat.js @@ -793,10 +793,14 @@ var dateTimeFormatInternalProperties = { }; -function dateTimeFormatLocaleData(locale) { +function dateTimeFormatLocaleData() { return { - ca: intl_availableCalendars(locale), - nu: getNumberingSystems(locale) + ca: intl_availableCalendars, + nu: getNumberingSystems, + default: { + ca: intl_defaultCalendar, + nu: intl_numberingSystem, + } }; } diff --git a/js/src/builtin/intl/NumberFormat.js b/js/src/builtin/intl/NumberFormat.js index 3b8471cdb8..d3fcaf89d8 100644 --- a/js/src/builtin/intl/NumberFormat.js +++ b/js/src/builtin/intl/NumberFormat.js @@ -446,9 +446,12 @@ function getNumberingSystems(locale) { } -function numberFormatLocaleData(locale) { +function numberFormatLocaleData() { return { - nu: getNumberingSystems(locale) + nu: getNumberingSystems, + default: { + nu: intl_numberingSystem, + } }; } diff --git a/js/src/builtin/intl/PluralRules.js b/js/src/builtin/intl/PluralRules.js index d02da59f02..84da3b7667 100644 --- a/js/src/builtin/intl/PluralRules.js +++ b/js/src/builtin/intl/PluralRules.js @@ -10,6 +10,7 @@ * Spec: ECMAScript 402 API, PluralRules, 1.3.3. */ var pluralRulesInternalProperties = { + localeData: pluralRulesLocaleData, _availableLocales: null, availableLocales: function() { @@ -20,9 +21,17 @@ var pluralRulesInternalProperties = { locales = intl_PluralRules_availableLocales(); addSpecialMissingLanguageTags(locales); return (this._availableLocales = locales); - } + }, + relevantExtensionKeys: [], }; + +function pluralRulesLocaleData() { + // PluralRules don't support any extension keys. + return {}; +} + + /** * Compute an internal properties object from |lazyPluralRulesData|. */ @@ -39,7 +48,7 @@ function resolvePluralRulesInternals(lazyPluralRulesData) { const r = ResolveLocale(callFunction(PluralRules.availableLocales, PluralRules), lazyPluralRulesData.requestedLocales, lazyPluralRulesData.opt, - noRelevantExtensionKeys, undefined); + PluralRules.relevantExtensionKeys, PluralRules.localeData); // Step 14. internalProps.locale = r.locale; diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 0717bfd490..ebb95c83e9 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -2467,6 +2467,7 @@ static const JSFunctionSpec intrinsic_functions[] = { 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), JS_FN("intl_FormatDateTime", intl_FormatDateTime, 2,0),