Bug 1270140 - Add Intl.RelativeTimeFormat. r=Waldo
☠☠ backed out by 59045a9d7990 ☠ ☠
authorZibi Braniecki <zbraniecki@mozilla.com>
Fri, 06 Oct 2017 07:00:08 -0700
changeset 427878 024ce7dd2f65d6266fc86b2c330ae2e3347df415
parent 427877 e6510aeb4e94df029cc47d2d00cd2f19ba5cc9d3
child 427879 33ce36cb798bb3c9ebf781558a9082dd0479ccf7
push id97
push userfmarier@mozilla.com
push dateSat, 14 Oct 2017 01:12:59 +0000
reviewersWaldo
bugs1270140
milestone58.0a1
Bug 1270140 - Add Intl.RelativeTimeFormat. r=Waldo MozReview-Commit-ID: GqetnVVmXXL
config/external/icu/defs.mozbuild
js/public/Class.h
js/src/builtin/Intl.cpp
js/src/builtin/Intl.h
js/src/builtin/Intl.js
js/src/jit/InlinableNatives.h
js/src/jit/MCallOptimize.cpp
js/src/jsfriendapi.h
js/src/shell/js.cpp
js/src/tests/Intl/RelativeTimeFormat/browser.js
js/src/tests/Intl/RelativeTimeFormat/construct-newtarget.js
js/src/tests/Intl/RelativeTimeFormat/format.js
js/src/tests/Intl/RelativeTimeFormat/relativetimeformat.js
js/src/tests/Intl/RelativeTimeFormat/shell.js
js/src/tests/Intl/RelativeTimeFormat/supportedLocalesOf.js
js/src/vm/CommonPropertyNames.h
js/src/vm/GlobalObject.h
js/src/vm/SelfHosting.cpp
--- a/config/external/icu/defs.mozbuild
+++ b/config/external/icu/defs.mozbuild
@@ -10,17 +10,16 @@ DEFINES.update(
 
     # Don't include obsolete header files.
     U_NO_DEFAULT_INCLUDE_UTF_HEADERS = 1,
 
     # Remove chunks of the library that we don't need (yet).
     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,
 )
 
 if not CONFIG['HAVE_LANGINFO_CODESET']:
     DEFINES['U_HAVE_NL_LANGINFO_CODESET'] = 0
 
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -854,17 +854,17 @@ static const uint32_t JSCLASS_FOREGROUND
 // with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was
 // previously allowed, but is now an ES5 violation and thus unsupported.
 //
 // JSCLASS_GLOBAL_APPLICATION_SLOTS is the number of slots reserved at
 // the beginning of every global object's slots for use by the
 // application.
 static const uint32_t JSCLASS_GLOBAL_APPLICATION_SLOTS = 5;
 static const uint32_t JSCLASS_GLOBAL_SLOT_COUNT =
-    JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 37;
+    JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 38;
 
 #define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n)                              \
     (JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n)))
 #define JSCLASS_GLOBAL_FLAGS                                                  \
     JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(0)
 #define JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(clasp)                              \
   (((clasp)->flags & JSCLASS_IS_GLOBAL)                                       \
    && JSCLASS_RESERVED_SLOTS(clasp) >= JSCLASS_GLOBAL_SLOT_COUNT)
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -7,16 +7,17 @@
 /*
  * The Intl module specified by standard ECMA-402,
  * ECMAScript Internationalization API Specification.
  */
 
 #include "builtin/Intl.h"
 
 #include "mozilla/Casting.h"
+#include "mozilla/FloatingPoint.h"
 #include "mozilla/HashFunctions.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/Range.h"
 #include "mozilla/TypeTraits.h"
 
 #include <string.h>
 
 #include "jsapi.h"
@@ -29,21 +30,23 @@
 
 #include "builtin/IntlTimeZoneData.h"
 #include "ds/Sort.h"
 #if ENABLE_INTL_API
 #include "unicode/ucal.h"
 #include "unicode/ucol.h"
 #include "unicode/udat.h"
 #include "unicode/udatpg.h"
+#include "unicode/udisplaycontext.h"
 #include "unicode/uenum.h"
 #include "unicode/uloc.h"
 #include "unicode/unum.h"
 #include "unicode/unumsys.h"
 #include "unicode/upluralrules.h"
+#include "unicode/ureldatefmt.h"
 #include "unicode/ustring.h"
 #endif
 #include "vm/DateTime.h"
 #include "vm/GlobalObject.h"
 #include "vm/Interpreter.h"
 #include "vm/SelfHosting.h"
 #include "vm/Stack.h"
 #include "vm/String.h"
@@ -741,16 +744,69 @@ u_strToUpper(UChar* dest, int32_t destCa
 const char*
 uloc_toUnicodeLocaleType(const char* keyword, const char* value)
 {
     MOZ_CRASH("uloc_toUnicodeLocaleType: Intl API disabled");
 }
 
 } // anonymous namespace
 
