diff options
author | Moonchild <moonchild@palemoon.org> | 2022-07-25 13:44:43 +0000 |
---|---|---|
committer | Moonchild <moonchild@palemoon.org> | 2022-07-25 13:44:43 +0000 |
commit | 7802bdb5f78f430de34fdfd9ac899f29f9974537 (patch) | |
tree | af2af37f56790bd99566c5566fafef3ec6b266de | |
parent | 6542ca6bcdf836ee1fb82b75d77adb0e9604b97b (diff) | |
parent | 824c2f0d4012f8e718dcca5c3db9b796a7530008 (diff) | |
download | uxp-7802bdb5f78f430de34fdfd9ac899f29f9974537.tar.gz |
Merge pull request 'Implement Intl.RelativeTimeFormat.' (#1974) from jobbautista9/UXP:1969-relativetimeformat-take3 into master
Reviewed-on: https://repo.palemoon.org/MoonchildProductions/UXP/pulls/1974
-rw-r--r-- | config/check_spidermonkey_style.py | 2 | ||||
-rw-r--r-- | config/external/icu/defs.mozbuild | 1 | ||||
-rw-r--r-- | intl/icu-patches/bug-1504656-relativetimeformat-plural-other-fallback.diff | 45 | ||||
-rw-r--r-- | intl/icu/source/i18n/reldatefmt.cpp | 20 | ||||
-rwxr-xr-x[-rw-r--r--] | intl/patch-icu.sh | 2 | ||||
-rwxr-xr-x | intl/update-icu.sh | 5 | ||||
-rw-r--r-- | js/public/Class.h | 2 | ||||
-rw-r--r-- | js/src/builtin/Intl.cpp | 306 | ||||
-rw-r--r-- | js/src/builtin/Intl.h | 25 | ||||
-rw-r--r-- | js/src/builtin/Intl.js | 269 | ||||
-rw-r--r-- | js/src/vm/CommonPropertyNames.h | 4 | ||||
-rw-r--r-- | js/src/vm/GlobalObject.h | 6 | ||||
-rw-r--r-- | js/src/vm/SelfHosting.cpp | 2 |
13 files changed, 669 insertions, 20 deletions
diff --git a/config/check_spidermonkey_style.py b/config/check_spidermonkey_style.py index 4667a1a2ff..eb272a81c6 100644 --- a/config/check_spidermonkey_style.py +++ b/config/check_spidermonkey_style.py @@ -86,12 +86,14 @@ included_inclnames_to_ignore = set([ 'unicode/ucol.h', # ICU 'unicode/udat.h', # ICU 'unicode/udatpg.h', # ICU + 'unicode/udisplaycontext.h',# ICU 'unicode/uenum.h', # ICU 'unicode/uniset.h', # ICU 'unicode/unorm.h', # ICU 'unicode/unum.h', # ICU 'unicode/unumsys.h', # ICU 'unicode/upluralrules.h', # ICU + 'unicode/ureldatefmt.h', # ICU 'unicode/ustring.h', # ICU 'unicode/utypes.h', # ICU 'vtune/VTuneWrapper.h' # VTune diff --git a/config/external/icu/defs.mozbuild b/config/external/icu/defs.mozbuild index a6249783f4..165564cf32 100644 --- a/config/external/icu/defs.mozbuild +++ b/config/external/icu/defs.mozbuild @@ -14,7 +14,6 @@ DEFINES.update( UCONFIG_NO_LEGACY_CONVERSION = True, UCONFIG_NO_TRANSLITERATION = True, UCONFIG_NO_REGULAR_EXPRESSIONS = True, - UCONFIG_NO_BREAK_ITERATION = True, # We don't need to pass data to and from legacy char* APIs. U_CHARSET_IS_UTF8 = True, diff --git a/intl/icu-patches/bug-1504656-relativetimeformat-plural-other-fallback.diff b/intl/icu-patches/bug-1504656-relativetimeformat-plural-other-fallback.diff new file mode 100644 index 0000000000..060ea5c0bf --- /dev/null +++ b/intl/icu-patches/bug-1504656-relativetimeformat-plural-other-fallback.diff @@ -0,0 +1,45 @@ +Workaround for https://unicode-org.atlassian.net/browse/ICU-20253 + +https://bugzilla.mozilla.org/show_bug.cgi?id=1504656 + +diff --git a/intl/icu/source/i18n/reldatefmt.cpp b/intl/icu/source/i18n/reldatefmt.cpp +--- a/intl/icu/source/i18n/reldatefmt.cpp ++++ b/intl/icu/source/i18n/reldatefmt.cpp +@@ -157,24 +157,30 @@ const UnicodeString& RelativeDateTimeCac + } + + // Use fallback cache for SimpleFormatter relativeUnits. + const SimpleFormatter* RelativeDateTimeCacheData::getRelativeDateTimeUnitFormatter( + int32_t fStyle, + URelativeDateTimeUnit unit, + int32_t pastFutureIndex, + int32_t pluralUnit) const { +- int32_t style = fStyle; +- do { +- if (relativeUnitsFormatters[style][unit][pastFutureIndex][pluralUnit] != nullptr) { +- return relativeUnitsFormatters[style][unit][pastFutureIndex][pluralUnit]; ++ while (true) { ++ int32_t style = fStyle; ++ do { ++ if (relativeUnitsFormatters[style][unit][pastFutureIndex][pluralUnit] != nullptr) { ++ return relativeUnitsFormatters[style][unit][pastFutureIndex][pluralUnit]; ++ } ++ style = fallBackCache[style]; ++ } while (style != -1); ++ ++ if (pluralUnit == StandardPlural::OTHER) { ++ return nullptr; // No formatter found. + } +- style = fallBackCache[style]; +- } while (style != -1); +- return nullptr; // No formatter found. ++ pluralUnit = StandardPlural::OTHER; ++ } + } + + static UBool getStringWithFallback( + const UResourceBundle *resource, + const char *key, + UnicodeString &result, + UErrorCode &status) { + int32_t len = 0; diff --git a/intl/icu/source/i18n/reldatefmt.cpp b/intl/icu/source/i18n/reldatefmt.cpp index 1a4da0f0b9..85637cb456 100644 --- a/intl/icu/source/i18n/reldatefmt.cpp +++ b/intl/icu/source/i18n/reldatefmt.cpp @@ -162,14 +162,20 @@ const UnicodeString& RelativeDateTimeCacheData::getAbsoluteUnitString( URelativeDateTimeUnit unit, int32_t pastFutureIndex, int32_t pluralUnit) const { - int32_t style = fStyle; - do { - if (relativeUnitsFormatters[style][unit][pastFutureIndex][pluralUnit] != nullptr) { - return relativeUnitsFormatters[style][unit][pastFutureIndex][pluralUnit]; + while (true) { + int32_t style = fStyle; + do { + if (relativeUnitsFormatters[style][unit][pastFutureIndex][pluralUnit] != nullptr) { + return relativeUnitsFormatters[style][unit][pastFutureIndex][pluralUnit]; + } + style = fallBackCache[style]; + } while (style != -1); + + if (pluralUnit == StandardPlural::OTHER) { + return nullptr; // No formatter found. } - style = fallBackCache[style]; - } while (style != -1); - return nullptr; // No formatter found. + pluralUnit = StandardPlural::OTHER; + } } static UBool getStringWithFallback( diff --git a/intl/patch-icu.sh b/intl/patch-icu.sh index 4c8cd89cba..a8e9977ea5 100644..100755 --- a/intl/patch-icu.sh +++ b/intl/patch-icu.sh @@ -21,7 +21,9 @@ for patch in \ suppress-warnings.diff \ bug-1172609-timezone-recreateDefault.diff \ bug-1198952-workaround-make-3.82-bug.diff \ + bug-1504656-relativetimeformat-plural-other-fallback.diff \ ; do echo "Applying local patch $patch" patch -d ${icu_dir}/../../ -p1 --no-backup-if-mismatch < ${icu_dir}/../icu-patches/$patch done + diff --git a/intl/update-icu.sh b/intl/update-icu.sh index 7e4c833c7b..ae22076ab3 100755 --- a/intl/update-icu.sh +++ b/intl/update-icu.sh @@ -7,11 +7,11 @@ set -e # Update to an ICU release: # Usage: update-icu.sh <URL of ICU GIT> <release tag name> -# E.g., for ICU 63.2: update-icu.sh https://github.com/unicode-org/icu.git release-63-2 +# E.g., for ICU 62.2: update-icu.sh https://github.com/unicode-org/icu.git release-62-2 # # Update to an ICU maintenance branch: # Usage: update-icu.sh <URL of ICU GIT> <maintenance name> -# E.g., for ICU 63.2: update-icu.sh https://github.com/unicode-org/icu.git maint/maint-63 +# E.g., for ICU 62.2: update-icu.sh https://github.com/unicode-org/icu.git maint/maint-62 if [ $# -lt 2 ]; then echo "Usage: update-icu.sh <URL of ICU GIT> <release tag name>" @@ -86,6 +86,7 @@ for patch in \ suppress-warnings.diff \ bug-1172609-timezone-recreateDefault.diff \ bug-1198952-workaround-make-3.82-bug.diff \ + bug-1504656-relativetimeformat-plural-other-fallback.diff \ ; do echo "Applying local patch $patch" patch -d ${icu_dir}/../../ -p1 --no-backup-if-mismatch < ${icu_dir}/../icu-patches/$patch diff --git a/js/public/Class.h b/js/public/Class.h index 634e5a281d..d7e2ab40db 100644 --- a/js/public/Class.h +++ b/js/public/Class.h @@ -913,7 +913,7 @@ struct JSClass { // application. #define JSCLASS_GLOBAL_APPLICATION_SLOTS 5 #define JSCLASS_GLOBAL_SLOT_COUNT \ - (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 46) + (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 47) #define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \ (JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n))) #define JSCLASS_GLOBAL_FLAGS \ diff --git a/js/src/builtin/Intl.cpp b/js/src/builtin/Intl.cpp index 5455b3a854..494a0c0473 100644 --- a/js/src/builtin/Intl.cpp +++ b/js/src/builtin/Intl.cpp @@ -11,6 +11,7 @@ #include "builtin/Intl.h" #include "mozilla/Casting.h" +#include "mozilla/FloatingPoint.h" #include "mozilla/PodOperations.h" #include "mozilla/Range.h" @@ -28,10 +29,12 @@ #include "unicode/ucol.h" #include "unicode/udat.h" #include "unicode/udatpg.h" +#include "unicode/udisplaycontext.h" #include "unicode/uenum.h" #include "unicode/unum.h" #include "unicode/unumsys.h" #include "unicode/upluralrules.h" +#include "unicode/ureldatefmt.h" #include "unicode/ustring.h" #include "vm/DateTime.h" #include "vm/GlobalObject.h" @@ -3205,6 +3208,303 @@ js::intl_GetPluralCategories(JSContext* cx, unsigned argc, Value* vp) return true; } +/**************** RelativeTimeFormat *****************/ + +static void relativeTimeFormat_finalize(FreeOp* fop, JSObject* obj); + +static const uint32_t URELATIVE_TIME_FORMAT_SLOT = 0; +static const uint32_t RELATIVE_TIME_FORMAT_SLOTS_COUNT = 1; + +static const ClassOps RelativeTimeFormatClassOps = { + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* getProperty */ + nullptr, /* enumerate */ + nullptr, /* newEnumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + relativeTimeFormat_finalize +}; + +static const Class RelativeTimeFormatClass = { + js_Object_str, + JSCLASS_HAS_RESERVED_SLOTS(RELATIVE_TIME_FORMAT_SLOTS_COUNT) | + JSCLASS_FOREGROUND_FINALIZE, + &RelativeTimeFormatClassOps +}; + +#if JS_HAS_TOSOURCE +static bool +relativeTimeFormat_toSource(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setString(cx->names().RelativeTimeFormat); + return true; +} +#endif + +static const JSFunctionSpec relativeTimeFormat_static_methods[] = { + JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_RelativeTimeFormat_supportedLocalesOf", 1, 0), + JS_FS_END +}; + +static const JSFunctionSpec relativeTimeFormat_methods[] = { + JS_SELF_HOSTED_FN("resolvedOptions", "Intl_RelativeTimeFormat_resolvedOptions", 0, 0), + JS_SELF_HOSTED_FN("format", "Intl_RelativeTimeFormat_format", 2, 0), +#if JS_HAS_TOSOURCE + JS_FN(js_toSource_str, relativeTimeFormat_toSource, 0, 0), +#endif + JS_FS_END +}; + +static const JSPropertySpec relativeTimeFormat_properties[] = { + JS_STRING_SYM_PS(toStringTag, "Intl.RelativeTimeFormat", JSPROP_READONLY), + JS_PS_END}; + +/** + * RelativeTimeFormat constructor. + * Spec: ECMAScript 402 API, RelativeTimeFormat, 1.1 + */ +static bool +RelativeTimeFormat(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + if (!ThrowIfNotConstructing(cx, args, "Intl.RelativeTimeFormat")) + return false; + + // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor). + RootedObject proto(cx); + if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto)) + return false; + + if (!proto) { + proto = GlobalObject::getOrCreateRelativeTimeFormatPrototype(cx, cx->global()); + if (!proto) + return false; + } + + RootedObject relativeTimeFormat(cx); + relativeTimeFormat = NewObjectWithGivenProto(cx, &RelativeTimeFormatClass, proto); + if (!relativeTimeFormat) + return false; + + relativeTimeFormat->as<NativeObject>().setReservedSlot(URELATIVE_TIME_FORMAT_SLOT, PrivateValue(nullptr)); + + RootedValue locales(cx, args.get(0)); + RootedValue options(cx, args.get(1)); + + // Step 3. + if (!IntlInitialize(cx, relativeTimeFormat, cx->names().InitializeRelativeTimeFormat, locales, options)) + return false; + + args.rval().setObject(*relativeTimeFormat); + return true; +} + +static void +relativeTimeFormat_finalize(FreeOp* fop, JSObject* obj) +{ + MOZ_ASSERT(fop->onMainThread()); + + // This is-undefined check shouldn't be necessary, but for internal + // brokenness in object allocation code. For the moment, hack around it by + // explicitly guarding against the possibility of the reserved slot not + // containing a private. See bug 949220. + const Value& slot = obj->as<NativeObject>().getReservedSlot(URELATIVE_TIME_FORMAT_SLOT); + if (!slot.isUndefined()) { + if (URelativeDateTimeFormatter* rtf = static_cast<URelativeDateTimeFormatter*>(slot.toPrivate())) + ureldatefmt_close(rtf); + } +} + +static JSObject* +CreateRelativeTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global) +{ + RootedFunction ctor(cx); + ctor = global->createConstructor(cx, &RelativeTimeFormat, cx->names().RelativeTimeFormat, 0); + if (!ctor) + return nullptr; + + RootedNativeObject proto(cx, GlobalObject::createBlankPrototype(cx, global, &RelativeTimeFormatClass)); + if (!proto) + return nullptr; + proto->setReservedSlot(URELATIVE_TIME_FORMAT_SLOT, PrivateValue(nullptr)); + + if (!LinkConstructorAndPrototype(cx, ctor, proto)) + return nullptr; + + if (!JS_DefineFunctions(cx, ctor, relativeTimeFormat_static_methods)) + return nullptr; + + if (!JS_DefineFunctions(cx, proto, relativeTimeFormat_methods)) + return nullptr; + + if (!JS_DefineProperties(cx, proto, relativeTimeFormat_properties)) + return nullptr; + + RootedValue options(cx); + if (!CreateDefaultOptions(cx, &options)) + return nullptr; + + if (!IntlInitialize(cx, proto, cx->names().InitializeRelativeTimeFormat, UndefinedHandleValue, + options)) + { + return nullptr; + } + + RootedValue ctorValue(cx, ObjectValue(*ctor)); + if (!DefineProperty(cx, Intl, cx->names().RelativeTimeFormat, ctorValue, nullptr, nullptr, 0)) { + return nullptr; + } + + return proto; +} + +bool +js::intl_RelativeTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 0); + + RootedValue result(cx); + // We're going to use ULocale availableLocales as per ICU recommendation: + // https://ssl.icu-project.org/trac/ticket/12756 + if (!intl_availableLocales(cx, uloc_countAvailable, uloc_getAvailable, &result)) + return false; + args.rval().set(result); + return true; +} + + +enum class RelativeTimeNumeric +{ + /** + * Only strings with numeric components like `1 day ago`. + */ + Always, + /** + * Natural-language strings like `yesterday` when possible, + * otherwise strings with numeric components as in `7 months ago`. + */ + Auto, +}; + +bool +js::intl_FormatRelativeTime(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 3); + + RootedObject relativeTimeFormat(cx, &args[0].toObject()); + + RootedObject internals(cx, GetInternals(cx, relativeTimeFormat)); + if (!internals) + return false; + + RootedValue value(cx); + + if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) + return false; + JSAutoByteString locale(cx, value.toString()); + if (!locale) + return false; + + if (!GetProperty(cx, internals, internals, cx->names().style, &value)) + return false; + RootedLinearString style(cx, value.toString()->ensureLinear(cx)); + if (!style) + return false; + + double t = args[1].toNumber(); + + UDateRelativeDateTimeFormatterStyle relDateTimeStyle; + + if (StringEqualsAscii(style, "short")) { + relDateTimeStyle = UDAT_STYLE_SHORT; + } else if (StringEqualsAscii(style, "narrow")) { + relDateTimeStyle = UDAT_STYLE_NARROW; + } else { + MOZ_ASSERT(StringEqualsAscii(style, "long")); + relDateTimeStyle = UDAT_STYLE_LONG; + } + + URelativeDateTimeUnit relDateTimeUnit; + { + JSLinearString* unit = args[2].toString()->ensureLinear(cx); + if (!unit) { + return false; + } + + if (StringEqualsAscii(unit, "second") || StringEqualsAscii(unit, "seconds")) { + relDateTimeUnit = UDAT_REL_UNIT_SECOND; + } else if (StringEqualsAscii(unit, "minute") || StringEqualsAscii(unit, "minutes")) { + relDateTimeUnit = UDAT_REL_UNIT_MINUTE; + } else if (StringEqualsAscii(unit, "hour") || StringEqualsAscii(unit, "hours")) { + relDateTimeUnit = UDAT_REL_UNIT_HOUR; + } else if (StringEqualsAscii(unit, "day") || StringEqualsAscii(unit, "days")) { + relDateTimeUnit = UDAT_REL_UNIT_DAY; + } else if (StringEqualsAscii(unit, "week") || StringEqualsAscii(unit, "weeks")) { + relDateTimeUnit = UDAT_REL_UNIT_WEEK; + } else if (StringEqualsAscii(unit, "month") || StringEqualsAscii(unit, "months")) { + relDateTimeUnit = UDAT_REL_UNIT_MONTH; + } else if (StringEqualsAscii(unit, "quarter") || StringEqualsAscii(unit, "quarters")) { + relDateTimeUnit = UDAT_REL_UNIT_QUARTER; + } else { + MOZ_ASSERT(StringEqualsAscii(unit, "year") || StringEqualsAscii(unit, "years")); + relDateTimeUnit = UDAT_REL_UNIT_YEAR; + } + } + + if (!GetProperty(cx, internals, internals, cx->names().numeric, &value)) + return false; + RootedLinearString numeric(cx, value.toString()->ensureLinear(cx)); + if (!numeric) + return false; + + RelativeTimeNumeric relDateTimeNumeric; + + if (StringEqualsAscii(numeric, "auto")) { + relDateTimeNumeric = RelativeTimeNumeric::Auto; + } else { + MOZ_ASSERT(StringEqualsAscii(numeric, "always")); + relDateTimeNumeric = RelativeTimeNumeric::Always; + } + + Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx); + if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE)) + return false; + UErrorCode status = U_ZERO_ERROR; + URelativeDateTimeFormatter* rtf = + ureldatefmt_open(icuLocale(locale.ptr()), nullptr, relDateTimeStyle, + UDISPCTX_CAPITALIZATION_FOR_STANDALONE, &status); + if (U_FAILURE(status)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + + int32_t size; + + if (relDateTimeNumeric == RelativeTimeNumeric::Auto) { + size = ureldatefmt_format(rtf, t, relDateTimeUnit, Char16ToUChar(chars.begin()), + INITIAL_CHAR_BUFFER_SIZE, &status); + } else { + MOZ_ASSERT(relDateTimeNumeric == RelativeTimeNumeric::Always); + size = ureldatefmt_formatNumeric(rtf, t, relDateTimeUnit, Char16ToUChar(chars.begin()), + INITIAL_CHAR_BUFFER_SIZE, &status); + } + + ScopedICUObject<URelativeDateTimeFormatter, ureldatefmt_close> closeRelativeTimeFormat(rtf); + + JSString* str = NewStringCopyN<CanGC>(cx, chars.begin(), size); + if (!str) + return false; + + args.rval().setString(str); + return true; +} + bool js::intl_GetCalendarInfo(JSContext* cx, unsigned argc, Value* vp) { @@ -3647,6 +3947,11 @@ GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global) if (!pluralRulesProto) return false; + RootedObject relativeTimeFmtProto(cx, CreateRelativeTimeFormatPrototype(cx, intl, global)); + if (!relativeTimeFmtProto) { + return false; + } + // The |Intl| object is fully set up now, so define the global property. RootedValue intlValue(cx, ObjectValue(*intl)); if (!DefineProperty(cx, global, cx->names().Intl, intlValue, nullptr, nullptr, @@ -3668,6 +3973,7 @@ GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global) global->setReservedSlot(DATE_TIME_FORMAT_PROTO, ObjectValue(*dateTimeFormatProto)); global->setReservedSlot(NUMBER_FORMAT_PROTO, ObjectValue(*numberFormatProto)); global->setReservedSlot(PLURAL_RULES_PROTO, ObjectValue(*pluralRulesProto)); + global->setReservedSlot(RELATIVE_TIME_FORMAT_PROTO, ObjectValue(*relativeTimeFmtProto)); // Also cache |Intl| to implement spec language that conditions behavior // based on values being equal to "the standard built-in |Intl| object". diff --git a/js/src/builtin/Intl.h b/js/src/builtin/Intl.h index fd1fc5da6a..330b8a4963 100644 --- a/js/src/builtin/Intl.h +++ b/js/src/builtin/Intl.h @@ -406,6 +406,31 @@ intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp); extern MOZ_MUST_USE bool intl_GetPluralCategories(JSContext* cx, unsigned argc, Value* vp); +/******************** RelativeTimeFormat ********************/ + +/** + * Returns an object indicating the supported locales for relative time format + * by having a true-valued property for each such locale with the + * canonicalized language tag as the property name. The object has no + * prototype. + * + * Usage: availableLocales = intl_RelativeTimeFormat_availableLocales() + */ +extern MOZ_MUST_USE bool +intl_RelativeTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp); + +/** + * Returns a relative time as a string formatted according to the effective + * locale and the formatting options of the given RelativeTimeFormat. + * + * t should be a number representing a number to be formatted. + * unit should be "second", "minute", "hour", "day", "week", "month", "quarter", or "year". + * + * Usage: formatted = intl_FormatRelativeTime(relativeTimeFormat, t, unit) + */ +extern MOZ_MUST_USE bool +intl_FormatRelativeTime(JSContext* cx, unsigned argc, Value* vp); + /** * Returns a plain object with calendar information for a single valid locale * (callers must perform this validation). The object will have these diff --git a/js/src/builtin/Intl.js b/js/src/builtin/Intl.js index ef0aa986a6..e7d1b58ed6 100644 --- a/js/src/builtin/Intl.js +++ b/js/src/builtin/Intl.js @@ -23,6 +23,7 @@ intl_FormatDateTime: false, intl_SelectPluralRule: false, intl_GetPluralCategories: false, + intl_FormatRelativeTime: false, intl_GetCalendarInfo: false, */ @@ -1275,7 +1276,8 @@ function setLazyData(internals, type, lazyData) { assert(internals.type === "partial", "can't set lazy data for anything but a newborn"); assert(type === "Collator" || type === "DateTimeFormat" || - type == "NumberFormat" || type === "PluralRules", + type === "NumberFormat" || type === "PluralRules" || + type === "RelativeTimeFormat", "bad type"); assert(IsObject(lazyData), "non-object lazy data"); @@ -1327,7 +1329,8 @@ function isInitializedIntlObject(obj) { assert(callFunction(std_Object_hasOwnProperty, internals, "type"), "missing type"); var type = internals.type; assert(type === "partial" || type === "Collator" || - type === "DateTimeFormat" || type === "NumberFormat" || type === "PluralRules", + type === "DateTimeFormat" || type === "NumberFormat" || + type === "PluralRules" || type === "RelativeTimeFormat", "unexpected type"); assert(callFunction(std_Object_hasOwnProperty, internals, "lazyData"), "missing lazyData"); assert(callFunction(std_Object_hasOwnProperty, internals, "internalProps"), "missing internalProps"); @@ -1381,14 +1384,24 @@ function getInternals(obj) var internalProps; var type = internals.type; - if (type === "Collator") - internalProps = resolveCollatorInternals(lazyData) - else if (type === "DateTimeFormat") - internalProps = resolveDateTimeFormatInternals(lazyData) - else if (type === "PluralRules") - internalProps = resolvePluralRulesInternals(lazyData) - else + + switch (type) { + case "Collator": + internalProps = resolveCollatorInternals(lazyData); + break; + case "DateTimeFormat": + internalProps = resolveDateTimeFormatInternals(lazyData); + break; + case "PluralRules": + internalProps = resolvePluralRulesInternals(lazyData); + break; + case "RelativeTimeFormat": + internalProps = resolveRelativeTimeFormatInternals(lazyData); + break; + default: // type === "NumberFormat" internalProps = resolveNumberFormatInternals(lazyData); + break; + } setInternalProperties(internals, internalProps); return internalProps; } @@ -3252,6 +3265,244 @@ function Intl_PluralRules_resolvedOptions() { } +/********** Intl.RelativeTimeFormat **********/ + +/** + * RelativeTimeFormat internal properties. + * + * Spec: ECMAScript 402 API, RelativeTimeFormat, 1.3.3. + */ +var relativeTimeFormatInternalProperties = { + localeData: relativeTimeFormatLocaleData, + _availableLocales: null, + availableLocales: function() // eslint-disable-line object-shorthand + { + var locales = this._availableLocales; + if (locales) + return locales; + + locales = intl_RelativeTimeFormat_availableLocales(); + addSpecialMissingLanguageTags(locales); + return (this._availableLocales = locales); + }, + relevantExtensionKeys: [], +}; + +function relativeTimeFormatLocaleData() { + // RelativeTimeFormat doesn't support any extension keys. + return {}; +} + +/** + * Compute an internal properties object from |lazyRelativeTimeFormatData|. + */ +function resolveRelativeTimeFormatInternals(lazyRelativeTimeFormatData) { + assert(IsObject(lazyRelativeTimeFormatData), "lazy data not an object?"); + + var internalProps = std_Object_create(null); + + var RelativeTimeFormat = relativeTimeFormatInternalProperties; + + // Steps 7-8. + const r = ResolveLocale(callFunction(RelativeTimeFormat.availableLocales, RelativeTimeFormat), + lazyRelativeTimeFormatData.requestedLocales, + lazyRelativeTimeFormatData.opt, + RelativeTimeFormat.relevantExtensionKeys, + RelativeTimeFormat.localeData); + + // Step 9-10. + internalProps.locale = r.locale; + + // Step 11. + assert(r.locale === r.dataLocale, + "resolved locale matches the resolved data-locale when no extension-keys are present"); + + // Step 13. + internalProps.style = lazyRelativeTimeFormatData.style; + + // Step 15. + internalProps.numeric = lazyRelativeTimeFormatData.numeric; + + // Steps 16-20 (Not relevant in our implementation). + + return internalProps; +} + +/** + * Returns an object containing the RelativeTimeFormat internal properties of |obj|, + * or throws a TypeError if |obj| isn't RelativeTimeFormat-initialized. + */ +function getRelativeTimeFormatInternals(obj, methodName) { + var internals = getIntlObjectInternals(obj, "RelativeTimeFormat", methodName); + assert(internals.type === "RelativeTimeFormat", "bad type escaped getIntlObjectInternals"); + + var internalProps = maybeInternalProperties(internals); + if (internalProps) + return internalProps; + + internalProps = resolveRelativeTimeFormatInternals(internals.lazyData); + setInternalProperties(internals, internalProps); + return internalProps; +} + +/** + * Initializes an object as a RelativeTimeFormat. + * + * This method is complicated a moderate bit by its implementing initialization + * as a *lazy* concept. Everything that must happen now, does -- but we defer + * all the work we can until the object is actually used as a RelativeTimeFormat. + * This later work occurs in |resolveRelativeTimeFormatInternals|; steps not noted + * here occur there. + * + * Spec: ECMAScript 402 API, RelativeTimeFormat, 1.1.1. + */ +function InitializeRelativeTimeFormat(relativeTimeFormat, locales, options) { + assert(IsObject(relativeTimeFormat), "InitializeRelativeTimeFormat"); + + if (isInitializedIntlObject(relativeTimeFormat)) + ThrowTypeError(JSMSG_INTL_OBJECT_REINITED); + + let internals = initializeIntlObject(relativeTimeFormat); + + // Lazy RelativeTimeFormat data has the following structure: + // + // { + // requestedLocales: List of locales, + // style: "long" / "short" / "narrow", + // numeric: "always" / "auto", + // + // opt: // opt object computed in InitializeRelativeTimeFormat + // { + // localeMatcher: "lookup" / "best fit", + // } + // } + // + // Note that lazy data is only installed as a final step of initialization, + // so every RelativeTimeFormat lazy data object has *all* these properties, never a + // subset of them. + const lazyRelativeTimeFormatData = std_Object_create(null); + + // Step 1. + let requestedLocales = CanonicalizeLocaleList(locales); + lazyRelativeTimeFormatData.requestedLocales = requestedLocales; + + // Steps 2-3. + if (options === undefined) + options = std_Object_create(null); + else + options = ToObject(options); + + // Step 4. + let opt = new Record(); + + // Steps 5-6. + let matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit"); + opt.localeMatcher = matcher; + + lazyRelativeTimeFormatData.opt = opt; + + // Steps 12-13. + const style = GetOption(options, "style", "string", ["long", "short", "narrow"], "long"); + lazyRelativeTimeFormatData.style = style; + + // Steps 14-15. + const numeric = GetOption(options, "numeric", "string", ["always", "auto"], "always"); + lazyRelativeTimeFormatData.numeric = numeric; + + setLazyData(internals, "RelativeTimeFormat", lazyRelativeTimeFormatData) +} + +/** + * Returns the subset of the given locale list for which this locale list has a + * matching (possibly fallback) locale. Locales appear in the same order in the + * returned list as in the input list. + * + * Spec: ECMAScript 402 API, RelativeTimeFormat, 1.3.2. + */ +function Intl_RelativeTimeFormat_supportedLocalesOf(locales /*, options*/) { + var options = arguments.length > 1 ? arguments[1] : undefined; + + // Step 1. + var availableLocales = callFunction(relativeTimeFormatInternalProperties.availableLocales, + relativeTimeFormatInternalProperties); + // Step 2. + let requestedLocales = CanonicalizeLocaleList(locales); + + // Step 3. + return SupportedLocales(availableLocales, requestedLocales, options); +} + +/** + * Returns a String value representing the written form of a relative date + * formatted according to the effective locale and the formatting options + * of this RelativeTimeFormat object. + * + * Spec: ECMAScript 402 API, RelativeTImeFormat, 1.4.3. + */ +function Intl_RelativeTimeFormat_format(value, unit) { + // Step 1. + let relativeTimeFormat = this; + + // Step 2. + let internals = getRelativeTimeFormatInternals(relativeTimeFormat, "format"); + + // Step 3. + let t = ToNumber(value); + + // Step 4. + let u = ToString(unit); + + // PartitionRelativeTimePattern, step 4. + if (!Number_isFinite(t)) { + ThrowRangeError(JSMSG_DATE_NOT_FINITE, "RelativeTimeFormat"); + } + + // PartitionRelativeTimePattern, step 5. + switch (u) { + case "second": + case "seconds": + case "minute": + case "minutes": + case "hour": + case "hours": + case "day": + case "days": + case "week": + case "weeks": + case "month": + case "months": + case "quarter": + case "quarters": + case "year": + case "years": + break; + default: + ThrowRangeError(JSMSG_INVALID_OPTION_VALUE, "unit", u); + } + + // Step 5. + return intl_FormatRelativeTime(relativeTimeFormat, t, u, internals.numeric); +} + +/** + * Returns the resolved options for a PluralRules object. + * + * Spec: ECMAScript 402 API, RelativeTimeFormat, 1.4.4. + */ +function Intl_RelativeTimeFormat_resolvedOptions() { + var internals = getRelativeTimeFormatInternals(this, "resolvedOptions"); + + // Steps 4-5. + var result = { + locale: internals.locale, + style: internals.style, + numeric: internals.numeric, + }; + + // Step 6. + return result; +} + function Intl_getCanonicalLocales(locales) { let codes = CanonicalizeLocaleList(locales); let result = []; diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index 445b8a6bb3..8ecd3a2083 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -182,6 +182,7 @@ macro(InitializeDateTimeFormat, InitializeDateTimeFormat, "InitializeDateTimeFormat") \ macro(InitializeNumberFormat, InitializeNumberFormat, "InitializeNumberFormat") \ macro(InitializePluralRules, InitializePluralRules, "InitializePluralRules") \ + macro(InitializeRelativeTimeFormat, InitializeRelativeTimeFormat, "InitializeRelativeTimeFormat") \ macro(innermost, innermost, "innermost") \ macro(inNursery, inNursery, "inNursery") \ macro(input, input, "input") \ @@ -297,6 +298,8 @@ macro(Reify, Reify, "Reify") \ macro(reject, reject, "reject") \ macro(rejected, rejected, "rejected") \ + macro(RelativeTimeFormat, RelativeTimeFormat, "RelativeTimeFormat") \ + macro(RelativeTimeFormatFormat, RelativeTimeFormatFormat, "Intl_RelativeTimeFormat_Format") \ macro(RequireObjectCoercible, RequireObjectCoercible, "RequireObjectCoercible") \ macro(resolve, resolve, "resolve") \ macro(resumeGenerator, resumeGenerator, "resumeGenerator") \ @@ -364,6 +367,7 @@ macro(unescape, unescape, "unescape") \ macro(uneval, uneval, "uneval") \ macro(unicode, unicode, "unicode") \ + macro(unit, unit, "unit") \ macro(uninitialized, uninitialized, "uninitialized") \ macro(unsized, unsized, "unsized") \ macro(unwatch, unwatch, "unwatch") \ diff --git a/js/src/vm/GlobalObject.h b/js/src/vm/GlobalObject.h index f9c0149f12..2e1b6ce87b 100644 --- a/js/src/vm/GlobalObject.h +++ b/js/src/vm/GlobalObject.h @@ -110,6 +110,7 @@ class GlobalObject : public NativeObject NUMBER_FORMAT_PROTO, DATE_TIME_FORMAT_PROTO, PLURAL_RULES_PROTO, + RELATIVE_TIME_FORMAT_PROTO, MODULE_PROTO, IMPORT_ENTRY_PROTO, EXPORT_ENTRY_PROTO, @@ -513,6 +514,11 @@ class GlobalObject : public NativeObject return getOrCreateObject(cx, global, PLURAL_RULES_PROTO, initIntlObject); } + static JSObject* + getOrCreateRelativeTimeFormatPrototype(JSContext* cx, Handle<GlobalObject*> global) { + return getOrCreateObject(cx, global, RELATIVE_TIME_FORMAT_PROTO, initIntlObject); + } + static bool ensureModulePrototypesCreated(JSContext *cx, Handle<GlobalObject*> global); JSObject* maybeGetModulePrototype() { diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 29c75c2064..058cc6b6d0 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -2520,6 +2520,8 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("intl_PluralRules_availableLocales", intl_PluralRules_availableLocales, 0,0), JS_FN("intl_GetPluralCategories", intl_GetPluralCategories, 2, 0), JS_FN("intl_SelectPluralRule", intl_SelectPluralRule, 2,0), + JS_FN("intl_RelativeTimeFormat_availableLocales", intl_RelativeTimeFormat_availableLocales, 0,0), + JS_FN("intl_FormatRelativeTime", intl_FormatRelativeTime, 3,0), JS_INLINABLE_FN("IsRegExpObject", intrinsic_IsInstanceOfBuiltin<RegExpObject>, 1,0, |