summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJob Bautista <jobbautista9@protonmail.com>2022-07-25 14:07:36 +0800
committerJob Bautista <jobbautista9@protonmail.com>2022-07-25 14:07:36 +0800
commit824c2f0d4012f8e718dcca5c3db9b796a7530008 (patch)
treeaf2af37f56790bd99566c5566fafef3ec6b266de
parent6542ca6bcdf836ee1fb82b75d77adb0e9604b97b (diff)
downloaduxp-824c2f0d4012f8e718dcca5c3db9b796a7530008.tar.gz
Issue #1969 - Implement Intl.RelativeTimeFormat.
Based on Mozilla bugs 1270140, 1504656, 1483545, and 1504334. Took note of Mozilla bug 1379222 which changed GetPrototypeFromCallableConstructor to GetPrototypeFromBuiltinConstructor. There are many other changes I did myself since the initial implementation by Mozilla wouldn't work with this codebase.
-rw-r--r--config/check_spidermonkey_style.py2
-rw-r--r--config/external/icu/defs.mozbuild1
-rw-r--r--intl/icu-patches/bug-1504656-relativetimeformat-plural-other-fallback.diff45
-rw-r--r--intl/icu/source/i18n/reldatefmt.cpp20
-rwxr-xr-x[-rw-r--r--]intl/patch-icu.sh2
-rwxr-xr-xintl/update-icu.sh5
-rw-r--r--js/public/Class.h2
-rw-r--r--js/src/builtin/Intl.cpp306
-rw-r--r--js/src/builtin/Intl.h25
-rw-r--r--js/src/builtin/Intl.js269
-rw-r--r--js/src/vm/CommonPropertyNames.h4
-rw-r--r--js/src/vm/GlobalObject.h6
-rw-r--r--js/src/vm/SelfHosting.cpp2
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,