+enum UDateRelativeDateTimeFormatterStyle {
+    UDAT_STYLE_LONG,
+    UDAT_STYLE_SHORT,
+    UDAT_STYLE_NARROW
+};
+
+enum URelativeDateTimeUnit {
+    UDAT_REL_UNIT_YEAR,
+    UDAT_REL_UNIT_QUARTER,
+    UDAT_REL_UNIT_MONTH,
+    UDAT_REL_UNIT_WEEK,
+    UDAT_REL_UNIT_DAY,
+    UDAT_REL_UNIT_HOUR,
+    UDAT_REL_UNIT_MINUTE,
+    UDAT_REL_UNIT_SECOND,
+};
+
+enum UDisplayContext {
+    UDISPCTX_STANDARD_NAMES,
+    UDISPCTX_DIALECT_NAMES,
+    UDISPCTX_CAPITALIZATION_NONE,
+    UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE,
+    UDISPCTX_CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE,
+    UDISPCTX_CAPITALIZATION_FOR_UI_LIST_OR_MENU,
+    UDISPCTX_CAPITALIZATION_FOR_STANDALONE,
+    UDISPCTX_LENGTH_FULL,
+    UDISPCTX_LENGTH_SHORT
+};
+
+typedef void* URelativeDateTimeFormatter;
+
+URelativeDateTimeFormatter*
+ureldatefmt_open(const char* locale, UNumberFormat* nfToAdopt,
+                 UDateRelativeDateTimeFormatterStyle width, UDisplayContext capitalizationContext,
+                 UErrorCode* status)
+{
+    MOZ_CRASH("ureldatefmt_open: Intl API disabled");
+}
+
+void
+ureldatefmt_close(URelativeDateTimeFormatter *reldatefmt)
+{
+    MOZ_CRASH("ureldatefmt_close: Intl API disabled");
+}
+
+int32_t
+ureldatefmt_format(const URelativeDateTimeFormatter* reldatefmt, double offset,
+                    URelativeDateTimeUnit unit, UChar* result, int32_t resultCapacity,
+                    UErrorCode* status)
+{
+    MOZ_CRASH("ureldatefmt_format: Intl API disabled");
+}
+
 #endif
 
 
 /******************** Common to Intl constructors ********************/
 
 static bool
 IntlInitialize(JSContext* cx, HandleObject obj, Handle<PropertyName*> initializer,
                HandleValue locales, HandleValue options)
@@ -3721,16 +3777,277 @@ js::intl_GetPluralCategories(JSContext* 
         if (!DefineDataElement(cx, res, i++, element))
             return false;
     } while (true);
 
     args.rval().setObject(*res);
     return true;
 }
 
