--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -99,16 +99,28 @@ Char16ToUChar(const char16_t* chars)
}
inline UChar*
Char16ToUChar(char16_t* chars)
{
MOZ_CRASH("Char16ToUChar: Intl API disabled");
}
+inline char16_t*
+UCharToChar16(UChar* chars)
+{
+ MOZ_CRASH("UCharToChar16: Intl API disabled");
+}
+
+inline const char16_t*
+UCharToChar16(const UChar* chars)
+{
+ MOZ_CRASH("UCharToChar16: Intl API disabled");
+}
+
struct UEnumeration;
int32_t
uenum_count(UEnumeration* en, UErrorCode* status)
{
MOZ_CRASH("uenum_count: Intl API disabled");
}
@@ -347,16 +359,37 @@ enum UCalendarDateFields {
UCAL_EXTENDED_YEAR,
UCAL_JULIAN_DAY,
UCAL_MILLISECONDS_IN_DAY,
UCAL_IS_LEAP_MONTH,
UCAL_FIELD_COUNT,
UCAL_DAY_OF_MONTH = UCAL_DATE
};
+enum UCalendarMonths {
+ UCAL_JANUARY,
+ UCAL_FEBRUARY,
+ UCAL_MARCH,
+ UCAL_APRIL,
+ UCAL_MAY,
+ UCAL_JUNE,
+ UCAL_JULY,
+ UCAL_AUGUST,
+ UCAL_SEPTEMBER,
+ UCAL_OCTOBER,
+ UCAL_NOVEMBER,
+ UCAL_DECEMBER,
+ UCAL_UNDECIMBER
+};
+
+enum UCalendarAMPMs {
+ UCAL_AM,
+ UCAL_PM
+};
+
UCalendar*
ucal_open(const UChar* zoneID, int32_t len, const char* locale,
UCalendarType type, UErrorCode* status)
{
MOZ_CRASH("ucal_open: Intl API disabled");
}
const char*
@@ -411,32 +444,47 @@ ucal_getCanonicalTimeZoneID(const UChar*
}
int32_t
ucal_getDefaultTimeZone(UChar* result, int32_t resultCapacity, UErrorCode* status)
{
MOZ_CRASH("ucal_getDefaultTimeZone: Intl API disabled");
}
+enum UDateTimePatternField {
+ UDATPG_YEAR_FIELD,
+ UDATPG_MONTH_FIELD,
+ UDATPG_WEEK_OF_YEAR_FIELD,
+ UDATPG_DAY_FIELD,
+};
+
typedef void* UDateTimePatternGenerator;
UDateTimePatternGenerator*
udatpg_open(const char* locale, UErrorCode* pErrorCode)
{
MOZ_CRASH("udatpg_open: Intl API disabled");
}
int32_t
udatpg_getBestPattern(UDateTimePatternGenerator* dtpg, const UChar* skeleton,
int32_t length, UChar* bestPattern, int32_t capacity,
UErrorCode* pErrorCode)
{
MOZ_CRASH("udatpg_getBestPattern: Intl API disabled");
}
+static const UChar *
+udatpg_getAppendItemName(const UDateTimePatternGenerator *dtpg,
+ UDateTimePatternField field,
+ int32_t *pLength)
+{
+ MOZ_CRASH("udatpg_getAppendItemName: Intl API disabled");
+}
+
void
udatpg_close(UDateTimePatternGenerator* dtpg)
{
MOZ_CRASH("udatpg_close: Intl API disabled");
}
typedef void* UCalendar;
typedef void* UDateFormat;
@@ -479,20 +527,56 @@ enum UDateFormatField {
UDAT_RELATED_YEAR_FIELD = 34,
UDAT_AM_PM_MIDNIGHT_NOON_FIELD = 35,
UDAT_FLEXIBLE_DAY_PERIOD_FIELD = 36,
UDAT_TIME_SEPARATOR_FIELD = 37,
UDAT_FIELD_COUNT = 38
};
enum UDateFormatStyle {
+ UDAT_FULL,
+ UDAT_LONG,
+ UDAT_MEDIUM,
+ UDAT_SHORT,
+ UDAT_DEFAULT = UDAT_MEDIUM,
UDAT_PATTERN = -2,
UDAT_IGNORE = UDAT_PATTERN
};
+enum UDateFormatSymbolType {
+ UDAT_ERAS,
+ UDAT_MONTHS,
+ UDAT_SHORT_MONTHS,
+ UDAT_WEEKDAYS,
+ UDAT_SHORT_WEEKDAYS,
+ UDAT_AM_PMS,
+ UDAT_LOCALIZED_CHARS,
+ UDAT_ERA_NAMES,
+ UDAT_NARROW_MONTHS,
+ UDAT_NARROW_WEEKDAYS,
+ UDAT_STANDALONE_MONTHS,
+ UDAT_STANDALONE_SHORT_MONTHS,
+ UDAT_STANDALONE_NARROW_MONTHS,
+ UDAT_STANDALONE_WEEKDAYS,
+ UDAT_STANDALONE_SHORT_WEEKDAYS,
+ UDAT_STANDALONE_NARROW_WEEKDAYS,
+ UDAT_QUARTERS,
+ UDAT_SHORT_QUARTERS,
+ UDAT_STANDALONE_QUARTERS,
+ UDAT_STANDALONE_SHORT_QUARTERS,
+ UDAT_SHORTER_WEEKDAYS,
+ UDAT_STANDALONE_SHORTER_WEEKDAYS,
+ UDAT_CYCLIC_YEARS_WIDE,
+ UDAT_CYCLIC_YEARS_ABBREVIATED,
+ UDAT_CYCLIC_YEARS_NARROW,
+ UDAT_ZODIAC_NAMES_WIDE,
+ UDAT_ZODIAC_NAMES_ABBREVIATED,
+ UDAT_ZODIAC_NAMES_NARROW
+};
+
int32_t
udat_countAvailable()
{
MOZ_CRASH("udat_countAvailable: Intl API disabled");
}
const char*
udat_getAvailable(int32_t localeIndex)
@@ -556,16 +640,23 @@ ufieldpositer_next(UFieldPositionIterato
void
udat_close(UDateFormat* format)
{
MOZ_CRASH("udat_close: Intl API disabled");
}
} // anonymous namespace
+static int32_t
+udat_getSymbols(const UDateFormat *fmt, UDateFormatSymbolType type, int32_t symbolIndex,
+ UChar *result, int32_t resultLength, UErrorCode *status)
+{
+ MOZ_CRASH("udat_getSymbols: Intl API disabled");
+}
+
#endif
/******************** Common to Intl constructors ********************/
static bool
IntlInitialize(JSContext* cx, HandleObject obj, Handle<PropertyName*> initializer,
HandleValue locales, HandleValue options)
@@ -2917,16 +3008,306 @@ js::intl_GetCalendarInfo(JSContext* cx,
if (!DefineProperty(cx, info, cx->names().weekendEnd, weekendEnd))
return false;
args.rval().setObject(*info);
return true;
}
+template<size_t N>
+inline bool
+MatchPart(const char** pattern, const char (&part)[N])
+{
+ if (strncmp(*pattern, part, N - 1))
+ return false;
+
+ *pattern += N - 1;
+ return true;
+}
+
+bool
+js::intl_ComputeDisplayNames(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 3);
+ // 1. Assert: locale is a string.
+ MOZ_ASSERT(args[0].isString());
+ // 2. Assert: style is a string.
+ MOZ_ASSERT(args[1].isString());
+ // 3. Assert: keys is an Array.
+ MOZ_ASSERT(args[2].isObject());
+
+ JSAutoByteString locale(cx, args[0].toString());
+ if (!locale)
+ return false;
+
+ JSAutoByteString style(cx, args[1].toString());
+ if (!style)
+ return false;
+
+ RootedArrayObject keys(cx, &args[2].toObject().as<ArrayObject>());
+ if (!keys)
+ return false;
+
+ // 4. Let result be ArrayCreate(0).
+ RootedArrayObject result(cx, NewDenseUnallocatedArray(cx, keys->length()));
+ if (!result)
+ return false;
+
+ UErrorCode status = U_ZERO_ERROR;
+
+ UDateFormat* fmt =
+ udat_open(UDAT_DEFAULT, UDAT_DEFAULT, icuLocale(locale.ptr()),
+ nullptr, 0, nullptr, 0, &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+ ScopedICUObject<UDateFormat, udat_close> datToClose(fmt);
+
+ // UDateTimePatternGenerator will be needed for translations of date and
+ // time fields like "month", "week", "day" etc.
+ UDateTimePatternGenerator* dtpg = udatpg_open(icuLocale(locale.ptr()), &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+ ScopedICUObject<UDateTimePatternGenerator, udatpg_close> datPgToClose(dtpg);
+
+ RootedValue keyValue(cx);
+ RootedString keyValStr(cx);
+ RootedValue wordVal(cx);
+ Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
+ if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE))
+ return false;
+
+ // 5. For each element of keys,
+ for (uint32_t i = 0; i < keys->length(); i++) {
+ /**
+ * We iterate over keys array looking for paths that we have code
+ * branches for.
+ *
+ * For any unknown path branch, the wordVal will keep NullValue and
+ * we'll throw at the end.
+ */
+
+ if (!GetElement(cx, keys, keys, i, &keyValue))
+ return false;
+
+ JSAutoByteString pattern;
+ keyValStr = keyValue.toString();
+ if (!pattern.encodeUtf8(cx, keyValStr))
+ return false;
+
+ wordVal.setNull();
+
+ // 5.a. Perform an implementation dependent algorithm to map a key to a
+ // corresponding display name.
+ const char* pat = pattern.ptr();
+
+ if (!MatchPart(&pat, "dates")) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+
+ if (!MatchPart(&pat, "/")) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+
+ if (MatchPart(&pat, "fields")) {
+ if (!MatchPart(&pat, "/")) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+
+ UDateTimePatternField fieldType;
+
+ if (MatchPart(&pat, "year")) {
+ fieldType = UDATPG_YEAR_FIELD;
+ } else if (MatchPart(&pat, "month")) {
+ fieldType = UDATPG_MONTH_FIELD;
+ } else if (MatchPart(&pat, "week")) {
+ fieldType = UDATPG_WEEK_OF_YEAR_FIELD;
+ } else if (MatchPart(&pat, "day")) {
+ fieldType = UDATPG_DAY_FIELD;
+ } else {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+
+ // This part must be the final part with no trailing data.
+ if (*pat != '\0') {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+
+ int32_t resultSize;
+
+ const UChar* value = udatpg_getAppendItemName(dtpg, fieldType, &resultSize);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ JSString* word = NewStringCopyN<CanGC>(cx, UCharToChar16(value), resultSize);
+ if (!word)
+ return false;
+
+ wordVal.setString(word);
+ } else if (MatchPart(&pat, "gregorian")) {
+ if (!MatchPart(&pat, "/")) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+
+ UDateFormatSymbolType symbolType;
+ int32_t index;
+
+ if (MatchPart(&pat, "months")) {
+ if (!MatchPart(&pat, "/")) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+
+ if (equal(style, "narrow")) {
+ symbolType = UDAT_STANDALONE_NARROW_MONTHS;
+ } else if (equal(style, "short")) {
+ symbolType = UDAT_STANDALONE_SHORT_MONTHS;
+ } else {
+ MOZ_ASSERT(equal(style, "long"));
+ symbolType = UDAT_STANDALONE_MONTHS;
+ }
+
+ if (MatchPart(&pat, "january")) {
+ index = UCAL_JANUARY;
+ } else if (MatchPart(&pat, "february")) {
+ index = UCAL_FEBRUARY;
+ } else if (MatchPart(&pat, "march")) {
+ index = UCAL_MARCH;
+ } else if (MatchPart(&pat, "april")) {
+ index = UCAL_APRIL;
+ } else if (MatchPart(&pat, "may")) {
+ index = UCAL_MAY;
+ } else if (MatchPart(&pat, "june")) {
+ index = UCAL_JUNE;
+ } else if (MatchPart(&pat, "july")) {
+ index = UCAL_JULY;
+ } else if (MatchPart(&pat, "august")) {
+ index = UCAL_AUGUST;
+ } else if (MatchPart(&pat, "september")) {
+ index = UCAL_SEPTEMBER;
+ } else if (MatchPart(&pat, "october")) {
+ index = UCAL_OCTOBER;
+ } else if (MatchPart(&pat, "november")) {
+ index = UCAL_NOVEMBER;
+ } else if (MatchPart(&pat, "december")) {
+ index = UCAL_DECEMBER;
+ } else {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+ } else if (MatchPart(&pat, "weekdays")) {
+ if (!MatchPart(&pat, "/")) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+
+ if (equal(style, "narrow")) {
+ symbolType = UDAT_STANDALONE_NARROW_WEEKDAYS;
+ } else if (equal(style, "short")) {
+ symbolType = UDAT_STANDALONE_SHORT_WEEKDAYS;
+ } else {
+ MOZ_ASSERT(equal(style, "long"));
+ symbolType = UDAT_STANDALONE_WEEKDAYS;
+ }
+
+ if (MatchPart(&pat, "monday")) {
+ index = UCAL_MONDAY;
+ } else if (MatchPart(&pat, "tuesday")) {
+ index = UCAL_TUESDAY;
+ } else if (MatchPart(&pat, "wednesday")) {
+ index = UCAL_WEDNESDAY;
+ } else if (MatchPart(&pat, "thursday")) {
+ index = UCAL_THURSDAY;
+ } else if (MatchPart(&pat, "friday")) {
+ index = UCAL_FRIDAY;
+ } else if (MatchPart(&pat, "saturday")) {
+ index = UCAL_SATURDAY;
+ } else if (MatchPart(&pat, "sunday")) {
+ index = UCAL_SUNDAY;
+ } else {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+ } else if (MatchPart(&pat, "dayperiods")) {
+ if (!MatchPart(&pat, "/")) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+
+ symbolType = UDAT_AM_PMS;
+
+ if (MatchPart(&pat, "am")) {
+ index = UCAL_AM;
+ } else if (MatchPart(&pat, "pm")) {
+ index = UCAL_PM;
+ } else {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+ } else {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+
+ // This part must be the final part with no trailing data.
+ if (*pat != '\0') {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+
+ int32_t resultSize =
+ udat_getSymbols(fmt, symbolType, index, Char16ToUChar(chars.begin()),
+ INITIAL_CHAR_BUFFER_SIZE, &status);
+ if (status == U_BUFFER_OVERFLOW_ERROR) {
+ if (!chars.resize(resultSize))
+ return false;
+ status = U_ZERO_ERROR;
+ udat_getSymbols(fmt, symbolType, index, Char16ToUChar(chars.begin()),
+ resultSize, &status);
+ }
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ JSString* word = NewStringCopyN<CanGC>(cx, chars.begin(), resultSize);
+ if (!word)
+ return false;
+
+ wordVal.setString(word);
+ } else {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+
+ MOZ_ASSERT(wordVal.isString());
+
+ // 5.b. Append the result string to result.
+ if (!DefineElement(cx, result, i, wordVal))
+ return false;
+ }
+
+ // 6. Return result.
+ args.rval().setObject(*result);
+ return true;
+}
+
/******************** Intl ********************/
const Class js::IntlClass = {
js_Object_str,
JSCLASS_HAS_CACHED_PROTO(JSProto_Intl)
};
#if JS_HAS_TOSOURCE
--- a/js/src/builtin/Intl.h
+++ b/js/src/builtin/Intl.h
@@ -384,28 +384,82 @@ intl_FormatDateTime(JSContext* cx, unsig
* considered the end of a weekend, e.g. 1 for en-US, 1 for en-GB,
* 1 for bn-IN (note that "weekend" is *not* necessarily two days)
*
* NOTE: "calendar" and "locale" properties are *not* added to the object.
*/
extern MOZ_MUST_USE bool
intl_GetCalendarInfo(JSContext* cx, unsigned argc, Value* vp);
+/**
+ * Returns an Array with CLDR-based fields display names.
+ * The function takes three arguments:
+ *
+ * locale
+ * BCP47 compliant locale string
+ * style
+ * A string with values: long or short or narrow
+ * keys
+ * An array or path-like strings that identify keys to be returned
+ * At the moment the following types of keys are supported:
+ *
+ * 'dates/fields/{year|month|week|day}'
+ * 'dates/gregorian/months/{january|...|december}'
+ * 'dates/gregorian/weekdays/{sunday|...|saturday}'
+ * 'dates/gregorian/dayperiods/{am|pm}'
+ *
+ * Example:
+ *
+ * let info = intl_ComputeDisplayNames(
+ * 'en-US',
+ * 'long',
+ * [
+ * 'dates/fields/year',
+ * 'dates/gregorian/months/january',
+ * 'dates/gregorian/weekdays/monday',
+ * 'dates/gregorian/dayperiods/am',
+ * ]
+ * );
+ *
+ * Returned value:
+ *
+ * [
+ * 'year',
+ * 'January',
+ * 'Monday',
+ * 'AM'
+ * ]
+ */
+extern MOZ_MUST_USE bool
+intl_ComputeDisplayNames(JSContext* cx, unsigned argc, Value* vp);
+
#if ENABLE_INTL_API
/**
* Cast char16_t* strings to UChar* strings used by ICU.
*/
inline const UChar*
Char16ToUChar(const char16_t* chars)
{
return reinterpret_cast<const UChar*>(chars);
}
inline UChar*
Char16ToUChar(char16_t* chars)
{
return reinterpret_cast<UChar*>(chars);
}
+
+inline char16_t*
+UCharToChar16(UChar* chars)
+{
+ return reinterpret_cast<char16_t*>(chars);
+}
+
+inline const char16_t*
+UCharToChar16(const UChar* chars)
+{
+ return reinterpret_cast<const char16_t*>(chars);
+}
#endif // ENABLE_INTL_API
} // namespace js
#endif /* builtin_Intl_h */
--- a/js/src/builtin/Intl.js
+++ b/js/src/builtin/Intl.js
@@ -4,17 +4,18 @@
/* Portions Copyright Norbert Lindenberg 2011-2012. */
/*global JSMSG_INTL_OBJECT_NOT_INITED: false, JSMSG_INVALID_LOCALES_ELEMENT: false,
JSMSG_INVALID_LANGUAGE_TAG: false, JSMSG_INVALID_LOCALE_MATCHER: false,
JSMSG_INVALID_OPTION_VALUE: false, JSMSG_INVALID_DIGITS_VALUE: false,
JSMSG_INTL_OBJECT_REINITED: false, JSMSG_INVALID_CURRENCY_CODE: false,
JSMSG_UNDEFINED_CURRENCY: false, JSMSG_INVALID_TIME_ZONE: false,
- JSMSG_DATE_NOT_FINITE: false,
+ JSMSG_DATE_NOT_FINITE: false, JSMSG_INVALID_KEYS_TYPE: false,
+ JSMSG_INVALID_KEY: false,
intl_Collator_availableLocales: false,
intl_availableCollations: false,
intl_CompareStrings: false,
intl_NumberFormat_availableLocales: false,
intl_numberingSystem: false,
intl_FormatNumber: false,
intl_DateTimeFormat_availableLocales: false,
intl_availableCalendars: false,
@@ -2999,8 +3000,131 @@ function Intl_getCalendarInfo(locales) {
localeData);
const result = intl_GetCalendarInfo(r.locale);
result.calendar = r.ca;
result.locale = r.locale;
return result;
}
+
+/**
+ * This function is a custom method designed after Intl API, but currently
+ * not part of the spec or spec proposal.
+ * We want to use it internally to retrieve translated values from CLDR in
+ * order to ensure they're aligned with what Intl API returns.
+ *
+ * This API may one day be a foundation for an ECMA402 API spec proposal.
+ *
+ * The function takes two arguments - locales which is a list of locale strings
+ * and options which is an object with two optional properties:
+ *
+ * keys:
+ * an Array of string values that are paths to individual terms
+ *
+ * style:
+ * a String with a value "long", "short" or "narrow"
+ *
+ * It returns an object with properties:
+ *
+ * locale:
+ * a negotiated locale string
+ *
+ * style:
+ * negotiated style
+ *
+ * values:
+ * A key-value pair list of requested keys and corresponding
+ * translated values
+ *
+ */
+function Intl_getDisplayNames(locales, options) {
+ // 1. Let requestLocales be ? CanonicalizeLocaleList(locales).
+ const requestedLocales = CanonicalizeLocaleList(locales);
+
+ // 2. If options is undefined, then
+ if (options === undefined)
+ // a. Let options be ObjectCreate(%ObjectPrototype%).
+ options = {};
+ // 3. Else,
+ else
+ // a. Let options be ? ToObject(options).
+ options = ToObject(options);
+
+ const DateTimeFormat = dateTimeFormatInternalProperties;
+
+ // 4. Let localeData be %DateTimeFormat%.[[localeData]].
+ const localeData = DateTimeFormat.localeData;
+
+ // 5. Let opt be a new Record.
+ const localeOpt = new Record();
+ // 6. Set localeOpt.[[localeMatcher]] to "best fit".
+ localeOpt.localeMatcher = "best fit";
+
+ // 7. Let r be ResolveLocale(%DateTimeFormat%.[[availableLocales]], requestedLocales, localeOpt,
+ // %DateTimeFormat%.[[relevantExtensionKeys]], localeData).
+ const r = ResolveLocale(callFunction(DateTimeFormat.availableLocales, 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");
+ // 9. Let keys be ? Get(options, "keys").
+ let keys = options.keys;
+
+ // 10. If keys is undefined,
+ if (keys === undefined) {
+ // a. Let keys be ArrayCreate(0).
+ keys = [];
+ } else if (!IsObject(keys)) {
+ // 11. Else,
+ // a. If Type(keys) is not Object, throw a TypeError exception.
+ ThrowTypeError(JSMSG_INVALID_KEYS_TYPE);
+ }
+
+ // 12. Let processedKeys be ArrayCreate(0).
+ // (This really should be a List, but we use an Array here in order that
+ // |intl_ComputeDisplayNames| may infallibly access the list's length via
+ // |ArrayObject::length|.)
+ let processedKeys = [];
+ // 13. Let len be ? ToLength(? Get(keys, "length")).
+ let len = ToLength(keys.length);
+ // 14. Let i be 0.
+ // 15. Repeat, while i < len
+ for (let i = 0; i < len; i++) {
+ // a. Let processedKey be ? ToString(? Get(keys, i)).
+ // b. Perform ? CreateDataPropertyOrThrow(processedKeys, i, processedKey).
+ callFunction(std_Array_push, processedKeys, ToString(keys[i]));
+ }
+
+ // 16. Let names be ? ComputeDisplayNames(r.[[locale]], style, processedKeys).
+ const names = intl_ComputeDisplayNames(r.locale, style, processedKeys);
+
+ // 17. Let values be ObjectCreate(%ObjectPrototype%).
+ const values = {};
+
+ // 18. Set i to 0.
+ // 19. Repeat, while i < len
+ for (let i = 0; i < len; i++) {
+ // a. Let key be ? Get(processedKeys, i).
+ const key = processedKeys[i];
+ // b. Let name be ? Get(names, i).
+ const name = names[i];
+ // c. Assert: Type(name) is string.
+ assert(typeof name === "string", "unexpected non-string value");
+ // d. Assert: the length of name is greater than zero.
+ assert(name.length > 0, "empty string value");
+ // e. Perform ? DefinePropertyOrThrow(values, key, name).
+ _DefineDataProperty(values, key, name);
+ }
+
+ // 20. Let options be ObjectCreate(%ObjectPrototype%).
+ // 21. Perform ! DefinePropertyOrThrow(result, "locale", r.[[locale]]).
+ // 22. Perform ! DefinePropertyOrThrow(result, "style", style).
+ // 23. Perform ! DefinePropertyOrThrow(result, "values", values).
+ const result = { locale: r.locale, style, values };
+
+ // 24. Return result.
+ return result;
+}
+
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -472,16 +472,18 @@ MSG_DEF(JSMSG_TRACELOGGER_ENABLE_FAIL, 1
// Intl
MSG_DEF(JSMSG_DATE_NOT_FINITE, 0, JSEXN_RANGEERR, "date value is not finite in DateTimeFormat.format()")
MSG_DEF(JSMSG_INTERNAL_INTL_ERROR, 0, JSEXN_ERR, "internal error while computing Intl data")
MSG_DEF(JSMSG_INTL_OBJECT_NOT_INITED, 3, JSEXN_TYPEERR, "Intl.{0}.prototype.{1} called on value that's not an object initialized as a {2}")
MSG_DEF(JSMSG_INTL_OBJECT_REINITED, 0, JSEXN_TYPEERR, "can't initialize object twice as an object of an Intl constructor")
MSG_DEF(JSMSG_INVALID_CURRENCY_CODE, 1, JSEXN_RANGEERR, "invalid currency code in NumberFormat(): {0}")
MSG_DEF(JSMSG_INVALID_DIGITS_VALUE, 1, JSEXN_RANGEERR, "invalid digits value: {0}")
+MSG_DEF(JSMSG_INVALID_KEYS_TYPE, 0, JSEXN_TYPEERR, "calendar info keys must be an object or undefined")
+MSG_DEF(JSMSG_INVALID_KEY, 1, JSEXN_RANGEERR, "invalid key: {0}")
MSG_DEF(JSMSG_INVALID_LANGUAGE_TAG, 1, JSEXN_RANGEERR, "invalid language tag: {0}")
MSG_DEF(JSMSG_INVALID_LOCALES_ELEMENT, 0, JSEXN_TYPEERR, "invalid element in locales argument")
MSG_DEF(JSMSG_INVALID_LOCALE_MATCHER, 1, JSEXN_RANGEERR, "invalid locale matcher in supportedLocalesOf(): {0}")
MSG_DEF(JSMSG_INVALID_OPTION_VALUE, 2, JSEXN_RANGEERR, "invalid value {1} for option {0}")
MSG_DEF(JSMSG_INVALID_TIME_ZONE, 1, JSEXN_RANGEERR, "invalid time zone in DateTimeFormat(): {0}")
MSG_DEF(JSMSG_UNDEFINED_CURRENCY, 0, JSEXN_TYPEERR, "undefined currency in NumberFormat() with currency style")
// RegExp
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -902,16 +902,17 @@ AddIntlExtras(JSContext* cx, unsigned ar
if (!args.get(0).isObject()) {
JS_ReportErrorASCII(cx, "addIntlExtras must be passed an object");
return false;
}
JS::RootedObject intl(cx, &args[0].toObject());
static const JSFunctionSpec funcs[] = {
JS_SELF_HOSTED_FN("getCalendarInfo", "Intl_getCalendarInfo", 1, 0),
+ JS_SELF_HOSTED_FN("getDisplayNames", "Intl_getDisplayNames", 2, 0),
JS_FS_END
};
if (!JS_DefineFunctions(cx, intl, funcs))
return false;
args.rval().setUndefined();
return true;
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/getDisplayNames.js
@@ -0,0 +1,238 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl')||!this.hasOwnProperty('addIntlExtras'))
+/* 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/. */
+
+// Tests the getCalendarInfo function with a diverse set of arguments.
+
+/*
+ * Test if getDisplayNames return value matches expected values.
+ */
+function checkDisplayNames(names, expected)
+{
+ assertEq(Object.getPrototypeOf(names), Object.prototype);
+
+ assertEq(names.locale, expected.locale);
+ assertEq(names.style, expected.style);
+
+ const nameValues = names.values;
+ const expectedValues = expected.values;
+
+ const nameValuesKeys = Object.getOwnPropertyNames(nameValues).sort();
+ const expectedValuesKeys = Object.getOwnPropertyNames(expectedValues).sort();
+
+ assertEqArray(nameValuesKeys, expectedValuesKeys);
+
+ for (let key of expectedValuesKeys)
+ assertEq(nameValues[key], expectedValues[key]);
+}
+
+addIntlExtras(Intl);
+
+let gDN = Intl.getDisplayNames;
+
+assertEq(gDN.length, 2);
+
+checkDisplayNames(gDN('en-US', {
+}), {
+ locale: 'en-US',
+ style: 'long',
+ values: {}
+});
+
+checkDisplayNames(gDN('en-US', {
+ keys: [
+ 'dates/gregorian/weekdays/wednesday'
+ ],
+ style: 'narrow'
+}), {
+ locale: 'en-US',
+ style: 'narrow',
+ values: {
+ 'dates/gregorian/weekdays/wednesday': 'W'
+ }
+});
+
+checkDisplayNames(gDN('en-US', {
+ keys: [
+ 'dates/fields/year',
+ 'dates/fields/month',
+ 'dates/fields/week',
+ 'dates/fields/day',
+ 'dates/gregorian/months/january',
+ 'dates/gregorian/months/february',
+ 'dates/gregorian/months/march',
+ 'dates/gregorian/weekdays/tuesday'
+ ]
+}), {
+ locale: 'en-US',
+ style: 'long',
+ values: {
+ 'dates/fields/year': 'year',
+ 'dates/fields/month': 'month',
+ 'dates/fields/week': 'week',
+ 'dates/fields/day': 'day',
+ 'dates/gregorian/months/january': 'January',
+ 'dates/gregorian/months/february': 'February',
+ 'dates/gregorian/months/march': 'March',
+ 'dates/gregorian/weekdays/tuesday': 'Tuesday',
+ }
+});
+
+checkDisplayNames(gDN('fr', {
+ keys: [
+ 'dates/fields/year',
+ 'dates/fields/day',
+ 'dates/gregorian/months/october',
+ 'dates/gregorian/weekdays/saturday',
+ 'dates/gregorian/dayperiods/pm'
+ ]
+}), {
+ locale: 'fr',
+ style: 'long',
+ values: {
+ 'dates/fields/year': 'année',
+ 'dates/fields/day': 'jour',
+ 'dates/gregorian/months/october': 'octobre',
+ 'dates/gregorian/weekdays/saturday': 'samedi',
+ 'dates/gregorian/dayperiods/pm': 'PM'
+ }
+});
+
+checkDisplayNames(gDN('it', {
+ style: 'short',
+ keys: [
+ 'dates/gregorian/weekdays/thursday',
+ 'dates/gregorian/months/august',
+ 'dates/gregorian/dayperiods/am',
+ 'dates/fields/month',
+ ]
+}), {
+ locale: 'it',
+ style: 'short',
+ values: {
+ 'dates/gregorian/weekdays/thursday': 'gio',
+ 'dates/gregorian/months/august': 'ago',
+ 'dates/gregorian/dayperiods/am': 'AM',
+ 'dates/fields/month': 'mese'
+ }
+});
+
+checkDisplayNames(gDN('ar', {
+ style: 'long',
+ keys: [
+ 'dates/gregorian/weekdays/thursday',
+ 'dates/gregorian/months/august',
+ 'dates/gregorian/dayperiods/am',
+ 'dates/fields/month',
+ ]
+}), {
+ locale: 'ar',
+ style: 'long',
+ values: {
+ 'dates/gregorian/weekdays/thursday': 'الخميس',
+ 'dates/gregorian/months/august': 'أغسطس',
+ 'dates/gregorian/dayperiods/am': 'ص',
+ 'dates/fields/month': 'الشهر'
+ }
+});
+
+/* Invalid input */
+
+assertThrowsInstanceOf(() => {
+ gDN('en-US', {
+ style: '',
+ keys: [
+ 'dates/gregorian/weekdays/thursday',
+ ]
+ });
+}, RangeError);
+
+assertThrowsInstanceOf(() => {
+ gDN('en-US', {
+ style: 'bogus',
+ keys: [
+ 'dates/gregorian/weekdays/thursday',
+ ]
+ });
+}, RangeError);
+
+assertThrowsInstanceOf(() => {
+ gDN('foo-X', {
+ keys: [
+ 'dates/gregorian/weekdays/thursday',
+ ]
+ });
+}, RangeError);
+
+const typeErrorKeys = [
+ null,
+ 'string',
+ Symbol.iterator,
+ 15,
+ 1,
+ 3.7,
+ NaN,
+ Infinity
+];
+
+for (let keys of typeErrorKeys) {
+ assertThrowsInstanceOf(() => {
+ gDN('en-US', {
+ keys
+ });
+ }, TypeError);
+}
+
+const rangeErrorKeys = [
+ [''],
+ ['foo'],
+ ['dates/foo'],
+ ['/dates/foo'],
+ ['dates/foo/foo'],
+ ['dates/fields'],
+ ['dates/fields/'],
+ ['dates/fields/foo'],
+ ['dates/fields/foo/month'],
+ ['/dates/foo/faa/bar/baz'],
+ ['dates///bar/baz'],
+ ['dates/gregorian'],
+ ['dates/gregorian/'],
+ ['dates/gregorian/foo'],
+ ['dates/gregorian/months'],
+ ['dates/gregorian/months/foo'],
+ ['dates/gregorian/weekdays'],
+ ['dates/gregorian/weekdays/foo'],
+ ['dates/gregorian/dayperiods'],
+ ['dates/gregorian/dayperiods/foo'],
+ ['dates/gregorian/months/الشهر'],
+ [3],
+ [null],
+ ['d', 'a', 't', 'e', 's'],
+ ['datesEXTRA'],
+ ['dates/fieldsEXTRA'],
+ ['dates/gregorianEXTRA'],
+ ['dates/gregorian/monthsEXTRA'],
+ ['dates/gregorian/weekdaysEXTRA'],
+ ['dates/fields/dayperiods/amEXTRA'],
+ ['dates/gregori\u1161n/months/january'],
+ ["dates/fields/year/"],
+ ["dates/fields/month/"],
+ ["dates/fields/week/"],
+ ["dates/fields/day/"],
+ ["dates/gregorian/months/january/"],
+ ["dates/gregorian/weekdays/saturday/"],
+ ["dates/gregorian/dayperiods/am/"],
+ ["dates/fields/months/january/"],
+];
+
+for (let keys of rangeErrorKeys) {
+ assertThrowsInstanceOf(() => {
+ gDN('en-US', {
+ keys
+ });
+ }, RangeError);
+}
+
+if (typeof reportCompare === 'function')
+ reportCompare(0, 0);
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2472,16 +2472,17 @@ static const JSFunctionSpec intrinsic_fu
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_defaultTimeZone", intl_defaultTimeZone, 0,0),
JS_FN("intl_defaultTimeZoneOffset", intl_defaultTimeZoneOffset, 0,0),
JS_FN("intl_FormatDateTime", intl_FormatDateTime, 2,0),
JS_FN("intl_FormatNumber", intl_FormatNumber, 2,0),
JS_FN("intl_GetCalendarInfo", intl_GetCalendarInfo, 1,0),
+ JS_FN("intl_ComputeDisplayNames", intl_ComputeDisplayNames, 3,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_INLINABLE_FN("IsRegExpObject",
intrinsic_IsInstanceOfBuiltin<RegExpObject>, 1,0,
--- a/toolkit/components/mozintl/MozIntl.cpp
+++ b/toolkit/components/mozintl/MozIntl.cpp
@@ -39,16 +39,42 @@ MozIntl::AddGetCalendarInfo(JS::Handle<J
if (!JS_DefineFunctions(cx, realIntlObj, funcs)) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
+NS_IMETHODIMP
+MozIntl::AddGetDisplayNames(JS::Handle<JS::Value> val, JSContext* cx)
+{
+ if (!val.isObject()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ JS::Rooted<JSObject*> realIntlObj(cx, js::CheckedUnwrap(&val.toObject()));
+ if (!realIntlObj) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ JSAutoCompartment ac(cx, realIntlObj);
+
+ static const JSFunctionSpec funcs[] = {
+ JS_SELF_HOSTED_FN("getDisplayNames", "Intl_getDisplayNames", 2, 0),
+ JS_FS_END
+ };
+
+ if (!JS_DefineFunctions(cx, realIntlObj, funcs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
NS_GENERIC_FACTORY_CONSTRUCTOR(MozIntl)
NS_DEFINE_NAMED_CID(MOZ_MOZINTL_CID);
static const Module::CIDEntry kMozIntlCIDs[] = {
{ &kMOZ_MOZINTL_CID, false, nullptr, MozIntlConstructor },
{ nullptr }
};
--- a/toolkit/components/mozintl/mozIMozIntl.idl
+++ b/toolkit/components/mozintl/mozIMozIntl.idl
@@ -4,9 +4,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
[scriptable, uuid(9f9bc42e-54f4-11e6-9aed-4b1429ac0ba0)]
interface mozIMozIntl : nsISupports
{
[implicit_jscontext] void addGetCalendarInfo(in jsval intlObject);
+ [implicit_jscontext] void addGetDisplayNames(in jsval intlObject);
};
--- a/toolkit/components/mozintl/test/test_mozintl.js
+++ b/toolkit/components/mozintl/test/test_mozintl.js
@@ -2,16 +2,17 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
function run_test() {
const mozIntl = Components.classes["@mozilla.org/mozintl;1"]
.getService(Components.interfaces.mozIMozIntl);
test_this_global(mozIntl);
test_cross_global(mozIntl);
+ test_methods_presence(mozIntl);
ok(true);
}
function test_this_global(mozIntl) {
let x = {};
mozIntl.addGetCalendarInfo(x);
@@ -25,8 +26,21 @@ function test_cross_global(mozIntl) {
mozIntl.addGetCalendarInfo(x);
var waivedX = Components.utils.waiveXrays(x);
equal(waivedX.getCalendarInfo instanceof Function, false);
equal(waivedX.getCalendarInfo instanceof global.Function, true);
equal(waivedX.getCalendarInfo() instanceof Object, false);
equal(waivedX.getCalendarInfo() instanceof global.Object, true);
}
+
+function test_methods_presence(mozIntl) {
+ equal(mozIntl.addGetCalendarInfo instanceof Function, true);
+ equal(mozIntl.addGetDisplayNames instanceof Function, true);
+
+ let x = {};
+
+ mozIntl.addGetCalendarInfo(x);
+ equal(x.getCalendarInfo instanceof Function, true);
+
+ mozIntl.addGetDisplayNames(x);
+ equal(x.getDisplayNames instanceof Function, true);
+}