+/**************** RelativeTimeFormat *****************/
+
+const ClassOps RelativeTimeFormatObject::classOps_ = {
+    nullptr, /* addProperty */
+    nullptr, /* delProperty */
+    nullptr, /* enumerate */
+    nullptr, /* newEnumerate */
+    nullptr, /* resolve */
+    nullptr, /* mayResolve */
+    RelativeTimeFormatObject::finalize
+};
+
+const Class RelativeTimeFormatObject::class_ = {
+    js_Object_str,
+    JSCLASS_HAS_RESERVED_SLOTS(RelativeTimeFormatObject::SLOT_COUNT) |
+    JSCLASS_FOREGROUND_FINALIZE,
+    &RelativeTimeFormatObject::classOps_
+};
+
+#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
+};
+
+/**
+ * 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.PluralRules"))
+        return false;
+
+    // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
+    RootedObject proto(cx);
+    if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
+        return false;
+
+    if (!proto) {
+        proto = GlobalObject::getOrCreateRelativeTimeFormatPrototype(cx, cx->global());
+        if (!proto)
+            return false;
+    }
+
+    Rooted<RelativeTimeFormatObject*> relativeTimeFormat(cx);
+    relativeTimeFormat = NewObjectWithGivenProto<RelativeTimeFormatObject>(cx, proto);
+    if (!relativeTimeFormat)
+        return false;
+
+    relativeTimeFormat->setReservedSlot(RelativeTimeFormatObject::INTERNALS_SLOT, NullValue());
+    relativeTimeFormat->setReservedSlot(RelativeTimeFormatObject::URELATIVE_TIME_FORMAT_SLOT, PrivateValue(nullptr));
+
+    HandleValue locales = args.get(0);
+    HandleValue options = args.get(1);
+
+    // Step 3.
+    if (!IntlInitialize(cx, relativeTimeFormat, cx->names().InitializeRelativeTimeFormat, locales, options))
+        return false;
+
+    args.rval().setObject(*relativeTimeFormat);
+    return true;
+}
+
+void
+RelativeTimeFormatObject::finalize(FreeOp* fop, JSObject* obj)
+{
+    MOZ_ASSERT(fop->onActiveCooperatingThread());
+
+    const Value& slot =
+        obj->as<RelativeTimeFormatObject>().getReservedSlot(RelativeTimeFormatObject::URELATIVE_TIME_FORMAT_SLOT);
+    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;
+
+    RootedObject proto(cx, GlobalObject::createBlankPrototype<PlainObject>(cx, global));
+    if (!proto)
+        return 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;
+
+    RootedValue ctorValue(cx, ObjectValue(*ctor));
+    if (!DefineDataProperty(cx, Intl, cx->names().RelativeTimeFormat, ctorValue, 0))
+        return nullptr;
+
+    return proto;
+}
+
+/* static */ bool
+js::GlobalObject::addRelativeTimeFormatConstructor(JSContext* cx, HandleObject intl)
+{
+    Handle<GlobalObject*> global = cx->global();
+
+    {
+        const HeapSlot& slot = global->getReservedSlotRef(RELATIVE_TIME_FORMAT_PROTO);
+        if (!slot.isUndefined()) {
+            MOZ_ASSERT(slot.isObject());
+            JS_ReportErrorASCII(cx,
+                                "the RelativeTimeFormat constructor can't be added "
+                                "multiple times in the same global");
+            return false;
+        }
+    }
+
+    JSObject* relativeTimeFormatProto = CreateRelativeTimeFormatPrototype(cx, intl, global);
+    if (!relativeTimeFormatProto)
+        return false;
+
+    global->setReservedSlot(RELATIVE_TIME_FORMAT_PROTO, ObjectValue(*relativeTimeFormatProto));
+    return true;
+}
+
+bool
+js::AddRelativeTimeFormatConstructor(JSContext* cx, JS::Handle<JSObject*> intl)
+{
+    return GlobalObject::addRelativeTimeFormatConstructor(cx, intl);
+}
+
+
+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;
+}
+
+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;
+    }
+
+    JSLinearString* unit = args[2].toString()->ensureLinear(cx);
+    if (!unit)
+        return false;
+
+    URelativeDateTimeUnit relDateTimeUnit;
+
+    if (StringEqualsAscii(unit, "second")) {
+        relDateTimeUnit = UDAT_REL_UNIT_SECOND;
+    } else if (StringEqualsAscii(unit, "minute")) {
+        relDateTimeUnit = UDAT_REL_UNIT_MINUTE;
+    } else if (StringEqualsAscii(unit, "hour")) {
+        relDateTimeUnit = UDAT_REL_UNIT_HOUR;
+    } else if (StringEqualsAscii(unit, "day")) {
+        relDateTimeUnit = UDAT_REL_UNIT_DAY;
+    } else if (StringEqualsAscii(unit, "week")) {
+        relDateTimeUnit = UDAT_REL_UNIT_WEEK;
+    } else if (StringEqualsAscii(unit, "month")) {
+        relDateTimeUnit = UDAT_REL_UNIT_MONTH;
+    } else if (StringEqualsAscii(unit, "quarter")) {
+        relDateTimeUnit = UDAT_REL_UNIT_QUARTER;
+    } else {
+        MOZ_ASSERT(StringEqualsAscii(unit, "year"));
+        relDateTimeUnit = UDAT_REL_UNIT_YEAR;
+    }
+
+    // ICU doesn't handle -0 well: work around this by converting it to +0.
+    // See: http://bugs.icu-project.org/trac/ticket/12936
+    if (IsNegativeZero(t))
+        t = +0.0;
+
+    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;
+    }
+
+    ScopedICUObject<URelativeDateTimeFormatter, ureldatefmt_close> closeRelativeTimeFormat(rtf);
+
+    JSString* str = Call(cx, [rtf, t, relDateTimeUnit](UChar* chars, int32_t size, UErrorCode* status) {
+            return ureldatefmt_format(rtf, t, relDateTimeUnit, chars, size, status);
+        });
+    if (!str)
+        return false;
+
+    args.rval().setString(str);
+    return true;
+}
+
 
 /******************** String ********************/
 
 static const char*
 CaseMappingLocale(JSLinearString* locale)
 {
     MOZ_ASSERT(locale->length() >= 2, "locale is a valid language tag");
 
--- a/js/src/builtin/Intl.h
+++ b/js/src/builtin/Intl.h
@@ -582,16 +582,58 @@ intl_SelectPluralRule(JSContext* cx, uns
  *
  * Example:
  *
  * intl_getPluralCategories('pl', 'cardinal'); // ['one', 'few', 'many', 'other']
  */
 extern MOZ_MUST_USE bool
 intl_GetPluralCategories(JSContext* cx, unsigned argc, Value* vp);
 
+/******************** RelativeTimeFormat ********************/
+
+class RelativeTimeFormatObject : public NativeObject
+{
+  public:
+    static const Class class_;
+
+    static constexpr uint32_t INTERNALS_SLOT = 0;
+    static constexpr uint32_t URELATIVE_TIME_FORMAT_SLOT = 1;
+    static constexpr uint32_t SLOT_COUNT = 2;
+
+    static_assert(INTERNALS_SLOT == INTL_INTERNALS_OBJECT_SLOT,
+                  "INTERNALS_SLOT must match self-hosting define for internals object slot");
+
+  private:
+    static const ClassOps classOps_;
+
+    static void finalize(FreeOp* fop, JSObject* obj);
+};
+
+/**
+ * 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);
 
 /******************** Intl ********************/
 
 /**
  * Returns a plain object with calendar information for a single valid locale
  * (callers must perform this validation).  The object will have these
  * properties:
  *
--- a/js/src/builtin/Intl.js
+++ b/js/src/builtin/Intl.js
@@ -1390,17 +1390,18 @@ function intlFallbackSymbol() {
 /**
  * Initializes the INTL_INTERNALS_OBJECT_SLOT of the given object.
  */
 function initializeIntlObject(obj, type, lazyData) {
     assert(IsObject(obj), "Non-object passed to initializeIntlObject");
     assert((type === "Collator" && IsCollator(obj)) ||
            (type === "DateTimeFormat" && IsDateTimeFormat(obj)) ||
            (type === "NumberFormat" && IsNumberFormat(obj)) ||
-           (type === "PluralRules" && IsPluralRules(obj)),
+           (type === "PluralRules" && IsPluralRules(obj)) ||
+           (type === "RelativeTimeFormat" && IsRelativeTimeFormat(obj)),
            "type must match the object's class");
     assert(IsObject(lazyData), "non-object lazy data");
 
     // The meaning of an internals object for an object |obj| is as follows.
     //
     // The .type property indicates the type of Intl object that |obj| is:
     // "Collator", "DateTimeFormat", "NumberFormat", or "PluralRules" (likely
     // with more coming in future Intl specs).
@@ -1457,27 +1458,30 @@ function maybeInternalProperties(interna
  * properties!), with structure specified above.
  *
  * Spec: ECMAScript Internationalization API Specification, 10.3.
  * Spec: ECMAScript Internationalization API Specification, 11.3.
  * Spec: ECMAScript Internationalization API Specification, 12.3.
  */
 function getIntlObjectInternals(obj) {
     assert(IsObject(obj), "getIntlObjectInternals called with non-Object");
-    assert(IsCollator(obj) || IsDateTimeFormat(obj) || IsNumberFormat(obj) || IsPluralRules(obj),
+    assert(IsCollator(obj) || IsDateTimeFormat(obj) ||
+           IsNumberFormat(obj) || IsPluralRules(obj) ||
+           IsRelativeTimeFormat(obj),
            "getIntlObjectInternals called with non-Intl object");
 
     var internals = UnsafeGetReservedSlot(obj, INTL_INTERNALS_OBJECT_SLOT);
 
     assert(IsObject(internals), "internals not an object");
     assert(hasOwn("type", internals), "missing type");
     assert((internals.type === "Collator" && IsCollator(obj)) ||
            (internals.type === "DateTimeFormat" && IsDateTimeFormat(obj)) ||
            (internals.type === "NumberFormat" && IsNumberFormat(obj)) ||
-           (internals.type === "PluralRules" && IsPluralRules(obj)),
+           (internals.type === "PluralRules" && IsPluralRules(obj)) ||
+           (internals.type === "RelativeTimeFormat" && IsRelativeTimeFormat(obj)),
            "type must match the object's class");
     assert(hasOwn("lazyData", internals), "missing lazyData");
     assert(hasOwn("internalProps", internals), "missing internalProps");
 
     return internals;
 }
 
 
@@ -3470,16 +3474,233 @@ function Intl_PluralRules_resolvedOption
         var p = optionalProperties[i];
         if (hasOwn(p, internals))
             _DefineDataProperty(result, p, internals[p]);
     }
     return result;
 }
 
 
+/********** 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;
+
+    // Step 16.
+    const r = ResolveLocale(callFunction(RelativeTimeFormat.availableLocales, RelativeTimeFormat),
+                            lazyRelativeTimeFormatData.requestedLocales,
+                            lazyRelativeTimeFormatData.opt,
+                            RelativeTimeFormat.relevantExtensionKeys,
+                            RelativeTimeFormat.localeData);
+
+    // Step 17.
+    internalProps.locale = r.locale;
+    internalProps.style = lazyRelativeTimeFormatData.style;
+
+    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) {
+    assert(IsObject(obj), "getRelativeTimeFormatInternals called with non-object");
+    assert(IsRelativeTimeFormat(obj), "getRelativeTimeFormatInternals called with non-RelativeTimeFormat");
+
+    var internals = getIntlObjectInternals(obj);
+    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),
+           "InitializeRelativeimeFormat called with non-object");
+    assert(IsRelativeTimeFormat(relativeTimeFormat),
+           "InitializeRelativeTimeFormat called with non-RelativeTimeFormat");
+
+    // Lazy RelativeTimeFormat data has the following structure:
+    //
+    //   {
+    //     requestedLocales: List of locales,
+    //     style: "long" / "short" / "narrow",
+    //
+    //     opt: // opt object computer 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 3.
+    let requestedLocales = CanonicalizeLocaleList(locales);
+    lazyRelativeTimeFormatData.requestedLocales = requestedLocales;
+
+    // Steps 4-5.
+    if (options === undefined)
+        options = {};
+    else
+        options = ToObject(options);
+
+    // Step 6.
+    let opt = new Record();
+
+    // Steps 7-8.
+    let matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
+    opt.localeMatcher = matcher;
+
+    lazyRelativeTimeFormatData.opt = opt;
+
+    // Steps 13-14.
+    const style = GetOption(options, "style", "string", ["long", "short", "narrow"], "long");
+    lazyRelativeTimeFormatData.style = style;
+
+    initializeIntlObject(relativeTimeFormat, "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.
+    if (!IsObject(relativeTimeFormat) || !IsRelativeTimeFormat(relativeTimeFormat))
+        ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "RelativeTimeFormat", "format", "RelativeTimeFormat");
+
+    // Ensure the RelativeTimeFormat internals are resolved.
+    getRelativeTimeFormatInternals(relativeTimeFormat);
+
+    // Step 3.
+    let t = ToNumber(value);
+
+    // Step 4.
+    let u = ToString(unit);
+
+    switch (u) {
+      case "second":
+      case "minute":
+      case "hour":
+      case "day":
+      case "week":
+      case "month":
+      case "quarter":
+      case "year":
+        break;
+      default:
+        ThrowRangeError(JSMSG_INVALID_OPTION_VALUE, "unit", u);
+    }
+
+    // Step 5.
+    return intl_FormatRelativeTime(relativeTimeFormat, t, u);
+}
+
+/**
+ * Returns the resolved options for a PluralRules object.
+ *
+ * Spec: ECMAScript 402 API, RelativeTimeFormat, 1.4.4.
+ */
+function Intl_RelativeTimeFormat_resolvedOptions() {
+    // Check "this RelativeTimeFormat object" per introduction of section 1.4.
+    if (!IsObject(this) || !IsRelativeTimeFormat(this)) {
+        ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "RelativeTimeFormat", "resolvedOptions",
+                       "RelativeTimeFormat");
+    }
+
+    var internals = getRelativeTimeFormatInternals(this, "resolvedOptions");
+
+    var result = {
+        locale: internals.locale,
+        style: internals.style,
+    };
+
+    return result;
+}
+
+
 /********** Intl **********/
 
 
 /**
  * 8.2.1 Intl.getCanonicalLocales ( locales )
  *
  * ES2017 Intl draft rev 947aa9a0c853422824a0c9510d8f09be3eb416b9
  */
--- a/js/src/jit/InlinableNatives.h
+++ b/js/src/jit/InlinableNatives.h
@@ -28,16 +28,17 @@
     _(AtomicsIsLockFree)            \
                                     \
     _(Boolean)                      \
                                     \
     _(IntlIsCollator)               \
     _(IntlIsDateTimeFormat)         \
     _(IntlIsNumberFormat)           \
     _(IntlIsPluralRules)            \
+    _(IntlIsRelativeTimeFormat)     \
                                     \
     _(MathAbs)                      \
     _(MathFloor)                    \
     _(MathCeil)                     \
     _(MathRound)                    \
     _(MathClz32)                    \
     _(MathSqrt)                     \
     _(MathATan2)                    \
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -119,16 +119,18 @@ IonBuilder::inlineNativeCall(CallInfo& c
       case InlinableNative::IntlIsCollator:
         return inlineHasClass(callInfo, &CollatorObject::class_);
       case InlinableNative::IntlIsDateTimeFormat:
         return inlineHasClass(callInfo, &DateTimeFormatObject::class_);
       case InlinableNative::IntlIsNumberFormat:
         return inlineHasClass(callInfo, &NumberFormatObject::class_);
       case InlinableNative::IntlIsPluralRules:
         return inlineHasClass(callInfo, &PluralRulesObject::class_);
+      case InlinableNative::IntlIsRelativeTimeFormat:
+        return inlineHasClass(callInfo, &RelativeTimeFormatObject::class_);
 
       // Math natives.
       case InlinableNative::MathAbs:
         return inlineMathAbs(callInfo);
       case InlinableNative::MathFloor:
         return inlineMathFloor(callInfo);
       case InlinableNative::MathCeil:
         return inlineMathCeil(callInfo);
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -3072,16 +3072,22 @@ ToWindowIfWindowProxy(JSObject* obj);
 // if passed bad input. But the current behavior is entirely under-specified
 // and emphatically not shippable on the web, and it *must* be fixed before
 // this functionality can be exposed in the real world. (There are also some
 // questions about whether the format exposed here is the *right* one to
 // standardize, that will also need to be resolved to ship this.)
 extern bool
 AddMozDateTimeFormatConstructor(JSContext* cx, JS::Handle<JSObject*> intl);
 
+// Create and add the Intl.RelativeTimeFormat constructor function to the provided
+// object.  This function throws if called more than once per realm/global
+// object.
+extern bool
+AddRelativeTimeFormatConstructor(JSContext* cx, JS::Handle<JSObject*> intl);
+
 class MOZ_STACK_CLASS JS_FRIEND_API(AutoAssertNoContentJS)
 {
   public:
     explicit AutoAssertNoContentJS(JSContext* cx);
     ~AutoAssertNoContentJS();
 
   private:
     JSContext* context_;
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -834,16 +834,19 @@ AddIntlExtras(JSContext* cx, unsigned ar
     };
 
     if (!JS_DefineFunctions(cx, intl, funcs))
         return false;
 
     if (!js::AddMozDateTimeFormatConstructor(cx, intl))
         return false;
 
+    if (!js::AddRelativeTimeFormatConstructor(cx, intl))
+        return false;
+
     args.rval().setUndefined();
     return true;
 }
 #endif // ENABLE_INTL_API
 
 static bool
 EvalAndPrint(JSContext* cx, const char* bytes, size_t length,
              int lineno, bool compileOnly)
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/RelativeTimeFormat/construct-newtarget.js
@@ -0,0 +1,23 @@
+// |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/. */
+
+addIntlExtras(Intl);
+
+var obj = new Intl.RelativeTimeFormat();
+
+// Test that new RTF produces an object with the right prototype.
+assertEq(Object.getPrototypeOf(obj), Intl.RelativeTimeFormat.prototype);
+
+// Test subclassing %Intl.RelativeTimeFormat% works correctly.
+class MyRelativeTimeFormat extends Intl.RelativeTimeFormat {}
+
+var obj = new MyRelativeTimeFormat();
+assertEq(obj instanceof MyRelativeTimeFormat, true);
+assertEq(obj instanceof Intl.RelativeTimeFormat, true);
+assertEq(Object.getPrototypeOf(obj), MyRelativeTimeFormat.prototype);
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/RelativeTimeFormat/format.js
@@ -0,0 +1,92 @@
+// |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 format function with a diverse set of locales and options.
+
+var rtf;
+
+addIntlExtras(Intl);
+
+
+rtf = new Intl.RelativeTimeFormat("en-US");
+assertEq(rtf.format(0, "second"), "now");
+assertEq(rtf.format(-0, "second"), "now");
+assertEq(rtf.format(-1, "second"), "1 second ago");
+assertEq(rtf.format(1, "second"), "in 1 second");
+
+assertEq(rtf.format(0, "minute"), "in 0 minutes");
+assertEq(rtf.format(-0, "minute"), "in 0 minutes");
+assertEq(rtf.format(-1, "minute"), "1 minute ago");
+assertEq(rtf.format(1, "minute"), "in 1 minute");
+
+assertEq(rtf.format(0, "hour"), "in 0 hours");
+assertEq(rtf.format(-0, "hour"), "in 0 hours");
+assertEq(rtf.format(-1, "hour"), "1 hour ago");
+assertEq(rtf.format(1, "hour"), "in 1 hour");
+
+assertEq(rtf.format(0, "day"), "today");
+assertEq(rtf.format(-0, "day"), "today");
+assertEq(rtf.format(-1, "day"), "yesterday");
+assertEq(rtf.format(1, "day"), "tomorrow");
+
+assertEq(rtf.format(0, "week"), "this week");
+assertEq(rtf.format(-0, "week"), "this week");
+assertEq(rtf.format(-1, "week"), "last week");
+assertEq(rtf.format(1, "week"), "next week");
+
+assertEq(rtf.format(0, "month"), "this month");
+assertEq(rtf.format(-0, "month"), "this month");
+assertEq(rtf.format(-1, "month"), "last month");
+assertEq(rtf.format(1, "month"), "next month");
+
+assertEq(rtf.format(0, "year"), "this year");
+assertEq(rtf.format(-0, "year"), "this year");
+assertEq(rtf.format(-1, "year"), "last year");
+assertEq(rtf.format(1, "year"), "next year");
+
+rtf = new Intl.RelativeTimeFormat("de");
+assertEq(rtf.format(-1, "day"), "gestern");
+assertEq(rtf.format(1, "day"), "morgen");
+
+rtf = new Intl.RelativeTimeFormat("ar");
+assertEq(rtf.format(-1, "day"), "أمس");
+assertEq(rtf.format(1, "day"), "غدًا");
+
+
+rtf = new Intl.RelativeTimeFormat("en-US");
+assertEq(rtf.format(Infinity, "year"), "in ∞ years");
+assertEq(rtf.format(-Infinity, "year"), "∞ years ago");
+
+var weirdValueCases = [
+  NaN,
+  "word",
+  [0,2],
+  {},
+];
+
+for (let c of weirdValueCases) {
+  assertEq(rtf.format(c, "year"), "in NaN years");
+};
+
+var weirdUnitCases = [
+  "test",
+  "SECOND",
+  "sEcOnD",
+  1,
+  NaN,
+  undefined,
+  null,
+  {},
+];
+
+for (let u of weirdUnitCases) {
+  assertThrows(function() {
+    var rtf = new Intl.RelativeTimeFormat("en-US");
+    rtf.format(1, u);
+  }, RangeError);
+};
+
+
+reportCompare(0, 0, 'ok');
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/RelativeTimeFormat/relativetimeformat.js
@@ -0,0 +1,16 @@
+// |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 format function with a diverse set of locales and options.
+
+var rtf;
+
+addIntlExtras(Intl);
+
+rtf = new Intl.RelativeTimeFormat("en-us");
+assertEq(rtf.resolvedOptions().locale, "en-US");
+assertEq(rtf.resolvedOptions().style, "long");
+
+reportCompare(0, 0, 'ok');
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/RelativeTimeFormat/supportedLocalesOf.js
@@ -0,0 +1,375 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl')||!this.hasOwnProperty('addIntlExtras')||xulRuntime.shell)
+// -- test in browser only that ICU has locale data for all Mozilla languages
+
+/* 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/. */
+
+// This array contains the locales that ICU supports in
+// number formatting whose languages Mozilla localizes Firefox into.
+// Current as of ICU 50.1.2 and Firefox March 2013.
+var locales = [
+    "af",
+    "af-NA",
+    "af-ZA",
+    "ar",
+    "ar-001",
+    "ar-AE",
+    "ar-BH",
+    "ar-DJ",
+    "ar-DZ",
+    "ar-EG",
+    "ar-EH",
+    "ar-ER",
+    "ar-IL",
+    "ar-IQ",
+    "ar-JO",
+    "ar-KM",
+    "ar-KW",
+    "ar-LB",
+    "ar-LY",
+    "ar-MA",
+    "ar-MR",
+    "ar-OM",
+    "ar-PS",
+    "ar-QA",
+    "ar-SA",
+    "ar-SD",
+    "ar-SO",
+    "ar-SY",
+    "ar-TD",
+    "ar-TN",
+    "ar-YE",
+    "as",
+    "as-IN",
+    "be",
+    "be-BY",
+    "bg",
+    "bg-BG",
+    "bn",
+    "bn-BD",
+    "bn-IN",
+    "br",
+    "br-FR",
+    "bs",
+    "bs-Cyrl",
+    "bs-Cyrl-BA",
+    "bs-Latn",
+    "bs-Latn-BA",
+    "ca",
+    "ca-AD",
+    "ca-ES",
+    "cs",
+    "cs-CZ",
+    "cy",
+    "cy-GB",
+    "da",
+    "da-DK",
+    "de",
+    "de-AT",
+    "de-BE",
+    "de-CH",
+    "de-DE",
+    "de-LI",
+    "de-LU",
+    "el",
+    "el-CY",
+    "el-GR",
+    "en",
+    "en-150",
+    "en-AG",
+    "en-AS",
+    "en-AU",
+    "en-BB",
+    "en-BE",
+    "en-BM",
+    "en-BS",
+    "en-BW",
+    "en-BZ",
+    "en-CA",
+    "en-CM",
+    "en-DM",
+    "en-FJ",
+    "en-FM",
+    "en-GB",
+    "en-GD",
+    "en-GG",
+    "en-GH",
+    "en-GI",
+    "en-GM",
+    "en-GU",
+    "en-GY",
+    "en-HK",
+    "en-IE",
+    "en-IM",
+    "en-IN",
+    "en-JE",
+    "en-JM",
+    "en-KE",
+    "en-KI",
+    "en-KN",
+    "en-KY",
+    "en-LC",
+    "en-LR",
+    "en-LS",
+    "en-MG",
+    "en-MH",
+    "en-MP",
+    "en-MT",
+    "en-MU",
+    "en-MW",
+    "en-NA",
+    "en-NG",
+    "en-NZ",
+    "en-PG",
+    "en-PH",
+    "en-PK",
+    "en-PR",
+    "en-PW",
+    "en-SB",
+    "en-SC",
+    "en-SG",
+    "en-SL",
+    "en-SS",
+    "en-SZ",
+    "en-TC",
+    "en-TO",
+    "en-TT",
+    "en-TZ",
+    "en-UG",
+    "en-UM",
+    "en-US",
+    "en-US-posix",
+    "en-VC",
+    "en-VG",
+    "en-VI",
+    "en-VU",
+    "en-WS",
+    "en-ZA",
+    "en-ZM",
+    "en-ZW",
+    "eo",
+    "es",
+    "es-419",
+    "es-AR",
+    "es-BO",
+    "es-CL",
+    "es-CO",
+    "es-CR",
+    "es-CU",
+    "es-DO",
+    "es-EA",
+    "es-EC",
+    "es-ES",
+    "es-GQ",
+    "es-GT",
+    "es-HN",
+    "es-IC",
+    "es-MX",
+    "es-NI",
+    "es-PA",
+    "es-PE",
+    "es-PH",
+    "es-PR",
+    "es-PY",
+    "es-SV",
+    "es-US",
+    "es-UY",
+    "es-VE",
+    "et",
+    "et-EE",
+    "eu",
+    "eu-ES",
+    "fa",
+    "fa-AF",
+    "fa-IR",
+    "ff",
+    "ff-SN",
+    "fi",
+    "fi-FI",
+    "fr",
+    "fr-BE",
+    "fr-BF",
+    "fr-BI",
+    "fr-BJ",
+    "fr-BL",
+    "fr-CA",
+    "fr-CD",
+    "fr-CF",
+    "fr-CG",
+    "fr-CH",
+    "fr-CI",
+    "fr-CM",
+    "fr-DJ",
+    "fr-DZ",
+    "fr-FR",
+    "fr-GA",
+    "fr-GF",
+    "fr-GN",
+    "fr-GP",
+    "fr-GQ",
+    "fr-HT",
+    "fr-KM",
+    "fr-LU",
+    "fr-MA",
+    "fr-MC",
+    "fr-MF",
+    "fr-MG",
+    "fr-ML",
+    "fr-MQ",
+    "fr-MR",
+    "fr-MU",
+    "fr-NC",
+    "fr-NE",
+    "fr-PF",
+    "fr-RE",
+    "fr-RW",
+    "fr-SC",
+    "fr-SN",
+    "fr-SY",
+    "fr-TD",
+    "fr-TG",
+    "fr-TN",
+    "fr-VU",
+    "fr-YT",
+    "ga",
+    "ga-IE",
+    "gl",
+    "gl-ES",
+    "gu",
+    "gu-IN",
+    "he",
+    "he-IL",
+    "hi",
+    "hi-IN",
+    "hr",
+    "hr-BA",
+    "hr-HR",
+    "hu",
+    "hu-HU",
+    "hy",
+    "hy-AM",
+    "id",
+    "id-ID",
+    "is",
+    "is-IS",
+    "it",
+    "it-CH",
+    "it-IT",
+    "it-SM",
+    "ja",
+    "ja-JP",
+    "kk",
+    "kk-Cyrl",
+    "kk-Cyrl-KZ",
+    "km",
+    "km-KH",
+    "kn",
+    "kn-IN",
+    "ko",
+    "ko-KP",
+    "ko-KR",
+    "lt",
+    "lt-LT",
+    "lv",
+    "lv-LV",
+    "mk",
+    "mk-MK",
+    "ml",
+    "ml-IN",
+    "mr",
+    "mr-IN",
+    "nb",
+    "nb-NO",
+    "nl",
+    "nl-AW",
+    "nl-BE",
+    "nl-CW",
+    "nl-NL",
+    "nl-SR",
+    "nl-SX",
+    "nn",
+    "nn-NO",
+    "or",
+    "or-IN",
+    "pa",
+    "pa-Arab",
+    "pa-Arab-PK",
+    "pa-Guru",
+    "pa-Guru-IN",
+    "pl",
+    "pl-PL",
+    "pt",
+    "pt-AO",
+    "pt-BR",
+    "pt-CV",
+    "pt-GW",
+    "pt-MO",
+    "pt-MZ",
+    "pt-PT",
+    "pt-ST",
+    "pt-TL",
+    "rm",
+    "rm-CH",
+    "ro",
+    "ro-MD",
+    "ro-RO",
+    "ru",
+    "ru-BY",
+    "ru-KG",
+    "ru-KZ",
+    "ru-MD",
+    "ru-RU",
+    "ru-UA",
+    "si",
+    "si-LK",
+    "sk",
+    "sk-SK",
+    "sl",
+    "sl-SI",
+    "sq",
+    "sq-AL",
+    "sq-MK",
+    "sr",
+    "sr-Cyrl",
+    "sr-Cyrl-BA",
+    "sr-Cyrl-ME",
+    "sr-Cyrl-RS",
+    "sr-Latn",
+    "sr-Latn-BA",
+    "sr-Latn-ME",
+    "sr-Latn-RS",
+    "sv",
+    "sv-AX",
+    "sv-FI",
+    "sv-SE",
+    "te",
+    "te-IN",
+    "th",
+    "th-TH",
+    "tr",
+    "tr-CY",
+    "tr-TR",
+    "uk",
+    "uk-UA",
+    "vi",
+    "vi-VN",
+    "zh",
+    "zh-Hans",
+    "zh-Hans-CN",
+    "zh-Hans-HK",
+    "zh-Hans-MO",
+    "zh-Hans-SG",
+    "zh-Hant",
+    "zh-Hant-HK",
+    "zh-Hant-MO",
+    "zh-Hant-TW",
+];
+
+addIntlExtras(Intl);
+
+const result = Intl.RelativeTimeFormat.supportedLocalesOf(locales);
+
+assertEqArray(locales, result);
+
+reportCompare(0, 0, 'ok');
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -180,16 +180,17 @@
     macro(incumbentGlobal, incumbentGlobal, "incumbentGlobal") \
     macro(index, index, "index") \
     macro(infinity, infinity, "infinity") \
     macro(Infinity, Infinity, "Infinity") \
     macro(InitializeCollator, InitializeCollator, "InitializeCollator") \
     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") \
     macro(instanceof, instanceof, "instanceof") \
     macro(int8, int8, "int8") \
     macro(int16, int16, "int16") \
     macro(int32, int32, "int32") \
     macro(Int8x16, Int8x16, "Int8x16") \
@@ -344,16 +345,18 @@
     macro(RegExpFlagsGetter, RegExpFlagsGetter, "RegExpFlagsGetter") \
     macro(RegExpMatcher, RegExpMatcher, "RegExpMatcher") \
     macro(RegExpSearcher, RegExpSearcher, "RegExpSearcher") \
     macro(RegExpTester, RegExpTester, "RegExpTester") \
     macro(RegExp_prototype_Exec, RegExp_prototype_Exec, "RegExp_prototype_Exec") \
     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") \
     macro(return, return_, "return") \
     macro(revoke, revoke, "revoke") \
     macro(rtl, rtl, "rtl") \
     macro(script, script, "script") \
     macro(scripts, scripts, "scripts") \
@@ -412,16 +415,17 @@
     macro(uint16, uint16, "uint16") \
     macro(uint32, uint32, "uint32") \
     macro(Uint8x16, Uint8x16, "Uint8x16") \
     macro(Uint16x8, Uint16x8, "Uint16x8") \
     macro(Uint32x4, Uint32x4, "Uint32x4") \
     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") \
     macro(UnwrapAndCallRegExpBuiltinExec, UnwrapAndCallRegExpBuiltinExec, "UnwrapAndCallRegExpBuiltinExec") \
     macro(url, url, "url") \
     macro(usage, usage, "usage") \
     macro(useAsm, useAsm, "use asm") \
     macro(useGrouping, useGrouping, "useGrouping") \
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -91,16 +91,17 @@ class GlobalObject : public NativeObject
         MAP_ITERATOR_PROTO,
         SET_ITERATOR_PROTO,
         COLLATOR_PROTO,
         NUMBER_FORMAT,
         NUMBER_FORMAT_PROTO,
         DATE_TIME_FORMAT,
         DATE_TIME_FORMAT_PROTO,
         PLURAL_RULES_PROTO,
+        RELATIVE_TIME_FORMAT_PROTO,
         MODULE_PROTO,
         IMPORT_ENTRY_PROTO,
         EXPORT_ENTRY_PROTO,
         REQUESTED_MODULE_PROTO,
         REGEXP_STATICS,
         RUNTIME_CODEGEN_ENABLED,
         DEBUGGERS,
         INTRINSICS,
@@ -473,16 +474,21 @@ class GlobalObject : public NativeObject
         return getOrCreateObject(cx, global, DATE_TIME_FORMAT_PROTO, initIntlObject);
     }
 
     static JSObject*
     getOrCreatePluralRulesPrototype(JSContext* cx, Handle<GlobalObject*> global) {
         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() {
         Value value = getSlot(MODULE_PROTO);
         return value.isUndefined() ? nullptr : &value.toObject();
     }
 
     JSObject* maybeGetImportEntryPrototype() {
@@ -773,16 +779,17 @@ class GlobalObject : public NativeObject
     static bool initAsyncGenerators(JSContext* cx, Handle<GlobalObject*> global);
 
     // Implemented in builtin/MapObject.cpp.
     static bool initMapIteratorProto(JSContext* cx, Handle<GlobalObject*> global);
     static bool initSetIteratorProto(JSContext* cx, Handle<GlobalObject*> global);
 
     // Implemented in Intl.cpp.
     static bool initIntlObject(JSContext* cx, Handle<GlobalObject*> global);
+    static bool addRelativeTimeFormatConstructor(JSContext* cx, HandleObject intl);
 
     // Implemented in builtin/ModuleObject.cpp
     static bool initModuleProto(JSContext* cx, Handle<GlobalObject*> global);
     static bool initImportEntryProto(JSContext* cx, Handle<GlobalObject*> global);
     static bool initExportEntryProto(JSContext* cx, Handle<GlobalObject*> global);
     static bool initRequestedModuleProto(JSContext* cx, Handle<GlobalObject*> global);
 
     // Implemented in builtin/TypedObject.cpp
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2595,31 +2595,36 @@ static const JSFunctionSpec intrinsic_fu
     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_FN("intl_patternForStyle", intl_patternForStyle, 3,0),
     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_FN("intl_toLocaleLowerCase", intl_toLocaleLowerCase, 2,0),
     JS_FN("intl_toLocaleUpperCase", intl_toLocaleUpperCase, 2,0),
 
     JS_INLINABLE_FN("IsCollator",
                     intrinsic_IsInstanceOfBuiltin<CollatorObject>, 1,0,
                     IntlIsCollator),
     JS_INLINABLE_FN("IsDateTimeFormat",
                     intrinsic_IsInstanceOfBuiltin<DateTimeFormatObject>, 1,0,
                     IntlIsDateTimeFormat),
     JS_INLINABLE_FN("IsNumberFormat",
                     intrinsic_IsInstanceOfBuiltin<NumberFormatObject>, 1,0,
                     IntlIsNumberFormat),
     JS_INLINABLE_FN("IsPluralRules",
                     intrinsic_IsInstanceOfBuiltin<PluralRulesObject>, 1,0,
                     IntlIsPluralRules),
+    JS_INLINABLE_FN("IsRelativeTimeFormat",
+                    intrinsic_IsInstanceOfBuiltin<RelativeTimeFormatObject>, 1,0,
+                    IntlIsRelativeTimeFormat),
     JS_FN("GetDateTimeFormatConstructor",
           intrinsic_GetBuiltinIntlConstructor<GlobalObject::getOrCreateDateTimeFormatConstructor>,
           0,0),
     JS_FN("GetNumberFormatConstructor",
           intrinsic_GetBuiltinIntlConstructor<GlobalObject::getOrCreateNumberFormatConstructor>,
           0,0),
 
     JS_FN("GetOwnPropertyDescriptorToArray", GetOwnPropertyDescriptorToArray, 2,0),